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> </pre>
<p>(For an explanation of <tt class="docutils literal">new=true</tt>, see table assignment in the wrapper section)</p> <p>(For an explanation of <tt class="docutils literal">new=true</tt>, see table assignment in the wrapper section)</p>
</li> </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> <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>, <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. 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) (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)`` * ``utils.prompt_yes_no(prompt, default)``
Presents a yes/no prompt to the user. If ``default`` is not *nil*, Presents a yes/no prompt to the user. If ``default`` is not *nil*,

@ -1,6 +1,13 @@
DFHack future 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 - removebadthoughts: add --dry-run option
DFHack v0.34.11-r2 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/'. 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! Something doesn't work, help!
============================= =============================

@ -36,16 +36,14 @@
* internal hash function, calling * internal hash function, calling
* the basic methods from md5.h * 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; MD5Context ctx;
//init md5 //init md5
MD5Init(&ctx); MD5Init(&ctx);
//update with our string //update with our string
MD5Update(&ctx, MD5Update(&ctx, data, length);
(unsigned char*)text.c_str(),
text.length());
//create the hash //create the hash
unsigned char buff[16] = ""; unsigned char buff[16] = "";
@ -95,10 +93,9 @@ md5wrapper::~md5wrapper()
*/ */
std::string md5wrapper::getHashFromString(std::string text) 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 * creates a MD5 hash from
* a file specified in "filename" and * a file specified in "filename" and

@ -31,7 +31,7 @@ class md5wrapper
* internal hash function, calling * internal hash function, calling
* the basic methods from md5.h * 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 * converts the numeric giets to
@ -52,6 +52,10 @@ class md5wrapper
*/ */
std::string getHashFromString(std::string text); 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 * creates a MD5 hash from
* a file specified in "filename" and * a file specified in "filename" and

@ -250,6 +250,9 @@ ADD_DEPENDENCIES(dfhack-client dfhack)
ADD_EXECUTABLE(dfhack-run dfhack-run.cpp) ADD_EXECUTABLE(dfhack-run dfhack-run.cpp)
ADD_EXECUTABLE(binpatch binpatch.cpp)
TARGET_LINK_LIBRARIES(binpatch dfhack-md5)
IF(BUILD_EGGY) IF(BUILD_EGGY)
SET_TARGET_PROPERTIES(dfhack PROPERTIES OUTPUT_NAME "egg" ) SET_TARGET_PROPERTIES(dfhack PROPERTIES OUTPUT_NAME "egg" )
else() else()
@ -329,7 +332,7 @@ install(FILES xml/symbols.xml
install(FILES ../dfhack.init-example install(FILES ../dfhack.init-example
DESTINATION ${DFHACK_BINARY_DESTINATION}) DESTINATION ${DFHACK_BINARY_DESTINATION})
install(TARGETS dfhack-run dfhack-client install(TARGETS dfhack-run dfhack-client binpatch
LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION}
RUNTIME 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") else if(first == "fpause")
{ {
World * w = getWorld(); World::SetPauseState(true);
w->SetPauseState(true);
con.print("The game was forced to pause!\n"); con.print("The game was forced to pause!\n");
} }
else if(first == "cls") else if(first == "cls")
@ -821,6 +820,8 @@ std::string Core::getHackPath()
#endif #endif
} }
void init_screen_module(Core *);
bool Core::Init() bool Core::Init()
{ {
if(started) if(started)
@ -866,6 +867,7 @@ bool Core::Init()
// Init global object pointers // Init global object pointers
df::global::InitGlobals(); df::global::InitGlobals();
init_screen_module(this);
cerr << "Initializing Console.\n"; cerr << "Initializing Console.\n";
// init the console. // init the console.
@ -1122,7 +1124,7 @@ void Core::doUpdate(color_ostream &out, bool first_update)
last_world_data_ptr = new_wdata; last_world_data_ptr = new_wdata;
last_local_map_ptr = new_mapdata; last_local_map_ptr = new_mapdata;
getWorld()->ClearPersistentCache(); World::ClearPersistentCache();
// and if the world is going away, we report the map change first // and if the world is going away, we report the map change first
if(had_map) if(had_map)
@ -1140,7 +1142,7 @@ void Core::doUpdate(color_ostream &out, bool first_update)
if (isMapLoaded() != had_map) if (isMapLoaded() != had_map)
{ {
getWorld()->ClearPersistentCache(); World::ClearPersistentCache();
onStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED); onStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED);
} }
} }
@ -1678,7 +1680,6 @@ TYPE * Core::get##TYPE() \
return s_mods.p##TYPE;\ return s_mods.p##TYPE;\
} }
MODULE_GETTER(World);
MODULE_GETTER(Materials); MODULE_GETTER(Materials);
MODULE_GETTER(Notes); MODULE_GETTER(Notes);
MODULE_GETTER(Graphic); MODULE_GETTER(Graphic);

