Merge branch 'master' of git://github.com/angavrilov/dfhack

develop
jj 2012-10-10 19:46:31 +02:00
commit 9148079745
50 changed files with 1986 additions and 864 deletions

@ -1914,6 +1914,12 @@ utils.insert_or_update(soul.skills, {new=true, id=..., rating=...}, 'id')
</pre>
<p>(For an explanation of <tt class="docutils literal">new=true</tt>, see table assignment in the wrapper section)</p>
</li>
<li><p class="first"><tt class="docutils literal">utils.erase_sorted_key(vector,key,field,cmpfun)</tt></p>
<p>Removes the item with the given key from the list. Returns: <em>did_erase, vector[idx], idx</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal">utils.erase_sorted(vector,item,field,cmpfun)</tt></p>
<p>Exactly like <tt class="docutils literal">erase_sorted_key</tt>, but if field is specified, takes the key from <tt class="docutils literal">item[field]</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">utils.prompt_yes_no(prompt, default)</tt></p>
<p>Presents a yes/no prompt to the user. If <tt class="docutils literal">default</tt> is not <em>nil</em>,
allows just pressing Enter to submit the default choice.

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

@ -1,6 +1,13 @@
DFHack future
Nothing yet!
Internals:
- 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.
- removebadthoughts: add --dry-run option
DFHack v0.34.11-r2

File diff suppressed because it is too large Load Diff

@ -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::
<md5-hash value='????????????????????????????????'/>
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!
=============================

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

@ -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<unsigned char*>(data),size);
}
/*
* creates a MD5 hash from
* a file specified in "filename" and

@ -250,6 +250,9 @@ 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" )
else()
@ -329,7 +332,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})

@ -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")
@ -821,6 +820,8 @@ std::string Core::getHackPath()
#endif
}
void init_screen_module(Core *);
bool Core::Init()
{
if(started)
@ -866,6 +867,7 @@ bool Core::Init()
// Init global object pointers
df::global::InitGlobals();
init_screen_module(this);
cerr << "Initializing Console.\n";
// init the console.
@ -1122,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)
@ -1140,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);
}
}
@ -1678,7 +1680,6 @@ TYPE * Core::get##TYPE() \
return s_mods.p##TYPE;\
}
MODULE_GETTER(World);
MODULE_GETTER(Materials);
MODULE_GETTER(Notes);
MODULE_GETTER(Graphic);

@ -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<PersistentDataItem> 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;
@ -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 }
};

@ -0,0 +1,314 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
A thread-safe logging console with a line editor for windows.
Based on linenoise win32 port,
copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
All rights reserved.
Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
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 <stdarg.h>
#include <errno.h>
#include <stdio.h>
#include <assert.h>
#include <iostream>
#include <fstream>
#include <istream>
#include <string>
#include <stdint.h>
#include <cstdio>
#include <cstdlib>
#include <sstream>
#include <vector>
#include <memory>
#include <md5wrapper.h>
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<Byte> 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<patch_byte> *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<patch_byte> &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();
}
std::string compute_hash(const std::vector<patch_byte> &pvec)
{
md5wrapper md5;
return md5.getHashFromBytes(pvec.data(), pvec.size());
}
int main (int argc, char *argv[])
{
if (argc <= 3)
{
cerr << "Usage: binpatch check|apply|remove <exe> <patch>" << 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<patch_byte> 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);
}
if (!save_file(bindata, exe_file + ".bak"))
{
cerr << "Could not create backup." << endl;
return 1;
}
if (!save_file(bindata, exe_file))
return 1;
cout << "Patched " << patch.entries.size()
<< " bytes, new hash: " << compute_hash(bindata) << endl;
return 0;
}

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

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

@ -47,14 +47,6 @@ namespace MapExtras
class DFHACK_EXPORT MapCache;
template<class R, class T> 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

@ -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<class R, class T> 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

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

@ -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<PersistentDataItem> *vec,
const std::string &key, bool prefix = false);
DFHACK_EXPORT void GetPersistentData(std::vector<PersistentDataItem> *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

@ -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 --
------------------------
@ -292,6 +310,7 @@ function Screen:onResize(w,h)
end
function Screen:updateLayout()
self:invoke_after('postUpdateLayout')
end
------------------------
@ -381,7 +400,7 @@ function FramedScreen:updateFrameSize()
self.frame_opaque = (gw == 0 and gh == 0)
end
function FramedScreen:updateLayout()
function FramedScreen:postUpdateLayout()
self:updateFrameSize()
end

@ -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
@ -171,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)
@ -182,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.selection<self.page_top and self.selection >0 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()

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

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

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

@ -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<df::tile_designation>(block->designation,pos);
auto des = index_tile<df::tile_designation>(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<uint16_t>(block1->walkable, pos1);
auto tile2 = MapExtras::index_tile<uint16_t>(block2->walkable, pos2);
auto tile1 = index_tile<uint16_t>(block1->walkable, pos1);
auto tile2 = index_tile<uint16_t>(block2->walkable, pos2);
return tile1 && tile1 == tile2;
}

@ -28,6 +28,7 @@ distribution.
#include <string>
#include <vector>
#include <map>
#include <set>
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<df::interface_key,std::set<string,less_sz> > *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.
*/

