Merge remote-tracking branch 'origin/develop' into 0.40.24-dev

develop
lethosor 2015-01-17 08:53:49 -05:00
commit 4d194da530
18 changed files with 879 additions and 5958 deletions

@ -188,5 +188,10 @@ ELSEIF(WIN32)
SET(CPACK_GENERATOR "ZIP")
ENDIF()
set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY 0)
set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${CMAKE_SYSTEM_NAME}")
IF(APPLE)
set(DFHACK_PACKAGE_PLATFORM_NAME OSX)
ELSE()
set(DFHACK_PACKAGE_PLATFORM_NAME ${CMAKE_SYSTEM_NAME})
ENDIF()
set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${DFHACK_PACKAGE_PLATFORM_NAME}")
INCLUDE(CPack)

19
NEWS

@ -1,6 +1,19 @@
DFHack Future
Internals
EventManager: fixed crash error with EQUIPMENT_CHANGE event.
key modifier state exposed to Lua
Fixes
dfhack script can now be run from other directories on OSX
New Plugins
blueprint: export part of your fortress to quickfort .csv files
New Scripts
hotkey-notes: print key, name, and jump position of hotkeys
Removed
embark.lua
needs_porting/*
New Tweaks
Misc Improvements
added support for searching more lists
DFHack 0.40.23-r1
Internals
@ -24,7 +37,7 @@ DFHack 0.40.23-r1
Fixed a display issue with PRINT_MODE:TEXT
Fixed a symbol error (MapExtras::BiomeInfo::MAX_LAYERS) when compiling DFHack in Debug mode
New Plugins
fortplan: designate construction of (limited) buildings from .csv file, quickfort-style
fortplan: designate construction of (limited) buildings from .csv file, quickfort-style
New Scripts
gui/stockpiles: an in-game interface for saving and loading stockpile
settings files.

File diff suppressed because it is too large Load Diff

@ -1477,6 +1477,23 @@ Options:
:maps: Exports all seventeen detailed maps
:all: Equivalent to calling all of the above, in that order
blueprint
---------
Exports a portion of your fortress into QuickFort style blueprint files.::
blueprint <x> <y> <z> <name> [dig] [build] [place] [query]
Options:
:x,y,z: Size of map area to export
:name: Name of export files
:dig: Export dig commands to "<name>-dig.csv"
:build: Export build commands to "<name>-build.csv"
:place: Export stockpile commands to "<name>-place.csv"
:query: Export query commands to "<name>-query.csv"
If only region and name are given, all exports are performed.
Job management
==============
@ -2291,10 +2308,6 @@ dfstatus
========
Show a quick overview of critical stock quantities, including food, dirnks, wood, and various bars.
embark
======
Allows to embark anywhere. Currently windows only.
exterminate
===========
Kills any unit of a given race.
@ -2394,6 +2407,10 @@ is "hfs-pit 1 0 0", ie single-tile wide with no walls or stairs.
First example is a four-across pit with stairs but no walls; second is a
two-across pit with stairs but no walls.
hotkey-notes
============
Lists the key, name, and jump position of your hotkeys.
lever
=====
Allow manipulation of in-game levers from the dfhack console.

@ -1091,6 +1091,7 @@ bool Core::Init()
screen_window = new Windows::top_level_window();
screen_window->addChild(new Windows::dfhack_dummy(5,10));
started = true;
modstate = 0;
cerr << "Starting the TCP listener.\n";
server = new ServerMain();
@ -1579,7 +1580,7 @@ int UnicodeAwareSym(const SDL::KeyboardEvent& ke)
//MEMO: return false if event is consumed
int Core::DFH_SDL_Event(SDL::Event* ev)
{
static bool alt = 0;
//static bool alt = 0;
// do NOT process events before we are ready.
if(!started) return true;
@ -1589,31 +1590,27 @@ int Core::DFH_SDL_Event(SDL::Event* ev)
{
auto ke = (SDL::KeyboardEvent *)ev;
if (ke->ksym.sym == SDL::K_LALT || ke->ksym.sym == SDL::K_RALT)
{
alt = (ev->type == SDL::ET_KEYDOWN);
}
else
if(ke->state == SDL::BTN_PRESSED && !hotkey_states[ke->ksym.sym])
if (ke->ksym.sym == SDL::K_LSHIFT || ke->ksym.sym == SDL::K_RSHIFT)
modstate = (ev->type == SDL::ET_KEYDOWN) ? modstate | MOD_SHIFT : modstate & ~MOD_SHIFT;
else if (ke->ksym.sym == SDL::K_LCTRL || ke->ksym.sym == SDL::K_RCTRL)
modstate = (ev->type == SDL::ET_KEYDOWN) ? modstate | MOD_CTRL : modstate & ~MOD_CTRL;
else if (ke->ksym.sym == SDL::K_LALT || ke->ksym.sym == SDL::K_RALT)
modstate = (ev->type == SDL::ET_KEYDOWN) ? modstate | MOD_ALT : modstate & ~MOD_ALT;
else if(ke->state == SDL::BTN_PRESSED && !hotkey_states[ke->ksym.sym])
{
hotkey_states[ke->ksym.sym] = true;
int mod = 0;
if (ke->ksym.mod & SDL::KMOD_SHIFT) mod |= 1;
if (ke->ksym.mod & SDL::KMOD_CTRL) mod |= 2;
if (alt) mod |= 4;
// Use unicode so Windows gives the correct value for the
// user's Input Language
if((ke->ksym.unicode & 0xff80) == 0)
{
int key = UnicodeAwareSym(*ke);
SelectHotkey(key, mod);
SelectHotkey(key, modstate);
}
else
{
// Pretend non-ascii characters don't happen:
SelectHotkey(ke->ksym.sym, mod);
SelectHotkey(ke->ksym.sym, modstate);
}
}
else if(ke->state == SDL::BTN_RELEASED)

@ -2008,10 +2008,12 @@ static void *checkaddr(lua_State *L, int idx, bool allow_null = false)
static uint32_t getImageBase() { return Core::getInstance().p->getBase(); }
static int getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); }
static int8_t getModstate() { return Core::getInstance().getModstate(); }
static const LuaWrapper::FunctionReg dfhack_internal_module[] = {
WRAP(getImageBase),
WRAP(getRebaseDelta),
WRAP(getModstate),
{ NULL, NULL }
};
@ -2351,6 +2353,22 @@ static int internal_runCommand(lua_State *L)
return 1;
}
static int internal_getModifiers(lua_State *L)
{
int8_t modstate = Core::getInstance().getModstate();
lua_newtable(L);
lua_pushstring(L, "shift");
lua_pushboolean(L, modstate & MOD_SHIFT);
lua_settable(L, -3);
lua_pushstring(L, "ctrl");
lua_pushboolean(L, modstate & MOD_CTRL);
lua_settable(L, -3);
lua_pushstring(L, "alt");
lua_pushboolean(L, modstate & MOD_ALT);
lua_settable(L, -3);
return 1;
}
static const luaL_Reg dfhack_internal_funcs[] = {
{ "getAddress", internal_getAddress },
{ "setAddress", internal_setAddress },
@ -2365,6 +2383,7 @@ static const luaL_Reg dfhack_internal_funcs[] = {
{ "diffscan", internal_diffscan },
{ "getDir", internal_getDir },
{ "runCommand", internal_runCommand },
{ "getModifiers", internal_getModifiers },
{ NULL, NULL }
};

@ -36,6 +36,10 @@ distribution.
#include "RemoteClient.h"
#define MOD_SHIFT 1
#define MOD_CTRL 2
#define MOD_ALT 4
struct WINDOW;
namespace tthread
@ -142,6 +146,7 @@ namespace DFHack
bool ClearKeyBindings(std::string keyspec);
bool AddKeyBinding(std::string keyspec, std::string cmdline);
std::vector<std::string> ListKeyBindings(std::string keyspec);
int8_t getModstate() { return modstate; }
std::string getHackPath();
@ -216,6 +221,7 @@ namespace DFHack
std::string cmdline;
std::string focus;
};
int8_t modstate;
std::map<int, std::vector<KeyBinding> > key_bindings;
std::map<int, bool> hotkey_states;

@ -763,8 +763,6 @@ static void manageEquipmentEvent(color_ostream& out) {
handle.eventHandler(out, (void*)&data);
}
}
if ( !hadEquipment )
delete temp;
//check for dropped items
for ( auto b = v.begin(); b != v.end(); b++ ) {
InventoryItem i = *b;
@ -777,6 +775,8 @@ static void manageEquipmentEvent(color_ostream& out) {
handle.eventHandler(out, (void*)&data);
}
}
if ( !hadEquipment )
delete temp;
//update equipment
vector<InventoryItem>& equipment = equipmentLog[unit->id];

@ -1,9 +0,0 @@
TODO:
copypaste.cpp
high value target - a proof of concept plugin to allow copy-pasting in DF; does both terrain and buildings/constructions
creaturemanager.cpp
modify skills and labors of creatures, kill creatures, etc; impressive but I suspect most functions implemented elsewhere
In progress:
hotkey-notes.lua
prints list of hotkeys with name and jump position

@ -1,479 +0,0 @@
// Console version of DF copy paste, proof of concept
// By belal
#include <iostream>
#include <iomanip>
#include <climits>
#include <vector>
#include <sstream>
#include <ctime>
#include <cstdio>
#include <fstream>
#define DFHACK_WANT_MISCUTILS
#define DFHACK_WANT_TILETYPES
#include <DFHack.h>
#include "modules/WindowIO.h"
using namespace DFHack;
//bool waitTillCursorState(DFHack::Context *DF, bool On);
//bool waitTillCursorPositionState(DFHack::Context *DF, int32_t x,int32_t y, int32_t z);
//change this if you are having problems getting correct results, lower if you would like to go faster
//const int WAIT_AMT = 25;
void sort(uint32_t &a,uint32_t &b)
{
if(a > b){
uint32_t c = b;
b = a;
a = c;
}
}
void sort(int32_t &a,int32_t &b)
{
if(a > b){
int16_t c = b;
b = a;
a = c;
}
}
void printVecOfVec(ostream &out, vector<vector<vector<string> > >vec,char sep)
{
for(size_t k=0;k<vec.size();k++)
{
for(size_t i =0;i<vec[k].size();i++)
{
for(size_t j=0;j<vec[k][i].size();j++)
{
out << vec[k][i][j];
if(j==vec[k][i].size()-1)
{
out << "\n";
}
else
{
out << sep;
}
}
}
out << "#<\n";
}
}
int main (int numargs, const char ** args)
{
map<string, string> buildCommands;
buildCommands["building_stockpilest"]="";
buildCommands["building_zonest"]="";
buildCommands["building_construction_blueprintst"]="";
buildCommands["building_wagonst"]="";
buildCommands["building_armor_standst"]="a";
buildCommands["building_bedst"]="b";
buildCommands["building_seatst"]="c";
buildCommands["building_burial_receptaclest"]="n";
buildCommands["building_doorst"]="d";
buildCommands["building_floodgatest"]="x";
buildCommands["building_floor_hatchst"]="H";
buildCommands["building_wall_gratest"]="W";
buildCommands["building_floor_gratest"]="G";
buildCommands["building_vertical_barsst"]="B";
buildCommands["building_floor_barsst"]="alt-b";
buildCommands["building_cabinetst"]="f";
buildCommands["building_containerst"]="h";
buildCommands["building_shopst"]="";
buildCommands["building_workshopst"]="";
buildCommands["building_alchemists_laboratoryst"]="wa";
buildCommands["building_carpenters_workshopst"]="wc";
buildCommands["building_farmers_workshopst"]="ww";
buildCommands["building_masons_workshopst"]="wm";
buildCommands["building_craftdwarfs_workshopst"]="wr";
buildCommands["building_jewelers_workshopst"]="wj";
buildCommands["building_metalsmiths_workshopst"]="wf";
buildCommands["building_magma_forgest"]="";
buildCommands["building_bowyers_workshopst"]="wb";
buildCommands["building_mechanics_workshopst"]="wt";
buildCommands["building_siege_workshopst"]="ws";
buildCommands["building_butchers_shopst"]="wU";
buildCommands["building_leather_worksst"]="we";
buildCommands["building_tanners_shopst"]="wn";
buildCommands["building_clothiers_shopst"]="wk";
buildCommands["building_fisheryst"]="wh";
buildCommands["building_stillst"]="wl";
buildCommands["building_loomst"]="wo";
buildCommands["building_quernst"]="wq";
buildCommands["building_kennelsst"]="k";
buildCommands["building_kitchenst"]="wz";
buildCommands["building_asheryst"]="wy";
buildCommands["building_dyers_shopst"]="wd";
buildCommands["building_millstonest"]="wM";
buildCommands["building_farm_plotst"]="p";
buildCommands["building_weapon_rackst"]="r";
buildCommands["building_statuest"]="s";
buildCommands["building_tablest"]="t";
buildCommands["building_paved_roadst"]="o";
buildCommands["building_bridgest"]="g";
buildCommands["building_wellst"]="l";
buildCommands["building_siege enginest"]="i";
buildCommands["building_catapultst"]="ic";
buildCommands["building_ballistast"]="ib";
buildCommands["building_furnacest"]="";
buildCommands["building_wood_furnacest"]="ew";
buildCommands["building_smelterst"]="es";
buildCommands["building_glass_furnacest"]="ek";
buildCommands["building_kilnst"]="ek";
buildCommands["building_magma_smelterst"]="es";
buildCommands["building_magma_glass_furnacest"]="ek";
buildCommands["building_magma_kilnst"]="ek";
buildCommands["building_glass_windowst"]="y";
buildCommands["building_gem_windowst"]="Y";
buildCommands["building_tradedepotst"]="D";
buildCommands["building_mechanismst"]="";
buildCommands["building_leverst"]="Tl";
buildCommands["building_pressure_platest"]="Tp";
buildCommands["building_cage_trapst"]="Tc";
buildCommands["building_stonefall_trapst"]="Ts";
buildCommands["building_weapon_trapst"]="Tw";
buildCommands["building_spikest"]="";
buildCommands["building_animal_trapst"]="m";
buildCommands["building_screw_pumpst"]="Ms";
buildCommands["building_water_wheelst"]="Mw";
buildCommands["building_windmillst"]="Mm";
buildCommands["building_gear_assemblyst"]="Mg";
buildCommands["building_horizontal_axlest"]="Mh";
buildCommands["building_vertical_axlest"]="Mv";
buildCommands["building_supportst"]="S";
buildCommands["building_cagest"]="j";
buildCommands["building_archery_targetst"]="A";
buildCommands["building_restraintst"]="v";
DFHack::ContextManager DFMgr("Memory.xml");
DFHack::Context *DF = DFMgr.getSingleContext();
try
{
DF->Attach();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
#ifndef LINUX_BUILD
cin.ignore();
#endif
return 1;
}
DFHack::Gui *Gui = DF->getGui();
DFHack::VersionInfo* mem = DF->getMemoryInfo();
DFHack::Process * p = DF->getProcess();
OffsetGroup * OG_Maps = mem->getGroup("Maps");
OffsetGroup * OG_MapBlock = OG_Maps->getGroup("block");
OffsetGroup * OG_LocalFt = OG_Maps->getGroup("features")->getGroup("local");
uint32_t designations = OG_MapBlock->getOffset("designation");
uint32_t block_feature1 = OG_MapBlock->getOffset("feature_local");
uint32_t block_feature2 = OG_MapBlock->getOffset("feature_global");
uint32_t region_x_offset = OG_Maps->getAddress("region_x");
uint32_t region_y_offset = OG_Maps->getAddress("region_y");
uint32_t region_z_offset = OG_Maps->getAddress("region_z");
uint32_t feature1_start_ptr = OG_LocalFt->getAddress("start_ptr");
int32_t regionX, regionY, regionZ;
// read position of the region inside DF world
p->readDWord (region_x_offset, (uint32_t &)regionX);
p->readDWord (region_y_offset, (uint32_t &)regionY);
p->readDWord (region_z_offset, (uint32_t &)regionZ);
while(1){
int32_t cx1,cy1,cz1;
cx1 = -30000;
while(cx1 == -30000)
{
DF->ForceResume();
cout << "Set cursor at first position, then press any key";
cin.ignore();
DF->Suspend();
Gui->getCursorCoords(cx1,cy1,cz1);
}
uint32_t tx1,ty1,tz1;
tx1 = cx1/16;
ty1 = cy1/16;
tz1 = cz1;
int32_t cx2,cy2,cz2;
cx2 = -30000;
while(cx2 == -30000)
{
DF->Resume();
cout << "Set cursor at second position, then press any key";
cin.ignore();
DF->Suspend();
Gui->getCursorCoords(cx2,cy2,cz2);
}
uint32_t tx2,ty2,tz2;
tx2 = cx2/16;
ty2 = cy2/16;
tz2 = cz2;
sort(tx1,tx2);
sort(ty1,ty2);
sort(tz1,tz2);
sort(cx1,cx2);
sort(cy1,cy2);
sort(cz1,cz2);
vector <vector<vector<string> > >dig(cz2-cz1+1,vector<vector<string> >(cy2-cy1+1,vector<string>(cx2-cx1+1)));
vector <vector<vector<string> > >build(cz2-cz1+1,vector<vector<string> >(cy2-cy1+1,vector<string>(cx2-cx1+1)));
mapblock40d block;
DFHack::Maps *Maps = DF->getMaps();
Maps->Start();
for(uint32_t y = ty1;y<=ty2;y++)
{
for(uint32_t x = tx1;x<=tx2;x++)
{
for(uint32_t z = tz1;z<=tz2;z++)
{
if(Maps->isValidBlock(x,y,z))
{
if(Maps->ReadBlock40d(x,y,z,&block))
{
int ystart,yend,xstart,xend;
ystart=xstart=0;
yend=xend=15;
if(y == ty2)
{
yend = cy2 % 16;
}
if(y == ty1)
{
ystart = cy1 % 16;
}
if(x == tx2)
{
xend = cx2 % 16;
}
if(x == tx1)
{
xstart = cx1 % 16;
}
int zidx = z-tz1;
for(int yy = ystart; yy <= yend;yy++)
{
int yidx = yy+(16*(y-ty1)-(cy1%16));
for(int xx = xstart; xx <= xend;xx++)
{
int xidx = xx+(16*(x-tx1)-(cx1%16));
int16_t tt = block.tiletypes[xx][yy];
DFHack::TileShape ts = DFHack::tileShape(tt);
if(DFHack::isOpenTerrain(tt) || DFHack::isFloorTerrain(tt))
{
dig[zidx][yidx][xidx] = "d";
}
else if(DFHack::STAIR_DOWN == ts)
{
dig [zidx][yidx][xidx] = "j";
build [zidx][yidx][xidx] = "Cd";
}
else if(DFHack::STAIR_UP == ts)
{
dig [zidx][yidx][xidx] = "u";
build [zidx][yidx][xidx] = "Cu";
}
else if(DFHack::STAIR_UPDOWN == ts)
{
dig [zidx][yidx][xidx] = "i";
build [zidx][yidx][xidx] = "Cx";
}
else if(DFHack::isRampTerrain(tt))
{
dig [zidx][yidx][xidx] = "r";
build [zidx][yidx][xidx] = "Cr";
}
else if(DFHack::isWallTerrain(tt))
{
build [zidx][yidx][xidx] = "Cw";
}
}
yidx++;
}
}
}
}
}
}
DFHack::Buildings * Bld = DF->getBuildings();
std::map <uint32_t, std::string> custom_workshop_types;
uint32_t numBuildings;
if(Bld->Start(numBuildings))
{
Bld->ReadCustomWorkshopTypes(custom_workshop_types);
for(uint32_t i = 0; i < numBuildings; i++)
{
DFHack::t_building temp;
Bld->Read(i, temp);
if(temp.type != 0xFFFFFFFF) // check if type isn't invalid
{
std::string typestr;
mem->resolveClassIDToClassname(temp.type, typestr);
if(temp.z == cz1 && cx1 <= temp.x1 && cx2 >= temp.x2 && cy1 <= temp.y1 && cy2 >= temp.y2)
{
string currStr = build[temp.z-cz1][temp.y1-cy1][temp.x1-cx1];
stringstream stream;
string newStr = buildCommands[typestr];
if(temp.x1 != temp.x2)
{
stream << "(" << temp.x2-temp.x1+1 << "x" << temp.y2-temp.y1+1 << ")";
newStr += stream.str();
}
build[temp.z-cz1][temp.y1-cy1][temp.x1-cx1] = newStr + currStr;
}
}
}
}
// for testing purposes
//ofstream outfile("test.txt");
// printVecOfVec(outfile, dig,'\t');
// outfile << endl;
// printVecOfVec(outfile, build,'\t');
// outfile << endl;
// outfile.close();
int32_t cx3,cy3,cz3,cx4,cy4,cz4;
uint32_t tx3,ty3,tz3,tx4,ty4,tz4;
char result;
while(1){
cx3 = -30000;
while(cx3 == -30000){
DF->Resume();
cout << "Set cursor at new position, then press any key:";
result = cin.get();
DF->Suspend();
Gui->getCursorCoords(cx3,cy3,cz3);
}
if(result == 'q'){
break;
}
cx4 = cx3+cx2-cx1;
cy4 = cy3+cy2-cy1;
cz4 = cz3+cz2-cz1;
tx3=cx3/16;
ty3=cy3/16;
tz3=cz3;
tx4=cx4/16;
ty4=cy4/16;
tz4=cz4;
DFHack::WindowIO * Win = DF->getWindowIO();
designations40d designationBlock;
for(uint32_t y = ty3;y<=ty4;y++)
{
for(uint32_t x = tx3;x<=tx4;x++)
{
for(uint32_t z = tz3;z<=tz4;z++)
{
Maps->Start();
Maps->ReadBlock40d(x,y,z,&block);
Maps->ReadDesignations(x,y,z,&designationBlock);
int ystart,yend,xstart,xend;
ystart=xstart=0;
yend=xend=15;
if(y == ty4){
yend = cy4 % 16;
}
if(y == ty3){
ystart = cy3 % 16;
}
if(x == tx4){
xend = cx4 % 16;
}
if(x == tx3){
xstart = cx3 % 16;
}
int zidx = z-tz3;
for(int yy = ystart; yy <= yend;yy++){
int yidx = yy+(16*(y-ty3)-(cy3%16));
for(int xx = xstart; xx <= xend;xx++){
int xidx = xx+(16*(x-tx3)-(cx3%16));
if(dig[zidx][yidx][xidx] != ""){
char test = dig[zidx][yidx][xidx].c_str()[0];
switch (test){
case 'd':
designationBlock[xx][yy].bits.dig = DFHack::designation_default;
break;
case 'i':
designationBlock[xx][yy].bits.dig = DFHack::designation_ud_stair;
break;
case 'u':
designationBlock[xx][yy].bits.dig = DFHack::designation_u_stair;
break;
case 'j':
designationBlock[xx][yy].bits.dig = DFHack::designation_d_stair;
break;
case 'r':
designationBlock[xx][yy].bits.dig = DFHack::designation_ramp;
break;
}
}
}
yidx++;
}
Maps->Start();
Maps->WriteDesignations(x,y,z,&designationBlock);
}
}
}
}
}
DF->Detach();
#ifndef LINUX_BUILD
std::cout << "Done. Press any key to continue" << std::endl;
cin.ignore();
#endif
return 0;
}
/*
bool waitTillCursorState(DFHack::Context *DF, bool On)
{
DFHack::WindowIO * w = DF->getWindowIO();
DFHack::Position * p = DF->getPosition();
int32_t x,y,z;
int tryCount = 0;
DF->Suspend();
bool cursorResult = p->getCursorCoords(x,y,z);
while(tryCount < 50 && On && !cursorResult || !On && cursorResult)
{
DF->Resume();
w->TypeSpecial(DFHack::WAIT,1,WAIT_AMT);
tryCount++;
DF->Suspend();
cursorResult = p->getCursorCoords(x,y,z);
}
if(tryCount >= 50)
{
cerr << "Something went wrong, cursor at x: " << x << " y: " << y << " z: " << z << endl;
return false;
}
DF->Resume();
return true;
}
bool waitTillCursorPositionState(DFHack::Context *DF, int32_t x,int32_t y, int32_t z)
{
DFHack::WindowIO * w = DF->getWindowIO();
DFHack::Position * p = DF->getPosition();
int32_t x2,y2,z2;
int tryCount = 0;
DF->Suspend();
bool cursorResult = p->getCursorCoords(x2,y2,z2);
while(tryCount < 50 && (x != x2 || y != y2 || z != z2))
{
DF->Resume();
w->TypeSpecial(DFHack::WAIT,1,WAIT_AMT);
tryCount++;
DF->Suspend();
cursorResult = p->getCursorCoords(x2,y2,z2);
}
if(tryCount >= 50)
{
cerr << "Something went wrong, cursor at x: " << x2 << " y: " << y2 << " z: " << z2 << endl;
return false;
}
DF->Resume();
return true;
}*/