@ -258,7 +258,7 @@ static PersistentDataItem persistent_by_struct(lua_State *state, int idx)
int id = lua_tointeger(state, -1); int id = lua_tointeger(state, -1);
lua_pop(state, 1); lua_pop(state, 1);
PersistentDataItem ref = Core::getInstance().getWorld()->GetPersistentData(id); PersistentDataItem ref = World::GetPersistentData(id);
if (ref.isValid()) if (ref.isValid())
{ {
@ -323,7 +323,7 @@ static PersistentDataItem get_persistent(lua_State *state)
{ {
const char *str = luaL_checkstring(state, 1); 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); auto ref = get_persistent(state);
bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref); bool ok = World::DeletePersistentData(ref);
lua_pushboolean(state, ok); lua_pushboolean(state, ok);
return 1; 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); bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false);
std::vector<PersistentDataItem> data; std::vector<PersistentDataItem> data;
Core::getInstance().getWorld()->GetPersistentData(&data, str, prefix); World::GetPersistentData(&data, str, prefix);
if (data.empty()) if (data.empty())
{ {
@ -396,7 +396,7 @@ static int dfhack_persistent_save(lua_State *state)
if (add) if (add)
{ {
ref = Core::getInstance().getWorld()->AddPersistentData(str); ref = World::AddPersistentData(str);
added = true; added = true;
} }
else if (lua_getmetatable(state, 1)) else if (lua_getmetatable(state, 1))
@ -409,13 +409,13 @@ static int dfhack_persistent_save(lua_State *state)
} }
else else
{ {
ref = Core::getInstance().getWorld()->GetPersistentData(str); ref = World::GetPersistentData(str);
} }
// Auto-add if not found // Auto-add if not found
if (!ref.isValid()) if (!ref.isValid())
{ {
ref = Core::getInstance().getWorld()->AddPersistentData(str); ref = World::AddPersistentData(str);
if (!ref.isValid()) if (!ref.isValid())
luaL_error(state, "cannot create persistent entry"); luaL_error(state, "cannot create persistent entry");
added = true; added = true;
@ -1146,6 +1146,7 @@ static const LuaWrapper::FunctionReg dfhack_screen_module[] = {
WRAPM(Screen, inGraphicsMode), WRAPM(Screen, inGraphicsMode),
WRAPM(Screen, clear), WRAPM(Screen, clear),
WRAPM(Screen, invalidate), WRAPM(Screen, invalidate),
WRAPM(Screen, getKeyDisplay),
{ NULL, NULL } { 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 Process;
class Module; class Module;
class World;
class Materials; class Materials;
class Notes; class Notes;
struct VersionInfo; struct VersionInfo;
@ -120,8 +119,6 @@ namespace DFHack
/// Is everything OK? /// Is everything OK?
bool isValid(void) { return !errorstate; } bool isValid(void) { return !errorstate; }
/// get the world module
World * getWorld();
/// get the materials module /// get the materials module
Materials * getMaterials(); Materials * getMaterials();
/// get the notes module /// get the notes module
@ -205,7 +202,6 @@ namespace DFHack
// Module storage // Module storage
struct struct
{ {
World * pWorld;
Materials * pMaterials; Materials * pMaterials;
Notes * pNotes; Notes * pNotes;
Graphic * pGraphic; Graphic * pGraphic;

@ -1,5 +1,12 @@
bool empty() const { return x.empty(); }
unsigned size() const { return x.size(); } unsigned size() const { return x.size(); }
void clear() {
x.clear();
y.clear();
z.clear();
}
coord operator[] (unsigned idx) const { coord operator[] (unsigned idx) const {
if (idx >= x.size()) if (idx >= x.size())
return coord(); return coord();

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

@ -151,6 +151,21 @@ typedef uint8_t biome_indices40d [9];
*/ */
typedef uint16_t t_temperatures [16][16]; 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 * The Maps module
* \ingroup grp_modules * \ingroup grp_modules

@ -128,6 +128,9 @@ namespace DFHack
DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL); DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL);
DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false); DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false);
DFHACK_EXPORT bool isDismissed(df::viewscreen *screen); 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 { class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen {

@ -55,8 +55,6 @@ namespace DFHack
class DFContextShared; class DFContextShared;
class DFHACK_EXPORT PersistentDataItem { class DFHACK_EXPORT PersistentDataItem {
friend class World;
int id; int id;
std::string key_value; std::string key_value;
@ -65,13 +63,17 @@ namespace DFHack
public: public:
static const int NumInts = 7; static const int NumInts = 7;
bool isValid() { return id != 0; } bool isValid() const { return id != 0; }
int entry_id() { return -id; } 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; } 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) { return int_values[i]; }
int ival(int i) const { return int_values[i]; }
PersistentDataItem() : id(0), str_value(0), int_values(0) {} PersistentDataItem() : id(0), str_value(0), int_values(0) {}
PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv) PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv)
@ -83,54 +85,42 @@ namespace DFHack
* \ingroup grp_modules * \ingroup grp_modules
* \ingroup grp_world * \ingroup grp_world
*/ */
class DFHACK_EXPORT World : public Module namespace World
{ {
public:
World();
~World();
bool Start();
bool Finish();
///true if paused, false if not ///true if paused, false if not
bool ReadPauseState(); DFHACK_EXPORT bool ReadPauseState();
///true if paused, false if not ///true if paused, false if not
void SetPauseState(bool paused); DFHACK_EXPORT void SetPauseState(bool paused);
uint32_t ReadCurrentTick(); DFHACK_EXPORT uint32_t ReadCurrentTick();
uint32_t ReadCurrentYear(); DFHACK_EXPORT uint32_t ReadCurrentYear();
uint32_t ReadCurrentMonth(); DFHACK_EXPORT uint32_t ReadCurrentMonth();
uint32_t ReadCurrentDay(); DFHACK_EXPORT uint32_t ReadCurrentDay();
uint8_t ReadCurrentWeather(); DFHACK_EXPORT uint8_t ReadCurrentWeather();
void SetCurrentWeather(uint8_t weather); DFHACK_EXPORT void SetCurrentWeather(uint8_t weather);
bool ReadGameMode(t_gamemodes& rd); DFHACK_EXPORT bool ReadGameMode(t_gamemodes& rd);
bool WriteGameMode(const t_gamemodes & wr); // this is very dangerous DFHACK_EXPORT bool WriteGameMode(const t_gamemodes & wr); // this is very dangerous
std::string ReadWorldFolder(); DFHACK_EXPORT std::string ReadWorldFolder();
// Store data in fake historical figure names. // Store data in fake historical figure names.
// This ensures that the values are stored in save games. // This ensures that the values are stored in save games.
PersistentDataItem AddPersistentData(const std::string &key); DFHACK_EXPORT PersistentDataItem AddPersistentData(const std::string &key);
PersistentDataItem GetPersistentData(const std::string &key); DFHACK_EXPORT PersistentDataItem GetPersistentData(const std::string &key);
PersistentDataItem GetPersistentData(int entry_id); DFHACK_EXPORT PersistentDataItem GetPersistentData(int entry_id);
// Calls GetPersistentData(key); if not found, adds and sets added to true. // 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. // 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. // Lists all items with the given key.
// If prefix is true, search for keys starting with key+"/". // If prefix is true, search for keys starting with key+"/".
// GetPersistentData(&vec,"",true) returns all items. // GetPersistentData(&vec,"",true) returns all items.
// Items have alphabetic order by key; same key ordering is undefined. // Items have alphabetic order by key; same key ordering is undefined.
void GetPersistentData(std::vector<PersistentDataItem> *vec, DFHACK_EXPORT void GetPersistentData(std::vector<PersistentDataItem> *vec,
const std::string &key, bool prefix = false); const std::string &key, bool prefix = false);
// Deletes the item; returns true if success. // Deletes the item; returns true if success.
bool DeletePersistentData(const PersistentDataItem &item); DFHACK_EXPORT bool DeletePersistentData(const PersistentDataItem &item);
void ClearPersistentCache(); DFHACK_EXPORT void ClearPersistentCache();
}
private:
struct Private;
Private *d;
bool BuildPersistentCache();
};
} }
#endif #endif

@ -93,6 +93,14 @@ function Painter.new(rect, pen)
return Painter{ rect = rect, pen = pen } return Painter{ rect = rect, pen = pen }
end 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() function Painter:isValidPos()
return self.x >= self.clip_x1 and self.x <= self.clip_x2 return self.x >= self.clip_x1 and self.x <= self.clip_x2
and self.y >= self.clip_y1 and self.y <= self.clip_y2 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) return self:advance(#text, nil)
end 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 -- -- Base screen object --
------------------------ ------------------------
@ -292,6 +310,7 @@ function Screen:onResize(w,h)
end end
function Screen:updateLayout() function Screen:updateLayout()
self:invoke_after('postUpdateLayout')
end end
------------------------ ------------------------
@ -381,7 +400,7 @@ function FramedScreen:updateFrameSize()
self.frame_opaque = (gw == 0 and gh == 0) self.frame_opaque = (gw == 0 and gh == 0)
end end
function FramedScreen:updateLayout() function FramedScreen:postUpdateLayout()
self:updateFrameSize() self:updateFrameSize()
end end

@ -51,10 +51,9 @@ function MessageBox:onRenderBody(dc)
end end
if self.on_accept then if self.on_accept then
local x,y = self.frame_rect.x1+1, self.frame_rect.y2+1 local fr = self.frame_rect
dscreen.paintString({fg=COLOR_LIGHTGREEN},x,y,'ESC') local dc2 = gui.Painter.new_xy(fr.x1+1,fr.y2+1,fr.x2-8,fr.y2+1)
dscreen.paintString({fg=COLOR_GREY},x+3,y,'/') dc2:key('LEAVESCREEN'):string('/'):key('MENU_CONFIRM')
dscreen.paintString({fg=COLOR_LIGHTGREEN},x+4,y,'y')
end end
end end
@ -171,10 +170,10 @@ ListBox = defclass(ListBox, MessageBox)
ListBox.focus_path = 'ListBox' ListBox.focus_path = 'ListBox'
ListBox.ATTRS{ ListBox.ATTRS{
selection = 0, selection = 1,
choices = {}, choices = {},
select_pen = DEFAULT_NIL, select_pen = DEFAULT_NIL,
on_input = DEFAULT_NIL on_select = DEFAULT_NIL
} }
function InputBox:preinit(info) function InputBox:preinit(info)
@ -182,84 +181,112 @@ function InputBox:preinit(info)
end end
function ListBox:init(info) 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 end
function ListBox:getWantedFrameSize() function ListBox:getWantedFrameSize()
local mw, mh = ListBox.super.getWantedFrameSize(self) 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 end
function ListBox:onRenderBody(dc) function ListBox:postUpdateLayout()
ListBox.super.onRenderBody(self, dc) self.page_size = self.frame_rect.height - #self.text - 3
self:moveCursor(0)
dc:newline(1) end
if self.selection>dc.height-3 then function ListBox:moveCursor(delta)
self.page_top=self.selection-(dc.height-3) local page = math.max(1, self.page_size)
elseif self.selection<self.page_top and self.selection >0 then local cnt = #self.choices
self.page_top=self.selection-1 local off = self.selection+delta-1
end local ds = math.abs(delta)
for i,entry in ipairs(self.choices) do
if type(entry)=="table" then if ds > 1 then
entry=entry[1] if off >= cnt+ds-1 then
end off = 0
if i>self.page_top then
if i == self.selection then
dc:pen(self.select_pen or COLOR_LIGHTCYAN)
else else
dc:pen(self.text_pen or COLOR_GREY) off = math.min(cnt-1, off)
end end
dc:string(entry) if off <= -ds then
dc:newline(1) off = cnt-1
else
off = math.max(0, off)
end end
end end
self.selection = 1 + off % cnt
self.page_top = 1 + page * math.floor((self.selection-1) / page)
end end
function ListBox:moveCursor(delta) function ListBox:onRenderBody(dc)
local newsel=self.selection+delta ListBox.super.onRenderBody(self, dc)
if #self.choices ~=0 then
if newsel<1 or newsel>#self.choices then dc:newline(1):pen(self.select_pen or COLOR_CYAN)
newsel=newsel % #self.choices
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 end
dc:newline(1)
end end
self.selection=newsel
end end
function ListBox:onInput(keys) function ListBox:onInput(keys)
if keys.SELECT then if keys.SELECT then
self:dismiss() self:dismiss()
local choice=self.choices[self.selection] local choice=self.choices[self.selection]
if self.on_input then if self.on_select then
self.on_input(self.selection,choice) self.on_select(self.selection, choice)
end end
if choice and choice[2] then if choice then
choice[2](choice,self.selection) -- maybe reverse the arguments? local callback = choice.on_select or choice[2]
if callback then
callback(choice, self.selection)
end
end end
elseif keys.LEAVESCREEN then elseif keys.LEAVESCREEN then
self:dismiss() self:dismiss()
if self.on_cancel then if self.on_cancel then
self.on_cancel() self.on_cancel()
end end
elseif keys.CURSOR_UP then elseif keys.STANDARDSCROLL_UP then
self:moveCursor(-1) self:moveCursor(-1)
elseif keys.CURSOR_DOWN then elseif keys.STANDARDSCROLL_DOWN then
self:moveCursor(1) self:moveCursor(1)
elseif keys.CURSOR_UP_FAST then elseif keys.STANDARDSCROLL_PAGEUP then
self:moveCursor(-10) self:moveCursor(-self.page_size)
elseif keys.CURSOR_DOWN_FAST then elseif keys.STANDARDSCROLL_PAGEDOWN then
self:moveCursor(10) self:moveCursor(self.page_size)
end end
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{ ListBox{
frame_title = title, frame_title = title,
text = text, text = text,
text_pen = tcolor, text_pen = tcolor,
choices = choices, choices = choices,
on_input = on_input, on_select = on_select,
on_cancel = on_cancel, on_cancel = on_cancel,
frame_width = min_width, frame_width = min_width,
}:show() }:show()

@ -253,7 +253,7 @@ end
DwarfOverlay = defclass(DwarfOverlay, gui.Screen) DwarfOverlay = defclass(DwarfOverlay, gui.Screen)
function DwarfOverlay:updateLayout() function DwarfOverlay:postUpdateLayout()
self.df_layout = getPanelLayout() self.df_layout = getPanelLayout()
end end
@ -352,8 +352,7 @@ end
MenuOverlay = defclass(MenuOverlay, DwarfOverlay) MenuOverlay = defclass(MenuOverlay, DwarfOverlay)
function MenuOverlay:updateLayout() function MenuOverlay:postUpdateLayout()
MenuOverlay.super.updateLayout(self)
self.frame_rect = self.df_layout.menu self.frame_rect = self.df_layout.menu
end 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 -- 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 {} enum = enum or {}
-- Loop for restarting search from scratch -- Loop for restarting search from scratch
@ -374,6 +374,11 @@ function DiffSearcher:find_interactive(prompt,data_type,condition_cb)
while true do while true do
print('') 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) local ok, value, delta = condition_cb(ccursor)
ccursor = ccursor + 1 ccursor = ccursor + 1

@ -454,7 +454,7 @@ df::coord2d Maps::getBlockTileBiomeRgn(df::map_block *block, df::coord2d pos)
if (!block || !world->world_data) if (!block || !world->world_data)
return df::coord2d(); 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; unsigned idx = des.bits.biome;
if (idx < 9) if (idx < 9)
{ {
@ -529,8 +529,8 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2)
if (!block1 || !block2) if (!block1 || !block2)
return false; return false;
auto tile1 = MapExtras::index_tile<uint16_t>(block1->walkable, pos1); auto tile1 = index_tile<uint16_t>(block1->walkable, pos1);
auto tile2 = MapExtras::index_tile<uint16_t>(block2->walkable, pos2); auto tile2 = index_tile<uint16_t>(block2->walkable, pos2);
return tile1 && tile1 == tile2; return tile1 && tile1 == tile2;
} }

@ -28,6 +28,7 @@ distribution.
#include <string> #include <string>
#include <vector> #include <vector>
#include <map> #include <map>
#include <set>
using namespace std; using namespace std;
#include "modules/Screen.h" #include "modules/Screen.h"
@ -64,6 +65,8 @@ using df::global::enabler;
using Screen::Pen; using Screen::Pen;
using std::string;
/* /*
* Screen painting API. * Screen painting API.
*/ */
@ -303,6 +306,51 @@ bool Screen::isDismissed(df::viewscreen *screen)
return screen->breakdown_level != interface_breakdown_types::NONE; 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. * Base DFHack viewscreen.
*/ */