@ -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<std::string, int> persistent_index;
Process * owner;
};
static int next_persistent_id = 0;
static std::multimap<std::string, int> persistent_index;
typedef std::pair<std::string, int> 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<df::historical_figure*> &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<df::historical_figure*> &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<PersistentDataItem> *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<PersistentDataItem> *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<df::historical_figure*> &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];

@ -1 +1 @@
Subproject commit 2f76de54dacd32af567b177adfb9d037fdf62d9b
Subproject commit 20c6d9c743f1c5a20bb288f427b101e9b2a138d7

@ -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 <math.h>
@ -683,17 +684,19 @@ command_result adv_bodyswap (color_ostream &out, std::vector <std::string> & 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)

@ -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<PersistentDataItem> 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;
}

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

@ -2,134 +2,168 @@
#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) do
{
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;
// don't do anything if the dwarf isn't going anywhere
if (!unit->path.dest.isValid())
break;
// skip dwarves that are dragging creatures or being dragged
if ((unit->relations.draggee_id != -1) || (unit->relations.dragger_id != -1))
break;
// 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
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;
break;
// skip unconscious units
if (unit->counters.unconscious > 0)
break;
// make sure source and dest map blocks are valid
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_occ->bits.unit_grounded = 0;
else
old_occ->bits.unit = 0;
// if there's already somebody standing at the destination, then force the unit to lay down
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_occ->bits.unit_grounded = 1;
else
new_occ->bits.unit = 1;
// move unit to destination
unit->pos = unit->path.dest;
unit->path.path.clear();
} while (0);
}
return CR_OK;
}
static command_result fastdwarf (color_ostream &out, vector <string> & 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 +173,16 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
commands.push_back(PluginCommand("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"
"fastdwarf: make dwarves faster.\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"
" fastdwarf <speed> (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;

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

@ -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();
@ -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)");
}
}

@ -117,13 +117,9 @@ command_result mode (color_ostream &out_, vector <string> & 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 <string> & parameters)
{
CoreSuspender suspend;
world->WriteGameMode(gm);
World::WriteGameMode(gm);
}
out << endl;

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

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

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

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

@ -87,19 +87,18 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCom
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
World *World = Core::getInstance().getWorld();
t_gamemodes gm;
World->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<string> & 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<string> & 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<string> & 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<string> & 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<string> & 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");

@ -106,9 +106,8 @@ command_result df_seedwatch(color_ostream &out, vector<string>& 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))

@ -347,10 +347,9 @@ static void load_engines()
{
clear_engines();
auto pworld = Core::getInstance().getWorld();
std::vector<PersistentDataItem> 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)
{

@ -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 <string> &parameters)
}
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);

@ -84,7 +84,6 @@ command_result weather (color_ostream &con, vector <string> & 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 <string> & 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.
}

@ -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<PersistentDataItem> 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;
}

@ -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,18 @@ 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.ival(0) = enable_autobutcher;
config_autobutcher = World::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;
@ -3420,6 +3422,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;
@ -3428,14 +3432,8 @@ command_result start_autobutcher(color_ostream &out)
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");
config_autobutcher = World::GetPersistentData("autobutcher/config");
if(config_autobutcher.isValid())
{
if (config_autobutcher.ival(0) == -1)
@ -3467,7 +3465,7 @@ command_result init_autobutcher(color_ostream &out)
// read watchlist from save
std::vector<PersistentDataItem> 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();
@ -3498,14 +3496,23 @@ 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.ival(0) = enable_autonestbox;
config_autonestbox = World::AddPersistentData("autonestbox/config");
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;
@ -3514,14 +3521,8 @@ command_result start_autonestbox(color_ostream &out)
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");
config_autonestbox = World::GetPersistentData("autonestbox/config");
if(config_autonestbox.isValid())
{
if (config_autonestbox.ival(0) == -1)

@ -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,134 @@ 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_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
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?', 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
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 +505,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 +558,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 +617,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 +669,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 +842,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 +881,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
@ -640,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
@ -656,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
@ -679,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
@ -694,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
@ -713,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
@ -731,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
@ -746,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
@ -760,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
@ -816,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
@ -830,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
@ -843,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.')
@ -865,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
@ -893,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:]],
@ -902,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
@ -963,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
@ -973,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
@ -989,10 +1464,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')

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

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

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

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

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