From 6d123687241f76775156faed5ebb48c4242236a0 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 19 Aug 2012 22:45:47 -0400 Subject: [PATCH 001/196] Added digtype to dig.cpp. Digtype allows designation of all veins of a given type. --- plugins/dig.cpp | 131 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 1 deletion(-) diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 21c199102..c1b6f6331 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -24,7 +24,7 @@ command_result diglx (color_ostream &out, vector & parameters); command_result digauto (color_ostream &out, vector & parameters); command_result digexp (color_ostream &out, vector & parameters); command_result digcircle (color_ostream &out, vector & parameters); - +command_result digtype (color_ostream &out, vector & parameters); DFHACK_PLUGIN("dig"); @@ -57,6 +57,18 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parameters) { return CR_NOT_IMPLEMENTED; } + +command_result digtype (color_ostream &out, vector & parameters) +{ + //mostly copy-pasted from digv + CoreSuspender suspend; + if ( parameters.size() > 1 ) + { + out.printerr("Too many parameters.\n"); + return CR_FAILURE; + } + + uint32_t targetDigType; + if ( parameters.size() == 1 ) + { + string parameter = parameters[0]; + if ( parameter == "clear" ) + targetDigType = tile_dig_designation::No; + else if ( parameter == "dig" ) + targetDigType = tile_dig_designation::Default; + else if ( parameter == "updown" ) + targetDigType = tile_dig_designation::UpDownStair; + else if ( parameter == "channel" ) + targetDigType = tile_dig_designation::Channel; + else if ( parameter == "ramp" ) + targetDigType = tile_dig_designation::Ramp; + else if ( parameter == "down" ) + targetDigType = tile_dig_designation::DownStair; + else if ( parameter == "up" ) + targetDigType = tile_dig_designation::UpStair; + else + { + out.printerr("Invalid parameter.\n"); + return CR_FAILURE; + } + } + else + { + targetDigType = -1; + } + + if (!Maps::IsValid()) + { + out.printerr("Map is not available!\n"); + return CR_FAILURE; + } + + int32_t cx, cy, cz; + uint32_t xMax,yMax,zMax; + Maps::getSize(xMax,yMax,zMax); + uint32_t tileXMax = xMax * 16; + uint32_t tileYMax = yMax * 16; + Gui::getCursorCoords(cx,cy,cz); + if (cx == -30000) + { + out.printerr("Cursor is not active. Point the cursor at a vein.\n"); + return CR_FAILURE; + } + DFHack::DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz); + MapExtras::MapCache * mCache = new MapExtras::MapCache; + df::tile_designation baseDes = mCache->designationAt(xy); + df::tiletype tt = mCache->tiletypeAt(xy); + int16_t veinmat = mCache->veinMaterialAt(xy); + if( veinmat == -1 ) + { + out.printerr("This tile is not a vein.\n"); + delete mCache; + return CR_FAILURE; + } + out.print("(%d,%d,%d) tiletype: %d, veinmat: %d, designation: 0x%x ... DIGGING!\n", cx,cy,cz, tt, veinmat, baseDes.whole); + + if ( targetDigType != -1 ) + { + baseDes.bits.dig = (tile_dig_designation::tile_dig_designation)targetDigType; + } + else + { + if ( baseDes.bits.dig == tile_dig_designation::No ) + { + baseDes.bits.dig = tile_dig_designation::Default; + } + } + + for( uint32_t z = 0; z < zMax; z++ ) + { + for( uint32_t x = 1; x < tileXMax-1; x++ ) + { + for( uint32_t y = 1; y < tileYMax-1; y++ ) + { + DFHack::DFCoord current(x,y,z); + int16_t vmat2 = mCache->veinMaterialAt(current); + if ( vmat2 != veinmat ) + continue; + tt = mCache->tiletypeAt(current); + if (!DFHack::isWallTerrain(tt)) + continue; + + //designate it for digging + df::tile_designation des = mCache->designationAt(current); + if ( !mCache->testCoord(current) ) + { + out.printerr("testCoord failed at (%d,%d,%d)\n", x, y, z); + delete mCache; + return CR_FAILURE; + } + + df::tile_designation designation = mCache->designationAt(current); + designation.bits.dig = baseDes.bits.dig; + mCache->setDesignationAt(current, designation); + } + } + } + + mCache->WriteAll(); + delete mCache; + return CR_OK; +} + From eac2f3f5d1fb93fb47b8a060892d1eb38af33ac5 Mon Sep 17 00:00:00 2001 From: expwnent Date: Tue, 21 Aug 2012 01:33:04 -0400 Subject: [PATCH 002/196] Added misery plugin: multiply effects of negative thoughts. --- plugins/CMakeLists.txt | 1 + plugins/misery.cpp | 174 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 plugins/misery.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 2c54aebad..670c9ce70 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -115,6 +115,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) + DFHACK_PLUGIN(misery misery.cpp) endif() diff --git a/plugins/misery.cpp b/plugins/misery.cpp new file mode 100644 index 000000000..f17a45599 --- /dev/null +++ b/plugins/misery.cpp @@ -0,0 +1,174 @@ +#include "PluginManager.h" +#include "Export.h" + +#include "DataDefs.h" +#include "df/world.h" +#include "df/ui.h" +#include "df/unit.h" +#include "df/unit_thought.h" +#include "df/unit_thought_type.h" + +#include +#include +#include +using namespace std; + +using namespace DFHack; + +static int factor = 1; +static map processedThoughtCountTable; + +//keep track of fake thoughts so you can remove them if requested +static vector > fakeThoughts; + +DFHACK_PLUGIN("misery"); + +command_result misery(color_ostream& out, vector& parameters); + +DFhackCExport command_result plugin_shutdown(color_ostream& out) { + factor = 1; + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream& out) { + static bool wasLoaded = false; + if ( factor == 1 || !df::global::world || !df::global::world->map.block_index ) { + if ( wasLoaded ) { + //we just unloaded the game: clear all data + factor = 1; + processedThoughtCountTable.clear(); + fakeThoughts.clear(); + wasLoaded = false; + } + return CR_OK; + } + + if ( !wasLoaded ) { + wasLoaded = true; + } + + int32_t race_id = df::global::ui->race_id; + int32_t civ_id = df::global::ui->civ_id; + for ( size_t a = 0; a < df::global::world->units.all.size(); a++ ) { + df::unit* unit = df::global::world->units.all[a]; //TODO: consider units.active + //living, native units only + if ( unit->race != race_id || unit->civ_id != civ_id ) + continue; + if ( unit->flags1.bits.dead ) + continue; + + int processedThoughtCount; + map::iterator i = processedThoughtCountTable.find(unit->id); + if ( i == processedThoughtCountTable.end() ) { + processedThoughtCount = unit->status.recent_events.size(); + processedThoughtCountTable[unit->id] = processedThoughtCount; + } else { + processedThoughtCount = (*i).second; + } + + if ( processedThoughtCount == unit->status.recent_events.size() ) { + continue; + } else if ( processedThoughtCount > unit->status.recent_events.size() ) { + processedThoughtCount = unit->status.recent_events.size(); + } + + //don't reprocess any old thoughts + vector newThoughts; + for ( size_t b = processedThoughtCount; b < unit->status.recent_events.size(); b++ ) { + df::unit_thought* oldThought = unit->status.recent_events[b]; + const char* bob = ENUM_ATTR(unit_thought_type, value, oldThought->type); + if ( bob[0] != '-' ) { + //out.print("unit %4d: old thought value = %s\n", unit->id, bob); + continue; + } + /*out.print("unit %4d: Duplicating thought type %d (%s), value %s, age %d, subtype %d, severity %d\n", + unit->id, + oldThought->type.value, + ENUM_ATTR(unit_thought_type, caption, (oldThought->type)), + //df::enum_traits::attr_table[oldThought->type].caption + bob, + oldThought->age, + oldThought->subtype, + oldThought->severity + );*/ + //add duplicate thoughts to the temp list + for ( size_t c = 0; c < factor; c++ ) { + df::unit_thought* thought = new df::unit_thought; + thought->type = unit->status.recent_events[b]->type; + thought->age = unit->status.recent_events[b]->age; + thought->subtype = unit->status.recent_events[b]->subtype; + thought->severity = unit->status.recent_events[b]->severity; + newThoughts.push_back(thought); + } + } + for ( size_t b = 0; b < newThoughts.size(); b++ ) { + fakeThoughts.push_back(std::pair(a, unit->status.recent_events.size())); + unit->status.recent_events.push_back(newThoughts[b]); + } + processedThoughtCountTable[unit->id] = unit->status.recent_events.size(); + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { + commands.push_back(PluginCommand("misery", "increase the intensity of negative dwarven thoughts", + &misery, false, + "misery: When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).\n" + "Usage:\n" + " misery enable n\n" + " enable misery with optional magnitude n. If specified, n must be positive.\n" + " misery n\n" + " same as \"misery enable n\"\n" + " misery enable\n" + " same as \"misery enable 2\"\n" + " misery disable\n" + " stop adding new negative thoughts. This will not remove existing duplicated thoughts. Equivalent to \"misery 1\"\n" + " misery clear\n" + " remove fake thoughts added in this session of DF. Saving makes them permanent! Does not change factor.\n\n" + )); + return CR_OK; +} + +command_result misery(color_ostream &out, vector& parameters) { + if ( !df::global::world || !df::global::world->map.block_index ) { + out.printerr("misery can only be enabled in fortress mode with a fully-loaded game.\n"); + return CR_FAILURE; + } + + if ( parameters.size() < 1 || parameters.size() > 2 ) { + return CR_WRONG_USAGE; + } + + if ( parameters[0] == "disable" ) { + if ( parameters.size() > 1 ) { + return CR_WRONG_USAGE; + } + factor = 1; + return CR_OK; + } else if ( parameters[0] == "enable" ) { + factor = 2; + if ( parameters.size() == 2 ) { + factor = atoi(parameters[1].c_str()); + if ( factor < 1 ) { + out.printerr("Second argument must be a positive integer.\n"); + return CR_WRONG_USAGE; + } + } + } else if ( parameters[0] == "clear" ) { + for ( size_t a = 0; a < fakeThoughts.size(); a++ ) { + int dorfIndex = fakeThoughts[a].first; + int thoughtIndex = fakeThoughts[a].second; + df::global::world->units.all[dorfIndex]->status.recent_events[thoughtIndex]->age = 1000000; + } + fakeThoughts.clear(); + } else { + int a = atoi(parameters[0].c_str()); + if ( a < 1 ) { + return CR_WRONG_USAGE; + } + factor = a; + } + + return CR_OK; +} From 76cd941084f952a226a0a7777fe72b085f790707 Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 20 Aug 2012 22:18:39 -0400 Subject: [PATCH 003/196] Added teledwarf: teleports dwarves to their destination instantly. --- plugins/fastdwarf.cpp | 96 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 16 deletions(-) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 5a2e2f206..c51684ae6 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -23,33 +23,88 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -static int enable_fastdwarf = false; +static bool enable_fastdwarf = false; +static bool enable_teledwarf = false; DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { // check run conditions - if(!world || !world->map.block_index || !enable_fastdwarf) + if(!world || !world->map.block_index) { - // give up if we shouldn't be running' + enable_fastdwarf = enable_teledwarf = false; return CR_OK; } int32_t race = ui->race_id; int32_t civ = ui->civ_id; - - for (size_t i = 0; i < world->units.all.size(); i++) - { - df::unit *unit = world->units.all[i]; - - if (unit->race == race && unit->civ_id == civ && unit->counters.job_counter > 0) - unit->counters.job_counter = 0; - // could also patch the unit->job.current_job->completion_timer + + if ( enable_fastdwarf ) { + for (size_t i = 0; i < world->units.all.size(); i++) + { + df::unit *unit = world->units.all[i]; + + if (unit->race == race && unit->civ_id == civ && unit->counters.job_counter > 0) + unit->counters.job_counter = 0; + // could also patch the unit->job.current_job->completion_timer + } + } + if ( enable_teledwarf ) { + for (size_t i = 0; i < world->units.all.size(); i++) + { + df::unit *unit = world->units.all[i]; + + if (unit->race != race || unit->civ_id != civ || unit->path.dest.x == -30000) + continue; + if (unit->relations.draggee_id != -1 || unit->relations.dragger_id != -1) + continue; + if (unit->relations.following != 0) + continue; + + //move immediately to destination + unit->pos.x = unit->path.dest.x; + unit->pos.y = unit->path.dest.y; + unit->pos.z = unit->path.dest.z; + } } return CR_OK; } static command_result fastdwarf (color_ostream &out, vector & parameters) { - if (parameters.size() == 1 && (parameters[0] == "0" || parameters[0] == "1")) + if (parameters.size() == 1) { + if ( parameters[0] == "0" ) { + enable_fastdwarf = true; + enable_teledwarf = false; + } else if ( parameters[0] == "1" ) { + enable_fastdwarf = false; + enable_teledwarf = false; + } else { + out.print("Incorrect usage.\n"); + return CR_OK; + } + } else if (parameters.size() == 2) { + if ( parameters[0] == "0" ) { + enable_fastdwarf = false; + } else if ( parameters[0] == "1" ) { + enable_fastdwarf = true; + } else { + out.print("Incorrect usage.\n"); + return CR_OK; + } + if ( parameters[1] == "0" ) { + enable_teledwarf = false; + } else if ( parameters[1] == "1" ) { + enable_teledwarf = true; + } else { + out.print("Incorrect usage.\n"); + return CR_OK; + } + } else if (parameters.size() == 0) { + //print status + } else { + out.print("Incorrect usage.\n"); + return CR_OK; + } + /*if (parameters.size() == 1 && (parameters[0] == "0" || parameters[0] == "1")) { if (parameters[0] == "0") enable_fastdwarf = 0; @@ -62,7 +117,7 @@ static command_result fastdwarf (color_ostream &out, vector & parameter out.print("Makes your minions move at ludicrous speeds.\n" "Activate with 'fastdwarf 1', deactivate with 'fastdwarf 0'.\n" "Current state: %d.\n", enable_fastdwarf); - } + }*/ return CR_OK; } @@ -70,8 +125,17 @@ static command_result fastdwarf (color_ostream &out, vector & parameter DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("fastdwarf", - "enable/disable fastdwarf (parameter=0/1)", - fastdwarf)); - + "enable/disable fastdwarf and teledwarf (parameters=0/1)", + fastdwarf, false, + "fastdwarf: controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and perform tasks quickly. Teledwarf makes dwarves move instantaneously, but do jobs at the same speed.\n" + "Usage:\n" + " fastdwarf 0 0: disable both speedydwarf and teledwarf\n" + " fastdwarf 0 1: disable speedydwarf, enable teledwarf\n" + " fastdwarf 1 0: enable speedydwarf, disable teledwarf\n" + " fastdwarf 1 1: enable speedydwarf, enable teledwarf\n" + " fastdwarf 0: disable speedydwarf, disable teledwarf\n" + " fastdwarf 1: enable speedydwarf, disable teledwarf\n" + )); + return CR_OK; } From 15adb17559992fd6feb87d26235c1280bf8c009c Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 23 Aug 2012 22:21:09 -0400 Subject: [PATCH 004/196] Converted tabs to spaces. --- plugins/CMakeLists.txt | 2 +- plugins/misery.cpp | 272 ++++++++++++++++++++--------------------- 2 files changed, 137 insertions(+), 137 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 670c9ce70..55a7c250a 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -115,7 +115,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) - DFHACK_PLUGIN(misery misery.cpp) + DFHACK_PLUGIN(misery misery.cpp) endif() diff --git a/plugins/misery.cpp b/plugins/misery.cpp index f17a45599..260eedd84 100644 --- a/plugins/misery.cpp +++ b/plugins/misery.cpp @@ -26,149 +26,149 @@ DFHACK_PLUGIN("misery"); command_result misery(color_ostream& out, vector& parameters); DFhackCExport command_result plugin_shutdown(color_ostream& out) { - factor = 1; - return CR_OK; + factor = 1; + return CR_OK; } DFhackCExport command_result plugin_onupdate(color_ostream& out) { - static bool wasLoaded = false; - if ( factor == 1 || !df::global::world || !df::global::world->map.block_index ) { - if ( wasLoaded ) { - //we just unloaded the game: clear all data - factor = 1; - processedThoughtCountTable.clear(); - fakeThoughts.clear(); - wasLoaded = false; - } - return CR_OK; - } - - if ( !wasLoaded ) { - wasLoaded = true; - } - - int32_t race_id = df::global::ui->race_id; - int32_t civ_id = df::global::ui->civ_id; - for ( size_t a = 0; a < df::global::world->units.all.size(); a++ ) { - df::unit* unit = df::global::world->units.all[a]; //TODO: consider units.active - //living, native units only - if ( unit->race != race_id || unit->civ_id != civ_id ) - continue; - if ( unit->flags1.bits.dead ) - continue; - - int processedThoughtCount; - map::iterator i = processedThoughtCountTable.find(unit->id); - if ( i == processedThoughtCountTable.end() ) { - processedThoughtCount = unit->status.recent_events.size(); - processedThoughtCountTable[unit->id] = processedThoughtCount; - } else { - processedThoughtCount = (*i).second; - } - - if ( processedThoughtCount == unit->status.recent_events.size() ) { - continue; - } else if ( processedThoughtCount > unit->status.recent_events.size() ) { - processedThoughtCount = unit->status.recent_events.size(); - } - - //don't reprocess any old thoughts - vector newThoughts; - for ( size_t b = processedThoughtCount; b < unit->status.recent_events.size(); b++ ) { - df::unit_thought* oldThought = unit->status.recent_events[b]; - const char* bob = ENUM_ATTR(unit_thought_type, value, oldThought->type); - if ( bob[0] != '-' ) { - //out.print("unit %4d: old thought value = %s\n", unit->id, bob); - continue; - } - /*out.print("unit %4d: Duplicating thought type %d (%s), value %s, age %d, subtype %d, severity %d\n", - unit->id, - oldThought->type.value, - ENUM_ATTR(unit_thought_type, caption, (oldThought->type)), - //df::enum_traits::attr_table[oldThought->type].caption - bob, - oldThought->age, - oldThought->subtype, - oldThought->severity - );*/ - //add duplicate thoughts to the temp list - for ( size_t c = 0; c < factor; c++ ) { - df::unit_thought* thought = new df::unit_thought; - thought->type = unit->status.recent_events[b]->type; - thought->age = unit->status.recent_events[b]->age; - thought->subtype = unit->status.recent_events[b]->subtype; - thought->severity = unit->status.recent_events[b]->severity; - newThoughts.push_back(thought); - } - } - for ( size_t b = 0; b < newThoughts.size(); b++ ) { - fakeThoughts.push_back(std::pair(a, unit->status.recent_events.size())); - unit->status.recent_events.push_back(newThoughts[b]); - } - processedThoughtCountTable[unit->id] = unit->status.recent_events.size(); - } - - return CR_OK; + static bool wasLoaded = false; + if ( factor == 1 || !df::global::world || !df::global::world->map.block_index ) { + if ( wasLoaded ) { + //we just unloaded the game: clear all data + factor = 1; + processedThoughtCountTable.clear(); + fakeThoughts.clear(); + wasLoaded = false; + } + return CR_OK; + } + + if ( !wasLoaded ) { + wasLoaded = true; + } + + int32_t race_id = df::global::ui->race_id; + int32_t civ_id = df::global::ui->civ_id; + for ( size_t a = 0; a < df::global::world->units.all.size(); a++ ) { + df::unit* unit = df::global::world->units.all[a]; //TODO: consider units.active + //living, native units only + if ( unit->race != race_id || unit->civ_id != civ_id ) + continue; + if ( unit->flags1.bits.dead ) + continue; + + int processedThoughtCount; + map::iterator i = processedThoughtCountTable.find(unit->id); + if ( i == processedThoughtCountTable.end() ) { + processedThoughtCount = unit->status.recent_events.size(); + processedThoughtCountTable[unit->id] = processedThoughtCount; + } else { + processedThoughtCount = (*i).second; + } + + if ( processedThoughtCount == unit->status.recent_events.size() ) { + continue; + } else if ( processedThoughtCount > unit->status.recent_events.size() ) { + processedThoughtCount = unit->status.recent_events.size(); + } + + //don't reprocess any old thoughts + vector newThoughts; + for ( size_t b = processedThoughtCount; b < unit->status.recent_events.size(); b++ ) { + df::unit_thought* oldThought = unit->status.recent_events[b]; + const char* bob = ENUM_ATTR(unit_thought_type, value, oldThought->type); + if ( bob[0] != '-' ) { + //out.print("unit %4d: old thought value = %s\n", unit->id, bob); + continue; + } + /*out.print("unit %4d: Duplicating thought type %d (%s), value %s, age %d, subtype %d, severity %d\n", + unit->id, + oldThought->type.value, + ENUM_ATTR(unit_thought_type, caption, (oldThought->type)), + //df::enum_traits::attr_table[oldThought->type].caption + bob, + oldThought->age, + oldThought->subtype, + oldThought->severity + );*/ + //add duplicate thoughts to the temp list + for ( size_t c = 0; c < factor; c++ ) { + df::unit_thought* thought = new df::unit_thought; + thought->type = unit->status.recent_events[b]->type; + thought->age = unit->status.recent_events[b]->age; + thought->subtype = unit->status.recent_events[b]->subtype; + thought->severity = unit->status.recent_events[b]->severity; + newThoughts.push_back(thought); + } + } + for ( size_t b = 0; b < newThoughts.size(); b++ ) { + fakeThoughts.push_back(std::pair(a, unit->status.recent_events.size())); + unit->status.recent_events.push_back(newThoughts[b]); + } + processedThoughtCountTable[unit->id] = unit->status.recent_events.size(); + } + + return CR_OK; } DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { - commands.push_back(PluginCommand("misery", "increase the intensity of negative dwarven thoughts", - &misery, false, - "misery: When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).\n" - "Usage:\n" - " misery enable n\n" - " enable misery with optional magnitude n. If specified, n must be positive.\n" - " misery n\n" - " same as \"misery enable n\"\n" - " misery enable\n" - " same as \"misery enable 2\"\n" - " misery disable\n" - " stop adding new negative thoughts. This will not remove existing duplicated thoughts. Equivalent to \"misery 1\"\n" - " misery clear\n" - " remove fake thoughts added in this session of DF. Saving makes them permanent! Does not change factor.\n\n" - )); - return CR_OK; + commands.push_back(PluginCommand("misery", "increase the intensity of negative dwarven thoughts", + &misery, false, + "misery: When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).\n" + "Usage:\n" + " misery enable n\n" + " enable misery with optional magnitude n. If specified, n must be positive.\n" + " misery n\n" + " same as \"misery enable n\"\n" + " misery enable\n" + " same as \"misery enable 2\"\n" + " misery disable\n" + " stop adding new negative thoughts. This will not remove existing duplicated thoughts. Equivalent to \"misery 1\"\n" + " misery clear\n" + " remove fake thoughts added in this session of DF. Saving makes them permanent! Does not change factor.\n\n" + )); + return CR_OK; } command_result misery(color_ostream &out, vector& parameters) { - if ( !df::global::world || !df::global::world->map.block_index ) { - out.printerr("misery can only be enabled in fortress mode with a fully-loaded game.\n"); - return CR_FAILURE; - } - - if ( parameters.size() < 1 || parameters.size() > 2 ) { - return CR_WRONG_USAGE; - } - - if ( parameters[0] == "disable" ) { - if ( parameters.size() > 1 ) { - return CR_WRONG_USAGE; - } - factor = 1; - return CR_OK; - } else if ( parameters[0] == "enable" ) { - factor = 2; - if ( parameters.size() == 2 ) { - factor = atoi(parameters[1].c_str()); - if ( factor < 1 ) { - out.printerr("Second argument must be a positive integer.\n"); - return CR_WRONG_USAGE; - } - } - } else if ( parameters[0] == "clear" ) { - for ( size_t a = 0; a < fakeThoughts.size(); a++ ) { - int dorfIndex = fakeThoughts[a].first; - int thoughtIndex = fakeThoughts[a].second; - df::global::world->units.all[dorfIndex]->status.recent_events[thoughtIndex]->age = 1000000; - } - fakeThoughts.clear(); - } else { - int a = atoi(parameters[0].c_str()); - if ( a < 1 ) { - return CR_WRONG_USAGE; - } - factor = a; - } - - return CR_OK; + if ( !df::global::world || !df::global::world->map.block_index ) { + out.printerr("misery can only be enabled in fortress mode with a fully-loaded game.\n"); + return CR_FAILURE; + } + + if ( parameters.size() < 1 || parameters.size() > 2 ) { + return CR_WRONG_USAGE; + } + + if ( parameters[0] == "disable" ) { + if ( parameters.size() > 1 ) { + return CR_WRONG_USAGE; + } + factor = 1; + return CR_OK; + } else if ( parameters[0] == "enable" ) { + factor = 2; + if ( parameters.size() == 2 ) { + factor = atoi(parameters[1].c_str()); + if ( factor < 1 ) { + out.printerr("Second argument must be a positive integer.\n"); + return CR_WRONG_USAGE; + } + } + } else if ( parameters[0] == "clear" ) { + for ( size_t a = 0; a < fakeThoughts.size(); a++ ) { + int dorfIndex = fakeThoughts[a].first; + int thoughtIndex = fakeThoughts[a].second; + df::global::world->units.all[dorfIndex]->status.recent_events[thoughtIndex]->age = 1000000; + } + fakeThoughts.clear(); + } else { + int a = atoi(parameters[0].c_str()); + if ( a < 1 ) { + return CR_WRONG_USAGE; + } + factor = a; + } + + return CR_OK; } From e12e7ddd40677f6048993bbbe12886f3982c0b43 Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 27 Aug 2012 14:42:00 -0400 Subject: [PATCH 005/196] Fixed some memory leaks. --- plugins/dig.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/dig.cpp b/plugins/dig.cpp index c1b6f6331..5e2bb9f89 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -963,7 +963,7 @@ command_result digexp (color_ostream &out, vector & parameters) mx.setDesignationAt(pos,des); } } - mx.WriteAll(); + mx.WriteAll(); } else for(uint32_t x = 0; x < x_max; x++) { @@ -1141,6 +1141,7 @@ command_result digv (color_ostream &out, vector & parameters) } } MCache->WriteAll(); + delete MCache; return CR_OK; } @@ -1354,6 +1355,7 @@ command_result digl (color_ostream &out, vector & parameters) } } MCache->WriteAll(); + delete MCache; return CR_OK; } From 1a5f05768f9abee0b932f712794045eef526cb2a Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 27 Aug 2012 15:06:41 -0400 Subject: [PATCH 006/196] Made it not run every tick. --- plugins/misery.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/misery.cpp b/plugins/misery.cpp index 260eedd84..9e0535d5d 100644 --- a/plugins/misery.cpp +++ b/plugins/misery.cpp @@ -20,6 +20,8 @@ static map processedThoughtCountTable; //keep track of fake thoughts so you can remove them if requested static vector > fakeThoughts; +static int count; +const int maxCount = 1000; DFHACK_PLUGIN("misery"); @@ -46,6 +48,12 @@ DFhackCExport command_result plugin_onupdate(color_ostream& out) { if ( !wasLoaded ) { wasLoaded = true; } + + if ( count < maxCount ) { + count++; + return CR_OK; + } + count = 0; int32_t race_id = df::global::ui->race_id; int32_t civ_id = df::global::ui->civ_id; @@ -172,3 +180,4 @@ command_result misery(color_ostream &out, vector& parameters) { return CR_OK; } + From d69a7c63e3fae75a9acb0de3e1038c9507dc4ed3 Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 27 Aug 2012 16:05:23 -0400 Subject: [PATCH 007/196] Tabs to spaces. --- plugins/misery.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/misery.cpp b/plugins/misery.cpp index 9e0535d5d..a0c0a2390 100644 --- a/plugins/misery.cpp +++ b/plugins/misery.cpp @@ -50,8 +50,8 @@ DFhackCExport command_result plugin_onupdate(color_ostream& out) { } if ( count < maxCount ) { - count++; - return CR_OK; + count++; + return CR_OK; } count = 0; From 54780cac3f03df28d51cfd4a2c59e0dc5369d391 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 31 Aug 2012 15:51:22 +0200 Subject: [PATCH 008/196] ruby: allow vmethod call with 6 args, use direct fptr --- plugins/ruby/README | 2 +- plugins/ruby/ruby-autogen-defs.rb | 12 ++++++++---- plugins/ruby/ruby.cpp | 32 +++++++++++++++---------------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/plugins/ruby/README b/plugins/ruby/README index c9a84fb37..9246fec88 100644 --- a/plugins/ruby/README +++ b/plugins/ruby/README @@ -177,7 +177,7 @@ Symbol. This works for array indexes too. ex: df.unit_find(:selected).status.labors[:HAUL_FOOD] = true df.map_tile_at(df.cursor).designation.liquid_type = :Water -Virtual method calls are supported for C++ objects, with a maximum of 4 +Virtual method calls are supported for C++ objects, with a maximum of 6 arguments. Arguments / return value are interpreted as Compound/Enums as specified in the vmethod definition in the xmls. diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 0cee6426f..874746798 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -698,8 +698,8 @@ module DFHack return if not addr @_tg._at(addr)._get end - alias next= _next= - alias prev= _prev= + alias next= _next= + alias prev= _prev= include Enumerable def each @@ -795,8 +795,12 @@ module DFHack v if v != 0 end - def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0) - vmethod_do_call(obj._memaddr, voff, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), vmethod_arg(a3)) + def self.vmethod_call(obj, voff, a0=0, a1=0, a2=0, a3=0, a4=0, a5=0) + this = obj._memaddr + vt = df.get_vtable_ptr(this) + fptr = df.memory_read_int32(vt + voff) & 0xffffffff + vmethod_do_call(this, fptr, vmethod_arg(a0), vmethod_arg(a1), vmethod_arg(a2), + vmethod_arg(a3), vmethod_arg(a4), vmethod_arg(a5)) end def self.vmethod_arg(arg) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 482714d2a..d3b269ffe 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -632,7 +632,7 @@ static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw) bool ret; ret = Core::getInstance().p->patchMemory((void*)rb_num2ulong(addr), - rb_string_value_ptr(&raw), strlen); + rb_string_value_ptr(&raw), strlen); return ret ? Qtrue : Qfalse; } @@ -838,8 +838,8 @@ static VALUE rb_dfmemory_bitarray_set(VALUE self, VALUE addr, VALUE idx, VALUE v /* call an arbitrary object virtual method */ #ifdef WIN32 -__declspec(naked) static int raw_vcall(char **that, unsigned long voff, unsigned long a0, - unsigned long a1, unsigned long a2, unsigned long a3) +__declspec(naked) static int raw_vcall(void *that, void *fptr, unsigned long a0, + unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5) { // __thiscall requires that the callee cleans up the stack // here we dont know how many arguments it will take, so @@ -848,6 +848,8 @@ __declspec(naked) static int raw_vcall(char **that, unsigned long voff, unsigned push ebp mov ebp, esp + push a5 + push a4 push a3 push a2 push a1 @@ -855,9 +857,7 @@ __declspec(naked) static int raw_vcall(char **that, unsigned long voff, unsigned mov ecx, that - mov eax, [ecx] - add eax, voff - call [eax] + call fptr mov esp, ebp pop ebp @@ -865,25 +865,25 @@ __declspec(naked) static int raw_vcall(char **that, unsigned long voff, unsigned } } #else -static int raw_vcall(char **that, unsigned long voff, unsigned long a0, - unsigned long a1, unsigned long a2, unsigned long a3) +static int raw_vcall(void *that, void *fptr, unsigned long a0, + unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5) { - int (*fptr)(char **me, int, int, int, int); - fptr = (decltype(fptr))*(void**)(*that + voff); - return fptr(that, a0, a1, a2, a3); + int (*t_fptr)(void *me, int, int, int, int, int, int); + t_fptr = (decltype(t_fptr))fptr; + return t_fptr(that, a0, a1, a2, a3, a4, a5); } #endif // call an arbitrary vmethod, convert args/ret to native values for raw_vcall -static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE cppvoff, VALUE a0, VALUE a1, VALUE a2, VALUE a3) +static VALUE rb_dfvcall(VALUE self, VALUE cppobj, VALUE fptr, VALUE a0, VALUE a1, VALUE a2, VALUE a3, VALUE a4, VALUE a5) { - return rb_int2inum(raw_vcall((char**)rb_num2ulong(cppobj), rb_num2ulong(cppvoff), + return rb_int2inum(raw_vcall((void*)rb_num2ulong(cppobj), (void*)rb_num2ulong(fptr), rb_num2ulong(a0), rb_num2ulong(a1), - rb_num2ulong(a2), rb_num2ulong(a3))); + rb_num2ulong(a2), rb_num2ulong(a3), + rb_num2ulong(a4), rb_num2ulong(a5))); } - // define module DFHack and its methods static void ruby_bind_dfhack(void) { rb_cDFHack = rb_define_module("DFHack"); @@ -902,7 +902,7 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1); rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1); rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1); - rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 6); + rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 8); rb_define_singleton_method(rb_cDFHack, "memory_read", RUBY_METHOD_FUNC(rb_dfmemory_read), 2); rb_define_singleton_method(rb_cDFHack, "memory_read_int8", RUBY_METHOD_FUNC(rb_dfmemory_read_int8), 1); From ce608e4f6aabef63d8e0da265e964b00109e5380 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 31 Aug 2012 19:16:13 +0200 Subject: [PATCH 009/196] ruby: yield announcements for further customization --- plugins/ruby/ui.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/ui.rb b/plugins/ruby/ui.rb index 9dded66c2..00f73184f 100644 --- a/plugins/ruby/ui.rb +++ b/plugins/ruby/ui.rb @@ -66,11 +66,12 @@ module DFHack world.status.reports << rep world.status.announcements << rep world.status.display_timer = 2000 + yield rep if block_given? end end - # add an announcement to display in a game popup message - # (eg "the megabeast foobar arrived") + # add an announcement to display in a game popup message + # (eg "the megabeast foobar arrived") def popup_announcement(str, color=nil, bright=nil) pop = PopupMessage.cpp_new(:text => str) pop.color = color if color From dcc9498dbc9e7bf5c477af28659aac2aac58b525 Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 1 Sep 2012 02:55:55 +0200 Subject: [PATCH 010/196] ruby: tweak apis --- plugins/ruby/building.rb | 8 +++++--- plugins/ruby/plant.rb | 12 ++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index af152e198..cb435b9d5 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -46,6 +46,7 @@ module DFHack raise "invalid building type #{type.inspect}" if not cls bld = cls.cpp_new bld.race = ui.race_id + subtype = WorkshopType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Workshop bld.setSubtype(subtype) if subtype != -1 bld.setCustomType(custom) if custom != -1 case type @@ -176,9 +177,10 @@ module DFHack # set building at position, with optional width/height def building_position(bld, pos, w=nil, h=nil) - bld.x1 = pos.x - bld.y1 = pos.y - bld.z = pos.z + x, y, z = (pos.respond_to?(:x) ? [pos.x, pos.y, pos.z] : pos) + bld.x1 = x + bld.y1 = y + bld.z = z bld.x2 = bld.x1+w-1 if w bld.y2 = bld.y1+h-1 if h building_setsize(bld) diff --git a/plugins/ruby/plant.rb b/plugins/ruby/plant.rb index 5d6b9d724..2f5a1c7c4 100644 --- a/plugins/ruby/plant.rb +++ b/plugins/ruby/plant.rb @@ -51,7 +51,7 @@ module DFHack end SaplingToTreeAge = 120960 - def cuttrees(material=nil, count_max=100) + def cuttrees(material=nil, count_max=100, quiet=false) if !material # list trees cnt = Hash.new(0) @@ -62,7 +62,7 @@ module DFHack } cnt.sort_by { |mat, c| c }.each { |mat, c| name = @raws_tree_name[mat] - puts " #{name} #{c}" + puts " #{name} #{c}" unless quiet } else cnt = 0 @@ -78,11 +78,11 @@ module DFHack break if cnt == count_max end } - puts "Updated #{cnt} plant designations" + puts "Updated #{cnt} plant designations" unless quiet end end - def growtrees(material=nil, count_max=100) + def growtrees(material=nil, count_max=100, quiet=false) if !material # list plants cnt = Hash.new(0) @@ -93,7 +93,7 @@ module DFHack } cnt.sort_by { |mat, c| c }.each { |mat, c| name = @raws_tree_name[mat] - puts " #{name} #{c}" + puts " #{name} #{c}" unless quiet } else cnt = 0 @@ -104,7 +104,7 @@ module DFHack cnt += 1 break if cnt == count_max } - puts "Grown #{cnt} saplings" + puts "Grown #{cnt} saplings" unless quiet end end end From acdf0ffb42e6196cf85d36228ed247b2dae357ad Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 1 Sep 2012 03:35:47 +0200 Subject: [PATCH 011/196] ruby: fix create building map occupancy --- plugins/ruby/building.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index cb435b9d5..d71a909c5 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -195,7 +195,7 @@ module DFHack z = bld.z (bld.x1..bld.x2).each { |x| (bld.y1..bld.y2).each { |y| - next if !extents or bld.room.extents[bld.room.width*(y-bld.room.y)+(x-bld.room.x)] == 0 + next if extents and bld.room.extents[bld.room.width*(y-bld.room.y)+(x-bld.room.x)] == 0 next if not mb = map_block_at(x, y, z) des = mb.designation[x%16][y%16] des.pile = stockpile From a4c028b3b7c3ff709ece1b23d8641de6703fdb07 Mon Sep 17 00:00:00 2001 From: jj Date: Sun, 2 Sep 2012 01:38:58 +0200 Subject: [PATCH 012/196] ruby: buildings tweaks --- plugins/ruby/building.rb | 41 ++++++++++++++++++++++++++++--- plugins/ruby/ruby-autogen-defs.rb | 24 +++++++++++++----- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index d71a909c5..398301b7a 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -211,15 +211,13 @@ module DFHack # link bld into other rooms if it is inside their extents def building_linkrooms(bld) - didstuff = false world.buildings.other[:ANY_FREE].each { |ob| next if !ob.is_room or ob.z != bld.z next if !ob.room.extents or !ob.isExtentShaped or ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)] == 0 - didstuff = true + ui.equipment.update.buildings = true ob.children << bld bld.parents << ob } - ui.equipment.update.buildings = true if didstuff end # link the building into the world, set map data, link rooms, bld.id @@ -276,6 +274,43 @@ module DFHack building_createdesign(bld, rough) end + # construct an abstract building (stockpile, farmplot, ...) + def building_construct_abstract(bld) + if bld.getType == :Stockpile + max = df.world.buildings.other[:STOCKPILE].map { |s| s.stockpile_number }.max + bld.stockpile_number = max.to_i + 1 + end + building_link bld + if !bld.flags.exists + bld.flags.exists = true + bld.initFarmSeasons + end + end + + def building_setowner(bld, unit) + return unless bld.is_room + return if bld.owner == unit + + if bld.owner + if idx = bld.owner.owned_buildings.index { |ob| ob.id == bld.id } + bld.owner.owned_buildings.delete_at(idx) + end + if spouse = bld.owner.relations.spouse_tg and + idx = spouse.owned_buildings.index { |ob| ob.id == bld.id } + spouse.owned_buildings.delete_at(idx) + end + end + bld.owner = unit + if unit + unit.owned_buildings << bld + if spouse = bld.owner.relations.spouse_tg and + !spouse.owned_buildings.index { |ob| ob.id == bld.id } and + bld.canUseSpouseRoom + spouse.owned_buildings << bld + end + end + end + # creates a job to deconstruct the building def building_deconstruct(bld) job = Job.cpp_new diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 874746798..9312bb69b 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -133,6 +133,8 @@ module DFHack def _field_names ; _fields_ancestors.map { |n, o, s| n } ; end def _rtti_classname ; self.class._rtti_classname ; end def _sizeof ; self.class._sizeof ; end + def ==(o) ; o.kind_of?(Compound) and o._memaddr == _memaddr ; end + @@inspecting = {} # avoid infinite recursion on mutually-referenced objects def inspect cn = self.class.name.sub(/^DFHack::/, '') @@ -289,12 +291,12 @@ module DFHack # XXX shaky... def _set(v) - if v.kind_of?(Pointer) - DFHack.memory_write_int32(@_memaddr, v._getp) - elsif v.kind_of?(MemStruct) - DFHack.memory_write_int32(@_memaddr, v._memaddr) - else - _get._set(v) + case v + when Pointer; DFHack.memory_write_int32(@_memaddr, v._getp) + when MemStruct; DFHack.memory_write_int32(@_memaddr, v._memaddr) + when Integer; DFHack.memory_write_int32(@_memaddr, v) + when nil; DFHack.memory_write_int32(@_memaddr, 0) + else _get._set(v) end end @@ -328,6 +330,16 @@ module DFHack self end + def _set(v) + case v + when Pointer; DFHack.memory_write_int32(@_memaddr, v._getp) + when MemStruct; DFHack.memory_write_int32(@_memaddr, v._memaddr) + when Integer; DFHack.memory_write_int32(@_memaddr, v) + when nil; DFHack.memory_write_int32(@_memaddr, 0) + else raise "cannot PointerAry._set(#{v.inspect})" + end + end + def [](i) addr = _getp(i) return if addr == 0 From afbace8af67d4a2a7b901728da8f9116f93d26d0 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 5 Sep 2012 19:18:21 +0200 Subject: [PATCH 013/196] ruby: alloc_building(:Furnace, :Smelter) --- plugins/ruby/building.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 398301b7a..e67605673 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -47,6 +47,7 @@ module DFHack bld = cls.cpp_new bld.race = ui.race_id subtype = WorkshopType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Workshop + subtype = FurnaceType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Furnace bld.setSubtype(subtype) if subtype != -1 bld.setCustomType(custom) if custom != -1 case type From e09b98216fdbc8f2d059d2894fe0f5082d9c0784 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 5 Sep 2012 20:39:13 +0200 Subject: [PATCH 014/196] ruby: fix vector[length+50] = 0 segfault --- plugins/ruby/ruby-autogen-defs.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 9312bb69b..042352849 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -476,7 +476,7 @@ module DFHack def []=(idx, v) idx += length if idx < 0 if idx >= length - insert_at(idx, 0) + insert_at(length, 0) while idx >= length elsif idx < 0 raise 'index out of bounds' end From 7b86f203c3b7493c6ceddbd84d40b1766bee7015 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 7 Sep 2012 22:47:44 +0200 Subject: [PATCH 015/196] add script/digfort.rb --- README.rst | 22 ++++++++++++++++++++++ scripts/digfort.rb | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 scripts/digfort.rb diff --git a/README.rst b/README.rst index 6c463911a..656d52d61 100644 --- a/README.rst +++ b/README.rst @@ -1446,3 +1446,25 @@ To remove all placed sources, call ``magmasource stop``. With no argument, this command shows an help message and list existing sources. + +digfort +========= +A script to designate an area for digging according to a plan in csv format. + +This script, inspired from quickfort, can designate an area for digging. +Your plan should be stored in a .csv file like this: +:: + # this is a comment + d;d;u;d;d;skip this tile;d + d;d;d;i + +Available tile shapes are named after the 'dig' menu shortcuts: +``d`` for dig, ``u`` for upstairs, ``d`` downstairs, ``i`` updown, +``h`` channel, ``r`` upward ramp, ``x`` remove designation. +Unrecognized characters are ignored (eg the 'skip this tile' in the sample). + +Empty lines and data after a ``#`` are ignored as comments. +To skip a row in your design, use a single ``;``. + +The script takes the plan filename, starting from the root df folder. + diff --git a/scripts/digfort.rb b/scripts/digfort.rb new file mode 100644 index 000000000..6d609f9af --- /dev/null +++ b/scripts/digfort.rb @@ -0,0 +1,38 @@ +# designate an area for digging according to a plan in csv format + +raise "usage: digfort " if not $script_args[0] +planfile = File.read($script_args[0]) + +if df.cursor.x == -30000 + raise "place the game cursor to the top-left corner of the design" +end + +tiles = planfile.lines.map { |l| + l.sub(/#.*/, '').split(';').map { |t| t.strip } +} + +x = x0 = df.cursor.x +y = df.cursor.y +z = df.cursor.z + +tiles.each { |line| + next if line.empty? or line == [''] + line.each { |tile| + t = df.map_tile_at(x, y, z) + s = t.shape_basic + case tile + when 'd'; t.dig(:Default) if s == :Wall + when 'u'; t.dig(:UpStair) if s == :Wall + when 'j'; t.dig(:DownStair) if s == :Wall or s == :Floor + when 'i'; t.dig(:UpDownStair) if s == :Wall + when 'h'; t.dig(:Channel) if s == :Wall or s == :Floor + when 'r'; t.dig(:Ramp) if s == :Wall + when 'x'; t.dig(:No) + end + x += 1 + } + x = x0 + y += 1 +} + +puts 'done' From cda17954c81973318b6214a47a463dee0024a77f Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 12 Sep 2012 01:07:19 +0200 Subject: [PATCH 016/196] ruby: add MapTile.dig(:Smooth) --- plugins/ruby/map.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index dccea7291..5b4e94218 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -189,8 +189,17 @@ module DFHack end def dig(mode=:Default) - designation.dig = mode - mapblock.flags.designated = true + if mode == :Smooth + if tilemat != :SOIL and caption !~ /smooth|pillar/i # XXX + # need to check if already smooth, otherwise re-setting + # des.smooth will carve a fortification + designation.dig = :No + designation.smooth = 1 + end + else + designation.dig = mode + end + mapblock.flags.designated = true if mode != :No end def spawn_liquid(quantity, is_magma=false, flowing=true) From 19a150fb38cc3b148995fcf6692bef1116c0b529 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 12 Sep 2012 17:38:00 +0200 Subject: [PATCH 017/196] ruby: fix dig smooth overdigging --- plugins/ruby/map.rb | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index 5b4e94218..d662a3436 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -190,16 +190,20 @@ module DFHack def dig(mode=:Default) if mode == :Smooth - if tilemat != :SOIL and caption !~ /smooth|pillar/i # XXX - # need to check if already smooth, otherwise re-setting - # des.smooth will carve a fortification + if tilemat != :SOIL and caption !~ /smooth|pillar|fortification/i and # XXX caption.. + designation.smooth == 0 and not df.world.job_list.find { |j| + # the game removes 'smooth' designation as soon as it assigns a job, if we + # re-set it the game may queue another :DetailWall that will carve a fortification + (j.job_type == :DetailWall or j.job_type == :DetailFloor) and df.same_pos?(j, self) + } designation.dig = :No designation.smooth = 1 + mapblock.flags.designated = true end else designation.dig = mode + mapblock.flags.designated = true if mode != :No end - mapblock.flags.designated = true if mode != :No end def spawn_liquid(quantity, is_magma=false, flowing=true) From a9ce19b826dbc8a0fcaf496b0ce8965cc0abe051 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 13 Sep 2012 19:07:37 +0200 Subject: [PATCH 018/196] ruby: add raw_rtti_classname to read real classname from RTTI info --- plugins/ruby/ruby-autogen-defs.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 042352849..344e92327 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -132,6 +132,7 @@ module DFHack def _fields_ancestors ; self.class._fields_ancestors.to_a ; end def _field_names ; _fields_ancestors.map { |n, o, s| n } ; end def _rtti_classname ; self.class._rtti_classname ; end + def _raw_rtti_classname ; df.get_rtti_classname(df.get_vtable_ptr(@_memaddr)) if self.class._rtti_classname ; end def _sizeof ; self.class._sizeof ; end def ==(o) ; o.kind_of?(Compound) and o._memaddr == _memaddr ; end From b05905a0bacb211ca86af70a3d1c8683328562da Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 14 Sep 2012 13:55:22 +0200 Subject: [PATCH 019/196] ruby: basic stl::set support, add Viewscreen.feed_keys --- plugins/ruby/ruby-autogen-defs.rb | 48 +++++++++++++++++++++++++++++- plugins/ruby/ruby.cpp | 49 +++++++++++++++++++++++++++++++ plugins/ruby/ui.rb | 9 ++++++ 3 files changed, 105 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 344e92327..1ddf25a9a 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -760,6 +760,52 @@ module DFHack end end + class StlSet + attr_accessor :_memaddr, :_enum + def self.cpp_new(init=nil, enum=nil) + ret = new DFHack.memory_stlset_new, enum + init.each { |k| ret.set(k) } if init + ret + end + + def initialize(addr, enum=nil) + addr = nil if addr == 0 + @_memaddr = addr + @_enum = enum + end + + def isset(key) + raise unless @_memaddr + key = @_enum.int(key) if _enum + DFHack.memory_stlset_isset(@_memaddr, key) + end + alias is_set? isset + + def set(key) + raise unless @_memaddr + key = @_enum.int(key) if _enum + DFHack.memory_stlset_set(@_memaddr, key) + end + + def delete(key) + raise unless @_memaddr + key = @_enum.int(key) if _enum + DFHack.memory_stlset_deletekey(@_memaddr, key) + end + + def clear + raise unless @_memaddr + DFHack.memory_stlset_clear(@_memaddr) + end + + def _cpp_delete + raise unless @_memaddr + DFHack.memory_stlset_delete(@_memaddr) + @_memaddr = nil + end + end + + # cpp rtti name -> rb class @rtti_n2c = {} @rtti_c2n = {} @@ -822,7 +868,7 @@ module DFHack when true; 1 when Integer; arg #when String; [arg].pack('p').unpack('L')[0] # raw pointer to buffer - when MemHack::Compound; arg._memaddr + when MemHack::Compound, StlSet; arg._memaddr else raise "bad vmethod arg #{arg.class}" end end diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index d3b269ffe..e291a770f 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -835,6 +835,49 @@ static VALUE rb_dfmemory_bitarray_set(VALUE self, VALUE addr, VALUE idx, VALUE v return Qtrue; } +// add basic support for std::set used for passing keyboard keys to viewscreens +#include +static VALUE rb_dfmemory_set_new(VALUE self) +{ + std::set *ptr = new std::set; + return rb_uint2inum((uint32_t)ptr); +} + +static VALUE rb_dfmemory_set_delete(VALUE self, VALUE set) +{ + std::set *ptr = (std::set*)rb_num2ulong(set); + if (ptr) + delete ptr; + return Qtrue; +} + +static VALUE rb_dfmemory_set_set(VALUE self, VALUE set, VALUE key) +{ + std::set *ptr = (std::set*)rb_num2ulong(set); + ptr->insert(rb_num2ulong(key)); + return Qtrue; +} + +static VALUE rb_dfmemory_set_isset(VALUE self, VALUE set, VALUE key) +{ + std::set *ptr = (std::set*)rb_num2ulong(set); + return ptr->count(rb_num2ulong(key)) ? Qtrue : Qfalse; +} + +static VALUE rb_dfmemory_set_deletekey(VALUE self, VALUE set, VALUE key) +{ + std::set *ptr = (std::set*)rb_num2ulong(set); + ptr->erase(rb_num2ulong(key)); + return Qtrue; +} + +static VALUE rb_dfmemory_set_clear(VALUE self, VALUE set) +{ + std::set *ptr = (std::set*)rb_num2ulong(set); + ptr->clear(); + return Qtrue; +} + /* call an arbitrary object virtual method */ #ifdef WIN32 @@ -950,4 +993,10 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "memory_bitarray_resize", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_resize), 2); rb_define_singleton_method(rb_cDFHack, "memory_bitarray_isset", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_isset), 2); rb_define_singleton_method(rb_cDFHack, "memory_bitarray_set", RUBY_METHOD_FUNC(rb_dfmemory_bitarray_set), 3); + rb_define_singleton_method(rb_cDFHack, "memory_stlset_new", RUBY_METHOD_FUNC(rb_dfmemory_set_new), 0); + rb_define_singleton_method(rb_cDFHack, "memory_stlset_delete", RUBY_METHOD_FUNC(rb_dfmemory_set_delete), 1); + rb_define_singleton_method(rb_cDFHack, "memory_stlset_set", RUBY_METHOD_FUNC(rb_dfmemory_set_set), 2); + rb_define_singleton_method(rb_cDFHack, "memory_stlset_isset", RUBY_METHOD_FUNC(rb_dfmemory_set_isset), 2); + rb_define_singleton_method(rb_cDFHack, "memory_stlset_deletekey", RUBY_METHOD_FUNC(rb_dfmemory_set_deletekey), 2); + rb_define_singleton_method(rb_cDFHack, "memory_stlset_clear", RUBY_METHOD_FUNC(rb_dfmemory_set_clear), 1); } diff --git a/plugins/ruby/ui.rb b/plugins/ruby/ui.rb index 00f73184f..a9dd05437 100644 --- a/plugins/ruby/ui.rb +++ b/plugins/ruby/ui.rb @@ -79,4 +79,13 @@ module DFHack world.status.popups << pop end end + + class Viewscreen + def feed_keys(*keys) + keyset = StlSet.cpp_new(keys, InterfaceKey) + ret = feed(keyset) + keyset._cpp_delete + ret + end + end end From bb9be9707074de025c02d424b2c1a34fa4b4938d Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 14 Sep 2012 14:49:40 +0200 Subject: [PATCH 020/196] update NEWS with ruby scripts --- NEWS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS b/NEWS index 43707f9a7..b22108b07 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,12 @@ DFHack v0.34.11-r2 (UNRELEASED) - setfps: set FPS cap at runtime, in case you want slow motion or speed-up. - fix/population-cap: run after every migrant wave to prevent exceeding the cap. - fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp. + - fix/loyaltycascade: fix units allegiance, eg after ordering a dwarf merchant kill. + - digfort: designate areas to dig from a csv file. + - growcrops: cheat to make farm crops instantly grow. + - magmasource: continuously spawn magma from any map tile. + - removebadthoughts: delete all negative thoughts from your dwarves. + - slayrace: instakill all units of a given race, optionally with magma. New GUI scripts: - gui/mechanisms: browse mechanism links of the current building. - gui/room-list: browse other rooms owned by the unit when assigning one. From eacca723e00423581be7f5aebb0c359eda8c0c41 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 14 Sep 2012 21:39:31 +0200 Subject: [PATCH 021/196] ruby: fix building subtype/customtype init value --- plugins/ruby/building.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index e67605673..10c8fcd96 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -48,8 +48,8 @@ module DFHack bld.race = ui.race_id subtype = WorkshopType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Workshop subtype = FurnaceType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Furnace - bld.setSubtype(subtype) if subtype != -1 - bld.setCustomType(custom) if custom != -1 + bld.setSubtype(subtype) + bld.setCustomType(custom) case type when :Furnace; bld.melt_remainder[world.raws.inorganics.length] = 0 when :Coffin; bld.initBurialFlags From 72141a3c9b189dc081ab9302a142edca3c3fa337 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 14 Sep 2012 21:45:01 +0200 Subject: [PATCH 022/196] ruby: use implicit init-value -1 for ref-target/refers-to in codegen --- plugins/ruby/codegen.pl | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 593216d71..8cd06b366 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -788,6 +788,7 @@ sub render_item_number { my $subtype = $item->getAttribute('ld:subtype'); my $meta = $item->getAttribute('ld:meta'); my $initvalue = $item->getAttribute('init-value'); + $initvalue ||= -1 if $item->getAttribute('refers-to') or $item->getAttribute('ref-target'); my $typename = $item->getAttribute('type-name'); undef $typename if ($meta and $meta eq 'bitfield-type'); my $g = $global_types{$typename} if ($typename); From 711a9bd9b2f6cbba96cc9fe0efa85e3835f94d2a Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 17 Sep 2012 21:09:28 +0200 Subject: [PATCH 023/196] add scripts/superdwarf --- scripts/superdwarf.rb | 61 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 scripts/superdwarf.rb diff --git a/scripts/superdwarf.rb b/scripts/superdwarf.rb new file mode 100644 index 000000000..7f5296b74 --- /dev/null +++ b/scripts/superdwarf.rb @@ -0,0 +1,61 @@ +# give super-dwarven speed to an unit + +$superdwarf_onupdate ||= nil +$superdwarf_ids ||= [] + +case $script_args[0] +when 'add' + if u = df.unit_find + $superdwarf_ids |= [u.id] + + $superdwarf_onupdate ||= df.onupdate_register(1) { + if $superdwarf_ids.empty? + df.onupdate_unregister($superdwarf_onupdate) + $superdwarf_onupdate = nil + else + $superdwarf_ids.each { |id| + if u = df.unit_find(id) and not u.flags1.dead + # faster walk/work + if u.counters.job_counter > 0 + u.counters.job_counter = 0 + end + + # no sleep + if u.counters2.sleepiness_timer > 10000 + u.counters2.sleepiness_timer = 1 + end + + # no break + if b = u.status.misc_traits.find { |t| t.id == :OnBreak } + b.value = 500_000 + end + else + $superdwarf_ids.delete id + end + } + end + } + else + puts "Select a creature using 'v'" + end + +when 'del' + if u = df.unit_find + $superdwarf_ids.delete u.id + else + puts "Select a creature using 'v'" + end + +when 'clear' + $superdwarf_ids.clear + +when 'list' + puts "current superdwarves:", $superdwarf_ids.map { |id| df.unit_find(id).name } + +else + puts "Usage:", + " - superdwarf add: give superspeed to currently selected creature", + " - superdwarf del: remove superspeed to current creature", + " - superdwarf clear: remove all superpowers", + " - superdwarf list: list super-dwarves" +end From 2032f8e4070292d5777fb6b28d007f9c05076630 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 17 Sep 2012 21:15:20 +0200 Subject: [PATCH 024/196] scripts/drainaquifer --- scripts/drainaquifer.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 scripts/drainaquifer.rb diff --git a/scripts/drainaquifer.rb b/scripts/drainaquifer.rb new file mode 100644 index 000000000..4e2ae4ff1 --- /dev/null +++ b/scripts/drainaquifer.rb @@ -0,0 +1,11 @@ +# remove all aquifers from the map + +count = 0 +df.each_map_block { |b| + if b.designation[0][0].water_table or b.designation[15][15].water_table + count += 1 + b.designation.each { |dx| dx.each { |dy| dy.water_table = false } } + end +} + +puts "cleared #{count} map blocks" From 57d01f00084f4e035a82a4bddeb3a5e28a278883 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 17 Sep 2012 21:16:01 +0200 Subject: [PATCH 025/196] ruby: use ui.follow_item/unit in unit_find, show non-english names by default --- plugins/ruby/item.rb | 2 ++ plugins/ruby/unit.rb | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/item.rb b/plugins/ruby/item.rb index 34b404505..032c0d8cf 100644 --- a/plugins/ruby/item.rb +++ b/plugins/ruby/item.rb @@ -26,6 +26,8 @@ module DFHack u = world.units.active[ui_selected_unit] u.inventory[ui_look_cursor].item if u and u.pos.z == cursor.z and ui_unit_view_mode.value == :Inventory and u.inventory[ui_look_cursor] + else + ui.follow_item_tg if ui.follow_item != -1 end end elsif what.kind_of?(Integer) diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 1a619c5ce..f0eeb1381 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -24,6 +24,8 @@ module DFHack when :LookAround k = ui_look_list.items[ui_look_cursor] k.unit if k.type == :Unit + else + ui.follow_unit_tg if ui.follow_unit != -1 end end elsif what.kind_of?(Integer) @@ -104,7 +106,7 @@ module DFHack end class LanguageName - def to_s(english=true) + def to_s(english=false) df.translate_name(self, english) end end From cc4639aad033625a8390d4114e9f26193acd4477 Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 18 Sep 2012 10:29:58 +0200 Subject: [PATCH 026/196] update NEWS/README with drainaquifer/superdwarf --- NEWS | 2 ++ README.rst | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/NEWS b/NEWS index b22108b07..fdc052da3 100644 --- a/NEWS +++ b/NEWS @@ -32,10 +32,12 @@ DFHack v0.34.11-r2 (UNRELEASED) - fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp. - fix/loyaltycascade: fix units allegiance, eg after ordering a dwarf merchant kill. - digfort: designate areas to dig from a csv file. + - drainaquifer: remove aquifers from the map. - growcrops: cheat to make farm crops instantly grow. - magmasource: continuously spawn magma from any map tile. - removebadthoughts: delete all negative thoughts from your dwarves. - slayrace: instakill all units of a given race, optionally with magma. + - superdwarf: per-creature fastdwarf. New GUI scripts: - gui/mechanisms: browse mechanism links of the current building. - gui/room-list: browse other rooms owned by the unit when assigning one. diff --git a/README.rst b/README.rst index 656d52d61..b5aa338e1 100644 --- a/README.rst +++ b/README.rst @@ -1468,3 +1468,19 @@ To skip a row in your design, use a single ``;``. The script takes the plan filename, starting from the root df folder. +superdwarf +========== +Similar to fastdwarf, per-creature. + +To make any creature superfast, target it ingame using 'v' and: +:: + superdwarf add + +Other options available: ``del``, ``clear``, ``list``. + +This plugin also shortens the 'sleeping' and 'on break' periods of targets. + +drainaquifer +============ +Remove all 'aquifer' tag from the map blocks. Irreversible. + From b470abed904f31aa3c886ffc47e84d0f14322a58 Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 18 Sep 2012 18:13:22 +0200 Subject: [PATCH 027/196] ruby: misc tweaks --- plugins/ruby/building.rb | 12 +++++++++++- plugins/ruby/ruby-autogen-defs.rb | 3 +-- plugins/ruby/ruby.rb | 8 ++++---- plugins/ruby/unit.rb | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 10c8fcd96..d88bbfc69 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -178,7 +178,17 @@ module DFHack # set building at position, with optional width/height def building_position(bld, pos, w=nil, h=nil) - x, y, z = (pos.respond_to?(:x) ? [pos.x, pos.y, pos.z] : pos) + if pos.respond_to?(:x1) + x, y, z = pos.x1, pos.y1, pos.z + w ||= pos.x2-pos.x1+1 if pos.respond_to?(:x2) + h ||= pos.y2-pos.y1+1 if pos.respond_to?(:y2) + elsif pos.respond_to?(:x) + x, y, z = pos.x, pos.y, pos.z + else + x, y, z = pos + end + w ||= pos.w if pos.respond_to?(:w) + h ||= pos.h if pos.respond_to?(:h) bld.x1 = x bld.y1 = y bld.z = z diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 1ddf25a9a..4614d178f 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -124,8 +124,7 @@ module DFHack case h when Hash; h.each { |k, v| send("#{k}=", v) } when Array; names = _field_names ; raise 'bad size' if names.length != h.length ; names.zip(h).each { |n, a| send("#{n}=", a) } - when Compound; _field_names.each { |n| send("#{n}=", h.send(n)) } - else raise 'wut?' + else _field_names.each { |n| send("#{n}=", h.send(n)) } end end def _fields ; self.class._fields.to_a ; end diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 8c2c97969..ef6af04e7 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -25,11 +25,11 @@ end module DFHack class OnupdateCallback attr_accessor :callback, :timelimit, :minyear, :minyeartick - def initialize(cb, tl) + def initialize(cb, tl, initdelay=0) @callback = cb @ticklimit = tl @minyear = (tl ? df.cur_year : 0) - @minyeartick = (tl ? df.cur_year_tick : 0) + @minyeartick = (tl ? df.cur_year_tick+initdelay : 0) end # run callback if timedout @@ -61,9 +61,9 @@ module DFHack # register a callback to be called every gframe or more # ex: DFHack.onupdate_register { DFHack.world.units[0].counters.job_counter = 0 } - def onupdate_register(ticklimit=nil, &b) + def onupdate_register(ticklimit=nil, initialtickdelay=0, &b) @onupdate_list ||= [] - @onupdate_list << OnupdateCallback.new(b, ticklimit) + @onupdate_list << OnupdateCallback.new(b, ticklimit, initialtickdelay) DFHack.onupdate_active = true if onext = @onupdate_list.sort.first DFHack.onupdate_minyear = onext.minyear diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index f0eeb1381..5289229a6 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -49,7 +49,7 @@ module DFHack end def unit_iscitizen(u) - u.race == ui.race_id and u.civ_id == ui.civ_id and !u.flags1.dead and !u.flags1.merchant and + u.race == ui.race_id and u.civ_id == ui.civ_id and !u.flags1.dead and !u.flags1.merchant and !u.flags1.forest and !u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and !u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and u.mood != :Berserk From 45c057b3d2e5809941fc618624b955ffa46b0b87 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 19 Sep 2012 14:25:14 +0200 Subject: [PATCH 028/196] ruby: handle ptr-strings in df-structures --- plugins/ruby/codegen.pl | 2 +- plugins/ruby/ruby-autogen-defs.rb | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 8cd06b366..a615ab964 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -965,7 +965,7 @@ sub render_item_bytes { my $subtype = $item->getAttribute('ld:subtype'); if ($subtype eq 'padding') { } elsif ($subtype eq 'static-string') { - my $size = $item->getAttribute('size'); + my $size = $item->getAttribute('size') || -1; push @lines_rb, "static_string($size)"; } else { print "no render bytes $subtype\n"; diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 4614d178f..64c08facb 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -423,11 +423,20 @@ module DFHack def initialize(length) @_length = length end + def length + if @_length == -1 + maxlen = 4096 - (@_memaddr & 0xfff) + maxlen += 4096 until len = DFHack.memory_read(@_memaddr, maxlen).index(?\0) + len + else + @_length + end + end def _get - DFHack.memory_read(@_memaddr, @_length) + DFHack.memory_read(@_memaddr, length) end def _set(v) - DFHack.memory_write(@_memaddr, v[0, @_length]) + DFHack.memory_write(@_memaddr, v[0, length]) end end From b6d986317239b7eac27511d488d3954e924178d3 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 19 Sep 2012 19:19:58 +0200 Subject: [PATCH 029/196] ruby: allow Civzone constants in building_alloc --- plugins/ruby/building.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index d88bbfc69..0a48386c5 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -48,6 +48,7 @@ module DFHack bld.race = ui.race_id subtype = WorkshopType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Workshop subtype = FurnaceType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Furnace + subtype = CivzoneType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Civzone bld.setSubtype(subtype) bld.setCustomType(custom) case type From 2d5942d5d71fbb66e9d649876497a7e58010aae6 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 20 Sep 2012 00:33:22 +0200 Subject: [PATCH 030/196] ruby: fix building linking --- plugins/ruby/building.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 0a48386c5..ad2acb155 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -225,7 +225,7 @@ module DFHack def building_linkrooms(bld) world.buildings.other[:ANY_FREE].each { |ob| next if !ob.is_room or ob.z != bld.z - next if !ob.room.extents or !ob.isExtentShaped or ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)] == 0 + next if !ob.room.extents or ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)] == 0 ui.equipment.update.buildings = true ob.children << bld bld.parents << ob From e154d5447b08354f787ebc1ce4cf087b7c998384 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 20 Sep 2012 01:33:10 +0200 Subject: [PATCH 031/196] ruby: fix building linking more --- plugins/ruby/building.rb | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index ad2acb155..4896aed48 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -21,8 +21,8 @@ module DFHack if b.room.extents dx = x - b.room.x dy = y - b.room.y - dx >= 0 and dx <= b.room.width and - dy >= 0 and dy <= b.room.height and + dx >= 0 and dx < b.room.width and + dy >= 0 and dy < b.room.height and b.room.extents[ dy*b.room.width + dx ] > 0 else b.x1 <= x and b.x2 >= x and @@ -221,14 +221,23 @@ module DFHack } end - # link bld into other rooms if it is inside their extents + # link bld into other rooms if it is inside their extents or vice versa def building_linkrooms(bld) world.buildings.other[:ANY_FREE].each { |ob| - next if !ob.is_room or ob.z != bld.z - next if !ob.room.extents or ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)] == 0 - ui.equipment.update.buildings = true - ob.children << bld - bld.parents << ob + next if ob.z != bld.z + if bld.is_room and bld.room.extents + next if ob.is_room or ob.x1 < bld.room.x or ob.x1 >= bld.room.x+bld.room.width or ob.y1 < bld.room.y or ob.y1 >= bld.room.y+bld.room.height + next if bld.room.extents[bld.room.width*(ob.y1-bld.room.y)+(ob.x1-bld.room.x)] == 0 + ui.equipment.update.buildings = true + bld.children << ob + ob.parents << bld + elsif ob.is_room and ob.room.extents + next if bld.is_room or bld.x1 < ob.room.x or bld.x1 >= ob.room.x+ob.room.width or bld.y1 < ob.room.y or bld.y1 >= ob.room.y+ob.room.height + next if ob.room.extents[ob.room.width*(bld.y1-ob.room.y)+(bld.x1-ob.room.x)].to_i == 0 + ui.equipment.update.buildings = true + ob.children << bld + bld.parents << ob + end } end From 1fd0654d635ebf8da74fb581eb8d3f16c98b0444 Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 19 Sep 2012 22:55:38 -0500 Subject: [PATCH 032/196] Restore stonesense to the proper revision --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 37a823541..2a62ba5ed 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 37a823541538023b9f3d0d1e8039cf32851de68d +Subproject commit 2a62ba5ed2607f4dbf0473e77502d4e19c19678e From 7ce772ae0ea69ab26009a27d71bd681382ecfac7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 20 Sep 2012 10:41:03 +0400 Subject: [PATCH 033/196] Add an API function that returns the selected building. --- LUA_API.rst | 4 +++ Lua API.html | 3 ++ library/LuaApi.cpp | 1 + library/include/modules/Gui.h | 4 +++ library/modules/Gui.cpp | 58 +++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+) diff --git a/LUA_API.rst b/LUA_API.rst index c5f9a1c58..898ab79c0 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -777,6 +777,10 @@ Gui module last case, the highlighted *contained item* is returned, not the container itself. +* ``dfhack.gui.getSelectedBuilding([silent])`` + + Returns the building selected via *'q'*, *'t'*, *'k'* or *'i'*. + * ``dfhack.gui.showAnnouncement(text,color[,is_bright])`` Adds a regular announcement with given text, color, and brightness. diff --git a/Lua API.html b/Lua API.html index 07f038bc4..97770ff77 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1032,6 +1032,9 @@ a full-screen item view of a container. Note that in the last case, the highlighted contained item is returned, not the container itself.

+
  • dfhack.gui.getSelectedBuilding([silent])

    +

    Returns the building selected via 'q', 't', 'k' or 'i'.

    +
  • dfhack.gui.showAnnouncement(text,color[,is_bright])

    Adds a regular announcement with given text, color, and brightness. The is_bright boolean actually seems to invert the brightness.

    diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index d73d14cf9..b2d41dc19 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -760,6 +760,7 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getSelectedJob), WRAPM(Gui, getSelectedUnit), WRAPM(Gui, getSelectedItem), + WRAPM(Gui, getSelectedBuilding), WRAPM(Gui, showAnnouncement), WRAPM(Gui, showZoomAnnouncement), WRAPM(Gui, showPopupAnnouncement), diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 97e8bd422..b06408f68 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -91,6 +91,10 @@ namespace DFHack DFHACK_EXPORT bool any_item_hotkey(df::viewscreen *top); DFHACK_EXPORT df::item *getSelectedItem(color_ostream &out, bool quiet = false); + // A building is selected via 'q', 't' or 'i' (civzone) + DFHACK_EXPORT bool any_building_hotkey(df::viewscreen *top); + DFHACK_EXPORT df::building *getSelectedBuilding(color_ostream &out, bool quiet = false); + // Show a plain announcement, or a titan-style popup message DFHACK_EXPORT void showAnnouncement(std::string message, int color = 7, bool bright = true); DFHACK_EXPORT void showZoomAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true); diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 1662f4467..10a20dc2e 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -933,6 +933,64 @@ df::item *Gui::getSelectedItem(color_ostream &out, bool quiet) return item; } +static df::building *getAnyBuilding(df::viewscreen *top) +{ + using namespace ui_sidebar_mode; + using df::global::ui; + using df::global::ui_look_list; + using df::global::ui_look_cursor; + using df::global::world; + using df::global::ui_sidebar_menus; + + if (!Gui::dwarfmode_hotkey(top)) + return NULL; + + switch (ui->main.mode) { + case LookAround: + { + if (!ui_look_list || !ui_look_cursor) + return NULL; + + auto item = vector_get(ui_look_list->items, *ui_look_cursor); + if (item && item->type == df::ui_look_list::T_items::Building) + return item->building; + else + return NULL; + } + case QueryBuilding: + case BuildingItems: + { + return world->selected_building; + } + case Zones: + case ZonesPenInfo: + case ZonesPitInfo: + case ZonesHospitalInfo: + { + if (ui_sidebar_menus) + return ui_sidebar_menus->zone.selected; + return NULL; + } + default: + return NULL; + } +} + +bool Gui::any_building_hotkey(df::viewscreen *top) +{ + return getAnyBuilding(top) != NULL; +} + +df::building *Gui::getSelectedBuilding(color_ostream &out, bool quiet) +{ + df::building *building = getAnyBuilding(Core::getTopViewscreen()); + + if (!building && !quiet) + out.printerr("No building is selected in the UI.\n"); + + return building; +} + // static void doShowAnnouncement( From c39a33722316ee91f7a42abcd76149788dd19196 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 20 Sep 2012 11:11:20 +0400 Subject: [PATCH 034/196] Add unit/item/job/building getter hook vmethods to dfhack_viewscreen. --- LUA_API.rst | 8 +++++++ Lua API.html | 10 ++++++++ library/include/modules/Screen.h | 18 ++++++++++++++ library/modules/Gui.cpp | 11 +++++++++ library/modules/Screen.cpp | 41 ++++++++++++++++++++++++++++++++ library/xml | 2 +- plugins/manipulator.cpp | 7 ++++++ 7 files changed, 96 insertions(+), 1 deletion(-) diff --git a/LUA_API.rst b/LUA_API.rst index 898ab79c0..d9b75c480 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -1467,6 +1467,14 @@ Supported callbacks and fields are: If this method is omitted, the screen is dismissed on receival of the ``LEAVESCREEN`` key. +* ``function screen:onGetSelectedUnit()`` +* ``function screen:onGetSelectedItem()`` +* ``function screen:onGetSelectedJob()`` +* ``function screen:onGetSelectedBuilding()`` + + Implement these to provide a return value for the matching + ``dfhack.gui.getSelected...`` function. + Internal API ------------ diff --git a/Lua API.html b/Lua API.html index 97770ff77..0da65b5fb 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1611,6 +1611,16 @@ options; if multiple interpretations exist, the table will contain multiple keys

    If this method is omitted, the screen is dismissed on receival of the LEAVESCREEN key.

  • +
  • function screen:onGetSelectedUnit()

    +
  • +
  • function screen:onGetSelectedItem()

    +
  • +
  • function screen:onGetSelectedJob()

    +
  • +
  • function screen:onGetSelectedBuilding()

    +

    Implement these to provide a return value for the matching +dfhack.gui.getSelected... function.

    +
  • diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 4f47205f2..fdad6c8ad 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -33,6 +33,14 @@ distribution. #include "df/graphic.h" #include "df/viewscreen.h" +namespace df +{ + struct job; + struct item; + struct unit; + struct building; +} + /** * \defgroup grp_screen utilities for painting to the screen * @ingroup grp_screen @@ -134,6 +142,7 @@ namespace DFHack virtual ~dfhack_viewscreen(); static bool is_instance(df::viewscreen *screen); + static dfhack_viewscreen *try_cast(df::viewscreen *screen); virtual void logic(); virtual void render(); @@ -146,6 +155,10 @@ namespace DFHack virtual std::string getFocusString() = 0; virtual void onShow() {}; virtual void onDismiss() {}; + virtual df::unit *getSelectedUnit() { return NULL; } + virtual df::item *getSelectedItem() { return NULL; } + virtual df::job *getSelectedJob() { return NULL; } + virtual df::building *getSelectedBuilding() { return NULL; } }; class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen { @@ -178,5 +191,10 @@ namespace DFHack virtual void onShow(); virtual void onDismiss(); + + virtual df::unit *getSelectedUnit(); + virtual df::item *getSelectedItem(); + virtual df::job *getSelectedJob(); + virtual df::building *getSelectedBuilding(); }; } diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 10a20dc2e..bc7deedc8 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -691,6 +691,8 @@ df::job *Gui::getSelectedJob(color_ostream &out, bool quiet) return job; } + else if (auto dfscreen = dfhack_viewscreen::try_cast(top)) + return dfscreen->getSelectedJob(); else return getSelectedWorkshopJob(out, quiet); } @@ -781,6 +783,9 @@ static df::unit *getAnyUnit(df::viewscreen *top) return NULL; } + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) + return dfscreen->getSelectedUnit(); + if (!Gui::dwarfmode_hotkey(top)) return NULL; @@ -875,6 +880,9 @@ static df::item *getAnyItem(df::viewscreen *top) return NULL; } + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) + return dfscreen->getSelectedItem(); + if (!Gui::dwarfmode_hotkey(top)) return NULL; @@ -942,6 +950,9 @@ static df::building *getAnyBuilding(df::viewscreen *top) using df::global::world; using df::global::ui_sidebar_menus; + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) + return dfscreen->getSelectedBuilding(); + if (!Gui::dwarfmode_hotkey(top)) return NULL; diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 9f258fe02..8057d17a2 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -50,6 +50,10 @@ using namespace DFHack; #include "df/tile_page.h" #include "df/interfacest.h" #include "df/enabler.h" +#include "df/unit.h" +#include "df/item.h" +#include "df/job.h" +#include "df/building.h" using namespace df::enums; using df::global::init; @@ -322,6 +326,11 @@ bool dfhack_viewscreen::is_instance(df::viewscreen *screen) return dfhack_screens.count(screen) != 0; } +dfhack_viewscreen *dfhack_viewscreen::try_cast(df::viewscreen *screen) +{ + return is_instance(screen) ? static_cast(screen) : NULL; +} + void dfhack_viewscreen::check_resize() { auto size = Screen::getWindowSize(); @@ -637,3 +646,35 @@ void dfhack_lua_viewscreen::onDismiss() lua_pushstring(Lua::Core::State, "onDismiss"); safe_call_lua(do_notify, 1, 0); } + +df::unit *dfhack_lua_viewscreen::getSelectedUnit() +{ + Lua::StackUnwinder frame(Lua::Core::State); + lua_pushstring(Lua::Core::State, "onGetSelectedUnit"); + safe_call_lua(do_notify, 1, 1); + return Lua::GetDFObject(Lua::Core::State, -1); +} + +df::item *dfhack_lua_viewscreen::getSelectedItem() +{ + Lua::StackUnwinder frame(Lua::Core::State); + lua_pushstring(Lua::Core::State, "onGetSelectedItem"); + safe_call_lua(do_notify, 1, 1); + return Lua::GetDFObject(Lua::Core::State, -1); +} + +df::job *dfhack_lua_viewscreen::getSelectedJob() +{ + Lua::StackUnwinder frame(Lua::Core::State); + lua_pushstring(Lua::Core::State, "onGetSelectedJob"); + safe_call_lua(do_notify, 1, 1); + return Lua::GetDFObject(Lua::Core::State, -1); +} + +df::building *dfhack_lua_viewscreen::getSelectedBuilding() +{ + Lua::StackUnwinder frame(Lua::Core::State); + lua_pushstring(Lua::Core::State, "onGetSelectedBuilding"); + safe_call_lua(do_notify, 1, 1); + return Lua::GetDFObject(Lua::Core::State, -1); +} diff --git a/library/xml b/library/xml index 8a78bfa21..eb4af94c7 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 8a78bfa218817765b0a80431e0cf25435ffb2179 +Subproject commit eb4af94c7e737d99c194381749ac6743e146913e diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 2b3fc86fc..033ad8b85 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -338,6 +338,8 @@ public: std::string getFocusString() { return "unitlabors"; } + df::unit *getSelectedUnit(); + viewscreen_unitlaborsst(vector &src, int cursor_pos); ~viewscreen_unitlaborsst() { }; @@ -986,6 +988,11 @@ void viewscreen_unitlaborsst::render() } } +df::unit *viewscreen_unitlaborsst::getSelectedUnit() +{ + return units[sel_row]->unit; +} + struct unitlist_hook : df::viewscreen_unitlistst { typedef df::viewscreen_unitlistst interpose_base; From 462bedb75708043f3ef9520e8fb84640ebaec945 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 20 Sep 2012 11:11:59 +0400 Subject: [PATCH 035/196] Fix the rename plugin and script to use the new getSelectedBuilding API. --- plugins/rename.cpp | 5 +++-- scripts/gui/rename.lua | 10 +++++----- scripts/gui/siege-engine.lua | 4 ++++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 99dc6848a..f9de83bdf 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -357,10 +357,11 @@ static command_result rename(color_ostream &out, vector ¶meters) if (parameters.size() != 2) return CR_WRONG_USAGE; - if (ui->main.mode != ui_sidebar_mode::QueryBuilding) + df::building *bld = Gui::getSelectedBuilding(out, true); + if (!bld) return CR_WRONG_USAGE; - if (!renameBuilding(world->selected_building, parameters[1])) + if (!renameBuilding(bld, parameters[1])) { out.printerr("This type of building is not supported.\n"); return CR_FAILURE; diff --git a/scripts/gui/rename.lua b/scripts/gui/rename.lua index a457a0bfd..70a09b2fa 100644 --- a/scripts/gui/rename.lua +++ b/scripts/gui/rename.lua @@ -13,10 +13,12 @@ local function verify_mode(expected) end end -if string.match(focus, '^dwarfmode/QueryBuilding/Some') then +local unit = dfhack.gui.getSelectedUnit(true) +local building = dfhack.gui.getSelectedBuilding(true) + +if building and (not unit or mode == 'building') then verify_mode('building') - local building = df.global.world.selected_building if plugin.canRenameBuilding(building) then dlg.showInputPrompt( 'Rename Building', @@ -30,9 +32,7 @@ if string.match(focus, '^dwarfmode/QueryBuilding/Some') then 'Cannot rename this type of building.', COLOR_LIGHTRED ) end -elseif dfhack.gui.getSelectedUnit(true) then - local unit = dfhack.gui.getSelectedUnit(true) - +elseif unit then if mode == 'unit-profession' then dlg.showInputPrompt( 'Rename Unit', diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index c98cb1676..f460bb211 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -74,6 +74,10 @@ function SiegeEngine:onDestroy() end end +function SiegeEngine:onGetSelectedBuilding() + return df.global.world.selected_building +end + function SiegeEngine:showCursor(enable) local cursor = guidm.getCursorPos() if cursor and not enable then From 1f7c10252e79415bf56c461cd2eb19a5018433fb Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 20 Sep 2012 11:48:53 +0400 Subject: [PATCH 036/196] Support renaming activity zones. This one required hooking the dwarfmode render method. --- README.rst | 8 +++++--- Readme.html | 8 +++++--- plugins/rename.cpp | 47 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 589451a9a..9e3d5d3f9 100644 --- a/README.rst +++ b/README.rst @@ -703,7 +703,8 @@ Options :rename unit-profession "custom profession": Change proffession name of the highlighted unit/creature. :rename building "name": Set a custom name for the selected building. - The building must be one of stockpile, workshop, furnace, trap or siege engine. + The building must be one of stockpile, workshop, furnace, trap, + siege engine or an activity zone. reveal ====== @@ -1549,7 +1550,8 @@ via a simple dialog in the game ui. * ``gui/rename [building]`` in 'q' mode changes the name of a building. - The selected building must be one of stockpile, workshop, furnace, trap or siege engine. + The selected building must be one of stockpile, workshop, furnace, trap, or siege engine. + It is also possible to rename zones from the 'i' menu. * ``gui/rename [unit]`` with a unit selected changes the nickname. @@ -1645,7 +1647,7 @@ is extracted from the workshop raws. machine connection cannot be modified. As a result, the workshop can only immediately connect to machine components built AFTER it. This also means that engines cannot be chained without intermediate -short axles that can be built later. +short axles that can be built later than both of the engines. Operation --------- diff --git a/Readme.html b/Readme.html index 4c005b6ff..7b41dc74b 100644 --- a/Readme.html +++ b/Readme.html @@ -1480,7 +1480,8 @@ highlighted unit/creature. rename building "name":  Set a custom name for the selected building. -The building must be one of stockpile, workshop, furnace, trap or siege engine. +The building must be one of stockpile, workshop, furnace, trap, +siege engine or an activity zone. @@ -2423,7 +2424,8 @@ configuration page, but configures parameters relevant to the modded power meter via a simple dialog in the game ui.

    • gui/rename [building] in 'q' mode changes the name of a building.

      -

      The selected building must be one of stockpile, workshop, furnace, trap or siege engine.

      +

      The selected building must be one of stockpile, workshop, furnace, trap, or siege engine. +It is also possible to rename zones from the 'i' menu.

    • gui/rename [unit] with a unit selected changes the nickname.

    • @@ -2497,7 +2499,7 @@ is extracted from the workshop raws.

      machine connection cannot be modified. As a result, the workshop can only immediately connect to machine components built AFTER it. This also means that engines cannot be chained without intermediate -short axles that can be built later.

      +short axles that can be built later than both of the engines.

    Operation

    diff --git a/plugins/rename.cpp b/plugins/rename.cpp index f9de83bdf..059a4be62 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -10,9 +10,11 @@ #include "modules/Translation.h" #include "modules/Units.h" #include "modules/World.h" +#include "modules/Screen.h" #include #include "df/ui.h" +#include "df/ui_sidebar_menus.h" #include "df/world.h" #include "df/squad.h" #include "df/unit.h" @@ -27,6 +29,8 @@ #include "df/building_furnacest.h" #include "df/building_trapst.h" #include "df/building_siegeenginest.h" +#include "df/building_civzonest.h" +#include "df/viewscreen_dwarfmodest.h" #include "RemoteServer.h" #include "rename.pb.h" @@ -43,6 +47,7 @@ using namespace df::enums; using namespace dfproto; using df::global::ui; +using df::global::ui_sidebar_menus; using df::global::world; DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); @@ -66,8 +71,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector main.mode == ui_sidebar_mode::Zones && + ui_sidebar_menus && ui_sidebar_menus->zone.selected && + !ui_sidebar_menus->zone.selected->name.empty()) + { + auto dims = Gui::getDwarfmodeViewDims(); + int width = dims.menu_x2 - dims.menu_x1 - 1; + + Screen::Pen pen(' ',COLOR_WHITE); + Screen::fillRect(pen, dims.menu_x1, dims.y1+1, dims.menu_x2, dims.y1+1); + + std::string name; + ui_sidebar_menus->zone.selected->getName(&name); + Screen::paintString(pen, dims.menu_x1+1, dims.y1+1, name.substr(0, width)); + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(dwarf_render_zone_hook, render); + static char getBuildingCode(df::building *bld) { CHECK_NULL_POINTER(bld); @@ -142,6 +174,9 @@ KNOWN_BUILDINGS static bool enable_building_rename(char code, bool enable) { + if (code == 'Z') + INTERPOSE_HOOK(dwarf_render_zone_hook, render).apply(enable); + switch (code) { #define BUILDING(code, cname, tag) \ case code: return INTERPOSE_HOOK(cname##_hook, getName).apply(enable); @@ -154,6 +189,8 @@ KNOWN_BUILDINGS static void disable_building_rename() { + INTERPOSE_HOOK(dwarf_render_zone_hook, render).remove(); + #define BUILDING(code, cname, tag) \ INTERPOSE_HOOK(cname##_hook, getName).remove(); KNOWN_BUILDINGS From 82dc1445cf86160cd3e7d33718b7229c1f591fc6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 20 Sep 2012 11:55:53 +0400 Subject: [PATCH 037/196] Support the Room list in getSelectedBuilding. --- library/modules/Gui.cpp | 4 ++++ library/xml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index bc7deedc8..eaeef9bf4 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -53,6 +53,7 @@ using namespace DFHack; #include "df/viewscreen_dungeon_monsterstatusst.h" #include "df/viewscreen_joblistst.h" #include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_buildinglistst.h" #include "df/viewscreen_itemst.h" #include "df/viewscreen_layer.h" #include "df/viewscreen_layer_workshop_profilest.h" @@ -950,6 +951,9 @@ static df::building *getAnyBuilding(df::viewscreen *top) using df::global::world; using df::global::ui_sidebar_menus; + if (auto screen = strict_virtual_cast(top)) + return vector_get(screen->buildings, screen->cursor); + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) return dfscreen->getSelectedBuilding(); diff --git a/library/xml b/library/xml index eb4af94c7..d52c7181f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit eb4af94c7e737d99c194381749ac6743e146913e +Subproject commit d52c7181fb439a5fead143188d17d659d82e7f89 From e2d6a14720e3fcad3d658040434dbcca9a72fa20 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 20 Sep 2012 12:27:03 +0400 Subject: [PATCH 038/196] Make manipulator re-read names and professions if change is suspected. Namely, if it either invoked View Unit itself, or was queried (possibly by the rename plugin) via getSelectedUnit. --- README.rst | 4 ++++ Readme.html | 2 ++ plugins/manipulator.cpp | 44 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 9e3d5d3f9..c6b0509e1 100644 --- a/README.rst +++ b/README.rst @@ -1510,6 +1510,10 @@ The following mouse shortcuts are also available: * Left-click on a unit's name or profession to view its properties. * Right-click on a unit's name or profession to zoom to it. +Pressing ESC normally returns to the unit screen, but Shift-ESC would exit +directly to the main dwarf mode screen. + + Liquids ======= diff --git a/Readme.html b/Readme.html index 7b41dc74b..fcf7dab6a 100644 --- a/Readme.html +++ b/Readme.html @@ -2395,6 +2395,8 @@ cursor onto that cell instead of toggling it.
  • Left-click on a unit's name or profession to view its properties.
  • Right-click on a unit's name or profession to zoom to it.
  • +

    Pressing ESC normally returns to the unit screen, but Shift-ESC would exit +directly to the main dwarf mode screen.

    Liquids

    diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 033ad8b85..a3ec72a47 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -331,6 +331,12 @@ class viewscreen_unitlaborsst : public dfhack_viewscreen { public: void feed(set *events); + void logic() { + dfhack_viewscreen::logic(); + if (do_refresh_names) + refreshNames(); + } + void render(); void resize(int w, int h) { calcSize(); } @@ -347,12 +353,14 @@ protected: vector units; altsort_mode altsort; + bool do_refresh_names; int first_row, sel_row, num_rows; int first_column, sel_column; int col_widths[DISP_COLUMN_MAX]; int col_offsets[DISP_COLUMN_MAX]; + void refreshNames(); void calcSize (); }; @@ -377,9 +385,6 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur if (!ENUM_ATTR(profession, can_assign_labor, unit->profession)) cur->allowEdit = false; - cur->name = Translation::TranslateName(&unit->name, false); - cur->transname = Translation::TranslateName(&unit->name, true); - cur->profession = Units::getProfessionName(unit); cur->color = Units::getProfessionColor(unit); units.push_back(cur); @@ -387,6 +392,8 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur altsort = ALTSORT_NAME; first_column = sel_column = 0; + refreshNames(); + first_row = 0; sel_row = cursor_pos; calcSize(); @@ -403,6 +410,21 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur first_row = units.size() - num_rows; } +void viewscreen_unitlaborsst::refreshNames() +{ + do_refresh_names = false; + + for (size_t i = 0; i < units.size(); i++) + { + UnitInfo *cur = units[i]; + df::unit *unit = cur->unit; + + cur->name = Translation::TranslateName(&unit->name, false); + cur->transname = Translation::TranslateName(&unit->name, true); + cur->profession = Units::getProfessionName(unit); + } +} + void viewscreen_unitlaborsst::calcSize() { num_rows = gps->dimy - 10; @@ -476,16 +498,25 @@ void viewscreen_unitlaborsst::calcSize() void viewscreen_unitlaborsst::feed(set *events) { - if (events->count(interface_key::LEAVESCREEN)) + bool leave_all = events->count(interface_key::LEAVESCREEN_ALL); + if (leave_all || events->count(interface_key::LEAVESCREEN)) { events->clear(); Screen::dismiss(this); + if (leave_all) + { + events->insert(interface_key::LEAVESCREEN); + parent->feed(events); + } return; } if (!units.size()) return; + if (do_refresh_names) + refreshNames(); + if (events->count(interface_key::CURSOR_UP) || events->count(interface_key::CURSOR_UPLEFT) || events->count(interface_key::CURSOR_UPRIGHT)) sel_row--; if (events->count(interface_key::CURSOR_UP_FAST) || events->count(interface_key::CURSOR_UPLEFT_FAST) || events->count(interface_key::CURSOR_UPRIGHT_FAST)) @@ -758,6 +789,8 @@ void viewscreen_unitlaborsst::feed(set *events) unitlist->feed(events); if (Screen::isDismissed(unitlist)) Screen::dismiss(this); + else + do_refresh_names = true; break; } } @@ -990,6 +1023,9 @@ void viewscreen_unitlaborsst::render() df::unit *viewscreen_unitlaborsst::getSelectedUnit() { + // This query might be from the rename plugin + do_refresh_names = true; + return units[sel_row]->unit; } From 1403bb126c65053e9d430a5876dd6d2ac50a2361 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 20 Sep 2012 15:38:58 +0400 Subject: [PATCH 039/196] README: move keybinding to the start and add a section for scripts. --- README.rst | 131 +++++++++--- Readme.html | 565 +++++++++++++++++++++++++++++----------------------- 2 files changed, 425 insertions(+), 271 deletions(-) diff --git a/README.rst b/README.rst index c6b0509e1..40d7b74e3 100644 --- a/README.rst +++ b/README.rst @@ -1,3 +1,7 @@ +############# +DFHack Readme +############# + ============ Introduction ============ @@ -97,11 +101,49 @@ the issues tracker on github, contact me (peterix@gmail.com) or visit the ============= The init file ============= -If your DF folder contains a file named dfhack.init, its contents will be run +If your DF folder contains a file named ``dfhack.init``, its contents will be run every time you start DF. This allows setting up keybindings. An example file -is provided as dfhack.init-example - you can tweak it and rename to dfhack.init +is provided as ``dfhack.init-example`` - you can tweak it and rename to dfhack.init if you want to use this functionality. +Setting keybindings +=================== + +To set keybindings, use the built-in ``keybinding`` command. Like any other +command it can be used at any time from the console, but it is also meaningful +in the DFHack init file. + +Currently it supports any combination of Ctrl/Alt/Shift with F1-F9, or A-Z. + +Possible ways to call the command: + +:keybinding list : List bindings active for the key combination. +:keybinding clear ...: Remove bindings for the specified keys. +:keybinding add "cmdline" "cmdline"...: Add bindings for the specified + key. +:keybinding set "cmdline" "cmdline"...: Clear, and then add bindings for + the specified key. + +The ** parameter above has the following *case-sensitive* syntax:: + + [Ctrl-][Alt-][Shift-]KEY[@context] + +where the *KEY* part can be F1-F9 or A-Z, and [] denote optional parts. + +When multiple commands are bound to the same key combination, DFHack selects +the first applicable one. Later 'add' commands, and earlier entries within one +'add' command have priority. Commands that are not specifically intended for use +as a hotkey are always considered applicable. + +The *context* part in the key specifier above can be used to explicitly restrict +the UI state where the binding would be applicable. If called without parameters, +the ``keybinding`` command among other things prints the current context string. +Only bindings with a *context* tag that either matches the current context fully, +or is a prefix ending at a '/' boundary would be considered for execution, i.e. +for context ``foo/bar/baz``, possible matches are any of ``@foo/bar/baz``, ``@foo/bar``, +``@foo`` or none. + + ======== Commands ======== @@ -576,27 +618,6 @@ Duplicate the selected job in a workshop: * In 'q' mode, when a job is highlighted within a workshop or furnace building, instantly duplicates the job. -keybinding -========== - -Manages DFHack keybindings. - -Currently it supports any combination of Ctrl/Alt/Shift with F1-F9, or A-Z. - -Options -------- -:keybinding list : List bindings active for the key combination. -:keybinding clear ...: Remove bindings for the specified keys. -:keybinding add "cmdline" "cmdline"...: Add bindings for the specified - key. -:keybinding set "cmdline" "cmdline"...: Clear, and then add bindings for - the specified key. - -When multiple commands are bound to the same key combination, DFHack selects -the first applicable one. Later 'add' commands, and earlier entries within one -'add' command have priority. Commands that are not specifically intended for use -as a hotkey are always considered applicable. - liquids ======= Allows adding magma, water and obsidian to the game. It replaces the normal @@ -1378,6 +1399,70 @@ also tries to have dwarves specialize in specific skills. For detailed usage information, see 'help autolabor'. +======= +Scripts +======= + +Lua or ruby scripts placed in the hack/scripts/ directory are considered for +execution as if they were native DFHack commands. They are listed at the end +of the 'ls' command output. + +Note: scripts in subdirectories of hack/scripts/ can still be called, but will +only be listed by ls if called as 'ls -a'. This is intended as a way to hide +scripts that are obscure, developer-oriented, or should be used as keybindings. + +Some notable scripts: + +quicksave +========= + +If called in dwarf mode, makes DF immediately auto-save the game by setting a flag +normally used in seasonal auto-save. + +setfps +====== + +Run ``setfps `` to set the FPS cap at runtime, in case you want to watch +combat in slow motion or something :) + + +fix/* +===== + +Scripts in this subdirectory fix various bugs and issues, some of them obscure. + +* fix/dead-units + + Removes uninteresting dead units from the unit list. Doesn't seem to give any + noticeable performance gain, but migrants normally stop if the unit list grows + to around 3000 units, and this script reduces it back. + +* fix/population-cap + + Run this after every migrant wave to ensure your population cap is not exceeded. + The issue with the cap is that it is compared to the population number reported + by the last caravan, so once it drops below the cap, migrants continue to come + until that number is updated again. + +* fix/stable-temp + + Instantly sets the temperature of all free-lying items to be in equilibrium with + the environment and stops temperature updates. In order to maintain this efficient + state however, use ``tweak stable-temp`` and ``tweak fast-heat``. + +* fix/item-occupancy + + Diagnoses and fixes issues with nonexistant 'items occupying site', usually + caused by autodump bugs or other hacking mishaps. + + +gui/* +===== + +Scripts that implement dialogs inserted into the main game window are put in this +directory. + + growcrops ========= Instantly grow seeds inside farming plots. diff --git a/Readme.html b/Readme.html index fcf7dab6a..977c4177d 100644 --- a/Readme.html +++ b/Readme.html @@ -4,7 +4,7 @@ - +DFHack Readme -
    - +
    +

    DFHack Readme

    -

    Introduction

    +

    Introduction

    DFHack is a Dwarf Fortress memory access library and a set of basic tools that use it. Tools come in the form of plugins or (not yet) external tools. It is an attempt to unite the various ways tools @@ -326,13 +326,16 @@ access DF memory and allow for easier development of new tools.

    Contents

    -

    Getting DFHack

    +

    Getting DFHack

    The project is currently hosted on github, for both source and binaries at http://github.com/peterix/dfhack

    Releases can be downloaded from here: https://github.com/peterix/dfhack/downloads

    All new releases are announced in the bay12 thread: http://tinyurl.com/dfhack-ng

    -

    Compatibility

    +

    Compatibility

    DFHack works on Windows XP, Vista, 7 or any modern Linux distribution. OSX is not supported due to lack of developers with a Mac.

    Currently, versions 0.34.08 - 0.34.11 are supported. If you need DFHack @@ -572,7 +578,7 @@ for older versions, look for older releases.

    It is possible to use the Windows DFHack under wine/OSX.

    -

    Installation/Removal

    +

    Installation/Removal

    Installing DFhack involves copying files into your DF folder. Copy the files from a release archive so that:

    @@ -594,7 +600,7 @@ remove the other DFHack files file created in your DF folder.

    -

    Using DFHack

    +

    Using DFHack

    DFHack basically extends what DF can do with something similar to the drop-down console found in Quake engine games. On Windows, this is a separate command line window. On linux, the terminal used to launch the dfhack script is taken over @@ -618,7 +624,7 @@ they are re-created every time it is loaded.

    Most of the commands come from plugins. Those reside in 'hack/plugins/'.

    -

    Something doesn't work, help!

    +

    Something doesn't work, help!

    First, don't panic :) Second, dfhack keeps a few log files in DF's folder - stderr.log and stdout.log. You can look at those and possibly find out what's happening. @@ -627,11 +633,55 @@ the issues tracker on github, contact me ( -

    The init file

    -

    If your DF folder contains a file named dfhack.init, its contents will be run +

    The init file

    +

    If your DF folder contains a file named dfhack.init, its contents will be run every time you start DF. This allows setting up keybindings. An example file -is provided as dfhack.init-example - you can tweak it and rename to dfhack.init +is provided as dfhack.init-example - you can tweak it and rename to dfhack.init if you want to use this functionality.

    +
    +

    Setting keybindings

    +

    To set keybindings, use the built-in keybinding command. Like any other +command it can be used at any time from the console, but it is also meaningful +in the DFHack init file.

    +

    Currently it supports any combination of Ctrl/Alt/Shift with F1-F9, or A-Z.

    +

    Possible ways to call the command:

    + +++ + + + + + + + + + + + + + +
    keybinding list <key>:
     List bindings active for the key combination.
    keybinding clear <key> <key>...:
     Remove bindings for the specified keys.
    keybinding add <key> "cmdline" "cmdline"...:
     Add bindings for the specified +key.
    keybinding set <key> "cmdline" "cmdline"...:
     Clear, and then add bindings for +the specified key.
    +

    The <key> parameter above has the following case-sensitive syntax:

    +
    +[Ctrl-][Alt-][Shift-]KEY[@context]
    +
    +

    where the KEY part can be F1-F9 or A-Z, and [] denote optional parts.

    +

    When multiple commands are bound to the same key combination, DFHack selects +the first applicable one. Later 'add' commands, and earlier entries within one +'add' command have priority. Commands that are not specifically intended for use +as a hotkey are always considered applicable.

    +

    The context part in the key specifier above can be used to explicitly restrict +the UI state where the binding would be applicable. If called without parameters, +the keybinding command among other things prints the current context string. +Only bindings with a context tag that either matches the current context fully, +or is a prefix ending at a '/' boundary would be considered for execution, i.e. +for context foo/bar/baz, possible matches are any of @foo/bar/baz, @foo/bar, +@foo or none.

    +

    Commands

    @@ -1309,40 +1359,8 @@ instantly duplicates the job.
    -
    -

    keybinding

    -

    Manages DFHack keybindings.

    -

    Currently it supports any combination of Ctrl/Alt/Shift with F1-F9, or A-Z.

    -
    -

    Options

    - --- - - - - - - - - - - - - - -
    keybinding list <key>:
     List bindings active for the key combination.
    keybinding clear <key> <key>...:
     Remove bindings for the specified keys.
    keybinding add <key> "cmdline" "cmdline"...:
     Add bindings for the specified -key.
    keybinding set <key> "cmdline" "cmdline"...:
     Clear, and then add bindings for -the specified key.
    -

    When multiple commands are bound to the same key combination, DFHack selects -the first applicable one. Later 'add' commands, and earlier entries within one -'add' command have priority. Commands that are not specifically intended for use -as a hotkey are always considered applicable.

    -
    -
    -

    liquids

    +

    liquids

    Allows adding magma, water and obsidian to the game. It replaces the normal dfhack command line and can't be used from a hotkey. Settings will be remembered as long as dfhack runs. Intended for use in combination with the command @@ -1355,13 +1373,13 @@ temperatures (creating heat traps). You've been warned.

    -

    liquids-here

    +

    liquids-here

    Run the liquid spawner with the current/last settings made in liquids (if no settings in liquids were made it paints a point of 7/7 magma by default).

    Intended to be used as keybinding. Requires an active in-game cursor.

    -

    mode

    +

    mode

    This command lets you see and change the game mode directly. Not all combinations are good for every situation and most of them will produce undesirable results. There are a few good ones though.

    @@ -1380,11 +1398,11 @@ You just created a returnable mountain home and gained an adventurer.

    I take no responsibility of anything that happens as a result of using this tool

    -

    extirpate

    +

    extirpate

    A tool for getting rid of trees and shrubs. By default, it only kills a tree/shrub under the cursor. The plants are turned into ashes instantly.

    -
    -

    Options

    +
    +

    Options

    @@ -1400,24 +1418,24 @@ a tree/shrub under the cursor. The plants are turned into ashes instantly.

    -

    grow

    +

    grow

    Makes all saplings present on the map grow into trees (almost) instantly.

    -

    immolate

    +

    immolate

    Very similar to extirpate, but additionally sets the plants on fire. The fires can and will spread ;)

    -

    probe

    +

    probe

    Can be used to determine tile properties like temperature.

    -

    prospect

    +

    prospect

    Prints a big list of all the present minerals and plants. By default, only the visible part of the map is scanned.

    -
    @@ -1432,14 +1450,14 @@ the visible part of the map is scanned.

    -

    Pre-embark estimate

    +

    Pre-embark estimate

    If called during the embark selection screen, displays an estimate of layer stone availability. If the 'all' option is specified, also estimates veins. The estimate is computed either for 1 embark tile of the blinking biome, or for all tiles of the embark rectangle.

    -
    -

    Options

    +
    +

    Options

    @@ -1451,14 +1469,14 @@ for all tiles of the embark rectangle.

    -

    regrass

    +

    regrass

    Regrows grass. Not much to it ;)

    -

    rename

    +

    rename

    Allows renaming various things.

    -
    @@ -1488,7 +1506,7 @@ siege engine or an activity zone.
    -

    reveal

    +

    reveal

    This reveals the map. By default, HFS will remain hidden so that the demons don't spawn. You can use 'reveal hell' to reveal everything. With hell revealed, you won't be able to unpause until you hide the map again. If you really want @@ -1497,33 +1515,33 @@ to unpause with hell revealed, use 'reveal demons'.

    you move. When you use it this way, you don't need to run 'unreveal'.

    -

    unreveal

    +

    unreveal

    Reverts the effects of 'reveal'.

    -

    revtoggle

    +

    revtoggle

    Switches between 'reveal' and 'unreveal'.

    -

    revflood

    +

    revflood

    This command will hide the whole map and then reveal all the tiles that have a path to the in-game cursor.

    -

    revforget

    +

    revforget

    When you use reveal, it saves information about what was/wasn't visible before revealing everything. Unreveal uses this information to hide things again. This command throws away the information. For example, use in cases where you abandoned with the fort revealed and no longer want the data.

    -

    lair

    +

    lair

    This command allows you to mark the map as 'monster lair', preventing item scatter on abandon. When invoked as 'lair reset', it does the opposite.

    Unlike reveal, this command doesn't save the information about tiles - you won't be able to restore state of real monster lairs using 'lair reset'.

    -
    @@ -1537,23 +1555,23 @@ won't be able to restore state of real monster lairs using 'lair reset'.

    -

    seedwatch

    +

    seedwatch

    Tool for turning cooking of seeds and plants on/off depending on how much you have of them.

    See 'seedwatch help' for detailed description.

    -

    showmood

    +

    showmood

    Shows all items needed for the currently active strange mood.

    -

    copystock

    +

    copystock

    Copies the parameters of the currently highlighted stockpile to the custom stockpile settings and switches to custom stockpile placement mode, effectively allowing you to copy/paste stockpiles easily.

    -

    ssense / stonesense

    +

    ssense / stonesense

    An isometric visualizer that runs in a second window. This requires working graphics acceleration and at least a dual core CPU (otherwise it will slow down DF).

    @@ -1566,7 +1584,7 @@ thread: http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository

    -

    tiletypes

    +

    tiletypes

    Can be used for painting map tiles and is an interactive command, much like liquids.

    The tool works with two set of options and a brush. The brush determines which @@ -1627,25 +1645,25 @@ up.

    For more details, see the 'help' command while using this.

    -

    tiletypes-commands

    +

    tiletypes-commands

    Runs tiletypes commands, separated by ;. This makes it possible to change tiletypes modes from a hotkey.

    -

    tiletypes-here

    +

    tiletypes-here

    Apply the current tiletypes options at the in-game cursor position, including the brush. Can be used from a hotkey.

    -

    tiletypes-here-point

    +

    tiletypes-here-point

    Apply the current tiletypes options at the in-game cursor position to a single tile. Can be used from a hotkey.

    -

    tweak

    +

    tweak

    Contains various tweaks for minor bugs (currently just one).

    -
    @@ -1712,22 +1730,22 @@ reagents.
    -

    tubefill

    +

    tubefill

    Fills all the adamantine veins again. Veins that were empty will be filled in too, but might still trigger a demon invasion (this is a known bug).

    -

    digv

    +

    digv

    Designates a whole vein for digging. Requires an active in-game cursor placed over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles).

    -

    digvx

    +

    digvx

    A permanent alias for 'digv x'.

    -

    digl

    +

    digl

    Designates layer stone for digging. Requires an active in-game cursor placed over a layer stone tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles). With the 'undo' option it @@ -1735,16 +1753,16 @@ will remove the dig designation instead (if you realize that digging out a 50 z-level deep layer was not such a good idea after all).

    -

    diglx

    +

    diglx

    A permanent alias for 'digl x'.

    -

    digexp

    +

    digexp

    This command can be used for exploratory mining.

    See: http://df.magmawiki.com/index.php/DF2010:Exploratory_mining

    There are two variables that can be set: pattern and filter.

    @@ -1765,7 +1783,7 @@ z-level deep layer was not such a good idea after all).

    -

    Filters:

    +

    Filters:

    @@ -1780,8 +1798,8 @@ z-level deep layer was not such a good idea after all).

    After you have a pattern set, you can use 'expdig' to apply it again.

    -
    -

    Examples:

    +
    +

    Examples:

    designate the diagonal 5 patter over all hidden tiles:
      @@ -1802,11 +1820,11 @@ z-level deep layer was not such a good idea after all).

    -

    digcircle

    +

    digcircle

    A command for easy designation of filled and hollow circles. It has several types of options.

    -

    Shape:

    +

    Shape:

    @@ -1821,7 +1839,7 @@ It has several types of options.

    -

    Action:

    +

    Action:

    @@ -1836,7 +1854,7 @@ It has several types of options.

    -

    Designation types:

    +

    Designation types:

    @@ -1858,8 +1876,8 @@ It has several types of options.

    After you have set the options, the command called with no options repeats with the last selected parameters.

    -
    -

    Examples:

    +
    +

    Examples:

    • 'digcircle filled 3' = Dig a filled circle with radius = 3.
    • 'digcircle' = Do it again.
    • @@ -1867,11 +1885,11 @@ repeats with the last selected parameters.

    -

    weather

    +

    weather

    Prints the current weather map by default.

    Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'.

    -
    @@ -1887,10 +1905,10 @@ repeats with the last selected parameters.

    -

    workflow

    +

    workflow

    Manage control of repeat jobs.

    -
    -

    Usage

    +
    +

    Usage

    workflow enable [option...], workflow disable [option...]

    If no options are specified, enables or disables the plugin. @@ -1911,7 +1929,7 @@ Otherwise, enables or disables any of the following options:

    -

    Function

    +

    Function

    When the plugin is enabled, it protects all repeat jobs from removal. If they do disappear due to any cause, they are immediately re-added to their workshop and suspended.

    @@ -1922,7 +1940,7 @@ the amount has to drop before jobs are resumed; this is intended to reduce the frequency of jobs being toggled.

    -

    Constraint examples

    +

    Constraint examples

    Keep metal bolts within 900-1000, and wood/bone within 150-200.

     workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
    @@ -1960,19 +1978,19 @@ command.
     
    -

    mapexport

    +

    mapexport

    Export the current loaded map as a file. This will be eventually usable with visualizers.

    -

    dwarfexport

    +

    dwarfexport

    Export dwarves to RuneSmith-compatible XML.

    -

    zone

    +

    zone

    Helps a bit with managing activity zones (pens, pastures and pits) and cages.

    -
    @@ -2010,8 +2028,8 @@ the cursor are listed.
    -
    -

    Filters:

    +
    +

    Filters:

    @@ -2068,7 +2086,7 @@ for war/hunt). Negatable.
    -

    Usage with single units

    +

    Usage with single units

    One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units @@ -2077,7 +2095,7 @@ and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way.

    -

    Usage with filters

    +

    Usage with filters

    All filters can be used together with the 'assign' command.

    Restrictions: It's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. @@ -2095,14 +2113,14 @@ are not properly added to your own stocks; slaughtering them should work).

    Most filters can be negated (e.g. 'not grazer' -> race is not a grazer).

    -

    Mass-renaming

    +

    Mass-renaming

    Using the 'nick' command you can set the same nickname for multiple units. If used without 'assign', 'all' or 'count' it will rename all units in the current default target zone. Combined with 'assign', 'all' or 'count' (and further optional filters) it will rename units matching the filter conditions.

    -

    Cage zones

    +

    Cage zones

    Using the 'tocages' command you can assign units to a set of cages, for example a room next to your butcher shop(s). They will be spread evenly among available cages to optimize hauling to and butchering from them. For this to work you need @@ -2112,8 +2130,8 @@ all cages you want to use. Then use 'zone set' (like with 'assign') and use would make no sense, but can be used together with 'nick' or 'remnick' and all the usual filters.

    -
    -

    Examples

    +
    +

    Examples

    zone assign all own ALPACA minage 3 maxage 10
    Assign all own alpacas who are between 3 and 10 years old to the selected @@ -2139,7 +2157,7 @@ on the current default zone.
    -

    autonestbox

    +

    autonestbox

    Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless @@ -2149,8 +2167,8 @@ to a 1x1 pasture is not a good idea. Only tame and domesticated own units are processed since pasturing half-trained wild egglayers could destroy your neat nestbox zones when they revert to wild. When called without options autonestbox will instantly run once.

    -
    -

    Options:

    +
    +

    Options:

    @@ -2168,7 +2186,7 @@ frames between runs.
    -

    autobutcher

    +

    autobutcher

    Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units will be processed.

    Named units will be completely ignored (to protect specific animals from @@ -2181,8 +2199,8 @@ units or with 'zone nick' to mass-rename units in pastures and cages).

    Once you have too much kids, the youngest will be butchered first. If you don't set any target count the following default will be used: 1 male kid, 5 female kids, 1 male adult, 5 female adults.

    -
    @@ -2234,8 +2252,8 @@ for future entries.
    -
    -

    Examples:

    +
    +

    Examples:

    You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, 1 male) of the race alpaca. Once the kids grow up the oldest adults will get slaughtered. Excess kids will get slaughtered starting with the youngest @@ -2266,7 +2284,7 @@ autobutcher unwatch ALPACA CAT

    -

    Note:

    +

    Note:

    Settings and watchlist are stored in the savegame, so that you can have different settings for each world. If you want to copy your watchlist to another savegame you can use the command list_export:

    @@ -2280,7 +2298,7 @@ autobutcher.bat
    -

    autolabor

    +

    autolabor

    Automatically manage dwarf labors.

    When enabled, autolabor periodically checks your dwarves and enables or disables labors. It tries to keep as many dwarves as possible busy but @@ -2292,8 +2310,59 @@ while it is enabled.

    For detailed usage information, see 'help autolabor'.

    +
    +
    +

    Scripts

    +

    Lua or ruby scripts placed in the hack/scripts/ directory are considered for +execution as if they were native DFHack commands. They are listed at the end +of the 'ls' command output.

    +

    Note: scripts in subdirectories of hack/scripts/ can still be called, but will +only be listed by ls if called as 'ls -a'. This is intended as a way to hide +scripts that are obscure, developer-oriented, or should be used as keybindings.

    +

    Some notable scripts:

    +
    +

    quicksave

    +

    If called in dwarf mode, makes DF immediately auto-save the game by setting a flag +normally used in seasonal auto-save.

    +
    +
    +

    setfps

    +

    Run setfps <number> to set the FPS cap at runtime, in case you want to watch +combat in slow motion or something :)

    +
    +
    +

    fix/*

    +

    Scripts in this subdirectory fix various bugs and issues, some of them obscure.

    +
      +
    • fix/dead-units

      +

      Removes uninteresting dead units from the unit list. Doesn't seem to give any +noticeable performance gain, but migrants normally stop if the unit list grows +to around 3000 units, and this script reduces it back.

      +
    • +
    • fix/population-cap

      +

      Run this after every migrant wave to ensure your population cap is not exceeded. +The issue with the cap is that it is compared to the population number reported +by the last caravan, so once it drops below the cap, migrants continue to come +until that number is updated again.

      +
    • +
    • fix/stable-temp

      +

      Instantly sets the temperature of all free-lying items to be in equilibrium with +the environment and stops temperature updates. In order to maintain this efficient +state however, use tweak stable-temp and tweak fast-heat.

      +
    • +
    • fix/item-occupancy

      +

      Diagnoses and fixes issues with nonexistant 'items occupying site', usually +caused by autodump bugs or other hacking mishaps.

      +
    • +
    +
    +
    +

    gui/*

    +

    Scripts that implement dialogs inserted into the main game window are put in this +directory.

    +
    -

    growcrops

    +

    growcrops

    Instantly grow seeds inside farming plots.

    With no argument, this command list the various seed types currently in use in your farming plots. @@ -2305,7 +2374,7 @@ growcrops plump 40

    -

    removebadthoughts

    +

    removebadthoughts

    This script remove negative thoughts from your dwarves. Very useful against tantrum spirals.

    With a selected unit in 'v' mode, will clear this unit's mind, otherwise @@ -2318,7 +2387,7 @@ you unpause.

    it removed.

    -

    slayrace

    +

    slayrace

    Kills any unit of a given race.

    With no argument, lists the available races.

    With the special argument 'him', targets only the selected creature.

    @@ -2344,7 +2413,7 @@ slayrace elve magma
    -

    magmasource

    +

    magmasource

    Create an infinite magma source on a tile.

    This script registers a map tile as a magma source, and every 12 game ticks that tile receives 1 new unit of flowing magma.

    @@ -2360,11 +2429,11 @@ To remove all placed sources, call magmasource stop
    -

    In-game interface tools

    +

    In-game interface tools

    These tools work by displaying dialogs or overlays in the game window, and are mostly implemented by lua scripts.

    -

    Dwarf Manipulator

    +

    Dwarf Manipulator

    Implemented by the manipulator plugin. To activate, open the unit screen and press 'l'.

    This tool implements a Dwarf Therapist-like interface within the game UI. The @@ -2398,14 +2467,14 @@ cursor onto that cell instead of toggling it.

    Pressing ESC normally returns to the unit screen, but Shift-ESC would exit directly to the main dwarf mode screen.

    -
    -

    Liquids

    +
    +

    Liquids

    Implemented by the gui/liquids script. To use, bind to a key and activate in the 'k' mode.

    While active, use the suggested keys to switch the usual liquids parameters, and Enter to select the target area and apply changes.

    -

    Mechanisms

    +

    Mechanisms

    Implemented by the gui/mechanims script. To use, bind to a key and activate in the 'q' mode.

    Lists mechanisms connected to the building, and their links. Navigating the list centers the view on the relevant linked buildings.

    @@ -2414,14 +2483,14 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter re-entering the mechanisms ui.

    -

    Power Meter

    +

    Power Meter

    Front-end to the power-meter plugin implemented by the gui/power-meter script. Bind to a key and activate after selecting Pressure Plate in the build menu.

    The script follows the general look and feel of the regular pressure plate build configuration page, but configures parameters relevant to the modded power meter building.

    -
    -

    Rename

    +
    +

    Rename

    Backed by the rename plugin, the gui/rename script allows entering the desired name via a simple dialog in the game ui.

      @@ -2437,14 +2506,14 @@ It is also possible to rename zones from the 'i' menu.

      The building or unit are automatically assumed when in relevant ui state.

    -

    Room List

    +

    Room List

    Implemented by the gui/room-list script. To use, bind to a key and activate in the 'q' mode, either immediately or after opening the assign owner page.

    The script lists other rooms owned by the same owner, or by the unit selected in the assign list, and allows unassigning them.

    -

    Siege Engine

    +

    Siege Engine

    Front-end to the siege-engine plugin implemented by the gui/siege-engine script. Bind to a key and activate after selecting a siege engine in 'q' mode.

    The main mode displays the current target, selected ammo item type, linked stockpiles and @@ -2468,14 +2537,14 @@ e.g. like making siegers bring their own, are something only Toady can do.

    -

    RAW hacks

    +

    RAW hacks

    These plugins detect certain structures in RAWs, and enhance them in various ways.

    -

    Steam Engine

    +

    Steam Engine

    The steam-engine plugin detects custom workshops with STEAM_ENGINE in their token, and turns them into real steam engines.

    -

    Rationale

    +

    Rationale

    The vanilla game contains only water wheels and windmills as sources of power, but windmills give relatively little power, and water wheels require flowing water, which must either be a real river and thus immovable and @@ -2486,7 +2555,7 @@ it can be done just by combining existing features of the game engine in a new way with some glue code and a bit of custom logic.

    -

    Construction

    +

    Construction

    The workshop needs water as its input, which it takes via a passable floor tile below it, like usual magma workshops do. The magma version also needs magma.

    @@ -2504,7 +2573,7 @@ This also means that engines cannot be chained without intermediate short axles that can be built later than both of the engines.

    -

    Operation

    +

    Operation

    In order to operate the engine, queue the Stoke Boiler job (optionally on repeat). A furnace operator will come, possibly bringing a bar of fuel, and perform it. As a result, a "boiling water" item will appear @@ -2529,7 +2598,7 @@ decrease it by further 4%, and also decrease the whole steam use rate by 10%.

    -

    Explosions

    +

    Explosions

    The engine must be constructed using barrel, pipe and piston from fire-safe, or in the magma version magma-safe metals.

    During operation weak parts get gradually worn out, and @@ -2538,7 +2607,7 @@ toppled during operation by a building destroyer, or a tantruming dwarf.

    -

    Save files

    +

    Save files

    It should be safe to load and view engine-using fortresses from a DF version without DFHack installed, except that in such case the engines won't work. However actually making modifications @@ -2549,7 +2618,7 @@ being generated.

    -

    Add Spatter

    +

    Add Spatter

    This plugin makes reactions with names starting with SPATTER_ADD_ produce contaminants on the items instead of improvements.

    Intended to give some use to all those poisons that can be bought from caravans.

    From b115edcf68c69deb432915a6fbcd21ff6345bfe0 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 20 Sep 2012 19:57:43 +0200 Subject: [PATCH 040/196] ruby: return selected zone in building_find --- plugins/ruby/building.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 4896aed48..68229c007 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -8,6 +8,8 @@ module DFHack k.building if k.type == :Building when :BuildingItems, :QueryBuilding world.selected_building + when :Zones, :ZonesPenInfo, :ZonesPitInfo, :ZonesHospitalInfo + ui_sidebar_menus.zone.selected end elsif what.kind_of?(Integer) From c5101b2ea4f2685dd306f91840ab471048e52495 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 21 Sep 2012 12:19:48 +0400 Subject: [PATCH 041/196] Group commands in the readme document by their general type. There are way too many of them now for a flat list to work. --- README.rst | 1536 ++++++++++++++++++----------------- Readme.html | 2217 +++++++++++++++++++++++++-------------------------- 2 files changed, 1902 insertions(+), 1851 deletions(-) diff --git a/README.rst b/README.rst index 40d7b74e3..b0258bb30 100644 --- a/README.rst +++ b/README.rst @@ -152,33 +152,109 @@ Almost all the commands support using the 'help ' built-in command to retrieve further help without having to look at this document. Alternatively, some accept a 'help'/'?' option on their command line. + +Game progress +============= + +die +--- +Instantly kills DF without saving. + +forcepause +---------- +Forces DF to pause. This is useful when your FPS drops below 1 and you lose +control of the game. + + * Activate with 'forcepause 1' + * Deactivate with 'forcepause 0' + +nopause +------- +Disables pausing (both manual and automatic) with the exception of pause forced +by 'reveal hell'. This is nice for digging under rivers. + +fastdwarf +--------- +Makes your minions move at ludicrous speeds. + + * Activate with 'fastdwarf 1' + * Deactivate with 'fastdwarf 0' + + +Game interface +============== + +follow +------ +Makes the game view follow the currently highlighted unit after you exit from +current menu/cursor mode. Handy for watching dwarves running around. Deactivated +by moving the view manually. + +tidlers +------- +Toggle between all possible positions where the idlers count can be placed. + +twaterlvl +--------- +Toggle between displaying/not displaying liquid depth as numbers. + +copystock +---------- +Copies the parameters of the currently highlighted stockpile to the custom +stockpile settings and switches to custom stockpile placement mode, effectively +allowing you to copy/paste stockpiles easily. + +rename +------ +Allows renaming various things. + +Options: + + :rename squad "name": Rename squad by index to 'name'. + :rename hotkey \"name\": Rename hotkey by index. This allows assigning + longer commands to the DF hotkeys. + :rename unit "nickname": Rename a unit/creature highlighted in the DF user + interface. + :rename unit-profession "custom profession": Change proffession name of the + highlighted unit/creature. + :rename building "name": Set a custom name for the selected building. + The building must be one of stockpile, workshop, furnace, trap, + siege engine or an activity zone. + + +Adventure mode +============== + adv-bodyswap -============ +------------ This allows taking control over your followers and other creatures in adventure mode. For example, you can make them pick up new arms and armor and equip them properly. -Usage ------ +Usage: + * When viewing unit details, body-swaps into that unit. * In the main adventure mode screen, reverts transient swap. advtools -======== +-------- A package of different adventure mode tools (currently just one) - -Usage ------ -:list-equipped [all]: List armor and weapons equipped by your companions. - If all is specified, also lists non-metal clothing. -:metal-detector [all-types] [non-trader]: Reveal metal armor and weapons in - shops. The options disable the checks - on item type and being in shop. +Usage: + + :list-equipped [all]: List armor and weapons equipped by your companions. + If all is specified, also lists non-metal clothing. + :metal-detector [all-types] [non-trader]: Reveal metal armor and weapons in + shops. The options disable the checks + on item type and being in shop. + + +Map modification +================ changelayer -=========== +----------- Changes material of the geology layer under cursor to the specified inorganic RAW material. Can have impact on all surrounding regions, not only your embark! By default changing stone to soil and vice versa is not allowed. By default @@ -191,18 +267,18 @@ as well, though. Mineral veins and gem clusters will stay on the map. Use tl;dr: You will end up with changing quite big areas in one go, especially if you use it in lower z levels. Use with care. -Options -------- -:all_biomes: Change selected layer for all biomes on your map. +Options: + + :all_biomes: Change selected layer for all biomes on your map. Result may be undesirable since the same layer can AND WILL be on different z-levels for different biomes. Use the tool 'probe' to get an idea how layers and biomes are distributed on your map. -:all_layers: Change all layers on your map (only for the selected biome + :all_layers: Change all layers on your map (only for the selected biome unless 'all_biomes' is added). Candy mountain, anyone? Will make your map quite boring, but tidy. -:force: Allow changing stone to soil and vice versa. !!THIS CAN HAVE + :force: Allow changing stone to soil and vice versa. !!THIS CAN HAVE WEIRD EFFECTS, USE WITH CARE!! Note that soil will not be magically replaced with stone. You will, however, get a stone floor after digging so it @@ -211,16 +287,16 @@ Options You will, however, get a soil floor after digging so it could be helpful for creating farm plots on maps with no soil. -:verbose: Give some details about what is being changed. -:trouble: Give some advice about known problems. + :verbose: Give some details about what is being changed. + :trouble: Give some advice about known problems. Examples: ---------- -``changelayer GRANITE`` + + ``changelayer GRANITE`` Convert layer at cursor position into granite. -``changelayer SILTY_CLAY force`` + ``changelayer SILTY_CLAY force`` Convert layer at cursor position into clay even if it's stone. -``changelayer MARBLE all_biomes all_layers`` + ``changelayer MARBLE all_biomes all_layers`` Convert all layers of all biomes which are not soil into marble. .. note:: @@ -239,18 +315,18 @@ Examples: You did save your game, right? changevein -========== +---------- Changes material of the vein under cursor to the specified inorganic RAW material. Only affects tiles within the current 16x16 block - for veins and large clusters, you will need to use this command multiple times. Example: --------- -``changevein NATIVE_PLATINUM`` + + ``changevein NATIVE_PLATINUM`` Convert vein at cursor position into platinum ore. changeitem -========== +---------- Allows changing item material and base quality. By default the item currently selected in the UI will be changed (you can select items in the 'k' list or inside containers/inventory). By default change is only allowed if materials @@ -261,474 +337,283 @@ in weirdness. To get an idea how the RAW id should look like, check some items with 'info'. Using 'force' might create items which are not touched by crafters/haulers. -Options -------- -:info: Don't change anything, print some info instead. -:here: Change all items at the cursor position. Requires in-game cursor. -:material, m: Change material. Must be followed by valid material RAW id. -:quality, q: Change base quality. Must be followed by number (0-5). -:force: Ignore subtypes, force change to new material. +Options: + + :info: Don't change anything, print some info instead. + :here: Change all items at the cursor position. Requires in-game cursor. + :material, m: Change material. Must be followed by valid material RAW id. + :quality, q: Change base quality. Must be followed by number (0-5). + :force: Ignore subtypes, force change to new material. Examples: ---------- -``changeitem m INORGANIC:GRANITE here`` + + ``changeitem m INORGANIC:GRANITE here`` Change material of all items under the cursor to granite. -``changeitem q 5`` + ``changeitem q 5`` Change currently selected item to masterpiece quality. -cursecheck -========== -Checks a single map tile or the whole map/world for cursed creatures (ghosts, -vampires, necromancers, werebeasts, zombies). +colonies +-------- +Allows listing all the vermin colonies on the map and optionally turning them into honey bee colonies. -With an active in-game cursor only the selected tile will be observed. -Without a cursor the whole map will be checked. +Options: -By default cursed creatures will be only counted in case you just want to find -out if you have any of them running around in your fort. Dead and passive -creatures (ghosts who were put to rest, killed vampires, ...) are ignored. -Undead skeletons, corpses, bodyparts and the like are all thrown into the curse -category "zombie". Anonymous zombies and resurrected body parts will show -as "unnamed creature". + :bees: turn colonies into honey bee colonies + +deramp (by zilpin) +------------------ +Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation. +It also removes any and all 'down ramps' that can remain after a cave-in (you don't have to designate anything for that to happen). -Options +feature ------- -:detail: Print full name, date of birth, date of curse and some status - info (some vampires might use fake identities in-game, though). -:nick: Set the type of curse as nickname (does not always show up - in-game, some vamps don't like nicknames). -:all: Include dead and passive cursed creatures (can result in a quite - long list after having FUN with necromancers). -:verbose: Print all curse tags (if you really want to know it all). +Enables management of map features. -Examples: ---------- -``cursecheck detail all`` - Give detailed info about all cursed creatures including deceased ones (no - in-game cursor). -``cursecheck nick`` - Give a nickname all living/active cursed creatures on the map(no in-game - cursor). +* Discovering a magma feature (magma pool, volcano, magma sea, or curious + underground structure) permits magma workshops and furnaces to be built. +* Discovering a cavern layer causes plants (trees, shrubs, and grass) from + that cavern to grow within your fortress. -.. note:: +Options: - * If you do a full search (with the option "all") former ghosts will show up - with the cursetype "unknown" because their ghostly flag is not set - anymore. But if you happen to find a living/active creature with cursetype - "unknown" please report that in the dfhack thread on the modding forum or - per irc. This is likely to happen with mods which introduce new types - of curses, for example. + :list: Lists all map features in your current embark by index. + :show X: Marks the selected map feature as discovered. + :hide X: Marks the selected map feature as undiscovered. -follow -====== -Makes the game view follow the currently highlighted unit after you exit from -current menu/cursor mode. Handy for watching dwarves running around. Deactivated -by moving the view manually. +liquids +------- +Allows adding magma, water and obsidian to the game. It replaces the normal +dfhack command line and can't be used from a hotkey. Settings will be remembered +as long as dfhack runs. Intended for use in combination with the command +liquids-here (which can be bound to a hotkey). -forcepause -========== -Forces DF to pause. This is useful when your FPS drops below 1 and you lose -control of the game. +For more information, refer to the command's internal help. - * Activate with 'forcepause 1' - * Deactivate with 'forcepause 0' +.. note:: -nopause -======= -Disables pausing (both manual and automatic) with the exception of pause forced -by 'reveal hell'. This is nice for digging under rivers. + Spawning and deleting liquids can F up pathing data and + temperatures (creating heat traps). You've been warned. -die -=== -Instantly kills DF without saving. +liquids-here +------------ +Run the liquid spawner with the current/last settings made in liquids (if no +settings in liquids were made it paints a point of 7/7 magma by default). -autodump -======== -This utility lets you quickly move all items designated to be dumped. -Items are instantly moved to the cursor position, the dump flag is unset, -and the forbid flag is set, as if it had been dumped normally. -Be aware that any active dump item tasks still point at the item. +Intended to be used as keybinding. Requires an active in-game cursor. -Cursor must be placed on a floor tile so the items can be dumped there. -Options -------- -:destroy: Destroy instead of dumping. Doesn't require a cursor. -:destroy-here: Destroy items only under the cursor. -:visible: Only process items that are not hidden. -:hidden: Only process hidden items. -:forbidden: Only process forbidden items (default: only unforbidden). +tiletypes +--------- +Can be used for painting map tiles and is an interactive command, much like +liquids. -autodump-destroy-here -===================== -Destroy items marked for dumping under cursor. Identical to autodump -destroy-here, but intended for use as keybinding. +The tool works with two set of options and a brush. The brush determines which +tiles will be processed. First set of options is the filter, which can exclude +some of the tiles from the brush by looking at the tile properties. The second +set of options is the paint - this determines how the selected tiles are +changed. -autodump-destroy-item -===================== -Destroy the selected item. The item may be selected in the 'k' list, or inside -a container. If called again before the game is resumed, cancels destroy. +Both paint and filter can have many different properties including things like +general shape (WALL, FLOOR, etc.), general material (SOIL, STONE, MINERAL, +etc.), state of 'designated', 'hidden' and 'light' flags. -burrow -====== -Miscellaneous burrow control. Allows manipulating burrows and automated burrow -expansion while digging. +The properties of filter and paint can be partially defined. This means that +you can for example do something like this: -Options -------- -:enable feature ...: -:disable feature ...: Enable or Disable features of the plugin. -:clear-unit burrow burrow ...: -:clear-tiles burrow burrow ...: Removes all units or tiles from the burrows. -:set-units target-burrow src-burrow ...: -:add-units target-burrow src-burrow ...: -:remove-units target-burrow src-burrow ...: Adds or removes units in source - burrows to/from the target burrow. Set is equivalent to clear and add. -:set-tiles target-burrow src-burrow ...: -:add-tiles target-burrow src-burrow ...: -:remove-tiles target-burrow src-burrow ...: Adds or removes tiles in source - burrows to/from the target burrow. In place of a source burrow it is - possible to use one of the following keywords: ABOVE_GROUND, - SUBTERRANEAN, INSIDE, OUTSIDE, LIGHT, DARK, HIDDEN, REVEALED - -Features --------- -:auto-grow: When a wall inside a burrow with a name ending in '+' is dug - out, the burrow is extended to newly-revealed adjacent walls. - This final '+' may be omitted in burrow name args of commands above. - Digging 1-wide corridors with the miner inside the burrow is SLOW. +:: -catsplosion -=========== -Makes cats just *multiply*. It is not a good idea to run this more than once or -twice. + filter material STONE + filter shape FORTIFICATION + paint shape FLOOR -clean -===== -Cleans all the splatter that get scattered all over the map, items and -creatures. In an old fortress, this can significantly reduce FPS lag. It can -also spoil your !!FUN!!, so think before you use it. +This will turn all stone fortifications into floors, preserving the material. -Options -------- -:map: Clean the map tiles. By default, it leaves mud and snow alone. -:units: Clean the creatures. Will also clean hostiles. -:items: Clean all the items. Even a poisoned blade. +Or this: +:: -Extra options for 'map' ------------------------ -:mud: Remove mud in addition to the normal stuff. -:snow: Also remove snow coverings. + filter shape FLOOR + filter material MINERAL + paint shape WALL -spotclean -========= -Works like 'clean map snow mud', but only for the tile under the cursor. Ideal -if you want to keep that bloody entrance 'clean map' would clean up. +Turning mineral vein floors back into walls. -cleanowned -========== -Confiscates items owned by dwarfs. By default, owned food on the floor -and rotten items are confistacted and dumped. +The tool also allows tweaking some tile flags: -Options -------- -:all: confiscate all owned items -:scattered: confiscated and dump all items scattered on the floor -:x: confiscate/dump items with wear level 'x' and more -:X: confiscate/dump items with wear level 'X' and more -:dryrun: a dry run. combine with other options to see what will happen - without it actually happening. +Or this: -Example: --------- -``cleanowned scattered X`` : This will confiscate rotten and dropped food, garbage on the floors and any worn items with 'X' damage and above. +:: -colonies -======== -Allows listing all the vermin colonies on the map and optionally turning them into honey bee colonies. + paint hidden 1 + paint hidden 0 -Options -------- -:bees: turn colonies into honey bee colonies +This will hide previously revealed tiles (or show hidden with the 0 option). -deramp (by zilpin) -================== -Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation. -It also removes any and all 'down ramps' that can remain after a cave-in (you don't have to designate anything for that to happen). +Any paint or filter option (or the entire paint or filter) can be disabled entirely by using the ANY keyword: -dfusion -======= -This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin. +:: -See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15 + paint hidden ANY + paint shape ANY + filter material any + filter shape any + filter any -Confirmed working DFusion plugins: ----------------------------------- -:simple_embark: allows changing the number of dwarves available on embark. +You can use several different brushes for painting tiles: + * Point. (point) + * Rectangular range. (range) + * A column ranging from current cursor to the first solid tile above. (column) + * DF map block - 16x16 tiles, in a regular grid. (block) -.. note:: - - * Some of the DFusion plugins aren't completely ported yet. This can lead to crashes. - * This is currently working only on Windows. - * The game will be suspended while you're using dfusion. Don't panic when it doen't respond. - -drybuckets -========== -This utility removes water from all buckets in your fortress, allowing them to be safely used for making lye. - -fastdwarf -========= -Makes your minions move at ludicrous speeds. - - * Activate with 'fastdwarf 1' - * Deactivate with 'fastdwarf 0' +Example: -feature -======= -Enables management of map features. +:: -* Discovering a magma feature (magma pool, volcano, magma sea, or curious - underground structure) permits magma workshops and furnaces to be built. -* Discovering a cavern layer causes plants (trees, shrubs, and grass) from - that cavern to grow within your fortress. + range 10 10 1 -Options -------- -:list: Lists all map features in your current embark by index. -:show X: Marks the selected map feature as discovered. -:hide X: Marks the selected map feature as undiscovered. +This will change the brush to a rectangle spanning 10x10 tiles on one z-level. +The range starts at the position of the cursor and goes to the east, south and +up. -filltraffic -=========== -Set traffic designations using flood-fill starting at the cursor. +For more details, see the 'help' command while using this. -Traffic Type Codes: -------------------- -:H: High Traffic -:N: Normal Traffic -:L: Low Traffic -:R: Restricted Traffic +tiletypes-commands +------------------ +Runs tiletypes commands, separated by ;. This makes it possible to change +tiletypes modes from a hotkey. -Other Options: +tiletypes-here -------------- -:X: Fill accross z-levels. -:B: Include buildings and stockpiles. -:P: Include empty space. - -Example: --------- -'filltraffic H' - When used in a room with doors, it will set traffic to HIGH in just that room. - -alltraffic -========== -Set traffic designations for every single tile of the map (useful for resetting traffic designations). +Apply the current tiletypes options at the in-game cursor position, including +the brush. Can be used from a hotkey. -Traffic Type Codes: -------------------- -:H: High Traffic -:N: Normal Traffic -:L: Low Traffic -:R: Restricted Traffic +tiletypes-here-point +-------------------- +Apply the current tiletypes options at the in-game cursor position to a single +tile. Can be used from a hotkey. -Example: +tubefill -------- -'alltraffic N' - Set traffic to 'normal' for all tiles. +Fills all the adamantine veins again. Veins that were empty will be filled in +too, but might still trigger a demon invasion (this is a known bug). -fixdiplomats -============ -Up to version 0.31.12, Elves only sent Diplomats to your fortress to propose -tree cutting quotas due to a bug; once that bug was fixed, Elves stopped caring -about excess tree cutting. This command adds a Diplomat position to all Elven -civilizations, allowing them to negotiate tree cutting quotas (and allowing you -to violate them and potentially start wars) in case you haven't already modified -your raws accordingly. +extirpate +--------- +A tool for getting rid of trees and shrubs. By default, it only kills +a tree/shrub under the cursor. The plants are turned into ashes instantly. -fixmerchants -============ -This command adds the Guild Representative position to all Human civilizations, -allowing them to make trade agreements (just as they did back in 0.28.181.40d -and earlier) in case you haven't already modified your raws accordingly. +Options: -fixveins -======== -Removes invalid references to mineral inclusions and restores missing ones. -Use this if you broke your embark with tools like tiletypes, or if you -accidentally placed a construction on top of a valuable mineral floor. + :shrubs: affect all shrubs on the map + :trees: affect all trees on the map + :all: affect every plant! -flows -===== -A tool for checking how many tiles contain flowing liquids. If you suspect that -your magma sea leaks into HFS, you can use this tool to be sure without -revealing the map. +grow +---- +Makes all saplings present on the map grow into trees (almost) instantly. -getplants -========= -This tool allows plant gathering and tree cutting by RAW ID. Specify the types -of trees to cut down and/or shrubs to gather by their plant names, separated -by spaces. +immolate +-------- +Very similar to extirpate, but additionally sets the plants on fire. The fires +can and *will* spread ;) -Options +regrass ------- -:-t: Select trees only (exclude shrubs) -:-s: Select shrubs only (exclude trees) -:-c: Clear designations instead of setting them -:-x: Apply selected action to all plants except those specified (invert - selection) - -Specifying both -t and -s will have no effect. If no plant IDs are specified, -all valid plant IDs will be listed. - -tidlers -======= -Toggle between all possible positions where the idlers count can be placed. +Regrows grass. Not much to it ;) -twaterlvl -========= -Toggle between displaying/not displaying liquid depth as numbers. +weather +------- +Prints the current weather map by default. -job -=== -Command for general job query and manipulation. +Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'. Options: - * no extra options - Print details of the current job. The job can be selected - in a workshop, or the unit/jobs screen. - * list - Print details of all jobs in the selected workshop. - * item-material - Replace the exact material - id in the job item. - * item-type - Replace the exact item type id in - the job item. - -job-material -============ -Alter the material of the selected job. - -Invoked as: job-material - -Intended to be used as a keybinding: - * In 'q' mode, when a job is highlighted within a workshop or furnace, - changes the material of the job. Only inorganic materials can be used - in this mode. - * In 'b' mode, during selection of building components positions the cursor - over the first available choice with the matching material. - -job-duplicate -============= -Duplicate the selected job in a workshop: - * In 'q' mode, when a job is highlighted within a workshop or furnace building, - instantly duplicates the job. - -liquids -======= -Allows adding magma, water and obsidian to the game. It replaces the normal -dfhack command line and can't be used from a hotkey. Settings will be remembered -as long as dfhack runs. Intended for use in combination with the command -liquids-here (which can be bound to a hotkey). -For more information, refer to the command's internal help. + :snow: make it snow everywhere. + :rain: make it rain. + :clear: clear the sky. -.. note:: - - Spawning and deleting liquids can F up pathing data and - temperatures (creating heat traps). You've been warned. -liquids-here -============ -Run the liquid spawner with the current/last settings made in liquids (if no -settings in liquids were made it paints a point of 7/7 magma by default). +Map inspection +============== -Intended to be used as keybinding. Requires an active in-game cursor. +cursecheck +---------- +Checks a single map tile or the whole map/world for cursed creatures (ghosts, +vampires, necromancers, werebeasts, zombies). -mode -==== -This command lets you see and change the game mode directly. -Not all combinations are good for every situation and most of them will -produce undesirable results. There are a few good ones though. +With an active in-game cursor only the selected tile will be observed. +Without a cursor the whole map will be checked. -.. admonition:: Example +By default cursed creatures will be only counted in case you just want to find +out if you have any of them running around in your fort. Dead and passive +creatures (ghosts who were put to rest, killed vampires, ...) are ignored. +Undead skeletons, corpses, bodyparts and the like are all thrown into the curse +category "zombie". Anonymous zombies and resurrected body parts will show +as "unnamed creature". - You are in fort game mode, managing your fortress and paused. - You switch to the arena game mode, *assume control of a creature* and then - switch to adventure game mode(1). - You just lost a fortress and gained an adventurer. - You could also do this. - You are in fort game mode, managing your fortress and paused at the esc menu. - You switch to the adventure game mode, then use Dfusion to *assume control of a creature* and then - save or retire. - You just created a returnable mountain home and gained an adventurer. +Options: + :detail: Print full name, date of birth, date of curse and some status + info (some vampires might use fake identities in-game, though). + :nick: Set the type of curse as nickname (does not always show up + in-game, some vamps don't like nicknames). + :all: Include dead and passive cursed creatures (can result in a quite + long list after having FUN with necromancers). + :verbose: Print all curse tags (if you really want to know it all). -I take no responsibility of anything that happens as a result of using this tool +Examples: -extirpate -========= -A tool for getting rid of trees and shrubs. By default, it only kills -a tree/shrub under the cursor. The plants are turned into ashes instantly. + ``cursecheck detail all`` + Give detailed info about all cursed creatures including deceased ones (no + in-game cursor). + ``cursecheck nick`` + Give a nickname all living/active cursed creatures on the map(no in-game + cursor). -Options -------- -:shrubs: affect all shrubs on the map -:trees: affect all trees on the map -:all: affect every plant! +.. note:: -grow -==== -Makes all saplings present on the map grow into trees (almost) instantly. + * If you do a full search (with the option "all") former ghosts will show up + with the cursetype "unknown" because their ghostly flag is not set + anymore. But if you happen to find a living/active creature with cursetype + "unknown" please report that in the dfhack thread on the modding forum or + per irc. This is likely to happen with mods which introduce new types + of curses, for example. -immolate -======== -Very similar to extirpate, but additionally sets the plants on fire. The fires -can and *will* spread ;) +flows +----- +A tool for checking how many tiles contain flowing liquids. If you suspect that +your magma sea leaks into HFS, you can use this tool to be sure without +revealing the map. probe -===== +----- Can be used to determine tile properties like temperature. prospect -======== +-------- Prints a big list of all the present minerals and plants. By default, only the visible part of the map is scanned. -Options -------- -:all: Scan the whole map, as if it was revealed. -:value: Show material value in the output. Most useful for gems. -:hell: Show the Z range of HFS tubes. Implies 'all'. +Options: + + :all: Scan the whole map, as if it was revealed. + :value: Show material value in the output. Most useful for gems. + :hell: Show the Z range of HFS tubes. Implies 'all'. Pre-embark estimate -------------------- +................... + If called during the embark selection screen, displays an estimate of layer stone availability. If the 'all' option is specified, also estimates veins. The estimate is computed either for 1 embark tile of the blinking biome, or for all tiles of the embark rectangle. -Options -------- -:all: processes all tiles, even hidden ones. - -regrass -======= -Regrows grass. Not much to it ;) - -rename -====== -Allows renaming various things. +Options: -Options -------- -:rename squad "name": Rename squad by index to 'name'. -:rename hotkey \"name\": Rename hotkey by index. This allows assigning - longer commands to the DF hotkeys. -:rename unit "nickname": Rename a unit/creature highlighted in the DF user - interface. -:rename unit-profession "custom profession": Change proffession name of the - highlighted unit/creature. -:rename building "name": Set a custom name for the selected building. - The building must be one of stockpile, workshop, furnace, trap, - siege engine or an activity zone. + :all: processes all tiles, even hidden ones. reveal -====== +------ This reveals the map. By default, HFS will remain hidden so that the demons don't spawn. You can use 'reveal hell' to reveal everything. With hell revealed, you won't be able to unpause until you hide the map again. If you really want @@ -738,167 +623,325 @@ Reveal also works in adventure mode, but any of its effects are negated once you move. When you use it this way, you don't need to run 'unreveal'. unreveal -======== +-------- Reverts the effects of 'reveal'. revtoggle -========= +--------- Switches between 'reveal' and 'unreveal'. revflood -======== +-------- This command will hide the whole map and then reveal all the tiles that have a path to the in-game cursor. revforget -========= +--------- When you use reveal, it saves information about what was/wasn't visible before revealing everything. Unreveal uses this information to hide things again. This command throws away the information. For example, use in cases where you abandoned with the fort revealed and no longer want the data. -lair -==== -This command allows you to mark the map as 'monster lair', preventing item -scatter on abandon. When invoked as 'lair reset', it does the opposite. +showmood +-------- +Shows all items needed for the currently active strange mood. -Unlike reveal, this command doesn't save the information about tiles - you -won't be able to restore state of real monster lairs using 'lair reset'. -Options -------- -:lair: Mark the map as monster lair -:lair reset: Mark the map as ordinary (not lair) +Designations +============ -seedwatch -========= -Tool for turning cooking of seeds and plants on/off depending on how much you -have of them. +burrow +------ +Miscellaneous burrow control. Allows manipulating burrows and automated burrow +expansion while digging. -See 'seedwatch help' for detailed description. +Options: -showmood -======== -Shows all items needed for the currently active strange mood. + **enable feature ...** + Enable features of the plugin. + **disable feature ...** + Disable features of the plugin. + **clear-unit burrow burrow ...** + Remove all units from the burrows. + **clear-tiles burrow burrow ...** + Remove all tiles from the burrows. + **set-units target-burrow src-burrow ...** + Clear target, and adds units from source burrows. + **add-units target-burrow src-burrow ...** + Add units from the source burrows to the target. + **remove-units target-burrow src-burrow ...** + Remove units in source burrows from the target. + **set-tiles target-burrow src-burrow ...** + Clear target and adds tiles from the source burrows. + **add-tiles target-burrow src-burrow ...** + Add tiles from the source burrows to the target. + **remove-tiles target-burrow src-burrow ...** + Remove tiles in source burrows from the target. + + For these three options, in place of a source burrow it is + possible to use one of the following keywords: ABOVE_GROUND, + SUBTERRANEAN, INSIDE, OUTSIDE, LIGHT, DARK, HIDDEN, REVEALED + +Features: + + :auto-grow: When a wall inside a burrow with a name ending in '+' is dug + out, the burrow is extended to newly-revealed adjacent walls. + This final '+' may be omitted in burrow name args of commands above. + Digging 1-wide corridors with the miner inside the burrow is SLOW. -copystock -========== -Copies the parameters of the currently highlighted stockpile to the custom -stockpile settings and switches to custom stockpile placement mode, effectively -allowing you to copy/paste stockpiles easily. +digv +---- +Designates a whole vein for digging. Requires an active in-game cursor placed +over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs +between the same-material tiles). -ssense / stonesense -=================== -An isometric visualizer that runs in a second window. This requires working -graphics acceleration and at least a dual core CPU (otherwise it will slow -down DF). +digvx +----- +A permanent alias for 'digv x'. -All the data resides in the 'stonesense' directory. For detailed instructions, -see stonesense/README.txt +digl +---- +Designates layer stone for digging. Requires an active in-game cursor placed +over a layer stone tile. With the 'x' option, it will traverse z-levels +(putting stairs between the same-material tiles). With the 'undo' option it +will remove the dig designation instead (if you realize that digging out a 50 +z-level deep layer was not such a good idea after all). -Compatible with Windows > XP SP3 and most modern Linux distributions. +diglx +----- +A permanent alias for 'digl x'. -Older versions, support and extra graphics can be found in the bay12 forum -thread: http://www.bay12forums.com/smf/index.php?topic=43260.0 +digexp +------ +This command can be used for exploratory mining. -Some additional resources: -http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository +See: http://df.magmawiki.com/index.php/DF2010:Exploratory_mining -tiletypes -========= -Can be used for painting map tiles and is an interactive command, much like -liquids. +There are two variables that can be set: pattern and filter. -The tool works with two set of options and a brush. The brush determines which -tiles will be processed. First set of options is the filter, which can exclude -some of the tiles from the brush by looking at the tile properties. The second -set of options is the paint - this determines how the selected tiles are -changed. +Patterns: -Both paint and filter can have many different properties including things like -general shape (WALL, FLOOR, etc.), general material (SOIL, STONE, MINERAL, -etc.), state of 'designated', 'hidden' and 'light' flags. + :diag5: diagonals separated by 5 tiles + :diag5r: diag5 rotated 90 degrees + :ladder: A 'ladder' pattern + :ladderr: ladder rotated 90 degrees + :clear: Just remove all dig designations + :cross: A cross, exactly in the middle of the map. -The properties of filter and paint can be partially defined. This means that -you can for example do something like this: +Filters: -:: + :all: designate whole z-level + :hidden: designate only hidden tiles of z-level (default) + :designated: Take current designation and apply pattern to it. - filter material STONE - filter shape FORTIFICATION - paint shape FLOOR +After you have a pattern set, you can use 'expdig' to apply it again. -This will turn all stone fortifications into floors, preserving the material. +Examples: -Or this: -:: + designate the diagonal 5 patter over all hidden tiles: + * expdig diag5 hidden + apply last used pattern and filter: + * expdig + Take current designations and replace them with the ladder pattern: + * expdig ladder designated - filter shape FLOOR - filter material MINERAL - paint shape WALL +digcircle +--------- +A command for easy designation of filled and hollow circles. +It has several types of options. -Turning mineral vein floors back into walls. +Shape: -The tool also allows tweaking some tile flags: + :hollow: Set the circle to hollow (default) + :filled: Set the circle to filled + :#: Diameter in tiles (default = 0, does nothing) -Or this: +Action: -:: + :set: Set designation (default) + :unset: Unset current designation + :invert: Invert designations already present - paint hidden 1 - paint hidden 0 +Designation types: -This will hide previously revealed tiles (or show hidden with the 0 option). + :dig: Normal digging designation (default) + :ramp: Ramp digging + :ustair: Staircase up + :dstair: Staircase down + :xstair: Staircase up/down + :chan: Dig channel -Any paint or filter option (or the entire paint or filter) can be disabled entirely by using the ANY keyword: +After you have set the options, the command called with no options +repeats with the last selected parameters. -:: +Examples: - paint hidden ANY - paint shape ANY - filter material any - filter shape any - filter any +* 'digcircle filled 3' = Dig a filled circle with radius = 3. +* 'digcircle' = Do it again. -You can use several different brushes for painting tiles: - * Point. (point) - * Rectangular range. (range) - * A column ranging from current cursor to the first solid tile above. (column) - * DF map block - 16x16 tiles, in a regular grid. (block) + +filltraffic +----------- +Set traffic designations using flood-fill starting at the cursor. + +Traffic Type Codes: + + :H: High Traffic + :N: Normal Traffic + :L: Low Traffic + :R: Restricted Traffic + +Other Options: + + :X: Fill accross z-levels. + :B: Include buildings and stockpiles. + :P: Include empty space. Example: -:: + 'filltraffic H' - When used in a room with doors, it will set traffic to HIGH in just that room. + +alltraffic +---------- +Set traffic designations for every single tile of the map (useful for resetting traffic designations). + +Traffic Type Codes: + + :H: High Traffic + :N: Normal Traffic + :L: Low Traffic + :R: Restricted Traffic + +Example: + + 'alltraffic N' - Set traffic to 'normal' for all tiles. + +getplants +--------- +This tool allows plant gathering and tree cutting by RAW ID. Specify the types +of trees to cut down and/or shrubs to gather by their plant names, separated +by spaces. + +Options: + + :-t: Select trees only (exclude shrubs) + :-s: Select shrubs only (exclude trees) + :-c: Clear designations instead of setting them + :-x: Apply selected action to all plants except those specified (invert + selection) + +Specifying both -t and -s will have no effect. If no plant IDs are specified, +all valid plant IDs will be listed. + + +Cleanup and garbage disposal +============================ + +clean +----- +Cleans all the splatter that get scattered all over the map, items and +creatures. In an old fortress, this can significantly reduce FPS lag. It can +also spoil your !!FUN!!, so think before you use it. + +Options: + + :map: Clean the map tiles. By default, it leaves mud and snow alone. + :units: Clean the creatures. Will also clean hostiles. + :items: Clean all the items. Even a poisoned blade. + +Extra options for 'map': + + :mud: Remove mud in addition to the normal stuff. + :snow: Also remove snow coverings. + +spotclean +--------- +Works like 'clean map snow mud', but only for the tile under the cursor. Ideal +if you want to keep that bloody entrance 'clean map' would clean up. + +autodump +-------- +This utility lets you quickly move all items designated to be dumped. +Items are instantly moved to the cursor position, the dump flag is unset, +and the forbid flag is set, as if it had been dumped normally. +Be aware that any active dump item tasks still point at the item. + +Cursor must be placed on a floor tile so the items can be dumped there. + +Options: + + :destroy: Destroy instead of dumping. Doesn't require a cursor. + :destroy-here: Destroy items only under the cursor. + :visible: Only process items that are not hidden. + :hidden: Only process hidden items. + :forbidden: Only process forbidden items (default: only unforbidden). + +autodump-destroy-here +--------------------- +Destroy items marked for dumping under cursor. Identical to autodump +destroy-here, but intended for use as keybinding. + +autodump-destroy-item +--------------------- +Destroy the selected item. The item may be selected in the 'k' list, or inside +a container. If called again before the game is resumed, cancels destroy. + +cleanowned +---------- +Confiscates items owned by dwarfs. By default, owned food on the floor +and rotten items are confistacted and dumped. + +Options: + + :all: confiscate all owned items + :scattered: confiscated and dump all items scattered on the floor + :x: confiscate/dump items with wear level 'x' and more + :X: confiscate/dump items with wear level 'X' and more + :dryrun: a dry run. combine with other options to see what will happen + without it actually happening. + +Example: + + ``cleanowned scattered X`` + This will confiscate rotten and dropped food, garbage on the floors and any + worn items with 'X' damage and above. - range 10 10 1 -This will change the brush to a rectangle spanning 10x10 tiles on one z-level. -The range starts at the position of the cursor and goes to the east, south and -up. -For more details, see the 'help' command while using this. +Bugfixes +======== -tiletypes-commands -================== -Runs tiletypes commands, separated by ;. This makes it possible to change -tiletypes modes from a hotkey. +drybuckets +---------- +This utility removes water from all buckets in your fortress, allowing them to be safely used for making lye. -tiletypes-here -============== -Apply the current tiletypes options at the in-game cursor position, including -the brush. Can be used from a hotkey. +fixdiplomats +------------ +Up to version 0.31.12, Elves only sent Diplomats to your fortress to propose +tree cutting quotas due to a bug; once that bug was fixed, Elves stopped caring +about excess tree cutting. This command adds a Diplomat position to all Elven +civilizations, allowing them to negotiate tree cutting quotas (and allowing you +to violate them and potentially start wars) in case you haven't already modified +your raws accordingly. -tiletypes-here-point -==================== -Apply the current tiletypes options at the in-game cursor position to a single -tile. Can be used from a hotkey. +fixmerchants +------------ +This command adds the Guild Representative position to all Human civilizations, +allowing them to make trade agreements (just as they did back in 0.28.181.40d +and earlier) in case you haven't already modified your raws accordingly. + +fixveins +-------- +Removes invalid references to mineral inclusions and restores missing ones. +Use this if you broke your embark with tools like tiletypes, or if you +accidentally placed a construction on top of a valuable mineral floor. tweak -===== +----- Contains various tweaks for minor bugs (currently just one). -Options -------- +Options: + :clear-missing: Remove the missing status from the selected unit. This allows engraving slabs for ghostly, but not yet found, creatures. @@ -944,136 +987,139 @@ Options the contents separately from the container. This forcefully skips child reagents. -tubefill -======== -Fills all the adamantine veins again. Veins that were empty will be filled in -too, but might still trigger a demon invasion (this is a known bug). -digv -==== -Designates a whole vein for digging. Requires an active in-game cursor placed -over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs -between the same-material tiles). +Mode switch and reclaim +======================= -digvx -===== -A permanent alias for 'digv x'. +lair +---- +This command allows you to mark the map as 'monster lair', preventing item +scatter on abandon. When invoked as 'lair reset', it does the opposite. -digl -==== -Designates layer stone for digging. Requires an active in-game cursor placed -over a layer stone tile. With the 'x' option, it will traverse z-levels -(putting stairs between the same-material tiles). With the 'undo' option it -will remove the dig designation instead (if you realize that digging out a 50 -z-level deep layer was not such a good idea after all). +Unlike reveal, this command doesn't save the information about tiles - you +won't be able to restore state of real monster lairs using 'lair reset'. -diglx -===== -A permanent alias for 'digl x'. +Options: -digexp -====== -This command can be used for exploratory mining. + :lair: Mark the map as monster lair + :lair reset: Mark the map as ordinary (not lair) -See: http://df.magmawiki.com/index.php/DF2010:Exploratory_mining +mode +---- +This command lets you see and change the game mode directly. +Not all combinations are good for every situation and most of them will +produce undesirable results. There are a few good ones though. -There are two variables that can be set: pattern and filter. +.. admonition:: Example -Patterns: ---------- -:diag5: diagonals separated by 5 tiles -:diag5r: diag5 rotated 90 degrees -:ladder: A 'ladder' pattern -:ladderr: ladder rotated 90 degrees -:clear: Just remove all dig designations -:cross: A cross, exactly in the middle of the map. + You are in fort game mode, managing your fortress and paused. + You switch to the arena game mode, *assume control of a creature* and then + switch to adventure game mode(1). + You just lost a fortress and gained an adventurer. + You could also do this. + You are in fort game mode, managing your fortress and paused at the esc menu. + You switch to the adventure game mode, then use Dfusion to *assume control of a creature* and then + save or retire. + You just created a returnable mountain home and gained an adventurer. -Filters: --------- -:all: designate whole z-level -:hidden: designate only hidden tiles of z-level (default) -:designated: Take current designation and apply pattern to it. -After you have a pattern set, you can use 'expdig' to apply it again. +I take no responsibility of anything that happens as a result of using this tool -Examples: ---------- -designate the diagonal 5 patter over all hidden tiles: - * expdig diag5 hidden -apply last used pattern and filter: - * expdig -Take current designations and replace them with the ladder pattern: - * expdig ladder designated -digcircle -========= -A command for easy designation of filled and hollow circles. -It has several types of options. +Visualizer and data export +========================== -Shape: --------- -:hollow: Set the circle to hollow (default) -:filled: Set the circle to filled -:#: Diameter in tiles (default = 0, does nothing) +ssense / stonesense +------------------- +An isometric visualizer that runs in a second window. This requires working +graphics acceleration and at least a dual core CPU (otherwise it will slow +down DF). -Action: -------- -:set: Set designation (default) -:unset: Unset current designation -:invert: Invert designations already present +All the data resides in the 'stonesense' directory. For detailed instructions, +see stonesense/README.txt -Designation types: ------------------- -:dig: Normal digging designation (default) -:ramp: Ramp digging -:ustair: Staircase up -:dstair: Staircase down -:xstair: Staircase up/down -:chan: Dig channel +Compatible with Windows > XP SP3 and most modern Linux distributions. -After you have set the options, the command called with no options -repeats with the last selected parameters. +Older versions, support and extra graphics can be found in the bay12 forum +thread: http://www.bay12forums.com/smf/index.php?topic=43260.0 -Examples: +Some additional resources: +http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository + +mapexport --------- -* 'digcircle filled 3' = Dig a filled circle with radius = 3. -* 'digcircle' = Do it again. +Export the current loaded map as a file. This will be eventually usable +with visualizers. -weather -======= -Prints the current weather map by default. +dwarfexport +----------= +Export dwarves to RuneSmith-compatible XML. -Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'. + +Job management +============== + +job +--- +Command for general job query and manipulation. Options: --------- -:snow: make it snow everywhere. -:rain: make it rain. -:clear: clear the sky. + *no extra options* + Print details of the current job. The job can be selected + in a workshop, or the unit/jobs screen. + **list** + Print details of all jobs in the selected workshop. + **item-material ** + Replace the exact material id in the job item. + **item-type ** + Replace the exact item type id in the job item. + +job-material +------------ +Alter the material of the selected job. + +Invoked as:: + + job-material + +Intended to be used as a keybinding: + + * In 'q' mode, when a job is highlighted within a workshop or furnace, + changes the material of the job. Only inorganic materials can be used + in this mode. + * In 'b' mode, during selection of building components positions the cursor + over the first available choice with the matching material. + +job-duplicate +------------- +Duplicate the selected job in a workshop: + * In 'q' mode, when a job is highlighted within a workshop or furnace building, + instantly duplicates the job. workflow -======== +-------- Manage control of repeat jobs. -Usage ------ -``workflow enable [option...], workflow disable [option...]`` +Usage: + + ``workflow enable [option...], workflow disable [option...]`` If no options are specified, enables or disables the plugin. Otherwise, enables or disables any of the following options: - drybuckets: Automatically empty abandoned water buckets. - auto-melt: Resume melt jobs when there are objects to melt. -``workflow jobs`` + ``workflow jobs`` List workflow-controlled jobs (if in a workshop, filtered by it). -``workflow list`` + ``workflow list`` List active constraints, and their job counts. -``workflow count [cnt-gap], workflow amount [cnt-gap]`` + ``workflow count [cnt-gap], workflow amount [cnt-gap]`` Set a constraint. The first form counts each stack as only 1 item. -``workflow unlimit `` + ``workflow unlimit `` Delete a constraint. Function --------- +........ + When the plugin is enabled, it protects all repeat jobs from removal. If they do disappear due to any cause, they are immediately re-added to their workshop and suspended. @@ -1086,7 +1132,8 @@ the frequency of jobs being toggled. Constraint examples -------------------- +................... + Keep metal bolts within 900-1000, and wood/bone within 150-200. :: @@ -1127,73 +1174,76 @@ Make sure there are always 80-100 units of dimple dye. on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material' command. -mapexport -========= -Export the current loaded map as a file. This will be eventually usable -with visualizers. -dwarfexport -=========== -Export dwarves to RuneSmith-compatible XML. +Fortress activity management +============================ + +seedwatch +--------- +Tool for turning cooking of seeds and plants on/off depending on how much you +have of them. + +See 'seedwatch help' for detailed description. zone -==== +---- Helps a bit with managing activity zones (pens, pastures and pits) and cages. Options: --------- -:set: Set zone or cage under cursor as default for future assigns. -:assign: Assign unit(s) to the pen or pit marked with the 'set' command. + + :set: Set zone or cage under cursor as default for future assigns. + :assign: Assign unit(s) to the pen or pit marked with the 'set' command. If no filters are set a unit must be selected in the in-game ui. Can also be followed by a valid zone id which will be set instead. -:unassign: Unassign selected creature from it's zone. -:nick: Mass-assign nicknames, must be followed by the name you want + :unassign: Unassign selected creature from it's zone. + :nick: Mass-assign nicknames, must be followed by the name you want to set. -:remnick: Mass-remove nicknames. -:tocages: Assign unit(s) to cages inside a pasture. -:uinfo: Print info about unit(s). If no filters are set a unit must + :remnick: Mass-remove nicknames. + :tocages: Assign unit(s) to cages inside a pasture. + :uinfo: Print info about unit(s). If no filters are set a unit must be selected in the in-game ui. -:zinfo: Print info about zone(s). If no filters are set zones under + :zinfo: Print info about zone(s). If no filters are set zones under the cursor are listed. -:verbose: Print some more info. -:filters: Print list of valid filter options. -:examples: Print some usage examples. -:not: Negates the next filter keyword. + :verbose: Print some more info. + :filters: Print list of valid filter options. + :examples: Print some usage examples. + :not: Negates the next filter keyword. Filters: --------- -:all: Process all units (to be used with additional filters). -:count: Must be followed by a number. Process only n units (to be used - with additional filters). -:unassigned: Not assigned to zone, chain or built cage. -:minage: Minimum age. Must be followed by number. -:maxage: Maximum age. Must be followed by number. -:race: Must be followed by a race RAW ID (e.g. BIRD_TURKEY, ALPACA, - etc). Negatable. -:caged: In a built cage. Negatable. -:own: From own civilization. Negatable. -:merchant: Is a merchant / belongs to a merchant. Should only be used for - pitting, not for stealing animals (slaughter should work). -:war: Trained war creature. Negatable. -:hunting: Trained hunting creature. Negatable. -:tamed: Creature is tame. Negatable. -:trained: Creature is trained. Finds war/hunting creatures as well as - creatures who have a training level greater than 'domesticated'. - If you want to specifically search for war/hunting creatures use - 'war' or 'hunting' Negatable. -:trainablewar: Creature can be trained for war (and is not already trained for - war/hunt). Negatable. -:trainablehunt: Creature can be trained for hunting (and is not already trained - for war/hunt). Negatable. -:male: Creature is male. Negatable. -:female: Creature is female. Negatable. -:egglayer: Race lays eggs. Negatable. -:grazer: Race is a grazer. Negatable. -:milkable: Race is milkable. Negatable. + + :all: Process all units (to be used with additional filters). + :count: Must be followed by a number. Process only n units (to be used + with additional filters). + :unassigned: Not assigned to zone, chain or built cage. + :minage: Minimum age. Must be followed by number. + :maxage: Maximum age. Must be followed by number. + :race: Must be followed by a race RAW ID (e.g. BIRD_TURKEY, ALPACA, + etc). Negatable. + :caged: In a built cage. Negatable. + :own: From own civilization. Negatable. + :merchant: Is a merchant / belongs to a merchant. Should only be used for + pitting, not for stealing animals (slaughter should work). + :war: Trained war creature. Negatable. + :hunting: Trained hunting creature. Negatable. + :tamed: Creature is tame. Negatable. + :trained: Creature is trained. Finds war/hunting creatures as well as + creatures who have a training level greater than 'domesticated'. + If you want to specifically search for war/hunting creatures use + 'war' or 'hunting' Negatable. + :trainablewar: Creature can be trained for war (and is not already trained for + war/hunt). Negatable. + :trainablehunt: Creature can be trained for hunting (and is not already trained + for war/hunt). Negatable. + :male: Creature is male. Negatable. + :female: Creature is female. Negatable. + :egglayer: Race lays eggs. Negatable. + :grazer: Race is a grazer. Negatable. + :milkable: Race is milkable. Negatable. Usage with single units ------------------------ +....................... + One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units @@ -1202,7 +1252,8 @@ and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way. Usage with filters ------------------- +.................. + All filters can be used together with the 'assign' command. Restrictions: It's not possible to assign units who are inside built cages @@ -1223,14 +1274,16 @@ are not properly added to your own stocks; slaughtering them should work). Most filters can be negated (e.g. 'not grazer' -> race is not a grazer). Mass-renaming -------------- +............. + Using the 'nick' command you can set the same nickname for multiple units. If used without 'assign', 'all' or 'count' it will rename all units in the current default target zone. Combined with 'assign', 'all' or 'count' (and further optional filters) it will rename units matching the filter conditions. Cage zones ----------- +.......... + Using the 'tocages' command you can assign units to a set of cages, for example a room next to your butcher shop(s). They will be spread evenly among available cages to optimize hauling to and butchering from them. For this to work you need @@ -1241,7 +1294,8 @@ would make no sense, but can be used together with 'nick' or 'remnick' and all the usual filters. Examples --------- +........ + ``zone assign all own ALPACA minage 3 maxage 10`` Assign all own alpacas who are between 3 and 10 years old to the selected pasture. @@ -1264,7 +1318,7 @@ Examples on the current default zone. autonestbox -=========== +----------- Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless @@ -1276,15 +1330,15 @@ nestbox zones when they revert to wild. When called without options autonestbox will instantly run once. Options: --------- -:start: Start running every X frames (df simulation ticks). - Default: X=6000, which would be every 60 seconds at 100fps. -:stop: Stop running automatically. -:sleep: Must be followed by number X. Changes the timer to sleep X - frames between runs. + + :start: Start running every X frames (df simulation ticks). + Default: X=6000, which would be every 60 seconds at 100fps. + :stop: Stop running automatically. + :sleep: Must be followed by number X. Changes the timer to sleep X + frames between runs. autobutcher -=========== +----------- Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units will be processed. @@ -1303,41 +1357,41 @@ If you don't set any target count the following default will be used: 1 male kid, 5 female kids, 1 male adult, 5 female adults. Options: --------- -:start: Start running every X frames (df simulation ticks). - Default: X=6000, which would be every 60 seconds at 100fps. -:stop: Stop running automatically. -:sleep: Must be followed by number X. Changes the timer to sleep - X frames between runs. -:watch R: Start watching a race. R can be a valid race RAW id (ALPACA, - BIRD_TURKEY, etc) or a list of ids seperated by spaces or - the keyword 'all' which affects all races on your current - watchlist. -:unwatch R: Stop watching race(s). The current target settings will be - remembered. R can be a list of ids or the keyword 'all'. -:forget R: Stop watching race(s) and forget it's/their target settings. - R can be a list of ids or the keyword 'all'. -:autowatch: Automatically adds all new races (animals you buy from merchants, - tame yourself or get from migrants) to the watch list using - default target count. -:noautowatch: Stop auto-adding new races to the watchlist. -:list: Print the current status and watchlist. -:list_export: Print status and watchlist in a format which can be used - to import them to another savegame (see notes). -:target fk mk fa ma R: Set target count for specified race(s). - fk = number of female kids, - mk = number of male kids, - fa = number of female adults, - ma = number of female adults. - R can be a list of ids or the keyword 'all' or 'new'. - R = 'all': change target count for all races on watchlist - and set the new default for the future. R = 'new': don't touch - current settings on the watchlist, only set the new default - for future entries. -:example: Print some usage examples. + + :start: Start running every X frames (df simulation ticks). + Default: X=6000, which would be every 60 seconds at 100fps. + :stop: Stop running automatically. + :sleep: Must be followed by number X. Changes the timer to sleep + X frames between runs. + :watch R: Start watching a race. R can be a valid race RAW id (ALPACA, + BIRD_TURKEY, etc) or a list of ids seperated by spaces or + the keyword 'all' which affects all races on your current + watchlist. + :unwatch R: Stop watching race(s). The current target settings will be + remembered. R can be a list of ids or the keyword 'all'. + :forget R: Stop watching race(s) and forget it's/their target settings. + R can be a list of ids or the keyword 'all'. + :autowatch: Automatically adds all new races (animals you buy from merchants, + tame yourself or get from migrants) to the watch list using + default target count. + :noautowatch: Stop auto-adding new races to the watchlist. + :list: Print the current status and watchlist. + :list_export: Print status and watchlist in a format which can be used + to import them to another savegame (see notes). + :target fk mk fa ma R: Set target count for specified race(s). + fk = number of female kids, + mk = number of male kids, + fa = number of female adults, + ma = number of female adults. + R can be a list of ids or the keyword 'all' or 'new'. + R = 'all': change target count for all races on watchlist + and set the new default for the future. R = 'new': don't touch + current settings on the watchlist, only set the new default + for future entries. + :example: Print some usage examples. Examples: ---------- + You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, 1 male) of the race alpaca. Once the kids grow up the oldest adults will get slaughtered. Excess kids will get slaughtered starting with the youngest @@ -1369,8 +1423,8 @@ add some new races with 'watch'. If you simply want to stop it completely use autobutcher unwatch ALPACA CAT -Note: ------ +**Note:** + Settings and watchlist are stored in the savegame, so that you can have different settings for each world. If you want to copy your watchlist to another savegame you can use the command list_export: @@ -1384,7 +1438,7 @@ another savegame you can use the command list_export: autolabor -========= +--------- Automatically manage dwarf labors. When enabled, autolabor periodically checks your dwarves and enables or @@ -1398,6 +1452,30 @@ also tries to have dwarves specialize in specific skills. For detailed usage information, see 'help autolabor'. +Other +===== + +catsplosion +----------- +Makes cats just *multiply*. It is not a good idea to run this more than once or +twice. + +dfusion +------- +This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin. + +See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15 + +Confirmed working DFusion plugins: + +:simple_embark: allows changing the number of dwarves available on embark. + +.. note:: + + * Some of the DFusion plugins aren't completely ported yet. This can lead to crashes. + * This is currently working only on Windows. + * The game will be suspended while you're using dfusion. Don't panic when it doen't respond. + ======= Scripts @@ -1413,19 +1491,6 @@ scripts that are obscure, developer-oriented, or should be used as keybindings. Some notable scripts: -quicksave -========= - -If called in dwarf mode, makes DF immediately auto-save the game by setting a flag -normally used in seasonal auto-save. - -setfps -====== - -Run ``setfps `` to set the FPS cap at runtime, in case you want to watch -combat in slow motion or something :) - - fix/* ===== @@ -1462,6 +1527,17 @@ gui/* Scripts that implement dialogs inserted into the main game window are put in this directory. +quicksave +========= + +If called in dwarf mode, makes DF immediately auto-save the game by setting a flag +normally used in seasonal auto-save. + +setfps +====== + +Run ``setfps `` to set the FPS cap at runtime, in case you want to watch +combat in slow motion or something :) growcrops ========= @@ -1684,10 +1760,12 @@ Exiting from the siege engine script via ESC reverts the view to the state prior the script. Shift-ESC retains the current viewport, and also exits from the 'q' mode to main menu. -**DISCLAIMER**: Siege engines are a very interesting feature, but currently nearly useless -because they haven't been updated since 2D and can only aim in four directions. This is an -attempt to bring them more up to date until Toady has time to work on it. Actual improvements, -e.g. like making siegers bring their own, are something only Toady can do. +.. admonition:: DISCLAIMER + + Siege engines are a very interesting feature, but currently nearly useless + because they haven't been updated since 2D and can only aim in four directions. This is an + attempt to bring them more up to date until Toady has time to work on it. Actual improvements, + e.g. like making siegers bring their own, are something only Toady can do. ========= @@ -1723,20 +1801,24 @@ The workshop needs water as its input, which it takes via a passable floor tile below it, like usual magma workshops do. The magma version also needs magma. -**ISSUE**: Since this building is a machine, and machine collapse -code cannot be modified, it would collapse over true open space. -As a loophole, down stair provides support to machines, while -being passable, so use them. +.. admonition:: ISSUE + + Since this building is a machine, and machine collapse + code cannot be modified, it would collapse over true open space. + As a loophole, down stair provides support to machines, while + being passable, so use them. After constructing the building itself, machines can be connected to the edge tiles that look like gear boxes. Their exact position is extracted from the workshop raws. -**ISSUE**: Like with collapse above, part of the code involved in -machine connection cannot be modified. As a result, the workshop -can only immediately connect to machine components built AFTER it. -This also means that engines cannot be chained without intermediate -short axles that can be built later than both of the engines. +.. admonition:: ISSUE + + Like with collapse above, part of the code involved in + machine connection cannot be modified. As a result, the workshop + can only immediately connect to machine components built AFTER it. + This also means that engines cannot be chained without intermediate + short axles that can be built later than both of the engines. Operation --------- diff --git a/Readme.html b/Readme.html index 977c4177d..be6856fb6 100644 --- a/Readme.html +++ b/Readme.html @@ -318,7 +318,7 @@ ul.auto-toc {

    DFHack Readme

    -

    Introduction

    +

    Introduction

    DFHack is a Dwarf Fortress memory access library and a set of basic tools that use it. Tools come in the form of plugins or (not yet) external tools. It is an attempt to unite the various ways tools @@ -326,250 +326,192 @@ access DF memory and allow for easier development of new tools.

    Contents

    -

    Getting DFHack

    +

    Getting DFHack

    The project is currently hosted on github, for both source and binaries at http://github.com/peterix/dfhack

    Releases can be downloaded from here: https://github.com/peterix/dfhack/downloads

    All new releases are announced in the bay12 thread: http://tinyurl.com/dfhack-ng

    -

    Compatibility

    +

    Compatibility

    DFHack works on Windows XP, Vista, 7 or any modern Linux distribution. OSX is not supported due to lack of developers with a Mac.

    Currently, versions 0.34.08 - 0.34.11 are supported. If you need DFHack @@ -578,7 +520,7 @@ for older versions, look for older releases.

    It is possible to use the Windows DFHack under wine/OSX.

    -

    Installation/Removal

    +

    Installation/Removal

    Installing DFhack involves copying files into your DF folder. Copy the files from a release archive so that:

    @@ -600,7 +542,7 @@ remove the other DFHack files file created in your DF folder.

    -

    Using DFHack

    +

    Using DFHack

    DFHack basically extends what DF can do with something similar to the drop-down console found in Quake engine games. On Windows, this is a separate command line window. On linux, the terminal used to launch the dfhack script is taken over @@ -624,7 +566,7 @@ they are re-created every time it is loaded.

    Most of the commands come from plugins. Those reside in 'hack/plugins/'.

    -

    Something doesn't work, help!

    +

    Something doesn't work, help!

    First, don't panic :) Second, dfhack keeps a few log files in DF's folder - stderr.log and stdout.log. You can look at those and possibly find out what's happening. @@ -633,13 +575,13 @@ the issues tracker on github, contact me ( -

    The init file

    +

    The init file

    If your DF folder contains a file named dfhack.init, its contents will be run every time you start DF. This allows setting up keybindings. An example file is provided as dfhack.init-example - you can tweak it and rename to dfhack.init if you want to use this functionality.

    -

    Setting keybindings

    +

    Setting keybindings

    To set keybindings, use the built-in keybinding command. Like any other command it can be used at any time from the console, but it is also meaningful in the DFHack init file.

    @@ -684,17 +626,107 @@ for context foo/bar/baz, possible matches are
    -

    Commands

    +

    Commands

    Almost all the commands support using the 'help <command-name>' built-in command to retrieve further help without having to look at this document. Alternatively, some accept a 'help'/'?' option on their command line.

    +
    +

    Game progress

    +
    +

    die

    +

    Instantly kills DF without saving.

    +
    +
    +

    forcepause

    +

    Forces DF to pause. This is useful when your FPS drops below 1 and you lose +control of the game.

    +
    +
      +
    • Activate with 'forcepause 1'
    • +
    • Deactivate with 'forcepause 0'
    • +
    +
    +
    +
    +

    nopause

    +

    Disables pausing (both manual and automatic) with the exception of pause forced +by 'reveal hell'. This is nice for digging under rivers.

    +
    +
    +

    fastdwarf

    +

    Makes your minions move at ludicrous speeds.

    +
    +
      +
    • Activate with 'fastdwarf 1'
    • +
    • Deactivate with 'fastdwarf 0'
    • +
    +
    +
    +
    +
    +

    Game interface

    +
    +

    follow

    +

    Makes the game view follow the currently highlighted unit after you exit from +current menu/cursor mode. Handy for watching dwarves running around. Deactivated +by moving the view manually.

    +
    +
    +

    tidlers

    +

    Toggle between all possible positions where the idlers count can be placed.

    +
    +
    +

    twaterlvl

    +

    Toggle between displaying/not displaying liquid depth as numbers.

    +
    +
    +

    copystock

    +

    Copies the parameters of the currently highlighted stockpile to the custom +stockpile settings and switches to custom stockpile placement mode, effectively +allowing you to copy/paste stockpiles easily.

    +
    +
    +

    rename

    +

    Allows renaming various things.

    +

    Options:

    +
    + +++ + + + + + + + + + + + + + + + + +
    rename squad <index> "name":
     Rename squad by index to 'name'.
    rename hotkey <index> "name":
     Rename hotkey by index. This allows assigning +longer commands to the DF hotkeys.
    rename unit "nickname":
     Rename a unit/creature highlighted in the DF user +interface.
    rename unit-profession "custom profession":
     Change proffession name of the +highlighted unit/creature.
    rename building "name":
     Set a custom name for the selected building. +The building must be one of stockpile, workshop, furnace, trap, +siege engine or an activity zone.
    +
    +
    +
    +
    +

    Adventure mode

    -

    adv-bodyswap

    +

    adv-bodyswap

    This allows taking control over your followers and other creatures in adventure mode. For example, you can make them pick up new arms and armor and equip them properly.

    -
    -

    Usage

    +

    Usage:

    • When viewing unit details, body-swaps into that unit.
    • @@ -702,12 +734,11 @@ properly.

    -
    -

    advtools

    +

    advtools

    A package of different adventure mode tools (currently just one)

    -
    -

    Usage

    +

    Usage:

    +
    @@ -723,10 +754,13 @@ on item type and being in shop.
    +
    +
    +

    Map modification

    -

    changelayer

    +

    changelayer

    Changes material of the geology layer under cursor to the specified inorganic RAW material. Can have impact on all surrounding regions, not only your embark! By default changing stone to soil and vice versa is not allowed. By default @@ -737,8 +771,8 @@ as well, though. Mineral veins and gem clusters will stay on the map. Use 'changevein' for them.

    tl;dr: You will end up with changing quite big areas in one go, especially if you use it in lower z levels. Use with care.

    -
    -

    Options

    +

    Options:

    +
    @@ -770,9 +804,9 @@ soil.
    -
    -
    -

    Examples:

    + +

    Examples:

    +
    changelayer GRANITE
    Convert layer at cursor position into granite.
    @@ -781,6 +815,7 @@ soil.
    changelayer MARBLE all_biomes all_layers
    Convert all layers of all biomes which are not soil into marble.
    +

    Note

      @@ -799,22 +834,21 @@ You did save your game, right?
    -
    -

    changevein

    +

    changevein

    Changes material of the vein under cursor to the specified inorganic RAW material. Only affects tiles within the current 16x16 block - for veins and large clusters, you will need to use this command multiple times.

    -
    -

    Example:

    +

    Example:

    +
    changevein NATIVE_PLATINUM
    Convert vein at cursor position into platinum ore.
    -
    +
    -

    changeitem

    +

    changeitem

    Allows changing item material and base quality. By default the item currently selected in the UI will be changed (you can select items in the 'k' list or inside containers/inventory). By default change is only allowed if materials @@ -824,8 +858,8 @@ with 'force'. Note that some attributes will not be touched, possibly resulting in weirdness. To get an idea how the RAW id should look like, check some items with 'info'. Using 'force' might create items which are not touched by crafters/haulers.

    -
    -

    Options

    +

    Options:

    +
    @@ -842,749 +876,83 @@ crafters/haulers.

    -
    -
    -

    Examples:

    + +

    Examples:

    +
    changeitem m INORGANIC:GRANITE here
    Change material of all items under the cursor to granite.
    changeitem q 5
    Change currently selected item to masterpiece quality.
    +
    -
    -
    -

    cursecheck

    -

    Checks a single map tile or the whole map/world for cursed creatures (ghosts, -vampires, necromancers, werebeasts, zombies).

    -

    With an active in-game cursor only the selected tile will be observed. -Without a cursor the whole map will be checked.

    -

    By default cursed creatures will be only counted in case you just want to find -out if you have any of them running around in your fort. Dead and passive -creatures (ghosts who were put to rest, killed vampires, ...) are ignored. -Undead skeletons, corpses, bodyparts and the like are all thrown into the curse -category "zombie". Anonymous zombies and resurrected body parts will show -as "unnamed creature".

    -
    -

    Options

    +
    +

    colonies

    +

    Allows listing all the vermin colonies on the map and optionally turning them into honey bee colonies.

    +

    Options:

    +
    - - - - - - - +
    detail:Print full name, date of birth, date of curse and some status -info (some vampires might use fake identities in-game, though).
    nick:Set the type of curse as nickname (does not always show up -in-game, some vamps don't like nicknames).
    all:Include dead and passive cursed creatures (can result in a quite -long list after having FUN with necromancers).
    verbose:Print all curse tags (if you really want to know it all).
    bees:turn colonies into honey bee colonies
    +
    -
    -

    Examples:

    -
    -
    cursecheck detail all
    -
    Give detailed info about all cursed creatures including deceased ones (no -in-game cursor).
    -
    cursecheck nick
    -
    Give a nickname all living/active cursed creatures on the map(no in-game -cursor).
    -
    -
    -

    Note

    -
      -
    • If you do a full search (with the option "all") former ghosts will show up -with the cursetype "unknown" because their ghostly flag is not set -anymore. But if you happen to find a living/active creature with cursetype -"unknown" please report that in the dfhack thread on the modding forum or -per irc. This is likely to happen with mods which introduce new types -of curses, for example.
    • -
    -
    -
    -
    -
    -

    follow

    -

    Makes the game view follow the currently highlighted unit after you exit from -current menu/cursor mode. Handy for watching dwarves running around. Deactivated -by moving the view manually.

    +
    +

    deramp (by zilpin)

    +

    Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation. +It also removes any and all 'down ramps' that can remain after a cave-in (you don't have to designate anything for that to happen).

    -
    -

    forcepause

    -

    Forces DF to pause. This is useful when your FPS drops below 1 and you lose -control of the game.

    -
    +
    +

    feature

    +

    Enables management of map features.

      -
    • Activate with 'forcepause 1'
    • -
    • Deactivate with 'forcepause 0'
    • +
    • Discovering a magma feature (magma pool, volcano, magma sea, or curious +underground structure) permits magma workshops and furnaces to be built.
    • +
    • Discovering a cavern layer causes plants (trees, shrubs, and grass) from +that cavern to grow within your fortress.
    -
    -
    -
    -

    nopause

    -

    Disables pausing (both manual and automatic) with the exception of pause forced -by 'reveal hell'. This is nice for digging under rivers.

    -
    -
    -

    die

    -

    Instantly kills DF without saving.

    -
    -
    -

    autodump

    -

    This utility lets you quickly move all items designated to be dumped. -Items are instantly moved to the cursor position, the dump flag is unset, -and the forbid flag is set, as if it had been dumped normally. -Be aware that any active dump item tasks still point at the item.

    -

    Cursor must be placed on a floor tile so the items can be dumped there.

    -
    -

    Options

    +

    Options:

    +
    - - - - - + - + - +
    destroy:Destroy instead of dumping. Doesn't require a cursor.
    destroy-here:Destroy items only under the cursor.
    visible:Only process items that are not hidden.
    list:Lists all map features in your current embark by index.
    hidden:Only process hidden items.
    show X:Marks the selected map feature as discovered.
    forbidden:Only process forbidden items (default: only unforbidden).
    hide X:Marks the selected map feature as undiscovered.
    +
    +
    +

    liquids

    +

    Allows adding magma, water and obsidian to the game. It replaces the normal +dfhack command line and can't be used from a hotkey. Settings will be remembered +as long as dfhack runs. Intended for use in combination with the command +liquids-here (which can be bound to a hotkey).

    +

    For more information, refer to the command's internal help.

    +
    +

    Note

    +

    Spawning and deleting liquids can F up pathing data and +temperatures (creating heat traps). You've been warned.

    -
    -

    autodump-destroy-here

    -

    Destroy items marked for dumping under cursor. Identical to autodump -destroy-here, but intended for use as keybinding.

    -
    -
    -

    autodump-destroy-item

    -

    Destroy the selected item. The item may be selected in the 'k' list, or inside -a container. If called again before the game is resumed, cancels destroy.

    -
    -

    burrow

    -

    Miscellaneous burrow control. Allows manipulating burrows and automated burrow -expansion while digging.

    -
    -

    Options

    - --- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    enable feature ...:
     
    disable feature ...:
     Enable or Disable features of the plugin.
    clear-unit burrow burrow ...:
     
    clear-tiles burrow burrow ...:
     Removes all units or tiles from the burrows.
    set-units target-burrow src-burrow ...:
     
    add-units target-burrow src-burrow ...:
     
    remove-units target-burrow src-burrow ...:
     Adds or removes units in source -burrows to/from the target burrow. Set is equivalent to clear and add.
    set-tiles target-burrow src-burrow ...:
     
    add-tiles target-burrow src-burrow ...:
     
    remove-tiles target-burrow src-burrow ...:
     Adds or removes tiles in source -burrows to/from the target burrow. In place of a source burrow it is -possible to use one of the following keywords: ABOVE_GROUND, -SUBTERRANEAN, INSIDE, OUTSIDE, LIGHT, DARK, HIDDEN, REVEALED
    -
    -
    -

    Features

    - --- - - - -
    auto-grow:When a wall inside a burrow with a name ending in '+' is dug -out, the burrow is extended to newly-revealed adjacent walls. -This final '+' may be omitted in burrow name args of commands above. -Digging 1-wide corridors with the miner inside the burrow is SLOW.
    -
    -
    -
    -

    catsplosion

    -

    Makes cats just multiply. It is not a good idea to run this more than once or -twice.

    -
    -
    -

    clean

    -

    Cleans all the splatter that get scattered all over the map, items and -creatures. In an old fortress, this can significantly reduce FPS lag. It can -also spoil your !!FUN!!, so think before you use it.

    -
    -

    Options

    - --- - - - - - - - -
    map:Clean the map tiles. By default, it leaves mud and snow alone.
    units:Clean the creatures. Will also clean hostiles.
    items:Clean all the items. Even a poisoned blade.
    -
    -
    -

    Extra options for 'map'

    - --- - - - - - -
    mud:Remove mud in addition to the normal stuff.
    snow:Also remove snow coverings.
    -
    -
    -
    -

    spotclean

    -

    Works like 'clean map snow mud', but only for the tile under the cursor. Ideal -if you want to keep that bloody entrance 'clean map' would clean up.

    -
    -
    -

    cleanowned

    -

    Confiscates items owned by dwarfs. By default, owned food on the floor -and rotten items are confistacted and dumped.

    -
    -

    Options

    - --- - - - - - - - - - - - -
    all:confiscate all owned items
    scattered:confiscated and dump all items scattered on the floor
    x:confiscate/dump items with wear level 'x' and more
    X:confiscate/dump items with wear level 'X' and more
    dryrun:a dry run. combine with other options to see what will happen -without it actually happening.
    -
    -
    -

    Example:

    -

    cleanowned scattered X : This will confiscate rotten and dropped food, garbage on the floors and any worn items with 'X' damage and above.

    -
    -
    -
    -

    colonies

    -

    Allows listing all the vermin colonies on the map and optionally turning them into honey bee colonies.

    -
    -

    Options

    - --- - - - -
    bees:turn colonies into honey bee colonies
    -
    -
    -
    -

    deramp (by zilpin)

    -

    Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation. -It also removes any and all 'down ramps' that can remain after a cave-in (you don't have to designate anything for that to happen).

    -
    -
    -

    dfusion

    -

    This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin.

    -

    See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15

    -
    -

    Confirmed working DFusion plugins:

    - --- - - - -
    simple_embark:allows changing the number of dwarves available on embark.
    -
    -

    Note

    -
      -
    • Some of the DFusion plugins aren't completely ported yet. This can lead to crashes.
    • -
    • This is currently working only on Windows.
    • -
    • The game will be suspended while you're using dfusion. Don't panic when it doen't respond.
    • -
    -
    -
    -
    -
    -

    drybuckets

    -

    This utility removes water from all buckets in your fortress, allowing them to be safely used for making lye.

    -
    -
    -

    fastdwarf

    -

    Makes your minions move at ludicrous speeds.

    -
    -
      -
    • Activate with 'fastdwarf 1'
    • -
    • Deactivate with 'fastdwarf 0'
    • -
    -
    -
    -
    -

    feature

    -

    Enables management of map features.

    -
      -
    • Discovering a magma feature (magma pool, volcano, magma sea, or curious -underground structure) permits magma workshops and furnaces to be built.
    • -
    • Discovering a cavern layer causes plants (trees, shrubs, and grass) from -that cavern to grow within your fortress.
    • -
    -
    -

    Options

    - --- - - - - - - - -
    list:Lists all map features in your current embark by index.
    show X:Marks the selected map feature as discovered.
    hide X:Marks the selected map feature as undiscovered.
    -
    -
    -
    -

    filltraffic

    -

    Set traffic designations using flood-fill starting at the cursor.

    -
    -

    Traffic Type Codes:

    - --- - - - - - - - - - -
    H:High Traffic
    N:Normal Traffic
    L:Low Traffic
    R:Restricted Traffic
    -
    -
    -

    Other Options:

    - --- - - - - - - - -
    X:Fill accross z-levels.
    B:Include buildings and stockpiles.
    P:Include empty space.
    -
    -
    -

    Example:

    -

    'filltraffic H' - When used in a room with doors, it will set traffic to HIGH in just that room.

    -
    -
    -
    -

    alltraffic

    -

    Set traffic designations for every single tile of the map (useful for resetting traffic designations).

    -
    -

    Traffic Type Codes:

    - --- - - - - - - - - - -
    H:High Traffic
    N:Normal Traffic
    L:Low Traffic
    R:Restricted Traffic
    -
    -
    -

    Example:

    -

    'alltraffic N' - Set traffic to 'normal' for all tiles.

    -
    -
    -
    -

    fixdiplomats

    -

    Up to version 0.31.12, Elves only sent Diplomats to your fortress to propose -tree cutting quotas due to a bug; once that bug was fixed, Elves stopped caring -about excess tree cutting. This command adds a Diplomat position to all Elven -civilizations, allowing them to negotiate tree cutting quotas (and allowing you -to violate them and potentially start wars) in case you haven't already modified -your raws accordingly.

    -
    -
    -

    fixmerchants

    -

    This command adds the Guild Representative position to all Human civilizations, -allowing them to make trade agreements (just as they did back in 0.28.181.40d -and earlier) in case you haven't already modified your raws accordingly.

    -
    -
    -

    fixveins

    -

    Removes invalid references to mineral inclusions and restores missing ones. -Use this if you broke your embark with tools like tiletypes, or if you -accidentally placed a construction on top of a valuable mineral floor.

    -
    -
    -

    flows

    -

    A tool for checking how many tiles contain flowing liquids. If you suspect that -your magma sea leaks into HFS, you can use this tool to be sure without -revealing the map.

    -
    -
    -

    getplants

    -

    This tool allows plant gathering and tree cutting by RAW ID. Specify the types -of trees to cut down and/or shrubs to gather by their plant names, separated -by spaces.

    -
    -

    Options

    - --- - - - - - - - - - -
    -t:Select trees only (exclude shrubs)
    -s:Select shrubs only (exclude trees)
    -c:Clear designations instead of setting them
    -x:Apply selected action to all plants except those specified (invert -selection)
    -

    Specifying both -t and -s will have no effect. If no plant IDs are specified, -all valid plant IDs will be listed.

    -
    -
    -
    -

    tidlers

    -

    Toggle between all possible positions where the idlers count can be placed.

    -
    -
    -

    twaterlvl

    -

    Toggle between displaying/not displaying liquid depth as numbers.

    -
    -
    -

    job

    -

    Command for general job query and manipulation.

    -
    -
    Options:
    -
      -
    • no extra options - Print details of the current job. The job can be selected -in a workshop, or the unit/jobs screen.
    • -
    • list - Print details of all jobs in the selected workshop.
    • -
    • item-material <item-idx> <material[:subtoken]> - Replace the exact material -id in the job item.
    • -
    • item-type <item-idx> <type[:subtype]> - Replace the exact item type id in -the job item.
    • -
    -
    -
    -
    -
    -

    job-material

    -

    Alter the material of the selected job.

    -

    Invoked as: job-material <inorganic-token>

    -
    -
    Intended to be used as a keybinding:
    -
      -
    • In 'q' mode, when a job is highlighted within a workshop or furnace, -changes the material of the job. Only inorganic materials can be used -in this mode.
    • -
    • In 'b' mode, during selection of building components positions the cursor -over the first available choice with the matching material.
    • -
    -
    -
    -
    -
    -

    job-duplicate

    -
    -
    Duplicate the selected job in a workshop:
    -
      -
    • In 'q' mode, when a job is highlighted within a workshop or furnace building, -instantly duplicates the job.
    • -
    -
    -
    -
    -
    -

    liquids

    -

    Allows adding magma, water and obsidian to the game. It replaces the normal -dfhack command line and can't be used from a hotkey. Settings will be remembered -as long as dfhack runs. Intended for use in combination with the command -liquids-here (which can be bound to a hotkey).

    -

    For more information, refer to the command's internal help.

    -
    -

    Note

    -

    Spawning and deleting liquids can F up pathing data and -temperatures (creating heat traps). You've been warned.

    -
    -
    -
    -

    liquids-here

    -

    Run the liquid spawner with the current/last settings made in liquids (if no -settings in liquids were made it paints a point of 7/7 magma by default).

    -

    Intended to be used as keybinding. Requires an active in-game cursor.

    -
    -
    -

    mode

    -

    This command lets you see and change the game mode directly. -Not all combinations are good for every situation and most of them will -produce undesirable results. There are a few good ones though.

    -
    -

    Example

    -

    You are in fort game mode, managing your fortress and paused. -You switch to the arena game mode, assume control of a creature and then -switch to adventure game mode(1). -You just lost a fortress and gained an adventurer. -You could also do this. -You are in fort game mode, managing your fortress and paused at the esc menu. -You switch to the adventure game mode, then use Dfusion to assume control of a creature and then -save or retire. -You just created a returnable mountain home and gained an adventurer.

    -
    -

    I take no responsibility of anything that happens as a result of using this tool

    -
    -
    -

    extirpate

    -

    A tool for getting rid of trees and shrubs. By default, it only kills -a tree/shrub under the cursor. The plants are turned into ashes instantly.

    -
    -

    Options

    - --- - - - - - - - -
    shrubs:affect all shrubs on the map
    trees:affect all trees on the map
    all:affect every plant!
    -
    -
    -
    -

    grow

    -

    Makes all saplings present on the map grow into trees (almost) instantly.

    -
    -
    -

    immolate

    -

    Very similar to extirpate, but additionally sets the plants on fire. The fires -can and will spread ;)

    -
    -
    -

    probe

    -

    Can be used to determine tile properties like temperature.

    -
    -
    -

    prospect

    -

    Prints a big list of all the present minerals and plants. By default, only -the visible part of the map is scanned.

    -
    -

    Options

    - --- - - - - - - - -
    all:Scan the whole map, as if it was revealed.
    value:Show material value in the output. Most useful for gems.
    hell:Show the Z range of HFS tubes. Implies 'all'.
    -
    -
    -

    Pre-embark estimate

    -

    If called during the embark selection screen, displays an estimate of layer -stone availability. If the 'all' option is specified, also estimates veins. -The estimate is computed either for 1 embark tile of the blinking biome, or -for all tiles of the embark rectangle.

    -
    -
    -

    Options

    - --- - - - -
    all:processes all tiles, even hidden ones.
    -
    -
    -
    -

    regrass

    -

    Regrows grass. Not much to it ;)

    -
    -
    -

    rename

    -

    Allows renaming various things.

    -
    -

    Options

    - --- - - - - - - - - - - - - - - - - -
    rename squad <index> "name":
     Rename squad by index to 'name'.
    rename hotkey <index> "name":
     Rename hotkey by index. This allows assigning -longer commands to the DF hotkeys.
    rename unit "nickname":
     Rename a unit/creature highlighted in the DF user -interface.
    rename unit-profession "custom profession":
     Change proffession name of the -highlighted unit/creature.
    rename building "name":
     Set a custom name for the selected building. -The building must be one of stockpile, workshop, furnace, trap, -siege engine or an activity zone.
    -
    -
    -
    -

    reveal

    -

    This reveals the map. By default, HFS will remain hidden so that the demons -don't spawn. You can use 'reveal hell' to reveal everything. With hell revealed, -you won't be able to unpause until you hide the map again. If you really want -to unpause with hell revealed, use 'reveal demons'.

    -

    Reveal also works in adventure mode, but any of its effects are negated once -you move. When you use it this way, you don't need to run 'unreveal'.

    -
    -
    -

    unreveal

    -

    Reverts the effects of 'reveal'.

    -
    -
    -

    revtoggle

    -

    Switches between 'reveal' and 'unreveal'.

    -
    -
    -

    revflood

    -

    This command will hide the whole map and then reveal all the tiles that have -a path to the in-game cursor.

    -
    -
    -

    revforget

    -

    When you use reveal, it saves information about what was/wasn't visible before -revealing everything. Unreveal uses this information to hide things again. -This command throws away the information. For example, use in cases where -you abandoned with the fort revealed and no longer want the data.

    -
    -
    -

    lair

    -

    This command allows you to mark the map as 'monster lair', preventing item -scatter on abandon. When invoked as 'lair reset', it does the opposite.

    -

    Unlike reveal, this command doesn't save the information about tiles - you -won't be able to restore state of real monster lairs using 'lair reset'.

    -
    -

    Options

    - --- - - - - - -
    lair:Mark the map as monster lair
    lair reset:Mark the map as ordinary (not lair)
    -
    -
    -
    -

    seedwatch

    -

    Tool for turning cooking of seeds and plants on/off depending on how much you -have of them.

    -

    See 'seedwatch help' for detailed description.

    -
    -
    -

    showmood

    -

    Shows all items needed for the currently active strange mood.

    -
    -
    -

    copystock

    -

    Copies the parameters of the currently highlighted stockpile to the custom -stockpile settings and switches to custom stockpile placement mode, effectively -allowing you to copy/paste stockpiles easily.

    -
    -
    -

    ssense / stonesense

    -

    An isometric visualizer that runs in a second window. This requires working -graphics acceleration and at least a dual core CPU (otherwise it will slow -down DF).

    -

    All the data resides in the 'stonesense' directory. For detailed instructions, -see stonesense/README.txt

    -

    Compatible with Windows > XP SP3 and most modern Linux distributions.

    -

    Older versions, support and extra graphics can be found in the bay12 forum -thread: http://www.bay12forums.com/smf/index.php?topic=43260.0

    -

    Some additional resources: -http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository

    +
    +

    liquids-here

    +

    Run the liquid spawner with the current/last settings made in liquids (if no +settings in liquids were made it paints a point of 7/7 magma by default).

    +

    Intended to be used as keybinding. Requires an active in-game cursor.

    -

    tiletypes

    +

    tiletypes

    Can be used for painting map tiles and is an interactive command, much like liquids.

    The tool works with two set of options and a brush. The brush determines which @@ -1645,107 +1013,280 @@ up.

    For more details, see the 'help' command while using this.

    -

    tiletypes-commands

    +

    tiletypes-commands

    Runs tiletypes commands, separated by ;. This makes it possible to change tiletypes modes from a hotkey.

    -

    tiletypes-here

    +

    tiletypes-here

    Apply the current tiletypes options at the in-game cursor position, including the brush. Can be used from a hotkey.

    -

    tiletypes-here-point

    +

    tiletypes-here-point

    Apply the current tiletypes options at the in-game cursor position to a single tile. Can be used from a hotkey.

    -
    -

    tweak

    -

    Contains various tweaks for minor bugs (currently just one).

    -
    -

    Options

    +
    +

    tubefill

    +

    Fills all the adamantine veins again. Veins that were empty will be filled in +too, but might still trigger a demon invasion (this is a known bug).

    +
    +
    +

    extirpate

    +

    A tool for getting rid of trees and shrubs. By default, it only kills +a tree/shrub under the cursor. The plants are turned into ashes instantly.

    +

    Options:

    +
    - + - + - + - + +
    clear-missing:Remove the missing status from the selected unit. -This allows engraving slabs for ghostly, but not yet -found, creatures.
    shrubs:affect all shrubs on the map
    clear-ghostly:Remove the ghostly status from the selected unit and mark -it as dead. This allows getting rid of bugged ghosts -which do not show up in the engraving slab menu at all, -even after using clear-missing. It works, but is -potentially very dangerous - so use with care. Probably -(almost certainly) it does not have the same effects like -a proper burial. You've been warned.
    trees:affect all trees on the map
    fixmigrant:Remove the resident/merchant flag from the selected unit. -Intended to fix bugged migrants/traders who stay at the -map edge and don't enter your fort. Only works for -dwarves (or generally the player's race in modded games). -Do NOT abuse this for 'real' caravan merchants (if you -really want to kidnap them, use 'tweak makeown' instead, -otherwise they will have their clothes set to forbidden etc).
    all:affect every plant!
    makeown:Force selected unit to become a member of your fort. -Can be abused to grab caravan merchants and escorts, even if -they don't belong to the player's race. Foreign sentients -(humans, elves) can be put to work, but you can't assign rooms -to them and they don't show up in DwarfTherapist because the -game treats them like pets. Grabbing draft animals from -a caravan can result in weirdness (animals go insane or berserk -and are not flagged as tame), but you are allowed to mark them -for slaughter. Grabbing wagons results in some funny spam, then -they are scuttled.
    +
    +
    +
    +

    grow

    +

    Makes all saplings present on the map grow into trees (almost) instantly.

    +
    +
    +

    immolate

    +

    Very similar to extirpate, but additionally sets the plants on fire. The fires +can and will spread ;)

    +
    +
    +

    regrass

    +

    Regrows grass. Not much to it ;)

    +
    +
    +

    weather

    +

    Prints the current weather map by default.

    +

    Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'.

    +

    Options:

    +
    + +++ + - + - + - - + +
    snow:make it snow everywhere.
    stable-cursor:Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode.
    rain:make it rain.
    patrol-duty:Makes Train orders not count as patrol duty to stop unhappy thoughts. -Does NOT fix the problem when soldiers go off-duty (i.e. civilian).
    clear:clear the sky.
    readable-build-plate:
     Fixes rendering of creature weight limits in pressure plate build menu.
    +
    +
    +
    +
    +

    Map inspection

    +
    +

    cursecheck

    +

    Checks a single map tile or the whole map/world for cursed creatures (ghosts, +vampires, necromancers, werebeasts, zombies).

    +

    With an active in-game cursor only the selected tile will be observed. +Without a cursor the whole map will be checked.

    +

    By default cursed creatures will be only counted in case you just want to find +out if you have any of them running around in your fort. Dead and passive +creatures (ghosts who were put to rest, killed vampires, ...) are ignored. +Undead skeletons, corpses, bodyparts and the like are all thrown into the curse +category "zombie". Anonymous zombies and resurrected body parts will show +as "unnamed creature".

    +

    Options:

    +
    + +++ + - + - + - + - - + +
    detail:Print full name, date of birth, date of curse and some status +info (some vampires might use fake identities in-game, though).
    stable-temp:Fixes performance bug 6012 by squashing jitter in temperature updates. -In very item-heavy forts with big stockpiles this can improve FPS by 50-100%
    nick:Set the type of curse as nickname (does not always show up +in-game, some vamps don't like nicknames).
    fast-heat:Further improves temperature update performance by ensuring that 1 degree -of item temperature is crossed in no more than specified number of frames -when updating from the environment temperature. This reduces the time it -takes for stable-temp to stop updates again when equilibrium is disturbed.
    all:Include dead and passive cursed creatures (can result in a quite +long list after having FUN with necromancers).
    fix-dimensions:Fixes subtracting small amount of thread/cloth/liquid from a stack -by splitting the stack and subtracting from the remaining single item. -This is a necessary addition to the binary patch in bug 808.
    verbose:Print all curse tags (if you really want to know it all).
    advmode-contained:
     Works around bug 6202, i.e. custom reactions with container inputs -in advmode. The issue is that the screen tries to force you to select -the contents separately from the container. This forcefully skips child -reagents.
    +
    +

    Examples:

    +
    +
    +
    cursecheck detail all
    +
    Give detailed info about all cursed creatures including deceased ones (no +in-game cursor).
    +
    cursecheck nick
    +
    Give a nickname all living/active cursed creatures on the map(no in-game +cursor).
    +
    +
    +
    +

    Note

    +
      +
    • If you do a full search (with the option "all") former ghosts will show up +with the cursetype "unknown" because their ghostly flag is not set +anymore. But if you happen to find a living/active creature with cursetype +"unknown" please report that in the dfhack thread on the modding forum or +per irc. This is likely to happen with mods which introduce new types +of curses, for example.
    • +
    +
    +
    +
    +

    flows

    +

    A tool for checking how many tiles contain flowing liquids. If you suspect that +your magma sea leaks into HFS, you can use this tool to be sure without +revealing the map.

    +
    +
    +

    probe

    +

    Can be used to determine tile properties like temperature.

    +
    +
    +

    prospect

    +

    Prints a big list of all the present minerals and plants. By default, only +the visible part of the map is scanned.

    +

    Options:

    +
    + +++ + + + + + + + +
    all:Scan the whole map, as if it was revealed.
    value:Show material value in the output. Most useful for gems.
    hell:Show the Z range of HFS tubes. Implies 'all'.
    +
    +
    +

    Pre-embark estimate

    +

    If called during the embark selection screen, displays an estimate of layer +stone availability. If the 'all' option is specified, also estimates veins. +The estimate is computed either for 1 embark tile of the blinking biome, or +for all tiles of the embark rectangle.

    +

    Options:

    +
    + +++ + + + +
    all:processes all tiles, even hidden ones.
    +
    +
    +
    +
    +

    reveal

    +

    This reveals the map. By default, HFS will remain hidden so that the demons +don't spawn. You can use 'reveal hell' to reveal everything. With hell revealed, +you won't be able to unpause until you hide the map again. If you really want +to unpause with hell revealed, use 'reveal demons'.

    +

    Reveal also works in adventure mode, but any of its effects are negated once +you move. When you use it this way, you don't need to run 'unreveal'.

    +
    +
    +

    unreveal

    +

    Reverts the effects of 'reveal'.

    +
    +
    +

    revtoggle

    +

    Switches between 'reveal' and 'unreveal'.

    +
    +
    +

    revflood

    +

    This command will hide the whole map and then reveal all the tiles that have +a path to the in-game cursor.

    +
    +
    +

    revforget

    +

    When you use reveal, it saves information about what was/wasn't visible before +revealing everything. Unreveal uses this information to hide things again. +This command throws away the information. For example, use in cases where +you abandoned with the fort revealed and no longer want the data.

    +
    +
    +

    showmood

    +

    Shows all items needed for the currently active strange mood.

    +
    +
    +
    +

    Designations

    +
    +

    burrow

    +

    Miscellaneous burrow control. Allows manipulating burrows and automated burrow +expansion while digging.

    +

    Options:

    +
    +
    +
    enable feature ...
    +
    Enable features of the plugin.
    +
    disable feature ...
    +
    Disable features of the plugin.
    +
    clear-unit burrow burrow ...
    +
    Remove all units from the burrows.
    +
    clear-tiles burrow burrow ...
    +
    Remove all tiles from the burrows.
    +
    set-units target-burrow src-burrow ...
    +
    Clear target, and adds units from source burrows.
    +
    add-units target-burrow src-burrow ...
    +
    Add units from the source burrows to the target.
    +
    remove-units target-burrow src-burrow ...
    +
    Remove units in source burrows from the target.
    +
    set-tiles target-burrow src-burrow ...
    +
    Clear target and adds tiles from the source burrows.
    +
    add-tiles target-burrow src-burrow ...
    +
    Add tiles from the source burrows to the target.
    +
    remove-tiles target-burrow src-burrow ...
    +

    Remove tiles in source burrows from the target.

    +

    For these three options, in place of a source burrow it is +possible to use one of the following keywords: ABOVE_GROUND, +SUBTERRANEAN, INSIDE, OUTSIDE, LIGHT, DARK, HIDDEN, REVEALED

    +
    +
    +
    +

    Features:

    +
    + +++ +
    auto-grow:When a wall inside a burrow with a name ending in '+' is dug +out, the burrow is extended to newly-revealed adjacent walls. +This final '+' may be omitted in burrow name args of commands above. +Digging 1-wide corridors with the miner inside the burrow is SLOW.
    -
    -
    -
    -

    tubefill

    -

    Fills all the adamantine veins again. Veins that were empty will be filled in -too, but might still trigger a demon invasion (this is a known bug).

    +
    -

    digv

    +

    digv

    Designates a whole vein for digging. Requires an active in-game cursor placed over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles).

    -

    digvx

    +

    digvx

    A permanent alias for 'digv x'.

    -

    digl

    +

    digl

    Designates layer stone for digging. Requires an active in-game cursor placed over a layer stone tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles). With the 'undo' option it @@ -1753,16 +1294,16 @@ will remove the dig designation instead (if you realize that digging out a 50 z-level deep layer was not such a good idea after all).

    -

    diglx

    +

    diglx

    A permanent alias for 'digl x'.

    -

    digexp

    +

    digexp

    This command can be used for exploratory mining.

    See: http://df.magmawiki.com/index.php/DF2010:Exploratory_mining

    There are two variables that can be set: pattern and filter.

    -
    -

    Patterns:

    +

    Patterns:

    +
    @@ -1781,9 +1322,9 @@ z-level deep layer was not such a good idea after all).

    -
    -
    -

    Filters:

    + +

    Filters:

    +
    @@ -1796,10 +1337,10 @@ z-level deep layer was not such a good idea after all).

    +

    After you have a pattern set, you can use 'expdig' to apply it again.

    -
    -
    -

    Examples:

    +

    Examples:

    +
    designate the diagonal 5 patter over all hidden tiles:
      @@ -1817,14 +1358,14 @@ z-level deep layer was not such a good idea after all).

    -
    +
    -

    digcircle

    +

    digcircle

    A command for easy designation of filled and hollow circles. It has several types of options.

    -
    -

    Shape:

    +

    Shape:

    +
    @@ -1837,78 +1378,470 @@ It has several types of options.

    +
    +

    Action:

    +
    + +++ + + + + + + + +
    set:Set designation (default)
    unset:Unset current designation
    invert:Invert designations already present
    +
    +

    Designation types:

    +
    + +++ + + + + + + + + + + + + + +
    dig:Normal digging designation (default)
    ramp:Ramp digging
    ustair:Staircase up
    dstair:Staircase down
    xstair:Staircase up/down
    chan:Dig channel
    +
    +

    After you have set the options, the command called with no options +repeats with the last selected parameters.

    +

    Examples:

    +
      +
    • 'digcircle filled 3' = Dig a filled circle with radius = 3.
    • +
    • 'digcircle' = Do it again.
    • +
    +
    +
    +

    filltraffic

    +

    Set traffic designations using flood-fill starting at the cursor.

    +

    Traffic Type Codes:

    +
    + +++ + + + + + + + + + +
    H:High Traffic
    N:Normal Traffic
    L:Low Traffic
    R:Restricted Traffic
    +
    +

    Other Options:

    +
    + +++ + + + + + + + +
    X:Fill accross z-levels.
    B:Include buildings and stockpiles.
    P:Include empty space.
    +
    +

    Example:

    +
    +'filltraffic H' - When used in a room with doors, it will set traffic to HIGH in just that room.
    +
    +
    +

    alltraffic

    +

    Set traffic designations for every single tile of the map (useful for resetting traffic designations).

    +

    Traffic Type Codes:

    +
    + +++ + + + + + + + + + +
    H:High Traffic
    N:Normal Traffic
    L:Low Traffic
    R:Restricted Traffic
    +
    +

    Example:

    +
    +'alltraffic N' - Set traffic to 'normal' for all tiles.
    +
    +
    +

    getplants

    +

    This tool allows plant gathering and tree cutting by RAW ID. Specify the types +of trees to cut down and/or shrubs to gather by their plant names, separated +by spaces.

    +

    Options:

    +
    + +++ + + + + + + + + + +
    -t:Select trees only (exclude shrubs)
    -s:Select shrubs only (exclude trees)
    -c:Clear designations instead of setting them
    -x:Apply selected action to all plants except those specified (invert +selection)
    +
    +

    Specifying both -t and -s will have no effect. If no plant IDs are specified, +all valid plant IDs will be listed.

    +
    +
    +
    +

    Cleanup and garbage disposal

    +
    +

    clean

    +

    Cleans all the splatter that get scattered all over the map, items and +creatures. In an old fortress, this can significantly reduce FPS lag. It can +also spoil your !!FUN!!, so think before you use it.

    +

    Options:

    +
    + +++ + + + + + + + +
    map:Clean the map tiles. By default, it leaves mud and snow alone.
    units:Clean the creatures. Will also clean hostiles.
    items:Clean all the items. Even a poisoned blade.
    +
    +

    Extra options for 'map':

    +
    + +++ + + + + + +
    mud:Remove mud in addition to the normal stuff.
    snow:Also remove snow coverings.
    +
    +
    +
    +

    spotclean

    +

    Works like 'clean map snow mud', but only for the tile under the cursor. Ideal +if you want to keep that bloody entrance 'clean map' would clean up.

    +
    +
    +

    autodump

    +

    This utility lets you quickly move all items designated to be dumped. +Items are instantly moved to the cursor position, the dump flag is unset, +and the forbid flag is set, as if it had been dumped normally. +Be aware that any active dump item tasks still point at the item.

    +

    Cursor must be placed on a floor tile so the items can be dumped there.

    +

    Options:

    +
    + +++ + + + + + + + + + + + +
    destroy:Destroy instead of dumping. Doesn't require a cursor.
    destroy-here:Destroy items only under the cursor.
    visible:Only process items that are not hidden.
    hidden:Only process hidden items.
    forbidden:Only process forbidden items (default: only unforbidden).
    +
    +
    +
    +

    autodump-destroy-here

    +

    Destroy items marked for dumping under cursor. Identical to autodump +destroy-here, but intended for use as keybinding.

    +
    +
    +

    autodump-destroy-item

    +

    Destroy the selected item. The item may be selected in the 'k' list, or inside +a container. If called again before the game is resumed, cancels destroy.

    +
    +
    +

    cleanowned

    +

    Confiscates items owned by dwarfs. By default, owned food on the floor +and rotten items are confistacted and dumped.

    +

    Options:

    +
    + +++ + + + + + + + + + + + +
    all:confiscate all owned items
    scattered:confiscated and dump all items scattered on the floor
    x:confiscate/dump items with wear level 'x' and more
    X:confiscate/dump items with wear level 'X' and more
    dryrun:a dry run. combine with other options to see what will happen +without it actually happening.
    +
    +

    Example:

    +
    +
    +
    cleanowned scattered X
    +
    This will confiscate rotten and dropped food, garbage on the floors and any +worn items with 'X' damage and above.
    +
    +
    +
    +
    +
    +

    Bugfixes

    +
    +

    drybuckets

    +

    This utility removes water from all buckets in your fortress, allowing them to be safely used for making lye.

    +
    +
    +

    fixdiplomats

    +

    Up to version 0.31.12, Elves only sent Diplomats to your fortress to propose +tree cutting quotas due to a bug; once that bug was fixed, Elves stopped caring +about excess tree cutting. This command adds a Diplomat position to all Elven +civilizations, allowing them to negotiate tree cutting quotas (and allowing you +to violate them and potentially start wars) in case you haven't already modified +your raws accordingly.

    +
    +
    +

    fixmerchants

    +

    This command adds the Guild Representative position to all Human civilizations, +allowing them to make trade agreements (just as they did back in 0.28.181.40d +and earlier) in case you haven't already modified your raws accordingly.

    +
    +
    +

    fixveins

    +

    Removes invalid references to mineral inclusions and restores missing ones. +Use this if you broke your embark with tools like tiletypes, or if you +accidentally placed a construction on top of a valuable mineral floor.

    -
    -

    Action:

    +
    +

    tweak

    +

    Contains various tweaks for minor bugs (currently just one).

    +

    Options:

    - + - + - + - -
    set:Set designation (default)
    clear-missing:Remove the missing status from the selected unit. +This allows engraving slabs for ghostly, but not yet +found, creatures.
    unset:Unset current designation
    clear-ghostly:Remove the ghostly status from the selected unit and mark +it as dead. This allows getting rid of bugged ghosts +which do not show up in the engraving slab menu at all, +even after using clear-missing. It works, but is +potentially very dangerous - so use with care. Probably +(almost certainly) it does not have the same effects like +a proper burial. You've been warned.
    invert:Invert designations already present
    fixmigrant:Remove the resident/merchant flag from the selected unit. +Intended to fix bugged migrants/traders who stay at the +map edge and don't enter your fort. Only works for +dwarves (or generally the player's race in modded games). +Do NOT abuse this for 'real' caravan merchants (if you +really want to kidnap them, use 'tweak makeown' instead, +otherwise they will have their clothes set to forbidden etc).
    -
    -
    -

    Designation types:

    - --- - + - + - + - + + - + - + + + + + +
    dig:Normal digging designation (default)
    makeown:Force selected unit to become a member of your fort. +Can be abused to grab caravan merchants and escorts, even if +they don't belong to the player's race. Foreign sentients +(humans, elves) can be put to work, but you can't assign rooms +to them and they don't show up in DwarfTherapist because the +game treats them like pets. Grabbing draft animals from +a caravan can result in weirdness (animals go insane or berserk +and are not flagged as tame), but you are allowed to mark them +for slaughter. Grabbing wagons results in some funny spam, then +they are scuttled.
    ramp:Ramp digging
    stable-cursor:Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode.
    ustair:Staircase up
    patrol-duty:Makes Train orders not count as patrol duty to stop unhappy thoughts. +Does NOT fix the problem when soldiers go off-duty (i.e. civilian).
    dstair:Staircase down
    readable-build-plate:
     Fixes rendering of creature weight limits in pressure plate build menu.
    xstair:Staircase up/down
    stable-temp:Fixes performance bug 6012 by squashing jitter in temperature updates. +In very item-heavy forts with big stockpiles this can improve FPS by 50-100%
    chan:Dig channel
    fast-heat:Further improves temperature update performance by ensuring that 1 degree +of item temperature is crossed in no more than specified number of frames +when updating from the environment temperature. This reduces the time it +takes for stable-temp to stop updates again when equilibrium is disturbed.
    fix-dimensions:Fixes subtracting small amount of thread/cloth/liquid from a stack +by splitting the stack and subtracting from the remaining single item. +This is a necessary addition to the binary patch in bug 808.
    advmode-contained:
     Works around bug 6202, i.e. custom reactions with container inputs +in advmode. The issue is that the screen tries to force you to select +the contents separately from the container. This forcefully skips child +reagents.
    -

    After you have set the options, the command called with no options -repeats with the last selected parameters.

    -
    -

    Examples:

    -
      -
    • 'digcircle filled 3' = Dig a filled circle with radius = 3.
    • -
    • 'digcircle' = Do it again.
    • -
    -
    -
    -

    weather

    -

    Prints the current weather map by default.

    -

    Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'.

    -
    -

    Options:

    +
    +

    Mode switch and reclaim

    +
    +

    lair

    +

    This command allows you to mark the map as 'monster lair', preventing item +scatter on abandon. When invoked as 'lair reset', it does the opposite.

    +

    Unlike reveal, this command doesn't save the information about tiles - you +won't be able to restore state of real monster lairs using 'lair reset'.

    +

    Options:

    +
    - - - + - +
    snow:make it snow everywhere.
    rain:make it rain.
    lair:Mark the map as monster lair
    clear:clear the sky.
    lair reset:Mark the map as ordinary (not lair)
    +
    +
    +
    +

    mode

    +

    This command lets you see and change the game mode directly. +Not all combinations are good for every situation and most of them will +produce undesirable results. There are a few good ones though.

    +
    +

    Example

    +

    You are in fort game mode, managing your fortress and paused. +You switch to the arena game mode, assume control of a creature and then +switch to adventure game mode(1). +You just lost a fortress and gained an adventurer. +You could also do this. +You are in fort game mode, managing your fortress and paused at the esc menu. +You switch to the adventure game mode, then use Dfusion to assume control of a creature and then +save or retire. +You just created a returnable mountain home and gained an adventurer.

    +
    +

    I take no responsibility of anything that happens as a result of using this tool

    +
    +
    +
    +

    Visualizer and data export

    +
    +

    ssense / stonesense

    +

    An isometric visualizer that runs in a second window. This requires working +graphics acceleration and at least a dual core CPU (otherwise it will slow +down DF).

    +

    All the data resides in the 'stonesense' directory. For detailed instructions, +see stonesense/README.txt

    +

    Compatible with Windows > XP SP3 and most modern Linux distributions.

    +

    Older versions, support and extra graphics can be found in the bay12 forum +thread: http://www.bay12forums.com/smf/index.php?topic=43260.0

    +

    Some additional resources: +http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository

    +
    +
    +

    mapexport

    +

    Export the current loaded map as a file. This will be eventually usable +with visualizers.

    +

    dwarfexport +----------= +Export dwarves to RuneSmith-compatible XML.

    +
    +
    +
    +

    Job management

    +
    +

    job

    +

    Command for general job query and manipulation.

    +
    +
    Options:
    +
    +
    no extra options
    +
    Print details of the current job. The job can be selected +in a workshop, or the unit/jobs screen.
    +
    list
    +
    Print details of all jobs in the selected workshop.
    +
    item-material <item-idx> <material[:subtoken]>
    +
    Replace the exact material id in the job item.
    +
    item-type <item-idx> <type[:subtype]>
    +
    Replace the exact item type id in the job item.
    +
    +
    +
    +
    +
    +

    job-material

    +

    Alter the material of the selected job.

    +

    Invoked as:

    +
    +job-material <inorganic-token>
    +
    +

    Intended to be used as a keybinding:

    +
    +
      +
    • In 'q' mode, when a job is highlighted within a workshop or furnace, +changes the material of the job. Only inorganic materials can be used +in this mode.
    • +
    • In 'b' mode, during selection of building components positions the cursor +over the first available choice with the matching material.
    • +
    +
    +
    +

    job-duplicate

    +
    +
    Duplicate the selected job in a workshop:
    +
      +
    • In 'q' mode, when a job is highlighted within a workshop or furnace building, +instantly duplicates the job.
    • +
    +
    +
    -

    workflow

    +

    workflow

    Manage control of repeat jobs.

    -
    -

    Usage

    +

    Usage:

    +
    workflow enable [option...], workflow disable [option...]

    If no options are specified, enables or disables the plugin. @@ -1927,9 +1860,9 @@ Otherwise, enables or disables any of the following options:

    workflow unlimit <constraint-spec>
    Delete a constraint.
    -
    +
    -

    Function

    +

    Function

    When the plugin is enabled, it protects all repeat jobs from removal. If they do disappear due to any cause, they are immediately re-added to their workshop and suspended.

    @@ -1940,7 +1873,7 @@ the amount has to drop before jobs are resumed; this is intended to reduce the frequency of jobs being toggled.

    -

    Constraint examples

    +

    Constraint examples

    Keep metal bolts within 900-1000, and wood/bone within 150-200.

     workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
    @@ -1977,20 +1910,20 @@ command.
     
    -
    -

    mapexport

    -

    Export the current loaded map as a file. This will be eventually usable -with visualizers.

    -
    -

    dwarfexport

    -

    Export dwarves to RuneSmith-compatible XML.

    +
    +

    Fortress activity management

    +
    +

    seedwatch

    +

    Tool for turning cooking of seeds and plants on/off depending on how much you +have of them.

    +

    See 'seedwatch help' for detailed description.

    -

    zone

    +

    zone

    Helps a bit with managing activity zones (pens, pastures and pits) and cages.

    -
    -

    Options:

    +

    Options:

    +
    @@ -2027,9 +1960,9 @@ the cursor are listed.
    -
    -
    -

    Filters:

    + +

    Filters:

    +
    @@ -2084,9 +2017,9 @@ for war/hunt). Negatable.
    -
    +
    -

    Usage with single units

    +

    Usage with single units

    One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units @@ -2095,7 +2028,7 @@ and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way.

    -

    Usage with filters

    +

    Usage with filters

    All filters can be used together with the 'assign' command.

    Restrictions: It's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. @@ -2113,14 +2046,14 @@ are not properly added to your own stocks; slaughtering them should work).

    Most filters can be negated (e.g. 'not grazer' -> race is not a grazer).

    -

    Mass-renaming

    +

    Mass-renaming

    Using the 'nick' command you can set the same nickname for multiple units. If used without 'assign', 'all' or 'count' it will rename all units in the current default target zone. Combined with 'assign', 'all' or 'count' (and further optional filters) it will rename units matching the filter conditions.

    -

    Cage zones

    +

    Cage zones

    Using the 'tocages' command you can assign units to a set of cages, for example a room next to your butcher shop(s). They will be spread evenly among available cages to optimize hauling to and butchering from them. For this to work you need @@ -2130,8 +2063,8 @@ all cages you want to use. Then use 'zone set' (like with 'assign') and use would make no sense, but can be used together with 'nick' or 'remnick' and all the usual filters.

    -
    -

    Examples

    +
    +

    Examples

    zone assign all own ALPACA minage 3 maxage 10
    Assign all own alpacas who are between 3 and 10 years old to the selected @@ -2157,7 +2090,7 @@ on the current default zone.
    -

    autonestbox

    +

    autonestbox

    Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless @@ -2167,8 +2100,8 @@ to a 1x1 pasture is not a good idea. Only tame and domesticated own units are processed since pasturing half-trained wild egglayers could destroy your neat nestbox zones when they revert to wild. When called without options autonestbox will instantly run once.

    -
    -

    Options:

    +

    Options:

    +
    @@ -2183,10 +2116,10 @@ frames between runs.
    -
    +
    -

    autobutcher

    +

    autobutcher

    Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units will be processed.

    Named units will be completely ignored (to protect specific animals from @@ -2199,8 +2132,8 @@ units or with 'zone nick' to mass-rename units in pastures and cages).

    Once you have too much kids, the youngest will be butchered first. If you don't set any target count the following default will be used: 1 male kid, 5 female kids, 1 male adult, 5 female adults.

    -
    -

    Options:

    +

    Options:

    +
    @@ -2251,9 +2184,8 @@ for future entries.
    -
    -
    -

    Examples:

    + +

    Examples:

    You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, 1 male) of the race alpaca. Once the kids grow up the oldest adults will get slaughtered. Excess kids will get slaughtered starting with the youngest @@ -2282,9 +2214,7 @@ add some new races with 'watch'. If you simply want to stop it completely use

     autobutcher unwatch ALPACA CAT
     
    -
    -
    -

    Note:

    +

    Note:

    Settings and watchlist are stored in the savegame, so that you can have different settings for each world. If you want to copy your watchlist to another savegame you can use the command list_export:

    @@ -2296,9 +2226,8 @@ Load the savegame where you want to copy the settings to, run the batch file (fr autobutcher.bat
    -
    -

    autolabor

    +

    autolabor

    Automatically manage dwarf labors.

    When enabled, autolabor periodically checks your dwarves and enables or disables labors. It tries to keep as many dwarves as possible busy but @@ -2311,8 +2240,39 @@ while it is enabled.

    For detailed usage information, see 'help autolabor'.

    +
    +

    Other

    +
    +

    catsplosion

    +

    Makes cats just multiply. It is not a good idea to run this more than once or +twice.

    +
    +
    +

    dfusion

    +

    This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin.

    +

    See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15

    +

    Confirmed working DFusion plugins:

    + +++ + + + +
    simple_embark:allows changing the number of dwarves available on embark.
    +
    +

    Note

    +
      +
    • Some of the DFusion plugins aren't completely ported yet. This can lead to crashes.
    • +
    • This is currently working only on Windows.
    • +
    • The game will be suspended while you're using dfusion. Don't panic when it doen't respond.
    • +
    +
    +
    +
    +
    -

    Scripts

    +

    Scripts

    Lua or ruby scripts placed in the hack/scripts/ directory are considered for execution as if they were native DFHack commands. They are listed at the end of the 'ls' command output.

    @@ -2320,18 +2280,8 @@ of the 'ls' command output.

    only be listed by ls if called as 'ls -a'. This is intended as a way to hide scripts that are obscure, developer-oriented, or should be used as keybindings.

    Some notable scripts:

    -
    -

    quicksave

    -

    If called in dwarf mode, makes DF immediately auto-save the game by setting a flag -normally used in seasonal auto-save.

    -
    -
    -

    setfps

    -

    Run setfps <number> to set the FPS cap at runtime, in case you want to watch -combat in slow motion or something :)

    -
    -

    fix/*

    +

    fix/*

    Scripts in this subdirectory fix various bugs and issues, some of them obscure.

    • fix/dead-units

      @@ -2357,12 +2307,22 @@ caused by autodump bugs or other hacking mishaps.

    -

    gui/*

    +

    gui/*

    Scripts that implement dialogs inserted into the main game window are put in this directory.

    +
    +

    quicksave

    +

    If called in dwarf mode, makes DF immediately auto-save the game by setting a flag +normally used in seasonal auto-save.

    +
    +
    +

    setfps

    +

    Run setfps <number> to set the FPS cap at runtime, in case you want to watch +combat in slow motion or something :)

    +
    -

    growcrops

    +

    growcrops

    Instantly grow seeds inside farming plots.

    With no argument, this command list the various seed types currently in use in your farming plots. @@ -2374,7 +2334,7 @@ growcrops plump 40

    -

    removebadthoughts

    +

    removebadthoughts

    This script remove negative thoughts from your dwarves. Very useful against tantrum spirals.

    With a selected unit in 'v' mode, will clear this unit's mind, otherwise @@ -2387,7 +2347,7 @@ you unpause.

    it removed.

    -

    slayrace

    +

    slayrace

    Kills any unit of a given race.

    With no argument, lists the available races.

    With the special argument 'him', targets only the selected creature.

    @@ -2413,7 +2373,7 @@ slayrace elve magma
    -

    magmasource

    +

    magmasource

    Create an infinite magma source on a tile.

    This script registers a map tile as a magma source, and every 12 game ticks that tile receives 1 new unit of flowing magma.

    @@ -2429,11 +2389,11 @@ To remove all placed sources, call magmasource stop
    -

    In-game interface tools

    +

    In-game interface tools

    These tools work by displaying dialogs or overlays in the game window, and are mostly implemented by lua scripts.

    -

    Dwarf Manipulator

    +

    Dwarf Manipulator

    Implemented by the manipulator plugin. To activate, open the unit screen and press 'l'.

    This tool implements a Dwarf Therapist-like interface within the game UI. The @@ -2467,14 +2427,14 @@ cursor onto that cell instead of toggling it.

    Pressing ESC normally returns to the unit screen, but Shift-ESC would exit directly to the main dwarf mode screen.

    -
    -

    Liquids

    +
    +

    Liquids

    Implemented by the gui/liquids script. To use, bind to a key and activate in the 'k' mode.

    While active, use the suggested keys to switch the usual liquids parameters, and Enter to select the target area and apply changes.

    -

    Mechanisms

    +

    Mechanisms

    Implemented by the gui/mechanims script. To use, bind to a key and activate in the 'q' mode.

    Lists mechanisms connected to the building, and their links. Navigating the list centers the view on the relevant linked buildings.

    @@ -2483,14 +2443,14 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter re-entering the mechanisms ui.

    -

    Power Meter

    +

    Power Meter

    Front-end to the power-meter plugin implemented by the gui/power-meter script. Bind to a key and activate after selecting Pressure Plate in the build menu.

    The script follows the general look and feel of the regular pressure plate build configuration page, but configures parameters relevant to the modded power meter building.

    -
    -

    Rename

    +
    +

    Rename

    Backed by the rename plugin, the gui/rename script allows entering the desired name via a simple dialog in the game ui.

      @@ -2506,14 +2466,14 @@ It is also possible to rename zones from the 'i' menu.

      The building or unit are automatically assumed when in relevant ui state.

    -

    Room List

    +

    Room List

    Implemented by the gui/room-list script. To use, bind to a key and activate in the 'q' mode, either immediately or after opening the assign owner page.

    The script lists other rooms owned by the same owner, or by the unit selected in the assign list, and allows unassigning them.

    -

    Siege Engine

    +

    Siege Engine

    Front-end to the siege-engine plugin implemented by the gui/siege-engine script. Bind to a key and activate after selecting a siege engine in 'q' mode.

    The main mode displays the current target, selected ammo item type, linked stockpiles and @@ -2530,21 +2490,24 @@ DF, or select another area manually.

    Exiting from the siege engine script via ESC reverts the view to the state prior to starting the script. Shift-ESC retains the current viewport, and also exits from the 'q' mode to main menu.

    -

    DISCLAIMER: Siege engines are a very interesting feature, but currently nearly useless +

    +

    DISCLAIMER

    +

    Siege engines are a very interesting feature, but currently nearly useless because they haven't been updated since 2D and can only aim in four directions. This is an attempt to bring them more up to date until Toady has time to work on it. Actual improvements, e.g. like making siegers bring their own, are something only Toady can do.

    +
    -

    RAW hacks

    +

    RAW hacks

    These plugins detect certain structures in RAWs, and enhance them in various ways.

    -

    Steam Engine

    +

    Steam Engine

    The steam-engine plugin detects custom workshops with STEAM_ENGINE in their token, and turns them into real steam engines.

    -

    Rationale

    +

    Rationale

    The vanilla game contains only water wheels and windmills as sources of power, but windmills give relatively little power, and water wheels require flowing water, which must either be a real river and thus immovable and @@ -2555,25 +2518,31 @@ it can be done just by combining existing features of the game engine in a new way with some glue code and a bit of custom logic.

    -

    Construction

    +

    Construction

    The workshop needs water as its input, which it takes via a passable floor tile below it, like usual magma workshops do. The magma version also needs magma.

    -

    ISSUE: Since this building is a machine, and machine collapse +

    +

    ISSUE

    +

    Since this building is a machine, and machine collapse code cannot be modified, it would collapse over true open space. As a loophole, down stair provides support to machines, while being passable, so use them.

    +

    After constructing the building itself, machines can be connected to the edge tiles that look like gear boxes. Their exact position is extracted from the workshop raws.

    -

    ISSUE: Like with collapse above, part of the code involved in +

    +

    ISSUE

    +

    Like with collapse above, part of the code involved in machine connection cannot be modified. As a result, the workshop can only immediately connect to machine components built AFTER it. This also means that engines cannot be chained without intermediate short axles that can be built later than both of the engines.

    +
    -

    Operation

    +

    Operation

    In order to operate the engine, queue the Stoke Boiler job (optionally on repeat). A furnace operator will come, possibly bringing a bar of fuel, and perform it. As a result, a "boiling water" item will appear @@ -2598,7 +2567,7 @@ decrease it by further 4%, and also decrease the whole steam use rate by 10%.

    -

    Explosions

    +

    Explosions

    The engine must be constructed using barrel, pipe and piston from fire-safe, or in the magma version magma-safe metals.

    During operation weak parts get gradually worn out, and @@ -2607,7 +2576,7 @@ toppled during operation by a building destroyer, or a tantruming dwarf.

    -

    Save files

    +

    Save files

    It should be safe to load and view engine-using fortresses from a DF version without DFHack installed, except that in such case the engines won't work. However actually making modifications @@ -2618,7 +2587,7 @@ being generated.

    -

    Add Spatter

    +

    Add Spatter

    This plugin makes reactions with names starting with SPATTER_ADD_ produce contaminants on the items instead of improvements.

    Intended to give some use to all those poisons that can be bought from caravans.

    From 7a06c949c7d3404dc78f7a5d3c561eb2d3023e2f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 21 Sep 2012 15:21:04 +0400 Subject: [PATCH 042/196] Document the lua class module. --- LUA_API.rst | 80 ++++++++++++++++++++++++++++++++++++++++++++++ Lua API.html | 89 +++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 161 insertions(+), 8 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index d9b75c480..b098aa054 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -1831,6 +1831,86 @@ function: argument specifies the indentation step size in spaces. For the other arguments see the original documentation link above. +class +===== + +Implements a trivial single-inheritance class system. + +* ``Foo = defclass(Foo[, ParentClass])`` + + Defines or updates class Foo. The ``Foo = defclass(Foo)`` syntax + is needed so that when the module or script is reloaded, the + class identity will be preserved through the preservation of + global variable values. + + The ``defclass`` function is defined as a stub in the global + namespace, and using it will auto-load the class module. + +* ``Class.super`` + + This class field is set by defclass to the parent class, and + allows a readable ``Class.super.method(self, ...)`` syntax for + calling superclass methods. + +* ``Class.ATTRS { foo = xxx, bar = yyy }`` + + Declares certain instance fields to be attributes, i.e. auto-initialized + from fields in the table used as the constructor argument. If omitted, + they are initialized with the default values specified in this declaration. + + If the default value should be *nil*, use ``ATTRS { foo = DEFAULT_NIL }``. + +* ``new_obj = Class{ foo = arg, bar = arg, ... }`` + + Calling the class as a function creates and initializes a new instance. + Initialization happens in this order: + + 1. An empty instance table is created, and its metatable set. + 2. The ``preinit`` method is called via ``invoke_before`` (see below) + with the table used as argument to the class. This method is intended + for validating and tweaking that argument table. + 3. Declared ATTRS are initialized from the argument table or their default values. + 4. The ``init`` method is called via ``invoke_after`` with the argument table. + This is the main constructor method. + 5. The ``postinit`` method is called via ``invoke_after`` with the argument table. + Place code that should be called after the object is fully constructed here. + +Predefined instance methods: + +* ``instance:assign{ foo = xxx }`` + + Assigns all values in the input table to the matching instance fields. + +* ``instance:callback(method_name, [args...])`` + + Returns a closure that invokes the specified method of the class, + properly passing in self, and optionally a number of initial arguments too. + The arguments given to the closure are appended to these. + +* ``instance:invoke_before(method_name, args...)`` + + Navigates the inheritance chain of the instance starting from the most specific + class, and invokes the specified method with the arguments if it is defined in + that specific class. Equivalent to the following definition in every class:: + + function Class:invoke_before(method, ...) + if rawget(Class, method) then + rawget(Class, method)(self, ...) + end + Class.super.invoke_before(method, ...) + end + +* ``instance:invoke_after(method_name, args...)`` + + Like invoke_before, only the method is called after the recursive call to super, + i.e. invocations happen in the parent to child order. + + These two methods are inspired by the Common Lisp before and after methods, and + are intended for implementing similar protocols for certain things. The class + library itself uses them for constructors. + +To avoid confusion, these methods cannot be redefined. + ======= Plugins diff --git a/Lua API.html b/Lua API.html index 0da65b5fb..f30bb6ec5 100644 --- a/Lua API.html +++ b/Lua API.html @@ -366,14 +366,15 @@ ul.auto-toc {
  • Global environment
  • utils
  • dumper
  • +
  • class
  • -
  • Plugins
  • The current version of DFHack has extensive support for @@ -1930,16 +1931,88 @@ the other arguments see the original documentation link above.

    +
    +

    class

    +

    Implements a trivial single-inheritance class system.

    +
      +
    • Foo = defclass(Foo[, ParentClass])

      +

      Defines or updates class Foo. The Foo = defclass(Foo) syntax +is needed so that when the module or script is reloaded, the +class identity will be preserved through the preservation of +global variable values.

      +

      The defclass function is defined as a stub in the global +namespace, and using it will auto-load the class module.

      +
    • +
    • Class.super

      +

      This class field is set by defclass to the parent class, and +allows a readable Class.super.method(self, ...) syntax for +calling superclass methods.

      +
    • +
    • Class.ATTRS { foo = xxx, bar = yyy }

      +

      Declares certain instance fields to be attributes, i.e. auto-initialized +from fields in the table used as the constructor argument. If omitted, +they are initialized with the default values specified in this declaration.

      +

      If the default value should be nil, use ATTRS { foo = DEFAULT_NIL }.

      +
    • +
    • new_obj = Class{ foo = arg, bar = arg, ... }

      +

      Calling the class as a function creates and initializes a new instance. +Initialization happens in this order:

      +
        +
      1. An empty instance table is created, and its metatable set.
      2. +
      3. The preinit method is called via invoke_before (see below) +with the table used as argument to the class. This method is intended +for validating and tweaking that argument table.
      4. +
      5. Declared ATTRS are initialized from the argument table or their default values.
      6. +
      7. The init method is called via invoke_after with the argument table. +This is the main constructor method.
      8. +
      9. The postinit method is called via invoke_after with the argument table. +Place code that should be called after the object is fully constructed here.
      10. +
      +
    • +
    +

    Predefined instance methods:

    +
      +
    • instance:assign{ foo = xxx }

      +

      Assigns all values in the input table to the matching instance fields.

      +
    • +
    • instance:callback(method_name, [args...])

      +

      Returns a closure that invokes the specified method of the class, +properly passing in self, and optionally a number of initial arguments too. +The arguments given to the closure are appended to these.

      +
    • +
    • instance:invoke_before(method_name, args...)

      +

      Navigates the inheritance chain of the instance starting from the most specific +class, and invokes the specified method with the arguments if it is defined in +that specific class. Equivalent to the following definition in every class:

      +
      +function Class:invoke_before(method, ...)
      +  if rawget(Class, method) then
      +    rawget(Class, method)(self, ...)
      +  end
      +  Class.super.invoke_before(method, ...)
      +end
      +
      +
    • +
    • instance:invoke_after(method_name, args...)

      +

      Like invoke_before, only the method is called after the recursive call to super, +i.e. invocations happen in the parent to child order.

      +

      These two methods are inspired by the Common Lisp before and after methods, and +are intended for implementing similar protocols for certain things. The class +library itself uses them for constructors.

      +
    • +
    +

    To avoid confusion, these methods cannot be redefined.

    +
    -

    Plugins

    +

    Plugins

    DFHack plugins may export native functions and events to lua contexts. They are automatically imported by mkmodule('plugins.<name>'); this means that a lua module file is still necessary for require to read.

    The following plugins have lua support.

    -

    burrows

    +

    burrows

    Implements extended burrow manipulations.

    Events:

      @@ -1977,13 +2050,13 @@ set is the same as used by the command line.

      The lua module file also re-exports functions from dfhack.burrows.

    -

    sort

    +

    sort

    Does not export any native functions as of now. Instead, it calls lua code to perform the actual ordering of list items.

    -

    Scripts

    +

    Scripts

    Any files with the .lua extension placed into hack/scripts/* are automatically used by the DFHack core as commands. The matching command name consists of the name of the file sans From b3ae67cc8bfe48520daf85b0e1fe614531c6bd6c Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 21 Sep 2012 15:47:46 +0200 Subject: [PATCH 043/196] add scripts/deathcause --- NEWS | 1 + scripts/deathcause.rb | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 scripts/deathcause.rb diff --git a/NEWS b/NEWS index 2a930ff2b..c17229743 100644 --- a/NEWS +++ b/NEWS @@ -33,6 +33,7 @@ DFHack v0.34.11-r2 (UNRELEASED) - fix/population-cap: run after every migrant wave to prevent exceeding the cap. - fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp. - fix/loyaltycascade: fix units allegiance, eg after ordering a dwarf merchant kill. + - deathcause: shows the circumstances of death for a given body. - digfort: designate areas to dig from a csv file. - drainaquifer: remove aquifers from the map. - growcrops: cheat to make farm crops instantly grow. diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb new file mode 100644 index 000000000..178ebbc87 --- /dev/null +++ b/scripts/deathcause.rb @@ -0,0 +1,37 @@ +# show death cause of a creature + +def display_event(e) + p e if $DEBUG + + str = "#{e.victim_tg.name} died in year #{e.year}" + str << " of #{e.death_cause}" if false + str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_tg.name}" if e.slayer != -1 + str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON + str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON + + puts str + '.' +end + +item = df.item_find(:selected) + +if !item or !item.kind_of?(DFHack::ItemBodyComponent) + item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) } +end + +if !item or !item.kind_of?(DFHack::ItemBodyComponent) + puts "Please select a corpse in the loo'k' menu" +else + hfig = item.hist_figure_id + if hfig == -1 + puts "Not a historical figure, cannot find info" + else + events = df.world.history.events + (0...events.length).reverse_each { |i| + if events[i].kind_of?(DFHack::HistoryEventHistFigureDiedst) and events[i].victim == hfig + display_event(events[i]) + break + end + } + end +end + From f7e414e397e2f3ee70805a6bbe68a40551e05f2a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 21 Sep 2012 19:00:18 +0400 Subject: [PATCH 044/196] Add a devel script to inject raw definitions into an existing world. --- scripts/devel/inject-raws.lua | 178 ++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 scripts/devel/inject-raws.lua diff --git a/scripts/devel/inject-raws.lua b/scripts/devel/inject-raws.lua new file mode 100644 index 000000000..a4ebeec1d --- /dev/null +++ b/scripts/devel/inject-raws.lua @@ -0,0 +1,178 @@ +-- Injects new reaction, item and building defs into the world. + +-- The savegame contains a list of the relevant definition tokens in +-- the right order, but all details are read from raws every time. +-- This allows just adding stub definitions, and simply saving and +-- reloading the game. + +local utils = require 'utils' + +local raws = df.global.world.raws + +print[[ +WARNING: THIS SCRIPT CAN PERMANENLY DAMAGE YOUR SAVE. + +This script attempts to inject new raw objects into your +world. If the injected references do not match the actual +edited raws, your save will refuse to load, or load but crash. +]] + +if not utils.prompt_yes_no('Did you make a backup?') then + qerror('Not backed up.') +end + +df.global.pause_state = true + +local changed = false + +function inject_reaction(name) + for _,v in ipairs(raws.reactions) do + if v.code == name then + print('Reaction '..name..' already exists.') + return + end + end + + print('Injecting reaction '..name) + changed = true + + raws.reactions:insert('#', { + new = true, + code = name, + name = 'Dummy reaction '..name, + index = #raws.reactions, + }) +end + +local building_types = { + workshop = { df.building_def_workshopst, raws.buildings.workshops }, + furnace = { df.building_def_furnacest, raws.buildings.furnaces }, +} + +function inject_building(btype, name) + for _,v in ipairs(raws.buildings.all) do + if v.code == name then + print('Building '..name..' already exists.') + return + end + end + + print('Injecting building '..name) + changed = true + + local typeinfo = building_types[btype] + + local id = raws.buildings.next_id + raws.buildings.next_id = id+1 + + raws.buildings.all:insert('#', { + new = typeinfo[1], + code = name, + name = 'Dummy '..btype..' '..name, + id = id, + }) + + typeinfo[2]:insert('#', raws.buildings.all[#raws.buildings.all-1]) +end + +local itemdefs = raws.itemdefs +local item_types = { + weapon = { df.itemdef_weaponst, itemdefs.weapons, 'weapon_type' }, + trainweapon = { df.itemdef_weaponst, itemdefs.weapons, 'training_weapon_type' }, + pick = { df.itemdef_weaponst, itemdefs.weapons, 'digger_type' }, + trapcomp = { df.itemdef_trapcompst, itemdefs.trapcomps, 'trapcomp_type' }, + toy = { df.itemdef_toyst, itemdefs.toys, 'toy_type' }, + tool = { df.itemdef_toolst, itemdefs.tools, 'tool_type' }, + instrument = { df.itemdef_instrumentst, itemdefs.instruments, 'instrument_type' }, + armor = { df.itemdef_armorst, itemdefs.armor, 'armor_type' }, + ammo = { df.itemdef_ammost, itemdefs.ammo, 'ammo_type' }, + siegeammo = { df.itemdef_siegeammost, itemdefs.siege_ammo, 'siegeammo_type' }, + gloves = { df.itemdef_glovest, itemdefs.gloves, 'gloves_type' }, + shoes = { df.itemdef_shoest, itemdefs.shoes, 'shoes_type' }, + shield = { df.itemdef_shieldst, itemdefs.shields, 'shield_type' }, + helm = { df.itemdef_helmst, itemdefs.helms, 'helm_type' }, + pants = { df.itemdef_pantsst, itemdefs.pants, 'pants_type' }, + food = { df.itemdef_foodst, itemdefs.food }, +} + +function add_to_civ(entity, bvec, id) + for _,v in ipairs(entity.resources[bvec]) do + if v == id then + return + end + end + + entity.resources[bvec]:insert('#', id) +end + +function add_to_dwarf_civs(btype, id) + local typeinfo = item_types[btype] + if not typeinfo[3] then + print('Not adding to civs.') + end + + for _,entity in ipairs(df.global.world.entities.all) do + if entity.race == df.global.ui.race_id then + add_to_civ(entity, typeinfo[3], id) + end + end +end + +function inject_item(btype, name) + for _,v in ipairs(itemdefs.all) do + if v.id == name then + print('Itemdef '..name..' already exists.') + return + end + end + + print('Injecting item '..name) + changed = true + + local typeinfo = item_types[btype] + local vec = typeinfo[2] + local id = #vec + + vec:insert('#', { + new = typeinfo[1], + id = name, + subtype = id, + name = name, + name_plural = name, + }) + + itemdefs.all:insert('#', vec[id]) + + add_to_dwarf_civs(btype, id) +end + +local args = {...} +local mode = nil +local ops = {} + +for _,kv in ipairs(args) do + if mode and string.match(kv, '^[%u_]+$') then + table.insert(ops, curry(mode, kv)) + elseif kv == 'reaction' then + mode = inject_reaction + elseif building_types[kv] then + mode = curry(inject_building, kv) + elseif item_types[kv] then + mode = curry(inject_item, kv) + else + qerror('Invalid option: '..kv) + end +end + +if #ops > 0 then + print('') + for _,v in ipairs(ops) do + v() + end +end + +if changed then + print('\nNow without unpausing save and reload the game to re-read raws.') +else + print('\nNo changes made.') +end From 038d62367eea44ba625989b8cd4c1380ba544607 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 22 Sep 2012 13:14:06 +0400 Subject: [PATCH 045/196] Implement explicit hook priority in vmethod interpose. This resolves a getName order conflict between power-meter and rename. --- library/VTableInterpose.cpp | 40 ++++++++++++++++++++++++++----- library/include/VTableInterpose.h | 19 ++++++++++----- plugins/rename.cpp | 2 +- 3 files changed, 48 insertions(+), 13 deletions(-) diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 046425653..48ae6109c 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -247,8 +247,9 @@ void VMethodInterposeLinkBase::set_chain(void *chain) addr_to_method_pointer_(chain_mptr, chain); } -VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr) - : host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method), chain_mptr(chain_mptr), +VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority) + : host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method), + chain_mptr(chain_mptr), priority(priority), applied(false), saved_chain(NULL), next(NULL), prev(NULL) { if (vmethod_idx < 0 || interpose_method == NULL) @@ -349,15 +350,26 @@ bool VMethodInterposeLinkBase::apply(bool enable) return false; // Retrieve the current vtable entry - void *old_ptr = host->get_vmethod_ptr(vmethod_idx); VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx]; + VMethodInterposeLinkBase *next_link = NULL; + while (old_link && old_link->host == host && old_link->priority > priority) + { + next_link = old_link; + old_link = old_link->prev; + } + + void *old_ptr = next_link ? next_link->saved_chain : host->get_vmethod_ptr(vmethod_idx); assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr)); // Apply the new method ptr set_chain(old_ptr); - if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) + if (next_link) + { + next_link->set_chain(interpose_method); + } + else if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) { set_chain(NULL); return false; @@ -365,8 +377,13 @@ bool VMethodInterposeLinkBase::apply(bool enable) // Push the current link into the home host applied = true; - host->interpose_list[vmethod_idx] = this; prev = old_link; + next = next_link; + + if (next_link) + next_link->prev = this; + else + host->interpose_list[vmethod_idx] = this; child_hosts.clear(); child_next.clear(); @@ -374,13 +391,22 @@ bool VMethodInterposeLinkBase::apply(bool enable) if (old_link && old_link->host == host) { // If the old link is home, just push into the plain chain - assert(old_link->next == NULL); + assert(old_link->next == next_link); old_link->next = this; // Child links belong to the topmost local entry child_hosts.swap(old_link->child_hosts); child_next.swap(old_link->child_next); } + else if (next_link) + { + if (old_link) + { + assert(old_link->child_next.count(next_link)); + old_link->child_next.erase(next_link); + old_link->child_next.insert(this); + } + } else { // If creating a new local chain, find children with same vmethod @@ -401,6 +427,8 @@ bool VMethodInterposeLinkBase::apply(bool enable) } } + assert (!next_link || (child_next.empty() && child_hosts.empty())); + // Chain subclass hooks for (auto it = child_next.begin(); it != child_next.end(); ++it) { diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h index 7ba6b67aa..aeb407a8c 100644 --- a/library/include/VTableInterpose.h +++ b/library/include/VTableInterpose.h @@ -87,7 +87,8 @@ namespace DFHack with code defined by DFHack, while retaining ability to call the original code. The API can be safely used from plugins, and multiple hooks for the same vmethod are - automatically chained in undefined order. + automatically chained (subclass before superclass; at same + level highest priority called first; undefined order otherwise). Usage: @@ -105,6 +106,8 @@ namespace DFHack }; IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo); + or + IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority); void init() { if (!INTERPOSE_HOOK(my_hack, foo).apply()) @@ -121,9 +124,11 @@ namespace DFHack static DFHack::VMethodInterposeLink interpose_##name; \ rtype interpose_fn_##name args -#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) \ +#define IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,priority) \ DFHack::VMethodInterposeLink \ - class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name); + class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name, priority); + +#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,0) #define INTERPOSE_NEXT(name) (this->*interpose_##name.chain) #define INTERPOSE_HOOK(class, name) (class::interpose_##name) @@ -140,6 +145,7 @@ namespace DFHack int vmethod_idx; void *interpose_method; // Pointer to the code of the interposing method void *chain_mptr; // Pointer to the chain field below + int priority; bool applied; void *saved_chain; // Previous pointer to the code @@ -155,7 +161,7 @@ namespace DFHack VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id); void find_child_hosts(virtual_identity *cur, void *vmptr); public: - VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr); + VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority); ~VMethodInterposeLinkBase(); bool is_applied() { return applied; } @@ -171,12 +177,13 @@ namespace DFHack operator Ptr () { return chain; } template - VMethodInterposeLink(Ptr target, Ptr2 src) + VMethodInterposeLink(Ptr target, Ptr2 src, int priority) : VMethodInterposeLinkBase( &Base::_identity, vmethod_pointer_to_idx(target), method_pointer_to_addr(src), - &chain + &chain, + priority ) { src = target; /* check compatibility */ } }; diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 059a4be62..ecebbb90c 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -130,7 +130,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) INTERPOSE_NEXT(getName)(buf); \ } \ }; \ - IMPLEMENT_VMETHOD_INTERPOSE(cname##_hook, getName); + IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cname##_hook, getName, 100); KNOWN_BUILDINGS #undef BUILDING From ebd4b94c2d26f18bfde389fed910eb69dccd7ced Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 22 Sep 2012 11:57:33 +0200 Subject: [PATCH 046/196] ruby: add onupdate_register_once --- plugins/ruby/ruby.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index ef6af04e7..aeae101d3 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -81,6 +81,13 @@ module DFHack end end + # same as onupdate_register, but remove the callback once it returns true + def onupdate_register_once(*a) + handle = onupdate_register(*a) { + onupdate_unregister(handle) if yield + } + end + TICKS_PER_YEAR = 1200*28*12 # this method is called by dfhack every 'onupdate' if onupdate_active is true def onupdate From 6f67a71e00fb319cd257f89f61af368958488664 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 22 Sep 2012 14:52:08 +0400 Subject: [PATCH 047/196] Search for cur_season and cur_season_tick in devel/find-offsets. --- library/LuaTools.cpp | 4 +- scripts/devel/find-offsets.lua | 96 ++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index a283d215c..8fce0076f 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1814,7 +1814,9 @@ void DFHack::Lua::Core::onUpdate(color_ostream &out) lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); run_timers(out, State, frame_timers, frame[1], ++frame_idx); - run_timers(out, State, tick_timers, frame[1], world->frame_counter); + + if (world) + run_timers(out, State, tick_timers, frame[1], world->frame_counter); } void DFHack::Lua::Core::Init(color_ostream &out) diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index 6fc127351..07a058474 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -803,6 +803,19 @@ end -- cur_year_tick -- +function stop_autosave() + if is_known 'd_init' then + local f = df.global.d_init.flags4 + if f.AUTOSAVE_SEASONAL or f.AUTOSAVE_YEARLY then + f.AUTOSAVE_SEASONAL = false + f.AUTOSAVE_YEARLY = false + print('Disabled seasonal and yearly autosave.') + end + else + dfhack.printerr('Could not disable autosave!') + end +end + local function find_cur_year_tick() local zone if os_type == 'windows' then @@ -815,6 +828,8 @@ local function find_cur_year_tick() return end + stop_autosave() + local addr = zone:find_counter([[ Searching for cur_year_tick. Please exit to main dwarfmode menu, then do as instructed below:]], @@ -824,6 +839,79 @@ menu, then do as instructed below:]], ms.found_offset('cur_year_tick', addr) end +-- +-- cur_season_tick +-- + +function step_n_frames(cnt) + local world = df.global.world + local ctick = world.frame_counter + local more = '' + while world.frame_counter-ctick < cnt do + print(" Please step the game "..(cnt-world.frame_counter+ctick)..more.." frames.") + more = ' more' + if not utils.prompt_yes_no(' Done?', true) then + return nil + end + end + return world.frame_counter-ctick +end + +local function find_cur_season_tick() + if not (is_known 'cur_year_tick') then + dfhack.printerr('Cannot search for cur_season_tick - prerequisites missing.') + return + end + + stop_autosave() + + local addr = searcher:find_interactive([[ +Searching for cur_season_tick. Please exit to main dwarfmode +menu, then do as instructed below:]], + 'int32_t', + function(ccursor) + if ccursor > 0 then + if not step_n_frames(10) then + return false + end + end + return true, math.floor(((df.global.cur_year_tick+10)%100800)/10) + end + ) + ms.found_offset('cur_season_tick', addr) +end + +-- +-- cur_season +-- + +local function find_cur_season() + if not (is_known 'cur_year_tick' and is_known 'cur_season_tick') then + dfhack.printerr('Cannot search for cur_season - prerequisites missing.') + return + end + + stop_autosave() + + local addr = searcher:find_interactive([[ +Searching for cur_season. Please exit to main dwarfmode +menu, then do as instructed below:]], + 'int8_t', + function(ccursor) + if ccursor > 0 then + local cst = df.global.cur_season_tick + df.global.cur_season_tick = 10079 + df.global.cur_year_tick = df.global.cur_year_tick + (10079-cst)*10 + if not step_n_frames(10) then + return false + end + end + return true, math.floor((df.global.cur_year_tick+10)/100800)%4 + end + ) + ms.found_offset('cur_season', addr) +end + -- -- process_jobs -- @@ -839,6 +927,8 @@ end local function find_process_jobs() local zone = get_process_zone() or searcher + stop_autosave() + local addr = zone:find_menu_cursor([[ Searching for process_jobs. Please do as instructed below:]], 'int8_t', @@ -856,6 +946,8 @@ end local function find_process_dig() local zone = get_process_zone() or searcher + stop_autosave() + local addr = zone:find_menu_cursor([[ Searching for process_dig. Please do as instructed below:]], 'int8_t', @@ -879,6 +971,8 @@ local function find_pause_state() end zone = zone or searcher + stop_autosave() + local addr = zone:find_menu_cursor([[ Searching for pause_state. Please do as instructed below:]], 'int8_t', @@ -930,6 +1024,8 @@ print('\nUnpausing globals:\n') exec_finder(find_cur_year, 'cur_year') exec_finder(find_cur_year_tick, 'cur_year_tick') +exec_finder(find_cur_season_tick, 'cur_season_tick') +exec_finder(find_cur_season, 'cur_season') exec_finder(find_process_jobs, 'process_jobs') exec_finder(find_process_dig, 'process_dig') exec_finder(find_pause_state, 'pause_state') From 825d21c91a24f7f95bf05ad10b54845054a36411 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 22 Sep 2012 21:00:13 +0400 Subject: [PATCH 048/196] Add a script to wake up units and stop breaks & parties. --- NEWS | 1 + README.rst | 8 ++++ scripts/siren.lua | 109 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 scripts/siren.lua diff --git a/NEWS b/NEWS index 22f64d7d6..d46c47e62 100644 --- a/NEWS +++ b/NEWS @@ -30,6 +30,7 @@ DFHack v0.34.11-r2 (UNRELEASED) New scripts: - fixnaked: removes thoughts about nakedness. - setfps: set FPS cap at runtime, in case you want slow motion or speed-up. + - siren: wakes up units, stops breaks and parties - but causes bad thoughts. - fix/population-cap: run after every migrant wave to prevent exceeding the cap. - fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp. New GUI scripts: diff --git a/README.rst b/README.rst index b0258bb30..97eb61a8f 100644 --- a/README.rst +++ b/README.rst @@ -1539,6 +1539,14 @@ setfps Run ``setfps `` to set the FPS cap at runtime, in case you want to watch combat in slow motion or something :) +siren +===== + +Wakes up sleeping units, cancels breaks and stops parties either everywhere, +or in the burrows given as arguments. In return, adds bad thoughts about +noise and tiredness. Also, the units with interrupted breaks will go on +break again a lot sooner. + growcrops ========= Instantly grow seeds inside farming plots. diff --git a/scripts/siren.lua b/scripts/siren.lua new file mode 100644 index 000000000..5371e3d7b --- /dev/null +++ b/scripts/siren.lua @@ -0,0 +1,109 @@ +-- Wakes up the sleeping, breaks up parties and stops breaks. + +local utils = require 'utils' + +local args = {...} +local burrows = {} +local bnames = {} + +if not dfhack.isMapLoaded() then + qerror('Map is not loaded.') +end + +for _,v in ipairs(args) do + local b = dfhack.burrows.findByName(v) + if not b then + qerror('Unknown burrow: '..v) + end + table.insert(bnames, v) + table.insert(burrows, b) +end + +function is_in_burrows(pos) + if #burrows == 0 then + return true + end + for _,v in ipairs(burrows) do + if dfhack.burrows.isAssignedTile(v, pos) then + return true + end + end +end + +function add_thought(unit, code) + for _,v in ipairs(unit.status.recent_events) do + if v.type == code then + v.age = 0 + return + end + end + + unit.status.recent_events:insert('#', { new = true, type = code }) +end + +function wake_unit(unit) + local job = unit.job.current_job + if not job or job.job_type ~= df.job_type.Sleep then + return + end + + if job.completion_timer > 0 then + unit.counters.unconscious = 0 + add_thought(unit, df.unit_thought_type.SleepNoiseWake) + elseif job.completion_timer < 0 then + add_thought(unit, df.unit_thought_type.Tired) + end + + job.pos:assign(unit.pos) + + job.completion_timer = 0 + + unit.path.dest:assign(unit.pos) + unit.path.path.x:resize(0) + unit.path.path.y:resize(0) + unit.path.path.z:resize(0) + + unit.counters.job_counter = 0 +end + +function stop_break(unit) + local counter = dfhack.units.getMiscTrait(unit, df.misc_trait_type.OnBreak) + if counter then + counter.id = df.misc_trait_type.TimeSinceBreak + counter.value = 100800 - 30*1200 + add_thought(unit, df.unit_thought_type.Tired) + end +end + +-- Stop rest +for _,v in ipairs(df.global.world.units.active) do + local x,y,z = dfhack.units.getPosition(v) + if x and not dfhack.units.isDead(v) and is_in_burrows(xyz2pos(x,y,z)) then + wake_unit(v) + stop_break(v) + end +end + +-- Stop parties +for _,v in ipairs(df.global.ui.parties) do + local pos = utils.getBuildingCenter(v.location) + if is_in_burrows(pos) then + v.timer = 0 + for _, u in ipairs(v.units) do + add_thought(unit, df.unit_thought_type.Tired) + end + end +end + +local place = 'the halls and tunnels' +if #bnames > 0 then + if #bnames == 1 then + place = bnames[1] + else + place = table.concat(bnames,', ',1,#bnames-1)..' and '..bnames[#bnames] + end +end +dfhack.gui.showAnnouncement( + 'A loud siren sounds throughout '..place..', waking the sleeping and startling the awake.', + COLOR_BROWN, true +) From f7272e4a8f09d11ee41b068277de1f61993cfaa8 Mon Sep 17 00:00:00 2001 From: jj Date: Sun, 23 Sep 2012 10:58:31 +0200 Subject: [PATCH 049/196] ruby: use cursor as default map_tile_at arg --- plugins/ruby/map.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index d662a3436..4e8bf9100 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -26,7 +26,7 @@ module DFHack end end - def map_tile_at(x, y=nil, z=nil) + def map_tile_at(x=df.cursor, y=nil, z=nil) x = x.pos if x.respond_to?(:pos) x, y, z = x.x, x.y, x.z if x.respond_to?(:x) b = map_block_at(x, y, z) From f2199c2951e195a8ee496fd75ee5b512d6e3e15e Mon Sep 17 00:00:00 2001 From: jj Date: Sun, 23 Sep 2012 11:44:27 +0200 Subject: [PATCH 050/196] ruby: fix activity zone_num --- plugins/ruby/building.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 68229c007..3f8842b44 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -299,9 +299,13 @@ module DFHack # construct an abstract building (stockpile, farmplot, ...) def building_construct_abstract(bld) - if bld.getType == :Stockpile + case bld.getType + when :Stockpile max = df.world.buildings.other[:STOCKPILE].map { |s| s.stockpile_number }.max bld.stockpile_number = max.to_i + 1 + when :Civzone + max = df.world.buildings.other[:ANY_ZONE].map { |z| z.zone_num }.max + bld.zone_num = max.to_i + 1 end building_link bld if !bld.flags.exists From 6385128c2854371369763e8c33e399b5da54908b Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 23 Sep 2012 16:41:14 +0400 Subject: [PATCH 051/196] Sort by units.active (arrival), and seek to top/bottom in manipulator. --- plugins/manipulator.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index a3ec72a47..0b0b99d48 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -247,12 +247,14 @@ struct UnitInfo string transname; string profession; int8_t color; + int active_index; }; enum altsort_mode { ALTSORT_NAME, ALTSORT_PROFESSION, ALTSORT_HAPPINESS, + ALTSORT_ARRIVAL, ALTSORT_MAX }; @@ -284,6 +286,14 @@ bool sortByHappiness (const UnitInfo *d1, const UnitInfo *d2) return (d1->unit->status.happiness < d2->unit->status.happiness); } +bool sortByArrival (const UnitInfo *d1, const UnitInfo *d2) +{ + if (descending) + return (d1->active_index > d2->active_index); + else + return (d1->active_index < d2->active_index); +} + bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) { if (sort_skill != job_skill::NONE) @@ -366,12 +376,18 @@ protected: viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cursor_pos) { + std::map active_idx; + auto &active = world->units.active; + for (size_t i = 0; i < active.size(); i++) + active_idx[active[i]] = i; + for (size_t i = 0; i < src.size(); i++) { UnitInfo *cur = new UnitInfo; df::unit *unit = src[i]; cur->unit = unit; cur->allowEdit = true; + cur->active_index = active_idx[unit]; if (unit->race != ui->race_id) cur->allowEdit = false; @@ -526,6 +542,15 @@ void viewscreen_unitlaborsst::feed(set *events) if (events->count(interface_key::CURSOR_DOWN_FAST) || events->count(interface_key::CURSOR_DOWNLEFT_FAST) || events->count(interface_key::CURSOR_DOWNRIGHT_FAST)) sel_row += 10; + if ((sel_row > 0) && events->count(interface_key::CURSOR_UP_Z_AUX)) + { + sel_row = 0; + } + if ((sel_row < units.size()-1) && events->count(interface_key::CURSOR_DOWN_Z_AUX)) + { + sel_row = units.size()-1; + } + if (sel_row < 0) sel_row = 0; if (sel_row > units.size() - 1) @@ -759,6 +784,9 @@ void viewscreen_unitlaborsst::feed(set *events) case ALTSORT_HAPPINESS: std::sort(units.begin(), units.end(), sortByHappiness); break; + case ALTSORT_ARRIVAL: + std::sort(units.begin(), units.end(), sortByArrival); + break; } } if (events->count(interface_key::CHANGETAB)) @@ -772,6 +800,9 @@ void viewscreen_unitlaborsst::feed(set *events) altsort = ALTSORT_HAPPINESS; break; case ALTSORT_HAPPINESS: + altsort = ALTSORT_ARRIVAL; + break; + case ALTSORT_ARRIVAL: altsort = ALTSORT_NAME; break; } @@ -1015,6 +1046,9 @@ void viewscreen_unitlaborsst::render() case ALTSORT_HAPPINESS: OutputString(15, x, gps->dimy - 2, "Happiness"); break; + case ALTSORT_ARRIVAL: + OutputString(15, x, gps->dimy - 2, "Arrival"); + break; default: OutputString(15, x, gps->dimy - 2, "Unknown"); break; From aefedb98872d6eb34fee1ba3d7a7c7994d49f12e Mon Sep 17 00:00:00 2001 From: jj Date: Sun, 23 Sep 2012 23:26:57 +0200 Subject: [PATCH 052/196] ruby: MapTile#offset, tweak #dig to check for existing jobs --- plugins/ruby/map.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index 4e8bf9100..9629c3f9e 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -67,6 +67,14 @@ module DFHack @mapblock = b end + def offset(dx, dy=nil, dz=0) + if dx.respond_to?(:x) + dz = dx.z if dx.respond_to?(:z) + dx, dy = dx.x, dx.y + end + df.map_tile_at(@x+dx, @y+dy, @z+dz) + end + def designation @mapblock.designation[@dx][@dy] end @@ -191,16 +199,20 @@ module DFHack def dig(mode=:Default) if mode == :Smooth if tilemat != :SOIL and caption !~ /smooth|pillar|fortification/i and # XXX caption.. - designation.smooth == 0 and not df.world.job_list.find { |j| + designation.smooth == 0 and (designation.hidden or not df.world.job_list.find { |j| # the game removes 'smooth' designation as soon as it assigns a job, if we # re-set it the game may queue another :DetailWall that will carve a fortification (j.job_type == :DetailWall or j.job_type == :DetailFloor) and df.same_pos?(j, self) - } + }) designation.dig = :No designation.smooth = 1 mapblock.flags.designated = true end else + return if mode != :No and designation.dig == :No and not designation.hidden and df.world.job_list.find { |j| + # someone already enroute to dig here, avoid 'Inappropriate dig square' spam + JobType::Type[j.job_type] == :Digging and df.same_pos?(j, self) + } designation.dig = mode mapblock.flags.designated = true if mode != :No end From b665c344daf0403587b05fca67f575325472c8f4 Mon Sep 17 00:00:00 2001 From: jj Date: Sun, 23 Sep 2012 23:37:57 +0200 Subject: [PATCH 053/196] add deathcause to README --- README.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index e495b1584..73147d061 100644 --- a/README.rst +++ b/README.rst @@ -1052,7 +1052,7 @@ Export the current loaded map as a file. This will be eventually usable with visualizers. dwarfexport -----------= +----------- Export dwarves to RuneSmith-compatible XML. @@ -1632,7 +1632,7 @@ With no argument, this command shows an help message and list existing sources. digfort -========= +======= A script to designate an area for digging according to a plan in csv format. This script, inspired from quickfort, can designate an area for digging. @@ -1668,6 +1668,12 @@ drainaquifer ============ Remove all 'aquifer' tag from the map blocks. Irreversible. +deathcause +========== +Focus a body part ingame, and this script will display the cause of death of +the creature. + + ======================= In-game interface tools ======================= From 8e2515881128557739c7657c08c2597b131850ef Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 23 Sep 2012 19:40:04 -0500 Subject: [PATCH 054/196] Sync up df-structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index d52c7181f..e6a59bfeb 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d52c7181fb439a5fead143188d17d659d82e7f89 +Subproject commit e6a59bfebd56e209a0719c173f93a33262751528 From a2bb3223440a0c50768a53e6cd01a2ac450cc175 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 24 Sep 2012 15:40:35 +0200 Subject: [PATCH 055/196] ruby: item_find now works in z/stocks --- plugins/ruby/item.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/item.rb b/plugins/ruby/item.rb index 032c0d8cf..fc840f7ad 100644 --- a/plugins/ruby/item.rb +++ b/plugins/ruby/item.rb @@ -4,9 +4,14 @@ module DFHack # arg similar to unit.rb/unit_find; no arg = 'k' menu def item_find(what=:selected, y=nil, z=nil) if what == :selected - if curview._rtti_classname == :viewscreen_itemst + case curview._rtti_classname + when :viewscreen_itemst ref = curview.entry_ref[curview.cursor_pos] ref.item_tg if ref.kind_of?(GeneralRefItem) + when :viewscreen_storesst # z/stocks + if curview.in_group_mode == 0 and curview.in_right_list == 1 + curview.items[curview.item_cursor] + end else case ui.main.mode when :LookAround From 627f0368306273e21abe35d938d8fd7d43583553 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 24 Sep 2012 19:13:33 +0400 Subject: [PATCH 056/196] Implement a special command parsing mode with one verbatim argument. Intended for script expressions, e.g. rb_eval. --- README.rst | 87 +++++++++----- Readme.html | 304 +++++++++++++++++++++++++++++------------------ library/Core.cpp | 28 ++++- 3 files changed, 277 insertions(+), 142 deletions(-) diff --git a/README.rst b/README.rst index 73147d061..5202b92a2 100644 --- a/README.rst +++ b/README.rst @@ -148,6 +148,19 @@ for context ``foo/bar/baz``, possible matches are any of ``@foo/bar/baz``, ``@fo Commands ======== +DFHack command syntax consists of a command name, followed by arguments separated +by whitespace. To include whitespace in an argument, quote it in double quotes. +To include a double quote character, use ``\"`` inside double quotes. + +If the first non-whitespace character of a line is ``#``, the line is treated +as a comment, i.e. a silent no-op command. + +If the first non-whitespace character is ``:``, the command is parsed in a special +alternative mode: first, non-whitespace characters immediately following the ``:`` +are used as the command name; the remaining part of the line, starting with the first +non-whitespace character *after* the command name, is used verbatim as the first argument. +This is intended for commands like ``rb_eval`` that evaluate script language statements. + Almost all the commands support using the 'help ' built-in command to retrieve further help without having to look at this document. Alternatively, some accept a 'help'/'?' option on their command line. @@ -1636,8 +1649,8 @@ digfort A script to designate an area for digging according to a plan in csv format. This script, inspired from quickfort, can designate an area for digging. -Your plan should be stored in a .csv file like this: -:: +Your plan should be stored in a .csv file like this:: + # this is a comment d;d;u;d;d;skip this tile;d d;d;d;i @@ -1656,8 +1669,8 @@ superdwarf ========== Similar to fastdwarf, per-creature. -To make any creature superfast, target it ingame using 'v' and: -:: +To make any creature superfast, target it ingame using 'v' and:: + superdwarf add Other options available: ``del``, ``clear``, ``list``. @@ -1748,16 +1761,6 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter re-entering the mechanisms ui. -Power Meter -=========== - -Front-end to the power-meter plugin implemented by the gui/power-meter script. Bind to a -key and activate after selecting Pressure Plate in the build menu. - -The script follows the general look and feel of the regular pressure plate build -configuration page, but configures parameters relevant to the modded power meter building. - - Rename ====== @@ -1786,11 +1789,25 @@ The script lists other rooms owned by the same owner, or by the unit selected in list, and allows unassigning them. +============= +Behavior Mods +============= + +These plugins, when activated via configuration UI or by detecting certain +structures in RAWs, modify the game engine behavior concerning the target +objects to add features not otherwise present. + + Siege Engine ============ -Front-end to the siege-engine plugin implemented by the gui/siege-engine script. Bind to a -key and activate after selecting a siege engine in 'q' mode. +The siege-engine plugin enables siege engines to be linked to stockpiles, and +aimed at an arbitrary rectangular area across Z levels, instead of the original +four directions. Also, catapults can be ordered to load arbitrary objects, not +just stones. + +The configuration front-end to the plugin is implemented by the gui/siege-engine +script. Bind it to a key and activate after selecting a siege engine in 'q' mode. The main mode displays the current target, selected ammo item type, linked stockpiles and the allowed operator skill range. The map tile color is changed to signify if it can be @@ -1799,7 +1816,7 @@ yellow for partially blocked. Pressing 'r' changes into the target selection mode, which works by highlighting two points with Enter like all designations. When a target area is set, the engine projectiles are -aimed at that area, or units within it, instead of the vanilla four directions. +aimed at that area, or units within it. After setting the target in this way for one engine, you can 'paste' the same area into others just by pressing 'p' in the main page of this script. The area to paste is kept until you quit @@ -1813,17 +1830,23 @@ menu. .. admonition:: DISCLAIMER - Siege engines are a very interesting feature, but currently nearly useless + Siege engines are a very interesting feature, but sadly almost useless in the current state because they haven't been updated since 2D and can only aim in four directions. This is an attempt to bring them more up to date until Toady has time to work on it. Actual improvements, e.g. like making siegers bring their own, are something only Toady can do. -========= -RAW hacks -========= +Power Meter +=========== + +The power-meter plugin implements a modified pressure plate that detects power being +supplied to gear boxes built in the four adjacent N/S/W/E tiles. + +The configuration front-end is implemented by the gui/power-meter script. Bind it to a +key and activate after selecting Pressure Plate in the build menu. -These plugins detect certain structures in RAWs, and enhance them in various ways. +The script follows the general look and feel of the regular pressure plate build +configuration page, but configures parameters relevant to the modded power meter building. Steam Engine @@ -1855,7 +1878,7 @@ The magma version also needs magma. .. admonition:: ISSUE Since this building is a machine, and machine collapse - code cannot be modified, it would collapse over true open space. + code cannot be hooked, it would collapse over true open space. As a loophole, down stair provides support to machines, while being passable, so use them. @@ -1866,7 +1889,7 @@ is extracted from the workshop raws. .. admonition:: ISSUE Like with collapse above, part of the code involved in - machine connection cannot be modified. As a result, the workshop + machine connection cannot be hooked. As a result, the workshop can only immediately connect to machine components built AFTER it. This also means that engines cannot be chained without intermediate short axles that can be built later than both of the engines. @@ -1879,8 +1902,13 @@ on repeat). A furnace operator will come, possibly bringing a bar of fuel, and perform it. As a result, a "boiling water" item will appear in the 't' view of the workshop. -**NOTE**: The completion of the job will actually consume one unit -of the appropriate liquids from below the workshop. +.. note:: + + The completion of the job will actually consume one unit + of the appropriate liquids from below the workshop. This means + that you cannot just raise 7 units of magma with a piston and + have infinite power. However, liquid consumption should be slow + enough that water can be supplied by a pond zone bucket chain. Every such item gives 100 power, up to a limit of 300 for coal, and 500 for a magma engine. The building can host twice that @@ -1928,9 +1956,12 @@ Add Spatter =========== This plugin makes reactions with names starting with ``SPATTER_ADD_`` -produce contaminants on the items instead of improvements. +produce contaminants on the items instead of improvements. The produced +contaminants are immune to being washed away by water or destroyed by +the ``clean items`` command. -Intended to give some use to all those poisons that can be bought from caravans. +The plugin is intended to give some use to all those poisons that can +be bought from caravans. :) To be really useful this needs patches from bug 808, ``tweak fix-dimensions`` and ``tweak advmode-contained``. diff --git a/Readme.html b/Readme.html index be6856fb6..7aebeb13a 100644 --- a/Readme.html +++ b/Readme.html @@ -432,72 +432,78 @@ access DF memory and allow for easier development of new tools.

  • Visualizer and data export
  • -
  • Job management
  • Commands

    +

    DFHack command syntax consists of a command name, followed by arguments separated +by whitespace. To include whitespace in an argument, quote it in double quotes. +To include a double quote character, use \" inside double quotes.

    +

    If the first non-whitespace character of a line is #, the line is treated +as a comment, i.e. a silent no-op command.

    +

    If the first non-whitespace character is :, the command is parsed in a special +alternative mode: first, non-whitespace characters immediately following the : +are used as the command name; the remaining part of the line, starting with the first +non-whitespace character after the command name, is used verbatim as the first argument. +This is intended for commands like rb_eval that evaluate script language statements.

    Almost all the commands support using the 'help <command-name>' built-in command to retrieve further help without having to look at this document. Alternatively, some accept a 'help'/'?' option on their command line.

    @@ -1782,15 +1798,16 @@ thread: mapexport

    Export the current loaded map as a file. This will be eventually usable with visualizers.

    -

    dwarfexport -----------= -Export dwarves to RuneSmith-compatible XML.

    +
    +
    +

    dwarfexport

    +

    Export dwarves to RuneSmith-compatible XML.

    -

    Job management

    +

    Job management

    -

    job

    +

    job

    Command for general job query and manipulation.

    Options:
    @@ -1809,7 +1826,7 @@ in a workshop, or the unit/jobs screen.
    -

    job-material

    +

    job-material

    Alter the material of the selected job.

    Invoked as:

    @@ -1827,7 +1844,7 @@ over the first available choice with the matching material.
     
     
    -

    job-duplicate

    +

    job-duplicate

    Duplicate the selected job in a workshop:
      @@ -1838,7 +1855,7 @@ instantly duplicates the job.
    -

    workflow

    +

    workflow

    Manage control of repeat jobs.

    Usage:

    @@ -1862,7 +1879,7 @@ Otherwise, enables or disables any of the following options:

    -

    Function

    +

    Function

    When the plugin is enabled, it protects all repeat jobs from removal. If they do disappear due to any cause, they are immediately re-added to their workshop and suspended.

    @@ -1873,7 +1890,7 @@ the amount has to drop before jobs are resumed; this is intended to reduce the frequency of jobs being toggled.

    -

    Constraint examples

    +

    Constraint examples

    Keep metal bolts within 900-1000, and wood/bone within 150-200.

     workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
    @@ -1912,15 +1929,15 @@ command.
     
    -

    Fortress activity management

    +

    Fortress activity management

    -

    seedwatch

    +

    seedwatch

    Tool for turning cooking of seeds and plants on/off depending on how much you have of them.

    See 'seedwatch help' for detailed description.

    -

    zone

    +

    zone

    Helps a bit with managing activity zones (pens, pastures and pits) and cages.

    Options:

    @@ -2019,7 +2036,7 @@ for war/hunt). Negatable.
    -

    Usage with single units

    +

    Usage with single units

    One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units @@ -2028,7 +2045,7 @@ and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way.

    -

    Usage with filters

    +

    Usage with filters

    All filters can be used together with the 'assign' command.

    Restrictions: It's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. @@ -2046,14 +2063,14 @@ are not properly added to your own stocks; slaughtering them should work).

    Most filters can be negated (e.g. 'not grazer' -> race is not a grazer).

    -

    Mass-renaming

    +

    Mass-renaming

    Using the 'nick' command you can set the same nickname for multiple units. If used without 'assign', 'all' or 'count' it will rename all units in the current default target zone. Combined with 'assign', 'all' or 'count' (and further optional filters) it will rename units matching the filter conditions.

    -

    Cage zones

    +

    Cage zones

    Using the 'tocages' command you can assign units to a set of cages, for example a room next to your butcher shop(s). They will be spread evenly among available cages to optimize hauling to and butchering from them. For this to work you need @@ -2064,7 +2081,7 @@ would make no sense, but can be used together with 'nick' or 'remnick' and all the usual filters.

    -

    Examples

    +

    Examples

    zone assign all own ALPACA minage 3 maxage 10
    Assign all own alpacas who are between 3 and 10 years old to the selected @@ -2090,7 +2107,7 @@ on the current default zone.
    -

    autonestbox

    +

    autonestbox

    Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless @@ -2119,7 +2136,7 @@ frames between runs.

    -

    autobutcher

    +

    autobutcher

    Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units will be processed.

    Named units will be completely ignored (to protect specific animals from @@ -2227,7 +2244,7 @@ autobutcher.bat

    -

    autolabor

    +

    autolabor

    Automatically manage dwarf labors.

    When enabled, autolabor periodically checks your dwarves and enables or disables labors. It tries to keep as many dwarves as possible busy but @@ -2241,14 +2258,14 @@ while it is enabled.

    -

    Other

    +

    Other

    -

    catsplosion

    +

    catsplosion

    Makes cats just multiply. It is not a good idea to run this more than once or twice.

    -

    dfusion

    +

    dfusion

    This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin.

    See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15

    Confirmed working DFusion plugins:

    @@ -2272,7 +2289,7 @@ twice.

    -

    Scripts

    +

    Scripts

    Lua or ruby scripts placed in the hack/scripts/ directory are considered for execution as if they were native DFHack commands. They are listed at the end of the 'ls' command output.

    @@ -2281,7 +2298,7 @@ only be listed by ls if called as 'ls -a'. This is intended as a way to hide scripts that are obscure, developer-oriented, or should be used as keybindings.

    Some notable scripts:

    -

    fix/*

    +

    fix/*

    Scripts in this subdirectory fix various bugs and issues, some of them obscure.

    • fix/dead-units

      @@ -2307,22 +2324,29 @@ caused by autodump bugs or other hacking mishaps.

    -

    gui/*

    +

    gui/*

    Scripts that implement dialogs inserted into the main game window are put in this directory.

    -

    quicksave

    +

    quicksave

    If called in dwarf mode, makes DF immediately auto-save the game by setting a flag normally used in seasonal auto-save.

    -

    setfps

    +

    setfps

    Run setfps <number> to set the FPS cap at runtime, in case you want to watch combat in slow motion or something :)

    +
    +

    siren

    +

    Wakes up sleeping units, cancels breaks and stops parties either everywhere, +or in the burrows given as arguments. In return, adds bad thoughts about +noise and tiredness. Also, the units with interrupted breaks will go on +break again a lot sooner.

    +
    -

    growcrops

    +

    growcrops

    Instantly grow seeds inside farming plots.

    With no argument, this command list the various seed types currently in use in your farming plots. @@ -2334,7 +2358,7 @@ growcrops plump 40

    -

    removebadthoughts

    +

    removebadthoughts

    This script remove negative thoughts from your dwarves. Very useful against tantrum spirals.

    With a selected unit in 'v' mode, will clear this unit's mind, otherwise @@ -2347,7 +2371,7 @@ you unpause.

    it removed.

    -

    slayrace

    +

    slayrace

    Kills any unit of a given race.

    With no argument, lists the available races.

    With the special argument 'him', targets only the selected creature.

    @@ -2373,7 +2397,7 @@ slayrace elve magma
    -

    magmasource

    +

    magmasource

    Create an infinite magma source on a tile.

    This script registers a map tile as a magma source, and every 12 game ticks that tile receives 1 new unit of flowing magma.

    @@ -2387,13 +2411,50 @@ magmasource here To remove all placed sources, call magmasource stop.

    With no argument, this command shows an help message and list existing sources.

    +
    +

    digfort

    +

    A script to designate an area for digging according to a plan in csv format.

    +

    This script, inspired from quickfort, can designate an area for digging. +Your plan should be stored in a .csv file like this:

    +
    +# this is a comment
    +d;d;u;d;d;skip this tile;d
    +d;d;d;i
    +
    +

    Available tile shapes are named after the 'dig' menu shortcuts: +d for dig, u for upstairs, d downstairs, i updown, +h channel, r upward ramp, x remove designation. +Unrecognized characters are ignored (eg the 'skip this tile' in the sample).

    +

    Empty lines and data after a # are ignored as comments. +To skip a row in your design, use a single ;.

    +

    The script takes the plan filename, starting from the root df folder.

    +
    +
    +

    superdwarf

    +

    Similar to fastdwarf, per-creature.

    +

    To make any creature superfast, target it ingame using 'v' and:

    +
    +superdwarf add
    +
    +

    Other options available: del, clear, list.

    +

    This plugin also shortens the 'sleeping' and 'on break' periods of targets.

    +
    +
    +

    drainaquifer

    +

    Remove all 'aquifer' tag from the map blocks. Irreversible.

    +
    +
    +

    deathcause

    +

    Focus a body part ingame, and this script will display the cause of death of +the creature.

    +
    -

    In-game interface tools

    +

    In-game interface tools

    These tools work by displaying dialogs or overlays in the game window, and are mostly implemented by lua scripts.

    -

    Dwarf Manipulator

    +

    Dwarf Manipulator

    Implemented by the manipulator plugin. To activate, open the unit screen and press 'l'.

    This tool implements a Dwarf Therapist-like interface within the game UI. The @@ -2428,13 +2489,13 @@ cursor onto that cell instead of toggling it. directly to the main dwarf mode screen.

    -

    Liquids

    +

    Liquids

    Implemented by the gui/liquids script. To use, bind to a key and activate in the 'k' mode.

    While active, use the suggested keys to switch the usual liquids parameters, and Enter to select the target area and apply changes.

    -

    Mechanisms

    +

    Mechanisms

    Implemented by the gui/mechanims script. To use, bind to a key and activate in the 'q' mode.

    Lists mechanisms connected to the building, and their links. Navigating the list centers the view on the relevant linked buildings.

    @@ -2442,15 +2503,8 @@ the view on the relevant linked buildings.

    focus on the current one. Shift-Enter has an effect equivalent to pressing Enter, and then re-entering the mechanisms ui.

    -
    -

    Power Meter

    -

    Front-end to the power-meter plugin implemented by the gui/power-meter script. Bind to a -key and activate after selecting Pressure Plate in the build menu.

    -

    The script follows the general look and feel of the regular pressure plate build -configuration page, but configures parameters relevant to the modded power meter building.

    -
    -

    Rename

    +

    Rename

    Backed by the rename plugin, the gui/rename script allows entering the desired name via a simple dialog in the game ui.

      @@ -2466,23 +2520,33 @@ It is also possible to rename zones from the 'i' menu.

      The building or unit are automatically assumed when in relevant ui state.

    -

    Room List

    +

    Room List

    Implemented by the gui/room-list script. To use, bind to a key and activate in the 'q' mode, either immediately or after opening the assign owner page.

    The script lists other rooms owned by the same owner, or by the unit selected in the assign list, and allows unassigning them.

    +
    +
    +

    Behavior Mods

    +

    These plugins, when activated via configuration UI or by detecting certain +structures in RAWs, modify the game engine behavior concerning the target +objects to add features not otherwise present.

    -

    Siege Engine

    -

    Front-end to the siege-engine plugin implemented by the gui/siege-engine script. Bind to a -key and activate after selecting a siege engine in 'q' mode.

    +

    Siege Engine

    +

    The siege-engine plugin enables siege engines to be linked to stockpiles, and +aimed at an arbitrary rectangular area across Z levels, instead of the original +four directions. Also, catapults can be ordered to load arbitrary objects, not +just stones.

    +

    The configuration front-end to the plugin is implemented by the gui/siege-engine +script. Bind it to a key and activate after selecting a siege engine in 'q' mode.

    The main mode displays the current target, selected ammo item type, linked stockpiles and the allowed operator skill range. The map tile color is changed to signify if it can be hit by the selected engine: green for fully reachable, blue for out of range, red for blocked, yellow for partially blocked.

    Pressing 'r' changes into the target selection mode, which works by highlighting two points with Enter like all designations. When a target area is set, the engine projectiles are -aimed at that area, or units within it, instead of the vanilla four directions.

    +aimed at that area, or units within it.

    After setting the target in this way for one engine, you can 'paste' the same area into others just by pressing 'p' in the main page of this script. The area to paste is kept until you quit DF, or select another area manually.

    @@ -2492,22 +2556,27 @@ the script. Shift-ESC retains the current viewport, and also exits from the 'q' menu.

    DISCLAIMER

    -

    Siege engines are a very interesting feature, but currently nearly useless +

    Siege engines are a very interesting feature, but sadly almost useless in the current state because they haven't been updated since 2D and can only aim in four directions. This is an attempt to bring them more up to date until Toady has time to work on it. Actual improvements, e.g. like making siegers bring their own, are something only Toady can do.

    +
    +

    Power Meter

    +

    The power-meter plugin implements a modified pressure plate that detects power being +supplied to gear boxes built in the four adjacent N/S/W/E tiles.

    +

    The configuration front-end is implemented by the gui/power-meter script. Bind it to a +key and activate after selecting Pressure Plate in the build menu.

    +

    The script follows the general look and feel of the regular pressure plate build +configuration page, but configures parameters relevant to the modded power meter building.

    -
    -

    RAW hacks

    -

    These plugins detect certain structures in RAWs, and enhance them in various ways.

    -

    Steam Engine

    +

    Steam Engine

    The steam-engine plugin detects custom workshops with STEAM_ENGINE in their token, and turns them into real steam engines.

    -

    Rationale

    +

    Rationale

    The vanilla game contains only water wheels and windmills as sources of power, but windmills give relatively little power, and water wheels require flowing water, which must either be a real river and thus immovable and @@ -2518,14 +2587,14 @@ it can be done just by combining existing features of the game engine in a new way with some glue code and a bit of custom logic.

    -

    Construction

    +

    Construction

    The workshop needs water as its input, which it takes via a passable floor tile below it, like usual magma workshops do. The magma version also needs magma.

    ISSUE

    Since this building is a machine, and machine collapse -code cannot be modified, it would collapse over true open space. +code cannot be hooked, it would collapse over true open space. As a loophole, down stair provides support to machines, while being passable, so use them.

    @@ -2535,20 +2604,26 @@ is extracted from the workshop raws.

    ISSUE

    Like with collapse above, part of the code involved in -machine connection cannot be modified. As a result, the workshop +machine connection cannot be hooked. As a result, the workshop can only immediately connect to machine components built AFTER it. This also means that engines cannot be chained without intermediate short axles that can be built later than both of the engines.

    -

    Operation

    +

    Operation

    In order to operate the engine, queue the Stoke Boiler job (optionally on repeat). A furnace operator will come, possibly bringing a bar of fuel, and perform it. As a result, a "boiling water" item will appear in the 't' view of the workshop.

    -

    NOTE: The completion of the job will actually consume one unit -of the appropriate liquids from below the workshop.

    +
    +

    Note

    +

    The completion of the job will actually consume one unit +of the appropriate liquids from below the workshop. This means +that you cannot just raise 7 units of magma with a piston and +have infinite power. However, liquid consumption should be slow +enough that water can be supplied by a pond zone bucket chain.

    +

    Every such item gives 100 power, up to a limit of 300 for coal, and 500 for a magma engine. The building can host twice that amount of items to provide longer autonomous running. When the @@ -2567,7 +2642,7 @@ decrease it by further 4%, and also decrease the whole steam use rate by 10%.

    -

    Explosions

    +

    Explosions

    The engine must be constructed using barrel, pipe and piston from fire-safe, or in the magma version magma-safe metals.

    During operation weak parts get gradually worn out, and @@ -2576,7 +2651,7 @@ toppled during operation by a building destroyer, or a tantruming dwarf.

    -

    Save files

    +

    Save files

    It should be safe to load and view engine-using fortresses from a DF version without DFHack installed, except that in such case the engines won't work. However actually making modifications @@ -2587,10 +2662,13 @@ being generated.

    -

    Add Spatter

    +

    Add Spatter

    This plugin makes reactions with names starting with SPATTER_ADD_ -produce contaminants on the items instead of improvements.

    -

    Intended to give some use to all those poisons that can be bought from caravans.

    +produce contaminants on the items instead of improvements. The produced +contaminants are immune to being washed away by water or destroyed by +the clean items command.

    +

    The plugin is intended to give some use to all those poisons that can +be bought from caravans. :)

    To be really useful this needs patches from bug 808, tweak fix-dimensions and tweak advmode-contained.

    diff --git a/library/Core.cpp b/library/Core.cpp index 735359a7f..8a8d39e06 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -126,8 +126,34 @@ struct Core::Private void Core::cheap_tokenise(string const& input, vector &output) { string *cur = NULL; + size_t i = 0; - for (size_t i = 0; i < input.size(); i++) { + // Check the first non-space character + while (i < input.size() && isspace(input[i])) i++; + + // Special verbatim argument mode? + if (i < input.size() && input[i] == ':') + { + // Read the command + std::string cmd; + i++; + while (i < input.size() && !isspace(input[i])) + cmd.push_back(input[i++]); + if (!cmd.empty()) + output.push_back(cmd); + + // Find the argument + while (i < input.size() && isspace(input[i])) i++; + + if (i < input.size()) + output.push_back(input.substr(i)); + + return; + } + + // Otherwise, parse in the regular quoted mode + for (; i < input.size(); i++) + { unsigned char c = input[i]; if (isspace(c)) { cur = NULL; From 821271383ed024dbcef469043ade36790cde89fe Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 24 Sep 2012 20:01:53 +0400 Subject: [PATCH 057/196] Add one more bad thought from siren for civilians. Originally a Tax Collector thought, but the message sounds appropriate. --- scripts/siren.lua | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/siren.lua b/scripts/siren.lua index 5371e3d7b..30d3aa07f 100644 --- a/scripts/siren.lua +++ b/scripts/siren.lua @@ -19,6 +19,8 @@ for _,v in ipairs(args) do table.insert(burrows, b) end +local in_siege = false + function is_in_burrows(pos) if #burrows == 0 then return true @@ -75,10 +77,21 @@ function stop_break(unit) end end +-- Check siege for thought purpose +for _,v in ipairs(df.global.ui.invasions.list) do + if v.flags.siege and v.flags.active then + in_siege = true + break + end +end + -- Stop rest for _,v in ipairs(df.global.world.units.active) do local x,y,z = dfhack.units.getPosition(v) - if x and not dfhack.units.isDead(v) and is_in_burrows(xyz2pos(x,y,z)) then + if x and dfhack.units.isCitizen(v) and is_in_burrows(xyz2pos(x,y,z)) then + if not in_siege and v.military.squad_index < 0 then + add_thought(v, df.unit_thought_type.LackProtection) + end wake_unit(v) stop_break(v) end From a622ee52e9e7063303ca04cc2b8ed1ae81d28a2d Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 24 Sep 2012 19:11:42 +0200 Subject: [PATCH 058/196] ruby: fix building_alloc Trap, and init Floodgates as closed --- plugins/ruby/building.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 3f8842b44..59f715515 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -51,12 +51,14 @@ module DFHack subtype = WorkshopType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Workshop subtype = FurnaceType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Furnace subtype = CivzoneType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Civzone + subtype = TrapType.int(subtype) if subtype.kind_of?(::Symbol) and type == :Trap bld.setSubtype(subtype) bld.setCustomType(custom) case type when :Furnace; bld.melt_remainder[world.raws.inorganics.length] = 0 when :Coffin; bld.initBurialFlags when :Trap; bld.unk_cc = 500 if bld.trap_type == :PressurePlate + when :Floodgate; bld.gate_flags.closed = true end bld end From f628c3732ae62c324c91553333ebd4e51f486090 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 24 Sep 2012 19:19:34 +0200 Subject: [PATCH 059/196] ruby: avoid smoothing grass/trees --- plugins/ruby/map.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index 9629c3f9e..371614704 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -198,7 +198,7 @@ module DFHack def dig(mode=:Default) if mode == :Smooth - if tilemat != :SOIL and caption !~ /smooth|pillar|fortification/i and # XXX caption.. + if (tilemat == :STONE or tilemat == :MINERAL) and caption !~ /smooth|pillar|fortification/i and # XXX caption.. designation.smooth == 0 and (designation.hidden or not df.world.job_list.find { |j| # the game removes 'smooth' designation as soon as it assigns a job, if we # re-set it the game may queue another :DetailWall that will carve a fortification From ac9e78a6130d896510f186cc8f8ed5046cac45be Mon Sep 17 00:00:00 2001 From: warmist Date: Wed, 26 Sep 2012 08:39:07 +0300 Subject: [PATCH 060/196] Fixed dfhack.interpreter argument order The argument order was incorrect. See: https://github.com/angavrilov/dfhack/blob/master/library/lua/dfhack.lua#L222 --- LUA_API.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LUA_API.rst b/LUA_API.rst index b098aa054..f532d2212 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -488,7 +488,7 @@ Input & Output lock. Using an explicit ``dfhack.with_suspend`` will prevent this, forcing the function to block on input with lock held. -* ``dfhack.interpreter([prompt[,env[,history_filename]]])`` +* ``dfhack.interpreter([prompt[,history_filename[,env]]])`` Starts an interactive lua interpreter, using the specified prompt string, global environment and command-line history file. From d975ce513509d12c1f69752270ce65de4f80d7aa Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 26 Sep 2012 15:08:54 +0200 Subject: [PATCH 061/196] ruby: add onstatechange_register_once --- plugins/ruby/ruby.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index aeae101d3..81b73b7ce 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -119,6 +119,14 @@ module DFHack @onstatechange_list.delete b end + # same as onstatechange_register, but auto-unregisters if the block returns true + def onstatechange_register_once + handle = onstatechange_register { |st| + onstatechange_unregister(handle) if yield(st) + } + end + + # this method is called by dfhack every 'onstatechange' def onstatechange(newstate) @onstatechange_list ||= [] From 7cc29250f8622a317da464a2b7356a246082abc0 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 26 Sep 2012 18:13:01 +0200 Subject: [PATCH 062/196] fix for updated viewlist_petst --- library/modules/Gui.cpp | 2 +- plugins/ruby/unit.rb | 14 ++++++++++---- plugins/sort.cpp | 2 +- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index eaeef9bf4..21156ac0a 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -766,7 +766,7 @@ static df::unit *getAnyUnit(df::viewscreen *top) { case df::viewscreen_petst::List: if (!vector_get(screen->is_vermin, screen->cursor)) - return (df::unit*)vector_get(screen->animal, screen->cursor); + return vector_get(screen->animal, screen->cursor).unit; return NULL; case df::viewscreen_petst::SelectTrainer: diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 5289229a6..139a375b2 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -12,9 +12,15 @@ module DFHack ref.unit_tg if ref.kind_of?(GeneralRefUnit) when :viewscreen_unitlistst v = curview - # TODO fix xml to use enums everywhere - page = DFHack::ViewscreenUnitlistst_TPage.int(v.page) - v.units[page][v.cursor_pos[page]] + v.units[v.page][v.cursor_pos[v.page]] + when :viewscreen_petst + v = curview + case v.mode + when :List + v.animal[v.cursor].unit if !v.is_vermin[v.cursor] + when :SelectTrainer + v.trainer_unit[v.trainer_cursor] + end else case ui.main.mode when :ViewUnits @@ -48,7 +54,7 @@ module DFHack } end - def unit_iscitizen(u) + def unit_iscitizen(u) u.race == ui.race_id and u.civ_id == ui.civ_id and !u.flags1.dead and !u.flags1.merchant and !u.flags1.forest and !u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and !u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and diff --git a/plugins/sort.cpp b/plugins/sort.cpp index 4b2bf7bbd..95ae109a8 100644 --- a/plugins/sort.cpp +++ b/plugins/sort.cpp @@ -356,7 +356,7 @@ DEFINE_SORT_HANDLER(unit_sorters, pet, "/List", animals) std::vector units; for (size_t i = 0; i < animals->animal.size(); i++) - units.push_back(animals->is_vermin[i] ? NULL : (df::unit*)animals->animal[i]); + units.push_back(animals->is_vermin[i] ? NULL : animals->animal[i].unit); if (compute_order(*pout, L, top, &order, units)) { From 164c61b6383d704a9a5cc52ed482e79ede5cd515 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 27 Sep 2012 10:43:42 +0400 Subject: [PATCH 063/196] Forbid stuff from bins in siege-engine, if forbid ammo standing order set. --- library/include/DataDefs.h | 3 +++ plugins/siege-engine.cpp | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 61d5dec41..0966c7f31 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -707,6 +707,9 @@ namespace DFHack { // Global object pointers #include "df/global_objects.h" +#define DF_GLOBAL_VALUE(name,defval) (df::global::name ? *df::global::name : defval) +#define DF_GLOBAL_FIELD(name,fname,defval) (df::global::name ? df::global::name->fname : defval) + // A couple of headers that have to be included at once #include "df/coord2d.h" #include "df/coord.h" diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index 2e362afec..7c880351e 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -1573,6 +1573,8 @@ struct projectile_hook : df::proj_itemst { if (next_pos.z == cur_pos.z && !isPassableTile(next_pos)) start_z = 49000; + bool forbid_ammo = DF_GLOBAL_VALUE(standing_orders_forbid_used_ammo, false); + MapExtras::MapCache mc; std::vector contents; Items::getContainedItems(item, &contents); @@ -1581,6 +1583,9 @@ struct projectile_hook : df::proj_itemst { { auto child = contents[i]; + if (forbid_ammo) + child->flags.bits.forbid = true; + // Liquids are vaporized so that they cover nearby units if (child->isLiquid()) { From 7e0365857d4af487ad07dfb43a85ed7b789075aa Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 27 Sep 2012 11:19:28 +0400 Subject: [PATCH 064/196] Edit docs a bit more. --- Lua API.html | 2 +- README.rst | 43 ++++++++++++++++++++++++++------------ Readme.html | 51 ++++++++++++++++++++++++++++++++------------- dfhack.init-example | 2 +- 4 files changed, 69 insertions(+), 29 deletions(-) diff --git a/Lua API.html b/Lua API.html index f30bb6ec5..36be1ba4a 100644 --- a/Lua API.html +++ b/Lua API.html @@ -781,7 +781,7 @@ running coroutine and let the C++ code release the core suspend lock. Using an explicit dfhack.with_suspend will prevent this, forcing the function to block on input with lock held.

    -
  • dfhack.interpreter([prompt[,env[,history_filename]]])

    +
  • dfhack.interpreter([prompt[,history_filename[,env]]])

    Starts an interactive lua interpreter, using the specified prompt string, global environment and command-line history file.

    If the interactive console is not accessible, returns nil, error.

    diff --git a/README.rst b/README.rst index 5202b92a2..35e44e5c1 100644 --- a/README.rst +++ b/README.rst @@ -159,6 +159,11 @@ If the first non-whitespace character is ``:``, the command is parsed in a speci alternative mode: first, non-whitespace characters immediately following the ``:`` are used as the command name; the remaining part of the line, starting with the first non-whitespace character *after* the command name, is used verbatim as the first argument. +The following two command lines are exactly equivalent: + + * ``:foo a b "c d" e f`` + * ``foo "a b \"c d\" e f"`` + This is intended for commands like ``rb_eval`` that evaluate script language statements. Almost all the commands support using the 'help ' built-in command @@ -616,14 +621,20 @@ Options: Pre-embark estimate ................... -If called during the embark selection screen, displays an estimate of layer -stone availability. If the 'all' option is specified, also estimates veins. -The estimate is computed either for 1 embark tile of the blinking biome, or -for all tiles of the embark rectangle. +If prospect is called during the embark selection screen, it displays an estimate of +layer stone availability. + +.. note:: + + The results of pre-embark prospect are an *estimate*, and can at best be expected + to be somewhere within +/- 30% of the true amount; sometimes it does a lot worse. + Especially, it is not clear how to precisely compute how many soil layers there + will be in a given embark tile, so it can report a whole extra layer, or omit one + that is actually present. Options: - :all: processes all tiles, even hidden ones. + :all: Also estimate vein mineral amounts. reveal ------ @@ -951,9 +962,9 @@ accidentally placed a construction on top of a valuable mineral floor. tweak ----- -Contains various tweaks for minor bugs (currently just one). +Contains various tweaks for minor bugs. -Options: +One-shot subcommands: :clear-missing: Remove the missing status from the selected unit. This allows engraving slabs for ghostly, but not yet @@ -982,6 +993,9 @@ Options: and are not flagged as tame), but you are allowed to mark them for slaughter. Grabbing wagons results in some funny spam, then they are scuttled. + +Subcommands that persist until disabled or DF quit: + :stable-cursor: Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode. :patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts. Does NOT fix the problem when soldiers go off-duty (i.e. civilian). @@ -1557,8 +1571,9 @@ siren Wakes up sleeping units, cancels breaks and stops parties either everywhere, or in the burrows given as arguments. In return, adds bad thoughts about -noise and tiredness. Also, the units with interrupted breaks will go on -break again a lot sooner. +noise, tiredness and lack of protection. Also, the units with interrupted +breaks will go on break again a lot sooner. The script is intended for +emergencies, e.g. when a siege appears, and all your military is partying. growcrops ========= @@ -1712,14 +1727,15 @@ Use the arrow keys or number pad to move the cursor around, holding Shift to move 10 tiles at a time. Press the Z-Up (<) and Z-Down (>) keys to move quickly between labor/skill -categories. +categories. The numpad Z-Up and Z-Down keys seek to the first or last unit +in the list. Press Enter to toggle the selected labor for the selected unit, or Shift+Enter to toggle all labors within the selected category. Press the ``+-`` keys to sort the unit list according to the currently selected skill/labor, and press the ``*/`` keys to sort the unit list by Name, Profession, -or Happiness (using Tab to select which sort method to use here). +Happiness, or Arrival order (using Tab to select which sort method to use here). With a unit selected, you can press the "v" key to view its properties (and possibly set a custom nickname or profession) or the "c" key to exit @@ -1776,7 +1792,7 @@ via a simple dialog in the game ui. * ``gui/rename unit-profession`` changes the selected unit's custom profession name. -The ``building`` or ``unit`` are automatically assumed when in relevant ui state. +The ``building`` or ``unit`` options are automatically assumed when in relevant ui state. Room List @@ -1816,7 +1832,8 @@ yellow for partially blocked. Pressing 'r' changes into the target selection mode, which works by highlighting two points with Enter like all designations. When a target area is set, the engine projectiles are -aimed at that area, or units within it. +aimed at that area, or units within it (this doesn't actually change the original aiming +code, instead the projectile trajectory parameters are rewritten as soon as it appears). After setting the target in this way for one engine, you can 'paste' the same area into others just by pressing 'p' in the main page of this script. The area to paste is kept until you quit diff --git a/Readme.html b/Readme.html index 7aebeb13a..3610ecd80 100644 --- a/Readme.html +++ b/Readme.html @@ -642,7 +642,14 @@ as a comment, i.e. a silent no-op command.

    alternative mode: first, non-whitespace characters immediately following the : are used as the command name; the remaining part of the line, starting with the first non-whitespace character after the command name, is used verbatim as the first argument. -This is intended for commands like rb_eval that evaluate script language statements.

    +The following two command lines are exactly equivalent:

    +
    +
      +
    • :foo a b "c d" e f
    • +
    • foo "a b \"c d\" e f"
    • +
    +
    +

    This is intended for commands like rb_eval that evaluate script language statements.

    Almost all the commands support using the 'help <command-name>' built-in command to retrieve further help without having to look at this document. Alternatively, some accept a 'help'/'?' option on their command line.

    @@ -1190,17 +1197,23 @@ the visible part of the map is scanned.

    Pre-embark estimate

    -

    If called during the embark selection screen, displays an estimate of layer -stone availability. If the 'all' option is specified, also estimates veins. -The estimate is computed either for 1 embark tile of the blinking biome, or -for all tiles of the embark rectangle.

    +

    If prospect is called during the embark selection screen, it displays an estimate of +layer stone availability.

    +
    +

    Note

    +

    The results of pre-embark prospect are an estimate, and can at best be expected +to be somewhere within +/- 30% of the true amount; sometimes it does a lot worse. +Especially, it is not clear how to precisely compute how many soil layers there +will be in a given embark tile, so it can report a whole extra layer, or omit one +that is actually present.

    +

    Options:

    - +
    all:processes all tiles, even hidden ones.
    all:Also estimate vein mineral amounts.
    @@ -1670,8 +1683,8 @@ accidentally placed a construction on top of a valuable mineral floor.

    tweak

    -

    Contains various tweaks for minor bugs (currently just one).

    -

    Options:

    +

    Contains various tweaks for minor bugs.

    +

    One-shot subcommands:

    @@ -1707,6 +1720,13 @@ and are not flagged as tame), but you are allowed to mark them for slaughter. Grabbing wagons results in some funny spam, then they are scuttled. + +
    +

    Subcommands that persist until disabled or DF quit:

    + +++ + +
    stable-cursor:Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode.
    patrol-duty:Makes Train orders not count as patrol duty to stop unhappy thoughts. @@ -2342,8 +2362,9 @@ combat in slow motion or something :)

    siren

    Wakes up sleeping units, cancels breaks and stops parties either everywhere, or in the burrows given as arguments. In return, adds bad thoughts about -noise and tiredness. Also, the units with interrupted breaks will go on -break again a lot sooner.

    +noise, tiredness and lack of protection. Also, the units with interrupted +breaks will go on break again a lot sooner. The script is intended for +emergencies, e.g. when a siege appears, and all your military is partying.

    growcrops

    @@ -2466,12 +2487,13 @@ denote skills not controlled by labors.

    Use the arrow keys or number pad to move the cursor around, holding Shift to move 10 tiles at a time.

    Press the Z-Up (<) and Z-Down (>) keys to move quickly between labor/skill -categories.

    +categories. The numpad Z-Up and Z-Down keys seek to the first or last unit +in the list.

    Press Enter to toggle the selected labor for the selected unit, or Shift+Enter to toggle all labors within the selected category.

    Press the +- keys to sort the unit list according to the currently selected skill/labor, and press the */ keys to sort the unit list by Name, Profession, -or Happiness (using Tab to select which sort method to use here).

    +Happiness, or Arrival order (using Tab to select which sort method to use here).

    With a unit selected, you can press the "v" key to view its properties (and possibly set a custom nickname or profession) or the "c" key to exit Manipulator and zoom to its position within your fortress.

    @@ -2517,7 +2539,7 @@ It is also possible to rename zones from the 'i' menu.

  • gui/rename unit-profession changes the selected unit's custom profession name.

  • -

    The building or unit are automatically assumed when in relevant ui state.

    +

    The building or unit options are automatically assumed when in relevant ui state.

    Room List

    @@ -2546,7 +2568,8 @@ hit by the selected engine: green for fully reachable, blue for out of range, re yellow for partially blocked.

    Pressing 'r' changes into the target selection mode, which works by highlighting two points with Enter like all designations. When a target area is set, the engine projectiles are -aimed at that area, or units within it.

    +aimed at that area, or units within it (this doesn't actually change the original aiming +code, instead the projectile trajectory parameters are rewritten as soon as it appears).

    After setting the target in this way for one engine, you can 'paste' the same area into others just by pressing 'p' in the main page of this script. The area to paste is kept until you quit DF, or select another area manually.

    diff --git a/dfhack.init-example b/dfhack.init-example index 83c3641b6..0bf344609 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -18,7 +18,7 @@ keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave # gui/rename script keybinding add Ctrl-Shift-N gui/rename -keybinding add Ctrl-Shift-P "gui/rename unit-profession" +keybinding add Alt-Shift-P "gui/rename unit-profession" ############################## # Generic adv mode bindings # From bd7cd690aa4260d4df9625a124a2a15480bdd848 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 28 Sep 2012 13:32:41 +0400 Subject: [PATCH 065/196] Implement the common suggestion of Shift-Enter in trade screens. --- README.rst | 3 ++- Readme.html | 3 +++ dfhack.init-example | 5 ++++ plugins/manipulator.cpp | 1 + plugins/tweak.cpp | 51 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 35e44e5c1..7e8678c9f 100644 --- a/README.rst +++ b/README.rst @@ -1013,7 +1013,8 @@ Subcommands that persist until disabled or DF quit: in advmode. The issue is that the screen tries to force you to select the contents separately from the container. This forcefully skips child reagents. - +:fast-trade: Makes Shift-Enter in the Move Goods to Depot and Trade screens select + the current item (fully, in case of a stack), and scroll down one line. Mode switch and reclaim ======================= diff --git a/Readme.html b/Readme.html index 3610ecd80..6155b72ce 100644 --- a/Readme.html +++ b/Readme.html @@ -1753,6 +1753,9 @@ in advmode. The issue is that the screen tries to force you to select the contents separately from the container. This forcefully skips child reagents.
    fast-trade:Makes Shift-Enter in the Move Goods to Depot and Trade screens select +the current item (fully, in case of a stack), and scroll down one line.
    diff --git a/dfhack.init-example b/dfhack.init-example index 0bf344609..8a3ec8f29 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -45,6 +45,7 @@ keybinding add Shift-R "job-material RHYOLITE" keybinding add Shift-I "job-material CINNABAR" keybinding add Shift-B "job-material COBALTITE" keybinding add Shift-O "job-material OBSIDIAN" +keybinding add Shift-T "job-material ORTHOCLASE" keybinding add Shift-G "job-material GLASS_GREEN" # browse linked mechanisms @@ -90,3 +91,7 @@ tweak fix-dimensions # make reactions requiring containers usable in advmode - the issue is # that the screen asks for those reagents to be selected directly tweak advmode-contained + +# support Shift-Enter in Trade and Move Goods to Depot screens for faster +# selection; it selects the current item or stack and scrolls down one line +tweak fast-trade diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 0b0b99d48..f3c6664a1 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -523,6 +523,7 @@ void viewscreen_unitlaborsst::feed(set *events) { events->insert(interface_key::LEAVESCREEN); parent->feed(events); + events->clear(); } return; } diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index fb286e0d7..6d0591d13 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -43,6 +43,8 @@ #include "df/reaction.h" #include "df/reaction_reagent_itemst.h" #include "df/reaction_reagent_flags.h" +#include "df/viewscreen_layer_assigntradest.h" +#include "df/viewscreen_tradegoodsst.h" #include @@ -109,6 +111,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector *input)) + { + if (layer_objects[1]->active && input->count(interface_key::SELECT_ALL)) + { + set tmp; tmp.insert(interface_key::SELECT); + INTERPOSE_NEXT(feed)(&tmp); + tmp.clear(); tmp.insert(interface_key::STANDARDSCROLL_DOWN); + INTERPOSE_NEXT(feed)(&tmp); + } + else + INTERPOSE_NEXT(feed)(input); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(fast_trade_assign_hook, feed); + +struct fast_trade_select_hook : df::viewscreen_tradegoodsst { + typedef df::viewscreen_tradegoodsst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (!(is_unloading || !has_traders || in_edit_count) + && input->count(interface_key::SELECT_ALL)) + { + set tmp; tmp.insert(interface_key::SELECT); + INTERPOSE_NEXT(feed)(&tmp); + if (in_edit_count) + INTERPOSE_NEXT(feed)(&tmp); + tmp.clear(); tmp.insert(interface_key::STANDARDSCROLL_DOWN); + INTERPOSE_NEXT(feed)(&tmp); + } + else + INTERPOSE_NEXT(feed)(input); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(fast_trade_select_hook, feed); + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -653,6 +699,11 @@ static command_result tweak(color_ostream &out, vector ¶meters) { enable_hook(out, INTERPOSE_HOOK(advmode_contained_hook, feed), parameters); } + else if (cmd == "fast-trade") + { + enable_hook(out, INTERPOSE_HOOK(fast_trade_assign_hook, feed), parameters); + enable_hook(out, INTERPOSE_HOOK(fast_trade_select_hook, feed), parameters); + } else return CR_WRONG_USAGE; From 9784ca559144ec24f513dee23002bf49100ba897 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 28 Sep 2012 16:08:42 +0400 Subject: [PATCH 066/196] Mention the new tweak in NEWS. --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 8f7c407e7..ffce93d23 100644 --- a/NEWS +++ b/NEWS @@ -27,6 +27,7 @@ DFHack v0.34.11-r2 (UNRELEASED) - tweak fast-heat: speeds up item heating & cooling, thus making stable-temp act faster. - tweak fix-dimensions: fixes subtracting small amounts from stacked liquids etc. - tweak advmode-contained: fixes UI bug in custom reactions with container inputs in advmode. + - tweak fast-trade: Shift-Enter for selecting items quckly in Trade and Move to Depot screens. New scripts: - fixnaked: removes thoughts about nakedness. - setfps: set FPS cap at runtime, in case you want slow motion or speed-up. From ba763b773d64c17a58ccd64f2b0581b6619c81e6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 28 Sep 2012 18:01:17 +0400 Subject: [PATCH 067/196] New tweaks for the military assign to position screen. --- NEWS | 2 + README.rst | 6 ++ dfhack.init-example | 11 ++++ plugins/lua/sort/items.lua | 8 ++- plugins/tweak.cpp | 112 +++++++++++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index ffce93d23..7bcd7513b 100644 --- a/NEWS +++ b/NEWS @@ -28,6 +28,8 @@ DFHack v0.34.11-r2 (UNRELEASED) - tweak fix-dimensions: fixes subtracting small amounts from stacked liquids etc. - tweak advmode-contained: fixes UI bug in custom reactions with container inputs in advmode. - tweak fast-trade: Shift-Enter for selecting items quckly in Trade and Move to Depot screens. + - tweak military-stable-assign: Stop rightmost list of military->Positions from jumping to top. + - tweak military-color-assigned: In same list, color already assigned units in brown & green. New scripts: - fixnaked: removes thoughts about nakedness. - setfps: set FPS cap at runtime, in case you want slow motion or speed-up. diff --git a/README.rst b/README.rst index 7e8678c9f..5801e6b45 100644 --- a/README.rst +++ b/README.rst @@ -1015,6 +1015,12 @@ Subcommands that persist until disabled or DF quit: reagents. :fast-trade: Makes Shift-Enter in the Move Goods to Depot and Trade screens select the current item (fully, in case of a stack), and scroll down one line. +:military-stable-assign: Preserve list order and cursor position when assigning to squad, + i.e. stop the rightmost list of the Positions page of the military + screen from constantly resetting to the top. +:military-color-assigned: Color squad candidates already assigned to other squads in brown/green + to make them stand out more in the list. + Mode switch and reclaim ======================= diff --git a/dfhack.init-example b/dfhack.init-example index 8a3ec8f29..2e656a609 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -48,6 +48,12 @@ keybinding add Shift-O "job-material OBSIDIAN" keybinding add Shift-T "job-material ORTHOCLASE" keybinding add Shift-G "job-material GLASS_GREEN" +# sort units and items +keybinding add Alt-Shift-N "sort-units name" "sort-items description" +keybinding add Alt-Shift-R "sort-units arrival" +keybinding add Alt-Shift-T "sort-units profession" "sort-items type material" +keybinding add Alt-Shift-Q "sort-units squad_position" "sort-items quality" + # browse linked mechanisms keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms @@ -95,3 +101,8 @@ tweak advmode-contained # support Shift-Enter in Trade and Move Goods to Depot screens for faster # selection; it selects the current item or stack and scrolls down one line tweak fast-trade + +# stop the right list in military->positions from resetting to top all the time +tweak military-stable-assign +# in same list, color units already assigned to squads in brown & green +tweak military-color-assigned diff --git a/plugins/lua/sort/items.lua b/plugins/lua/sort/items.lua index 13b62ff9b..5c6d4a5ba 100644 --- a/plugins/lua/sort/items.lua +++ b/plugins/lua/sort/items.lua @@ -23,12 +23,18 @@ orders.description = { end } -orders.quality = { +orders.base_quality = { key = function(item) return item:getQuality() end } +orders.quality = { + key = function(item) + return item:getOverallQuality() + end +} + orders.improvement = { key = function(item) return item:getImprovementQuality() diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 6d0591d13..939c94b3b 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -45,6 +45,7 @@ #include "df/reaction_reagent_flags.h" #include "df/viewscreen_layer_assigntradest.h" #include "df/viewscreen_tradegoodsst.h" +#include "df/viewscreen_layer_militaryst.h" #include @@ -61,6 +62,7 @@ using df::global::ui_menu_width; using df::global::ui_area_map_width; using namespace DFHack::Gui; +using Screen::Pen; static command_result tweak(color_ostream &out, vector & parameters); @@ -114,6 +116,13 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector *input)) + { + if (inPositionsMode() && !layer_objects[0]->active) + { + auto pos_list = layer_objects[1]; + auto plist = layer_objects[2]; + auto &cand = positions.candidates; + + // Save the candidate list and cursors + std::vector copy = cand; + int cursor = plist->getListCursor(); + int pos_cursor = pos_list->getListCursor(); + + INTERPOSE_NEXT(feed)(input); + + if (inPositionsMode() && !layer_objects[0]->active) + { + bool is_select = input->count(interface_key::SELECT); + + // Resort the candidate list and restore cursor + // on add to squad OR scroll in the position list. + if (!plist->active || is_select) + { + // Since we don't know the actual sorting order, preserve + // the ordering of the items in the list before keypress. + // This does the right thing even if the list was sorted + // with sort-units. + std::set prev, next; + prev.insert(copy.begin(), copy.end()); + next.insert(cand.begin(), cand.end()); + std::vector out; + + // (old-before-cursor) (new) |cursor| (old-after-cursor) + for (int i = 0; i < cursor && i < (int)copy.size(); i++) + if (next.count(copy[i])) out.push_back(copy[i]); + for (size_t i = 0; i < cand.size(); i++) + if (!prev.count(cand[i])) out.push_back(cand[i]); + int new_cursor = out.size(); + for (int i = cursor; i < (int)copy.size(); i++) + if (next.count(copy[i])) out.push_back(copy[i]); + + cand.swap(out); + plist->setListLength(cand.size()); + if (new_cursor < (int)cand.size()) + plist->setListCursor(new_cursor); + } + + // Preserve the position list index on remove from squad + if (pos_list->active && is_select) + pos_list->setListCursor(pos_cursor); + } + } + else + INTERPOSE_NEXT(feed)(input); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + if (inPositionsMode()) + { + auto plist = layer_objects[2]; + int x1 = plist->getX1(), y1 = plist->getY1(); + int x2 = plist->getX2(), y2 = plist->getY2(); + int i1 = plist->getFirstVisible(), i2 = plist->getLastVisible(); + + for (int y = y1, i = i1; i <= i2; i++, y++) + { + auto unit = vector_get(positions.candidates, i); + if (!unit || unit->military.squad_index < 0) + continue; + + for (int x = x1; x <= x2; x++) + { + Pen cur_tile = Screen::readTile(x, y); + if (!cur_tile.valid()) continue; + cur_tile.fg = (cur_tile.fg == COLOR_GREY ? COLOR_BROWN : COLOR_GREEN); + Screen::paintTile(cur_tile, x, y); + } + } + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, render); + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -704,6 +808,14 @@ static command_result tweak(color_ostream &out, vector ¶meters) enable_hook(out, INTERPOSE_HOOK(fast_trade_assign_hook, feed), parameters); enable_hook(out, INTERPOSE_HOOK(fast_trade_select_hook, feed), parameters); } + else if (cmd == "military-stable-assign") + { + enable_hook(out, INTERPOSE_HOOK(military_assign_hook, feed), parameters); + } + else if (cmd == "military-color-assigned") + { + enable_hook(out, INTERPOSE_HOOK(military_assign_hook, render), parameters); + } else return CR_WRONG_USAGE; From 783b218eec3bc1b1c4c33f57700db39fc92069a4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 28 Sep 2012 18:50:01 +0400 Subject: [PATCH 068/196] Try a different color scheme when highlighting squad members in tweak. --- plugins/tweak.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 939c94b3b..d54c4a5ec 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -622,6 +622,7 @@ struct military_assign_hook : df::viewscreen_layer_militaryst { int x1 = plist->getX1(), y1 = plist->getY1(); int x2 = plist->getX2(), y2 = plist->getY2(); int i1 = plist->getFirstVisible(), i2 = plist->getLastVisible(); + int si = plist->getListCursor(); for (int y = y1, i = i1; i <= i2; i++, y++) { @@ -633,7 +634,7 @@ struct military_assign_hook : df::viewscreen_layer_militaryst { { Pen cur_tile = Screen::readTile(x, y); if (!cur_tile.valid()) continue; - cur_tile.fg = (cur_tile.fg == COLOR_GREY ? COLOR_BROWN : COLOR_GREEN); + cur_tile.fg = (i == si) ? COLOR_BROWN : COLOR_GREEN; Screen::paintTile(cur_tile, x, y); } } From c4f26d0aa819e6038968d152c98198e48e52e8e1 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 28 Sep 2012 10:44:35 +0200 Subject: [PATCH 069/196] manipulator: make up/down keys wrap around --- plugins/manipulator.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index f3c6664a1..d676dc2a1 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -534,6 +534,8 @@ void viewscreen_unitlaborsst::feed(set *events) if (do_refresh_names) refreshNames(); + int old_sel_row = sel_row; + if (events->count(interface_key::CURSOR_UP) || events->count(interface_key::CURSOR_UPLEFT) || events->count(interface_key::CURSOR_UPRIGHT)) sel_row--; if (events->count(interface_key::CURSOR_UP_FAST) || events->count(interface_key::CURSOR_UPLEFT_FAST) || events->count(interface_key::CURSOR_UPRIGHT_FAST)) @@ -553,9 +555,20 @@ void viewscreen_unitlaborsst::feed(set *events) } if (sel_row < 0) - sel_row = 0; + { + if (old_sel_row == 0 && events->count(interface_key::CURSOR_UP)) + sel_row = units.size() - 1; + else + sel_row = 0; + } + if (sel_row > units.size() - 1) - sel_row = units.size() - 1; + { + if (old_sel_row == units.size()-1 && events->count(interface_key::CURSOR_DOWN)) + sel_row = 0; + else + sel_row = units.size() - 1; + } if (sel_row < first_row) first_row = sel_row; From ca0097b91089a7012b3aa3106721623545803dd4 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 28 Sep 2012 13:52:54 +0200 Subject: [PATCH 070/196] manipulator: better screen width usage --- plugins/manipulator.cpp | 104 ++++++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index d676dc2a1..7451f1373 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -439,6 +439,7 @@ void viewscreen_unitlaborsst::refreshNames() cur->transname = Translation::TranslateName(&unit->name, true); cur->profession = Units::getProfessionName(unit); } + calcSize(); } void viewscreen_unitlaborsst::calcSize() @@ -448,39 +449,92 @@ void viewscreen_unitlaborsst::calcSize() num_rows = units.size(); int num_columns = gps->dimx - DISP_COLUMN_MAX - 1; + + // min/max width of columns + int col_minwidth[DISP_COLUMN_MAX]; + int col_maxwidth[DISP_COLUMN_MAX]; + col_minwidth[DISP_COLUMN_HAPPINESS] = 4; + col_maxwidth[DISP_COLUMN_HAPPINESS] = 4; + col_minwidth[DISP_COLUMN_NAME] = 0; + col_maxwidth[DISP_COLUMN_NAME] = 0; + col_minwidth[DISP_COLUMN_PROFESSION] = 0; + col_maxwidth[DISP_COLUMN_PROFESSION] = 0; + col_minwidth[DISP_COLUMN_LABORS] = num_columns*3/5; // 60% + col_maxwidth[DISP_COLUMN_LABORS] = NUM_COLUMNS; + + // get max_name/max_prof from strings length + for (size_t i = 0; i < units.size(); i++) + { + if (col_maxwidth[DISP_COLUMN_NAME] < units[i]->name.size()) + col_maxwidth[DISP_COLUMN_NAME] = units[i]->name.size(); + if (col_maxwidth[DISP_COLUMN_PROFESSION] < units[i]->profession.size()) + col_maxwidth[DISP_COLUMN_PROFESSION] = units[i]->profession.size(); + } + + // check how much room we have + int width_min = 0, width_max = 0; for (int i = 0; i < DISP_COLUMN_MAX; i++) - col_widths[i] = 0; - while (num_columns > 0) { - num_columns--; - // need at least 4 digits for happiness - if (col_widths[DISP_COLUMN_HAPPINESS] < 4) + width_min += col_minwidth[i]; + width_max += col_maxwidth[i]; + } + + if (width_max <= num_columns) + { + // lots of space, distribute leftover (except last column) + int col_margin = (num_columns - width_max) / (DISP_COLUMN_MAX-1); + int col_margin_r = (num_columns - width_max) % (DISP_COLUMN_MAX-1); + for (int i = DISP_COLUMN_MAX-1; i>=0; i--) { - col_widths[DISP_COLUMN_HAPPINESS]++; - continue; + col_widths[i] = col_maxwidth[i]; + + if (i < DISP_COLUMN_MAX-1) + { + col_widths[i] += col_margin; + + if (col_margin_r) + { + col_margin_r--; + col_widths[i]++; + } + } } - // of remaining, 20% for Name, 20% for Profession, 60% for Labors - switch (num_columns % 5) + } + else if (width_min <= num_columns) + { + // constrained, give between min and max to every column + int space = num_columns - width_min; + // max size columns not yet seen may consume + int next_consume_max = width_max - width_min; + + for (int i = 0; i < DISP_COLUMN_MAX; i++) { - case 0: case 2: case 4: - col_widths[DISP_COLUMN_LABORS]++; - break; - case 1: - col_widths[DISP_COLUMN_NAME]++; - break; - case 3: - col_widths[DISP_COLUMN_PROFESSION]++; - break; + // divide evenly remaining space + int col_margin = space / (DISP_COLUMN_MAX-i); + + // take more if the columns after us cannot + next_consume_max -= (col_maxwidth[i]-col_minwidth[i]); + if (col_margin < space-next_consume_max) + col_margin = space - next_consume_max; + + // no more than maxwidth + if (col_margin > col_maxwidth[i] - col_minwidth[i]) + col_margin = col_maxwidth[i] - col_minwidth[i]; + + col_widths[i] = col_minwidth[i] + col_margin; + + space -= col_margin; } } - - while (col_widths[DISP_COLUMN_LABORS] > NUM_COLUMNS) + else { - col_widths[DISP_COLUMN_LABORS]--; - if (col_widths[DISP_COLUMN_LABORS] & 1) - col_widths[DISP_COLUMN_NAME]++; - else - col_widths[DISP_COLUMN_PROFESSION]++; + // should not happen, min screen is 80x25 + int space = num_columns; + for (int i = 0; i < DISP_COLUMN_MAX; i++) + { + col_widths[i] = space / (DISP_COLUMN_MAX-i); + space -= col_widths[i]; + } } for (int i = 0; i < DISP_COLUMN_MAX; i++) From 4a495c2c7fa9cef78628a3026a434322ca7418d1 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 28 Sep 2012 18:55:13 +0200 Subject: [PATCH 071/196] manipulator: make cursor_down_z go to beginning of next group --- plugins/manipulator.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 7451f1373..b04c80799 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -648,11 +648,13 @@ void viewscreen_unitlaborsst::feed(set *events) } if ((sel_column != NUM_COLUMNS - 1) && events->count(interface_key::CURSOR_DOWN_Z)) { - // go to end of current column group; if already at the end, go to the end of the next one - sel_column++; + // go to beginning of next group int cur = columns[sel_column].group; - while ((sel_column < NUM_COLUMNS - 1) && columns[sel_column + 1].group == cur) - sel_column++; + int next = sel_column+1; + while ((next < NUM_COLUMNS) && (columns[next].group == cur)) + next++; + if ((next < NUM_COLUMNS) && (columns[next].group != cur)) + sel_column = next; } if (sel_column < 0) From 687657f2da175db827109384737d6a2220cf1c32 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 28 Sep 2012 18:58:58 +0200 Subject: [PATCH 072/196] manipulator: show full group when navigating labor groups --- plugins/manipulator.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index b04c80799..953cf2ba9 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -662,6 +662,17 @@ void viewscreen_unitlaborsst::feed(set *events) if (sel_column > NUM_COLUMNS - 1) sel_column = NUM_COLUMNS - 1; + if (events->count(interface_key::CURSOR_DOWN_Z) || events->count(interface_key::CURSOR_UP_Z)) + { + // when moving by group, ensure the whole group is shown onscreen + int endgroup_column = sel_column; + while ((endgroup_column < NUM_COLUMNS-1) && columns[endgroup_column+1].group == columns[sel_column].group) + endgroup_column++; + + if (first_column < endgroup_column - col_widths[DISP_COLUMN_LABORS] + 1) + first_column = endgroup_column - col_widths[DISP_COLUMN_LABORS] + 1; + } + if (sel_column < first_column) first_column = sel_column; if (first_column < sel_column - col_widths[DISP_COLUMN_LABORS] + 1) From 53c6ee4673153aa8f8ad6f2c082d8a100e8f75c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Fri, 28 Sep 2012 21:57:12 +0200 Subject: [PATCH 073/196] Track structures and stonesense --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index e6a59bfeb..9773484b8 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e6a59bfebd56e209a0719c173f93a33262751528 +Subproject commit 9773484b8430417ad488423ddeae3832f011ce9d diff --git a/plugins/stonesense b/plugins/stonesense index 2a62ba5ed..75df76626 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 2a62ba5ed2607f4dbf0473e77502d4e19c19678e +Subproject commit 75df766263b23182820a1e07b330e64f87d5c9b7 From 79ac1a81b9c2751c162fc413c91ef0ee84766d01 Mon Sep 17 00:00:00 2001 From: expwnent Date: Fri, 28 Sep 2012 16:36:37 -0400 Subject: [PATCH 074/196] It was backwards when calling fastdwarf with just one argument. Also made it print the current status of fastdwarf when no arguments are given. --- plugins/fastdwarf.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index c51684ae6..400bff23b 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -72,10 +72,10 @@ static command_result fastdwarf (color_ostream &out, vector & parameter { if (parameters.size() == 1) { if ( parameters[0] == "0" ) { - enable_fastdwarf = true; + enable_fastdwarf = false; enable_teledwarf = false; } else if ( parameters[0] == "1" ) { - enable_fastdwarf = false; + enable_fastdwarf = true; enable_teledwarf = false; } else { out.print("Incorrect usage.\n"); @@ -100,6 +100,7 @@ static command_result fastdwarf (color_ostream &out, vector & parameter } } else if (parameters.size() == 0) { //print status + out.print("Current state: fast = %d, teleport = %d.\n", enable_fastdwarf, enable_teledwarf); } else { out.print("Incorrect usage.\n"); return CR_OK; From 634c281f00dfb6c5a4170044062ceff00ceb82c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Fri, 28 Sep 2012 22:44:14 +0200 Subject: [PATCH 075/196] Track clsocket --- depends/clsocket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/clsocket b/depends/clsocket index d0b2d0750..178a4916d 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit d0b2d0750dc2d529a152eba4f3f519f69ff7eab0 +Subproject commit 178a4916da838e46e46e769e566e47fff6eff8f8 From 44a74ddc38abf86d093db2e7ff2aa3af24bf8173 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 28 Sep 2012 22:58:26 +0200 Subject: [PATCH 076/196] ruby: remove unused var --- plugins/ruby/ruby.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index e291a770f..7bd6d13e8 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -205,8 +205,6 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch static command_result df_rubyeval(color_ostream &out, std::vector & parameters) { - command_result ret; - if (parameters.size() == 1 && (parameters[0] == "help" || parameters[0] == "?")) { out.print("This command executes an arbitrary ruby statement.\n"); From 1bde32fa5b72a070a81c142b4044f8fd355e2145 Mon Sep 17 00:00:00 2001 From: expwnent Date: Fri, 28 Sep 2012 19:06:22 -0400 Subject: [PATCH 077/196] Fixed a tile occupancy problem with teleporting dwarves. --- plugins/fastdwarf.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 400bff23b..4c235394b 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -2,6 +2,7 @@ #include "Console.h" #include "Export.h" #include "PluginManager.h" +#include "modules/MapCache.h" #include "DataDefs.h" #include "df/ui.h" @@ -48,6 +49,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) } } if ( enable_teledwarf ) { + MapExtras::MapCache *MCache = new MapExtras::MapCache(); for (size_t i = 0; i < world->units.all.size(); i++) { df::unit *unit = world->units.all[i]; @@ -59,11 +61,19 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (unit->relations.following != 0) continue; + MapExtras::Block* block = MCache->BlockAtTile(unit->pos); + df::coord2d pos(unit->pos.x % 16, unit->pos.y % 16); + df::tile_occupancy occ = block->OccupancyAt(pos); + occ.bits.unit = 0; + block->setOccupancyAt(pos, occ); + //move immediately to destination unit->pos.x = unit->path.dest.x; unit->pos.y = unit->path.dest.y; unit->pos.z = unit->path.dest.z; } + MCache->WriteAll(); + delete MCache; } return CR_OK; } From d41c4849f96eef155b41eac29d887ce926bd1781 Mon Sep 17 00:00:00 2001 From: expwnent Date: Fri, 28 Sep 2012 19:17:12 -0400 Subject: [PATCH 078/196] Fixed unit_grounded in teleporting dwarf tile. --- plugins/fastdwarf.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 4c235394b..aea14e7f0 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -65,6 +65,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) df::coord2d pos(unit->pos.x % 16, unit->pos.y % 16); df::tile_occupancy occ = block->OccupancyAt(pos); occ.bits.unit = 0; + occ.bits.unit_grounded = 0; block->setOccupancyAt(pos, occ); //move immediately to destination From 12f5e6028ce91e67143c7dad317ffe1430603193 Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 29 Sep 2012 01:38:32 +0200 Subject: [PATCH 079/196] manipulator: avoid NULL deref with unassigned job in unit list --- plugins/manipulator.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 953cf2ba9..feceb08ec 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -383,8 +383,12 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur for (size_t i = 0; i < src.size(); i++) { - UnitInfo *cur = new UnitInfo; df::unit *unit = src[i]; + if (!unit) + continue; + + UnitInfo *cur = new UnitInfo; + cur->unit = unit; cur->allowEdit = true; cur->active_index = active_idx[unit]; From cd18660f0edfb131d9ae1f3fe30a7e34f6339070 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 29 Sep 2012 02:58:30 +0200 Subject: [PATCH 080/196] Add documentation for added plugins - digtype, misery, changes in fastdwarf --- NEWS | 3 +++ README.rst | 38 ++++++++++++++++++++++++++++++++++---- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 7bcd7513b..ee6dd6592 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,9 @@ DFHack v0.34.11-r2 (UNRELEASED) - liquids: can paint permaflow, i.e. what makes rivers power water wheels. - prospect: pre-embark prospector accounts for caves & magma sea in its estimate. - rename: supports renaming stockpiles, workshops, traps, siege engines. + - fastdwarf: now has an additional option to make dwarves teleport to their destination. + - misery: multiplies every negative thought gained (2x by default). + - digtype: designates every tile of the same type of vein on the map for 'digging' (any dig designation). New tweaks: - tweak stable-cursor: keeps exact cursor position between d/k/t/q/v etc menus. - tweak patrol-duty: makes Train orders reduce patrol timer, like the binary patch does. diff --git a/README.rst b/README.rst index 5801e6b45..5cf79cdf5 100644 --- a/README.rst +++ b/README.rst @@ -193,11 +193,14 @@ by 'reveal hell'. This is nice for digging under rivers. fastdwarf --------- -Makes your minions move at ludicrous speeds. - - * Activate with 'fastdwarf 1' - * Deactivate with 'fastdwarf 0' +Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and perform tasks quickly. Teledwarf makes dwarves move instantaneously, but do jobs at the same speed. + * 'fastdwarf 0 0' disables both + * 'fastdwarf 0 1' disables speedydwarf and enables teledwarf + * 'fastdwarf 1 0' enables speedydwarf and disables teledwarf + * 'fastdwarf 1 1' enables both + * 'fastdwarf 0' disables both + * 'fastdwarf 1' enables speedydwarf and disables teledwarf Game interface ============== @@ -804,6 +807,22 @@ Examples: * 'digcircle' = Do it again. +digtype +------- +For every tile on the map of the same vein type as the selected tile, this command designates it to have the same designation as the selected tile. If the selected tile has no designation, they will be dig designated. +If an argument is given, the designation of the selected tile is ignored, and all appropriate tiles are set to the specified designation. + +Options: + + :dig: + :channel: + :ramp: + :updown: up/down stairs + :up: up stairs + :down: down stairs + :clear: clear designation + + filltraffic ----------- Set traffic designations using flood-fill starting at the cursor. @@ -1510,6 +1529,17 @@ Confirmed working DFusion plugins: * This is currently working only on Windows. * The game will be suspended while you're using dfusion. Don't panic when it doen't respond. +misery +------ +When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default). + +Usage: + +:misery enable n: enable misery with optional magnitude n. If specified, n must be positive. +:misery n: same as "misery enable n" +:misery enable: same as "misery enable 2" +:misery disable: stop adding new negative thoughts. This will not remove existing duplicated thoughts. Equivalent to "misery 1" +:misery clear: remove fake thoughts added in this session of DF. Saving makes them permanent! Does not change factor. ======= Scripts From 09ddccea6cf539fb00098b2e6a34245c35ea815c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 29 Sep 2012 03:06:39 +0200 Subject: [PATCH 081/196] Purge html files, add docutils cmake module --- CMake/Modules/FindDocutils.cmake | 3 + CMakeLists.txt | 1 + Compile.html | 546 ------ Lua API.html | 2092 ----------------------- Readme.html | 2704 ------------------------------ 5 files changed, 4 insertions(+), 5342 deletions(-) create mode 100644 CMake/Modules/FindDocutils.cmake delete mode 100644 Compile.html delete mode 100644 Lua API.html delete mode 100644 Readme.html diff --git a/CMake/Modules/FindDocutils.cmake b/CMake/Modules/FindDocutils.cmake new file mode 100644 index 000000000..8103628df --- /dev/null +++ b/CMake/Modules/FindDocutils.cmake @@ -0,0 +1,3 @@ +FIND_PROGRAM(RST2HTML_EXECUTABLE NAMES rst2html rst2html.py) +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(Docutils DEFAULT_MSG RST2HTML_EXECUTABLE) \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index dfb13cd5d..9bd4ad974 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,6 +145,7 @@ include_directories(depends/clsocket/src) add_subdirectory(depends) +find_package(Docutils) # build the lib itself IF(BUILD_LIBRARY) diff --git a/Compile.html b/Compile.html deleted file mode 100644 index e17e57e22..000000000 --- a/Compile.html +++ /dev/null @@ -1,546 +0,0 @@ - - - - - - -Building DFHACK - - - -
    -

    Building DFHACK

    - - -
    -

    Linux

    -

    On Linux, DFHack acts as a library that shadows parts of the SDL API using LD_PRELOAD.

    -
    -

    How to get the code

    -

    DFHack doesn't have any kind of system of code snapshots in place, so you will have to get code from the github repository using git. -Having a 'git' package installed is the minimal requirement, but some sort of git gui or git integration for your favorite text editor/IDE will certainly help.

    -

    The code resides here: https://github.com/peterix/dfhack

    -

    If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address:

    -
    -git clone git://github.com/peterix/dfhack.git
    -cd dfhack
    -git submodule init
    -git submodule update
    -
    -

    If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode).

    -
    -
    -

    Dependencies

    -

    DFHack is meant to be installed into an existing DF folder, so get one ready.

    -

    For building, you need a 32-bit version of GCC. For example, to build DFHack on -a 64-bit distribution like Arch, you'll need the multilib development tools and libraries.

    -

    Before you can build anything, you'll also need cmake. It is advisable to also get -ccmake on distributions that split the cmake package into multiple parts.

    -

    For the code generation parts, you need perl and the XML::LibXML and XML::LibXSLT perl packages. -You should be able to find them in your distro repositories (on Arch linux 'perl-xml-libxml' and 'perl-xml-libxslt').

    -
    -
    -

    Build

    -

    Building is fairly straightforward. Enter the build folder and start the build like this:

    -
    -cd build
    -cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/home/user/DF
    -make install
    -
    -

    Obviously, replace the install path with path to your DF. This will build the library -along with the normal set of plugins and install them into your DF folder.

    -

    Alternatively, you can use ccmake instead of cmake:

    -
    -cd build
    -ccmake ..
    -make install
    -
    -

    This will show a curses-based interface that lets you set all of the -extra options.

    -

    You can also use a cmake-friendly IDE like KDevelop 4 or the cmake-gui -program.

    -
    -
    -
    -

    Windows

    -

    On Windows, DFHack replaces the SDL library distributed with DF.

    -
    -

    How to get the code

    -

    DFHack doesn't have any kind of system of code snapshots in place, so you will have to get code from the github repository using git. -You will need some sort of Windows port of git, or a GUI. Some examples:

    -
    - -
    -

    The code resides here: https://github.com/peterix/dfhack

    -

    If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address:

    -
    -git clone git://github.com/peterix/dfhack.git
    -cd dfhack
    -git submodule init
    -git submodule update
    -
    -

    The tortoisegit GUI should have the equivalent options included.

    -

    If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode).

    -
    -
    -

    Dependencies

    -

    First, you need cmake. Get the win32 installer version from the official -site: http://www.cmake.org/cmake/resources/software.html

    -

    It has the usual installer wizard. Make sure you let it add its binary folder -to your binary search PATH so the tool can be later run from anywhere.

    -

    You'll need a copy of Microsoft Visual C++ 2010. The Express version is sufficient. -Grab it from Microsoft's site.

    -

    For the code generation parts, you'll need perl and XML::LibXML. You can install them like this:

    -
      -
    • download and install strawberry perl from http://strawberryperl.com/
    • -
    • reboot so that the system can pick up the new binary path
    • -
    • open a cmd.exe window and run "cpan XML::LibXML" (obviously without the quotes). This can take a while to complete.
    • -
    • Same with "cpan XML::LibXSLT".
    • -
    -

    If you already have a different version of perl (for example the one from cygwin), you can run into some trouble. Either remove the other perl install from PATH, or install libxml and libxslt for it instead. Strawberry perl works though and has all the required packages.

    -
    -
    -

    Build

    -

    There are several different batch files in the build folder along with a script that's used for picking the DF path.

    -

    First, run set_df_path.vbs and point the dialog that pops up at your DF folder that you want to use for development. -Next, run one of the scripts with generate prefix. These create the MSVC solution file(s):

    -
      -
    • all will create a solution with everything enabled (and the kitchen sink).
    • -
    • gui will pop up the cmake gui and let you pick and choose what to build. This is probably what you want most of the time. Set the options you are interested in, then hit configure, then generate. More options can appear after the configure step.
    • -
    • minimal will create a minimal solution with just the bare necessities - the main library and standard plugins.
    • -
    -

    Then you can either open the solution with MSVC or use one of the msbuild scripts:

    -
      -
    • Scripts with build prefix will only build.
    • -
    • Scripts with install prefix will build DFHack and install it to the previously selected DF path.
    • -
    • Scripts with package prefix will build and create a .zip package of DFHack.
    • -
    -

    When you open the solution in MSVC, make sure you never use the Debug builds. Those aren't -binary-compatible with DF. If you try to use a debug build with DF, you'll only get crashes. -So pick either Release or RelWithDebInfo build and build the INSTALL target.

    -

    The debug scripts actually do RelWithDebInfo builds.

    -
    -
    -
    -

    Build types

    -

    cmake allows you to pick a build type by changing this -variable: CMAKE_BUILD_TYPE

    -
    -cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE
    -
    -

    Without specifying a build type or 'None', cmake uses the -CMAKE_CXX_FLAGS variable for building.

    -

    Valid and useful build types include 'Release', 'Debug' and -'RelWithDebInfo'. 'Debug' is not available on Windows.

    -
    -
    -

    Using the library as a developer

    -

    Currently, the most direct way to use the library is to write a plugin that can be loaded by it. -All the plugins can be found in the 'plugins' folder. There's no in-depth documentation -on how to write one yet, but it should be easy enough to copy one and just follow the pattern.

    -

    Other than through plugins, it is possible to use DFHack via remote access interface, or by writing Lua scripts.

    -

    The most important parts of DFHack are the Core, Console, Modules and Plugins.

    -
      -
    • Core acts as the centerpiece of DFHack - it acts as a filter between DF and SDL and synchronizes the various plugins with DF.
    • -
    • Console is a thread-safe console that can be used to invoke commands exported by Plugins.
    • -
    • Modules actually describe the way to access information in DF's memory. You can get them from the Core. Most modules are split into two parts: high-level and low-level. Higl-level is mostly method calls, low-level publicly visible pointers to DF's data structures.
    • -
    • Plugins are the tools that use all the other stuff to make things happen. A plugin can have a list of commands that it exports and an onupdate function that will be called each DF game tick.
    • -
    -

    Rudimentary API documentation can be built using doxygen (see build options with ccmake or cmake-gui).

    -

    DFHack consists of variously licensed code, but invariably weak copyleft. -The main license is zlib/libpng, some bits are MIT licensed, and some are BSD licensed.

    -

    Feel free to add your own extensions and plugins. Contributing back to -the dfhack repository is welcome and the right thing to do :)

    -
    -

    DF data structure definitions

    -

    DFHack uses information about the game data structures, represented via xml files in the library/xml/ submodule.

    -

    Data structure layouts are described in files following the df.*.xml name pattern. This information is transformed by a perl script into C++ headers describing the structures, and associated metadata for the Lua wrapper. These headers and data are then compiled into the DFHack libraries, thus necessitating a compatibility break every time layouts change; in return it significantly boosts the efficiency and capabilities of DFHack code.

    -

    Global object addresses are stored in symbols.xml, which is copied to the dfhack release package and loaded as data at runtime.

    -
    -
    -

    Remote access interface

    -

    DFHack supports remote access by exchanging Google protobuf messages via a TCP socket. Both the core and plugins can define remotely accessible methods. The dfhack-run command uses this interface to invoke ordinary console commands.

    -

    Currently the supported set of requests is limited, because the developers don't know what exactly is most useful.

    -

    Protocol client implementations exist for Java and C#.

    -
    -
    -

    Contributing to DFHack

    -

    Several things should be kept in mind when contributing to DFHack.

    -
    -

    Coding style

    -

    DFhack uses ANSI formatting and four spaces as indentation. Line -endings are UNIX. The files use UTF-8 encoding. Code not following this -won't make me happy, because I'll have to fix it. There's a good chance -I'll make you fix it ;)

    -
    -
    -

    How to get new code into DFHack

    -

    You can send patches or make a clone of the github repo and ask me on -the IRC channel to pull your code in. I'll review it and see if there -are any problems. I'll fix them if they are minor.

    -

    Fixes are higher in priority. If you want to work on something, but -don't know what, check out http://github.com/peterix/dfhack/issues -- -this is also a good place to dump new ideas and/or bugs that need -fixing.

    -
    -
    -

    Memory research

    -

    If you want to do memory research, you'll need some tools and some knowledge. -In general, you'll need a good memory viewer and optionally something -to look at machine code without getting crazy :)

    -

    Good windows tools include:

    -
      -
    • Cheat Engine
    • -
    • IDA Pro (the free version)
    • -
    -

    Good linux tools:

    -
      -
    • angavrilov's df-structures gui (visit us on IRC for details).
    • -
    • edb (Evan's Debugger)
    • -
    • IDA Pro running under wine.
    • -
    • Some of the tools residing in the legacy dfhack branch.
    • -
    -

    Using publicly known information and analyzing the game's data is preferred.

    -
    -
    -
    -
    - - diff --git a/Lua API.html b/Lua API.html deleted file mode 100644 index 36be1ba4a..000000000 --- a/Lua API.html +++ /dev/null @@ -1,2092 +0,0 @@ - - - - - - -DFHack Lua API - - - -
    -

    DFHack Lua API

    - - -

    The current version of DFHack has extensive support for -the Lua scripting language, providing access to:

    -
      -
    1. Raw data structures used by the game.
    2. -
    3. Many C++ functions for high-level access to these -structures, and interaction with dfhack itself.
    4. -
    5. Some functions exported by C++ plugins.
    6. -
    -

    Lua code can be used both for writing scripts, which -are treated by DFHack command line prompt almost as -native C++ commands, and invoked by plugins written in c++.

    -

    This document describes native API available to Lua in detail. -It does not describe all of the utility functions -implemented by Lua files located in hack/lua/...

    -
    -

    DF data structure wrapper

    -

    DF structures described by the xml files in library/xml are exported -to lua code as a tree of objects and functions under the df global, -which broadly maps to the df namespace in C++.

    -

    WARNING: The wrapper provides almost raw access to the memory -of the game, so mistakes in manipulating objects are as likely to -crash the game as equivalent plain C++ code would be. E.g. NULL -pointer access is safely detected, but dangling pointers aren't.

    -

    Objects managed by the wrapper can be broadly classified into the following groups:

    -
      -
    1. Typed object pointers (references).

      -

      References represent objects in DF memory with a known type.

      -

      In addition to fields and methods defined by the wrapped type, -every reference has some built-in properties and methods.

      -
    2. -
    3. Untyped pointers

      -

      Represented as lightuserdata.

      -

      In assignment to a pointer NULL can be represented either as -nil, or a NULL lightuserdata; reading a NULL pointer field -returns nil.

      -
    4. -
    5. Named types

      -

      Objects in the df tree that represent identity of struct, class, -enum and bitfield types. They host nested named types, static -methods, builtin properties & methods, and, for enums and bitfields, -the bi-directional mapping between key names and values.

      -
    6. -
    7. The global object

      -

      df.global corresponds to the df::global namespace, and -behaves as a mix between a named type and a reference, containing -both nested types and fields corresponding to global symbols.

      -
    8. -
    -

    In addition to the global object and top-level types the df -global also contains a few global builtin utility functions.

    -
    -

    Typed object references

    -

    The underlying primitive lua object is userdata with a metatable. -Every structured field access produces a new userdata instance.

    -

    All typed objects have the following built-in features:

    -
      -
    • ref1 == ref2, tostring(ref)

      -

      References implement equality by type & pointer value, and string conversion.

      -
    • -
    • pairs(ref)

      -

      Returns an iterator for the sequence of actual C++ field names -and values. Fields are enumerated in memory order. Methods and -lua wrapper properties are not included in the iteration.

      -

      WARNING: a few of the data structures (like ui_look_list) -contain unions with pointers to different types with vtables. -Using pairs on such structs is an almost sure way to crash with -an access violation.

      -
    • -
    • ref._kind

      -

      Returns one of: primitive, struct, container, -or bitfield, as appropriate for the referenced object.

      -
    • -
    • ref._type

      -

      Returns the named type object or a string that represents -the referenced object type.

      -
    • -
    • ref:sizeof()

      -

      Returns size, address

      -
    • -
    • ref:new()

      -

      Allocates a new instance of the same type, and copies data -from the current object.

      -
    • -
    • ref:delete()

      -

      Destroys the object with the C++ delete operator. -If destructor is not available, returns false.

      -

      WARNING: the lua reference object remains as a dangling -pointer, like a raw C++ pointer would.

      -
    • -
    • ref:assign(object)

      -

      Assigns data from object to ref. Object must either be another -ref of a compatible type, or a lua table; in the latter case -special recursive assignment rules are applied.

      -
    • -
    • ref:_displace(index[,step])

      -

      Returns a new reference with the pointer adjusted by index*step. -Step defaults to the natural object size.

      -
    • -
    -
    -

    Primitive references

    -

    References of the _kind 'primitive' are used for objects -that don't fit any of the other reference types. Such -references can only appear as a value of a pointer field, -or as a result of calling the _field() method.

    -

    They behave as structs with one field value of the right type.

    -

    To make working with numeric buffers easier, they also allow -numeric indices. Note that other than excluding negative values -no bound checking is performed, since buffer length is not available. -Index 0 is equivalent to the value field.

    -
    -
    -

    Struct references

    -

    Struct references are used for class and struct objects.

    -

    They implement the following features:

    -
      -
    • ref.field, ref.field = value

      -

      Valid fields of the structure may be accessed by subscript.

      -

      Primitive typed fields, i.e. numbers & strings, are converted -to/from matching lua values. The value of a pointer is a reference -to the target, or nil/NULL. Complex types are represented by -a reference to the field within the structure; unless recursive -lua table assignment is used, such fields can only be read.

      -

      NOTE: In case of inheritance, superclass fields have precedence -over the subclass, but fields shadowed in this way can still -be accessed as ref['subclasstype.field']. -This shadowing order is necessary because vtable-based classes -are automatically exposed in their exact type, and the reverse -rule would make access to superclass fields unreliable.

      -
    • -
    • ref._field(field)

      -

      Returns a reference to a valid field. That is, unlike regular -subscript, it returns a reference to the field within the structure -even for primitive typed fields and pointers.

      -
    • -
    • ref:vmethod(args...)

      -

      Named virtual methods are also exposed, subject to the same -shadowing rules.

      -
    • -
    • pairs(ref)

      -

      Enumerates all real fields (but not methods) in memory -(= declaration) order.

      -
    • -
    -
    -
    -

    Container references

    -

    Containers represent vectors and arrays, possibly resizable.

    -

    A container field can associate an enum to the container -reference, which allows accessing elements using string keys -instead of numerical indices.

    -

    Implemented features:

    -
      -
    • ref._enum

      -

      If the container has an associated enum, returns the matching -named type object.

      -
    • -
    • #ref

      -

      Returns the length of the container.

      -
    • -
    • ref[index]

      -

      Accesses the container element, using either a 0-based numerical -index, or, if an enum is associated, a valid enum key string.

      -

      Accessing an invalid index is an error, but some container types -may return a default value, or auto-resize instead for convenience. -Currently this relaxed mode is implemented by df-flagarray aka BitArray.

      -
    • -
    • ref._field(index)

      -

      Like with structs, returns a pointer to the array element, if possible. -Flag and bit arrays cannot return such pointer, so it fails with an error.

      -
    • -
    • pairs(ref), ipairs(ref)

      -

      If the container has no associated enum, both behave identically, -iterating over numerical indices in order. Otherwise, ipairs still -uses numbers, while pairs tries to substitute enum keys whenever -possible.

      -
    • -
    • ref:resize(new_size)

      -

      Resizes the container if supported, or fails with an error.

      -
    • -
    • ref:insert(index,item)

      -

      Inserts a new item at the specified index. To add at the end, -use #ref, or just '#' as index.

      -
    • -
    • ref:erase(index)

      -

      Removes the element at the given valid index.

      -
    • -
    -
    -
    -

    Bitfield references

    -

    Bitfields behave like special fixed-size containers. -Consider them to be something in between structs and -fixed-size vectors.

    -

    The _enum property points to the bitfield type. -Numerical indices correspond to the shift value, -and if a subfield occupies multiple bits, the -ipairs order would have a gap.

    -

    Since currently there is no API to allocate a bitfield -object fully in GC-managed lua heap, consider using the -lua table assignment feature outlined below in order to -pass bitfield values to dfhack API functions that need -them, e.g. matinfo:matches{metal=true}.

    -
    -
    -
    -

    Named types

    -

    Named types are exposed in the df tree with names identical -to the C++ version, except for the :: vs . difference.

    -

    All types and the global object have the following features:

    -
      -
    • type._kind

      -

      Evaluates to one of struct-type, class-type, enum-type, -bitfield-type or global.

      -
    • -
    • type._identity

      -

      Contains a lightuserdata pointing to the underlying -DFHack::type_instance object.

      -
    • -
    -

    Types excluding the global object also support:

    -
      -
    • type:sizeof()

      -

      Returns the size of an object of the type.

      -
    • -
    • type:new()

      -

      Creates a new instance of an object of the type.

      -
    • -
    • type:is_instance(object)

      -

      Returns true if object is same or subclass type, or a reference -to an object of same or subclass type. It is permissible to pass -nil, NULL or non-wrapper value as object; in this case the -method returns nil.

      -
    • -
    -

    In addition to this, enum and bitfield types contain a -bi-directional mapping between key strings and values, and -also map _first_item and _last_item to the min and -max values.

    -

    Struct and class types with instance-vector attribute in the -xml have a type.find(key) function that wraps the find -method provided in C++.

    -
    -
    -

    Global functions

    -

    The df table itself contains the following functions and values:

    -
      -
    • NULL, df.NULL

      -

      Contains the NULL lightuserdata.

      -
    • -
    • df.isnull(obj)

      -

      Evaluates to true if obj is nil or NULL; false otherwise.

      -
    • -
    • df.isvalid(obj[,allow_null])

      -

      For supported objects returns one of type, voidptr, ref.

      -

      If allow_null is true, and obj is nil or NULL, returns null.

      -

      Otherwise returns nil.

      -
    • -
    • df.sizeof(obj)

      -

      For types and refs identical to obj:sizeof(). -For lightuserdata returns nil, address

      -
    • -
    • df.new(obj), df.delete(obj), df.assign(obj, obj2)

      -

      Equivalent to using the matching methods of obj.

      -
    • -
    • df._displace(obj,index[,step])

      -

      For refs equivalent to the method, but also works with -lightuserdata (step is mandatory then).

      -
    • -
    • df.is_instance(type,obj)

      -

      Equivalent to the method, but also allows a reference as proxy for its type.

      -
    • -
    • df.new(ptype[,count])

      -

      Allocate a new instance, or an array of built-in types. -The ptype argument is a string from the following list: -string, int8_t, uint8_t, int16_t, uint16_t, -int32_t, uint32_t, int64_t, uint64_t, bool, -float, double. All of these except string can be -used with the count argument to allocate an array.

      -
    • -
    • df.reinterpret_cast(type,ptr)

      -

      Converts ptr to a ref of specified type. The type may be anything -acceptable to df.is_instance. Ptr may be nil, a ref, -a lightuserdata, or a number.

      -

      Returns nil if NULL, or a ref.

      -
    • -
    -
    -
    -

    Recursive table assignment

    -

    Recursive assignment is invoked when a lua table is assigned -to a C++ object or field, i.e. one of:

    -
      -
    • ref:assign{...}
    • -
    • ref.field = {...}
    • -
    -

    The general mode of operation is that all fields of the table -are assigned to the fields of the target structure, roughly -emulating the following code:

    -
    -function rec_assign(ref,table)
    -    for key,value in pairs(table) do
    -        ref[key] = value
    -    end
    -end
    -
    -

    Since assigning a table to a field using = invokes the same -process, it is recursive.

    -

    There are however some variations to this process depending -on the type of the field being assigned to:

    -
      -
    1. If the table contains an assign field, it is -applied first, using the ref:assign(value) method. -It is never assigned as a usual field.

      -
    2. -
    3. When a table is assigned to a non-NULL pointer field -using the ref.field = {...} syntax, it is applied -to the target of the pointer instead.

      -

      If the pointer is NULL, the table is checked for a new field:

      -
        -
      1. If it is nil or false, assignment fails with an error.
      2. -
      3. If it is true, the pointer is initialized with a newly -allocated object of the declared target type of the pointer.
      4. -
      5. Otherwise, table.new must be a named type, or an -object of a type compatible with the pointer. The pointer -is initialized with the result of calling table.new:new().
      6. -
      -

      After this auto-vivification process, assignment proceeds -as if the pointer wasn't NULL.

      -

      Obviously, the new field inside the table is always skipped -during the actual per-field assignment processing.

      -
    4. -
    5. If the target of the assignment is a container, a separate -rule set is used:

      -
        -
      1. If the table contains neither assign nor resize -fields, it is interpreted as an ordinary 1-based lua -array. The container is resized to the #-size of the -table, and elements are assigned in numeric order:

        -
        -ref:resize(#table);
        -for i=1,#table do ref[i-1] = table[i] end
        -
        -
      2. -
      3. Otherwise, resize must be true, false, or -an explicit number. If it is not false, the container -is resized. After that the usual struct-like 'pairs' -assignment is performed.

        -

        In case resize is true, the size is computed -by scanning the table for the largest numeric key.

        -
      4. -
      -

      This means that in order to reassign only one element of -a container using this system, it is necessary to use:

      -
      -{ resize=false, [idx]=value }
      -
      -
    6. -
    -

    Since nil inside a table is indistinguishable from missing key, -it is necessary to use df.NULL as a null pointer value.

    -

    This system is intended as a way to define a nested object -tree using pure lua data structures, and then materialize it in -C++ memory in one go. Note that if pointer auto-vivification -is used, an error in the middle of the recursive walk would -not destroy any objects allocated in this way, so the user -should be prepared to catch the error and do the necessary -cleanup.

    -
    -
    -
    -

    DFHack API

    -

    DFHack utility functions are placed in the dfhack global tree.

    -
    -

    Native utilities

    -
    -

    Input & Output

    -
      -
    • dfhack.print(args...)

      -

      Output tab-separated args as standard lua print would do, -but without a newline.

      -
    • -
    • print(args...), dfhack.println(args...)

      -

      A replacement of the standard library print function that -works with DFHack output infrastructure.

      -
    • -
    • dfhack.printerr(args...)

      -

      Same as println; intended for errors. Uses red color and logs to stderr.log.

      -
    • -
    • dfhack.color([color])

      -

      Sets the current output color. If color is nil or -1, resets to default. -Returns the previous color value.

      -
    • -
    • dfhack.is_interactive()

      -

      Checks if the thread can access the interactive console and returns true or false.

      -
    • -
    • dfhack.lineedit([prompt[,history_filename]])

      -

      If the thread owns the interactive console, shows a prompt -and returns the entered string. Otherwise returns nil, error.

      -

      Depending on the context, this function may actually yield the -running coroutine and let the C++ code release the core suspend -lock. Using an explicit dfhack.with_suspend will prevent -this, forcing the function to block on input with lock held.

      -
    • -
    • dfhack.interpreter([prompt[,history_filename[,env]]])

      -

      Starts an interactive lua interpreter, using the specified prompt -string, global environment and command-line history file.

      -

      If the interactive console is not accessible, returns nil, error.

      -
    • -
    -
    -
    -

    Exception handling

    -
      -
    • dfhack.error(msg[,level[,verbose]])

      -

      Throws a dfhack exception object with location and stack trace. -The verbose parameter controls whether the trace is printed by default.

      -
    • -
    • qerror(msg[,level])

      -

      Calls dfhack.error() with verbose being false. Intended to -be used for user-caused errors in scripts, where stack traces are not -desirable.

      -
    • -
    • dfhack.pcall(f[,args...])

      -

      Invokes f via xpcall, using an error function that attaches -a stack trace to the error. The same function is used by SafeCall -in C++, and dfhack.safecall.

      -
    • -
    • safecall(f[,args...]), dfhack.safecall(f[,args...])

      -

      Just like pcall, but also prints the error using printerr before -returning. Intended as a convenience function.

      -
    • -
    • dfhack.saferesume(coroutine[,args...])

      -

      Compares to coroutine.resume like dfhack.safecall vs pcall.

      -
    • -
    • dfhack.exception

      -

      Metatable of error objects used by dfhack. The objects have the -following properties:

      -
      -
      err.where
      -

      The location prefix string, or nil.

      -
      -
      err.message
      -

      The base message string.

      -
      -
      err.stacktrace
      -

      The stack trace string, or nil.

      -
      -
      err.cause
      -

      A different exception object, or nil.

      -
      -
      err.thread
      -

      The coroutine that has thrown the exception.

      -
      -
      err.verbose
      -

      Boolean, or nil; specifies if where and stacktrace should be printed.

      -
      -
      tostring(err), or err:tostring([verbose])
      -

      Converts the exception to string.

      -
      -
      -
    • -
    • dfhack.exception.verbose

      -

      The default value of the verbose argument of err:tostring().

      -
    • -
    -
    -
    -

    Miscellaneous

    -
      -
    • dfhack.VERSION

      -

      DFHack version string constant.

      -
    • -
    • dfhack.curry(func,args...), or curry(func,args...)

      -

      Returns a closure that invokes the function with args combined -both from the curry call and the closure call itself. I.e. -curry(func,a,b)(c,d) equals func(a,b,c,d).

      -
    • -
    -
    -
    -

    Locking and finalization

    -
      -
    • dfhack.with_suspend(f[,args...])

      -

      Calls f with arguments after grabbing the DF core suspend lock. -Suspending is necessary for accessing a consistent state of DF memory.

      -

      Returned values and errors are propagated through after releasing -the lock. It is safe to nest suspends.

      -

      Every thread is allowed only one suspend per DF frame, so it is best -to group operations together in one big critical section. A plugin -can choose to run all lua code inside a C++-side suspend lock.

      -
    • -
    • dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])

      -

      Invokes fn with args, and after it returns or throws an -error calls cleanup_fn with cleanup_args. Any return values from -fn are propagated, and errors are re-thrown.

      -

      The num_cleanup_args integer specifies the number of cleanup_args, -and the always boolean specifies if cleanup should be called in any case, -or only in case of an error.

      -
    • -
    • dfhack.with_finalize(cleanup_fn,fn[,args...])

      -

      Calls fn with arguments, then finalizes with cleanup_fn. -Implemented using call_with_finalizer(0,true,...).

      -
    • -
    • dfhack.with_onerror(cleanup_fn,fn[,args...])

      -

      Calls fn with arguments, then finalizes with cleanup_fn on any thrown error. -Implemented using call_with_finalizer(0,false,...).

      -
    • -
    • dfhack.with_temp_object(obj,fn[,args...])

      -

      Calls fn(obj,args...), then finalizes with obj:delete().

      -
    • -
    -
    -
    -

    Persistent configuration storage

    -

    This api is intended for storing configuration options in the world itself. -It probably should be restricted to data that is world-dependent.

    -

    Entries are identified by a string key, but it is also possible to manage -multiple entries with the same key; their identity is determined by entry_id. -Every entry has a mutable string value, and an array of 7 mutable ints.

    -
      -
    • dfhack.persistent.get(key), entry:get()

      -

      Retrieves a persistent config record with the given string key, -or refreshes an already retrieved entry. If there are multiple -entries with the same key, it is undefined which one is retrieved -by the first version of the call.

      -

      Returns entry, or nil if not found.

      -
    • -
    • dfhack.persistent.delete(key), entry:delete()

      -

      Removes an existing entry. Returns true if succeeded.

      -
    • -
    • dfhack.persistent.get_all(key[,match_prefix])

      -

      Retrieves all entries with the same key, or starting with key..'/'. -Calling get_all('',true) will match all entries.

      -

      If none found, returns nil; otherwise returns an array of entries.

      -
    • -
    • dfhack.persistent.save({key=str1, ...}[,new]), entry:save([new])

      -

      Saves changes in an entry, or creates a new one. Passing true as -new forces creation of a new entry even if one already exists; -otherwise the existing one is simply updated. -Returns entry, did_create_new

      -
    • -
    -

    Since the data is hidden in data structures owned by the DF world, -and automatically stored in the save game, these save and retrieval -functions can just copy values in memory without doing any actual I/O. -However, currently every entry has a 180+-byte dead-weight overhead.

    -
    -
    -

    Material info lookup

    -

    A material info record has fields:

    -
      -
    • type, index, material

      -

      DF material code pair, and a reference to the material object.

      -
    • -
    • mode

      -

      One of 'builtin', 'inorganic', 'plant', 'creature'.

      -
    • -
    • inorganic, plant, creature

      -

      If the material is of the matching type, contains a reference to the raw object.

      -
    • -
    • figure

      -

      For a specific creature material contains a ref to the historical figure.

      -
    • -
    -

    Functions:

    -
      -
    • dfhack.matinfo.decode(type,index)

      -

      Looks up material info for the given number pair; if not found, returs nil.

      -
    • -
    • ....decode(matinfo), ....decode(item), ....decode(obj)

      -

      Uses matinfo.type/matinfo.index, item getter vmethods, -or obj.mat_type/obj.mat_index to get the code pair.

      -
    • -
    • dfhack.matinfo.find(token[,token...])

      -

      Looks up material by a token string, or a pre-split string token sequence.

      -
    • -
    • dfhack.matinfo.getToken(...), info:getToken()

      -

      Applies decode and constructs a string token.

      -
    • -
    • info:toString([temperature[,named]])

      -

      Returns the human-readable name at the given temperature.

      -
    • -
    • info:getCraftClass()

      -

      Returns the classification used for craft skills.

      -
    • -
    • info:matches(obj)

      -

      Checks if the material matches job_material_category or job_item. -Accept dfhack_material_category auto-assign table.

      -
    • -
    -
    -
    -
    -

    C++ function wrappers

    -

    Thin wrappers around C++ functions, similar to the ones for virtual methods. -One notable difference is that these explicit wrappers allow argument count -adjustment according to the usual lua rules, so trailing false/nil arguments -can be omitted.

    -
      -
    • dfhack.getOSType()

      -

      Returns the OS type string from symbols.xml.

      -
    • -
    • dfhack.getDFVersion()

      -

      Returns the DF version string from symbols.xml.

      -
    • -
    • dfhack.getDFPath()

      -

      Returns the DF directory path.

      -
    • -
    • dfhack.getHackPath()

      -

      Returns the dfhack directory path, i.e. ".../df/hack/".

      -
    • -
    • dfhack.getTickCount()

      -

      Returns the tick count in ms, exactly as DF ui uses.

      -
    • -
    • dfhack.isWorldLoaded()

      -

      Checks if the world is loaded.

      -
    • -
    • dfhack.isMapLoaded()

      -

      Checks if the world and map are loaded.

      -
    • -
    • dfhack.TranslateName(name[,in_english,only_last_name])

      -

      Convert a language_name or only the last name part to string.

      -
    • -
    -
    -

    Gui module

    -
      -
    • dfhack.gui.getCurViewscreen([skip_dismissed])

      -

      Returns the topmost viewscreen. If skip_dismissed is true, -ignores screens already marked to be removed.

      -
    • -
    • dfhack.gui.getFocusString(viewscreen)

      -

      Returns a string representation of the current focus position -in the ui. The string has a "screen/foo/bar/baz..." format.

      -
    • -
    • dfhack.gui.getCurFocus([skip_dismissed])

      -

      Returns the focus string of the current viewscreen.

      -
    • -
    • dfhack.gui.getSelectedWorkshopJob([silent])

      -

      When a job is selected in 'q' mode, returns the job, else -prints error unless silent and returns nil.

      -
    • -
    • dfhack.gui.getSelectedJob([silent])

      -

      Returns the job selected in a workshop or unit/jobs screen.

      -
    • -
    • dfhack.gui.getSelectedUnit([silent])

      -

      Returns the unit selected via 'v', 'k', unit/jobs, or -a full-screen item view of a cage or suchlike.

      -
    • -
    • dfhack.gui.getSelectedItem([silent])

      -

      Returns the item selected via 'v' ->inventory, 'k', 't', or -a full-screen item view of a container. Note that in the -last case, the highlighted contained item is returned, not -the container itself.

      -
    • -
    • dfhack.gui.getSelectedBuilding([silent])

      -

      Returns the building selected via 'q', 't', 'k' or 'i'.

      -
    • -
    • dfhack.gui.showAnnouncement(text,color[,is_bright])

      -

      Adds a regular announcement with given text, color, and brightness. -The is_bright boolean actually seems to invert the brightness.

      -
    • -
    • dfhack.gui.showZoomAnnouncement(type,pos,text,color[,is_bright])

      -

      Like above, but also specifies a position you can zoom to from the announcement menu.

      -
    • -
    • dfhack.gui.showPopupAnnouncement(text,color[,is_bright])

      -

      Pops up a titan-style modal announcement window.

      -
    • -
    • dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright])

      -

      Uses the type to look up options from announcements.txt, and calls the -above operations accordingly. If enabled, pauses and zooms to position.

      -
    • -
    -
    -
    -

    Job module

    -
      -
    • dfhack.job.cloneJobStruct(job)

      -

      Creates a deep copy of the given job.

      -
    • -
    • dfhack.job.printJobDetails(job)

      -

      Prints info about the job.

      -
    • -
    • dfhack.job.printItemDetails(jobitem,idx)

      -

      Prints info about the job item.

      -
    • -
    • dfhack.job.getHolder(job)

      -

      Returns the building holding the job.

      -
    • -
    • dfhack.job.getWorker(job)

      -

      Returns the unit performing the job.

      -
    • -
    • dfhack.job.checkBuildingsNow()

      -

      Instructs the game to check buildings for jobs next frame and assign workers.

      -
    • -
    • dfhack.job.checkDesignationsNow()

      -

      Instructs the game to check designations for jobs next frame and assign workers.

      -
    • -
    • dfhack.job.is_equal(job1,job2)

      -

      Compares important fields in the job and nested item structures.

      -
    • -
    • dfhack.job.is_item_equal(job_item1,job_item2)

      -

      Compares important fields in the job item structures.

      -
    • -
    • dfhack.job.listNewlyCreated(first_id)

      -

      Returns the current value of df.global.job_next_id, and -if there are any jobs with first_id <= id < job_next_id, -a lua list containing them.

      -
    • -
    -
    -
    -

    Units module

    -
      -
    • dfhack.units.getPosition(unit)

      -

      Returns true x,y,z of the unit, or nil if invalid; may be not equal to unit.pos if caged.

      -
    • -
    • dfhack.units.getContainer(unit)

      -

      Returns the container (cage) item or nil.

      -
    • -
    • dfhack.units.setNickname(unit,nick)

      -

      Sets the unit's nickname properly.

      -
    • -
    • dfhack.units.getVisibleName(unit)

      -

      Returns the language_name object visible in game, accounting for false identities.

      -
    • -
    • dfhack.units.getIdentity(unit)

      -

      Returns the false identity of the unit if it has one, or nil.

      -
    • -
    • dfhack.units.getNemesis(unit)

      -

      Returns the nemesis record of the unit if it has one, or nil.

      -
    • -
    • dfhack.units.isHidingCurse(unit)

      -

      Checks if the unit hides improved attributes from its curse.

      -
    • -
    • dfhack.units.getPhysicalAttrValue(unit, attr_type)

      -
    • -
    • dfhack.units.getMentalAttrValue(unit, attr_type)

      -

      Computes the effective attribute value, including curse effect.

      -
    • -
    • dfhack.units.isCrazed(unit)

      -
    • -
    • dfhack.units.isOpposedToLife(unit)

      -
    • -
    • dfhack.units.hasExtravision(unit)

      -
    • -
    • dfhack.units.isBloodsucker(unit)

      -

      Simple checks of caste attributes that can be modified by curses.

      -
    • -
    • dfhack.units.getMiscTrait(unit, type[, create])

      -

      Finds (or creates if requested) a misc trait object with the given id.

      -
    • -
    • dfhack.units.isDead(unit)

      -

      The unit is completely dead and passive, or a ghost.

      -
    • -
    • dfhack.units.isAlive(unit)

      -

      The unit isn't dead or undead.

      -
    • -
    • dfhack.units.isSane(unit)

      -

      The unit is capable of rational action, i.e. not dead, insane, zombie, or active werewolf.

      -
    • -
    • dfhack.units.isDwarf(unit)

      -

      The unit is of the correct race of the fortress.

      -
    • -
    • dfhack.units.isCitizen(unit)

      -

      The unit is an alive sane citizen of the fortress; wraps the -same checks the game uses to decide game-over by extinction.

      -
    • -
    • dfhack.units.getAge(unit[,true_age])

      -

      Returns the age of the unit in years as a floating-point value. -If true_age is true, ignores false identities.

      -
    • -
    • dfhack.units.getEffectiveSkill(unit, skill)

      -

      Computes the effective rating for the given skill, taking into account exhaustion, pain etc.

      -
    • -
    • dfhack.units.computeMovementSpeed(unit)

      -

      Computes number of frames * 100 it takes the unit to move in its current state of mind and body.

      -
    • -
    • dfhack.units.getNoblePositions(unit)

      -

      Returns a list of tables describing noble position assignments, or nil. -Every table has fields entity, assignment and position.

      -
    • -
    • dfhack.units.getProfessionName(unit[,ignore_noble,plural])

      -

      Retrieves the profession name using custom profession, noble assignments -or raws. The ignore_noble boolean disables the use of noble positions.

      -
    • -
    • dfhack.units.getCasteProfessionName(race,caste,prof_id[,plural])

      -

      Retrieves the profession name for the given race/caste using raws.

      -
    • -
    • dfhack.units.getProfessionColor(unit[,ignore_noble])

      -

      Retrieves the color associated with the profession, using noble assignments -or raws. The ignore_noble boolean disables the use of noble positions.

      -
    • -
    • dfhack.units.getCasteProfessionColor(race,caste,prof_id)

      -

      Retrieves the profession color for the given race/caste using raws.

      -
    • -
    -
    -
    -

    Items module

    -
      -
    • dfhack.items.getPosition(item)

      -

      Returns true x,y,z of the item, or nil if invalid; may be not equal to item.pos if in inventory.

      -
    • -
    • dfhack.items.getDescription(item, type[, decorate])

      -

      Returns the string description of the item, as produced by the getItemDescription -method. If decorate is true, also adds markings for quality and improvements.

      -
    • -
    • dfhack.items.getGeneralRef(item, type)

      -

      Searches for a general_ref with the given type.

      -
    • -
    • dfhack.items.getSpecificRef(item, type)

      -

      Searches for a specific_ref with the given type.

      -
    • -
    • dfhack.items.getOwner(item)

      -

      Returns the owner unit or nil.

      -
    • -
    • dfhack.items.setOwner(item,unit)

      -

      Replaces the owner of the item. If unit is nil, removes ownership. -Returns false in case of error.

      -
    • -
    • dfhack.items.getContainer(item)

      -

      Returns the container item or nil.

      -
    • -
    • dfhack.items.getContainedItems(item)

      -

      Returns a list of items contained in this one.

      -
    • -
    • dfhack.items.moveToGround(item,pos)

      -

      Move the item to the ground at position. Returns false if impossible.

      -
    • -
    • dfhack.items.moveToContainer(item,container)

      -

      Move the item to the container. Returns false if impossible.

      -
    • -
    • dfhack.items.moveToBuilding(item,building,use_mode)

      -

      Move the item to the building. Returns false if impossible.

      -
    • -
    • dfhack.items.moveToInventory(item,unit,use_mode,body_part)

      -

      Move the item to the unit inventory. Returns false if impossible.

      -
    • -
    • dfhack.items.remove(item[, no_uncat])

      -

      Removes the item, and marks it for garbage collection unless no_uncat is true.

      -
    • -
    • dfhack.items.makeProjectile(item)

      -

      Turns the item into a projectile, and returns the new object, or nil if impossible.

      -
    • -
    -
    -
    -

    Maps module

    -
      -
    • dfhack.maps.getSize()

      -

      Returns map size in blocks: x, y, z

      -
    • -
    • dfhack.maps.getTileSize()

      -

      Returns map size in tiles: x, y, z

      -
    • -
    • dfhack.maps.getBlock(x,y,z)

      -

      Returns a map block object for given x,y,z in local block coordinates.

      -
    • -
    • dfhack.maps.isValidTilePos(coords), or isValidTilePos(x,y,z)``

      -

      Checks if the given df::coord or x,y,z in local tile coordinates are valid.

      -
    • -
    • dfhack.maps.getTileBlock(coords), or getTileBlock(x,y,z)

      -

      Returns a map block object for given df::coord or x,y,z in local tile coordinates.

      -
    • -
    • dfhack.maps.ensureTileBlock(coords), or ensureTileBlock(x,y,z)

      -

      Like getTileBlock, but if the block is not allocated, try creating it.

      -
    • -
    • dfhack.maps.getRegionBiome(region_coord2d), or getRegionBiome(x,y)

      -

      Returns the biome info struct for the given global map region.

      -
    • -
    • dfhack.maps.enableBlockUpdates(block[,flow,temperature])

      -

      Enables updates for liquid flow or temperature, unless already active.

      -
    • -
    • dfhack.maps.spawnFlow(pos,type,mat_type,mat_index,dimension)

      -

      Spawns a new flow (i.e. steam/mist/dust/etc) at the given pos, and with -the given parameters. Returns it, or nil if unsuccessful.

      -
    • -
    • dfhack.maps.getGlobalInitFeature(index)

      -

      Returns the global feature object with the given index.

      -
    • -
    • dfhack.maps.getLocalInitFeature(region_coord2d,index)

      -

      Returns the local feature object with the given region coords and index.

      -
    • -
    • dfhack.maps.getTileBiomeRgn(coords), or getTileBiomeRgn(x,y,z)

      -

      Returns x, y for use with getRegionBiome.

      -
    • -
    • dfhack.maps.canWalkBetween(pos1, pos2)

      -

      Checks if a dwarf may be able to walk between the two tiles, -using a pathfinding cache maintained by the game. Note that -this cache is only updated when the game is unpaused, and thus -can get out of date if doors are forbidden or unforbidden, or -tools like liquids or tiletypes are used. It also cannot possibly -take into account anything that depends on the actual units, like -burrows, or the presence of invaders.

      -
    • -
    -
    -
    -

    Burrows module

    -
      -
    • dfhack.burrows.findByName(name)

      -

      Returns the burrow pointer or nil.

      -
    • -
    • dfhack.burrows.clearUnits(burrow)

      -

      Removes all units from the burrow.

      -
    • -
    • dfhack.burrows.isAssignedUnit(burrow,unit)

      -

      Checks if the unit is in the burrow.

      -
    • -
    • dfhack.burrows.setAssignedUnit(burrow,unit,enable)

      -

      Adds or removes the unit from the burrow.

      -
    • -
    • dfhack.burrows.clearTiles(burrow)

      -

      Removes all tiles from the burrow.

      -
    • -
    • dfhack.burrows.listBlocks(burrow)

      -

      Returns a table of map block pointers.

      -
    • -
    • dfhack.burrows.isAssignedTile(burrow,tile_coord)

      -

      Checks if the tile is in burrow.

      -
    • -
    • dfhack.burrows.setAssignedTile(burrow,tile_coord,enable)

      -

      Adds or removes the tile from the burrow. Returns false if invalid coords.

      -
    • -
    • dfhack.burrows.isAssignedBlockTile(burrow,block,x,y)

      -

      Checks if the tile within the block is in burrow.

      -
    • -
    • dfhack.burrows.setAssignedBlockTile(burrow,block,x,y,enable)

      -

      Adds or removes the tile from the burrow. Returns false if invalid coords.

      -
    • -
    -
    -
    -

    Buildings module

    -
      -
    • dfhack.buildings.setOwner(item,unit)

      -

      Replaces the owner of the building. If unit is nil, removes ownership. -Returns false in case of error.

      -
    • -
    • dfhack.buildings.getSize(building)

      -

      Returns width, height, centerx, centery.

      -
    • -
    • dfhack.buildings.findAtTile(pos), or findAtTile(x,y,z)

      -

      Scans the buildings for the one located at the given tile. -Does not work on civzones. Warning: linear scan if the map -tile indicates there are buildings at it.

      -
    • -
    • dfhack.buildings.findCivzonesAt(pos), or findCivzonesAt(x,y,z)

      -

      Scans civzones, and returns a lua sequence of those that touch -the given tile, or nil if none.

      -
    • -
    • dfhack.buildings.getCorrectSize(width, height, type, subtype, custom, direction)

      -

      Computes correct dimensions for the specified building type and orientation, -using width and height for flexible dimensions. -Returns is_flexible, width, height, center_x, center_y.

      -
    • -
    • dfhack.buildings.checkFreeTiles(pos,size[,extents,change_extents,allow_occupied])

      -

      Checks if the rectangle defined by pos and size, and possibly extents, -can be used for placing a building. If change_extents is true, bad tiles -are removed from extents. If allow_occupied, the occupancy test is skipped.

      -
    • -
    • dfhack.buildings.countExtentTiles(extents,defval)

      -

      Returns the number of tiles included by extents, or defval.

      -
    • -
    • dfhack.buildings.containsTile(building, x, y[, room])

      -

      Checks if the building contains the specified tile, either directly, or as room.

      -
    • -
    • dfhack.buildings.hasSupport(pos,size)

      -

      Checks if a bridge constructed at specified position would have -support from terrain, and thus won't collapse if retracted.

      -
    • -
    -

    Low-level building creation functions;

    -
      -
    • dfhack.buildings.allocInstance(pos, type, subtype, custom)

      -

      Creates a new building instance of given type, subtype and custom type, -at specified position. Returns the object, or nil in case of an error.

      -
    • -
    • dfhack.buildings.setSize(building, width, height, direction)

      -

      Configures an object returned by allocInstance, using specified -parameters wherever appropriate. If the building has fixed size along -any dimension, the corresponding input parameter will be ignored. -Returns false if the building cannot be placed, or true, width, -height, rect_area, true_area. Returned width and height are the -final values used by the building; true_area is less than rect_area -if any tiles were removed from designation.

      -
    • -
    • dfhack.buildings.constructAbstract(building)

      -

      Links a fully configured object created by allocInstance into the -world. The object must be an abstract building, i.e. a stockpile or civzone. -Returns true, or false if impossible.

      -
    • -
    • dfhack.buildings.constructWithItems(building, items)

      -

      Links a fully configured object created by allocInstance into the -world for construction, using a list of specific items as material. -Returns true, or false if impossible.

      -
    • -
    • dfhack.buildings.constructWithFilters(building, job_items)

      -

      Links a fully configured object created by allocInstance into the -world for construction, using a list of job_item filters as inputs. -Returns true, or false if impossible. Filter objects are claimed -and possibly destroyed in any case. -Use a negative quantity field value to auto-compute the amount -from the size of the building.

      -
    • -
    • dfhack.buildings.deconstruct(building)

      -

      Destroys the building, or queues a deconstruction job. -Returns true if the building was destroyed and deallocated immediately.

      -
    • -
    -

    More high-level functions are implemented in lua and can be loaded by -require('dfhack.buildings'). See hack/lua/dfhack/buildings.lua.

    -

    Among them are:

    -
      -
    • dfhack.buildings.getFiltersByType(argtable,type,subtype,custom)

      -

      Returns a sequence of lua structures, describing input item filters -suitable for the specified building type, or nil if unknown or invalid. -The returned sequence is suitable for use as the job_items argument -of constructWithFilters. -Uses tables defined in buildings.lua.

      -

      Argtable members material (the default name), bucket, barrel, -chain, mechanism, screw, pipe, anvil, weapon are used to -augment the basic attributes with more detailed information if the -building has input items with the matching name (see the tables for naming details). -Note that it is impossible to override any properties this way, only supply those that -are not mentioned otherwise; one exception is that flags2.non_economic -is automatically cleared if an explicit material is specified.

      -
    • -
    • dfhack.buildings.constructBuilding{...}

      -

      Creates a building in one call, using options contained -in the argument table. Returns the building, or nil, error.

      -

      NOTE: Despite the name, unless the building is abstract, -the function creates it in an 'unconstructed' stage, with -a queued in-game job that will actually construct it. I.e. -the function replicates programmatically what can be done -through the construct building menu in the game ui, except -that it does less environment constraint checking.

      -

      The following options can be used:

      -
        -
      • pos = coordinates, or x = ..., y = ..., z = ...

        -

        Mandatory. Specifies the left upper corner of the building.

        -
      • -
      • type = df.building_type.FOO, subtype = ..., custom = ...

        -

        Mandatory. Specifies the type of the building. Obviously, subtype -and custom are only expected if the type requires them.

        -
      • -
      • fields = { ... }

        -

        Initializes fields of the building object after creation with df.assign.

        -
      • -
      • width = ..., height = ..., direction = ...

        -

        Sets size and orientation of the building. If it is -fixed-size, specified dimensions are ignored.

        -
      • -
      • full_rectangle = true

        -

        For buildings like stockpiles or farm plots that can normally -accomodate individual tile exclusion, forces an error if any -tiles within the specified width*height are obstructed.

        -
      • -
      • items = { item, item ... }, or filters = { {...}, {...}... }

        -

        Specifies explicit items or item filters to use in construction. -It is the job of the user to ensure they are correct for the building type.

        -
      • -
      • abstract = true

        -

        Specifies that the building is abstract and does not require construction. -Required for stockpiles and civzones; an error otherwise.

        -
      • -
      • material = {...}, mechanism = {...}, ...

        -

        If none of items, filter, or abstract is used, -the function uses getFiltersByType to compute the input -item filters, and passes the argument table through. If no filters -can be determined this way, constructBuilding throws an error.

        -
      • -
      -
    • -
    -
    -
    -

    Constructions module

    -
      -
    • dfhack.constructions.designateNew(pos,type,item_type,mat_index)

      -

      Designates a new construction at given position. If there already is -a planned but not completed construction there, changes its type. -Returns true, or false if obstructed. -Note that designated constructions are technically buildings.

      -
    • -
    • dfhack.constructions.designateRemove(pos), or designateRemove(x,y,z)

      -

      If there is a construction or a planned construction at the specified -coordinates, designates it for removal, or instantly cancels the planned one. -Returns true, was_only_planned if removed; or false if none found.

      -
    • -
    -
    -
    -

    Screen API

    -

    The screen module implements support for drawing to the tiled screen of the game. -Note that drawing only has any effect when done from callbacks, so it can only -be feasibly used in the core context.

    -

    Basic painting functions:

    -
      -
    • dfhack.screen.getWindowSize()

      -

      Returns width, height of the screen.

      -
    • -
    • dfhack.screen.getMousePos()

      -

      Returns x,y of the tile the mouse is over.

      -
    • -
    • dfhack.screen.inGraphicsMode()

      -

      Checks if [GRAPHICS:YES] was specified in init.

      -
    • -
    • dfhack.screen.paintTile(pen,x,y[,char,tile])

      -

      Paints a tile using given parameters. Pen is a table with following possible fields:

      -
      -
      ch
      -

      Provides the ordinary tile character, as either a 1-character string or a number. -Can be overridden with the char function parameter.

      -
      -
      fg
      -

      Foreground color for the ordinary tile. Defaults to COLOR_GREY (7).

      -
      -
      bg
      -

      Background color for the ordinary tile. Defaults to COLOR_BLACK (0).

      -
      -
      bold
      -

      Bright/bold text flag. If nil, computed based on (fg & 8); fg is masked to 3 bits. -Otherwise should be true/false.

      -
      -
      tile
      -

      Graphical tile id. Ignored unless [GRAPHICS:YES] was in init.txt.

      -
      -
      tile_color = true
      -

      Specifies that the tile should be shaded with fg/bg.

      -
      -
      tile_fg, tile_bg
      -

      If specified, overrides tile_color and supplies shading colors directly.

      -
      -
      -

      Returns false if coordinates out of bounds, or other error.

      -
    • -
    • dfhack.screen.readTile(x,y)

      -

      Retrieves the contents of the specified tile from the screen buffers. -Returns a pen, or nil if invalid or TrueType.

      -
    • -
    • dfhack.screen.paintString(pen,x,y,text)

      -

      Paints the string starting at x,y. Uses the string characters -in sequence to override the ch field of pen.

      -

      Returns true if painting at least one character succeeded.

      -
    • -
    • dfhack.screen.fillRect(pen,x1,y1,x2,y2)

      -

      Fills the rectangle specified by the coordinates with the given pen. -Returns true if painting at least one character succeeded.

      -
    • -
    • dfhack.screen.findGraphicsTile(pagename,x,y)

      -

      Finds a tile from a graphics set (i.e. the raws used for creatures), -if in graphics mode and loaded.

      -

      Returns: tile, tile_grayscale, or nil if not found. -The values can then be used for the tile field of pen structures.

      -
    • -
    • dfhack.screen.clear()

      -

      Fills the screen with blank background.

      -
    • -
    • dfhack.screen.invalidate()

      -

      Requests repaint of the screen by setting a flag. Unlike other -functions in this section, this may be used at any time.

      -
    • -
    -

    In order to actually be able to paint to the screen, it is necessary -to create and register a viewscreen (basically a modal dialog) with -the game.

    -

    NOTE: As a matter of policy, in order to avoid user confusion, all -interface screens added by dfhack should bear the "DFHack" signature.

    -

    Screens are managed with the following functions:

    -
      -
    • dfhack.screen.show(screen[,below])

      -

      Displays the given screen, possibly placing it below a different one. -The screen must not be already shown. Returns true if success.

      -
    • -
    • dfhack.screen.dismiss(screen[,to_first])

      -

      Marks the screen to be removed when the game enters its event loop. -If to_first is true, all screens up to the first one will be deleted.

      -
    • -
    • dfhack.screen.isDismissed(screen)

      -

      Checks if the screen is already marked for removal.

      -
    • -
    -

    Apart from a native viewscreen object, these functions accept a table -as a screen. In this case, show creates a new native viewscreen -that delegates all processing to methods stored in that table.

    -

    NOTE: Lua-implemented screens are only supported in the core context.

    -

    Supported callbacks and fields are:

    -
      -
    • screen._native

      -

      Initialized by show with a reference to the backing viewscreen -object, and removed again when the object is deleted.

      -
    • -
    • function screen:onShow()

      -

      Called by dfhack.screen.show if successful.

      -
    • -
    • function screen:onDismiss()

      -

      Called by dfhack.screen.dismiss if successful.

      -
    • -
    • function screen:onDestroy()

      -

      Called from the destructor when the viewscreen is deleted.

      -
    • -
    • function screen:onResize(w, h)

      -

      Called before onRender or onIdle when the window size has changed.

      -
    • -
    • function screen:onRender()

      -

      Called when the viewscreen should paint itself. This is the only context -where the above painting functions work correctly.

      -

      If omitted, the screen is cleared; otherwise it should do that itself. -In order to make a see-through dialog, call self._native.parent:render().

      -
    • -
    • function screen:onIdle()

      -

      Called every frame when the screen is on top of the stack.

      -
    • -
    • function screen:onHelp()

      -

      Called when the help keybinding is activated (usually '?').

      -
    • -
    • function screen:onInput(keys)

      -

      Called when keyboard or mouse events are available. -If any keys are pressed, the keys argument is a table mapping them to true. -Note that this refers to logical keybingings computed from real keys via -options; if multiple interpretations exist, the table will contain multiple keys.

      -

      The table also may contain special keys:

      -
      -
      _STRING
      -

      Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience.

      -
      -
      _MOUSE_L, _MOUSE_R
      -

      If the left or right mouse button is pressed.

      -
      -
      -

      If this method is omitted, the screen is dismissed on receival of the LEAVESCREEN key.

      -
    • -
    • function screen:onGetSelectedUnit()

      -
    • -
    • function screen:onGetSelectedItem()

      -
    • -
    • function screen:onGetSelectedJob()

      -
    • -
    • function screen:onGetSelectedBuilding()

      -

      Implement these to provide a return value for the matching -dfhack.gui.getSelected... function.

      -
    • -
    -
    -
    -

    Internal API

    -

    These functions are intended for the use by dfhack developers, -and are only documented here for completeness:

    -
      -
    • dfhack.internal.scripts

      -

      The table used by dfhack.run_script() to give every script its own -global environment, persistent between calls to the script.

      -
    • -
    • dfhack.internal.getAddress(name)

      -

      Returns the global address name, or nil.

      -
    • -
    • dfhack.internal.setAddress(name, value)

      -

      Sets the global address name. Returns the value of getAddress before the change.

      -
    • -
    • dfhack.internal.getVTable(name)

      -

      Returns the pre-extracted vtable address name, or nil.

      -
    • -
    • dfhack.internal.getRebaseDelta()

      -

      Returns the ASLR rebase offset of the DF executable.

      -
    • -
    • dfhack.internal.getMemRanges()

      -

      Returns a sequence of tables describing virtual memory ranges of the process.

      -
    • -
    • dfhack.internal.patchMemory(dest,src,count)

      -

      Like memmove below, but works even if dest is read-only memory, e.g. code. -If destination overlaps a completely invalid memory region, or another error -occurs, returns false.

      -
    • -
    • dfhack.internal.memmove(dest,src,count)

      -

      Wraps the standard memmove function. Accepts both numbers and refs as pointers.

      -
    • -
    • dfhack.internal.memcmp(ptr1,ptr2,count)

      -

      Wraps the standard memcmp function.

      -
    • -
    • dfhack.internal.memscan(haystack,count,step,needle,nsize)

      -

      Searches for needle of nsize bytes in haystack, -using count steps of step bytes. -Returns: step_idx, sum_idx, found_ptr, or nil if not found.

      -
    • -
    • dfhack.internal.diffscan(old_data, new_data, start_idx, end_idx, eltsize[, oldval, newval, delta])

      -

      Searches for differences between buffers at ptr1 and ptr2, as integers of size eltsize. -The oldval, newval or delta arguments may be used to specify additional constraints. -Returns: found_index, or nil if end reached.

      -
    • -
    -
    -
    -
    -

    Core interpreter context

    -

    While plugins can create any number of interpreter instances, -there is one special context managed by dfhack core. It is the -only context that can receive events from DF and plugins.

    -

    Core context specific functions:

    -
      -
    • dfhack.is_core_context

      -

      Boolean value; true in the core context.

      -
    • -
    • dfhack.timeout(time,mode,callback)

      -

      Arranges for the callback to be called once the specified -period of time passes. The mode argument specifies the -unit of time used, and may be one of 'frames' (raw FPS), -'ticks' (unpaused FPS), 'days', 'months', -'years' (in-game time). All timers other than -'frames' are cancelled when the world is unloaded, -and cannot be queued until it is loaded again. -Returns the timer id, or nil if unsuccessful due to -world being unloaded.

      -
    • -
    • dfhack.timeout_active(id[,new_callback])

      -

      Returns the active callback with the given id, or nil -if inactive or nil id. If called with 2 arguments, replaces -the current callback with the given value, if still active. -Using timeout_active(id,nil) cancels the timer.

      -
    • -
    • dfhack.onStateChange.foo = function(code)

      -

      Event. Receives the same codes as plugin_onstatechange in C++.

      -
    • -
    -
    -

    Event type

    -

    An event is a native object transparently wrapping a lua table, -and implementing a __call metamethod. When it is invoked, it loops -through the table with next and calls all contained values. -This is intended as an extensible way to add listeners.

    -

    This type itself is available in any context, but only the -core context has the actual events defined by C++ code.

    -

    Features:

    -
      -
    • dfhack.event.new()

      -

      Creates a new instance of an event.

      -
    • -
    • event[key] = function

      -

      Sets the function as one of the listeners. Assign nil to remove it.

      -

      NOTE: The df.NULL key is reserved for the use by -the C++ owner of the event; it is an error to try setting it.

      -
    • -
    • #event

      -

      Returns the number of non-nil listeners.

      -
    • -
    • pairs(event)

      -

      Iterates over all listeners in the table.

      -
    • -
    • event(args...)

      -

      Invokes all listeners contained in the event in an arbitrary -order using dfhack.safecall.

      -
    • -
    -
    -
    -
    -
    -

    Lua Modules

    -

    DFHack sets up the lua interpreter so that the built-in require -function can be used to load shared lua code from hack/lua/. -The dfhack namespace reference itself may be obtained via -require('dfhack'), although it is initially created as a -global by C++ bootstrap code.

    -

    The following module management functions are provided:

    -
      -
    • mkmodule(name)

      -

      Creates an environment table for the module. Intended to be used as:

      -
      -local _ENV = mkmodule('foo')
      -...
      -return _ENV
      -
      -

      If called the second time, returns the same table; thus providing reload support.

      -
    • -
    • reload(name)

      -

      Reloads a previously require-d module "name" from the file. -Intended as a help for module development.

      -
    • -
    • dfhack.BASE_G

      -

      This variable contains the root global environment table, which is -used as a base for all module and script environments. Its contents -should be kept limited to the standard Lua library and API described -in this document.

      -
    • -
    -
    -

    Global environment

    -

    A number of variables and functions are provided in the base global -environment by the mandatory init file dfhack.lua:

    -
      -
    • Color constants

      -

      These are applicable both for dfhack.color() and color fields -in DF functions or structures:

      -

      COLOR_RESET, COLOR_BLACK, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN, -COLOR_RED, COLOR_MAGENTA, COLOR_BROWN, COLOR_GREY, COLOR_DARKGREY, -COLOR_LIGHTBLUE, COLOR_LIGHTGREEN, COLOR_LIGHTCYAN, COLOR_LIGHTRED, -COLOR_LIGHTMAGENTA, COLOR_YELLOW, COLOR_WHITE

      -
    • -
    • dfhack.onStateChange event codes

      -

      Available only in the core context, as is the event itself:

      -

      SC_WORLD_LOADED, SC_WORLD_UNLOADED, SC_MAP_LOADED, -SC_MAP_UNLOADED, SC_VIEWSCREEN_CHANGED, SC_CORE_INITIALIZED

      -
    • -
    • Functions already described above

      -

      safecall, qerror, mkmodule, reload

      -
    • -
    • printall(obj)

      -

      If the argument is a lua table or DF object reference, prints all fields.

      -
    • -
    • copyall(obj)

      -

      Returns a shallow copy of the table or reference as a lua table.

      -
    • -
    • pos2xyz(obj)

      -

      The object must have fields x, y and z. Returns them as 3 values. -If obj is nil, or x is -30000 (the usual marker for undefined -coordinates), returns nil.

      -
    • -
    • xyz2pos(x,y,z)

      -

      Returns a table with x, y and z as fields.

      -
    • -
    • safe_index(obj,index...)

      -

      Walks a sequence of dereferences, which may be represented by numbers or strings. -Returns nil if any of obj or indices is nil, or a numeric index is out of array bounds.

      -
    • -
    -
    -
    -

    utils

    -
      -
    • utils.compare(a,b)

      -

      Comparator function; returns -1 if a<b, 1 if a>b, 0 otherwise.

      -
    • -
    • utils.compare_name(a,b)

      -

      Comparator for names; compares empty string last.

      -
    • -
    • utils.is_container(obj)

      -

      Checks if obj is a container ref.

      -
    • -
    • utils.make_index_sequence(start,end)

      -

      Returns a lua sequence of numbers in start..end.

      -
    • -
    • utils.make_sort_order(data, ordering)

      -

      Computes a sorted permutation of objects in data, as a table of integer -indices into the data sequence. Uses data.n as input length -if present.

      -

      The ordering argument is a sequence of ordering specs, represented -as lua tables with following possible fields:

      -
      -
      ord.key = function(value)
      -

      Computes comparison key from input data value. Not called on nil. -If omitted, the comparison key is the value itself.

      -
      -
      ord.key_table = function(data)
      -

      Computes a key table from the data table in one go.

      -
      -
      ord.compare = function(a,b)
      -

      Comparison function. Defaults to utils.compare above. -Called on non-nil keys; nil sorts last.

      -
      -
      ord.nil_first = true/false
      -

      If true, nil keys are sorted first instead of last.

      -
      -
      ord.reverse = true/false
      -

      If true, sort non-nil keys in descending order.

      -
      -
      -

      For every comparison during sorting the specs are applied in -order until an unambiguous decision is reached. Sorting is stable.

      -

      Example of sorting a sequence by field foo:

      -
      -local spec = { key = function(v) return v.foo end }
      -local order = utils.make_sort_order(data, { spec })
      -local output = {}
      -for i = 1,#order do output[i] = data[order[i]] end
      -
      -

      Separating the actual reordering of the sequence in this -way enables applying the same permutation to multiple arrays. -This function is used by the sort plugin.

      -
    • -
    • utils.assign(tgt, src)

      -

      Does a recursive assignment of src into tgt. -Uses df.assign if tgt is a native object ref; otherwise -recurses into lua tables.

      -
    • -
    • utils.clone(obj, deep)

      -

      Performs a shallow, or semi-deep copy of the object as a lua table tree. -The deep mode recurses into lua tables and subobjects, except pointers -to other heap objects. -Null pointers are represented as df.NULL. Zero-based native containers -are converted to 1-based lua sequences.

      -
    • -
    • utils.clone_with_default(obj, default, force)

      -

      Copies the object, using the default lua table tree -as a guide to which values should be skipped as uninteresting. -The force argument makes it always return a non-nil value.

      -
    • -
    • utils.sort_vector(vector,field,cmpfun)

      -

      Sorts a native vector or lua sequence using the comparator function. -If field is not nil, applies the comparator to the field instead -of the whole object.

      -
    • -
    • utils.binsearch(vector,key,field,cmpfun,min,max)

      -

      Does a binary search in a native vector or lua sequence for -key, using cmpfun and field like sort_vector. -If min and max are specified, they are used as the -search subrange bounds.

      -

      If found, returns item, true, idx. Otherwise returns -nil, false, insert_idx, where insert_idx is the correct -insertion point.

      -
    • -
    • utils.insert_sorted(vector,item,field,cmpfun)

      -

      Does a binary search, and inserts item if not found. -Returns did_insert, vector[idx], idx.

      -
    • -
    • utils.insert_or_update(vector,item,field,cmpfun)

      -

      Like insert_sorted, but also assigns the item into -the vector cell if insertion didn't happen.

      -

      As an example, you can use this to set skill values:

      -
      -utils.insert_or_update(soul.skills, {new=true, id=..., rating=...}, 'id')
      -
      -

      (For an explanation of new=true, see table assignment in the wrapper section)

      -
    • -
    • utils.prompt_yes_no(prompt, default)

      -

      Presents a yes/no prompt to the user. If default is not nil, -allows just pressing Enter to submit the default choice. -If the user enters 'abort', throws an error.

      -
    • -
    • utils.prompt_input(prompt, checkfun, quit_str)

      -

      Presents a prompt to input data, until a valid string is entered. -Once checkfun(input) returns true, ..., passes the values -through. If the user enters the quit_str (defaults to '~~~'), -throws an error.

      -
    • -
    • utils.check_number(text)

      -

      A prompt_input checkfun that verifies a number input.

      -
    • -
    -
    -
    -

    dumper

    -

    A third-party lua table dumper module from -http://lua-users.org/wiki/DataDumper. Defines one -function:

    -
      -
    • dumper.DataDumper(value, varname, fastmode, ident, indent_step)

      -

      Returns value converted to a string. The indent_step -argument specifies the indentation step size in spaces. For -the other arguments see the original documentation link above.

      -
    • -
    -
    -
    -

    class

    -

    Implements a trivial single-inheritance class system.

    -
      -
    • Foo = defclass(Foo[, ParentClass])

      -

      Defines or updates class Foo. The Foo = defclass(Foo) syntax -is needed so that when the module or script is reloaded, the -class identity will be preserved through the preservation of -global variable values.

      -

      The defclass function is defined as a stub in the global -namespace, and using it will auto-load the class module.

      -
    • -
    • Class.super

      -

      This class field is set by defclass to the parent class, and -allows a readable Class.super.method(self, ...) syntax for -calling superclass methods.

      -
    • -
    • Class.ATTRS { foo = xxx, bar = yyy }

      -

      Declares certain instance fields to be attributes, i.e. auto-initialized -from fields in the table used as the constructor argument. If omitted, -they are initialized with the default values specified in this declaration.

      -

      If the default value should be nil, use ATTRS { foo = DEFAULT_NIL }.

      -
    • -
    • new_obj = Class{ foo = arg, bar = arg, ... }

      -

      Calling the class as a function creates and initializes a new instance. -Initialization happens in this order:

      -
        -
      1. An empty instance table is created, and its metatable set.
      2. -
      3. The preinit method is called via invoke_before (see below) -with the table used as argument to the class. This method is intended -for validating and tweaking that argument table.
      4. -
      5. Declared ATTRS are initialized from the argument table or their default values.
      6. -
      7. The init method is called via invoke_after with the argument table. -This is the main constructor method.
      8. -
      9. The postinit method is called via invoke_after with the argument table. -Place code that should be called after the object is fully constructed here.
      10. -
      -
    • -
    -

    Predefined instance methods:

    -
      -
    • instance:assign{ foo = xxx }

      -

      Assigns all values in the input table to the matching instance fields.

      -
    • -
    • instance:callback(method_name, [args...])

      -

      Returns a closure that invokes the specified method of the class, -properly passing in self, and optionally a number of initial arguments too. -The arguments given to the closure are appended to these.

      -
    • -
    • instance:invoke_before(method_name, args...)

      -

      Navigates the inheritance chain of the instance starting from the most specific -class, and invokes the specified method with the arguments if it is defined in -that specific class. Equivalent to the following definition in every class:

      -
      -function Class:invoke_before(method, ...)
      -  if rawget(Class, method) then
      -    rawget(Class, method)(self, ...)
      -  end
      -  Class.super.invoke_before(method, ...)
      -end
      -
      -
    • -
    • instance:invoke_after(method_name, args...)

      -

      Like invoke_before, only the method is called after the recursive call to super, -i.e. invocations happen in the parent to child order.

      -

      These two methods are inspired by the Common Lisp before and after methods, and -are intended for implementing similar protocols for certain things. The class -library itself uses them for constructors.

      -
    • -
    -

    To avoid confusion, these methods cannot be redefined.

    -
    -
    -
    -

    Plugins

    -

    DFHack plugins may export native functions and events -to lua contexts. They are automatically imported by -mkmodule('plugins.<name>'); this means that a lua -module file is still necessary for require to read.

    -

    The following plugins have lua support.

    -
    -

    burrows

    -

    Implements extended burrow manipulations.

    -

    Events:

    -
      -
    • onBurrowRename.foo = function(burrow)

      -

      Emitted when a burrow might have been renamed either through -the game UI, or renameBurrow().

      -
    • -
    • onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype,worker)

      -

      Emitted when a tile might have been dug out. Only tracked if the -auto-growing burrows feature is enabled.

      -
    • -
    -

    Native functions:

    -
      -
    • renameBurrow(burrow,name)

      -

      Renames the burrow, emitting onBurrowRename and updating auto-grow state properly.

      -
    • -
    • findByName(burrow,name)

      -

      Finds a burrow by name, using the same rules as the plugin command line interface. -Namely, trailing '+' characters marking auto-grow burrows are ignored.

      -
    • -
    • copyUnits(target,source,enable)

      -

      Applies units from source burrow to target. The enable -parameter specifies if they are to be added or removed.

      -
    • -
    • copyTiles(target,source,enable)

      -

      Applies tiles from source burrow to target. The enable -parameter specifies if they are to be added or removed.

      -
    • -
    • setTilesByKeyword(target,keyword,enable)

      -

      Adds or removes tiles matching a predefined keyword. The keyword -set is the same as used by the command line.

      -
    • -
    -

    The lua module file also re-exports functions from dfhack.burrows.

    -
    -
    -

    sort

    -

    Does not export any native functions as of now. Instead, it -calls lua code to perform the actual ordering of list items.

    -
    -
    -
    -

    Scripts

    -

    Any files with the .lua extension placed into hack/scripts/* -are automatically used by the DFHack core as commands. The -matching command name consists of the name of the file sans -the extension.

    -

    If the first line of the script is a one-line comment, it is -used by the built-in ls and help commands.

    -

    NOTE: Scripts placed in subdirectories still can be accessed, but -do not clutter the ls command list; thus it is preferred -for obscure developer-oriented scripts and scripts used by tools. -When calling such scripts, always use '/' as the separator for -directories, e.g. devel/lua-example.

    -

    Scripts are re-read from disk every time they are used -(this may be changed later to check the file change time); however -the global variable values persist in memory between calls. -Every script gets its own separate environment for global -variables.

    -

    Arguments are passed in to the scripts via the ... built-in -quasi-variable; when the script is called by the DFHack core, -they are all guaranteed to be non-nil strings.

    -

    DFHack core invokes the scripts in the core context (see above); -however it is possible to call them from any lua code (including -from other scripts) in any context, via the same function the core uses:

    -
      -
    • dfhack.run_script(name[,args...])

      -

      Run a lua script in hack/scripts/, as if it was started from dfhack command-line. -The name argument should be the name stem, as would be used on the command line.

      -
    • -
    -

    Note that this function lets errors propagate to the caller.

    -
    -
    - - diff --git a/Readme.html b/Readme.html deleted file mode 100644 index 6155b72ce..000000000 --- a/Readme.html +++ /dev/null @@ -1,2704 +0,0 @@ - - - - - - -DFHack Readme - - - -
    -

    DFHack Readme

    - -
    -

    Introduction

    -

    DFHack is a Dwarf Fortress memory access library and a set of basic -tools that use it. Tools come in the form of plugins or (not yet) -external tools. It is an attempt to unite the various ways tools -access DF memory and allow for easier development of new tools.

    -
    -

    Contents

    - -
    -
    -
    -

    Getting DFHack

    -

    The project is currently hosted on github, for both source and -binaries at http://github.com/peterix/dfhack

    -

    Releases can be downloaded from here: https://github.com/peterix/dfhack/downloads

    -

    All new releases are announced in the bay12 thread: http://tinyurl.com/dfhack-ng

    -
    -
    -

    Compatibility

    -

    DFHack works on Windows XP, Vista, 7 or any modern Linux distribution. -OSX is not supported due to lack of developers with a Mac.

    -

    Currently, versions 0.34.08 - 0.34.11 are supported. If you need DFHack -for older versions, look for older releases.

    -

    On Windows, you have to use the SDL version of DF.

    -

    It is possible to use the Windows DFHack under wine/OSX.

    -
    -
    -

    Installation/Removal

    -

    Installing DFhack involves copying files into your DF folder. -Copy the files from a release archive so that:

    -
    -
      -
    • On Windows, SDL.dll is replaced
    • -
    • On Linux, the 'dfhack' script is placed in the same folder as the 'df' script
    • -
    -
    -

    Uninstalling is basically the same, in reverse:

    -
    -
      -
    • On Windows, first delete SDL.dll and rename SDLreal.dll to SDL.dll. Then -remove the other DFHack files
    • -
    • On Linux, Remove the DFHack files.
    • -
    -
    -

    The stonesense plugin might require some additional libraries on Linux.

    -

    If any of the plugins or dfhack itself refuses to load, check the stderr.log -file created in your DF folder.

    -
    -
    -

    Using DFHack

    -

    DFHack basically extends what DF can do with something similar to the drop-down -console found in Quake engine games. On Windows, this is a separate command line -window. On linux, the terminal used to launch the dfhack script is taken over -(so, make sure you start from a terminal). Basic interaction with dfhack -involves entering commands into the console. For some basic instroduction, -use the 'help' command. To list all possible commands, use the 'ls' command. -Many commands have their own help or detailed description. You can use -'command help' or 'command ?' to show that.

    -

    The command line has some nice line editing capabilities, including history -that's preserved between different runs of DF (use up/down keys to go through -the history).

    -

    The second way to interact with DFHack is to bind the available commands -to in-game hotkeys. The old way to do this is via the hotkey/zoom menu (normally -opened with the 'h' key). Binding the commands is done by assigning a command as -a hotkey name (with 'n').

    -

    A new and more flexible way is the keybinding command in the dfhack console. -However, bindings created this way are not automatically remembered between runs -of the game, so it becomes necessary to use the dfhack.init file to ensure that -they are re-created every time it is loaded.

    -

    Interactive commands like 'liquids' cannot be used as hotkeys.

    -

    Most of the commands come from plugins. Those reside in 'hack/plugins/'.

    -
    -
    -

    Something doesn't work, help!

    -

    First, don't panic :) Second, dfhack keeps a few log files in DF's folder -- stderr.log and stdout.log. You can look at those and possibly find out what's -happening. -If you found a bug, you can either report it in the bay12 DFHack thread, -the issues tracker on github, contact me (peterix@gmail.com) or visit the -#dfhack IRC channel on freenode.

    -
    -
    -

    The init file

    -

    If your DF folder contains a file named dfhack.init, its contents will be run -every time you start DF. This allows setting up keybindings. An example file -is provided as dfhack.init-example - you can tweak it and rename to dfhack.init -if you want to use this functionality.

    -
    -

    Setting keybindings

    -

    To set keybindings, use the built-in keybinding command. Like any other -command it can be used at any time from the console, but it is also meaningful -in the DFHack init file.

    -

    Currently it supports any combination of Ctrl/Alt/Shift with F1-F9, or A-Z.

    -

    Possible ways to call the command:

    - --- - - - - - - - - - - - - - -
    keybinding list <key>:
     List bindings active for the key combination.
    keybinding clear <key> <key>...:
     Remove bindings for the specified keys.
    keybinding add <key> "cmdline" "cmdline"...:
     Add bindings for the specified -key.
    keybinding set <key> "cmdline" "cmdline"...:
     Clear, and then add bindings for -the specified key.
    -

    The <key> parameter above has the following case-sensitive syntax:

    -
    -[Ctrl-][Alt-][Shift-]KEY[@context]
    -
    -

    where the KEY part can be F1-F9 or A-Z, and [] denote optional parts.

    -

    When multiple commands are bound to the same key combination, DFHack selects -the first applicable one. Later 'add' commands, and earlier entries within one -'add' command have priority. Commands that are not specifically intended for use -as a hotkey are always considered applicable.

    -

    The context part in the key specifier above can be used to explicitly restrict -the UI state where the binding would be applicable. If called without parameters, -the keybinding command among other things prints the current context string. -Only bindings with a context tag that either matches the current context fully, -or is a prefix ending at a '/' boundary would be considered for execution, i.e. -for context foo/bar/baz, possible matches are any of @foo/bar/baz, @foo/bar, -@foo or none.

    -
    -
    -
    -

    Commands

    -

    DFHack command syntax consists of a command name, followed by arguments separated -by whitespace. To include whitespace in an argument, quote it in double quotes. -To include a double quote character, use \" inside double quotes.

    -

    If the first non-whitespace character of a line is #, the line is treated -as a comment, i.e. a silent no-op command.

    -

    If the first non-whitespace character is :, the command is parsed in a special -alternative mode: first, non-whitespace characters immediately following the : -are used as the command name; the remaining part of the line, starting with the first -non-whitespace character after the command name, is used verbatim as the first argument. -The following two command lines are exactly equivalent:

    -
    -
      -
    • :foo a b "c d" e f
    • -
    • foo "a b \"c d\" e f"
    • -
    -
    -

    This is intended for commands like rb_eval that evaluate script language statements.

    -

    Almost all the commands support using the 'help <command-name>' built-in command -to retrieve further help without having to look at this document. Alternatively, -some accept a 'help'/'?' option on their command line.

    -
    -

    Game progress

    -
    -

    die

    -

    Instantly kills DF without saving.

    -
    -
    -

    forcepause

    -

    Forces DF to pause. This is useful when your FPS drops below 1 and you lose -control of the game.

    -
    -
      -
    • Activate with 'forcepause 1'
    • -
    • Deactivate with 'forcepause 0'
    • -
    -
    -
    -
    -

    nopause

    -

    Disables pausing (both manual and automatic) with the exception of pause forced -by 'reveal hell'. This is nice for digging under rivers.

    -
    -
    -

    fastdwarf

    -

    Makes your minions move at ludicrous speeds.

    -
    -
      -
    • Activate with 'fastdwarf 1'
    • -
    • Deactivate with 'fastdwarf 0'
    • -
    -
    -
    -
    -
    -

    Game interface

    -
    -

    follow

    -

    Makes the game view follow the currently highlighted unit after you exit from -current menu/cursor mode. Handy for watching dwarves running around. Deactivated -by moving the view manually.

    -
    -
    -

    tidlers

    -

    Toggle between all possible positions where the idlers count can be placed.

    -
    -
    -

    twaterlvl

    -

    Toggle between displaying/not displaying liquid depth as numbers.

    -
    -
    -

    copystock

    -

    Copies the parameters of the currently highlighted stockpile to the custom -stockpile settings and switches to custom stockpile placement mode, effectively -allowing you to copy/paste stockpiles easily.

    -
    -
    -

    rename

    -

    Allows renaming various things.

    -

    Options:

    -
    - --- - - - - - - - - - - - - - - - - -
    rename squad <index> "name":
     Rename squad by index to 'name'.
    rename hotkey <index> "name":
     Rename hotkey by index. This allows assigning -longer commands to the DF hotkeys.
    rename unit "nickname":
     Rename a unit/creature highlighted in the DF user -interface.
    rename unit-profession "custom profession":
     Change proffession name of the -highlighted unit/creature.
    rename building "name":
     Set a custom name for the selected building. -The building must be one of stockpile, workshop, furnace, trap, -siege engine or an activity zone.
    -
    -
    -
    -
    -

    Adventure mode

    -
    -

    adv-bodyswap

    -

    This allows taking control over your followers and other creatures in adventure -mode. For example, you can make them pick up new arms and armor and equip them -properly.

    -

    Usage:

    -
    -
      -
    • When viewing unit details, body-swaps into that unit.
    • -
    • In the main adventure mode screen, reverts transient swap.
    • -
    -
    -
    -
    -

    advtools

    -

    A package of different adventure mode tools (currently just one)

    -

    Usage:

    -
    - --- - - - - - - - -
    list-equipped [all]:
     List armor and weapons equipped by your companions. -If all is specified, also lists non-metal clothing.
    metal-detector [all-types] [non-trader]:
     Reveal metal armor and weapons in -shops. The options disable the checks -on item type and being in shop.
    -
    -
    -
    -
    -

    Map modification

    -
    -

    changelayer

    -

    Changes material of the geology layer under cursor to the specified inorganic -RAW material. Can have impact on all surrounding regions, not only your embark! -By default changing stone to soil and vice versa is not allowed. By default -changes only the layer at the cursor position. Note that one layer can stretch -across lots of z levels. By default changes only the geology which is linked -to the biome under the cursor. That geology might be linked to other biomes -as well, though. Mineral veins and gem clusters will stay on the map. Use -'changevein' for them.

    -

    tl;dr: You will end up with changing quite big areas in one go, especially if -you use it in lower z levels. Use with care.

    -

    Options:

    -
    - --- - - - - - - - - - - - -
    all_biomes:Change selected layer for all biomes on your map. -Result may be undesirable since the same layer can AND WILL -be on different z-levels for different biomes. Use the tool -'probe' to get an idea how layers and biomes are distributed -on your map.
    all_layers:Change all layers on your map (only for the selected biome -unless 'all_biomes' is added). -Candy mountain, anyone? Will make your map quite boring, -but tidy.
    force:Allow changing stone to soil and vice versa. !!THIS CAN HAVE -WEIRD EFFECTS, USE WITH CARE!! -Note that soil will not be magically replaced with stone. -You will, however, get a stone floor after digging so it -will allow the floor to be engraved. -Note that stone will not be magically replaced with soil. -You will, however, get a soil floor after digging so it -could be helpful for creating farm plots on maps with no -soil.
    verbose:Give some details about what is being changed.
    trouble:Give some advice about known problems.
    -
    -

    Examples:

    -
    -
    -
    changelayer GRANITE
    -
    Convert layer at cursor position into granite.
    -
    changelayer SILTY_CLAY force
    -
    Convert layer at cursor position into clay even if it's stone.
    -
    changelayer MARBLE all_biomes all_layers
    -
    Convert all layers of all biomes which are not soil into marble.
    -
    -
    -
    -

    Note

    -
      -
    • If you use changelayer and nothing happens, try to pause/unpause the game -for a while and try to move the cursor to another tile. Then try again. -If that doesn't help try temporarily changing some other layer, undo your -changes and try again for the layer you want to change. Saving -and reloading your map might also help.
    • -
    • You should be fine if you only change single layers without the use -of 'force'. Still it's advisable to save your game before messing with -the map.
    • -
    • When you force changelayer to convert soil to stone you might experience -weird stuff (flashing tiles, tiles changed all over place etc). -Try reverting the changes manually or even better use an older savegame. -You did save your game, right?
    • -
    -
    -
    -
    -

    changevein

    -

    Changes material of the vein under cursor to the specified inorganic RAW -material. Only affects tiles within the current 16x16 block - for veins and -large clusters, you will need to use this command multiple times.

    -

    Example:

    -
    -
    -
    changevein NATIVE_PLATINUM
    -
    Convert vein at cursor position into platinum ore.
    -
    -
    -
    -
    -

    changeitem

    -

    Allows changing item material and base quality. By default the item currently -selected in the UI will be changed (you can select items in the 'k' list -or inside containers/inventory). By default change is only allowed if materials -is of the same subtype (for example wood<->wood, stone<->stone etc). But since -some transformations work pretty well and may be desired you can override this -with 'force'. Note that some attributes will not be touched, possibly resulting -in weirdness. To get an idea how the RAW id should look like, check some items -with 'info'. Using 'force' might create items which are not touched by -crafters/haulers.

    -

    Options:

    -
    - --- - - - - - - - - - - - -
    info:Don't change anything, print some info instead.
    here:Change all items at the cursor position. Requires in-game cursor.
    material, m:Change material. Must be followed by valid material RAW id.
    quality, q:Change base quality. Must be followed by number (0-5).
    force:Ignore subtypes, force change to new material.
    -
    -

    Examples:

    -
    -
    -
    changeitem m INORGANIC:GRANITE here
    -
    Change material of all items under the cursor to granite.
    -
    changeitem q 5
    -
    Change currently selected item to masterpiece quality.
    -
    -
    -
    -
    -

    colonies

    -

    Allows listing all the vermin colonies on the map and optionally turning them into honey bee colonies.

    -

    Options:

    -
    - --- - - - -
    bees:turn colonies into honey bee colonies
    -
    -
    -
    -

    deramp (by zilpin)

    -

    Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation. -It also removes any and all 'down ramps' that can remain after a cave-in (you don't have to designate anything for that to happen).

    -
    -
    -

    feature

    -

    Enables management of map features.

    -
      -
    • Discovering a magma feature (magma pool, volcano, magma sea, or curious -underground structure) permits magma workshops and furnaces to be built.
    • -
    • Discovering a cavern layer causes plants (trees, shrubs, and grass) from -that cavern to grow within your fortress.
    • -
    -

    Options:

    -
    - --- - - - - - - - -
    list:Lists all map features in your current embark by index.
    show X:Marks the selected map feature as discovered.
    hide X:Marks the selected map feature as undiscovered.
    -
    -
    -
    -

    liquids

    -

    Allows adding magma, water and obsidian to the game. It replaces the normal -dfhack command line and can't be used from a hotkey. Settings will be remembered -as long as dfhack runs. Intended for use in combination with the command -liquids-here (which can be bound to a hotkey).

    -

    For more information, refer to the command's internal help.

    -
    -

    Note

    -

    Spawning and deleting liquids can F up pathing data and -temperatures (creating heat traps). You've been warned.

    -
    -
    -
    -

    liquids-here

    -

    Run the liquid spawner with the current/last settings made in liquids (if no -settings in liquids were made it paints a point of 7/7 magma by default).

    -

    Intended to be used as keybinding. Requires an active in-game cursor.

    -
    -
    -

    tiletypes

    -

    Can be used for painting map tiles and is an interactive command, much like -liquids.

    -

    The tool works with two set of options and a brush. The brush determines which -tiles will be processed. First set of options is the filter, which can exclude -some of the tiles from the brush by looking at the tile properties. The second -set of options is the paint - this determines how the selected tiles are -changed.

    -

    Both paint and filter can have many different properties including things like -general shape (WALL, FLOOR, etc.), general material (SOIL, STONE, MINERAL, -etc.), state of 'designated', 'hidden' and 'light' flags.

    -

    The properties of filter and paint can be partially defined. This means that -you can for example do something like this:

    -
    -filter material STONE
    -filter shape FORTIFICATION
    -paint shape FLOOR
    -
    -

    This will turn all stone fortifications into floors, preserving the material.

    -

    Or this:

    -
    -filter shape FLOOR
    -filter material MINERAL
    -paint shape WALL
    -
    -

    Turning mineral vein floors back into walls.

    -

    The tool also allows tweaking some tile flags:

    -

    Or this:

    -
    -paint hidden 1
    -paint hidden 0
    -
    -

    This will hide previously revealed tiles (or show hidden with the 0 option).

    -

    Any paint or filter option (or the entire paint or filter) can be disabled entirely by using the ANY keyword:

    -
    -paint hidden ANY
    -paint shape ANY
    -filter material any
    -filter shape any
    -filter any
    -
    -
    -
    You can use several different brushes for painting tiles:
    -
      -
    • Point. (point)
    • -
    • Rectangular range. (range)
    • -
    • A column ranging from current cursor to the first solid tile above. (column)
    • -
    • DF map block - 16x16 tiles, in a regular grid. (block)
    • -
    -
    -
    -

    Example:

    -
    -range 10 10 1
    -
    -

    This will change the brush to a rectangle spanning 10x10 tiles on one z-level. -The range starts at the position of the cursor and goes to the east, south and -up.

    -

    For more details, see the 'help' command while using this.

    -
    -
    -

    tiletypes-commands

    -

    Runs tiletypes commands, separated by ;. This makes it possible to change -tiletypes modes from a hotkey.

    -
    -
    -

    tiletypes-here

    -

    Apply the current tiletypes options at the in-game cursor position, including -the brush. Can be used from a hotkey.

    -
    -
    -

    tiletypes-here-point

    -

    Apply the current tiletypes options at the in-game cursor position to a single -tile. Can be used from a hotkey.

    -
    -
    -

    tubefill

    -

    Fills all the adamantine veins again. Veins that were empty will be filled in -too, but might still trigger a demon invasion (this is a known bug).

    -
    -
    -

    extirpate

    -

    A tool for getting rid of trees and shrubs. By default, it only kills -a tree/shrub under the cursor. The plants are turned into ashes instantly.

    -

    Options:

    -
    - --- - - - - - - - -
    shrubs:affect all shrubs on the map
    trees:affect all trees on the map
    all:affect every plant!
    -
    -
    -
    -

    grow

    -

    Makes all saplings present on the map grow into trees (almost) instantly.

    -
    -
    -

    immolate

    -

    Very similar to extirpate, but additionally sets the plants on fire. The fires -can and will spread ;)

    -
    -
    -

    regrass

    -

    Regrows grass. Not much to it ;)

    -
    -
    -

    weather

    -

    Prints the current weather map by default.

    -

    Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'.

    -

    Options:

    -
    - --- - - - - - - - -
    snow:make it snow everywhere.
    rain:make it rain.
    clear:clear the sky.
    -
    -
    -
    -
    -

    Map inspection

    -
    -

    cursecheck

    -

    Checks a single map tile or the whole map/world for cursed creatures (ghosts, -vampires, necromancers, werebeasts, zombies).

    -

    With an active in-game cursor only the selected tile will be observed. -Without a cursor the whole map will be checked.

    -

    By default cursed creatures will be only counted in case you just want to find -out if you have any of them running around in your fort. Dead and passive -creatures (ghosts who were put to rest, killed vampires, ...) are ignored. -Undead skeletons, corpses, bodyparts and the like are all thrown into the curse -category "zombie". Anonymous zombies and resurrected body parts will show -as "unnamed creature".

    -

    Options:

    -
    - --- - - - - - - - - - -
    detail:Print full name, date of birth, date of curse and some status -info (some vampires might use fake identities in-game, though).
    nick:Set the type of curse as nickname (does not always show up -in-game, some vamps don't like nicknames).
    all:Include dead and passive cursed creatures (can result in a quite -long list after having FUN with necromancers).
    verbose:Print all curse tags (if you really want to know it all).
    -
    -

    Examples:

    -
    -
    -
    cursecheck detail all
    -
    Give detailed info about all cursed creatures including deceased ones (no -in-game cursor).
    -
    cursecheck nick
    -
    Give a nickname all living/active cursed creatures on the map(no in-game -cursor).
    -
    -
    -
    -

    Note

    -
      -
    • If you do a full search (with the option "all") former ghosts will show up -with the cursetype "unknown" because their ghostly flag is not set -anymore. But if you happen to find a living/active creature with cursetype -"unknown" please report that in the dfhack thread on the modding forum or -per irc. This is likely to happen with mods which introduce new types -of curses, for example.
    • -
    -
    -
    -
    -

    flows

    -

    A tool for checking how many tiles contain flowing liquids. If you suspect that -your magma sea leaks into HFS, you can use this tool to be sure without -revealing the map.

    -
    -
    -

    probe

    -

    Can be used to determine tile properties like temperature.

    -
    -
    -

    prospect

    -

    Prints a big list of all the present minerals and plants. By default, only -the visible part of the map is scanned.

    -

    Options:

    -
    - --- - - - - - - - -
    all:Scan the whole map, as if it was revealed.
    value:Show material value in the output. Most useful for gems.
    hell:Show the Z range of HFS tubes. Implies 'all'.
    -
    -
    -

    Pre-embark estimate

    -

    If prospect is called during the embark selection screen, it displays an estimate of -layer stone availability.

    -
    -

    Note

    -

    The results of pre-embark prospect are an estimate, and can at best be expected -to be somewhere within +/- 30% of the true amount; sometimes it does a lot worse. -Especially, it is not clear how to precisely compute how many soil layers there -will be in a given embark tile, so it can report a whole extra layer, or omit one -that is actually present.

    -
    -

    Options:

    -
    - --- - - - -
    all:Also estimate vein mineral amounts.
    -
    -
    -
    -
    -

    reveal

    -

    This reveals the map. By default, HFS will remain hidden so that the demons -don't spawn. You can use 'reveal hell' to reveal everything. With hell revealed, -you won't be able to unpause until you hide the map again. If you really want -to unpause with hell revealed, use 'reveal demons'.

    -

    Reveal also works in adventure mode, but any of its effects are negated once -you move. When you use it this way, you don't need to run 'unreveal'.

    -
    -
    -

    unreveal

    -

    Reverts the effects of 'reveal'.

    -
    -
    -

    revtoggle

    -

    Switches between 'reveal' and 'unreveal'.

    -
    -
    -

    revflood

    -

    This command will hide the whole map and then reveal all the tiles that have -a path to the in-game cursor.

    -
    -
    -

    revforget

    -

    When you use reveal, it saves information about what was/wasn't visible before -revealing everything. Unreveal uses this information to hide things again. -This command throws away the information. For example, use in cases where -you abandoned with the fort revealed and no longer want the data.

    -
    -
    -

    showmood

    -

    Shows all items needed for the currently active strange mood.

    -
    -
    -
    -

    Designations

    -
    -

    burrow

    -

    Miscellaneous burrow control. Allows manipulating burrows and automated burrow -expansion while digging.

    -

    Options:

    -
    -
    -
    enable feature ...
    -
    Enable features of the plugin.
    -
    disable feature ...
    -
    Disable features of the plugin.
    -
    clear-unit burrow burrow ...
    -
    Remove all units from the burrows.
    -
    clear-tiles burrow burrow ...
    -
    Remove all tiles from the burrows.
    -
    set-units target-burrow src-burrow ...
    -
    Clear target, and adds units from source burrows.
    -
    add-units target-burrow src-burrow ...
    -
    Add units from the source burrows to the target.
    -
    remove-units target-burrow src-burrow ...
    -
    Remove units in source burrows from the target.
    -
    set-tiles target-burrow src-burrow ...
    -
    Clear target and adds tiles from the source burrows.
    -
    add-tiles target-burrow src-burrow ...
    -
    Add tiles from the source burrows to the target.
    -
    remove-tiles target-burrow src-burrow ...
    -

    Remove tiles in source burrows from the target.

    -

    For these three options, in place of a source burrow it is -possible to use one of the following keywords: ABOVE_GROUND, -SUBTERRANEAN, INSIDE, OUTSIDE, LIGHT, DARK, HIDDEN, REVEALED

    -
    -
    -
    -

    Features:

    -
    - --- - - - -
    auto-grow:When a wall inside a burrow with a name ending in '+' is dug -out, the burrow is extended to newly-revealed adjacent walls. -This final '+' may be omitted in burrow name args of commands above. -Digging 1-wide corridors with the miner inside the burrow is SLOW.
    -
    -
    -
    -

    digv

    -

    Designates a whole vein for digging. Requires an active in-game cursor placed -over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs -between the same-material tiles).

    -
    -
    -

    digvx

    -

    A permanent alias for 'digv x'.

    -
    -
    -

    digl

    -

    Designates layer stone for digging. Requires an active in-game cursor placed -over a layer stone tile. With the 'x' option, it will traverse z-levels -(putting stairs between the same-material tiles). With the 'undo' option it -will remove the dig designation instead (if you realize that digging out a 50 -z-level deep layer was not such a good idea after all).

    -
    -
    -

    diglx

    -

    A permanent alias for 'digl x'.

    -
    -
    -

    digexp

    -

    This command can be used for exploratory mining.

    -

    See: http://df.magmawiki.com/index.php/DF2010:Exploratory_mining

    -

    There are two variables that can be set: pattern and filter.

    -

    Patterns:

    -
    - --- - - - - - - - - - - - - - -
    diag5:diagonals separated by 5 tiles
    diag5r:diag5 rotated 90 degrees
    ladder:A 'ladder' pattern
    ladderr:ladder rotated 90 degrees
    clear:Just remove all dig designations
    cross:A cross, exactly in the middle of the map.
    -
    -

    Filters:

    -
    - --- - - - - - - - -
    all:designate whole z-level
    hidden:designate only hidden tiles of z-level (default)
    designated:Take current designation and apply pattern to it.
    -
    -

    After you have a pattern set, you can use 'expdig' to apply it again.

    -

    Examples:

    -
    -
    -
    designate the diagonal 5 patter over all hidden tiles:
    -
      -
    • expdig diag5 hidden
    • -
    -
    -
    apply last used pattern and filter:
    -
      -
    • expdig
    • -
    -
    -
    Take current designations and replace them with the ladder pattern:
    -
      -
    • expdig ladder designated
    • -
    -
    -
    -
    -
    -
    -

    digcircle

    -

    A command for easy designation of filled and hollow circles. -It has several types of options.

    -

    Shape:

    -
    - --- - - - - - - - -
    hollow:Set the circle to hollow (default)
    filled:Set the circle to filled
    #:Diameter in tiles (default = 0, does nothing)
    -
    -

    Action:

    -
    - --- - - - - - - - -
    set:Set designation (default)
    unset:Unset current designation
    invert:Invert designations already present
    -
    -

    Designation types:

    -
    - --- - - - - - - - - - - - - - -
    dig:Normal digging designation (default)
    ramp:Ramp digging
    ustair:Staircase up
    dstair:Staircase down
    xstair:Staircase up/down
    chan:Dig channel
    -
    -

    After you have set the options, the command called with no options -repeats with the last selected parameters.

    -

    Examples:

    -
      -
    • 'digcircle filled 3' = Dig a filled circle with radius = 3.
    • -
    • 'digcircle' = Do it again.
    • -
    -
    -
    -

    filltraffic

    -

    Set traffic designations using flood-fill starting at the cursor.

    -

    Traffic Type Codes:

    -
    - --- - - - - - - - - - -
    H:High Traffic
    N:Normal Traffic
    L:Low Traffic
    R:Restricted Traffic
    -
    -

    Other Options:

    -
    - --- - - - - - - - -
    X:Fill accross z-levels.
    B:Include buildings and stockpiles.
    P:Include empty space.
    -
    -

    Example:

    -
    -'filltraffic H' - When used in a room with doors, it will set traffic to HIGH in just that room.
    -
    -
    -

    alltraffic

    -

    Set traffic designations for every single tile of the map (useful for resetting traffic designations).

    -

    Traffic Type Codes:

    -
    - --- - - - - - - - - - -
    H:High Traffic
    N:Normal Traffic
    L:Low Traffic
    R:Restricted Traffic
    -
    -

    Example:

    -
    -'alltraffic N' - Set traffic to 'normal' for all tiles.
    -
    -
    -

    getplants

    -

    This tool allows plant gathering and tree cutting by RAW ID. Specify the types -of trees to cut down and/or shrubs to gather by their plant names, separated -by spaces.

    -

    Options:

    -
    - --- - - - - - - - - - -
    -t:Select trees only (exclude shrubs)
    -s:Select shrubs only (exclude trees)
    -c:Clear designations instead of setting them
    -x:Apply selected action to all plants except those specified (invert -selection)
    -
    -

    Specifying both -t and -s will have no effect. If no plant IDs are specified, -all valid plant IDs will be listed.

    -
    -
    -
    -

    Cleanup and garbage disposal

    -
    -

    clean

    -

    Cleans all the splatter that get scattered all over the map, items and -creatures. In an old fortress, this can significantly reduce FPS lag. It can -also spoil your !!FUN!!, so think before you use it.

    -

    Options:

    -
    - --- - - - - - - - -
    map:Clean the map tiles. By default, it leaves mud and snow alone.
    units:Clean the creatures. Will also clean hostiles.
    items:Clean all the items. Even a poisoned blade.
    -
    -

    Extra options for 'map':

    -
    - --- - - - - - -
    mud:Remove mud in addition to the normal stuff.
    snow:Also remove snow coverings.
    -
    -
    -
    -

    spotclean

    -

    Works like 'clean map snow mud', but only for the tile under the cursor. Ideal -if you want to keep that bloody entrance 'clean map' would clean up.

    -
    -
    -

    autodump

    -

    This utility lets you quickly move all items designated to be dumped. -Items are instantly moved to the cursor position, the dump flag is unset, -and the forbid flag is set, as if it had been dumped normally. -Be aware that any active dump item tasks still point at the item.

    -

    Cursor must be placed on a floor tile so the items can be dumped there.

    -

    Options:

    -
    - --- - - - - - - - - - - - -
    destroy:Destroy instead of dumping. Doesn't require a cursor.
    destroy-here:Destroy items only under the cursor.
    visible:Only process items that are not hidden.
    hidden:Only process hidden items.
    forbidden:Only process forbidden items (default: only unforbidden).
    -
    -
    -
    -

    autodump-destroy-here

    -

    Destroy items marked for dumping under cursor. Identical to autodump -destroy-here, but intended for use as keybinding.

    -
    -
    -

    autodump-destroy-item

    -

    Destroy the selected item. The item may be selected in the 'k' list, or inside -a container. If called again before the game is resumed, cancels destroy.

    -
    -
    -

    cleanowned

    -

    Confiscates items owned by dwarfs. By default, owned food on the floor -and rotten items are confistacted and dumped.

    -

    Options:

    -
    - --- - - - - - - - - - - - -
    all:confiscate all owned items
    scattered:confiscated and dump all items scattered on the floor
    x:confiscate/dump items with wear level 'x' and more
    X:confiscate/dump items with wear level 'X' and more
    dryrun:a dry run. combine with other options to see what will happen -without it actually happening.
    -
    -

    Example:

    -
    -
    -
    cleanowned scattered X
    -
    This will confiscate rotten and dropped food, garbage on the floors and any -worn items with 'X' damage and above.
    -
    -
    -
    -
    -
    -

    Bugfixes

    -
    -

    drybuckets

    -

    This utility removes water from all buckets in your fortress, allowing them to be safely used for making lye.

    -
    -
    -

    fixdiplomats

    -

    Up to version 0.31.12, Elves only sent Diplomats to your fortress to propose -tree cutting quotas due to a bug; once that bug was fixed, Elves stopped caring -about excess tree cutting. This command adds a Diplomat position to all Elven -civilizations, allowing them to negotiate tree cutting quotas (and allowing you -to violate them and potentially start wars) in case you haven't already modified -your raws accordingly.

    -
    -
    -

    fixmerchants

    -

    This command adds the Guild Representative position to all Human civilizations, -allowing them to make trade agreements (just as they did back in 0.28.181.40d -and earlier) in case you haven't already modified your raws accordingly.

    -
    -
    -

    fixveins

    -

    Removes invalid references to mineral inclusions and restores missing ones. -Use this if you broke your embark with tools like tiletypes, or if you -accidentally placed a construction on top of a valuable mineral floor.

    -
    -
    -

    tweak

    -

    Contains various tweaks for minor bugs.

    -

    One-shot subcommands:

    - --- - - - - - - - - - -
    clear-missing:Remove the missing status from the selected unit. -This allows engraving slabs for ghostly, but not yet -found, creatures.
    clear-ghostly:Remove the ghostly status from the selected unit and mark -it as dead. This allows getting rid of bugged ghosts -which do not show up in the engraving slab menu at all, -even after using clear-missing. It works, but is -potentially very dangerous - so use with care. Probably -(almost certainly) it does not have the same effects like -a proper burial. You've been warned.
    fixmigrant:Remove the resident/merchant flag from the selected unit. -Intended to fix bugged migrants/traders who stay at the -map edge and don't enter your fort. Only works for -dwarves (or generally the player's race in modded games). -Do NOT abuse this for 'real' caravan merchants (if you -really want to kidnap them, use 'tweak makeown' instead, -otherwise they will have their clothes set to forbidden etc).
    makeown:Force selected unit to become a member of your fort. -Can be abused to grab caravan merchants and escorts, even if -they don't belong to the player's race. Foreign sentients -(humans, elves) can be put to work, but you can't assign rooms -to them and they don't show up in DwarfTherapist because the -game treats them like pets. Grabbing draft animals from -a caravan can result in weirdness (animals go insane or berserk -and are not flagged as tame), but you are allowed to mark them -for slaughter. Grabbing wagons results in some funny spam, then -they are scuttled.
    -

    Subcommands that persist until disabled or DF quit:

    - --- - - - - - - - - - - - - - - - - - - - -
    stable-cursor:Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode.
    patrol-duty:Makes Train orders not count as patrol duty to stop unhappy thoughts. -Does NOT fix the problem when soldiers go off-duty (i.e. civilian).
    readable-build-plate:
     Fixes rendering of creature weight limits in pressure plate build menu.
    stable-temp:Fixes performance bug 6012 by squashing jitter in temperature updates. -In very item-heavy forts with big stockpiles this can improve FPS by 50-100%
    fast-heat:Further improves temperature update performance by ensuring that 1 degree -of item temperature is crossed in no more than specified number of frames -when updating from the environment temperature. This reduces the time it -takes for stable-temp to stop updates again when equilibrium is disturbed.
    fix-dimensions:Fixes subtracting small amount of thread/cloth/liquid from a stack -by splitting the stack and subtracting from the remaining single item. -This is a necessary addition to the binary patch in bug 808.
    advmode-contained:
     Works around bug 6202, i.e. custom reactions with container inputs -in advmode. The issue is that the screen tries to force you to select -the contents separately from the container. This forcefully skips child -reagents.
    fast-trade:Makes Shift-Enter in the Move Goods to Depot and Trade screens select -the current item (fully, in case of a stack), and scroll down one line.
    -
    -
    -
    -

    Mode switch and reclaim

    -
    -

    lair

    -

    This command allows you to mark the map as 'monster lair', preventing item -scatter on abandon. When invoked as 'lair reset', it does the opposite.

    -

    Unlike reveal, this command doesn't save the information about tiles - you -won't be able to restore state of real monster lairs using 'lair reset'.

    -

    Options:

    -
    - --- - - - - - -
    lair:Mark the map as monster lair
    lair reset:Mark the map as ordinary (not lair)
    -
    -
    -
    -

    mode

    -

    This command lets you see and change the game mode directly. -Not all combinations are good for every situation and most of them will -produce undesirable results. There are a few good ones though.

    -
    -

    Example

    -

    You are in fort game mode, managing your fortress and paused. -You switch to the arena game mode, assume control of a creature and then -switch to adventure game mode(1). -You just lost a fortress and gained an adventurer. -You could also do this. -You are in fort game mode, managing your fortress and paused at the esc menu. -You switch to the adventure game mode, then use Dfusion to assume control of a creature and then -save or retire. -You just created a returnable mountain home and gained an adventurer.

    -
    -

    I take no responsibility of anything that happens as a result of using this tool

    -
    -
    -
    -

    Visualizer and data export

    -
    -

    ssense / stonesense

    -

    An isometric visualizer that runs in a second window. This requires working -graphics acceleration and at least a dual core CPU (otherwise it will slow -down DF).

    -

    All the data resides in the 'stonesense' directory. For detailed instructions, -see stonesense/README.txt

    -

    Compatible with Windows > XP SP3 and most modern Linux distributions.

    -

    Older versions, support and extra graphics can be found in the bay12 forum -thread: http://www.bay12forums.com/smf/index.php?topic=43260.0

    -

    Some additional resources: -http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository

    -
    -
    -

    mapexport

    -

    Export the current loaded map as a file. This will be eventually usable -with visualizers.

    -
    -
    -

    dwarfexport

    -

    Export dwarves to RuneSmith-compatible XML.

    -
    -
    -
    -

    Job management

    -
    -

    job

    -

    Command for general job query and manipulation.

    -
    -
    Options:
    -
    -
    no extra options
    -
    Print details of the current job. The job can be selected -in a workshop, or the unit/jobs screen.
    -
    list
    -
    Print details of all jobs in the selected workshop.
    -
    item-material <item-idx> <material[:subtoken]>
    -
    Replace the exact material id in the job item.
    -
    item-type <item-idx> <type[:subtype]>
    -
    Replace the exact item type id in the job item.
    -
    -
    -
    -
    -
    -

    job-material

    -

    Alter the material of the selected job.

    -

    Invoked as:

    -
    -job-material <inorganic-token>
    -
    -

    Intended to be used as a keybinding:

    -
    -
      -
    • In 'q' mode, when a job is highlighted within a workshop or furnace, -changes the material of the job. Only inorganic materials can be used -in this mode.
    • -
    • In 'b' mode, during selection of building components positions the cursor -over the first available choice with the matching material.
    • -
    -
    -
    -
    -

    job-duplicate

    -
    -
    Duplicate the selected job in a workshop:
    -
      -
    • In 'q' mode, when a job is highlighted within a workshop or furnace building, -instantly duplicates the job.
    • -
    -
    -
    -
    -
    -

    workflow

    -

    Manage control of repeat jobs.

    -

    Usage:

    -
    -
    -
    workflow enable [option...], workflow disable [option...]
    -

    If no options are specified, enables or disables the plugin. -Otherwise, enables or disables any of the following options:

    -
      -
    • drybuckets: Automatically empty abandoned water buckets.
    • -
    • auto-melt: Resume melt jobs when there are objects to melt.
    • -
    -
    -
    workflow jobs
    -
    List workflow-controlled jobs (if in a workshop, filtered by it).
    -
    workflow list
    -
    List active constraints, and their job counts.
    -
    workflow count <constraint-spec> <cnt-limit> [cnt-gap], workflow amount <constraint-spec> <cnt-limit> [cnt-gap]
    -
    Set a constraint. The first form counts each stack as only 1 item.
    -
    workflow unlimit <constraint-spec>
    -
    Delete a constraint.
    -
    -
    -
    -

    Function

    -

    When the plugin is enabled, it protects all repeat jobs from removal. -If they do disappear due to any cause, they are immediately re-added to their -workshop and suspended.

    -

    In addition, when any constraints on item amounts are set, repeat jobs that -produce that kind of item are automatically suspended and resumed as the item -amount goes above or below the limit. The gap specifies how much below the limit -the amount has to drop before jobs are resumed; this is intended to reduce -the frequency of jobs being toggled.

    -
    -
    -

    Constraint examples

    -

    Keep metal bolts within 900-1000, and wood/bone within 150-200.

    -
    -workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
    -workflow amount AMMO:ITEM_AMMO_BOLTS/WOOD,BONE 200 50
    -
    -

    Keep the number of prepared food & drink stacks between 90 and 120

    -
    -workflow count FOOD 120 30
    -workflow count DRINK 120 30
    -
    -

    Make sure there are always 25-30 empty bins/barrels/bags.

    -
    -workflow count BIN 30
    -workflow count BARREL 30
    -workflow count BOX/CLOTH,SILK,YARN 30
    -
    -

    Make sure there are always 15-20 coal and 25-30 copper bars.

    -
    -workflow count BAR//COAL 20
    -workflow count BAR//COPPER 30
    -
    -

    Collect 15-20 sand bags and clay boulders.

    -
    -workflow count POWDER_MISC/SAND 20
    -workflow count BOULDER/CLAY 20
    -
    -

    Make sure there are always 80-100 units of dimple dye.

    -
    -  workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20
    -
    -In order for this to work, you have to set the material of the PLANT input
    -on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material'
    -command.
    -
    -
    -
    -
    -
    -

    Fortress activity management

    -
    -

    seedwatch

    -

    Tool for turning cooking of seeds and plants on/off depending on how much you -have of them.

    -

    See 'seedwatch help' for detailed description.

    -
    -
    -

    zone

    -

    Helps a bit with managing activity zones (pens, pastures and pits) and cages.

    -

    Options:

    -
    - --- - - - - - - - - - - - - - - - - - - - - - - - - - -
    set:Set zone or cage under cursor as default for future assigns.
    assign:Assign unit(s) to the pen or pit marked with the 'set' command. -If no filters are set a unit must be selected in the in-game ui. -Can also be followed by a valid zone id which will be set -instead.
    unassign:Unassign selected creature from it's zone.
    nick:Mass-assign nicknames, must be followed by the name you want -to set.
    remnick:Mass-remove nicknames.
    tocages:Assign unit(s) to cages inside a pasture.
    uinfo:Print info about unit(s). If no filters are set a unit must -be selected in the in-game ui.
    zinfo:Print info about zone(s). If no filters are set zones under -the cursor are listed.
    verbose:Print some more info.
    filters:Print list of valid filter options.
    examples:Print some usage examples.
    not:Negates the next filter keyword.
    -
    -

    Filters:

    -
    - --- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    all:Process all units (to be used with additional filters).
    count:Must be followed by a number. Process only n units (to be used -with additional filters).
    unassigned:Not assigned to zone, chain or built cage.
    minage:Minimum age. Must be followed by number.
    maxage:Maximum age. Must be followed by number.
    race:Must be followed by a race RAW ID (e.g. BIRD_TURKEY, ALPACA, -etc). Negatable.
    caged:In a built cage. Negatable.
    own:From own civilization. Negatable.
    merchant:Is a merchant / belongs to a merchant. Should only be used for -pitting, not for stealing animals (slaughter should work).
    war:Trained war creature. Negatable.
    hunting:Trained hunting creature. Negatable.
    tamed:Creature is tame. Negatable.
    trained:Creature is trained. Finds war/hunting creatures as well as -creatures who have a training level greater than 'domesticated'. -If you want to specifically search for war/hunting creatures use -'war' or 'hunting' Negatable.
    trainablewar:Creature can be trained for war (and is not already trained for -war/hunt). Negatable.
    trainablehunt:Creature can be trained for hunting (and is not already trained -for war/hunt). Negatable.
    male:Creature is male. Negatable.
    female:Creature is female. Negatable.
    egglayer:Race lays eggs. Negatable.
    grazer:Race is a grazer. Negatable.
    milkable:Race is milkable. Negatable.
    -
    -
    -

    Usage with single units

    -

    One convenient way to use the zone tool is to bind the command 'zone assign' to -a hotkey, maybe also the command 'zone set'. Place the in-game cursor over -a pen/pasture or pit, use 'zone set' to mark it. Then you can select units -on the map (in 'v' or 'k' mode), in the unit list or from inside cages -and use 'zone assign' to assign them to their new home. Allows pitting your -own dwarves, by the way.

    -
    -
    -

    Usage with filters

    -

    All filters can be used together with the 'assign' command.

    -

    Restrictions: It's not possible to assign units who are inside built cages -or chained because in most cases that won't be desirable anyways. -It's not possible to cage owned pets because in that case the owner -uncages them after a while which results in infinite hauling back and forth.

    -

    Usually you should always use the filter 'own' (which implies tame) unless you -want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless -you specify 'race DWARF' (so it's safe to use 'assign all own' to one big -pasture if you want to have all your animals at the same place). 'egglayer' and -'milkable' should be used together with 'female' unless you have a mod with -egg-laying male elves who give milk or whatever. Merchants and their animals are -ignored unless you specify 'merchant' (pitting them should be no problem, -but stealing and pasturing their animals is not a good idea since currently they -are not properly added to your own stocks; slaughtering them should work).

    -

    Most filters can be negated (e.g. 'not grazer' -> race is not a grazer).

    -
    -
    -

    Mass-renaming

    -

    Using the 'nick' command you can set the same nickname for multiple units. -If used without 'assign', 'all' or 'count' it will rename all units in the -current default target zone. Combined with 'assign', 'all' or 'count' (and -further optional filters) it will rename units matching the filter conditions.

    -
    -
    -

    Cage zones

    -

    Using the 'tocages' command you can assign units to a set of cages, for example -a room next to your butcher shop(s). They will be spread evenly among available -cages to optimize hauling to and butchering from them. For this to work you need -to build cages and then place one pen/pasture activity zone above them, covering -all cages you want to use. Then use 'zone set' (like with 'assign') and use -'zone tocages filter1 filter2 ...'. 'tocages' overwrites 'assign' because it -would make no sense, but can be used together with 'nick' or 'remnick' and all -the usual filters.

    -
    -
    -

    Examples

    -
    -
    zone assign all own ALPACA minage 3 maxage 10
    -
    Assign all own alpacas who are between 3 and 10 years old to the selected -pasture.
    -
    zone assign all own caged grazer nick ineedgrass
    -
    Assign all own grazers who are sitting in cages on stockpiles (e.g. after -buying them from merchants) to the selected pasture and give them -the nickname 'ineedgrass'.
    -
    zone assign all own not grazer not race CAT
    -
    Assign all own animals who are not grazers, excluding cats.
    -
    zone assign count 5 own female milkable
    -
    Assign up to 5 own female milkable creatures to the selected pasture.
    -
    zone assign all own race DWARF maxage 2
    -
    Throw all useless kids into a pit :)
    -
    zone nick donttouchme
    -
    Nicknames all units in the current default zone or cage to 'donttouchme'. -Mostly intended to be used for special pastures or cages which are not marked -as rooms you want to protect from autobutcher.
    -
    zone tocages count 50 own tame male not grazer
    -
    Stuff up to 50 owned tame male animals who are not grazers into cages built -on the current default zone.
    -
    -
    -
    -
    -

    autonestbox

    -

    Assigns unpastured female egg-layers to nestbox zones. Requires that you create -pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox -must be in the top left corner. Only 1 unit will be assigned per pen, regardless -of the size. The age of the units is currently not checked, most birds grow up -quite fast. Egglayers who are also grazers will be ignored, since confining them -to a 1x1 pasture is not a good idea. Only tame and domesticated own units are -processed since pasturing half-trained wild egglayers could destroy your neat -nestbox zones when they revert to wild. When called without options autonestbox -will instantly run once.

    -

    Options:

    -
    - --- - - - - - - - -
    start:Start running every X frames (df simulation ticks). -Default: X=6000, which would be every 60 seconds at 100fps.
    stop:Stop running automatically.
    sleep:Must be followed by number X. Changes the timer to sleep X -frames between runs.
    -
    -
    -
    -

    autobutcher

    -

    Assigns lifestock for slaughter once it reaches a specific count. Requires that -you add the target race(s) to a watch list. Only tame units will be processed.

    -

    Named units will be completely ignored (to protect specific animals from -autobutcher you can give them nicknames with the tool 'rename unit' for single -units or with 'zone nick' to mass-rename units in pastures and cages).

    -

    Creatures trained for war or hunting will be ignored as well.

    -

    Creatures assigned to cages will be ignored if the cage is defined as a room -(to avoid butchering unnamed zoo animals).

    -

    Once you have too much adults, the oldest will be butchered first. -Once you have too much kids, the youngest will be butchered first. -If you don't set any target count the following default will be used: -1 male kid, 5 female kids, 1 male adult, 5 female adults.

    -

    Options:

    -
    - --- - - - - - - - - - - - - - - - - - - - - - - - - - - -
    start:Start running every X frames (df simulation ticks). -Default: X=6000, which would be every 60 seconds at 100fps.
    stop:Stop running automatically.
    sleep:Must be followed by number X. Changes the timer to sleep -X frames between runs.
    watch R:Start watching a race. R can be a valid race RAW id (ALPACA, -BIRD_TURKEY, etc) or a list of ids seperated by spaces or -the keyword 'all' which affects all races on your current -watchlist.
    unwatch R:Stop watching race(s). The current target settings will be -remembered. R can be a list of ids or the keyword 'all'.
    forget R:Stop watching race(s) and forget it's/their target settings. -R can be a list of ids or the keyword 'all'.
    autowatch:Automatically adds all new races (animals you buy from merchants, -tame yourself or get from migrants) to the watch list using -default target count.
    noautowatch:Stop auto-adding new races to the watchlist.
    list:Print the current status and watchlist.
    list_export:Print status and watchlist in a format which can be used -to import them to another savegame (see notes).
    target fk mk fa ma R:
     Set target count for specified race(s). -fk = number of female kids, -mk = number of male kids, -fa = number of female adults, -ma = number of female adults. -R can be a list of ids or the keyword 'all' or 'new'. -R = 'all': change target count for all races on watchlist -and set the new default for the future. R = 'new': don't touch -current settings on the watchlist, only set the new default -for future entries.
    example:Print some usage examples.
    -
    -

    Examples:

    -

    You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, -1 male) of the race alpaca. Once the kids grow up the oldest adults will get -slaughtered. Excess kids will get slaughtered starting with the youngest -to allow that the older ones grow into adults. Any unnamed cats will -be slaughtered as soon as possible.

    -
    -autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY
    -autobutcher target 0 0 0 0 CAT
    -autobutcher watch ALPACA BIRD_TURKEY CAT
    -autobutcher start
    -
    -

    Automatically put all new races onto the watchlist and mark unnamed tame units -for slaughter as soon as they arrive in your fort. Settings already made -for specific races will be left untouched.

    -
    -autobutcher target 0 0 0 0 new
    -autobutcher autowatch
    -autobutcher start
    -
    -

    Stop watching the races alpaca and cat, but remember the target count -settings so that you can use 'unwatch' without the need to enter the -values again. Note: 'autobutcher unwatch all' works, but only makes sense -if you want to keep the plugin running with the 'autowatch' feature or manually -add some new races with 'watch'. If you simply want to stop it completely use -'autobutcher stop' instead.

    -
    -autobutcher unwatch ALPACA CAT
    -
    -

    Note:

    -

    Settings and watchlist are stored in the savegame, so that you can have -different settings for each world. If you want to copy your watchlist to -another savegame you can use the command list_export:

    -
    -Load savegame where you made the settings.
    -Start a CMD shell and navigate to the df directory. Type the following into the shell:
    -dfhack-run autobutcher list_export > autobutcher.bat
    -Load the savegame where you want to copy the settings to, run the batch file (from the shell):
    -autobutcher.bat
    -
    -
    -
    -

    autolabor

    -

    Automatically manage dwarf labors.

    -

    When enabled, autolabor periodically checks your dwarves and enables or -disables labors. It tries to keep as many dwarves as possible busy but -also tries to have dwarves specialize in specific skills.

    -
    -

    Note

    -

    Warning: autolabor will override any manual changes you make to labors -while it is enabled.

    -
    -

    For detailed usage information, see 'help autolabor'.

    -
    -
    -
    -

    Other

    -
    -

    catsplosion

    -

    Makes cats just multiply. It is not a good idea to run this more than once or -twice.

    -
    -
    -

    dfusion

    -

    This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin.

    -

    See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15

    -

    Confirmed working DFusion plugins:

    - --- - - - -
    simple_embark:allows changing the number of dwarves available on embark.
    -
    -

    Note

    -
      -
    • Some of the DFusion plugins aren't completely ported yet. This can lead to crashes.
    • -
    • This is currently working only on Windows.
    • -
    • The game will be suspended while you're using dfusion. Don't panic when it doen't respond.
    • -
    -
    -
    -
    -
    -
    -

    Scripts

    -

    Lua or ruby scripts placed in the hack/scripts/ directory are considered for -execution as if they were native DFHack commands. They are listed at the end -of the 'ls' command output.

    -

    Note: scripts in subdirectories of hack/scripts/ can still be called, but will -only be listed by ls if called as 'ls -a'. This is intended as a way to hide -scripts that are obscure, developer-oriented, or should be used as keybindings.

    -

    Some notable scripts:

    -
    -

    fix/*

    -

    Scripts in this subdirectory fix various bugs and issues, some of them obscure.

    -
      -
    • fix/dead-units

      -

      Removes uninteresting dead units from the unit list. Doesn't seem to give any -noticeable performance gain, but migrants normally stop if the unit list grows -to around 3000 units, and this script reduces it back.

      -
    • -
    • fix/population-cap

      -

      Run this after every migrant wave to ensure your population cap is not exceeded. -The issue with the cap is that it is compared to the population number reported -by the last caravan, so once it drops below the cap, migrants continue to come -until that number is updated again.

      -
    • -
    • fix/stable-temp

      -

      Instantly sets the temperature of all free-lying items to be in equilibrium with -the environment and stops temperature updates. In order to maintain this efficient -state however, use tweak stable-temp and tweak fast-heat.

      -
    • -
    • fix/item-occupancy

      -

      Diagnoses and fixes issues with nonexistant 'items occupying site', usually -caused by autodump bugs or other hacking mishaps.

      -
    • -
    -
    -
    -

    gui/*

    -

    Scripts that implement dialogs inserted into the main game window are put in this -directory.

    -
    -
    -

    quicksave

    -

    If called in dwarf mode, makes DF immediately auto-save the game by setting a flag -normally used in seasonal auto-save.

    -
    -
    -

    setfps

    -

    Run setfps <number> to set the FPS cap at runtime, in case you want to watch -combat in slow motion or something :)

    -
    -
    -

    siren

    -

    Wakes up sleeping units, cancels breaks and stops parties either everywhere, -or in the burrows given as arguments. In return, adds bad thoughts about -noise, tiredness and lack of protection. Also, the units with interrupted -breaks will go on break again a lot sooner. The script is intended for -emergencies, e.g. when a siege appears, and all your military is partying.

    -
    -
    -

    growcrops

    -

    Instantly grow seeds inside farming plots.

    -

    With no argument, this command list the various seed types currently in -use in your farming plots. -With a seed type, the script will grow 100 of these seeds, ready to be -harvested. You can change the number with a 2nd argument.

    -

    For exemple, to grow 40 plump helmet spawn:

    -
    -growcrops plump 40
    -
    -
    -
    -

    removebadthoughts

    -

    This script remove negative thoughts from your dwarves. Very useful against -tantrum spirals.

    -

    With a selected unit in 'v' mode, will clear this unit's mind, otherwise -clear all your fort's units minds.

    -

    Individual dwarf happiness may not increase right after this command is run, -but in the short term your dwarves will get much more joyful. -The thoughts are set to be very old, and the game will remove them soon when -you unpause.

    -

    With the optional -v parameter, the script will dump the negative thoughts -it removed.

    -
    -
    -

    slayrace

    -

    Kills any unit of a given race.

    -

    With no argument, lists the available races.

    -

    With the special argument 'him', targets only the selected creature.

    -

    Any non-dead non-caged unit of the specified race gets its blood_count -set to 0, which means immediate death at the next game tick. For creatures -such as vampires, also set animal.vanish_countdown to 2.

    -

    An alternate mode is selected by adding a 2nd argument to the command, -magma. In this case, a column of 7/7 magma is generated on top of the -targets until they die (Warning: do not call on magma-safe creatures. Also, -using this mode for birds is not recommanded.)

    -

    Will target any unit on a revealed tile of the map, including ambushers.

    -

    Ex:

    -
    -slayrace gob
    -
    -

    To kill a single creature, select the unit with the 'v' cursor and:

    -
    -slayrace him
    -
    -

    To purify all elves on the map with fire (may have side-effects):

    -
    -slayrace elve magma
    -
    -
    -
    -

    magmasource

    -

    Create an infinite magma source on a tile.

    -

    This script registers a map tile as a magma source, and every 12 game ticks -that tile receives 1 new unit of flowing magma.

    -

    Place the game cursor where you want to create the source (must be a -flow-passable tile, and not too high in the sky) and call:

    -
    -magmasource here
    -
    -

    To add more than 1 unit everytime, call the command again.

    -

    To delete one source, place the cursor over its tile and use delete-here. -To remove all placed sources, call magmasource stop.

    -

    With no argument, this command shows an help message and list existing sources.

    -
    -
    -

    digfort

    -

    A script to designate an area for digging according to a plan in csv format.

    -

    This script, inspired from quickfort, can designate an area for digging. -Your plan should be stored in a .csv file like this:

    -
    -# this is a comment
    -d;d;u;d;d;skip this tile;d
    -d;d;d;i
    -
    -

    Available tile shapes are named after the 'dig' menu shortcuts: -d for dig, u for upstairs, d downstairs, i updown, -h channel, r upward ramp, x remove designation. -Unrecognized characters are ignored (eg the 'skip this tile' in the sample).

    -

    Empty lines and data after a # are ignored as comments. -To skip a row in your design, use a single ;.

    -

    The script takes the plan filename, starting from the root df folder.

    -
    -
    -

    superdwarf

    -

    Similar to fastdwarf, per-creature.

    -

    To make any creature superfast, target it ingame using 'v' and:

    -
    -superdwarf add
    -
    -

    Other options available: del, clear, list.

    -

    This plugin also shortens the 'sleeping' and 'on break' periods of targets.

    -
    -
    -

    drainaquifer

    -

    Remove all 'aquifer' tag from the map blocks. Irreversible.

    -
    -
    -

    deathcause

    -

    Focus a body part ingame, and this script will display the cause of death of -the creature.

    -
    -
    -
    -

    In-game interface tools

    -

    These tools work by displaying dialogs or overlays in the game window, and -are mostly implemented by lua scripts.

    -
    -

    Dwarf Manipulator

    -

    Implemented by the manipulator plugin. To activate, open the unit screen and -press 'l'.

    -

    This tool implements a Dwarf Therapist-like interface within the game UI. The -far left column displays the unit's Happiness (color-coded based on its -value), and the right half of the screen displays each dwarf's labor settings -and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand -Master, and U-Z for Legendary thru Legendary+5). Cells with red backgrounds -denote skills not controlled by labors.

    -

    Use the arrow keys or number pad to move the cursor around, holding Shift to -move 10 tiles at a time.

    -

    Press the Z-Up (<) and Z-Down (>) keys to move quickly between labor/skill -categories. The numpad Z-Up and Z-Down keys seek to the first or last unit -in the list.

    -

    Press Enter to toggle the selected labor for the selected unit, or Shift+Enter -to toggle all labors within the selected category.

    -

    Press the +- keys to sort the unit list according to the currently selected -skill/labor, and press the */ keys to sort the unit list by Name, Profession, -Happiness, or Arrival order (using Tab to select which sort method to use here).

    -

    With a unit selected, you can press the "v" key to view its properties (and -possibly set a custom nickname or profession) or the "c" key to exit -Manipulator and zoom to its position within your fortress.

    -

    The following mouse shortcuts are also available:

    -
      -
    • Click on a column header to sort the unit list. Left-click to sort it in one -direction (descending for happiness or labors/skills, ascending for name or -profession) and right-click to sort it in the opposite direction.
    • -
    • Left-click on a labor cell to toggle that labor. Right-click to move the -cursor onto that cell instead of toggling it.
    • -
    • Left-click on a unit's name or profession to view its properties.
    • -
    • Right-click on a unit's name or profession to zoom to it.
    • -
    -

    Pressing ESC normally returns to the unit screen, but Shift-ESC would exit -directly to the main dwarf mode screen.

    -
    -
    -

    Liquids

    -

    Implemented by the gui/liquids script. To use, bind to a key and activate in the 'k' mode.

    -

    While active, use the suggested keys to switch the usual liquids parameters, and Enter -to select the target area and apply changes.

    -
    -
    -

    Mechanisms

    -

    Implemented by the gui/mechanims script. To use, bind to a key and activate in the 'q' mode.

    -

    Lists mechanisms connected to the building, and their links. Navigating the list centers -the view on the relevant linked buildings.

    -

    To exit, press ESC or Enter; ESC recenters on the original building, while Enter leaves -focus on the current one. Shift-Enter has an effect equivalent to pressing Enter, and then -re-entering the mechanisms ui.

    -
    -
    -

    Rename

    -

    Backed by the rename plugin, the gui/rename script allows entering the desired name -via a simple dialog in the game ui.

    -
      -
    • gui/rename [building] in 'q' mode changes the name of a building.

      -

      The selected building must be one of stockpile, workshop, furnace, trap, or siege engine. -It is also possible to rename zones from the 'i' menu.

      -
    • -
    • gui/rename [unit] with a unit selected changes the nickname.

      -
    • -
    • gui/rename unit-profession changes the selected unit's custom profession name.

      -
    • -
    -

    The building or unit options are automatically assumed when in relevant ui state.

    -
    -
    -

    Room List

    -

    Implemented by the gui/room-list script. To use, bind to a key and activate in the 'q' mode, -either immediately or after opening the assign owner page.

    -

    The script lists other rooms owned by the same owner, or by the unit selected in the assign -list, and allows unassigning them.

    -
    -
    -
    -

    Behavior Mods

    -

    These plugins, when activated via configuration UI or by detecting certain -structures in RAWs, modify the game engine behavior concerning the target -objects to add features not otherwise present.

    -
    -

    Siege Engine

    -

    The siege-engine plugin enables siege engines to be linked to stockpiles, and -aimed at an arbitrary rectangular area across Z levels, instead of the original -four directions. Also, catapults can be ordered to load arbitrary objects, not -just stones.

    -

    The configuration front-end to the plugin is implemented by the gui/siege-engine -script. Bind it to a key and activate after selecting a siege engine in 'q' mode.

    -

    The main mode displays the current target, selected ammo item type, linked stockpiles and -the allowed operator skill range. The map tile color is changed to signify if it can be -hit by the selected engine: green for fully reachable, blue for out of range, red for blocked, -yellow for partially blocked.

    -

    Pressing 'r' changes into the target selection mode, which works by highlighting two points -with Enter like all designations. When a target area is set, the engine projectiles are -aimed at that area, or units within it (this doesn't actually change the original aiming -code, instead the projectile trajectory parameters are rewritten as soon as it appears).

    -

    After setting the target in this way for one engine, you can 'paste' the same area into others -just by pressing 'p' in the main page of this script. The area to paste is kept until you quit -DF, or select another area manually.

    -

    Pressing 't' switches to a mode for selecting a stockpile to take ammo from.

    -

    Exiting from the siege engine script via ESC reverts the view to the state prior to starting -the script. Shift-ESC retains the current viewport, and also exits from the 'q' mode to main -menu.

    -
    -

    DISCLAIMER

    -

    Siege engines are a very interesting feature, but sadly almost useless in the current state -because they haven't been updated since 2D and can only aim in four directions. This is an -attempt to bring them more up to date until Toady has time to work on it. Actual improvements, -e.g. like making siegers bring their own, are something only Toady can do.

    -
    -
    -
    -

    Power Meter

    -

    The power-meter plugin implements a modified pressure plate that detects power being -supplied to gear boxes built in the four adjacent N/S/W/E tiles.

    -

    The configuration front-end is implemented by the gui/power-meter script. Bind it to a -key and activate after selecting Pressure Plate in the build menu.

    -

    The script follows the general look and feel of the regular pressure plate build -configuration page, but configures parameters relevant to the modded power meter building.

    -
    -
    -

    Steam Engine

    -

    The steam-engine plugin detects custom workshops with STEAM_ENGINE in -their token, and turns them into real steam engines.

    -
    -

    Rationale

    -

    The vanilla game contains only water wheels and windmills as sources of -power, but windmills give relatively little power, and water wheels require -flowing water, which must either be a real river and thus immovable and -limited in supply, or actually flowing and thus laggy.

    -

    Steam engines are an alternative to water reactors that actually makes -sense, and hopefully doesn't lag. Also, unlike e.g. animal treadmills, -it can be done just by combining existing features of the game engine -in a new way with some glue code and a bit of custom logic.

    -
    -
    -

    Construction

    -

    The workshop needs water as its input, which it takes via a -passable floor tile below it, like usual magma workshops do. -The magma version also needs magma.

    -
    -

    ISSUE

    -

    Since this building is a machine, and machine collapse -code cannot be hooked, it would collapse over true open space. -As a loophole, down stair provides support to machines, while -being passable, so use them.

    -
    -

    After constructing the building itself, machines can be connected -to the edge tiles that look like gear boxes. Their exact position -is extracted from the workshop raws.

    -
    -

    ISSUE

    -

    Like with collapse above, part of the code involved in -machine connection cannot be hooked. As a result, the workshop -can only immediately connect to machine components built AFTER it. -This also means that engines cannot be chained without intermediate -short axles that can be built later than both of the engines.

    -
    -
    -
    -

    Operation

    -

    In order to operate the engine, queue the Stoke Boiler job (optionally -on repeat). A furnace operator will come, possibly bringing a bar of fuel, -and perform it. As a result, a "boiling water" item will appear -in the 't' view of the workshop.

    -
    -

    Note

    -

    The completion of the job will actually consume one unit -of the appropriate liquids from below the workshop. This means -that you cannot just raise 7 units of magma with a piston and -have infinite power. However, liquid consumption should be slow -enough that water can be supplied by a pond zone bucket chain.

    -
    -

    Every such item gives 100 power, up to a limit of 300 for coal, -and 500 for a magma engine. The building can host twice that -amount of items to provide longer autonomous running. When the -boiler gets filled to capacity, all queued jobs are suspended; -once it drops back to 3+1 or 5+1 items, they are re-enabled.

    -

    While the engine is providing power, steam is being consumed. -The consumption speed includes a fixed 10% waste rate, and -the remaining 90% are applied proportionally to the actual -load in the machine. With the engine at nominal 300 power with -150 load in the system, it will consume steam for actual -300*(10% + 90%*150/300) = 165 power.

    -

    Masterpiece mechanism and chain will decrease the mechanical -power drawn by the engine itself from 10 to 5. Masterpiece -barrel decreases waste rate by 4%. Masterpiece piston and pipe -decrease it by further 4%, and also decrease the whole steam -use rate by 10%.

    -
    -
    -

    Explosions

    -

    The engine must be constructed using barrel, pipe and piston -from fire-safe, or in the magma version magma-safe metals.

    -

    During operation weak parts get gradually worn out, and -eventually the engine explodes. It should also explode if -toppled during operation by a building destroyer, or a -tantruming dwarf.

    -
    -
    -

    Save files

    -

    It should be safe to load and view engine-using fortresses -from a DF version without DFHack installed, except that in such -case the engines won't work. However actually making modifications -to them, or machines they connect to (including by pulling levers), -can easily result in inconsistent state once this plugin is -available again. The effects may be as weird as negative power -being generated.

    -
    -
    -
    -

    Add Spatter

    -

    This plugin makes reactions with names starting with SPATTER_ADD_ -produce contaminants on the items instead of improvements. The produced -contaminants are immune to being washed away by water or destroyed by -the clean items command.

    -

    The plugin is intended to give some use to all those poisons that can -be bought from caravans. :)

    -

    To be really useful this needs patches from bug 808, tweak fix-dimensions -and tweak advmode-contained.

    -
    -
    -
    - - From 38f2ae399a443ffa6f215c5d3c8ff2329de333ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sat, 29 Sep 2012 03:30:38 +0200 Subject: [PATCH 082/196] Generate html documentation as part of the normal build. --- CMakeLists.txt | 21 ++++++++++++++++++++- fixTexts.sh | 5 ----- testTexts.sh | 5 +++++ 3 files changed, 25 insertions(+), 6 deletions(-) delete mode 100755 fixTexts.sh create mode 100755 testTexts.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bd4ad974..ee3f1d2e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,11 +147,30 @@ add_subdirectory(depends) find_package(Docutils) +set (RST_FILES +README +COMPILE +LUA_API +) + +set (RST_PROCESSED_FILES "") +IF(RST2HTML_EXECUTABLE) + foreach(F ${RST_FILES}) + add_custom_command( + OUTPUT "${dfhack_BINARY_DIR}/${F}.html" + COMMAND ${RST2HTML_EXECUTABLE} "${dfhack_SOURCE_DIR}/${F}.rst" > "${dfhack_BINARY_DIR}/${F}.html" + COMMENT "Translating ${F} to html" + DEPENDS "${dfhack_SOURCE_DIR}/${F}.rst") + list (APPEND RST_PROCESSED_FILES "${dfhack_BINARY_DIR}/${F}.html") + endforeach() + add_custom_target(HTML_DOCS ALL DEPENDS ${RST_PROCESSED_FILES}) +ENDIF() + # build the lib itself IF(BUILD_LIBRARY) add_subdirectory (library) ## install the default documentation files - install(FILES LICENSE Readme.html Compile.html Lua\ API.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) + install(FILES LICENSE ${RST_PROCESSED_FILES} DESTINATION ${DFHACK_USERDOC_DESTINATION}) endif() #build the plugins diff --git a/fixTexts.sh b/fixTexts.sh deleted file mode 100755 index 669fa068b..000000000 --- a/fixTexts.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -rst2html README.rst > Readme.html -rst2html COMPILE.rst > Compile.html -rst2html DEVEL.rst > Devel.html -rst2html LUA_API.rst > Lua\ API.html diff --git a/testTexts.sh b/testTexts.sh new file mode 100755 index 000000000..0189ae2a2 --- /dev/null +++ b/testTexts.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# this script is used for easy testing of the rst documentation files. +rst2html README.rst > README.html +rst2html COMPILE.rst > COMPILE.html +rst2html LUA_API.rst > LUA_API.html From eaddd0e646c57a8e72a3dd3c0d95871ec7b25b2e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 29 Sep 2012 12:47:41 +0400 Subject: [PATCH 083/196] Try shutting up a few random warnings. --- library/include/RemoteServer.h | 1 + library/modules/Maps.cpp | 2 +- library/modules/Screen.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index d89b44959..7a6940bbe 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -65,6 +65,7 @@ namespace DFHack RPCService *owner, const char *name, int flags) : RPCFunctionBase(in, out), name(name), flags(flags), owner(owner), id(-1) {} + virtual ~ServerFunctionBase() {} RPCService *owner; int16_t id; diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index f1f40f19c..608068b8e 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -585,7 +585,7 @@ bool MapExtras::Block::Allocate() if (!block) return false; - delete item_counts; + delete[] item_counts; delete tiles; delete basemats; init(); diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 8057d17a2..e9d19c931 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -195,7 +195,7 @@ bool Screen::drawBorder(const std::string &title) if (!gps) return false; int dimx = gps->dimx, dimy = gps->dimy; - Pen border(0xDB, 8); + Pen border('\xDB', 8); Pen text(0, 0, 7); Pen signature(0, 0, 8); From 7a74088287ed94517b30e6e9eadfef908da20043 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 29 Sep 2012 13:01:11 +0400 Subject: [PATCH 084/196] Make backspace seek to top left corner in manipulator. --- plugins/manipulator.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index feceb08ec..164b13dd7 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -385,7 +385,11 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src, int cur { df::unit *unit = src[i]; if (!unit) + { + if (cursor_pos > i) + cursor_pos--; continue; + } UnitInfo *cur = new UnitInfo; @@ -628,6 +632,9 @@ void viewscreen_unitlaborsst::feed(set *events) sel_row = units.size() - 1; } + if (events->count(interface_key::STRING_A000)) + sel_row = 0; + if (sel_row < first_row) first_row = sel_row; if (first_row < sel_row - num_rows + 1) @@ -661,6 +668,9 @@ void viewscreen_unitlaborsst::feed(set *events) sel_column = next; } + if (events->count(interface_key::STRING_A000)) + sel_column = 0; + if (sel_column < 0) sel_column = 0; if (sel_column > NUM_COLUMNS - 1) From cb2acc169c3dd2ddfc4af36c4829c6f50561c40b Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 29 Sep 2012 13:17:28 +0400 Subject: [PATCH 085/196] Edit docs. --- .gitignore | 3 +++ NEWS | 1 + README.rst | 34 ++++++++++++++++++++++++++-------- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index bdb9474bb..9f2b009c6 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,9 @@ build/tools build/plugins build/depends build/install_manifest.txt +build/README.html +build/LUA_API.html +build/COMPILE.html #ignore Kdevelop stuff .kdev4 diff --git a/NEWS b/NEWS index ee6dd6592..20c51f415 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,7 @@ DFHack v0.34.11-r2 (UNRELEASED) - prospect: pre-embark prospector accounts for caves & magma sea in its estimate. - rename: supports renaming stockpiles, workshops, traps, siege engines. - fastdwarf: now has an additional option to make dwarves teleport to their destination. + New commands: - misery: multiplies every negative thought gained (2x by default). - digtype: designates every tile of the same type of vein on the map for 'digging' (any dig designation). New tweaks: diff --git a/README.rst b/README.rst index 5cf79cdf5..a36cba607 100644 --- a/README.rst +++ b/README.rst @@ -1746,6 +1746,14 @@ In-game interface tools These tools work by displaying dialogs or overlays in the game window, and are mostly implemented by lua scripts. +.. note:: + + In order to avoid user confusion, as a matter of policy all these tools + display the word "DFHack" on the screen somewhere while active. + + As an exception, the tweak plugin described above does not follow this + guideline because it arguably just fixes small usability bugs in the game UI. + Dwarf Manipulator ================= @@ -1765,7 +1773,7 @@ move 10 tiles at a time. Press the Z-Up (<) and Z-Down (>) keys to move quickly between labor/skill categories. The numpad Z-Up and Z-Down keys seek to the first or last unit -in the list. +in the list. Backspace seeks to the top left corner. Press Enter to toggle the selected labor for the selected unit, or Shift+Enter to toggle all labors within the selected category. @@ -1850,6 +1858,12 @@ These plugins, when activated via configuration UI or by detecting certain structures in RAWs, modify the game engine behavior concerning the target objects to add features not otherwise present. +.. admonition:: DISCLAIMER + + The plugins in this section have mostly been created for fun as an interesting + technical challenge, and do not represent any long-term plans to produce more + similar modifications of the game. + Siege Engine ============ @@ -1859,6 +1873,17 @@ aimed at an arbitrary rectangular area across Z levels, instead of the original four directions. Also, catapults can be ordered to load arbitrary objects, not just stones. +Rationale +--------- + +Siege engines are a very interesting feature, but sadly almost useless in the current state +because they haven't been updated since 2D and can only aim in four directions. This is an +attempt to bring them more up to date until Toady has time to work on it. Actual improvements, +e.g. like making siegers bring their own, are something only Toady can do. + +Configuration UI +---------------- + The configuration front-end to the plugin is implemented by the gui/siege-engine script. Bind it to a key and activate after selecting a siege engine in 'q' mode. @@ -1882,13 +1907,6 @@ Exiting from the siege engine script via ESC reverts the view to the state prior the script. Shift-ESC retains the current viewport, and also exits from the 'q' mode to main menu. -.. admonition:: DISCLAIMER - - Siege engines are a very interesting feature, but sadly almost useless in the current state - because they haven't been updated since 2D and can only aim in four directions. This is an - attempt to bring them more up to date until Toady has time to work on it. Actual improvements, - e.g. like making siegers bring their own, are something only Toady can do. - Power Meter =========== From 6fd002382f08ee1f39976865e143104d6249f0fd Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 29 Sep 2012 12:12:56 +0200 Subject: [PATCH 086/196] ruby: fix cmake dependency on codegen.out.xml --- plugins/ruby/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index e10ee38bf..b8c90d134 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -20,7 +20,9 @@ ENDIF(DL_RUBY AND NOT APPLE) ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.rb COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.rb - DEPENDS ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl + # cmake quirk: depending on codegen.out.xml or generate_headers only is not enough, needs both + # test by manually patching any library/xml/moo.xml, run make ruby-autogen-rb -j2, and check build/plugins/ruby/ruby-autogen.rb for patched xml data + DEPENDS generate_headers ${dfhack_SOURCE_DIR}/library/include/df/codegen.out.xml ${CMAKE_CURRENT_SOURCE_DIR}/codegen.pl COMMENT ruby-autogen.rb ) ADD_CUSTOM_TARGET(ruby-autogen-rb DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.rb) From 48da06ec371421a48f801f6da1add6e437ccfa06 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 29 Sep 2012 15:18:04 +0400 Subject: [PATCH 087/196] Add an API function for reading the nominal skill level. --- LUA_API.rst | 5 +++++ library/LuaApi.cpp | 1 + library/include/modules/Units.h | 1 + library/modules/Units.cpp | 21 ++++++++++++++++++--- 4 files changed, 25 insertions(+), 3 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index f532d2212..bf7ee45a7 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -918,6 +918,11 @@ Units module Returns the age of the unit in years as a floating-point value. If ``true_age`` is true, ignores false identities. +* ``dfhack.units.getNominalSkill(unit, skill[, use_rust])`` + + Retrieves the nominal skill level for the given unit. If ``use_rust`` + is *true*, subtracts the rust penalty. + * ``dfhack.units.getEffectiveSkill(unit, skill)`` Computes the effective rating for the given skill, taking into account exhaustion, pain etc. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index b2d41dc19..dab99f48a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -828,6 +828,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isDwarf), WRAPM(Units, isCitizen), WRAPM(Units, getAge), + WRAPM(Units, getNominalSkill), WRAPM(Units, getEffectiveSkill), WRAPM(Units, computeMovementSpeed), WRAPM(Units, getProfessionName), diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 65f0b58a0..2019eb652 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -233,6 +233,7 @@ DFHACK_EXPORT bool isDwarf(df::unit *unit); DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); +DFHACK_EXPORT int getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust = false); DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); DFHACK_EXPORT int computeMovementSpeed(df::unit *unit); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 01b7b50f4..2644f4ab2 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -876,7 +876,7 @@ inline void adjust_skill_rating(int &rating, bool is_adventure, int value, int d } } -int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id) +int Units::getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust) { CHECK_NULL_POINTER(unit); @@ -892,9 +892,24 @@ int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id) df::enum_field key(skill_id); auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key); - int rating = 0; if (skill) - rating = std::max(0, int(skill->rating) - skill->rusty); + { + int rating = int(skill->rating); + if (use_rust) + rating -= skill->rusty; + return std::max(0, rating); + } + + return 0; +} + +int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id) +{ + /* + * This is 100% reverse-engineered from DF code. + */ + + int rating = getNominalSkill(unit, skill_id, true); // Apply special states From 62cde96724622a08f9dd6d4bf83f1763a02fd9df Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 29 Sep 2012 17:21:49 +0400 Subject: [PATCH 088/196] Add a script to reclassify 'individual choice' weapons based on unit skill. --- NEWS | 1 + README.rst | 40 ++++++--- dfhack.init-example | 3 + library/lua/utils.lua | 21 +++++ library/modules/Gui.cpp | 31 +++++++ scripts/gui/choose-weapons.lua | 154 +++++++++++++++++++++++++++++++++ 6 files changed, 237 insertions(+), 13 deletions(-) create mode 100644 scripts/gui/choose-weapons.lua diff --git a/NEWS b/NEWS index 20c51f415..fe7b5b667 100644 --- a/NEWS +++ b/NEWS @@ -56,6 +56,7 @@ DFHack v0.34.11-r2 (UNRELEASED) - gui/rename: renaming stockpiles, workshops and units via an in-game dialog. - gui/power-meter: front-end for the Power Meter plugin. - gui/siege-engine: front-end for the Siege Engine plugin. + - gui/choose-weapons: auto-choose matching weapons in the military equip screen. Autolabor plugin: - can set nonidle hauler percentage. - broker excluded from all labors when needed at depot. diff --git a/README.rst b/README.rst index a36cba607..7ca375039 100644 --- a/README.rst +++ b/README.rst @@ -1800,19 +1800,19 @@ Pressing ESC normally returns to the unit screen, but Shift-ESC would exit directly to the main dwarf mode screen. -Liquids -======= +gui/liquids +=========== -Implemented by the gui/liquids script. To use, bind to a key and activate in the 'k' mode. +To use, bind to a key and activate in the 'k' mode. While active, use the suggested keys to switch the usual liquids parameters, and Enter to select the target area and apply changes. -Mechanisms -========== +gui/mechanisms +============== -Implemented by the gui/mechanims script. To use, bind to a key and activate in the 'q' mode. +To use, bind to a key and activate in the 'q' mode. Lists mechanisms connected to the building, and their links. Navigating the list centers the view on the relevant linked buildings. @@ -1822,10 +1822,10 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter re-entering the mechanisms ui. -Rename -====== +gui/rename +========== -Backed by the rename plugin, the gui/rename script allows entering the desired name +Backed by the rename plugin, this script allows entering the desired name via a simple dialog in the game ui. * ``gui/rename [building]`` in 'q' mode changes the name of a building. @@ -1840,16 +1840,30 @@ via a simple dialog in the game ui. The ``building`` or ``unit`` options are automatically assumed when in relevant ui state. -Room List -========= +gui/room-list +============= -Implemented by the gui/room-list script. To use, bind to a key and activate in the 'q' mode, -either immediately or after opening the assign owner page. +To use, bind to a key and activate in the 'q' mode, either immediately or after opening +the assign owner page. The script lists other rooms owned by the same owner, or by the unit selected in the assign list, and allows unassigning them. +gui/choose-weapons +================== + +Bind to a key, and activate in the Equip->View/Customize page of the military screen. + +Depending on the cursor location, it rewrites all 'individual choice weapon' entries +in the selected squad or position to use a specific weapon type matching the assigned +unit's top skill. If the cursor is in the rightmost list over a weapon entry, it rewrites +only that entry, and does it even if it is not 'individual choice'. + +Rationale: individual choice seems to be unreliable when there is a weapon shortage, +and may lead to inappropriate weapons being selected. + + ============= Behavior Mods ============= diff --git a/dfhack.init-example b/dfhack.init-example index 2e656a609..8505f5acc 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -69,6 +69,9 @@ keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter # siege engine control keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine +# military weapon auto-select +keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons + ############################ # UI and game logic tweaks # ############################ diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 9fa473ed8..b46363cd7 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -361,6 +361,27 @@ function insert_or_update(vector,item,field,cmp) return added,cur,pos end +-- Binary search and erase +function erase_sorted_key(vector,key,field,cmp) + local cur,found,pos = binsearch(vector,key,field,cmp) + if found then + if df.isvalid(vector) then + vector:erase(pos) + else + table.remove(vector, pos) + end + end + return found,cur,pos +end + +function erase_sorted(vector,item,field,cmp) + local key = item + if field and item then + key = item[field] + end + return erase_sorted_key(vector,key,field,cmp) +end + -- Calls a method with a string temporary function call_with_string(obj,methodname,...) return dfhack.with_temp_object( diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 21156ac0a..2afb36205 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -347,6 +347,37 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_military) break; } + case df::viewscreen_layer_militaryst::Equip: + { + focus += "/" + enum_item_key(screen->equip.mode); + + switch (screen->equip.mode) + { + case df::viewscreen_layer_militaryst::T_equip::Customize: + { + if (screen->equip.edit_mode < 0) + focus += "/View"; + else + focus += "/" + enum_item_key(screen->equip.edit_mode); + break; + } + case df::viewscreen_layer_militaryst::T_equip::Uniform: + break; + case df::viewscreen_layer_militaryst::T_equip::Priority: + { + if (screen->equip.prio_in_move >= 0) + focus += "/Move"; + else + focus += "/View"; + break; + } + } + + static const char *lists[] = { "/Squads", "/Positions", "/Choices" }; + focus += lists[cur_list]; + break; + } + default: break; } diff --git a/scripts/gui/choose-weapons.lua b/scripts/gui/choose-weapons.lua new file mode 100644 index 000000000..85ad62b6e --- /dev/null +++ b/scripts/gui/choose-weapons.lua @@ -0,0 +1,154 @@ +-- Rewrite individual choice weapons into specific types. + +local utils = require 'utils' +local dlg = require 'gui.dialogs' + +local defs = df.global.world.raws.itemdefs +local entity = df.global.ui.main.fortress_entity +local tasks = df.global.ui.tasks +local equipment = df.global.ui.equipment + +function find_best_weapon(unit,mode) + local best = nil + local skill = nil + local skill_level = nil + local count = 0 + local function try(id,iskill) + local slevel = dfhack.units.getNominalSkill(unit,iskill) + -- Choose most skill + if (skill ~= nil and slevel > skill_level) + or (skill == nil and slevel > 0) then + best,skill,skill_level,count = id,iskill,slevel,0 + end + -- Then most produced within same skill + if skill == iskill then + local cnt = tasks.created_weapons[id] + if cnt > count then + best,count = id,cnt + end + end + end + for _,id in ipairs(entity.resources.weapon_type) do + local def = defs.weapons[id] + if def.skill_ranged >= 0 then + if mode == nil or mode == 'ranged' then + try(id, def.skill_ranged) + end + else + if mode == nil or mode == 'melee' then + try(id, def.skill_melee) + end + end + end + return best +end + +function unassign_wrong_items(unit,position,spec,subtype) + for i=#spec.assigned-1,0,-1 do + local id = spec.assigned[i] + local item = df.item.find(id) + + if item.subtype.subtype ~= subtype then + spec.assigned:erase(i) + + -- TODO: somewhat unexplored area; maybe missing some steps + utils.erase_sorted(position.assigned_items,id) + if utils.erase_sorted(equipment.items_assigned.WEAPON,item,'id') then + utils.insert_sorted(equipment.items_unassigned.WEAPON,item,'id') + end + equipment.update.weapon = true + unit.military.pickup_flags.update = true + end + end +end + +local count = 0 + +function adjust_uniform_spec(unit,position,spec,force) + if not unit then return end + local best + if spec.indiv_choice.melee then + best = find_best_weapon(unit, 'melee') + elseif spec.indiv_choice.ranged then + best = find_best_weapon(unit, 'ranged') + elseif spec.indiv_choice.any or force then + best = find_best_weapon(unit, nil) + end + if best then + count = count + 1 + spec.item_filter.item_subtype = best + spec.indiv_choice.any = false + spec.indiv_choice.melee = false + spec.indiv_choice.ranged = false + unassign_wrong_items(unit, position, spec, best) + end +end + +function adjust_position(unit,position,force) + if not unit then + local fig = df.historical_figure.find(position.occupant) + if not fig then return end + unit = df.unit.find(fig.unit_id) + end + + for _,v in ipairs(position.uniform.weapon) do + adjust_uniform_spec(unit, position, v, force) + end +end + +function adjust_squad(squad, force) + for _,pos in ipairs(squad.positions) do + adjust_position(nil, pos, force) + end +end + +local args = {...} +local vs = dfhack.gui.getCurViewscreen() +local vstype = df.viewscreen_layer_militaryst +if not vstype:is_instance(vs) then + qerror('Call this from the military screen') +end + +if vs.page == vstype.T_page.Equip +and vs.equip.mode == vstype.T_equip.T_mode.Customize then + local slist = vs.layer_objects[0] + local squad = vs.equip.squads[slist:getListCursor()] + + if slist.active then + print('Adjusting squad.') + adjust_squad(squad) + else + local plist = vs.layer_objects[1] + local pidx = plist:getListCursor() + local pos = squad.positions[pidx] + local unit = vs.equip.units[pidx] + + if plist.active then + print('Adjusting position.') + adjust_position(unit, pos) + elseif unit and vs.equip.edit_mode < 0 then + local wlist = vs.layer_objects[2] + local idx = wlist:getListCursor() + local cat = vs.equip.assigned.category[idx] + + if wlist.active and cat == df.uniform_category.weapon then + print('Adjusting spec.') + adjust_uniform_spec(unit, pos, vs.equip.assigned.spec[idx], true) + end + end + end +else + qerror('Call this from the Equip page of military screen') +end + +if count > 1 then + dlg.showMessage( + 'Choose Weapons', + 'Updated '..count..' uniform entries.', COLOR_GREEN + ) +elseif count == 0 then + dlg.showMessage( + 'Choose Weapons', + 'Did not find any entries to update.', COLOR_YELLOW + ) +end From b1ad92e310e89c72db271ee013b9daf4cf1afba7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 30 Sep 2012 02:52:30 +0200 Subject: [PATCH 089/196] Track structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 9773484b8..98d7ebf6a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 9773484b8430417ad488423ddeae3832f011ce9d +Subproject commit 98d7ebf6a7995a0150b4320adfe8d7131f8174cf From 8812238bf6358b267508082ff2aecfb87115ebe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 30 Sep 2012 04:03:37 +0200 Subject: [PATCH 090/196] Update license, add contributors file, bump release number --- CMakeLists.txt | 11 ++++++----- COMPILE.rst => Compile.rst | 0 LICENSE | 2 +- LUA_API.rst => LUA Api.rst | 0 README.rst => Readme.rst | 2 +- library/Core.cpp | 2 +- library/DataDefs.cpp | 2 +- library/Hooks-darwin.cpp | 2 +- library/Hooks-egg.cpp | 2 +- library/Hooks-linux.cpp | 2 +- library/Hooks-windows.cpp | 2 +- library/LuaApi.cpp | 4 ++-- library/LuaTools.cpp | 4 ++-- library/LuaTypes.cpp | 4 ++-- library/LuaWrapper.cpp | 4 ++-- library/MiscUtils.cpp | 2 +- library/PlugLoad-windows.cpp | 2 +- library/PluginManager.cpp | 2 +- library/Process-darwin.cpp | 2 +- library/Process-linux.cpp | 2 +- library/Process-windows.cpp | 2 +- library/TileTypes.cpp | 4 ++-- library/Types.cpp | 2 +- library/VTableInterpose.cpp | 2 +- library/VersionInfoFactory.cpp | 2 +- library/include/BitArray.h | 2 +- library/include/ColorText.h | 2 +- library/include/Console.h | 2 +- library/include/Core.h | 2 +- library/include/DFHack.h | 2 +- library/include/DataDefs.h | 2 +- library/include/DataFuncs.h | 2 +- library/include/DataIdentity.h | 2 +- library/include/Error.h | 2 +- library/include/Export.h | 2 +- library/include/Hooks.h | 2 +- library/include/Internal.h | 2 +- library/include/LuaTools.h | 2 +- library/include/LuaWrapper.h | 2 +- library/include/MemAccess.h | 2 +- library/include/MiscUtils.h | 2 +- library/include/Module.h | 2 +- library/include/ModuleFactory.h | 2 +- library/include/PluginManager.h | 2 +- library/include/Pragma.h | 2 +- library/include/RemoteClient.h | 2 +- library/include/RemoteServer.h | 2 +- library/include/RemoteTools.h | 2 +- library/include/TileTypes.h | 2 +- library/include/Types.h | 2 +- library/include/VTableInterpose.h | 2 +- library/include/VersionInfo.h | 2 +- library/include/VersionInfoFactory.h | 2 +- library/include/modules/Buildings.h | 2 +- library/include/modules/Burrows.h | 2 +- library/include/modules/Constructions.h | 2 +- library/include/modules/Engravings.h | 2 +- library/include/modules/Graphic.h | 2 +- library/include/modules/Gui.h | 2 +- library/include/modules/Items.h | 2 +- library/include/modules/Job.h | 2 +- library/include/modules/MapCache.h | 2 +- library/include/modules/Maps.h | 2 +- library/include/modules/Materials.h | 2 +- library/include/modules/Screen.h | 2 +- library/include/modules/Translation.h | 2 +- library/include/modules/Units.h | 2 +- library/include/modules/Vegetation.h | 2 +- library/include/modules/Windows.h | 2 +- library/include/modules/World.h | 2 +- library/include/modules/kitchen.h | 2 +- library/modules/Buildings.cpp | 2 +- library/modules/Burrows.cpp | 2 +- library/modules/Constructions.cpp | 2 +- library/modules/Engravings.cpp | 2 +- library/modules/Graphic.cpp | 2 +- library/modules/Gui.cpp | 2 +- library/modules/Items.cpp | 2 +- library/modules/Job.cpp | 2 +- library/modules/Maps.cpp | 2 +- library/modules/Materials.cpp | 2 +- library/modules/Screen.cpp | 2 +- library/modules/Translation.cpp | 2 +- library/modules/Units.cpp | 2 +- library/modules/Vegetation.cpp | 2 +- library/modules/Windows.cpp | 2 +- library/modules/World.cpp | 2 +- 87 files changed, 95 insertions(+), 94 deletions(-) rename COMPILE.rst => Compile.rst (100%) rename LUA_API.rst => LUA Api.rst (100%) rename README.rst => Readme.rst (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ee3f1d2e6..c8ec900e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,7 +63,7 @@ set(DF_VERSION_MINOR "34") set(DF_VERSION_PATCH "11") set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}") -SET(DFHACK_RELEASE "r1" CACHE STRING "Current release revision.") +SET(DFHACK_RELEASE "r2" CACHE STRING "Current release revision.") set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${DFHACK_RELEASE}") add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}") @@ -148,11 +148,12 @@ add_subdirectory(depends) find_package(Docutils) set (RST_FILES -README -COMPILE -LUA_API +"Readme" +"Compile" +"LUA Api" +"Contributors" ) - + set (RST_PROCESSED_FILES "") IF(RST2HTML_EXECUTABLE) foreach(F ${RST_FILES}) diff --git a/COMPILE.rst b/Compile.rst similarity index 100% rename from COMPILE.rst rename to Compile.rst diff --git a/LICENSE b/LICENSE index 96ab022d9..2a9a5513b 100644 --- a/LICENSE +++ b/LICENSE @@ -2,7 +2,7 @@ License of dfhack https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/LUA_API.rst b/LUA Api.rst similarity index 100% rename from LUA_API.rst rename to LUA Api.rst diff --git a/README.rst b/Readme.rst similarity index 99% rename from README.rst rename to Readme.rst index 7ca375039..b5c0f335d 100644 --- a/README.rst +++ b/Readme.rst @@ -31,7 +31,7 @@ Compatibility DFHack works on Windows XP, Vista, 7 or any modern Linux distribution. OSX is not supported due to lack of developers with a Mac. -Currently, versions 0.34.08 - 0.34.11 are supported. If you need DFHack +Currently, version 0.34.11 is supported (and tested). If you need DFHack for older versions, look for older releases. On Windows, you have to use the SDL version of DF. diff --git a/library/Core.cpp b/library/Core.cpp index 8a8d39e06..1015194ad 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index fa2aacf78..93e75e280 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/Hooks-darwin.cpp b/library/Hooks-darwin.cpp index 190616b86..31e191e1b 100644 --- a/library/Hooks-darwin.cpp +++ b/library/Hooks-darwin.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/Hooks-egg.cpp b/library/Hooks-egg.cpp index 8abd0ae8a..842573eca 100644 --- a/library/Hooks-egg.cpp +++ b/library/Hooks-egg.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/Hooks-linux.cpp b/library/Hooks-linux.cpp index ef1a765cb..31c0323f6 100644 --- a/library/Hooks-linux.cpp +++ b/library/Hooks-linux.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/Hooks-windows.cpp b/library/Hooks-windows.cpp index a586a0e85..1e6679855 100644 --- a/library/Hooks-windows.cpp +++ b/library/Hooks-windows.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index dab99f48a..352e21ccf 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1,6 +1,6 @@ -/* +/* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 8fce0076f..6bf218ba0 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1,6 +1,6 @@ -/* +/* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 9f2689fa9..877609e56 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -1,6 +1,6 @@ -/* +/* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 9f2fc2f16..3da79e932 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -1,6 +1,6 @@ -/* +/* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index b73d730c1..a05e7eba5 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/PlugLoad-windows.cpp b/library/PlugLoad-windows.cpp index 714960e64..df8397f96 100644 --- a/library/PlugLoad-windows.cpp +++ b/library/PlugLoad-windows.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index ceb644e60..0c80639b4 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 3893cfc5f..c5e4e4b85 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 1fecbab78..f88279b3f 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index db58c4d33..966468fb5 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/TileTypes.cpp b/library/TileTypes.cpp index 5564e5e3f..804d5e772 100644 --- a/library/TileTypes.cpp +++ b/library/TileTypes.cpp @@ -1,6 +1,6 @@ -/* +/* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/Types.cpp b/library/Types.cpp index 72f8bdb8c..1ba87f839 100644 --- a/library/Types.cpp +++ b/library/Types.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 48ae6109c..141450615 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index 4ac53dd1a..e8c0561cd 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/BitArray.h b/library/include/BitArray.h index ff68ea1d1..a507a38a4 100644 --- a/library/include/BitArray.h +++ b/library/include/BitArray.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/ColorText.h b/library/include/ColorText.h index 50d1f3623..006fa119d 100644 --- a/library/include/ColorText.h +++ b/library/include/ColorText.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/Console.h b/library/include/Console.h index ba59324e9..8d848135a 100644 --- a/library/include/Console.h +++ b/library/include/Console.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/Core.h b/library/include/Core.h index e1f1cf3fb..d2d7080da 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/DFHack.h b/library/include/DFHack.h index ac5d78cf0..d606df94b 100644 --- a/library/include/DFHack.h +++ b/library/include/DFHack.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 0966c7f31..6c5ae5e62 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 01a798e34..f34b9cc73 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index 21dc68d1a..0b88e0195 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/Error.h b/library/include/Error.h index 448c1a4f2..4e3ff269c 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/Export.h b/library/include/Export.h index 8e00dbaed..c3d007117 100644 --- a/library/include/Export.h +++ b/library/include/Export.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/Hooks.h b/library/include/Hooks.h index 325af43eb..7f522a33d 100644 --- a/library/include/Hooks.h +++ b/library/include/Hooks.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/Internal.h b/library/include/Internal.h index 12b7852a1..f92814cd4 100644 --- a/library/include/Internal.h +++ b/library/include/Internal.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 3330e23e7..fddbdbc26 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 9e4490220..ab694c4a8 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index a226018a6..0bc027c5c 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index bbc88a2bb..61dd69297 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/Module.h b/library/include/Module.h index 7433fce49..639edea6a 100644 --- a/library/include/Module.h +++ b/library/include/Module.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/ModuleFactory.h b/library/include/ModuleFactory.h index 276ccabb3..1f3d4222a 100644 --- a/library/include/ModuleFactory.h +++ b/library/include/ModuleFactory.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 38f0e2e50..9ef16703a 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/Pragma.h b/library/include/Pragma.h index e688810d2..b6f776725 100644 --- a/library/include/Pragma.h +++ b/library/include/Pragma.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h index 37aaea9b1..1e8264cec 100644 --- a/library/include/RemoteClient.h +++ b/library/include/RemoteClient.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index 7a6940bbe..df2f42712 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/RemoteTools.h b/library/include/RemoteTools.h index e87e8026b..e70cefc00 100644 --- a/library/include/RemoteTools.h +++ b/library/include/RemoteTools.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/TileTypes.h b/library/include/TileTypes.h index 55628b3df..d21fb3c17 100644 --- a/library/include/TileTypes.h +++ b/library/include/TileTypes.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/Types.h b/library/include/Types.h index 1f211649d..3b9bf00b6 100644 --- a/library/include/Types.h +++ b/library/include/Types.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h index aeb407a8c..b52b32270 100644 --- a/library/include/VTableInterpose.h +++ b/library/include/VTableInterpose.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/VersionInfo.h b/library/include/VersionInfo.h index 5ff20516e..2ea4170fa 100644 --- a/library/include/VersionInfo.h +++ b/library/include/VersionInfo.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/VersionInfoFactory.h b/library/include/VersionInfoFactory.h index 08b1ea76e..f69f37fee 100644 --- a/library/include/VersionInfoFactory.h +++ b/library/include/VersionInfoFactory.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 639df6865..266aadcb8 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Burrows.h b/library/include/modules/Burrows.h index e26a2dc20..49b7df3c1 100644 --- a/library/include/modules/Burrows.h +++ b/library/include/modules/Burrows.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Constructions.h b/library/include/modules/Constructions.h index f8a3e5c61..41959b2d7 100644 --- a/library/include/modules/Constructions.h +++ b/library/include/modules/Constructions.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Engravings.h b/library/include/modules/Engravings.h index ea5eae62c..bf30c62a8 100644 --- a/library/include/modules/Engravings.h +++ b/library/include/modules/Engravings.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Graphic.h b/library/include/modules/Graphic.h index 6c003b4bc..a4b2dad10 100644 --- a/library/include/modules/Graphic.h +++ b/library/include/modules/Graphic.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mr�zek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mr�zek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index b06408f68..147f27883 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 81c8e1285..367ce9b41 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 9ce72c326..490d79a34 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index 262e70bbf..ac083075f 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 869b21580..3150acccf 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index fb5a6353c..e44ca89ed 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index fdad6c8ad..a2e64a515 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Translation.h b/library/include/modules/Translation.h index f75bf6339..03521797a 100644 --- a/library/include/modules/Translation.h +++ b/library/include/modules/Translation.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 2019eb652..ab1a5f63a 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Vegetation.h b/library/include/modules/Vegetation.h index 83a192b98..89ba5ff6c 100644 --- a/library/include/modules/Vegetation.h +++ b/library/include/modules/Vegetation.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/Windows.h b/library/include/modules/Windows.h index 7dbc9f1ad..700b88851 100644 --- a/library/include/modules/Windows.h +++ b/library/include/modules/Windows.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/World.h b/library/include/modules/World.h index 8b0fe3f59..1cd57e2f2 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/include/modules/kitchen.h b/library/include/modules/kitchen.h index f9d9d3458..ca88e9163 100644 --- a/library/include/modules/kitchen.h +++ b/library/include/modules/kitchen.h @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 9e78edd3e..89106ca87 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Burrows.cpp b/library/modules/Burrows.cpp index 62f62779a..48a2ca3a8 100644 --- a/library/modules/Burrows.cpp +++ b/library/modules/Burrows.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Constructions.cpp b/library/modules/Constructions.cpp index 2d61c447a..16c1f1b89 100644 --- a/library/modules/Constructions.cpp +++ b/library/modules/Constructions.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Engravings.cpp b/library/modules/Engravings.cpp index bff17ef5f..c2e0e6fce 100644 --- a/library/modules/Engravings.cpp +++ b/library/modules/Engravings.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Graphic.cpp b/library/modules/Graphic.cpp index 2f490c233..4188df2b8 100644 --- a/library/modules/Graphic.cpp +++ b/library/modules/Graphic.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mr�zek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mr�zek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 2afb36205..b211f9f2b 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index b8c697a48..1118afcfe 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index b74a4b73f..df2009d0a 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 608068b8e..5ef4ce829 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index db9c9c7df..fbc441493 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index e9d19c931..0b9279d2d 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Translation.cpp b/library/modules/Translation.cpp index db1305161..6f4ca2b04 100644 --- a/library/modules/Translation.cpp +++ b/library/modules/Translation.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 2644f4ab2..6d71f5a15 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Vegetation.cpp b/library/modules/Vegetation.cpp index a76c3a2cd..1a6779553 100644 --- a/library/modules/Vegetation.cpp +++ b/library/modules/Vegetation.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/Windows.cpp b/library/modules/Windows.cpp index 218b6f7e0..4e749322f 100644 --- a/library/modules/Windows.cpp +++ b/library/modules/Windows.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 67b8c1236..a4c4c5604 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -1,6 +1,6 @@ /* https://github.com/peterix/dfhack -Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) +Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any From dd9be6cc7c81a3d577f6283a798c78b522013d9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 30 Sep 2012 04:16:54 +0200 Subject: [PATCH 091/196] The usual derp with files not added: Contributors.rst --- CMakeLists.txt | 2 +- Contributors.rst | 62 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 Contributors.rst diff --git a/CMakeLists.txt b/CMakeLists.txt index c8ec900e4..8e69111dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,7 +153,7 @@ set (RST_FILES "LUA Api" "Contributors" ) - + set (RST_PROCESSED_FILES "") IF(RST2HTML_EXECUTABLE) foreach(F ${RST_FILES}) diff --git a/Contributors.rst b/Contributors.rst new file mode 100644 index 000000000..112a847e7 --- /dev/null +++ b/Contributors.rst @@ -0,0 +1,62 @@ +Contributors +============ +The following is a list of people who have contributed to **DFHack**. +If you belong here and are missing please add yourself and send me (peterix) a pull request :-) + +- Petr Mrázek +- Alexander Gavrilov +- doomchild +- Quietust +- jj +- Warmist +- Robert Heinrich +- simon +- Kelly Martin +- mizipzor +- Simon Jackson +- belal +- RusAnon +- Raoul XQ +- Matthew Cline +- Mike Stewart +- Timothy Collett +- RossM +- Tom Prince +- Jared Adams +- expwnent +- Erik Youngren +- Espen Wiborg +- Tim Walberg +- Mikko Juola +- rampaging-poet +- U-glouglou\\simon +- Clayton Hughes +- zilpin +- Will Rogers +- NMLittle +- root +- reverb +- Zhentar +- Valentin Ochs +- Priit Laes +- kmartin +- Neil Little +- rout +- rofl0r +- harlanplayford +- gsvslto +- sami +- potato +- playfordh +- feng1st +- comestible +- Rumrusher +- Rinin +- Raoul van Putten +- John Shade +- John Beisley +- Feng +- Donald Ruegsegger +- Caldfir +- Antalia +- Angus Mezick \ No newline at end of file From e2d810ddc2bbedc1dcdc154b299f0dee7452f8aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 30 Sep 2012 06:44:00 +0200 Subject: [PATCH 092/196] Put HTML back. --- CMakeLists.txt | 40 +- Compile.html | 554 +++++++++ Contributors.html | 391 +++++++ LUA Api.html | 2104 +++++++++++++++++++++++++++++++++ Readme.html | 2808 +++++++++++++++++++++++++++++++++++++++++++++ fixTexts.sh | 6 + testTexts.sh | 5 - 7 files changed, 5883 insertions(+), 25 deletions(-) create mode 100644 Compile.html create mode 100644 Contributors.html create mode 100644 LUA Api.html create mode 100644 Readme.html create mode 100755 fixTexts.sh delete mode 100755 testTexts.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e69111dd..c1ced670c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -147,31 +147,31 @@ add_subdirectory(depends) find_package(Docutils) -set (RST_FILES -"Readme" -"Compile" -"LUA Api" -"Contributors" -) - -set (RST_PROCESSED_FILES "") -IF(RST2HTML_EXECUTABLE) - foreach(F ${RST_FILES}) - add_custom_command( - OUTPUT "${dfhack_BINARY_DIR}/${F}.html" - COMMAND ${RST2HTML_EXECUTABLE} "${dfhack_SOURCE_DIR}/${F}.rst" > "${dfhack_BINARY_DIR}/${F}.html" - COMMENT "Translating ${F} to html" - DEPENDS "${dfhack_SOURCE_DIR}/${F}.rst") - list (APPEND RST_PROCESSED_FILES "${dfhack_BINARY_DIR}/${F}.html") - endforeach() - add_custom_target(HTML_DOCS ALL DEPENDS ${RST_PROCESSED_FILES}) -ENDIF() +#set (RST_FILES +#"Readme" +#"Compile" +#"LUA Api" +#"Contributors" +#) + +#set (RST_PROCESSED_FILES "") +#IF(RST2HTML_EXECUTABLE) +# foreach(F ${RST_FILES}) +# add_custom_command( +# OUTPUT "${dfhack_BINARY_DIR}/${F}.html" +# COMMAND ${RST2HTML_EXECUTABLE} "${dfhack_SOURCE_DIR}/${F}.rst" "${dfhack_BINARY_DIR}/${F}.html" +# COMMENT "Translating ${F} to html" +# DEPENDS "${dfhack_SOURCE_DIR}/${F}.rst") +# list (APPEND RST_PROCESSED_FILES "${dfhack_BINARY_DIR}/${F}.html") +# endforeach() +# add_custom_target(HTML_DOCS ALL DEPENDS ${RST_PROCESSED_FILES}) +#ENDIF() # build the lib itself IF(BUILD_LIBRARY) add_subdirectory (library) ## install the default documentation files - install(FILES LICENSE ${RST_PROCESSED_FILES} DESTINATION ${DFHACK_USERDOC_DESTINATION}) + install(FILES LICENSE "LUA Api.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) endif() #build the plugins diff --git a/Compile.html b/Compile.html new file mode 100644 index 000000000..41d409112 --- /dev/null +++ b/Compile.html @@ -0,0 +1,554 @@ + + + + + + +Building DFHACK + + + +
    +

    Building DFHACK

    + + +
    +

    Linux

    +

    On Linux, DFHack acts as a library that shadows parts of the SDL API using LD_PRELOAD.

    +
    +

    How to get the code

    +

    DFHack doesn't have any kind of system of code snapshots in place, so you will have to get code from the github repository using git. +Having a 'git' package installed is the minimal requirement, but some sort of git gui or git integration for your favorite text editor/IDE will certainly help.

    +

    The code resides here: https://github.com/peterix/dfhack

    +

    If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address:

    +
    +git clone git://github.com/peterix/dfhack.git
    +cd dfhack
    +git submodule init
    +git submodule update
    +
    +

    If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode).

    +
    +
    +

    Dependencies

    +

    DFHack is meant to be installed into an existing DF folder, so get one ready.

    +

    For building, you need a 32-bit version of GCC. For example, to build DFHack on +a 64-bit distribution like Arch, you'll need the multilib development tools and libraries.

    +

    Before you can build anything, you'll also need cmake. It is advisable to also get +ccmake on distributions that split the cmake package into multiple parts.

    +

    For the code generation parts, you need perl and the XML::LibXML and XML::LibXSLT perl packages. +You should be able to find them in your distro repositories (on Arch linux 'perl-xml-libxml' and 'perl-xml-libxslt').

    +
    +
    +

    Build

    +

    Building is fairly straightforward. Enter the build folder and start the build like this:

    +
    +cd build
    +cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/home/user/DF
    +make install
    +
    +

    Obviously, replace the install path with path to your DF. This will build the library +along with the normal set of plugins and install them into your DF folder.

    +

    Alternatively, you can use ccmake instead of cmake:

    +
    +cd build
    +ccmake ..
    +make install
    +
    +

    This will show a curses-based interface that lets you set all of the +extra options.

    +

    You can also use a cmake-friendly IDE like KDevelop 4 or the cmake-gui +program.

    +
    +
    +
    +

    Windows

    +

    On Windows, DFHack replaces the SDL library distributed with DF.

    +
    +

    How to get the code

    +

    DFHack doesn't have any kind of system of code snapshots in place, so you will have to get code from the github repository using git. +You will need some sort of Windows port of git, or a GUI. Some examples:

    +
    + +
    +

    The code resides here: https://github.com/peterix/dfhack

    +

    If you just want to compile DFHack or work on it by contributing patches, it's quite enough to clone from the read-only address:

    +
    +git clone git://github.com/peterix/dfhack.git
    +cd dfhack
    +git submodule init
    +git submodule update
    +
    +

    The tortoisegit GUI should have the equivalent options included.

    +

    If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode).

    +
    +
    +

    Dependencies

    +

    First, you need cmake. Get the win32 installer version from the official +site: http://www.cmake.org/cmake/resources/software.html

    +

    It has the usual installer wizard. Make sure you let it add its binary folder +to your binary search PATH so the tool can be later run from anywhere.

    +

    You'll need a copy of Microsoft Visual C++ 2010. The Express version is sufficient. +Grab it from Microsoft's site.

    +

    For the code generation parts, you'll need perl and XML::LibXML. You can install them like this:

    +
      +
    • download and install strawberry perl from http://strawberryperl.com/
    • +
    • reboot so that the system can pick up the new binary path
    • +
    • open a cmd.exe window and run "cpan XML::LibXML" (obviously without the quotes). This can take a while to complete.
    • +
    • Same with "cpan XML::LibXSLT".
    • +
    +

    If you already have a different version of perl (for example the one from cygwin), you can run into some trouble. Either remove the other perl install from PATH, or install libxml and libxslt for it instead. Strawberry perl works though and has all the required packages.

    +
    +
    +

    Build

    +

    There are several different batch files in the build folder along with a script that's used for picking the DF path.

    +

    First, run set_df_path.vbs and point the dialog that pops up at your DF folder that you want to use for development. +Next, run one of the scripts with generate prefix. These create the MSVC solution file(s):

    +
      +
    • all will create a solution with everything enabled (and the kitchen sink).
    • +
    • gui will pop up the cmake gui and let you pick and choose what to build. This is probably what you want most of the time. Set the options you are interested in, then hit configure, then generate. More options can appear after the configure step.
    • +
    • minimal will create a minimal solution with just the bare necessities - the main library and standard plugins.
    • +
    +

    Then you can either open the solution with MSVC or use one of the msbuild scripts:

    +
      +
    • Scripts with build prefix will only build.
    • +
    • Scripts with install prefix will build DFHack and install it to the previously selected DF path.
    • +
    • Scripts with package prefix will build and create a .zip package of DFHack.
    • +
    +

    When you open the solution in MSVC, make sure you never use the Debug builds. Those aren't +binary-compatible with DF. If you try to use a debug build with DF, you'll only get crashes. +So pick either Release or RelWithDebInfo build and build the INSTALL target.

    +

    The debug scripts actually do RelWithDebInfo builds.

    +
    +
    +
    +

    Build types

    +

    cmake allows you to pick a build type by changing this +variable: CMAKE_BUILD_TYPE

    +
    +cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE
    +
    +

    Without specifying a build type or 'None', cmake uses the +CMAKE_CXX_FLAGS variable for building.

    +

    Valid and useful build types include 'Release', 'Debug' and +'RelWithDebInfo'. 'Debug' is not available on Windows.

    +
    +
    +

    Using the library as a developer

    +

    Currently, the most direct way to use the library is to write a plugin that can be loaded by it. +All the plugins can be found in the 'plugins' folder. There's no in-depth documentation +on how to write one yet, but it should be easy enough to copy one and just follow the pattern.

    +

    Other than through plugins, it is possible to use DFHack via remote access interface, or by writing Lua scripts.

    +

    The most important parts of DFHack are the Core, Console, Modules and Plugins.

    +
      +
    • Core acts as the centerpiece of DFHack - it acts as a filter between DF and SDL and synchronizes the various plugins with DF.
    • +
    • Console is a thread-safe console that can be used to invoke commands exported by Plugins.
    • +
    • Modules actually describe the way to access information in DF's memory. You can get them from the Core. Most modules are split into two parts: high-level and low-level. Higl-level is mostly method calls, low-level publicly visible pointers to DF's data structures.
    • +
    • Plugins are the tools that use all the other stuff to make things happen. A plugin can have a list of commands that it exports and an onupdate function that will be called each DF game tick.
    • +
    +

    Rudimentary API documentation can be built using doxygen (see build options with ccmake or cmake-gui).

    +

    DFHack consists of variously licensed code, but invariably weak copyleft. +The main license is zlib/libpng, some bits are MIT licensed, and some are BSD licensed.

    +

    Feel free to add your own extensions and plugins. Contributing back to +the dfhack repository is welcome and the right thing to do :)

    +
    +

    DF data structure definitions

    +

    DFHack uses information about the game data structures, represented via xml files in the library/xml/ submodule.

    +

    Data structure layouts are described in files following the df.*.xml name pattern. This information is transformed by a perl script into C++ headers describing the structures, and associated metadata for the Lua wrapper. These headers and data are then compiled into the DFHack libraries, thus necessitating a compatibility break every time layouts change; in return it significantly boosts the efficiency and capabilities of DFHack code.

    +

    Global object addresses are stored in symbols.xml, which is copied to the dfhack release package and loaded as data at runtime.

    +
    +
    +

    Remote access interface

    +

    DFHack supports remote access by exchanging Google protobuf messages via a TCP socket. Both the core and plugins can define remotely accessible methods. The dfhack-run command uses this interface to invoke ordinary console commands.

    +

    Currently the supported set of requests is limited, because the developers don't know what exactly is most useful.

    +

    Protocol client implementations exist for Java and C#.

    +
    +
    +

    Contributing to DFHack

    +

    Several things should be kept in mind when contributing to DFHack.

    +
    +

    Coding style

    +

    DFhack uses ANSI formatting and four spaces as indentation. Line +endings are UNIX. The files use UTF-8 encoding. Code not following this +won't make me happy, because I'll have to fix it. There's a good chance +I'll make you fix it ;)

    +
    +
    +

    How to get new code into DFHack

    +

    You can send patches or make a clone of the github repo and ask me on +the IRC channel to pull your code in. I'll review it and see if there +are any problems. I'll fix them if they are minor.

    +

    Fixes are higher in priority. If you want to work on something, but +don't know what, check out http://github.com/peterix/dfhack/issues -- +this is also a good place to dump new ideas and/or bugs that need +fixing.

    +
    +
    +

    Memory research

    +

    If you want to do memory research, you'll need some tools and some knowledge. +In general, you'll need a good memory viewer and optionally something +to look at machine code without getting crazy :)

    +

    Good windows tools include:

    +
      +
    • Cheat Engine
    • +
    • IDA Pro (the free version)
    • +
    +

    Good linux tools:

    +
      +
    • angavrilov's df-structures gui (visit us on IRC for details).
    • +
    • edb (Evan's Debugger)
    • +
    • IDA Pro running under wine.
    • +
    • Some of the tools residing in the legacy dfhack branch.
    • +
    +

    Using publicly known information and analyzing the game's data is preferred.

    +
    +
    +
    +
    + + diff --git a/Contributors.html b/Contributors.html new file mode 100644 index 000000000..72cb8bdfc --- /dev/null +++ b/Contributors.html @@ -0,0 +1,391 @@ + + + + + + +Contributors + + + +
    +

    Contributors

    + +

    The following is a list of people who have contributed to DFHack. +If you belong here and are missing please add yourself and send me (peterix) a pull request :-)

    + +
    + + diff --git a/LUA Api.html b/LUA Api.html new file mode 100644 index 000000000..047ef9786 --- /dev/null +++ b/LUA Api.html @@ -0,0 +1,2104 @@ + + + + + + +DFHack Lua API + + + +
    +

    DFHack Lua API

    + + +

    The current version of DFHack has extensive support for +the Lua scripting language, providing access to:

    +
      +
    1. Raw data structures used by the game.
    2. +
    3. Many C++ functions for high-level access to these +structures, and interaction with dfhack itself.
    4. +
    5. Some functions exported by C++ plugins.
    6. +
    +

    Lua code can be used both for writing scripts, which +are treated by DFHack command line prompt almost as +native C++ commands, and invoked by plugins written in c++.

    +

    This document describes native API available to Lua in detail. +It does not describe all of the utility functions +implemented by Lua files located in hack/lua/...

    +
    +

    DF data structure wrapper

    +

    DF structures described by the xml files in library/xml are exported +to lua code as a tree of objects and functions under the df global, +which broadly maps to the df namespace in C++.

    +

    WARNING: The wrapper provides almost raw access to the memory +of the game, so mistakes in manipulating objects are as likely to +crash the game as equivalent plain C++ code would be. E.g. NULL +pointer access is safely detected, but dangling pointers aren't.

    +

    Objects managed by the wrapper can be broadly classified into the following groups:

    +
      +
    1. Typed object pointers (references).

      +

      References represent objects in DF memory with a known type.

      +

      In addition to fields and methods defined by the wrapped type, +every reference has some built-in properties and methods.

      +
    2. +
    3. Untyped pointers

      +

      Represented as lightuserdata.

      +

      In assignment to a pointer NULL can be represented either as +nil, or a NULL lightuserdata; reading a NULL pointer field +returns nil.

      +
    4. +
    5. Named types

      +

      Objects in the df tree that represent identity of struct, class, +enum and bitfield types. They host nested named types, static +methods, builtin properties & methods, and, for enums and bitfields, +the bi-directional mapping between key names and values.

      +
    6. +
    7. The global object

      +

      df.global corresponds to the df::global namespace, and +behaves as a mix between a named type and a reference, containing +both nested types and fields corresponding to global symbols.

      +
    8. +
    +

    In addition to the global object and top-level types the df +global also contains a few global builtin utility functions.

    +
    +

    Typed object references

    +

    The underlying primitive lua object is userdata with a metatable. +Every structured field access produces a new userdata instance.

    +

    All typed objects have the following built-in features:

    +
      +
    • ref1 == ref2, tostring(ref)

      +

      References implement equality by type & pointer value, and string conversion.

      +
    • +
    • pairs(ref)

      +

      Returns an iterator for the sequence of actual C++ field names +and values. Fields are enumerated in memory order. Methods and +lua wrapper properties are not included in the iteration.

      +

      WARNING: a few of the data structures (like ui_look_list) +contain unions with pointers to different types with vtables. +Using pairs on such structs is an almost sure way to crash with +an access violation.

      +
    • +
    • ref._kind

      +

      Returns one of: primitive, struct, container, +or bitfield, as appropriate for the referenced object.

      +
    • +
    • ref._type

      +

      Returns the named type object or a string that represents +the referenced object type.

      +
    • +
    • ref:sizeof()

      +

      Returns size, address

      +
    • +
    • ref:new()

      +

      Allocates a new instance of the same type, and copies data +from the current object.

      +
    • +
    • ref:delete()

      +

      Destroys the object with the C++ delete operator. +If destructor is not available, returns false.

      +

      WARNING: the lua reference object remains as a dangling +pointer, like a raw C++ pointer would.

      +
    • +
    • ref:assign(object)

      +

      Assigns data from object to ref. Object must either be another +ref of a compatible type, or a lua table; in the latter case +special recursive assignment rules are applied.

      +
    • +
    • ref:_displace(index[,step])

      +

      Returns a new reference with the pointer adjusted by index*step. +Step defaults to the natural object size.

      +
    • +
    +
    +

    Primitive references

    +

    References of the _kind 'primitive' are used for objects +that don't fit any of the other reference types. Such +references can only appear as a value of a pointer field, +or as a result of calling the _field() method.

    +

    They behave as structs with one field value of the right type.

    +

    To make working with numeric buffers easier, they also allow +numeric indices. Note that other than excluding negative values +no bound checking is performed, since buffer length is not available. +Index 0 is equivalent to the value field.

    +
    +
    +

    Struct references

    +

    Struct references are used for class and struct objects.

    +

    They implement the following features:

    +
      +
    • ref.field, ref.field = value

      +

      Valid fields of the structure may be accessed by subscript.

      +

      Primitive typed fields, i.e. numbers & strings, are converted +to/from matching lua values. The value of a pointer is a reference +to the target, or nil/NULL. Complex types are represented by +a reference to the field within the structure; unless recursive +lua table assignment is used, such fields can only be read.

      +

      NOTE: In case of inheritance, superclass fields have precedence +over the subclass, but fields shadowed in this way can still +be accessed as ref['subclasstype.field']. +This shadowing order is necessary because vtable-based classes +are automatically exposed in their exact type, and the reverse +rule would make access to superclass fields unreliable.

      +
    • +
    • ref._field(field)

      +

      Returns a reference to a valid field. That is, unlike regular +subscript, it returns a reference to the field within the structure +even for primitive typed fields and pointers.

      +
    • +
    • ref:vmethod(args...)

      +

      Named virtual methods are also exposed, subject to the same +shadowing rules.

      +
    • +
    • pairs(ref)

      +

      Enumerates all real fields (but not methods) in memory +(= declaration) order.

      +
    • +
    +
    +
    +

    Container references

    +

    Containers represent vectors and arrays, possibly resizable.

    +

    A container field can associate an enum to the container +reference, which allows accessing elements using string keys +instead of numerical indices.

    +

    Implemented features:

    +
      +
    • ref._enum

      +

      If the container has an associated enum, returns the matching +named type object.

      +
    • +
    • #ref

      +

      Returns the length of the container.

      +
    • +
    • ref[index]

      +

      Accesses the container element, using either a 0-based numerical +index, or, if an enum is associated, a valid enum key string.

      +

      Accessing an invalid index is an error, but some container types +may return a default value, or auto-resize instead for convenience. +Currently this relaxed mode is implemented by df-flagarray aka BitArray.

      +
    • +
    • ref._field(index)

      +

      Like with structs, returns a pointer to the array element, if possible. +Flag and bit arrays cannot return such pointer, so it fails with an error.

      +
    • +
    • pairs(ref), ipairs(ref)

      +

      If the container has no associated enum, both behave identically, +iterating over numerical indices in order. Otherwise, ipairs still +uses numbers, while pairs tries to substitute enum keys whenever +possible.

      +
    • +
    • ref:resize(new_size)

      +

      Resizes the container if supported, or fails with an error.

      +
    • +
    • ref:insert(index,item)

      +

      Inserts a new item at the specified index. To add at the end, +use #ref, or just '#' as index.

      +
    • +
    • ref:erase(index)

      +

      Removes the element at the given valid index.

      +
    • +
    +
    +
    +

    Bitfield references

    +

    Bitfields behave like special fixed-size containers. +Consider them to be something in between structs and +fixed-size vectors.

    +

    The _enum property points to the bitfield type. +Numerical indices correspond to the shift value, +and if a subfield occupies multiple bits, the +ipairs order would have a gap.

    +

    Since currently there is no API to allocate a bitfield +object fully in GC-managed lua heap, consider using the +lua table assignment feature outlined below in order to +pass bitfield values to dfhack API functions that need +them, e.g. matinfo:matches{metal=true}.

    +
    +
    +
    +

    Named types

    +

    Named types are exposed in the df tree with names identical +to the C++ version, except for the :: vs . difference.

    +

    All types and the global object have the following features:

    +
      +
    • type._kind

      +

      Evaluates to one of struct-type, class-type, enum-type, +bitfield-type or global.

      +
    • +
    • type._identity

      +

      Contains a lightuserdata pointing to the underlying +DFHack::type_instance object.

      +
    • +
    +

    Types excluding the global object also support:

    +
      +
    • type:sizeof()

      +

      Returns the size of an object of the type.

      +
    • +
    • type:new()

      +

      Creates a new instance of an object of the type.

      +
    • +
    • type:is_instance(object)

      +

      Returns true if object is same or subclass type, or a reference +to an object of same or subclass type. It is permissible to pass +nil, NULL or non-wrapper value as object; in this case the +method returns nil.

      +
    • +
    +

    In addition to this, enum and bitfield types contain a +bi-directional mapping between key strings and values, and +also map _first_item and _last_item to the min and +max values.

    +

    Struct and class types with instance-vector attribute in the +xml have a type.find(key) function that wraps the find +method provided in C++.

    +
    +
    +

    Global functions

    +

    The df table itself contains the following functions and values:

    +
      +
    • NULL, df.NULL

      +

      Contains the NULL lightuserdata.

      +
    • +
    • df.isnull(obj)

      +

      Evaluates to true if obj is nil or NULL; false otherwise.

      +
    • +
    • df.isvalid(obj[,allow_null])

      +

      For supported objects returns one of type, voidptr, ref.

      +

      If allow_null is true, and obj is nil or NULL, returns null.

      +

      Otherwise returns nil.

      +
    • +
    • df.sizeof(obj)

      +

      For types and refs identical to obj:sizeof(). +For lightuserdata returns nil, address

      +
    • +
    • df.new(obj), df.delete(obj), df.assign(obj, obj2)

      +

      Equivalent to using the matching methods of obj.

      +
    • +
    • df._displace(obj,index[,step])

      +

      For refs equivalent to the method, but also works with +lightuserdata (step is mandatory then).

      +
    • +
    • df.is_instance(type,obj)

      +

      Equivalent to the method, but also allows a reference as proxy for its type.

      +
    • +
    • df.new(ptype[,count])

      +

      Allocate a new instance, or an array of built-in types. +The ptype argument is a string from the following list: +string, int8_t, uint8_t, int16_t, uint16_t, +int32_t, uint32_t, int64_t, uint64_t, bool, +float, double. All of these except string can be +used with the count argument to allocate an array.

      +
    • +
    • df.reinterpret_cast(type,ptr)

      +

      Converts ptr to a ref of specified type. The type may be anything +acceptable to df.is_instance. Ptr may be nil, a ref, +a lightuserdata, or a number.

      +

      Returns nil if NULL, or a ref.

      +
    • +
    +
    +
    +

    Recursive table assignment

    +

    Recursive assignment is invoked when a lua table is assigned +to a C++ object or field, i.e. one of:

    +
      +
    • ref:assign{...}
    • +
    • ref.field = {...}
    • +
    +

    The general mode of operation is that all fields of the table +are assigned to the fields of the target structure, roughly +emulating the following code:

    +
    +function rec_assign(ref,table)
    +    for key,value in pairs(table) do
    +        ref[key] = value
    +    end
    +end
    +
    +

    Since assigning a table to a field using = invokes the same +process, it is recursive.

    +

    There are however some variations to this process depending +on the type of the field being assigned to:

    +
      +
    1. If the table contains an assign field, it is +applied first, using the ref:assign(value) method. +It is never assigned as a usual field.

      +
    2. +
    3. When a table is assigned to a non-NULL pointer field +using the ref.field = {...} syntax, it is applied +to the target of the pointer instead.

      +

      If the pointer is NULL, the table is checked for a new field:

      +
        +
      1. If it is nil or false, assignment fails with an error.
      2. +
      3. If it is true, the pointer is initialized with a newly +allocated object of the declared target type of the pointer.
      4. +
      5. Otherwise, table.new must be a named type, or an +object of a type compatible with the pointer. The pointer +is initialized with the result of calling table.new:new().
      6. +
      +

      After this auto-vivification process, assignment proceeds +as if the pointer wasn't NULL.

      +

      Obviously, the new field inside the table is always skipped +during the actual per-field assignment processing.

      +
    4. +
    5. If the target of the assignment is a container, a separate +rule set is used:

      +
        +
      1. If the table contains neither assign nor resize +fields, it is interpreted as an ordinary 1-based lua +array. The container is resized to the #-size of the +table, and elements are assigned in numeric order:

        +
        +ref:resize(#table);
        +for i=1,#table do ref[i-1] = table[i] end
        +
        +
      2. +
      3. Otherwise, resize must be true, false, or +an explicit number. If it is not false, the container +is resized. After that the usual struct-like 'pairs' +assignment is performed.

        +

        In case resize is true, the size is computed +by scanning the table for the largest numeric key.

        +
      4. +
      +

      This means that in order to reassign only one element of +a container using this system, it is necessary to use:

      +
      +{ resize=false, [idx]=value }
      +
      +
    6. +
    +

    Since nil inside a table is indistinguishable from missing key, +it is necessary to use df.NULL as a null pointer value.

    +

    This system is intended as a way to define a nested object +tree using pure lua data structures, and then materialize it in +C++ memory in one go. Note that if pointer auto-vivification +is used, an error in the middle of the recursive walk would +not destroy any objects allocated in this way, so the user +should be prepared to catch the error and do the necessary +cleanup.

    +
    +
    +
    +

    DFHack API

    +

    DFHack utility functions are placed in the dfhack global tree.

    +
    +

    Native utilities

    +
    +

    Input & Output

    +
      +
    • dfhack.print(args...)

      +

      Output tab-separated args as standard lua print would do, +but without a newline.

      +
    • +
    • print(args...), dfhack.println(args...)

      +

      A replacement of the standard library print function that +works with DFHack output infrastructure.

      +
    • +
    • dfhack.printerr(args...)

      +

      Same as println; intended for errors. Uses red color and logs to stderr.log.

      +
    • +
    • dfhack.color([color])

      +

      Sets the current output color. If color is nil or -1, resets to default. +Returns the previous color value.

      +
    • +
    • dfhack.is_interactive()

      +

      Checks if the thread can access the interactive console and returns true or false.

      +
    • +
    • dfhack.lineedit([prompt[,history_filename]])

      +

      If the thread owns the interactive console, shows a prompt +and returns the entered string. Otherwise returns nil, error.

      +

      Depending on the context, this function may actually yield the +running coroutine and let the C++ code release the core suspend +lock. Using an explicit dfhack.with_suspend will prevent +this, forcing the function to block on input with lock held.

      +
    • +
    • dfhack.interpreter([prompt[,history_filename[,env]]])

      +

      Starts an interactive lua interpreter, using the specified prompt +string, global environment and command-line history file.

      +

      If the interactive console is not accessible, returns nil, error.

      +
    • +
    +
    +
    +

    Exception handling

    +
      +
    • dfhack.error(msg[,level[,verbose]])

      +

      Throws a dfhack exception object with location and stack trace. +The verbose parameter controls whether the trace is printed by default.

      +
    • +
    • qerror(msg[,level])

      +

      Calls dfhack.error() with verbose being false. Intended to +be used for user-caused errors in scripts, where stack traces are not +desirable.

      +
    • +
    • dfhack.pcall(f[,args...])

      +

      Invokes f via xpcall, using an error function that attaches +a stack trace to the error. The same function is used by SafeCall +in C++, and dfhack.safecall.

      +
    • +
    • safecall(f[,args...]), dfhack.safecall(f[,args...])

      +

      Just like pcall, but also prints the error using printerr before +returning. Intended as a convenience function.

      +
    • +
    • dfhack.saferesume(coroutine[,args...])

      +

      Compares to coroutine.resume like dfhack.safecall vs pcall.

      +
    • +
    • dfhack.exception

      +

      Metatable of error objects used by dfhack. The objects have the +following properties:

      +
      +
      err.where
      +

      The location prefix string, or nil.

      +
      +
      err.message
      +

      The base message string.

      +
      +
      err.stacktrace
      +

      The stack trace string, or nil.

      +
      +
      err.cause
      +

      A different exception object, or nil.

      +
      +
      err.thread
      +

      The coroutine that has thrown the exception.

      +
      +
      err.verbose
      +

      Boolean, or nil; specifies if where and stacktrace should be printed.

      +
      +
      tostring(err), or err:tostring([verbose])
      +

      Converts the exception to string.

      +
      +
      +
    • +
    • dfhack.exception.verbose

      +

      The default value of the verbose argument of err:tostring().

      +
    • +
    +
    +
    +

    Miscellaneous

    +
      +
    • dfhack.VERSION

      +

      DFHack version string constant.

      +
    • +
    • dfhack.curry(func,args...), or curry(func,args...)

      +

      Returns a closure that invokes the function with args combined +both from the curry call and the closure call itself. I.e. +curry(func,a,b)(c,d) equals func(a,b,c,d).

      +
    • +
    +
    +
    +

    Locking and finalization

    +
      +
    • dfhack.with_suspend(f[,args...])

      +

      Calls f with arguments after grabbing the DF core suspend lock. +Suspending is necessary for accessing a consistent state of DF memory.

      +

      Returned values and errors are propagated through after releasing +the lock. It is safe to nest suspends.

      +

      Every thread is allowed only one suspend per DF frame, so it is best +to group operations together in one big critical section. A plugin +can choose to run all lua code inside a C++-side suspend lock.

      +
    • +
    • dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])

      +

      Invokes fn with args, and after it returns or throws an +error calls cleanup_fn with cleanup_args. Any return values from +fn are propagated, and errors are re-thrown.

      +

      The num_cleanup_args integer specifies the number of cleanup_args, +and the always boolean specifies if cleanup should be called in any case, +or only in case of an error.

      +
    • +
    • dfhack.with_finalize(cleanup_fn,fn[,args...])

      +

      Calls fn with arguments, then finalizes with cleanup_fn. +Implemented using call_with_finalizer(0,true,...).

      +
    • +
    • dfhack.with_onerror(cleanup_fn,fn[,args...])

      +

      Calls fn with arguments, then finalizes with cleanup_fn on any thrown error. +Implemented using call_with_finalizer(0,false,...).

      +
    • +
    • dfhack.with_temp_object(obj,fn[,args...])

      +

      Calls fn(obj,args...), then finalizes with obj:delete().

      +
    • +
    +
    +
    +

    Persistent configuration storage

    +

    This api is intended for storing configuration options in the world itself. +It probably should be restricted to data that is world-dependent.

    +

    Entries are identified by a string key, but it is also possible to manage +multiple entries with the same key; their identity is determined by entry_id. +Every entry has a mutable string value, and an array of 7 mutable ints.

    +
      +
    • dfhack.persistent.get(key), entry:get()

      +

      Retrieves a persistent config record with the given string key, +or refreshes an already retrieved entry. If there are multiple +entries with the same key, it is undefined which one is retrieved +by the first version of the call.

      +

      Returns entry, or nil if not found.

      +
    • +
    • dfhack.persistent.delete(key), entry:delete()

      +

      Removes an existing entry. Returns true if succeeded.

      +
    • +
    • dfhack.persistent.get_all(key[,match_prefix])

      +

      Retrieves all entries with the same key, or starting with key..'/'. +Calling get_all('',true) will match all entries.

      +

      If none found, returns nil; otherwise returns an array of entries.

      +
    • +
    • dfhack.persistent.save({key=str1, ...}[,new]), entry:save([new])

      +

      Saves changes in an entry, or creates a new one. Passing true as +new forces creation of a new entry even if one already exists; +otherwise the existing one is simply updated. +Returns entry, did_create_new

      +
    • +
    +

    Since the data is hidden in data structures owned by the DF world, +and automatically stored in the save game, these save and retrieval +functions can just copy values in memory without doing any actual I/O. +However, currently every entry has a 180+-byte dead-weight overhead.

    +
    +
    +

    Material info lookup

    +

    A material info record has fields:

    +
      +
    • type, index, material

      +

      DF material code pair, and a reference to the material object.

      +
    • +
    • mode

      +

      One of 'builtin', 'inorganic', 'plant', 'creature'.

      +
    • +
    • inorganic, plant, creature

      +

      If the material is of the matching type, contains a reference to the raw object.

      +
    • +
    • figure

      +

      For a specific creature material contains a ref to the historical figure.

      +
    • +
    +

    Functions:

    +
      +
    • dfhack.matinfo.decode(type,index)

      +

      Looks up material info for the given number pair; if not found, returs nil.

      +
    • +
    • ....decode(matinfo), ....decode(item), ....decode(obj)

      +

      Uses matinfo.type/matinfo.index, item getter vmethods, +or obj.mat_type/obj.mat_index to get the code pair.

      +
    • +
    • dfhack.matinfo.find(token[,token...])

      +

      Looks up material by a token string, or a pre-split string token sequence.

      +
    • +
    • dfhack.matinfo.getToken(...), info:getToken()

      +

      Applies decode and constructs a string token.

      +
    • +
    • info:toString([temperature[,named]])

      +

      Returns the human-readable name at the given temperature.

      +
    • +
    • info:getCraftClass()

      +

      Returns the classification used for craft skills.

      +
    • +
    • info:matches(obj)

      +

      Checks if the material matches job_material_category or job_item. +Accept dfhack_material_category auto-assign table.

      +
    • +
    +
    +
    +
    +

    C++ function wrappers

    +

    Thin wrappers around C++ functions, similar to the ones for virtual methods. +One notable difference is that these explicit wrappers allow argument count +adjustment according to the usual lua rules, so trailing false/nil arguments +can be omitted.

    +
      +
    • dfhack.getOSType()

      +

      Returns the OS type string from symbols.xml.

      +
    • +
    • dfhack.getDFVersion()

      +

      Returns the DF version string from symbols.xml.

      +
    • +
    • dfhack.getDFPath()

      +

      Returns the DF directory path.

      +
    • +
    • dfhack.getHackPath()

      +

      Returns the dfhack directory path, i.e. ".../df/hack/".

      +
    • +
    • dfhack.getTickCount()

      +

      Returns the tick count in ms, exactly as DF ui uses.

      +
    • +
    • dfhack.isWorldLoaded()

      +

      Checks if the world is loaded.

      +
    • +
    • dfhack.isMapLoaded()

      +

      Checks if the world and map are loaded.

      +
    • +
    • dfhack.TranslateName(name[,in_english,only_last_name])

      +

      Convert a language_name or only the last name part to string.

      +
    • +
    +
    +

    Gui module

    +
      +
    • dfhack.gui.getCurViewscreen([skip_dismissed])

      +

      Returns the topmost viewscreen. If skip_dismissed is true, +ignores screens already marked to be removed.

      +
    • +
    • dfhack.gui.getFocusString(viewscreen)

      +

      Returns a string representation of the current focus position +in the ui. The string has a "screen/foo/bar/baz..." format.

      +
    • +
    • dfhack.gui.getCurFocus([skip_dismissed])

      +

      Returns the focus string of the current viewscreen.

      +
    • +
    • dfhack.gui.getSelectedWorkshopJob([silent])

      +

      When a job is selected in 'q' mode, returns the job, else +prints error unless silent and returns nil.

      +
    • +
    • dfhack.gui.getSelectedJob([silent])

      +

      Returns the job selected in a workshop or unit/jobs screen.

      +
    • +
    • dfhack.gui.getSelectedUnit([silent])

      +

      Returns the unit selected via 'v', 'k', unit/jobs, or +a full-screen item view of a cage or suchlike.

      +
    • +
    • dfhack.gui.getSelectedItem([silent])

      +

      Returns the item selected via 'v' ->inventory, 'k', 't', or +a full-screen item view of a container. Note that in the +last case, the highlighted contained item is returned, not +the container itself.

      +
    • +
    • dfhack.gui.getSelectedBuilding([silent])

      +

      Returns the building selected via 'q', 't', 'k' or 'i'.

      +
    • +
    • dfhack.gui.showAnnouncement(text,color[,is_bright])

      +

      Adds a regular announcement with given text, color, and brightness. +The is_bright boolean actually seems to invert the brightness.

      +
    • +
    • dfhack.gui.showZoomAnnouncement(type,pos,text,color[,is_bright])

      +

      Like above, but also specifies a position you can zoom to from the announcement menu.

      +
    • +
    • dfhack.gui.showPopupAnnouncement(text,color[,is_bright])

      +

      Pops up a titan-style modal announcement window.

      +
    • +
    • dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright])

      +

      Uses the type to look up options from announcements.txt, and calls the +above operations accordingly. If enabled, pauses and zooms to position.

      +
    • +
    +
    +
    +

    Job module

    +
      +
    • dfhack.job.cloneJobStruct(job)

      +

      Creates a deep copy of the given job.

      +
    • +
    • dfhack.job.printJobDetails(job)

      +

      Prints info about the job.

      +
    • +
    • dfhack.job.printItemDetails(jobitem,idx)

      +

      Prints info about the job item.

      +
    • +
    • dfhack.job.getHolder(job)

      +

      Returns the building holding the job.

      +
    • +
    • dfhack.job.getWorker(job)

      +

      Returns the unit performing the job.

      +
    • +
    • dfhack.job.checkBuildingsNow()

      +

      Instructs the game to check buildings for jobs next frame and assign workers.

      +
    • +
    • dfhack.job.checkDesignationsNow()

      +

      Instructs the game to check designations for jobs next frame and assign workers.

      +
    • +
    • dfhack.job.is_equal(job1,job2)

      +

      Compares important fields in the job and nested item structures.

      +
    • +
    • dfhack.job.is_item_equal(job_item1,job_item2)

      +

      Compares important fields in the job item structures.

      +
    • +
    • dfhack.job.listNewlyCreated(first_id)

      +

      Returns the current value of df.global.job_next_id, and +if there are any jobs with first_id <= id < job_next_id, +a lua list containing them.

      +
    • +
    +
    +
    +

    Units module

    +
      +
    • dfhack.units.getPosition(unit)

      +

      Returns true x,y,z of the unit, or nil if invalid; may be not equal to unit.pos if caged.

      +
    • +
    • dfhack.units.getContainer(unit)

      +

      Returns the container (cage) item or nil.

      +
    • +
    • dfhack.units.setNickname(unit,nick)

      +

      Sets the unit's nickname properly.

      +
    • +
    • dfhack.units.getVisibleName(unit)

      +

      Returns the language_name object visible in game, accounting for false identities.

      +
    • +
    • dfhack.units.getIdentity(unit)

      +

      Returns the false identity of the unit if it has one, or nil.

      +
    • +
    • dfhack.units.getNemesis(unit)

      +

      Returns the nemesis record of the unit if it has one, or nil.

      +
    • +
    • dfhack.units.isHidingCurse(unit)

      +

      Checks if the unit hides improved attributes from its curse.

      +
    • +
    • dfhack.units.getPhysicalAttrValue(unit, attr_type)

      +
    • +
    • dfhack.units.getMentalAttrValue(unit, attr_type)

      +

      Computes the effective attribute value, including curse effect.

      +
    • +
    • dfhack.units.isCrazed(unit)

      +
    • +
    • dfhack.units.isOpposedToLife(unit)

      +
    • +
    • dfhack.units.hasExtravision(unit)

      +
    • +
    • dfhack.units.isBloodsucker(unit)

      +

      Simple checks of caste attributes that can be modified by curses.

      +
    • +
    • dfhack.units.getMiscTrait(unit, type[, create])

      +

      Finds (or creates if requested) a misc trait object with the given id.

      +
    • +
    • dfhack.units.isDead(unit)

      +

      The unit is completely dead and passive, or a ghost.

      +
    • +
    • dfhack.units.isAlive(unit)

      +

      The unit isn't dead or undead.

      +
    • +
    • dfhack.units.isSane(unit)

      +

      The unit is capable of rational action, i.e. not dead, insane, zombie, or active werewolf.

      +
    • +
    • dfhack.units.isDwarf(unit)

      +

      The unit is of the correct race of the fortress.

      +
    • +
    • dfhack.units.isCitizen(unit)

      +

      The unit is an alive sane citizen of the fortress; wraps the +same checks the game uses to decide game-over by extinction.

      +
    • +
    • dfhack.units.getAge(unit[,true_age])

      +

      Returns the age of the unit in years as a floating-point value. +If true_age is true, ignores false identities.

      +
    • +
    • dfhack.units.getNominalSkill(unit, skill[, use_rust])

      +

      Retrieves the nominal skill level for the given unit. If use_rust +is true, subtracts the rust penalty.

      +
    • +
    • dfhack.units.getEffectiveSkill(unit, skill)

      +

      Computes the effective rating for the given skill, taking into account exhaustion, pain etc.

      +
    • +
    • dfhack.units.computeMovementSpeed(unit)

      +

      Computes number of frames * 100 it takes the unit to move in its current state of mind and body.

      +
    • +
    • dfhack.units.getNoblePositions(unit)

      +

      Returns a list of tables describing noble position assignments, or nil. +Every table has fields entity, assignment and position.

      +
    • +
    • dfhack.units.getProfessionName(unit[,ignore_noble,plural])

      +

      Retrieves the profession name using custom profession, noble assignments +or raws. The ignore_noble boolean disables the use of noble positions.

      +
    • +
    • dfhack.units.getCasteProfessionName(race,caste,prof_id[,plural])

      +

      Retrieves the profession name for the given race/caste using raws.

      +
    • +
    • dfhack.units.getProfessionColor(unit[,ignore_noble])

      +

      Retrieves the color associated with the profession, using noble assignments +or raws. The ignore_noble boolean disables the use of noble positions.

      +
    • +
    • dfhack.units.getCasteProfessionColor(race,caste,prof_id)

      +

      Retrieves the profession color for the given race/caste using raws.

      +
    • +
    +
    +
    +

    Items module

    +
      +
    • dfhack.items.getPosition(item)

      +

      Returns true x,y,z of the item, or nil if invalid; may be not equal to item.pos if in inventory.

      +
    • +
    • dfhack.items.getDescription(item, type[, decorate])

      +

      Returns the string description of the item, as produced by the getItemDescription +method. If decorate is true, also adds markings for quality and improvements.

      +
    • +
    • dfhack.items.getGeneralRef(item, type)

      +

      Searches for a general_ref with the given type.

      +
    • +
    • dfhack.items.getSpecificRef(item, type)

      +

      Searches for a specific_ref with the given type.

      +
    • +
    • dfhack.items.getOwner(item)

      +

      Returns the owner unit or nil.

      +
    • +
    • dfhack.items.setOwner(item,unit)

      +

      Replaces the owner of the item. If unit is nil, removes ownership. +Returns false in case of error.

      +
    • +
    • dfhack.items.getContainer(item)

      +

      Returns the container item or nil.

      +
    • +
    • dfhack.items.getContainedItems(item)

      +

      Returns a list of items contained in this one.

      +
    • +
    • dfhack.items.moveToGround(item,pos)

      +

      Move the item to the ground at position. Returns false if impossible.

      +
    • +
    • dfhack.items.moveToContainer(item,container)

      +

      Move the item to the container. Returns false if impossible.

      +
    • +
    • dfhack.items.moveToBuilding(item,building,use_mode)

      +

      Move the item to the building. Returns false if impossible.

      +
    • +
    • dfhack.items.moveToInventory(item,unit,use_mode,body_part)

      +

      Move the item to the unit inventory. Returns false if impossible.

      +
    • +
    • dfhack.items.remove(item[, no_uncat])

      +

      Removes the item, and marks it for garbage collection unless no_uncat is true.

      +
    • +
    • dfhack.items.makeProjectile(item)

      +

      Turns the item into a projectile, and returns the new object, or nil if impossible.

      +
    • +
    +
    +
    +

    Maps module

    +
      +
    • dfhack.maps.getSize()

      +

      Returns map size in blocks: x, y, z

      +
    • +
    • dfhack.maps.getTileSize()

      +

      Returns map size in tiles: x, y, z

      +
    • +
    • dfhack.maps.getBlock(x,y,z)

      +

      Returns a map block object for given x,y,z in local block coordinates.

      +
    • +
    • dfhack.maps.isValidTilePos(coords), or isValidTilePos(x,y,z)``

      +

      Checks if the given df::coord or x,y,z in local tile coordinates are valid.

      +
    • +
    • dfhack.maps.getTileBlock(coords), or getTileBlock(x,y,z)

      +

      Returns a map block object for given df::coord or x,y,z in local tile coordinates.

      +
    • +
    • dfhack.maps.ensureTileBlock(coords), or ensureTileBlock(x,y,z)

      +

      Like getTileBlock, but if the block is not allocated, try creating it.

      +
    • +
    • dfhack.maps.getRegionBiome(region_coord2d), or getRegionBiome(x,y)

      +

      Returns the biome info struct for the given global map region.

      +
    • +
    • dfhack.maps.enableBlockUpdates(block[,flow,temperature])

      +

      Enables updates for liquid flow or temperature, unless already active.

      +
    • +
    • dfhack.maps.spawnFlow(pos,type,mat_type,mat_index,dimension)

      +

      Spawns a new flow (i.e. steam/mist/dust/etc) at the given pos, and with +the given parameters. Returns it, or nil if unsuccessful.

      +
    • +
    • dfhack.maps.getGlobalInitFeature(index)

      +

      Returns the global feature object with the given index.

      +
    • +
    • dfhack.maps.getLocalInitFeature(region_coord2d,index)

      +

      Returns the local feature object with the given region coords and index.

      +
    • +
    • dfhack.maps.getTileBiomeRgn(coords), or getTileBiomeRgn(x,y,z)

      +

      Returns x, y for use with getRegionBiome.

      +
    • +
    • dfhack.maps.canWalkBetween(pos1, pos2)

      +

      Checks if a dwarf may be able to walk between the two tiles, +using a pathfinding cache maintained by the game. Note that +this cache is only updated when the game is unpaused, and thus +can get out of date if doors are forbidden or unforbidden, or +tools like liquids or tiletypes are used. It also cannot possibly +take into account anything that depends on the actual units, like +burrows, or the presence of invaders.

      +
    • +
    +
    +
    +

    Burrows module

    +
      +
    • dfhack.burrows.findByName(name)

      +

      Returns the burrow pointer or nil.

      +
    • +
    • dfhack.burrows.clearUnits(burrow)

      +

      Removes all units from the burrow.

      +
    • +
    • dfhack.burrows.isAssignedUnit(burrow,unit)

      +

      Checks if the unit is in the burrow.

      +
    • +
    • dfhack.burrows.setAssignedUnit(burrow,unit,enable)

      +

      Adds or removes the unit from the burrow.

      +
    • +
    • dfhack.burrows.clearTiles(burrow)

      +

      Removes all tiles from the burrow.

      +
    • +
    • dfhack.burrows.listBlocks(burrow)

      +

      Returns a table of map block pointers.

      +
    • +
    • dfhack.burrows.isAssignedTile(burrow,tile_coord)

      +

      Checks if the tile is in burrow.

      +
    • +
    • dfhack.burrows.setAssignedTile(burrow,tile_coord,enable)

      +

      Adds or removes the tile from the burrow. Returns false if invalid coords.

      +
    • +
    • dfhack.burrows.isAssignedBlockTile(burrow,block,x,y)

      +

      Checks if the tile within the block is in burrow.

      +
    • +
    • dfhack.burrows.setAssignedBlockTile(burrow,block,x,y,enable)

      +

      Adds or removes the tile from the burrow. Returns false if invalid coords.

      +
    • +
    +
    +
    +

    Buildings module

    +
      +
    • dfhack.buildings.setOwner(item,unit)

      +

      Replaces the owner of the building. If unit is nil, removes ownership. +Returns false in case of error.

      +
    • +
    • dfhack.buildings.getSize(building)

      +

      Returns width, height, centerx, centery.

      +
    • +
    • dfhack.buildings.findAtTile(pos), or findAtTile(x,y,z)

      +

      Scans the buildings for the one located at the given tile. +Does not work on civzones. Warning: linear scan if the map +tile indicates there are buildings at it.

      +
    • +
    • dfhack.buildings.findCivzonesAt(pos), or findCivzonesAt(x,y,z)

      +

      Scans civzones, and returns a lua sequence of those that touch +the given tile, or nil if none.

      +
    • +
    • dfhack.buildings.getCorrectSize(width, height, type, subtype, custom, direction)

      +

      Computes correct dimensions for the specified building type and orientation, +using width and height for flexible dimensions. +Returns is_flexible, width, height, center_x, center_y.

      +
    • +
    • dfhack.buildings.checkFreeTiles(pos,size[,extents,change_extents,allow_occupied])

      +

      Checks if the rectangle defined by pos and size, and possibly extents, +can be used for placing a building. If change_extents is true, bad tiles +are removed from extents. If allow_occupied, the occupancy test is skipped.

      +
    • +
    • dfhack.buildings.countExtentTiles(extents,defval)

      +

      Returns the number of tiles included by extents, or defval.

      +
    • +
    • dfhack.buildings.containsTile(building, x, y[, room])

      +

      Checks if the building contains the specified tile, either directly, or as room.

      +
    • +
    • dfhack.buildings.hasSupport(pos,size)

      +

      Checks if a bridge constructed at specified position would have +support from terrain, and thus won't collapse if retracted.

      +
    • +
    +

    Low-level building creation functions;

    +
      +
    • dfhack.buildings.allocInstance(pos, type, subtype, custom)

      +

      Creates a new building instance of given type, subtype and custom type, +at specified position. Returns the object, or nil in case of an error.

      +
    • +
    • dfhack.buildings.setSize(building, width, height, direction)

      +

      Configures an object returned by allocInstance, using specified +parameters wherever appropriate. If the building has fixed size along +any dimension, the corresponding input parameter will be ignored. +Returns false if the building cannot be placed, or true, width, +height, rect_area, true_area. Returned width and height are the +final values used by the building; true_area is less than rect_area +if any tiles were removed from designation.

      +
    • +
    • dfhack.buildings.constructAbstract(building)

      +

      Links a fully configured object created by allocInstance into the +world. The object must be an abstract building, i.e. a stockpile or civzone. +Returns true, or false if impossible.

      +
    • +
    • dfhack.buildings.constructWithItems(building, items)

      +

      Links a fully configured object created by allocInstance into the +world for construction, using a list of specific items as material. +Returns true, or false if impossible.

      +
    • +
    • dfhack.buildings.constructWithFilters(building, job_items)

      +

      Links a fully configured object created by allocInstance into the +world for construction, using a list of job_item filters as inputs. +Returns true, or false if impossible. Filter objects are claimed +and possibly destroyed in any case. +Use a negative quantity field value to auto-compute the amount +from the size of the building.

      +
    • +
    • dfhack.buildings.deconstruct(building)

      +

      Destroys the building, or queues a deconstruction job. +Returns true if the building was destroyed and deallocated immediately.

      +
    • +
    +

    More high-level functions are implemented in lua and can be loaded by +require('dfhack.buildings'). See hack/lua/dfhack/buildings.lua.

    +

    Among them are:

    +
      +
    • dfhack.buildings.getFiltersByType(argtable,type,subtype,custom)

      +

      Returns a sequence of lua structures, describing input item filters +suitable for the specified building type, or nil if unknown or invalid. +The returned sequence is suitable for use as the job_items argument +of constructWithFilters. +Uses tables defined in buildings.lua.

      +

      Argtable members material (the default name), bucket, barrel, +chain, mechanism, screw, pipe, anvil, weapon are used to +augment the basic attributes with more detailed information if the +building has input items with the matching name (see the tables for naming details). +Note that it is impossible to override any properties this way, only supply those that +are not mentioned otherwise; one exception is that flags2.non_economic +is automatically cleared if an explicit material is specified.

      +
    • +
    • dfhack.buildings.constructBuilding{...}

      +

      Creates a building in one call, using options contained +in the argument table. Returns the building, or nil, error.

      +

      NOTE: Despite the name, unless the building is abstract, +the function creates it in an 'unconstructed' stage, with +a queued in-game job that will actually construct it. I.e. +the function replicates programmatically what can be done +through the construct building menu in the game ui, except +that it does less environment constraint checking.

      +

      The following options can be used:

      +
        +
      • pos = coordinates, or x = ..., y = ..., z = ...

        +

        Mandatory. Specifies the left upper corner of the building.

        +
      • +
      • type = df.building_type.FOO, subtype = ..., custom = ...

        +

        Mandatory. Specifies the type of the building. Obviously, subtype +and custom are only expected if the type requires them.

        +
      • +
      • fields = { ... }

        +

        Initializes fields of the building object after creation with df.assign.

        +
      • +
      • width = ..., height = ..., direction = ...

        +

        Sets size and orientation of the building. If it is +fixed-size, specified dimensions are ignored.

        +
      • +
      • full_rectangle = true

        +

        For buildings like stockpiles or farm plots that can normally +accomodate individual tile exclusion, forces an error if any +tiles within the specified width*height are obstructed.

        +
      • +
      • items = { item, item ... }, or filters = { {...}, {...}... }

        +

        Specifies explicit items or item filters to use in construction. +It is the job of the user to ensure they are correct for the building type.

        +
      • +
      • abstract = true

        +

        Specifies that the building is abstract and does not require construction. +Required for stockpiles and civzones; an error otherwise.

        +
      • +
      • material = {...}, mechanism = {...}, ...

        +

        If none of items, filter, or abstract is used, +the function uses getFiltersByType to compute the input +item filters, and passes the argument table through. If no filters +can be determined this way, constructBuilding throws an error.

        +
      • +
      +
    • +
    +
    +
    +

    Constructions module

    +
      +
    • dfhack.constructions.designateNew(pos,type,item_type,mat_index)

      +

      Designates a new construction at given position. If there already is +a planned but not completed construction there, changes its type. +Returns true, or false if obstructed. +Note that designated constructions are technically buildings.

      +
    • +
    • dfhack.constructions.designateRemove(pos), or designateRemove(x,y,z)

      +

      If there is a construction or a planned construction at the specified +coordinates, designates it for removal, or instantly cancels the planned one. +Returns true, was_only_planned if removed; or false if none found.

      +
    • +
    +
    +
    +

    Screen API

    +

    The screen module implements support for drawing to the tiled screen of the game. +Note that drawing only has any effect when done from callbacks, so it can only +be feasibly used in the core context.

    +

    Basic painting functions:

    +
      +
    • dfhack.screen.getWindowSize()

      +

      Returns width, height of the screen.

      +
    • +
    • dfhack.screen.getMousePos()

      +

      Returns x,y of the tile the mouse is over.

      +
    • +
    • dfhack.screen.inGraphicsMode()

      +

      Checks if [GRAPHICS:YES] was specified in init.

      +
    • +
    • dfhack.screen.paintTile(pen,x,y[,char,tile])

      +

      Paints a tile using given parameters. Pen is a table with following possible fields:

      +
      +
      ch
      +

      Provides the ordinary tile character, as either a 1-character string or a number. +Can be overridden with the char function parameter.

      +
      +
      fg
      +

      Foreground color for the ordinary tile. Defaults to COLOR_GREY (7).

      +
      +
      bg
      +

      Background color for the ordinary tile. Defaults to COLOR_BLACK (0).

      +
      +
      bold
      +

      Bright/bold text flag. If nil, computed based on (fg & 8); fg is masked to 3 bits. +Otherwise should be true/false.

      +
      +
      tile
      +

      Graphical tile id. Ignored unless [GRAPHICS:YES] was in init.txt.

      +
      +
      tile_color = true
      +

      Specifies that the tile should be shaded with fg/bg.

      +
      +
      tile_fg, tile_bg
      +

      If specified, overrides tile_color and supplies shading colors directly.

      +
      +
      +

      Returns false if coordinates out of bounds, or other error.

      +
    • +
    • dfhack.screen.readTile(x,y)

      +

      Retrieves the contents of the specified tile from the screen buffers. +Returns a pen, or nil if invalid or TrueType.

      +
    • +
    • dfhack.screen.paintString(pen,x,y,text)

      +

      Paints the string starting at x,y. Uses the string characters +in sequence to override the ch field of pen.

      +

      Returns true if painting at least one character succeeded.

      +
    • +
    • dfhack.screen.fillRect(pen,x1,y1,x2,y2)

      +

      Fills the rectangle specified by the coordinates with the given pen. +Returns true if painting at least one character succeeded.

      +
    • +
    • dfhack.screen.findGraphicsTile(pagename,x,y)

      +

      Finds a tile from a graphics set (i.e. the raws used for creatures), +if in graphics mode and loaded.

      +

      Returns: tile, tile_grayscale, or nil if not found. +The values can then be used for the tile field of pen structures.

      +
    • +
    • dfhack.screen.clear()

      +

      Fills the screen with blank background.

      +
    • +
    • dfhack.screen.invalidate()

      +

      Requests repaint of the screen by setting a flag. Unlike other +functions in this section, this may be used at any time.

      +
    • +
    +

    In order to actually be able to paint to the screen, it is necessary +to create and register a viewscreen (basically a modal dialog) with +the game.

    +

    NOTE: As a matter of policy, in order to avoid user confusion, all +interface screens added by dfhack should bear the "DFHack" signature.

    +

    Screens are managed with the following functions:

    +
      +
    • dfhack.screen.show(screen[,below])

      +

      Displays the given screen, possibly placing it below a different one. +The screen must not be already shown. Returns true if success.

      +
    • +
    • dfhack.screen.dismiss(screen[,to_first])

      +

      Marks the screen to be removed when the game enters its event loop. +If to_first is true, all screens up to the first one will be deleted.

      +
    • +
    • dfhack.screen.isDismissed(screen)

      +

      Checks if the screen is already marked for removal.

      +
    • +
    +

    Apart from a native viewscreen object, these functions accept a table +as a screen. In this case, show creates a new native viewscreen +that delegates all processing to methods stored in that table.

    +

    NOTE: Lua-implemented screens are only supported in the core context.

    +

    Supported callbacks and fields are:

    +
      +
    • screen._native

      +

      Initialized by show with a reference to the backing viewscreen +object, and removed again when the object is deleted.

      +
    • +
    • function screen:onShow()

      +

      Called by dfhack.screen.show if successful.

      +
    • +
    • function screen:onDismiss()

      +

      Called by dfhack.screen.dismiss if successful.

      +
    • +
    • function screen:onDestroy()

      +

      Called from the destructor when the viewscreen is deleted.

      +
    • +
    • function screen:onResize(w, h)

      +

      Called before onRender or onIdle when the window size has changed.

      +
    • +
    • function screen:onRender()

      +

      Called when the viewscreen should paint itself. This is the only context +where the above painting functions work correctly.

      +

      If omitted, the screen is cleared; otherwise it should do that itself. +In order to make a see-through dialog, call self._native.parent:render().

      +
    • +
    • function screen:onIdle()

      +

      Called every frame when the screen is on top of the stack.

      +
    • +
    • function screen:onHelp()

      +

      Called when the help keybinding is activated (usually '?').

      +
    • +
    • function screen:onInput(keys)

      +

      Called when keyboard or mouse events are available. +If any keys are pressed, the keys argument is a table mapping them to true. +Note that this refers to logical keybingings computed from real keys via +options; if multiple interpretations exist, the table will contain multiple keys.

      +

      The table also may contain special keys:

      +
      +
      _STRING
      +

      Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience.

      +
      +
      _MOUSE_L, _MOUSE_R
      +

      If the left or right mouse button is pressed.

      +
      +
      +

      If this method is omitted, the screen is dismissed on receival of the LEAVESCREEN key.

      +
    • +
    • function screen:onGetSelectedUnit()

      +
    • +
    • function screen:onGetSelectedItem()

      +
    • +
    • function screen:onGetSelectedJob()

      +
    • +
    • function screen:onGetSelectedBuilding()

      +

      Implement these to provide a return value for the matching +dfhack.gui.getSelected... function.

      +
    • +
    +
    +
    +

    Internal API

    +

    These functions are intended for the use by dfhack developers, +and are only documented here for completeness:

    +
      +
    • dfhack.internal.scripts

      +

      The table used by dfhack.run_script() to give every script its own +global environment, persistent between calls to the script.

      +
    • +
    • dfhack.internal.getAddress(name)

      +

      Returns the global address name, or nil.

      +
    • +
    • dfhack.internal.setAddress(name, value)

      +

      Sets the global address name. Returns the value of getAddress before the change.

      +
    • +
    • dfhack.internal.getVTable(name)

      +

      Returns the pre-extracted vtable address name, or nil.

      +
    • +
    • dfhack.internal.getRebaseDelta()

      +

      Returns the ASLR rebase offset of the DF executable.

      +
    • +
    • dfhack.internal.getMemRanges()

      +

      Returns a sequence of tables describing virtual memory ranges of the process.

      +
    • +
    • dfhack.internal.patchMemory(dest,src,count)

      +

      Like memmove below, but works even if dest is read-only memory, e.g. code. +If destination overlaps a completely invalid memory region, or another error +occurs, returns false.

      +
    • +
    • dfhack.internal.memmove(dest,src,count)

      +

      Wraps the standard memmove function. Accepts both numbers and refs as pointers.

      +
    • +
    • dfhack.internal.memcmp(ptr1,ptr2,count)

      +

      Wraps the standard memcmp function.

      +
    • +
    • dfhack.internal.memscan(haystack,count,step,needle,nsize)

      +

      Searches for needle of nsize bytes in haystack, +using count steps of step bytes. +Returns: step_idx, sum_idx, found_ptr, or nil if not found.

      +
    • +
    • dfhack.internal.diffscan(old_data, new_data, start_idx, end_idx, eltsize[, oldval, newval, delta])

      +

      Searches for differences between buffers at ptr1 and ptr2, as integers of size eltsize. +The oldval, newval or delta arguments may be used to specify additional constraints. +Returns: found_index, or nil if end reached.

      +
    • +
    +
    +
    +
    +

    Core interpreter context

    +

    While plugins can create any number of interpreter instances, +there is one special context managed by dfhack core. It is the +only context that can receive events from DF and plugins.

    +

    Core context specific functions:

    +
      +
    • dfhack.is_core_context

      +

      Boolean value; true in the core context.

      +
    • +
    • dfhack.timeout(time,mode,callback)

      +

      Arranges for the callback to be called once the specified +period of time passes. The mode argument specifies the +unit of time used, and may be one of 'frames' (raw FPS), +'ticks' (unpaused FPS), 'days', 'months', +'years' (in-game time). All timers other than +'frames' are cancelled when the world is unloaded, +and cannot be queued until it is loaded again. +Returns the timer id, or nil if unsuccessful due to +world being unloaded.

      +
    • +
    • dfhack.timeout_active(id[,new_callback])

      +

      Returns the active callback with the given id, or nil +if inactive or nil id. If called with 2 arguments, replaces +the current callback with the given value, if still active. +Using timeout_active(id,nil) cancels the timer.

      +
    • +
    • dfhack.onStateChange.foo = function(code)

      +

      Event. Receives the same codes as plugin_onstatechange in C++.

      +
    • +
    +
    +

    Event type

    +

    An event is a native object transparently wrapping a lua table, +and implementing a __call metamethod. When it is invoked, it loops +through the table with next and calls all contained values. +This is intended as an extensible way to add listeners.

    +

    This type itself is available in any context, but only the +core context has the actual events defined by C++ code.

    +

    Features:

    +
      +
    • dfhack.event.new()

      +

      Creates a new instance of an event.

      +
    • +
    • event[key] = function

      +

      Sets the function as one of the listeners. Assign nil to remove it.

      +

      NOTE: The df.NULL key is reserved for the use by +the C++ owner of the event; it is an error to try setting it.

      +
    • +
    • #event

      +

      Returns the number of non-nil listeners.

      +
    • +
    • pairs(event)

      +

      Iterates over all listeners in the table.

      +
    • +
    • event(args...)

      +

      Invokes all listeners contained in the event in an arbitrary +order using dfhack.safecall.

      +
    • +
    +
    +
    +
    +
    +

    Lua Modules

    +

    DFHack sets up the lua interpreter so that the built-in require +function can be used to load shared lua code from hack/lua/. +The dfhack namespace reference itself may be obtained via +require('dfhack'), although it is initially created as a +global by C++ bootstrap code.

    +

    The following module management functions are provided:

    +
      +
    • mkmodule(name)

      +

      Creates an environment table for the module. Intended to be used as:

      +
      +local _ENV = mkmodule('foo')
      +...
      +return _ENV
      +
      +

      If called the second time, returns the same table; thus providing reload support.

      +
    • +
    • reload(name)

      +

      Reloads a previously require-d module "name" from the file. +Intended as a help for module development.

      +
    • +
    • dfhack.BASE_G

      +

      This variable contains the root global environment table, which is +used as a base for all module and script environments. Its contents +should be kept limited to the standard Lua library and API described +in this document.

      +
    • +
    +
    +

    Global environment

    +

    A number of variables and functions are provided in the base global +environment by the mandatory init file dfhack.lua:

    +
      +
    • Color constants

      +

      These are applicable both for dfhack.color() and color fields +in DF functions or structures:

      +

      COLOR_RESET, COLOR_BLACK, COLOR_BLUE, COLOR_GREEN, COLOR_CYAN, +COLOR_RED, COLOR_MAGENTA, COLOR_BROWN, COLOR_GREY, COLOR_DARKGREY, +COLOR_LIGHTBLUE, COLOR_LIGHTGREEN, COLOR_LIGHTCYAN, COLOR_LIGHTRED, +COLOR_LIGHTMAGENTA, COLOR_YELLOW, COLOR_WHITE

      +
    • +
    • dfhack.onStateChange event codes

      +

      Available only in the core context, as is the event itself:

      +

      SC_WORLD_LOADED, SC_WORLD_UNLOADED, SC_MAP_LOADED, +SC_MAP_UNLOADED, SC_VIEWSCREEN_CHANGED, SC_CORE_INITIALIZED

      +
    • +
    • Functions already described above

      +

      safecall, qerror, mkmodule, reload

      +
    • +
    • printall(obj)

      +

      If the argument is a lua table or DF object reference, prints all fields.

      +
    • +
    • copyall(obj)

      +

      Returns a shallow copy of the table or reference as a lua table.

      +
    • +
    • pos2xyz(obj)

      +

      The object must have fields x, y and z. Returns them as 3 values. +If obj is nil, or x is -30000 (the usual marker for undefined +coordinates), returns nil.

      +
    • +
    • xyz2pos(x,y,z)

      +

      Returns a table with x, y and z as fields.

      +
    • +
    • safe_index(obj,index...)

      +

      Walks a sequence of dereferences, which may be represented by numbers or strings. +Returns nil if any of obj or indices is nil, or a numeric index is out of array bounds.

      +
    • +
    +
    +
    +

    utils

    +
      +
    • utils.compare(a,b)

      +

      Comparator function; returns -1 if a<b, 1 if a>b, 0 otherwise.

      +
    • +
    • utils.compare_name(a,b)

      +

      Comparator for names; compares empty string last.

      +
    • +
    • utils.is_container(obj)

      +

      Checks if obj is a container ref.

      +
    • +
    • utils.make_index_sequence(start,end)

      +

      Returns a lua sequence of numbers in start..end.

      +
    • +
    • utils.make_sort_order(data, ordering)

      +

      Computes a sorted permutation of objects in data, as a table of integer +indices into the data sequence. Uses data.n as input length +if present.

      +

      The ordering argument is a sequence of ordering specs, represented +as lua tables with following possible fields:

      +
      +
      ord.key = function(value)
      +

      Computes comparison key from input data value. Not called on nil. +If omitted, the comparison key is the value itself.

      +
      +
      ord.key_table = function(data)
      +

      Computes a key table from the data table in one go.

      +
      +
      ord.compare = function(a,b)
      +

      Comparison function. Defaults to utils.compare above. +Called on non-nil keys; nil sorts last.

      +
      +
      ord.nil_first = true/false
      +

      If true, nil keys are sorted first instead of last.

      +
      +
      ord.reverse = true/false
      +

      If true, sort non-nil keys in descending order.

      +
      +
      +

      For every comparison during sorting the specs are applied in +order until an unambiguous decision is reached. Sorting is stable.

      +

      Example of sorting a sequence by field foo:

      +
      +local spec = { key = function(v) return v.foo end }
      +local order = utils.make_sort_order(data, { spec })
      +local output = {}
      +for i = 1,#order do output[i] = data[order[i]] end
      +
      +

      Separating the actual reordering of the sequence in this +way enables applying the same permutation to multiple arrays. +This function is used by the sort plugin.

      +
    • +
    • utils.assign(tgt, src)

      +

      Does a recursive assignment of src into tgt. +Uses df.assign if tgt is a native object ref; otherwise +recurses into lua tables.

      +
    • +
    • utils.clone(obj, deep)

      +

      Performs a shallow, or semi-deep copy of the object as a lua table tree. +The deep mode recurses into lua tables and subobjects, except pointers +to other heap objects. +Null pointers are represented as df.NULL. Zero-based native containers +are converted to 1-based lua sequences.

      +
    • +
    • utils.clone_with_default(obj, default, force)

      +

      Copies the object, using the default lua table tree +as a guide to which values should be skipped as uninteresting. +The force argument makes it always return a non-nil value.

      +
    • +
    • utils.sort_vector(vector,field,cmpfun)

      +

      Sorts a native vector or lua sequence using the comparator function. +If field is not nil, applies the comparator to the field instead +of the whole object.

      +
    • +
    • utils.binsearch(vector,key,field,cmpfun,min,max)

      +

      Does a binary search in a native vector or lua sequence for +key, using cmpfun and field like sort_vector. +If min and max are specified, they are used as the +search subrange bounds.

      +

      If found, returns item, true, idx. Otherwise returns +nil, false, insert_idx, where insert_idx is the correct +insertion point.

      +
    • +
    • utils.insert_sorted(vector,item,field,cmpfun)

      +

      Does a binary search, and inserts item if not found. +Returns did_insert, vector[idx], idx.

      +
    • +
    • utils.insert_or_update(vector,item,field,cmpfun)

      +

      Like insert_sorted, but also assigns the item into +the vector cell if insertion didn't happen.

      +

      As an example, you can use this to set skill values:

      +
      +utils.insert_or_update(soul.skills, {new=true, id=..., rating=...}, 'id')
      +
      +

      (For an explanation of new=true, see table assignment in the wrapper section)

      +
    • +
    • utils.prompt_yes_no(prompt, default)

      +

      Presents a yes/no prompt to the user. If default is not nil, +allows just pressing Enter to submit the default choice. +If the user enters 'abort', throws an error.

      +
    • +
    • utils.prompt_input(prompt, checkfun, quit_str)

      +

      Presents a prompt to input data, until a valid string is entered. +Once checkfun(input) returns true, ..., passes the values +through. If the user enters the quit_str (defaults to '~~~'), +throws an error.

      +
    • +
    • utils.check_number(text)

      +

      A prompt_input checkfun that verifies a number input.

      +
    • +
    +
    +
    +

    dumper

    +

    A third-party lua table dumper module from +http://lua-users.org/wiki/DataDumper. Defines one +function:

    +
      +
    • dumper.DataDumper(value, varname, fastmode, ident, indent_step)

      +

      Returns value converted to a string. The indent_step +argument specifies the indentation step size in spaces. For +the other arguments see the original documentation link above.

      +
    • +
    +
    +
    +

    class

    +

    Implements a trivial single-inheritance class system.

    +
      +
    • Foo = defclass(Foo[, ParentClass])

      +

      Defines or updates class Foo. The Foo = defclass(Foo) syntax +is needed so that when the module or script is reloaded, the +class identity will be preserved through the preservation of +global variable values.

      +

      The defclass function is defined as a stub in the global +namespace, and using it will auto-load the class module.

      +
    • +
    • Class.super

      +

      This class field is set by defclass to the parent class, and +allows a readable Class.super.method(self, ...) syntax for +calling superclass methods.

      +
    • +
    • Class.ATTRS { foo = xxx, bar = yyy }

      +

      Declares certain instance fields to be attributes, i.e. auto-initialized +from fields in the table used as the constructor argument. If omitted, +they are initialized with the default values specified in this declaration.

      +

      If the default value should be nil, use ATTRS { foo = DEFAULT_NIL }.

      +
    • +
    • new_obj = Class{ foo = arg, bar = arg, ... }

      +

      Calling the class as a function creates and initializes a new instance. +Initialization happens in this order:

      +
        +
      1. An empty instance table is created, and its metatable set.
      2. +
      3. The preinit method is called via invoke_before (see below) +with the table used as argument to the class. This method is intended +for validating and tweaking that argument table.
      4. +
      5. Declared ATTRS are initialized from the argument table or their default values.
      6. +
      7. The init method is called via invoke_after with the argument table. +This is the main constructor method.
      8. +
      9. The postinit method is called via invoke_after with the argument table. +Place code that should be called after the object is fully constructed here.
      10. +
      +
    • +
    +

    Predefined instance methods:

    +
      +
    • instance:assign{ foo = xxx }

      +

      Assigns all values in the input table to the matching instance fields.

      +
    • +
    • instance:callback(method_name, [args...])

      +

      Returns a closure that invokes the specified method of the class, +properly passing in self, and optionally a number of initial arguments too. +The arguments given to the closure are appended to these.

      +
    • +
    • instance:invoke_before(method_name, args...)

      +

      Navigates the inheritance chain of the instance starting from the most specific +class, and invokes the specified method with the arguments if it is defined in +that specific class. Equivalent to the following definition in every class:

      +
      +function Class:invoke_before(method, ...)
      +  if rawget(Class, method) then
      +    rawget(Class, method)(self, ...)
      +  end
      +  Class.super.invoke_before(method, ...)
      +end
      +
      +
    • +
    • instance:invoke_after(method_name, args...)

      +

      Like invoke_before, only the method is called after the recursive call to super, +i.e. invocations happen in the parent to child order.

      +

      These two methods are inspired by the Common Lisp before and after methods, and +are intended for implementing similar protocols for certain things. The class +library itself uses them for constructors.

      +
    • +
    +

    To avoid confusion, these methods cannot be redefined.

    +
    +
    +
    +

    Plugins

    +

    DFHack plugins may export native functions and events +to lua contexts. They are automatically imported by +mkmodule('plugins.<name>'); this means that a lua +module file is still necessary for require to read.

    +

    The following plugins have lua support.

    +
    +

    burrows

    +

    Implements extended burrow manipulations.

    +

    Events:

    +
      +
    • onBurrowRename.foo = function(burrow)

      +

      Emitted when a burrow might have been renamed either through +the game UI, or renameBurrow().

      +
    • +
    • onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype,worker)

      +

      Emitted when a tile might have been dug out. Only tracked if the +auto-growing burrows feature is enabled.

      +
    • +
    +

    Native functions:

    +
      +
    • renameBurrow(burrow,name)

      +

      Renames the burrow, emitting onBurrowRename and updating auto-grow state properly.

      +
    • +
    • findByName(burrow,name)

      +

      Finds a burrow by name, using the same rules as the plugin command line interface. +Namely, trailing '+' characters marking auto-grow burrows are ignored.

      +
    • +
    • copyUnits(target,source,enable)

      +

      Applies units from source burrow to target. The enable +parameter specifies if they are to be added or removed.

      +
    • +
    • copyTiles(target,source,enable)

      +

      Applies tiles from source burrow to target. The enable +parameter specifies if they are to be added or removed.

      +
    • +
    • setTilesByKeyword(target,keyword,enable)

      +

      Adds or removes tiles matching a predefined keyword. The keyword +set is the same as used by the command line.

      +
    • +
    +

    The lua module file also re-exports functions from dfhack.burrows.

    +
    +
    +

    sort

    +

    Does not export any native functions as of now. Instead, it +calls lua code to perform the actual ordering of list items.

    +
    +
    +
    +

    Scripts

    +

    Any files with the .lua extension placed into hack/scripts/* +are automatically used by the DFHack core as commands. The +matching command name consists of the name of the file sans +the extension.

    +

    If the first line of the script is a one-line comment, it is +used by the built-in ls and help commands.

    +

    NOTE: Scripts placed in subdirectories still can be accessed, but +do not clutter the ls command list; thus it is preferred +for obscure developer-oriented scripts and scripts used by tools. +When calling such scripts, always use '/' as the separator for +directories, e.g. devel/lua-example.

    +

    Scripts are re-read from disk every time they are used +(this may be changed later to check the file change time); however +the global variable values persist in memory between calls. +Every script gets its own separate environment for global +variables.

    +

    Arguments are passed in to the scripts via the ... built-in +quasi-variable; when the script is called by the DFHack core, +they are all guaranteed to be non-nil strings.

    +

    DFHack core invokes the scripts in the core context (see above); +however it is possible to call them from any lua code (including +from other scripts) in any context, via the same function the core uses:

    +
      +
    • dfhack.run_script(name[,args...])

      +

      Run a lua script in hack/scripts/, as if it was started from dfhack command-line. +The name argument should be the name stem, as would be used on the command line.

      +
    • +
    +

    Note that this function lets errors propagate to the caller.

    +
    +
    + + diff --git a/Readme.html b/Readme.html new file mode 100644 index 000000000..caf1f6a0e --- /dev/null +++ b/Readme.html @@ -0,0 +1,2808 @@ + + + + + + +DFHack Readme + + + +
    +

    DFHack Readme

    + +
    +

    Introduction

    +

    DFHack is a Dwarf Fortress memory access library and a set of basic +tools that use it. Tools come in the form of plugins or (not yet) +external tools. It is an attempt to unite the various ways tools +access DF memory and allow for easier development of new tools.

    +
    +

    Contents

    + +
    +
    +
    +

    Getting DFHack

    +

    The project is currently hosted on github, for both source and +binaries at http://github.com/peterix/dfhack

    +

    Releases can be downloaded from here: https://github.com/peterix/dfhack/downloads

    +

    All new releases are announced in the bay12 thread: http://tinyurl.com/dfhack-ng

    +
    +
    +

    Compatibility

    +

    DFHack works on Windows XP, Vista, 7 or any modern Linux distribution. +OSX is not supported due to lack of developers with a Mac.

    +

    Currently, version 0.34.11 is supported (and tested). If you need DFHack +for older versions, look for older releases.

    +

    On Windows, you have to use the SDL version of DF.

    +

    It is possible to use the Windows DFHack under wine/OSX.

    +
    +
    +

    Installation/Removal

    +

    Installing DFhack involves copying files into your DF folder. +Copy the files from a release archive so that:

    +
    +
      +
    • On Windows, SDL.dll is replaced
    • +
    • On Linux, the 'dfhack' script is placed in the same folder as the 'df' script
    • +
    +
    +

    Uninstalling is basically the same, in reverse:

    +
    +
      +
    • On Windows, first delete SDL.dll and rename SDLreal.dll to SDL.dll. Then +remove the other DFHack files
    • +
    • On Linux, Remove the DFHack files.
    • +
    +
    +

    The stonesense plugin might require some additional libraries on Linux.

    +

    If any of the plugins or dfhack itself refuses to load, check the stderr.log +file created in your DF folder.

    +
    +
    +

    Using DFHack

    +

    DFHack basically extends what DF can do with something similar to the drop-down +console found in Quake engine games. On Windows, this is a separate command line +window. On linux, the terminal used to launch the dfhack script is taken over +(so, make sure you start from a terminal). Basic interaction with dfhack +involves entering commands into the console. For some basic instroduction, +use the 'help' command. To list all possible commands, use the 'ls' command. +Many commands have their own help or detailed description. You can use +'command help' or 'command ?' to show that.

    +

    The command line has some nice line editing capabilities, including history +that's preserved between different runs of DF (use up/down keys to go through +the history).

    +

    The second way to interact with DFHack is to bind the available commands +to in-game hotkeys. The old way to do this is via the hotkey/zoom menu (normally +opened with the 'h' key). Binding the commands is done by assigning a command as +a hotkey name (with 'n').

    +

    A new and more flexible way is the keybinding command in the dfhack console. +However, bindings created this way are not automatically remembered between runs +of the game, so it becomes necessary to use the dfhack.init file to ensure that +they are re-created every time it is loaded.

    +

    Interactive commands like 'liquids' cannot be used as hotkeys.

    +

    Most of the commands come from plugins. Those reside in 'hack/plugins/'.

    +
    +
    +

    Something doesn't work, help!

    +

    First, don't panic :) Second, dfhack keeps a few log files in DF's folder +- stderr.log and stdout.log. You can look at those and possibly find out what's +happening. +If you found a bug, you can either report it in the bay12 DFHack thread, +the issues tracker on github, contact me (peterix@gmail.com) or visit the +#dfhack IRC channel on freenode.

    +
    +
    +

    The init file

    +

    If your DF folder contains a file named dfhack.init, its contents will be run +every time you start DF. This allows setting up keybindings. An example file +is provided as dfhack.init-example - you can tweak it and rename to dfhack.init +if you want to use this functionality.

    +
    +

    Setting keybindings

    +

    To set keybindings, use the built-in keybinding command. Like any other +command it can be used at any time from the console, but it is also meaningful +in the DFHack init file.

    +

    Currently it supports any combination of Ctrl/Alt/Shift with F1-F9, or A-Z.

    +

    Possible ways to call the command:

    + +++ + + + + + + + + + + + + + +
    keybinding list <key>:
     List bindings active for the key combination.
    keybinding clear <key> <key>...:
     Remove bindings for the specified keys.
    keybinding add <key> "cmdline" "cmdline"...:
     Add bindings for the specified +key.
    keybinding set <key> "cmdline" "cmdline"...:
     Clear, and then add bindings for +the specified key.
    +

    The <key> parameter above has the following case-sensitive syntax:

    +
    +[Ctrl-][Alt-][Shift-]KEY[@context]
    +
    +

    where the KEY part can be F1-F9 or A-Z, and [] denote optional parts.

    +

    When multiple commands are bound to the same key combination, DFHack selects +the first applicable one. Later 'add' commands, and earlier entries within one +'add' command have priority. Commands that are not specifically intended for use +as a hotkey are always considered applicable.

    +

    The context part in the key specifier above can be used to explicitly restrict +the UI state where the binding would be applicable. If called without parameters, +the keybinding command among other things prints the current context string. +Only bindings with a context tag that either matches the current context fully, +or is a prefix ending at a '/' boundary would be considered for execution, i.e. +for context foo/bar/baz, possible matches are any of @foo/bar/baz, @foo/bar, +@foo or none.

    +
    +
    +
    +

    Commands

    +

    DFHack command syntax consists of a command name, followed by arguments separated +by whitespace. To include whitespace in an argument, quote it in double quotes. +To include a double quote character, use \" inside double quotes.

    +

    If the first non-whitespace character of a line is #, the line is treated +as a comment, i.e. a silent no-op command.

    +

    If the first non-whitespace character is :, the command is parsed in a special +alternative mode: first, non-whitespace characters immediately following the : +are used as the command name; the remaining part of the line, starting with the first +non-whitespace character after the command name, is used verbatim as the first argument. +The following two command lines are exactly equivalent:

    +
    +
      +
    • :foo a b "c d" e f
    • +
    • foo "a b \"c d\" e f"
    • +
    +
    +

    This is intended for commands like rb_eval that evaluate script language statements.

    +

    Almost all the commands support using the 'help <command-name>' built-in command +to retrieve further help without having to look at this document. Alternatively, +some accept a 'help'/'?' option on their command line.

    +
    +

    Game progress

    +
    +

    die

    +

    Instantly kills DF without saving.

    +
    +
    +

    forcepause

    +

    Forces DF to pause. This is useful when your FPS drops below 1 and you lose +control of the game.

    +
    +
      +
    • Activate with 'forcepause 1'
    • +
    • Deactivate with 'forcepause 0'
    • +
    +
    +
    +
    +

    nopause

    +

    Disables pausing (both manual and automatic) with the exception of pause forced +by 'reveal hell'. This is nice for digging under rivers.

    +
    +
    +

    fastdwarf

    +

    Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and perform tasks quickly. Teledwarf makes dwarves move instantaneously, but do jobs at the same speed.

    +
    +
      +
    • 'fastdwarf 0 0' disables both
    • +
    • 'fastdwarf 0 1' disables speedydwarf and enables teledwarf
    • +
    • 'fastdwarf 1 0' enables speedydwarf and disables teledwarf
    • +
    • 'fastdwarf 1 1' enables both
    • +
    • 'fastdwarf 0' disables both
    • +
    • 'fastdwarf 1' enables speedydwarf and disables teledwarf
    • +
    +
    +
    +
    +
    +

    Game interface

    +
    +

    follow

    +

    Makes the game view follow the currently highlighted unit after you exit from +current menu/cursor mode. Handy for watching dwarves running around. Deactivated +by moving the view manually.

    +
    +
    +

    tidlers

    +

    Toggle between all possible positions where the idlers count can be placed.

    +
    +
    +

    twaterlvl

    +

    Toggle between displaying/not displaying liquid depth as numbers.

    +
    +
    +

    copystock

    +

    Copies the parameters of the currently highlighted stockpile to the custom +stockpile settings and switches to custom stockpile placement mode, effectively +allowing you to copy/paste stockpiles easily.

    +
    +
    +

    rename

    +

    Allows renaming various things.

    +

    Options:

    +
    + +++ + + + + + + + + + + + + + + + + +
    rename squad <index> "name":
     Rename squad by index to 'name'.
    rename hotkey <index> "name":
     Rename hotkey by index. This allows assigning +longer commands to the DF hotkeys.
    rename unit "nickname":
     Rename a unit/creature highlighted in the DF user +interface.
    rename unit-profession "custom profession":
     Change proffession name of the +highlighted unit/creature.
    rename building "name":
     Set a custom name for the selected building. +The building must be one of stockpile, workshop, furnace, trap, +siege engine or an activity zone.
    +
    +
    +
    +
    +

    Adventure mode

    +
    +

    adv-bodyswap

    +

    This allows taking control over your followers and other creatures in adventure +mode. For example, you can make them pick up new arms and armor and equip them +properly.

    +

    Usage:

    +
    +
      +
    • When viewing unit details, body-swaps into that unit.
    • +
    • In the main adventure mode screen, reverts transient swap.
    • +
    +
    +
    +
    +

    advtools

    +

    A package of different adventure mode tools (currently just one)

    +

    Usage:

    +
    + +++ + + + + + + + +
    list-equipped [all]:
     List armor and weapons equipped by your companions. +If all is specified, also lists non-metal clothing.
    metal-detector [all-types] [non-trader]:
     Reveal metal armor and weapons in +shops. The options disable the checks +on item type and being in shop.
    +
    +
    +
    +
    +

    Map modification

    +
    +

    changelayer

    +

    Changes material of the geology layer under cursor to the specified inorganic +RAW material. Can have impact on all surrounding regions, not only your embark! +By default changing stone to soil and vice versa is not allowed. By default +changes only the layer at the cursor position. Note that one layer can stretch +across lots of z levels. By default changes only the geology which is linked +to the biome under the cursor. That geology might be linked to other biomes +as well, though. Mineral veins and gem clusters will stay on the map. Use +'changevein' for them.

    +

    tl;dr: You will end up with changing quite big areas in one go, especially if +you use it in lower z levels. Use with care.

    +

    Options:

    +
    + +++ + + + + + + + + + + + +
    all_biomes:Change selected layer for all biomes on your map. +Result may be undesirable since the same layer can AND WILL +be on different z-levels for different biomes. Use the tool +'probe' to get an idea how layers and biomes are distributed +on your map.
    all_layers:Change all layers on your map (only for the selected biome +unless 'all_biomes' is added). +Candy mountain, anyone? Will make your map quite boring, +but tidy.
    force:Allow changing stone to soil and vice versa. !!THIS CAN HAVE +WEIRD EFFECTS, USE WITH CARE!! +Note that soil will not be magically replaced with stone. +You will, however, get a stone floor after digging so it +will allow the floor to be engraved. +Note that stone will not be magically replaced with soil. +You will, however, get a soil floor after digging so it +could be helpful for creating farm plots on maps with no +soil.
    verbose:Give some details about what is being changed.
    trouble:Give some advice about known problems.
    +
    +

    Examples:

    +
    +
    +
    changelayer GRANITE
    +
    Convert layer at cursor position into granite.
    +
    changelayer SILTY_CLAY force
    +
    Convert layer at cursor position into clay even if it's stone.
    +
    changelayer MARBLE all_biomes all_layers
    +
    Convert all layers of all biomes which are not soil into marble.
    +
    +
    +
    +

    Note

    +
      +
    • If you use changelayer and nothing happens, try to pause/unpause the game +for a while and try to move the cursor to another tile. Then try again. +If that doesn't help try temporarily changing some other layer, undo your +changes and try again for the layer you want to change. Saving +and reloading your map might also help.
    • +
    • You should be fine if you only change single layers without the use +of 'force'. Still it's advisable to save your game before messing with +the map.
    • +
    • When you force changelayer to convert soil to stone you might experience +weird stuff (flashing tiles, tiles changed all over place etc). +Try reverting the changes manually or even better use an older savegame. +You did save your game, right?
    • +
    +
    +
    +
    +

    changevein

    +

    Changes material of the vein under cursor to the specified inorganic RAW +material. Only affects tiles within the current 16x16 block - for veins and +large clusters, you will need to use this command multiple times.

    +

    Example:

    +
    +
    +
    changevein NATIVE_PLATINUM
    +
    Convert vein at cursor position into platinum ore.
    +
    +
    +
    +
    +

    changeitem

    +

    Allows changing item material and base quality. By default the item currently +selected in the UI will be changed (you can select items in the 'k' list +or inside containers/inventory). By default change is only allowed if materials +is of the same subtype (for example wood<->wood, stone<->stone etc). But since +some transformations work pretty well and may be desired you can override this +with 'force'. Note that some attributes will not be touched, possibly resulting +in weirdness. To get an idea how the RAW id should look like, check some items +with 'info'. Using 'force' might create items which are not touched by +crafters/haulers.

    +

    Options:

    +
    + +++ + + + + + + + + + + + +
    info:Don't change anything, print some info instead.
    here:Change all items at the cursor position. Requires in-game cursor.
    material, m:Change material. Must be followed by valid material RAW id.
    quality, q:Change base quality. Must be followed by number (0-5).
    force:Ignore subtypes, force change to new material.
    +
    +

    Examples:

    +
    +
    +
    changeitem m INORGANIC:GRANITE here
    +
    Change material of all items under the cursor to granite.
    +
    changeitem q 5
    +
    Change currently selected item to masterpiece quality.
    +
    +
    +
    +
    +

    colonies

    +

    Allows listing all the vermin colonies on the map and optionally turning them into honey bee colonies.

    +

    Options:

    +
    + +++ + + + +
    bees:turn colonies into honey bee colonies
    +
    +
    +
    +

    deramp (by zilpin)

    +

    Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation. +It also removes any and all 'down ramps' that can remain after a cave-in (you don't have to designate anything for that to happen).

    +
    +
    +

    feature

    +

    Enables management of map features.

    +
      +
    • Discovering a magma feature (magma pool, volcano, magma sea, or curious +underground structure) permits magma workshops and furnaces to be built.
    • +
    • Discovering a cavern layer causes plants (trees, shrubs, and grass) from +that cavern to grow within your fortress.
    • +
    +

    Options:

    +
    + +++ + + + + + + + +
    list:Lists all map features in your current embark by index.
    show X:Marks the selected map feature as discovered.
    hide X:Marks the selected map feature as undiscovered.
    +
    +
    +
    +

    liquids

    +

    Allows adding magma, water and obsidian to the game. It replaces the normal +dfhack command line and can't be used from a hotkey. Settings will be remembered +as long as dfhack runs. Intended for use in combination with the command +liquids-here (which can be bound to a hotkey).

    +

    For more information, refer to the command's internal help.

    +
    +

    Note

    +

    Spawning and deleting liquids can F up pathing data and +temperatures (creating heat traps). You've been warned.

    +
    +
    +
    +

    liquids-here

    +

    Run the liquid spawner with the current/last settings made in liquids (if no +settings in liquids were made it paints a point of 7/7 magma by default).

    +

    Intended to be used as keybinding. Requires an active in-game cursor.

    +
    +
    +

    tiletypes

    +

    Can be used for painting map tiles and is an interactive command, much like +liquids.

    +

    The tool works with two set of options and a brush. The brush determines which +tiles will be processed. First set of options is the filter, which can exclude +some of the tiles from the brush by looking at the tile properties. The second +set of options is the paint - this determines how the selected tiles are +changed.

    +

    Both paint and filter can have many different properties including things like +general shape (WALL, FLOOR, etc.), general material (SOIL, STONE, MINERAL, +etc.), state of 'designated', 'hidden' and 'light' flags.

    +

    The properties of filter and paint can be partially defined. This means that +you can for example do something like this:

    +
    +filter material STONE
    +filter shape FORTIFICATION
    +paint shape FLOOR
    +
    +

    This will turn all stone fortifications into floors, preserving the material.

    +

    Or this:

    +
    +filter shape FLOOR
    +filter material MINERAL
    +paint shape WALL
    +
    +

    Turning mineral vein floors back into walls.

    +

    The tool also allows tweaking some tile flags:

    +

    Or this:

    +
    +paint hidden 1
    +paint hidden 0
    +
    +

    This will hide previously revealed tiles (or show hidden with the 0 option).

    +

    Any paint or filter option (or the entire paint or filter) can be disabled entirely by using the ANY keyword:

    +
    +paint hidden ANY
    +paint shape ANY
    +filter material any
    +filter shape any
    +filter any
    +
    +
    +
    You can use several different brushes for painting tiles:
    +
      +
    • Point. (point)
    • +
    • Rectangular range. (range)
    • +
    • A column ranging from current cursor to the first solid tile above. (column)
    • +
    • DF map block - 16x16 tiles, in a regular grid. (block)
    • +
    +
    +
    +

    Example:

    +
    +range 10 10 1
    +
    +

    This will change the brush to a rectangle spanning 10x10 tiles on one z-level. +The range starts at the position of the cursor and goes to the east, south and +up.

    +

    For more details, see the 'help' command while using this.

    +
    +
    +

    tiletypes-commands

    +

    Runs tiletypes commands, separated by ;. This makes it possible to change +tiletypes modes from a hotkey.

    +
    +
    +

    tiletypes-here

    +

    Apply the current tiletypes options at the in-game cursor position, including +the brush. Can be used from a hotkey.

    +
    +
    +

    tiletypes-here-point

    +

    Apply the current tiletypes options at the in-game cursor position to a single +tile. Can be used from a hotkey.

    +
    +
    +

    tubefill

    +

    Fills all the adamantine veins again. Veins that were empty will be filled in +too, but might still trigger a demon invasion (this is a known bug).

    +
    +
    +

    extirpate

    +

    A tool for getting rid of trees and shrubs. By default, it only kills +a tree/shrub under the cursor. The plants are turned into ashes instantly.

    +

    Options:

    +
    + +++ + + + + + + + +
    shrubs:affect all shrubs on the map
    trees:affect all trees on the map
    all:affect every plant!
    +
    +
    +
    +

    grow

    +

    Makes all saplings present on the map grow into trees (almost) instantly.

    +
    +
    +

    immolate

    +

    Very similar to extirpate, but additionally sets the plants on fire. The fires +can and will spread ;)

    +
    +
    +

    regrass

    +

    Regrows grass. Not much to it ;)

    +
    +
    +

    weather

    +

    Prints the current weather map by default.

    +

    Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'.

    +

    Options:

    +
    + +++ + + + + + + + +
    snow:make it snow everywhere.
    rain:make it rain.
    clear:clear the sky.
    +
    +
    +
    +
    +

    Map inspection

    +
    +

    cursecheck

    +

    Checks a single map tile or the whole map/world for cursed creatures (ghosts, +vampires, necromancers, werebeasts, zombies).

    +

    With an active in-game cursor only the selected tile will be observed. +Without a cursor the whole map will be checked.

    +

    By default cursed creatures will be only counted in case you just want to find +out if you have any of them running around in your fort. Dead and passive +creatures (ghosts who were put to rest, killed vampires, ...) are ignored. +Undead skeletons, corpses, bodyparts and the like are all thrown into the curse +category "zombie". Anonymous zombies and resurrected body parts will show +as "unnamed creature".

    +

    Options:

    +
    + +++ + + + + + + + + + +
    detail:Print full name, date of birth, date of curse and some status +info (some vampires might use fake identities in-game, though).
    nick:Set the type of curse as nickname (does not always show up +in-game, some vamps don't like nicknames).
    all:Include dead and passive cursed creatures (can result in a quite +long list after having FUN with necromancers).
    verbose:Print all curse tags (if you really want to know it all).
    +
    +

    Examples:

    +
    +
    +
    cursecheck detail all
    +
    Give detailed info about all cursed creatures including deceased ones (no +in-game cursor).
    +
    cursecheck nick
    +
    Give a nickname all living/active cursed creatures on the map(no in-game +cursor).
    +
    +
    +
    +

    Note

    +
      +
    • If you do a full search (with the option "all") former ghosts will show up +with the cursetype "unknown" because their ghostly flag is not set +anymore. But if you happen to find a living/active creature with cursetype +"unknown" please report that in the dfhack thread on the modding forum or +per irc. This is likely to happen with mods which introduce new types +of curses, for example.
    • +
    +
    +
    +
    +

    flows

    +

    A tool for checking how many tiles contain flowing liquids. If you suspect that +your magma sea leaks into HFS, you can use this tool to be sure without +revealing the map.

    +
    +
    +

    probe

    +

    Can be used to determine tile properties like temperature.

    +
    +
    +

    prospect

    +

    Prints a big list of all the present minerals and plants. By default, only +the visible part of the map is scanned.

    +

    Options:

    +
    + +++ + + + + + + + +
    all:Scan the whole map, as if it was revealed.
    value:Show material value in the output. Most useful for gems.
    hell:Show the Z range of HFS tubes. Implies 'all'.
    +
    +
    +

    Pre-embark estimate

    +

    If prospect is called during the embark selection screen, it displays an estimate of +layer stone availability.

    +
    +

    Note

    +

    The results of pre-embark prospect are an estimate, and can at best be expected +to be somewhere within +/- 30% of the true amount; sometimes it does a lot worse. +Especially, it is not clear how to precisely compute how many soil layers there +will be in a given embark tile, so it can report a whole extra layer, or omit one +that is actually present.

    +
    +

    Options:

    +
    + +++ + + + +
    all:Also estimate vein mineral amounts.
    +
    +
    +
    +
    +

    reveal

    +

    This reveals the map. By default, HFS will remain hidden so that the demons +don't spawn. You can use 'reveal hell' to reveal everything. With hell revealed, +you won't be able to unpause until you hide the map again. If you really want +to unpause with hell revealed, use 'reveal demons'.

    +

    Reveal also works in adventure mode, but any of its effects are negated once +you move. When you use it this way, you don't need to run 'unreveal'.

    +
    +
    +

    unreveal

    +

    Reverts the effects of 'reveal'.

    +
    +
    +

    revtoggle

    +

    Switches between 'reveal' and 'unreveal'.

    +
    +
    +

    revflood

    +

    This command will hide the whole map and then reveal all the tiles that have +a path to the in-game cursor.

    +
    +
    +

    revforget

    +

    When you use reveal, it saves information about what was/wasn't visible before +revealing everything. Unreveal uses this information to hide things again. +This command throws away the information. For example, use in cases where +you abandoned with the fort revealed and no longer want the data.

    +
    +
    +

    showmood

    +

    Shows all items needed for the currently active strange mood.

    +
    +
    +
    +

    Designations

    +
    +

    burrow

    +

    Miscellaneous burrow control. Allows manipulating burrows and automated burrow +expansion while digging.

    +

    Options:

    +
    +
    +
    enable feature ...
    +
    Enable features of the plugin.
    +
    disable feature ...
    +
    Disable features of the plugin.
    +
    clear-unit burrow burrow ...
    +
    Remove all units from the burrows.
    +
    clear-tiles burrow burrow ...
    +
    Remove all tiles from the burrows.
    +
    set-units target-burrow src-burrow ...
    +
    Clear target, and adds units from source burrows.
    +
    add-units target-burrow src-burrow ...
    +
    Add units from the source burrows to the target.
    +
    remove-units target-burrow src-burrow ...
    +
    Remove units in source burrows from the target.
    +
    set-tiles target-burrow src-burrow ...
    +
    Clear target and adds tiles from the source burrows.
    +
    add-tiles target-burrow src-burrow ...
    +
    Add tiles from the source burrows to the target.
    +
    remove-tiles target-burrow src-burrow ...
    +

    Remove tiles in source burrows from the target.

    +

    For these three options, in place of a source burrow it is +possible to use one of the following keywords: ABOVE_GROUND, +SUBTERRANEAN, INSIDE, OUTSIDE, LIGHT, DARK, HIDDEN, REVEALED

    +
    +
    +
    +

    Features:

    +
    + +++ + + + +
    auto-grow:When a wall inside a burrow with a name ending in '+' is dug +out, the burrow is extended to newly-revealed adjacent walls. +This final '+' may be omitted in burrow name args of commands above. +Digging 1-wide corridors with the miner inside the burrow is SLOW.
    +
    +
    +
    +

    digv

    +

    Designates a whole vein for digging. Requires an active in-game cursor placed +over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs +between the same-material tiles).

    +
    +
    +

    digvx

    +

    A permanent alias for 'digv x'.

    +
    +
    +

    digl

    +

    Designates layer stone for digging. Requires an active in-game cursor placed +over a layer stone tile. With the 'x' option, it will traverse z-levels +(putting stairs between the same-material tiles). With the 'undo' option it +will remove the dig designation instead (if you realize that digging out a 50 +z-level deep layer was not such a good idea after all).

    +
    +
    +

    diglx

    +

    A permanent alias for 'digl x'.

    +
    +
    +

    digexp

    +

    This command can be used for exploratory mining.

    +

    See: http://df.magmawiki.com/index.php/DF2010:Exploratory_mining

    +

    There are two variables that can be set: pattern and filter.

    +

    Patterns:

    +
    + +++ + + + + + + + + + + + + + +
    diag5:diagonals separated by 5 tiles
    diag5r:diag5 rotated 90 degrees
    ladder:A 'ladder' pattern
    ladderr:ladder rotated 90 degrees
    clear:Just remove all dig designations
    cross:A cross, exactly in the middle of the map.
    +
    +

    Filters:

    +
    + +++ + + + + + + + +
    all:designate whole z-level
    hidden:designate only hidden tiles of z-level (default)
    designated:Take current designation and apply pattern to it.
    +
    +

    After you have a pattern set, you can use 'expdig' to apply it again.

    +

    Examples:

    +
    +
    +
    designate the diagonal 5 patter over all hidden tiles:
    +
      +
    • expdig diag5 hidden
    • +
    +
    +
    apply last used pattern and filter:
    +
      +
    • expdig
    • +
    +
    +
    Take current designations and replace them with the ladder pattern:
    +
      +
    • expdig ladder designated
    • +
    +
    +
    +
    +
    +
    +

    digcircle

    +

    A command for easy designation of filled and hollow circles. +It has several types of options.

    +

    Shape:

    +
    + +++ + + + + + + + +
    hollow:Set the circle to hollow (default)
    filled:Set the circle to filled
    #:Diameter in tiles (default = 0, does nothing)
    +
    +

    Action:

    +
    + +++ + + + + + + + +
    set:Set designation (default)
    unset:Unset current designation
    invert:Invert designations already present
    +
    +

    Designation types:

    +
    + +++ + + + + + + + + + + + + + +
    dig:Normal digging designation (default)
    ramp:Ramp digging
    ustair:Staircase up
    dstair:Staircase down
    xstair:Staircase up/down
    chan:Dig channel
    +
    +

    After you have set the options, the command called with no options +repeats with the last selected parameters.

    +

    Examples:

    +
      +
    • 'digcircle filled 3' = Dig a filled circle with radius = 3.
    • +
    • 'digcircle' = Do it again.
    • +
    +
    +
    +

    digtype

    +

    For every tile on the map of the same vein type as the selected tile, this command designates it to have the same designation as the selected tile. If the selected tile has no designation, they will be dig designated. +If an argument is given, the designation of the selected tile is ignored, and all appropriate tiles are set to the specified designation.

    +

    Options:

    +
    + +++ + + + + + + + + + + + + + + + +
    dig:
    channel:
    ramp:
    updown:up/down stairs
    up:up stairs
    down:down stairs
    clear:clear designation
    +
    +
    +
    +

    filltraffic

    +

    Set traffic designations using flood-fill starting at the cursor.

    +

    Traffic Type Codes:

    +
    + +++ + + + + + + + + + +
    H:High Traffic
    N:Normal Traffic
    L:Low Traffic
    R:Restricted Traffic
    +
    +

    Other Options:

    +
    + +++ + + + + + + + +
    X:Fill accross z-levels.
    B:Include buildings and stockpiles.
    P:Include empty space.
    +
    +

    Example:

    +
    +'filltraffic H' - When used in a room with doors, it will set traffic to HIGH in just that room.
    +
    +
    +

    alltraffic

    +

    Set traffic designations for every single tile of the map (useful for resetting traffic designations).

    +

    Traffic Type Codes:

    +
    + +++ + + + + + + + + + +
    H:High Traffic
    N:Normal Traffic
    L:Low Traffic
    R:Restricted Traffic
    +
    +

    Example:

    +
    +'alltraffic N' - Set traffic to 'normal' for all tiles.
    +
    +
    +

    getplants

    +

    This tool allows plant gathering and tree cutting by RAW ID. Specify the types +of trees to cut down and/or shrubs to gather by their plant names, separated +by spaces.

    +

    Options:

    +
    + +++ + + + + + + + + + +
    -t:Select trees only (exclude shrubs)
    -s:Select shrubs only (exclude trees)
    -c:Clear designations instead of setting them
    -x:Apply selected action to all plants except those specified (invert +selection)
    +
    +

    Specifying both -t and -s will have no effect. If no plant IDs are specified, +all valid plant IDs will be listed.

    +
    +
    +
    +

    Cleanup and garbage disposal

    +
    +

    clean

    +

    Cleans all the splatter that get scattered all over the map, items and +creatures. In an old fortress, this can significantly reduce FPS lag. It can +also spoil your !!FUN!!, so think before you use it.

    +

    Options:

    +
    + +++ + + + + + + + +
    map:Clean the map tiles. By default, it leaves mud and snow alone.
    units:Clean the creatures. Will also clean hostiles.
    items:Clean all the items. Even a poisoned blade.
    +
    +

    Extra options for 'map':

    +
    + +++ + + + + + +
    mud:Remove mud in addition to the normal stuff.
    snow:Also remove snow coverings.
    +
    +
    +
    +

    spotclean

    +

    Works like 'clean map snow mud', but only for the tile under the cursor. Ideal +if you want to keep that bloody entrance 'clean map' would clean up.

    +
    +
    +

    autodump

    +

    This utility lets you quickly move all items designated to be dumped. +Items are instantly moved to the cursor position, the dump flag is unset, +and the forbid flag is set, as if it had been dumped normally. +Be aware that any active dump item tasks still point at the item.

    +

    Cursor must be placed on a floor tile so the items can be dumped there.

    +

    Options:

    +
    + +++ + + + + + + + + + + + +
    destroy:Destroy instead of dumping. Doesn't require a cursor.
    destroy-here:Destroy items only under the cursor.
    visible:Only process items that are not hidden.
    hidden:Only process hidden items.
    forbidden:Only process forbidden items (default: only unforbidden).
    +
    +
    +
    +

    autodump-destroy-here

    +

    Destroy items marked for dumping under cursor. Identical to autodump +destroy-here, but intended for use as keybinding.

    +
    +
    +

    autodump-destroy-item

    +

    Destroy the selected item. The item may be selected in the 'k' list, or inside +a container. If called again before the game is resumed, cancels destroy.

    +
    +
    +

    cleanowned

    +

    Confiscates items owned by dwarfs. By default, owned food on the floor +and rotten items are confistacted and dumped.

    +

    Options:

    +
    + +++ + + + + + + + + + + + +
    all:confiscate all owned items
    scattered:confiscated and dump all items scattered on the floor
    x:confiscate/dump items with wear level 'x' and more
    X:confiscate/dump items with wear level 'X' and more
    dryrun:a dry run. combine with other options to see what will happen +without it actually happening.
    +
    +

    Example:

    +
    +
    +
    cleanowned scattered X
    +
    This will confiscate rotten and dropped food, garbage on the floors and any +worn items with 'X' damage and above.
    +
    +
    +
    +
    +
    +

    Bugfixes

    +
    +

    drybuckets

    +

    This utility removes water from all buckets in your fortress, allowing them to be safely used for making lye.

    +
    +
    +

    fixdiplomats

    +

    Up to version 0.31.12, Elves only sent Diplomats to your fortress to propose +tree cutting quotas due to a bug; once that bug was fixed, Elves stopped caring +about excess tree cutting. This command adds a Diplomat position to all Elven +civilizations, allowing them to negotiate tree cutting quotas (and allowing you +to violate them and potentially start wars) in case you haven't already modified +your raws accordingly.

    +
    +
    +

    fixmerchants

    +

    This command adds the Guild Representative position to all Human civilizations, +allowing them to make trade agreements (just as they did back in 0.28.181.40d +and earlier) in case you haven't already modified your raws accordingly.

    +
    +
    +

    fixveins

    +

    Removes invalid references to mineral inclusions and restores missing ones. +Use this if you broke your embark with tools like tiletypes, or if you +accidentally placed a construction on top of a valuable mineral floor.

    +
    +
    +

    tweak

    +

    Contains various tweaks for minor bugs.

    +

    One-shot subcommands:

    + +++ + + + + + + + + + +
    clear-missing:Remove the missing status from the selected unit. +This allows engraving slabs for ghostly, but not yet +found, creatures.
    clear-ghostly:Remove the ghostly status from the selected unit and mark +it as dead. This allows getting rid of bugged ghosts +which do not show up in the engraving slab menu at all, +even after using clear-missing. It works, but is +potentially very dangerous - so use with care. Probably +(almost certainly) it does not have the same effects like +a proper burial. You've been warned.
    fixmigrant:Remove the resident/merchant flag from the selected unit. +Intended to fix bugged migrants/traders who stay at the +map edge and don't enter your fort. Only works for +dwarves (or generally the player's race in modded games). +Do NOT abuse this for 'real' caravan merchants (if you +really want to kidnap them, use 'tweak makeown' instead, +otherwise they will have their clothes set to forbidden etc).
    makeown:Force selected unit to become a member of your fort. +Can be abused to grab caravan merchants and escorts, even if +they don't belong to the player's race. Foreign sentients +(humans, elves) can be put to work, but you can't assign rooms +to them and they don't show up in DwarfTherapist because the +game treats them like pets. Grabbing draft animals from +a caravan can result in weirdness (animals go insane or berserk +and are not flagged as tame), but you are allowed to mark them +for slaughter. Grabbing wagons results in some funny spam, then +they are scuttled.
    +

    Subcommands that persist until disabled or DF quit:

    + +++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    stable-cursor:Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode.
    patrol-duty:Makes Train orders not count as patrol duty to stop unhappy thoughts. +Does NOT fix the problem when soldiers go off-duty (i.e. civilian).
    readable-build-plate:
     Fixes rendering of creature weight limits in pressure plate build menu.
    stable-temp:Fixes performance bug 6012 by squashing jitter in temperature updates. +In very item-heavy forts with big stockpiles this can improve FPS by 50-100%
    fast-heat:Further improves temperature update performance by ensuring that 1 degree +of item temperature is crossed in no more than specified number of frames +when updating from the environment temperature. This reduces the time it +takes for stable-temp to stop updates again when equilibrium is disturbed.
    fix-dimensions:Fixes subtracting small amount of thread/cloth/liquid from a stack +by splitting the stack and subtracting from the remaining single item. +This is a necessary addition to the binary patch in bug 808.
    advmode-contained:
     Works around bug 6202, i.e. custom reactions with container inputs +in advmode. The issue is that the screen tries to force you to select +the contents separately from the container. This forcefully skips child +reagents.
    fast-trade:Makes Shift-Enter in the Move Goods to Depot and Trade screens select +the current item (fully, in case of a stack), and scroll down one line.
    military-stable-assign:
     Preserve list order and cursor position when assigning to squad, +i.e. stop the rightmost list of the Positions page of the military +screen from constantly resetting to the top.
    military-color-assigned:
     Color squad candidates already assigned to other squads in brown/green +to make them stand out more in the list.
    +
    +
    +
    +

    Mode switch and reclaim

    +
    +

    lair

    +

    This command allows you to mark the map as 'monster lair', preventing item +scatter on abandon. When invoked as 'lair reset', it does the opposite.

    +

    Unlike reveal, this command doesn't save the information about tiles - you +won't be able to restore state of real monster lairs using 'lair reset'.

    +

    Options:

    +
    + +++ + + + + + +
    lair:Mark the map as monster lair
    lair reset:Mark the map as ordinary (not lair)
    +
    +
    +
    +

    mode

    +

    This command lets you see and change the game mode directly. +Not all combinations are good for every situation and most of them will +produce undesirable results. There are a few good ones though.

    +
    +

    Example

    +

    You are in fort game mode, managing your fortress and paused. +You switch to the arena game mode, assume control of a creature and then +switch to adventure game mode(1). +You just lost a fortress and gained an adventurer. +You could also do this. +You are in fort game mode, managing your fortress and paused at the esc menu. +You switch to the adventure game mode, then use Dfusion to assume control of a creature and then +save or retire. +You just created a returnable mountain home and gained an adventurer.

    +
    +

    I take no responsibility of anything that happens as a result of using this tool

    +
    +
    +
    +

    Visualizer and data export

    +
    +

    ssense / stonesense

    +

    An isometric visualizer that runs in a second window. This requires working +graphics acceleration and at least a dual core CPU (otherwise it will slow +down DF).

    +

    All the data resides in the 'stonesense' directory. For detailed instructions, +see stonesense/README.txt

    +

    Compatible with Windows > XP SP3 and most modern Linux distributions.

    +

    Older versions, support and extra graphics can be found in the bay12 forum +thread: http://www.bay12forums.com/smf/index.php?topic=43260.0

    +

    Some additional resources: +http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository

    +
    +
    +

    mapexport

    +

    Export the current loaded map as a file. This will be eventually usable +with visualizers.

    +
    +
    +

    dwarfexport

    +

    Export dwarves to RuneSmith-compatible XML.

    +
    +
    +
    +

    Job management

    +
    +

    job

    +

    Command for general job query and manipulation.

    +
    +
    Options:
    +
    +
    no extra options
    +
    Print details of the current job. The job can be selected +in a workshop, or the unit/jobs screen.
    +
    list
    +
    Print details of all jobs in the selected workshop.
    +
    item-material <item-idx> <material[:subtoken]>
    +
    Replace the exact material id in the job item.
    +
    item-type <item-idx> <type[:subtype]>
    +
    Replace the exact item type id in the job item.
    +
    +
    +
    +
    +
    +

    job-material

    +

    Alter the material of the selected job.

    +

    Invoked as:

    +
    +job-material <inorganic-token>
    +
    +

    Intended to be used as a keybinding:

    +
    +
      +
    • In 'q' mode, when a job is highlighted within a workshop or furnace, +changes the material of the job. Only inorganic materials can be used +in this mode.
    • +
    • In 'b' mode, during selection of building components positions the cursor +over the first available choice with the matching material.
    • +
    +
    +
    +
    +

    job-duplicate

    +
    +
    Duplicate the selected job in a workshop:
    +
      +
    • In 'q' mode, when a job is highlighted within a workshop or furnace building, +instantly duplicates the job.
    • +
    +
    +
    +
    +
    +

    workflow

    +

    Manage control of repeat jobs.

    +

    Usage:

    +
    +
    +
    workflow enable [option...], workflow disable [option...]
    +

    If no options are specified, enables or disables the plugin. +Otherwise, enables or disables any of the following options:

    +
      +
    • drybuckets: Automatically empty abandoned water buckets.
    • +
    • auto-melt: Resume melt jobs when there are objects to melt.
    • +
    +
    +
    workflow jobs
    +
    List workflow-controlled jobs (if in a workshop, filtered by it).
    +
    workflow list
    +
    List active constraints, and their job counts.
    +
    workflow count <constraint-spec> <cnt-limit> [cnt-gap], workflow amount <constraint-spec> <cnt-limit> [cnt-gap]
    +
    Set a constraint. The first form counts each stack as only 1 item.
    +
    workflow unlimit <constraint-spec>
    +
    Delete a constraint.
    +
    +
    +
    +

    Function

    +

    When the plugin is enabled, it protects all repeat jobs from removal. +If they do disappear due to any cause, they are immediately re-added to their +workshop and suspended.

    +

    In addition, when any constraints on item amounts are set, repeat jobs that +produce that kind of item are automatically suspended and resumed as the item +amount goes above or below the limit. The gap specifies how much below the limit +the amount has to drop before jobs are resumed; this is intended to reduce +the frequency of jobs being toggled.

    +
    +
    +

    Constraint examples

    +

    Keep metal bolts within 900-1000, and wood/bone within 150-200.

    +
    +workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
    +workflow amount AMMO:ITEM_AMMO_BOLTS/WOOD,BONE 200 50
    +
    +

    Keep the number of prepared food & drink stacks between 90 and 120

    +
    +workflow count FOOD 120 30
    +workflow count DRINK 120 30
    +
    +

    Make sure there are always 25-30 empty bins/barrels/bags.

    +
    +workflow count BIN 30
    +workflow count BARREL 30
    +workflow count BOX/CLOTH,SILK,YARN 30
    +
    +

    Make sure there are always 15-20 coal and 25-30 copper bars.

    +
    +workflow count BAR//COAL 20
    +workflow count BAR//COPPER 30
    +
    +

    Collect 15-20 sand bags and clay boulders.

    +
    +workflow count POWDER_MISC/SAND 20
    +workflow count BOULDER/CLAY 20
    +
    +

    Make sure there are always 80-100 units of dimple dye.

    +
    +  workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20
    +
    +In order for this to work, you have to set the material of the PLANT input
    +on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material'
    +command.
    +
    +
    +
    +
    +
    +

    Fortress activity management

    +
    +

    seedwatch

    +

    Tool for turning cooking of seeds and plants on/off depending on how much you +have of them.

    +

    See 'seedwatch help' for detailed description.

    +
    +
    +

    zone

    +

    Helps a bit with managing activity zones (pens, pastures and pits) and cages.

    +

    Options:

    +
    + +++ + + + + + + + + + + + + + + + + + + + + + + + + + +
    set:Set zone or cage under cursor as default for future assigns.
    assign:Assign unit(s) to the pen or pit marked with the 'set' command. +If no filters are set a unit must be selected in the in-game ui. +Can also be followed by a valid zone id which will be set +instead.
    unassign:Unassign selected creature from it's zone.
    nick:Mass-assign nicknames, must be followed by the name you want +to set.
    remnick:Mass-remove nicknames.
    tocages:Assign unit(s) to cages inside a pasture.
    uinfo:Print info about unit(s). If no filters are set a unit must +be selected in the in-game ui.
    zinfo:Print info about zone(s). If no filters are set zones under +the cursor are listed.
    verbose:Print some more info.
    filters:Print list of valid filter options.
    examples:Print some usage examples.
    not:Negates the next filter keyword.
    +
    +

    Filters:

    +
    + +++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    all:Process all units (to be used with additional filters).
    count:Must be followed by a number. Process only n units (to be used +with additional filters).
    unassigned:Not assigned to zone, chain or built cage.
    minage:Minimum age. Must be followed by number.
    maxage:Maximum age. Must be followed by number.
    race:Must be followed by a race RAW ID (e.g. BIRD_TURKEY, ALPACA, +etc). Negatable.
    caged:In a built cage. Negatable.
    own:From own civilization. Negatable.
    merchant:Is a merchant / belongs to a merchant. Should only be used for +pitting, not for stealing animals (slaughter should work).
    war:Trained war creature. Negatable.
    hunting:Trained hunting creature. Negatable.
    tamed:Creature is tame. Negatable.
    trained:Creature is trained. Finds war/hunting creatures as well as +creatures who have a training level greater than 'domesticated'. +If you want to specifically search for war/hunting creatures use +'war' or 'hunting' Negatable.
    trainablewar:Creature can be trained for war (and is not already trained for +war/hunt). Negatable.
    trainablehunt:Creature can be trained for hunting (and is not already trained +for war/hunt). Negatable.
    male:Creature is male. Negatable.
    female:Creature is female. Negatable.
    egglayer:Race lays eggs. Negatable.
    grazer:Race is a grazer. Negatable.
    milkable:Race is milkable. Negatable.
    +
    +
    +

    Usage with single units

    +

    One convenient way to use the zone tool is to bind the command 'zone assign' to +a hotkey, maybe also the command 'zone set'. Place the in-game cursor over +a pen/pasture or pit, use 'zone set' to mark it. Then you can select units +on the map (in 'v' or 'k' mode), in the unit list or from inside cages +and use 'zone assign' to assign them to their new home. Allows pitting your +own dwarves, by the way.

    +
    +
    +

    Usage with filters

    +

    All filters can be used together with the 'assign' command.

    +

    Restrictions: It's not possible to assign units who are inside built cages +or chained because in most cases that won't be desirable anyways. +It's not possible to cage owned pets because in that case the owner +uncages them after a while which results in infinite hauling back and forth.

    +

    Usually you should always use the filter 'own' (which implies tame) unless you +want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless +you specify 'race DWARF' (so it's safe to use 'assign all own' to one big +pasture if you want to have all your animals at the same place). 'egglayer' and +'milkable' should be used together with 'female' unless you have a mod with +egg-laying male elves who give milk or whatever. Merchants and their animals are +ignored unless you specify 'merchant' (pitting them should be no problem, +but stealing and pasturing their animals is not a good idea since currently they +are not properly added to your own stocks; slaughtering them should work).

    +

    Most filters can be negated (e.g. 'not grazer' -> race is not a grazer).

    +
    +
    +

    Mass-renaming

    +

    Using the 'nick' command you can set the same nickname for multiple units. +If used without 'assign', 'all' or 'count' it will rename all units in the +current default target zone. Combined with 'assign', 'all' or 'count' (and +further optional filters) it will rename units matching the filter conditions.

    +
    +
    +

    Cage zones

    +

    Using the 'tocages' command you can assign units to a set of cages, for example +a room next to your butcher shop(s). They will be spread evenly among available +cages to optimize hauling to and butchering from them. For this to work you need +to build cages and then place one pen/pasture activity zone above them, covering +all cages you want to use. Then use 'zone set' (like with 'assign') and use +'zone tocages filter1 filter2 ...'. 'tocages' overwrites 'assign' because it +would make no sense, but can be used together with 'nick' or 'remnick' and all +the usual filters.

    +
    +
    +

    Examples

    +
    +
    zone assign all own ALPACA minage 3 maxage 10
    +
    Assign all own alpacas who are between 3 and 10 years old to the selected +pasture.
    +
    zone assign all own caged grazer nick ineedgrass
    +
    Assign all own grazers who are sitting in cages on stockpiles (e.g. after +buying them from merchants) to the selected pasture and give them +the nickname 'ineedgrass'.
    +
    zone assign all own not grazer not race CAT
    +
    Assign all own animals who are not grazers, excluding cats.
    +
    zone assign count 5 own female milkable
    +
    Assign up to 5 own female milkable creatures to the selected pasture.
    +
    zone assign all own race DWARF maxage 2
    +
    Throw all useless kids into a pit :)
    +
    zone nick donttouchme
    +
    Nicknames all units in the current default zone or cage to 'donttouchme'. +Mostly intended to be used for special pastures or cages which are not marked +as rooms you want to protect from autobutcher.
    +
    zone tocages count 50 own tame male not grazer
    +
    Stuff up to 50 owned tame male animals who are not grazers into cages built +on the current default zone.
    +
    +
    +
    +
    +

    autonestbox

    +

    Assigns unpastured female egg-layers to nestbox zones. Requires that you create +pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox +must be in the top left corner. Only 1 unit will be assigned per pen, regardless +of the size. The age of the units is currently not checked, most birds grow up +quite fast. Egglayers who are also grazers will be ignored, since confining them +to a 1x1 pasture is not a good idea. Only tame and domesticated own units are +processed since pasturing half-trained wild egglayers could destroy your neat +nestbox zones when they revert to wild. When called without options autonestbox +will instantly run once.

    +

    Options:

    +
    + +++ + + + + + + + +
    start:Start running every X frames (df simulation ticks). +Default: X=6000, which would be every 60 seconds at 100fps.
    stop:Stop running automatically.
    sleep:Must be followed by number X. Changes the timer to sleep X +frames between runs.
    +
    +
    +
    +

    autobutcher

    +

    Assigns lifestock for slaughter once it reaches a specific count. Requires that +you add the target race(s) to a watch list. Only tame units will be processed.

    +

    Named units will be completely ignored (to protect specific animals from +autobutcher you can give them nicknames with the tool 'rename unit' for single +units or with 'zone nick' to mass-rename units in pastures and cages).

    +

    Creatures trained for war or hunting will be ignored as well.

    +

    Creatures assigned to cages will be ignored if the cage is defined as a room +(to avoid butchering unnamed zoo animals).

    +

    Once you have too much adults, the oldest will be butchered first. +Once you have too much kids, the youngest will be butchered first. +If you don't set any target count the following default will be used: +1 male kid, 5 female kids, 1 male adult, 5 female adults.

    +

    Options:

    +
    + +++ + + + + + + + + + + + + + + + + + + + + + + + + + + +
    start:Start running every X frames (df simulation ticks). +Default: X=6000, which would be every 60 seconds at 100fps.
    stop:Stop running automatically.
    sleep:Must be followed by number X. Changes the timer to sleep +X frames between runs.
    watch R:Start watching a race. R can be a valid race RAW id (ALPACA, +BIRD_TURKEY, etc) or a list of ids seperated by spaces or +the keyword 'all' which affects all races on your current +watchlist.
    unwatch R:Stop watching race(s). The current target settings will be +remembered. R can be a list of ids or the keyword 'all'.
    forget R:Stop watching race(s) and forget it's/their target settings. +R can be a list of ids or the keyword 'all'.
    autowatch:Automatically adds all new races (animals you buy from merchants, +tame yourself or get from migrants) to the watch list using +default target count.
    noautowatch:Stop auto-adding new races to the watchlist.
    list:Print the current status and watchlist.
    list_export:Print status and watchlist in a format which can be used +to import them to another savegame (see notes).
    target fk mk fa ma R:
     Set target count for specified race(s). +fk = number of female kids, +mk = number of male kids, +fa = number of female adults, +ma = number of female adults. +R can be a list of ids or the keyword 'all' or 'new'. +R = 'all': change target count for all races on watchlist +and set the new default for the future. R = 'new': don't touch +current settings on the watchlist, only set the new default +for future entries.
    example:Print some usage examples.
    +
    +

    Examples:

    +

    You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, +1 male) of the race alpaca. Once the kids grow up the oldest adults will get +slaughtered. Excess kids will get slaughtered starting with the youngest +to allow that the older ones grow into adults. Any unnamed cats will +be slaughtered as soon as possible.

    +
    +autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY
    +autobutcher target 0 0 0 0 CAT
    +autobutcher watch ALPACA BIRD_TURKEY CAT
    +autobutcher start
    +
    +

    Automatically put all new races onto the watchlist and mark unnamed tame units +for slaughter as soon as they arrive in your fort. Settings already made +for specific races will be left untouched.

    +
    +autobutcher target 0 0 0 0 new
    +autobutcher autowatch
    +autobutcher start
    +
    +

    Stop watching the races alpaca and cat, but remember the target count +settings so that you can use 'unwatch' without the need to enter the +values again. Note: 'autobutcher unwatch all' works, but only makes sense +if you want to keep the plugin running with the 'autowatch' feature or manually +add some new races with 'watch'. If you simply want to stop it completely use +'autobutcher stop' instead.

    +
    +autobutcher unwatch ALPACA CAT
    +
    +

    Note:

    +

    Settings and watchlist are stored in the savegame, so that you can have +different settings for each world. If you want to copy your watchlist to +another savegame you can use the command list_export:

    +
    +Load savegame where you made the settings.
    +Start a CMD shell and navigate to the df directory. Type the following into the shell:
    +dfhack-run autobutcher list_export > autobutcher.bat
    +Load the savegame where you want to copy the settings to, run the batch file (from the shell):
    +autobutcher.bat
    +
    +
    +
    +

    autolabor

    +

    Automatically manage dwarf labors.

    +

    When enabled, autolabor periodically checks your dwarves and enables or +disables labors. It tries to keep as many dwarves as possible busy but +also tries to have dwarves specialize in specific skills.

    +
    +

    Note

    +

    Warning: autolabor will override any manual changes you make to labors +while it is enabled.

    +
    +

    For detailed usage information, see 'help autolabor'.

    +
    +
    +
    +

    Other

    +
    +

    catsplosion

    +

    Makes cats just multiply. It is not a good idea to run this more than once or +twice.

    +
    +
    +

    dfusion

    +

    This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin.

    +

    See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15

    +

    Confirmed working DFusion plugins:

    + +++ + + + +
    simple_embark:allows changing the number of dwarves available on embark.
    +
    +

    Note

    +
      +
    • Some of the DFusion plugins aren't completely ported yet. This can lead to crashes.
    • +
    • This is currently working only on Windows.
    • +
    • The game will be suspended while you're using dfusion. Don't panic when it doen't respond.
    • +
    +
    +
    +
    +

    misery

    +

    When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).

    +

    Usage:

    + +++ + + + + + + + + + + + + +
    misery enable n:
     enable misery with optional magnitude n. If specified, n must be positive.
    misery n:same as "misery enable n"
    misery enable:same as "misery enable 2"
    misery disable:stop adding new negative thoughts. This will not remove existing duplicated thoughts. Equivalent to "misery 1"
    misery clear:remove fake thoughts added in this session of DF. Saving makes them permanent! Does not change factor.
    +
    +
    +
    +
    +

    Scripts

    +

    Lua or ruby scripts placed in the hack/scripts/ directory are considered for +execution as if they were native DFHack commands. They are listed at the end +of the 'ls' command output.

    +

    Note: scripts in subdirectories of hack/scripts/ can still be called, but will +only be listed by ls if called as 'ls -a'. This is intended as a way to hide +scripts that are obscure, developer-oriented, or should be used as keybindings.

    +

    Some notable scripts:

    +
    +

    fix/*

    +

    Scripts in this subdirectory fix various bugs and issues, some of them obscure.

    +
      +
    • fix/dead-units

      +

      Removes uninteresting dead units from the unit list. Doesn't seem to give any +noticeable performance gain, but migrants normally stop if the unit list grows +to around 3000 units, and this script reduces it back.

      +
    • +
    • fix/population-cap

      +

      Run this after every migrant wave to ensure your population cap is not exceeded. +The issue with the cap is that it is compared to the population number reported +by the last caravan, so once it drops below the cap, migrants continue to come +until that number is updated again.

      +
    • +
    • fix/stable-temp

      +

      Instantly sets the temperature of all free-lying items to be in equilibrium with +the environment and stops temperature updates. In order to maintain this efficient +state however, use tweak stable-temp and tweak fast-heat.

      +
    • +
    • fix/item-occupancy

      +

      Diagnoses and fixes issues with nonexistant 'items occupying site', usually +caused by autodump bugs or other hacking mishaps.

      +
    • +
    +
    +
    +

    gui/*

    +

    Scripts that implement dialogs inserted into the main game window are put in this +directory.

    +
    +
    +

    quicksave

    +

    If called in dwarf mode, makes DF immediately auto-save the game by setting a flag +normally used in seasonal auto-save.

    +
    +
    +

    setfps

    +

    Run setfps <number> to set the FPS cap at runtime, in case you want to watch +combat in slow motion or something :)

    +
    +
    +

    siren

    +

    Wakes up sleeping units, cancels breaks and stops parties either everywhere, +or in the burrows given as arguments. In return, adds bad thoughts about +noise, tiredness and lack of protection. Also, the units with interrupted +breaks will go on break again a lot sooner. The script is intended for +emergencies, e.g. when a siege appears, and all your military is partying.

    +
    +
    +

    growcrops

    +

    Instantly grow seeds inside farming plots.

    +

    With no argument, this command list the various seed types currently in +use in your farming plots. +With a seed type, the script will grow 100 of these seeds, ready to be +harvested. You can change the number with a 2nd argument.

    +

    For exemple, to grow 40 plump helmet spawn:

    +
    +growcrops plump 40
    +
    +
    +
    +

    removebadthoughts

    +

    This script remove negative thoughts from your dwarves. Very useful against +tantrum spirals.

    +

    With a selected unit in 'v' mode, will clear this unit's mind, otherwise +clear all your fort's units minds.

    +

    Individual dwarf happiness may not increase right after this command is run, +but in the short term your dwarves will get much more joyful. +The thoughts are set to be very old, and the game will remove them soon when +you unpause.

    +

    With the optional -v parameter, the script will dump the negative thoughts +it removed.

    +
    +
    +

    slayrace

    +

    Kills any unit of a given race.

    +

    With no argument, lists the available races.

    +

    With the special argument 'him', targets only the selected creature.

    +

    Any non-dead non-caged unit of the specified race gets its blood_count +set to 0, which means immediate death at the next game tick. For creatures +such as vampires, also set animal.vanish_countdown to 2.

    +

    An alternate mode is selected by adding a 2nd argument to the command, +magma. In this case, a column of 7/7 magma is generated on top of the +targets until they die (Warning: do not call on magma-safe creatures. Also, +using this mode for birds is not recommanded.)

    +

    Will target any unit on a revealed tile of the map, including ambushers.

    +

    Ex:

    +
    +slayrace gob
    +
    +

    To kill a single creature, select the unit with the 'v' cursor and:

    +
    +slayrace him
    +
    +

    To purify all elves on the map with fire (may have side-effects):

    +
    +slayrace elve magma
    +
    +
    +
    +

    magmasource

    +

    Create an infinite magma source on a tile.

    +

    This script registers a map tile as a magma source, and every 12 game ticks +that tile receives 1 new unit of flowing magma.

    +

    Place the game cursor where you want to create the source (must be a +flow-passable tile, and not too high in the sky) and call:

    +
    +magmasource here
    +
    +

    To add more than 1 unit everytime, call the command again.

    +

    To delete one source, place the cursor over its tile and use delete-here. +To remove all placed sources, call magmasource stop.

    +

    With no argument, this command shows an help message and list existing sources.

    +
    +
    +

    digfort

    +

    A script to designate an area for digging according to a plan in csv format.

    +

    This script, inspired from quickfort, can designate an area for digging. +Your plan should be stored in a .csv file like this:

    +
    +# this is a comment
    +d;d;u;d;d;skip this tile;d
    +d;d;d;i
    +
    +

    Available tile shapes are named after the 'dig' menu shortcuts: +d for dig, u for upstairs, d downstairs, i updown, +h channel, r upward ramp, x remove designation. +Unrecognized characters are ignored (eg the 'skip this tile' in the sample).

    +

    Empty lines and data after a # are ignored as comments. +To skip a row in your design, use a single ;.

    +

    The script takes the plan filename, starting from the root df folder.

    +
    +
    +

    superdwarf

    +

    Similar to fastdwarf, per-creature.

    +

    To make any creature superfast, target it ingame using 'v' and:

    +
    +superdwarf add
    +
    +

    Other options available: del, clear, list.

    +

    This plugin also shortens the 'sleeping' and 'on break' periods of targets.

    +
    +
    +

    drainaquifer

    +

    Remove all 'aquifer' tag from the map blocks. Irreversible.

    +
    +
    +

    deathcause

    +

    Focus a body part ingame, and this script will display the cause of death of +the creature.

    +
    +
    +
    +

    In-game interface tools

    +

    These tools work by displaying dialogs or overlays in the game window, and +are mostly implemented by lua scripts.

    +
    +

    Note

    +

    In order to avoid user confusion, as a matter of policy all these tools +display the word "DFHack" on the screen somewhere while active.

    +

    As an exception, the tweak plugin described above does not follow this +guideline because it arguably just fixes small usability bugs in the game UI.

    +
    +
    +

    Dwarf Manipulator

    +

    Implemented by the manipulator plugin. To activate, open the unit screen and +press 'l'.

    +

    This tool implements a Dwarf Therapist-like interface within the game UI. The +far left column displays the unit's Happiness (color-coded based on its +value), and the right half of the screen displays each dwarf's labor settings +and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand +Master, and U-Z for Legendary thru Legendary+5). Cells with red backgrounds +denote skills not controlled by labors.

    +

    Use the arrow keys or number pad to move the cursor around, holding Shift to +move 10 tiles at a time.

    +

    Press the Z-Up (<) and Z-Down (>) keys to move quickly between labor/skill +categories. The numpad Z-Up and Z-Down keys seek to the first or last unit +in the list. Backspace seeks to the top left corner.

    +

    Press Enter to toggle the selected labor for the selected unit, or Shift+Enter +to toggle all labors within the selected category.

    +

    Press the +- keys to sort the unit list according to the currently selected +skill/labor, and press the */ keys to sort the unit list by Name, Profession, +Happiness, or Arrival order (using Tab to select which sort method to use here).

    +

    With a unit selected, you can press the "v" key to view its properties (and +possibly set a custom nickname or profession) or the "c" key to exit +Manipulator and zoom to its position within your fortress.

    +

    The following mouse shortcuts are also available:

    +
      +
    • Click on a column header to sort the unit list. Left-click to sort it in one +direction (descending for happiness or labors/skills, ascending for name or +profession) and right-click to sort it in the opposite direction.
    • +
    • Left-click on a labor cell to toggle that labor. Right-click to move the +cursor onto that cell instead of toggling it.
    • +
    • Left-click on a unit's name or profession to view its properties.
    • +
    • Right-click on a unit's name or profession to zoom to it.
    • +
    +

    Pressing ESC normally returns to the unit screen, but Shift-ESC would exit +directly to the main dwarf mode screen.

    +
    +
    +

    gui/liquids

    +

    To use, bind to a key and activate in the 'k' mode.

    +

    While active, use the suggested keys to switch the usual liquids parameters, and Enter +to select the target area and apply changes.

    +
    +
    +

    gui/mechanisms

    +

    To use, bind to a key and activate in the 'q' mode.

    +

    Lists mechanisms connected to the building, and their links. Navigating the list centers +the view on the relevant linked buildings.

    +

    To exit, press ESC or Enter; ESC recenters on the original building, while Enter leaves +focus on the current one. Shift-Enter has an effect equivalent to pressing Enter, and then +re-entering the mechanisms ui.

    +
    +
    +

    gui/rename

    +

    Backed by the rename plugin, this script allows entering the desired name +via a simple dialog in the game ui.

    +
      +
    • gui/rename [building] in 'q' mode changes the name of a building.

      +

      The selected building must be one of stockpile, workshop, furnace, trap, or siege engine. +It is also possible to rename zones from the 'i' menu.

      +
    • +
    • gui/rename [unit] with a unit selected changes the nickname.

      +
    • +
    • gui/rename unit-profession changes the selected unit's custom profession name.

      +
    • +
    +

    The building or unit options are automatically assumed when in relevant ui state.

    +
    +
    +

    gui/room-list

    +

    To use, bind to a key and activate in the 'q' mode, either immediately or after opening +the assign owner page.

    +

    The script lists other rooms owned by the same owner, or by the unit selected in the assign +list, and allows unassigning them.

    +
    +
    +

    gui/choose-weapons

    +

    Bind to a key, and activate in the Equip->View/Customize page of the military screen.

    +

    Depending on the cursor location, it rewrites all 'individual choice weapon' entries +in the selected squad or position to use a specific weapon type matching the assigned +unit's top skill. If the cursor is in the rightmost list over a weapon entry, it rewrites +only that entry, and does it even if it is not 'individual choice'.

    +

    Rationale: individual choice seems to be unreliable when there is a weapon shortage, +and may lead to inappropriate weapons being selected.

    +
    +
    +
    +

    Behavior Mods

    +

    These plugins, when activated via configuration UI or by detecting certain +structures in RAWs, modify the game engine behavior concerning the target +objects to add features not otherwise present.

    +
    +

    DISCLAIMER

    +

    The plugins in this section have mostly been created for fun as an interesting +technical challenge, and do not represent any long-term plans to produce more +similar modifications of the game.

    +
    +
    +

    Siege Engine

    +

    The siege-engine plugin enables siege engines to be linked to stockpiles, and +aimed at an arbitrary rectangular area across Z levels, instead of the original +four directions. Also, catapults can be ordered to load arbitrary objects, not +just stones.

    +
    +

    Rationale

    +

    Siege engines are a very interesting feature, but sadly almost useless in the current state +because they haven't been updated since 2D and can only aim in four directions. This is an +attempt to bring them more up to date until Toady has time to work on it. Actual improvements, +e.g. like making siegers bring their own, are something only Toady can do.

    +
    +
    +

    Configuration UI

    +

    The configuration front-end to the plugin is implemented by the gui/siege-engine +script. Bind it to a key and activate after selecting a siege engine in 'q' mode.

    +

    The main mode displays the current target, selected ammo item type, linked stockpiles and +the allowed operator skill range. The map tile color is changed to signify if it can be +hit by the selected engine: green for fully reachable, blue for out of range, red for blocked, +yellow for partially blocked.

    +

    Pressing 'r' changes into the target selection mode, which works by highlighting two points +with Enter like all designations. When a target area is set, the engine projectiles are +aimed at that area, or units within it (this doesn't actually change the original aiming +code, instead the projectile trajectory parameters are rewritten as soon as it appears).

    +

    After setting the target in this way for one engine, you can 'paste' the same area into others +just by pressing 'p' in the main page of this script. The area to paste is kept until you quit +DF, or select another area manually.

    +

    Pressing 't' switches to a mode for selecting a stockpile to take ammo from.

    +

    Exiting from the siege engine script via ESC reverts the view to the state prior to starting +the script. Shift-ESC retains the current viewport, and also exits from the 'q' mode to main +menu.

    +
    +
    +
    +

    Power Meter

    +

    The power-meter plugin implements a modified pressure plate that detects power being +supplied to gear boxes built in the four adjacent N/S/W/E tiles.

    +

    The configuration front-end is implemented by the gui/power-meter script. Bind it to a +key and activate after selecting Pressure Plate in the build menu.

    +

    The script follows the general look and feel of the regular pressure plate build +configuration page, but configures parameters relevant to the modded power meter building.

    +
    +
    +

    Steam Engine

    +

    The steam-engine plugin detects custom workshops with STEAM_ENGINE in +their token, and turns them into real steam engines.

    +
    +

    Rationale

    +

    The vanilla game contains only water wheels and windmills as sources of +power, but windmills give relatively little power, and water wheels require +flowing water, which must either be a real river and thus immovable and +limited in supply, or actually flowing and thus laggy.

    +

    Steam engines are an alternative to water reactors that actually makes +sense, and hopefully doesn't lag. Also, unlike e.g. animal treadmills, +it can be done just by combining existing features of the game engine +in a new way with some glue code and a bit of custom logic.

    +
    +
    +

    Construction

    +

    The workshop needs water as its input, which it takes via a +passable floor tile below it, like usual magma workshops do. +The magma version also needs magma.

    +
    +

    ISSUE

    +

    Since this building is a machine, and machine collapse +code cannot be hooked, it would collapse over true open space. +As a loophole, down stair provides support to machines, while +being passable, so use them.

    +
    +

    After constructing the building itself, machines can be connected +to the edge tiles that look like gear boxes. Their exact position +is extracted from the workshop raws.

    +
    +

    ISSUE

    +

    Like with collapse above, part of the code involved in +machine connection cannot be hooked. As a result, the workshop +can only immediately connect to machine components built AFTER it. +This also means that engines cannot be chained without intermediate +short axles that can be built later than both of the engines.

    +
    +
    +
    +

    Operation

    +

    In order to operate the engine, queue the Stoke Boiler job (optionally +on repeat). A furnace operator will come, possibly bringing a bar of fuel, +and perform it. As a result, a "boiling water" item will appear +in the 't' view of the workshop.

    +
    +

    Note

    +

    The completion of the job will actually consume one unit +of the appropriate liquids from below the workshop. This means +that you cannot just raise 7 units of magma with a piston and +have infinite power. However, liquid consumption should be slow +enough that water can be supplied by a pond zone bucket chain.

    +
    +

    Every such item gives 100 power, up to a limit of 300 for coal, +and 500 for a magma engine. The building can host twice that +amount of items to provide longer autonomous running. When the +boiler gets filled to capacity, all queued jobs are suspended; +once it drops back to 3+1 or 5+1 items, they are re-enabled.

    +

    While the engine is providing power, steam is being consumed. +The consumption speed includes a fixed 10% waste rate, and +the remaining 90% are applied proportionally to the actual +load in the machine. With the engine at nominal 300 power with +150 load in the system, it will consume steam for actual +300*(10% + 90%*150/300) = 165 power.

    +

    Masterpiece mechanism and chain will decrease the mechanical +power drawn by the engine itself from 10 to 5. Masterpiece +barrel decreases waste rate by 4%. Masterpiece piston and pipe +decrease it by further 4%, and also decrease the whole steam +use rate by 10%.

    +
    +
    +

    Explosions

    +

    The engine must be constructed using barrel, pipe and piston +from fire-safe, or in the magma version magma-safe metals.

    +

    During operation weak parts get gradually worn out, and +eventually the engine explodes. It should also explode if +toppled during operation by a building destroyer, or a +tantruming dwarf.

    +
    +
    +

    Save files

    +

    It should be safe to load and view engine-using fortresses +from a DF version without DFHack installed, except that in such +case the engines won't work. However actually making modifications +to them, or machines they connect to (including by pulling levers), +can easily result in inconsistent state once this plugin is +available again. The effects may be as weird as negative power +being generated.

    +
    +
    +
    +

    Add Spatter

    +

    This plugin makes reactions with names starting with SPATTER_ADD_ +produce contaminants on the items instead of improvements. The produced +contaminants are immune to being washed away by water or destroyed by +the clean items command.

    +

    The plugin is intended to give some use to all those poisons that can +be bought from caravans. :)

    +

    To be really useful this needs patches from bug 808, tweak fix-dimensions +and tweak advmode-contained.

    +
    +
    +
    + + diff --git a/fixTexts.sh b/fixTexts.sh new file mode 100755 index 000000000..e8d890448 --- /dev/null +++ b/fixTexts.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# this script is used for easy testing of the rst documentation files. +rst2html Readme.rst Readme.html +rst2html Compile.rst Compile.html +rst2html LUA\ Api.rst LUA\ Api.html +rst2html Contributors.rst > Contributors.html diff --git a/testTexts.sh b/testTexts.sh deleted file mode 100755 index 0189ae2a2..000000000 --- a/testTexts.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -# this script is used for easy testing of the rst documentation files. -rst2html README.rst > README.html -rst2html COMPILE.rst > COMPILE.html -rst2html LUA_API.rst > LUA_API.html From bb39b2e45bdbe8fda2f99607e9ca56319a602843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 30 Sep 2012 06:55:52 +0200 Subject: [PATCH 093/196] Lua API, not LUA Api. --- CMakeLists.txt | 2 +- LUA Api.html => Lua API.html | 0 LUA Api.rst => Lua API.rst | 0 fixTexts.sh | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename LUA Api.html => Lua API.html (100%) rename LUA Api.rst => Lua API.rst (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index c1ced670c..14436dcff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,7 +171,7 @@ find_package(Docutils) IF(BUILD_LIBRARY) add_subdirectory (library) ## install the default documentation files - install(FILES LICENSE "LUA Api.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) + install(FILES LICENSE "Lua API.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) endif() #build the plugins diff --git a/LUA Api.html b/Lua API.html similarity index 100% rename from LUA Api.html rename to Lua API.html diff --git a/LUA Api.rst b/Lua API.rst similarity index 100% rename from LUA Api.rst rename to Lua API.rst diff --git a/fixTexts.sh b/fixTexts.sh index e8d890448..917199411 100755 --- a/fixTexts.sh +++ b/fixTexts.sh @@ -2,5 +2,5 @@ # this script is used for easy testing of the rst documentation files. rst2html Readme.rst Readme.html rst2html Compile.rst Compile.html -rst2html LUA\ Api.rst LUA\ Api.html +rst2html Lua\ API.rst Lua\ API.html rst2html Contributors.rst > Contributors.html From 47e04863eb1bd04c1914afb2e354d65088e81e7c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 30 Sep 2012 09:02:14 +0400 Subject: [PATCH 094/196] Stop fixTexts.sh from rebuilding html files that didn't change. --- fixTexts.sh | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/fixTexts.sh b/fixTexts.sh index 917199411..689dab8e2 100755 --- a/fixTexts.sh +++ b/fixTexts.sh @@ -1,6 +1,17 @@ #!/bin/bash + +cd `dirname $0` + +function process() { + if [ "$1" -nt "$2" ]; then + rst2html "$1" "$2" + else + echo "$2 - up to date." + fi +} + # this script is used for easy testing of the rst documentation files. -rst2html Readme.rst Readme.html -rst2html Compile.rst Compile.html -rst2html Lua\ API.rst Lua\ API.html -rst2html Contributors.rst > Contributors.html +process Readme.rst Readme.html +process Compile.rst Compile.html +process Lua\ API.rst Lua\ API.html +process Contributors.rst Contributors.html From 4de245d6911cf0e53c01d89aed3a18b285ce2168 Mon Sep 17 00:00:00 2001 From: jj Date: Sun, 30 Sep 2012 17:08:03 +0200 Subject: [PATCH 095/196] ruby: install generated autogen after copying *.rb --- plugins/ruby/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/CMakeLists.txt b/plugins/ruby/CMakeLists.txt index b8c90d134..9d821f941 100644 --- a/plugins/ruby/CMakeLists.txt +++ b/plugins/ruby/CMakeLists.txt @@ -34,8 +34,8 @@ ADD_DEPENDENCIES(ruby ruby-autogen-rb) INSTALL(FILES ${RUBYLIB} DESTINATION ${DFHACK_LIBRARY_DESTINATION}) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.rb DESTINATION hack/ruby) - INSTALL(DIRECTORY . DESTINATION hack/ruby FILES_MATCHING PATTERN "*.rb") + +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/ruby-autogen.rb DESTINATION hack/ruby) From 67adf2dde3c505fa57b10463c19bda2b4780b5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Sun, 30 Sep 2012 23:24:24 +0200 Subject: [PATCH 096/196] Add stonesense contributors. --- Contributors.html | 15 +++++++++++++-- Contributors.rst | 16 ++++++++++++++-- fixTexts.sh | 10 +++++----- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/Contributors.html b/Contributors.html index 72cb8bdfc..227525bfb 100644 --- a/Contributors.html +++ b/Contributors.html @@ -325,8 +325,8 @@ ul.auto-toc {

    Contributors

    -

    The following is a list of people who have contributed to DFHack. -If you belong here and are missing please add yourself and send me (peterix) a pull request :-)

    +

    If you belong here and are missing, please add yourself and send me (peterix) a pull request :-)

    +

    The following is a list of people who have contributed to DFHack.

    +

    And those are the cool people who made stonesense.

    +
    diff --git a/Contributors.rst b/Contributors.rst index 112a847e7..b10877718 100644 --- a/Contributors.rst +++ b/Contributors.rst @@ -1,7 +1,8 @@ Contributors ============ +If you belong here and are missing, please add yourself and send me (peterix) a pull request :-) + The following is a list of people who have contributed to **DFHack**. -If you belong here and are missing please add yourself and send me (peterix) a pull request :-) - Petr Mrázek - Alexander Gavrilov @@ -59,4 +60,15 @@ If you belong here and are missing please add yourself and send me (peterix) a p - Donald Ruegsegger - Caldfir - Antalia -- Angus Mezick \ No newline at end of file +- Angus Mezick + +And those are the cool people who made **stonesense**. + +- Kris Parker +- Japa +- Jonas Ask +- Petr Mrázek +- Caldfir +- 8Z +- Alexander Gavrilov +- Timothy Collett diff --git a/fixTexts.sh b/fixTexts.sh index 917199411..d58fe997d 100755 --- a/fixTexts.sh +++ b/fixTexts.sh @@ -1,6 +1,6 @@ #!/bin/bash -# this script is used for easy testing of the rst documentation files. -rst2html Readme.rst Readme.html -rst2html Compile.rst Compile.html -rst2html Lua\ API.rst Lua\ API.html -rst2html Contributors.rst > Contributors.html +# regenerate documentation after editing the .rst files. Requires python and docutils. +rst2html --no-generator --no-datestamp Readme.rst Readme.html +rst2html --no-generator --no-datestamp Compile.rst Compile.html +rst2html --no-generator --no-datestamp Lua\ API.rst Lua\ API.html +rst2html --no-generator --no-datestamp Contributors.rst Contributors.html From d9cd427121d0edd2fb4f1983fe30ac629a03c2f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Mon, 1 Oct 2012 01:22:52 +0200 Subject: [PATCH 097/196] Update NEWS --- NEWS | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index fe7b5b667..65339ad64 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,8 @@ -DFHack v0.34.11-r2 (UNRELEASED) +DFHack future + + Nothing yet! + +DFHack v0.34.11-r2 Internals: - full support for Mac OS X. From fbc2d85609808b69f27f5653d8898eabfbfce242 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 1 Oct 2012 11:14:00 +0200 Subject: [PATCH 098/196] ruby: rename building_isitemfree to item_isfree, add trader flag check --- plugins/ruby/building.rb | 12 +----------- plugins/ruby/item.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 59f715515..08c12a841 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -352,23 +352,13 @@ module DFHack job end - # check item flags to see if it is suitable for use as a building material - def building_isitemfree(i) - !i.flags.in_job and - !i.flags.in_inventory and - !i.flags.removed and - !i.flags.in_building and - !i.flags.owned and - !i.flags.forbid - end - # exemple usage def buildbed(pos=cursor) raise 'where to ?' if pos.x < 0 item = world.items.all.find { |i| i.kind_of?(ItemBedst) and - building_isitemfree(i) + item_isfree(i) } raise 'no free bed, build more !' if not item diff --git a/plugins/ruby/item.rb b/plugins/ruby/item.rb index fc840f7ad..9a254d9e8 100644 --- a/plugins/ruby/item.rb +++ b/plugins/ruby/item.rb @@ -47,5 +47,17 @@ module DFHack raise "what what?" end end + + # check item flags to see if it is suitable for use as a job input material + def item_isfree(i) + !i.flags.trader and + !i.flags.in_job and + !i.flags.in_inventory and + !i.flags.removed and + !i.flags.in_building and + !i.flags.owned and + !i.flags.forbid + end + end end From 150704f1af024143575b92429b432ae0c22aea08 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 1 Oct 2012 16:05:07 +0200 Subject: [PATCH 099/196] ruby: raise when using unknown symbols in viewscreen.feed --- plugins/ruby/ruby-autogen-defs.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 64c08facb..4470c8022 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -785,6 +785,7 @@ module DFHack def isset(key) raise unless @_memaddr key = @_enum.int(key) if _enum + raise "unknown key #{key.inspect}" if key.kind_of?(::Symbol) DFHack.memory_stlset_isset(@_memaddr, key) end alias is_set? isset @@ -792,12 +793,14 @@ module DFHack def set(key) raise unless @_memaddr key = @_enum.int(key) if _enum + raise "unknown key #{key.inspect}" if key.kind_of?(::Symbol) DFHack.memory_stlset_set(@_memaddr, key) end def delete(key) raise unless @_memaddr key = @_enum.int(key) if _enum + raise "unknown key #{key.inspect}" if key.kind_of?(::Symbol) DFHack.memory_stlset_deletekey(@_memaddr, key) end From 83117fca40ded212ca0f419718f2a7ab2861c961 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 1 Oct 2012 17:57:38 -0500 Subject: [PATCH 100/196] Sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 2f76de54d..d9c765cce 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2f76de54dacd32af567b177adfb9d037fdf62d9b +Subproject commit d9c765ccea1d98ac08d33316dbb12f62df271690 From 7440e80e6c6af23284403db3af103a5f35358f0f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Oct 2012 13:49:31 +0400 Subject: [PATCH 101/196] Add an API function to retrieve interface key bindings for display. --- library/Core.cpp | 3 ++ library/LuaApi.cpp | 1 + library/include/modules/Screen.h | 3 ++ library/modules/Screen.cpp | 48 ++++++++++++++++++++++++++++++++ library/xml | 2 +- 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index 1015194ad..bc90abc6b 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -821,6 +821,8 @@ std::string Core::getHackPath() #endif } +void init_screen_module(Core *); + bool Core::Init() { if(started) @@ -866,6 +868,7 @@ bool Core::Init() // Init global object pointers df::global::InitGlobals(); + init_screen_module(this); cerr << "Initializing Console.\n"; // init the console. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 352e21ccf..aba6301d2 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1146,6 +1146,7 @@ static const LuaWrapper::FunctionReg dfhack_screen_module[] = { WRAPM(Screen, inGraphicsMode), WRAPM(Screen, clear), WRAPM(Screen, invalidate), + WRAPM(Screen, getKeyDisplay), { NULL, NULL } }; diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index a2e64a515..ccd7f2f8d 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -128,6 +128,9 @@ namespace DFHack DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL); DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false); DFHACK_EXPORT bool isDismissed(df::viewscreen *screen); + + /// Retrieve the string representation of the bound key. + DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key); } class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen { diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 0b9279d2d..29f718266 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -28,6 +28,7 @@ distribution. #include #include #include +#include using namespace std; #include "modules/Screen.h" @@ -64,6 +65,8 @@ using df::global::enabler; using Screen::Pen; +using std::string; + /* * Screen painting API. */ @@ -303,6 +306,51 @@ bool Screen::isDismissed(df::viewscreen *screen) return screen->breakdown_level != interface_breakdown_types::NONE; } +#ifdef _LINUX +// Link to the libgraphics class directly: +class DFHACK_EXPORT enabler_inputst { + public: + std::string GetKeyDisplay(int binding); +}; +#else +struct less_sz { + bool operator() (const string &a, const string &b) const { + if (a.size() < b.size()) return true; + if (a.size() > b.size()) return false; + return a < b; + } +}; +static std::map > *keydisplay = NULL; +#endif + +void init_screen_module(Core *core) +{ +#ifdef _LINUX + core = core; +#else + if (!core->vinfo->getAddress("keydisplay", keydisplay)) + keydisplay = NULL; +#endif +} + +string Screen::getKeyDisplay(df::interface_key key) +{ +#ifdef _LINUX + auto enabler = (enabler_inputst*)df::global::enabler; + if (enabler) + return enabler->GetKeyDisplay(key); +#else + if (keydisplay) + { + auto it = keydisplay->find(key); + if (it != keydisplay->end() && !it->second.empty()) + return *it->second.begin(); + } +#endif + + return "?"; +} + /* * Base DFHack viewscreen. */ diff --git a/library/xml b/library/xml index 2f76de54d..4c210ddb5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2f76de54dacd32af567b177adfb9d037fdf62d9b +Subproject commit 4c210ddb5338d3a519d03f768da828c33480e4fe From 9d5adf1b2fb3e79420966967528dcf96cb879eb2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Oct 2012 15:25:59 +0400 Subject: [PATCH 102/196] Update the lua screens to use the new key display string API function. --- library/lua/gui.lua | 18 ++++++++++++++++++ library/lua/gui/dialogs.lua | 7 +++---- scripts/gui/liquids.lua | 22 +++++++++++----------- scripts/gui/mechanisms.lua | 4 ++-- scripts/gui/power-meter.lua | 8 +++++--- scripts/gui/room-list.lua | 4 ++-- scripts/gui/siege-engine.lua | 32 +++++++++++++++++--------------- 7 files changed, 58 insertions(+), 37 deletions(-) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 6eaa98606..22066c077 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -93,6 +93,14 @@ function Painter.new(rect, pen) return Painter{ rect = rect, pen = pen } end +function Painter.new_xy(x1,y1,x2,y2,pen) + return Painter{ rect = mkdims_xy(x1,y1,x2,y2), pen = pen } +end + +function Painter.new_wh(x,y,w,h,pen) + return Painter{ rect = mkdims_wh(x,y,w,h), pen = pen } +end + function Painter:isValidPos() return self.x >= self.clip_x1 and self.x <= self.clip_x2 and self.y >= self.clip_y1 and self.y <= self.clip_y2 @@ -210,6 +218,16 @@ function Painter:string(text,pen,...) return self:advance(#text, nil) end +function Painter:key(code,pen,bg,...) + if type(code) == 'string' then + code = df.interface_key[code] + end + return self:string( + dscreen.getKeyDisplay(code), + pen or COLOR_LIGHTGREEN, bg or self.cur_pen.bg, ... + ) +end + ------------------------ -- Base screen object -- ------------------------ diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index b1a96a558..707b36d52 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -51,10 +51,9 @@ function MessageBox:onRenderBody(dc) end if self.on_accept then - local x,y = self.frame_rect.x1+1, self.frame_rect.y2+1 - dscreen.paintString({fg=COLOR_LIGHTGREEN},x,y,'ESC') - dscreen.paintString({fg=COLOR_GREY},x+3,y,'/') - dscreen.paintString({fg=COLOR_LIGHTGREEN},x+4,y,'y') + local fr = self.frame_rect + local dc2 = gui.Painter.new_xy(fr.x1+1,fr.y2+1,fr.x2-8,fr.y2+1) + dc2:key('LEAVESCREEN'):string('/'):key('MENU_CONFIRM') end end diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua index cddb9f01d..322ae6952 100644 --- a/scripts/gui/liquids.lua +++ b/scripts/gui/liquids.lua @@ -17,8 +17,8 @@ local brushes = { } local paints = { - { tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'w' }, - { tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'l' }, + { tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'D_LOOK_ARENA_WATER' }, + { tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'D_LOOK_ARENA_MAGMA' }, { tag = 'obsidian', caption = 'Obsidian Wall' }, { tag = 'obsidian_floor', caption = 'Obsidian Floor' }, { tag = 'riversource', caption = 'River Source' }, @@ -64,7 +64,7 @@ function Toggle:render(dc) if item then dc:string(item.caption) if item.key then - dc:string(" ("):string(item.key, COLOR_LIGHTGREEN):string(")") + dc:string(" ("):key(item.key):string(")") end else dc:string('NONE', COLOR_RED) @@ -160,9 +160,9 @@ function LiquidsUI:onRenderBody(dc) dc:newline():pen(COLOR_GREY) - dc:newline(1):string("b", COLOR_LIGHTGREEN):string(": ") + dc:newline(1):key('CUSTOM_B'):string(": ") self.brush:render(dc) - dc:newline(1):string("p", COLOR_LIGHTGREEN):string(": ") + dc:newline(1):key('CUSTOM_P'):string(": ") self.paint:render(dc) local paint = self.paint:get() @@ -170,8 +170,8 @@ function LiquidsUI:onRenderBody(dc) dc:newline() if paint.liquid then dc:newline(1):string("Amount: "..self.amount) - dc:advance(1):string("("):string("-+", COLOR_LIGHTGREEN):string(")") - dc:newline(3):string("s", COLOR_LIGHTGREEN):string(": ") + dc:advance(1):string("("):key('SECONDSCROLL_UP'):key('SECONDSCROLL_DOWN'):string(")") + dc:newline(3):key('CUSTOM_S'):string(": ") self.set:render(dc) else dc:advance(0,2) @@ -179,17 +179,17 @@ function LiquidsUI:onRenderBody(dc) dc:newline() if paint.flow then - dc:newline(1):string("f", COLOR_LIGHTGREEN):string(": ") + dc:newline(1):key('CUSTOM_F'):string(": ") self.flow:render(dc) - dc:newline(1):string("r", COLOR_LIGHTGREEN):string(": ") + dc:newline(1):key('CUSTOM_R'):string(": ") self.permaflow:render(dc) else dc:advance(0,2) end dc:newline():newline(1):pen(COLOR_WHITE) - dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") - dc:string("Enter", COLOR_LIGHTGREEN):string(": Paint") + dc:key('LEAVESCREEN'):string(": Back, ") + dc:key('SELECT'):string(": Paint") end function ensure_blocks(cursor, size, cb) diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua index d1e8ec803..607625524 100644 --- a/scripts/gui/mechanisms.lua +++ b/scripts/gui/mechanisms.lua @@ -89,8 +89,8 @@ function MechanismList:onRenderBody(dc) end dc:newline():newline(1):pen(COLOR_WHITE) - dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") - dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch") + dc:key('LEAVESCREEN'):string(": Back, ") + dc:key('SELECT'):string(": Switch") end function MechanismList:changeSelected(delta) diff --git a/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua index 6c2f699ac..81b56e29f 100644 --- a/scripts/gui/power-meter.lua +++ b/scripts/gui/power-meter.lua @@ -34,7 +34,8 @@ function PowerMeter:onRenderBody(dc) dc:string("Excess power range:") - dc:newline(3):string("as", COLOR_LIGHTGREEN) + dc:newline(3):key('BUILDING_TRIGGER_MIN_WATER_DOWN') + dc:key('BUILDING_TRIGGER_MIN_WATER_UP') dc:string(": Min ") if self.min_power <= 0 then dc:string("(any)") @@ -42,7 +43,8 @@ function PowerMeter:onRenderBody(dc) dc:string(''..self.min_power) end - dc:newline(3):string("zx", COLOR_LIGHTGREEN) + dc:newline(3):key('BUILDING_TRIGGER_MAX_WATER_DOWN') + dc:key('BUILDING_TRIGGER_MAX_WATER_UP') dc:string(": Max ") if self.max_power < 0 then dc:string("(any)") @@ -51,7 +53,7 @@ function PowerMeter:onRenderBody(dc) end dc:newline():newline(1) - dc:string("i",COLOR_LIGHTGREEN):string(": ") + dc:key('CUSTOM_I'):string(": ") if self.invert then dc:string("Inverted") else diff --git a/scripts/gui/room-list.lua b/scripts/gui/room-list.lua index 0de82db5f..1f3e663da 100644 --- a/scripts/gui/room-list.lua +++ b/scripts/gui/room-list.lua @@ -185,10 +185,10 @@ function RoomList:onRenderBody(dc) end dc:newline():newline(1):pen(COLOR_WHITE) - dc:string("Esc", COLOR_LIGHTGREEN):string(": Back") + dc:key('LEAVESCREEN'):string(": Back") if can_modify(sel_item) then - dc:string(", "):string("Enter", COLOR_LIGHTGREEN) + dc:string(", "):key('SELECT') if sel_item.obj.owner == sel_item.owner then dc:string(": Unassign") else diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index f460bb211..c518a9cf8 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -204,14 +204,14 @@ function SiegeEngine:onRenderBody_main(dc) dc:string("None (default)") end - dc:newline(3):string("r",COLOR_LIGHTGREEN):string(": Rectangle") + dc:newline(3):key('CUSTOM_R'):string(": Rectangle") if last_target_min then - dc:string(", "):string("p",COLOR_LIGHTGREEN):string(": Paste") + dc:string(", "):key('CUSTOM_P'):string(": Paste") end dc:newline(3) if target_min then - dc:string("x",COLOR_LIGHTGREEN):string(": Clear, ") - dc:string("z",COLOR_LIGHTGREEN):string(": Zoom") + dc:key('CUSTOM_X'):string(": Clear, ") + dc:key('CUSTOM_Z'):string(": Zoom") end dc:newline():newline(1) @@ -219,7 +219,7 @@ function SiegeEngine:onRenderBody_main(dc) dc:string("Uses ballista arrows") else local item = plugin.getAmmoItem(self.building) - dc:string("u",COLOR_LIGHTGREEN):string(": Use ") + dc:key('CUSTOM_U'):string(": Use ") if item_choice_idx[item] then dc:string(item_choices[item_choice_idx[item]].caption) else @@ -228,18 +228,20 @@ function SiegeEngine:onRenderBody_main(dc) end dc:newline():newline(1) - dc:string("t",COLOR_LIGHTGREEN):string(": Take from stockpile"):newline(3) + dc:key('CUSTOM_T'):string(": Take from stockpile"):newline(3) local links = plugin.getStockpileLinks(self.building) local bottom = dc.height - 5 if links then - dc:string("d",COLOR_LIGHTGREEN):string(": Delete, ") - dc:string("o",COLOR_LIGHTGREEN):string(": Zoom"):newline() + dc:key('CUSTOM_D'):string(": Delete, ") + dc:key('CUSTOM_O'):string(": Zoom"):newline() self:renderStockpiles(dc, links, bottom-2-dc:localY()) dc:newline():newline() end local prof = self.building:getWorkshopProfile() or {} - dc:seek(1,math.max(dc:localY(),19)):string('ghjk',COLOR_LIGHTGREEN)dc:string(': ') + dc:seek(1,math.max(dc:localY(),19)) + dc:key('CUSTOM_G'):key('CUSTOM_H'):key('CUSTOM_J'):key('CUSTOM_K') + dc:string(': ') dc:string(df.skill_rating.attrs[prof.min_level or 0].caption):string('-') dc:string(df.skill_rating.attrs[math.min(LEGENDARY,prof.max_level or 3000)].caption) dc:newline():newline() @@ -357,7 +359,7 @@ function SiegeEngine:onRenderBody_aim(dc) dc:newline(2):string('ERROR', COLOR_RED) end - dc:newline():newline(1):string("Enter",COLOR_LIGHTGREEN) + dc:newline():newline(1):key('SELECT') if first then dc:string(": Finish rectangle") else @@ -367,7 +369,7 @@ function SiegeEngine:onRenderBody_aim(dc) local target_min, target_max = plugin.getTargetArea(self.building) if target_min then - dc:newline(1):string("z",COLOR_LIGHTGREEN):string(": Zoom to current target") + dc:newline(1):key('CUSTOM_Z'):string(": Zoom to current target") end if first then @@ -412,9 +414,9 @@ function SiegeEngine:onRenderBody_pile(dc) if plugin.isLinkedToPile(self.building, sel) then dc:string("Already taking from here"):newline():newline(2) - dc:string("d", COLOR_LIGHTGREEN):string(": Delete link") + dc:key('CUSTOM_D'):string(": Delete link") else - dc:string("Enter",COLOR_LIGHTGREEN):string(": Take from this pile") + dc:key('SELECT'):string(": Take from this pile") end elseif sel then dc:string(utils.getBuildingName(sel), COLOR_DARKGREY) @@ -460,8 +462,8 @@ function SiegeEngine:onRenderBody(dc) self.mode.render(dc) dc:seek(1, math.max(dc:localY(), 21)):pen(COLOR_WHITE) - dc:string("ESC", COLOR_LIGHTGREEN):string(": Back, ") - dc:string("c", COLOR_LIGHTGREEN):string(": Recenter") + dc:key('LEAVESCREEN'):string(": Back, ") + dc:key('CUSTOM_C'):string(": Recenter") end function SiegeEngine:onInput(keys) From abf503fcdca4dd6c07171f93e4c4bc3ed85759d2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Oct 2012 16:45:17 +0400 Subject: [PATCH 103/196] Fix the ListBox dialog to behave in a more consistent way. --- library/lua/gui.lua | 3 +- library/lua/gui/dialogs.lua | 112 +++++++++++++++++++++------------- library/lua/gui/dwarfmode.lua | 5 +- 3 files changed, 74 insertions(+), 46 deletions(-) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 22066c077..a5e4f7503 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -310,6 +310,7 @@ function Screen:onResize(w,h) end function Screen:updateLayout() + self:invoke_after('postUpdateLayout') end ------------------------ @@ -399,7 +400,7 @@ function FramedScreen:updateFrameSize() self.frame_opaque = (gw == 0 and gh == 0) end -function FramedScreen:updateLayout() +function FramedScreen:postUpdateLayout() self:updateFrameSize() end diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 707b36d52..553bb3a4b 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -170,10 +170,10 @@ ListBox = defclass(ListBox, MessageBox) ListBox.focus_path = 'ListBox' ListBox.ATTRS{ - selection = 0, + selection = 1, choices = {}, select_pen = DEFAULT_NIL, - on_input = DEFAULT_NIL + on_select = DEFAULT_NIL } function InputBox:preinit(info) @@ -181,84 +181,112 @@ function InputBox:preinit(info) end function ListBox:init(info) - self.page_top = 0 + self.page_top = 1 +end + +local function choice_text(entry) + if type(entry)=="table" then + return entry.caption or entry[1] + else + return entry + end end function ListBox:getWantedFrameSize() local mw, mh = ListBox.super.getWantedFrameSize(self) - return mw, mh+#self.choices + for _,v in ipairs(self.choices) do + local text = choice_text(v) + mw = math.max(mw, #text+2) + end + return mw, mh+#self.choices+1 end -function ListBox:onRenderBody(dc) - ListBox.super.onRenderBody(self, dc) - - dc:newline(1) +function ListBox:postUpdateLayout() + self.page_size = self.frame_rect.height - #self.text - 3 + self:moveCursor(0) +end - if self.selection>dc.height-3 then - self.page_top=self.selection-(dc.height-3) - elseif self.selection0 then - self.page_top=self.selection-1 - end - for i,entry in ipairs(self.choices) do - if type(entry)=="table" then - entry=entry[1] +function ListBox:moveCursor(delta) + local page = math.max(1, self.page_size) + local cnt = #self.choices + local off = self.selection+delta-1 + local ds = math.abs(delta) + + if ds > 1 then + if off >= cnt+ds-1 then + off = 0 + else + off = math.min(cnt-1, off) end - if i>self.page_top then - if i == self.selection then - dc:pen(self.select_pen or COLOR_LIGHTCYAN) - else - dc:pen(self.text_pen or COLOR_GREY) - end - dc:string(entry) - dc:newline(1) + if off <= -ds then + off = cnt-1 + else + off = math.max(0, off) end end + + self.selection = 1 + off % cnt + self.page_top = 1 + page * math.floor((self.selection-1) / page) end -function ListBox:moveCursor(delta) - local newsel=self.selection+delta - if #self.choices ~=0 then - if newsel<1 or newsel>#self.choices then - newsel=newsel % #self.choices +function ListBox:onRenderBody(dc) + ListBox.super.onRenderBody(self, dc) + + dc:newline(1):pen(self.select_pen or COLOR_CYAN) + + local choices = self.choices + local iend = math.min(#choices, self.page_top+self.page_size-1) + + for i = self.page_top,iend do + local text = choice_text(choices[i]) + if text then + dc.cur_pen.bold = (i == self.selection); + dc:string(text) + else + dc:string('?ERROR?', COLOR_LIGHTRED) end + dc:newline(1) end - self.selection=newsel end function ListBox:onInput(keys) if keys.SELECT then self:dismiss() + local choice=self.choices[self.selection] - if self.on_input then - self.on_input(self.selection,choice) + if self.on_select then + self.on_select(self.selection, choice) end - if choice and choice[2] then - choice[2](choice,self.selection) -- maybe reverse the arguments? + if choice then + local callback = choice.on_select or choice[2] + if callback then + callback(choice, self.selection) + end end elseif keys.LEAVESCREEN then self:dismiss() if self.on_cancel then self.on_cancel() end - elseif keys.CURSOR_UP then + elseif keys.STANDARDSCROLL_UP then self:moveCursor(-1) - elseif keys.CURSOR_DOWN then + elseif keys.STANDARDSCROLL_DOWN then self:moveCursor(1) - elseif keys.CURSOR_UP_FAST then - self:moveCursor(-10) - elseif keys.CURSOR_DOWN_FAST then - self:moveCursor(10) + elseif keys.STANDARDSCROLL_PAGEUP then + self:moveCursor(-self.page_size) + elseif keys.STANDARDSCROLL_PAGEDOWN then + self:moveCursor(self.page_size) end end -function showListPrompt(title, text, tcolor, choices, on_input, on_cancel, min_width) +function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width) ListBox{ frame_title = title, text = text, text_pen = tcolor, choices = choices, - on_input = on_input, + on_select = on_select, on_cancel = on_cancel, frame_width = min_width, }:show() diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index ba3cfbe6c..125e71220 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -253,7 +253,7 @@ end DwarfOverlay = defclass(DwarfOverlay, gui.Screen) -function DwarfOverlay:updateLayout() +function DwarfOverlay:postUpdateLayout() self.df_layout = getPanelLayout() end @@ -352,8 +352,7 @@ end MenuOverlay = defclass(MenuOverlay, DwarfOverlay) -function MenuOverlay:updateLayout() - MenuOverlay.super.updateLayout(self) +function MenuOverlay:postUpdateLayout() self.frame_rect = self.df_layout.menu end From bd3d3061ae72973fa0ddf5349bf6a9f59b662e28 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Oct 2012 18:01:28 +0400 Subject: [PATCH 104/196] Add a module that wraps the dialogs as "blocking" coroutine functions. --- library/lua/gui/script.lua | 151 +++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 library/lua/gui/script.lua diff --git a/library/lua/gui/script.lua b/library/lua/gui/script.lua new file mode 100644 index 000000000..021a4fa52 --- /dev/null +++ b/library/lua/gui/script.lua @@ -0,0 +1,151 @@ +-- Support for scripted interaction sequences via coroutines. + +local _ENV = mkmodule('gui.script') + +local dlg = require('gui.dialogs') + +--[[ + Example: + + start(function() + sleep(100, 'frames') + print(showYesNoPrompt('test', 'print true?')) + end) +]] + +-- Table of running background scripts. +if not scripts then + scripts = {} + setmetatable(scripts, { __mode = 'k' }) +end + +local function do_resume(inst, ...) + inst.gen = inst.gen + 1 + return (dfhack.saferesume(inst.coro, ...)) +end + +-- Starts a new background script by calling the function. +function start(fn,...) + local coro = coroutine.create(fn) + local inst = { + coro = coro, + gen = 0, + } + scripts[coro] = inst + return do_resume(inst, ...) +end + +-- Checks if called from a background script +function in_script() + return scripts[coroutine.running()] ~= nil +end + +local function getinst() + local inst = scripts[coroutine.running()] + if not inst then + error('Not in a gui script coroutine.') + end + return inst +end + +local function invoke_resume(inst,gen,quiet,...) + local state = coroutine.status(inst.coro) + if state ~= 'suspended' then + if state ~= 'dead' then + dfhack.printerr(debug.traceback('resuming a non-waiting continuation')) + end + elseif inst.gen > gen then + if not quiet then + dfhack.printerr(debug.traceback('resuming an expired continuation')) + end + else + do_resume(inst, ...) + end +end + +-- Returns a callback that resumes the script from wait with given return values +function mkresume(...) + local inst = getinst() + return curry(invoke_resume, inst, inst.gen, false, ...) +end + +-- Like mkresume, but does not complain if already resumed from this wait +function qresume(...) + local inst = getinst() + return curry(invoke_resume, inst, inst.gen, true, ...) +end + +-- Wait until a mkresume callback is called, then return its arguments. +-- Once it returns, all mkresume callbacks created before are invalidated. +function wait() + getinst() -- check that the context is right + return coroutine.yield() +end + +-- Wraps dfhack.timeout for coroutines. +function sleep(time, quantity) + if dfhack.timeout(time, quantity, mkresume()) then + wait() + return true + else + return false + end +end + +-- Some dialog wrappers: + +function showMessage(title, text, tcolor) + dlg.MessageBox{ + frame_title = title, + text = text, + text_pen = tcolor, + on_close = qresume(nil) + }:show() + + return wait() +end + +function showYesNoPrompt(title, text, tcolor) + dlg.MessageBox{ + frame_title = title, + text = text, + text_pen = tcolor, + on_accept = mkresume(true), + on_cancel = mkresume(false), + on_close = qresume(nil) + }:show() + + return wait() +end + +function showInputPrompt(title, text, tcolor, input, min_width) + dlg.InputBox{ + frame_title = title, + text = text, + text_pen = tcolor, + input = input, + frame_width = min_width, + on_input = mkresume(true), + on_cancel = mkresume(false), + on_close = qresume(nil) + }:show() + + return wait() +end + +function showListPrompt(title, text, tcolor, choices, min_width) + dlg.ListBox{ + frame_title = title, + text = text, + text_pen = tcolor, + choices = choices, + frame_width = min_width, + on_select = mkresume(true), + on_cancel = mkresume(false), + on_close = qresume(nil) + }:show() + + return wait() +end + +return _ENV From 7962f24cce8f9049a0e280cd3f1d04c6c9520a65 Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 2 Oct 2012 10:20:54 -0500 Subject: [PATCH 105/196] Display actual key bindings for Manipulator --- plugins/manipulator.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 164b13dd7..633098a10 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -1105,30 +1105,30 @@ void viewscreen_unitlaborsst::render() } int x = 2; - OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key + OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::SELECT)); OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle labor, "); - OutputString(10, x, gps->dimy - 3, "Shift+Enter"); // SELECT_ALL key + OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::SELECT_ALL)); OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle Group, "); - OutputString(10, x, gps->dimy - 3, "v"); // UNITJOB_VIEW key + OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW)); OutputString(15, x, gps->dimy - 3, ": ViewCre, "); - OutputString(10, x, gps->dimy - 3, "c"); // UNITJOB_ZOOM_CRE key + OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::UNITJOB_ZOOM_CRE)); OutputString(15, x, gps->dimy - 3, ": Zoom-Cre"); x = 2; - OutputString(10, x, gps->dimy - 2, "Esc"); // LEAVESCREEN key + OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::LEAVESCREEN)); OutputString(15, x, gps->dimy - 2, ": Done, "); - OutputString(10, x, gps->dimy - 2, "+"); // SECONDSCROLL_DOWN key - OutputString(10, x, gps->dimy - 2, "-"); // SECONDSCROLL_UP key + OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_DOWN)); + OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_UP)); OutputString(15, x, gps->dimy - 2, ": Sort by Skill, "); - OutputString(10, x, gps->dimy - 2, "*"); // SECONDSCROLL_PAGEDOWN key - OutputString(10, x, gps->dimy - 2, "/"); // SECONDSCROLL_PAGEUP key + OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEDOWN)); + OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEUP)); OutputString(15, x, gps->dimy - 2, ": Sort by ("); - OutputString(10, x, gps->dimy - 2, "Tab"); // CHANGETAB key + OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::CHANGETAB)); OutputString(15, x, gps->dimy - 2, ") "); switch (altsort) { @@ -1182,7 +1182,7 @@ struct unitlist_hook : df::viewscreen_unitlistst if (units[page].size()) { int x = 2; - OutputString(12, x, gps->dimy - 2, "l"); // UNITVIEW_PRF_PROF key + OutputString(12, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::UNITVIEW_PRF_PROF)); OutputString(15, x, gps->dimy - 2, ": Manage labors (DFHack)"); } } From 33aead34b43799e440e58586a215df69c8c3c02e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Oct 2012 19:53:16 +0400 Subject: [PATCH 106/196] Implement a more automated search mode based on keys for some globals. --- library/lua/memscan.lua | 7 +- scripts/devel/find-offsets.lua | 306 +++++++++++++++++++++++++++++---- 2 files changed, 279 insertions(+), 34 deletions(-) diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index 970f821c2..6764a0d58 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -358,7 +358,7 @@ end -- Interactive search utility -function DiffSearcher:find_interactive(prompt,data_type,condition_cb) +function DiffSearcher:find_interactive(prompt,data_type,condition_cb,iter_limit) enum = enum or {} -- Loop for restarting search from scratch @@ -374,6 +374,11 @@ function DiffSearcher:find_interactive(prompt,data_type,condition_cb) while true do print('') + if iter_limit and ccursor >= iter_limit then + dfhack.printerr(' Iteration limit reached without a solution.') + break + end + local ok, value, delta = condition_cb(ccursor) ccursor = ccursor + 1 diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index 07a058474..45f59e154 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -2,6 +2,7 @@ local utils = require 'utils' local ms = require 'memscan' +local gui = require 'gui' local is_known = dfhack.internal.getAddress @@ -53,6 +54,35 @@ end local searcher = ms.DiffSearcher.new(data) +local function get_screen(class, prompt) + if not is_known('gview') then + print('Please navigate to '..prompt) + if not utils.prompt_yes_no('Proceed?', true) then + return nil + end + return true + end + + while true do + local cs = dfhack.gui.getCurViewscreen(true) + if not df.is_instance(class, cs) then + print('Please navigate to '..prompt) + if not utils.prompt_yes_no('Proceed?', true) then + return nil + end + else + return cs + end + end +end + +local function screen_title() + return get_screen(df.viewscreen_titlest, 'the title screen') +end +local function screen_dwarfmode() + return get_screen(df.viewscreen_dwarfmodest, 'the main dwarf mode screen') +end + local function validate_offset(name,validator,addr,tname,...) local obj = data:object_by_field(addr,tname,...) if obj and not validator(obj) then @@ -136,13 +166,95 @@ local function list_index_choices(length_func) end end +local function can_feed() + return not force_scan['nofeed'] and is_known 'gview' +end + +local function dwarfmode_feed_input(...) + local screen = screen_dwarfmode() + if not df.isvalid(screen) then + qerror('could not retrieve dwarfmode screen') + end + for _,v in ipairs({...}) do + gui.simulateInput(screen, v) + end +end + +local function dwarfmode_to_top() + if not can_feed() then + return false + end + + local screen = screen_dwarfmode() + if not df.isvalid(screen) then + return false + end + + for i=0,10 do + if is_known 'ui' and df.global.ui.main.mode == df.ui_sidebar_mode.Default then + break + end + gui.simulateInput(screen, 'LEAVESCREEN') + end + + -- force pause just in case + screen.keyRepeat = 1 + return true +end + +local function feed_menu_choice(catnames,catkeys,enum) + return function (idx) + idx = idx % #catnames + 1 + dwarfmode_feed_input(catkeys[idx]) + if enum then + return true, enum[catnames[idx]] + else + return true, catnames[idx] + end + end +end + +local function feed_list_choice(count,upkey,downkey) + return function(idx) + if idx > 0 then + local ok, len + if type(count) == 'number' then + ok, len = true, count + else + ok, len = pcall(count) + end + if not ok then + len = 5 + elseif len > 10 then + len = 10 + end + + local hcnt = len-1 + local rix = 1 + (idx-1) % (hcnt*2) + + if rix >= hcnt then + dwarfmode_feed_input(upkey or 'SECONDSCROLL_UP') + return true, hcnt*2 - rix + else + dwarfmode_feed_input(donwkey or 'SECONDSCROLL_DOWN') + return true, rix + end + else + print(' Please select the first list item.') + if not utils.prompt_yes_no(' Proceed?') then + return false + end + return true, 0 + end + end +end + -- -- Cursor group -- local function find_cursor() - print('\nPlease navigate to the title screen to find cursor.') - if not utils.prompt_yes_no('Proceed?', true) then + if not screen_title() then return false end @@ -354,13 +466,35 @@ local function is_valid_world(world) end local function find_world() - local addr = searcher:find_menu_cursor([[ + local catnames = { + 'Corpses', 'Refuse', 'Stone', 'Wood', 'Gems', 'Bars', 'Cloth', 'Leather', 'Ammo', 'Coins' + } + local catkeys = { + 'STOCKPILE_GRAVEYARD', 'STOCKPILE_REFUSE', 'STOCKPILE_STONE', 'STOCKPILE_WOOD', + 'STOCKPILE_GEM', 'STOCKPILE_BARBLOCK', 'STOCKPILE_CLOTH', 'STOCKPILE_LEATHER', + 'STOCKPILE_AMMO', 'STOCKPILE_COINS' + } + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_STOCKPILES') + + addr = searcher:find_interactive( + 'Auto-searching for world.', + 'int32_t', + feed_menu_choice(catnames, catkeys, df.stockpile_category), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for world. Please open the stockpile creation menu, and select different types as instructed below:]], - 'int32_t', - { 'Corpses', 'Refuse', 'Stone', 'Wood', 'Gems', 'Bars', 'Cloth', 'Leather', 'Ammo', 'Coins' }, - df.stockpile_category - ) + 'int32_t', catnames, df.stockpile_category + ) + end + validate_offset('world', is_valid_world, addr, df.world, 'selected_stockpile_type') end @@ -385,14 +519,37 @@ local function is_valid_ui(ui) end local function find_ui() - local addr = searcher:find_menu_cursor([[ + local catnames = { + 'DesignateMine', 'DesignateChannel', 'DesignateRemoveRamps', + 'DesignateUpStair', 'DesignateDownStair', 'DesignateUpDownStair', + 'DesignateUpRamp', 'DesignateChopTrees' + } + local catkeys = { + 'DESIGNATE_DIG', 'DESIGNATE_CHANNEL', 'DESIGNATE_DIG_REMOVE_STAIRS_RAMPS', + 'DESIGNATE_STAIR_UP', 'DESIGNATE_STAIR_DOWN', 'DESIGNATE_STAIR_UPDOWN', + 'DESIGNATE_RAMP', 'DESIGNATE_CHOP' + } + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_DESIGNATE') + + addr = searcher:find_interactive( + 'Auto-searching for ui.', + 'int16_t', + feed_menu_choice(catnames, catkeys, df.ui_sidebar_mode), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui. Please open the designation menu, and switch modes as instructed below:]], - 'int16_t', - { 'DesignateMine', 'DesignateChannel', 'DesignateRemoveRamps', 'DesignateUpStair', - 'DesignateDownStair', 'DesignateUpDownStair', 'DesignateUpRamp', 'DesignateChopTrees' }, - df.ui_sidebar_mode - ) + 'int16_t', catnames, df.ui_sidebar_mode + ) + end + validate_offset('ui', is_valid_ui, addr, df.ui, 'main', 'mode') end @@ -421,14 +578,32 @@ local function is_valid_ui_sidebar_menus(usm) end local function find_ui_sidebar_menus() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_BUILDJOB') + + addr = searcher:find_interactive([[ +Auto-searching for ui_sidebar_menus. Please select a Mason, +Craftsdwarfs, or Carpenters workshop, open the Add Job +menu, and move the cursor within:]], + 'int32_t', + feed_list_choice(7), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_sidebar_menus. Please switch to 'q' mode, select a Mason, Craftsdwarfs, or Carpenters workshop, open the Add Job menu, and move the cursor within:]], - 'int32_t', - { 0, 1, 2, 3, 4, 5, 6 }, - ordinal_names - ) + 'int32_t', + { 0, 1, 2, 3, 4, 5, 6 }, + ordinal_names + ) + end + validate_offset('ui_sidebar_menus', is_valid_ui_sidebar_menus, addr, df.ui_sidebar_menus, 'workshop_job', 'cursor') end @@ -455,14 +630,41 @@ local function is_valid_ui_build_selector(ubs) end local function find_ui_build_selector() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + addr = searcher:find_interactive([[ +Auto-searching for ui_build_selector. This requires mechanisms.]], + 'int32_t', + function(idx) + if idx == 0 then + dwarfmode_to_top() + dwarfmode_feed_input( + 'D_BUILDING', + 'HOTKEY_BUILDING_TRAP', + 'HOTKEY_BUILDING_TRAP_TRIGGER', + 'BUILDING_TRIGGER_ENABLE_CREATURE' + ) + else + dwarfmode_feed_input('BUILDING_TRIGGER_MIN_SIZE_DOWN') + end + return true, 50000 - 1000*idx + end, + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_build_selector. Please start constructing a pressure plate, and enable creatures. Then change the min weight as requested, remembering that the ui truncates the number, so when it shows "Min (5000df", it means 50000:]], - 'int32_t', - { 50000, 49000, 48000, 47000, 46000, 45000, 44000 } - ) + 'int32_t', + { 50000, 49000, 48000, 47000, 46000, 45000, 44000 } + ) + end + validate_offset('ui_build_selector', is_valid_ui_build_selector, addr, df.ui_build_selector, 'plate_info', 'unit_min') end @@ -601,13 +803,33 @@ end -- local function find_ui_unit_view_mode() - local addr = searcher:find_menu_cursor([[ + local catnames = { 'General', 'Inventory', 'Preferences', 'Wounds' } + local catkeys = { 'UNITVIEW_GEN', 'UNITVIEW_INV', 'UNITVIEW_PRF', 'UNITVIEW_WND' } + local addr + + if dwarfmode_to_top() and is_known('ui_selected_unit') then + dwarfmode_feed_input('D_VIEWUNIT') + + if df.global.ui_selected_unit < 0 then + df.global.ui_selected_unit = 0 + end + + addr = searcher:find_interactive( + 'Auto-searching for ui_unit_view_mode.', + 'int32_t', + feed_menu_choice(catnames, catkeys, df.ui_unit_view_mode.T_value), + 10 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_unit_view_mode. Having selected a unit with 'v', switch the pages as requested:]], - 'int32_t', - { 'General', 'Inventory', 'Preferences', 'Wounds' }, - df.ui_unit_view_mode.T_value - ) + 'int32_t', catnames, df.ui_unit_view_mode.T_value + ) + end + ms.found_offset('ui_unit_view_mode', addr) end @@ -620,14 +842,32 @@ local function look_item_list_count() end local function find_ui_look_cursor() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_LOOK') + + addr = searcher:find_interactive([[ +Auto-searching for ui_look_cursor. Please select a tile +with at least 5 items or units on the ground, and move +the cursor as instructed:]], + 'int32_t', + feed_list_choice(look_item_list_count), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_look_cursor. Please activate the 'k' mode, find a tile with many items or units on the ground, and select list entries as instructed:]], - 'int32_t', - list_index_choices(look_item_list_count), - ordinal_names - ) + 'int32_t', + list_index_choices(look_item_list_count), + ordinal_names + ) + end + ms.found_offset('ui_look_cursor', addr) end @@ -989,10 +1229,10 @@ end print('\nInitial globals (need title screen):\n') +exec_finder(find_gview, 'gview') exec_finder(find_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' }) exec_finder(find_announcements, 'announcements') exec_finder(find_d_init, 'd_init') -exec_finder(find_gview, 'gview') exec_finder(find_enabler, 'enabler') exec_finder(find_gps, 'gps') From 9f687f64a431e44f0c2f122589358d78b34d71b5 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 3 Oct 2012 12:58:05 +0400 Subject: [PATCH 107/196] Fix build. --- library/xml | 2 +- plugins/advtools.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/library/xml b/library/xml index 4c210ddb5..20c6d9c74 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 4c210ddb5338d3a519d03f768da828c33480e4fe +Subproject commit 20c6d9c743f1c5a20bb288f427b101e9b2a138d7 diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index d674f5528..d6224f5c2 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -30,6 +30,7 @@ #include "df/viewscreen_optionst.h" #include "df/viewscreen_dungeonmodest.h" #include "df/viewscreen_dungeon_monsterstatusst.h" +#include "df/nemesis_flags.h" #include @@ -683,17 +684,19 @@ command_result adv_bodyswap (color_ostream &out, std::vector & par // Permanently re-link everything if (permanent) { + using namespace df::enums::nemesis_flags; + ui_advmode->player_id = linear_index(world->nemesis.all, new_nemesis); // Flag 0 appears to be the 'active adventurer' flag, and // the player_id field above seems to be computed using it // when a savegame is loaded. // Also, unless this is set, it is impossible to retire. - real_nemesis->flags.set(0, false); - new_nemesis->flags.set(0, true); + real_nemesis->flags.set(ACTIVE_ADVENTURER, false); + new_nemesis->flags.set(ACTIVE_ADVENTURER, true); - real_nemesis->flags.set(1, true); // former retired adventurer - new_nemesis->flags.set(2, true); // blue color in legends + real_nemesis->flags.set(RETIRED_ADVENTURER, true); // former retired adventurer + new_nemesis->flags.set(ADVENTURER, true); // blue color in legends // Reassign companions and acquaintances if (!no_make_leader) From 3a522768a2d16d9cd3a0bfcf53f1c877450d4bdd Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 3 Oct 2012 19:07:04 +0400 Subject: [PATCH 108/196] Implement feed-based finders for the remaining applicable globals. --- scripts/devel/find-offsets.lua | 373 +++++++++++++++++++++++++++------ 1 file changed, 304 insertions(+), 69 deletions(-) diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index 45f59e154..2f7f1287c 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -180,6 +180,21 @@ local function dwarfmode_feed_input(...) end end +local function dwarfmode_step_frames(count) + local screen = screen_dwarfmode() + if not df.isvalid(screen) then + qerror('could not retrieve dwarfmode screen') + end + + for i = 1,(count or 1) do + gui.simulateInput(screen, 'D_ONESTEP') + if screen.keyRepeat ~= 1 then + qerror('Could not step one frame: . did not work') + end + screen:logic() + end +end + local function dwarfmode_to_top() if not can_feed() then return false @@ -241,9 +256,33 @@ local function feed_list_choice(count,upkey,downkey) end else print(' Please select the first list item.') - if not utils.prompt_yes_no(' Proceed?') then + if not utils.prompt_yes_no(' Proceed?', true) then + return false + end + return true, 0 + end + end +end + +local function feed_menu_bool(enter_seq, exit_seq) + return function(idx) + if idx == 0 then + if not utils.prompt_yes_no(' Proceed?', true) then + return false + end + return true, 0 + end + if idx == 5 then + print(' Please resize the game window.') + if not utils.prompt_yes_no(' Proceed?', true) then return false end + end + if idx%2 == 1 then + dwarfmode_feed_input(table.unpack(enter_seq)) + return true, 1 + else + dwarfmode_feed_input(table.unpack(exit_seq)) return true, 0 end end @@ -880,14 +919,32 @@ local function building_item_list_count() end local function find_ui_building_item_cursor() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_BUILDITEM') + + addr = searcher:find_interactive([[ +Auto-searching for ui_building_item_cursor. Please highlight a +workshop, trade depot or other building with at least 5 contained +items, and select as instructed:]], + 'int32_t', + feed_list_choice(building_item_list_count), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_building_item_cursor. Please activate the 't' mode, find a cluttered workshop, trade depot, or other building with many contained items, and select as instructed:]], - 'int32_t', - list_index_choices(building_item_list_count), - ordinal_names - ) + 'int32_t', + list_index_choices(building_item_list_count), + ordinal_names + ) + end + ms.found_offset('ui_building_item_cursor', addr) end @@ -896,17 +953,37 @@ end -- local function find_ui_workshop_in_add() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_BUILDJOB') + + addr = searcher:find_interactive([[ +Auto-searching for ui_workshop_in_add. Please select a +workshop, e.g. Carpenters or Masons.]], + 'int8_t', + feed_menu_bool( + { 'BUILDJOB_CANCEL', 'BUILDJOB_ADD' }, + { 'SELECT', 'SELECT', 'SELECT', 'SELECT', 'SELECT' } + ), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_workshop_in_add. Please activate the 'q' mode, find a workshop without jobs (or delete jobs), and do as instructed below. NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int8_t', - { 1, 0 }, - { [1] = 'enter the add job menu', - [0] = 'add job, thus exiting the menu' } - ) + 'int8_t', + { 1, 0 }, + { [1] = 'enter the add job menu', + [0] = 'add job, thus exiting the menu' } + ) + end + ms.found_offset('ui_workshop_in_add', addr) end @@ -919,13 +996,30 @@ local function workshop_job_list_count() end local function find_ui_workshop_job_cursor() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_BUILDJOB') + + addr = searcher:find_interactive([[ +Auto-searching for ui_workshop_job_cursor. Please highlight a +workshop with at least 5 contained jobs, and select as instructed:]], + 'int32_t', + feed_list_choice(workshop_job_list_count), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_workshop_job_cursor. Please activate the 'q' mode, find a workshop with many jobs, and select as instructed:]], - 'int32_t', - list_index_choices(workshop_job_list_count), - ordinal_names - ) + 'int32_t', + list_index_choices(workshop_job_list_count), + ordinal_names + ) + end + ms.found_offset('ui_workshop_job_cursor', addr) end @@ -934,17 +1028,39 @@ end -- local function find_ui_building_in_assign() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_BUILDJOB') + + addr = searcher:find_interactive([[ +Auto-searching for ui_building_in_assign. Please select a room, +i.e. a bedroom, tomb, office, dining room or statue garden.]], + 'int8_t', + feed_menu_bool( + { { 'BUILDJOB_STATUE_ASSIGN', 'BUILDJOB_COFFIN_ASSIGN', + 'BUILDJOB_CHAIR_ASSIGN', 'BUILDJOB_TABLE_ASSIGN', + 'BUILDJOB_BED_ASSIGN' } }, + { 'LEAVESCREEN' } + ), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_building_in_assign. Please activate the 'q' mode, select a room building (e.g. a bedroom) and do as instructed below. NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int8_t', - { 1, 0 }, - { [1] = 'enter the Assign owner menu', - [0] = 'press Esc to exit assign' } - ) + 'int8_t', + { 1, 0 }, + { [1] = 'enter the Assign owner menu', + [0] = 'press Esc to exit assign' } + ) + end + ms.found_offset('ui_building_in_assign', addr) end @@ -953,17 +1069,39 @@ end -- local function find_ui_building_in_resize() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_BUILDJOB') + + addr = searcher:find_interactive([[ +Auto-searching for ui_building_in_resize. Please select a room, +i.e. a bedroom, tomb, office, dining room or statue garden.]], + 'int8_t', + feed_menu_bool( + { { 'BUILDJOB_STATUE_SIZE', 'BUILDJOB_COFFIN_SIZE', + 'BUILDJOB_CHAIR_SIZE', 'BUILDJOB_TABLE_SIZE', + 'BUILDJOB_BED_SIZE' } }, + { 'LEAVESCREEN' } + ), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_building_in_resize. Please activate the 'q' mode, select a room building (e.g. a bedroom) and do as instructed below. NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int8_t', - { 1, 0 }, - { [1] = 'enter the Resize room mode', - [0] = 'press Esc to exit resize' } - ) + 'int8_t', + { 1, 0 }, + { [1] = 'enter the Resize room mode', + [0] = 'press Esc to exit resize' } + ) + end + ms.found_offset('ui_building_in_resize', addr) end @@ -971,13 +1109,40 @@ end -- window_x -- +local function feed_window_xyz(dec,inc,step) + return function(idx) + if idx == 0 then + for i = 1,30 do dwarfmode_feed_input(dec) end + else + dwarfmode_feed_input(inc) + end + return true, nil, step + end +end + local function find_window_x() - local addr = searcher:find_counter([[ + local addr + + if dwarfmode_to_top() then + addr = searcher:find_interactive( + 'Auto-searching for window_x.', + 'int32_t', + feed_window_xyz('CURSOR_LEFT_FAST', 'CURSOR_RIGHT', 10), + 20 + ) + + dwarfmode_feed_input('D_HOTKEY1') + end + + if not addr then + addr = searcher:find_counter([[ Searching for window_x. Please exit to main dwarfmode menu, scroll to the LEFT edge, then do as instructed:]], - 'int32_t', 10, - 'Please press Right to scroll right one step.' - ) + 'int32_t', 10, + 'Please press Right to scroll right one step.' + ) + end + ms.found_offset('window_x', addr) end @@ -986,12 +1151,28 @@ end -- local function find_window_y() - local addr = searcher:find_counter([[ + local addr + + if dwarfmode_to_top() then + addr = searcher:find_interactive( + 'Auto-searching for window_y.', + 'int32_t', + feed_window_xyz('CURSOR_UP_FAST', 'CURSOR_DOWN', 10), + 20 + ) + + dwarfmode_feed_input('D_HOTKEY1') + end + + if not addr then + addr = searcher:find_counter([[ Searching for window_y. Please exit to main dwarfmode menu, scroll to the TOP edge, then do as instructed:]], - 'int32_t', 10, - 'Please press Down to scroll down one step.' - ) + 'int32_t', 10, + 'Please press Down to scroll down one step.' + ) + end + ms.found_offset('window_y', addr) end @@ -1000,14 +1181,30 @@ end -- local function find_window_z() - local addr = searcher:find_counter([[ + local addr + + if dwarfmode_to_top() then + addr = searcher:find_interactive( + 'Auto-searching for window_z.', + 'int32_t', + feed_window_xyz('CURSOR_UP_Z', 'CURSOR_DOWN_Z', -1), + 30 + ) + + dwarfmode_feed_input('D_HOTKEY1') + end + + if not addr then + addr = searcher:find_counter([[ Searching for window_z. Please exit to main dwarfmode menu, scroll to a Z level near surface, then do as instructed below. NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int32_t', -1, - "Please press '>' to scroll one Z level down." - ) + 'int32_t', -1, + "Please press '>' to scroll one Z level down." + ) + end + ms.found_offset('window_z', addr) end @@ -1056,6 +1253,27 @@ function stop_autosave() end end +function step_n_frames(cnt, feed) + local world = df.global.world + local ctick = world.frame_counter + + if feed then + print(" Auto-stepping "..cnt.." frames.") + dwarfmode_step_frames(cnt) + return world.frame_counter-ctick + end + + local more = '' + while world.frame_counter-ctick < cnt do + print(" Please step the game "..(cnt-world.frame_counter+ctick)..more.." frames.") + more = ' more' + if not utils.prompt_yes_no(' Proceed?', true) then + return nil + end + end + return world.frame_counter-ctick +end + local function find_cur_year_tick() local zone if os_type == 'windows' then @@ -1070,12 +1288,21 @@ local function find_cur_year_tick() stop_autosave() - local addr = zone:find_counter([[ -Searching for cur_year_tick. Please exit to main dwarfmode -menu, then do as instructed below:]], - 'int32_t', 1, - "Please press '.' to step the game one frame." + local feed = dwarfmode_to_top() + local addr = zone:find_interactive( + 'Searching for cur_year_tick.', + 'int32_t', + function(idx) + if idx > 0 then + if not step_n_frames(1, feed) then + return false + end + end + return true, nil, 1 + end, + 20 ) + ms.found_offset('cur_year_tick', addr) end @@ -1083,20 +1310,6 @@ end -- cur_season_tick -- -function step_n_frames(cnt) - local world = df.global.world - local ctick = world.frame_counter - local more = '' - while world.frame_counter-ctick < cnt do - print(" Please step the game "..(cnt-world.frame_counter+ctick)..more.." frames.") - more = ' more' - if not utils.prompt_yes_no(' Done?', true) then - return nil - end - end - return world.frame_counter-ctick -end - local function find_cur_season_tick() if not (is_known 'cur_year_tick') then dfhack.printerr('Cannot search for cur_season_tick - prerequisites missing.') @@ -1105,13 +1318,14 @@ local function find_cur_season_tick() stop_autosave() + local feed = dwarfmode_to_top() local addr = searcher:find_interactive([[ Searching for cur_season_tick. Please exit to main dwarfmode menu, then do as instructed below:]], 'int32_t', function(ccursor) if ccursor > 0 then - if not step_n_frames(10) then + if not step_n_frames(10, feed) then return false end end @@ -1133,6 +1347,7 @@ local function find_cur_season() stop_autosave() + local feed = dwarfmode_to_top() local addr = searcher:find_interactive([[ Searching for cur_season. Please exit to main dwarfmode menu, then do as instructed below:]], @@ -1142,7 +1357,7 @@ menu, then do as instructed below:]], local cst = df.global.cur_season_tick df.global.cur_season_tick = 10079 df.global.cur_year_tick = df.global.cur_year_tick + (10079-cst)*10 - if not step_n_frames(10) then + if not step_n_frames(10, feed) then return false end end @@ -1203,7 +1418,7 @@ end -- local function find_pause_state() - local zone + local zone, addr if os_type == 'linux' or os_type == 'darwin' then zone = zoomed_searcher('ui_look_cursor', 32) elseif os_type == 'windows' then @@ -1213,13 +1428,33 @@ local function find_pause_state() stop_autosave() - local addr = zone:find_menu_cursor([[ + if dwarfmode_to_top() then + addr = zone:find_interactive( + 'Auto-searching for pause_state', + 'int8_t', + function(idx) + if idx%2 == 0 then + dwarfmode_feed_input('D_ONESTEP') + return true, 0 + else + screen_dwarfmode():logic() + return true, 1 + end + end, + 20 + ) + end + + if not addr then + addr = zone:find_menu_cursor([[ Searching for pause_state. Please do as instructed below:]], - 'int8_t', - { 1, 0 }, - { [1] = 'PAUSE the game', - [0] = 'UNPAUSE the game' } - ) + 'int8_t', + { 1, 0 }, + { [1] = 'PAUSE the game', + [0] = 'UNPAUSE the game' } + ) + end + ms.found_offset('pause_state', addr) end From faa131942c283cc7bcd809a469b2e22f411fa728 Mon Sep 17 00:00:00 2001 From: Quietust Date: Thu, 4 Oct 2012 20:14:50 -0500 Subject: [PATCH 109/196] Partial rewrite of 'fastdwarf' plugin: * add "fastdwarf 2" to use DF builtin "turbo speed" debug setting * use Units::isCitizen() instead of race/civ check * only scan active units, not all of them * do both fastdwarf and teledwarf in the same loop * teledwarf: don't use MapCache - it's faster to do it directly * teledwarf: don't clear both occupancy flags - check unit 'prone' flag * teledwarf: set proper unit occupancy flag at destination tile * teledwarf: if destination tile has standing unit, make dwarf lie down * cleanup 'fastdwarf' command * improve help text --- plugins/fastdwarf.cpp | 201 ++++++++++++++++++++++++------------------ 1 file changed, 117 insertions(+), 84 deletions(-) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index aea14e7f0..f1ecb0baa 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -2,134 +2,165 @@ #include "Console.h" #include "Export.h" #include "PluginManager.h" -#include "modules/MapCache.h" +#include "modules/Units.h" +#include "modules/Maps.h" #include "DataDefs.h" -#include "df/ui.h" #include "df/world.h" #include "df/unit.h" +#include "df/map_block.h" using std::string; using std::vector; using namespace DFHack; using df::global::world; -using df::global::ui; // dfhack interface DFHACK_PLUGIN("fastdwarf"); +static bool enable_fastdwarf = false; +static bool enable_teledwarf = false; + DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { + if (df::global::debug_turbospeed) + *df::global::debug_turbospeed = false; return CR_OK; } -static bool enable_fastdwarf = false; -static bool enable_teledwarf = false; - DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { - // check run conditions - if(!world || !world->map.block_index) + // do we even need to do anything at all? + if (!enable_fastdwarf && !enable_teledwarf) + return CR_OK; + + // make sure the world is actually loaded + if (!world || !world->map.block_index) { enable_fastdwarf = enable_teledwarf = false; return CR_OK; } - int32_t race = ui->race_id; - int32_t civ = ui->civ_id; - - if ( enable_fastdwarf ) { - for (size_t i = 0; i < world->units.all.size(); i++) + + df::map_block *old_block, *new_block; + for (size_t i = 0; i < world->units.active.size(); i++) + { + df::unit *unit = world->units.active[i]; + // citizens only + if (!Units::isCitizen(unit)) + continue; + + if (enable_fastdwarf) { - df::unit *unit = world->units.all[i]; - - if (unit->race == race && unit->civ_id == civ && unit->counters.job_counter > 0) + + if (unit->counters.job_counter > 0) unit->counters.job_counter = 0; // could also patch the unit->job.current_job->completion_timer } - } - if ( enable_teledwarf ) { - MapExtras::MapCache *MCache = new MapExtras::MapCache(); - for (size_t i = 0; i < world->units.all.size(); i++) + if (enable_teledwarf) { - df::unit *unit = world->units.all[i]; - - if (unit->race != race || unit->civ_id != civ || unit->path.dest.x == -30000) + // don't do anything if the dwarf isn't going anywhere + if (unit->path.dest.x == -30000) continue; - if (unit->relations.draggee_id != -1 || unit->relations.dragger_id != -1) + + // skip dwarves that are dragging creatures or being dragged + if ((unit->relations.draggee_id != -1) || (unit->relations.dragger_id != -1)) continue; + + // skip dwarves that are following other units if (unit->relations.following != 0) continue; - - MapExtras::Block* block = MCache->BlockAtTile(unit->pos); - df::coord2d pos(unit->pos.x % 16, unit->pos.y % 16); - df::tile_occupancy occ = block->OccupancyAt(pos); - occ.bits.unit = 0; - occ.bits.unit_grounded = 0; - block->setOccupancyAt(pos, occ); - - //move immediately to destination + + old_block = Maps::getTileBlock(unit->pos.x, unit->pos.y, unit->pos.z); + new_block = Maps::getTileBlock(unit->path.dest.x, unit->path.dest.y, unit->path.dest.z); + // just to be safe, prevent the dwarf from being moved to an unallocated map block! + if (!old_block || !new_block) + continue; + + // clear appropriate occupancy flags at old tile + if (unit->flags1.bits.on_ground) + // this is technically wrong, but the game will recompute this as needed + old_block->occupancy[unit->pos.x & 0xF][unit->pos.y & 0xF].bits.unit_grounded = 0; + else + old_block->occupancy[unit->pos.x & 0xF][unit->pos.y & 0xF].bits.unit = 0; + + // if there's already somebody standing at the destination, then force the unit to lay down + if (new_block->occupancy[unit->path.dest.x & 0xF][unit->path.dest.y & 0xF].bits.unit) + unit->flags1.bits.on_ground = 1; + + // set appropriate occupancy flags at new tile + if (unit->flags1.bits.on_ground) + new_block->occupancy[unit->path.dest.x & 0xF][unit->path.dest.y & 0xF].bits.unit_grounded = 1; + else + new_block->occupancy[unit->path.dest.x & 0xF][unit->path.dest.y & 0xF].bits.unit = 1; + + // move unit to destination unit->pos.x = unit->path.dest.x; unit->pos.y = unit->path.dest.y; unit->pos.z = unit->path.dest.z; } - MCache->WriteAll(); - delete MCache; } return CR_OK; } static command_result fastdwarf (color_ostream &out, vector & parameters) { - if (parameters.size() == 1) { - if ( parameters[0] == "0" ) { - enable_fastdwarf = false; - enable_teledwarf = false; - } else if ( parameters[0] == "1" ) { - enable_fastdwarf = true; - enable_teledwarf = false; - } else { - out.print("Incorrect usage.\n"); - return CR_OK; + if (parameters.size() > 2) { + out.print("Incorrect usage.\n"); + return CR_FAILURE; + } + + if (parameters.size() <= 2) + { + if (parameters.size() == 2) + { + if (parameters[1] == "0") + enable_teledwarf = false; + else if (parameters[1] == "1") + enable_teledwarf = true; + else + { + out.print("Incorrect usage.\n"); + return CR_FAILURE; + } } - } else if (parameters.size() == 2) { - if ( parameters[0] == "0" ) { + else + enable_teledwarf = false; + if (parameters[0] == "0") + { enable_fastdwarf = false; - } else if ( parameters[0] == "1" ) { + if (df::global::debug_turbospeed) + *df::global::debug_turbospeed = false; + } + else if (parameters[0] == "1") + { enable_fastdwarf = true; - } else { - out.print("Incorrect usage.\n"); - return CR_OK; + if (df::global::debug_turbospeed) + *df::global::debug_turbospeed = false; } - if ( parameters[1] == "0" ) { - enable_teledwarf = false; - } else if ( parameters[1] == "1" ) { - enable_teledwarf = true; - } else { - out.print("Incorrect usage.\n"); - return CR_OK; + else if (parameters[0] == "2") + { + if (df::global::debug_turbospeed) + { + enable_fastdwarf = false; + *df::global::debug_turbospeed = true; + } + else + { + out.print("Speed level 2 not available.\n"); + return CR_FAILURE; + } } - } else if (parameters.size() == 0) { - //print status - out.print("Current state: fast = %d, teleport = %d.\n", enable_fastdwarf, enable_teledwarf); - } else { - out.print("Incorrect usage.\n"); - return CR_OK; - } - /*if (parameters.size() == 1 && (parameters[0] == "0" || parameters[0] == "1")) - { - if (parameters[0] == "0") - enable_fastdwarf = 0; else - enable_fastdwarf = 1; - out.print("fastdwarf %sactivated.\n", (enable_fastdwarf ? "" : "de")); + { + out.print("Incorrect usage.\n"); + return CR_FAILURE; + } } - else - { - out.print("Makes your minions move at ludicrous speeds.\n" - "Activate with 'fastdwarf 1', deactivate with 'fastdwarf 0'.\n" - "Current state: %d.\n", enable_fastdwarf); - }*/ + + out.print("Current state: fast = %d, teleport = %d.\n", + (df::global::debug_turbospeed && *df::global::debug_turbospeed) ? 2 : (enable_fastdwarf ? 1 : 0), + enable_teledwarf ? 1 : 0); return CR_OK; } @@ -139,14 +170,16 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector (tele)\n" + "Valid values for speed:\n" + " * 0 - Make dwarves move and work at standard speed.\n" + " * 1 - Make dwarves move and work at maximum speed.\n" + " * 2 - Make ALL creatures move and work at maximum speed.\n" + "Valid values for (tele):\n" + " * 0 - Disable dwarf teleportation (default)\n" + " * 1 - Make dwarves teleport to their destinations instantly.\n" )); return CR_OK; From 6b3d85eb0feb556a3dd73144d1e61009e2753e56 Mon Sep 17 00:00:00 2001 From: Quietust Date: Thu, 4 Oct 2012 20:17:33 -0500 Subject: [PATCH 110/196] Don't make teledwarf skip everything after it if there's a problem --- plugins/fastdwarf.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index f1ecb0baa..4964b542a 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -52,12 +52,12 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (enable_fastdwarf) { - if (unit->counters.job_counter > 0) unit->counters.job_counter = 0; // could also patch the unit->job.current_job->completion_timer } - if (enable_teledwarf) + + if (enable_teledwarf) do { // don't do anything if the dwarf isn't going anywhere if (unit->path.dest.x == -30000) @@ -73,7 +73,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) old_block = Maps::getTileBlock(unit->pos.x, unit->pos.y, unit->pos.z); new_block = Maps::getTileBlock(unit->path.dest.x, unit->path.dest.y, unit->path.dest.z); - // just to be safe, prevent the dwarf from being moved to an unallocated map block! + // make sure source and dest map blocks are valid if (!old_block || !new_block) continue; @@ -98,7 +98,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) unit->pos.x = unit->path.dest.x; unit->pos.y = unit->path.dest.y; unit->pos.z = unit->path.dest.z; - } + } while (0); } return CR_OK; } From ddcc2ee90da2cdd787a81aab09adfc118fd7a97d Mon Sep 17 00:00:00 2001 From: Quietust Date: Thu, 4 Oct 2012 20:20:35 -0500 Subject: [PATCH 111/196] Should use "break" in this construct, not "continue" --- plugins/fastdwarf.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 4964b542a..3f3905ecc 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -61,21 +61,21 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { // don't do anything if the dwarf isn't going anywhere if (unit->path.dest.x == -30000) - continue; + break; // skip dwarves that are dragging creatures or being dragged if ((unit->relations.draggee_id != -1) || (unit->relations.dragger_id != -1)) - continue; + break; // skip dwarves that are following other units if (unit->relations.following != 0) - continue; + break; old_block = Maps::getTileBlock(unit->pos.x, unit->pos.y, unit->pos.z); new_block = Maps::getTileBlock(unit->path.dest.x, unit->path.dest.y, unit->path.dest.z); // make sure source and dest map blocks are valid if (!old_block || !new_block) - continue; + break; // clear appropriate occupancy flags at old tile if (unit->flags1.bits.on_ground) From cbbb164dad740cdaa08fde9dec763b7259676996 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 5 Oct 2012 20:44:29 +0200 Subject: [PATCH 112/196] ruby: add MapTile distance_to and spiral_search --- plugins/ruby/map.rb | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index 371614704..7c616d722 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -240,5 +240,35 @@ module DFHack def spawn_magma(quantity=7) spawn_liquid(quantity, true) end + + # yield a serie of tiles until the block returns true, returns the matching tile + # the yielded tiles form a (squared) spiral centered here in the current zlevel + # eg for radius 4, yields (-4, -4), (-4, -3), .., (-4, 3), + # (-4, 4), (-3, 4), .., (4, 4), .., (4, -4), .., (-3, -4) + # then move on to radius 5 + def spiral_search(maxradius=100, minradius=0, step=1) + if minradius == 0 + return self if yield self + minradius += step + end + + sides = [[0, 1], [1, 0], [0, -1], [-1, 0]] + (minradius..maxradius).step(step) { |r| + sides.length.times { |s| + dxr, dyr = sides[(s-1) % sides.length] + dx, dy = sides[s] + (-r...r).step(step) { |v| + t = offset(dxr*r + dx*v, dyr*r + dy*v) + return t if t and yield t + } + } + } + nil + end + + # returns dx^2+dy^2+dz^2 + def distance_to(ot) + (x-ot.x)**2 + (y-ot.y)**2 + (z-ot.z)**2 + end end end From 5396a67465a374260f81baef03c03381d8a09059 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 6 Oct 2012 12:40:46 +0400 Subject: [PATCH 113/196] Some tweaking and edited NEWS. --- NEWS | 5 +++- .../include/df/custom/coord_path.methods.inc | 7 +++++ library/include/modules/MapCache.h | 8 ------ library/include/modules/Maps.h | 15 +++++++++++ library/modules/Maps.cpp | 6 ++--- plugins/fastdwarf.cpp | 27 ++++++++++--------- 6 files changed, 44 insertions(+), 24 deletions(-) diff --git a/NEWS b/NEWS index 65339ad64..e066b7431 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ DFHack future - Nothing yet! + Internals: + - support for displaying active keybindings properly. + Misc improvements: + - fastdwarf: new mode using debug flags, and some internal consistency fixes. DFHack v0.34.11-r2 diff --git a/library/include/df/custom/coord_path.methods.inc b/library/include/df/custom/coord_path.methods.inc index 9acebb82a..5421796e3 100644 --- a/library/include/df/custom/coord_path.methods.inc +++ b/library/include/df/custom/coord_path.methods.inc @@ -1,5 +1,12 @@ +bool empty() const { return x.empty(); } unsigned size() const { return x.size(); } +void clear() { + x.clear(); + y.clear(); + z.clear(); +} + coord operator[] (unsigned idx) const { if (idx >= x.size()) return coord(); diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index ac083075f..c1c478bc6 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -47,14 +47,6 @@ namespace MapExtras class DFHACK_EXPORT MapCache; -template inline R index_tile(T &v, df::coord2d p) { - return v[p.x&15][p.y&15]; -} - -inline bool is_valid_tile_coord(df::coord2d p) { - return (p.x & ~15) == 0 && (p.y & ~15) == 0; -} - class Block; class BlockInfo diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 3150acccf..632e8ec13 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -151,6 +151,21 @@ typedef uint8_t biome_indices40d [9]; */ typedef uint16_t t_temperatures [16][16]; +/** + * Index a tile array by a 2D coordinate, clipping it to mod 16 + */ +template inline R index_tile(T &v, df::coord2d p) { + return v[p.x&15][p.y&15]; +} + +/** + * Check if a 2D coordinate is in the 0-15 range. + */ +inline bool is_valid_tile_coord(df::coord2d p) { + return (p.x & ~15) == 0 && (p.y & ~15) == 0; +} + + /** * The Maps module * \ingroup grp_modules diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 5ef4ce829..482b950ba 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -454,7 +454,7 @@ df::coord2d Maps::getBlockTileBiomeRgn(df::map_block *block, df::coord2d pos) if (!block || !world->world_data) return df::coord2d(); - auto des = MapExtras::index_tile(block->designation,pos); + auto des = index_tile(block->designation,pos); unsigned idx = des.bits.biome; if (idx < 9) { @@ -529,8 +529,8 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) if (!block1 || !block2) return false; - auto tile1 = MapExtras::index_tile(block1->walkable, pos1); - auto tile2 = MapExtras::index_tile(block2->walkable, pos2); + auto tile1 = index_tile(block1->walkable, pos1); + auto tile2 = index_tile(block2->walkable, pos2); return tile1 && tile1 == tile2; } diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 3f3905ecc..28104b909 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -60,7 +60,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (enable_teledwarf) do { // don't do anything if the dwarf isn't going anywhere - if (unit->path.dest.x == -30000) + if (!unit->path.dest.isValid()) break; // skip dwarves that are dragging creatures or being dragged @@ -71,33 +71,36 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (unit->relations.following != 0) break; - old_block = Maps::getTileBlock(unit->pos.x, unit->pos.y, unit->pos.z); - new_block = Maps::getTileBlock(unit->path.dest.x, unit->path.dest.y, unit->path.dest.z); + // skip unconscious units + if (unit->counters.unconscious > 0) + break; + // make sure source and dest map blocks are valid - if (!old_block || !new_block) + auto old_occ = Maps::getTileOccupancy(unit->pos); + auto new_occ = Maps::getTileOccupancy(unit->path.dest); + if (!old_occ || !new_occ) break; // clear appropriate occupancy flags at old tile if (unit->flags1.bits.on_ground) // this is technically wrong, but the game will recompute this as needed - old_block->occupancy[unit->pos.x & 0xF][unit->pos.y & 0xF].bits.unit_grounded = 0; + old_occ->bits.unit_grounded = 0; else - old_block->occupancy[unit->pos.x & 0xF][unit->pos.y & 0xF].bits.unit = 0; + old_occ->bits.unit = 0; // if there's already somebody standing at the destination, then force the unit to lay down - if (new_block->occupancy[unit->path.dest.x & 0xF][unit->path.dest.y & 0xF].bits.unit) + if (new_occ->bits.unit) unit->flags1.bits.on_ground = 1; // set appropriate occupancy flags at new tile if (unit->flags1.bits.on_ground) - new_block->occupancy[unit->path.dest.x & 0xF][unit->path.dest.y & 0xF].bits.unit_grounded = 1; + new_occ->bits.unit_grounded = 1; else - new_block->occupancy[unit->path.dest.x & 0xF][unit->path.dest.y & 0xF].bits.unit = 1; + new_occ->bits.unit = 1; // move unit to destination - unit->pos.x = unit->path.dest.x; - unit->pos.y = unit->path.dest.y; - unit->pos.z = unit->path.dest.z; + unit->pos = unit->path.dest; + unit->path.path.clear(); } while (0); } return CR_OK; From 6fefd0907281b41bbcbe27df1bea113ab8c46c1b Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 6 Oct 2012 12:51:34 +0400 Subject: [PATCH 114/196] Fix re-enabling autobutcher after being disabled. --- NEWS | 2 ++ plugins/zone.cpp | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index e066b7431..b36d2f121 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ DFHack future Internals: - support for displaying active keybindings properly. + Notable bugfixes: + - autobutcher can be re-enabled again after being stopped. Misc improvements: - fastdwarf: new mode using debug flags, and some internal consistency fixes. diff --git a/plugins/zone.cpp b/plugins/zone.cpp index c496f49b6..f32a77a2a 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -3408,10 +3408,10 @@ command_result start_autobutcher(color_ostream &out) auto pworld = Core::getInstance().getWorld(); enable_autobutcher = true; + if (!config_autobutcher.isValid()) { config_autobutcher = pworld->AddPersistentData("autobutcher/config"); - config_autobutcher.ival(0) = enable_autobutcher; config_autobutcher.ival(1) = sleep_autobutcher; config_autobutcher.ival(2) = enable_autobutcher_autowatch; config_autobutcher.ival(3) = default_fk; @@ -3420,6 +3420,8 @@ command_result start_autobutcher(color_ostream &out) config_autobutcher.ival(6) = default_ma; } + config_autobutcher.ival(0) = enable_autobutcher; + out << "Starting autobutcher." << endl; init_autobutcher(out); return CR_OK; From 696cc4a911a077463def422189444a3acf3e46db Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 6 Oct 2012 13:07:11 +0400 Subject: [PATCH 115/196] Stop autobutcher and autonestbox crashing if started without world. --- plugins/zone.cpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index f32a77a2a..6c1af4768 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -3412,6 +3412,13 @@ command_result start_autobutcher(color_ostream &out) if (!config_autobutcher.isValid()) { config_autobutcher = pworld->AddPersistentData("autobutcher/config"); + + if (!config_autobutcher.isValid()) + { + out << "Cannot enable autobutcher without a world!" << endl; + return CR_OK; + } + config_autobutcher.ival(1) = sleep_autobutcher; config_autobutcher.ival(2) = enable_autobutcher_autowatch; config_autobutcher.ival(3) = default_fk; @@ -3431,11 +3438,6 @@ command_result init_autobutcher(color_ostream &out) { cleanup_autobutcher(out); auto pworld = Core::getInstance().getWorld(); - if(!pworld) - { - out << "Autobutcher has no world to read from!" << endl; - return CR_OK; - } config_autobutcher = pworld->GetPersistentData("autobutcher/config"); if(config_autobutcher.isValid()) @@ -3502,12 +3504,22 @@ command_result start_autonestbox(color_ostream &out) { auto pworld = Core::getInstance().getWorld(); enable_autonestbox = true; + if (!config_autobutcher.isValid()) { config_autonestbox = pworld->AddPersistentData("autonestbox/config"); - config_autonestbox.ival(0) = enable_autonestbox; + + if (!config_autobutcher.isValid()) + { + out << "Cannot enable autonestbox without a world!" << endl; + return CR_OK; + } + config_autonestbox.ival(1) = sleep_autonestbox; } + + config_autonestbox.ival(0) = enable_autonestbox; + out << "Starting autonestbox." << endl; init_autonestbox(out); return CR_OK; @@ -3517,11 +3529,6 @@ command_result init_autonestbox(color_ostream &out) { cleanup_autonestbox(out); auto pworld = Core::getInstance().getWorld(); - if(!pworld) - { - out << "Autonestbox has no world to read from!" << endl; - return CR_OK; - } config_autonestbox = pworld->GetPersistentData("autonestbox/config"); if(config_autonestbox.isValid()) From 459c69046bbbde9877b29858495f2593731983b4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 6 Oct 2012 13:46:20 +0400 Subject: [PATCH 116/196] Dissolve the World module class into a namespace. It made accessing persistent data way too cumbersome. --- library/Core.cpp | 8 +- library/LuaApi.cpp | 14 ++-- library/include/Core.h | 4 - library/include/modules/World.h | 68 ++++++++--------- library/modules/World.cpp | 126 +++++++++----------------------- plugins/autolabor.cpp | 14 ++-- plugins/burrows.cpp | 4 +- plugins/follow.cpp | 5 +- plugins/mode.cpp | 8 +- plugins/power-meter.cpp | 6 +- plugins/rename.cpp | 6 +- plugins/reveal.cpp | 18 ++--- plugins/seedwatch.cpp | 6 +- plugins/siege-engine.cpp | 45 +++++------- plugins/weather.cpp | 9 +-- plugins/workflow.cpp | 16 ++-- plugins/zone.cpp | 22 ++---- 17 files changed, 134 insertions(+), 245 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index bc90abc6b..a8000070f 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -627,8 +627,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve } else if(first == "fpause") { - World * w = getWorld(); - w->SetPauseState(true); + World::SetPauseState(true); con.print("The game was forced to pause!\n"); } else if(first == "cls") @@ -1125,7 +1124,7 @@ void Core::doUpdate(color_ostream &out, bool first_update) last_world_data_ptr = new_wdata; last_local_map_ptr = new_mapdata; - getWorld()->ClearPersistentCache(); + World::ClearPersistentCache(); // and if the world is going away, we report the map change first if(had_map) @@ -1143,7 +1142,7 @@ void Core::doUpdate(color_ostream &out, bool first_update) if (isMapLoaded() != had_map) { - getWorld()->ClearPersistentCache(); + World::ClearPersistentCache(); onStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED); } } @@ -1681,7 +1680,6 @@ TYPE * Core::get##TYPE() \ return s_mods.p##TYPE;\ } -MODULE_GETTER(World); MODULE_GETTER(Materials); MODULE_GETTER(Notes); MODULE_GETTER(Graphic); diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index aba6301d2..0df96f066 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -258,7 +258,7 @@ static PersistentDataItem persistent_by_struct(lua_State *state, int idx) int id = lua_tointeger(state, -1); lua_pop(state, 1); - PersistentDataItem ref = Core::getInstance().getWorld()->GetPersistentData(id); + PersistentDataItem ref = World::GetPersistentData(id); if (ref.isValid()) { @@ -323,7 +323,7 @@ static PersistentDataItem get_persistent(lua_State *state) { const char *str = luaL_checkstring(state, 1); - return Core::getInstance().getWorld()->GetPersistentData(str); + return World::GetPersistentData(str); } } @@ -342,7 +342,7 @@ static int dfhack_persistent_delete(lua_State *state) auto ref = get_persistent(state); - bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref); + bool ok = World::DeletePersistentData(ref); lua_pushboolean(state, ok); return 1; @@ -356,7 +356,7 @@ static int dfhack_persistent_get_all(lua_State *state) bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false); std::vector data; - Core::getInstance().getWorld()->GetPersistentData(&data, str, prefix); + World::GetPersistentData(&data, str, prefix); if (data.empty()) { @@ -396,7 +396,7 @@ static int dfhack_persistent_save(lua_State *state) if (add) { - ref = Core::getInstance().getWorld()->AddPersistentData(str); + ref = World::AddPersistentData(str); added = true; } else if (lua_getmetatable(state, 1)) @@ -409,13 +409,13 @@ static int dfhack_persistent_save(lua_State *state) } else { - ref = Core::getInstance().getWorld()->GetPersistentData(str); + ref = World::GetPersistentData(str); } // Auto-add if not found if (!ref.isValid()) { - ref = Core::getInstance().getWorld()->AddPersistentData(str); + ref = World::AddPersistentData(str); if (!ref.isValid()) luaL_error(state, "cannot create persistent entry"); added = true; diff --git a/library/include/Core.h b/library/include/Core.h index d2d7080da..b3db50c74 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -54,7 +54,6 @@ namespace DFHack { class Process; class Module; - class World; class Materials; class Notes; struct VersionInfo; @@ -120,8 +119,6 @@ namespace DFHack /// Is everything OK? bool isValid(void) { return !errorstate; } - /// get the world module - World * getWorld(); /// get the materials module Materials * getMaterials(); /// get the notes module @@ -205,7 +202,6 @@ namespace DFHack // Module storage struct { - World * pWorld; Materials * pMaterials; Notes * pNotes; Graphic * pGraphic; diff --git a/library/include/modules/World.h b/library/include/modules/World.h index 1cd57e2f2..f4c31dcf3 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -55,8 +55,6 @@ namespace DFHack class DFContextShared; class DFHACK_EXPORT PersistentDataItem { - friend class World; - int id; std::string key_value; @@ -65,13 +63,17 @@ namespace DFHack public: static const int NumInts = 7; - bool isValid() { return id != 0; } - int entry_id() { return -id; } + bool isValid() const { return id != 0; } + int entry_id() const { return -id; } + + int raw_id() const { return id; } - const std::string &key() { return key_value; } + const std::string &key() const { return key_value; } std::string &val() { return *str_value; } + const std::string &val() const { return *str_value; } int &ival(int i) { return int_values[i]; } + int ival(int i) const { return int_values[i]; } PersistentDataItem() : id(0), str_value(0), int_values(0) {} PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv) @@ -83,54 +85,42 @@ namespace DFHack * \ingroup grp_modules * \ingroup grp_world */ - class DFHACK_EXPORT World : public Module + namespace World { - public: - World(); - ~World(); - bool Start(); - bool Finish(); - ///true if paused, false if not - bool ReadPauseState(); + DFHACK_EXPORT bool ReadPauseState(); ///true if paused, false if not - void SetPauseState(bool paused); - - uint32_t ReadCurrentTick(); - uint32_t ReadCurrentYear(); - uint32_t ReadCurrentMonth(); - uint32_t ReadCurrentDay(); - uint8_t ReadCurrentWeather(); - void SetCurrentWeather(uint8_t weather); - bool ReadGameMode(t_gamemodes& rd); - bool WriteGameMode(const t_gamemodes & wr); // this is very dangerous - std::string ReadWorldFolder(); + DFHACK_EXPORT void SetPauseState(bool paused); + + DFHACK_EXPORT uint32_t ReadCurrentTick(); + DFHACK_EXPORT uint32_t ReadCurrentYear(); + DFHACK_EXPORT uint32_t ReadCurrentMonth(); + DFHACK_EXPORT uint32_t ReadCurrentDay(); + DFHACK_EXPORT uint8_t ReadCurrentWeather(); + DFHACK_EXPORT void SetCurrentWeather(uint8_t weather); + DFHACK_EXPORT bool ReadGameMode(t_gamemodes& rd); + DFHACK_EXPORT bool WriteGameMode(const t_gamemodes & wr); // this is very dangerous + DFHACK_EXPORT std::string ReadWorldFolder(); // Store data in fake historical figure names. // This ensures that the values are stored in save games. - PersistentDataItem AddPersistentData(const std::string &key); - PersistentDataItem GetPersistentData(const std::string &key); - PersistentDataItem GetPersistentData(int entry_id); + DFHACK_EXPORT PersistentDataItem AddPersistentData(const std::string &key); + DFHACK_EXPORT PersistentDataItem GetPersistentData(const std::string &key); + DFHACK_EXPORT PersistentDataItem GetPersistentData(int entry_id); // Calls GetPersistentData(key); if not found, adds and sets added to true. // The result can still be not isValid() e.g. if the world is not loaded. - PersistentDataItem GetPersistentData(const std::string &key, bool *added); + DFHACK_EXPORT PersistentDataItem GetPersistentData(const std::string &key, bool *added); // Lists all items with the given key. // If prefix is true, search for keys starting with key+"/". // GetPersistentData(&vec,"",true) returns all items. // Items have alphabetic order by key; same key ordering is undefined. - void GetPersistentData(std::vector *vec, - const std::string &key, bool prefix = false); + DFHACK_EXPORT void GetPersistentData(std::vector *vec, + const std::string &key, bool prefix = false); // Deletes the item; returns true if success. - bool DeletePersistentData(const PersistentDataItem &item); + DFHACK_EXPORT bool DeletePersistentData(const PersistentDataItem &item); - void ClearPersistentCache(); - - private: - struct Private; - Private *d; - - bool BuildPersistentCache(); - }; + DFHACK_EXPORT void ClearPersistentCache(); + } } #endif diff --git a/library/modules/World.cpp b/library/modules/World.cpp index a4c4c5604..65d0c7cb8 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -48,89 +48,34 @@ using namespace DFHack; using df::global::world; -Module* DFHack::createWorld() -{ - return new World(); -} - -struct World::Private -{ - Private() - { - Inited = PauseInited = StartedWeather = StartedMode = false; - next_persistent_id = 0; - } - bool Inited; - - bool PauseInited; - bool StartedWeather; - bool StartedMode; - - int next_persistent_id; - std::multimap persistent_index; - - Process * owner; -}; - +static int next_persistent_id = 0; +static std::multimap persistent_index; typedef std::pair T_persistent_item; -World::World() -{ - Core & c = Core::getInstance(); - d = new Private; - d->owner = c.p; - - if(df::global::pause_state) - d->PauseInited = true; - - if(df::global::current_weather) - d->StartedWeather = true; - if (df::global::gamemode && df::global::gametype) - d->StartedMode = true; - - d->Inited = true; -} - -World::~World() -{ - delete d; -} - -bool World::Start() -{ - return true; -} - -bool World::Finish() -{ - return true; -} - bool World::ReadPauseState() { - if(!d->PauseInited) return false; - return *df::global::pause_state; + return DF_GLOBAL_VALUE(pause_state, false); } void World::SetPauseState(bool paused) { - if (d->PauseInited) - *df::global::pause_state = paused; + bool dummy; + DF_GLOBAL_VALUE(pause_state, dummy) = paused; } uint32_t World::ReadCurrentYear() { - return *df::global::cur_year; + return DF_GLOBAL_VALUE(cur_year, 0); } uint32_t World::ReadCurrentTick() { - return *df::global::cur_year_tick; + return DF_GLOBAL_VALUE(cur_year_tick, 0); } bool World::ReadGameMode(t_gamemodes& rd) { - if(d->Inited && d->StartedMode) + if(df::global::gamemode && df::global::gametype) { rd.g_mode = (DFHack::GameMode)*df::global::gamemode; rd.g_type = (DFHack::GameType)*df::global::gametype; @@ -140,7 +85,7 @@ bool World::ReadGameMode(t_gamemodes& rd) } bool World::WriteGameMode(const t_gamemodes & wr) { - if(d->Inited && d->StartedMode) + if(df::global::gamemode && df::global::gametype) { *df::global::gamemode = wr.g_mode; *df::global::gametype = wr.g_type; @@ -173,24 +118,24 @@ specified by memory.xml gets me the current month/date. */ uint32_t World::ReadCurrentMonth() { - return this->ReadCurrentTick() / 1200 / 28; + return ReadCurrentTick() / 1200 / 28; } uint32_t World::ReadCurrentDay() { - return ((this->ReadCurrentTick() / 1200) % 28) + 1; + return ((ReadCurrentTick() / 1200) % 28) + 1; } uint8_t World::ReadCurrentWeather() { - if (d->Inited && d->StartedWeather) + if (df::global::current_weather) return (*df::global::current_weather)[2][2]; return 0; } void World::SetCurrentWeather(uint8_t weather) { - if (d->Inited && d->StartedWeather) + if (df::global::current_weather) memset(df::global::current_weather, weather, 25); } @@ -206,13 +151,13 @@ static PersistentDataItem dataFromHFig(df::historical_figure *hfig) void World::ClearPersistentCache() { - d->next_persistent_id = 0; - d->persistent_index.clear(); + next_persistent_id = 0; + persistent_index.clear(); } -bool World::BuildPersistentCache() +static bool BuildPersistentCache() { - if (d->next_persistent_id) + if (next_persistent_id) return true; if (!Core::getInstance().isWorldLoaded()) return false; @@ -220,20 +165,20 @@ bool World::BuildPersistentCache() std::vector &hfvec = df::historical_figure::get_vector(); // Determine the next entry id as min(-100, lowest_id-1) - d->next_persistent_id = -100; + next_persistent_id = -100; if (hfvec.size() > 0 && hfvec[0]->id <= -100) - d->next_persistent_id = hfvec[0]->id-1; + next_persistent_id = hfvec[0]->id-1; // Add the entries to the lookup table - d->persistent_index.clear(); + persistent_index.clear(); for (size_t i = 0; i < hfvec.size() && hfvec[i]->id <= -100; i++) { if (!hfvec[i]->name.has_name || hfvec[i]->name.first_name.empty()) continue; - d->persistent_index.insert(T_persistent_item(hfvec[i]->name.first_name, -hfvec[i]->id)); + persistent_index.insert(T_persistent_item(hfvec[i]->name.first_name, -hfvec[i]->id)); } return true; @@ -247,14 +192,14 @@ PersistentDataItem World::AddPersistentData(const std::string &key) std::vector &hfvec = df::historical_figure::get_vector(); df::historical_figure *hfig = new df::historical_figure(); - hfig->id = d->next_persistent_id--; + hfig->id = next_persistent_id--; hfig->name.has_name = true; hfig->name.first_name = key; memset(hfig->name.words, 0xFF, sizeof(hfig->name.words)); hfvec.insert(hfvec.begin(), hfig); - d->persistent_index.insert(T_persistent_item(key, -hfig->id)); + persistent_index.insert(T_persistent_item(key, -hfig->id)); return dataFromHFig(hfig); } @@ -264,8 +209,8 @@ PersistentDataItem World::GetPersistentData(const std::string &key) if (!BuildPersistentCache()) return PersistentDataItem(); - auto it = d->persistent_index.find(key); - if (it != d->persistent_index.end()) + auto it = persistent_index.find(key); + if (it != persistent_index.end()) return GetPersistentData(it->second); return PersistentDataItem(); @@ -305,24 +250,24 @@ void World::GetPersistentData(std::vector *vec, const std::s if (!BuildPersistentCache()) return; - auto eqrange = d->persistent_index.equal_range(key); + auto eqrange = persistent_index.equal_range(key); if (prefix) { if (key.empty()) { - eqrange.first = d->persistent_index.begin(); - eqrange.second = d->persistent_index.end(); + eqrange.first = persistent_index.begin(); + eqrange.second = persistent_index.end(); } else { std::string bound = key; if (bound[bound.size()-1] != '/') bound += "/"; - eqrange.first = d->persistent_index.lower_bound(bound); + eqrange.first = persistent_index.lower_bound(bound); bound[bound.size()-1]++; - eqrange.second = d->persistent_index.lower_bound(bound); + eqrange.second = persistent_index.lower_bound(bound); } } @@ -336,25 +281,26 @@ void World::GetPersistentData(std::vector *vec, const std::s bool World::DeletePersistentData(const PersistentDataItem &item) { - if (item.id > -100) + int id = item.raw_id(); + if (id > -100) return false; if (!BuildPersistentCache()) return false; std::vector &hfvec = df::historical_figure::get_vector(); - auto eqrange = d->persistent_index.equal_range(item.key_value); + auto eqrange = persistent_index.equal_range(item.key()); for (auto it2 = eqrange.first; it2 != eqrange.second; ) { auto it = it2; ++it2; - if (it->second != -item.id) + if (it->second != -id) continue; - d->persistent_index.erase(it); + persistent_index.erase(it); - int idx = binsearch_index(hfvec, item.id); + int idx = binsearch_index(hfvec, id); if (idx >= 0) { delete hfvec[idx]; diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index c39b126c9..42ccae4c2 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -547,9 +547,7 @@ static void reset_labor(df::unit_labor labor) static void init_state() { - auto pworld = Core::getInstance().getWorld(); - - config = pworld->GetPersistentData("autolabor/config"); + config = World::GetPersistentData("autolabor/config"); if (config.isValid() && config.ival(0) == -1) config.ival(0) = 0; @@ -558,7 +556,7 @@ static void init_state() if (!enable_autolabor) return; - auto cfg_haulpct = pworld->GetPersistentData("autolabor/haulpct"); + auto cfg_haulpct = World::GetPersistentData("autolabor/haulpct"); if (cfg_haulpct.isValid()) { hauler_pct = cfg_haulpct.ival(0); @@ -572,7 +570,7 @@ static void init_state() labor_infos.resize(ARRAY_COUNT(default_labor_infos)); std::vector items; - pworld->GetPersistentData(&items, "autolabor/labors/", true); + World::GetPersistentData(&items, "autolabor/labors/", true); for (auto p = items.begin(); p != items.end(); p++) { @@ -594,7 +592,7 @@ static void init_state() std::stringstream name; name << "autolabor/labors/" << i; - labor_infos[i].config = pworld->AddPersistentData(name.str()); + labor_infos[i].config = World::AddPersistentData(name.str()); labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive; labor_infos[i].active_dwarfs = 0; @@ -633,11 +631,9 @@ static void generate_labor_to_skill_map() static void enable_plugin(color_ostream &out) { - auto pworld = Core::getInstance().getWorld(); - if (!config.isValid()) { - config = pworld->AddPersistentData("autolabor/config"); + config = World::AddPersistentData("autolabor/config"); config.ival(0) = 0; } diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index 0b66a7b9a..edcc01ecf 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -281,7 +281,7 @@ static void reset_tracking() static void init_map(color_ostream &out) { - auto config = Core::getInstance().getWorld()->GetPersistentData("burrows/config"); + auto config = World::GetPersistentData("burrows/config"); if (config.isValid()) { auto_grow = !!(config.ival(0) & 1); @@ -307,7 +307,7 @@ static void deinit_map(color_ostream &out) static PersistentDataItem create_config(color_ostream &out) { bool created; - auto rv = Core::getInstance().getWorld()->GetPersistentData("burrows/config", &created); + auto rv = World::GetPersistentData("burrows/config", &created); if (created && rv.isValid()) rv.ival(0) = 0; if (!rv.isValid()) diff --git a/plugins/follow.cpp b/plugins/follow.cpp index 96693df8d..5c14780a3 100644 --- a/plugins/follow.cpp +++ b/plugins/follow.cpp @@ -65,8 +65,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { if (!followedUnit) return CR_OK; //Don't do anything if we're not following a unit - DFHack::World *world = Core::getInstance().getWorld(); - if (world->ReadPauseState() && prevX==-1) return CR_OK; //Wait until the game is unpaused after first running "follow" to begin following + if (World::ReadPauseState() && prevX==-1) return CR_OK; //Wait until the game is unpaused after first running "follow" to begin following df::coord &unitPos = followedUnit->pos; @@ -120,7 +119,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) Gui::setViewCoords(x, y, z); //If, for some reason, the cursor is active and the screen is still moving, move the cursor along with the screen - if (c_x != -30000 && !world->ReadPauseState()) + if (c_x != -30000 && !World::ReadPauseState()) Gui::setCursorCoords(c_x - (prevX-x), c_y - (prevY-y), z); //Save this round's stuff for next time so we can monitor for changes made by the user diff --git a/plugins/mode.cpp b/plugins/mode.cpp index f9e6cd10c..d18cf7529 100644 --- a/plugins/mode.cpp +++ b/plugins/mode.cpp @@ -117,13 +117,9 @@ command_result mode (color_ostream &out_, vector & parameters) return CR_WRONG_USAGE; } - World *world; - { CoreSuspender suspend; - world = Core::getInstance().getWorld(); - world->Start(); - world->ReadGameMode(gm); + World::ReadGameMode(gm); } printCurrentModes(gm, out); @@ -202,7 +198,7 @@ command_result mode (color_ostream &out_, vector & parameters) { CoreSuspender suspend; - world->WriteGameMode(gm); + World::WriteGameMode(gm); } out << endl; diff --git a/plugins/power-meter.cpp b/plugins/power-meter.cpp index 17261adb2..8db6c6f30 100644 --- a/plugins/power-meter.cpp +++ b/plugins/power-meter.cpp @@ -177,8 +177,7 @@ static bool makePowerMeter(df::pressure_plate_info *info, int min_power, int max if (!enabled) { - auto pworld = Core::getInstance().getWorld(); - auto entry = pworld->GetPersistentData("power-meter/enabled", NULL); + auto entry = World::GetPersistentData("power-meter/enabled", NULL); if (!entry.isValid()) return false; @@ -202,8 +201,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan switch (event) { case SC_WORLD_LOADED: { - auto pworld = Core::getInstance().getWorld(); - bool enable = pworld->GetPersistentData("power-meter/enabled").isValid(); + bool enable = World::GetPersistentData("power-meter/enabled").isValid(); if (enable) { diff --git a/plugins/rename.cpp b/plugins/rename.cpp index ecebbb90c..bffb33111 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -215,8 +215,7 @@ static void init_buildings(bool enable) if (enable) { - auto pworld = Core::getInstance().getWorld(); - auto entry = pworld->GetPersistentData("rename/building_types"); + auto entry = World::GetPersistentData("rename/building_types"); if (entry.isValid()) { @@ -245,8 +244,7 @@ static bool renameBuilding(df::building *bld, std::string name) if (!name.empty() && !is_enabled_building(code)) { - auto pworld = Core::getInstance().getWorld(); - auto entry = pworld->GetPersistentData("rename/building_types", NULL); + auto entry = World::GetPersistentData("rename/building_types", NULL); if (!entry.isValid()) return false; diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index f35424309..8db7c9112 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -87,19 +87,18 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector ReadGameMode(gm); + World::ReadGameMode(gm); if(gm.g_mode == game_mode::DWARF) { // if the map is revealed and we're in fortress mode, force the game to pause. if(revealed == REVEALED) { - World->SetPauseState(true); + World::SetPauseState(true); } else if(nopause_state) { - World->SetPauseState(false); + World::SetPauseState(false); } } return CR_OK; @@ -185,14 +184,13 @@ command_result reveal(color_ostream &out, vector & params) CoreSuspender suspend; - World *World = Core::getInstance().getWorld(); if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); return CR_FAILURE; } t_gamemodes gm; - World->ReadGameMode(gm); + World::ReadGameMode(gm); if(gm.g_mode == game_mode::ADVENTURE) { revealAdventure(out); @@ -234,7 +232,7 @@ command_result reveal(color_ostream &out, vector & params) if(pause) { revealed = REVEALED; - World->SetPauseState(true); + World::SetPauseState(true); } else revealed = DEMON_REVEALED; @@ -264,14 +262,13 @@ command_result unreveal(color_ostream &out, vector & params) } CoreSuspender suspend; - World *World = Core::getInstance().getWorld(); if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); return CR_FAILURE; } t_gamemodes gm; - World->ReadGameMode(gm); + World::ReadGameMode(gm); if(gm.g_mode != game_mode::DWARF) { con.printerr("Only in fortress mode.\n"); @@ -337,7 +334,6 @@ command_result revflood(color_ostream &out, vector & params) } CoreSuspender suspend; uint32_t x_max,y_max,z_max; - World * World = Core::getInstance().getWorld(); if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); @@ -349,7 +345,7 @@ command_result revflood(color_ostream &out, vector & params) return CR_FAILURE; } t_gamemodes gm; - World->ReadGameMode(gm); + World::ReadGameMode(gm); if(gm.g_type != game_type::DWARF_MAIN && gm.g_mode != game_mode::DWARF ) { out.printerr("Only in proper dwarf mode.\n"); diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index 676c02ed5..ae87e4381 100755 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -106,9 +106,8 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) materialsReverser[world->raws.plants.all[i]->id] = i; } - World *w = Core::getInstance().getWorld(); t_gamemodes gm; - w->ReadGameMode(gm);// FIXME: check return value + World::ReadGameMode(gm);// FIXME: check return value // if game mode isn't fortress mode if(gm.g_mode != game_mode::DWARF || @@ -296,9 +295,8 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) return CR_OK; counter = 0; - World *w = Core::getInstance().getWorld(); t_gamemodes gm; - w->ReadGameMode(gm);// FIXME: check return value + World::ReadGameMode(gm);// FIXME: check return value // if game mode isn't fortress mode if(gm.g_mode != game_mode::DWARF || !(gm.g_type == game_type::DWARF_MAIN || gm.g_type == game_type::DWARF_RECLAIM)) diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index 7c880351e..324c924c4 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -347,10 +347,9 @@ static void load_engines() { clear_engines(); - auto pworld = Core::getInstance().getWorld(); std::vector vec; - pworld->GetPersistentData(&vec, "siege-engine/target/", true); + World::GetPersistentData(&vec, "siege-engine/target/", true); for (auto it = vec.begin(); it != vec.end(); ++it) { auto engine = find_engine(df::building::find(it->ival(0)), true); @@ -359,7 +358,7 @@ static void load_engines() engine->target.second = df::coord(it->ival(4), it->ival(5), it->ival(6)); } - pworld->GetPersistentData(&vec, "siege-engine/ammo/", true); + World::GetPersistentData(&vec, "siege-engine/ammo/", true); for (auto it = vec.begin(); it != vec.end(); ++it) { auto engine = find_engine(df::building::find(it->ival(0)), true); @@ -368,7 +367,7 @@ static void load_engines() engine->ammo_item_type = (df::item_type)it->ival(2); } - pworld->GetPersistentData(&vec, "siege-engine/stockpiles/", true); + World::GetPersistentData(&vec, "siege-engine/stockpiles/", true); for (auto it = vec.begin(); it != vec.end(); ++it) { auto engine = find_engine(df::building::find(it->ival(0)), true); @@ -377,14 +376,14 @@ static void load_engines() auto pile = df::building::find(it->ival(1)); if (!pile || pile->getType() != building_type::Stockpile) { - pworld->DeletePersistentData(*it); + World::DeletePersistentData(*it); continue;; } engine->stockpiles.insert(it->ival(1)); } - pworld->GetPersistentData(&vec, "siege-engine/profiles/", true); + World::GetPersistentData(&vec, "siege-engine/profiles/", true); for (auto it = vec.begin(); it != vec.end(); ++it) { auto engine = find_engine(df::building::find(it->ival(0)), true); @@ -393,7 +392,7 @@ static void load_engines() engine->profile.max_level = it->ival(2); } - pworld->GetPersistentData(&vec, "siege-engine/profile-workers/", true); + World::GetPersistentData(&vec, "siege-engine/profile-workers/", true); for (auto it = vec.begin(); it != vec.end(); ++it) { auto engine = find_engine(df::building::find(it->ival(0)), true); @@ -402,7 +401,7 @@ static void load_engines() auto unit = df::unit::find(it->ival(1)); if (!unit || !Units::isCitizen(unit)) { - pworld->DeletePersistentData(*it); + World::DeletePersistentData(*it); continue; } engine->profile.permitted_workers.push_back(it->ival(1)); @@ -434,9 +433,8 @@ static void clearTargetArea(df::building_siegeenginest *bld) if (auto engine = find_engine(bld)) engine->target = coord_range(); - auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/target/%d", bld->id); - pworld->DeletePersistentData(pworld->GetPersistentData(key)); + World::DeletePersistentData(World::GetPersistentData(key)); } static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, df::coord target_max) @@ -447,9 +445,8 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, if (!enable_plugin()) return false; - auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/target/%d", bld->id); - auto entry = pworld->GetPersistentData(key, NULL); + auto entry = World::GetPersistentData(key, NULL); if (!entry.isValid()) return false; @@ -491,9 +488,8 @@ static int setAmmoItem(lua_State *L) if (!is_valid_enum_item(item_type)) luaL_argerror(L, 2, "invalid item type"); - auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/ammo/%d", engine->id); - auto entry = pworld->GetPersistentData(key, NULL); + auto entry = World::GetPersistentData(key, NULL); if (!entry.isValid()) return 0; @@ -523,9 +519,8 @@ static void forgetStockpileLink(EngineInfo *engine, int pile_id) { engine->stockpiles.erase(pile_id); - auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/stockpiles/%d/%d", engine->id, pile_id); - pworld->DeletePersistentData(pworld->GetPersistentData(key)); + World::DeletePersistentData(World::GetPersistentData(key)); } static void update_stockpile_links(EngineInfo *engine) @@ -583,9 +578,8 @@ static bool addStockpileLink(df::building_siegeenginest *bld, df::building_stock if (!enable_plugin()) return false; - auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/stockpiles/%d/%d", bld->id, pile->id); - auto entry = pworld->GetPersistentData(key, NULL); + auto entry = World::GetPersistentData(key, NULL); if (!entry.isValid()) return false; @@ -620,9 +614,8 @@ static df::workshop_profile *saveWorkshopProfile(df::building_siegeenginest *bld return NULL; // Save skill limits - auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/profiles/%d", bld->id); - auto entry = pworld->GetPersistentData(key, NULL); + auto entry = World::GetPersistentData(key, NULL); if (!entry.isValid()) return NULL; @@ -637,18 +630,18 @@ static df::workshop_profile *saveWorkshopProfile(df::building_siegeenginest *bld auto &workers = engine->profile.permitted_workers; key = stl_sprintf("siege-engine/profile-workers/%d", bld->id); - pworld->GetPersistentData(&vec, key, true); + World::GetPersistentData(&vec, key, true); for (auto it = vec.begin(); it != vec.end(); ++it) { if (linear_index(workers, it->ival(1)) < 0) - pworld->DeletePersistentData(*it); + World::DeletePersistentData(*it); } for (size_t i = 0; i < workers.size(); i++) { key = stl_sprintf("siege-engine/profile-workers/%d/%d", bld->id, workers[i]); - entry = pworld->GetPersistentData(key, NULL); + entry = World::GetPersistentData(key, NULL); if (!entry.isValid()) continue; entry.ival(0) = engine->id; @@ -1802,8 +1795,7 @@ static bool enable_plugin() if (is_enabled) return true; - auto pworld = Core::getInstance().getWorld(); - auto entry = pworld->GetPersistentData("siege-engine/enabled", NULL); + auto entry = World::GetPersistentData("siege-engine/enabled", NULL); if (!entry.isValid()) return false; @@ -1828,8 +1820,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan case SC_MAP_LOADED: if (!gamemode || *gamemode == game_mode::DWARF) { - auto pworld = Core::getInstance().getWorld(); - bool enable = pworld->GetPersistentData("siege-engine/enabled").isValid(); + bool enable = World::GetPersistentData("siege-engine/enabled").isValid(); if (enable) { diff --git a/plugins/weather.cpp b/plugins/weather.cpp index 33fa45fd3..98476ef2c 100644 --- a/plugins/weather.cpp +++ b/plugins/weather.cpp @@ -84,7 +84,6 @@ command_result weather (color_ostream &con, vector & parameters) CoreSuspender suspend; - DFHack::World * w = Core::getInstance().getWorld(); if(!df::global::current_weather) { con << "Weather support seems broken :(" << std::endl; @@ -123,22 +122,22 @@ command_result weather (color_ostream &con, vector & parameters) if(rain) { con << "Here comes the rain." << std::endl; - w->SetCurrentWeather(weather_type::Rain); + World::SetCurrentWeather(weather_type::Rain); } if(snow) { con << "Snow everywhere!" << std::endl; - w->SetCurrentWeather(weather_type::Snow); + World::SetCurrentWeather(weather_type::Snow); } if(clear) { con << "Suddenly, sunny weather!" << std::endl; - w->SetCurrentWeather(weather_type::None); + World::SetCurrentWeather(weather_type::None); } if(val_override != -1) { con << "I have no damn idea what this is... " << val_override << std::endl; - w->SetCurrentWeather(val_override); + World::SetCurrentWeather(val_override); } // FIXME: weather lock needs map ID to work reliably... needs to be implemented. } diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 98258682e..2720baa83 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -438,9 +438,7 @@ static void start_protect(color_ostream &out) static void init_state(color_ostream &out) { - auto pworld = Core::getInstance().getWorld(); - - config = pworld->GetPersistentData("workflow/config"); + config = World::GetPersistentData("workflow/config"); if (config.isValid() && config.ival(0) == -1) config.ival(0) = 0; @@ -448,14 +446,14 @@ static void init_state(color_ostream &out) // Parse constraints std::vector items; - pworld->GetPersistentData(&items, "workflow/constraints"); + World::GetPersistentData(&items, "workflow/constraints"); for (int i = items.size()-1; i >= 0; i--) { if (get_constraint(out, items[i].val(), &items[i])) continue; out.printerr("Lost constraint %s\n", items[i].val().c_str()); - pworld->DeletePersistentData(items[i]); + World::DeletePersistentData(items[i]); } last_tick_frame_count = world->frame_counter; @@ -469,11 +467,9 @@ static void init_state(color_ostream &out) static void enable_plugin(color_ostream &out) { - auto pworld = Core::getInstance().getWorld(); - if (!config.isValid()) { - config = pworld->AddPersistentData("workflow/config"); + config = World::AddPersistentData("workflow/config"); config.ival(0) = 0; } @@ -729,7 +725,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str nct->config = *cfg; else { - nct->config = Core::getInstance().getWorld()->AddPersistentData("workflow/constraints"); + nct->config = World::AddPersistentData("workflow/constraints"); nct->init(str); } @@ -743,7 +739,7 @@ static void delete_constraint(ItemConstraint *cv) if (idx >= 0) vector_erase_at(constraints, idx); - Core::getInstance().getWorld()->DeletePersistentData(cv->config); + World::DeletePersistentData(cv->config); delete cv; } diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 6c1af4768..542ce8a02 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -2769,10 +2769,7 @@ public: if(!rconfig.isValid()) { string keyname = "autobutcher/watchlist/" + getRaceName(raceId); - auto pworld = Core::getInstance().getWorld(); - rconfig = pworld->GetPersistentData(keyname); - if(!rconfig.isValid()) - rconfig = pworld->AddPersistentData(keyname); + rconfig = World::GetPersistentData(keyname, NULL); } if(rconfig.isValid()) { @@ -2795,7 +2792,7 @@ public: { if(!rconfig.isValid()) return; - Core::getInstance().getWorld()->DeletePersistentData(rconfig); + World::DeletePersistentData(rconfig); } void SortUnitsByAge() @@ -3405,13 +3402,11 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) command_result start_autobutcher(color_ostream &out) { - auto pworld = Core::getInstance().getWorld(); - enable_autobutcher = true; if (!config_autobutcher.isValid()) { - config_autobutcher = pworld->AddPersistentData("autobutcher/config"); + config_autobutcher = World::AddPersistentData("autobutcher/config"); if (!config_autobutcher.isValid()) { @@ -3437,9 +3432,8 @@ command_result start_autobutcher(color_ostream &out) command_result init_autobutcher(color_ostream &out) { cleanup_autobutcher(out); - auto pworld = Core::getInstance().getWorld(); - config_autobutcher = pworld->GetPersistentData("autobutcher/config"); + config_autobutcher = World::GetPersistentData("autobutcher/config"); if(config_autobutcher.isValid()) { if (config_autobutcher.ival(0) == -1) @@ -3471,7 +3465,7 @@ command_result init_autobutcher(color_ostream &out) // read watchlist from save std::vector items; - pworld->GetPersistentData(&items, "autobutcher/watchlist/", true); + World::GetPersistentData(&items, "autobutcher/watchlist/", true); for (auto p = items.begin(); p != items.end(); p++) { string key = p->key(); @@ -3502,12 +3496,11 @@ command_result cleanup_autobutcher(color_ostream &out) command_result start_autonestbox(color_ostream &out) { - auto pworld = Core::getInstance().getWorld(); enable_autonestbox = true; if (!config_autobutcher.isValid()) { - config_autonestbox = pworld->AddPersistentData("autonestbox/config"); + config_autonestbox = World::AddPersistentData("autonestbox/config"); if (!config_autobutcher.isValid()) { @@ -3528,9 +3521,8 @@ command_result start_autonestbox(color_ostream &out) command_result init_autonestbox(color_ostream &out) { cleanup_autonestbox(out); - auto pworld = Core::getInstance().getWorld(); - config_autonestbox = pworld->GetPersistentData("autonestbox/config"); + config_autonestbox = World::GetPersistentData("autonestbox/config"); if(config_autonestbox.isValid()) { if (config_autonestbox.ival(0) == -1) From ab90e3eefe4966139d38f33bf629969c1cf8fa4e Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 7 Oct 2012 17:35:41 -0500 Subject: [PATCH 117/196] Manipulator - take false identities into account --- plugins/manipulator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 633098a10..b3852437c 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -443,8 +443,8 @@ void viewscreen_unitlaborsst::refreshNames() UnitInfo *cur = units[i]; df::unit *unit = cur->unit; - cur->name = Translation::TranslateName(&unit->name, false); - cur->transname = Translation::TranslateName(&unit->name, true); + cur->name = Translation::TranslateName(Units::getVisibleName(unit), false); + cur->transname = Translation::TranslateName(Units::getVisibleName(unit), true); cur->profession = Units::getProfessionName(unit); } calcSize(); From 408f0cb06e682c437dbe82b64eb229d41b65fe44 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 8 Oct 2012 12:10:02 +0400 Subject: [PATCH 118/196] Add a small stand-alone utility for managing binary patches. --- Lua API.html | 6 + Lua API.rst | 8 + NEWS | 2 + Readme.html | 577 ++++++++++++++++++++++------------------- Readme.rst | 30 +++ library/CMakeLists.txt | 3 +- library/binpatch.cpp | 308 ++++++++++++++++++++++ 7 files changed, 660 insertions(+), 274 deletions(-) create mode 100644 library/binpatch.cpp diff --git a/Lua API.html b/Lua API.html index 047ef9786..06fa5418e 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1914,6 +1914,12 @@ utils.insert_or_update(soul.skills, {new=true, id=..., rating=...}, 'id')

    (For an explanation of new=true, see table assignment in the wrapper section)

  • +
  • utils.erase_sorted_key(vector,key,field,cmpfun)

    +

    Removes the item with the given key from the list. Returns: did_erase, vector[idx], idx.

    +
  • +
  • utils.erase_sorted(vector,item,field,cmpfun)

    +

    Exactly like erase_sorted_key, but if field is specified, takes the key from item[field].

    +
  • utils.prompt_yes_no(prompt, default)

    Presents a yes/no prompt to the user. If default is not nil, allows just pressing Enter to submit the default choice. diff --git a/Lua API.rst b/Lua API.rst index bf7ee45a7..fbb4b7d82 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1806,6 +1806,14 @@ utils (For an explanation of ``new=true``, see table assignment in the wrapper section) +* ``utils.erase_sorted_key(vector,key,field,cmpfun)`` + + Removes the item with the given key from the list. Returns: *did_erase, vector[idx], idx*. + +* ``utils.erase_sorted(vector,item,field,cmpfun)`` + + Exactly like ``erase_sorted_key``, but if field is specified, takes the key from ``item[field]``. + * ``utils.prompt_yes_no(prompt, default)`` Presents a yes/no prompt to the user. If ``default`` is not *nil*, diff --git a/NEWS b/NEWS index b36d2f121..5072cebc3 100644 --- a/NEWS +++ b/NEWS @@ -4,8 +4,10 @@ DFHack future - support for displaying active keybindings properly. Notable bugfixes: - autobutcher can be re-enabled again after being stopped. + - stopped Dwarf Manipulator from unmasking vampires. Misc improvements: - fastdwarf: new mode using debug flags, and some internal consistency fixes. + - added a small stand-alone utility for applying and removing binary patches. DFHack v0.34.11-r2 diff --git a/Readme.html b/Readme.html index caf1f6a0e..c1d2d0d3c 100644 --- a/Readme.html +++ b/Readme.html @@ -338,187 +338,190 @@ access DF memory and allow for easier development of new tools.

  • Getting DFHack
  • Compatibility
  • Installation/Removal
  • -
  • Using DFHack
  • -
  • Something doesn't work, help!
  • -
  • The init file
  • -

    Something doesn't work, help!

    +

    Something doesn't work, help!

    First, don't panic :) Second, dfhack keeps a few log files in DF's folder - stderr.log and stdout.log. You can look at those and possibly find out what's happening. @@ -596,13 +627,13 @@ the issues tracker on github, contact me ( -

    The init file

    +

    The init file

    If your DF folder contains a file named dfhack.init, its contents will be run every time you start DF. This allows setting up keybindings. An example file is provided as dfhack.init-example - you can tweak it and rename to dfhack.init if you want to use this functionality.

    -

    Setting keybindings

    +

    Setting keybindings

    To set keybindings, use the built-in keybinding command. Like any other command it can be used at any time from the console, but it is also meaningful in the DFHack init file.

    @@ -647,7 +678,7 @@ for context foo/bar/baz, possible matches are
    -

    Commands

    +

    Commands

    DFHack command syntax consists of a command name, followed by arguments separated by whitespace. To include whitespace in an argument, quote it in double quotes. To include a double quote character, use \" inside double quotes.

    @@ -669,13 +700,13 @@ The following two command lines are exactly equivalent:

    to retrieve further help without having to look at this document. Alternatively, some accept a 'help'/'?' option on their command line.

    -

    Game progress

    +

    Game progress

    -

    die

    +

    die

    Instantly kills DF without saving.

    -

    forcepause

    +

    forcepause

    Forces DF to pause. This is useful when your FPS drops below 1 and you lose control of the game.

    @@ -686,12 +717,12 @@ control of the game.

    -

    nopause

    +

    nopause

    Disables pausing (both manual and automatic) with the exception of pause forced by 'reveal hell'. This is nice for digging under rivers.

    -

    fastdwarf

    +

    fastdwarf

    Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and perform tasks quickly. Teledwarf makes dwarves move instantaneously, but do jobs at the same speed.

      @@ -706,29 +737,29 @@ by 'reveal hell'. This is nice for digging under rivers.

    -

    Game interface

    +

    Game interface

    -

    follow

    +

    follow

    Makes the game view follow the currently highlighted unit after you exit from current menu/cursor mode. Handy for watching dwarves running around. Deactivated by moving the view manually.

    -

    tidlers

    +

    tidlers

    Toggle between all possible positions where the idlers count can be placed.

    -

    twaterlvl

    +

    twaterlvl

    Toggle between displaying/not displaying liquid depth as numbers.

    -

    copystock

    +

    copystock

    Copies the parameters of the currently highlighted stockpile to the custom stockpile settings and switches to custom stockpile placement mode, effectively allowing you to copy/paste stockpiles easily.

    -

    rename

    +

    rename

    Allows renaming various things.

    Options:

    @@ -762,9 +793,9 @@ siege engine or an activity zone.
    -

    Adventure mode

    +

    Adventure mode

    -

    adv-bodyswap

    +

    adv-bodyswap

    This allows taking control over your followers and other creatures in adventure mode. For example, you can make them pick up new arms and armor and equip them properly.

    @@ -777,7 +808,7 @@ properly.

    -

    advtools

    +

    advtools

    A package of different adventure mode tools (currently just one)

    Usage:

    @@ -800,9 +831,9 @@ on item type and being in shop.
    -

    Map modification

    +

    Map modification

    -

    changelayer

    +

    changelayer

    Changes material of the geology layer under cursor to the specified inorganic RAW material. Can have impact on all surrounding regions, not only your embark! By default changing stone to soil and vice versa is not allowed. By default @@ -877,7 +908,7 @@ You did save your game, right?

    -

    changevein

    +

    changevein

    Changes material of the vein under cursor to the specified inorganic RAW material. Only affects tiles within the current 16x16 block - for veins and large clusters, you will need to use this command multiple times.

    @@ -890,7 +921,7 @@ large clusters, you will need to use this command multiple times.

    -

    changeitem

    +

    changeitem

    Allows changing item material and base quality. By default the item currently selected in the UI will be changed (you can select items in the 'k' list or inside containers/inventory). By default change is only allowed if materials @@ -930,7 +961,7 @@ crafters/haulers.

    -

    colonies

    +

    colonies

    Allows listing all the vermin colonies on the map and optionally turning them into honey bee colonies.

    Options:

    @@ -945,12 +976,12 @@ crafters/haulers.

    -

    deramp (by zilpin)

    +

    deramp (by zilpin)

    Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation. It also removes any and all 'down ramps' that can remain after a cave-in (you don't have to designate anything for that to happen).

    -

    feature

    +

    feature

    Enables management of map features.

    • Discovering a magma feature (magma pool, volcano, magma sea, or curious @@ -975,7 +1006,7 @@ that cavern to grow within your fortress.
    -

    liquids

    +

    liquids

    Allows adding magma, water and obsidian to the game. It replaces the normal dfhack command line and can't be used from a hotkey. Settings will be remembered as long as dfhack runs. Intended for use in combination with the command @@ -988,13 +1019,13 @@ temperatures (creating heat traps). You've been warned.

    -

    liquids-here

    +

    liquids-here

    Run the liquid spawner with the current/last settings made in liquids (if no settings in liquids were made it paints a point of 7/7 magma by default).

    Intended to be used as keybinding. Requires an active in-game cursor.

    -

    tiletypes

    +

    tiletypes

    Can be used for painting map tiles and is an interactive command, much like liquids.

    The tool works with two set of options and a brush. The brush determines which @@ -1055,27 +1086,27 @@ up.

    For more details, see the 'help' command while using this.

    -

    tiletypes-commands

    +

    tiletypes-commands

    Runs tiletypes commands, separated by ;. This makes it possible to change tiletypes modes from a hotkey.

    -

    tiletypes-here

    +

    tiletypes-here

    Apply the current tiletypes options at the in-game cursor position, including the brush. Can be used from a hotkey.

    -

    tiletypes-here-point

    +

    tiletypes-here-point

    Apply the current tiletypes options at the in-game cursor position to a single tile. Can be used from a hotkey.

    -

    tubefill

    +

    tubefill

    Fills all the adamantine veins again. Veins that were empty will be filled in too, but might still trigger a demon invasion (this is a known bug).

    -

    extirpate

    +

    extirpate

    A tool for getting rid of trees and shrubs. By default, it only kills a tree/shrub under the cursor. The plants are turned into ashes instantly.

    Options:

    @@ -1095,20 +1126,20 @@ a tree/shrub under the cursor. The plants are turned into ashes instantly.

    -

    grow

    +

    grow

    Makes all saplings present on the map grow into trees (almost) instantly.

    -

    immolate

    +

    immolate

    Very similar to extirpate, but additionally sets the plants on fire. The fires can and will spread ;)

    -

    regrass

    +

    regrass

    Regrows grass. Not much to it ;)

    -

    weather

    +

    weather

    Prints the current weather map by default.

    Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'.

    Options:

    @@ -1129,9 +1160,9 @@ can and will spread ;)

    -

    Map inspection

    +

    Map inspection

    -

    cursecheck

    +

    cursecheck

    Checks a single map tile or the whole map/world for cursed creatures (ghosts, vampires, necromancers, werebeasts, zombies).

    With an active in-game cursor only the selected tile will be observed. @@ -1186,17 +1217,17 @@ of curses, for example.

    -

    flows

    +

    flows

    A tool for checking how many tiles contain flowing liquids. If you suspect that your magma sea leaks into HFS, you can use this tool to be sure without revealing the map.

    -

    probe

    +

    probe

    Can be used to determine tile properties like temperature.

    -

    prospect

    +

    prospect

    Prints a big list of all the present minerals and plants. By default, only the visible part of the map is scanned.

    Options:

    @@ -1215,7 +1246,7 @@ the visible part of the map is scanned.

    -

    Pre-embark estimate

    +

    Pre-embark estimate

    If prospect is called during the embark selection screen, it displays an estimate of layer stone availability.

    @@ -1240,7 +1271,7 @@ that is actually present.

    -

    reveal

    +

    reveal

    This reveals the map. By default, HFS will remain hidden so that the demons don't spawn. You can use 'reveal hell' to reveal everything. With hell revealed, you won't be able to unpause until you hide the map again. If you really want @@ -1249,34 +1280,34 @@ to unpause with hell revealed, use 'reveal demons'.

    you move. When you use it this way, you don't need to run 'unreveal'.

    -

    unreveal

    +

    unreveal

    Reverts the effects of 'reveal'.

    -

    revtoggle

    +

    revtoggle

    Switches between 'reveal' and 'unreveal'.

    -

    revflood

    +

    revflood

    This command will hide the whole map and then reveal all the tiles that have a path to the in-game cursor.

    -

    revforget

    +

    revforget

    When you use reveal, it saves information about what was/wasn't visible before revealing everything. Unreveal uses this information to hide things again. This command throws away the information. For example, use in cases where you abandoned with the fort revealed and no longer want the data.

    -

    showmood

    +

    showmood

    Shows all items needed for the currently active strange mood.

    -

    Designations

    +

    Designations

    -

    burrow

    +

    burrow

    Miscellaneous burrow control. Allows manipulating burrows and automated burrow expansion while digging.

    Options:

    @@ -1324,17 +1355,17 @@ Digging 1-wide corridors with the miner inside the burrow is SLOW.
    -

    digv

    +

    digv

    Designates a whole vein for digging. Requires an active in-game cursor placed over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles).

    -

    digvx

    +

    digvx

    A permanent alias for 'digv x'.

    -

    digl

    +

    digl

    Designates layer stone for digging. Requires an active in-game cursor placed over a layer stone tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles). With the 'undo' option it @@ -1342,11 +1373,11 @@ will remove the dig designation instead (if you realize that digging out a 50 z-level deep layer was not such a good idea after all).

    -

    diglx

    +

    diglx

    A permanent alias for 'digl x'.

    -

    digexp

    +

    digexp

    This command can be used for exploratory mining.

    See: http://df.magmawiki.com/index.php/DF2010:Exploratory_mining

    There are two variables that can be set: pattern and filter.

    @@ -1409,7 +1440,7 @@ z-level deep layer was not such a good idea after all).

    -

    digcircle

    +

    digcircle

    A command for easy designation of filled and hollow circles. It has several types of options.

    Shape:

    @@ -1472,7 +1503,7 @@ repeats with the last selected parameters.

    -

    digtype

    +

    digtype

    For every tile on the map of the same vein type as the selected tile, this command designates it to have the same designation as the selected tile. If the selected tile has no designation, they will be dig designated. If an argument is given, the designation of the selected tile is ignored, and all appropriate tiles are set to the specified designation.

    Options:

    @@ -1500,7 +1531,7 @@ If an argument is given, the designation of the selected tile is ignored, and al
    -

    filltraffic

    +

    filltraffic

    Set traffic designations using flood-fill starting at the cursor.

    Traffic Type Codes:

    @@ -1539,7 +1570,7 @@ If an argument is given, the designation of the selected tile is ignored, and al 'filltraffic H' - When used in a room with doors, it will set traffic to HIGH in just that room.
    -

    alltraffic

    +

    alltraffic

    Set traffic designations for every single tile of the map (useful for resetting traffic designations).

    Traffic Type Codes:

    @@ -1563,7 +1594,7 @@ If an argument is given, the designation of the selected tile is ignored, and al 'alltraffic N' - Set traffic to 'normal' for all tiles.
    -

    getplants

    +

    getplants

    This tool allows plant gathering and tree cutting by RAW ID. Specify the types of trees to cut down and/or shrubs to gather by their plant names, separated by spaces.

    @@ -1590,9 +1621,9 @@ all valid plant IDs will be listed.

    -

    Cleanup and garbage disposal

    +

    Cleanup and garbage disposal

    -

    clean

    +

    clean

    Cleans all the splatter that get scattered all over the map, items and creatures. In an old fortress, this can significantly reduce FPS lag. It can also spoil your !!FUN!!, so think before you use it.

    @@ -1626,12 +1657,12 @@ also spoil your !!FUN!!, so think before you use it.

    -

    spotclean

    +

    spotclean

    Works like 'clean map snow mud', but only for the tile under the cursor. Ideal if you want to keep that bloody entrance 'clean map' would clean up.

    -

    autodump

    +

    autodump

    This utility lets you quickly move all items designated to be dumped. Items are instantly moved to the cursor position, the dump flag is unset, and the forbid flag is set, as if it had been dumped normally. @@ -1658,17 +1689,17 @@ Be aware that any active dump item tasks still point at the item.

    -

    autodump-destroy-here

    +

    autodump-destroy-here

    Destroy items marked for dumping under cursor. Identical to autodump destroy-here, but intended for use as keybinding.

    -

    autodump-destroy-item

    +

    autodump-destroy-item

    Destroy the selected item. The item may be selected in the 'k' list, or inside a container. If called again before the game is resumed, cancels destroy.

    -

    cleanowned

    +

    cleanowned

    Confiscates items owned by dwarfs. By default, owned food on the floor and rotten items are confistacted and dumped.

    Options:

    @@ -1702,13 +1733,13 @@ worn items with 'X' damage and above.
    -

    Bugfixes

    +

    Bugfixes

    -

    drybuckets

    +

    drybuckets

    This utility removes water from all buckets in your fortress, allowing them to be safely used for making lye.

    -

    fixdiplomats

    +

    fixdiplomats

    Up to version 0.31.12, Elves only sent Diplomats to your fortress to propose tree cutting quotas due to a bug; once that bug was fixed, Elves stopped caring about excess tree cutting. This command adds a Diplomat position to all Elven @@ -1717,19 +1748,19 @@ to violate them and potentially start wars) in case you haven't already modified your raws accordingly.

    -

    fixmerchants

    +

    fixmerchants

    This command adds the Guild Representative position to all Human civilizations, allowing them to make trade agreements (just as they did back in 0.28.181.40d and earlier) in case you haven't already modified your raws accordingly.

    -

    fixveins

    +

    fixveins

    Removes invalid references to mineral inclusions and restores missing ones. Use this if you broke your embark with tools like tiletypes, or if you accidentally placed a construction on top of a valuable mineral floor.

    -

    tweak

    +

    tweak

    Contains various tweaks for minor bugs.

    One-shot subcommands:

    @@ -1817,9 +1848,9 @@ to make them stand out more in the list.
    -

    Mode switch and reclaim

    +

    Mode switch and reclaim

    -

    lair

    +

    lair

    This command allows you to mark the map as 'monster lair', preventing item scatter on abandon. When invoked as 'lair reset', it does the opposite.

    Unlike reveal, this command doesn't save the information about tiles - you @@ -1839,7 +1870,7 @@ won't be able to restore state of real monster lairs using 'lair reset'.

    -

    mode

    +

    mode

    This command lets you see and change the game mode directly. Not all combinations are good for every situation and most of them will produce undesirable results. There are a few good ones though.

    @@ -1859,9 +1890,9 @@ You just created a returnable mountain home and gained an adventurer.

    -

    Visualizer and data export

    +

    Visualizer and data export

    -

    ssense / stonesense

    +

    ssense / stonesense

    An isometric visualizer that runs in a second window. This requires working graphics acceleration and at least a dual core CPU (otherwise it will slow down DF).

    @@ -1874,19 +1905,19 @@ thread: http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository

    -

    mapexport

    +

    mapexport

    Export the current loaded map as a file. This will be eventually usable with visualizers.

    -

    dwarfexport

    +

    dwarfexport

    Export dwarves to RuneSmith-compatible XML.

    -

    Job management

    +

    Job management

    -

    job

    +

    job

    Command for general job query and manipulation.

    Options:
    @@ -1905,7 +1936,7 @@ in a workshop, or the unit/jobs screen.
    -

    job-material

    +

    job-material

    Alter the material of the selected job.

    Invoked as:

    @@ -1923,7 +1954,7 @@ over the first available choice with the matching material.
     
     
    -

    job-duplicate

    +

    job-duplicate

    Duplicate the selected job in a workshop:
      @@ -1934,7 +1965,7 @@ instantly duplicates the job.
    -

    workflow

    +

    workflow

    Manage control of repeat jobs.

    Usage:

    @@ -1958,7 +1989,7 @@ Otherwise, enables or disables any of the following options:

    -

    Function

    +

    Function

    When the plugin is enabled, it protects all repeat jobs from removal. If they do disappear due to any cause, they are immediately re-added to their workshop and suspended.

    @@ -1969,7 +2000,7 @@ the amount has to drop before jobs are resumed; this is intended to reduce the frequency of jobs being toggled.

    -

    Constraint examples

    +

    Constraint examples

    Keep metal bolts within 900-1000, and wood/bone within 150-200.

     workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
    @@ -2008,15 +2039,15 @@ command.
     
    -

    Fortress activity management

    +

    Fortress activity management

    -

    seedwatch

    +

    seedwatch

    Tool for turning cooking of seeds and plants on/off depending on how much you have of them.

    See 'seedwatch help' for detailed description.

    -

    zone

    +

    zone

    Helps a bit with managing activity zones (pens, pastures and pits) and cages.

    Options:

    @@ -2115,7 +2146,7 @@ for war/hunt). Negatable.
    -

    Usage with single units

    +

    Usage with single units

    One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units @@ -2124,7 +2155,7 @@ and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way.

    -

    Usage with filters

    +

    Usage with filters

    All filters can be used together with the 'assign' command.

    Restrictions: It's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. @@ -2142,14 +2173,14 @@ are not properly added to your own stocks; slaughtering them should work).

    Most filters can be negated (e.g. 'not grazer' -> race is not a grazer).

    -

    Mass-renaming

    +

    Mass-renaming

    Using the 'nick' command you can set the same nickname for multiple units. If used without 'assign', 'all' or 'count' it will rename all units in the current default target zone. Combined with 'assign', 'all' or 'count' (and further optional filters) it will rename units matching the filter conditions.

    -

    Cage zones

    +

    Cage zones

    Using the 'tocages' command you can assign units to a set of cages, for example a room next to your butcher shop(s). They will be spread evenly among available cages to optimize hauling to and butchering from them. For this to work you need @@ -2160,7 +2191,7 @@ would make no sense, but can be used together with 'nick' or 'remnick' and all the usual filters.

    -

    Examples

    +

    Examples

    zone assign all own ALPACA minage 3 maxage 10
    Assign all own alpacas who are between 3 and 10 years old to the selected @@ -2186,7 +2217,7 @@ on the current default zone.
    -

    autonestbox

    +

    autonestbox

    Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless @@ -2215,7 +2246,7 @@ frames between runs.

    -

    autobutcher

    +

    autobutcher

    Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units will be processed.

    Named units will be completely ignored (to protect specific animals from @@ -2323,7 +2354,7 @@ autobutcher.bat

    -

    autolabor

    +

    autolabor

    Automatically manage dwarf labors.

    When enabled, autolabor periodically checks your dwarves and enables or disables labors. It tries to keep as many dwarves as possible busy but @@ -2337,14 +2368,14 @@ while it is enabled.

    -

    Other

    +

    Other

    -

    catsplosion

    +

    catsplosion

    Makes cats just multiply. It is not a good idea to run this more than once or twice.

    -

    dfusion

    +

    dfusion

    This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin.

    See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15

    Confirmed working DFusion plugins:

    @@ -2366,7 +2397,7 @@ twice.

    -

    misery

    +

    misery

    When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).

    Usage:

    @@ -2390,7 +2421,7 @@ twice.

    -

    Scripts

    +

    Scripts

    Lua or ruby scripts placed in the hack/scripts/ directory are considered for execution as if they were native DFHack commands. They are listed at the end of the 'ls' command output.

    @@ -2399,7 +2430,7 @@ only be listed by ls if called as 'ls -a'. This is intended as a way to hide scripts that are obscure, developer-oriented, or should be used as keybindings.

    Some notable scripts:

    -

    fix/*

    +

    fix/*

    Scripts in this subdirectory fix various bugs and issues, some of them obscure.

    • fix/dead-units

      @@ -2425,22 +2456,22 @@ caused by autodump bugs or other hacking mishaps.

    -

    gui/*

    +

    gui/*

    Scripts that implement dialogs inserted into the main game window are put in this directory.

    -

    quicksave

    +

    quicksave

    If called in dwarf mode, makes DF immediately auto-save the game by setting a flag normally used in seasonal auto-save.

    -

    setfps

    +

    setfps

    Run setfps <number> to set the FPS cap at runtime, in case you want to watch combat in slow motion or something :)

    -

    siren

    +

    siren

    Wakes up sleeping units, cancels breaks and stops parties either everywhere, or in the burrows given as arguments. In return, adds bad thoughts about noise, tiredness and lack of protection. Also, the units with interrupted @@ -2448,7 +2479,7 @@ breaks will go on break again a lot sooner. The script is intended for emergencies, e.g. when a siege appears, and all your military is partying.

    -

    growcrops

    +

    growcrops

    Instantly grow seeds inside farming plots.

    With no argument, this command list the various seed types currently in use in your farming plots. @@ -2460,7 +2491,7 @@ growcrops plump 40

    -

    removebadthoughts

    +

    removebadthoughts

    This script remove negative thoughts from your dwarves. Very useful against tantrum spirals.

    With a selected unit in 'v' mode, will clear this unit's mind, otherwise @@ -2473,7 +2504,7 @@ you unpause.

    it removed.

    -

    slayrace

    +

    slayrace

    Kills any unit of a given race.

    With no argument, lists the available races.

    With the special argument 'him', targets only the selected creature.

    @@ -2499,7 +2530,7 @@ slayrace elve magma
    -

    magmasource

    +

    magmasource

    Create an infinite magma source on a tile.

    This script registers a map tile as a magma source, and every 12 game ticks that tile receives 1 new unit of flowing magma.

    @@ -2514,7 +2545,7 @@ To remove all placed sources, call magmasource stop

    With no argument, this command shows an help message and list existing sources.

    -

    digfort

    +

    digfort

    A script to designate an area for digging according to a plan in csv format.

    This script, inspired from quickfort, can designate an area for digging. Your plan should be stored in a .csv file like this:

    @@ -2532,7 +2563,7 @@ To skip a row in your design, use a single ;.<

    The script takes the plan filename, starting from the root df folder.

    -

    superdwarf

    +

    superdwarf

    Similar to fastdwarf, per-creature.

    To make any creature superfast, target it ingame using 'v' and:

    @@ -2542,17 +2573,17 @@ superdwarf add
     

    This plugin also shortens the 'sleeping' and 'on break' periods of targets.

    -

    drainaquifer

    +

    drainaquifer

    Remove all 'aquifer' tag from the map blocks. Irreversible.

    -

    deathcause

    +

    deathcause

    Focus a body part ingame, and this script will display the cause of death of the creature.

    -

    In-game interface tools

    +

    In-game interface tools

    These tools work by displaying dialogs or overlays in the game window, and are mostly implemented by lua scripts.

    @@ -2563,7 +2594,7 @@ display the word "DFHack" on the screen somewhere while active.

    guideline because it arguably just fixes small usability bugs in the game UI.

    -

    Dwarf Manipulator

    +

    Dwarf Manipulator

    Implemented by the manipulator plugin. To activate, open the unit screen and press 'l'.

    This tool implements a Dwarf Therapist-like interface within the game UI. The @@ -2599,13 +2630,13 @@ cursor onto that cell instead of toggling it. directly to the main dwarf mode screen.

    -

    gui/liquids

    +

    gui/liquids

    To use, bind to a key and activate in the 'k' mode.

    While active, use the suggested keys to switch the usual liquids parameters, and Enter to select the target area and apply changes.

    -

    gui/mechanisms

    +

    gui/mechanisms

    To use, bind to a key and activate in the 'q' mode.

    Lists mechanisms connected to the building, and their links. Navigating the list centers the view on the relevant linked buildings.

    @@ -2614,7 +2645,7 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter re-entering the mechanisms ui.

    -

    gui/rename

    +

    gui/rename

    Backed by the rename plugin, this script allows entering the desired name via a simple dialog in the game ui.

      @@ -2630,14 +2661,14 @@ It is also possible to rename zones from the 'i' menu.

      The building or unit options are automatically assumed when in relevant ui state.

    -

    gui/room-list

    +

    gui/room-list

    To use, bind to a key and activate in the 'q' mode, either immediately or after opening the assign owner page.

    The script lists other rooms owned by the same owner, or by the unit selected in the assign list, and allows unassigning them.

    -

    gui/choose-weapons

    +

    gui/choose-weapons

    Bind to a key, and activate in the Equip->View/Customize page of the military screen.

    Depending on the cursor location, it rewrites all 'individual choice weapon' entries in the selected squad or position to use a specific weapon type matching the assigned @@ -2648,7 +2679,7 @@ and may lead to inappropriate weapons being selected.

    -

    Behavior Mods

    +

    Behavior Mods

    These plugins, when activated via configuration UI or by detecting certain structures in RAWs, modify the game engine behavior concerning the target objects to add features not otherwise present.

    @@ -2659,20 +2690,20 @@ technical challenge, and do not represent any long-term plans to produce more similar modifications of the game.

    -

    Siege Engine

    +

    Siege Engine

    The siege-engine plugin enables siege engines to be linked to stockpiles, and aimed at an arbitrary rectangular area across Z levels, instead of the original four directions. Also, catapults can be ordered to load arbitrary objects, not just stones.

    -

    Rationale

    +

    Rationale

    Siege engines are a very interesting feature, but sadly almost useless in the current state because they haven't been updated since 2D and can only aim in four directions. This is an attempt to bring them more up to date until Toady has time to work on it. Actual improvements, e.g. like making siegers bring their own, are something only Toady can do.

    -

    Configuration UI

    +

    Configuration UI

    The configuration front-end to the plugin is implemented by the gui/siege-engine script. Bind it to a key and activate after selecting a siege engine in 'q' mode.

    The main mode displays the current target, selected ammo item type, linked stockpiles and @@ -2693,7 +2724,7 @@ menu.

    -

    Power Meter

    +

    Power Meter

    The power-meter plugin implements a modified pressure plate that detects power being supplied to gear boxes built in the four adjacent N/S/W/E tiles.

    The configuration front-end is implemented by the gui/power-meter script. Bind it to a @@ -2702,11 +2733,11 @@ key and activate after selecting Pressure Plate in the build menu.

    configuration page, but configures parameters relevant to the modded power meter building.

    -

    Steam Engine

    +

    Steam Engine

    The steam-engine plugin detects custom workshops with STEAM_ENGINE in their token, and turns them into real steam engines.

    -

    Rationale

    +

    Rationale

    The vanilla game contains only water wheels and windmills as sources of power, but windmills give relatively little power, and water wheels require flowing water, which must either be a real river and thus immovable and @@ -2717,7 +2748,7 @@ it can be done just by combining existing features of the game engine in a new way with some glue code and a bit of custom logic.

    -

    Construction

    +

    Construction

    The workshop needs water as its input, which it takes via a passable floor tile below it, like usual magma workshops do. The magma version also needs magma.

    @@ -2741,7 +2772,7 @@ short axles that can be built later than both of the engines.

    -

    Operation

    +

    Operation

    In order to operate the engine, queue the Stoke Boiler job (optionally on repeat). A furnace operator will come, possibly bringing a bar of fuel, and perform it. As a result, a "boiling water" item will appear @@ -2772,7 +2803,7 @@ decrease it by further 4%, and also decrease the whole steam use rate by 10%.

    -

    Explosions

    +

    Explosions

    The engine must be constructed using barrel, pipe and piston from fire-safe, or in the magma version magma-safe metals.

    During operation weak parts get gradually worn out, and @@ -2781,7 +2812,7 @@ toppled during operation by a building destroyer, or a tantruming dwarf.

    -

    Save files

    +

    Save files

    It should be safe to load and view engine-using fortresses from a DF version without DFHack installed, except that in such case the engines won't work. However actually making modifications @@ -2792,7 +2823,7 @@ being generated.

    -

    Add Spatter

    +

    Add Spatter

    This plugin makes reactions with names starting with SPATTER_ADD_ produce contaminants on the items instead of improvements. The produced contaminants are immune to being washed away by water or destroyed by diff --git a/Readme.rst b/Readme.rst index b5c0f335d..bed837f0b 100644 --- a/Readme.rst +++ b/Readme.rst @@ -88,6 +88,36 @@ Interactive commands like 'liquids' cannot be used as hotkeys. Most of the commands come from plugins. Those reside in 'hack/plugins/'. +Patched binaries +================ + +On linux and OSX, users of patched binaries may have to find the relevant +section in symbols.xml, and add a new line with the checksum of their +executable:: + + + +In order to find the correct value of the hash, look into stderr.log; +DFHack prints an error there if it does not recognize the hash. + +DFHack includes a small stand-alone utility for applying and removing +binary patches from the game executable. Use it from the regular operating +system console: + + * ``binpatch check "Dwarf Fortress.exe" patch.dif`` + + Checks and prints if the patch is currently applied. + + * ``binpatch apply "Dwarf Fortress.exe" patch.dif`` + + Applies the patch, unless it is already applied or in conflict. + + * ``binpatch remove "Dwarf Fortress.exe" patch.dif`` + + Removes the patch, unless it is already removed. + +The patches are expected to be encoded in text format used by IDA. + ============================= Something doesn't work, help! ============================= diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 536f4d34d..a6ce58877 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -249,6 +249,7 @@ ADD_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp ${ ADD_DEPENDENCIES(dfhack-client dfhack) ADD_EXECUTABLE(dfhack-run dfhack-run.cpp) +ADD_EXECUTABLE(binpatch binpatch.cpp) IF(BUILD_EGGY) SET_TARGET_PROPERTIES(dfhack PROPERTIES OUTPUT_NAME "egg" ) @@ -329,7 +330,7 @@ install(FILES xml/symbols.xml install(FILES ../dfhack.init-example DESTINATION ${DFHACK_BINARY_DESTINATION}) -install(TARGETS dfhack-run dfhack-client +install(TARGETS dfhack-run dfhack-client binpatch LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) diff --git a/library/binpatch.cpp b/library/binpatch.cpp new file mode 100644 index 000000000..10188db8c --- /dev/null +++ b/library/binpatch.cpp @@ -0,0 +1,308 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2011 Petr Mrázek + +A thread-safe logging console with a line editor for windows. + +Based on linenoise win32 port, +copyright 2010, Jon Griffiths . +All rights reserved. +Based on linenoise, copyright 2010, Salvatore Sanfilippo . +The original linenoise can be found at: http://github.com/antirez/linenoise + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Redis nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using std::cout; +using std::cerr; +using std::endl; + +typedef unsigned char patch_byte; + +struct BinaryPatch { + struct Byte { + unsigned offset; + patch_byte old_val, new_val; + }; + enum State { + Conflict = 0, + Unapplied = 1, + Applied = 2, + Partial = 3 + }; + + std::vector entries; + + bool loadDIF(std::string name); + State checkState(const patch_byte *ptr, size_t len); + + void apply(patch_byte *ptr, size_t len, bool newv); +}; + +inline bool is_hex(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +bool BinaryPatch::loadDIF(std::string name) +{ + entries.clear(); + + std::ifstream infile(name); + if(infile.bad()) + { + cerr << "Cannot open file: " << name << endl; + return false; + } + + std::string s; + while(std::getline(infile, s)) + { + // Parse lines that begin with "[0-9a-f]+:" + size_t idx = s.find(':'); + if (idx == std::string::npos || idx == 0 || idx > 8) + continue; + + bool ok = true; + for (size_t i = 0; i < idx; i++) + if (!is_hex(s[i])) + ok = false; + if (!ok) + continue; + + unsigned off, oval, nval; + int nchar = 0; + int cnt = sscanf(s.c_str(), "%x: %x %x%n", &off, &oval, &nval, &nchar); + + if (cnt < 3) + { + cerr << "Could not parse: " << s << endl; + return false; + } + + for (size_t i = nchar; i < s.size(); i++) + { + if (!isspace(s[i])) + { + cerr << "Garbage at end of line: " << s << endl; + return false; + } + } + + if (oval >= 256 || nval >= 256) + { + cerr << "Invalid byte values: " << s << endl; + return false; + } + + Byte bv = { off, patch_byte(oval), patch_byte(nval) }; + entries.push_back(bv); + } + + if (entries.empty()) + { + cerr << "No lines recognized." << endl; + return false; + } + + return true; +} + +BinaryPatch::State BinaryPatch::checkState(const patch_byte *ptr, size_t len) +{ + int state = 0; + + for (size_t i = 0; i < entries.size(); i++) + { + Byte &bv = entries[i]; + + if (bv.offset >= len) + { + cerr << "Offset out of range: 0x" << std::hex << bv.offset << std::dec << endl; + return Conflict; + } + + patch_byte cv = ptr[bv.offset]; + if (bv.old_val == cv) + state |= Unapplied; + else if (bv.new_val == cv) + state |= Applied; + else + { + cerr << std::hex << bv.offset << ": " << bv.old_val << " " << bv.new_val + << ", but currently " << cv << std::dec << endl; + return Conflict; + } + } + + return State(state); +} + +void BinaryPatch::apply(patch_byte *ptr, size_t len, bool newv) +{ + for (size_t i = 0; i < entries.size(); i++) + { + Byte &bv = entries[i]; + assert (bv.offset < len); + + ptr[bv.offset] = (newv ? bv.new_val : bv.old_val); + } +} + +bool load_file(std::vector *pvec, std::string fname) +{ + FILE *f = fopen(fname.c_str(), "rb"); + if (!f) + { + cerr << "Cannot open file: " << fname << endl; + return false; + } + + fseek(f, 0, SEEK_END); + pvec->resize(ftell(f)); + fseek(f, 0, SEEK_SET); + size_t cnt = fread(pvec->data(), 1, pvec->size(), f); + fclose(f); + + return cnt == pvec->size(); +} + +bool save_file(const std::vector &pvec, std::string fname) +{ + FILE *f = fopen(fname.c_str(), "wb"); + if (!f) + { + cerr << "Cannot open file: " << fname << endl; + return false; + } + + size_t cnt = fwrite(pvec.data(), 1, pvec.size(), f); + fclose(f); + + return cnt == pvec.size(); +} + +int main (int argc, char *argv[]) +{ + if (argc <= 3) + { + cerr << "Usage: binpatch check|apply|remove " << endl; + return 2; + } + + std::string cmd = argv[1]; + + if (cmd != "check" && cmd != "apply" && cmd != "remove") + { + cerr << "Invalid command: " << cmd << endl; + return 2; + } + + std::string exe_file = argv[2]; + std::vector bindata; + if (!load_file(&bindata, exe_file)) + return 2; + + BinaryPatch patch; + if (!patch.loadDIF(argv[3])) + return 2; + + BinaryPatch::State state = patch.checkState(bindata.data(), bindata.size()); + if (state == BinaryPatch::Conflict) + return 1; + + if (cmd == "check") + { + switch (state) + { + case BinaryPatch::Unapplied: + cout << "Currently not applied." << endl; + break; + case BinaryPatch::Applied: + cout << "Currently applied." << endl; + break; + case BinaryPatch::Partial: + cout << "Currently partially applied." << endl; + break; + default: + break; + } + + return 0; + } + else if (cmd == "apply") + { + if (state == BinaryPatch::Applied) + { + cout << "Already applied." << endl; + return 0; + } + + patch.apply(bindata.data(), bindata.size(), true); + } + else if (cmd == "remove") + { + if (state == BinaryPatch::Unapplied) + { + cout << "Already removed." << endl; + return 0; + } + + patch.apply(bindata.data(), bindata.size(), false); + } + + std::string bak_file = exe_file + ".bak"; + remove(bak_file.c_str()); + + if (rename(exe_file.c_str(), bak_file.c_str()) != 0) + { + cerr << "Could not create backup." << endl; + return 1; + } + + if (!save_file(bindata, exe_file)) + return 1; + + cout << "Patched " << patch.entries.size() << " bytes." << endl; + return 0; +} From 28f0fed0aa1d339d60228969b0e6b174da27e42c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 8 Oct 2012 16:22:35 +0400 Subject: [PATCH 119/196] Redo the way binpatch backs up, so as not to lose the executable perms. --- library/binpatch.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/library/binpatch.cpp b/library/binpatch.cpp index 10188db8c..88ef2f004 100644 --- a/library/binpatch.cpp +++ b/library/binpatch.cpp @@ -291,10 +291,7 @@ int main (int argc, char *argv[]) patch.apply(bindata.data(), bindata.size(), false); } - std::string bak_file = exe_file + ".bak"; - remove(bak_file.c_str()); - - if (rename(exe_file.c_str(), bak_file.c_str()) != 0) + if (!save_file(bindata, exe_file + ".bak")) { cerr << "Could not create backup." << endl; return 1; From 7224c8746aa44249d1b1c12088e6d4aeefa4ac22 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 8 Oct 2012 16:47:52 +0400 Subject: [PATCH 120/196] Print the new md5 hash after modification in binpatch. --- depends/md5/md5wrapper.cpp | 9 +++------ depends/md5/md5wrapper.h | 6 +++++- library/CMakeLists.txt | 2 ++ library/binpatch.cpp | 11 ++++++++++- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/depends/md5/md5wrapper.cpp b/depends/md5/md5wrapper.cpp index e12b65780..d9f857c5d 100644 --- a/depends/md5/md5wrapper.cpp +++ b/depends/md5/md5wrapper.cpp @@ -36,16 +36,14 @@ * internal hash function, calling * the basic methods from md5.h */ -std::string md5wrapper::hashit(std::string text) +std::string md5wrapper::hashit(unsigned char *data, size_t length) { MD5Context ctx; //init md5 MD5Init(&ctx); //update with our string - MD5Update(&ctx, - (unsigned char*)text.c_str(), - text.length()); + MD5Update(&ctx, data, length); //create the hash unsigned char buff[16] = ""; @@ -95,10 +93,9 @@ md5wrapper::~md5wrapper() */ std::string md5wrapper::getHashFromString(std::string text) { - return this->hashit(text); + return this->hashit((unsigned char*)text.data(), text.length()); } - /* * creates a MD5 hash from * a file specified in "filename" and diff --git a/depends/md5/md5wrapper.h b/depends/md5/md5wrapper.h index 1a41192a1..0b534b61d 100644 --- a/depends/md5/md5wrapper.h +++ b/depends/md5/md5wrapper.h @@ -31,7 +31,7 @@ class md5wrapper * internal hash function, calling * the basic methods from md5.h */ - std::string hashit(std::string text); + std::string hashit(unsigned char *data, size_t length); /* * converts the numeric giets to @@ -52,6 +52,10 @@ class md5wrapper */ std::string getHashFromString(std::string text); + std::string getHashFromBytes(const unsigned char *data, size_t size) { + return hashit(const_cast(data),size); + } + /* * creates a MD5 hash from * a file specified in "filename" and diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index a6ce58877..6f33d5c8a 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -249,7 +249,9 @@ ADD_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp ${ ADD_DEPENDENCIES(dfhack-client dfhack) ADD_EXECUTABLE(dfhack-run dfhack-run.cpp) + ADD_EXECUTABLE(binpatch binpatch.cpp) +TARGET_LINK_LIBRARIES(binpatch dfhack-md5) IF(BUILD_EGGY) SET_TARGET_PROPERTIES(dfhack PROPERTIES OUTPUT_NAME "egg" ) diff --git a/library/binpatch.cpp b/library/binpatch.cpp index 88ef2f004..815ac5b92 100644 --- a/library/binpatch.cpp +++ b/library/binpatch.cpp @@ -53,6 +53,8 @@ POSSIBILITY OF SUCH DAMAGE. #include +#include + using std::cout; using std::cerr; using std::endl; @@ -222,6 +224,12 @@ bool save_file(const std::vector &pvec, std::string fname) return cnt == pvec.size(); } +std::string compute_hash(const std::vector &pvec) +{ + md5wrapper md5; + return md5.getHashFromBytes(pvec.data(), pvec.size()); +} + int main (int argc, char *argv[]) { if (argc <= 3) @@ -300,6 +308,7 @@ int main (int argc, char *argv[]) if (!save_file(bindata, exe_file)) return 1; - cout << "Patched " << patch.entries.size() << " bytes." << endl; + cout << "Patched " << patch.entries.size() + << " bytes, new hash: " << compute_hash(bindata) << endl; return 0; } From 5cf42fd6f8b61efe302738d20bb1585b2704a5f3 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 10 Oct 2012 14:57:45 +0200 Subject: [PATCH 121/196] ruby: add Matinfo#=== --- plugins/ruby/material.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/ruby/material.rb b/plugins/ruby/material.rb index 4a92118d6..ca0a64779 100644 --- a/plugins/ruby/material.rb +++ b/plugins/ruby/material.rb @@ -189,6 +189,10 @@ module DFHack end def to_s ; token ; end + + def ===(other) + other.mat_index == mat_index and other.mat_type == mat_type + end end class << self From 7c969f774f5ea86aaf9251c02e187014c73986c6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 10 Oct 2012 18:22:01 +0400 Subject: [PATCH 122/196] Split the liquipowder fix-dimensions hook into separate liquid and powder. The item_liquipowder vtable is completely optimized out by MSVC. --- plugins/tweak.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index d54c4a5ec..8488ba3e8 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -34,7 +34,8 @@ #include "df/ui_build_selector.h" #include "df/building_trapst.h" #include "df/item_actual.h" -#include "df/item_liquipowder.h" +#include "df/item_liquid_miscst.h" +#include "df/item_powder_miscst.h" #include "df/item_barst.h" #include "df/item_threadst.h" #include "df/item_clothst.h" @@ -402,8 +403,8 @@ static void correct_dimension(df::item_actual *self, int32_t &delta, int32_t dim if (copy) copy->categorize(true); } -struct dimension_lqp_hook : df::item_liquipowder { - typedef df::item_liquipowder interpose_base; +struct dimension_liquid_hook : df::item_liquid_miscst { + typedef df::item_liquid_miscst interpose_base; DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) { @@ -412,7 +413,19 @@ struct dimension_lqp_hook : df::item_liquipowder { } }; -IMPLEMENT_VMETHOD_INTERPOSE(dimension_lqp_hook, subtractDimension); +IMPLEMENT_VMETHOD_INTERPOSE(dimension_liquid_hook, subtractDimension); + +struct dimension_powder_hook : df::item_powder_miscst { + typedef df::item_powder_miscst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) + { + correct_dimension(this, delta, dimension); + return INTERPOSE_NEXT(subtractDimension)(delta); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(dimension_powder_hook, subtractDimension); struct dimension_bar_hook : df::item_barst { typedef df::item_barst interpose_base; @@ -795,7 +808,8 @@ static command_result tweak(color_ostream &out, vector ¶meters) } else if (cmd == "fix-dimensions") { - enable_hook(out, INTERPOSE_HOOK(dimension_lqp_hook, subtractDimension), parameters); + enable_hook(out, INTERPOSE_HOOK(dimension_liquid_hook, subtractDimension), parameters); + enable_hook(out, INTERPOSE_HOOK(dimension_powder_hook, subtractDimension), parameters); enable_hook(out, INTERPOSE_HOOK(dimension_bar_hook, subtractDimension), parameters); enable_hook(out, INTERPOSE_HOOK(dimension_thread_hook, subtractDimension), parameters); enable_hook(out, INTERPOSE_HOOK(dimension_cloth_hook, subtractDimension), parameters); From 41de37a5c116d14175de54e3dfa9367454967868 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 10 Oct 2012 18:29:59 +0400 Subject: [PATCH 123/196] Add a special workshop for add spatter reactions into example raws. --- plugins/raw/building_spatter.txt | 22 ++++++++++++++++++++++ plugins/raw/reaction_spatter.txt | 30 +++++++++++++++--------------- 2 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 plugins/raw/building_spatter.txt diff --git a/plugins/raw/building_spatter.txt b/plugins/raw/building_spatter.txt new file mode 100644 index 000000000..7bde8297b --- /dev/null +++ b/plugins/raw/building_spatter.txt @@ -0,0 +1,22 @@ +building_spatter + +[OBJECT:BUILDING] + +[BUILDING_WORKSHOP:GREASING_STATION] + [NAME:Greasing Station] + [NAME_COLOR:2:0:1] + [DIM:1:1] + [WORK_LOCATION:1:1] + [BUILD_LABOR:DYER] + [BUILD_KEY:CUSTOM_ALT_G] + [BLOCK:1:0] + [TILE:0:1:150] + [COLOR:0:1:0:0:1] + [TILE:1:1:150] + [COLOR:1:1:MAT] + [TILE:2:1:8] + [COLOR:2:1:MAT] + [TILE:3:1:8] + [COLOR:3:1:7:5:0] + [BUILD_ITEM:1:BUCKET:NONE:NONE:NONE][CAN_USE_ARTIFACT] + [BUILD_ITEM:1:NONE:NONE:NONE:NONE][BUILDMAT] diff --git a/plugins/raw/reaction_spatter.txt b/plugins/raw/reaction_spatter.txt index 81f9a2b62..95b9f1d33 100644 --- a/plugins/raw/reaction_spatter.txt +++ b/plugins/raw/reaction_spatter.txt @@ -7,7 +7,7 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_OBJECT_LIQUID] [NAME:coat object with liquid] [ADVENTURE_MODE_ENABLED] - [SKILL:WAX_WORKING] + [SKILL:DYER] [REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE] [MIN_DIMENSION:150] [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] @@ -28,10 +28,10 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_WEAPON_EXTRACT] [NAME:coat weapon with extract] - [BUILDING:CRAFTSMAN:NONE] - [SKILL:WAX_WORKING] - [REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE] - [MIN_DIMENSION:150] + [BUILDING:GREASING_STATION:CUSTOM_W] + [SKILL:DYER] + [REAGENT:extract:100:LIQUID_MISC:NONE:NONE:NONE] + [MIN_DIMENSION:100] [REACTION_CLASS:CREATURE_EXTRACT] [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] [REAGENT:extract container:1:NONE:NONE:NONE:NONE] @@ -51,8 +51,8 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_AMMO_EXTRACT] [NAME:coat ammo with extract] - [BUILDING:CRAFTSMAN:NONE] - [SKILL:WAX_WORKING] + [BUILDING:GREASING_STATION:CUSTOM_A] + [SKILL:DYER] [REAGENT:extract:50:LIQUID_MISC:NONE:NONE:NONE] [MIN_DIMENSION:50] [REACTION_CLASS:CREATURE_EXTRACT] @@ -75,8 +75,8 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_WEAPON_GCS] [NAME:coat weapon with GCS venom] - [BUILDING:CRAFTSMAN:NONE] - [SKILL:WAX_WORKING] + [BUILDING:GREASING_STATION:NONE] + [SKILL:DYER] [REAGENT:extract:150:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON] [MIN_DIMENSION:150] [REACTION_CLASS:CREATURE_EXTRACT] @@ -95,8 +95,8 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_AMMO_GCS] [NAME:coat ammo with GCS venom] - [BUILDING:CRAFTSMAN:NONE] - [SKILL:WAX_WORKING] + [BUILDING:GREASING_STATION:NONE] + [SKILL:DYER] [REAGENT:extract:50:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON] [MIN_DIMENSION:50] [REACTION_CLASS:CREATURE_EXTRACT] @@ -115,8 +115,8 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_WEAPON_GDS] [NAME:coat weapon with GDS venom] - [BUILDING:CRAFTSMAN:NONE] - [SKILL:WAX_WORKING] + [BUILDING:GREASING_STATION:NONE] + [SKILL:DYER] [REAGENT:extract:150:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON] [MIN_DIMENSION:150] [REACTION_CLASS:CREATURE_EXTRACT] @@ -135,8 +135,8 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_AMMO_GDS] [NAME:coat ammo with GDS venom] - [BUILDING:CRAFTSMAN:NONE] - [SKILL:WAX_WORKING] + [BUILDING:GREASING_STATION:NONE] + [SKILL:DYER] [REAGENT:extract:50:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON] [MIN_DIMENSION:50] [REACTION_CLASS:CREATURE_EXTRACT] From 46a24a83fb660d46aed0a8304fb68d00e675c785 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 10 Oct 2012 18:01:57 +0200 Subject: [PATCH 124/196] ruby: fix item_find(:selected) in item details screen --- plugins/ruby/item.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/item.rb b/plugins/ruby/item.rb index 9a254d9e8..469ec7449 100644 --- a/plugins/ruby/item.rb +++ b/plugins/ruby/item.rb @@ -6,8 +6,12 @@ module DFHack if what == :selected case curview._rtti_classname when :viewscreen_itemst - ref = curview.entry_ref[curview.cursor_pos] - ref.item_tg if ref.kind_of?(GeneralRefItem) + if ref = curview.entry_ref[curview.cursor_pos] + ref.item_tg if ref.kind_of?(GeneralRefItem) + else + # not a container + curview.item + end when :viewscreen_storesst # z/stocks if curview.in_group_mode == 0 and curview.in_right_list == 1 curview.items[curview.item_cursor] From e3e01107b91c9b22524c1c0d1e93d3932b53f491 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 10 Oct 2012 18:56:23 +0200 Subject: [PATCH 125/196] fix deathcause for new df-structures --- scripts/deathcause.rb | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb index 178ebbc87..0ed54d81a 100644 --- a/scripts/deathcause.rb +++ b/scripts/deathcause.rb @@ -1,15 +1,13 @@ # show death cause of a creature -def display_event(e) - p e if $DEBUG - - str = "#{e.victim_tg.name} died in year #{e.year}" - str << " of #{e.death_cause}" if false - str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_tg.name}" if e.slayer != -1 +def display_death_event(e) + str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}" + str << " (cause: #{e.death_cause.to_s.downcase})," + str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1 str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON - puts str + '.' + puts str.chomp(',') + '.' end item = df.item_find(:selected) @@ -21,14 +19,15 @@ end if !item or !item.kind_of?(DFHack::ItemBodyComponent) puts "Please select a corpse in the loo'k' menu" else - hfig = item.hist_figure_id - if hfig == -1 - puts "Not a historical figure, cannot find info" + hf = item.hist_figure_id + if hf == -1 + # TODO try to retrieve info from the unit (u = item.unit_tg) + puts "Not a historical figure, cannot death find info" else events = df.world.history.events (0...events.length).reverse_each { |i| - if events[i].kind_of?(DFHack::HistoryEventHistFigureDiedst) and events[i].victim == hfig - display_event(events[i]) + if events[i].kind_of?(DFHack::HistoryEventHistFigureDiedst) and events[i].victim_hf == hf + display_death_event(events[i]) break end } From b3b93f818d097f88cb2517df4de5fb64b1ac3d69 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 10 Oct 2012 19:25:30 +0200 Subject: [PATCH 126/196] slayrace: show number of creatures per race --- scripts/slayrace.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/slayrace.rb b/scripts/slayrace.rb index b1fc24e35..749d0189b 100644 --- a/scripts/slayrace.rb +++ b/scripts/slayrace.rb @@ -33,12 +33,14 @@ slayit = lambda { |u| end } -all_races = df.world.units.active.map { |u| - u.race_tg.creature_id if checkunit[u] -}.compact.uniq.sort +all_races = Hash.new(0) + +df.world.units.active.map { |u| + all_races[u.race_tg.creature_id] += 1 if checkunit[u] +} if !race - puts all_races + all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" } elsif race == 'him' if him = df.unit_find slayit[him] @@ -46,7 +48,7 @@ elsif race == 'him' puts "Choose target" end else - raw_race = df.match_rawname(race, all_races) + raw_race = df.match_rawname(race, all_races.keys) raise 'invalid race' if not raw_race race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race } From b5f5d1f85b56fe0d7bd804fe881ce8a50310de10 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 10 Oct 2012 19:42:36 +0200 Subject: [PATCH 127/196] removebadthoughts: add --dry-run option --- NEWS | 1 + Readme.rst | 16 +++++++------ scripts/removebadthoughts.rb | 44 ++++++++++++++++++++++++------------ 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/NEWS b/NEWS index 65339ad64..192b93aed 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ DFHack future Nothing yet! + - removebadthoughts: add --dry-run option DFHack v0.34.11-r2 diff --git a/Readme.rst b/Readme.rst index b5c0f335d..7ce15015a 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1632,16 +1632,18 @@ removebadthoughts This script remove negative thoughts from your dwarves. Very useful against tantrum spirals. -With a selected unit in 'v' mode, will clear this unit's mind, otherwise -clear all your fort's units minds. +The script can target a single creature, when used with the ``him`` argument, +or the whole fort population, with ``all``. + +To show every bad thought present without actually removing them, run the +script with the ``-n`` or ``--dry-run`` argument. This can give a quick +hint on what bothers your dwarves the most. Individual dwarf happiness may not increase right after this command is run, but in the short term your dwarves will get much more joyful. -The thoughts are set to be very old, and the game will remove them soon when -you unpause. -With the optional ``-v`` parameter, the script will dump the negative thoughts -it removed. +Internals: the thoughts are set to be very old, so that the game remove them +quickly after you unpause. slayrace @@ -1650,7 +1652,7 @@ Kills any unit of a given race. With no argument, lists the available races. -With the special argument 'him', targets only the selected creature. +With the special argument ``him``, targets only the selected creature. Any non-dead non-caged unit of the specified race gets its ``blood_count`` set to 0, which means immediate death at the next game tick. For creatures diff --git a/scripts/removebadthoughts.rb b/scripts/removebadthoughts.rb index 99b742643..ff98f8f65 100644 --- a/scripts/removebadthoughts.rb +++ b/scripts/removebadthoughts.rb @@ -1,27 +1,43 @@ # remove bad thoughts for the selected unit or the whole fort -# with removebadthoughts -v, dump the bad thoughts types we removed -verbose = $script_args.delete('-v') +dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n') -if u = df.unit_find(:selected) - targets = [u] -else - targets = df.unit_citizens -end +$script_args << 'all' if dry_run and $script_args.empty? seenbad = Hash.new(0) -targets.each { |u| +clear_mind = lambda { |u| u.status.recent_events.each { |e| next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-' seenbad[e.type] += 1 - e.age = 0x1000_0000 + e.age = 0x1000_0000 unless dry_run } } -if verbose - seenbad.sort_by { |k, v| v }.each { |k, v| puts " #{v} #{k}" } -end +summary = lambda { + seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt| + puts " #{thought} #{cnt}" + } + count = seenbad.values.inject(0) { |sum, cnt| sum+cnt } + puts "Removed #{count} bad thought#{'s' if count != 1}." if count > 0 and not dry_run +} -count = seenbad.values.inject(0) { |s, v| s+v } -puts "removed #{count} bad thought#{'s' if count != 1}" +case $script_args[0] +when 'him' + if u = df.unit_find + clear_mind[u] + summary[] + else + puts 'Please select a dwarf ingame' + end + +when 'all' + df.unit_citizens.each { |uu| + clear_mind[uu] + } + summary[] + +else + puts "Usage: removebadthoughts [--dry-run] " + +end From 599577780add8e8c7caaba281b584c50dacdb9a0 Mon Sep 17 00:00:00 2001 From: ab9rf Date: Wed, 10 Oct 2012 23:46:01 -0500 Subject: [PATCH 128/196] Sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index d9c765cce..a5a8fd689 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d9c765ccea1d98ac08d33316dbb12f62df271690 +Subproject commit a5a8fd68947b60fcb2d1c03b4f05bdf48cfcf7a5 From 010417c8122bdb16cc881ccba6e2d236a8c01491 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 11 Oct 2012 12:36:17 +0400 Subject: [PATCH 129/196] Compute detailed focus string for the hauling menu. --- library/modules/Gui.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index b211f9f2b..04a453c06 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -85,6 +85,8 @@ using namespace DFHack; #include "df/assign_trade_status.h" #include "df/announcement_flags.h" #include "df/announcements.h" +#include "df/stop_depart_condition.h" +#include "df/route_stockpile_link.h" using namespace df::enums; using df::global::gview; @@ -303,6 +305,45 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) focus += "/List"; break; + case Hauling: + if (ui->hauling.in_assign_vehicle) + { + auto vehicle = vector_get(ui->hauling.vehicles, ui->hauling.cursor_vehicle); + focus += "/AssignVehicle/" + std::string(vehicle ? "Some" : "None"); + } + else + { + int idx = ui->hauling.cursor_top; + auto route = vector_get(ui->hauling.view_routes, idx); + auto stop = vector_get(ui->hauling.view_stops, idx); + std::string tag = stop ? "Stop" : (route ? "Route" : "None"); + + if (ui->hauling.in_name) + focus += "/Rename/" + tag; + else if (ui->hauling.in_stop) + { + int sidx = ui->hauling.cursor_stop; + auto cond = vector_get(ui->hauling.stop_conditions, sidx); + auto link = vector_get(ui->hauling.stop_links, sidx); + + focus += "/DefineStop"; + + if (cond) + focus += "/Cond/" + enum_item_key(cond->mode); + else if (link) + { + focus += "/Link/"; + if (link->mode.bits.give) focus += "Give"; + if (link->mode.bits.take) focus += "Take"; + } + else + focus += "/None"; + } + else + focus += "/Select/" + tag; + } + break; + default: break; } From 5206236b014850518e2de5f47e4534bc1b10bd48 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 11 Oct 2012 15:10:19 +0400 Subject: [PATCH 130/196] Look through missing intermediate bases when interposing subclasses. --- library/VTableInterpose.cpp | 20 ++++++++++++++++++-- library/include/VTableInterpose.h | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 141450615..d8a07e830 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -281,9 +281,10 @@ VMethodInterposeLinkBase *VMethodInterposeLinkBase::get_first_interpose(virtual_ return item; } -void VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr) +bool VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr) { auto &children = cur->getChildren(); + bool found = false; for (size_t i = 0; i < children.size(); i++) { @@ -298,17 +299,32 @@ void VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmp continue; child_next.insert(base); + found = true; } - else + else if (child->vtable_ptr) { void *cptr = child->get_vmethod_ptr(vmethod_idx); if (cptr != vmptr) continue; child_hosts.insert(child); + found = true; + find_child_hosts(child, vmptr); } + else + { + // If this vtable is not known, but any of the children + // have the same vmethod, this one definitely does too + if (find_child_hosts(child, vmptr)) + { + child_hosts.insert(child); + found = true; + } + } } + + return found; } void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from) diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h index b52b32270..bd3c23036 100644 --- a/library/include/VTableInterpose.h +++ b/library/include/VTableInterpose.h @@ -159,7 +159,7 @@ namespace DFHack void on_host_delete(virtual_identity *host); VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id); - void find_child_hosts(virtual_identity *cur, void *vmptr); + bool find_child_hosts(virtual_identity *cur, void *vmptr); public: VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority); ~VMethodInterposeLinkBase(); From 2865e1373aaa366b1121806408726a9b8c087fa6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 11 Oct 2012 17:34:34 +0400 Subject: [PATCH 131/196] Experimental API for associating tile bitmasks with persistent data. Use block_square_event_world_constructionst objects with the same bogus negative id as the matching historical figure object. --- Lua API.html | 27 ++++++++ Lua API.rst | 34 ++++++++++ library/LuaApi.cpp | 52 ++++++++++++++- .../df/custom/tile_bitmask.methods.inc | 6 +- library/include/modules/World.h | 9 +++ library/modules/World.cpp | 65 +++++++++++++++++++ library/xml | 2 +- 7 files changed, 189 insertions(+), 6 deletions(-) diff --git a/Lua API.html b/Lua API.html index 06fa5418e..125687110 100644 --- a/Lua API.html +++ b/Lua API.html @@ -932,6 +932,21 @@ Returns entry, did_create_new

    and automatically stored in the save game, these save and retrieval functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead.

    +

    It is also possible to associate one bit per map tile with an entry, +using these two methods:

    +
      +
    • entry:getTilemask(block[, create])

      +

      Retrieves the tile bitmask associated with this entry in the given map +block. If create is true, an empty mask is created if none exists; +otherwise the function returns nil, which must be assumed to be the same +as an all-zero mask.

      +
    • +
    • entry:deleteTilemask(block)

      +

      Deletes the associated tile mask from the given map block.

      +
    • +
    +

    Note that these masks are only saved in fortress mode, and also that deleting +the persistent entry will NOT delete the associated masks.

    Material info lookup

    @@ -1286,6 +1301,18 @@ tools like liquids or tiletypes are used. It also cannot possibly take into account anything that depends on the actual units, like burrows, or the presence of invaders.

    +
  • dfhack.maps.hasTileAssignment(tilemask)

    +

    Checks if the tile_bitmask object is not nil and contains any set bits; returns true or false.

    +
  • +
  • dfhack.maps.getTileAssignment(tilemask,x,y)

    +

    Checks if the tile_bitmask object is not nil and has the relevant bit set; returns true or false.

    +
  • +
  • dfhack.maps.setTileAssignment(tilemask,x,y,enable)

    +

    Sets the relevant bit in the tile_bitmask object to the enable argument.

    +
  • +
  • dfhack.maps.resetTileAssignment(tilemask[,enable])

    +

    Sets all bits in the mask to the enable argument.

    +
  • diff --git a/Lua API.rst b/Lua API.rst index fbb4b7d82..7ad6aec23 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -646,6 +646,24 @@ and automatically stored in the save game, these save and retrieval functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead. +It is also possible to associate one bit per map tile with an entry, +using these two methods: + +* ``entry:getTilemask(block[, create])`` + + Retrieves the tile bitmask associated with this entry in the given map + block. If ``create`` is *true*, an empty mask is created if none exists; + otherwise the function returns *nil*, which must be assumed to be the same + as an all-zero mask. + +* ``entry:deleteTilemask(block)`` + + Deletes the associated tile mask from the given map block. + +Note that these masks are only saved in fortress mode, and also that deleting +the persistent entry will **NOT** delete the associated masks. + + Material info lookup -------------------- @@ -1079,6 +1097,22 @@ Maps module take into account anything that depends on the actual units, like burrows, or the presence of invaders. +* ``dfhack.maps.hasTileAssignment(tilemask)`` + + Checks if the tile_bitmask object is not *nil* and contains any set bits; returns *true* or *false*. + +* ``dfhack.maps.getTileAssignment(tilemask,x,y)`` + + Checks if the tile_bitmask object is not *nil* and has the relevant bit set; returns *true* or *false*. + +* ``dfhack.maps.setTileAssignment(tilemask,x,y,enable)`` + + Sets the relevant bit in the tile_bitmask object to the *enable* argument. + +* ``dfhack.maps.resetTileAssignment(tilemask[,enable])`` + + Sets all bits in the mask to the *enable* argument. + Burrows module -------------- diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 0df96f066..ef571bcb7 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -30,6 +30,7 @@ distribution. #include "MemAccess.h" #include "Core.h" +#include "Error.h" #include "VersionInfo.h" #include "tinythread.h" // must be last due to MS stupidity @@ -311,12 +312,12 @@ static PersistentDataItem get_persistent(lua_State *state) if (lua_istable(state, 1)) { + Lua::StackUnwinder frame(state); + if (!lua_getmetatable(state, 1) || !lua_rawequal(state, -1, lua_upvalueindex(1))) luaL_argerror(state, 1, "invalid table type"); - lua_settop(state, 1); - return persistent_by_struct(state, 1); } else @@ -446,11 +447,38 @@ static int dfhack_persistent_save(lua_State *state) return 2; } +static int dfhack_persistent_getTilemask(lua_State *state) +{ + CoreSuspender suspend; + + lua_settop(state, 3); + auto ref = get_persistent(state); + auto block = Lua::CheckDFObject(state, 2); + bool create = lua_toboolean(state, 3); + + Lua::PushDFObject(state, World::getPersistentTilemask(ref, block, create)); + return 1; +} + +static int dfhack_persistent_deleteTilemask(lua_State *state) +{ + CoreSuspender suspend; + + lua_settop(state, 2); + auto ref = get_persistent(state); + auto block = Lua::CheckDFObject(state, 2); + + lua_pushboolean(state, World::deletePersistentTilemask(ref, block)); + return 1; +} + static const luaL_Reg dfhack_persistent_funcs[] = { { "get", dfhack_persistent_get }, { "delete", dfhack_persistent_delete }, { "get_all", dfhack_persistent_get_all }, { "save", dfhack_persistent_save }, + { "getTilemask", dfhack_persistent_getTilemask }, + { "deleteTilemask", dfhack_persistent_deleteTilemask }, { NULL, NULL } }; @@ -937,6 +965,22 @@ static const luaL_Reg dfhack_items_funcs[] = { /***** Maps module *****/ +static bool hasTileAssignment(df::tile_bitmask *bm) { + return bm && bm->has_assignments(); +} +static bool getTileAssignment(df::tile_bitmask *bm, int x, int y) { + return bm && bm->getassignment(x,y); +} +static void setTileAssignment(df::tile_bitmask *bm, int x, int y, bool val) { + CHECK_NULL_POINTER(bm); + bm->setassignment(x,y,val); +} +static void resetTileAssignment(df::tile_bitmask *bm, bool val) { + CHECK_NULL_POINTER(bm); + if (val) bm->set_all(); + else bm->clear(); +} + static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPN(getBlock, (df::map_block* (*)(int32_t,int32_t,int32_t))Maps::getBlock), WRAPM(Maps, enableBlockUpdates), @@ -944,6 +988,10 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPM(Maps, getLocalInitFeature), WRAPM(Maps, canWalkBetween), WRAPM(Maps, spawnFlow), + WRAPN(hasTileAssignment, hasTileAssignment), + WRAPN(getTileAssignment, getTileAssignment), + WRAPN(setTileAssignment, setTileAssignment), + WRAPN(resetTileAssignment, resetTileAssignment), { NULL, NULL } }; diff --git a/library/include/df/custom/tile_bitmask.methods.inc b/library/include/df/custom/tile_bitmask.methods.inc index 18b312a08..e3414e334 100644 --- a/library/include/df/custom/tile_bitmask.methods.inc +++ b/library/include/df/custom/tile_bitmask.methods.inc @@ -16,7 +16,7 @@ inline bool getassignment( const df::coord2d &xy ) } inline bool getassignment( int x, int y ) { - return (bits[y] & (1 << x)); + return (bits[(y&15)] & (1 << (x&15))); } inline void setassignment( const df::coord2d &xy, bool bit ) { @@ -25,9 +25,9 @@ inline void setassignment( const df::coord2d &xy, bool bit ) inline void setassignment( int x, int y, bool bit ) { if(bit) - bits[y] |= (1 << x); + bits[(y&15)] |= (1 << (x&15)); else - bits[y] &= ~(1 << x); + bits[(y&15)] &= ~(1 << (x&15)); } bool has_assignments() { diff --git a/library/include/modules/World.h b/library/include/modules/World.h index f4c31dcf3..a945c4e72 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -37,6 +37,12 @@ distribution. #include "DataDefs.h" +namespace df +{ + struct tile_bitmask; + struct map_block; +} + namespace DFHack { typedef df::game_mode GameMode; @@ -120,6 +126,9 @@ namespace DFHack DFHACK_EXPORT bool DeletePersistentData(const PersistentDataItem &item); DFHACK_EXPORT void ClearPersistentCache(); + + DFHACK_EXPORT df::tile_bitmask *getPersistentTilemask(const PersistentDataItem &item, df::map_block *block, bool create = false); + DFHACK_EXPORT bool deletePersistentTilemask(const PersistentDataItem &item, df::map_block *block); } } #endif diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 65d0c7cb8..f3283c3a3 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -38,13 +38,18 @@ using namespace std; #include "ModuleFactory.h" #include "Core.h" +#include "modules/Maps.h" + #include "MiscUtils.h" #include "DataDefs.h" #include "df/world.h" #include "df/historical_figure.h" +#include "df/map_block.h" +#include "df/block_square_event_world_constructionst.h" using namespace DFHack; +using namespace df::enums; using df::global::world; @@ -312,3 +317,63 @@ bool World::DeletePersistentData(const PersistentDataItem &item) return false; } + +df::tile_bitmask *World::getPersistentTilemask(const PersistentDataItem &item, df::map_block *block, bool create) +{ + if (!block) + return NULL; + + int id = item.raw_id(); + if (id > -100) + return NULL; + + for (size_t i = 0; i < block->block_events.size(); i++) + { + auto ev = block->block_events[i]; + if (ev->getType() != block_square_event_type::world_construction) + continue; + auto wcsev = strict_virtual_cast(ev); + if (!wcsev || wcsev->construction_id != id) + continue; + return &wcsev->tile_bitmask; + } + + if (!create) + return NULL; + + auto ev = df::allocate(); + if (!ev) + return NULL; + + ev->construction_id = id; + ev->tile_bitmask.clear(); + vector_insert_at(block->block_events, 0, (df::block_square_event*)ev); + + return &ev->tile_bitmask; +} + +bool World::deletePersistentTilemask(const PersistentDataItem &item, df::map_block *block) +{ + if (!block) + return false; + int id = item.raw_id(); + if (id > -100) + return false; + + bool found = false; + for (int i = block->block_events.size()-1; i >= 0; i--) + { + auto ev = block->block_events[i]; + if (ev->getType() != block_square_event_type::world_construction) + continue; + auto wcsev = strict_virtual_cast(ev); + if (!wcsev || wcsev->construction_id != id) + continue; + + delete wcsev; + vector_erase_at(block->block_events, i); + found = true; + } + + return found; +} diff --git a/library/xml b/library/xml index 20c6d9c74..aec106cdc 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 20c6d9c743f1c5a20bb288f427b101e9b2a138d7 +Subproject commit aec106cdc32083bbcc6d6dd27d9e069f5525ea92 From 5f9489a8432d638b2ecb2e264c592025a1e1dde6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 11 Oct 2012 19:32:41 +0400 Subject: [PATCH 132/196] Start making a script for viewing and changing minecart Guide paths. --- dfhack.init-example | 3 + scripts/gui/guide-path.lua | 201 +++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 scripts/gui/guide-path.lua diff --git a/dfhack.init-example b/dfhack.init-example index 8505f5acc..3ecbf4342 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -72,6 +72,9 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine # military weapon auto-select keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons +# minecart Guide path +keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path + ############################ # UI and game logic tweaks # ############################ diff --git a/scripts/gui/guide-path.lua b/scripts/gui/guide-path.lua new file mode 100644 index 000000000..f3e27d917 --- /dev/null +++ b/scripts/gui/guide-path.lua @@ -0,0 +1,201 @@ +-- Show and manipulate the path used by Guide Cart orders. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local dlg = require 'gui.dialogs' + +local tile_attrs = df.tiletype.attrs + +function same_xyz(a,b) + return a and b and a.x == b.x and a.y == b.y and a.z == b.z +end + +GuidePathUI = defclass(GuidePathUI, guidm.MenuOverlay) + +GuidePathUI.focus_path = 'guide-path' + +GuidePathUI.ATTRS { + route = DEFAULT_NIL, + stop = DEFAULT_NIL, + order = DEFAULT_NIL, +} + +function GuidePathUI:init() + self.saved_mode = df.global.ui.main.mode + + for i=0,#self.route.stops-1 do + if self.route.stops[i] == self.stop then + self.stop_idx = i + break + end + end + + if not self.stop_idx then + error('Could not find stop index') + end + + self.next_stop = self.route.stops[(self.stop_idx+1)%#self.route.stops] +end + +function GuidePathUI:onShow() + -- with cursor, but without those ugly lines from native hauling mode + df.global.ui.main.mode = df.ui_sidebar_mode.Stockpiles +end + +function GuidePathUI:onDestroy() + df.global.ui.main.mode = self.saved_mode +end + +local function getTileType(cursor) + local block = dfhack.maps.getTileBlock(cursor) + if block then + return block.tiletype[cursor.x%16][cursor.y%16] + else + return 0 + end +end + +local function isTrackTile(tile) + return tile_attrs[tile].special == df.tiletype_special.TRACK +end + +local function paintMapTile(dc, vp, cursor, pos, ...) + if not same_xyz(cursor, pos) then + local stile = vp:tileToScreen(pos) + if stile.z == 0 then + dc:seek(stile.x,stile.y):char(...) + end + end +end + +local function get_path_point(gpath,i) + return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i]) +end + +local function blink_visible(delay) + return math.floor(dfhack.getTickCount()/delay) % 2 == 0 +end + +function GuidePathUI:renderPath(cursor) + local gpath = self.order.guide_path + local startp = self.stop.pos + local endp = self.next_stop.pos + local vp = self:getViewport() + local dc = gui.Painter.new(self.df_layout.map) + local visible = blink_visible(500) + + if visible then + paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN) + end + + local ok = nil + local pcnt = #gpath.x + if pcnt > 0 then + ok = true + + for i = 0,pcnt-1 do + local pt = get_path_point(gpath, i) + if i == 0 and not same_xyz(startp,pt) then + ok = false + end + if i == pcnt-1 and not same_xyz(endp,pt) then + ok = false + end + local tile = getTileType(pt) + if not isTrackTile(tile) then + ok = false + end + if visible then + local char = '+' + if i < pcnt-1 then + local npt = get_path_point(gpath, i+1) + if npt.x == pt.x+1 then + char = 26 + elseif npt.x == pt.x-1 then + char = 27 + elseif npt.y == pt.y+1 then + char = 25 + elseif npt.y == pt.y-1 then + char = 24 + end + end + local color = COLOR_LIGHTGREEN + if not ok then color = COLOR_LIGHTRED end + paintMapTile(dc, vp, cursor, pt, char, color) + end + end + end + + if blink_visible(120) then + paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN) + end + + return ok +end + +function GuidePathUI:onRenderBody(dc) + dc:clear():seek(1,1):pen(COLOR_WHITE):string("Guide Path") + + dc:seek(2,3) + + local cursor = guidm.getCursorPos() + local path_ok = self:renderPath(cursor) + + if path_ok == nil then + dc:string('No saved path', COLOR_DARKGREY) + elseif path_ok then + dc:string('Valid path', COLOR_GREEN) + else + dc:string('Invalid path', COLOR_RED) + end + + dc:newline():newline(1) + dc:key('CUSTOM_Z'):string(": Reset path, ",COLOR_GREY,nil,path_ok~=nil) + dc:key('CUSTOM_P'):string(": Find path",COLOR_GREY,nil,false) + dc:newline(1) + dc:key('CUSTOM_C'):string(": Zoom cur, ") + dc:key('CUSTOM_N'):string(": Zoom next") + + dc:newline():newline(1):string('At cursor:') + dc:newline():newline(2) + + local tile = getTileType(cursor) + if isTrackTile(tile) then + dc:string('Track '..tile_attrs[tile].direction, COLOR_GREEN) + else + dc:string('No track', COLOR_DARKGREY) + end + + dc:newline():newline(1) + dc:key('LEAVESCREEN'):string(": Back") +end + +function GuidePathUI:onInput(keys) + if keys.CUSTOM_C then + self:moveCursorTo(copyall(self.stop.pos)) + elseif keys.CUSTOM_N then + self:moveCursorTo(copyall(self.next_stop.pos)) + elseif keys.CUSTOM_Z then + local gpath = self.order.guide_path + gpath.x:resize(0) + gpath.y:resize(0) + gpath.z:resize(0) + elseif keys.LEAVESCREEN then + self:dismiss() + elseif self:propagateMoveKeys(keys) then + return + end +end + +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/Hauling/DefineStop/Cond/Guide') then + qerror("This script requires the main dwarfmode view in 'h' mode over a Guide order") +end + +local hauling = df.global.ui.hauling +local route = hauling.view_routes[hauling.cursor_top] +local stop = hauling.view_stops[hauling.cursor_top] +local order = hauling.stop_conditions[hauling.cursor_stop] + +local list = GuidePathUI{ route = route, stop = stop, order = order } +list:show() From 424c37c0ea23cd9fb822ce61a3964e16502478f8 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 11 Oct 2012 17:51:49 +0200 Subject: [PATCH 133/196] ruby: fix codegen to handle composite vtables --- plugins/ruby/codegen.pl | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index a615ab964..8dae55597 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -232,7 +232,11 @@ sub render_global_class { render_struct_fields($type); my $vms = $type->findnodes('child::virtual-methods')->[0]; - render_class_vmethods($vms) if $vms; + if ($vms) + { + my $voff = render_class_vmethods_voff($parent); + render_class_vmethods($vms, $voff); + } }; push @lines_rb, "end\n"; } @@ -368,9 +372,29 @@ sub render_container_reftarget { } } +# return the size of the parent's vtables +sub render_class_vmethods_voff { + my ($name) = @_; + + return 0 if !$name; + + my $type = $global_types{$name}; + my $parent = $type->getAttribute('inherits-from'); + + my $voff = render_class_vmethods_voff($parent); + my $vms = $type->findnodes('child::virtual-methods')->[0]; + + for my $meth ($vms->findnodes('child::vmethod')) + { + $voff += 4 if $meth->getAttribute('is-destructor') and $os eq 'linux'; + $voff += 4; + } + + return $voff; +} + sub render_class_vmethods { - my ($vms) = @_; - my $voff = 0; + my ($vms, $voff) = @_; for my $meth ($vms->findnodes('child::vmethod')) { From 12eeb9e5c0f5b15bd9976e07f4ba09d34774d3f8 Mon Sep 17 00:00:00 2001 From: ab9rf Date: Fri, 12 Oct 2012 00:38:45 -0500 Subject: [PATCH 134/196] Correct wrong variable use in start_autonestbox. --- plugins/zone.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 542ce8a02..2c63b6af7 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -3498,11 +3498,11 @@ command_result start_autonestbox(color_ostream &out) { enable_autonestbox = true; - if (!config_autobutcher.isValid()) + if (!config_autonestbox.isValid()) { config_autonestbox = World::AddPersistentData("autonestbox/config"); - if (!config_autobutcher.isValid()) + if (!config_autonestbox.isValid()) { out << "Cannot enable autonestbox without a world!" << endl; return CR_OK; From 0547ee7f831e9e10442ea52bbe0ebfeb344f9f90 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 12 Oct 2012 11:12:31 +0200 Subject: [PATCH 135/196] ruby: add magic "nolock " prefix to run ruby without Suspending main df --- plugins/ruby/ruby.cpp | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 7bd6d13e8..13190f70d 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -118,18 +118,8 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -// send a single ruby line to be evaluated by the ruby thread -DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command) +static command_result do_plugin_eval_ruby(color_ostream &out, const char *command) { - // if dlopen failed - if (!r_thread) - return CR_FAILURE; - - // wrap all ruby code inside a suspend block - // if we dont do that and rely on ruby code doing it, we'll deadlock in - // onupdate - CoreSuspender suspend; - command_result ret; // ensure ruby thread is idle @@ -159,6 +149,27 @@ DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *c return ret; } +// send a single ruby line to be evaluated by the ruby thread +DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command) +{ + // if dlopen failed + if (!r_thread) + return CR_FAILURE; + + if (!strncmp(command, "nolock ", 7)) { + // debug only! + // run ruby commands without locking the main thread + // useful when the game is frozen after a segfault + return do_plugin_eval_ruby(out, command+7); + } else { + // wrap all ruby code inside a suspend block + // if we dont do that and rely on ruby code doing it, we'll deadlock in + // onupdate + CoreSuspender suspend; + return do_plugin_eval_ruby(out, command); + } +} + DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { if (!r_thread) From 1f88c0eeed1999a252b0b3f0dd40252a7b983f99 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 12 Oct 2012 11:42:42 +0200 Subject: [PATCH 136/196] ruby: codegen: avoid bad vector_reftg accessor --- plugins/ruby/codegen.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 8dae55597..ff69853af 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -279,7 +279,6 @@ sub render_struct_field_refs { my ($parent, $field, $name) = @_; my $reftg = $field->getAttribute('ref-target'); - render_field_reftarget($parent, $field, $name, $reftg) if ($reftg); my $refto = $field->getAttribute('refers-to'); render_field_refto($parent, $name, $refto) if ($refto); @@ -289,6 +288,8 @@ sub render_struct_field_refs { if ($meta and $meta eq 'container' and $item) { my $itemreftg = $item->getAttribute('ref-target'); render_container_reftarget($parent, $item, $name, $itemreftg) if $itemreftg; + } elsif ($reftg) { + render_field_reftarget($parent, $field, $name, $reftg); } } From c089534f7315190b6df7d6ce6390b99fc3adc6b0 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 12 Oct 2012 13:33:58 +0200 Subject: [PATCH 137/196] ruby: fix assigning value to pointer to number --- plugins/ruby/ruby-autogen-defs.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 4470c8022..d5bcb08f4 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -282,6 +282,10 @@ module DFHack DFHack.memory_read_int32(@_memaddr) & 0xffffffff end + def _setp(v) + DFHack.memory_write_int32(@_memaddr, v) + end + def _get addr = _getp return if addr == 0 @@ -294,7 +298,15 @@ module DFHack case v when Pointer; DFHack.memory_write_int32(@_memaddr, v._getp) when MemStruct; DFHack.memory_write_int32(@_memaddr, v._memaddr) - when Integer; DFHack.memory_write_int32(@_memaddr, v) + when Integer + if @_tg and @_tg.kind_of?(MemHack::Number) + if _getp == 0 + _setp(DFHack.malloc(@_tg._bits/8)) + end + @_tg._at(_getp)._set(v) + else + DFHack.memory_write_int32(@_memaddr, v) + end when nil; DFHack.memory_write_int32(@_memaddr, 0) else _get._set(v) end From 33bd8103de43d1a6cb1d8a0f48819e4d753a94ce Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 15 Oct 2012 15:30:00 +0400 Subject: [PATCH 138/196] Extract an abstract View superclass from Screen to handle widget trees. --- Lua API.rst | 12 ++ library/lua/dfhack.lua | 16 ++ library/lua/gui.lua | 283 ++++++++++++++++++++++++---------- library/lua/gui/dialogs.lua | 2 +- library/lua/gui/dwarfmode.lua | 18 ++- scripts/gui/guide-path.lua | 4 - scripts/gui/hello-world.lua | 7 +- scripts/gui/siege-engine.lua | 6 +- 8 files changed, 249 insertions(+), 99 deletions(-) diff --git a/Lua API.rst b/Lua API.rst index 7ad6aec23..bbee8646c 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1727,6 +1727,18 @@ environment by the mandatory init file dfhack.lua: Returns a table with x, y and z as fields. +* ``same_xyz(a,b)`` + + Checks if ``a`` and ``b`` have the same x, y and z fields. + +* ``get_path_xyz(path,i)`` + + Returns ``path.x[i], path.y[i], path.z[i]``. + +* ``pos2xy(obj)``, ``xy2pos(x,y)``, ``same_xy(a,b)``, ``get_path_xy(a,b)`` + + Same as above, but for 2D coordinates. + * ``safe_index(obj,index...)`` Walks a sequence of dereferences, which may be represented by numbers or strings. diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index ce3be5a87..9fc94a025 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -157,6 +157,14 @@ function xyz2pos(x,y,z) end end +function same_xyz(a,b) + return a and b and a.x == b.x and a.y == b.y and a.z == b.z +end + +function get_path_xyz(path,i) + return path.x[i], path.y[i], path.z[i] +end + function pos2xy(pos) if pos then local x = pos.x @@ -174,6 +182,14 @@ function xy2pos(x,y) end end +function same_xy(a,b) + return a and b and a.x == b.x and a.y == b.y +end + +function get_path_xy(path,i) + return path.x[i], path.y[i] +end + function safe_index(obj,idx,...) if obj == nil or idx == nil then return nil diff --git a/library/lua/gui.lua b/library/lua/gui.lua index a5e4f7503..4f98e1bde 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -50,8 +50,8 @@ function mkdims_wh(x1,y1,w,h) end function inset(rect,dx1,dy1,dx2,dy2) return mkdims_xy( - rect.x1+dx1, rect.y1+dy1, - rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1) + rect.x1+dx1, rect.y1+(dy1 or dx1), + rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1 or dx1) ) end function is_in_rect(rect,x,y) @@ -68,45 +68,68 @@ local function to_pen(default, pen, bg, bold) end end ----------------------------- --- Clipped painter object -- ----------------------------- +function getKeyDisplay(code) + if type(code) == 'string' then + code = df.interface_key[code] + end + return dscreen.getKeyDisplay(code) +end -Painter = defclass(Painter, nil) +----------------------------------- +-- Clipped view rectangle object -- +----------------------------------- -function Painter:init(args) - local rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize()) - local crect = args.clip_rect or rect - self:assign{ - x = rect.x1, y = rect.y1, - x1 = rect.x1, clip_x1 = crect.x1, - y1 = rect.y1, clip_y1 = crect.y1, - x2 = rect.x2, clip_x2 = crect.x2, - y2 = rect.y2, clip_y2 = crect.y2, - width = rect.x2-rect.x1+1, - height = rect.y2-rect.y1+1, - cur_pen = to_pen(nil, args.pen or COLOR_GREY) - } +ViewRect = defclass(ViewRect, nil) + +function ViewRect:init(args) + if args.view_rect then + self:assign(args.view_rect) + else + local rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize()) + local crect = args.clip_rect or rect + self:assign{ + x1 = rect.x1, clip_x1 = crect.x1, + y1 = rect.y1, clip_y1 = crect.y1, + x2 = rect.x2, clip_x2 = crect.x2, + y2 = rect.y2, clip_y2 = crect.y2, + width = rect.x2-rect.x1+1, + height = rect.y2-rect.y1+1, + } + end + if args.clip_view then + local cr = args.clip_view + self:assign{ + clip_x1 = math.max(self.clip_x1, cr.clip_x1), + clip_y1 = math.max(self.clip_y1, cr.clip_y1), + clip_x2 = math.min(self.clip_x2, cr.clip_x2), + clip_y2 = math.min(self.clip_y2, cr.clip_y2), + } + end end -function Painter.new(rect, pen) - return Painter{ rect = rect, pen = pen } +function ViewRect:isDefunct() + return (self.clip_x1 > self.clip_x2 or self.clip_y1 > self.clip_y2) end -function Painter.new_xy(x1,y1,x2,y2,pen) - return Painter{ rect = mkdims_xy(x1,y1,x2,y2), pen = pen } +function ViewRect:inClipGlobalXY(x,y) + return x >= self.clip_x1 and x <= self.clip_x2 + and y >= self.clip_y1 and y <= self.clip_y2 end -function Painter.new_wh(x,y,w,h,pen) - return Painter{ rect = mkdims_wh(x,y,w,h), pen = pen } +function ViewRect:inClipLocalXY(x,y) + return (x+self.x1) >= self.clip_x1 and (x+self.x1) <= self.clip_x2 + and (y+self.y1) >= self.clip_y1 and (y+self.y1) <= self.clip_y2 end -function Painter:isValidPos() - return self.x >= self.clip_x1 and self.x <= self.clip_x2 - and self.y >= self.clip_y1 and self.y <= self.clip_y2 +function ViewRect:localXY(x,y) + return x-self.x1, y-self.y1 end -function Painter:viewport(x,y,w,h) +function ViewRect:globalXY(x,y) + return x+self.x1, y+self.y1 +end + +function ViewRect:viewport(x,y,w,h) if type(x) == 'table' then x,y,w,h = x.x1, x.y1, x.width, x.height end @@ -121,17 +144,57 @@ function Painter:viewport(x,y,w,h) clip_y1 = math.max(self.clip_y1, y1), clip_x2 = math.min(self.clip_x2, x2), clip_y2 = math.min(self.clip_y2, y2), - -- Pen - cur_pen = self.cur_pen } + return mkinstance(ViewRect, vp) +end + +---------------------------- +-- Clipped painter object -- +---------------------------- + +Painter = defclass(Painter, ViewRect) + +function Painter:init(args) + self.x = self.x1 + self.y = self.y1 + self.cur_pen = to_pen(nil, args.pen or COLOR_GREY) +end + +function Painter.new(rect, pen) + return Painter{ rect = rect, pen = pen } +end + +function Painter.new_view(view_rect, pen) + return Painter{ view_rect = view_rect, pen = pen } +end + +function Painter.new_xy(x1,y1,x2,y2,pen) + return Painter{ rect = mkdims_xy(x1,y1,x2,y2), pen = pen } +end + +function Painter.new_wh(x,y,w,h,pen) + return Painter{ rect = mkdims_wh(x,y,w,h), pen = pen } +end + +function Painter:isValidPos() + return self:inClipGlobalXY(self.x, self.y) +end + +function Painter:viewport(x,y,w,h) + local vp = ViewRect.viewport(x,y,w,h) + vp.cur_pen = self.cur_pen return mkinstance(Painter, vp):seek(0,0) end -function Painter:localX() +function Painter:cursor() + return self.x - self.x1, self.y - self.y1 +end + +function Painter:cursorX() return self.x - self.x1 end -function Painter:localY() +function Painter:cursorY() return self.y - self.y1 end @@ -219,25 +282,109 @@ function Painter:string(text,pen,...) end function Painter:key(code,pen,bg,...) - if type(code) == 'string' then - code = df.interface_key[code] - end return self:string( - dscreen.getKeyDisplay(code), + getKeyDisplay(code), pen or COLOR_LIGHTGREEN, bg or self.cur_pen.bg, ... ) end +-------------------------- +-- Abstract view object -- +-------------------------- + +View = defclass(View) + +View.ATTRS { + active = true, + hidden = false, +} + +function View:init(args) + self.subviews = {} +end + +function View:getWindowSize() + local rect = self.frame_body + return rect.width, rect.height +end + +function View:getMousePos() + local rect = self.frame_body + local x,y = dscreen.getMousePos() + if rect and rect:inClipGlobalXY(x,y) then + return rect:localXY(x,y) + end +end + +function View:computeFrame(parent_rect) + return mkdims_wh(0,0,parent_rect.width,parent_rect.height) +end + +function View:updateLayout(parent_rect) + if not parent_rect then + parent_rect = self.frame_parent_rect + else + self.frame_parent_rect = parent_rect + end + + local frame_rect,body_rect = self:computeFrame(parent_rect) + + self.frame_rect = frame_rect + self.frame_body = parent_rect:viewport(body_rect or frame_rect) + + for _,child in ipairs(self.subviews) do + child:updateLayout(frame_body) + end + + self:invoke_after('postUpdateLayout',frame_body) +end + +function View:renderSubviews(dc) + for _,child in ipairs(self.subviews) do + if not child.hidden then + child:render(dc) + end + end +end + +function View:render(dc) + local sub_dc = Painter{ + view_rect = self.frame_body, + clip_view = dc + } + + self:onRenderBody(sub_dc) + + self:renderSubviews(sub_dc) +end + +function View:onRenderBody(dc) +end + +function View:inputToSubviews(keys) + for _,child in ipairs(self.subviews) do + if child.active and child:onInput(keys) then + return true + end + end + + return false +end + +function View:onInput(keys) + return self:inputToSubviews(keys) +end + ------------------------ -- Base screen object -- ------------------------ -Screen = defclass(Screen) +Screen = defclass(Screen, View) Screen.text_input_mode = false function Screen:postinit() - self:updateLayout() + self:onResize(dscreen.getWindowSize()) end Screen.isDismissed = dscreen.isDismissed @@ -254,14 +401,6 @@ function Screen:invalidate() dscreen.invalidate() end -function Screen:getWindowSize() - return dscreen.getWindowSize() -end - -function Screen:getMousePos() - return dscreen.getMousePos() -end - function Screen:renderParent() if self._native and self._native.parent then self._native.parent:render() @@ -290,7 +429,7 @@ function Screen:onAboutToShow() end function Screen:onShow() - self:updateLayout() + self:onResize(dscreen.getWindowSize()) end function Screen:dismiss() @@ -306,11 +445,11 @@ function Screen:onDestroy() end function Screen:onResize(w,h) - self:updateLayout() + self:updateLayout(ViewRect{ rect = mkdims_wh(0,0,w,h) }) end -function Screen:updateLayout() - self:invoke_after('postUpdateLayout') +function Screen:onRender() + self:render(Painter.new()) end ------------------------ @@ -372,6 +511,7 @@ FramedScreen.ATTRS{ frame_title = DEFAULT_NIL, frame_width = DEFAULT_NIL, frame_height = DEFAULT_NIL, + frame_inset = 0, } local function hint_coord(gap,hint) @@ -388,52 +528,35 @@ function FramedScreen:getWantedFrameSize() return self.frame_width, self.frame_height end -function FramedScreen:updateFrameSize() - local sw, sh = dscreen.getWindowSize() - local iw, ih = sw-2, sh-2 +function FramedScreen:computeFrame(parent_rect) + local sw, sh = parent_rect.width, parent_rect.height + local ins = self.frame_inset + local thickness = 2+ins*2 + local iw, ih = sw-thickness, sh-thickness local fw, fh = self:getWantedFrameSize() local width = math.min(fw or iw, iw) local height = math.min(fh or ih, ih) local gw, gh = iw-width, ih-height local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint) - self.frame_rect = mkdims_wh(x1+1,y1+1,width,height) self.frame_opaque = (gw == 0 and gh == 0) + return mkdims_wh(x1,y1,width+thickness,height+thickness), + mkdims_wh(x1+1+ins,y1+1+ins,width,height) end -function FramedScreen:postUpdateLayout() - self:updateFrameSize() -end - -function FramedScreen:getWindowSize() - local rect = self.frame_rect - return rect.width, rect.height -end - -function FramedScreen:getMousePos() - local rect = self.frame_rect - local x,y = dscreen.getMousePos() - if is_in_rect(rect,x,y) then - return x-rect.x1, y-rect.y1 - end -end - -function FramedScreen:onRender() +function FramedScreen:render(dc) local rect = self.frame_rect - local x1,y1,x2,y2 = rect.x1-1, rect.y1-1, rect.x2+1, rect.y2+1 + local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2 if self.frame_opaque then - dscreen.clear() + dc:clear() else self:renderParent() - dscreen.fillRect(CLEAR_PEN,x1,y1,x2,y2) + dc:fill(rect, CLEAR_PEN) end paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title) - self:onRenderBody(Painter.new(rect)) -end - -function FramedScreen:onRenderBody(dc) + FramedScreen.super.render(self, dc) end return _ENV diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 553bb3a4b..75f7349d1 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -120,7 +120,7 @@ function InputBox:onRenderBody(dc) dc:newline(1) dc:pen(self.input_pen or COLOR_LIGHTCYAN) - dc:fill(1,dc:localY(),dc.width-2,dc:localY()) + dc:fill(1,dc:cursorY(),dc.width-2,dc:cursorY()) local cursor = '_' if math.floor(dfhack.getTickCount()/300) % 2 == 0 then diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 125e71220..4056fbfcc 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -253,8 +253,9 @@ end DwarfOverlay = defclass(DwarfOverlay, gui.Screen) -function DwarfOverlay:postUpdateLayout() +function DwarfOverlay:updateLayout(parent_rect) self.df_layout = getPanelLayout() + DwarfOverlay.super.updateLayout(self, parent_rect) end function DwarfOverlay:getViewport(old_vp) @@ -352,12 +353,13 @@ end MenuOverlay = defclass(MenuOverlay, DwarfOverlay) -function MenuOverlay:postUpdateLayout() - self.frame_rect = self.df_layout.menu -end +MenuOverlay.ATTRS { + frame_inset = 0 +} -MenuOverlay.getWindowSize = gui.FramedScreen.getWindowSize -MenuOverlay.getMousePos = gui.FramedScreen.getMousePos +function MenuOverlay:computeFrame(parent_rect) + return self.df_layout.menu, gui.inset(self.df_layout.menu, self.frame_inset) +end function MenuOverlay:onAboutToShow(below) MenuOverlay.super.onAboutToShow(self,below) @@ -368,7 +370,7 @@ function MenuOverlay:onAboutToShow(below) end end -function MenuOverlay:onRender() +function MenuOverlay:render(dc) self:renderParent() local menu = self.df_layout.menu @@ -379,7 +381,7 @@ function MenuOverlay:onRender() menu.x1+1, menu.y2+1, "DFHack" ) - self:onRenderBody(gui.Painter.new(menu)) + MenuOverlay.super.render(self, dc) end end diff --git a/scripts/gui/guide-path.lua b/scripts/gui/guide-path.lua index f3e27d917..4d32a99c2 100644 --- a/scripts/gui/guide-path.lua +++ b/scripts/gui/guide-path.lua @@ -7,10 +7,6 @@ local dlg = require 'gui.dialogs' local tile_attrs = df.tiletype.attrs -function same_xyz(a,b) - return a and b and a.x == b.x and a.y == b.y and a.z == b.z -end - GuidePathUI = defclass(GuidePathUI, guidm.MenuOverlay) GuidePathUI.focus_path = 'guide-path' diff --git a/scripts/gui/hello-world.lua b/scripts/gui/hello-world.lua index c8cd3bd01..122f2cd38 100644 --- a/scripts/gui/hello-world.lua +++ b/scripts/gui/hello-world.lua @@ -7,12 +7,13 @@ local text = 'Woohoo, lua viewscreen :)' local screen = gui.FramedScreen{ frame_style = gui.GREY_LINE_FRAME, frame_title = 'Hello World', - frame_width = #text+6, - frame_height = 3, + frame_width = #text, + frame_height = 1, + frame_inset = 1, } function screen:onRenderBody(dc) - dc:seek(3,1):string(text, COLOR_LIGHTGREEN) + dc:string(text, COLOR_LIGHTGREEN) end function screen:onInput(keys) diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index c518a9cf8..9a5d76066 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -234,12 +234,12 @@ function SiegeEngine:onRenderBody_main(dc) if links then dc:key('CUSTOM_D'):string(": Delete, ") dc:key('CUSTOM_O'):string(": Zoom"):newline() - self:renderStockpiles(dc, links, bottom-2-dc:localY()) + self:renderStockpiles(dc, links, bottom-2-dc:cursorY()) dc:newline():newline() end local prof = self.building:getWorkshopProfile() or {} - dc:seek(1,math.max(dc:localY(),19)) + dc:seek(1,math.max(dc:cursorY(),19)) dc:key('CUSTOM_G'):key('CUSTOM_H'):key('CUSTOM_J'):key('CUSTOM_K') dc:string(': ') dc:string(df.skill_rating.attrs[prof.min_level or 0].caption):string('-') @@ -461,7 +461,7 @@ function SiegeEngine:onRenderBody(dc) self.mode.render(dc) - dc:seek(1, math.max(dc:localY(), 21)):pen(COLOR_WHITE) + dc:seek(1, math.max(dc:cursorY(), 21)):pen(COLOR_WHITE) dc:key('LEAVESCREEN'):string(": Back, ") dc:key('CUSTOM_C'):string(": Recenter") end From abfe2754fbf60199f116514a5d0498a8a90769a4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 15 Oct 2012 20:03:18 +0400 Subject: [PATCH 139/196] Start implementing common widgets for lua screens. --- library/lua/gui.lua | 134 +++++++++++++++++++++++++--------- library/lua/gui/dialogs.lua | 43 ++++------- library/lua/gui/dwarfmode.lua | 9 ++- library/lua/gui/widgets.lua | 80 ++++++++++++++++++++ scripts/gui/guide-path.lua | 8 +- scripts/gui/power-meter.lua | 4 + 6 files changed, 209 insertions(+), 69 deletions(-) create mode 100644 library/lua/gui/widgets.lua diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 4f98e1bde..ea2a79da8 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -48,16 +48,74 @@ end function mkdims_wh(x1,y1,w,h) return { x1=x1, y1=y1, x2=x1+w-1, y2=y1+h-1, width=w, height=h } end -function inset(rect,dx1,dy1,dx2,dy2) - return mkdims_xy( - rect.x1+dx1, rect.y1+(dy1 or dx1), - rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1 or dx1) - ) -end function is_in_rect(rect,x,y) return x and y and x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2 end +local function align_coord(gap,align,lv,rv) + if gap <= 0 then + return 0 + end + if not align then + if rv and not lv then + align = 1.0 + elseif lv and not rv then + align = 0.0 + else + align = 0.5 + end + end + return math.floor(gap*align) +end + +function compute_frame_rect(wavail,havail,spec,xgap,ygap) + if not spec then + return mkdims_wh(0,0,wavail,havail) + end + + local sw = wavail - (spec.l or 0) - (spec.r or 0) + local sh = havail - (spec.t or 0) - (spec.b or 0) + local rqw = math.min(sw, (spec.w or sw)+xgap) + local rqh = math.min(sh, (spec.h or sh)+ygap) + local ax = align_coord(sw - rqw, spec.xalign, spec.l, spec.r) + local ay = align_coord(sh - rqh, spec.yalign, spec.t, spec.b) + + local rect = mkdims_wh((spec.l or 0) + ax, (spec.t or 0) + ay, rqw, rqh) + rect.wgap = sw - rqw + rect.hgap = sh - rqh + return rect +end + +local function parse_inset(inset) + local l,r,t,b + if type(inset) == 'table' then + l,r = inset.l or inset.x, inset.r or inset.x + t,b = inset.t or inset.y, inset.b or inset.y + else + l = inset or 0 + t,r,b = l,l,l + end + return l,r,t,b +end + +function inset_frame(rect, inset, gap) + gap = gap or 0 + local l,t,r,b = parse_inset(inset) + return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) +end + +function compute_frame_body(wavail, havail, spec, inset, gap) + gap = gap or 0 + local l,t,r,b = parse_inset(inset) + local rect = compute_frame_rect(wavail, havail, spec, gap*2+l+r, gap*2+t+b) + local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) + return rect, body +end + +function blink_visible(delay) + return math.floor(dfhack.getTickCount()/delay) % 2 == 0 +end + local function to_pen(default, pen, bg, bold) if pen == nil then return default or {} @@ -296,13 +354,35 @@ View = defclass(View) View.ATTRS { active = true, - hidden = false, + visible = true, + view_id = DEFAULT_NIL, } function View:init(args) self.subviews = {} end +function View:addviews(list) + local sv = self.subviews + + for _,obj in ipairs(list) do + table.insert(sv, obj) + + local id = obj.view_id + if id and type(id) ~= 'number' and sv[id] == nil then + sv[id] = obj + end + end + + for _,dir in ipairs(list) do + for id,obj in pairs(dir) do + if id and type(id) ~= 'number' and sv[id] == nil then + sv[id] = obj + end + end + end +end + function View:getWindowSize() local rect = self.frame_body return rect.width, rect.height @@ -320,6 +400,12 @@ function View:computeFrame(parent_rect) return mkdims_wh(0,0,parent_rect.width,parent_rect.height) end +function View:updateSubviewLayout(frame_body) + for _,child in ipairs(self.subviews) do + child:updateLayout(frame_body) + end +end + function View:updateLayout(parent_rect) if not parent_rect then parent_rect = self.frame_parent_rect @@ -332,16 +418,14 @@ function View:updateLayout(parent_rect) self.frame_rect = frame_rect self.frame_body = parent_rect:viewport(body_rect or frame_rect) - for _,child in ipairs(self.subviews) do - child:updateLayout(frame_body) - end + self:updateSubviewLayout(self.frame_body) - self:invoke_after('postUpdateLayout',frame_body) + self:invoke_after('postUpdateLayout', self.frame_body) end function View:renderSubviews(dc) for _,child in ipairs(self.subviews) do - if not child.hidden then + if child.visible then child:render(dc) end end @@ -512,46 +596,28 @@ FramedScreen.ATTRS{ frame_width = DEFAULT_NIL, frame_height = DEFAULT_NIL, frame_inset = 0, + frame_background = CLEAR_PEN, } -local function hint_coord(gap,hint) - if hint and hint > 0 then - return math.min(hint,gap) - elseif hint and hint < 0 then - return math.max(0,gap-hint) - else - return math.floor(gap/2) - end -end - function FramedScreen:getWantedFrameSize() return self.frame_width, self.frame_height end function FramedScreen:computeFrame(parent_rect) local sw, sh = parent_rect.width, parent_rect.height - local ins = self.frame_inset - local thickness = 2+ins*2 - local iw, ih = sw-thickness, sh-thickness local fw, fh = self:getWantedFrameSize() - local width = math.min(fw or iw, iw) - local height = math.min(fh or ih, ih) - local gw, gh = iw-width, ih-height - local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint) - self.frame_opaque = (gw == 0 and gh == 0) - return mkdims_wh(x1,y1,width+thickness,height+thickness), - mkdims_wh(x1+1+ins,y1+1+ins,width,height) + return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1) end function FramedScreen:render(dc) local rect = self.frame_rect local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2 - if self.frame_opaque then + if rect.wgap <= 0 and rect.hgap <= 0 then dc:clear() else self:renderParent() - dc:fill(rect, CLEAR_PEN) + dc:fill(rect, self.frame_background) end paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 75f7349d1..10a43c22e 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -3,6 +3,7 @@ local _ENV = mkmodule('gui.dialogs') local gui = require('gui') +local widgets = require('gui.widgets') local utils = require('utils') local dscreen = dfhack.screen @@ -101,8 +102,6 @@ InputBox = defclass(InputBox, MessageBox) InputBox.focus_path = 'InputBox' InputBox.ATTRS{ - input = '', - input_pen = DEFAULT_NIL, on_input = DEFAULT_NIL, } @@ -110,46 +109,36 @@ function InputBox:preinit(info) info.on_accept = nil end +function InputBox:init(info) + self:addviews{ + widgets.EditField{ + view_id = 'edit', + text = info.input, + text_pen = info.input_pen, + frame = { l = 1, r = 1, h = 1 }, + } + } +end + function InputBox:getWantedFrameSize() local mw, mh = InputBox.super.getWantedFrameSize(self) + self.subviews.edit.frame.t = mh return mw, mh+2 end -function InputBox:onRenderBody(dc) - InputBox.super.onRenderBody(self, dc) - - dc:newline(1) - dc:pen(self.input_pen or COLOR_LIGHTCYAN) - dc:fill(1,dc:cursorY(),dc.width-2,dc:cursorY()) - - local cursor = '_' - if math.floor(dfhack.getTickCount()/300) % 2 == 0 then - cursor = ' ' - end - local txt = self.input .. cursor - if #txt > dc.width-2 then - txt = string.char(27)..string.sub(txt, #txt-dc.width+4) - end - dc:string(txt) -end - function InputBox:onInput(keys) if keys.SELECT then self:dismiss() if self.on_input then - self.on_input(self.input) + self.on_input(self.subviews.edit.text) end elseif keys.LEAVESCREEN then self:dismiss() if self.on_cancel then self.on_cancel() end - elseif keys._STRING then - if keys._STRING == 0 then - self.input = string.sub(self.input, 1, #self.input-1) - else - self.input = self.input .. string.char(keys._STRING) - end + else + self:inputToSubviews(keys) end end diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 4056fbfcc..0c1b0599c 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -354,11 +354,12 @@ end MenuOverlay = defclass(MenuOverlay, DwarfOverlay) MenuOverlay.ATTRS { - frame_inset = 0 + frame_inset = 0, + frame_background = CLEAR_PEN, } function MenuOverlay:computeFrame(parent_rect) - return self.df_layout.menu, gui.inset(self.df_layout.menu, self.frame_inset) + return self.df_layout.menu, gui.inset_frame(self.df_layout.menu, self.frame_inset) end function MenuOverlay:onAboutToShow(below) @@ -381,6 +382,10 @@ function MenuOverlay:render(dc) menu.x1+1, menu.y2+1, "DFHack" ) + if self.frame_background then + dc:fill(menu, self.frame_background) + end + MenuOverlay.super.render(self, dc) end end diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua new file mode 100644 index 000000000..010ea5510 --- /dev/null +++ b/library/lua/gui/widgets.lua @@ -0,0 +1,80 @@ +-- Simple widgets for screens + +local _ENV = mkmodule('gui.widgets') + +local gui = require('gui') +local utils = require('utils') + +local dscreen = dfhack.screen + +------------ +-- Widget -- +------------ + +Widget = defclass(Widget, gui.View) + +Widget.ATTRS { + frame = DEFAULT_NIL, + frame_inset = DEFAULT_NIL, + frame_background = DEFAULT_NIL, +} + +function Widget:computeFrame(parent_rect) + local sw, sh = parent_rect.width, parent_rect.height + return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset) +end + +function Widget:render(dc) + if self.frame_background then + dc:fill(self.frame_rect, self.frame_background) + end + + Widget.super.render(self, dc) +end + +---------------- +-- Edit field -- +---------------- + +EditField = defclass(EditField, Widget) + +EditField.ATTRS{ + text = '', + text_pen = DEFAULT_NIL, + on_char = DEFAULT_NIL, + on_change = DEFAULT_NIL, +} + +function EditField:onRenderBody(dc) + dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0) + + local cursor = '_' + if not self.active or gui.blink_visible(300) then + cursor = ' ' + end + local txt = self.text .. cursor + if #txt > dc.width then + txt = string.char(27)..string.sub(txt, #txt-dc.width+2) + end + dc:string(txt) +end + +function EditField:onInput(keys) + if keys._STRING then + local old = self.text + if keys._STRING == 0 then + self.text = string.sub(old, 1, #old-1) + else + local cv = string.char(keys._STRING) + if not self.on_char or self.on_char(cv, old) then + self.text = old .. cv + end + end + if self.on_change and self.text ~= old then + self.on_change(self.text, old) + end + return true + end +end + +return _ENV diff --git a/scripts/gui/guide-path.lua b/scripts/gui/guide-path.lua index 4d32a99c2..1546150b7 100644 --- a/scripts/gui/guide-path.lua +++ b/scripts/gui/guide-path.lua @@ -69,17 +69,13 @@ local function get_path_point(gpath,i) return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i]) end -local function blink_visible(delay) - return math.floor(dfhack.getTickCount()/delay) % 2 == 0 -end - function GuidePathUI:renderPath(cursor) local gpath = self.order.guide_path local startp = self.stop.pos local endp = self.next_stop.pos local vp = self:getViewport() local dc = gui.Painter.new(self.df_layout.map) - local visible = blink_visible(500) + local visible = gui.blink_visible(500) if visible then paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN) @@ -123,7 +119,7 @@ function GuidePathUI:renderPath(cursor) end end - if blink_visible(120) then + if gui.blink_visible(120) then paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN) end diff --git a/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua index 81b56e29f..ac30c9273 100644 --- a/scripts/gui/power-meter.lua +++ b/scripts/gui/power-meter.lua @@ -12,6 +12,10 @@ PowerMeter = defclass(PowerMeter, guidm.MenuOverlay) PowerMeter.focus_path = 'power-meter' +PowerMeter.ATTRS { + frame_background = false +} + function PowerMeter:init() self:assign{ min_power = 0, max_power = -1, invert = false, From d336abfd974c3a721bf71ba4be0156501877365c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 16 Oct 2012 14:18:35 +0400 Subject: [PATCH 140/196] Add label and list widgets, and switch stock dialogs to them. --- library/lua/dfhack.lua | 4 + library/lua/gui.lua | 18 +- library/lua/gui/dialogs.lua | 164 ++++---------- library/lua/gui/widgets.lua | 424 +++++++++++++++++++++++++++++++++++- library/lua/utils.lua | 18 ++ 5 files changed, 502 insertions(+), 126 deletions(-) diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 9fc94a025..3f57e5722 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -125,6 +125,10 @@ end -- Misc functions +NEWLINE = "\n" +COMMA = "," +PERIOD = "." + function printall(table) local ok,f,t,k = pcall(pairs,table) if ok then diff --git a/library/lua/gui.lua b/library/lua/gui.lua index ea2a79da8..125b59544 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -116,7 +116,7 @@ function blink_visible(delay) return math.floor(dfhack.getTickCount()/delay) % 2 == 0 end -local function to_pen(default, pen, bg, bold) +function to_pen(default, pen, bg, bold) if pen == nil then return default or {} elseif type(pen) ~= 'table' then @@ -363,6 +363,8 @@ function View:init(args) end function View:addviews(list) + if not list then return end + local sv = self.subviews for _,obj in ipairs(list) do @@ -413,11 +415,15 @@ function View:updateLayout(parent_rect) self.frame_parent_rect = parent_rect end + self:invoke_before('preUpdateLayout', parent_rect) + local frame_rect,body_rect = self:computeFrame(parent_rect) self.frame_rect = frame_rect self.frame_body = parent_rect:viewport(body_rect or frame_rect) + self:invoke_after('postComputeFrame', self.frame_body) + self:updateSubviewLayout(self.frame_body) self:invoke_after('postUpdateLayout', self.frame_body) @@ -432,6 +438,8 @@ function View:renderSubviews(dc) end function View:render(dc) + self:onRenderFrame(dc, self.frame_rect) + local sub_dc = Painter{ view_rect = self.frame_body, clip_view = dc @@ -442,6 +450,9 @@ function View:render(dc) self:renderSubviews(sub_dc) end +function View:onRenderFrame(dc,rect) +end + function View:onRenderBody(dc) end @@ -609,8 +620,7 @@ function FramedScreen:computeFrame(parent_rect) return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1) end -function FramedScreen:render(dc) - local rect = self.frame_rect +function FramedScreen:onRenderFrame(dc, rect) local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2 if rect.wgap <= 0 and rect.hgap <= 0 then @@ -621,8 +631,6 @@ function FramedScreen:render(dc) end paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title) - - FramedScreen.super.render(self, dc) end return _ENV diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 10a43c22e..7d8058a93 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -14,47 +14,35 @@ MessageBox.focus_path = 'MessageBox' MessageBox.ATTRS{ frame_style = gui.GREY_LINE_FRAME, + frame_inset = 1, -- new attrs - text = {}, on_accept = DEFAULT_NIL, on_cancel = DEFAULT_NIL, on_close = DEFAULT_NIL, - text_pen = DEFAULT_NIL, } -function MessageBox:preinit(info) - if type(info.text) == 'string' then - info.text = utils.split_string(info.text, "\n") - end +function MessageBox:init(info) + self:addviews{ + widgets.Label{ + view_id = 'label', + text = info.text, + text_pen = info.text_pen, + frame = { l = 0, t = 0 }, + auto_height = true + } + } end function MessageBox:getWantedFrameSize() - local text = self.text - local w = #(self.frame_title or '') + 4 - w = math.max(w, 20) - w = math.max(self.frame_width or w, w) - for _, l in ipairs(text) do - w = math.max(w, #l) - end - local h = #text+1 - if h > 1 then - h = h+1 - end - return w+2, #text+2 + local label = self.subviews.label + local width = math.max(self.frame_width or 0, 20, #(self.frame_title or '') + 4) + return math.max(width, label:getTextWidth()), label:getTextHeight() end -function MessageBox:onRenderBody(dc) - if #self.text > 0 then - dc:newline(1):pen(self.text_pen or COLOR_GREY) - for _, l in ipairs(self.text or {}) do - dc:string(l):newline(1) - end - end - +function MessageBox:onRenderFrame(dc,rect) + MessageBox.super.onRenderFrame(self,dc,rect) if self.on_accept then - local fr = self.frame_rect - local dc2 = gui.Painter.new_xy(fr.x1+1,fr.y2+1,fr.x2-8,fr.y2+1) - dc2:key('LEAVESCREEN'):string('/'):key('MENU_CONFIRM') + dc:seek(rect.x1+2,rect.y2):key('LEAVESCREEN'):string('/'):key('MENU_CONFIRM') end end @@ -75,6 +63,8 @@ function MessageBox:onInput(keys) if self.on_cancel then self.on_cancel() end + else + self:inputToSubviews(keys) end end @@ -115,14 +105,14 @@ function InputBox:init(info) view_id = 'edit', text = info.input, text_pen = info.input_pen, - frame = { l = 1, r = 1, h = 1 }, + frame = { l = 0, r = 0, h = 1 }, } } end function InputBox:getWantedFrameSize() local mw, mh = InputBox.super.getWantedFrameSize(self) - self.subviews.edit.frame.t = mh + self.subviews.edit.frame.t = mh+1 return mw, mh+2 end @@ -165,107 +155,47 @@ ListBox.ATTRS{ on_select = DEFAULT_NIL } -function InputBox:preinit(info) +function ListBox:preinit(info) info.on_accept = nil end function ListBox:init(info) - self.page_top = 1 -end + local spen = gui.to_pen(COLOR_CYAN, info.select_pen, nil, false) + local cpen = gui.to_pen(COLOR_LIGHTCYAN, info.cursor_pen or info.select_pen, nil, true) -local function choice_text(entry) - if type(entry)=="table" then - return entry.caption or entry[1] - else - return entry - end + self:addviews{ + widgets.List{ + view_id = 'list', + selected = info.selected, + choices = info.choices, + text_pen = spen, + cursor_pen = cpen, + on_submit = function(sel,obj) + self:dismiss() + if self.on_select then self.on_select(sel, obj) end + local cb = obj.on_select or obj[2] + if cb then cb(obj, sel) end + end, + frame = { l = 0, r = 0 }, + } + } end function ListBox:getWantedFrameSize() - local mw, mh = ListBox.super.getWantedFrameSize(self) - for _,v in ipairs(self.choices) do - local text = choice_text(v) - mw = math.max(mw, #text+2) - end - return mw, mh+#self.choices+1 -end - -function ListBox:postUpdateLayout() - self.page_size = self.frame_rect.height - #self.text - 3 - self:moveCursor(0) -end - -function ListBox:moveCursor(delta) - local page = math.max(1, self.page_size) - local cnt = #self.choices - local off = self.selection+delta-1 - local ds = math.abs(delta) - - if ds > 1 then - if off >= cnt+ds-1 then - off = 0 - else - off = math.min(cnt-1, off) - end - if off <= -ds then - off = cnt-1 - else - off = math.max(0, off) - end - end - - self.selection = 1 + off % cnt - self.page_top = 1 + page * math.floor((self.selection-1) / page) -end - -function ListBox:onRenderBody(dc) - ListBox.super.onRenderBody(self, dc) - - dc:newline(1):pen(self.select_pen or COLOR_CYAN) - - local choices = self.choices - local iend = math.min(#choices, self.page_top+self.page_size-1) - - for i = self.page_top,iend do - local text = choice_text(choices[i]) - if text then - dc.cur_pen.bold = (i == self.selection); - dc:string(text) - else - dc:string('?ERROR?', COLOR_LIGHTRED) - end - dc:newline(1) - end + local mw, mh = InputBox.super.getWantedFrameSize(self) + local list = self.subviews.list + list.frame.t = mh+1 + return math.max(mw, list:getContentWidth()), mh+1+list:getContentHeight() end function ListBox:onInput(keys) - if keys.SELECT then - self:dismiss() - - local choice=self.choices[self.selection] - if self.on_select then - self.on_select(self.selection, choice) - end - - if choice then - local callback = choice.on_select or choice[2] - if callback then - callback(choice, self.selection) - end - end - elseif keys.LEAVESCREEN then + if keys.LEAVESCREEN then self:dismiss() if self.on_cancel then self.on_cancel() end - elseif keys.STANDARDSCROLL_UP then - self:moveCursor(-1) - elseif keys.STANDARDSCROLL_DOWN then - self:moveCursor(1) - elseif keys.STANDARDSCROLL_PAGEUP then - self:moveCursor(-self.page_size) - elseif keys.STANDARDSCROLL_PAGEDOWN then - self:moveCursor(self.page_size) + else + self:inputToSubviews(keys) end end diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 010ea5510..f3796d0e3 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -7,6 +7,21 @@ local utils = require('utils') local dscreen = dfhack.screen +local function show_view(view,vis,act) + if view then + view.visible = vis + view.active = act + end +end + +local function getval(obj) + if type(obj) == 'function' then + return obj() + else + return obj + end +end + ------------ -- Widget -- ------------ @@ -24,12 +39,62 @@ function Widget:computeFrame(parent_rect) return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset) end -function Widget:render(dc) +function Widget:onRenderFrame(dc, rect) if self.frame_background then - dc:fill(self.frame_rect, self.frame_background) + dc:fill(rect, self.frame_background) end +end + +----------- +-- Panel -- +----------- + +Panel = defclass(Panel, Widget) - Widget.super.render(self, dc) +Panel.ATTRS { + on_render = DEFAULT_NIL, +} + +function Panel:init(args) + self:addviews(args.subviews) +end + +function Panel:onRenderBody(dc) + if self.on_render then self.on_render(dc) end +end + +----------- +-- Pages -- +----------- + +Pages = defclass(Pages, Panel) + +function Pages:init(args) + for _,v in ipairs(self.subviews) do + show_view(v, false, false) + end + self:setSelected(args.selected or 1) +end + +function Pages:setSelected(idx) + if type(idx) ~= 'number' then + local key = idx + if type(idx) == 'string' then + key = self.subviews[key] + end + idx = utils.linear_index(self.subviews, key) + if not idx then + error('Unknown page: '..key) + end + end + + show_view(self.subviews[self.selected], false, false) + self.selected = math.min(math.max(1, idx), #self.subviews) + show_view(self.subviews[self.selected], true, true) +end + +function Pages:getSelected() + return self.selected, self.subviews[self.selected] end ---------------- @@ -43,6 +108,7 @@ EditField.ATTRS{ text_pen = DEFAULT_NIL, on_char = DEFAULT_NIL, on_change = DEFAULT_NIL, + on_submit = DEFAULT_NIL, } function EditField:onRenderBody(dc) @@ -60,7 +126,10 @@ function EditField:onRenderBody(dc) end function EditField:onInput(keys) - if keys._STRING then + if self.on_submit and keys.SELECT then + self.on_submit(self.text) + return true + elseif keys._STRING then local old = self.text if keys._STRING == 0 then self.text = string.sub(old, 1, #old-1) @@ -77,4 +146,351 @@ function EditField:onInput(keys) end end +----------- +-- Label -- +----------- + +function parse_label_text(obj) + local text = obj.text or {} + if type(text) ~= 'table' then + text = { text } + end + local curline = nil + local out = { } + local active = nil + local idtab = nil + for _,v in ipairs(text) do + local vv + if type(v) == 'string' then + vv = utils.split_string(v, NEWLINE) + else + vv = { v } + end + + for i = 1,#vv do + local cv = vv[i] + if i > 1 then + if not curline then + table.insert(out, {}) + end + curline = nil + end + if cv ~= '' then + if not curline then + curline = {} + table.insert(out, curline) + end + + if type(cv) == 'string' then + table.insert(curline, { text = cv }) + else + table.insert(curline, cv) + + if cv.on_activate then + active = active or {} + table.insert(active, cv) + end + + if cv.id then + idtab = idtab or {} + idtab[cv.id] = cv + end + end + end + end + end + obj.text_lines = out + obj.text_active = active + obj.text_ids = idtab +end + +function render_text(obj,dc,x0,y0,pen,dpen) + local width = 0 + for iline,line in ipairs(obj.text_lines) do + local x = 0 + if dc then + dc:seek(x+x0,y0+iline-1) + end + for _,token in ipairs(line) do + token.line = iline + token.x1 = x + + if token.gap then + x = x + token.gap + if dc then + dc:advance(token.gap) + end + end + + if token.text or token.key then + local text = getval(token.text) or '' + local keypen + + if dc then + if getval(token.disabled) then + dc:pen(getval(token.dpen) or dpen) + keypen = COLOR_GREEN + else + dc:pen(getval(token.pen) or pen) + keypen = COLOR_LIGHTGREEN + end + end + + x = x + #text + + if token.key then + local keystr = gui.getKeyDisplay(token.key) + local sep = token.key_sep or '' + + if sep == '()' then + if dc then + dc:string(text) + dc:string(' ('):string(keystr,keypen):string(')') + end + x = x + 3 + else + if dc then + dc:string(keystr,keypen):string(sep):string(text) + end + x = x + #sep + end + else + if dc then + dc:string(text) + end + end + end + + token.x2 = x + end + width = math.max(width, x) + end + obj.text_width = width +end + +function check_text_keys(self, keys) + if self.text_active then + for _,item in ipairs(self.text_active) do + if item.key and keys[item.key] and not getval(item.disabled) then + item.on_activate() + return true + end + end + end +end + +Label = defclass(Label, Widget) + +Label.ATTRS{ + text_pen = COLOR_WHITE, + text_dpen = COLOR_DARKGREY, + auto_height = true, +} + +function Label:init(args) + self:setText(args.text) +end + +function Label:setText(text) + self.text = text + parse_label_text(self) + + if self.auto_height then + self.frame = self.frame or {} + self.frame.h = self:getTextHeight() + end +end + +function Label:itemById(id) + if self.text_ids then + return self.text_ids[id] + end +end + +function Label:getTextHeight() + return #self.text_lines +end + +function Label:getTextWidth() + render_text(self) + return self.text_width +end + +function Label:onRenderBody(dc) + render_text(self,dc,0,0,self.text_pen,self.text_dpen) +end + +function Label:onInput(keys) + return check_text_keys(self, keys) +end + +---------- +-- List -- +---------- + +List = defclass(List, Widget) + +STANDARDSCROLL = { + STANDARDSCROLL_UP = -1, + STANDARDSCROLL_DOWN = 1, + STANDARDSCROLL_PAGEUP = '-page', + STANDARDSCROLL_PAGEDOWN = '+page', +} + +List.ATTRS{ + text_pen = COLOR_CYAN, + cursor_pen = COLOR_LIGHTCYAN, + cursor_dpen = DEFAULT_NIL, + inactive_pen = DEFAULT_NIL, + on_select = DEFAULT_NIL, + on_submit = DEFAULT_NIL, + row_height = 1, + scroll_keys = STANDARDSCROLL, +} + +function List:init(info) + self.page_top = 1 + self.page_size = 1 + self:setChoices(info.choices, info.selected) +end + +function List:setChoices(choices, selected) + self.choices = choices or {} + + for i,v in ipairs(self.choices) do + if type(v) ~= 'table' then + v = { text = v } + self.choices[i] = v + end + v.text = v.text or v.caption or v[1] + parse_label_text(v) + end + + self:setSelected(selected) +end + +function List:setSelected(selected) + self.selected = selected or self.selected or 1 + self:moveCursor(0, true) + return self.selected +end + +function List:getSelected() + return self.selected, self.choices[self.selected] +end + +function List:getContentWidth() + local width = 0 + for i,v in ipairs(self.choices) do + render_text(v) + local roww = v.text_width + if v.key then + roww = roww + 3 + #gui.getKeyDisplay(v.key) + end + width = math.max(width, roww) + end + return width +end + +function List:getContentHeight() + return #self.choices * self.row_height +end + +function List:postComputeFrame(body) + self.page_size = math.max(1, math.floor(body.height / self.row_height)) + self:moveCursor(0) +end + +function List:moveCursor(delta, force_cb) + local page = math.max(1, self.page_size) + local cnt = #self.choices + local off = self.selected+delta-1 + local ds = math.abs(delta) + + if ds > 1 then + if off >= cnt+ds-1 then + off = 0 + else + off = math.min(cnt-1, off) + end + if off <= -ds then + off = cnt-1 + else + off = math.max(0, off) + end + end + + self.selected = 1 + off % cnt + self.page_top = 1 + page * math.floor((self.selected-1) / page) + + if (force_cb or delta ~= 0) and self.on_select then + self.on_select(self:getSelected()) + end +end + +function List:onRenderBody(dc) + local choices = self.choices + local top = self.page_top + local iend = math.min(#choices, top+self.page_size-1) + + for i = top,iend do + local obj = choices[i] + local current = (i == self.selected) + local cur_pen = self.text_pen + local cur_dpen = cur_pen + + if current and active then + cur_pen = self.cursor_pen + cur_dpen = self.cursor_dpen or self.text_pen + elseif current then + cur_pen = self.inactive_pen or self.cursor_pen + cur_dpen = self.inactive_pen or self.text_pen + end + + local y = (i - top)*self.row_height + render_text(obj, dc, 0, y, cur_pen, cur_dpen) + + if obj.key then + local keystr = gui.getKeyDisplay(obj.key) + dc:seek(dc.width-2-#keystr,y):pen(self.text_pen) + dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')') + end + end +end + +function List:onInput(keys) + if self.on_submit and keys.SELECT then + self.on_submit(self:getSelected()) + return true + else + for k,v in pairs(self.scroll_keys) do + if keys[k] then + if v == '+page' then + v = self.page_size + elseif v == '-page' then + v = -self.page_size + end + + self:moveCursor(v) + return true + end + end + + for i,v in ipairs(self.choices) do + if v.key and keys[v.key] then + self:setSelected(i) + if self.on_submit then + self.on_submit(self:getSelected()) + end + return true + end + end + + local current = self.choices[self.selected] + if current then + return check_text_keys(current, keys) + end + end +end + return _ENV diff --git a/library/lua/utils.lua b/library/lua/utils.lua index b46363cd7..2507c9964 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -302,6 +302,24 @@ function sort_vector(vector,field,cmp) return vector end +-- Linear search + +function linear_index(vector,obj) + local min,max + if df.isvalid(vector) then + min,max = 0,#vector-1 + else + min,max = 1,#vector + end + for i=min,max do + if vector[i] == obj then + return i + end + end + return nil +end + + -- Binary search in a vector or lua table function binsearch(vector,key,field,cmp,min,max) if not(min and max) then From 023dc82564f9f896cffadfede284bb01d9fa7800 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 16 Oct 2012 18:33:00 +0400 Subject: [PATCH 141/196] Implement a material selection dialog. --- library/lua/class.lua | 14 +- library/lua/gui.lua | 4 +- library/lua/gui/materials.lua | 311 ++++++++++++++++++++++++++++++++++ library/lua/gui/script.lua | 12 ++ library/lua/gui/widgets.lua | 29 +++- 5 files changed, 361 insertions(+), 9 deletions(-) create mode 100644 library/lua/gui/materials.lua diff --git a/library/lua/class.lua b/library/lua/class.lua index 7b142e499..bcfff13e2 100644 --- a/library/lua/class.lua +++ b/library/lua/class.lua @@ -3,16 +3,16 @@ local _ENV = mkmodule('class') -- Metatable template for a class -class_obj = {} or class_obj +class_obj = class_obj or {} -- Methods shared by all classes -common_methods = {} or common_methods +common_methods = common_methods or {} -- Forbidden names for class fields and methods. reserved_names = { super = true, ATTRS = true } -- Attribute table metatable -attrs_meta = {} or attrs_meta +attrs_meta = attrs_meta or {} -- Create or updates a class; a class has metamethods and thus own metatable. function defclass(class,parent) @@ -133,6 +133,14 @@ function common_methods:callback(method, ...) return dfhack.curry(self[method], self, ...) end +function common_methods:cb_getfield(field) + return function() return self[field] end +end + +function common_methods:cb_setfield(field) + return function(val) self[field] = val end +end + function common_methods:assign(data) for k,v in pairs(data) do self[k] = v diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 125b59544..90eeb823a 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -377,7 +377,7 @@ function View:addviews(list) end for _,dir in ipairs(list) do - for id,obj in pairs(dir) do + for id,obj in pairs(dir.subviews) do if id and type(id) ~= 'number' and sv[id] == nil then sv[id] = obj end @@ -616,7 +616,7 @@ end function FramedScreen:computeFrame(parent_rect) local sw, sh = parent_rect.width, parent_rect.height - local fw, fh = self:getWantedFrameSize() + local fw, fh = self:getWantedFrameSize(parent_rect) return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1) end diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua new file mode 100644 index 000000000..0cf3c21d9 --- /dev/null +++ b/library/lua/gui/materials.lua @@ -0,0 +1,311 @@ +-- Stock dialog for selecting materials + +local _ENV = mkmodule('gui.materials') + +local gui = require('gui') +local widgets = require('gui.widgets') +local utils = require('utils') + +ARROW = string.char(26) + +CREATURE_BASE = 19 +PLANT_BASE = 419 + +MaterialDialog = defclass(MaterialDialog, gui.FramedScreen) + +MaterialDialog.focus_path = 'MaterialDialog' + +MaterialDialog.ATTRS{ + prompt = 'Type or select a material from this list', + frame_style = gui.GREY_LINE_FRAME, + frame_inset = 1, + frame_title = 'Select Material', + -- new attrs + on_select = DEFAULT_NIL, + on_cancel = DEFAULT_NIL, + on_close = DEFAULT_NIL, +} + +function MaterialDialog:init(info) + self:addviews{ + widgets.Label{ + text = { + self.prompt, '\n\n', + 'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN } + }, + text_pen = COLOR_WHITE, + frame = { l = 0, t = 0 }, + }, + widgets.Label{ + view_id = 'back', + visible = false, + text = { { key = 'LEAVESCREEN', text = ': Back' } }, + frame = { r = 0, b = 0 }, + auto_width = true, + }, + widgets.List{ + view_id = 'list', + frame = { l = 0, r = 0, t = 6, b = 2 }, + on_submit = self:callback('onSubmitItem'), + }, + widgets.EditField{ + view_id = 'edit', + frame = { l = 2, t = 4 }, + on_change = self:callback('onFilterChoices'), + on_char = self:callback('onFilterChar'), + }, + widgets.Label{ + view_id = 'not_found', + text = 'No matching materials.', + text_pen = COLOR_LIGHTRED, + frame = { l = 2, r = 0, t = 6 }, + }, + widgets.Label{ + text = { { + key = 'SELECT', text = ': Select', + disabled = function() return self.subviews.not_found.visible end + } }, + frame = { l = 0, b = 0 }, + } + } + self:initBuiltinMode() +end + +function MaterialDialog:getWantedFrameSize(rect) + return math.max(40, #self.prompt), math.min(28, rect.height-8) +end + +function MaterialDialog:onDestroy() + if self.on_close then + self.on_close() + end +end + +function MaterialDialog:initBuiltinMode() + local choices = { + { text = ' none', mat_type = -1, mat_index = -1 }, + { text = ARROW..' inorganic', key = 'CUSTOM_SHIFT_I', + cb = self:callback('initInorganicMode') }, + { text = ARROW..' creature', key = 'CUSTOM_SHIFT_C', + cb = self:callback('initCreatureMode')}, + { text = ARROW..' plant', key = 'CUSTOM_SHIFT_P', + cb = self:callback('initPlantMode') }, + } + + self:addMaterials( + choices, + df.global.world.raws.mat_table.builtin, 0, -1, + df.builtin_mats._last_item + ) + + self:pushContext('Any material', choices) +end + +function MaterialDialog:initInorganicMode() + local choices = {} + + self:addMaterials( + choices, + df.global.world.raws.inorganics, 0, nil, + nil, 'material' + ) + + self:pushContext('Inorganic materials', choices) +end + +function MaterialDialog:initCreatureMode() + local choices = {} + + for i,v in ipairs(df.global.world.raws.creatures.all) do + self:addObjectChoice(choices, v, v.name[0], CREATURE_BASE, i) + end + + self:pushContext('Creature materials', choices) +end + +function MaterialDialog:initPlantMode() + local choices = {} + + for i,v in ipairs(df.global.world.raws.plants.all) do + self:addObjectChoice(choices, v, v.name, PLANT_BASE, i) + end + + self:pushContext('Plant materials', choices) +end + +function MaterialDialog:addObjectChoice(choices, obj, name, typ, index) + if #obj.material == 1 then + self:addMaterial(choices, obj.material[0], typ, index, true) + else + table.insert(choices, { + text = ARROW..' '..name, mat_type = typ, mat_index = index, + ctx = name, obj = obj, cb = self:callback('onSelectObj') + }) + end +end + +function MaterialDialog:onSelectObj(item) + local choices = {} + self:addMaterials(choices, item.obj.material, item.mat_type, item.mat_index) + self:pushContext(item.ctx, choices) +end + +function MaterialDialog:addMaterials(choices, vector, tid, index, maxid, field) + for i=0,(maxid or #vector-1) do + local mat = vector[i] + if mat and field then + mat = mat[field] + end + if mat then + local typ, idx + if index then + typ, idx = tid+i, index + else + typ, idx = tid, i + end + self:addMaterial(choices, mat, typ, idx) + end + end +end + +function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix) + local state = 0 + if mat.heat.melting_point <= 10015 then + state = 1 + end + local name = mat.state_name[state] + name = string.gsub(name, '^frozen ','') + name = string.gsub(name, '^molten ','') + name = string.gsub(name, '^condensed ','') + local key + if pfix and mat.prefix ~= '' then + name = mat.prefix .. ' ' .. name + key = mat.prefix + end + table.insert(choices, { + text = ' '..name, + search_key = key, + material = mat, + mat_type = typ, mat_index = idx + }) +end + +function MaterialDialog:pushContext(name, choices) + if not self.back_stack then + self.back_stack = {} + self.subviews.back.visible = false + else + table.insert(self.back_stack, { + context_str = self.context_str, + all_choices = self.all_choices, + edit_text = self.subviews.edit.text, + choices = self.choices, + selected = self.subviews.list.selected, + }) + self.subviews.back.visible = true + end + + self.context_str = name + self.all_choices = choices + self.subviews.edit.text = '' + self:setChoices(choices, 1) +end + +function MaterialDialog:onGoBack() + local save = table.remove(self.back_stack) + self.subviews.back.visible = (#self.back_stack > 0) + + self.context_str = save.context_str + self.all_choices = save.all_choices + self.subviews.edit.text = save.edit_text + self:setChoices(save.choices, save.selected) +end + +function MaterialDialog:setChoices(choices, pos) + self.choices = choices + self.subviews.list:setChoices(self.choices, pos) + self.subviews.not_found.visible = (#self.choices == 0) +end + +function MaterialDialog:onFilterChoices(text) + local tokens = utils.split_string(text, ' ') + local choices = {} + + for i,v in ipairs(self.all_choices) do + local ok = true + local search_key = v.search_key or v.text + for _,key in ipairs(tokens) do + if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then + ok = false + break + end + end + if ok then + table.insert(choices, v) + end + end + + self:setChoices(choices) +end + +local bad_chars = { + ['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true, + ['['] = true, [']'] = true, ['('] = true, [')'] = true, +} + +function MaterialDialog:onFilterChar(char, text) + if bad_chars[char] then + return false + end + if char == ' ' then + if #self.choices == 1 then + self.subviews.list:submit() + return false + end + return string.match(text, '%S$') + end + return true +end + +function MaterialDialog:submitMaterial(typ, index) + self:dismiss() + + if self.on_select then + local info = dfhack.matinfo.decode(typ, index) + self.on_select(info, typ, index) + end +end + +function MaterialDialog:onSubmitItem(idx, item) + if item.cb then + item:cb(idx) + else + self:submitMaterial(item.mat_type, item.mat_index) + end +end + +function MaterialDialog:onInput(keys) + if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then + if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then + self:onGoBack() + else + self:dismiss() + if self.on_cancel then + self.on_cancel() + end + end + else + self:inputToSubviews(keys) + end +end + +function showMaterialPrompt(title, prompt, on_select, on_cancel) + MaterialDialog{ + frame_title = title, + prompt = prompt, + on_select = on_select, + on_cancel = on_cancel, + }:show() +end + +return _ENV diff --git a/library/lua/gui/script.lua b/library/lua/gui/script.lua index 021a4fa52..06079c426 100644 --- a/library/lua/gui/script.lua +++ b/library/lua/gui/script.lua @@ -148,4 +148,16 @@ function showListPrompt(title, text, tcolor, choices, min_width) return wait() end +function showMaterialPrompt(title, prompt) + require('gui.materials').MaterialDialog{ + frame_title = title, + prompt = prompt, + on_select = mkresume(true, + on_cancel = mkresume(false), + on_close = qresume(nil) + }:show() + + return wait() +end + return _ENV diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index f3796d0e3..eb2e8d05d 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -242,6 +242,8 @@ function render_text(obj,dc,x0,y0,pen,dpen) local keystr = gui.getKeyDisplay(token.key) local sep = token.key_sep or '' + x = x + #keystr + if sep == '()' then if dc then dc:string(text) @@ -285,6 +287,7 @@ Label.ATTRS{ text_pen = COLOR_WHITE, text_dpen = COLOR_DARKGREY, auto_height = true, + auto_width = false, } function Label:init(args) @@ -301,6 +304,13 @@ function Label:setText(text) end end +function Label:preUpdateLayout() + if self.auto_width then + self.frame = self.frame or {} + self.frame.w = self:getTextWidth() + end +end + function Label:itemById(id) if self.text_ids then return self.text_ids[id] @@ -404,6 +414,13 @@ end function List:moveCursor(delta, force_cb) local page = math.max(1, self.page_size) local cnt = #self.choices + + if cnt < 1 then + self.page_top = 1 + self.selected = 1 + return + end + local off = self.selected+delta-1 local ds = math.abs(delta) @@ -458,9 +475,15 @@ function List:onRenderBody(dc) end end +function List:submit() + if self.on_submit and #self.choices > 0 then + self.on_submit(self:getSelected()) + end +end + function List:onInput(keys) if self.on_submit and keys.SELECT then - self.on_submit(self:getSelected()) + self:submit() return true else for k,v in pairs(self.scroll_keys) do @@ -479,9 +502,7 @@ function List:onInput(keys) for i,v in ipairs(self.choices) do if v.key and keys[v.key] then self:setSelected(i) - if self.on_submit then - self.on_submit(self:getSelected()) - end + self:submit() return true end end From 7aae869d728dad27504518245028b5ec6105f98f Mon Sep 17 00:00:00 2001 From: Timothy Collett Date: Tue, 16 Oct 2012 15:58:34 -0400 Subject: [PATCH 142/196] Add OS X build instructions to the Compile file --- Compile.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Compile.rst b/Compile.rst index 8fca0e1f5..a71642060 100644 --- a/Compile.rst +++ b/Compile.rst @@ -63,6 +63,34 @@ extra options. You can also use a cmake-friendly IDE like KDevelop 4 or the cmake-gui program. +======== +Mac OS X +======== + +1. Download and unpack a copy of the latest DF +2. Install Xcode from Mac App Store +3. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools. +4. Install MacPorts. +5. Install dependencies from MacPorts: + * sudo port install gcc45 +universal cmake +universal git-core +universal (This will take some time—maybe hours, depending on your machine.) + * At some point during this process, it may ask you to install a Java environment; let it do so. +6. Install perl dependencies + 1. sudo cpan (If this is the first time you've run cpan, you will need to go through the setup process. Just stick with the defaults for everything and you'll be fine.) + 2. install XML::LibXML + 3. install XML::LibXSLT +7. Get the dfhack source + 1. git clone https://github.com/danaris/dfhack.git + 2. cd dfhack + 3. git submodule init + 4. git submodule update +8. mkdir build-osx +9. cd build-osx +10. export CC=/opt/local/bin/gcc-mp-4.5 +11. export CXX=/opt/local/bin/g++-mp-4.5 +12. cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/path/to/DF/directory +13. make +14. make install + ======= Windows ======= From 8fd1dd04bb72eecb8056f9b262a10e828c639bdd Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 16 Oct 2012 19:27:48 -0500 Subject: [PATCH 143/196] Display dfhack version number at end of baseline 'help' message. --- library/Core.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index a8000070f..7766b3591 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -374,6 +374,8 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve " unload PLUGIN|all - Unload a plugin or all loaded plugins.\n" " reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" ); + + con.print("\nDFHack version " DFHACK_VERSION ".\n"); } else if (parts.size() == 1) { From b14e4e97f543b5b2a107ce0a808f507c68716e41 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 09:41:48 +0400 Subject: [PATCH 144/196] Natively support entry icons in the List widget. --- library/lua/gui/materials.lua | 17 +++++++++-------- library/lua/gui/widgets.lua | 23 +++++++++++++++++++++-- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index 0cf3c21d9..17fcf2928 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -46,6 +46,7 @@ function MaterialDialog:init(info) widgets.List{ view_id = 'list', frame = { l = 0, r = 0, t = 6, b = 2 }, + icon_width = 2, on_submit = self:callback('onSubmitItem'), }, widgets.EditField{ @@ -83,12 +84,12 @@ end function MaterialDialog:initBuiltinMode() local choices = { - { text = ' none', mat_type = -1, mat_index = -1 }, - { text = ARROW..' inorganic', key = 'CUSTOM_SHIFT_I', + { text = 'none', mat_type = -1, mat_index = -1 }, + { icon = ARROW, text = 'inorganic', key = 'CUSTOM_SHIFT_I', cb = self:callback('initInorganicMode') }, - { text = ARROW..' creature', key = 'CUSTOM_SHIFT_C', + { icon = ARROW, text = 'creature', key = 'CUSTOM_SHIFT_C', cb = self:callback('initCreatureMode')}, - { text = ARROW..' plant', key = 'CUSTOM_SHIFT_P', + { icon = ARROW, text = 'plant', key = 'CUSTOM_SHIFT_P', cb = self:callback('initPlantMode') }, } @@ -138,8 +139,8 @@ function MaterialDialog:addObjectChoice(choices, obj, name, typ, index) self:addMaterial(choices, obj.material[0], typ, index, true) else table.insert(choices, { - text = ARROW..' '..name, mat_type = typ, mat_index = index, - ctx = name, obj = obj, cb = self:callback('onSelectObj') + icon = ARROW, text = name, mat_type = typ, mat_index = index, + obj = obj, cb = self:callback('onSelectObj') }) end end @@ -147,7 +148,7 @@ end function MaterialDialog:onSelectObj(item) local choices = {} self:addMaterials(choices, item.obj.material, item.mat_type, item.mat_index) - self:pushContext(item.ctx, choices) + self:pushContext(item.text, choices) end function MaterialDialog:addMaterials(choices, vector, tid, index, maxid, field) @@ -183,7 +184,7 @@ function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix) key = mat.prefix end table.insert(choices, { - text = ' '..name, + text = name, search_key = key, material = mat, mat_type = typ, mat_index = idx diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index eb2e8d05d..16568aa16 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -222,6 +222,13 @@ function render_text(obj,dc,x0,y0,pen,dpen) end end + if token.tile then + x = x + 1 + if dc then + dc:char(nil, token.tile) + end + end + if token.text or token.key then local text = getval(token.text) or '' local keypen @@ -356,6 +363,7 @@ List.ATTRS{ on_submit = DEFAULT_NIL, row_height = 1, scroll_keys = STANDARDSCROLL, + icon_width = DEFAULT_NIL, } function List:init(info) @@ -399,7 +407,7 @@ function List:getContentWidth() end width = math.max(width, roww) end - return width + return width + (self.icon_width or 0) end function List:getContentHeight() @@ -449,6 +457,7 @@ function List:onRenderBody(dc) local choices = self.choices local top = self.page_top local iend = math.min(#choices, top+self.page_size-1) + local iw = self.icon_width for i = top,iend do local obj = choices[i] @@ -465,7 +474,17 @@ function List:onRenderBody(dc) end local y = (i - top)*self.row_height - render_text(obj, dc, 0, y, cur_pen, cur_dpen) + + if iw and obj.icon then + dc:seek(0, y) + if type(obj.icon) == 'table' then + dc:char(nil,obj.icon) + else + dc:string(obj.icon, obj.icon_pen or cur_pen) + end + end + + render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen) if obj.key then local keystr = gui.getKeyDisplay(obj.key) From ad4f9908fb437a989f80ea6d334e0b44d5372c16 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 10:41:50 +0400 Subject: [PATCH 145/196] Extract a generic filtered list widget from the materials dialog. --- library/lua/gui/dialogs.lua | 18 ++-- library/lua/gui/materials.lua | 81 +++--------------- library/lua/gui/script.lua | 3 +- library/lua/gui/widgets.lua | 149 ++++++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 78 deletions(-) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 7d8058a93..cb8d66176 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -149,8 +149,8 @@ ListBox = defclass(ListBox, MessageBox) ListBox.focus_path = 'ListBox' ListBox.ATTRS{ - selection = 1, - choices = {}, + with_filter = false, + cursor_pen = DEFAULT_NIL, select_pen = DEFAULT_NIL, on_select = DEFAULT_NIL } @@ -160,11 +160,16 @@ function ListBox:preinit(info) end function ListBox:init(info) - local spen = gui.to_pen(COLOR_CYAN, info.select_pen, nil, false) - local cpen = gui.to_pen(COLOR_LIGHTCYAN, info.cursor_pen or info.select_pen, nil, true) + local spen = gui.to_pen(COLOR_CYAN, self.select_pen, nil, false) + local cpen = gui.to_pen(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true) + + local list_widget = widgets.List + if self.with_filter then + list_widget = widgets.FilteredList + end self:addviews{ - widgets.List{ + list_widget{ view_id = 'list', selected = info.selected, choices = info.choices, @@ -199,7 +204,7 @@ function ListBox:onInput(keys) end end -function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width) +function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width, filter) ListBox{ frame_title = title, text = text, @@ -208,6 +213,7 @@ function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_ on_select = on_select, on_cancel = on_cancel, frame_width = min_width, + with_filter = filter, }:show() end diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index 17fcf2928..e8f1e19ca 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -43,28 +43,17 @@ function MaterialDialog:init(info) frame = { r = 0, b = 0 }, auto_width = true, }, - widgets.List{ + widgets.FilteredList{ view_id = 'list', - frame = { l = 0, r = 0, t = 6, b = 2 }, + not_found_label = 'No matching materials', + frame = { l = 0, r = 0, t = 4, b = 2 }, icon_width = 2, on_submit = self:callback('onSubmitItem'), }, - widgets.EditField{ - view_id = 'edit', - frame = { l = 2, t = 4 }, - on_change = self:callback('onFilterChoices'), - on_char = self:callback('onFilterChar'), - }, - widgets.Label{ - view_id = 'not_found', - text = 'No matching materials.', - text_pen = COLOR_LIGHTRED, - frame = { l = 2, r = 0, t = 6 }, - }, widgets.Label{ text = { { key = 'SELECT', text = ': Select', - disabled = function() return self.subviews.not_found.visible end + disabled = function() return not self.subviews.list:canSubmit() end } }, frame = { l = 0, b = 0 }, } @@ -198,18 +187,15 @@ function MaterialDialog:pushContext(name, choices) else table.insert(self.back_stack, { context_str = self.context_str, - all_choices = self.all_choices, - edit_text = self.subviews.edit.text, - choices = self.choices, - selected = self.subviews.list.selected, + all_choices = self.subviews.list:getChoices(), + edit_text = self.subviews.list:getFilter(), + selected = self.subviews.list:getSelected(), }) self.subviews.back.visible = true end self.context_str = name - self.all_choices = choices - self.subviews.edit.text = '' - self:setChoices(choices, 1) + self.subviews.list:setChoices(choices, 1) end function MaterialDialog:onGoBack() @@ -217,55 +203,8 @@ function MaterialDialog:onGoBack() self.subviews.back.visible = (#self.back_stack > 0) self.context_str = save.context_str - self.all_choices = save.all_choices - self.subviews.edit.text = save.edit_text - self:setChoices(save.choices, save.selected) -end - -function MaterialDialog:setChoices(choices, pos) - self.choices = choices - self.subviews.list:setChoices(self.choices, pos) - self.subviews.not_found.visible = (#self.choices == 0) -end - -function MaterialDialog:onFilterChoices(text) - local tokens = utils.split_string(text, ' ') - local choices = {} - - for i,v in ipairs(self.all_choices) do - local ok = true - local search_key = v.search_key or v.text - for _,key in ipairs(tokens) do - if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then - ok = false - break - end - end - if ok then - table.insert(choices, v) - end - end - - self:setChoices(choices) -end - -local bad_chars = { - ['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true, - ['['] = true, [']'] = true, ['('] = true, [')'] = true, -} - -function MaterialDialog:onFilterChar(char, text) - if bad_chars[char] then - return false - end - if char == ' ' then - if #self.choices == 1 then - self.subviews.list:submit() - return false - end - return string.match(text, '%S$') - end - return true + self.subviews.list:setChoices(save.all_choices) + self.subviews.list:setFilter(save.edit_text, save.selected) end function MaterialDialog:submitMaterial(typ, index) diff --git a/library/lua/gui/script.lua b/library/lua/gui/script.lua index 06079c426..e15f6c1b9 100644 --- a/library/lua/gui/script.lua +++ b/library/lua/gui/script.lua @@ -133,13 +133,14 @@ function showInputPrompt(title, text, tcolor, input, min_width) return wait() end -function showListPrompt(title, text, tcolor, choices, min_width) +function showListPrompt(title, text, tcolor, choices, min_width, filter) dlg.ListBox{ frame_title = title, text = text, text_pen = tcolor, choices = choices, frame_width = min_width, + with_filter = filter, on_select = mkresume(true), on_cancel = mkresume(false), on_close = qresume(nil) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 16568aa16..0a6d99ba6 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -22,6 +22,14 @@ local function getval(obj) end end +local function map_opttab(tab,idx) + if tab then + return tab[idx] + else + return idx + end +end + ------------ -- Widget -- ------------ @@ -393,6 +401,10 @@ function List:setSelected(selected) return self.selected end +function List:getChoices() + return self.choices +end + function List:getSelected() return self.selected, self.choices[self.selected] end @@ -533,4 +545,141 @@ function List:onInput(keys) end end +------------------- +-- Filtered List -- +------------------- + +FilteredList = defclass(FilteredList, Widget) + +function FilteredList:init(info) + self.edit = EditField{ + text_pen = info.cursor_pen, + frame = { l = info.icon_width, t = 0 }, + on_change = self:callback('onFilterChange'), + on_char = self:callback('onFilterChar'), + } + self.list = List{ + frame = { t = 2 }, + text_pen = info.text_pen, + cursor_pen = info.cursor_pen, + inactive_pen = info.inactive_pen, + row_height = info.row_height, + scroll_keys = info.scroll_keys, + icon_width = info.icon_width, + } + if info.on_select then + self.list.on_select = function() + return info.on_select(self:getSelected()) + end + end + if info.on_submit then + self.list.on_submit = function() + return info.on_submit(self:getSelected()) + end + end + self.not_found = Label{ + visible = false, + text = info.not_found_label or 'No matches', + text_pen = COLOR_LIGHTRED, + frame = { l = info.icon_width, t = 2 }, + } + self:addviews{ self.list, self.edit, self.not_found } + self:setChoices(info.choices, info.selected) +end + +function FilteredList:getChoices() + return self.choices +end + +function FilteredList:setChoices(choices, pos) + choices = choices or {} + self.choices = choices + self.edit.text = '' + self.list:setChoices(choices, pos) + self.not_found.visible = (#choices == 0) +end + +function FilteredList:submit() + return self.list:submit() +end + +function FilteredList:canSubmit() + return not self.not_found.visible +end + +function FilteredList:getSelected() + local i,v = self.list:getSelected() + return map_opttab(self.choice_index, i), v +end + +function FilteredList:getContentWidth() + return self.list:getContentWidth() +end + +function FilteredList:getContentHeight() + return self.list:getContentHeight() + 2 +end + +function FilteredList:getFilter() + return self.edit.text, self.list.choices +end + +function FilteredList:setFilter(filter, pos) + local choices = self.choices + local cidx = nil + + filter = filter or '' + self.edit.text = filter + + if filter ~= '' then + local tokens = utils.split_string(filter, ' ') + local ipos = pos + + choices = {} + cidx = {} + pos = nil + + for i,v in ipairs(self.choices) do + local ok = true + local search_key = v.search_key or v.text + for _,key in ipairs(tokens) do + if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then + ok = false + break + end + end + if ok then + table.insert(choices, v) + cidx[#choices] = i + if ipos == i then + pos = #choices + end + end + end + end + + self.choice_index = cidx + self.list:setChoices(choices, pos) + self.not_found.visible = (#choices == 0) +end + +function FilteredList:onFilterChange(text) + self:setFilter(text) +end + +local bad_chars = { + ['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true, + ['['] = true, [']'] = true, ['('] = true, [')'] = true, +} + +function FilteredList:onFilterChar(char, text) + if bad_chars[char] then + return false + end + if char == ' ' then + return string.match(text, '%S$') + end + return true +end + return _ENV From 0046b093f761fb42f03fccb32a56efcf854c2b41 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 11:49:11 +0400 Subject: [PATCH 146/196] Link visibility and event handling order. - Hidden widgets don't receive events. - Children handle events in top to bottom order. --- library/lua/gui.lua | 7 +++++-- library/lua/gui/widgets.lua | 11 +++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 90eeb823a..18b0d67d8 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -457,8 +457,11 @@ function View:onRenderBody(dc) end function View:inputToSubviews(keys) - for _,child in ipairs(self.subviews) do - if child.active and child:onInput(keys) then + local children = self.subviews + + for i=#children,1,-1 do + local child = children[i] + if child.visible and child.active and child:onInput(keys) then return true end end diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 0a6d99ba6..05100c00b 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -7,10 +7,9 @@ local utils = require('utils') local dscreen = dfhack.screen -local function show_view(view,vis,act) +local function show_view(view,vis) if view then view.visible = vis - view.active = act end end @@ -79,7 +78,7 @@ Pages = defclass(Pages, Panel) function Pages:init(args) for _,v in ipairs(self.subviews) do - show_view(v, false, false) + v.visible = false end self:setSelected(args.selected or 1) end @@ -96,9 +95,9 @@ function Pages:setSelected(idx) end end - show_view(self.subviews[self.selected], false, false) + show_view(self.subviews[self.selected], false) self.selected = math.min(math.max(1, idx), #self.subviews) - show_view(self.subviews[self.selected], true, true) + show_view(self.subviews[self.selected], true) end function Pages:getSelected() @@ -583,7 +582,7 @@ function FilteredList:init(info) text_pen = COLOR_LIGHTRED, frame = { l = info.icon_width, t = 2 }, } - self:addviews{ self.list, self.edit, self.not_found } + self:addviews{ self.edit, self.list, self.not_found } self:setChoices(info.choices, info.selected) end From ffe70e9ee71731bf93f07a67ba70f37e774cec4e Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 17 Oct 2012 14:40:28 +0200 Subject: [PATCH 147/196] ruby: unregister onupdate callback on exception --- plugins/ruby/ruby.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 81b73b7ce..ab095e8d8 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -48,6 +48,7 @@ module DFHack end end rescue + df.onupdate_unregister self puts_err "onupdate cb #$!", $!.backtrace end From 5f4f540bfaf1dcff0c057b043c1c4e23cb7fd335 Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 17 Oct 2012 07:57:14 -0500 Subject: [PATCH 148/196] Fix "fastdwarf" command to print syntax where appropriate, silence warning --- plugins/fastdwarf.cpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 28104b909..6292fc85e 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -42,7 +42,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } - df::map_block *old_block, *new_block; for (size_t i = 0; i < world->units.active.size(); i++) { df::unit *unit = world->units.active[i]; @@ -108,12 +107,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) static command_result fastdwarf (color_ostream &out, vector & parameters) { - if (parameters.size() > 2) { - out.print("Incorrect usage.\n"); - return CR_FAILURE; - } + if (parameters.size() > 2) + return CR_WRONG_USAGE; - if (parameters.size() <= 2) + if ((parameters.size() == 1) || (parameters.size() == 2)) { if (parameters.size() == 2) { @@ -122,10 +119,7 @@ static command_result fastdwarf (color_ostream &out, vector & parameter else if (parameters[1] == "1") enable_teledwarf = true; else - { - out.print("Incorrect usage.\n"); - return CR_FAILURE; - } + return CR_WRONG_USAGE; } else enable_teledwarf = false; @@ -155,10 +149,7 @@ static command_result fastdwarf (color_ostream &out, vector & parameter } } else - { - out.print("Incorrect usage.\n"); - return CR_FAILURE; - } + return CR_WRONG_USAGE; } out.print("Current state: fast = %d, teleport = %d.\n", From 4f7895f5717f074dd4f3058aa86fbfe88835aa51 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 18:29:15 +0400 Subject: [PATCH 149/196] Support restricting the set of materials presented in the dialog. --- library/lua/gui/dialogs.lua | 1 + library/lua/gui/materials.lua | 103 +++++++++++++++++++++------------- 2 files changed, 66 insertions(+), 38 deletions(-) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index cb8d66176..079760604 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -173,6 +173,7 @@ function ListBox:init(info) view_id = 'list', selected = info.selected, choices = info.choices, + icon_width = info.icon_width, text_pen = spen, cursor_pen = cpen, on_submit = function(sel,obj) diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index e8f1e19ca..56b3eee5f 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -21,6 +21,11 @@ MaterialDialog.ATTRS{ frame_inset = 1, frame_title = 'Select Material', -- new attrs + none_caption = 'none', + use_inorganic = true, + use_creature = true, + use_plant = true, + mat_filter = DEFAULT_NIL, on_select = DEFAULT_NIL, on_cancel = DEFAULT_NIL, on_close = DEFAULT_NIL, @@ -73,20 +78,32 @@ end function MaterialDialog:initBuiltinMode() local choices = { - { text = 'none', mat_type = -1, mat_index = -1 }, - { icon = ARROW, text = 'inorganic', key = 'CUSTOM_SHIFT_I', - cb = self:callback('initInorganicMode') }, - { icon = ARROW, text = 'creature', key = 'CUSTOM_SHIFT_C', - cb = self:callback('initCreatureMode')}, - { icon = ARROW, text = 'plant', key = 'CUSTOM_SHIFT_P', - cb = self:callback('initPlantMode') }, + { text = self.none_caption, mat_type = -1, mat_index = -1 }, } - self:addMaterials( - choices, - df.global.world.raws.mat_table.builtin, 0, -1, - df.builtin_mats._last_item - ) + if self.use_inorganic then + table.insert(choices, { + icon = ARROW, text = 'inorganic', key = 'CUSTOM_SHIFT_I', + cb = self:callback('initInorganicMode') + }) + end + if self.use_creature then + table.insert(choices, { + icon = ARROW, text = 'creature', key = 'CUSTOM_SHIFT_C', + cb = self:callback('initCreatureMode') + }) + end + if self.use_plant then + table.insert(choices, { + icon = ARROW, text = 'plant', key = 'CUSTOM_SHIFT_P', + cb = self:callback('initPlantMode') + }) + end + + local table = df.global.world.raws.mat_table.builtin + for i=0,df.builtin_mats._last_item do + self:addMaterial(choices, table[i], i, -1, false, nil) + end self:pushContext('Any material', choices) end @@ -94,11 +111,9 @@ end function MaterialDialog:initInorganicMode() local choices = {} - self:addMaterials( - choices, - df.global.world.raws.inorganics, 0, nil, - nil, 'material' - ) + for i,mat in ipairs(df.global.world.raws.inorganics) do + self:addMaterial(choices, mat.material, 0, i, false, mat) + end self:pushContext('Inorganic materials', choices) end @@ -124,8 +139,26 @@ function MaterialDialog:initPlantMode() end function MaterialDialog:addObjectChoice(choices, obj, name, typ, index) - if #obj.material == 1 then - self:addMaterial(choices, obj.material[0], typ, index, true) + -- Check if any eligible children + local count = #obj.material + local idx = 0 + + if self.mat_filter then + count = 0 + for i,v in ipairs(obj.material) do + if self.mat_filter(v, obj, typ+i, index) then + count = count + 1 + if count > 1 then break end + idx = i + end + end + end + + -- Create an entry + if count < 1 then + return + elseif count == 1 then + self:addMaterial(choices, obj.material[idx], typ+idx, index, true, obj) else table.insert(choices, { icon = ARROW, text = name, mat_type = typ, mat_index = index, @@ -136,29 +169,19 @@ end function MaterialDialog:onSelectObj(item) local choices = {} - self:addMaterials(choices, item.obj.material, item.mat_type, item.mat_index) + for i,v in ipairs(item.obj.material) do + self:addMaterial(choices, v, item.mat_type+i, item.mat_index, false, item.obj) + end self:pushContext(item.text, choices) end -function MaterialDialog:addMaterials(choices, vector, tid, index, maxid, field) - for i=0,(maxid or #vector-1) do - local mat = vector[i] - if mat and field then - mat = mat[field] - end - if mat then - local typ, idx - if index then - typ, idx = tid+i, index - else - typ, idx = tid, i - end - self:addMaterial(choices, mat, typ, idx) - end +function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix, parent) + -- Check the filter + if self.mat_filter and not self.mat_filter(mat, parent, typ, idx) then + return end -end -function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix) + -- Find the material name local state = 0 if mat.heat.melting_point <= 10015 then state = 1 @@ -167,11 +190,14 @@ function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix) name = string.gsub(name, '^frozen ','') name = string.gsub(name, '^molten ','') name = string.gsub(name, '^condensed ','') + + -- Add prefix if requested local key if pfix and mat.prefix ~= '' then name = mat.prefix .. ' ' .. name key = mat.prefix end + table.insert(choices, { text = name, search_key = key, @@ -239,10 +265,11 @@ function MaterialDialog:onInput(keys) end end -function showMaterialPrompt(title, prompt, on_select, on_cancel) +function showMaterialPrompt(title, prompt, on_select, on_cancel, mat_filter) MaterialDialog{ frame_title = title, prompt = prompt, + mat_filter = mat_filter, on_select = on_select, on_cancel = on_cancel, }:show() From 2d4935bc17039c63d9ffc229578f3d397ffc8b80 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 19:16:18 +0400 Subject: [PATCH 150/196] Add lua API functions for verifying item and material vs job_item flags. --- Lua API.rst | 9 +++++++++ NEWS | 3 +++ library/LuaApi.cpp | 2 ++ library/include/modules/Job.h | 4 ++++ library/modules/Job.cpp | 26 ++++++++++++++++++++++++++ scripts/gui/guide-path.lua | 4 ++-- 6 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Lua API.rst b/Lua API.rst index bbee8646c..4d689ee2b 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -863,6 +863,15 @@ Job module if there are any jobs with ``first_id <= id < job_next_id``, a lua list containing them. +* ``dfhack.job.isSuitableItem(job_item, item_type, item_subtype)`` + + Does basic sanity checks to verify if the suggested item type matches + the flags in the job item. + +* ``dfhack.job.isSuitableMaterial(job_item, mat_type, mat_index)`` + + Likewise, if replacing material. + Units module ------------ diff --git a/NEWS b/NEWS index 40e9315c5..61e8a5b3e 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ DFHack future Internals: - support for displaying active keybindings properly. + - support for reusable widgets in lua screen library. Notable bugfixes: - autobutcher can be re-enabled again after being stopped. - stopped Dwarf Manipulator from unmasking vampires. @@ -9,6 +10,8 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option + New GUI scripts: + - gui/guide-path: displays the cached path for minecart Guide orders. DFHack v0.34.11-r2 diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index ef571bcb7..9f380c353 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -809,6 +809,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { WRAPM(Job,getWorker), WRAPM(Job,checkBuildingsNow), WRAPM(Job,checkDesignationsNow), + WRAPM(Job,isSuitableItem), + WRAPM(Job,isSuitableMaterial), WRAPN(is_equal, jobEqual), WRAPN(is_item_equal, jobItemEqual), { NULL, NULL } diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 490d79a34..853813073 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -32,6 +32,7 @@ distribution. #include "DataDefs.h" #include "df/job_item_ref.h" +#include "df/item_type.h" namespace df { @@ -69,6 +70,9 @@ namespace DFHack DFHACK_EXPORT bool attachJobItem(df::job *job, df::item *item, df::job_item_ref::T_role role, int filter_idx = -1, int insert_idx = -1); + + DFHACK_EXPORT bool isSuitableItem(df::job_item *item, df::item_type itype, int isubtype); + DFHACK_EXPORT bool isSuitableMaterial(df::job_item *item, int mat_type, int mat_index); } DFHACK_EXPORT bool operator== (const df::job_item &a, const df::job_item &b); diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index df2009d0a..def3b4192 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -361,3 +361,29 @@ bool DFHack::Job::attachJobItem(df::job *job, df::item *item, return true; } + +bool Job::isSuitableItem(df::job_item *item, df::item_type itype, int isubtype) +{ + CHECK_NULL_POINTER(item); + + if (itype == item_type::NONE) + return true; + + ItemTypeInfo iinfo(itype, isubtype); + MaterialInfo minfo(item); + + return iinfo.isValid() && iinfo.matches(*item, &minfo); +} + +bool Job::isSuitableMaterial(df::job_item *item, int mat_type, int mat_index) +{ + CHECK_NULL_POINTER(item); + + if (mat_type == -1 && mat_index == -1) + return true; + + ItemTypeInfo iinfo(item); + MaterialInfo minfo(mat_type, mat_index); + + return minfo.isValid() && iinfo.matches(*item, &minfo); +} diff --git a/scripts/gui/guide-path.lua b/scripts/gui/guide-path.lua index 1546150b7..a807e032d 100644 --- a/scripts/gui/guide-path.lua +++ b/scripts/gui/guide-path.lua @@ -143,8 +143,8 @@ function GuidePathUI:onRenderBody(dc) end dc:newline():newline(1) - dc:key('CUSTOM_Z'):string(": Reset path, ",COLOR_GREY,nil,path_ok~=nil) - dc:key('CUSTOM_P'):string(": Find path",COLOR_GREY,nil,false) + dc:key('CUSTOM_Z'):string(": Reset path",COLOR_GREY,nil,path_ok~=nil) + --dc:key('CUSTOM_P'):string(": Find path",COLOR_GREY,nil,false) dc:newline(1) dc:key('CUSTOM_C'):string(": Zoom cur, ") dc:key('CUSTOM_N'):string(": Zoom next") From 2bbd00a8ecffa92eb20f50aa8f0f78dfe702ef36 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 20:58:37 +0400 Subject: [PATCH 151/196] Add pairs and ipairs support for objects in df tree. --- library/LuaWrapper.cpp | 132 ++++++++++++++++++++++++++++++++++------- 1 file changed, 109 insertions(+), 23 deletions(-) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 3da79e932..75e3b697c 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -1265,6 +1265,51 @@ static void BuildTypeMetatable(lua_State *state, type_identity *type) * Recursive walk of scopes to construct the df... tree. */ +static int wtype_pnext(lua_State *L) +{ + lua_settop(L, 2); /* create a 2nd argument if there isn't one */ + if (lua_next(L, lua_upvalueindex(1))) + return 2; + lua_pushnil(L); + return 1; +} + +static int wtype_pairs(lua_State *state) +{ + lua_pushvalue(state, lua_upvalueindex(1)); + lua_pushcclosure(state, wtype_pnext, 1); + lua_pushnil(state); + lua_pushnil(state); + return 3; +} + +static int wtype_inext(lua_State *L) +{ + int i = luaL_checkint(L, 2); + i++; /* next value */ + if (i <= lua_tointeger(L, lua_upvalueindex(2))) + { + lua_pushinteger(L, i); + lua_rawgeti(L, lua_upvalueindex(1), i); + return 2; + } + else + { + lua_pushnil(L); + return 1; + } +} + +static int wtype_ipairs(lua_State *state) +{ + lua_pushvalue(state, lua_upvalueindex(1)); + lua_pushvalue(state, lua_upvalueindex(3)); + lua_pushcclosure(state, wtype_inext, 2); + lua_pushnil(state); + lua_pushvalue(state, lua_upvalueindex(2)); + return 3; +} + static void RenderTypeChildren(lua_State *state, const std::vector &children); void LuaWrapper::AssociateId(lua_State *state, int table, int val, const char *name) @@ -1278,7 +1323,7 @@ void LuaWrapper::AssociateId(lua_State *state, int table, int val, const char *n lua_rawset(state, table); } -static void FillEnumKeys(lua_State *state, int ftable, enum_identity *eid) +static void FillEnumKeys(lua_State *state, int ix_meta, int ftable, enum_identity *eid) { const char *const *keys = eid->getKeys(); @@ -1296,11 +1341,17 @@ static void FillEnumKeys(lua_State *state, int ftable, enum_identity *eid) if (eid->getFirstItem() <= eid->getLastItem()) { + lua_pushvalue(state, base+1); + lua_pushinteger(state, eid->getFirstItem()-1); + lua_pushinteger(state, eid->getLastItem()); + lua_pushcclosure(state, wtype_ipairs, 3); + lua_setfield(state, ix_meta, "__ipairs"); + lua_pushinteger(state, eid->getFirstItem()); - lua_setfield(state, base+1, "_first_item"); + lua_setfield(state, ftable, "_first_item"); lua_pushinteger(state, eid->getLastItem()); - lua_setfield(state, base+1, "_last_item"); + lua_setfield(state, ftable, "_last_item"); } SaveInTable(state, eid, &DFHACK_ENUM_TABLE_TOKEN); @@ -1321,7 +1372,7 @@ static void FillEnumKeys(lua_State *state, int ftable, enum_identity *eid) lua_setmetatable(state, ftable); } -static void FillBitfieldKeys(lua_State *state, int ftable, bitfield_identity *eid) +static void FillBitfieldKeys(lua_State *state, int ix_meta, int ftable, bitfield_identity *eid) { // Create a new table attached to ftable as __index lua_newtable(state); @@ -1338,11 +1389,17 @@ static void FillBitfieldKeys(lua_State *state, int ftable, bitfield_identity *ei i += bits[i].size-1; } + lua_pushvalue(state, base+1); + lua_pushinteger(state, -1); + lua_pushinteger(state, eid->getNumBits()-1); + lua_pushcclosure(state, wtype_ipairs, 3); + lua_setfield(state, ix_meta, "__ipairs"); + lua_pushinteger(state, 0); - lua_setfield(state, base+1, "_first_item"); + lua_setfield(state, ftable, "_first_item"); lua_pushinteger(state, eid->getNumBits()-1); - lua_setfield(state, base+1, "_last_item"); + lua_setfield(state, ftable, "_last_item"); SaveInTable(state, eid, &DFHACK_ENUM_TABLE_TOKEN); @@ -1355,7 +1412,12 @@ static void RenderType(lua_State *state, compound_identity *node) assert(node->getName()); std::string name = node->getFullName(); - int base = lua_gettop(state); + // Frame: + // base+1 - outer table + // base+2 - metatable of outer table + // base+3 - inner table + // base+4 - pairs table + Lua::StackUnwinder base(state); lua_newtable(state); if (!lua_checkstack(state, 20)) @@ -1365,51 +1427,59 @@ static void RenderType(lua_State *state, compound_identity *node) // metatable lua_newtable(state); + int ix_meta = base+2; lua_dup(state); lua_setmetatable(state, base+1); lua_pushstring(state, name.c_str()); - lua_setfield(state, base+2, "__metatable"); + lua_setfield(state, ix_meta, "__metatable"); lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPE_TOSTRING_NAME); - lua_setfield(state, base+2, "__tostring"); + lua_setfield(state, ix_meta, "__tostring"); lua_pushlightuserdata(state, node); - lua_rawsetp(state, base+2, &DFHACK_IDENTITY_FIELD_TOKEN); + lua_rawsetp(state, ix_meta, &DFHACK_IDENTITY_FIELD_TOKEN); // inner table lua_newtable(state); + int ftable = base+3; lua_dup(state); - lua_setfield(state, base+2, "__index"); + lua_setfield(state, ix_meta, "__index"); - int ftable = base+3; + // pairs table + lua_newtable(state); + int ptable = base+4; + + lua_pushvalue(state, ptable); + lua_pushcclosure(state, wtype_pairs, 1); + lua_setfield(state, ix_meta, "__pairs"); switch (node->type()) { case IDTYPE_STRUCT: lua_pushstring(state, "struct-type"); lua_setfield(state, ftable, "_kind"); - IndexStatics(state, base+2, base+3, (struct_identity*)node); + IndexStatics(state, ix_meta, ftable, (struct_identity*)node); break; case IDTYPE_CLASS: lua_pushstring(state, "class-type"); lua_setfield(state, ftable, "_kind"); - IndexStatics(state, base+2, base+3, (struct_identity*)node); + IndexStatics(state, ix_meta, ftable, (struct_identity*)node); break; case IDTYPE_ENUM: lua_pushstring(state, "enum-type"); lua_setfield(state, ftable, "_kind"); - FillEnumKeys(state, ftable, (enum_identity*)node); + FillEnumKeys(state, ix_meta, ftable, (enum_identity*)node); break; case IDTYPE_BITFIELD: lua_pushstring(state, "bitfield-type"); lua_setfield(state, ftable, "_kind"); - FillBitfieldKeys(state, ftable, (bitfield_identity*)node); + FillBitfieldKeys(state, ix_meta, ftable, (bitfield_identity*)node); break; case IDTYPE_GLOBAL: @@ -1425,14 +1495,14 @@ static void RenderType(lua_State *state, compound_identity *node) BuildTypeMetatable(state, node); lua_dup(state); - lua_setmetatable(state, base+3); + lua_setmetatable(state, ftable); lua_getfield(state, -1, "__newindex"); - lua_setfield(state, base+2, "__newindex"); + lua_setfield(state, ix_meta, "__newindex"); lua_getfield(state, -1, "__pairs"); - lua_setfield(state, base+2, "__pairs"); + lua_setfield(state, ix_meta, "__pairs"); - lua_pop(state, 3); + base += 1; return; } @@ -1454,17 +1524,25 @@ static void RenderType(lua_State *state, compound_identity *node) lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_IS_INSTANCE_NAME); lua_setfield(state, ftable, "is_instance"); - lua_pop(state, 2); + base += 1; } static void RenderTypeChildren(lua_State *state, const std::vector &children) { + // fieldtable pairstable | + int base = lua_gettop(state); + for (size_t i = 0; i < children.size(); i++) { RenderType(state, children[i]); lua_pushstring(state, children[i]->getName()); lua_swap(state); - lua_rawset(state, -3); + + // save in both tables + lua_pushvalue(state, -2); + lua_pushvalue(state, -2); + lua_rawset(state, base); + lua_rawset(state, base-1); } } @@ -1524,10 +1602,13 @@ static int DoAttach(lua_State *state) { // Assign df a metatable with read-only contents lua_newtable(state); + lua_newtable(state); // Render the type structure RenderTypeChildren(state, compound_identity::getTopScope()); + lua_swap(state); // -> pairstable fieldtable + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); lua_setfield(state, -2, "sizeof"); lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_NEW_NAME); @@ -1558,7 +1639,12 @@ static int DoAttach(lua_State *state) lua_pushcfunction(state, meta_isnull); lua_setfield(state, -2, "isnull"); - freeze_table(state, false, "df"); + freeze_table(state, true, "df"); + + lua_swap(state); + lua_pushcclosure(state, wtype_pairs, 1); + lua_setfield(state, -2, "__pairs"); + lua_pop(state, 1); } return 1; From 1e2570f4cbbf8fe3ab0c3912418482e56f73a6cf Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 21:42:06 +0400 Subject: [PATCH 152/196] Add an extremely simple item selection dialog by wrapping ListBox. --- library/lua/gui/dialogs.lua | 2 +- library/lua/gui/materials.lua | 66 +++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 079760604..5811e94e6 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -191,7 +191,7 @@ function ListBox:getWantedFrameSize() local mw, mh = InputBox.super.getWantedFrameSize(self) local list = self.subviews.list list.frame.t = mh+1 - return math.max(mw, list:getContentWidth()), mh+1+list:getContentHeight() + return math.max(mw, list:getContentWidth()), mh+1+math.min(20,list:getContentHeight()) end function ListBox:onInput(keys) diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index 56b3eee5f..baf641976 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -4,6 +4,7 @@ local _ENV = mkmodule('gui.materials') local gui = require('gui') local widgets = require('gui.widgets') +local dlg = require('gui.dialogs') local utils = require('utils') ARROW = string.char(26) @@ -275,4 +276,69 @@ function showMaterialPrompt(title, prompt, on_select, on_cancel, mat_filter) }:show() end +local itemdefs = df.global.world.raws.itemdefs +local itemtype_info = { + TRAPPARTS = { name = 'mechanisms' }, + WEAPON = { defs = itemdefs.weapons }, + TRAPCOMP = { defs = itemdefs.trapcomps }, + TOY = { defs = itemdefs.toys }, + TOOL = { defs = itemdefs.tools }, + INSTRUMENT = { defs = itemdefs.instruments }, + ARMOR = { defs = itemdefs.armor }, + AMMO = { defs = itemdefs.ammo }, + SIEGEAMMO = { defs = itemdefs.siege_ammo }, + GLOVES = { defs = itemdefs.gloves }, + SHOES = { defs = itemdefs.shoes }, + SHIELD = { defs = itemdefs.shields }, + HELM = { defs = itemdefs.helms }, + PANTS = { defs = itemdefs.pants }, + FOOD = { defs = itemdefs.food }, +} + +function ItemTypeDialog(args) + args.text = args.prompt or 'Type or select an item type' + args.text_pen = COLOR_WHITE + args.with_filter = true + args.icon_width = 2 + + local choices = { { + icon = '?', text = args.none_caption or 'none', item_type = -1, item_subtype = -1 + } } + local filter = args.item_filter + + for itype = 0,df.item_type._last_item do + local key = df.item_type[itype] + local info = itemtype_info[key] + + if not filter or filter(itype,-1) then + local name = key + local icon + if info and info.defs then + name = 'any '..name + icon = '+' + end + if info and info.name then + name = info.name + end + table.insert(choices, { + icon = icon, text = string.lower(name), item_type = itype, item_subtype = -1 + }) + end + + if info and info.defs then + for subtype,def in ipairs(info.defs) do + if not filter or filter(itype,subtype,def) then + table.insert(choices, { + icon = '\x1e', text = ' '..def.name, item_type = itype, item_subtype = subtype + }) + end + end + end + end + + args.choices = choices + + return dlg.ListBox(args) +end + return _ENV From 2b1d856214e30ff832e664141070a77b9a736c4c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 20 Oct 2012 17:06:33 +0400 Subject: [PATCH 153/196] Add a few utility functions to the lua api. --- Compile.html | 104 ++++++++++++++++++++++++-------- Compile.rst | 49 +++++++++------ Lua API.html | 33 +++++++++- Lua API.rst | 22 ++++++- Readme.html | 17 +++--- library/LuaApi.cpp | 25 ++++++++ library/include/modules/Items.h | 4 ++ library/modules/Items.cpp | 51 ++++++++++++---- library/xml | 2 +- 9 files changed, 244 insertions(+), 63 deletions(-) diff --git a/Compile.html b/Compile.html index 41d409112..e5f6ca679 100644 --- a/Compile.html +++ b/Compile.html @@ -334,20 +334,21 @@ ul.auto-toc {
  • Build
  • -
  • Windows
  • +
    +

    Mac OS X

    +
      +
    1. Download and unpack a copy of the latest DF

      +
    2. +
    3. Install Xcode from Mac App Store

      +
    4. +
    5. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools.

      +
    6. +
    7. Install MacPorts.

      +
    8. +
    9. Install dependencies from MacPorts:

      +
        +
      • sudo port install gcc45 +universal cmake +universal git-core +universal

        +

        This will take some time—maybe hours, depending on your machine.

        +
      • +
      • At some point during this process, it may ask you to install a Java environment; let it do so.

        +
      • +
      +
    10. +
    11. Install perl dependencies

      +
      +
        +
      1. sudo cpan

        +

        If this is the first time you've run cpan, you will need to go through the setup +process. Just stick with the defaults for everything and you'll be fine.

        +
      2. +
      3. install XML::LibXML

        +
      4. +
      5. install XML::LibXSLT

        +
      6. +
      +
      +
    12. +
    13. Get the dfhack source:

      +
      +git clone https://github.com/danaris/dfhack.git
      +cd dfhack
      +git submodule init
      +git submodule update
      +
      +
    14. +
    15. Build dfhack:

      +
      +mkdir build-osx
      +cd build-osx
      +export CC=/opt/local/bin/gcc-mp-4.5
      +export CXX=/opt/local/bin/g++-mp-4.5
      +cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/path/to/DF/directory
      +make
      +make install
      +
      +
    16. +
    +
    -

    Windows

    +

    Windows

    On Windows, DFHack replaces the SDL library distributed with DF.

    -

    How to get the code

    +

    How to get the code

    DFHack doesn't have any kind of system of code snapshots in place, so you will have to get code from the github repository using git. You will need some sort of Windows port of git, or a GUI. Some examples:

    @@ -428,7 +484,7 @@ git submodule update

    If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode).

    -

    Dependencies

    +

    Dependencies

    First, you need cmake. Get the win32 installer version from the official site: http://www.cmake.org/cmake/resources/software.html

    It has the usual installer wizard. Make sure you let it add its binary folder @@ -445,7 +501,7 @@ Grab it from Microsoft's site.

    If you already have a different version of perl (for example the one from cygwin), you can run into some trouble. Either remove the other perl install from PATH, or install libxml and libxslt for it instead. Strawberry perl works though and has all the required packages.

    -

    Build

    +

    Build

    There are several different batch files in the build folder along with a script that's used for picking the DF path.

    First, run set_df_path.vbs and point the dialog that pops up at your DF folder that you want to use for development. Next, run one of the scripts with generate prefix. These create the MSVC solution file(s):

    @@ -467,7 +523,7 @@ So pick either Release or RelWithDebInfo build and build the INSTALL target.

    -

    Build types

    +

    Build types

    cmake allows you to pick a build type by changing this variable: CMAKE_BUILD_TYPE

    @@ -479,7 +535,7 @@ cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE
     'RelWithDebInfo'. 'Debug' is not available on Windows.

    -

    Using the library as a developer

    +

    Using the library as a developer

    Currently, the most direct way to use the library is to write a plugin that can be loaded by it. All the plugins can be found in the 'plugins' folder. There's no in-depth documentation on how to write one yet, but it should be easy enough to copy one and just follow the pattern.

    @@ -497,29 +553,29 @@ The main license is zlib/libpng, some bits are MIT licensed, and some are BSD li

    Feel free to add your own extensions and plugins. Contributing back to the dfhack repository is welcome and the right thing to do :)

    -

    DF data structure definitions

    +

    DF data structure definitions

    DFHack uses information about the game data structures, represented via xml files in the library/xml/ submodule.

    Data structure layouts are described in files following the df.*.xml name pattern. This information is transformed by a perl script into C++ headers describing the structures, and associated metadata for the Lua wrapper. These headers and data are then compiled into the DFHack libraries, thus necessitating a compatibility break every time layouts change; in return it significantly boosts the efficiency and capabilities of DFHack code.

    Global object addresses are stored in symbols.xml, which is copied to the dfhack release package and loaded as data at runtime.

    -

    Remote access interface

    +

    Remote access interface

    DFHack supports remote access by exchanging Google protobuf messages via a TCP socket. Both the core and plugins can define remotely accessible methods. The dfhack-run command uses this interface to invoke ordinary console commands.

    Currently the supported set of requests is limited, because the developers don't know what exactly is most useful.

    Protocol client implementations exist for Java and C#.

    -

    Contributing to DFHack

    +

    Contributing to DFHack

    Several things should be kept in mind when contributing to DFHack.

    -

    Coding style

    +

    Coding style

    DFhack uses ANSI formatting and four spaces as indentation. Line endings are UNIX. The files use UTF-8 encoding. Code not following this won't make me happy, because I'll have to fix it. There's a good chance I'll make you fix it ;)

    -

    How to get new code into DFHack

    +

    How to get new code into DFHack

    You can send patches or make a clone of the github repo and ask me on the IRC channel to pull your code in. I'll review it and see if there are any problems. I'll fix them if they are minor.

    @@ -529,7 +585,7 @@ this is also a good place to dump new ideas and/or bugs that need fixing.

    -

    Memory research

    +

    Memory research

    If you want to do memory research, you'll need some tools and some knowledge. In general, you'll need a good memory viewer and optionally something to look at machine code without getting crazy :)

    diff --git a/Compile.rst b/Compile.rst index a71642060..3340fc97e 100644 --- a/Compile.rst +++ b/Compile.rst @@ -72,24 +72,39 @@ Mac OS X 3. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools. 4. Install MacPorts. 5. Install dependencies from MacPorts: - * sudo port install gcc45 +universal cmake +universal git-core +universal (This will take some time—maybe hours, depending on your machine.) - * At some point during this process, it may ask you to install a Java environment; let it do so. + + * ``sudo port install gcc45 +universal cmake +universal git-core +universal`` + + This will take some time—maybe hours, depending on your machine. + + * At some point during this process, it may ask you to install a Java environment; let it do so. + 6. Install perl dependencies - 1. sudo cpan (If this is the first time you've run cpan, you will need to go through the setup process. Just stick with the defaults for everything and you'll be fine.) - 2. install XML::LibXML - 3. install XML::LibXSLT -7. Get the dfhack source - 1. git clone https://github.com/danaris/dfhack.git - 2. cd dfhack - 3. git submodule init - 4. git submodule update -8. mkdir build-osx -9. cd build-osx -10. export CC=/opt/local/bin/gcc-mp-4.5 -11. export CXX=/opt/local/bin/g++-mp-4.5 -12. cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/path/to/DF/directory -13. make -14. make install + + 1. ``sudo cpan`` + + If this is the first time you've run cpan, you will need to go through the setup + process. Just stick with the defaults for everything and you'll be fine. + + 2. ``install XML::LibXML`` + 3. ``install XML::LibXSLT`` + +7. Get the dfhack source:: + + git clone https://github.com/danaris/dfhack.git + cd dfhack + git submodule init + git submodule update + +8. Build dfhack:: + + mkdir build-osx + cd build-osx + export CC=/opt/local/bin/gcc-mp-4.5 + export CXX=/opt/local/bin/g++-mp-4.5 + cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/path/to/DF/directory + make + make install ======= Windows diff --git a/Lua API.html b/Lua API.html index 125687110..9c18b6585 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1110,6 +1110,13 @@ above operations accordingly. If enabled, pauses and zooms to position.

    if there are any jobs with first_id <= id < job_next_id, a lua list containing them.

    +
  • dfhack.job.isSuitableItem(job_item, item_type, item_subtype)

    +

    Does basic sanity checks to verify if the suggested item type matches +the flags in the job item.

    +
  • +
  • dfhack.job.isSuitableMaterial(job_item, mat_type, mat_index)

    +

    Likewise, if replacing material.

    +
  • @@ -1250,6 +1257,15 @@ Returns false in case of error.

  • dfhack.items.makeProjectile(item)

    Turns the item into a projectile, and returns the new object, or nil if impossible.

  • +
  • dfhack.items.isCasteMaterial(item_type)

    +

    Returns true if this item type uses a creature/caste pair as its material.

    +
  • +
  • dfhack.items.getSubtypeCount(item_type)

    +

    Returns the number of raw-defined subtypes of the given item type, or -1 if not applicable.

    +
  • +
  • dfhack.items.getSubtypeDef(item_type, subtype)

    +

    Returns the raw definition for the given item type and subtype, or nil if invalid.

    +
  • @@ -1264,7 +1280,7 @@ Returns false in case of error.

  • dfhack.maps.getBlock(x,y,z)

    Returns a map block object for given x,y,z in local block coordinates.

  • -
  • dfhack.maps.isValidTilePos(coords), or isValidTilePos(x,y,z)``

    +
  • dfhack.maps.isValidTilePos(coords), or isValidTilePos(x,y,z)

    Checks if the given df::coord or x,y,z in local tile coordinates are valid.

  • dfhack.maps.getTileBlock(coords), or getTileBlock(x,y,z)

    @@ -1273,6 +1289,12 @@ Returns false in case of error.

  • dfhack.maps.ensureTileBlock(coords), or ensureTileBlock(x,y,z)

    Like getTileBlock, but if the block is not allocated, try creating it.

  • +
  • dfhack.maps.getTileType(coords), or getTileType(x,y,z)

    +

    Returns the tile type at the given coordinates, or nil if invalid.

    +
  • +
  • dfhack.maps.getTileFlags(coords), or getTileFlags(x,y,z)

    +

    Returns designation and occupancy references for the given coordinates, or nil, nil if invalid.

    +
  • dfhack.maps.getRegionBiome(region_coord2d), or getRegionBiome(x,y)

    Returns the biome info struct for the given global map region.

  • @@ -1838,6 +1860,15 @@ coordinates), returns nil.

  • xyz2pos(x,y,z)

    Returns a table with x, y and z as fields.

  • +
  • same_xyz(a,b)

    +

    Checks if a and b have the same x, y and z fields.

    +
  • +
  • get_path_xyz(path,i)

    +

    Returns path.x[i], path.y[i], path.z[i].

    +
  • +
  • pos2xy(obj), xy2pos(x,y), same_xy(a,b), get_path_xy(a,b)

    +

    Same as above, but for 2D coordinates.

    +
  • safe_index(obj,index...)

    Walks a sequence of dereferences, which may be represented by numbers or strings. Returns nil if any of obj or indices is nil, or a numeric index is out of array bounds.

    diff --git a/Lua API.rst b/Lua API.rst index 4d689ee2b..185672816 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1043,6 +1043,18 @@ Items module Turns the item into a projectile, and returns the new object, or *nil* if impossible. +* ``dfhack.items.isCasteMaterial(item_type)`` + + Returns *true* if this item type uses a creature/caste pair as its material. + +* ``dfhack.items.getSubtypeCount(item_type)`` + + Returns the number of raw-defined subtypes of the given item type, or *-1* if not applicable. + +* ``dfhack.items.getSubtypeDef(item_type, subtype)`` + + Returns the raw definition for the given item type and subtype, or *nil* if invalid. + Maps module ----------- @@ -1059,7 +1071,7 @@ Maps module Returns a map block object for given x,y,z in local block coordinates. -* ``dfhack.maps.isValidTilePos(coords)``, or isValidTilePos(x,y,z)`` +* ``dfhack.maps.isValidTilePos(coords)``, or ``isValidTilePos(x,y,z)`` Checks if the given df::coord or x,y,z in local tile coordinates are valid. @@ -1071,6 +1083,14 @@ Maps module Like ``getTileBlock``, but if the block is not allocated, try creating it. +* ``dfhack.maps.getTileType(coords)``, or ``getTileType(x,y,z)`` + + Returns the tile type at the given coordinates, or *nil* if invalid. + +* ``dfhack.maps.getTileFlags(coords)``, or ``getTileFlags(x,y,z)`` + + Returns designation and occupancy references for the given coordinates, or *nil, nil* if invalid. + * ``dfhack.maps.getRegionBiome(region_coord2d)``, or ``getRegionBiome(x,y)`` Returns the biome info struct for the given global map region. diff --git a/Readme.html b/Readme.html index c1d2d0d3c..6df1afcad 100644 --- a/Readme.html +++ b/Readme.html @@ -2494,20 +2494,21 @@ growcrops plump 40

    removebadthoughts

    This script remove negative thoughts from your dwarves. Very useful against tantrum spirals.

    -

    With a selected unit in 'v' mode, will clear this unit's mind, otherwise -clear all your fort's units minds.

    +

    The script can target a single creature, when used with the him argument, +or the whole fort population, with all.

    +

    To show every bad thought present without actually removing them, run the +script with the -n or --dry-run argument. This can give a quick +hint on what bothers your dwarves the most.

    Individual dwarf happiness may not increase right after this command is run, -but in the short term your dwarves will get much more joyful. -The thoughts are set to be very old, and the game will remove them soon when -you unpause.

    -

    With the optional -v parameter, the script will dump the negative thoughts -it removed.

    +but in the short term your dwarves will get much more joyful.

    +

    Internals: the thoughts are set to be very old, so that the game remove them +quickly after you unpause.

  • slayrace

    Kills any unit of a given race.

    With no argument, lists the available races.

    -

    With the special argument 'him', targets only the selected creature.

    +

    With the special argument him, targets only the selected creature.

    Any non-dead non-caged unit of the specified race gets its blood_count set to 0, which means immediate death at the next game tick. For creatures such as vampires, also set animal.vanish_countdown to 2.

    diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 9f380c353..aa6c93c32 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -82,6 +82,7 @@ distribution. #include "df/flow_info.h" #include "df/unit_misc_trait.h" #include "df/proj_itemst.h" +#include "df/itemdef.h" #include #include @@ -937,6 +938,9 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, setOwner), WRAPM(Items, getContainer), WRAPM(Items, getDescription), + WRAPM(Items, isCasteMaterial), + WRAPM(Items, getSubtypeCount), + WRAPM(Items, getSubtypeDef), WRAPN(moveToGround, items_moveToGround), WRAPN(moveToContainer, items_moveToContainer), WRAPN(moveToBuilding, items_moveToBuilding), @@ -1018,6 +1022,25 @@ static int maps_ensureTileBlock(lua_State *L) return 1; } +static int maps_getTileType(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + auto ptype = Maps::getTileType(pos); + if (ptype) + lua_pushinteger(L, *ptype); + else + lua_pushnil(L); + return 1; +} + +static int maps_getTileFlags(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + Lua::PushDFObject(L, Maps::getTileDesignation(pos)); + Lua::PushDFObject(L, Maps::getTileOccupancy(pos)); + return 2; +} + static int maps_getRegionBiome(lua_State *L) { auto pos = CheckCoordXY(L, 1, true); @@ -1035,6 +1058,8 @@ static const luaL_Reg dfhack_maps_funcs[] = { { "isValidTilePos", maps_isValidTilePos }, { "getTileBlock", maps_getTileBlock }, { "ensureTileBlock", maps_ensureTileBlock }, + { "getTileType", maps_getTileType }, + { "getTileFlags", maps_getTileFlags }, { "getRegionBiome", maps_getRegionBiome }, { "getTileBiomeRgn", maps_getTileBiomeRgn }, { NULL, NULL } diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 367ce9b41..92329980a 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -123,6 +123,10 @@ struct dfh_item namespace Items { +DFHACK_EXPORT bool isCasteMaterial(df::item_type itype); +DFHACK_EXPORT int getSubtypeCount(df::item_type itype); +DFHACK_EXPORT df::itemdef *getSubtypeDef(df::item_type itype, int subtype); + /// Look for a particular item by ID DFHACK_EXPORT df::item * findItemByID(int32_t id); diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 1118afcfe..4e75c8a6b 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -109,30 +109,47 @@ using df::global::proj_next_id; ITEM(PANTS, pants, itemdef_pantsst) \ ITEM(FOOD, food, itemdef_foodst) -bool ItemTypeInfo::decode(df::item_type type_, int16_t subtype_) +int Items::getSubtypeCount(df::item_type itype) { using namespace df::enums::item_type; - type = type_; - subtype = subtype_; - custom = NULL; - df::world_raws::T_itemdefs &defs = df::global::world->raws.itemdefs; - switch (type_) { - case NONE: - return false; + switch (itype) { +#define ITEM(type,vec,tclass) \ + case type: \ + return defs.vec.size(); +ITEMDEF_VECTORS +#undef ITEM + + default: + return -1; + } +} + +df::itemdef *Items::getSubtypeDef(df::item_type itype, int subtype) +{ + using namespace df::enums::item_type; + df::world_raws::T_itemdefs &defs = df::global::world->raws.itemdefs; + + switch (itype) { #define ITEM(type,vec,tclass) \ case type: \ - custom = vector_get(defs.vec, subtype); \ - break; + return vector_get(defs.vec, subtype); ITEMDEF_VECTORS #undef ITEM default: - break; + return NULL; } +} + +bool ItemTypeInfo::decode(df::item_type type_, int16_t subtype_) +{ + type = type_; + subtype = subtype_; + custom = Items::getSubtypeDef(type_, subtype_); return isValid(); } @@ -171,6 +188,10 @@ ITEMDEF_VECTORS break; } + const char *name = ENUM_ATTR(item_type, caption, type); + if (name) + return name; + return toLower(ENUM_KEY_STR(item_type, type)); } @@ -219,6 +240,11 @@ ITEMDEF_VECTORS return (subtype >= 0); } +bool Items::isCasteMaterial(df::item_type itype) +{ + return ENUM_ATTR(item_type, is_caste_mat, itype); +} + bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) { using namespace df::enums::item_type; @@ -226,6 +252,9 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) if (!isValid()) return mat ? mat->matches(item) : false; + if (Items::isCasteMaterial(type) && mat && !mat->isNone()) + return false; + df::job_item_flags1 ok1, mask1, item_ok1, item_mask1; df::job_item_flags2 ok2, mask2, item_ok2, item_mask2; df::job_item_flags3 ok3, mask3, item_ok3, item_mask3; diff --git a/library/xml b/library/xml index aec106cdc..0ed2d1dc5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit aec106cdc32083bbcc6d6dd27d9e069f5525ea92 +Subproject commit 0ed2d1dc5547737fbb838568fc66671098f2c11a From 5388ad475f85b162f4570bf1fbf410b620e5b553 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 20 Oct 2012 20:14:50 +0400 Subject: [PATCH 154/196] Fix a bug in lua wrapper caused by the recent pairs() addition. It inadvertently removed all functions like df.new from the df table. --- library/LuaWrapper.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 75e3b697c..1ce405c29 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -1641,10 +1641,13 @@ static int DoAttach(lua_State *state) freeze_table(state, true, "df"); - lua_swap(state); + // pairstable dftable dfmeta + + lua_pushvalue(state, -3); lua_pushcclosure(state, wtype_pairs, 1); lua_setfield(state, -2, "__pairs"); lua_pop(state, 1); + lua_remove(state, -2); } return 1; From 795961bfc4c1148a13483d739bda8fdf6923fb41 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 20 Oct 2012 20:31:45 +0400 Subject: [PATCH 155/196] Fix the treatment of non_economic in Materials/Items modules. non_economic == !(is boulder && is inorganic && is economic) --- library/modules/Items.cpp | 14 ++++++++++---- library/modules/Materials.cpp | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 4e75c8a6b..7af8c549b 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -256,11 +256,11 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) return false; df::job_item_flags1 ok1, mask1, item_ok1, item_mask1; - df::job_item_flags2 ok2, mask2, item_ok2, item_mask2; + df::job_item_flags2 ok2, mask2, item_ok2, item_mask2, xmask2; df::job_item_flags3 ok3, mask3, item_ok3, item_mask3; ok1.whole = mask1.whole = item_ok1.whole = item_mask1.whole = 0; - ok2.whole = mask2.whole = item_ok2.whole = item_mask2.whole = 0; + ok2.whole = mask2.whole = item_ok2.whole = item_mask2.whole = xmask2.whole = 0; ok3.whole = mask3.whole = item_ok3.whole = item_mask3.whole = 0; if (mat) { @@ -281,12 +281,16 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) RQ(1,not_bin); RQ(1,lye_bearing); RQ(2,dye); RQ(2,dyeable); RQ(2,dyed); RQ(2,glass_making); RQ(2,screw); - RQ(2,building_material); RQ(2,fire_safe); RQ(2,magma_safe); RQ(2,non_economic); + RQ(2,building_material); RQ(2,fire_safe); RQ(2,magma_safe); RQ(2,totemable); RQ(2,plaster_containing); RQ(2,body_part); RQ(2,lye_milk_free); RQ(2,blunt); RQ(2,unengraved); RQ(2,hair_wool); RQ(3,any_raw_material); RQ(3,non_pressed); RQ(3,food_storage); + // only checked if boulder + + xmask2.bits.non_economic = true; + // Compute the ok mask OK(1,solid); @@ -306,7 +310,7 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) case BOULDER: OK(1,sharpenable); - OK(2,non_economic); + xmask2.bits.non_economic = false; case BAR: OK(3,any_raw_material); case BLOCKS: @@ -432,6 +436,8 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) #undef OK #undef RQ + mask2.whole &= ~xmask2.whole; + return bits_match(item.flags1.whole, ok1.whole, mask1.whole) && bits_match(item.flags2.whole, ok2.whole, mask2.whole) && bits_match(item.flags3.whole, ok3.whole, mask3.whole) && diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index fbc441493..a94d49181 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -497,7 +497,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &ma TEST(fire_safe, material->heat.melting_point > 11000); TEST(magma_safe, material->heat.melting_point > 12000); TEST(deep_material, FLAG(inorganic, inorganic_flags::SPECIAL)); - TEST(non_economic, inorganic && !(ui && ui->economic_stone[index])); + TEST(non_economic, !inorganic || !(ui && vector_get(ui->economic_stone, index))); TEST(plant, plant); TEST(silk, MAT_FLAG(SILK)); From 0c9f1e0af498678656e759725c25ee44ec17a31d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 20 Oct 2012 21:01:22 +0400 Subject: [PATCH 156/196] Check the item type against job_item_vector_id when matching to jobs. This will prevent setting an invalid item type via the job command. --- library/include/modules/Items.h | 4 +++- library/modules/Items.cpp | 26 +++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 92329980a..f9871669a 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -40,6 +40,7 @@ distribution. #include "df/building_actual.h" #include "df/body_part_raw.h" #include "df/unit_inventory_item.h" +#include "df/job_item_vector_id.h" namespace df { @@ -86,7 +87,8 @@ namespace DFHack bool find(const std::string &token); - bool matches(const df::job_item &item, MaterialInfo *mat = NULL); + bool matches(df::job_item_vector_id vec_id); + bool matches(const df::job_item &item, MaterialInfo *mat = NULL, bool skip_vector = false); }; inline bool operator== (const ItemTypeInfo &a, const ItemTypeInfo &b) { diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 7af8c549b..0a78c727f 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -245,7 +245,28 @@ bool Items::isCasteMaterial(df::item_type itype) return ENUM_ATTR(item_type, is_caste_mat, itype); } -bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) +bool ItemTypeInfo::matches(df::job_item_vector_id vec_id) +{ + auto other_id = ENUM_ATTR(job_item_vector_id, other, vec_id); + + auto explicit_item = ENUM_ATTR(items_other_id, item, other_id); + if (explicit_item != item_type::NONE && type != explicit_item) + return false; + + auto generic_item = ENUM_ATTR(items_other_id, generic_item, other_id); + if (generic_item.size > 0) + { + for (size_t i = 0; i < generic_item.size; i++) + if (generic_item.items[i] == type) + return true; + + return false; + } + + return true; +} + +bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool skip_vector) { using namespace df::enums::item_type; @@ -255,6 +276,9 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) if (Items::isCasteMaterial(type) && mat && !mat->isNone()) return false; + if (!skip_vector && !matches(item.vector_id)) + return false; + df::job_item_flags1 ok1, mask1, item_ok1, item_mask1; df::job_item_flags2 ok2, mask2, item_ok2, item_mask2, xmask2; df::job_item_flags3 ok3, mask3, item_ok3, item_mask3; From fbba4caab25b703a22157053f45ac627e64121b8 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 20 Oct 2012 21:16:00 +0400 Subject: [PATCH 157/196] Suppress checking the cookable material flag if the item is a container. --- library/modules/Items.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 0a78c727f..cf99d9426 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -279,11 +279,11 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool ski if (!skip_vector && !matches(item.vector_id)) return false; - df::job_item_flags1 ok1, mask1, item_ok1, item_mask1; + df::job_item_flags1 ok1, mask1, item_ok1, item_mask1, xmask1; df::job_item_flags2 ok2, mask2, item_ok2, item_mask2, xmask2; df::job_item_flags3 ok3, mask3, item_ok3, item_mask3; - ok1.whole = mask1.whole = item_ok1.whole = item_mask1.whole = 0; + ok1.whole = mask1.whole = item_ok1.whole = item_mask1.whole = xmask1.whole = 0; ok2.whole = mask2.whole = item_ok2.whole = item_mask2.whole = xmask2.whole = 0; ok3.whole = mask3.whole = item_ok3.whole = item_mask3.whole = 0; @@ -362,11 +362,13 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool ski case CAGE: OK(1,milk); OK(1,milkable); + xmask1.bits.cookable = true; break; case BUCKET: case FLASK: OK(1,milk); + xmask1.bits.cookable = true; break; case TOOL: @@ -374,6 +376,7 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool ski OK(1,milk); OK(2,lye_milk_free); OK(2,blunt); + xmask1.bits.cookable = true; if (VIRTUAL_CAST_VAR(def, df::itemdef_toolst, custom)) { df::tool_uses key(tool_uses::FOOD_STORAGE); @@ -389,11 +392,13 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool ski OK(1,milk); OK(2,lye_milk_free); OK(3,food_storage); + xmask1.bits.cookable = true; break; case BOX: OK(1,bag); OK(1,sand_bearing); OK(1,milk); OK(2,dye); OK(2,plaster_containing); + xmask1.bits.cookable = true; break; case BIN: @@ -460,6 +465,7 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool ski #undef OK #undef RQ + mask1.whole &= ~xmask1.whole; mask2.whole &= ~xmask2.whole; return bits_match(item.flags1.whole, ok1.whole, mask1.whole) && From dee0c97584607d39d0a263a0fdd9024786f0c6e3 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 20 Oct 2012 21:57:36 +0400 Subject: [PATCH 158/196] Add a gui script for viewing and changing job_item properties. --- NEWS | 1 + dfhack.init-example | 3 + library/lua/gui/materials.lua | 45 +++---- library/lua/gui/widgets.lua | 16 ++- library/xml | 2 +- scripts/gui/workshop-job.lua | 237 ++++++++++++++++++++++++++++++++++ 6 files changed, 271 insertions(+), 33 deletions(-) create mode 100644 scripts/gui/workshop-job.lua diff --git a/NEWS b/NEWS index 61e8a5b3e..5567258bc 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,7 @@ DFHack future - removebadthoughts: add --dry-run option New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. + - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. DFHack v0.34.11-r2 diff --git a/dfhack.init-example b/dfhack.init-example index 3ecbf4342..239d68a0f 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -75,6 +75,9 @@ keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons # minecart Guide path keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path +# workshop job details +# keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job + ############################ # UI and game logic tweaks # ############################ diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index baf641976..871c60014 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -238,8 +238,7 @@ function MaterialDialog:submitMaterial(typ, index) self:dismiss() if self.on_select then - local info = dfhack.matinfo.decode(typ, index) - self.on_select(info, typ, index) + self.on_select(typ, index) end end @@ -276,25 +275,6 @@ function showMaterialPrompt(title, prompt, on_select, on_cancel, mat_filter) }:show() end -local itemdefs = df.global.world.raws.itemdefs -local itemtype_info = { - TRAPPARTS = { name = 'mechanisms' }, - WEAPON = { defs = itemdefs.weapons }, - TRAPCOMP = { defs = itemdefs.trapcomps }, - TOY = { defs = itemdefs.toys }, - TOOL = { defs = itemdefs.tools }, - INSTRUMENT = { defs = itemdefs.instruments }, - ARMOR = { defs = itemdefs.armor }, - AMMO = { defs = itemdefs.ammo }, - SIEGEAMMO = { defs = itemdefs.siege_ammo }, - GLOVES = { defs = itemdefs.gloves }, - SHOES = { defs = itemdefs.shoes }, - SHIELD = { defs = itemdefs.shields }, - HELM = { defs = itemdefs.helms }, - PANTS = { defs = itemdefs.pants }, - FOOD = { defs = itemdefs.food }, -} - function ItemTypeDialog(args) args.text = args.prompt or 'Type or select an item type' args.text_pen = COLOR_WHITE @@ -307,26 +287,24 @@ function ItemTypeDialog(args) local filter = args.item_filter for itype = 0,df.item_type._last_item do - local key = df.item_type[itype] - local info = itemtype_info[key] + local attrs = df.item_type.attrs[itype] + local defcnt = dfhack.items.getSubtypeCount(itype) if not filter or filter(itype,-1) then - local name = key + local name = attrs.caption or df.item_type[itype] local icon - if info and info.defs then + if defcnt >= 0 then name = 'any '..name icon = '+' end - if info and info.name then - name = info.name - end table.insert(choices, { icon = icon, text = string.lower(name), item_type = itype, item_subtype = -1 }) end - if info and info.defs then - for subtype,def in ipairs(info.defs) do + if defcnt > 0 then + for subtype = 0,defcnt-1 do + local def = dfhack.items.getSubtypeDef(itype, subtype) if not filter or filter(itype,subtype,def) then table.insert(choices, { icon = '\x1e', text = ' '..def.name, item_type = itype, item_subtype = subtype @@ -338,6 +316,13 @@ function ItemTypeDialog(args) args.choices = choices + if args.on_select then + local cb = args.on_select + args.on_select = function(idx, obj) + return cb(obj.item_type, obj.item_subtype) + end + end + return dlg.ListBox(args) end diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 05100c00b..e6a9a4d72 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -211,6 +211,11 @@ function parse_label_text(obj) obj.text_ids = idtab end +local function is_disabled(token) + return (token.disabled ~= nil and getval(token.disabled)) or + (token.enabled ~= nil and not getval(token.enabled)) +end + function render_text(obj,dc,x0,y0,pen,dpen) local width = 0 for iline,line in ipairs(obj.text_lines) do @@ -241,7 +246,7 @@ function render_text(obj,dc,x0,y0,pen,dpen) local keypen if dc then - if getval(token.disabled) then + if is_disabled(token) then dc:pen(getval(token.dpen) or dpen) keypen = COLOR_GREEN else @@ -287,7 +292,7 @@ end function check_text_keys(self, keys) if self.text_active then for _,item in ipairs(self.text_active) do - if item.key and keys[item.key] and not getval(item.disabled) then + if item.key and keys[item.key] and not is_disabled(item) then item.on_activate() return true end @@ -361,6 +366,13 @@ STANDARDSCROLL = { STANDARDSCROLL_PAGEDOWN = '+page', } +SECONDSCROLL = { + SECONDSCROLL_UP = -1, + SECONDSCROLL_DOWN = 1, + SECONDSCROLL_PAGEUP = '-page', + SECONDSCROLL_PAGEDOWN = '+page', +} + List.ATTRS{ text_pen = COLOR_CYAN, cursor_pen = COLOR_LIGHTCYAN, diff --git a/library/xml b/library/xml index 0ed2d1dc5..b9b2e8c6d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 0ed2d1dc5547737fbb838568fc66671098f2c11a +Subproject commit b9b2e8c6d2141f13966ed965b3f3ffe924e527db diff --git a/scripts/gui/workshop-job.lua b/scripts/gui/workshop-job.lua new file mode 100644 index 000000000..973d5cc17 --- /dev/null +++ b/scripts/gui/workshop-job.lua @@ -0,0 +1,237 @@ +-- Show and modify properties of jobs in a workshop. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local guimat = require 'gui.materials' +local widgets = require 'gui.widgets' +local dlg = require 'gui.dialogs' + +JobDetails = defclass(JobDetails, guidm.MenuOverlay) + +JobDetails.focus_path = 'workshop-job' + +JobDetails.ATTRS { + job = DEFAULT_NIL, + frame_inset = 1, + frame_background = COLOR_BLACK, +} + +function JobDetails:init(args) + local status = { text = 'No worker', pen = COLOR_DARKGREY } + local worker = dfhack.job.getWorker(self.job) + if self.job.flags.suspend then + status = { text = 'Suspended', pen = COLOR_RED } + elseif worker then + status = { text = dfhack.TranslateName(dfhack.unit.getVisibleName(worker)), pen = COLOR_GREEN } + end + + self:addviews{ + widgets.Label{ + frame = { l = 0, t = 0 }, + text = { + { text = df.job_type.attrs[self.job.job_type].caption }, NEWLINE, NEWLINE, + ' ', status + } + }, + widgets.Label{ + frame = { l = 0, t = 4 }, + text = { + { key = 'CUSTOM_I', text = ': Input item, ', + enabled = self:callback('canChangeIType'), + on_activate = self:callback('onChangeIType') }, + { key = 'CUSTOM_M', text = ': Material', + enabled = self:callback('canChangeMat'), + on_activate = self:callback('onChangeMat') } + } + }, + widgets.List{ + view_id = 'list', + frame = { t = 6, b = 2 }, + row_height = 4, + scroll_keys = widgets.SECONDSCROLL, + }, + widgets.Label{ + frame = { l = 0, b = 0 }, + text = { + { key = 'LEAVESCREEN', text = ': Back', + on_activate = self:callback('dismiss') } + } + }, + } + + self:initListChoices() +end + +function describe_item_type(iobj) + local itemline = 'any item' + if iobj.item_type >= 0 then + itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type + local def = dfhack.items.getSubtypeDef(iobj.item_type, iobj.item_subtype) + local count = dfhack.items.getSubtypeCount(iobj.item_type, iobj.item_subtype) + if def then + itemline = def.name + elseif count >= 0 then + itemline = 'any '..itemline + end + end + return itemline +end + +function is_caste_mat(iobj) + if iobj.item_type >= 0 then + return df.item_type.attrs[iobj.item_type].is_caste_mat + end + return false +end + +function describe_material(iobj) + local matline = 'any material' + if is_caste_mat(iobj) then + matline = 'material not applicable' + elseif iobj.mat_type >= 0 then + local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) + if info then + matline = info:toString() + else + matline = iobj.mat_type..':'..iobj.mat_index + end + end + return matline +end + +function list_flags(list, bitfield) + for name,val in pairs(bitfield) do + if val then + table.insert(list, name) + end + end +end + +function JobDetails:initListChoices() + local items = {} + for i,ref in ipairs(self.job.items) do + local idx = ref.job_item_idx + if idx >= 0 then + items[idx] = (items[idx] or 0) + 1 + end + end + + local choices = {} + for i,iobj in ipairs(self.job.job_items) do + local head = 'Item '..(i+1)..': '..(items[idx] or 0)..' of '..iobj.quantity + if iobj.min_dimension > 0 then + head = head .. '(size '..iobj.min_dimension..')' + end + + local line1 = {} + local reaction = df.reaction.find(iobj.reaction_id) + if reaction and #iobj.contains > 0 then + for _,ri in ipairs(iobj.contains) do + table.insert(line1, 'has '..utils.call_with_string( + reaction.reagents[ri],'getDescription',iobj.reaction_id + )) + end + end + if iobj.metal_ore >= 0 then + local ore = dfhack.matinfo.decode(0, iobj.metal_ore) + if ore then + table.insert(line1, 'ore of '..ore:toString()) + end + end + if iobj.has_material_reaction_product ~= '' then + table.insert(line1, 'product '..iobj.has_material_reaction_product) + end + if iobj.reaction_class ~= '' then + table.insert(line1, 'class '..iobj.reaction_class) + end + if iobj.has_tool_use >= 0 then + table.insert(line1, 'has use '..df.tool_uses[iobj.has_tool_use]) + end + list_flags(line1, iobj.flags1) + list_flags(line1, iobj.flags2) + list_flags(line1, iobj.flags3) + if #line1 == 0 then + table.insert(line1, 'no flags') + end + + table.insert(choices, { + index = i, + iobj = iobj, + text = { + head, NEWLINE, + ' ', { text = curry(describe_item_type, iobj) }, NEWLINE, + ' ', { text = curry(describe_material, iobj) }, NEWLINE, + ' ', table.concat(line1, ', '), NEWLINE + } + }) + end + + self.subviews.list:setChoices(choices) +end + +function JobDetails:canChangeIType() + local idx, obj = self.subviews.list:getSelected() + return obj ~= nil +end + +function JobDetails:onChangeIType() + local idx, obj = self.subviews.list:getSelected() + guimat.ItemTypeDialog{ + prompt = 'Please select a new item type for input '..idx, + none_caption = 'any item', + item_filter = curry(dfhack.job.isSuitableItem, obj.iobj), + on_select = function(item_type, item_subtype) + obj.iobj.item_type = item_type + obj.iobj.item_subtype = item_subtype + end + }:show() +end + +function JobDetails:canChangeMat() + local idx, obj = self.subviews.list:getSelected() + return obj ~= nil and not is_caste_mat(obj.iobj) +end + +function JobDetails:onChangeMat() + local idx, obj = self.subviews.list:getSelected() + + if obj.iobj.item_type == -1 and obj.iobj.mat_type == -1 then + dlg.showMessage( + 'Bug Alert', + { 'Please set a specific item type first.\n\n', + 'Otherwise the material will be matched\n', + 'incorrectly due to a limitation in DF code.' }, + COLOR_YELLOW + ) + return + end + + guimat.MaterialDialog{ + prompt = 'Please select a new material for input '..idx, + none_caption = 'any material', + mat_filter = function(mat,parent,mat_type,mat_index) + return dfhack.job.isSuitableMaterial(obj.iobj, mat_type, mat_index) + end, + on_select = function(mat_type, mat_index) + if idx == 1 + and self.job.mat_type == obj.iobj.mat_type + and self.job.mat_index == obj.iobj.mat_index + and self.job.job_type ~= df.job_type.PrepareMeal + then + self.job.mat_type = mat_type + self.job.mat_index = mat_index + end + + obj.iobj.mat_type = mat_type + obj.iobj.mat_index = mat_index + end + }:show() +end + +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then + qerror("This script requires a workshop job selected in the 'q' mode") +end + +local dlg = JobDetails{ job = dfhack.gui.getSelectedJob() } +dlg:show() From 1f7a01d6854ff528f7ffadb4fa31c2495ab83c9c Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 20 Oct 2012 22:35:39 +0200 Subject: [PATCH 159/196] follow rename unit.military.squad_index to squad_id in df-structures --- library/RemoteTools.cpp | 4 ++-- plugins/lua/sort/units.lua | 6 +++--- plugins/ruby/unit.rb | 2 +- plugins/tweak.cpp | 2 +- scripts/siren.lua | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index b371d60fa..718d1b181 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -292,9 +292,9 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, if (!unit->custom_profession.empty()) info->set_custom_profession(unit->custom_profession); - if (unit->military.squad_index >= 0) + if (unit->military.squad_id >= 0) { - info->set_squad_id(unit->military.squad_index); + info->set_squad_id(unit->military.squad_id); info->set_squad_position(unit->military.squad_position); } } diff --git a/plugins/lua/sort/units.lua b/plugins/lua/sort/units.lua index d8ae83a38..c124e52d9 100644 --- a/plugins/lua/sort/units.lua +++ b/plugins/lua/sort/units.lua @@ -93,7 +93,7 @@ orders.race = { orders.squad = { key = function(unit) - local sidx = unit.military.squad_index + local sidx = unit.military.squad_id if sidx >= 0 then return sidx end @@ -102,7 +102,7 @@ orders.squad = { orders.squad_position = { key = function(unit) - local sidx = unit.military.squad_index + local sidx = unit.military.squad_id if sidx >= 0 then return sidx * 1000 + unit.military.squad_position end @@ -115,4 +115,4 @@ orders.happiness = { end } -return _ENV \ No newline at end of file +return _ENV diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 139a375b2..4fbf75d8d 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -92,7 +92,7 @@ module DFHack # filter 'attend meeting' not u.specific_refs.find { |s| s.type == :ACTIVITY } and # filter soldiers (TODO check schedule) - u.military.squad_index == -1 and + u.military.squad_id == -1 and # filter 'on break' not u.status.misc_traits.find { |t| t.id == :OnBreak } end diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 8488ba3e8..b4af2a4ba 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -640,7 +640,7 @@ struct military_assign_hook : df::viewscreen_layer_militaryst { for (int y = y1, i = i1; i <= i2; i++, y++) { auto unit = vector_get(positions.candidates, i); - if (!unit || unit->military.squad_index < 0) + if (!unit || unit->military.squad_id < 0) continue; for (int x = x1; x <= x2; x++) diff --git a/scripts/siren.lua b/scripts/siren.lua index 30d3aa07f..4fc574ed0 100644 --- a/scripts/siren.lua +++ b/scripts/siren.lua @@ -89,7 +89,7 @@ end for _,v in ipairs(df.global.world.units.active) do local x,y,z = dfhack.units.getPosition(v) if x and dfhack.units.isCitizen(v) and is_in_burrows(xyz2pos(x,y,z)) then - if not in_siege and v.military.squad_index < 0 then + if not in_siege and v.military.squad_id < 0 then add_thought(v, df.unit_thought_type.LackProtection) end wake_unit(v) From 46938625fdedbb23bf7b7ebd57bc77c21758dcf2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 21 Oct 2012 21:45:51 +0400 Subject: [PATCH 160/196] Pass the hotkey keys to dwarfmode from overlays, and fix gui/workshop-job. --- dfhack.init-example | 2 +- library/lua/gui/dwarfmode.lua | 48 +++++++++++++++++++----- scripts/gui/workshop-job.lua | 69 ++++++++++++++++++++++++----------- 3 files changed, 87 insertions(+), 32 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index 239d68a0f..27801b73d 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -76,7 +76,7 @@ keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path # workshop job details -# keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job +keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job ############################ # UI and game logic tweaks # diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 0c1b0599c..cef52ae24 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -238,6 +238,19 @@ local function get_movement_delta(key, delta, big_step) end end +HOTKEY_KEYS = {} + +for i,v in ipairs(df.global.ui.main.hotkeys) do + HOTKEY_KEYS['D_HOTKEY'..(i+1)] = v +end + +local function get_hotkey_target(key) + local hk = HOTKEY_KEYS[key] + if hk and hk.cmd == df.ui_hotkey.T_cmd.Zoom then + return xyz2pos(hk.x, hk.y, hk.z) + end +end + function Viewport:scrollByKey(key) local dx, dy, dz = get_movement_delta(key, 10, 20) if dx then @@ -247,7 +260,12 @@ function Viewport:scrollByKey(key) self.z + dz ) else - return self + local hk_pos = get_hotkey_target(key) + if hk_pos then + return self:centerOn(hk_pos) + else + return self + end end end @@ -286,9 +304,11 @@ function DwarfOverlay:selectBuilding(building,cursor,viewport,gap) end function DwarfOverlay:propagateMoveKeys(keys) - for code,_ in pairs(MOVEMENT_KEYS) do - if keys[code] then - self:sendInputToParent(code) + for code,_ in pairs(keys) do + if MOVEMENT_KEYS[code] or HOTKEY_KEYS[code] then + if not HOTKEY_KEYS[code] or get_hotkey_target(code) then + self:sendInputToParent(code) + end return code end end @@ -305,8 +325,8 @@ function DwarfOverlay:simulateViewScroll(keys, anchor, no_clip_cursor) return 'A_MOVE_SAME_SQUARE' end - for code,_ in pairs(MOVEMENT_KEYS) do - if keys[code] then + for code,_ in pairs(keys) do + if MOVEMENT_KEYS[code] or HOTKEY_KEYS[code] then local vp = self:getViewport():scrollByKey(code) if (cursor and not no_clip_cursor) or no_clip_cursor == false then vp = vp:reveal(anchor,4,20,4,true) @@ -329,16 +349,26 @@ function DwarfOverlay:simulateCursorMovement(keys, anchor) return 'A_MOVE_SAME_SQUARE' end - for code,_ in pairs(MOVEMENT_KEYS) do - if keys[code] then + for code,_ in pairs(keys) do + if MOVEMENT_KEYS[code] then local dx, dy, dz = get_movement_delta(code, 1, 10) local ncur = xyz2pos(cx+dx, cy+dy, cz+dz) if dfhack.maps.isValidTilePos(ncur) then setCursorPos(ncur) self:getViewport():reveal(ncur,4,10,6,true):set() - return code end + + return code + elseif HOTKEY_KEYS[code] then + local pos = get_hotkey_target(code) + + if pos and dfhack.maps.isValidTilePos(pos) then + setCursorPos(pos) + self:getViewport():centerOn(pos):set() + end + + return code end end end diff --git a/scripts/gui/workshop-job.lua b/scripts/gui/workshop-job.lua index 973d5cc17..74e33595f 100644 --- a/scripts/gui/workshop-job.lua +++ b/scripts/gui/workshop-job.lua @@ -18,12 +18,14 @@ JobDetails.ATTRS { } function JobDetails:init(args) + self.building = dfhack.job.getHolder(self.job) + local status = { text = 'No worker', pen = COLOR_DARKGREY } local worker = dfhack.job.getWorker(self.job) if self.job.flags.suspend then status = { text = 'Suspended', pen = COLOR_RED } elseif worker then - status = { text = dfhack.TranslateName(dfhack.unit.getVisibleName(worker)), pen = COLOR_GREEN } + status = { text = dfhack.TranslateName(dfhack.units.getVisibleName(worker)), pen = COLOR_GREEN } end self:addviews{ @@ -63,6 +65,14 @@ function JobDetails:init(args) self:initListChoices() end +function JobDetails:onGetSelectedBuilding() + return self.building +end + +function JobDetails:onGetSelectedJob() + return self.job +end + function describe_item_type(iobj) local itemline = 'any item' if iobj.item_type >= 0 then @@ -79,10 +89,7 @@ function describe_item_type(iobj) end function is_caste_mat(iobj) - if iobj.item_type >= 0 then - return df.item_type.attrs[iobj.item_type].is_caste_mat - end - return false + return dfhack.items.isCasteMaterial(iobj.item_type) end function describe_material(iobj) @@ -119,7 +126,7 @@ function JobDetails:initListChoices() local choices = {} for i,iobj in ipairs(self.job.job_items) do - local head = 'Item '..(i+1)..': '..(items[idx] or 0)..' of '..iobj.quantity + local head = 'Item '..(i+1)..': '..(items[i] or 0)..' of '..iobj.quantity if iobj.min_dimension > 0 then head = head .. '(size '..iobj.min_dimension..')' end @@ -175,16 +182,22 @@ function JobDetails:canChangeIType() return obj ~= nil end +function JobDetails:setItemType(obj, item_type, item_subtype) + obj.iobj.item_type = item_type + obj.iobj.item_subtype = item_subtype + + if is_caste_mat(obj.iobj) then + self:setMaterial(obj, -1, -1) + end +end + function JobDetails:onChangeIType() local idx, obj = self.subviews.list:getSelected() guimat.ItemTypeDialog{ prompt = 'Please select a new item type for input '..idx, none_caption = 'any item', item_filter = curry(dfhack.job.isSuitableItem, obj.iobj), - on_select = function(item_type, item_subtype) - obj.iobj.item_type = item_type - obj.iobj.item_subtype = item_subtype - end + on_select = self:callback('setItemType', obj) }:show() end @@ -193,6 +206,20 @@ function JobDetails:canChangeMat() return obj ~= nil and not is_caste_mat(obj.iobj) end +function JobDetails:setMaterial(obj, mat_type, mat_index) + if obj.index == 0 + and self.job.mat_type == obj.iobj.mat_type + and self.job.mat_index == obj.iobj.mat_index + and self.job.job_type ~= df.job_type.PrepareMeal + then + self.job.mat_type = mat_type + self.job.mat_index = mat_index + end + + obj.iobj.mat_type = mat_type + obj.iobj.mat_index = mat_index +end + function JobDetails:onChangeMat() local idx, obj = self.subviews.list:getSelected() @@ -213,20 +240,18 @@ function JobDetails:onChangeMat() mat_filter = function(mat,parent,mat_type,mat_index) return dfhack.job.isSuitableMaterial(obj.iobj, mat_type, mat_index) end, - on_select = function(mat_type, mat_index) - if idx == 1 - and self.job.mat_type == obj.iobj.mat_type - and self.job.mat_index == obj.iobj.mat_index - and self.job.job_type ~= df.job_type.PrepareMeal - then - self.job.mat_type = mat_type - self.job.mat_index = mat_index - end + on_select = self:callback('setMaterial', obj) + }:show() +end - obj.iobj.mat_type = mat_type - obj.iobj.mat_index = mat_index +function JobDetails:onInput(keys) + if self:propagateMoveKeys(keys) then + if df.global.world.selected_building ~= self.building then + self:dismiss() end - }:show() + else + JobDetails.super.onInput(self, keys) + end end if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then From cfbdf47f6e75048193f906ad41218a436ce1b892 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 22 Oct 2012 19:37:12 +0200 Subject: [PATCH 161/196] follow rename Carried->Hauled in df-structures unit inventory mode --- library/include/modules/Items.h | 2 +- plugins/advtools.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index f9871669a..217a0f28a 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -161,7 +161,7 @@ DFHACK_EXPORT bool moveToGround(MapExtras::MapCache &mc, df::item *item, df::coo DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df::item *container); DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode); DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit, - df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Carried, int body_part = -1); + df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Hauled, int body_part = -1); /// Makes the item removed and marked for garbage collection DFHACK_EXPORT bool remove(MapExtras::MapCache &mc, df::item *item, bool no_uncat = false); diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index d6224f5c2..59b88087b 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -321,7 +321,7 @@ std::string getUnitNameProfession(df::unit *unit) } enum InventoryMode { - INV_CARRIED, + INV_HAULED, INV_WEAPON, INV_WORN, INV_IN_CONTAINER @@ -355,8 +355,8 @@ void listUnitInventory(std::vector *list, df::unit *unit) InventoryMode mode; switch (item->mode) { - case df::unit_inventory_item::Carried: - mode = INV_CARRIED; + case df::unit_inventory_item::Hauled: + mode = INV_HAULED; break; case df::unit_inventory_item::Weapon: mode = INV_WEAPON; From 27c7dfde4f715e42c9bec87f88c07c0d696f6c43 Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 23 Oct 2012 12:14:21 -0500 Subject: [PATCH 162/196] ANY_FREE -> IN_PLAY, to match terminology used in DF's error messages --- library/modules/Buildings.cpp | 2 +- plugins/autolabor.cpp | 2 +- plugins/siege-engine.cpp | 2 +- plugins/workflow.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 89106ca87..6421114a1 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -762,7 +762,7 @@ static void markBuildingTiles(df::building *bld, bool remove) static void linkRooms(df::building *bld) { - auto &vec = world->buildings.other[buildings_other_id::ANY_FREE]; + auto &vec = world->buildings.other[buildings_other_id::IN_PLAY]; bool changed = false; diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 42ccae4c2..44984cfdb 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1547,7 +1547,7 @@ static int stockcheck(color_ostream &out, vector & parameters) } - std::vector &items = world->items.other[items_other_id::ANY_FREE]; + std::vector &items = world->items.other[items_other_id::IN_PLAY]; // Precompute a bitmask with the bad flags df::item_flags bad_flags; diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index 324c924c4..5c7b8833a 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -493,7 +493,7 @@ static int setAmmoItem(lua_State *L) if (!entry.isValid()) return 0; - engine->ammo_vector_id = job_item_vector_id::ANY_FREE; + engine->ammo_vector_id = job_item_vector_id::IN_PLAY; engine->ammo_item_type = item_type; FOR_ENUM_ITEMS(job_item_vector_id, id) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 2720baa83..57020b161 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1133,7 +1133,7 @@ static void map_job_items(color_ostream &out) bool dry_buckets = isOptionEnabled(CF_DRYBUCKETS); - std::vector &items = world->items.other[items_other_id::ANY_FREE]; + std::vector &items = world->items.other[items_other_id::IN_PLAY]; for (size_t i = 0; i < items.size(); i++) { From 09f8e8e4192d7fe8a61a39c49749a3820f47ed37 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 23 Oct 2012 21:42:03 +0400 Subject: [PATCH 163/196] Start working on gui for workflow. --- NEWS | 1 + dfhack.init-example | 3 + library/LuaApi.cpp | 13 ++- library/include/LuaTools.h | 2 + plugins/lua/workflow.lua | 14 +++ plugins/workflow.cpp | 195 ++++++++++++++++++++++++++++++----- scripts/gui/workflow.lua | 202 +++++++++++++++++++++++++++++++++++++ 7 files changed, 401 insertions(+), 29 deletions(-) create mode 100644 plugins/lua/workflow.lua create mode 100644 scripts/gui/workflow.lua diff --git a/NEWS b/NEWS index 5567258bc..b4712c22d 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,7 @@ DFHack future New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. + - gui/workflow: a front-end for the workflow plugin. DFHack v0.34.11-r2 diff --git a/dfhack.init-example b/dfhack.init-example index 27801b73d..d4c7dc233 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -78,6 +78,9 @@ keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path # workshop job details keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job +# workflow front-end +keybinding add Ctrl-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow + ############################ # UI and game logic tweaks # ############################ diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index aa6c93c32..593ac3d8d 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -500,7 +500,9 @@ static void OpenPersistent(lua_State *state) * Material info lookup * ************************/ -static void push_matinfo(lua_State *state, MaterialInfo &info) +static int DFHACK_MATINFO_TOKEN = 0; + +void Lua::Push(lua_State *state, MaterialInfo &info) { if (!info.isValid()) { @@ -509,7 +511,7 @@ static void push_matinfo(lua_State *state, MaterialInfo &info) } lua_newtable(state); - lua_pushvalue(state, lua_upvalueindex(1)); + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_MATINFO_TOKEN); lua_setmetatable(state, -2); lua_pushinteger(state, info.type); @@ -564,7 +566,7 @@ static int dfhack_matinfo_find(lua_State *state) info.find(tokens); } - push_matinfo(state, info); + Lua::Push(state, info); return 1; } @@ -632,7 +634,7 @@ static int dfhack_matinfo_decode(lua_State *state) { MaterialInfo info; decode_matinfo(state, &info, true); - push_matinfo(state, info); + Lua::Push(state, info); return 1; } @@ -711,6 +713,9 @@ static void OpenMatinfo(lua_State *state) { luaL_getsubtable(state, lua_gettop(state), "matinfo"); + lua_dup(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_MATINFO_TOKEN); + lua_dup(state); luaL_setfuncs(state, dfhack_matinfo_funcs, 1); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index fddbdbc26..ec4917972 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -36,6 +36,7 @@ distribution. namespace DFHack { class function_identity_base; + struct MaterialInfo; namespace Units { struct NoblePosition; @@ -283,6 +284,7 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT void Push(lua_State *state, df::coord obj); DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj); void Push(lua_State *state, const Units::NoblePosition &pos); + DFHACK_EXPORT void Push(lua_State *state, MaterialInfo &info); template inline void Push(lua_State *state, T *ptr) { PushDFObject(state, ptr); } diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua new file mode 100644 index 000000000..748484052 --- /dev/null +++ b/plugins/lua/workflow.lua @@ -0,0 +1,14 @@ +local _ENV = mkmodule('plugins.workflow') + +--[[ + + Native functions: + + * isEnabled() + * setEnabled(enable) + * listConstraints([job]) -> {...} + * setConstraint(token, by_count, goal[, gap]) -> {...} + +--]] + +return _ENV diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 2720baa83..af35c6533 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -4,6 +4,9 @@ #include "PluginManager.h" #include "MiscUtils.h" +#include "LuaTools.h" +#include "DataFuncs.h" + #include "modules/Materials.h" #include "modules/Items.h" #include "modules/Gui.h" @@ -743,6 +746,20 @@ static void delete_constraint(ItemConstraint *cv) delete cv; } +static bool deleteConstraint(std::string name) +{ + for (size_t i = 0; i < constraints.size(); i++) + { + if (constraints[i]->config.val() != name) + continue; + + delete_constraint(constraints[i]); + return true; + } + + return false; +} + /****************************** * JOB-CONSTRAINT MAPPING * ******************************/ @@ -1347,6 +1364,153 @@ static void process_constraints(color_ostream &out) update_jobs_by_constraints(out); } +static void update_data_structures(color_ostream &out) +{ + if (enabled) { + check_lost_jobs(out, 0); + recover_jobs(out); + update_job_data(out); + map_job_constraints(out); + map_job_items(out); + } +} + +/************* + * LUA API * + *************/ + +static bool isEnabled() { return enabled; } + +static void setEnabled(color_ostream &out, bool enable) +{ + if (enable && !enabled) + { + enable_plugin(out); + } + else if (!enable && enabled) + { + enabled = false; + setOptionEnabled(CF_ENABLED, false); + stop_protect(out); + } +} + +static void push_constraint(lua_State *L, ItemConstraint *cv) +{ + lua_newtable(L); + int ctable = lua_gettop(L); + + Lua::SetField(L, cv->config.entry_id(), ctable, "id"); + Lua::SetField(L, cv->config.val(), ctable, "token"); + + // Constraint key + + Lua::SetField(L, cv->item.type, ctable, "item_type"); + Lua::SetField(L, cv->item.subtype, ctable, "item_subtype"); + + Lua::SetField(L, cv->is_craft, ctable, "is_craft"); + + Lua::PushDFObject(L, &cv->mat_mask); + lua_setfield(L, -2, "mat_mask"); + + Lua::SetField(L, cv->material.type, ctable, "mat_type"); + Lua::SetField(L, cv->material.index, ctable, "mat_index"); + + Lua::SetField(L, (int)cv->min_quality, ctable, "min_quality"); + + // Constraint value + + Lua::SetField(L, cv->goalByCount(), ctable, "goal_by_count"); + Lua::SetField(L, cv->goalCount(), ctable, "goal_value"); + Lua::SetField(L, cv->goalGap(), ctable, "goal_gap"); + + Lua::SetField(L, cv->item_amount, ctable, "cur_amount"); + Lua::SetField(L, cv->item_count, ctable, "cur_count"); + Lua::SetField(L, cv->item_inuse, ctable, "cur_in_use"); + + // Current state value + + if (cv->request_resume) + Lua::SetField(L, "resume", ctable, "request"); + else if (cv->request_suspend) + Lua::SetField(L, "suspend", ctable, "request"); + + lua_newtable(L); + + for (size_t i = 0, j = 0; i < cv->jobs.size(); i++) + { + if (!cv->jobs[i]->isLive()) continue; + Lua::PushDFObject(L, cv->jobs[i]->actual_job); + lua_rawseti(L, -2, ++j); + } + + lua_setfield(L, ctable, "jobs"); +} + +static int listConstraints(lua_State *L) +{ + auto job = Lua::CheckDFObject(L, 1); + ProtectedJob *pj = NULL; + if (job) + pj = get_known(job->id); + + if (!enabled || (job && !pj)) + { + lua_pushnil(L); + return 1; + } + + color_ostream &out = *Lua::GetOutput(L); + update_data_structures(out); + + lua_newtable(L); + + auto &vec = (pj ? pj->constraints : constraints); + + for (size_t i = 0; i < vec.size(); i++) + { + push_constraint(L, vec[i]); + lua_rawseti(L, -2, i+1); + } + + return 1; +} + +static int setConstraint(lua_State *L) +{ + auto token = luaL_checkstring(L, 1); + bool by_count = lua_toboolean(L, 2); + int count = luaL_checkint(L, 3); + int gap = luaL_optint(L, 4, -1); + + color_ostream &out = *Lua::GetOutput(L); + + ItemConstraint *icv = get_constraint(out, token); + if (!icv) + luaL_error(L, "invalid constraint: %s", token); + + icv->setGoalByCount(by_count); + icv->setGoalCount(count); + icv->setGoalGap(gap); + + process_constraints(out); + push_constraint(L, icv); + return 1; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(isEnabled), + DFHACK_LUA_FUNCTION(setEnabled), + DFHACK_LUA_FUNCTION(deleteConstraint), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(listConstraints), + DFHACK_LUA_COMMAND(setConstraint), + DFHACK_LUA_END +}; + /****************************** * PRINTING AND THE COMMAND * ******************************/ @@ -1490,13 +1654,7 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet return CR_FAILURE; } - if (enabled) { - check_lost_jobs(out, 0); - recover_jobs(out); - update_job_data(out); - map_job_constraints(out); - map_job_items(out); - } + update_data_structures(out); df::building *workshop = NULL; //FIXME: unused variable! @@ -1514,18 +1672,11 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet if (cmd == "enable" || cmd == "disable") { bool enable = (cmd == "enable"); - if (enable && !enabled) - { - enable_plugin(out); - } - else if (!enable && parameters.size() == 1) + if (enable) + setEnabled(out, true); + else if (parameters.size() == 1) { - if (enabled) - { - enabled = false; - setOptionEnabled(CF_ENABLED, false); - stop_protect(out); - } + setEnabled(out, false); out << "The plugin is disabled." << endl; return CR_OK; @@ -1643,14 +1794,8 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet if (parameters.size() != 2) return CR_WRONG_USAGE; - for (size_t i = 0; i < constraints.size(); i++) - { - if (constraints[i]->config.val() != parameters[1]) - continue; - - delete_constraint(constraints[i]); + if (deleteConstraint(parameters[1])) return CR_OK; - } out.printerr("Constraint not found: %s\n", parameters[1].c_str()); return CR_FAILURE; diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua new file mode 100644 index 000000000..17413d46e --- /dev/null +++ b/scripts/gui/workflow.lua @@ -0,0 +1,202 @@ +-- A GUI front-end for the workflow plugin. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local guimat = require 'gui.materials' +local widgets = require 'gui.widgets' +local dlg = require 'gui.dialogs' + +local workflow = require 'plugins.workflow' + +function check_enabled(cb,...) + if workflow.isEnabled() then + return cb(...) + else + dlg.showYesNoPrompt( + 'Enable Plugin', + { 'The workflow plugin is not enabled currently.', NEWLINE, NEWLINE + 'Press ', { key = 'MENU_CONFIRM' }, ' to enable it.' }, + COLOR_YELLOW, + curry(function(...) + workflow.setEnabled(true) + return cb(...) + end,...) + ) + end +end + +JobConstraints = defclass(JobConstraints, guidm.MenuOverlay) + +JobConstraints.focus_path = 'workflow-job' + +JobConstraints.ATTRS { + job = DEFAULT_NIL, + frame_inset = 1, + frame_background = COLOR_BLACK, +} + +function JobConstraints:init(args) + self.building = dfhack.job.getHolder(self.job) + + local status = { text = 'No worker', pen = COLOR_DARKGREY } + local worker = dfhack.job.getWorker(self.job) + if self.job.flags.suspend then + status = { text = 'Suspended', pen = COLOR_RED } + elseif worker then + status = { text = dfhack.TranslateName(dfhack.units.getVisibleName(worker)), pen = COLOR_GREEN } + end + + self:addviews{ + widgets.Label{ + frame = { l = 0, t = 0 }, + text = { + 'Workflow Constraints' + } + }, + widgets.List{ + view_id = 'list', + frame = { t = 2, b = 2 }, + row_height = 4, + scroll_keys = widgets.SECONDSCROLL, + }, + widgets.Label{ + frame = { l = 0, b = 0 }, + text = { + { key = 'LEAVESCREEN', text = ': Back', + on_activate = self:callback('dismiss') } + } + }, + } + + self:initListChoices(args.clist) +end + +function JobConstraints:onGetSelectedBuilding() + return self.building +end + +function JobConstraints:onGetSelectedJob() + return self.job +end + +function describe_item_type(iobj) + local itemline = 'any item' + if iobj.item_type >= 0 then + itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type + local def = dfhack.items.getSubtypeDef(iobj.item_type, iobj.item_subtype) + local count = dfhack.items.getSubtypeCount(iobj.item_type, iobj.item_subtype) + if def then + itemline = def.name + elseif count >= 0 then + itemline = 'any '..itemline + end + end + return itemline +end + +function is_caste_mat(iobj) + return dfhack.items.isCasteMaterial(iobj.item_type) +end + +function describe_material(iobj) + local matline = 'any material' + if is_caste_mat(iobj) then + matline = 'material not applicable' + elseif iobj.mat_type >= 0 then + local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) + if info then + matline = info:toString() + else + matline = iobj.mat_type..':'..iobj.mat_index + end + end + return matline +end + +function list_flags(list, bitfield) + for name,val in pairs(bitfield) do + if val then + table.insert(list, name) + end + end +end + +function JobConstraints:initListChoices(clist) + clist = clist or workflow.listConstraints(self.job) + + local choices = {} + + for i,cons in ipairs(clist) do + local goal = (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value + local curval + if cons.goal_by_count then + goal = goal .. ' stacks' + curval = cons.cur_count + else + goal = goal .. ' items' + curval = cons.cur_amount + end + local order_pen = COLOR_GREY + if cons.request == 'resume' then + order_pen = COLOR_GREEN + elseif cons.request == 'suspend' then + order_pen = COLOR_RED + end + local itemstr + if cons.is_craft then + itemstr = 'any craft' + else + itemstr = describe_item_type(cons) + end + if cons.min_quality > 0 then + itemstr = itemstr .. ' ('..df.item_quality[cons.min_quality]..')' + end + local matstr = describe_material(cons) + local matflagstr = '' + local matflags = {} + list_flags(matflags, cons.mat_mask) + if #matflags > 0 then + matflagstr = 'class: '..table.concat(matflags, ', ') + end + + table.insert(choices, { + text = { + goal, ' ', { text = '(now '..curval..')', pen = order_pen }, NEWLINE, + ' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', matflagstr + }, + obj = cons + }) + end + + self.subviews.list:setChoices(choices) +end + +function JobConstraints:onInput(keys) + if self:propagateMoveKeys(keys) then + if df.global.world.selected_building ~= self.building then + self:dismiss() + end + else + JobConstraints.super.onInput(self, keys) + end +end + +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then + qerror("This script requires a workshop job selected in the 'q' mode") +end + +check_enabled(function() + local job = dfhack.gui.getSelectedJob() + if not job.flags['repeat'] then + dlg.showMessage('Not Supported', 'Workflow only tracks repeat jobs.', COLOR_LIGHTRED) + return + end + local clist = workflow.listConstraints(job) + if not clist then + dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) + return + end + JobConstraints{ job = job, clist = clist }:show() +end) + From cd852e22404223c66ade5af0e973431a637ffe0d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 24 Oct 2012 17:51:47 +0400 Subject: [PATCH 164/196] Fix one more place that was using ANY_FREE. --- plugins/devel/stockcheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/devel/stockcheck.cpp b/plugins/devel/stockcheck.cpp index 8ace55cdc..452a637fc 100644 --- a/plugins/devel/stockcheck.cpp +++ b/plugins/devel/stockcheck.cpp @@ -135,7 +135,7 @@ static command_result stockcheck(color_ostream &out, vector & parameter } - std::vector &items = world->items.other[items_other_id::ANY_FREE]; + std::vector &items = world->items.other[items_other_id::IN_PLAY]; // Precompute a bitmask with the bad flags df::item_flags bad_flags; From 616c57257daaf2e420d452c6997d9897dca5fac9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 24 Oct 2012 19:25:06 +0400 Subject: [PATCH 165/196] Support changing the constraint ranges from the workflow gui script. TODO: implement creating completely new constraints. --- library/lua/gui/widgets.lua | 39 ++++++----- plugins/workflow.cpp | 5 +- scripts/gui/workflow.lua | 132 +++++++++++++++++++++++++++++++++++- 3 files changed, 156 insertions(+), 20 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index e6a9a4d72..0ec92030b 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -216,7 +216,7 @@ local function is_disabled(token) (token.enabled ~= nil and not getval(token.enabled)) end -function render_text(obj,dc,x0,y0,pen,dpen) +function render_text(obj,dc,x0,y0,pen,dpen,disabled) local width = 0 for iline,line in ipairs(obj.text_lines) do local x = 0 @@ -246,7 +246,7 @@ function render_text(obj,dc,x0,y0,pen,dpen) local keypen if dc then - if is_disabled(token) then + if disabled or is_disabled(token) then dc:pen(getval(token.dpen) or dpen) keypen = COLOR_GREEN else @@ -305,6 +305,8 @@ Label = defclass(Label, Widget) Label.ATTRS{ text_pen = COLOR_WHITE, text_dpen = COLOR_DARKGREY, + disabled = DEFAULT_NIL, + enabled = DEFAULT_NIL, auto_height = true, auto_width = false, } @@ -346,11 +348,13 @@ function Label:getTextWidth() end function Label:onRenderBody(dc) - render_text(self,dc,0,0,self.text_pen,self.text_dpen) + render_text(self,dc,0,0,self.text_pen,self.text_dpen,is_disabled(self)) end function Label:onInput(keys) - return check_text_keys(self, keys) + if not is_disabled(self) then + return check_text_keys(self, keys) + end end ---------- @@ -376,7 +380,6 @@ SECONDSCROLL = { List.ATTRS{ text_pen = COLOR_CYAN, cursor_pen = COLOR_LIGHTCYAN, - cursor_dpen = DEFAULT_NIL, inactive_pen = DEFAULT_NIL, on_select = DEFAULT_NIL, on_submit = DEFAULT_NIL, @@ -417,7 +420,9 @@ function List:getChoices() end function List:getSelected() - return self.selected, self.choices[self.selected] + if #self.choices > 0 then + return self.selected, self.choices[self.selected] + end end function List:getContentWidth() @@ -485,15 +490,11 @@ function List:onRenderBody(dc) for i = top,iend do local obj = choices[i] local current = (i == self.selected) - local cur_pen = self.text_pen - local cur_dpen = cur_pen + local cur_pen = self.cursor_pen + local cur_dpen = self.text_pen - if current and active then - cur_pen = self.cursor_pen - cur_dpen = self.cursor_dpen or self.text_pen - elseif current then + if not self.active then cur_pen = self.inactive_pen or self.cursor_pen - cur_dpen = self.inactive_pen or self.text_pen end local y = (i - top)*self.row_height @@ -503,11 +504,15 @@ function List:onRenderBody(dc) if type(obj.icon) == 'table' then dc:char(nil,obj.icon) else - dc:string(obj.icon, obj.icon_pen or cur_pen) + if current then + dc:string(obj.icon, obj.icon_pen or cur_pen) + else + dc:string(obj.icon, obj.icon_pen or cur_dpen) + end end end - render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen) + render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current) if obj.key then local keystr = gui.getKeyDisplay(obj.key) @@ -620,7 +625,9 @@ end function FilteredList:getSelected() local i,v = self.list:getSelected() - return map_opttab(self.choice_index, i), v + if i then + return map_opttab(self.choice_index, i), v + end end function FilteredList:getContentWidth() diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index b58a0763c..29538b6c9 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -306,8 +306,9 @@ public: void setGoalCount(int v) { config.ival(0) = v; } int goalGap() { - int gcnt = std::max(1, goalCount()/2); - return std::min(gcnt, config.ival(1) <= 0 ? 5 : config.ival(1)); + int cval = (config.ival(1) <= 0) ? 5 : config.ival(1); + int cmax = std::max(goalCount()-5, goalCount()/2); + return std::max(1, std::min(cmax, cval)); } void setGoalGap(int v) { config.ival(1) = v; } diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index 17413d46e..c5d28cb98 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -36,6 +36,8 @@ JobConstraints.ATTRS { frame_background = COLOR_BLACK, } +local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false } + function JobConstraints:init(args) self.building = dfhack.job.getHolder(self.job) @@ -56,13 +58,54 @@ function JobConstraints:init(args) }, widgets.List{ view_id = 'list', - frame = { t = 2, b = 2 }, + frame = { t = 2, b = 6 }, row_height = 4, scroll_keys = widgets.SECONDSCROLL, }, + widgets.Label{ + frame = { l = 0, b = 3 }, + enabled = self:callback('isAnySelected'), + text = { + { key = 'BUILDING_TRIGGER_ENABLE_CREATURE', + text = function() + local cons = self:getCurConstraint() or null_cons + if cons.goal_by_count then + return ': Count stacks ' + else + return ': Count items ' + end + end, + on_activate = self:callback('onChangeUnit') }, + { key = 'BUILDING_TRIGGER_ENABLE_MAGMA', text = ': Modify', + on_activate = self:callback('onEditRange') }, + NEWLINE, ' ', + { key = 'BUILDING_TRIGGER_MIN_SIZE_DOWN', + on_activate = self:callback('onIncRange', 'goal_gap', 5) }, + { key = 'BUILDING_TRIGGER_MIN_SIZE_UP', + on_activate = self:callback('onIncRange', 'goal_gap', -1) }, + { text = function() + local cons = self:getCurConstraint() or null_cons + return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap) + end }, + { key = 'BUILDING_TRIGGER_MAX_SIZE_DOWN', + on_activate = self:callback('onIncRange', 'goal_value', -1) }, + { key = 'BUILDING_TRIGGER_MAX_SIZE_UP', + on_activate = self:callback('onIncRange', 'goal_value', 5) }, + { text = function() + local cons = self:getCurConstraint() or null_cons + return string.format(': Max %-4d', cons.goal_value) + end }, + } + }, widgets.Label{ frame = { l = 0, b = 0 }, text = { + { key = 'CUSTOM_N', text = ': New limit, ', + on_activate = self:callback('onNewConstraint') }, + { key = 'CUSTOM_X', text = ': Delete', + enabled = self:callback('isAnySelected'), + on_activate = self:callback('onDeleteConstraint') }, + NEWLINE, NEWLINE, { key = 'LEAVESCREEN', text = ': Back', on_activate = self:callback('dismiss') } } @@ -157,7 +200,14 @@ function JobConstraints:initListChoices(clist) local matflags = {} list_flags(matflags, cons.mat_mask) if #matflags > 0 then - matflagstr = 'class: '..table.concat(matflags, ', ') + matflags[1] = 'any '..matflags[1] + if matstr == 'any material' then + matstr = table.concat(matflags, ', ') + matflags = {} + end + end + if #matflags > 0 then + matflagstr = table.concat(matflags, ', ') end table.insert(choices, { @@ -172,6 +222,84 @@ function JobConstraints:initListChoices(clist) self.subviews.list:setChoices(choices) end +function JobConstraints:isAnySelected() + return self.subviews.list:getSelected() ~= nil +end + +function JobConstraints:getCurConstraint() + local i,v = self.subviews.list:getSelected() + if v then return v.obj end +end + +function JobConstraints:getCurUnit() + local cons = self:getCurConstraint() + if cons and cons.goal_by_count then + return 'stacks' + else + return 'items' + end +end + +function JobConstraints:saveConstraint(cons) + workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap) + self:initListChoices() +end + +function JobConstraints:onChangeUnit() + local cons = self:getCurConstraint() + cons.goal_by_count = not cons.goal_by_count + self:saveConstraint(cons) +end + +function JobConstraints:onEditRange() + local cons = self:getCurConstraint() + dlg.showInputPrompt( + 'Input Range', + 'Enter the new constraint range:', + COLOR_WHITE, + (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value, + function(text) + local maxv = string.match(text, '^%s*(%d+)%s*$') + if maxv then + cons.goal_value = maxv + return self:saveConstraint(cons) + end + local minv,maxv = string.match(text, '^%s*(%d+)-(%d+)%s*$') + if minv and maxv and minv ~= maxv then + cons.goal_value = math.max(minv,maxv) + cons.goal_gap = math.abs(maxv-minv) + return self:saveConstraint(cons) + end + dlg.showMessage('Invalid Range', 'This range is invalid: '..text, COLOR_LIGHTRED) + end + ) +end + +function JobConstraints:onIncRange(field, delta) + local cons = self:getCurConstraint() + if not cons.goal_by_count then + delta = delta * 5 + end + cons[field] = math.max(1, cons[field] + delta) + self:saveConstraint(cons) +end + +function JobConstraints:onNewConstraint() +end + +function JobConstraints:onDeleteConstraint() + local cons = self:getCurConstraint() + dlg.showYesNoPrompt( + 'Delete Constraint', + 'Really delete the current constraint?', + COLOR_YELLOW, + function() + workflow.deleteConstraint(cons.token) + self:initListChoices() + end + ) +end + function JobConstraints:onInput(keys) if self:propagateMoveKeys(keys) then if df.global.world.selected_building ~= self.building then From 59ec9b304e70d756cefe7f90eff986e9a1c44d06 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 24 Oct 2012 21:49:30 +0400 Subject: [PATCH 166/196] Implement adding workflow constraints from gui/workflow. --- library/lua/utils.lua | 28 +++- library/modules/Materials.cpp | 2 +- plugins/lua/workflow.lua | 261 ++++++++++++++++++++++++++++++++++ plugins/workflow.cpp | 9 +- scripts/gui/workflow.lua | 110 +++++++++++--- 5 files changed, 384 insertions(+), 26 deletions(-) diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 2507c9964..8070b85de 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -283,6 +283,16 @@ function clone_with_default(obj,default,force) return rv end +function parse_bitfield_int(value, type_ref) + local res = {} + for i,v in ipairs(type_ref) do + if bit32.extract(value, i) ~= 0 then + res[v] = true + end + end + return res +end + -- Sort a vector or lua table function sort_vector(vector,field,cmp) local fcmp = compare_field(field,cmp) @@ -304,16 +314,26 @@ end -- Linear search -function linear_index(vector,obj) +function linear_index(vector,key,field) local min,max if df.isvalid(vector) then min,max = 0,#vector-1 else min,max = 1,#vector end - for i=min,max do - if vector[i] == obj then - return i + if field then + for i=min,max do + local obj = vector[i] + if obj[field] == key then + return i, obj + end + end + else + for i=min,max do + local obj = vector[i] + if obj == key then + return i, obj + end end end return nil diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index a94d49181..454fdf66d 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -312,7 +312,7 @@ std::string MaterialInfo::getToken() else if (index == 1) return "COAL:CHARCOAL"; } - return material->id; + return material->id + ":NONE"; case Inorganic: return "INORGANIC:" + inorganic->id; case Creature: diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index 748484052..598bf8841 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -1,5 +1,7 @@ local _ENV = mkmodule('plugins.workflow') +local utils = require 'utils' + --[[ Native functions: @@ -8,7 +10,266 @@ local _ENV = mkmodule('plugins.workflow') * setEnabled(enable) * listConstraints([job]) -> {...} * setConstraint(token, by_count, goal[, gap]) -> {...} + * deleteConstraint(token) -> true/false --]] +local reaction_id_cache = nil + +if dfhack.is_core_context then + dfhack.onStateChange[_ENV] = function(code) + if code == SC_MAP_LOADED then + reaction_id_cache = nil + end + end +end + +local function get_reaction(name) + if not reaction_id_cache then + reaction_id_cache = {} + for i,v in ipairs(df.global.world.raws.reactions) do + reaction_id_cache[v.code] = i + end + end + local id = reaction_id_cache[name] or -1 + return id, df.reaction.find(id) +end + +local job_outputs = {} + +function job_outputs.CustomReaction(callback, job) + local rid, r = get_reaction(job.reaction_name) + + for i,prod in ipairs(r.products) do + if df.reaction_product_itemst:is_instance(prod) then + local mat_type, mat_index = prod.mat_type, prod.mat_index + local mat_mask + + local get_mat_prod = prod.flags.GET_MATERIAL_PRODUCT + if get_mat_prod or prod.flags.GET_MATERIAL_SAME then + local reagent_code = prod.get_material.reagent_code + local reagent_idx, src = utils.linear_index(r.reagents, reagent_code, 'code') + if not reagent_idx then goto continue end + + local item_idx, jitem = utils.linear_index(job.job_items, reagent_idx, 'reagent_index') + if jitem then + mat_type, mat_index = jitem.mat_type, jitem.mat_index + else + if not df.reaction_reagent_itemst:is_instance(src) then goto continue end + mat_type, mat_index = src.mat_type, src.mat_index + end + + if get_mat_prod then + local mat = dfhack.matinfo.decode(mat_type, mat_index) + + mat_type, mat_index = -1, -1 + + if mat then + local p_code = prod.get_material.product_code + local rp = mat.material.reaction_product + local idx = utils.linear_index(rp.id, p_code) + if not idx then + goto continue + end + mat_type, mat_index = rp.material.mat_type[idx], rp.material.mat_index[idx] + else + if code == "SOAP_MAT" then + mat_mask = { soap = true } + end + end + end + end + + callback{ + is_craft = prod.flags.CRAFTS, + item_type = prod.item_type, item_subtype = prod.item_subtype, + mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask + } + end + ::continue:: + end +end + +local function guess_job_material(job) + if job.job_type == df.job_type.PrepareMeal then + return -1, -1, nil + end + + local mat_type, mat_index = job.mat_type, job.mat_index + local mask_whole = job.material_category.whole + local mat_mask + + local jmat = df.job_type.attrs[job.job_type].material + if jmat then + mat_type, mat_index = df.builtin_mats[jmat] or -1, -1 + if mat_type < 0 and df.job_material_category[jmat] then + mat_mask = { [jmat] = true } + end + end + + if not mat_mask and mask_whole ~= 0 then + mat_mask = utils.parse_bitfield_int(mask_whole, df.job_material_category) + if mat_mask.wood2 then + mat_mask.wood = true + mat_mask.wood2 = nil + end + end + + if mat_type < 0 and #job.job_items > 0 then + local item0 = job.job_items[0] + if #job.job_items == 1 or item0.item_type == df.item_type.PLANT then + mat_type, mat_index = item0.mat_type, item0.mat_index + + if item0.item_type == df.item_type.WOOD then + mat_mask = mat_mask or {} + mat_mask.wood = true + end + end + end + + return mat_type, mat_index, mat_mask +end + +function default_output(callback, job, mat_type, mat_index, mat_mask) + local itype = df.job_type.attrs[job.job_type].item + if itype >= 0 then + local subtype = nil + if df.item_type.attrs[itype].is_rawable then + subtype = job.item_subtype + end + callback{ + item_type = itype, item_subtype = subtype, + mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask + } + end +end + +function job_outputs.SmeltOre(callback, job) + local mat = dfhack.matinfo.decode(job.job_items[0]) + if mat and mat.inorganic then + for i,v in ipairs(mat.inorganic.metal_ore.mat_index) do + callback{ item_type = df.item_type.BAR, mat_type = 0, mat_index = v } + end + else + callback{ item_type = df.item_type.BAR, mat_type = 0 } + end +end + +function job_outputs.ExtractMetalStrands(callback, job) + local mat = dfhack.matinfo.decode(job.job_items[0]) + if mat and mat.inorganic then + for i,v in ipairs(mat.inorganic.thread_metal.mat_index) do + callback{ item_type = df.item_type.THREAD, mat_type = 0, mat_index = v } + end + else + callback{ item_type = df.item_type.THREAD, mat_type = 0 } + end +end + +function job_outputs.PrepareMeal(callback, job) + if job.mat_type ~= -1 then + for i,v in ipairs(df.global.world.raws.itemdefs.food) do + if v.level == job.mat_type then + callback{ item_type = df.item_type.FOOD, item_subtype = i } + end + end + else + callback{ item_type = df.item_type.FOOD } + end +end + +function job_outputs.MakeCrafts(callback, job) + local mat_type, mat_index, mat_mask = guess_job_material(job) + callback{ is_craft = true, mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask } +end + +local plant_products = { + BrewDrink = 'DRINK', + MillPlants = 'MILL', + ProcessPlants = 'THREAD', + ProcessPlantsBag = 'LEAVES', + ProcessPlantsBarrel = 'EXTRACT_BARREL', + ProcessPlantsVial = 'EXTRACT_VIAL', + ExtractFromPlants = 'EXTRACT_STILL_VIAL', +} + +for job,flag in pairs(plant_products) do + local ttag = 'type_'..string.lower(flag) + local itag = 'idx_'..string.lower(flag) + job_outputs[job] = function(callback, job) + local mat_type, mat_index = -1, -1 + local mat = dfhack.matinfo.decode(job.job_items[0]) + if mat and mat.plant and mat.plant.flags[flag] then + mat_type = mat.plant.material_defs[ttag] + mat_index = mat.plant.material_defs[itag] + end + default_output(callback, job, mat_type, mat_index) + end +end + +local function enum_job_outputs(callback, job) + local handler = job_outputs[df.job_type[job.job_type]] + if handler then + handler(callback, job) + else + default_output(callback, job, guess_job_material(job)) + end +end + +function listJobOutputs(job) + local res = {} + enum_job_outputs(curry(table.insert, res), job) + return res +end + +function constraintToToken(cspec) + local token + if cspec.is_craft then + token = 'CRAFTS' + else + token = df.item_type[cspec.item_type] or error('invalid item type: '..cspec.item_type) + if cspec.item_subtype and cspec.item_subtype >= 0 then + local def = dfhack.items.getSubtypeDef(cspec.item_type, cspec.item_subtype) + if def then + token = token..':'..def.id + else + error('invalid subtype '..cspec.item_subtype..' of '..token) + end + end + end + local mask_part + if cspec.mat_mask then + local lst = {} + for n,v in pairs(cspec.mat_mask) do + if v then table.insert(lst,n) end + end + mask_part = table.concat(lst, ',') + end + local mat_part + if cspec.mat_type and cspec.mat_type >= 0 then + local mat = dfhack.matinfo.decode(cspec.mat_type, cspec.mat_index or -1) + if mat then + mat_part = mat:getToken() + else + error('invalid material: '..cspec.mat_type..':'..(cspec.mat_index or -1)) + end + end + local qpart + if cspec.quality and cspec.quality > 0 then + qpart = df.item_quality[cspec.quality] or error('invalid quality: '..cspec.quality) + end + + if mask_part or mat_part or qpart then + token = token .. '/' .. (mask_part or '') + if mat_part or qpart then + token = token .. '/' .. (mat_part or '') + if qpart then + token = token .. '/' .. (qpart or '') + end + end + end + + return token +end + return _ENV diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 29538b6c9..a6bb3a41a 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -323,6 +323,7 @@ public: void init(const std::string &str) { config.val() = str; + config.ival(0) = 10; config.ival(2) = 0; } @@ -1481,7 +1482,7 @@ static int setConstraint(lua_State *L) { auto token = luaL_checkstring(L, 1); bool by_count = lua_toboolean(L, 2); - int count = luaL_checkint(L, 3); + int count = luaL_optint(L, 3, -1); int gap = luaL_optint(L, 4, -1); color_ostream &out = *Lua::GetOutput(L); @@ -1491,8 +1492,10 @@ static int setConstraint(lua_State *L) luaL_error(L, "invalid constraint: %s", token); icv->setGoalByCount(by_count); - icv->setGoalCount(count); - icv->setGoalGap(gap); + if (!lua_isnil(L, 3)) + icv->setGoalCount(count); + if (!lua_isnil(L, 4)) + icv->setGoalGap(gap); process_constraints(out); push_constraint(L, icv); diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index c5d28cb98..9dfb7dd76 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -125,10 +125,13 @@ end function describe_item_type(iobj) local itemline = 'any item' - if iobj.item_type >= 0 then + if iobj.is_craft then + itemline = 'any craft' + elseif iobj.item_type >= 0 then itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type - local def = dfhack.items.getSubtypeDef(iobj.item_type, iobj.item_subtype) - local count = dfhack.items.getSubtypeCount(iobj.item_type, iobj.item_subtype) + local subtype = iobj.item_subtype or -1 + local def = dfhack.items.getSubtypeDef(iobj.item_type, subtype) + local count = dfhack.items.getSubtypeCount(iobj.item_type, subtype) if def then itemline = def.name elseif count >= 0 then @@ -139,14 +142,14 @@ function describe_item_type(iobj) end function is_caste_mat(iobj) - return dfhack.items.isCasteMaterial(iobj.item_type) + return dfhack.items.isCasteMaterial(iobj.item_type or -1) end function describe_material(iobj) local matline = 'any material' if is_caste_mat(iobj) then - matline = 'material not applicable' - elseif iobj.mat_type >= 0 then + matline = 'no material' + elseif (iobj.mat_type or -1) >= 0 then local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) if info then matline = info:toString() @@ -157,12 +160,16 @@ function describe_material(iobj) return matline end -function list_flags(list, bitfield) - for name,val in pairs(bitfield) do - if val then - table.insert(list, name) +function list_flags(bitfield) + local list = {} + if bitfield then + for name,val in pairs(bitfield) do + if val then + table.insert(list, name) + end end end + return list end function JobConstraints:initListChoices(clist) @@ -186,19 +193,13 @@ function JobConstraints:initListChoices(clist) elseif cons.request == 'suspend' then order_pen = COLOR_RED end - local itemstr - if cons.is_craft then - itemstr = 'any craft' - else - itemstr = describe_item_type(cons) - end + local itemstr = describe_item_type(cons) if cons.min_quality > 0 then itemstr = itemstr .. ' ('..df.item_quality[cons.min_quality]..')' end local matstr = describe_material(cons) local matflagstr = '' - local matflags = {} - list_flags(matflags, cons.mat_mask) + local matflags = list_flags(cons.mat_mask) if #matflags > 0 then matflags[1] = 'any '..matflags[1] if matstr == 'any material' then @@ -284,7 +285,80 @@ function JobConstraints:onIncRange(field, delta) self:saveConstraint(cons) end +function make_constraint_variants(outputs) + local variants = {} + local known = {} + local function register(cons) + cons.token = workflow.constraintToToken(cons) + if not known[cons.token] then + known[cons.token] = true + table.insert(variants, cons) + end + end + + local generic = {} + local anymat = {} + for i,cons in ipairs(outputs) do + local mask = cons.mat_mask + if (cons.mat_type or -1) >= 0 then + cons.mat_mask = nil + end + register(cons) + if mask then + table.insert(generic, { + item_type = cons.item_type, + item_subtype = cons.item_subtype, + is_craft = cons.is_craft, + mat_mask = mask + }) + end + table.insert(anymat, { + item_type = cons.item_type, + item_subtype = cons.item_subtype, + is_craft = cons.is_craft + }) + end + for i,cons in ipairs(generic) do register(cons) end + for i,cons in ipairs(anymat) do register(cons) end + + return variants +end + function JobConstraints:onNewConstraint() + local outputs = workflow.listJobOutputs(self.job) + if #outputs == 0 then + dlg.showMessage('Unsupported', 'Workflow cannot guess the outputs of this job.', COLOR_LIGHTRED) + return + end + + local variants = make_constraint_variants(outputs) + + local choices = {} + for i,cons in ipairs(variants) do + local itemstr = describe_item_type(cons) + local matstr = describe_material(cons) + local matflags = list_flags(cons.mat_mask or {}) + if #matflags > 0 then + local fstr = table.concat(matflags, '/') + if matstr == 'any material' then + matstr = 'any '..fstr + else + matstr = 'any '..fstr..' '..matstr + end + end + + table.insert(choices, { text = itemstr..' of '..matstr, obj = cons }) + end + + dlg.showListPrompt( + 'Job Outputs', + 'Select one of the job outputs:', + COLOR_WHITE, + choices, + function(idx,item) + self:saveConstraint(item.obj) + end + ) end function JobConstraints:onDeleteConstraint() From a7f1ceb75d79ab1fc2d2a28c44961f7d8767a9c9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 24 Oct 2012 23:01:31 +0400 Subject: [PATCH 167/196] Link lua to workflow. --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 91d578215..2f663f805 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -100,7 +100,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(stockpiles stockpiles.cpp) DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) DFHACK_PLUGIN(jobutils jobutils.cpp) - DFHACK_PLUGIN(workflow workflow.cpp) + DFHACK_PLUGIN(workflow workflow.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(showmood showmood.cpp) DFHACK_PLUGIN(fixveins fixveins.cpp) DFHACK_PLUGIN(fixpositions fixpositions.cpp) From 2d83b4fa39f58a52104206e4ce732dc094abca5c Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 24 Oct 2012 14:07:46 -0500 Subject: [PATCH 168/196] We don't need docutils --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 14436dcff..41c38bd44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,7 +145,7 @@ include_directories(depends/clsocket/src) add_subdirectory(depends) -find_package(Docutils) +#find_package(Docutils) #set (RST_FILES #"Readme" From a1dd31aab32bdecfb360cf8bb40e5c7c836a6deb Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 25 Oct 2012 12:09:39 +0400 Subject: [PATCH 169/196] Tweak the mechanics for showing and rendering lua screens. - Place new screens below any dismissed ones on top. - When asked to render a dismissed lua screen, call render() on its parent to avoid producing a black screen frame when unlucky. --- library/lua/gui.lua | 9 +++++---- library/lua/gui/dwarfmode.lua | 6 ++---- library/modules/Screen.cpp | 7 ++++++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 18b0d67d8..15d03742f 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -513,17 +513,18 @@ function Screen:sendInputToParent(...) end end -function Screen:show(below) +function Screen:show(parent) if self._native then error("This screen is already on display") end - self:onAboutToShow(below) - if not dscreen.show(self, below) then + parent = parent or dfhack.gui.getCurViewscreen(true) + self:onAboutToShow(parent) + if not dscreen.show(self, parent.child) then error('Could not show screen') end end -function Screen:onAboutToShow() +function Screen:onAboutToShow(parent) end function Screen:onShow() diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index cef52ae24..8c537639b 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -373,10 +373,8 @@ function DwarfOverlay:simulateCursorMovement(keys, anchor) end end -function DwarfOverlay:onAboutToShow(below) - local screen = dfhack.gui.getCurViewscreen() - if below then screen = below.parent end - if not df.viewscreen_dwarfmodest:is_instance(screen) then +function DwarfOverlay:onAboutToShow(parent) + if not df.viewscreen_dwarfmodest:is_instance(parent) then error("This screen requires the main dwarfmode view") end end diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 29f718266..d89b3688d 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -639,7 +639,12 @@ dfhack_lua_viewscreen::~dfhack_lua_viewscreen() void dfhack_lua_viewscreen::render() { - if (Screen::isDismissed(this)) return; + if (Screen::isDismissed(this)) + { + if (parent) + parent->render(); + return; + } dfhack_viewscreen::render(); From 8eebfa007c60f210a365c91af029152a4a0c689d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 25 Oct 2012 12:15:18 +0400 Subject: [PATCH 170/196] Tweak the workflow gui script to make the UI operate smoother. --- library/lua/utils.lua | 17 +++++ plugins/lua/workflow.lua | 51 ++++++++++++-- plugins/workflow.cpp | 21 +++--- scripts/gui/workflow.lua | 125 +++++++++++------------------------ scripts/gui/workshop-job.lua | 38 ++++++++--- 5 files changed, 144 insertions(+), 108 deletions(-) diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 8070b85de..e7267038c 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -283,7 +283,11 @@ function clone_with_default(obj,default,force) return rv end +-- Parse an integer value into a bitfield table function parse_bitfield_int(value, type_ref) + if value == 0 then + return nil + end local res = {} for i,v in ipairs(type_ref) do if bit32.extract(value, i) ~= 0 then @@ -293,6 +297,19 @@ function parse_bitfield_int(value, type_ref) return res end +-- List the enabled flag names in the bitfield table +function list_bitfield_flags(bitfield, list) + list = list or {} + if bitfield then + for name,val in pairs(bitfield) do + if val then + table.insert(list, name) + end + end + end + return list +end + -- Sort a vector or lua table function sort_vector(vector,field,cmp) local fcmp = compare_field(field,cmp) diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index 598bf8841..391045a96 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -9,7 +9,7 @@ local utils = require 'utils' * isEnabled() * setEnabled(enable) * listConstraints([job]) -> {...} - * setConstraint(token, by_count, goal[, gap]) -> {...} + * setConstraint(token[, by_count, goal, gap]) -> {...} * deleteConstraint(token) -> true/false --]] @@ -40,6 +40,10 @@ local job_outputs = {} function job_outputs.CustomReaction(callback, job) local rid, r = get_reaction(job.reaction_name) + if not r then + return + end + for i,prod in ipairs(r.products) do if df.reaction_product_itemst:is_instance(prod) then local mat_type, mat_index = prod.mat_type, prod.mat_index @@ -239,11 +243,7 @@ function constraintToToken(cspec) end local mask_part if cspec.mat_mask then - local lst = {} - for n,v in pairs(cspec.mat_mask) do - if v then table.insert(lst,n) end - end - mask_part = table.concat(lst, ',') + mask_part = table.concat(utils.list_bitfield_flags(cspec.mat_mask), ',') end local mat_part if cspec.mat_type and cspec.mat_type >= 0 then @@ -272,4 +272,43 @@ function constraintToToken(cspec) return token end +function listWeakenedConstraints(outputs) + local variants = {} + local known = {} + local function register(cons) + cons.token = constraintToToken(cons) + if not known[cons.token] then + known[cons.token] = true + table.insert(variants, cons) + end + end + + local generic = {} + local anymat = {} + for i,cons in ipairs(outputs) do + local mask = cons.mat_mask + if (cons.mat_type or -1) >= 0 then + cons.mat_mask = nil + end + register(cons) + if mask then + table.insert(generic, { + item_type = cons.item_type, + item_subtype = cons.item_subtype, + is_craft = cons.is_craft, + mat_mask = mask + }) + end + table.insert(anymat, { + item_type = cons.item_type, + item_subtype = cons.item_subtype, + is_craft = cons.is_craft + }) + end + for i,cons in ipairs(generic) do register(cons) end + for i,cons in ipairs(anymat) do register(cons) end + + return variants +end + return _ENV diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index a6bb3a41a..19709b68c 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1452,19 +1452,23 @@ static void push_constraint(lua_State *L, ItemConstraint *cv) static int listConstraints(lua_State *L) { auto job = Lua::CheckDFObject(L, 1); - ProtectedJob *pj = NULL; - if (job) - pj = get_known(job->id); - if (!enabled || (job && !pj)) - { - lua_pushnil(L); + lua_pushnil(L); + + if (!enabled || (job && !isSupportedJob(job))) return 1; - } color_ostream &out = *Lua::GetOutput(L); update_data_structures(out); + ProtectedJob *pj = NULL; + if (job) + { + pj = get_known(job->id); + if (!pj) + return 1; + } + lua_newtable(L); auto &vec = (pj ? pj->constraints : constraints); @@ -1491,7 +1495,8 @@ static int setConstraint(lua_State *L) if (!icv) luaL_error(L, "invalid constraint: %s", token); - icv->setGoalByCount(by_count); + if (!lua_isnil(L, 2)) + icv->setGoalByCount(by_count); if (!lua_isnil(L, 3)) icv->setGoalCount(count); if (!lua_isnil(L, 4)) diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index 9dfb7dd76..b0a01a37c 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -26,6 +26,23 @@ function check_enabled(cb,...) end end +function check_repeat(job, cb) + if job.flags['repeat'] then + return cb(job) + else + dlg.showYesNoPrompt( + 'Not Repeat Job', + { 'Workflow only tracks repeating jobs.', NEWLINE, NEWLINE, + 'Press ', { key = 'MENU_CONFIRM' }, ' to make this one repeat.' }, + COLOR_YELLOW, + function() + job.flags['repeat'] = true + return cb(job) + end + ) + end +end + JobConstraints = defclass(JobConstraints, guidm.MenuOverlay) JobConstraints.focus_path = 'workflow-job' @@ -41,14 +58,6 @@ local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false } function JobConstraints:init(args) self.building = dfhack.job.getHolder(self.job) - local status = { text = 'No worker', pen = COLOR_DARKGREY } - local worker = dfhack.job.getWorker(self.job) - if self.job.flags.suspend then - status = { text = 'Suspended', pen = COLOR_RED } - elseif worker then - status = { text = dfhack.TranslateName(dfhack.units.getVisibleName(worker)), pen = COLOR_GREEN } - end - self:addviews{ widgets.Label{ frame = { l = 0, t = 0 }, @@ -160,19 +169,7 @@ function describe_material(iobj) return matline end -function list_flags(bitfield) - local list = {} - if bitfield then - for name,val in pairs(bitfield) do - if val then - table.insert(list, name) - end - end - end - return list -end - -function JobConstraints:initListChoices(clist) +function JobConstraints:initListChoices(clist, sel_token) clist = clist or workflow.listConstraints(self.job) local choices = {} @@ -199,7 +196,7 @@ function JobConstraints:initListChoices(clist) end local matstr = describe_material(cons) local matflagstr = '' - local matflags = list_flags(cons.mat_mask) + local matflags = utils.list_bitfield_flags(cons.mat_mask) if #matflags > 0 then matflags[1] = 'any '..matflags[1] if matstr == 'any material' then @@ -216,11 +213,17 @@ function JobConstraints:initListChoices(clist) goal, ' ', { text = '(now '..curval..')', pen = order_pen }, NEWLINE, ' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', matflagstr }, + token = cons.token, obj = cons }) end - self.subviews.list:setChoices(choices) + local selidx = nil + if sel_token then + selidx = utils.linear_index(choices, sel_token, 'token') + end + + self.subviews.list:setChoices(choices, selidx) end function JobConstraints:isAnySelected() @@ -232,18 +235,9 @@ function JobConstraints:getCurConstraint() if v then return v.obj end end -function JobConstraints:getCurUnit() - local cons = self:getCurConstraint() - if cons and cons.goal_by_count then - return 'stacks' - else - return 'items' - end -end - function JobConstraints:saveConstraint(cons) - workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap) - self:initListChoices() + local out = workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap) + self:initListChoices(nil, out.token) end function JobConstraints:onChangeUnit() @@ -285,45 +279,6 @@ function JobConstraints:onIncRange(field, delta) self:saveConstraint(cons) end -function make_constraint_variants(outputs) - local variants = {} - local known = {} - local function register(cons) - cons.token = workflow.constraintToToken(cons) - if not known[cons.token] then - known[cons.token] = true - table.insert(variants, cons) - end - end - - local generic = {} - local anymat = {} - for i,cons in ipairs(outputs) do - local mask = cons.mat_mask - if (cons.mat_type or -1) >= 0 then - cons.mat_mask = nil - end - register(cons) - if mask then - table.insert(generic, { - item_type = cons.item_type, - item_subtype = cons.item_subtype, - is_craft = cons.is_craft, - mat_mask = mask - }) - end - table.insert(anymat, { - item_type = cons.item_type, - item_subtype = cons.item_subtype, - is_craft = cons.is_craft - }) - end - for i,cons in ipairs(generic) do register(cons) end - for i,cons in ipairs(anymat) do register(cons) end - - return variants -end - function JobConstraints:onNewConstraint() local outputs = workflow.listJobOutputs(self.job) if #outputs == 0 then @@ -331,13 +286,13 @@ function JobConstraints:onNewConstraint() return end - local variants = make_constraint_variants(outputs) + local variants = workflow.listWeakenedConstraints(outputs) local choices = {} for i,cons in ipairs(variants) do local itemstr = describe_item_type(cons) local matstr = describe_material(cons) - local matflags = list_flags(cons.mat_mask or {}) + local matflags = utils.list_bitfield_flags(cons.mat_mask) if #matflags > 0 then local fstr = table.concat(matflags, '/') if matstr == 'any material' then @@ -352,7 +307,7 @@ function JobConstraints:onNewConstraint() dlg.showListPrompt( 'Job Outputs', - 'Select one of the job outputs:', + 'Select one of the possible outputs:', COLOR_WHITE, choices, function(idx,item) @@ -390,15 +345,13 @@ end check_enabled(function() local job = dfhack.gui.getSelectedJob() - if not job.flags['repeat'] then - dlg.showMessage('Not Supported', 'Workflow only tracks repeat jobs.', COLOR_LIGHTRED) - return - end - local clist = workflow.listConstraints(job) - if not clist then - dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) - return - end - JobConstraints{ job = job, clist = clist }:show() + check_repeat(job, function(job) + local clist = workflow.listConstraints(job) + if not clist then + dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) + return + end + JobConstraints{ job = job, clist = clist }:show() + end) end) diff --git a/scripts/gui/workshop-job.lua b/scripts/gui/workshop-job.lua index 74e33595f..c4e203614 100644 --- a/scripts/gui/workshop-job.lua +++ b/scripts/gui/workshop-job.lua @@ -220,18 +220,40 @@ function JobDetails:setMaterial(obj, mat_type, mat_index) obj.iobj.mat_index = mat_index end +function JobDetails:findUnambiguousItem(iobj) + local count = 0 + local itype + + for i = 0,df.item_type._last_item do + if dfhack.job.isSuitableItem(iobj, i, -1) then + count = count + 1 + if count > 1 then return nil end + itype = i + end + end + + return itype +end + function JobDetails:onChangeMat() local idx, obj = self.subviews.list:getSelected() if obj.iobj.item_type == -1 and obj.iobj.mat_type == -1 then - dlg.showMessage( - 'Bug Alert', - { 'Please set a specific item type first.\n\n', - 'Otherwise the material will be matched\n', - 'incorrectly due to a limitation in DF code.' }, - COLOR_YELLOW - ) - return + -- If the job allows only one specific item type, use it + local vitype = self:findUnambiguousItem(obj.iobj) + + if vitype then + obj.iobj.item_type = vitype + else + dlg.showMessage( + 'Bug Alert', + { 'Please set a specific item type first.\n\n', + 'Otherwise the material will be matched\n', + 'incorrectly due to a limitation in DF code.' }, + COLOR_YELLOW + ) + return + end end guimat.MaterialDialog{ From 79d2cb1a5cef1c94137264b4f0de135bd0f4c841 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 25 Oct 2012 12:44:23 +0400 Subject: [PATCH 171/196] Remove the C++ version of the job output deduction code and switch to lua. --- dfhack.init-example | 2 +- plugins/lua/workflow.lua | 18 +++- plugins/workflow.cpp | 191 ++++++--------------------------------- 3 files changed, 44 insertions(+), 167 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index d4c7dc233..2cdb114be 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -79,7 +79,7 @@ keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job # workflow front-end -keybinding add Ctrl-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow +keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow ############################ # UI and game logic tweaks # diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index 391045a96..4c011b24c 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -64,12 +64,12 @@ function job_outputs.CustomReaction(callback, job) end if get_mat_prod then + local p_code = prod.get_material.product_code local mat = dfhack.matinfo.decode(mat_type, mat_index) mat_type, mat_index = -1, -1 if mat then - local p_code = prod.get_material.product_code local rp = mat.material.reaction_product local idx = utils.linear_index(rp.id, p_code) if not idx then @@ -77,7 +77,7 @@ function job_outputs.CustomReaction(callback, job) end mat_type, mat_index = rp.material.mat_type[idx], rp.material.mat_index[idx] else - if code == "SOAP_MAT" then + if p_code == "SOAP_MAT" then mat_mask = { soap = true } end end @@ -106,7 +106,7 @@ local function guess_job_material(job) local jmat = df.job_type.attrs[job.job_type].material if jmat then mat_type, mat_index = df.builtin_mats[jmat] or -1, -1 - if mat_type < 0 and df.job_material_category[jmat] then + if mat_type < 0 and df.dfhack_material_category[jmat] then mat_mask = { [jmat] = true } end end @@ -220,6 +220,18 @@ local function enum_job_outputs(callback, job) end end +function doEnumJobOutputs(native_cb, job) + local function cb(info) + native_cb( + info.item_type, info.item_subtype, + info.mat_mask, info.mat_type, info.mat_index, + info.is_craft + ) + end + + enum_job_outputs(cb, job) +end + function listJobOutputs(job) local res = {} enum_job_outputs(curry(table.insert, res), job) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 19709b68c..8280adb54 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -379,13 +379,6 @@ static bool isSupportedJob(df::job *job) job->job_type == job_type::CollectSand); } -static void enumLiveJobs(std::map &rv) -{ - df::job_list_link *p = world->job_list.next; - for (; p; p = p->next) - rv[p->item->id] = p->item; -} - static bool isOptionEnabled(unsigned flag) { return config.isValid() && (config.ival(0) & flag) != 0; @@ -828,71 +821,6 @@ static void link_job_constraint(ProtectedJob *pj, df::item_type itype, int16_t i } } -static void compute_custom_job(ProtectedJob *pj, df::job *job) -{ - if (pj->reaction_id < 0) - pj->reaction_id = linear_index(df::reaction::get_vector(), - &df::reaction::code, job->reaction_name); - - df::reaction *r = df::reaction::find(pj->reaction_id); - if (!r) - return; - - for (size_t i = 0; i < r->products.size(); i++) - { - using namespace df::enums::reaction_product_item_flags; - - VIRTUAL_CAST_VAR(prod, df::reaction_product_itemst, r->products[i]); - if (!prod || (prod->item_type < (df::item_type)0 && !prod->flags.is_set(CRAFTS))) - continue; - - MaterialInfo mat(prod); - df::dfhack_material_category mat_mask(0); - - bool get_mat_prod = prod->flags.is_set(GET_MATERIAL_PRODUCT); - if (get_mat_prod || prod->flags.is_set(GET_MATERIAL_SAME)) - { - int reagent_idx = linear_index(r->reagents, &df::reaction_reagent::code, - prod->get_material.reagent_code); - if (reagent_idx < 0) - continue; - - int item_idx = linear_index(job->job_items, &df::job_item::reagent_index, reagent_idx); - if (item_idx >= 0) - mat.decode(job->job_items[item_idx]); - else - { - VIRTUAL_CAST_VAR(src, df::reaction_reagent_itemst, r->reagents[reagent_idx]); - if (!src) - continue; - mat.decode(src); - } - - if (get_mat_prod) - { - std::string code = prod->get_material.product_code; - - if (mat.isValid()) - { - int idx = linear_index(mat.material->reaction_product.id, code); - if (idx < 0) - continue; - - mat.decode(mat.material->reaction_product.material, idx); - } - else - { - if (code == "SOAP_MAT") - mat_mask.bits.soap = true; - } - } - } - - link_job_constraint(pj, prod->item_type, prod->item_subtype, - mat_mask, mat.type, mat.index, prod->flags.is_set(CRAFTS)); - } -} - static void guess_job_material(df::job *job, MaterialInfo &mat, df::dfhack_material_category &mat_mask) { using namespace df::enums::job_type; @@ -934,100 +862,24 @@ static void guess_job_material(df::job *job, MaterialInfo &mat, df::dfhack_mater } } -static void compute_job_outputs(color_ostream &out, ProtectedJob *pj) +static int cbEnumJobOutputs(lua_State *L) { - using namespace df::enums::job_type; - - // Custom reactions handled in another function - df::job *job = pj->job_copy; - - if (job->job_type == CustomReaction) - { - compute_custom_job(pj, job); - return; - } - - // Item type & subtype - df::item_type itype = ENUM_ATTR(job_type, item, job->job_type); - int16_t isubtype = job->item_subtype; - - if (itype == item_type::NONE && job->job_type != MakeCrafts) - return; - - // Item material & material category - MaterialInfo mat; - df::dfhack_material_category mat_mask; - guess_job_material(job, mat, mat_mask); - - // Job-specific code - switch (job->job_type) - { - case SmeltOre: - if (mat.inorganic) - { - std::vector &ores = mat.inorganic->metal_ore.mat_index; - for (size_t i = 0; i < ores.size(); i++) - link_job_constraint(pj, item_type::BAR, -1, 0, 0, ores[i]); - } - return; + auto pj = (ProtectedJob*)lua_touserdata(L, lua_upvalueindex(1)); - case ExtractMetalStrands: - if (mat.inorganic) - { - std::vector &threads = mat.inorganic->thread_metal.mat_index; - for (size_t i = 0; i < threads.size(); i++) - link_job_constraint(pj, item_type::THREAD, -1, 0, 0, threads[i]); - } - return; + lua_settop(L, 6); - case PrepareMeal: - if (job->mat_type != -1) - { - std::vector &food = df::itemdef_foodst::get_vector(); - for (size_t i = 0; i < food.size(); i++) - if (food[i]->level == job->mat_type) - link_job_constraint(pj, item_type::FOOD, i, 0, -1, -1); - return; - } - break; - - case MakeCrafts: - link_job_constraint(pj, item_type::NONE, -1, mat_mask, mat.type, mat.index, true); - return; - -#define PLANT_PROCESS_MAT(flag, tag) \ - if (mat.plant && mat.plant->flags.is_set(plant_raw_flags::flag)) \ - mat.decode(mat.plant->material_defs.type_##tag, \ - mat.plant->material_defs.idx_##tag); \ - else mat.decode(-1); - case BrewDrink: - PLANT_PROCESS_MAT(DRINK, drink); - break; - case MillPlants: - PLANT_PROCESS_MAT(MILL, mill); - break; - case ProcessPlants: - PLANT_PROCESS_MAT(THREAD, thread); - break; - case ProcessPlantsBag: - PLANT_PROCESS_MAT(LEAVES, leaves); - break; - case ProcessPlantsBarrel: - PLANT_PROCESS_MAT(EXTRACT_BARREL, extract_barrel); - break; - case ProcessPlantsVial: - PLANT_PROCESS_MAT(EXTRACT_VIAL, extract_vial); - break; - case ExtractFromPlants: - PLANT_PROCESS_MAT(EXTRACT_STILL_VIAL, extract_still_vial); - break; -#undef PLANT_PROCESS_MAT + df::dfhack_material_category mat_mask(0); + if (!lua_isnil(L, 3)) + Lua::CheckDFAssign(L, &mat_mask, 3); - default: - break; - } + link_job_constraint( + pj, + (df::item_type)luaL_optint(L, 1, -1), luaL_optint(L, 2, -1), + mat_mask, luaL_optint(L, 4, -1), luaL_optint(L, 5, -1), + lua_toboolean(L, 6) + ); - link_job_constraint(pj, itype, isubtype, mat_mask, mat.type, mat.index); + return 0; } static void map_job_constraints(color_ostream &out) @@ -1040,19 +892,32 @@ static void map_job_constraints(color_ostream &out) constraints[i]->is_active = false; } + auto L = Lua::Core::State; + Lua::StackUnwinder frame(L); + + bool ok = Lua::PushModulePublic(out, L, "plugins.workflow", "doEnumJobOutputs"); + if (!ok) + out.printerr("The workflow lua module is not available.\n"); + for (TKnownJobs::const_iterator it = known_jobs.begin(); it != known_jobs.end(); ++it) { ProtectedJob *pj = it->second; pj->constraints.clear(); - if (!pj->isLive()) + if (!ok || !pj->isLive()) continue; if (!melt_active && pj->actual_job->job_type == job_type::MeltMetalObject) melt_active = pj->isResumed(); - compute_job_outputs(out, pj); + // Call the lua module + lua_pushvalue(L, -1); + lua_pushlightuserdata(L, pj); + lua_pushcclosure(L, cbEnumJobOutputs, 1); + Lua::PushDFObject(L, pj->job_copy); + + Lua::SafeCall(out, L, 2, 0); } } From 1f994295b87c26a841a9074cd8451994c0ba6e38 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 25 Oct 2012 13:20:41 +0400 Subject: [PATCH 172/196] Consider assigned vehicles in use, and tweak text color rendering. --- library/lua/gui/widgets.lua | 5 +++-- plugins/workflow.cpp | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 0ec92030b..e526408fb 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -246,11 +246,12 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) local keypen if dc then + local tpen = getval(token.pen) if disabled or is_disabled(token) then - dc:pen(getval(token.dpen) or dpen) + dc:pen(getval(token.dpen) or tpen or dpen) keypen = COLOR_GREEN else - dc:pen(getval(token.pen) or pen) + dc:pen(tpen or pen) keypen = COLOR_LIGHTGREEN end end diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 8280adb54..ca8388413 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -40,6 +40,7 @@ #include "df/plant_raw.h" #include "df/inorganic_raw.h" #include "df/builtin_mats.h" +#include "df/vehicle.h" using std::vector; using std::string; @@ -994,6 +995,15 @@ static bool itemInRealJob(df::item *item) != job_type_class::Hauling; } +static bool isRouteVehicle(df::item *item) +{ + int id = item->getVehicleID(); + if (id < 0) return false; + + auto vehicle = df::vehicle::find(id); + return vehicle && vehicle->route_id >= 0; +} + static void map_job_items(color_ostream &out) { for (size_t i = 0; i < constraints.size(); i++) @@ -1103,6 +1113,7 @@ static void map_job_items(color_ostream &out) item->flags.bits.owned || item->flags.bits.in_chest || item->isAssignedToStockpile() || + isRouteVehicle(item) || itemInRealJob(item) || itemBusy(item)) { From 1b9d11090ff4e3d9a0e6df01ec9560c82a45a182 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 25 Oct 2012 13:44:01 +0200 Subject: [PATCH 173/196] ruby: ANY_FREE/IN_PLAY --- plugins/ruby/building.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 08c12a841..e863ec5d5 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -227,7 +227,7 @@ module DFHack # link bld into other rooms if it is inside their extents or vice versa def building_linkrooms(bld) - world.buildings.other[:ANY_FREE].each { |ob| + world.buildings.other[:IN_PLAY].each { |ob| next if ob.z != bld.z if bld.is_room and bld.room.extents next if ob.is_room or ob.x1 < bld.room.x or ob.x1 >= bld.room.x+bld.room.width or ob.y1 < bld.room.y or ob.y1 >= bld.room.y+bld.room.height From e23052a570cf8a090a5c99b0f0c4c114c4db7620 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 26 Oct 2012 11:52:41 +0400 Subject: [PATCH 174/196] Fix an error in gui/workflow when enabling, and tweak color. --- NEWS | 3 +++ scripts/gui/workflow.lua | 23 ++++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/NEWS b/NEWS index b4712c22d..fc9266401 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,9 @@ DFHack future - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. - gui/workflow: a front-end for the workflow plugin. + Workflow plugin: + - properly considers minecarts assigned to routes busy. + - code for deducing job outputs rewritten in lua for flexibility. DFHack v0.34.11-r2 diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index b0a01a37c..8dc958062 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -9,26 +9,26 @@ local dlg = require 'gui.dialogs' local workflow = require 'plugins.workflow' -function check_enabled(cb,...) +function check_enabled(cb) if workflow.isEnabled() then - return cb(...) + return cb() else dlg.showYesNoPrompt( 'Enable Plugin', - { 'The workflow plugin is not enabled currently.', NEWLINE, NEWLINE + { 'The workflow plugin is not enabled currently.', NEWLINE, NEWLINE, 'Press ', { key = 'MENU_CONFIRM' }, ' to enable it.' }, COLOR_YELLOW, - curry(function(...) + function() workflow.setEnabled(true) - return cb(...) - end,...) + return cb() + end ) end end function check_repeat(job, cb) if job.flags['repeat'] then - return cb(job) + return cb() else dlg.showYesNoPrompt( 'Not Repeat Job', @@ -37,7 +37,7 @@ function check_repeat(job, cb) COLOR_YELLOW, function() job.flags['repeat'] = true - return cb(job) + return cb() end ) end @@ -188,7 +188,7 @@ function JobConstraints:initListChoices(clist, sel_token) if cons.request == 'resume' then order_pen = COLOR_GREEN elseif cons.request == 'suspend' then - order_pen = COLOR_RED + order_pen = COLOR_BLUE end local itemstr = describe_item_type(cons) if cons.min_quality > 0 then @@ -343,9 +343,10 @@ if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Wor qerror("This script requires a workshop job selected in the 'q' mode") end +local job = dfhack.gui.getSelectedJob() + check_enabled(function() - local job = dfhack.gui.getSelectedJob() - check_repeat(job, function(job) + check_repeat(job, function() local clist = workflow.listConstraints(job) if not clist then dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) From b976e01b8cd8a931fb2d8889473946d8a23af0c9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 26 Oct 2012 20:29:21 +0400 Subject: [PATCH 175/196] Follow the change in the base-type of job_skill. --- library/modules/Units.cpp | 3 +-- library/xml | 2 +- plugins/add-spatter.cpp | 4 ++-- plugins/manipulator.cpp | 8 ++++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 6d71f5a15..7a0a7549b 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -889,8 +889,7 @@ int Units::getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust // Retrieve skill from unit soul: - df::enum_field key(skill_id); - auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key); + auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, skill_id); if (skill) { diff --git a/library/xml b/library/xml index b9b2e8c6d..ee38c5446 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b9b2e8c6d2141f13966ed965b3f3ffe924e527db +Subproject commit ee38c5446e527edcdc9d8c3e23a4ae138d213546 diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp index 425ffe9d0..ca37c8ee3 100644 --- a/plugins/add-spatter.cpp +++ b/plugins/add-spatter.cpp @@ -246,7 +246,7 @@ struct product_hook : improvement_product { (df::unit *unit, std::vector *out_items, std::vector *in_reag, std::vector *in_items, - int32_t quantity, int16_t skill, + int32_t quantity, df::job_skill skill, df::historical_entity *entity, df::world_site *site) ) { if (auto product = products[this]) @@ -279,7 +279,7 @@ struct product_hook : improvement_product { break; } - int rating = unit ? Units::getEffectiveSkill(unit, df::job_skill(skill)) : 0; + int rating = unit ? Units::getEffectiveSkill(unit, skill) : 0; int size = int(probability*(1.0f + 0.06f*rating)); // +90% at legendary object->addContaminant( diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index b3852437c..b6d30ab1e 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -298,8 +298,8 @@ bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) { if (sort_skill != job_skill::NONE) { - df::unit_skill *s1 = binsearch_in_vector>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); - df::unit_skill *s2 = binsearch_in_vector>(d2->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); + df::unit_skill *s1 = binsearch_in_vector(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); + df::unit_skill *s2 = binsearch_in_vector(d2->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); int l1 = s1 ? s1->rating : 0; int l2 = s2 ? s2->rating : 0; int e1 = s1 ? s1->experience : 0; @@ -1030,7 +1030,7 @@ void viewscreen_unitlaborsst::render() fg = 9; if (columns[col_offset].skill != job_skill::NONE) { - df::unit_skill *skill = binsearch_in_vector>(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill); + df::unit_skill *skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill); if ((skill != NULL) && (skill->rating || skill->experience)) { int level = skill->rating; @@ -1086,7 +1086,7 @@ void viewscreen_unitlaborsst::render() } else { - df::unit_skill *skill = binsearch_in_vector>(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill); + df::unit_skill *skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill); if (skill) { int level = skill->rating; From c5d8bd9db6f56fb133779425f801107a15314eb9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 26 Oct 2012 23:30:44 +0400 Subject: [PATCH 176/196] Add a tweak to stop stockpiling items on weapon racks and armor stands. --- NEWS | 2 + plugins/tweak.cpp | 110 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/NEWS b/NEWS index fc9266401..f7ccc711d 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,8 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option + New tweaks: + - tweak armory: prevents stockpiling of armor and weapons stored on stands and racks. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index b4af2a4ba..b6ef68f2f 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -47,6 +47,15 @@ #include "df/viewscreen_layer_assigntradest.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_layer_militaryst.h" +#include "df/squad_position.h" +#include "df/item_weaponst.h" +#include "df/item_armorst.h" +#include "df/item_helmst.h" +#include "df/item_pantsst.h" +#include "df/item_shoesst.h" +#include "df/item_glovesst.h" +#include "df/item_shieldst.h" +#include "df/building_armorstandst.h" #include @@ -124,6 +133,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector getSpecificSquad()); + if (!squad) + return false; + + if (any_position) + { + for (size_t i = 0; i < squad->positions.size(); i++) + { + if (binsearch_index(squad->positions[i]->assigned_items, item->id) >= 0) + return true; + } + } + else + { + auto cpos = vector_get(squad->positions, holder->getSpecificPosition()); + if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) + return true; + } + + return false; +} + +static bool is_in_armory(df::item *item, df::building_type btype, bool any_position) +{ + if (item->flags.bits.in_inventory || item->flags.bits.on_ground) + return false; + + auto holder_ref = Items::getGeneralRef(item, general_ref_type::BUILDING_HOLDER); + if (!holder_ref) + return false; + + auto holder = holder_ref->getBuilding(); + if (!holder || holder->getType() != btype) + return false; + + return belongs_to_position(item, holder, any_position); +} + +struct armory_weapon_hook : df::item_weaponst { + typedef df::item_weaponst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) + { + if (is_in_armory(this, building_type::Weaponrack, true)) + return false; + + return INTERPOSE_NEXT(isCollected)(); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(armory_weapon_hook, isCollected); + +template struct armory_hook : Item { + typedef Item interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) + { + if (is_in_armory(this, building_type::Armorstand, false)) + return false; + + return INTERPOSE_NEXT(isCollected)(); + } +}; + +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); + +/*struct armory_armorstand_hook : df::building_armorstandst { + typedef df::building_armorstandst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, canStoreItem, (df::item *item, bool subtract_jobs)) + { + if (specific_squad >= 0 && specific_position >= 0) + return item->isArmorNotClothing() && belongs_to_position(item, this, false); + + return INTERPOSE_NEXT(canStoreItem)(item, subtract_jobs); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(armory_armorstand_hook, canStoreItem);*/ + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -831,6 +930,17 @@ static command_result tweak(color_ostream &out, vector ¶meters) { enable_hook(out, INTERPOSE_HOOK(military_assign_hook, render), parameters); } + else if (cmd == "armory") + { + enable_hook(out, INTERPOSE_HOOK(armory_weapon_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + //enable_hook(out, INTERPOSE_HOOK(armory_armorstand_hook, canStoreItem), parameters); + } else return CR_WRONG_USAGE; From 4c2c6a19115e98d22dcddcf640afb2e9100083d3 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 26 Oct 2012 23:53:18 +0400 Subject: [PATCH 177/196] Fix handling of Collect Webs jobs in workflow. --- NEWS | 1 + library/xml | 2 +- plugins/workflow.cpp | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index f7ccc711d..6b87a52b8 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ DFHack future Workflow plugin: - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. + - logic fix: collecting webs produces silk, and ungathered webs are not thread. DFHack v0.34.11-r2 diff --git a/library/xml b/library/xml index ee38c5446..4a6903dc9 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ee38c5446e527edcdc9d8c3e23a4ae138d213546 +Subproject commit 4a6903dc9b8d4cd21c5c5d74df4e9f74a4dd58dd diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index ca8388413..d46daed44 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1055,6 +1055,8 @@ static void map_job_items(color_ostream &out) break; case item_type::THREAD: + if (item->flags.bits.spider_web) + continue; if (item->getTotalDimension() < 15000) is_invalid = true; break; From 34f33a8c91b0782f6d8dc43de09a8318141b8dca Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 27 Oct 2012 15:35:11 +0400 Subject: [PATCH 178/196] Fix the error message produced by binpatch when a mismatch is detected. --- library/binpatch.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/binpatch.cpp b/library/binpatch.cpp index 815ac5b92..60e2c4ca7 100644 --- a/library/binpatch.cpp +++ b/library/binpatch.cpp @@ -171,8 +171,9 @@ BinaryPatch::State BinaryPatch::checkState(const patch_byte *ptr, size_t len) state |= Applied; else { - cerr << std::hex << bv.offset << ": " << bv.old_val << " " << bv.new_val - << ", but currently " << cv << std::dec << endl; + cerr << std::hex << bv.offset << ": " + << unsigned(bv.old_val) << " " << unsigned(bv.new_val) + << ", but currently " << unsigned(cv) << std::dec << endl; return Conflict; } } From e353f5f03ea9f68be9b5947e29f939dd3e3193f3 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 27 Oct 2012 20:16:27 +0400 Subject: [PATCH 179/196] Add a script to complement the weapon rack binary patch. --- NEWS | 1 + dfhack.init-example | 6 ++ library/lua/class.lua | 10 +- library/lua/gui/widgets.lua | 17 +-- scripts/gui/assign-rack.lua | 202 ++++++++++++++++++++++++++++++++++++ 5 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 scripts/gui/assign-rack.lua diff --git a/NEWS b/NEWS index 6b87a52b8..5103cf921 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,7 @@ DFHack future - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. - gui/workflow: a front-end for the workflow plugin. + - gui/assign-rack: works together with a binary patch to fix weapon racks. Workflow plugin: - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. diff --git a/dfhack.init-example b/dfhack.init-example index 2cdb114be..7dfa86016 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -81,6 +81,9 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job # workflow front-end keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow +# assign weapon racks to squads so that they can be used +keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack + ############################ # UI and game logic tweaks # ############################ @@ -118,3 +121,6 @@ tweak fast-trade tweak military-stable-assign # in same list, color units already assigned to squads in brown & green tweak military-color-assigned + +# stop squad equpment stored on weapon racks and armor stands from being stockpiled +tweak armory diff --git a/library/lua/class.lua b/library/lua/class.lua index bcfff13e2..e18bad9a9 100644 --- a/library/lua/class.lua +++ b/library/lua/class.lua @@ -65,10 +65,14 @@ end local function apply_attrs(obj, attrs, init_table) for k,v in pairs(attrs) do - if v == DEFAULT_NIL then - v = nil + local init_v = init_table[k] + if init_v ~= nil then + obj[k] = init_v + elseif v == DEFAULT_NIL then + obj[k] = nil + else + obj[k] = v end - obj[k] = init_table[k] or v end end diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index e526408fb..e731af068 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -501,14 +501,17 @@ function List:onRenderBody(dc) local y = (i - top)*self.row_height if iw and obj.icon then - dc:seek(0, y) - if type(obj.icon) == 'table' then - dc:char(nil,obj.icon) - else - if current then - dc:string(obj.icon, obj.icon_pen or cur_pen) + local icon = getval(obj.icon) + if icon then + dc:seek(0, y) + if type(icon) == 'table' then + dc:char(nil,icon) else - dc:string(obj.icon, obj.icon_pen or cur_dpen) + if current then + dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen) + else + dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen) + end end end end diff --git a/scripts/gui/assign-rack.lua b/scripts/gui/assign-rack.lua new file mode 100644 index 000000000..d358dfff1 --- /dev/null +++ b/scripts/gui/assign-rack.lua @@ -0,0 +1,202 @@ +-- Assign weapon racks to squads. Requires patch from bug 1445. + +--[[ + + Required patches: + + v0.34.11 linux: http://pastebin.com/mt5EUgFZ + v0.34.11 windows: http://pastebin.com/09nRCybe + +]] + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local widgets = require 'gui.widgets' +local dlg = require 'gui.dialogs' + +AssignRack = defclass(AssignRack, guidm.MenuOverlay) + +AssignRack.focus_path = 'assign-rack' + +AssignRack.ATTRS { + building = DEFAULT_NIL, + frame_inset = 1, + frame_background = COLOR_BLACK, +} + +function list_squads(building,squad_table,squad_list) + local sqlist = building:getSquads() + if not sqlist then + return + end + + for i,v in ipairs(sqlist) do + local obj = df.squad.find(v.squad_id) + if obj then + if not squad_table[v.squad_id] then + squad_table[v.squad_id] = { id = v.squad_id, obj = obj } + table.insert(squad_list, squad_table[v.squad_id]) + end + + -- Set specific use flags + for n,ok in pairs(v.mode) do + if ok then + squad_table[v.squad_id][n] = true + end + end + + -- Check if any use is possible + local btype = building:getType() + if btype == df.building_type.Bed then + if v.mode.sleep then + squad_table[v.squad_id].any = true + end + elseif btype == df.building.Weaponrack then + if v.mode.train or v.mode.indiv_eq then + squad_table[v.squad_id].any = true + end + else + if v.mode.indiv_eq then + squad_table[v.squad_id].any = true + end + end + end + end + + for i,v in ipairs(building.parents) do + list_squads(v, squad_table, squad_list) + end +end + +function filter_invalid(list, id) + for i=#list-1,0,-1 do + local bld = df.building.find(list[i]) + if not bld or bld:getSpecificSquad() ~= id then + list:erase(i) + end + end +end + +function AssignRack:init(args) + self.squad_table = {} + self.squad_list = {} + list_squads(self.building, self.squad_table, self.squad_list) + table.sort(self.squad_list, function(a,b) return a.id < b.id end) + + self.choices = {} + for i,v in ipairs(self.squad_list) do + if v.any and (v.train or v.indiv_eq) then + local name = v.obj.alias + if name == '' then + name = dfhack.TranslateName(v.obj.name, true) + end + + filter_invalid(v.obj.rack_combat, v.id) + filter_invalid(v.obj.rack_training, v.id) + + table.insert(self.choices, { + icon = self:callback('isSelected', v), + icon_pen = COLOR_LIGHTGREEN, + obj = v, + text = { + name, NEWLINE, ' ', + { text = function() + return string.format('%d combat, %d training', #v.obj.rack_combat, #v.obj.rack_training) + end } + } + }) + end + end + + self:addviews{ + widgets.Label{ + frame = { l = 0, t = 0 }, + text = { + 'Assign Weapon Rack' + } + }, + widgets.List{ + view_id = 'list', + frame = { t = 2, b = 2 }, + icon_width = 2, row_height = 2, + scroll_keys = widgets.SECONDSCROLL, + choices = self.choices, + on_submit = self:callback('onSubmit'), + }, + widgets.Label{ + frame = { l = 0, t = 2 }, + text_pen = COLOR_LIGHTRED, + text = 'No appropriate barracks\n\nNote: weapon racks use the\nIndividual equipment flag', + visible = (#self.choices == 0), + }, + widgets.Label{ + frame = { l = 0, b = 0 }, + text = { + { key = 'LEAVESCREEN', text = ': Back', + on_activate = self:callback('dismiss') } + } + }, + } +end + +function AssignRack:isSelected(info) + if self.building.specific_squad == info.id then + return '\xfb' + else + return nil + end +end + +function AssignRack:onSubmit(idx, choice) + local rid = self.building.id + local curid = self.building.specific_squad + + local cur = df.squad.find(curid) + if cur then + utils.erase_sorted(cur.rack_combat, rid) + utils.erase_sorted(cur.rack_training, rid) + end + + self.building.specific_squad = -1 + df.global.ui.equipment.update.buildings = true + + local new = df.squad.find(choice.obj.id) + if new and choice.obj.id ~= curid then + self.building.specific_squad = choice.obj.id + + if choice.obj.indiv_eq then + utils.insert_sorted(new.rack_combat, rid) + end + if choice.obj.train then + utils.insert_sorted(new.rack_training, rid) + end + end +end + +function AssignRack:onInput(keys) + if self:propagateMoveKeys(keys) then + if df.global.world.selected_building ~= self.building then + self:dismiss() + end + else + AssignRack.super.onInput(self, keys) + end +end + +if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some/Weaponrack' then + qerror("This script requires a weapon rack selected in the 'q' mode") +end + +AssignRack{ building = dfhack.gui.getSelectedBuilding() }:show() + +if not already_warned then + already_warned = true + dlg.showMessage( + 'BUG ALERT', + { 'This script requires a binary patch from', NEWLINE, + 'bug 1445 on the tracker. Otherwise the game', NEWLINE, + 'will lose your settings due to a bug.' }, + COLOR_YELLOW + ) +end From 92a32777774e2c96b3ce312056539138544a47b1 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 27 Oct 2012 21:58:40 +0400 Subject: [PATCH 180/196] Add a MemoryPatcher class as an optimization of scattered patchMemory. This class can cache the set of memory regions during its lifetime, and make them writable only once. This avoids e.g. re-reading /proc/*/maps once for every modified vtable in interpose code. --- library/Core.cpp | 65 +++++++++++++++++++++++++++++-------- library/VTableInterpose.cpp | 20 ++++++++---- library/include/DataDefs.h | 3 +- library/include/MemAccess.h | 17 ++++++++++ 4 files changed, 83 insertions(+), 22 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 7766b3591..dc620e805 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1615,15 +1615,27 @@ void ClassNameCheck::getKnownClassNames(std::vector &names) names.push_back(*it); } -bool Process::patchMemory(void *target, const void* src, size_t count) +MemoryPatcher::MemoryPatcher(Process *p) : p(p) +{ + if (!p) + p = Core::getInstance().p; +} + +MemoryPatcher::~MemoryPatcher() +{ + close(); +} + +bool MemoryPatcher::verifyAccess(void *target, size_t count, bool write) { uint8_t *sptr = (uint8_t*)target; uint8_t *eptr = sptr + count; // Find the valid memory ranges - std::vector ranges; - getMemRanges(ranges); + if (ranges.empty()) + p->getMemRanges(ranges); + // Find the ranges that this area spans unsigned start = 0; while (start < ranges.size() && ranges[start].end <= sptr) start++; @@ -1645,24 +1657,49 @@ bool Process::patchMemory(void *target, const void* src, size_t count) if (!ranges[i].valid || !(ranges[i].read || ranges[i].execute) || ranges[i].shared) return false; - // Apply writable permissions & update - bool ok = true; + if (!write) + return true; - for (unsigned i = start; i < end && ok; i++) + // Apply writable permissions & update + for (unsigned i = start; i < end; i++) { - t_memrange perms = ranges[i]; + auto &perms = ranges[i]; + if (perms.write && perms.read) + continue; + + save.push_back(perms); perms.write = perms.read = true; - if (!setPermisions(perms, perms)) - ok = false; + if (!p->setPermisions(perms, perms)) + return false; } - if (ok) - memmove(target, src, count); + return true; +} + +bool MemoryPatcher::write(void *target, const void *src, size_t size) +{ + if (!makeWritable(target, size)) + return false; + + memmove(target, src, size); + return true; +} - for (unsigned i = start; i < end && ok; i++) - setPermisions(ranges[i], ranges[i]); +void MemoryPatcher::close() +{ + for (size_t i = 0; i < save.size(); i++) + p->setPermisions(save[i], save[i]); + + save.clear(); + ranges.clear(); +}; + + +bool Process::patchMemory(void *target, const void* src, size_t count) +{ + MemoryPatcher patcher(this); - return ok; + return patcher.write(target, src, count); } /******************************************************************************* diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index d8a07e830..3f9423b45 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -166,12 +166,12 @@ void *virtual_identity::get_vmethod_ptr(int idx) return vtable[idx]; } -bool virtual_identity::set_vmethod_ptr(int idx, void *ptr) +bool virtual_identity::set_vmethod_ptr(MemoryPatcher &patcher, int idx, void *ptr) { assert(idx >= 0); void **vtable = (void**)vtable_ptr; if (!vtable) return NULL; - return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*)); + return patcher.write(&vtable[idx], &ptr, sizeof(void*)); } /* @@ -344,7 +344,9 @@ void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from) auto last = this; while (last->prev) last = last->prev; - from->set_vmethod_ptr(vmethod_idx, last->saved_chain); + MemoryPatcher patcher; + + from->set_vmethod_ptr(patcher, vmethod_idx, last->saved_chain); // Unlink the chains child_hosts.erase(from); @@ -379,13 +381,15 @@ bool VMethodInterposeLinkBase::apply(bool enable) assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr)); // Apply the new method ptr + MemoryPatcher patcher; + set_chain(old_ptr); if (next_link) { next_link->set_chain(interpose_method); } - else if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) + else if (!host->set_vmethod_ptr(patcher, vmethod_idx, interpose_method)) { set_chain(NULL); return false; @@ -459,7 +463,7 @@ bool VMethodInterposeLinkBase::apply(bool enable) { auto nhost = *it; assert(nhost->interpose_list[vmethod_idx] == old_link); - nhost->set_vmethod_ptr(vmethod_idx, interpose_method); + nhost->set_vmethod_ptr(patcher, vmethod_idx, interpose_method); nhost->interpose_list[vmethod_idx] = this; } @@ -496,9 +500,11 @@ void VMethodInterposeLinkBase::remove() } else { + MemoryPatcher patcher; + // Remove from the list in the identity and vtable host->interpose_list[vmethod_idx] = prev; - host->set_vmethod_ptr(vmethod_idx, saved_chain); + host->set_vmethod_ptr(patcher, vmethod_idx, saved_chain); for (auto it = child_next.begin(); it != child_next.end(); ++it) { @@ -515,7 +521,7 @@ void VMethodInterposeLinkBase::remove() auto nhost = *it; assert(nhost->interpose_list[vmethod_idx] == this); nhost->interpose_list[vmethod_idx] = prev; - nhost->set_vmethod_ptr(vmethod_idx, saved_chain); + nhost->set_vmethod_ptr(patcher, vmethod_idx, saved_chain); if (prev) prev->child_hosts.insert(nhost); } diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 6c5ae5e62..32ffabc32 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -294,6 +294,7 @@ namespace DFHack #endif class DFHACK_EXPORT VMethodInterposeLinkBase; + class MemoryPatcher; class DFHACK_EXPORT virtual_identity : public struct_identity { static std::map known; @@ -313,7 +314,7 @@ namespace DFHack bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); } void *get_vmethod_ptr(int index); - bool set_vmethod_ptr(int index, void *ptr); + bool set_vmethod_ptr(MemoryPatcher &patcher, int index, void *ptr); public: virtual_identity(size_t size, TAllocateFn alloc, diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 0bc027c5c..1b8e687b9 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -315,5 +315,22 @@ namespace DFHack // Get list of names given to ClassNameCheck constructors. static void getKnownClassNames(std::vector &names); }; + + class DFHACK_EXPORT MemoryPatcher + { + Process *p; + std::vector ranges, save; + public: + MemoryPatcher(Process *p = NULL); + ~MemoryPatcher(); + + bool verifyAccess(void *target, size_t size, bool write = false); + bool makeWritable(void *target, size_t size) { + return verifyAccess(target, size, true); + } + bool write(void *target, const void *src, size_t size); + + void close(); + }; } #endif From fdaa2a35a1d3f30ba5b58372536334b7bcc52c2f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 28 Oct 2012 09:34:50 +0400 Subject: [PATCH 181/196] Fix name ambiguity in MemoryPatcher constructor. --- library/Core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index dc620e805..9986c3396 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1615,7 +1615,7 @@ void ClassNameCheck::getKnownClassNames(std::vector &names) names.push_back(*it); } -MemoryPatcher::MemoryPatcher(Process *p) : p(p) +MemoryPatcher::MemoryPatcher(Process *p_) : p(p_) { if (!p) p = Core::getInstance().p; From 4aa1999347f85d83dc27d7ef1e6e4cdd6e07561e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 28 Oct 2012 11:50:28 +0400 Subject: [PATCH 182/196] Add a lua api function for patching multiple individual bytes. --- Lua API.html | 9 ++++++ Lua API.rst | 12 +++++++ library/Core.cpp | 5 +-- library/LuaApi.cpp | 76 ++++++++++++++++++++++++++++++++++++++++++++ library/LuaTools.cpp | 5 ++- 5 files changed, 102 insertions(+), 5 deletions(-) diff --git a/Lua API.html b/Lua API.html index 9c18b6585..be52ea985 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1714,6 +1714,15 @@ global environment, persistent between calls to the script.

    If destination overlaps a completely invalid memory region, or another error occurs, returns false.

    +
  • dfhack.internal.patchBytes(write_table[, verify_table])

    +

    The first argument must be a lua table, which is interpreted as a mapping from +memory addresses to byte values that should be stored there. The second argument +may be a similar table of values that need to be checked before writing anything.

    +

    The function takes care to either apply all of write_table, or none of it. +An empty write_table with a nonempty verify_table can be used to reasonably +safely check if the memory contains certain values.

    +

    Returns true if successful, or nil, error_msg, address if not.

    +
  • dfhack.internal.memmove(dest,src,count)

    Wraps the standard memmove function. Accepts both numbers and refs as pointers.

  • diff --git a/Lua API.rst b/Lua API.rst index 185672816..8dad3663b 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1581,6 +1581,18 @@ and are only documented here for completeness: If destination overlaps a completely invalid memory region, or another error occurs, returns false. +* ``dfhack.internal.patchBytes(write_table[, verify_table])`` + + The first argument must be a lua table, which is interpreted as a mapping from + memory addresses to byte values that should be stored there. The second argument + may be a similar table of values that need to be checked before writing anything. + + The function takes care to either apply all of ``write_table``, or none of it. + An empty ``write_table`` with a nonempty ``verify_table`` can be used to reasonably + safely check if the memory contains certain values. + + Returns *true* if successful, or *nil, error_msg, address* if not. + * ``dfhack.internal.memmove(dest,src,count)`` Wraps the standard memmove function. Accepts both numbers and refs as pointers. diff --git a/library/Core.cpp b/library/Core.cpp index 9986c3396..111774f26 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1657,14 +1657,11 @@ bool MemoryPatcher::verifyAccess(void *target, size_t count, bool write) if (!ranges[i].valid || !(ranges[i].read || ranges[i].execute) || ranges[i].shared) return false; - if (!write) - return true; - // Apply writable permissions & update for (unsigned i = start; i < end; i++) { auto &perms = ranges[i]; - if (perms.write && perms.read) + if ((perms.write || !write) && perms.read) continue; save.push_back(perms); diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 593ac3d8d..6ca1188d9 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1535,6 +1535,81 @@ static int internal_patchMemory(lua_State *L) return 1; } +static int internal_patchBytes(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 2); + + MemoryPatcher patcher; + + if (!lua_isnil(L, 2)) + { + luaL_checktype(L, 2, LUA_TTABLE); + + lua_pushnil(L); + + while (lua_next(L, 2)) + { + uint8_t *addr = (uint8_t*)checkaddr(L, -2, true); + int isnum; + uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum); + if (!isnum) + luaL_error(L, "invalid value in verify table"); + lua_pop(L, 1); + + if (!patcher.verifyAccess(addr, 1, false)) + { + lua_pushnil(L); + lua_pushstring(L, "invalid verify address"); + lua_pushvalue(L, -3); + return 3; + } + + if (*addr != value) + { + lua_pushnil(L); + lua_pushstring(L, "wrong verify value"); + lua_pushvalue(L, -3); + return 3; + } + } + } + + lua_pushnil(L); + + while (lua_next(L, 1)) + { + uint8_t *addr = (uint8_t*)checkaddr(L, -2, true); + int isnum; + uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum); + if (!isnum) + luaL_error(L, "invalid value in write table"); + lua_pop(L, 1); + + if (!patcher.verifyAccess(addr, 1, true)) + { + lua_pushnil(L); + lua_pushstring(L, "invalid write address"); + lua_pushvalue(L, -3); + return 3; + } + } + + lua_pushnil(L); + + while (lua_next(L, 1)) + { + uint8_t *addr = (uint8_t*)checkaddr(L, -2, true); + uint8_t value = (uint8_t)lua_tounsigned(L, -1); + lua_pop(L, 1); + + *addr = value; + } + + lua_pushboolean(L, true); + return 1; +} + static int internal_memmove(lua_State *L) { void *dest = checkaddr(L, 1); @@ -1626,6 +1701,7 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "getVTable", internal_getVTable }, { "getMemRanges", internal_getMemRanges }, { "patchMemory", internal_patchMemory }, + { "patchBytes", internal_patchBytes }, { "memmove", internal_memmove }, { "memcmp", internal_memcmp }, { "memscan", internal_memscan }, diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 6bf218ba0..c052b88aa 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -107,7 +107,8 @@ static void signal_typeid_error(color_ostream *out, lua_State *state, type_identity *type, const char *msg, int val_index, bool perr, bool signal) { - std::string error = stl_sprintf(msg, type->getFullName().c_str()); + std::string typestr = type ? type->getFullName() : "any pointer"; + std::string error = stl_sprintf(msg, typestr.c_str()); if (signal) { @@ -134,6 +135,8 @@ void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_ if (lua_isnil(state, val_index)) return NULL; + if (lua_islightuserdata(state, val_index) && !lua_touserdata(state, val_index)) + return NULL; void *rv = get_object_internal(state, type, val_index, exact_type, false); From fa88ee5f171159e0518e73dea2c351a67fec5a08 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 28 Oct 2012 15:37:16 +0400 Subject: [PATCH 183/196] Update the armory tweak to protect other potential squad equipment. Specifically clothing in cabinets and flasks/backpacks/quivers in boxes. --- plugins/tweak.cpp | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index b6ef68f2f..dd504fee3 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -55,6 +55,9 @@ #include "df/item_shoesst.h" #include "df/item_glovesst.h" #include "df/item_shieldst.h" +#include "df/item_flaskst.h" +#include "df/item_backpackst.h" +#include "df/item_quiverst.h" #include "df/building_armorstandst.h" #include @@ -672,7 +675,11 @@ IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, render); static bool belongs_to_position(df::item *item, df::building *holder, bool any_position) { - auto squad = df::squad::find(holder->getSpecificSquad()); + int sid = holder->getSpecificSquad(); + if (sid < 0) + return false; + + auto squad = df::squad::find(sid); if (!squad) return false; @@ -694,7 +701,7 @@ static bool belongs_to_position(df::item *item, df::building *holder, bool any_p return false; } -static bool is_in_armory(df::item *item, df::building_type btype, bool any_position) +static bool is_in_armory(df::item *item, bool any_position) { if (item->flags.bits.in_inventory || item->flags.bits.on_ground) return false; @@ -704,7 +711,7 @@ static bool is_in_armory(df::item *item, df::building_type btype, bool any_posit return false; auto holder = holder_ref->getBuilding(); - if (!holder || holder->getType() != btype) + if (!holder) return false; return belongs_to_position(item, holder, any_position); @@ -715,7 +722,7 @@ struct armory_weapon_hook : df::item_weaponst { DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { - if (is_in_armory(this, building_type::Weaponrack, true)) + if (is_in_armory(this, true)) return false; return INTERPOSE_NEXT(isCollected)(); @@ -729,7 +736,7 @@ template struct armory_hook : Item { DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { - if (is_in_armory(this, building_type::Armorstand, false)) + if (is_in_armory(this, false)) return false; return INTERPOSE_NEXT(isCollected)(); @@ -742,20 +749,9 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollecte template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); - -/*struct armory_armorstand_hook : df::building_armorstandst { - typedef df::building_armorstandst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, canStoreItem, (df::item *item, bool subtract_jobs)) - { - if (specific_squad >= 0 && specific_position >= 0) - return item->isArmorNotClothing() && belongs_to_position(item, this, false); - - return INTERPOSE_NEXT(canStoreItem)(item, subtract_jobs); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(armory_armorstand_hook, canStoreItem);*/ +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { @@ -939,7 +935,9 @@ static command_result tweak(color_ostream &out, vector ¶meters) enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - //enable_hook(out, INTERPOSE_HOOK(armory_armorstand_hook, canStoreItem), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); } else return CR_WRONG_USAGE; From ed4904fb6641e4cd23dba11ad7221554a0bf2826 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 28 Oct 2012 21:13:28 +0400 Subject: [PATCH 184/196] Add a tweak that makes dwarves haul equipment from stockpiles to the armory. --- NEWS | 1 + dfhack.init-example | 1 + library/xml | 2 +- plugins/tweak.cpp | 152 ++++++++++++++++++++++++++++++++++++++------ 4 files changed, 134 insertions(+), 22 deletions(-) diff --git a/NEWS b/NEWS index 5103cf921..c8a0f5770 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,7 @@ DFHack future - removebadthoughts: add --dry-run option New tweaks: - tweak armory: prevents stockpiling of armor and weapons stored on stands and racks. + - tweak stock-armory: makes dwarves actively haul equipment from stockpiles to the armory. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/dfhack.init-example b/dfhack.init-example index 7dfa86016..2b7ac2cc9 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -124,3 +124,4 @@ tweak military-color-assigned # stop squad equpment stored on weapon racks and armor stands from being stockpiled tweak armory +tweak stock-armory diff --git a/library/xml b/library/xml index 4a6903dc9..4bfc5b197 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 4a6903dc9b8d4cd21c5c5d74df4e9f74a4dd58dd +Subproject commit 4bfc5b197de9ca096a11b9c1594e043bd8ae707a diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index dd504fee3..9dc3ae468 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -10,6 +10,7 @@ #include "modules/Screen.h" #include "modules/Units.h" #include "modules/Items.h" +#include "modules/Job.h" #include "MiscUtils.h" @@ -58,7 +59,12 @@ #include "df/item_flaskst.h" #include "df/item_backpackst.h" #include "df/item_quiverst.h" +#include "df/building_weaponrackst.h" #include "df/building_armorstandst.h" +#include "df/building_cabinetst.h" +#include "df/building_boxst.h" +#include "df/job.h" +#include "df/general_ref_building_holderst.h" #include @@ -139,6 +145,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector getSpecificSquad(); if (sid < 0) @@ -683,7 +691,9 @@ static bool belongs_to_position(df::item *item, df::building *holder, bool any_p if (!squad) return false; - if (any_position) + int position = holder->getSpecificPosition(); + + if (position == -1 && holder->getType() == building_type::Weaponrack) { for (size_t i = 0; i < squad->positions.size(); i++) { @@ -693,7 +703,7 @@ static bool belongs_to_position(df::item *item, df::building *holder, bool any_p } else { - auto cpos = vector_get(squad->positions, holder->getSpecificPosition()); + auto cpos = vector_get(squad->positions, position); if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) return true; } @@ -701,7 +711,7 @@ static bool belongs_to_position(df::item *item, df::building *holder, bool any_p return false; } -static bool is_in_armory(df::item *item, bool any_position) +static bool is_in_armory(df::item *item) { if (item->flags.bits.in_inventory || item->flags.bits.on_ground) return false; @@ -714,35 +724,22 @@ static bool is_in_armory(df::item *item, bool any_position) if (!holder) return false; - return belongs_to_position(item, holder, any_position); + return belongs_to_position(item, holder); } -struct armory_weapon_hook : df::item_weaponst { - typedef df::item_weaponst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) - { - if (is_in_armory(this, true)) - return false; - - return INTERPOSE_NEXT(isCollected)(); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(armory_weapon_hook, isCollected); - template struct armory_hook : Item { typedef Item interpose_base; DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { - if (is_in_armory(this, false)) + if (is_in_armory(this)) return false; return INTERPOSE_NEXT(isCollected)(); } }; +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); @@ -753,6 +750,112 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollecte template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +static void check_stock_armory(df::building *target, int squad_idx) +{ + auto squad = df::squad::find(squad_idx); + if (!squad) + return; + + int position = target->getSpecificPosition(); + + for (size_t i = 0; i < squad->positions.size(); i++) + { + if (position >= 0 && position != int(i)) + continue; + + auto pos = squad->positions[i]; + + for (size_t j = 0; j < pos->assigned_items.size(); j++) + { + auto item = df::item::find(pos->assigned_items[j]); + if (!item || item->stockpile_countdown > 0) + continue; + + if (item->flags.bits.in_job || + item->flags.bits.removed || + item->flags.bits.in_building || + item->flags.bits.encased || + item->flags.bits.owned || + item->flags.bits.forbid || + item->flags.bits.on_fire) + continue; + + auto top = item; + + while (top->flags.bits.in_inventory) + { + auto parent = Items::getContainer(top); + if (!parent) break; + top = parent; + } + + if (Items::getGeneralRef(top, general_ref_type::UNIT_HOLDER)) + continue; + + if (is_in_armory(item)) + continue; + + if (!target->canStoreItem(item, true)) + continue; + + auto href = df::allocate(); + if (!href) + continue; + + auto job = new df::job(); + + job->pos = df::coord(target->centerx, target->centery, target->z); + + switch (target->getType()) { + case building_type::Weaponrack: + job->job_type = job_type::StoreWeapon; + job->flags.bits.specific_dropoff = true; + break; + case building_type::Armorstand: + job->job_type = job_type::StoreArmor; + job->flags.bits.specific_dropoff = true; + break; + case building_type::Cabinet: + job->job_type = job_type::StoreItemInCabinet; + break; + default: + job->job_type = job_type::StoreItemInChest; + break; + } + + if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) + { + delete job; + delete href; + continue; + } + + href->building_id = target->id; + target->jobs.push_back(job); + job->references.push_back(href); + + Job::linkIntoWorld(job); + } + } +} + +template struct stock_armory_hook : Building { + typedef Building interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) + { + if (this->specific_squad >= 0 && DF_GLOBAL_VALUE(cur_year_tick,0) % 50 == 0) + check_stock_armory(this, this->specific_squad); + + INTERPOSE_NEXT(updateAction)(); + } +}; + +template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -928,7 +1031,7 @@ static command_result tweak(color_ostream &out, vector ¶meters) } else if (cmd == "armory") { - enable_hook(out, INTERPOSE_HOOK(armory_weapon_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); @@ -939,6 +1042,13 @@ static command_result tweak(color_ostream &out, vector ¶meters) enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); } + else if (cmd == "stock-armory") + { + enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); + enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); + enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); + enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); + } else return CR_WRONG_USAGE; From 7219200d173e922d7e2ef42eb6c3fd8ac159be53 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 28 Oct 2012 22:05:00 +0400 Subject: [PATCH 185/196] Split the armory tweak into a separate plugin - it is too big now. --- NEWS | 5 +- dfhack.init-example | 4 - plugins/CMakeLists.txt | 1 + plugins/fix-armory.cpp | 376 +++++++++++++++++++++++++++++++++++++++++ plugins/tweak.cpp | 214 ----------------------- 5 files changed, 379 insertions(+), 221 deletions(-) create mode 100644 plugins/fix-armory.cpp diff --git a/NEWS b/NEWS index c8a0f5770..810f0cd55 100644 --- a/NEWS +++ b/NEWS @@ -10,9 +10,8 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option - New tweaks: - - tweak armory: prevents stockpiling of armor and weapons stored on stands and racks. - - tweak stock-armory: makes dwarves actively haul equipment from stockpiles to the armory. + New commands: + - fix-armory: activates a plugin that makes armor stands and weapon racks be used again. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/dfhack.init-example b/dfhack.init-example index 2b7ac2cc9..d7f3f5399 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -121,7 +121,3 @@ tweak fast-trade tweak military-stable-assign # in same list, color units already assigned to squads in brown & green tweak military-color-assigned - -# stop squad equpment stored on weapon racks and armor stands from being stockpiled -tweak armory -tweak stock-armory diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 2f663f805..de1aa3441 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -124,6 +124,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(add-spatter add-spatter.cpp) + DFHACK_PLUGIN(fix-armory fix-armory.cpp) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp new file mode 100644 index 000000000..06adbbe4c --- /dev/null +++ b/plugins/fix-armory.cpp @@ -0,0 +1,376 @@ +// Fixes containers in barracks to actually work as intended. + +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Gui.h" +#include "modules/Screen.h" +#include "modules/Units.h" +#include "modules/Items.h" +#include "modules/Job.h" +#include "modules/World.h" + +#include "MiscUtils.h" + +#include "DataDefs.h" +#include +#include "df/ui.h" +#include "df/world.h" +#include "df/squad.h" +#include "df/unit.h" +#include "df/squad_position.h" +#include "df/item_weaponst.h" +#include "df/item_armorst.h" +#include "df/item_helmst.h" +#include "df/item_pantsst.h" +#include "df/item_shoesst.h" +#include "df/item_glovesst.h" +#include "df/item_shieldst.h" +#include "df/item_flaskst.h" +#include "df/item_backpackst.h" +#include "df/item_quiverst.h" +#include "df/building_weaponrackst.h" +#include "df/building_armorstandst.h" +#include "df/building_cabinetst.h" +#include "df/building_boxst.h" +#include "df/job.h" +#include "df/general_ref_building_holderst.h" +#include "df/barrack_preference_category.h" + +#include + +using std::vector; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; + +using df::global::ui; +using df::global::world; +using df::global::gamemode; +using df::global::ui_build_selector; +using df::global::ui_menu_width; +using df::global::ui_area_map_width; + +using namespace DFHack::Gui; +using Screen::Pen; + +static command_result fix_armory(color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("fix-armory"); + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "fix-armory", "Enables or disables the fix-armory plugin.", fix_armory, false, + " fix-armory enable\n" + " Enables the tweaks.\n" + " fix-armory disable\n" + " Disables the tweaks. All equipment will be hauled off to stockpiles.\n" + )); + + if (Core::getInstance().isMapLoaded()) + plugin_onstatechange(out, SC_MAP_LOADED); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + return CR_OK; +} + +static bool belongs_to_position(df::item *item, df::building *holder) +{ + int sid = holder->getSpecificSquad(); + if (sid < 0) + return false; + + auto squad = df::squad::find(sid); + if (!squad) + return false; + + int position = holder->getSpecificPosition(); + + if (position == -1 && holder->getType() == building_type::Weaponrack) + { + for (size_t i = 0; i < squad->positions.size(); i++) + { + if (binsearch_index(squad->positions[i]->assigned_items, item->id) >= 0) + return true; + } + } + else + { + auto cpos = vector_get(squad->positions, position); + if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) + return true; + } + + return false; +} + +static bool is_in_armory(df::item *item) +{ + if (item->flags.bits.in_inventory || item->flags.bits.on_ground) + return false; + + auto holder_ref = Items::getGeneralRef(item, general_ref_type::BUILDING_HOLDER); + if (!holder_ref) + return false; + + auto holder = holder_ref->getBuilding(); + if (!holder) + return false; + + return belongs_to_position(item, holder); +} + +template struct armory_hook : Item { + typedef Item interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) + { + if (is_in_armory(this)) + return false; + + return INTERPOSE_NEXT(isCollected)(); + } +}; + +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); + +static bool can_store_item(df::item *item) +{ + if (!item || item->stockpile_countdown > 0) + return false; + + if (item->flags.bits.in_job || + item->flags.bits.removed || + item->flags.bits.in_building || + item->flags.bits.encased || + item->flags.bits.owned || + item->flags.bits.forbid || + item->flags.bits.on_fire) + return false; + + auto top = item; + + while (top->flags.bits.in_inventory) + { + auto parent = Items::getContainer(top); + if (!parent) break; + top = parent; + } + + if (Items::getGeneralRef(top, general_ref_type::UNIT_HOLDER)) + return false; + + if (is_in_armory(item)) + return false; + + return true; +} + +static void try_store_item(std::vector &vec, df::item *item) +{ + for (size_t i = 0; i < vec.size(); i++) + { + auto target = df::building::find(vec[i]); + if (!target) + continue; + + if (!target->canStoreItem(item, true)) + continue; + + auto href = df::allocate(); + if (!href) + return; + + auto job = new df::job(); + + job->pos = df::coord(target->centerx, target->centery, target->z); + + switch (target->getType()) { + case building_type::Weaponrack: + job->job_type = job_type::StoreWeapon; + job->flags.bits.specific_dropoff = true; + break; + case building_type::Armorstand: + job->job_type = job_type::StoreArmor; + job->flags.bits.specific_dropoff = true; + break; + case building_type::Cabinet: + job->job_type = job_type::StoreItemInCabinet; + break; + default: + job->job_type = job_type::StoreItemInChest; + break; + } + + if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) + { + delete job; + delete href; + continue; + } + + href->building_id = target->id; + target->jobs.push_back(job); + job->references.push_back(href); + + Job::linkIntoWorld(job); + return; + } +} + +static void try_store_item_set(std::vector &items, df::squad *squad, df::squad_position *pos) +{ + for (size_t j = 0; j < items.size(); j++) + { + auto item = df::item::find(items[j]); + + if (!can_store_item(item)) + continue; + + if (item->isWeapon()) + try_store_item(squad->rack_combat, item); + else if (item->isClothing()) + try_store_item(pos->preferences[barrack_preference_category::Cabinet], item); + else if (item->isArmorNotClothing()) + try_store_item(pos->preferences[barrack_preference_category::Armorstand], item); + else + try_store_item(pos->preferences[barrack_preference_category::Box], item); + } +} + +static bool is_enabled = false; + +DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) +{ + if (!is_enabled) + return CR_OK; + + if (DF_GLOBAL_VALUE(cur_year_tick,1) % 50 != 0) + return CR_OK; + + auto &squads = df::global::world->squads.all; + + for (size_t si = 0; si < squads.size(); si++) + { + auto squad = squads[si]; + + for (size_t i = 0; i < squad->positions.size(); i++) + { + auto pos = squad->positions[i]; + + try_store_item_set(pos->assigned_items, squad, pos); + } + } + + return CR_OK; +} + +static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, bool enable) +{ + if (!hook.apply(enable)) + out.printerr("Could not %s hook.\n", enable?"activate":"deactivate"); +} + +static void enable_hooks(color_ostream &out, bool enable) +{ + is_enabled = enable; + + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); +} + +static void enable_plugin(color_ostream &out) +{ + auto entry = World::GetPersistentData("fix-armory/enabled", NULL); + if (!entry.isValid()) + { + out.printerr("Could not save the status.\n"); + return; + } + + enable_hooks(out, true); +} + +static void disable_plugin(color_ostream &out) +{ + auto entry = World::GetPersistentData("fix-armory/enabled"); + World::DeletePersistentData(entry); + + enable_hooks(out, false); +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + if (!gamemode || *gamemode == game_mode::DWARF) + { + bool enable = World::GetPersistentData("fix-armory/enabled").isValid(); + + if (enable) + { + out.print("Enabling the fix-armory plugin.\n"); + enable_hooks(out, true); + } + else + enable_hooks(out, false); + } + break; + case SC_MAP_UNLOADED: + enable_hooks(out, false); + default: + break; + } + + return CR_OK; +} + +static command_result fix_armory(color_ostream &out, vector ¶meters) +{ + CoreSuspender suspend; + + if (parameters.empty()) + return CR_WRONG_USAGE; + + string cmd = parameters[0]; + + if (cmd == "enable") + { + enable_plugin(out); + } + else if (cmd == "disable") + { + disable_plugin(out); + } + else + return CR_WRONG_USAGE; + + return CR_OK; +} diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 9dc3ae468..93e5ab3bc 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -49,20 +49,6 @@ #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_layer_militaryst.h" #include "df/squad_position.h" -#include "df/item_weaponst.h" -#include "df/item_armorst.h" -#include "df/item_helmst.h" -#include "df/item_pantsst.h" -#include "df/item_shoesst.h" -#include "df/item_glovesst.h" -#include "df/item_shieldst.h" -#include "df/item_flaskst.h" -#include "df/item_backpackst.h" -#include "df/item_quiverst.h" -#include "df/building_weaponrackst.h" -#include "df/building_armorstandst.h" -#include "df/building_cabinetst.h" -#include "df/building_boxst.h" #include "df/job.h" #include "df/general_ref_building_holderst.h" @@ -142,11 +128,6 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector getSpecificSquad(); - if (sid < 0) - return false; - - auto squad = df::squad::find(sid); - if (!squad) - return false; - - int position = holder->getSpecificPosition(); - - if (position == -1 && holder->getType() == building_type::Weaponrack) - { - for (size_t i = 0; i < squad->positions.size(); i++) - { - if (binsearch_index(squad->positions[i]->assigned_items, item->id) >= 0) - return true; - } - } - else - { - auto cpos = vector_get(squad->positions, position); - if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) - return true; - } - - return false; -} - -static bool is_in_armory(df::item *item) -{ - if (item->flags.bits.in_inventory || item->flags.bits.on_ground) - return false; - - auto holder_ref = Items::getGeneralRef(item, general_ref_type::BUILDING_HOLDER); - if (!holder_ref) - return false; - - auto holder = holder_ref->getBuilding(); - if (!holder) - return false; - - return belongs_to_position(item, holder); -} - -template struct armory_hook : Item { - typedef Item interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) - { - if (is_in_armory(this)) - return false; - - return INTERPOSE_NEXT(isCollected)(); - } -}; - -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); - -static void check_stock_armory(df::building *target, int squad_idx) -{ - auto squad = df::squad::find(squad_idx); - if (!squad) - return; - - int position = target->getSpecificPosition(); - - for (size_t i = 0; i < squad->positions.size(); i++) - { - if (position >= 0 && position != int(i)) - continue; - - auto pos = squad->positions[i]; - - for (size_t j = 0; j < pos->assigned_items.size(); j++) - { - auto item = df::item::find(pos->assigned_items[j]); - if (!item || item->stockpile_countdown > 0) - continue; - - if (item->flags.bits.in_job || - item->flags.bits.removed || - item->flags.bits.in_building || - item->flags.bits.encased || - item->flags.bits.owned || - item->flags.bits.forbid || - item->flags.bits.on_fire) - continue; - - auto top = item; - - while (top->flags.bits.in_inventory) - { - auto parent = Items::getContainer(top); - if (!parent) break; - top = parent; - } - - if (Items::getGeneralRef(top, general_ref_type::UNIT_HOLDER)) - continue; - - if (is_in_armory(item)) - continue; - - if (!target->canStoreItem(item, true)) - continue; - - auto href = df::allocate(); - if (!href) - continue; - - auto job = new df::job(); - - job->pos = df::coord(target->centerx, target->centery, target->z); - - switch (target->getType()) { - case building_type::Weaponrack: - job->job_type = job_type::StoreWeapon; - job->flags.bits.specific_dropoff = true; - break; - case building_type::Armorstand: - job->job_type = job_type::StoreArmor; - job->flags.bits.specific_dropoff = true; - break; - case building_type::Cabinet: - job->job_type = job_type::StoreItemInCabinet; - break; - default: - job->job_type = job_type::StoreItemInChest; - break; - } - - if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) - { - delete job; - delete href; - continue; - } - - href->building_id = target->id; - target->jobs.push_back(job); - job->references.push_back(href); - - Job::linkIntoWorld(job); - } - } -} - -template struct stock_armory_hook : Building { - typedef Building interpose_base; - - DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) - { - if (this->specific_squad >= 0 && DF_GLOBAL_VALUE(cur_year_tick,0) % 50 == 0) - check_stock_armory(this, this->specific_squad); - - INTERPOSE_NEXT(updateAction)(); - } -}; - -template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); -template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); -template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); -template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); - static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -1029,26 +835,6 @@ static command_result tweak(color_ostream &out, vector ¶meters) { enable_hook(out, INTERPOSE_HOOK(military_assign_hook, render), parameters); } - else if (cmd == "armory") - { - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - } - else if (cmd == "stock-armory") - { - enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); - enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); - enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); - enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); - } else return CR_WRONG_USAGE; From abeb9d0f0c9a96606b9f76278dc66b0ed20a7ef7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 29 Oct 2012 20:32:39 +0400 Subject: [PATCH 186/196] Add documentation. --- Readme.html | 370 ++++++++++++++++++++++++++++++++++------------------ Readme.rst | 146 +++++++++++++++++++++ 2 files changed, 392 insertions(+), 124 deletions(-) diff --git a/Readme.html b/Readme.html index 6df1afcad..0fa1d10be 100644 --- a/Readme.html +++ b/Readme.html @@ -434,94 +434,99 @@ access DF memory and allow for easier development of new tools.

  • fixmerchants
  • fixveins
  • tweak
  • +
  • fix-armory
  • -
  • Mode switch and reclaim
  • @@ -1846,11 +1853,49 @@ to make them stand out more in the list.
    +
    +

    fix-armory

    +

    Enables a fix for storage of squad equipment in barracks.

    +

    Specifically, it prevents your haulers from moving that equipment +to stockpiles, and instead queues jobs to store it on weapon racks, +armor stands, and in containers.

    +
    +

    Note

    +

    In order to actually be used, weapon racks have to be patched and +assigned to a squad. See documentation for gui/assign-rack below.

    +

    Also, the default capacity of armor stands is way too low, so check out +http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 +for a patch addressing that too.

    +
    +

    Note that the buildings in the armory are used as follows:

    +
      +
    • Weapon racks when fixed are used to store any assigned weapons. +Each rack belongs to a specific squad, and can store up to 5 weapons.
    • +
    • Armor stands belong to specific squad members and are used for +armor and shields. By default one stand can store one item of each +type (hence one boot or gauntlet); if patched, the limit is raised to 2, +which should be sufficient.
    • +
    • Cabinets are used to store assigned clothing for a specific squad member. +They are never used to store owned clothing.
    • +
    • Chests (boxes, etc) are used for a flask, backpack or quiver assigned +to the squad member. Due to a bug, food is dropped out of the backpack +when it is stored.
    • +
    +

    Contrary to the common misconception, all these uses are controlled by the +Individual Equipment usage flag; the Squad Equipment mode means nothing.

    +
    +

    Warning

    +

    Although armor stands, cabinets and chests properly belong only to one +squad member, the owner of the building used to create the barracks will +randomly use any containers inside the room. Thus, it is recommended to +always create the armory from a weapon rack.

    +
    +
    -

    Mode switch and reclaim

    +

    Mode switch and reclaim

    -

    lair

    +

    lair

    This command allows you to mark the map as 'monster lair', preventing item scatter on abandon. When invoked as 'lair reset', it does the opposite.

    Unlike reveal, this command doesn't save the information about tiles - you @@ -1870,7 +1915,7 @@ won't be able to restore state of real monster lairs using 'lair reset'.

    -

    mode

    +

    mode

    This command lets you see and change the game mode directly. Not all combinations are good for every situation and most of them will produce undesirable results. There are a few good ones though.

    @@ -1890,9 +1935,9 @@ You just created a returnable mountain home and gained an adventurer.

    -

    Visualizer and data export

    +

    Visualizer and data export

    -

    ssense / stonesense

    +

    ssense / stonesense

    An isometric visualizer that runs in a second window. This requires working graphics acceleration and at least a dual core CPU (otherwise it will slow down DF).

    @@ -1905,19 +1950,19 @@ thread: http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository

    -

    mapexport

    +

    mapexport

    Export the current loaded map as a file. This will be eventually usable with visualizers.

    -

    dwarfexport

    +

    dwarfexport

    Export dwarves to RuneSmith-compatible XML.

    -

    Job management

    +

    Job management

    -

    job

    +

    job

    Command for general job query and manipulation.

    Options:
    @@ -1936,7 +1981,7 @@ in a workshop, or the unit/jobs screen.
    -

    job-material

    +

    job-material

    Alter the material of the selected job.

    Invoked as:

    @@ -1954,7 +1999,7 @@ over the first available choice with the matching material.
     
     
    -

    job-duplicate

    +

    job-duplicate

    Duplicate the selected job in a workshop:
      @@ -1965,7 +2010,7 @@ instantly duplicates the job.
    -

    workflow

    +

    workflow

    Manage control of repeat jobs.

    Usage:

    @@ -1989,7 +2034,7 @@ Otherwise, enables or disables any of the following options:

    -

    Function

    +

    Function

    When the plugin is enabled, it protects all repeat jobs from removal. If they do disappear due to any cause, they are immediately re-added to their workshop and suspended.

    @@ -1998,9 +2043,11 @@ produce that kind of item are automatically suspended and resumed as the item amount goes above or below the limit. The gap specifies how much below the limit the amount has to drop before jobs are resumed; this is intended to reduce the frequency of jobs being toggled.

    +

    Check out the gui/workflow script below for a simple front-end integrated +in the game UI.

    -

    Constraint examples

    +

    Constraint examples

    Keep metal bolts within 900-1000, and wood/bone within 150-200.

     workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
    @@ -2039,15 +2086,15 @@ command.
     
    -

    Fortress activity management

    +

    Fortress activity management

    -

    seedwatch

    +

    seedwatch

    Tool for turning cooking of seeds and plants on/off depending on how much you have of them.

    See 'seedwatch help' for detailed description.

    -

    zone

    +

    zone

    Helps a bit with managing activity zones (pens, pastures and pits) and cages.

    Options:

    @@ -2146,7 +2193,7 @@ for war/hunt). Negatable.
    -

    Usage with single units

    +

    Usage with single units

    One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units @@ -2155,7 +2202,7 @@ and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way.

    -

    Usage with filters

    +

    Usage with filters

    All filters can be used together with the 'assign' command.

    Restrictions: It's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. @@ -2173,14 +2220,14 @@ are not properly added to your own stocks; slaughtering them should work).

    Most filters can be negated (e.g. 'not grazer' -> race is not a grazer).

    -

    Mass-renaming

    +

    Mass-renaming

    Using the 'nick' command you can set the same nickname for multiple units. If used without 'assign', 'all' or 'count' it will rename all units in the current default target zone. Combined with 'assign', 'all' or 'count' (and further optional filters) it will rename units matching the filter conditions.

    -

    Cage zones

    +

    Cage zones

    Using the 'tocages' command you can assign units to a set of cages, for example a room next to your butcher shop(s). They will be spread evenly among available cages to optimize hauling to and butchering from them. For this to work you need @@ -2191,7 +2238,7 @@ would make no sense, but can be used together with 'nick' or 'remnick' and all the usual filters.

    -

    Examples

    +

    Examples

    zone assign all own ALPACA minage 3 maxage 10
    Assign all own alpacas who are between 3 and 10 years old to the selected @@ -2217,7 +2264,7 @@ on the current default zone.
    -

    autonestbox

    +

    autonestbox

    Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless @@ -2246,7 +2293,7 @@ frames between runs.

    -

    autobutcher

    +

    autobutcher

    Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units will be processed.

    Named units will be completely ignored (to protect specific animals from @@ -2354,7 +2401,7 @@ autobutcher.bat

    -

    autolabor

    +

    autolabor

    Automatically manage dwarf labors.

    When enabled, autolabor periodically checks your dwarves and enables or disables labors. It tries to keep as many dwarves as possible busy but @@ -2368,14 +2415,14 @@ while it is enabled.

    -

    Other

    +

    Other

    -

    catsplosion

    +

    catsplosion

    Makes cats just multiply. It is not a good idea to run this more than once or twice.

    -

    dfusion

    +

    dfusion

    This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin.

    See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15

    Confirmed working DFusion plugins:

    @@ -2397,7 +2444,7 @@ twice.

    -

    misery

    +

    misery

    When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).

    Usage:

    @@ -2421,7 +2468,7 @@ twice.

    -

    Scripts

    +

    Scripts

    Lua or ruby scripts placed in the hack/scripts/ directory are considered for execution as if they were native DFHack commands. They are listed at the end of the 'ls' command output.

    @@ -2430,7 +2477,7 @@ only be listed by ls if called as 'ls -a'. This is intended as a way to hide scripts that are obscure, developer-oriented, or should be used as keybindings.

    Some notable scripts:

    -

    fix/*

    +

    fix/*

    Scripts in this subdirectory fix various bugs and issues, some of them obscure.

    • fix/dead-units

      @@ -2456,22 +2503,22 @@ caused by autodump bugs or other hacking mishaps.

    -

    gui/*

    +

    gui/*

    Scripts that implement dialogs inserted into the main game window are put in this directory.

    -

    quicksave

    +

    quicksave

    If called in dwarf mode, makes DF immediately auto-save the game by setting a flag normally used in seasonal auto-save.

    -

    setfps

    +

    setfps

    Run setfps <number> to set the FPS cap at runtime, in case you want to watch combat in slow motion or something :)

    -

    siren

    +

    siren

    Wakes up sleeping units, cancels breaks and stops parties either everywhere, or in the burrows given as arguments. In return, adds bad thoughts about noise, tiredness and lack of protection. Also, the units with interrupted @@ -2479,7 +2526,7 @@ breaks will go on break again a lot sooner. The script is intended for emergencies, e.g. when a siege appears, and all your military is partying.

    -

    growcrops

    +

    growcrops

    Instantly grow seeds inside farming plots.

    With no argument, this command list the various seed types currently in use in your farming plots. @@ -2491,7 +2538,7 @@ growcrops plump 40

    -

    removebadthoughts

    +

    removebadthoughts

    This script remove negative thoughts from your dwarves. Very useful against tantrum spirals.

    The script can target a single creature, when used with the him argument, @@ -2505,7 +2552,7 @@ but in the short term your dwarves will get much more joyful.

    quickly after you unpause.

    -

    slayrace

    +

    slayrace

    Kills any unit of a given race.

    With no argument, lists the available races.

    With the special argument him, targets only the selected creature.

    @@ -2531,7 +2578,7 @@ slayrace elve magma
    -

    magmasource

    +

    magmasource

    Create an infinite magma source on a tile.

    This script registers a map tile as a magma source, and every 12 game ticks that tile receives 1 new unit of flowing magma.

    @@ -2546,7 +2593,7 @@ To remove all placed sources, call magmasource stop

    With no argument, this command shows an help message and list existing sources.

    -

    digfort

    +

    digfort

    A script to designate an area for digging according to a plan in csv format.

    This script, inspired from quickfort, can designate an area for digging. Your plan should be stored in a .csv file like this:

    @@ -2564,7 +2611,7 @@ To skip a row in your design, use a single ;.<

    The script takes the plan filename, starting from the root df folder.

    -

    superdwarf

    +

    superdwarf

    Similar to fastdwarf, per-creature.

    To make any creature superfast, target it ingame using 'v' and:

    @@ -2574,17 +2621,17 @@ superdwarf add
     

    This plugin also shortens the 'sleeping' and 'on break' periods of targets.

    -

    drainaquifer

    +

    drainaquifer

    Remove all 'aquifer' tag from the map blocks. Irreversible.

    -

    deathcause

    +

    deathcause

    Focus a body part ingame, and this script will display the cause of death of the creature.

    -

    In-game interface tools

    +

    In-game interface tools

    These tools work by displaying dialogs or overlays in the game window, and are mostly implemented by lua scripts.

    @@ -2595,7 +2642,7 @@ display the word "DFHack" on the screen somewhere while active.

    guideline because it arguably just fixes small usability bugs in the game UI.

    -

    Dwarf Manipulator

    +

    Dwarf Manipulator

    Implemented by the manipulator plugin. To activate, open the unit screen and press 'l'.

    This tool implements a Dwarf Therapist-like interface within the game UI. The @@ -2631,13 +2678,13 @@ cursor onto that cell instead of toggling it. directly to the main dwarf mode screen.

    -

    gui/liquids

    +

    gui/liquids

    To use, bind to a key and activate in the 'k' mode.

    While active, use the suggested keys to switch the usual liquids parameters, and Enter to select the target area and apply changes.

    -

    gui/mechanisms

    +

    gui/mechanisms

    To use, bind to a key and activate in the 'q' mode.

    Lists mechanisms connected to the building, and their links. Navigating the list centers the view on the relevant linked buildings.

    @@ -2646,7 +2693,7 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter re-entering the mechanisms ui.

    -

    gui/rename

    +

    gui/rename

    Backed by the rename plugin, this script allows entering the desired name via a simple dialog in the game ui.

      @@ -2662,14 +2709,14 @@ It is also possible to rename zones from the 'i' menu.

      The building or unit options are automatically assumed when in relevant ui state.

    -

    gui/room-list

    +

    gui/room-list

    To use, bind to a key and activate in the 'q' mode, either immediately or after opening the assign owner page.

    The script lists other rooms owned by the same owner, or by the unit selected in the assign list, and allows unassigning them.

    -

    gui/choose-weapons

    +

    gui/choose-weapons

    Bind to a key, and activate in the Equip->View/Customize page of the military screen.

    Depending on the cursor location, it rewrites all 'individual choice weapon' entries in the selected squad or position to use a specific weapon type matching the assigned @@ -2678,9 +2725,84 @@ only that entry, and does it even if it is not 'individual choice'.

    Rationale: individual choice seems to be unreliable when there is a weapon shortage, and may lead to inappropriate weapons being selected.

    +
    +

    gui/guide-path

    +

    Bind to a key, and activate in the Hauling menu with the cursor over a Guide order.

    +

    The script displays the cached path that will be used by the order; the game +computes it when the order is executed for the first time.

    +
    +
    +

    gui/workshop-job

    +

    Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

    +

    The script shows a list of the input reagents of the selected job, and allows changing +them like the job item-type and job item-material commands.

    +

    Specifically, pressing the 'i' key pops up a dialog that lets you select an item +type from a list. Pressing 'm', unless the item type does not allow a material, +lets you choose a material.

    +
    +

    Warning

    +

    Due to the way input reagent matching works in DF, you must select an item type +if you select a material, or the material will be matched incorrectly in some cases. +If you press 'm' without choosing an item type, the script will auto-choose it +if there is only one valid choice, or pop up an error message box instead of the +material selection dialog.

    +
    +

    Note that both materials and item types presented in the dialogs are filtered +by the job input flags, and even the selected item type for material selection, +or material for item type selection. Many jobs would let you select only one +input item type.

    +

    For example, if you choose a plant input item type for your prepare meal job, +it will only let you select cookable materials.

    +

    If you choose a barrel item instead (meaning things stored in barrels, like +drink or milk), it will let you select any material, since in this case the +material is matched against the barrel itself. Then, if you select, say, iron, +and then try to change the input item type, now it won't let you select plant; +you have to unset the material first.

    +
    +
    +

    gui/workflow

    +

    Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

    +

    This script provides a simple interface to constraints managed by the workflow +plugin. When active, it displays a list of all constraints applicable to the +current job, and their current status.

    +

    A constraint specifies a certain range to be compared against either individual +item or whole stack count, an item type and optionally a material. When the +current count is below the lower bound of the range, the job is resumed; if it +is above or equal to the top bound, it will be suspended. Within the range, the +specific constraint has no effect on the job; others may still affect it.

    +

    Pressing 'c' switches the current constraint between counting stacks or items. +Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the +bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting +items and expanding the range each gives a 5x bonus).

    +

    Pressing 'n' produces a list of possible outputs of this job as guessed by +workflow, and lets you create a new constraint by just choosing one. If you +don't see the choice you want in the list, it likely means you have to adjust +the job material first using job item-material or gui/workshop-job, +as described in workflow documentation above. In this manner, this feature +can be used for troubleshooting jobs that don't match the right constraints.

    +
    +
    +

    gui/assign-rack

    +

    Bind to a key, and activate when viewing a weapon rack in the 'q' mode.

    +

    This script is part of a group of related fixes to make the armory storage +work again. The existing issues are:

    +
      +
    • Weapon racks have to each be assigned to a specific squad, like with +beds/boxes/armor stands and individual squad members, but nothing in +the game does this. This issue is what this script addresses.
    • +
    • Even if assigned by the script, the game will unassign the racks again without a binary patch. +Check the comments for this bug to get it: +http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
    • +
    • Haulers still take equpment stored in the armory away to the stockpiles, +unless the fix-armory plugin above is used.
    • +
    +

    The script interface simply lets you designate one of the squads that +are assigned to the barracks/armory containing the selected stand as +the intended user.

    +
    -

    Behavior Mods

    +

    Behavior Mods

    These plugins, when activated via configuration UI or by detecting certain structures in RAWs, modify the game engine behavior concerning the target objects to add features not otherwise present.

    @@ -2691,20 +2813,20 @@ technical challenge, and do not represent any long-term plans to produce more similar modifications of the game.

    -

    Siege Engine

    +

    Siege Engine

    The siege-engine plugin enables siege engines to be linked to stockpiles, and aimed at an arbitrary rectangular area across Z levels, instead of the original four directions. Also, catapults can be ordered to load arbitrary objects, not just stones.

    -

    Rationale

    +

    Rationale

    Siege engines are a very interesting feature, but sadly almost useless in the current state because they haven't been updated since 2D and can only aim in four directions. This is an attempt to bring them more up to date until Toady has time to work on it. Actual improvements, e.g. like making siegers bring their own, are something only Toady can do.

    -

    Configuration UI

    +

    Configuration UI

    The configuration front-end to the plugin is implemented by the gui/siege-engine script. Bind it to a key and activate after selecting a siege engine in 'q' mode.

    The main mode displays the current target, selected ammo item type, linked stockpiles and @@ -2725,7 +2847,7 @@ menu.

    -

    Power Meter

    +

    Power Meter

    The power-meter plugin implements a modified pressure plate that detects power being supplied to gear boxes built in the four adjacent N/S/W/E tiles.

    The configuration front-end is implemented by the gui/power-meter script. Bind it to a @@ -2734,11 +2856,11 @@ key and activate after selecting Pressure Plate in the build menu.

    configuration page, but configures parameters relevant to the modded power meter building.

    -

    Steam Engine

    +

    Steam Engine

    The steam-engine plugin detects custom workshops with STEAM_ENGINE in their token, and turns them into real steam engines.

    -

    Rationale

    +

    Rationale

    The vanilla game contains only water wheels and windmills as sources of power, but windmills give relatively little power, and water wheels require flowing water, which must either be a real river and thus immovable and @@ -2749,7 +2871,7 @@ it can be done just by combining existing features of the game engine in a new way with some glue code and a bit of custom logic.

    -

    Construction

    +

    Construction

    The workshop needs water as its input, which it takes via a passable floor tile below it, like usual magma workshops do. The magma version also needs magma.

    @@ -2773,7 +2895,7 @@ short axles that can be built later than both of the engines.

    -

    Operation

    +

    Operation

    In order to operate the engine, queue the Stoke Boiler job (optionally on repeat). A furnace operator will come, possibly bringing a bar of fuel, and perform it. As a result, a "boiling water" item will appear @@ -2804,7 +2926,7 @@ decrease it by further 4%, and also decrease the whole steam use rate by 10%.

    -

    Explosions

    +

    Explosions

    The engine must be constructed using barrel, pipe and piston from fire-safe, or in the magma version magma-safe metals.

    During operation weak parts get gradually worn out, and @@ -2813,7 +2935,7 @@ toppled during operation by a building destroyer, or a tantruming dwarf.

    -

    Save files

    +

    Save files

    It should be safe to load and view engine-using fortresses from a DF version without DFHack installed, except that in such case the engines won't work. However actually making modifications @@ -2824,7 +2946,7 @@ being generated.

    -

    Add Spatter

    +

    Add Spatter

    This plugin makes reactions with names starting with SPATTER_ADD_ produce contaminants on the items instead of improvements. The produced contaminants are immune to being washed away by water or destroyed by diff --git a/Readme.rst b/Readme.rst index 39d44d975..ad42035a5 100644 --- a/Readme.rst +++ b/Readme.rst @@ -231,6 +231,8 @@ Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and p * 'fastdwarf 1 1' enables both * 'fastdwarf 0' disables both * 'fastdwarf 1' enables speedydwarf and disables teledwarf + * 'fastdwarf 2 ...' sets a native debug flag in the game memory + that implements an even more aggressive version of speedydwarf. Game interface ============== @@ -1070,6 +1072,51 @@ Subcommands that persist until disabled or DF quit: :military-color-assigned: Color squad candidates already assigned to other squads in brown/green to make them stand out more in the list. +fix-armory +---------- + +Enables a fix for storage of squad equipment in barracks. + +Specifically, it prevents your haulers from moving that equipment +to stockpiles, and instead queues jobs to store it on weapon racks, +armor stands, and in containers. + +.. note:: + + In order to actually be used, weapon racks have to be patched and + assigned to a squad. See documentation for ``gui/assign-rack`` below. + + Also, the default capacity of armor stands is way too low, so check out + http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 + for a patch addressing that too. + +Note that the buildings in the armory are used as follows: + +* Weapon racks when fixed are used to store any assigned weapons. + Each rack belongs to a specific squad, and can store up to 5 weapons. + +* Armor stands belong to specific squad members and are used for + armor and shields. By default one stand can store one item of each + type (hence one boot or gauntlet); if patched, the limit is raised to 2, + which should be sufficient. + +* Cabinets are used to store assigned clothing for a specific squad member. + They are **never** used to store owned clothing. + +* Chests (boxes, etc) are used for a flask, backpack or quiver assigned + to the squad member. Due to a bug, food is dropped out of the backpack + when it is stored. + +Contrary to the common misconception, all these uses are controlled by the +*Individual Equipment* usage flag; the Squad Equipment mode means nothing. + +.. warning:: + + Although armor stands, cabinets and chests properly belong only to one + squad member, the owner of the building used to create the barracks will + randomly use any containers inside the room. Thus, it is recommended to + always create the armory from a weapon rack. + Mode switch and reclaim ======================= @@ -1213,6 +1260,9 @@ amount goes above or below the limit. The gap specifies how much below the limit the amount has to drop before jobs are resumed; this is intended to reduce the frequency of jobs being toggled. +Check out the ``gui/workflow`` script below for a simple front-end integrated +in the game UI. + Constraint examples ................... @@ -1896,6 +1946,102 @@ Rationale: individual choice seems to be unreliable when there is a weapon short and may lead to inappropriate weapons being selected. +gui/guide-path +============== + +Bind to a key, and activate in the Hauling menu with the cursor over a Guide order. + +The script displays the cached path that will be used by the order; the game +computes it when the order is executed for the first time. + + +gui/workshop-job +================ + +Bind to a key, and activate with a job selected in a workshop in the 'q' mode. + +The script shows a list of the input reagents of the selected job, and allows changing +them like the ``job item-type`` and ``job item-material`` commands. + +Specifically, pressing the 'i' key pops up a dialog that lets you select an item +type from a list. Pressing 'm', unless the item type does not allow a material, +lets you choose a material. + +.. warning:: + + Due to the way input reagent matching works in DF, you must select an item type + if you select a material, or the material will be matched incorrectly in some cases. + If you press 'm' without choosing an item type, the script will auto-choose it + if there is only one valid choice, or pop up an error message box instead of the + material selection dialog. + +Note that both materials and item types presented in the dialogs are filtered +by the job input flags, and even the selected item type for material selection, +or material for item type selection. Many jobs would let you select only one +input item type. + +For example, if you choose a *plant* input item type for your prepare meal job, +it will only let you select cookable materials. + +If you choose a *barrel* item instead (meaning things stored in barrels, like +drink or milk), it will let you select any material, since in this case the +material is matched against the barrel itself. Then, if you select, say, iron, +and then try to change the input item type, now it won't let you select *plant*; +you have to unset the material first. + + +gui/workflow +============ + +Bind to a key, and activate with a job selected in a workshop in the 'q' mode. + +This script provides a simple interface to constraints managed by the workflow +plugin. When active, it displays a list of all constraints applicable to the +current job, and their current status. + +A constraint specifies a certain range to be compared against either individual +*item* or whole *stack* count, an item type and optionally a material. When the +current count is below the lower bound of the range, the job is resumed; if it +is above or equal to the top bound, it will be suspended. Within the range, the +specific constraint has no effect on the job; others may still affect it. + +Pressing 'c' switches the current constraint between counting stacks or items. +Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the +bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting +items and expanding the range each gives a 5x bonus). + +Pressing 'n' produces a list of possible outputs of this job as guessed by +workflow, and lets you create a new constraint by just choosing one. If you +don't see the choice you want in the list, it likely means you have to adjust +the job material first using ``job item-material`` or ``gui/workshop-job``, +as described in ``workflow`` documentation above. In this manner, this feature +can be used for troubleshooting jobs that don't match the right constraints. + + +gui/assign-rack +=============== + +Bind to a key, and activate when viewing a weapon rack in the 'q' mode. + +This script is part of a group of related fixes to make the armory storage +work again. The existing issues are: + +* Weapon racks have to each be assigned to a specific squad, like with + beds/boxes/armor stands and individual squad members, but nothing in + the game does this. This issue is what this script addresses. + +* Even if assigned by the script, **the game will unassign the racks again without a binary patch**. + Check the comments for this bug to get it: + http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 + +* Haulers still take equpment stored in the armory away to the stockpiles, + unless the ``fix-armory`` plugin above is used. + +The script interface simply lets you designate one of the squads that +are assigned to the barracks/armory containing the selected stand as +the intended user. + + ============= Behavior Mods ============= From 7646fa6aa3f04900a85a5c7b65d77d1ef9569c32 Mon Sep 17 00:00:00 2001 From: Mathias Rav Date: Mon, 29 Oct 2012 22:18:50 +0100 Subject: [PATCH 187/196] Add dfstream plugin. Broadcasts the Dwarf Fortress display on TCP port 8008. For use with https://github.com/Mortal/dfstream --- plugins/CMakeLists.txt | 1 + plugins/dfstream.cpp | 413 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 414 insertions(+) create mode 100644 plugins/dfstream.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 91d578215..f19947325 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -127,6 +127,7 @@ if (BUILD_SUPPORTED) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) + DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) endif() diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp new file mode 100644 index 000000000..0815146b0 --- /dev/null +++ b/plugins/dfstream.cpp @@ -0,0 +1,413 @@ +// TODO: which of these includes are actually needed? +#include "Core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "df/graphic.h" +#include "df/enabler.h" +#include "df/viewscreen_unitlistst.h" +#include "df/interface_key.h" +#include "df/unit.h" +#include "df/unit_soul.h" +#include "df/unit_skill.h" +#include "df/creature_graphics_role.h" +#include "df/creature_raw.h" +#include "df/caste_raw.h" + +using namespace DFHack; +using namespace df::enums; + +using df::global::gps; +using df::global::enabler; + +//--- SNIP class definition from g_src ---------------------------------------- +enum zoom_commands { zoom_in, zoom_out, zoom_reset, zoom_fullscreen, zoom_resetgrid }; + +class renderer { + void cleanup_arrays(); +protected: + friend class renderer_decorator; + unsigned char *screen; + long *screentexpos; + char *screentexpos_addcolor; + unsigned char *screentexpos_grayscale; + unsigned char *screentexpos_cf; + unsigned char *screentexpos_cbr; + // For partial printing: + unsigned char *screen_old; + long *screentexpos_old; + char *screentexpos_addcolor_old; + unsigned char *screentexpos_grayscale_old; + unsigned char *screentexpos_cf_old; + unsigned char *screentexpos_cbr_old; + + //void gps_allocate(int x, int y); + //Either screen_to_texid(int x, int y); +public: + //void display(); + virtual void update_tile(int x, int y) = 0; + virtual void update_all() = 0; + virtual void render() = 0; + virtual void set_fullscreen(); // Should read from enabler.is_fullscreen() + virtual void zoom(zoom_commands cmd); + virtual void resize(int w, int h) = 0; + virtual void grid_resize(int w, int h) = 0; + //void swap_arrays(); + renderer() { + screen = NULL; + screentexpos = NULL; + screentexpos_addcolor = NULL; + screentexpos_grayscale = NULL; + screentexpos_cf = NULL; + screentexpos_cbr = NULL; + screen_old = NULL; + screentexpos_old = NULL; + screentexpos_addcolor_old = NULL; + screentexpos_grayscale_old = NULL; + screentexpos_cf_old = NULL; + screentexpos_cbr_old = NULL; + } + virtual ~renderer(); + virtual bool get_mouse_coords(int &x, int &y) = 0; + virtual bool uses_opengl(); +}; +//---- END class definition from g_src ---------------------------------------- + +// The error messages are taken from the clsocket source code +const char * translate_socket_error(CSimpleSocket::CSocketError err) { + switch (err) { + case CSimpleSocket::SocketError: + return "Generic socket error translates to error below."; + case CSimpleSocket::SocketSuccess: + return "No socket error."; + case CSimpleSocket::SocketInvalidSocket: + return "Invalid socket handle."; + case CSimpleSocket::SocketInvalidAddress: + return "Invalid destination address specified."; + case CSimpleSocket::SocketInvalidPort: + return "Invalid destination port specified."; + case CSimpleSocket::SocketConnectionRefused: + return "No server is listening at remote address."; + case CSimpleSocket::SocketTimedout: + return "Timed out while attempting operation."; + case CSimpleSocket::SocketEwouldblock: + return "Operation would block if socket were blocking."; + case CSimpleSocket::SocketNotconnected: + return "Currently not connected."; + case CSimpleSocket::SocketEinprogress: + return "Socket is non-blocking and the connection cannot be completed immediately"; + case CSimpleSocket::SocketInterrupted: + return "Call was interrupted by a signal that was caught before a valid connection arrived."; + case CSimpleSocket::SocketConnectionAborted: + return "The connection has been aborted."; + case CSimpleSocket::SocketProtocolError: + return "Invalid protocol for operation."; + case CSimpleSocket::SocketFirewallError: + return "Firewall rules forbid connection."; + case CSimpleSocket::SocketInvalidSocketBuffer: + return "The receive buffer point outside the process's address space."; + case CSimpleSocket::SocketConnectionReset: + return "Connection was forcibly closed by the remote host."; + case CSimpleSocket::SocketAddressInUse: + return "Address already in use."; + case CSimpleSocket::SocketInvalidPointer: + return "Pointer type supplied as argument is invalid."; + case CSimpleSocket::SocketEunknown: + return "Unknown error please report to mark@carrierlabs.com"; + default: + return "No such CSimpleSocket error"; + } +} + +// Owns the thread that accepts TCP connections and forwards messages to clients; +// has a mutex +class client_pool { + typedef tthread::mutex mutex; + + mutex clients_lock; + std::vector clients; + + // TODO - delete this at some point + tthread::thread * accepter; + + static void accept_clients(void * client_pool_pointer) { + client_pool * p = reinterpret_cast(client_pool_pointer); + CPassiveSocket socket; + socket.Initialize(); + if (socket.Listen((const uint8 *)"0.0.0.0", 8008)) { + std::cout << "Listening on a socket" << std::endl; + } else { + std::cout << "Not listening: " << socket.GetSocketError() << std::endl; + std::cout << translate_socket_error(socket.GetSocketError()) << std::endl; + } + while (true) { + CActiveSocket * client = socket.Accept(); + if (client != 0) { + lock l(*p); + p->clients.push_back(client); + } + } + } + +public: + class lock { + tthread::lock_guard l; + public: + lock(client_pool & p) + : l(p.clients_lock) + { + } + }; + friend class client_pool::lock; + + client_pool() { + accepter = new tthread::thread(accept_clients, this); + } + + // MUST have lock + bool has_clients() { + return !clients.empty(); + } + + // MUST have lock + void add_client(CActiveSocket * sock) { + clients.push_back(sock); + } + + // MUST have lock + void broadcast(const std::string & message) { + unsigned int sz = htonl(message.size()); + for (size_t i = 0; i < clients.size(); ++i) { + clients[i]->Send(reinterpret_cast(&sz), sizeof(sz)); + clients[i]->Send((const uint8 *) message.c_str(), message.size()); + } + } +}; + +// A decorator (in the design pattern sense) of the DF renderer class. +// Sends the screen contents to a client_pool. +class renderer_decorator : public renderer { + // the renderer we're decorating + renderer * inner; + + // how many frames have passed since we last sent a frame + int framesNotPrinted; + + // set to false in the destructor + bool * alive; + + // clients to which we send the frame + client_pool clients; + + // The following three methods facilitate copying of state to the inner object + void set_inner_to_null() { + inner->screen = NULL; + inner->screentexpos = NULL; + inner->screentexpos_addcolor = NULL; + inner->screentexpos_grayscale = NULL; + inner->screentexpos_cf = NULL; + inner->screentexpos_cbr = NULL; + inner->screen_old = NULL; + inner->screentexpos_old = NULL; + inner->screentexpos_addcolor_old = NULL; + inner->screentexpos_grayscale_old = NULL; + inner->screentexpos_cf_old = NULL; + inner->screentexpos_cbr_old = NULL; + } + + void copy_from_inner() { + screen = inner->screen; + screentexpos = inner->screentexpos; + screentexpos_addcolor = inner->screentexpos_addcolor; + screentexpos_grayscale = inner->screentexpos_grayscale; + screentexpos_cf = inner->screentexpos_cf; + screentexpos_cbr = inner->screentexpos_cbr; + screen_old = inner->screen_old; + screentexpos_old = inner->screentexpos_old; + screentexpos_addcolor_old = inner->screentexpos_addcolor_old; + screentexpos_grayscale_old = inner->screentexpos_grayscale_old; + screentexpos_cf_old = inner->screentexpos_cf_old; + screentexpos_cbr_old = inner->screentexpos_cbr_old; + } + + void copy_to_inner() { + inner->screen = screen; + inner->screentexpos = screentexpos; + inner->screentexpos_addcolor = screentexpos_addcolor; + inner->screentexpos_grayscale = screentexpos_grayscale; + inner->screentexpos_cf = screentexpos_cf; + inner->screentexpos_cbr = screentexpos_cbr; + inner->screen_old = screen_old; + inner->screentexpos_old = screentexpos_old; + inner->screentexpos_addcolor_old = screentexpos_addcolor_old; + inner->screentexpos_grayscale_old = screentexpos_grayscale_old; + inner->screentexpos_cf_old = screentexpos_cf_old; + inner->screentexpos_cbr_old = screentexpos_cbr_old; + } + +public: + renderer_decorator(renderer * inner, bool * alive) + : inner(inner) + , framesNotPrinted(0) + , alive(alive) + { + copy_from_inner(); + } + virtual void update_tile(int x, int y) { + copy_to_inner(); + inner->update_tile(x, y); + } + virtual void update_all() { + copy_to_inner(); + inner->update_all(); + } + virtual void render() { + copy_to_inner(); + inner->render(); + + ++framesNotPrinted; + int gfps = enabler->calculated_gfps; + if (gfps == 0) gfps = 1; + // send a frame roughly every 128 mibiseconds (1 second = 1024 mibiseconds) + if ((framesNotPrinted * 1024) / gfps <= 128) return; + + client_pool::lock lock(clients); + if (!clients.has_clients()) return; + framesNotPrinted = 0; + std::stringstream frame; + frame << gps->dimx << ' ' << gps->dimy << " 0 0 " << gps->dimx << ' ' << gps->dimy << '\n'; + unsigned char * sc_ = gps->screen; + for (int y = 0; y < gps->dimy; ++y) { + unsigned char * sc = sc_; + for (int x = 0; x < gps->dimx; ++x) { + unsigned char ch = sc[0]; + unsigned char bold = (sc[3] != 0) * 8; + unsigned char translate[] = + { 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 }; + unsigned char fg = translate[(sc[1] + bold) % 16]; + unsigned char bg = translate[sc[2] % 16]*16; + frame.put(ch); + frame.put(fg+bg); + sc += 4*gps->dimy; + } + sc_ += 4; + } + clients.broadcast(frame.str()); + } + virtual void set_fullscreen() { inner->set_fullscreen(); } + virtual void zoom(zoom_commands cmd) { + copy_to_inner(); + inner->zoom(cmd); + } + virtual void resize(int w, int h) { + copy_to_inner(); + inner->resize(w, h); + copy_from_inner(); + } + virtual void grid_resize(int w, int h) { + copy_to_inner(); + inner->grid_resize(w, h); + copy_from_inner(); + } + virtual ~renderer_decorator() { + *alive = false; + if (inner) set_inner_to_null(); + delete inner; + inner = 0; + } + virtual bool get_mouse_coords(int &x, int &y) { return inner->get_mouse_coords(x, y); } + virtual bool uses_opengl() { return inner->uses_opengl(); } + + static renderer_decorator * hook(renderer *& ptr, bool * alive) { + renderer_decorator * r = new renderer_decorator(ptr, alive); + ptr = r; + return r; + } + + static void unhook(renderer *& ptr, renderer_decorator * dec, color_ostream & out) { + dec->copy_to_inner(); + ptr = dec->inner; + dec->inner = 0; + delete dec; + } +}; + +DFHACK_PLUGIN("dfstream"); + +inline renderer *& active_renderer() { + return reinterpret_cast(enabler->renderer); +} + +// This class is a smart pointer around a renderer_decorator. +// It should only be assigned r_d pointers that use the alive-pointer of this +// instance. +// If the r_d has been deleted by an external force, this smart pointer doesn't +// redelete it. +class auto_renderer_decorator { + renderer_decorator * p; +public: + // pass this member to the ctor of renderer_decorator + bool alive; + + auto_renderer_decorator() + : p(0) + { + } + + ~auto_renderer_decorator() { + reset(); + } + + void reset() { + if (*this) { + delete p; + p = 0; + } + } + + operator bool() { + return (p != 0) && alive; + } + + auto_renderer_decorator & operator=(renderer_decorator *p) { + reset(); + this->p = p; + } + + renderer_decorator * get() { + return p; + } + + renderer_decorator * operator->() { + return get(); + } +}; + +auto_renderer_decorator decorator; + +DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) +{ + if (!decorator) { + decorator = renderer_decorator::hook(active_renderer(), &decorator.alive); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + decorator.reset(); + return CR_OK; +} +// vim:set sw=4 sts=4 et: From 3a6b0357a29b4367798d73b6df6a4de45b140a39 Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 29 Oct 2012 16:56:22 -0500 Subject: [PATCH 188/196] Fix compilation on Windows, incorporate latest df-structures data --- plugins/dfstream.cpp | 113 ++++++++++--------------------------------- 1 file changed, 25 insertions(+), 88 deletions(-) diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp index 0815146b0..a7bc1015e 100644 --- a/plugins/dfstream.cpp +++ b/plugins/dfstream.cpp @@ -1,90 +1,26 @@ -// TODO: which of these includes are actually needed? #include "Core.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" +#include "DataDefs.h" #include "df/graphic.h" #include "df/enabler.h" -#include "df/viewscreen_unitlistst.h" -#include "df/interface_key.h" -#include "df/unit.h" -#include "df/unit_soul.h" -#include "df/unit_skill.h" -#include "df/creature_graphics_role.h" -#include "df/creature_raw.h" -#include "df/caste_raw.h" +#include "df/renderer.h" + +#include +#include +#include "PassiveSocket.h" +#include "tinythread.h" using namespace DFHack; using namespace df::enums; +using std::string; +using std::vector; using df::global::gps; using df::global::enabler; -//--- SNIP class definition from g_src ---------------------------------------- -enum zoom_commands { zoom_in, zoom_out, zoom_reset, zoom_fullscreen, zoom_resetgrid }; - -class renderer { - void cleanup_arrays(); -protected: - friend class renderer_decorator; - unsigned char *screen; - long *screentexpos; - char *screentexpos_addcolor; - unsigned char *screentexpos_grayscale; - unsigned char *screentexpos_cf; - unsigned char *screentexpos_cbr; - // For partial printing: - unsigned char *screen_old; - long *screentexpos_old; - char *screentexpos_addcolor_old; - unsigned char *screentexpos_grayscale_old; - unsigned char *screentexpos_cf_old; - unsigned char *screentexpos_cbr_old; - - //void gps_allocate(int x, int y); - //Either screen_to_texid(int x, int y); -public: - //void display(); - virtual void update_tile(int x, int y) = 0; - virtual void update_all() = 0; - virtual void render() = 0; - virtual void set_fullscreen(); // Should read from enabler.is_fullscreen() - virtual void zoom(zoom_commands cmd); - virtual void resize(int w, int h) = 0; - virtual void grid_resize(int w, int h) = 0; - //void swap_arrays(); - renderer() { - screen = NULL; - screentexpos = NULL; - screentexpos_addcolor = NULL; - screentexpos_grayscale = NULL; - screentexpos_cf = NULL; - screentexpos_cbr = NULL; - screen_old = NULL; - screentexpos_old = NULL; - screentexpos_addcolor_old = NULL; - screentexpos_grayscale_old = NULL; - screentexpos_cf_old = NULL; - screentexpos_cbr_old = NULL; - } - virtual ~renderer(); - virtual bool get_mouse_coords(int &x, int &y) = 0; - virtual bool uses_opengl(); -}; -//---- END class definition from g_src ---------------------------------------- - // The error messages are taken from the clsocket source code const char * translate_socket_error(CSimpleSocket::CSocketError err) { switch (err) { @@ -146,7 +82,7 @@ class client_pool { client_pool * p = reinterpret_cast(client_pool_pointer); CPassiveSocket socket; socket.Initialize(); - if (socket.Listen((const uint8 *)"0.0.0.0", 8008)) { + if (socket.Listen((const uint8_t *)"0.0.0.0", 8008)) { std::cout << "Listening on a socket" << std::endl; } else { std::cout << "Not listening: " << socket.GetSocketError() << std::endl; @@ -190,17 +126,17 @@ public: void broadcast(const std::string & message) { unsigned int sz = htonl(message.size()); for (size_t i = 0; i < clients.size(); ++i) { - clients[i]->Send(reinterpret_cast(&sz), sizeof(sz)); - clients[i]->Send((const uint8 *) message.c_str(), message.size()); + clients[i]->Send(reinterpret_cast(&sz), sizeof(sz)); + clients[i]->Send((const uint8_t *) message.c_str(), message.size()); } } }; // A decorator (in the design pattern sense) of the DF renderer class. // Sends the screen contents to a client_pool. -class renderer_decorator : public renderer { +class renderer_decorator : public df::renderer { // the renderer we're decorating - renderer * inner; + df::renderer * inner; // how many frames have passed since we last sent a frame int framesNotPrinted; @@ -258,7 +194,7 @@ class renderer_decorator : public renderer { } public: - renderer_decorator(renderer * inner, bool * alive) + renderer_decorator(df::renderer * inner, bool * alive) : inner(inner) , framesNotPrinted(0) , alive(alive) @@ -307,7 +243,7 @@ public: clients.broadcast(frame.str()); } virtual void set_fullscreen() { inner->set_fullscreen(); } - virtual void zoom(zoom_commands cmd) { + virtual void zoom(df::zoom_commands cmd) { copy_to_inner(); inner->zoom(cmd); } @@ -327,16 +263,16 @@ public: delete inner; inner = 0; } - virtual bool get_mouse_coords(int &x, int &y) { return inner->get_mouse_coords(x, y); } + virtual bool get_mouse_coords(int *x, int *y) { return inner->get_mouse_coords(x, y); } virtual bool uses_opengl() { return inner->uses_opengl(); } - static renderer_decorator * hook(renderer *& ptr, bool * alive) { + static renderer_decorator * hook(df::renderer *& ptr, bool * alive) { renderer_decorator * r = new renderer_decorator(ptr, alive); ptr = r; return r; } - static void unhook(renderer *& ptr, renderer_decorator * dec, color_ostream & out) { + static void unhook(df::renderer *& ptr, renderer_decorator * dec, color_ostream & out) { dec->copy_to_inner(); ptr = dec->inner; dec->inner = 0; @@ -346,8 +282,8 @@ public: DFHACK_PLUGIN("dfstream"); -inline renderer *& active_renderer() { - return reinterpret_cast(enabler->renderer); +inline df::renderer *& active_renderer() { + return enabler->renderer; } // This class is a smart pointer around a renderer_decorator. @@ -384,6 +320,7 @@ public: auto_renderer_decorator & operator=(renderer_decorator *p) { reset(); this->p = p; + return *this; } renderer_decorator * get() { From cd14bdfd43658d6a9e705f33a1efbfbfd9e7f1a8 Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 29 Oct 2012 22:29:07 -0500 Subject: [PATCH 189/196] Disable dfstream by default --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 1d20550ed..65c6e236c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -128,7 +128,7 @@ if (BUILD_SUPPORTED) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) - DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) + #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) endif() From dc2805b1f32aa26beb521c5e14ae3fee3ff2ccb6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 30 Oct 2012 10:38:32 +0400 Subject: [PATCH 190/196] Link the renderer vtable from libgraphics on linux. --- library/Core.cpp | 2 +- library/modules/Screen.cpp | 46 +++++++++++++++++++++++++++++++++++++- library/xml | 2 +- plugins/dfstream.cpp | 44 +++++++++++++++++++++++------------- 4 files changed, 75 insertions(+), 19 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 111774f26..26c0acbb0 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -869,7 +869,6 @@ bool Core::Init() // Init global object pointers df::global::InitGlobals(); - init_screen_module(this); cerr << "Initializing Console.\n"; // init the console. @@ -895,6 +894,7 @@ bool Core::Init() */ // initialize data defs virtual_identity::Init(this); + init_screen_module(this); // initialize common lua context Lua::Core::Init(con); diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index d89b3688d..e8d23261b 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -55,6 +55,7 @@ using namespace DFHack; #include "df/item.h" #include "df/job.h" #include "df/building.h" +#include "df/renderer.h" using namespace df::enums; using df::global::init; @@ -312,6 +313,47 @@ class DFHACK_EXPORT enabler_inputst { public: std::string GetKeyDisplay(int binding); }; + +class DFHACK_EXPORT renderer { + unsigned char *screen; + long *screentexpos; + char *screentexpos_addcolor; + unsigned char *screentexpos_grayscale; + unsigned char *screentexpos_cf; + unsigned char *screentexpos_cbr; + // For partial printing: + unsigned char *screen_old; + long *screentexpos_old; + char *screentexpos_addcolor_old; + unsigned char *screentexpos_grayscale_old; + unsigned char *screentexpos_cf_old; + unsigned char *screentexpos_cbr_old; +public: + virtual void update_tile(int x, int y) {}; + virtual void update_all() {}; + virtual void render() {}; + virtual void set_fullscreen(); + virtual void zoom(df::zoom_commands cmd); + virtual void resize(int w, int h) {}; + virtual void grid_resize(int w, int h) {}; + renderer() { + screen = NULL; + screentexpos = NULL; + screentexpos_addcolor = NULL; + screentexpos_grayscale = NULL; + screentexpos_cf = NULL; + screentexpos_cbr = NULL; + screen_old = NULL; + screentexpos_old = NULL; + screentexpos_addcolor_old = NULL; + screentexpos_grayscale_old = NULL; + screentexpos_cf_old = NULL; + screentexpos_cbr_old = NULL; + } + virtual ~renderer(); + virtual bool get_mouse_coords(int &x, int &y) { return false; } + virtual bool uses_opengl(); +}; #else struct less_sz { bool operator() (const string &a, const string &b) const { @@ -326,7 +368,9 @@ static std::map > *keydisplay = NULL; void init_screen_module(Core *core) { #ifdef _LINUX - core = core; + renderer tmp; + if (!strict_virtual_cast((virtual_ptr)&tmp)) + cerr << "Could not fetch the renderer vtable." << std::endl; #else if (!core->vinfo->getAddress("keydisplay", keydisplay)) keydisplay = NULL; diff --git a/library/xml b/library/xml index 4bfc5b197..378f91d09 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 4bfc5b197de9ca096a11b9c1594e043bd8ae707a +Subproject commit 378f91d0964e09e3cfa0ecbea100ef2cc12a59ab diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp index a7bc1015e..6144389d7 100644 --- a/plugins/dfstream.cpp +++ b/plugins/dfstream.cpp @@ -148,19 +148,19 @@ class renderer_decorator : public df::renderer { client_pool clients; // The following three methods facilitate copying of state to the inner object - void set_inner_to_null() { - inner->screen = NULL; - inner->screentexpos = NULL; - inner->screentexpos_addcolor = NULL; - inner->screentexpos_grayscale = NULL; - inner->screentexpos_cf = NULL; - inner->screentexpos_cbr = NULL; - inner->screen_old = NULL; - inner->screentexpos_old = NULL; - inner->screentexpos_addcolor_old = NULL; - inner->screentexpos_grayscale_old = NULL; - inner->screentexpos_cf_old = NULL; - inner->screentexpos_cbr_old = NULL; + void set_to_null() { + screen = NULL; + screentexpos = NULL; + screentexpos_addcolor = NULL; + screentexpos_grayscale = NULL; + screentexpos_cf = NULL; + screentexpos_cbr = NULL; + screen_old = NULL; + screentexpos_old = NULL; + screentexpos_addcolor_old = NULL; + screentexpos_grayscale_old = NULL; + screentexpos_cf_old = NULL; + screentexpos_cbr_old = NULL; } void copy_from_inner() { @@ -259,9 +259,12 @@ public: } virtual ~renderer_decorator() { *alive = false; - if (inner) set_inner_to_null(); - delete inner; - inner = 0; + if (inner) { + copy_to_inner(); + delete inner; + inner = 0; + } + set_to_null(); } virtual bool get_mouse_coords(int *x, int *y) { return inner->get_mouse_coords(x, y); } virtual bool uses_opengl() { return inner->uses_opengl(); } @@ -336,6 +339,11 @@ auto_renderer_decorator decorator; DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { + if (!df::renderer::_identity.can_allocate()) + { + out.printerr("Cannot allocate a renderer\n"); + return CR_OK; + } if (!decorator) { decorator = renderer_decorator::hook(active_renderer(), &decorator.alive); } @@ -344,6 +352,10 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector Date: Tue, 30 Oct 2012 12:40:26 +0400 Subject: [PATCH 191/196] Add a few utility functions to the main library. --- Lua API.html | 6 ++++++ Lua API.rst | 8 ++++++++ library/LuaApi.cpp | 2 ++ library/MiscUtils.cpp | 5 +++++ library/include/MiscUtils.h | 2 ++ library/include/modules/Items.h | 5 +++++ library/modules/Items.cpp | 14 ++++++++++++++ plugins/siege-engine.cpp | 5 ----- 8 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Lua API.html b/Lua API.html index be52ea985..a52347104 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1239,6 +1239,12 @@ Returns false in case of error.

  • dfhack.items.getContainedItems(item)

    Returns a list of items contained in this one.

  • +
  • dfhack.items.getHolderBuilding(item)

    +

    Returns the holder building or nil.

    +
  • +
  • dfhack.items.getHolderUnit(item)

    +

    Returns the holder unit or nil.

    +
  • dfhack.items.moveToGround(item,pos)

    Move the item to the ground at position. Returns false if impossible.

  • diff --git a/Lua API.rst b/Lua API.rst index 8dad3663b..bd712d301 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1019,6 +1019,14 @@ Items module Returns a list of items contained in this one. +* ``dfhack.items.getHolderBuilding(item)`` + + Returns the holder building or *nil*. + +* ``dfhack.items.getHolderUnit(item)`` + + Returns the holder unit or *nil*. + * ``dfhack.items.moveToGround(item,pos)`` Move the item to the ground at position. Returns *false* if impossible. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 6ca1188d9..e7424ad50 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -942,6 +942,8 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, getOwner), WRAPM(Items, setOwner), WRAPM(Items, getContainer), + WRAPM(Items, getHolderBuilding), + WRAPM(Items, getHolderUnit), WRAPM(Items, getDescription), WRAPM(Items, isCasteMaterial), WRAPM(Items, getSubtypeCount), diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index a05e7eba5..53c403026 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -148,6 +148,11 @@ bool prefix_matches(const std::string &prefix, const std::string &key, std::stri return false; } +int random_int(int max) +{ + return int(int64_t(rand())*max/(int64_t(RAND_MAX)+1)); +} + #ifdef LINUX_BUILD // Linux uint64_t GetTimeMs64() { diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index 61dd69297..f5b4e8ded 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -331,6 +331,8 @@ inline T clip_range(T a, T1 minv, T2 maxv) { return a; } +DFHACK_EXPORT int random_int(int max); + /** * Returns the amount of milliseconds elapsed since the UNIX epoch. * Works on both windows and linux. diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 217a0f28a..34ca98162 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -151,6 +151,11 @@ DFHACK_EXPORT df::item *getContainer(df::item *item); /// which items does it contain? DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector *items); +/// which building holds it? +DFHACK_EXPORT df::building *getHolderBuilding(df::item *item); +/// which unit holds it? +DFHACK_EXPORT df::unit *getHolderUnit(df::item *item); + /// Returns the true position of the item. DFHACK_EXPORT df::coord getPosition(df::item *item); diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index cf99d9426..877f8abe0 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -592,6 +592,20 @@ void Items::getContainedItems(df::item *item, std::vector *items) } } +df::building *Items::getHolderBuilding(df::item * item) +{ + auto ref = getGeneralRef(item, general_ref_type::BUILDING_HOLDER); + + return ref ? ref->getBuilding() : NULL; +} + +df::unit *Items::getHolderUnit(df::item * item) +{ + auto ref = getGeneralRef(item, general_ref_type::UNIT_HOLDER); + + return ref ? ref->getUnit() : NULL; +} + df::coord Items::getPosition(df::item *item) { CHECK_NULL_POINTER(item); diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index 5c7b8833a..4b2060e99 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -132,11 +132,6 @@ static void orient_engine(df::building_siegeenginest *bld, df::coord target) df::building_siegeenginest::Up; } -static int random_int(int val) -{ - return int(int64_t(rand())*val/RAND_MAX); -} - static int point_distance(df::coord speed) { return std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z))); From 4fa826d2b92a8461a422469dcd38cdf50ad97337 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 30 Oct 2012 13:11:57 +0400 Subject: [PATCH 192/196] Block instant creation of store in stockpile jobs when removing uniforms. --- plugins/fix-armory.cpp | 211 +++++++++++++++++++++++++++++++---------- 1 file changed, 163 insertions(+), 48 deletions(-) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 06adbbe4c..6bfdfe421 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -11,6 +11,7 @@ #include "modules/Items.h" #include "modules/Job.h" #include "modules/World.h" +#include "modules/Maps.h" #include "MiscUtils.h" @@ -21,6 +22,7 @@ #include "df/squad.h" #include "df/unit.h" #include "df/squad_position.h" +#include "df/items_other_id.h" #include "df/item_weaponst.h" #include "df/item_armorst.h" #include "df/item_helmst.h" @@ -84,6 +86,32 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) return CR_OK; } +// Check if the item is assigned to any use controlled by the military tab +static bool is_assigned_item(df::item *item) +{ + if (!ui) + return false; + + auto type = item->getType(); + int idx = binsearch_index(ui->equipment.items_assigned[type], item->id); + if (idx < 0) + return false; + + // Exclude weapons used by miners, wood cutters etc + switch (type) { + case item_type::WEAPON: + if (binsearch_index(ui->equipment.work_weapons, item->id) >= 0) + return false; + break; + + default: + break; + } + + return true; +} + +// Check if the item is assigned to the squad member who owns this armory building static bool belongs_to_position(df::item *item, df::building *holder) { int sid = holder->getSpecificSquad(); @@ -96,6 +124,7 @@ static bool belongs_to_position(df::item *item, df::building *holder) int position = holder->getSpecificPosition(); + // Weapon racks belong to the whole squad, i.e. can be used by any position if (position == -1 && holder->getType() == building_type::Weaponrack) { for (size_t i = 0; i < squad->positions.size(); i++) @@ -114,19 +143,17 @@ static bool belongs_to_position(df::item *item, df::building *holder) return false; } +// Check if the item is appropriately stored in an armory building static bool is_in_armory(df::item *item) { if (item->flags.bits.in_inventory || item->flags.bits.on_ground) return false; - auto holder_ref = Items::getGeneralRef(item, general_ref_type::BUILDING_HOLDER); - if (!holder_ref) - return false; - - auto holder = holder_ref->getBuilding(); + auto holder = Items::getHolderBuilding(item); if (!holder) return false; + // If indeed in a building, check if it is the right one return belongs_to_position(item, holder); } @@ -135,11 +162,47 @@ template struct armory_hook : Item { DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { + // Normally this vmethod is used to prevent stockpiling of uncollected webs. + // This uses it to also block stockpiling of items in the armory. if (is_in_armory(this)) return false; + // Also never let items in process of being removed from uniform be stockpiled at once + if (this->flags.bits.in_inventory) + { + auto holder = Items::getHolderUnit(this); + + if (holder && binsearch_index(holder->military.uniform_drop, this->id) >= 0) + { + if (is_assigned_item(this)) + return false; + } + } + return INTERPOSE_NEXT(isCollected)(); } + + DEFINE_VMETHOD_INTERPOSE(bool, moveToGround, (int16_t x, int16_t y, int16_t z)) + { + bool rv = INTERPOSE_NEXT(moveToGround)(x, y, z); + + // Prevent instant restockpiling of dropped assigned items. + if (is_assigned_item(this)) + { + // The original vmethod adds the item to this vector to force instant check + auto &ovec = world->items.other[items_other_id::ANY_RECENTLY_DROPPED]; + + // If it is indeed there, remove it + if (erase_from_vector(ovec, &df::item::id, this->id)) + { + // and queue it to be checked normally in 0.5-1 in-game days + this->stockpile_countdown = 12 + random_int(12); + this->stockpile_delay = 0; + } + } + + return rv; + } }; template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); @@ -153,11 +216,20 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollecte template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); + +// Check if this item is loose and can be moved to armory static bool can_store_item(df::item *item) { - if (!item || item->stockpile_countdown > 0) - return false; - if (item->flags.bits.in_job || item->flags.bits.removed || item->flags.bits.in_building || @@ -185,6 +257,69 @@ static bool can_store_item(df::item *item) return true; } +// Queue a job to store the item in the building, if possible +static bool try_store_item(df::building *target, df::item *item) +{ + // Check if the dwarves can path between the target and the item + df::coord tpos(target->centerx, target->centery, target->z); + df::coord ipos = Items::getPosition(item); + + if (!Maps::canWalkBetween(tpos, ipos)) + return false; + + // Check if the target has enough space left + if (!target->canStoreItem(item, true)) + return false; + + // Create the job + auto href = df::allocate(); + if (!href) + return false; + + auto job = new df::job(); + + job->pos = tpos; + + // Choose the job type - correct matching is needed so that + // later canStoreItem calls would take the job into account. + switch (target->getType()) { + case building_type::Weaponrack: + job->job_type = job_type::StoreWeapon; + // Without this flag dwarves will pick up the item, and + // then dismiss the job and put it back into the stockpile: + job->flags.bits.specific_dropoff = true; + break; + case building_type::Armorstand: + job->job_type = job_type::StoreArmor; + job->flags.bits.specific_dropoff = true; + break; + case building_type::Cabinet: + job->job_type = job_type::StoreItemInCabinet; + break; + default: + job->job_type = job_type::StoreItemInChest; + break; + } + + // job <-> item link + if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) + { + delete job; + delete href; + return false; + } + + // job <-> building link + href->building_id = target->id; + target->jobs.push_back(job); + job->references.push_back(href); + + // add to job list + Job::linkIntoWorld(job); + return true; +} + +// Store the item into the first building in the list that would accept it. static void try_store_item(std::vector &vec, df::item *item) { for (size_t i = 0; i < vec.size(); i++) @@ -193,56 +328,23 @@ static void try_store_item(std::vector &vec, df::item *item) if (!target) continue; - if (!target->canStoreItem(item, true)) - continue; - - auto href = df::allocate(); - if (!href) + if (try_store_item(target, item)) return; - - auto job = new df::job(); - - job->pos = df::coord(target->centerx, target->centery, target->z); - - switch (target->getType()) { - case building_type::Weaponrack: - job->job_type = job_type::StoreWeapon; - job->flags.bits.specific_dropoff = true; - break; - case building_type::Armorstand: - job->job_type = job_type::StoreArmor; - job->flags.bits.specific_dropoff = true; - break; - case building_type::Cabinet: - job->job_type = job_type::StoreItemInCabinet; - break; - default: - job->job_type = job_type::StoreItemInChest; - break; - } - - if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) - { - delete job; - delete href; - continue; - } - - href->building_id = target->id; - target->jobs.push_back(job); - job->references.push_back(href); - - Job::linkIntoWorld(job); - return; } } +// Store the items into appropriate armory buildings static void try_store_item_set(std::vector &items, df::squad *squad, df::squad_position *pos) { for (size_t j = 0; j < items.size(); j++) { auto item = df::item::find(items[j]); + // bad id, or cooldown timer still counting + if (!item || item->stockpile_countdown > 0) + continue; + + // not loose if (!can_store_item(item)) continue; @@ -264,9 +366,11 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_ev if (!is_enabled) return CR_OK; + // Process every 50th frame, sort of like regular stockpiling does if (DF_GLOBAL_VALUE(cur_year_tick,1) % 50 != 0) return CR_OK; + // Loop over squads auto &squads = df::global::world->squads.all; for (size_t si = 0; si < squads.size(); si++) @@ -304,6 +408,17 @@ static void enable_hooks(color_ostream &out, bool enable) enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); } static void enable_plugin(color_ostream &out) From b46885bb3cdca263d669565954f3d924e32a36e0 Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 30 Oct 2012 11:48:28 -0500 Subject: [PATCH 193/196] Fix compile --- plugins/dfstream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp index 6144389d7..1ed906881 100644 --- a/plugins/dfstream.cpp +++ b/plugins/dfstream.cpp @@ -339,7 +339,7 @@ auto_renderer_decorator decorator; DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (!df::renderer::_identity.can_allocate()) + if (!df::renderer::_identity.can_instantiate()) { out.printerr("Cannot allocate a renderer\n"); return CR_OK; From c4f544d796a7f3e319303728f54bd5e53e08de99 Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 30 Oct 2012 13:50:14 -0500 Subject: [PATCH 194/196] Fix stupid MSVC --- plugins/fix-armory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 6bfdfe421..9162c9456 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -172,7 +172,7 @@ template struct armory_hook : Item { { auto holder = Items::getHolderUnit(this); - if (holder && binsearch_index(holder->military.uniform_drop, this->id) >= 0) + if (holder && ::binsearch_index(holder->military.uniform_drop, this->id) >= 0) { if (is_assigned_item(this)) return false; From 05b73af9bb23ff379dc2904f2d4891e708be09de Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 30 Oct 2012 23:20:34 +0400 Subject: [PATCH 195/196] Extend fix-armory with support for storing ammo in barracks. --- library/xml | 2 +- plugins/fix-armory.cpp | 200 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 196 insertions(+), 6 deletions(-) diff --git a/library/xml b/library/xml index 378f91d09..fcacacce7 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 378f91d0964e09e3cfa0ecbea100ef2cc12a59ab +Subproject commit fcacacce7cf09cf70f011fea87b5be416da73457 diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 9162c9456..56374fdad 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -22,6 +22,7 @@ #include "df/squad.h" #include "df/unit.h" #include "df/squad_position.h" +#include "df/squad_ammo_spec.h" #include "df/items_other_id.h" #include "df/item_weaponst.h" #include "df/item_armorst.h" @@ -33,12 +34,15 @@ #include "df/item_flaskst.h" #include "df/item_backpackst.h" #include "df/item_quiverst.h" +#include "df/item_ammost.h" #include "df/building_weaponrackst.h" #include "df/building_armorstandst.h" #include "df/building_cabinetst.h" #include "df/building_boxst.h" +#include "df/building_squad_use.h" #include "df/job.h" #include "df/general_ref_building_holderst.h" +#include "df/general_ref_building_destinationst.h" #include "df/barrack_preference_category.h" #include @@ -111,6 +115,67 @@ static bool is_assigned_item(df::item *item) return true; } +static bool is_squad_ammo(df::item *item, df::squad *squad, bool train, bool combat) +{ + for (size_t i = 0; i < squad->ammunition.size(); i++) + { + auto spec = squad->ammunition[i]; + bool cs = spec->flags.bits.use_combat; + bool ts = spec->flags.bits.use_training; + + if (((cs || !ts) && combat) || (ts && train)) + { + if (binsearch_index(spec->assigned, item->id) >= 0) + return true; + } + } + + return false; +} + +static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_id) +{ + auto squads = holder->getSquads(); + + if (squads) + { + bool target = holder->getType() == building_type::ArcheryTarget; + + for (size_t i = 0; i < squads->size(); i++) + { + auto use = (*squads)[i]; + + if (squad_id >= 0 && use->squad_id != squad_id) + continue; + + bool combat = use->mode.bits.squad_eq; + bool train = target && use->mode.bits.train; + + if (combat || train) + { + auto squad = df::squad::find(use->squad_id); + + if (squad && is_squad_ammo(item, squad, combat, train)) + return true; + } + } + } + + for (size_t i = 0; i < holder->parents.size(); i++) + if (can_store_ammo_rec(item, holder->parents[i], squad_id)) + return true; + + return false; +} + +static bool can_store_ammo(df::item *item, df::building *holder) +{ + if (holder->getType() != building_type::Box) + return false; + + return can_store_ammo_rec(item, holder, holder->getSpecificSquad()); +} + // Check if the item is assigned to the squad member who owns this armory building static bool belongs_to_position(df::item *item, df::building *holder) { @@ -154,7 +219,10 @@ static bool is_in_armory(df::item *item) return false; // If indeed in a building, check if it is the right one - return belongs_to_position(item, holder); + if (item->getType() == item_type::AMMO) + return can_store_ammo(item, holder); + else + return belongs_to_position(item, holder); } template struct armory_hook : Item { @@ -196,6 +264,8 @@ template struct armory_hook : Item { if (erase_from_vector(ovec, &df::item::id, this->id)) { // and queue it to be checked normally in 0.5-1 in-game days + // (this is a grace period in case the uniform is dropped just + // for a moment due to a momentary glitch) this->stockpile_countdown = 12 + random_int(12); this->stockpile_delay = 0; } @@ -215,6 +285,7 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollect template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); @@ -226,10 +297,16 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGro template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); // Check if this item is loose and can be moved to armory static bool can_store_item(df::item *item) { + // bad id, or cooldown timer still counting + if (!item || item->stockpile_countdown > 0) + return false; + + // bad flags? if (item->flags.bits.in_job || item->flags.bits.removed || item->flags.bits.in_building || @@ -239,6 +316,7 @@ static bool can_store_item(df::item *item) item->flags.bits.on_fire) return false; + // in unit inventory? auto top = item; while (top->flags.bits.in_inventory) @@ -251,6 +329,7 @@ static bool can_store_item(df::item *item) if (Items::getGeneralRef(top, general_ref_type::UNIT_HOLDER)) return false; + // already in armory? if (is_in_armory(item)) return false; @@ -280,6 +359,8 @@ static bool try_store_item(df::building *target, df::item *item) job->pos = tpos; + bool dest = false; + // Choose the job type - correct matching is needed so that // later canStoreItem calls would take the job into account. switch (target->getType()) { @@ -295,9 +376,11 @@ static bool try_store_item(df::building *target, df::item *item) break; case building_type::Cabinet: job->job_type = job_type::StoreItemInCabinet; + dest = true; break; default: job->job_type = job_type::StoreItemInChest; + dest = true; break; } @@ -314,6 +397,17 @@ static bool try_store_item(df::building *target, df::item *item) target->jobs.push_back(job); job->references.push_back(href); + if (dest) + { + auto rdest = df::allocate(); + + if (rdest) + { + rdest->building_id = target->id; + job->references.push_back(rdest); + } + } + // add to job list Job::linkIntoWorld(job); return true; @@ -340,10 +434,6 @@ static void try_store_item_set(std::vector &items, df::squad *squad, df { auto item = df::item::find(items[j]); - // bad id, or cooldown timer still counting - if (!item || item->stockpile_countdown > 0) - continue; - // not loose if (!can_store_item(item)) continue; @@ -359,6 +449,102 @@ static void try_store_item_set(std::vector &items, df::squad *squad, df } } +// Use a data structure sorted by free space, to even out the load +typedef std::map > ammo_box_set; + +static void index_boxes(df::building *root, ammo_box_set &group) +{ + if (root->getType() == building_type::Box) + { + //color_ostream_proxy out(Core::getInstance().getConsole()); + //out.print("%08x %d\n", unsigned(root), root->getFreeCapacity(true)); + + group[root->getFreeCapacity(true)].insert(root); + } + + for (size_t i = 0; i < root->children.size(); i++) + index_boxes(root->children[i], group); +} + +static bool try_store_ammo(df::item *item, ammo_box_set &group) +{ + int volume = item->getVolume(); + + for (auto it = group.rbegin(); it != group.rend(); ++it) + { + if (it->first < volume) + break; + + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) + { + auto bld = *it2; + + if (try_store_item(bld, item)) + { + it->second.erase(bld); + group[bld->getFreeCapacity(true)].insert(bld); + return true; + } + } + } + + return false; +} + +// Collect chests for ammo storage +static void index_ammo_boxes(df::squad *squad, ammo_box_set &train_set, ammo_box_set &combat_set) +{ + for (size_t j = 0; j < squad->rooms.size(); j++) + { + auto room = squad->rooms[j]; + auto bld = df::building::find(room->building_id); + + // Chests in rooms marked for Squad Equipment used for combat ammo + if (room->mode.bits.squad_eq) + index_boxes(bld, combat_set); + + // Chests in archery ranges used for training-only ammo + if (room->mode.bits.train && bld->getType() == building_type::ArcheryTarget) + index_boxes(bld, train_set); + } +} + +// Store ammo into appropriate chests +static void try_store_ammo(df::squad *squad) +{ + bool indexed = false; + ammo_box_set train_set, combat_set; + + for (size_t i = 0; i < squad->ammunition.size(); i++) + { + auto spec = squad->ammunition[i]; + bool cs = spec->flags.bits.use_combat; + bool ts = spec->flags.bits.use_training; + + for (size_t j = 0; j < spec->assigned.size(); j++) + { + auto item = df::item::find(spec->assigned[j]); + + // not loose + if (!can_store_item(item)) + continue; + + if (!indexed) + { + indexed = true; + index_ammo_boxes(squad, train_set, combat_set); + } + + if (cs && try_store_ammo(item, combat_set)) + continue; + if (ts && try_store_ammo(item, train_set)) + continue; + if (!(ts || cs) && try_store_ammo(item, combat_set)) + continue; + } + } +} + static bool is_enabled = false; DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) @@ -383,6 +569,8 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_ev try_store_item_set(pos->assigned_items, squad, pos); } + + try_store_ammo(squad); } return CR_OK; @@ -408,6 +596,7 @@ static void enable_hooks(color_ostream &out, bool enable) enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); @@ -419,6 +608,7 @@ static void enable_hooks(color_ostream &out, bool enable) enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); } static void enable_plugin(color_ostream &out) From 69bdb0f8b8e5e0c1dbfb9a86695000d9e4c1e992 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 31 Oct 2012 11:31:31 +0400 Subject: [PATCH 196/196] Update fix-armory documentation and comments. --- NEWS | 6 +- Readme.html | 26 ++++++-- Readme.rst | 30 +++++++-- plugins/fix-armory.cpp | 147 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 180 insertions(+), 29 deletions(-) diff --git a/NEWS b/NEWS index 810f0cd55..0f9bf4c3d 100644 --- a/NEWS +++ b/NEWS @@ -10,8 +10,6 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option - New commands: - - fix-armory: activates a plugin that makes armor stands and weapon racks be used again. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. @@ -21,6 +19,10 @@ DFHack future - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. - logic fix: collecting webs produces silk, and ungathered webs are not thread. + New Fix Armory plugin: + Together with a couple of binary patches and the gui/assign-rack script, + this plugin makes weapon racks, armor stands, chests and cabinets in + properly designated barracks be used again for storage of squad equipment. DFHack v0.34.11-r2 diff --git a/Readme.html b/Readme.html index 0fa1d10be..0da481c88 100644 --- a/Readme.html +++ b/Readme.html @@ -1862,14 +1862,15 @@ armor stands, and in containers.

    Note

    In order to actually be used, weapon racks have to be patched and -assigned to a squad. See documentation for gui/assign-rack below.

    +manually assigned to a squad. See documentation for gui/assign-rack +below.

    Also, the default capacity of armor stands is way too low, so check out http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 for a patch addressing that too.

    Note that the buildings in the armory are used as follows:

      -
    • Weapon racks when fixed are used to store any assigned weapons. +
    • Weapon racks (when patched) are used to store any assigned weapons. Each rack belongs to a specific squad, and can store up to 5 weapons.
    • Armor stands belong to specific squad members and are used for armor and shields. By default one stand can store one item of each @@ -1878,11 +1879,9 @@ which should be sufficient.
    • Cabinets are used to store assigned clothing for a specific squad member. They are never used to store owned clothing.
    • Chests (boxes, etc) are used for a flask, backpack or quiver assigned -to the squad member. Due to a bug, food is dropped out of the backpack -when it is stored.
    • +to the squad member. Due to a probable bug, food is dropped out of the +backpack when it is stored.
    -

    Contrary to the common misconception, all these uses are controlled by the -Individual Equipment usage flag; the Squad Equipment mode means nothing.

    Warning

    Although armor stands, cabinets and chests properly belong only to one @@ -1890,6 +1889,21 @@ squad member, the owner of the building used to create the barracks will randomly use any containers inside the room. Thus, it is recommended to always create the armory from a weapon rack.

    +

    Contrary to the common misconception, all these uses are controlled by the +Individual Equipment usage flag. The Squad Equipment flag is actually +intended for ammo, but the game does even less in that area than for armor +and weapons. This plugin implements the following rules almost from scratch:

    +
      +
    • Combat ammo is stored in chests inside rooms with Squad Equipment enabled.
    • +
    • If a chest is assigned to a squad member due to Individual Equipment also +being set, it is only used for that squad's ammo; otherwise, any squads +with Squad Equipment on the room will use all of the chests at random.
    • +
    • Training ammo is stored in chests inside archery ranges designated from +archery targets, and controlled by the same Train flag as archery training +itself. This is inspired by some defunct code for weapon racks.
    • +
    +

    There are some minor traces in the game code to suggest that the first of +these rules is intended by Toady; the rest are invented by this plugin.

    diff --git a/Readme.rst b/Readme.rst index ad42035a5..0b2fbc378 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1084,7 +1084,8 @@ armor stands, and in containers. .. note:: In order to actually be used, weapon racks have to be patched and - assigned to a squad. See documentation for ``gui/assign-rack`` below. + manually assigned to a squad. See documentation for ``gui/assign-rack`` + below. Also, the default capacity of armor stands is way too low, so check out http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 @@ -1092,7 +1093,7 @@ armor stands, and in containers. Note that the buildings in the armory are used as follows: -* Weapon racks when fixed are used to store any assigned weapons. +* Weapon racks (when patched) are used to store any assigned weapons. Each rack belongs to a specific squad, and can store up to 5 weapons. * Armor stands belong to specific squad members and are used for @@ -1104,11 +1105,8 @@ Note that the buildings in the armory are used as follows: They are **never** used to store owned clothing. * Chests (boxes, etc) are used for a flask, backpack or quiver assigned - to the squad member. Due to a bug, food is dropped out of the backpack - when it is stored. - -Contrary to the common misconception, all these uses are controlled by the -*Individual Equipment* usage flag; the Squad Equipment mode means nothing. + to the squad member. Due to a probable bug, food is dropped out of the + backpack when it is stored. .. warning:: @@ -1117,6 +1115,24 @@ Contrary to the common misconception, all these uses are controlled by the randomly use any containers inside the room. Thus, it is recommended to always create the armory from a weapon rack. +Contrary to the common misconception, all these uses are controlled by the +*Individual Equipment* usage flag. The *Squad Equipment* flag is actually +intended for ammo, but the game does even less in that area than for armor +and weapons. This plugin implements the following rules almost from scratch: + +* Combat ammo is stored in chests inside rooms with Squad Equipment enabled. + +* If a chest is assigned to a squad member due to Individual Equipment also + being set, it is only used for that squad's ammo; otherwise, any squads + with Squad Equipment on the room will use all of the chests at random. + +* Training ammo is stored in chests inside archery ranges designated from + archery targets, and controlled by the same Train flag as archery training + itself. This is inspired by some defunct code for weapon racks. + +There are some minor traces in the game code to suggest that the first of +these rules is intended by Toady; the rest are invented by this plugin. + Mode switch and reclaim ======================= diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 56374fdad..ade9e4252 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -90,6 +90,48 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) return CR_OK; } +/* + * PART 1 - Stop restockpiling of items stored in the armory. + * + * For everything other than ammo this is quite straightforward, + * since the uniform switch code already tries to store items + * in barracks containers, and it is thus known what the intention + * is. Moreover these containers know which squad and member they + * belong to. + * + * For ammo there is no such code (in fact, ammo is never removed + * from a quiver, except when it is dropped itself), so I had to + * apply some improvisation. There is one place where BOX containers + * with Squad Equipment set are used as an anchor location for a + * pathfinding check when assigning ammo, so presumably that's + * the correct place. I however wanted to also differentiate + * training ammo, so came up with the following rules: + * + * 1. Combat ammo and ammo without any allowed use can be stored + * in BOXes marked for Squad Equipment, either directly or via + * containing room. No-allowed-use ammo is assumed to be reserved + * for emergency combat use, or something like that. + * 1a. If assigned to a squad position, that box can be used _only_ + * for ammo assigned to that specific _squad_. Otherwise, if + * multiple squads can use this room, they will store their + * ammo all mixed up. + * 2. Training ammo can be stored in BOXes within archery ranges + * (designated from archery target) that are enabled for Training. + * Train-only ammo in particular can _only_ be stored in such + * boxes. The inspiration for this comes from some broken code + * for weapon racks in Training rooms. + * + * As an additional feature (partially needed due to the constraints + * of working from an external hack), this plugin also blocks instant + * queueing of stockpiling jobs for items blocked on the ground, if + * these items are assigned to any squad. + * + * Since there apparently still are bugs that cause uniform items to be + * momentarily dropped on ground, this delay is set not to the minimally + * necessary 50 ticks, but to 0.5 - 1.0 in-game days, so as to provide a + * grace period during which the items can be instantly picked up again. + */ + // Check if the item is assigned to any use controlled by the military tab static bool is_assigned_item(df::item *item) { @@ -104,6 +146,8 @@ static bool is_assigned_item(df::item *item) // Exclude weapons used by miners, wood cutters etc switch (type) { case item_type::WEAPON: + // the game code also checks this for ammo, funnily enough + // maybe it's not just for weapons?.. if (binsearch_index(ui->equipment.work_weapons, item->id) >= 0) return false; break; @@ -115,6 +159,7 @@ static bool is_assigned_item(df::item *item) return true; } +// Check if this ammo item is assigned to this squad with one of the specified uses static bool is_squad_ammo(df::item *item, df::squad *squad, bool train, bool combat) { for (size_t i = 0; i < squad->ammunition.size(); i++) @@ -123,6 +168,7 @@ static bool is_squad_ammo(df::item *item, df::squad *squad, bool train, bool com bool cs = spec->flags.bits.use_combat; bool ts = spec->flags.bits.use_training; + // no-use ammo assumed to be combat if (((cs || !ts) && combat) || (ts && train)) { if (binsearch_index(spec->assigned, item->id) >= 0) @@ -133,6 +179,7 @@ static bool is_squad_ammo(df::item *item, df::squad *squad, bool train, bool com return false; } +// Recursively check room parents to find out if this ammo item is allowed here static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_id) { auto squads = holder->getSquads(); @@ -145,10 +192,13 @@ static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_i { auto use = (*squads)[i]; + // For containers assigned to a squad, only consider that squad if (squad_id >= 0 && use->squad_id != squad_id) continue; + // Squad Equipment -> combat bool combat = use->mode.bits.squad_eq; + // Archery target with Train -> training bool train = target && use->mode.bits.train; if (combat || train) @@ -168,11 +218,14 @@ static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_i return false; } +// Check if the ammo item can be stored in this container static bool can_store_ammo(df::item *item, df::building *holder) { + // Only chests if (holder->getType() != building_type::Box) return false; + // with appropriate flags set return can_store_ammo_rec(item, holder, holder->getSpecificSquad()); } @@ -225,33 +278,71 @@ static bool is_in_armory(df::item *item) return belongs_to_position(item, holder); } +/* + * Hooks used to affect stockpiling code as it runs, and prevent it + * from doing unwanted stuff. + * + * Toady can simply add these checks directly to the stockpiling code; + * we have to abuse some handy item vmethods. + */ + template struct armory_hook : Item { typedef Item interpose_base; + /* + * This vmethod is called by the actual stockpiling code before it + * tries to queue a job, and is normally used to prevent stockpiling + * of uncollected webs. + */ DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { - // Normally this vmethod is used to prevent stockpiling of uncollected webs. - // This uses it to also block stockpiling of items in the armory. + // Block stockpiling of items in the armory. if (is_in_armory(this)) return false; - // Also never let items in process of being removed from uniform be stockpiled at once + /* + * When an item is removed from inventory due to Pickup Equipment + * process, the unit code directly invokes the stockpiling code + * and thus creates the job even before the item is actually dropped + * on the ground. We don't want this at all, especially due to the + * grace period idea. + * + * With access to source, that code can just be changed to simply + * drop the item on ground, without running stockpiling code. + */ if (this->flags.bits.in_inventory) { auto holder = Items::getHolderUnit(this); - if (holder && ::binsearch_index(holder->military.uniform_drop, this->id) >= 0) + // When that call happens, the item is still in inventory + if (holder && is_assigned_item(this)) { - if (is_assigned_item(this)) + // And its ID is is this vector + if (::binsearch_index(holder->military.uniform_drop, this->id) >= 0) return false; } } + // Call the original vmethod return INTERPOSE_NEXT(isCollected)(); } + /* + * This vmethod is used to actually put the item on the ground. + * When it does that, it also adds it to a vector of items to be + * instanly restockpiled by a loop in another part of the code. + * + * We don't want this either, even more than when removing from + * uniform, because this can happen in lots of various situations, + * including deconstructed containers etc, and we want our own + * armory code below to have a chance to look at the item. + * + * The logical place for this code is in the loop that processes + * that vector, but that part is not virtual. + */ DEFINE_VMETHOD_INTERPOSE(bool, moveToGround, (int16_t x, int16_t y, int16_t z)) { + // First, let it do its work bool rv = INTERPOSE_NEXT(moveToGround)(x, y, z); // Prevent instant restockpiling of dropped assigned items. @@ -299,6 +390,15 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToG template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +/* + * PART 2 - Actively queue jobs to haul assigned items to the armory. + * + * The logical place for this of course is in the same code that decides + * to put stuff in stockpiles, alongside the checks to prevent moving + * stuff away from the armory. We just run it independently every 50 + * simulation frames. + */ + // Check if this item is loose and can be moved to armory static bool can_store_item(df::item *item) { @@ -397,6 +497,8 @@ static bool try_store_item(df::building *target, df::item *item) target->jobs.push_back(job); job->references.push_back(href); + // Two of the jobs need this link to find the job in canStoreItem(). + // They also don't actually need BUILDING_HOLDER, but it doesn't hurt. if (dest) { auto rdest = df::allocate(); @@ -438,6 +540,7 @@ static void try_store_item_set(std::vector &items, df::squad *squad, df if (!can_store_item(item)) continue; + // queue jobs to put it in the appropriate container if (item->isWeapon()) try_store_item(squad->rack_combat, item); else if (item->isClothing()) @@ -449,23 +552,30 @@ static void try_store_item_set(std::vector &items, df::squad *squad, df } } -// Use a data structure sorted by free space, to even out the load +// For storing ammo, use a data structure sorted by free space, to even out the load typedef std::map > ammo_box_set; -static void index_boxes(df::building *root, ammo_box_set &group) +// Enumerate boxes in the room, adding them to the set +static void index_boxes(df::building *root, ammo_box_set &group, int squad_id) { if (root->getType() == building_type::Box) { - //color_ostream_proxy out(Core::getInstance().getConsole()); - //out.print("%08x %d\n", unsigned(root), root->getFreeCapacity(true)); + int id = root->getSpecificSquad(); - group[root->getFreeCapacity(true)].insert(root); + if (id < 0 || id == squad_id) + { + //color_ostream_proxy out(Core::getInstance().getConsole()); + //out.print("%08x %d\n", unsigned(root), root->getFreeCapacity(true)); + + group[root->getFreeCapacity(true)].insert(root); + } } for (size_t i = 0; i < root->children.size(); i++) - index_boxes(root->children[i], group); + index_boxes(root->children[i], group, squad_id); } +// Loop over the set from most empty to least empty static bool try_store_ammo(df::item *item, ammo_box_set &group) { int volume = item->getVolume(); @@ -501,11 +611,11 @@ static void index_ammo_boxes(df::squad *squad, ammo_box_set &train_set, ammo_box // Chests in rooms marked for Squad Equipment used for combat ammo if (room->mode.bits.squad_eq) - index_boxes(bld, combat_set); + index_boxes(bld, combat_set, squad->id); - // Chests in archery ranges used for training-only ammo + // Chests in archery ranges used for training ammo if (room->mode.bits.train && bld->getType() == building_type::ArcheryTarget) - index_boxes(bld, train_set); + index_boxes(bld, train_set, squad->id); } } @@ -529,16 +639,25 @@ static void try_store_ammo(df::squad *squad) if (!can_store_item(item)) continue; + // compute the maps lazily if (!indexed) { indexed = true; index_ammo_boxes(squad, train_set, combat_set); } + // BUG: if the same container is in both sets, + // when a job is queued, the free space in the other + // set will not be updated, which could lead to uneven + // loading - but not to overflowing the container! + + // As said above, combat goes into Squad Equipment if (cs && try_store_ammo(item, combat_set)) continue; + // Training goes into Archery Range with Train if (ts && try_store_ammo(item, train_set)) continue; + // No use goes into combat if (!(ts || cs) && try_store_ammo(item, combat_set)) continue; }