@ -48,89 +48,34 @@ using namespace DFHack;
using df::global::world; using df::global::world;
Module* DFHack::createWorld() static int next_persistent_id = 0;
{ static std::multimap<std::string, int> persistent_index;
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;
};
typedef std::pair<std::string, int> T_persistent_item; 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() bool World::ReadPauseState()
{ {
if(!d->PauseInited) return false; return DF_GLOBAL_VALUE(pause_state, false);
return *df::global::pause_state;
} }
void World::SetPauseState(bool paused) void World::SetPauseState(bool paused)
{ {
if (d->PauseInited) bool dummy;
*df::global::pause_state = paused; DF_GLOBAL_VALUE(pause_state, dummy) = paused;
} }
uint32_t World::ReadCurrentYear() uint32_t World::ReadCurrentYear()
{ {
return *df::global::cur_year; return DF_GLOBAL_VALUE(cur_year, 0);
} }
uint32_t World::ReadCurrentTick() uint32_t World::ReadCurrentTick()
{ {
return *df::global::cur_year_tick; return DF_GLOBAL_VALUE(cur_year_tick, 0);
} }
bool World::ReadGameMode(t_gamemodes& rd) 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_mode = (DFHack::GameMode)*df::global::gamemode;
rd.g_type = (DFHack::GameType)*df::global::gametype; 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) 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::gamemode = wr.g_mode;
*df::global::gametype = wr.g_type; *df::global::gametype = wr.g_type;
@ -173,24 +118,24 @@ specified by memory.xml gets me the current month/date.
*/ */
uint32_t World::ReadCurrentMonth() uint32_t World::ReadCurrentMonth()
{ {
return this->ReadCurrentTick() / 1200 / 28; return ReadCurrentTick() / 1200 / 28;
} }
uint32_t World::ReadCurrentDay() uint32_t World::ReadCurrentDay()
{ {
return ((this->ReadCurrentTick() / 1200) % 28) + 1; return ((ReadCurrentTick() / 1200) % 28) + 1;
} }
uint8_t World::ReadCurrentWeather() uint8_t World::ReadCurrentWeather()
{ {
if (d->Inited && d->StartedWeather) if (df::global::current_weather)
return (*df::global::current_weather)[2][2]; return (*df::global::current_weather)[2][2];
return 0; return 0;
} }
void World::SetCurrentWeather(uint8_t weather) void World::SetCurrentWeather(uint8_t weather)
{ {
if (d->Inited && d->StartedWeather) if (df::global::current_weather)
memset(df::global::current_weather, weather, 25); memset(df::global::current_weather, weather, 25);
} }
@ -206,13 +151,13 @@ static PersistentDataItem dataFromHFig(df::historical_figure *hfig)
void World::ClearPersistentCache() void World::ClearPersistentCache()
{ {
d->next_persistent_id = 0; next_persistent_id = 0;
d->persistent_index.clear(); persistent_index.clear();
} }
bool World::BuildPersistentCache() static bool BuildPersistentCache()
{ {
if (d->next_persistent_id) if (next_persistent_id)
return true; return true;
if (!Core::getInstance().isWorldLoaded()) if (!Core::getInstance().isWorldLoaded())
return false; return false;
@ -220,20 +165,20 @@ bool World::BuildPersistentCache()
std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector(); std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector();
// Determine the next entry id as min(-100, lowest_id-1) // 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) 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 // 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++) 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()) if (!hfvec[i]->name.has_name || hfvec[i]->name.first_name.empty())
continue; 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; return true;
@ -247,14 +192,14 @@ PersistentDataItem World::AddPersistentData(const std::string &key)
std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector(); std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector();
df::historical_figure *hfig = new df::historical_figure(); 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.has_name = true;
hfig->name.first_name = key; hfig->name.first_name = key;
memset(hfig->name.words, 0xFF, sizeof(hfig->name.words)); memset(hfig->name.words, 0xFF, sizeof(hfig->name.words));
hfvec.insert(hfvec.begin(), hfig); 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); return dataFromHFig(hfig);
} }
@ -264,8 +209,8 @@ PersistentDataItem World::GetPersistentData(const std::string &key)
if (!BuildPersistentCache()) if (!BuildPersistentCache())
return PersistentDataItem(); return PersistentDataItem();
auto it = d->persistent_index.find(key); auto it = persistent_index.find(key);
if (it != d->persistent_index.end()) if (it != persistent_index.end())
return GetPersistentData(it->second); return GetPersistentData(it->second);
return PersistentDataItem(); return PersistentDataItem();
@ -305,24 +250,24 @@ void World::GetPersistentData(std::vector<PersistentDataItem> *vec, const std::s
if (!BuildPersistentCache()) if (!BuildPersistentCache())
return; return;
auto eqrange = d->persistent_index.equal_range(key); auto eqrange = persistent_index.equal_range(key);
if (prefix) if (prefix)
{ {
if (key.empty()) if (key.empty())
{ {
eqrange.first = d->persistent_index.begin(); eqrange.first = persistent_index.begin();
eqrange.second = d->persistent_index.end(); eqrange.second = persistent_index.end();
} }
else else
{ {
std::string bound = key; std::string bound = key;
if (bound[bound.size()-1] != '/') if (bound[bound.size()-1] != '/')
bound += "/"; bound += "/";
eqrange.first = d->persistent_index.lower_bound(bound); eqrange.first = persistent_index.lower_bound(bound);
bound[bound.size()-1]++; 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) bool World::DeletePersistentData(const PersistentDataItem &item)
{ {
if (item.id > -100) int id = item.raw_id();
if (id > -100)
return false; return false;
if (!BuildPersistentCache()) if (!BuildPersistentCache())
return false; return false;
std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector(); 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; ) for (auto it2 = eqrange.first; it2 != eqrange.second; )
{ {
auto it = it2; ++it2; auto it = it2; ++it2;
if (it->second != -item.id) if (it->second != -id)
continue; 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) { if (idx >= 0) {
delete hfvec[idx]; delete hfvec[idx];

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

@ -30,6 +30,7 @@
#include "df/viewscreen_optionst.h" #include "df/viewscreen_optionst.h"
#include "df/viewscreen_dungeonmodest.h" #include "df/viewscreen_dungeonmodest.h"
#include "df/viewscreen_dungeon_monsterstatusst.h" #include "df/viewscreen_dungeon_monsterstatusst.h"
#include "df/nemesis_flags.h"
#include <math.h> #include <math.h>
@ -683,17 +684,19 @@ command_result adv_bodyswap (color_ostream &out, std::vector <std::string> & par
// Permanently re-link everything // Permanently re-link everything
if (permanent) if (permanent)
{ {
using namespace df::enums::nemesis_flags;
ui_advmode->player_id = linear_index(world->nemesis.all, new_nemesis); ui_advmode->player_id = linear_index(world->nemesis.all, new_nemesis);
// Flag 0 appears to be the 'active adventurer' flag, and // Flag 0 appears to be the 'active adventurer' flag, and
// the player_id field above seems to be computed using it // the player_id field above seems to be computed using it
// when a savegame is loaded. // when a savegame is loaded.
// Also, unless this is set, it is impossible to retire. // Also, unless this is set, it is impossible to retire.
real_nemesis->flags.set(0, false); real_nemesis->flags.set(ACTIVE_ADVENTURER, false);
new_nemesis->flags.set(0, true); new_nemesis->flags.set(ACTIVE_ADVENTURER, true);
real_nemesis->flags.set(1, true); // former retired adventurer real_nemesis->flags.set(RETIRED_ADVENTURER, true); // former retired adventurer
new_nemesis->flags.set(2, true); // blue color in legends new_nemesis->flags.set(ADVENTURER, true); // blue color in legends
// Reassign companions and acquaintances // Reassign companions and acquaintances
if (!no_make_leader) if (!no_make_leader)

@ -547,9 +547,7 @@ static void reset_labor(df::unit_labor labor)
static void init_state() static void init_state()
{ {
auto pworld = Core::getInstance().getWorld(); config = World::GetPersistentData("autolabor/config");
config = pworld->GetPersistentData("autolabor/config");
if (config.isValid() && config.ival(0) == -1) if (config.isValid() && config.ival(0) == -1)
config.ival(0) = 0; config.ival(0) = 0;
@ -558,7 +556,7 @@ static void init_state()
if (!enable_autolabor) if (!enable_autolabor)
return; return;
auto cfg_haulpct = pworld->GetPersistentData("autolabor/haulpct"); auto cfg_haulpct = World::GetPersistentData("autolabor/haulpct");
if (cfg_haulpct.isValid()) if (cfg_haulpct.isValid())
{ {
hauler_pct = cfg_haulpct.ival(0); hauler_pct = cfg_haulpct.ival(0);
@ -572,7 +570,7 @@ static void init_state()
labor_infos.resize(ARRAY_COUNT(default_labor_infos)); labor_infos.resize(ARRAY_COUNT(default_labor_infos));
std::vector<PersistentDataItem> items; 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++) for (auto p = items.begin(); p != items.end(); p++)
{ {
@ -594,7 +592,7 @@ static void init_state()
std::stringstream name; std::stringstream name;
name << "autolabor/labors/" << i; 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].is_exclusive = default_labor_infos[i].is_exclusive;
labor_infos[i].active_dwarfs = 0; labor_infos[i].active_dwarfs = 0;
@ -633,11 +631,9 @@ static void generate_labor_to_skill_map()
static void enable_plugin(color_ostream &out) static void enable_plugin(color_ostream &out)
{ {
auto pworld = Core::getInstance().getWorld();
if (!config.isValid()) if (!config.isValid())
{ {
config = pworld->AddPersistentData("autolabor/config"); config = World::AddPersistentData("autolabor/config");
config.ival(0) = 0; config.ival(0) = 0;
} }

@ -281,7 +281,7 @@ static void reset_tracking()
static void init_map(color_ostream &out) static void init_map(color_ostream &out)
{ {
auto config = Core::getInstance().getWorld()->GetPersistentData("burrows/config"); auto config = World::GetPersistentData("burrows/config");
if (config.isValid()) if (config.isValid())
{ {
auto_grow = !!(config.ival(0) & 1); auto_grow = !!(config.ival(0) & 1);
@ -307,7 +307,7 @@ static void deinit_map(color_ostream &out)
static PersistentDataItem create_config(color_ostream &out) static PersistentDataItem create_config(color_ostream &out)
{ {
bool created; bool created;
auto rv = Core::getInstance().getWorld()->GetPersistentData("burrows/config", &created); auto rv = World::GetPersistentData("burrows/config", &created);
if (created && rv.isValid()) if (created && rv.isValid())
rv.ival(0) = 0; rv.ival(0) = 0;
if (!rv.isValid()) if (!rv.isValid())

@ -2,134 +2,168 @@
#include "Console.h" #include "Console.h"
#include "Export.h" #include "Export.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "modules/MapCache.h" #include "modules/Units.h"
#include "modules/Maps.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "df/ui.h"
#include "df/world.h" #include "df/world.h"
#include "df/unit.h" #include "df/unit.h"
#include "df/map_block.h"
using std::string; using std::string;
using std::vector; using std::vector;
using namespace DFHack; using namespace DFHack;
using df::global::world; using df::global::world;
using df::global::ui;
// dfhack interface // dfhack interface
DFHACK_PLUGIN("fastdwarf"); DFHACK_PLUGIN("fastdwarf");
static bool enable_fastdwarf = false;
static bool enable_teledwarf = false;
DFhackCExport command_result plugin_shutdown ( color_ostream &out ) DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{ {
if (df::global::debug_turbospeed)
*df::global::debug_turbospeed = false;
return CR_OK; return CR_OK;
} }
static bool enable_fastdwarf = false;
static bool enable_teledwarf = false;
DFhackCExport command_result plugin_onupdate ( color_ostream &out ) DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
// check run conditions // do we even need to do anything at all?
if(!world || !world->map.block_index) 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; enable_fastdwarf = enable_teledwarf = false;
return CR_OK; return CR_OK;
} }
int32_t race = ui->race_id;
int32_t civ = ui->civ_id;
if ( enable_fastdwarf ) { df::map_block *old_block, *new_block;
for (size_t i = 0; i < world->units.all.size(); i++) for (size_t i = 0; i < world->units.active.size(); i++)
{ {
df::unit *unit = world->units.all[i]; df::unit *unit = world->units.active[i];
// citizens only
if (!Units::isCitizen(unit))
continue;
if (unit->race == race && unit->civ_id == civ && unit->counters.job_counter > 0) if (enable_fastdwarf)
{
if (unit->counters.job_counter > 0)
unit->counters.job_counter = 0; unit->counters.job_counter = 0;
// could also patch the unit->job.current_job->completion_timer // could also patch the unit->job.current_job->completion_timer
} }
}
if ( enable_teledwarf ) { if (enable_teledwarf) do
MapExtras::MapCache *MCache = new MapExtras::MapCache();
for (size_t i = 0; i < world->units.all.size(); i++)
{ {
df::unit *unit = world->units.all[i]; // don't do anything if the dwarf isn't going anywhere
if (!unit->path.dest.isValid())
break;
if (unit->race != race || unit->civ_id != civ || unit->path.dest.x == -30000) // skip dwarves that are dragging creatures or being dragged
continue; if ((unit->relations.draggee_id != -1) || (unit->relations.dragger_id != -1))
if (unit->relations.draggee_id != -1 || unit->relations.dragger_id != -1) break;
continue;
// skip dwarves that are following other units
if (unit->relations.following != 0) if (unit->relations.following != 0)
continue; 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;
MapExtras::Block* block = MCache->BlockAtTile(unit->pos); // if there's already somebody standing at the destination, then force the unit to lay down
df::coord2d pos(unit->pos.x % 16, unit->pos.y % 16); if (new_occ->bits.unit)
df::tile_occupancy occ = block->OccupancyAt(pos); unit->flags1.bits.on_ground = 1;
occ.bits.unit = 0;
occ.bits.unit_grounded = 0; // set appropriate occupancy flags at new tile
block->setOccupancyAt(pos, occ); if (unit->flags1.bits.on_ground)
new_occ->bits.unit_grounded = 1;
//move immediately to destination else
unit->pos.x = unit->path.dest.x; new_occ->bits.unit = 1;
unit->pos.y = unit->path.dest.y;
unit->pos.z = unit->path.dest.z; // move unit to destination
} unit->pos = unit->path.dest;
MCache->WriteAll(); unit->path.path.clear();
delete MCache; } while (0);
} }
return CR_OK; return CR_OK;
} }
static command_result fastdwarf (color_ostream &out, vector <string> & parameters) static command_result fastdwarf (color_ostream &out, vector <string> & parameters)
{ {
if (parameters.size() == 1) { if (parameters.size() > 2) {
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;
}
} else if (parameters.size() == 2) {
if ( parameters[0] == "0" ) {
enable_fastdwarf = false;
} else if ( parameters[0] == "1" ) {
enable_fastdwarf = true;
} else {
out.print("Incorrect usage.\n"); out.print("Incorrect usage.\n");
return CR_OK; return CR_FAILURE;
} }
if ( parameters[1] == "0" ) {
if (parameters.size() <= 2)
{
if (parameters.size() == 2)
{
if (parameters[1] == "0")
enable_teledwarf = false; enable_teledwarf = false;
} else if ( parameters[1] == "1" ) { else if (parameters[1] == "1")
enable_teledwarf = true; enable_teledwarf = true;
} else { else
{
out.print("Incorrect usage.\n"); out.print("Incorrect usage.\n");
return CR_OK; 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")) else
{ enable_teledwarf = false;
if (parameters[0] == "0") if (parameters[0] == "0")
enable_fastdwarf = 0; {
enable_fastdwarf = false;
if (df::global::debug_turbospeed)
*df::global::debug_turbospeed = false;
}
else if (parameters[0] == "1")
{
enable_fastdwarf = true;
if (df::global::debug_turbospeed)
*df::global::debug_turbospeed = false;
}
else if (parameters[0] == "2")
{
if (df::global::debug_turbospeed)
{
enable_fastdwarf = false;
*df::global::debug_turbospeed = true;
}
else else
enable_fastdwarf = 1; {
out.print("fastdwarf %sactivated.\n", (enable_fastdwarf ? "" : "de")); out.print("Speed level 2 not available.\n");
return CR_FAILURE;
}
} }
else else
{ {
out.print("Makes your minions move at ludicrous speeds.\n" out.print("Incorrect usage.\n");
"Activate with 'fastdwarf 1', deactivate with 'fastdwarf 0'.\n" return CR_FAILURE;
"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; return CR_OK;
} }
@ -139,14 +173,16 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
commands.push_back(PluginCommand("fastdwarf", commands.push_back(PluginCommand("fastdwarf",
"enable/disable fastdwarf and teledwarf (parameters=0/1)", "enable/disable fastdwarf and teledwarf (parameters=0/1)",
fastdwarf, false, 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" "Usage:\n"
" fastdwarf 0 0: disable both speedydwarf and teledwarf\n" " fastdwarf <speed> (tele)\n"
" fastdwarf 0 1: disable speedydwarf, enable teledwarf\n" "Valid values for speed:\n"
" fastdwarf 1 0: enable speedydwarf, disable teledwarf\n" " * 0 - Make dwarves move and work at standard speed.\n"
" fastdwarf 1 1: enable speedydwarf, enable teledwarf\n" " * 1 - Make dwarves move and work at maximum speed.\n"
" fastdwarf 0: disable speedydwarf, disable teledwarf\n" " * 2 - Make ALL creatures move and work at maximum speed.\n"
" fastdwarf 1: enable speedydwarf, disable teledwarf\n" "Valid values for (tele):\n"
" * 0 - Disable dwarf teleportation (default)\n"
" * 1 - Make dwarves teleport to their destinations instantly.\n"
)); ));
return CR_OK; 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 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; df::coord &unitPos = followedUnit->pos;
@ -120,7 +119,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
Gui::setViewCoords(x, y, z); 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, 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); 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 //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]; UnitInfo *cur = units[i];
df::unit *unit = cur->unit; df::unit *unit = cur->unit;
cur->name = Translation::TranslateName(&unit->name, false); cur->name = Translation::TranslateName(Units::getVisibleName(unit), false);
cur->transname = Translation::TranslateName(&unit->name, true); cur->transname = Translation::TranslateName(Units::getVisibleName(unit), true);
cur->profession = Units::getProfessionName(unit); cur->profession = Units::getProfessionName(unit);
} }
calcSize(); calcSize();
@ -1105,30 +1105,30 @@ void viewscreen_unitlaborsst::render()
} }
int x = 2; 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(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(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(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"); OutputString(15, x, gps->dimy - 3, ": Zoom-Cre");
x = 2; 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(15, x, gps->dimy - 2, ": Done, ");
OutputString(10, x, gps->dimy - 2, "+"); // SECONDSCROLL_DOWN key OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_DOWN));
OutputString(10, x, gps->dimy - 2, "-"); // SECONDSCROLL_UP key OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_UP));
OutputString(15, x, gps->dimy - 2, ": Sort by Skill, "); OutputString(15, x, gps->dimy - 2, ": Sort by Skill, ");
OutputString(10, x, gps->dimy - 2, "*"); // SECONDSCROLL_PAGEDOWN key OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEDOWN));
OutputString(10, x, gps->dimy - 2, "/"); // SECONDSCROLL_PAGEUP key OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEUP));
OutputString(15, x, gps->dimy - 2, ": Sort by ("); 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, ") "); OutputString(15, x, gps->dimy - 2, ") ");
switch (altsort) switch (altsort)
{ {
@ -1182,7 +1182,7 @@ struct unitlist_hook : df::viewscreen_unitlistst
if (units[page].size()) if (units[page].size())
{ {
int x = 2; 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)"); 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; return CR_WRONG_USAGE;
} }
World *world;
{ {
CoreSuspender suspend; CoreSuspender suspend;
world = Core::getInstance().getWorld(); World::ReadGameMode(gm);
world->Start();
world->ReadGameMode(gm);
} }
printCurrentModes(gm, out); printCurrentModes(gm, out);
@ -202,7 +198,7 @@ command_result mode (color_ostream &out_, vector <string> & parameters)
{ {
CoreSuspender suspend; CoreSuspender suspend;
world->WriteGameMode(gm); World::WriteGameMode(gm);
} }
out << endl; out << endl;

@ -177,8 +177,7 @@ static bool makePowerMeter(df::pressure_plate_info *info, int min_power, int max
if (!enabled) if (!enabled)
{ {
auto pworld = Core::getInstance().getWorld(); auto entry = World::GetPersistentData("power-meter/enabled", NULL);
auto entry = pworld->GetPersistentData("power-meter/enabled", NULL);
if (!entry.isValid()) if (!entry.isValid())
return false; return false;
@ -202,8 +201,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
switch (event) { switch (event) {
case SC_WORLD_LOADED: case SC_WORLD_LOADED:
{ {
auto pworld = Core::getInstance().getWorld(); bool enable = World::GetPersistentData("power-meter/enabled").isValid();
bool enable = pworld->GetPersistentData("power-meter/enabled").isValid();
if (enable) 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] [REACTION:SPATTER_ADD_OBJECT_LIQUID]
[NAME:coat object with liquid] [NAME:coat object with liquid]
[ADVENTURE_MODE_ENABLED] [ADVENTURE_MODE_ENABLED]
[SKILL:WAX_WORKING] [SKILL:DYER]
[REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE] [REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE]
[MIN_DIMENSION:150] [MIN_DIMENSION:150]
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT] [DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
@ -28,10 +28,10 @@ Reaction name must start with 'SPATTER_ADD_':
[REACTION:SPATTER_ADD_WEAPON_EXTRACT] [REACTION:SPATTER_ADD_WEAPON_EXTRACT]
[NAME:coat weapon with extract] [NAME:coat weapon with extract]
[BUILDING:CRAFTSMAN:NONE] [BUILDING:GREASING_STATION:CUSTOM_W]
[SKILL:WAX_WORKING] [SKILL:DYER]
[REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE] [REAGENT:extract:100:LIQUID_MISC:NONE:NONE:NONE]
[MIN_DIMENSION:150] [MIN_DIMENSION:100]
[REACTION_CLASS:CREATURE_EXTRACT] [REACTION_CLASS:CREATURE_EXTRACT]
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT] [DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
[REAGENT:extract container:1:NONE:NONE:NONE:NONE] [REAGENT:extract container:1:NONE:NONE:NONE:NONE]
@ -51,8 +51,8 @@ Reaction name must start with 'SPATTER_ADD_':
[REACTION:SPATTER_ADD_AMMO_EXTRACT] [REACTION:SPATTER_ADD_AMMO_EXTRACT]
[NAME:coat ammo with extract] [NAME:coat ammo with extract]
[BUILDING:CRAFTSMAN:NONE] [BUILDING:GREASING_STATION:CUSTOM_A]
[SKILL:WAX_WORKING] [SKILL:DYER]
[REAGENT:extract:50:LIQUID_MISC:NONE:NONE:NONE] [REAGENT:extract:50:LIQUID_MISC:NONE:NONE:NONE]
[MIN_DIMENSION:50] [MIN_DIMENSION:50]
[REACTION_CLASS:CREATURE_EXTRACT] [REACTION_CLASS:CREATURE_EXTRACT]
@ -75,8 +75,8 @@ Reaction name must start with 'SPATTER_ADD_':
[REACTION:SPATTER_ADD_WEAPON_GCS] [REACTION:SPATTER_ADD_WEAPON_GCS]
[NAME:coat weapon with GCS venom] [NAME:coat weapon with GCS venom]
[BUILDING:CRAFTSMAN:NONE] [BUILDING:GREASING_STATION:NONE]
[SKILL:WAX_WORKING] [SKILL:DYER]
[REAGENT:extract:150:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON] [REAGENT:extract:150:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON]
[MIN_DIMENSION:150] [MIN_DIMENSION:150]
[REACTION_CLASS:CREATURE_EXTRACT] [REACTION_CLASS:CREATURE_EXTRACT]
@ -95,8 +95,8 @@ Reaction name must start with 'SPATTER_ADD_':
[REACTION:SPATTER_ADD_AMMO_GCS] [REACTION:SPATTER_ADD_AMMO_GCS]
[NAME:coat ammo with GCS venom] [NAME:coat ammo with GCS venom]
[BUILDING:CRAFTSMAN:NONE] [BUILDING:GREASING_STATION:NONE]
[SKILL:WAX_WORKING] [SKILL:DYER]
[REAGENT:extract:50:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON] [REAGENT:extract:50:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON]
[MIN_DIMENSION:50] [MIN_DIMENSION:50]
[REACTION_CLASS:CREATURE_EXTRACT] [REACTION_CLASS:CREATURE_EXTRACT]
@ -115,8 +115,8 @@ Reaction name must start with 'SPATTER_ADD_':
[REACTION:SPATTER_ADD_WEAPON_GDS] [REACTION:SPATTER_ADD_WEAPON_GDS]
[NAME:coat weapon with GDS venom] [NAME:coat weapon with GDS venom]
[BUILDING:CRAFTSMAN:NONE] [BUILDING:GREASING_STATION:NONE]
[SKILL:WAX_WORKING] [SKILL:DYER]
[REAGENT:extract:150:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON] [REAGENT:extract:150:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON]
[MIN_DIMENSION:150] [MIN_DIMENSION:150]
[REACTION_CLASS:CREATURE_EXTRACT] [REACTION_CLASS:CREATURE_EXTRACT]
@ -135,8 +135,8 @@ Reaction name must start with 'SPATTER_ADD_':
[REACTION:SPATTER_ADD_AMMO_GDS] [REACTION:SPATTER_ADD_AMMO_GDS]
[NAME:coat ammo with GDS venom] [NAME:coat ammo with GDS venom]
[BUILDING:CRAFTSMAN:NONE] [BUILDING:GREASING_STATION:NONE]
[SKILL:WAX_WORKING] [SKILL:DYER]
[REAGENT:extract:50:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON] [REAGENT:extract:50:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON]
[MIN_DIMENSION:50] [MIN_DIMENSION:50]
[REACTION_CLASS:CREATURE_EXTRACT] [REACTION_CLASS:CREATURE_EXTRACT]

@ -215,8 +215,7 @@ static void init_buildings(bool enable)
if (enable) if (enable)
{ {
auto pworld = Core::getInstance().getWorld(); auto entry = World::GetPersistentData("rename/building_types");
auto entry = pworld->GetPersistentData("rename/building_types");
if (entry.isValid()) if (entry.isValid())
{ {
@ -245,8 +244,7 @@ static bool renameBuilding(df::building *bld, std::string name)
if (!name.empty() && !is_enabled_building(code)) if (!name.empty() && !is_enabled_building(code))
{ {
auto pworld = Core::getInstance().getWorld(); auto entry = World::GetPersistentData("rename/building_types", NULL);
auto entry = pworld->GetPersistentData("rename/building_types", NULL);
if (!entry.isValid()) if (!entry.isValid())
return false; return false;

@ -87,19 +87,18 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCom
DFhackCExport command_result plugin_onupdate ( color_ostream &out ) DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
World *World = Core::getInstance().getWorld();
t_gamemodes gm; t_gamemodes gm;
World->ReadGameMode(gm); World::ReadGameMode(gm);
if(gm.g_mode == game_mode::DWARF) if(gm.g_mode == game_mode::DWARF)
{ {
// if the map is revealed and we're in fortress mode, force the game to pause. // if the map is revealed and we're in fortress mode, force the game to pause.
if(revealed == REVEALED) if(revealed == REVEALED)
{ {
World->SetPauseState(true); World::SetPauseState(true);
} }
else if(nopause_state) else if(nopause_state)
{ {
World->SetPauseState(false); World::SetPauseState(false);
} }
} }
return CR_OK; return CR_OK;
@ -185,14 +184,13 @@ command_result reveal(color_ostream &out, vector<string> & params)
CoreSuspender suspend; CoreSuspender suspend;
World *World = Core::getInstance().getWorld();
if (!Maps::IsValid()) if (!Maps::IsValid())
{ {
out.printerr("Map is not available!\n"); out.printerr("Map is not available!\n");
return CR_FAILURE; return CR_FAILURE;
} }
t_gamemodes gm; t_gamemodes gm;
World->ReadGameMode(gm); World::ReadGameMode(gm);
if(gm.g_mode == game_mode::ADVENTURE) if(gm.g_mode == game_mode::ADVENTURE)
{ {
revealAdventure(out); revealAdventure(out);
@ -234,7 +232,7 @@ command_result reveal(color_ostream &out, vector<string> & params)
if(pause) if(pause)
{ {
revealed = REVEALED; revealed = REVEALED;
World->SetPauseState(true); World::SetPauseState(true);
} }
else else
revealed = DEMON_REVEALED; revealed = DEMON_REVEALED;
@ -264,14 +262,13 @@ command_result unreveal(color_ostream &out, vector<string> & params)
} }
CoreSuspender suspend; CoreSuspender suspend;
World *World = Core::getInstance().getWorld();
if (!Maps::IsValid()) if (!Maps::IsValid())
{ {
out.printerr("Map is not available!\n"); out.printerr("Map is not available!\n");
return CR_FAILURE; return CR_FAILURE;
} }
t_gamemodes gm; t_gamemodes gm;
World->ReadGameMode(gm); World::ReadGameMode(gm);
if(gm.g_mode != game_mode::DWARF) if(gm.g_mode != game_mode::DWARF)
{ {
con.printerr("Only in fortress mode.\n"); con.printerr("Only in fortress mode.\n");
@ -337,7 +334,6 @@ command_result revflood(color_ostream &out, vector<string> & params)
} }
CoreSuspender suspend; CoreSuspender suspend;
uint32_t x_max,y_max,z_max; uint32_t x_max,y_max,z_max;
World * World = Core::getInstance().getWorld();
if (!Maps::IsValid()) if (!Maps::IsValid())
{ {
out.printerr("Map is not available!\n"); out.printerr("Map is not available!\n");
@ -349,7 +345,7 @@ command_result revflood(color_ostream &out, vector<string> & params)
return CR_FAILURE; return CR_FAILURE;
} }
t_gamemodes gm; t_gamemodes gm;
World->ReadGameMode(gm); World::ReadGameMode(gm);
if(gm.g_type != game_type::DWARF_MAIN && gm.g_mode != game_mode::DWARF ) if(gm.g_type != game_type::DWARF_MAIN && gm.g_mode != game_mode::DWARF )
{ {
out.printerr("Only in proper dwarf mode.\n"); 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; materialsReverser[world->raws.plants.all[i]->id] = i;
} }
World *w = Core::getInstance().getWorld();
t_gamemodes gm; 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 game mode isn't fortress mode
if(gm.g_mode != game_mode::DWARF || if(gm.g_mode != game_mode::DWARF ||
@ -296,9 +295,8 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
return CR_OK; return CR_OK;
counter = 0; counter = 0;
World *w = Core::getInstance().getWorld();
t_gamemodes gm; 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 game mode isn't fortress mode
if(gm.g_mode != game_mode::DWARF || if(gm.g_mode != game_mode::DWARF ||
!(gm.g_type == game_type::DWARF_MAIN || gm.g_type == game_type::DWARF_RECLAIM)) !(gm.g_type == game_type::DWARF_MAIN || gm.g_type == game_type::DWARF_RECLAIM))

@ -347,10 +347,9 @@ static void load_engines()
{ {
clear_engines(); clear_engines();
auto pworld = Core::getInstance().getWorld();
std::vector<PersistentDataItem> vec; 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) for (auto it = vec.begin(); it != vec.end(); ++it)
{ {
auto engine = find_engine(df::building::find(it->ival(0)), true); 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)); 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) for (auto it = vec.begin(); it != vec.end(); ++it)
{ {
auto engine = find_engine(df::building::find(it->ival(0)), true); 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); 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) for (auto it = vec.begin(); it != vec.end(); ++it)
{ {
auto engine = find_engine(df::building::find(it->ival(0)), true); 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)); auto pile = df::building::find(it->ival(1));
if (!pile || pile->getType() != building_type::Stockpile) if (!pile || pile->getType() != building_type::Stockpile)
{ {
pworld->DeletePersistentData(*it); World::DeletePersistentData(*it);
continue;; continue;;
} }
engine->stockpiles.insert(it->ival(1)); 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) for (auto it = vec.begin(); it != vec.end(); ++it)
{ {
auto engine = find_engine(df::building::find(it->ival(0)), true); 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); 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) for (auto it = vec.begin(); it != vec.end(); ++it)
{ {
auto engine = find_engine(df::building::find(it->ival(0)), true); 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)); auto unit = df::unit::find(it->ival(1));
if (!unit || !Units::isCitizen(unit)) if (!unit || !Units::isCitizen(unit))
{ {
pworld->DeletePersistentData(*it); World::DeletePersistentData(*it);
continue; continue;
} }
engine->profile.permitted_workers.push_back(it->ival(1)); 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)) if (auto engine = find_engine(bld))
engine->target = coord_range(); engine->target = coord_range();
auto pworld = Core::getInstance().getWorld();
auto key = stl_sprintf("siege-engine/target/%d", bld->id); 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) 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()) if (!enable_plugin())
return false; return false;
auto pworld = Core::getInstance().getWorld();
auto key = stl_sprintf("siege-engine/target/%d", bld->id); 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()) if (!entry.isValid())
return false; return false;
@ -491,9 +488,8 @@ static int setAmmoItem(lua_State *L)
if (!is_valid_enum_item(item_type)) if (!is_valid_enum_item(item_type))
luaL_argerror(L, 2, "invalid 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 key = stl_sprintf("siege-engine/ammo/%d", engine->id);
auto entry = pworld->GetPersistentData(key, NULL); auto entry = World::GetPersistentData(key, NULL);
if (!entry.isValid()) if (!entry.isValid())
return 0; return 0;
@ -523,9 +519,8 @@ static void forgetStockpileLink(EngineInfo *engine, int pile_id)
{ {
engine->stockpiles.erase(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); 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) static void update_stockpile_links(EngineInfo *engine)
@ -583,9 +578,8 @@ static bool addStockpileLink(df::building_siegeenginest *bld, df::building_stock
if (!enable_plugin()) if (!enable_plugin())
return false; return false;
auto pworld = Core::getInstance().getWorld();
auto key = stl_sprintf("siege-engine/stockpiles/%d/%d", bld->id, pile->id); 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()) if (!entry.isValid())
return false; return false;
@ -620,9 +614,8 @@ static df::workshop_profile *saveWorkshopProfile(df::building_siegeenginest *bld
return NULL; return NULL;
// Save skill limits // Save skill limits
auto pworld = Core::getInstance().getWorld();
auto key = stl_sprintf("siege-engine/profiles/%d", bld->id); 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()) if (!entry.isValid())
return NULL; return NULL;
@ -637,18 +630,18 @@ static df::workshop_profile *saveWorkshopProfile(df::building_siegeenginest *bld
auto &workers = engine->profile.permitted_workers; auto &workers = engine->profile.permitted_workers;
key = stl_sprintf("siege-engine/profile-workers/%d", bld->id); 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) for (auto it = vec.begin(); it != vec.end(); ++it)
{ {
if (linear_index(workers, it->ival(1)) < 0) if (linear_index(workers, it->ival(1)) < 0)
pworld->DeletePersistentData(*it); World::DeletePersistentData(*it);
} }
for (size_t i = 0; i < workers.size(); i++) for (size_t i = 0; i < workers.size(); i++)
{ {
key = stl_sprintf("siege-engine/profile-workers/%d/%d", bld->id, workers[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()) if (!entry.isValid())
continue; continue;
entry.ival(0) = engine->id; entry.ival(0) = engine->id;
@ -1802,8 +1795,7 @@ static bool enable_plugin()
if (is_enabled) if (is_enabled)
return true; return true;
auto pworld = Core::getInstance().getWorld(); auto entry = World::GetPersistentData("siege-engine/enabled", NULL);
auto entry = pworld->GetPersistentData("siege-engine/enabled", NULL);
if (!entry.isValid()) if (!entry.isValid())
return false; return false;
@ -1828,8 +1820,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
case SC_MAP_LOADED: case SC_MAP_LOADED:
if (!gamemode || *gamemode == game_mode::DWARF) if (!gamemode || *gamemode == game_mode::DWARF)
{ {
auto pworld = Core::getInstance().getWorld(); bool enable = World::GetPersistentData("siege-engine/enabled").isValid();
bool enable = pworld->GetPersistentData("siege-engine/enabled").isValid();
if (enable) if (enable)
{ {

@ -34,7 +34,8 @@
#include "df/ui_build_selector.h" #include "df/ui_build_selector.h"
#include "df/building_trapst.h" #include "df/building_trapst.h"
#include "df/item_actual.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_barst.h"
#include "df/item_threadst.h" #include "df/item_threadst.h"
#include "df/item_clothst.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); if (copy) copy->categorize(true);
} }
struct dimension_lqp_hook : df::item_liquipowder { struct dimension_liquid_hook : df::item_liquid_miscst {
typedef df::item_liquipowder interpose_base; typedef df::item_liquid_miscst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) 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 { struct dimension_bar_hook : df::item_barst {
typedef df::item_barst interpose_base; 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") 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_bar_hook, subtractDimension), parameters);
enable_hook(out, INTERPOSE_HOOK(dimension_thread_hook, subtractDimension), parameters); enable_hook(out, INTERPOSE_HOOK(dimension_thread_hook, subtractDimension), parameters);
enable_hook(out, INTERPOSE_HOOK(dimension_cloth_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; CoreSuspender suspend;
DFHack::World * w = Core::getInstance().getWorld();
if(!df::global::current_weather) if(!df::global::current_weather)
{ {
con << "Weather support seems broken :(" << std::endl; con << "Weather support seems broken :(" << std::endl;
@ -123,22 +122,22 @@ command_result weather (color_ostream &con, vector <string> & parameters)
if(rain) if(rain)
{ {
con << "Here comes the rain." << std::endl; con << "Here comes the rain." << std::endl;
w->SetCurrentWeather(weather_type::Rain); World::SetCurrentWeather(weather_type::Rain);
} }
if(snow) if(snow)
{ {
con << "Snow everywhere!" << std::endl; con << "Snow everywhere!" << std::endl;
w->SetCurrentWeather(weather_type::Snow); World::SetCurrentWeather(weather_type::Snow);
} }
if(clear) if(clear)
{ {
con << "Suddenly, sunny weather!" << std::endl; con << "Suddenly, sunny weather!" << std::endl;
w->SetCurrentWeather(weather_type::None); World::SetCurrentWeather(weather_type::None);
} }
if(val_override != -1) if(val_override != -1)
{ {
con << "I have no damn idea what this is... " << val_override << std::endl; 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. // 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) static void init_state(color_ostream &out)
{ {
auto pworld = Core::getInstance().getWorld(); config = World::GetPersistentData("workflow/config");
config = pworld->GetPersistentData("workflow/config");
if (config.isValid() && config.ival(0) == -1) if (config.isValid() && config.ival(0) == -1)
config.ival(0) = 0; config.ival(0) = 0;
@ -448,14 +446,14 @@ static void init_state(color_ostream &out)
// Parse constraints // Parse constraints
std::vector<PersistentDataItem> items; std::vector<PersistentDataItem> items;
pworld->GetPersistentData(&items, "workflow/constraints"); World::GetPersistentData(&items, "workflow/constraints");
for (int i = items.size()-1; i >= 0; i--) { for (int i = items.size()-1; i >= 0; i--) {
if (get_constraint(out, items[i].val(), &items[i])) if (get_constraint(out, items[i].val(), &items[i]))
continue; continue;
out.printerr("Lost constraint %s\n", items[i].val().c_str()); 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; 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) static void enable_plugin(color_ostream &out)
{ {
auto pworld = Core::getInstance().getWorld();
if (!config.isValid()) if (!config.isValid())
{ {
config = pworld->AddPersistentData("workflow/config"); config = World::AddPersistentData("workflow/config");
config.ival(0) = 0; config.ival(0) = 0;
} }
@ -729,7 +725,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str
nct->config = *cfg; nct->config = *cfg;
else else
{ {
nct->config = Core::getInstance().getWorld()->AddPersistentData("workflow/constraints"); nct->config = World::AddPersistentData("workflow/constraints");
nct->init(str); nct->init(str);
} }
@ -743,7 +739,7 @@ static void delete_constraint(ItemConstraint *cv)
if (idx >= 0) if (idx >= 0)
vector_erase_at(constraints, idx); vector_erase_at(constraints, idx);
Core::getInstance().getWorld()->DeletePersistentData(cv->config); World::DeletePersistentData(cv->config);
delete cv; delete cv;
} }

@ -2769,10 +2769,7 @@ public:
if(!rconfig.isValid()) if(!rconfig.isValid())
{ {
string keyname = "autobutcher/watchlist/" + getRaceName(raceId); string keyname = "autobutcher/watchlist/" + getRaceName(raceId);
auto pworld = Core::getInstance().getWorld(); rconfig = World::GetPersistentData(keyname, NULL);
rconfig = pworld->GetPersistentData(keyname);
if(!rconfig.isValid())
rconfig = pworld->AddPersistentData(keyname);
} }
if(rconfig.isValid()) if(rconfig.isValid())
{ {
@ -2795,7 +2792,7 @@ public:
{ {
if(!rconfig.isValid()) if(!rconfig.isValid())
return; return;
Core::getInstance().getWorld()->DeletePersistentData(rconfig); World::DeletePersistentData(rconfig);
} }
void SortUnitsByAge() void SortUnitsByAge()
@ -3405,13 +3402,18 @@ command_result autoButcher( color_ostream &out, bool verbose = false )
command_result start_autobutcher(color_ostream &out) command_result start_autobutcher(color_ostream &out)
{ {
auto pworld = Core::getInstance().getWorld();
enable_autobutcher = true; enable_autobutcher = true;
if (!config_autobutcher.isValid()) if (!config_autobutcher.isValid())
{ {
config_autobutcher = pworld->AddPersistentData("autobutcher/config"); config_autobutcher = World::AddPersistentData("autobutcher/config");
config_autobutcher.ival(0) = enable_autobutcher;
if (!config_autobutcher.isValid())
{
out << "Cannot enable autobutcher without a world!" << endl;
return CR_OK;
}
config_autobutcher.ival(1) = sleep_autobutcher; config_autobutcher.ival(1) = sleep_autobutcher;
config_autobutcher.ival(2) = enable_autobutcher_autowatch; config_autobutcher.ival(2) = enable_autobutcher_autowatch;
config_autobutcher.ival(3) = default_fk; 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(6) = default_ma;
} }
config_autobutcher.ival(0) = enable_autobutcher;
out << "Starting autobutcher." << endl; out << "Starting autobutcher." << endl;
init_autobutcher(out); init_autobutcher(out);
return CR_OK; return CR_OK;
@ -3428,14 +3432,8 @@ command_result start_autobutcher(color_ostream &out)
command_result init_autobutcher(color_ostream &out) command_result init_autobutcher(color_ostream &out)
{ {
cleanup_autobutcher(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.isValid())
{ {
if (config_autobutcher.ival(0) == -1) if (config_autobutcher.ival(0) == -1)
@ -3467,7 +3465,7 @@ command_result init_autobutcher(color_ostream &out)
// read watchlist from save // read watchlist from save
std::vector<PersistentDataItem> items; 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++) for (auto p = items.begin(); p != items.end(); p++)
{ {
string key = p->key(); string key = p->key();
@ -3498,14 +3496,23 @@ command_result cleanup_autobutcher(color_ostream &out)
command_result start_autonestbox(color_ostream &out) command_result start_autonestbox(color_ostream &out)
{ {
auto pworld = Core::getInstance().getWorld();
enable_autonestbox = true; enable_autonestbox = true;
if (!config_autobutcher.isValid()) if (!config_autobutcher.isValid())
{ {
config_autonestbox = pworld->AddPersistentData("autonestbox/config"); config_autonestbox = World::AddPersistentData("autonestbox/config");
config_autonestbox.ival(0) = enable_autonestbox;
if (!config_autobutcher.isValid())
{
out << "Cannot enable autonestbox without a world!" << endl;
return CR_OK;
}
config_autonestbox.ival(1) = sleep_autonestbox; config_autonestbox.ival(1) = sleep_autonestbox;
} }
config_autonestbox.ival(0) = enable_autonestbox;
out << "Starting autonestbox." << endl; out << "Starting autonestbox." << endl;
init_autonestbox(out); init_autonestbox(out);
return CR_OK; return CR_OK;
@ -3514,14 +3521,8 @@ command_result start_autonestbox(color_ostream &out)
command_result init_autonestbox(color_ostream &out) command_result init_autonestbox(color_ostream &out)
{ {
cleanup_autonestbox(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.isValid())
{ {
if (config_autonestbox.ival(0) == -1) if (config_autonestbox.ival(0) == -1)

@ -2,6 +2,7 @@
local utils = require 'utils' local utils = require 'utils'
local ms = require 'memscan' local ms = require 'memscan'
local gui = require 'gui'
local is_known = dfhack.internal.getAddress local is_known = dfhack.internal.getAddress
@ -53,6 +54,35 @@ end
local searcher = ms.DiffSearcher.new(data) 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 function validate_offset(name,validator,addr,tname,...)
local obj = data:object_by_field(addr,tname,...) local obj = data:object_by_field(addr,tname,...)
if obj and not validator(obj) then if obj and not validator(obj) then
@ -136,13 +166,134 @@ local function list_index_choices(length_func)
end end
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 -- Cursor group
-- --
local function find_cursor() local function find_cursor()
print('\nPlease navigate to the title screen to find cursor.') if not screen_title() then
if not utils.prompt_yes_no('Proceed?', true) then
return false return false
end end
@ -354,13 +505,35 @@ local function is_valid_world(world)
end end
local function find_world() 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 Searching for world. Please open the stockpile creation
menu, and select different types as instructed below:]], menu, and select different types as instructed below:]],
'int32_t', 'int32_t', catnames, df.stockpile_category
{ 'Corpses', 'Refuse', 'Stone', 'Wood', 'Gems', 'Bars', 'Cloth', 'Leather', 'Ammo', 'Coins' },
df.stockpile_category
) )
end
validate_offset('world', is_valid_world, addr, df.world, 'selected_stockpile_type') validate_offset('world', is_valid_world, addr, df.world, 'selected_stockpile_type')
end end
@ -385,14 +558,37 @@ local function is_valid_ui(ui)
end end
local function find_ui() 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 Searching for ui. Please open the designation
menu, and switch modes as instructed below:]], menu, and switch modes as instructed below:]],
'int16_t', 'int16_t', catnames, df.ui_sidebar_mode
{ 'DesignateMine', 'DesignateChannel', 'DesignateRemoveRamps', 'DesignateUpStair',
'DesignateDownStair', 'DesignateUpDownStair', 'DesignateUpRamp', 'DesignateChopTrees' },
df.ui_sidebar_mode
) )
end
validate_offset('ui', is_valid_ui, addr, df.ui, 'main', 'mode') validate_offset('ui', is_valid_ui, addr, df.ui, 'main', 'mode')
end end
@ -421,7 +617,23 @@ local function is_valid_ui_sidebar_menus(usm)
end end
local function find_ui_sidebar_menus() 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, Searching for ui_sidebar_menus. Please switch to 'q' mode,
select a Mason, Craftsdwarfs, or Carpenters workshop, open select a Mason, Craftsdwarfs, or Carpenters workshop, open
the Add Job menu, and move the cursor within:]], the Add Job menu, and move the cursor within:]],
@ -429,6 +641,8 @@ the Add Job menu, and move the cursor within:]],
{ 0, 1, 2, 3, 4, 5, 6 }, { 0, 1, 2, 3, 4, 5, 6 },
ordinal_names ordinal_names
) )
end
validate_offset('ui_sidebar_menus', is_valid_ui_sidebar_menus, validate_offset('ui_sidebar_menus', is_valid_ui_sidebar_menus,
addr, df.ui_sidebar_menus, 'workshop_job', 'cursor') addr, df.ui_sidebar_menus, 'workshop_job', 'cursor')
end end
@ -455,7 +669,32 @@ local function is_valid_ui_build_selector(ubs)
end end
local function find_ui_build_selector() 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 Searching for ui_build_selector. Please start constructing
a pressure plate, and enable creatures. Then change the min a pressure plate, and enable creatures. Then change the min
weight as requested, remembering that the ui truncates the weight as requested, remembering that the ui truncates the
@ -463,6 +702,8 @@ number, so when it shows "Min (5000df", it means 50000:]],
'int32_t', 'int32_t',
{ 50000, 49000, 48000, 47000, 46000, 45000, 44000 } { 50000, 49000, 48000, 47000, 46000, 45000, 44000 }
) )
end
validate_offset('ui_build_selector', is_valid_ui_build_selector, validate_offset('ui_build_selector', is_valid_ui_build_selector,
addr, df.ui_build_selector, 'plate_info', 'unit_min') addr, df.ui_build_selector, 'plate_info', 'unit_min')
end end
@ -601,13 +842,33 @@ end
-- --
local function find_ui_unit_view_mode() 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 Searching for ui_unit_view_mode. Having selected a unit
with 'v', switch the pages as requested:]], with 'v', switch the pages as requested:]],
'int32_t', 'int32_t', catnames, df.ui_unit_view_mode.T_value
{ 'General', 'Inventory', 'Preferences', 'Wounds' },
df.ui_unit_view_mode.T_value
) )
end
ms.found_offset('ui_unit_view_mode', addr) ms.found_offset('ui_unit_view_mode', addr)
end end
@ -620,7 +881,23 @@ local function look_item_list_count()
end end
local function find_ui_look_cursor() 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' Searching for ui_look_cursor. Please activate the 'k'
mode, find a tile with many items or units on the ground, mode, find a tile with many items or units on the ground,
and select list entries as instructed:]], and select list entries as instructed:]],
@ -628,6 +905,8 @@ and select list entries as instructed:]],
list_index_choices(look_item_list_count), list_index_choices(look_item_list_count),
ordinal_names ordinal_names
) )
end
ms.found_offset('ui_look_cursor', addr) ms.found_offset('ui_look_cursor', addr)
end end
@ -640,7 +919,23 @@ local function building_item_list_count()
end end
local function find_ui_building_item_cursor() 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' Searching for ui_building_item_cursor. Please activate the 't'
mode, find a cluttered workshop, trade depot, or other building mode, find a cluttered workshop, trade depot, or other building
with many contained items, and select as instructed:]], with many contained items, and select as instructed:]],
@ -648,6 +943,8 @@ with many contained items, and select as instructed:]],
list_index_choices(building_item_list_count), list_index_choices(building_item_list_count),
ordinal_names ordinal_names
) )
end
ms.found_offset('ui_building_item_cursor', addr) ms.found_offset('ui_building_item_cursor', addr)
end end
@ -656,7 +953,25 @@ end
-- --
local function find_ui_workshop_in_add() 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' Searching for ui_workshop_in_add. Please activate the 'q'
mode, find a workshop without jobs (or delete jobs), mode, find a workshop without jobs (or delete jobs),
and do as instructed below. and do as instructed below.
@ -667,6 +982,8 @@ NOTE: If not done after first 3-4 steps, resize the game window.]],
{ [1] = 'enter the add job menu', { [1] = 'enter the add job menu',
[0] = 'add job, thus exiting the menu' } [0] = 'add job, thus exiting the menu' }
) )
end
ms.found_offset('ui_workshop_in_add', addr) ms.found_offset('ui_workshop_in_add', addr)
end end
@ -679,13 +996,30 @@ local function workshop_job_list_count()
end end
local function find_ui_workshop_job_cursor() 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' Searching for ui_workshop_job_cursor. Please activate the 'q'
mode, find a workshop with many jobs, and select as instructed:]], mode, find a workshop with many jobs, and select as instructed:]],
'int32_t', 'int32_t',
list_index_choices(workshop_job_list_count), list_index_choices(workshop_job_list_count),
ordinal_names ordinal_names
) )
end
ms.found_offset('ui_workshop_job_cursor', addr) ms.found_offset('ui_workshop_job_cursor', addr)
end end
@ -694,7 +1028,27 @@ end
-- --
local function find_ui_building_in_assign() 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 Searching for ui_building_in_assign. Please activate
the 'q' mode, select a room building (e.g. a bedroom) the 'q' mode, select a room building (e.g. a bedroom)
and do as instructed below. and do as instructed below.
@ -705,6 +1059,8 @@ NOTE: If not done after first 3-4 steps, resize the game window.]],
{ [1] = 'enter the Assign owner menu', { [1] = 'enter the Assign owner menu',
[0] = 'press Esc to exit assign' } [0] = 'press Esc to exit assign' }
) )
end
ms.found_offset('ui_building_in_assign', addr) ms.found_offset('ui_building_in_assign', addr)
end end
@ -713,7 +1069,27 @@ end
-- --
local function find_ui_building_in_resize() 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 Searching for ui_building_in_resize. Please activate
the 'q' mode, select a room building (e.g. a bedroom) the 'q' mode, select a room building (e.g. a bedroom)
and do as instructed below. and do as instructed below.
@ -724,6 +1100,8 @@ NOTE: If not done after first 3-4 steps, resize the game window.]],
{ [1] = 'enter the Resize room mode', { [1] = 'enter the Resize room mode',
[0] = 'press Esc to exit resize' } [0] = 'press Esc to exit resize' }
) )
end
ms.found_offset('ui_building_in_resize', addr) ms.found_offset('ui_building_in_resize', addr)
end end
@ -731,13 +1109,40 @@ end
-- window_x -- 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 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, Searching for window_x. Please exit to main dwarfmode menu,
scroll to the LEFT edge, then do as instructed:]], scroll to the LEFT edge, then do as instructed:]],
'int32_t', 10, 'int32_t', 10,
'Please press Right to scroll right one step.' 'Please press Right to scroll right one step.'
) )
end
ms.found_offset('window_x', addr) ms.found_offset('window_x', addr)
end end
@ -746,12 +1151,28 @@ end
-- --
local function find_window_y() 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, Searching for window_y. Please exit to main dwarfmode menu,
scroll to the TOP edge, then do as instructed:]], scroll to the TOP edge, then do as instructed:]],
'int32_t', 10, 'int32_t', 10,
'Please press Down to scroll down one step.' 'Please press Down to scroll down one step.'
) )
end
ms.found_offset('window_y', addr) ms.found_offset('window_y', addr)
end end
@ -760,7 +1181,21 @@ end
-- --
local function find_window_z() 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, Searching for window_z. Please exit to main dwarfmode menu,
scroll to a Z level near surface, then do as instructed below. scroll to a Z level near surface, then do as instructed below.
@ -768,6 +1203,8 @@ NOTE: If not done after first 3-4 steps, resize the game window.]],
'int32_t', -1, 'int32_t', -1,
"Please press '>' to scroll one Z level down." "Please press '>' to scroll one Z level down."
) )
end
ms.found_offset('window_z', addr) ms.found_offset('window_z', addr)
end end
@ -816,6 +1253,27 @@ function stop_autosave()
end end
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 function find_cur_year_tick()
local zone local zone
if os_type == 'windows' then if os_type == 'windows' then
@ -830,12 +1288,21 @@ local function find_cur_year_tick()
stop_autosave() stop_autosave()
local addr = zone:find_counter([[ local feed = dwarfmode_to_top()
Searching for cur_year_tick. Please exit to main dwarfmode local addr = zone:find_interactive(
menu, then do as instructed below:]], 'Searching for cur_year_tick.',
'int32_t', 1, 'int32_t',
"Please press '.' to step the game one frame." 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) ms.found_offset('cur_year_tick', addr)
end end
@ -843,20 +1310,6 @@ end
-- cur_season_tick -- 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() local function find_cur_season_tick()
if not (is_known 'cur_year_tick') then if not (is_known 'cur_year_tick') then
dfhack.printerr('Cannot search for cur_season_tick - prerequisites missing.') dfhack.printerr('Cannot search for cur_season_tick - prerequisites missing.')
@ -865,13 +1318,14 @@ local function find_cur_season_tick()
stop_autosave() stop_autosave()
local feed = dwarfmode_to_top()
local addr = searcher:find_interactive([[ local addr = searcher:find_interactive([[
Searching for cur_season_tick. Please exit to main dwarfmode Searching for cur_season_tick. Please exit to main dwarfmode
menu, then do as instructed below:]], menu, then do as instructed below:]],
'int32_t', 'int32_t',
function(ccursor) function(ccursor)
if ccursor > 0 then if ccursor > 0 then
if not step_n_frames(10) then if not step_n_frames(10, feed) then
return false return false
end end
end end
@ -893,6 +1347,7 @@ local function find_cur_season()
stop_autosave() stop_autosave()
local feed = dwarfmode_to_top()
local addr = searcher:find_interactive([[ local addr = searcher:find_interactive([[
Searching for cur_season. Please exit to main dwarfmode Searching for cur_season. Please exit to main dwarfmode
menu, then do as instructed below:]], menu, then do as instructed below:]],
@ -902,7 +1357,7 @@ menu, then do as instructed below:]],
local cst = df.global.cur_season_tick local cst = df.global.cur_season_tick
df.global.cur_season_tick = 10079 df.global.cur_season_tick = 10079
df.global.cur_year_tick = df.global.cur_year_tick + (10079-cst)*10 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 return false
end end
end end
@ -963,7 +1418,7 @@ end
-- --
local function find_pause_state() local function find_pause_state()
local zone local zone, addr
if os_type == 'linux' or os_type == 'darwin' then if os_type == 'linux' or os_type == 'darwin' then
zone = zoomed_searcher('ui_look_cursor', 32) zone = zoomed_searcher('ui_look_cursor', 32)
elseif os_type == 'windows' then elseif os_type == 'windows' then
@ -973,13 +1428,33 @@ local function find_pause_state()
stop_autosave() 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:]], Searching for pause_state. Please do as instructed below:]],
'int8_t', 'int8_t',
{ 1, 0 }, { 1, 0 },
{ [1] = 'PAUSE the game', { [1] = 'PAUSE the game',
[0] = 'UNPAUSE the game' } [0] = 'UNPAUSE the game' }
) )
end
ms.found_offset('pause_state', addr) ms.found_offset('pause_state', addr)
end end
@ -989,10 +1464,10 @@ end
print('\nInitial globals (need title screen):\n') print('\nInitial globals (need title screen):\n')
exec_finder(find_gview, 'gview')
exec_finder(find_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' }) exec_finder(find_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' })
exec_finder(find_announcements, 'announcements') exec_finder(find_announcements, 'announcements')
exec_finder(find_d_init, 'd_init') exec_finder(find_d_init, 'd_init')
exec_finder(find_gview, 'gview')
exec_finder(find_enabler, 'enabler') exec_finder(find_enabler, 'enabler')
exec_finder(find_gps, 'gps') exec_finder(find_gps, 'gps')