File diff suppressed because it is too large Load Diff

@ -1,40 +0,0 @@
-- prints info on assigned hotkeys to the console
local hotkeys = {'F1 ', 'F2 ', 'F3 ', 'F4 ', 'F5 ', 'F6 ',
'F7 ', 'F8 ', 'F9 ', 'F10', 'F11', 'F12'}
for i=1, #hotkeys do
local hk = hotkeys[i]
hk = {id=hk}
-- PLACEHOLDER PROPERTIES ONLY!
hk.name = '_name'
hk.x = df.global.window_x
hk.y = df.global.window_y
hk.z = df.global.window_z
print(hk.id..' '..hk.name..' X= '..hk.x..', Y= '..hk.y..', Z= '..hk.z)
end
--[[
# the (very) old Python version...
from context import Context, ContextManager
cm = ContextManager("Memory.xml")
df = cm.get_single_context()
df.attach()
gui = df.gui
print "Hotkeys"
hotkeys = gui.read_hotkeys()
for key in hotkeys:
print "x: %d\ny: %d\tz: %d\ttext: %s" % (key.x, key.y, key.z, key.name)
df.detach()
print "Done. Press any key to continue"
raw_input()
]]--

@ -1,5 +1,6 @@
#!/bin/sh
PWD=`dirname "${0}"`
cd "${PWD}"
#thanks to Iriel for figuring this out
OSREV=`uname -r | cut -d. -f1`
if [ "$OSREV" -ge 11 ] ; then
@ -11,7 +12,6 @@ else
fi
old_tty_settings=$(stty -g)
cd "${PWD}"
DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib ./dwarfort.exe "$@"
stty "$old_tty_settings"
echo ""

@ -91,6 +91,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(automaterial automaterial.cpp)
DFHACK_PLUGIN(automelt automelt.cpp)
DFHACK_PLUGIN(autotrade autotrade.cpp)
DFHACK_PLUGIN(blueprint blueprint.cpp)
DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(building-hacks building-hacks.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(buildingplan buildingplan-lib.cpp buildingplan.cpp)

@ -0,0 +1,682 @@
//Blueprint
//By cdombroski
//Translates a region of tiles specified by the cursor and arguments/prompts into a series of blueprint files suitable for digfort/buildingplan/quickfort
#include <Console.h>
#include <PluginManager.h>
#include "modules/Buildings.h"
#include "modules/Gui.h"
#include "modules/MapCache.h"
#include "df/building_axle_horizontalst.h"
#include "df/building_bridgest.h"
#include "df/building_constructionst.h"
#include "df/building_furnacest.h"
#include "df/building_rollersst.h"
#include "df/building_screw_pumpst.h"
#include "df/building_siegeenginest.h"
#include "df/building_trapst.h"
#include "df/building_water_wheelst.h"
#include "df/building_workshopst.h"
using std::string;
using std::endl;
using std::vector;
using std::ofstream;
using std::swap;
using std::find;
using std::pair;
using namespace DFHack;
using namespace df::enums;
DFHACK_PLUGIN("blueprint");
enum phase {DIG=1, BUILD=2, PLACE=4, QUERY=8};
command_result blueprint(color_ostream &out, vector <string> &parameters);
DFhackCExport command_result plugin_init(color_ostream &out, vector<PluginCommand> &commands)
{
commands.push_back(PluginCommand("blueprint", "Convert map tiles into a blueprint", blueprint, false));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown(color_ostream &out)
{
return CR_OK;
}
command_result help(color_ostream &out)
{
out << "blueprint width height depth name [dig] [build] [place] [query]" << endl
<< " width, height, depth: area to translate in tiles" << endl
<< " name: base name for blueprint files" << endl
<< " dig: generate blueprints for digging" << endl
<< " build: generate blueprints for building" << endl
<< " place: generate blueprints for stockpiles" << endl
<< " query: generate blueprints for querying (room designations)" << endl
<< " defaults to generating all blueprints" << endl
<< endl
<< "blueprint translates a portion of your fortress into blueprints suitable for" << endl
<< " digfort/fortplan/quickfort. Blueprints are created in the DF folder with names" << endl
<< " following a \"name-phase.csv\" pattern. Translation starts at the current" << endl
<< " cursor location and includes all tiles in the range specified." << endl;
return CR_OK;
}
pair<uint32_t, uint32_t> get_building_size(df::building* b)
{
return pair<uint32_t, uint32_t>(b->x2 - b->x1 + 1, b->y2 - b->y1 + 1);
}
char get_tile_dig(MapExtras::MapCache mc, int32_t x, int32_t y, int32_t z)
{
df::tiletype tt = mc.tiletypeAt(DFCoord(x, y, z));
df::tiletype_shape ts = tileShape(tt);
switch (ts)
{
case tiletype_shape::EMPTY:
case tiletype_shape::RAMP_TOP:
return 'h';
case tiletype_shape::FLOOR:
case tiletype_shape::BOULDER:
case tiletype_shape::PEBBLES:
case tiletype_shape::BROOK_TOP:
return 'd';
case tiletype_shape::FORTIFICATION:
return 'F';
case tiletype_shape::STAIR_UP:
return 'u';
case tiletype_shape::STAIR_DOWN:
return 'j';
case tiletype_shape::STAIR_UPDOWN:
return 'i';
case tiletype_shape::RAMP:
return 'r';
default:
return ' ';
}
}
string get_tile_build(uint32_t x, uint32_t y, df::building* b)
{
if (! b)
return " ";
bool at_nw_corner = x == b->x1 && y == b->y1;
bool at_se_corner = x == b->x2 && y == b->y2;
bool at_center = x == b->centerx && y == b->centery;
pair<uint32_t, uint32_t> size = get_building_size(b);
stringstream out;// = stringstream();
switch(b->getType())
{
case building_type::Armorstand:
return "a";
case building_type::Bed:
return "b";
case building_type::Chair:
return "c";
case building_type::Door:
return "d";
case building_type::Floodgate:
return "x";
case building_type::Cabinet:
return "f";
case building_type::Box:
return "h";
//case building_type::Kennel is missing
case building_type::FarmPlot:
if(!at_nw_corner)
return "`";
out << "p(" << size.first << "x" << size.second << ")";
return out.str();
case building_type::Weaponrack:
return "r";
case building_type::Statue:
return "s";
case building_type::Table:
return "t";
case building_type::RoadPaved:
if(! at_nw_corner)
return "`";
out << "o(" << size.first << "x" << size.second << ")";
return out.str();
case building_type::RoadDirt:
if(! at_nw_corner)
return "`";
out << "O(" << size.first << "x" << size.second << ")";
return out.str();
case building_type::Bridge:
if(! at_nw_corner)
return "`";
switch(((df::building_bridgest*) b)->direction)
{
case df::building_bridgest::T_direction::Down:
out << "gx";
break;
case df::building_bridgest::T_direction::Left:
out << "ga";
break;
case df::building_bridgest::T_direction::Up:
out << "gw";
break;
case df::building_bridgest::T_direction::Right:
out << "gd";
break;
case df::building_bridgest::T_direction::Retracting:
out << "gs";
break;
}
out << "(" << size.first << "x" << size.second << ")";
return out.str();
case building_type::Well:
return "l";
case building_type::SiegeEngine:
if (! at_center)
return "`";
return ((df::building_siegeenginest*) b)->type == df::siegeengine_type::Ballista ? "ib" : "ic";
case building_type::Workshop:
if (! at_center)
return "`";
switch (((df::building_workshopst*) b)->type)
{
case workshop_type::Leatherworks:
return "we";
case workshop_type::Quern:
return "wq";
case workshop_type::Millstone:
return "wM";
case workshop_type::Loom:
return "wo";
case workshop_type::Clothiers:
return "wk";
case workshop_type::Bowyers:
return "wb";
case workshop_type::Carpenters:
return "wc";
case workshop_type::MetalsmithsForge:
return "wf";
case workshop_type::MagmaForge:
return "wv";
case workshop_type::Jewelers:
return "wj";
case workshop_type::Masons:
return "wm";
case workshop_type::Butchers:
return "wu";
case workshop_type::Tanners:
return "wn";
case workshop_type::Craftsdwarfs:
return "wr";
case workshop_type::Siege:
return "ws";
case workshop_type::Mechanics:
return "wt";
case workshop_type::Still:
return "wl";
case workshop_type::Farmers:
return "ww";
case workshop_type::Kitchen:
return "wz";
case workshop_type::Fishery:
return "wh";
case workshop_type::Ashery:
return "wy";
case workshop_type::Dyers:
return "wd";
case workshop_type::Custom:
//can't do anything with custom workshop
return "`";
}
case building_type::Furnace:
if (! at_center)
return "`";
switch (((df::building_furnacest*) b)->type)
{
case furnace_type::WoodFurnace:
return "ew";
case furnace_type::Smelter:
return "es";
case furnace_type::GlassFurnace:
return "eg";
case furnace_type::Kiln:
return "ek";
case furnace_type::MagmaSmelter:
return "el";
case furnace_type::MagmaGlassFurnace:
return "ea";
case furnace_type::MagmaKiln:
return "en";
case furnace_type::Custom:
//can't do anything with custom furnace
return "`";
}
case building_type::WindowGlass:
return "y";
case building_type::WindowGem:
return "Y";
case building_type::Construction:
switch (((df::building_constructionst*) b)->type)
{
case construction_type::Fortification:
return "CF";
case construction_type::Wall:
return "CW";
case construction_type::Floor:
return "Cf";
case construction_type::UpStair:
return "Cu";
case construction_type::DownStair:
return "Cj";
case construction_type::UpDownStair:
return "Cx";
case construction_type::Ramp:
return "Cr";
case construction_type::TrackN:
return "trackN";
case construction_type::TrackS:
return "trackS";
case construction_type::TrackE:
return "trackE";
case construction_type::TrackW:
return "trackW";
case construction_type::TrackNS:
return "trackNS";
case construction_type::TrackNE:
return "trackNE";
case construction_type::TrackNW:
return "trackNW";
case construction_type::TrackSE:
return "trackSE";
case construction_type::TrackSW:
return "trackSW";
case construction_type::TrackEW:
return "trackEW";
case construction_type::TrackNSE:
return "trackNSE";
case construction_type::TrackNSW:
return "trackNSW";
case construction_type::TrackNEW:
return "trackNEW";
case construction_type::TrackSEW:
return "trackSEW";
case construction_type::TrackNSEW:
return "trackNSEW";
case construction_type::TrackRampN:
return "trackrampN";
case construction_type::TrackRampS:
return "trackrampS";
case construction_type::TrackRampE:
return "trackrampE";
case construction_type::TrackRampW:
return "trackrampW";
case construction_type::TrackRampNS:
return "trackrampNS";
case construction_type::TrackRampNE:
return "trackrampNE";
case construction_type::TrackRampNW:
return "trackrampNW";
case construction_type::TrackRampSE:
return "trackrampSE";
case construction_type::TrackRampSW:
return "trackrampSW";
case construction_type::TrackRampEW:
return "trackrampEW";
case construction_type::TrackRampNSE:
return "trackrampNSE";
case construction_type::TrackRampNSW:
return "trackrampNSW";
case construction_type::TrackRampNEW:
return "trackrampNEW";
case construction_type::TrackRampSEW:
return "trackrampSEW";
case construction_type::TrackRampNSEW:
return "trackrampNSEW";
}
case building_type::Shop:
if (! at_center)
return "`";
return "z";
case building_type::AnimalTrap:
return "m";
case building_type::Chain:
return "v";
case building_type::Cage:
return "j";
case building_type::TradeDepot:
if (! at_center)
return "`";
return "D";
case building_type::Trap:
switch (((df::building_trapst*) b)->trap_type)
{
case trap_type::StoneFallTrap:
return "Ts";
case trap_type::WeaponTrap:
return "Tw";
case trap_type::Lever:
return "Tl";
case trap_type::PressurePlate:
return "Tp";
case trap_type::CageTrap:
return "Tc";
case trap_type::TrackStop:
df::building_trapst* ts = (df::building_trapst*) b;
out << "CS";
if (ts->use_dump)
{
if (ts->dump_x_shift == 0)
{
if (ts->dump_y_shift > 0)
out << "dd";
else
out << "d";
}
else
{
if (ts->dump_x_shift > 0)
out << "ddd";
else
out << "dddd";
}
}
switch (ts->friction)
{
case 10:
out << "a";
case 50:
out << "a";
case 500:
out << "a";
case 10000:
out << "a";
}
return out.str();
}
case building_type::ScrewPump:
if (! at_se_corner) //screw pumps anchor at bottom/right
return "`";
switch (((df::building_screw_pumpst*) b)->direction)
{
case screw_pump_direction::FromNorth:
return "Msu";
case screw_pump_direction::FromEast:
return "Msk";
case screw_pump_direction::FromSouth:
return "Msm";
case screw_pump_direction::FromWest:
return "Msh";
}
case building_type::WaterWheel:
if (! at_center)
return "`";
//s swaps orientation which defaults to vertical
return ((df::building_water_wheelst*) b)->is_vertical ? "Mw" : "Mws";
case building_type::Windmill:
if (! at_center)
return "`";
return "Mm";
case building_type::GearAssembly:
return "Mg";
case building_type::AxleHorizontal:
if (! at_nw_corner) //a guess based on how constructions work
return "`";
//same as water wheel but reversed
out << "Mh" << (((df::building_axle_horizontalst*) b)->is_vertical ? "s" : "")
<< "(" << size.first << "x" << size.second << ")";
return out.str();
case building_type::AxleVertical:
return "Mv";
case building_type::Rollers:
if (! at_nw_corner)
return "`";
out << "Mr";
switch (((df::building_rollersst*) b)->direction)
{
case screw_pump_direction::FromNorth:
break;
case screw_pump_direction::FromEast:
out << "s";
case screw_pump_direction::FromSouth:
out << "s";
case screw_pump_direction::FromWest:
out << "s";
}
out << "(" << size.first << "x" << size.second << ")";
return out.str();
case building_type::Support:
return "S";
case building_type::ArcheryTarget:
return "A";
case building_type::TractionBench:
return "R";
case building_type::Hatch:
return "H";
case building_type::Slab:
//how to mine alt key?!?
//alt+s
return " ";
case building_type::NestBox:
return "N";
case building_type::Hive:
//alt+h
return " ";
case building_type::GrateWall:
return "W";
case building_type::GrateFloor:
return "G";
case building_type::BarsVertical:
return "B";
case building_type::BarsFloor:
//alt+b
return " ";
default:
return " ";
}
}
string get_tile_place(uint32_t x, uint32_t y, df::building* b)
{
if (! b || b->getType() != building_type::Stockpile)
return " ";
if (b->x1 != x || b->y1 != y)
return "`";
pair<uint32_t, uint32_t> size = get_building_size(b);
df::building_stockpilest* sp = (df::building_stockpilest*) b;
stringstream out;// = stringstream();
switch (sp->settings.flags.whole)
{
case df::stockpile_group_set::mask_animals:
out << "a";
break;
case df::stockpile_group_set::mask_food:
out << "f";
break;
case df::stockpile_group_set::mask_furniture:
out << "u";
break;
case df::stockpile_group_set::mask_corpses:
out << "y";
break;
case df::stockpile_group_set::mask_refuse:
out << "r";
break;
case df::stockpile_group_set::mask_wood:
out << "w";
break;
case df::stockpile_group_set::mask_stone:
out << "s";
break;
case df::stockpile_group_set::mask_gems:
out << "e";
break;
case df::stockpile_group_set::mask_bars_blocks:
out << "b";
break;
case df::stockpile_group_set::mask_cloth:
out << "h";
break;
case df::stockpile_group_set::mask_leather:
out << "l";
break;
case df::stockpile_group_set::mask_ammo:
out << "z";
break;
case df::stockpile_group_set::mask_coins:
out << "n";
break;
case df::stockpile_group_set::mask_finished_goods:
out << "g";
break;
case df::stockpile_group_set::mask_weapons:
out << "p";
break;
case df::stockpile_group_set::mask_armor:
out << "d";
break;
default: //multiple stockpile type
return "`";
}
out << "("<< size.first << "x" << size.second << ")";
return out.str();
}
string get_tile_query(df::building* b)
{
if (b && b->is_room)
return "r+";
return " ";
}
command_result do_transform(DFCoord start, DFCoord end, string name, uint32_t phases)
{
ofstream dig, build, place, query;
if (phases & QUERY)
{
//query = ofstream((name + "-query.csv").c_str(), ofstream::trunc);
query.open(name+"-query.csv", ofstream::trunc);
query << "#query" << endl;
}
if (phases & PLACE)
{
//place = ofstream(name + "-place.csv", ofstream::trunc);
place.open(name+"-place.csv", ofstream::trunc);
place << "#place" << endl;
}
if (phases & BUILD)
{
//build = ofstream(name + "-build.csv", ofstream::trunc);
build.open(name+"-build.csv", ofstream::trunc);
build << "#build" << endl;
}
if (phases & DIG)
{
//dig = ofstream(name + "-dig.csv", ofstream::trunc);
dig.open(name+"-dig.csv", ofstream::trunc);
dig << "#dig" << endl;
}
if (start.x > end.x)
{
swap(start.x, end.x);
start.x++;
end.x++;
}
if (start.y > end.y)
{
swap(start.y, end.y);
start.y++;
end.y++;
}
if (start.z > end.z)
{
swap(start.z, end.z);
start.z++;
end.z++;
}
MapExtras::MapCache mc;
for (int32_t z = start.z; z < end.z; z++)
{
for (int32_t y = start.y; y < end.y; y++)
{
for (int32_t x = start.x; x < end.x; x++)
{
df::building* b = DFHack::Buildings::findAtTile(DFCoord(x, y, z));
if (phases & QUERY)
query << get_tile_query(b) << ',';
if (phases & PLACE)
place << get_tile_place(x, y, b) << ',';
if (phases & BUILD)
build << get_tile_build(x, y, b) << ',';
if (phases & DIG)
dig << get_tile_dig(mc, x, y, z) << ',';
}
if (phases & QUERY)
query << "#" << endl;
if (phases & PLACE)
place << "#" << endl;
if (phases & BUILD)
build << "#" << endl;
if (phases & DIG)
dig << "#" << endl;
}
if (z < end.z - 1)
{
if (phases & QUERY)
query << "#<" << endl;
if (phases & PLACE)
place << "#<" << endl;
if (phases & BUILD)
build << "#<" << endl;
if (phases & DIG)
dig << "#<" << endl;
}
}
if (phases & QUERY)
query.close();
if (phases & PLACE)
place.close();
if (phases & BUILD)
build.close();
if (phases & DIG)
dig.close();
return CR_OK;
}
bool cmd_option_exists(vector<string>& parameters, const string& option)
{
return find(parameters.begin(), parameters.end(), option) != parameters.end();
}
command_result blueprint(color_ostream &out, vector<string> &parameters)
{
if (parameters.size() < 4 || parameters.size() > 8)
return help(out);
CoreSuspender suspend;
if (!Maps::IsValid())
{
out.printerr("Map is not available!\n");
return CR_FAILURE;
}
int32_t x, y, z;
if (!Gui::getCursorCoords(x, y, z))
{
out.printerr("Can't get cursor coords! Make sure you have an active cursor in DF.\n");
return CR_FAILURE;
}
DFCoord start (x, y, z);
DFCoord end (x + stoi(parameters[0]), y + stoi(parameters[1]), z + stoi(parameters[2]));
if (parameters.size() == 4)
return do_transform(start, end, parameters[3], DIG | BUILD | PLACE | QUERY);
uint32_t option = 0;
if (cmd_option_exists(parameters, "dig"))
option |= DIG;
if (cmd_option_exists(parameters, "build"))
option |= BUILD;
if (cmd_option_exists(parameters, "place"))
option |= PLACE;
if (cmd_option_exists(parameters, "query"))
option |= QUERY;
return do_transform(start, end, parameters[3], option);
}

@ -43,6 +43,9 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(gview);
REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(ui_building_assign_units);
REQUIRE_GLOBAL(ui_building_in_assign);
REQUIRE_GLOBAL(ui_building_item_cursor);
/*
Search Plugin
@ -1379,7 +1382,7 @@ IMPLEMENT_HOOKS(df::viewscreen_buildinglistst, roomlist_search);
//
// START: Announcement list search
//
class annoucnement_search : public search_generic<df::viewscreen_announcelistst, df::report*>
class announcement_search : public search_generic<df::viewscreen_announcelistst, df::report*>
{
public:
void render() const
@ -1407,7 +1410,7 @@ private:
};
IMPLEMENT_HOOKS(df::viewscreen_announcelistst, annoucnement_search);
IMPLEMENT_HOOKS(df::viewscreen_announcelistst, announcement_search);
//
// END: Announcement list search
@ -1671,6 +1674,68 @@ IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, burrow_search);
//
//
// START: Room assignment search
//
typedef search_generic<df::viewscreen_dwarfmodest, df::unit*> room_assign_search_base;
class room_assign_search : public room_assign_search_base
{
public:
bool can_init(df::viewscreen_dwarfmodest *screen)
{
if (ui->main.mode == df::ui_sidebar_mode::QueryBuilding && *ui_building_in_assign)
{
return room_assign_search_base::can_init(screen);
}
return false;
}
string get_element_description(df::unit *element) const
{
return element ? get_unit_description(element) : "Nobody";
}
void render() const
{
auto dims = Gui::getDwarfmodeViewDims();
int left_margin = dims.menu_x1 + 1;
int x = left_margin;
int y = 19;
print_search_option(x, y);
}
vector<df::unit *> *get_primary_list()
{
return ui_building_assign_units;
}
virtual int32_t * get_viewscreen_cursor()
{
return ui_building_item_cursor;
}
bool should_check_input(set<df::interface_key> *input)
{
if (input->count(interface_key::SECONDSCROLL_UP) || input->count(interface_key::SECONDSCROLL_DOWN)
|| input->count(interface_key::SECONDSCROLL_PAGEUP) || input->count(interface_key::SECONDSCROLL_PAGEDOWN))
{
end_entry_mode();
return false;
}
return true;
}
};
IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, room_assign_search);
//
// END: Room assignment search
//
#define SEARCH_HOOKS \
HOOK_ACTION(unitlist_search_hook) \
HOOK_ACTION(roomlist_search_hook) \
@ -1681,10 +1746,11 @@ IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, burrow_search);
HOOK_ACTION(military_search_hook) \
HOOK_ACTION(nobles_search_hook) \
HOOK_ACTION(profiles_search_hook) \
HOOK_ACTION(annoucnement_search_hook) \
HOOK_ACTION(announcement_search_hook) \
HOOK_ACTION(joblist_search_hook) \
HOOK_ACTION(burrow_search_hook) \
HOOK_ACTION(stockpile_search_hook)
HOOK_ACTION(stockpile_search_hook) \
HOOK_ACTION(room_assign_search_hook)
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)
{

@ -1,8 +1,10 @@
-- allows to do jobs in adv. mode.
--[==[
version: 0.011
version: 0.012
changelog:
*0.012
- fix for some jobs not finding correct building.
*0.011
- fixed crash with building jobs (other jobs might have been crashing too!)
- fixed bug with building asking twice to input items
@ -1123,7 +1125,7 @@ function usetool:openShopWindow(building)
local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType())
if filter_pile then
local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z,building=building}
local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},building=building
,screen=self,bld=building,common=filter_pile.common}
choices={}
for k,v in pairs(filter_pile) do

@ -0,0 +1,9 @@
-- prints info on assigned hotkeys to the console
for i=1, #df.global.ui.main.hotkeys do
local hk = df.global.ui.main.hotkeys[i-1]
local key = dfhack.screen.getKeyDisplay(df.interface_key.D_HOTKEY1 + i - 1)
if hk.cmd ~= -1 then
print(key..': '..hk.name..': x='..hk.x..' y='..hk.y..' z='..hk.z)
end
end