@ -17,8 +17,8 @@ local brushes = {
} }
local paints = { local paints = {
{ tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'w' }, { tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'D_LOOK_ARENA_WATER' },
{ tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'l' }, { tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'D_LOOK_ARENA_MAGMA' },
{ tag = 'obsidian', caption = 'Obsidian Wall' }, { tag = 'obsidian', caption = 'Obsidian Wall' },
{ tag = 'obsidian_floor', caption = 'Obsidian Floor' }, { tag = 'obsidian_floor', caption = 'Obsidian Floor' },
{ tag = 'riversource', caption = 'River Source' }, { tag = 'riversource', caption = 'River Source' },
@ -64,7 +64,7 @@ function Toggle:render(dc)
if item then if item then
dc:string(item.caption) dc:string(item.caption)
if item.key then if item.key then
dc:string(" ("):string(item.key, COLOR_LIGHTGREEN):string(")") dc:string(" ("):key(item.key):string(")")
end end
else else
dc:string('NONE', COLOR_RED) dc:string('NONE', COLOR_RED)
@ -160,9 +160,9 @@ function LiquidsUI:onRenderBody(dc)
dc:newline():pen(COLOR_GREY) dc:newline():pen(COLOR_GREY)
dc:newline(1):string("b", COLOR_LIGHTGREEN):string(": ") dc:newline(1):key('CUSTOM_B'):string(": ")
self.brush:render(dc) self.brush:render(dc)
dc:newline(1):string("p", COLOR_LIGHTGREEN):string(": ") dc:newline(1):key('CUSTOM_P'):string(": ")
self.paint:render(dc) self.paint:render(dc)
local paint = self.paint:get() local paint = self.paint:get()
@ -170,8 +170,8 @@ function LiquidsUI:onRenderBody(dc)
dc:newline() dc:newline()
if paint.liquid then if paint.liquid then
dc:newline(1):string("Amount: "..self.amount) dc:newline(1):string("Amount: "..self.amount)
dc:advance(1):string("("):string("-+", COLOR_LIGHTGREEN):string(")") dc:advance(1):string("("):key('SECONDSCROLL_UP'):key('SECONDSCROLL_DOWN'):string(")")
dc:newline(3):string("s", COLOR_LIGHTGREEN):string(": ") dc:newline(3):key('CUSTOM_S'):string(": ")
self.set:render(dc) self.set:render(dc)
else else
dc:advance(0,2) dc:advance(0,2)
@ -179,17 +179,17 @@ function LiquidsUI:onRenderBody(dc)
dc:newline() dc:newline()
if paint.flow then if paint.flow then
dc:newline(1):string("f", COLOR_LIGHTGREEN):string(": ") dc:newline(1):key('CUSTOM_F'):string(": ")
self.flow:render(dc) self.flow:render(dc)
dc:newline(1):string("r", COLOR_LIGHTGREEN):string(": ") dc:newline(1):key('CUSTOM_R'):string(": ")
self.permaflow:render(dc) self.permaflow:render(dc)
else else
dc:advance(0,2) dc:advance(0,2)
end end
dc:newline():newline(1):pen(COLOR_WHITE) dc:newline():newline(1):pen(COLOR_WHITE)
dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") dc:key('LEAVESCREEN'):string(": Back, ")
dc:string("Enter", COLOR_LIGHTGREEN):string(": Paint") dc:key('SELECT'):string(": Paint")
end end
function ensure_blocks(cursor, size, cb) function ensure_blocks(cursor, size, cb)

@ -89,8 +89,8 @@ function MechanismList:onRenderBody(dc)
end end
dc:newline():newline(1):pen(COLOR_WHITE) dc:newline():newline(1):pen(COLOR_WHITE)
dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") dc:key('LEAVESCREEN'):string(": Back, ")
dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch") dc:key('SELECT'):string(": Switch")
end end
function MechanismList:changeSelected(delta) function MechanismList:changeSelected(delta)

@ -34,7 +34,8 @@ function PowerMeter:onRenderBody(dc)
dc:string("Excess power range:") 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 ") dc:string(": Min ")
if self.min_power <= 0 then if self.min_power <= 0 then
dc:string("(any)") dc:string("(any)")
@ -42,7 +43,8 @@ function PowerMeter:onRenderBody(dc)
dc:string(''..self.min_power) dc:string(''..self.min_power)
end 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 ") dc:string(": Max ")
if self.max_power < 0 then if self.max_power < 0 then
dc:string("(any)") dc:string("(any)")
@ -51,7 +53,7 @@ function PowerMeter:onRenderBody(dc)
end end
dc:newline():newline(1) dc:newline():newline(1)
dc:string("i",COLOR_LIGHTGREEN):string(": ") dc:key('CUSTOM_I'):string(": ")
if self.invert then if self.invert then
dc:string("Inverted") dc:string("Inverted")
else else

@ -185,10 +185,10 @@ function RoomList:onRenderBody(dc)
end end
dc:newline():newline(1):pen(COLOR_WHITE) 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 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 if sel_item.obj.owner == sel_item.owner then
dc:string(": Unassign") dc:string(": Unassign")
else else

@ -204,14 +204,14 @@ function SiegeEngine:onRenderBody_main(dc)
dc:string("None (default)") dc:string("None (default)")
end end
dc:newline(3):string("r",COLOR_LIGHTGREEN):string(": Rectangle") dc:newline(3):key('CUSTOM_R'):string(": Rectangle")
if last_target_min then if last_target_min then
dc:string(", "):string("p",COLOR_LIGHTGREEN):string(": Paste") dc:string(", "):key('CUSTOM_P'):string(": Paste")
end end
dc:newline(3) dc:newline(3)
if target_min then if target_min then
dc:string("x",COLOR_LIGHTGREEN):string(": Clear, ") dc:key('CUSTOM_X'):string(": Clear, ")
dc:string("z",COLOR_LIGHTGREEN):string(": Zoom") dc:key('CUSTOM_Z'):string(": Zoom")
end end
dc:newline():newline(1) dc:newline():newline(1)
@ -219,7 +219,7 @@ function SiegeEngine:onRenderBody_main(dc)
dc:string("Uses ballista arrows") dc:string("Uses ballista arrows")
else else
local item = plugin.getAmmoItem(self.building) 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 if item_choice_idx[item] then
dc:string(item_choices[item_choice_idx[item]].caption) dc:string(item_choices[item_choice_idx[item]].caption)
else else
@ -228,18 +228,20 @@ function SiegeEngine:onRenderBody_main(dc)
end end
dc:newline():newline(1) 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 links = plugin.getStockpileLinks(self.building)
local bottom = dc.height - 5 local bottom = dc.height - 5
if links then if links then
dc:string("d",COLOR_LIGHTGREEN):string(": Delete, ") dc:key('CUSTOM_D'):string(": Delete, ")
dc:string("o",COLOR_LIGHTGREEN):string(": Zoom"):newline() dc:key('CUSTOM_O'):string(": Zoom"):newline()
self:renderStockpiles(dc, links, bottom-2-dc:localY()) self:renderStockpiles(dc, links, bottom-2-dc:localY())
dc:newline():newline() dc:newline():newline()
end end
local prof = self.building:getWorkshopProfile() or {} 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[prof.min_level or 0].caption):string('-')
dc:string(df.skill_rating.attrs[math.min(LEGENDARY,prof.max_level or 3000)].caption) dc:string(df.skill_rating.attrs[math.min(LEGENDARY,prof.max_level or 3000)].caption)
dc:newline():newline() dc:newline():newline()
@ -357,7 +359,7 @@ function SiegeEngine:onRenderBody_aim(dc)
dc:newline(2):string('ERROR', COLOR_RED) dc:newline(2):string('ERROR', COLOR_RED)
end end
dc:newline():newline(1):string("Enter",COLOR_LIGHTGREEN) dc:newline():newline(1):key('SELECT')
if first then if first then
dc:string(": Finish rectangle") dc:string(": Finish rectangle")
else else
@ -367,7 +369,7 @@ function SiegeEngine:onRenderBody_aim(dc)
local target_min, target_max = plugin.getTargetArea(self.building) local target_min, target_max = plugin.getTargetArea(self.building)
if target_min then 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 end
if first then if first then
@ -412,9 +414,9 @@ function SiegeEngine:onRenderBody_pile(dc)
if plugin.isLinkedToPile(self.building, sel) then if plugin.isLinkedToPile(self.building, sel) then
dc:string("Already taking from here"):newline():newline(2) 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 else
dc:string("Enter",COLOR_LIGHTGREEN):string(": Take from this pile") dc:key('SELECT'):string(": Take from this pile")
end end
elseif sel then elseif sel then
dc:string(utils.getBuildingName(sel), COLOR_DARKGREY) dc:string(utils.getBuildingName(sel), COLOR_DARKGREY)
@ -460,8 +462,8 @@ function SiegeEngine:onRenderBody(dc)
self.mode.render(dc) self.mode.render(dc)
dc:seek(1, math.max(dc:localY(), 21)):pen(COLOR_WHITE) dc:seek(1, math.max(dc:localY(), 21)):pen(COLOR_WHITE)
dc:string("ESC", COLOR_LIGHTGREEN):string(": Back, ") dc:key('LEAVESCREEN'):string(": Back, ")
dc:string("c", COLOR_LIGHTGREEN):string(": Recenter") dc:key('CUSTOM_C'):string(": Recenter")
end end
function SiegeEngine:onInput(keys) function SiegeEngine:onInput(keys)