Merge branch 'master' of https://github.com/angavrilov/dfhack into experimental-dontmerge

develop
Warmist 2012-08-28 20:18:12 +03:00
commit fee3cfda96
46 changed files with 2209 additions and 508 deletions

@ -724,15 +724,20 @@ can be omitted.
Gui module
----------
* ``dfhack.gui.getCurViewscreen()``
* ``dfhack.gui.getCurViewscreen([skip_dismissed])``
Returns the viewscreen that is current in the core.
Returns the topmost viewscreen. If ``skip_dismissed`` is *true*,
ignores screens already marked to be removed.
* ``dfhack.gui.getFocusString(viewscreen)``
Returns a string representation of the current focus position
in the ui. The string has a "screen/foo/bar/baz..." format.
* ``dfhack.gui.getCurFocus([skip_dismissed])``
Returns the focus string of the current viewscreen.
* ``dfhack.gui.getSelectedWorkshopJob([silent])``
When a job is selected in *'q'* mode, returns the job, else
@ -875,6 +880,15 @@ Units module
Retrieves the profession name for the given race/caste using raws.
* ``dfhack.units.getProfessionColor(unit[,ignore_noble])``
Retrieves the color associated with the profession, using noble assignments
or raws. The ``ignore_noble`` boolean disables the use of noble positions.
* ``dfhack.units.getCasteProfessionColor(race,caste,prof_id)``
Retrieves the profession color for the given race/caste using raws.
Items module
------------
@ -1027,6 +1041,11 @@ Burrows module
Buildings module
----------------
* ``dfhack.buildings.setOwner(item,unit)``
Replaces the owner of the building. If unit is *nil*, removes ownership.
Returns *false* in case of error.
* ``dfhack.buildings.getSize(building)``
Returns *width, height, centerx, centery*.
@ -1291,9 +1310,10 @@ Screens are managed with the following functions:
Displays the given screen, possibly placing it below a different one.
The screen must not be already shown. Returns *true* if success.
* ``dfhack.screen.dismiss(screen)``
* ``dfhack.screen.dismiss(screen[,to_first])``
Marks the screen to be removed when the game enters its event loop.
If ``to_first`` is *true*, all screens up to the first one will be deleted.
* ``dfhack.screen.isDismissed(screen)``
@ -1312,10 +1332,22 @@ Supported callbacks and fields are:
Initialized by ``show`` with a reference to the backing viewscreen
object, and removed again when the object is deleted.
* ``function screen:onShow()``
Called by ``dfhack.screen.show`` if successful.
* ``function screen:onDismiss()``
Called by ``dfhack.screen.dismiss`` if successful.
* ``function screen:onDestroy()``
Called from the destructor when the viewscreen is deleted.
* ``function screen:onResize(w, h)``
Called before ``onRender`` or ``onIdle`` when the window size has changed.
* ``function screen:onRender()``
Called when the viewscreen should paint itself. This is the only context

@ -987,13 +987,17 @@ can be omitted.</p>
<div class="section" id="gui-module">
<h3><a class="toc-backref" href="#id18">Gui module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.gui.getCurViewscreen()</tt></p>
<p>Returns the viewscreen that is current in the core.</p>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.getCurViewscreen([skip_dismissed])</span></tt></p>
<p>Returns the topmost viewscreen. If <tt class="docutils literal">skip_dismissed</tt> is <em>true</em>,
ignores screens already marked to be removed.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.gui.getFocusString(viewscreen)</tt></p>
<p>Returns a string representation of the current focus position
in the ui. The string has a &quot;screen/foo/bar/baz...&quot; format.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.getCurFocus([skip_dismissed])</span></tt></p>
<p>Returns the focus string of the current viewscreen.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.getSelectedWorkshopJob([silent])</span></tt></p>
<p>When a job is selected in <em>'q'</em> mode, returns the job, else
prints error unless silent and returns <em>nil</em>.</p>
@ -1109,6 +1113,13 @@ or raws. The <tt class="docutils literal">ignore_noble</tt> boolean disables the
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.units.getCasteProfessionName(race,caste,prof_id[,plural])</span></tt></p>
<p>Retrieves the profession name for the given race/caste using raws.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.units.getProfessionColor(unit[,ignore_noble])</span></tt></p>
<p>Retrieves the color associated with the profession, using noble assignments
or raws. The <tt class="docutils literal">ignore_noble</tt> boolean disables the use of noble positions.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getCasteProfessionColor(race,caste,prof_id)</tt></p>
<p>Retrieves the profession color for the given race/caste using raws.</p>
</li>
</ul>
</div>
<div class="section" id="items-module">
@ -1233,6 +1244,10 @@ burrows, or the presence of invaders.</p>
<div class="section" id="buildings-module">
<h3><a class="toc-backref" href="#id24">Buildings module</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.setOwner(item,unit)</tt></p>
<p>Replaces the owner of the building. If unit is <em>nil</em>, removes ownership.
Returns <em>false</em> in case of error.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.getSize(building)</tt></p>
<p>Returns <em>width, height, centerx, centery</em>.</p>
</li>
@ -1465,8 +1480,9 @@ interface screens added by dfhack should bear the &quot;DFHack&quot; signature.<
<p>Displays the given screen, possibly placing it below a different one.
The screen must not be already shown. Returns <em>true</em> if success.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.screen.dismiss(screen)</tt></p>
<p>Marks the screen to be removed when the game enters its event loop.</p>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.screen.dismiss(screen[,to_first])</span></tt></p>
<p>Marks the screen to be removed when the game enters its event loop.
If <tt class="docutils literal">to_first</tt> is <em>true</em>, all screens up to the first one will be deleted.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.screen.isDismissed(screen)</tt></p>
<p>Checks if the screen is already marked for removal.</p>
@ -1482,9 +1498,18 @@ that delegates all processing to methods stored in that table.</p>
<p>Initialized by <tt class="docutils literal">show</tt> with a reference to the backing viewscreen
object, and removed again when the object is deleted.</p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onShow()</tt></p>
<p>Called by <tt class="docutils literal">dfhack.screen.show</tt> if successful.</p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onDismiss()</tt></p>
<p>Called by <tt class="docutils literal">dfhack.screen.dismiss</tt> if successful.</p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onDestroy()</tt></p>
<p>Called from the destructor when the viewscreen is deleted.</p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onResize(w, h)</tt></p>
<p>Called before <tt class="docutils literal">onRender</tt> or <tt class="docutils literal">onIdle</tt> when the window size has changed.</p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onRender()</tt></p>
<p>Called when the viewscreen should paint itself. This is the only context
where the above painting functions work correctly.</p>

@ -45,3 +45,16 @@ keybinding add Shift-G "job-material GLASS_GREEN"
# browse linked mechanisms
keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms
# browse rooms of same owner
keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work
# interface for the liquids plugin
keybinding add Alt-L@dwarfmode/LookAround gui/liquids
###################
# UI logic tweaks #
###################
# stabilize the cursor of dwarfmode when switching menus
tweak stable-cursor

@ -1260,7 +1260,7 @@ bool Core::ncurses_wgetch(int in, int & out)
// FIXME: copypasta, push into a method!
if(df::global::ui && df::global::gview)
{
df::viewscreen * ws = Gui::GetCurrentScreen();
df::viewscreen * ws = Gui::getCurViewscreen();
if (strict_virtual_cast<df::viewscreen_dwarfmodest>(ws) &&
df::global::ui->main.mode != ui_sidebar_mode::Hotkeys &&
df::global::ui->main.hotkeys[idx].cmd == df::ui_hotkey::T_cmd::None)

@ -218,8 +218,10 @@ virtual_identity::virtual_identity(size_t size, TAllocateFn alloc,
virtual_identity::~virtual_identity()
{
// Remove interpose entries, so that they don't try accessing this object later
for (int i = interpose_list.size()-1; i >= 0; i--)
interpose_list[i]->remove();
for (auto it = interpose_list.begin(); it != interpose_list.end(); ++it)
if (it->second)
it->second->on_host_delete(this);
interpose_list.clear();
}
/* Vtable name to identity lookup. */

@ -77,6 +77,7 @@ distribution.
#include "df/job_material_category.h"
#include "df/burrow.h"
#include "df/building_civzonest.h"
#include "df/region_map_entry.h"
#include <lua.h>
#include <lauxlib.h>
@ -749,6 +750,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = {
static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, getCurViewscreen),
WRAPM(Gui, getFocusString),
WRAPM(Gui, getCurFocus),
WRAPM(Gui, getSelectedWorkshopJob),
WRAPM(Gui, getSelectedJob),
WRAPM(Gui, getSelectedUnit),
@ -814,6 +816,8 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getAge),
WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName),
WRAPM(Units, getProfessionColor),
WRAPM(Units, getCasteProfessionColor),
{ NULL, NULL }
};
@ -928,8 +932,7 @@ static int maps_getRegionBiome(lua_State *L)
static int maps_getTileBiomeRgn(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos));
return 1;
return Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos));
}
static const luaL_Reg dfhack_maps_funcs[] = {
@ -984,6 +987,7 @@ static bool buildings_containsTile(df::building *bld, int x, int y, bool room) {
}
static const LuaWrapper::FunctionReg dfhack_buildings_module[] = {
WRAPM(Buildings, setOwner),
WRAPM(Buildings, allocInstance),
WRAPM(Buildings, checkFreeTiles),
WRAPM(Buildings, countExtentTiles),

@ -185,15 +185,18 @@ Plugin::~Plugin()
}
bool Plugin::load(color_ostream &con)
{
{
RefAutolock lock(access);
if(state == PS_BROKEN)
if(state == PS_LOADED)
{
return false;
return true;
}
else if(state == PS_LOADED)
else if(state != PS_UNLOADED)
{
return true;
return false;
}
state = PS_LOADING;
}
// enter suspend
CoreSuspender suspend;
@ -202,6 +205,7 @@ bool Plugin::load(color_ostream &con)
if(!plug)
{
con.printerr("Can't load plugin %s\n", filename.c_str());
RefAutolock lock(access);
state = PS_BROKEN;
return false;
}
@ -211,6 +215,7 @@ bool Plugin::load(color_ostream &con)
{
con.printerr("Plugin %s has no name or version.\n", filename.c_str());
ClosePlugin(plug);
RefAutolock lock(access);
state = PS_BROKEN;
return false;
}
@ -219,9 +224,11 @@ bool Plugin::load(color_ostream &con)
con.printerr("Plugin %s was not built for this version of DFHack.\n"
"Plugin: %s, DFHack: %s\n", *plug_name, *plug_version, DFHACK_VERSION);
ClosePlugin(plug);
RefAutolock lock(access);
state = PS_BROKEN;
return false;
}
RefAutolock lock(access);
plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init");
if(!plugin_init)
{
@ -273,8 +280,11 @@ bool Plugin::unload(color_ostream &con)
}
// wait for all calls to finish
access->wait();
state = PS_UNLOADING;
access->unlock();
// enter suspend
CoreSuspender suspend;
access->lock();
// notify plugin about shutdown, if it has a shutdown function
command_result cr = CR_OK;
if(plugin_shutdown)

@ -154,6 +154,73 @@ bool virtual_identity::set_vmethod_ptr(int idx, void *ptr)
return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*));
}
/*
VMethod interposing data structures.
In order to properly support adding and removing hooks,
it is necessary to track them. This is what this class
is for. The task is further complicated by propagating
hooks to child classes that use exactly the same original
vmethod implementation.
Every applied link contains in the saved_chain field a
pointer to the next vmethod body that should be called
by the hook the link represents. This is the actual
control flow structure that needs to be maintained.
There also are connections between link objects themselves,
which constitute the bookkeeping for doing that. Finally,
every link is associated with a fixed virtual_identity host,
which represents the point in the class hierarchy where
the hook is applied.
When there are no subclasses (i.e. only one host), the
structures look like this:
+--------------+ +------------+
| link1 |-next------->| link2 |-next=NULL
|s_c: original |<-------prev-|s_c: $link1 |<--+
+--------------+ +------------+ |
|
host->interpose_list[vmethod_idx] ------+
vtable: $link2
The original vtable entry is stored in the saved_chain of the
first link. The interpose_list map points to the last one.
The hooks are called in order: link2 -> link1 -> original.
When there are subclasses that use the same vmethod, but don't
hook it, the topmost link gets a set of the child_hosts, and
the hosts have the link added to their interpose_list:
+--------------+ +----------------+
| link0 @host0 |<--+-interpose_list-| host1 |
| |-child_hosts-+----->| vtable: $link |
+--------------+ | | +----------------+
| |
| | +----------------+
+-interpose_list-| host2 |
+----->| vtable: $link |
+----------------+
When a child defines its own hook, the child_hosts link is
severed and replaced with a child_next pointer to the new
hook. The hook still points back the chain with prev.
All child links to subclasses of host2 are migrated from
link1 to link2.
+--------------+-next=NULL +--------------+-next=NULL
| link1 @host1 |-child_next------->| link2 @host2 |-child_*--->subclasses
| |<-------------prev-|s_c: $link1 |
+--------------+<-------+ +--------------+<-------+
| |
+--------------+ | +--------------+ |
| host1 |-i_list-+ | host2 |-i_list-+
|vtable: $link1| |vtable: $link2|
+--------------+ +--------------+
*/
void VMethodInterposeLinkBase::set_chain(void *chain)
{
saved_chain = chain;
@ -162,7 +229,7 @@ void VMethodInterposeLinkBase::set_chain(void *chain)
VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr)
: host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method), chain_mptr(chain_mptr),
saved_chain(NULL), next(NULL), prev(NULL)
applied(false), saved_chain(NULL), next(NULL), prev(NULL)
{
if (vmethod_idx < 0 || interpose_method == NULL)
{
@ -179,6 +246,75 @@ VMethodInterposeLinkBase::~VMethodInterposeLinkBase()
remove();
}
VMethodInterposeLinkBase *VMethodInterposeLinkBase::get_first_interpose(virtual_identity *id)
{
auto item = id->interpose_list[vmethod_idx];
if (!item)
return NULL;
if (item->host != id)
return NULL;
while (item->prev && item->prev->host == id)
item = item->prev;
return item;
}
void VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr)
{
auto &children = cur->getChildren();
for (size_t i = 0; i < children.size(); i++)
{
auto child = static_cast<virtual_identity*>(children[i]);
auto base = get_first_interpose(child);
if (base)
{
assert(base->prev == NULL);
if (base->saved_chain != vmptr)
continue;
child_next.insert(base);
}
else
{
void *cptr = child->get_vmethod_ptr(vmethod_idx);
if (cptr != vmptr)
continue;
child_hosts.insert(child);
find_child_hosts(child, vmptr);
}
}
}
void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from)
{
if (from == host)
{
// When in own host, fully delete
remove();
}
else
{
// Otherwise, drop the link to that child:
assert(child_hosts.count(from) != 0 &&
from->interpose_list[vmethod_idx] == this);
// Find and restore the original vmethod ptr
auto last = this;
while (last->prev) last = last->prev;
from->set_vmethod_ptr(vmethod_idx, last->saved_chain);
// Unlink the chains
child_hosts.erase(from);
from->interpose_list[vmethod_idx] = NULL;
}
}
bool VMethodInterposeLinkBase::apply()
{
if (is_applied())
@ -188,33 +324,73 @@ bool VMethodInterposeLinkBase::apply()
// Retrieve the current vtable entry
void *old_ptr = host->get_vmethod_ptr(vmethod_idx);
assert(old_ptr != NULL);
// Check if there are other interpose entries for the same slot
VMethodInterposeLinkBase *old_link = NULL;
for (int i = host->interpose_list.size()-1; i >= 0; i--)
{
if (host->interpose_list[i]->vmethod_idx != vmethod_idx)
continue;
VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx];
old_link = host->interpose_list[i];
assert(old_link->next == NULL && old_ptr == old_link->interpose_method);
break;
}
assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr));
// Apply the new method ptr
set_chain(old_ptr);
if (!host->set_vmethod_ptr(vmethod_idx, interpose_method))
{
set_chain(NULL);
return false;
}
set_chain(old_ptr);
host->interpose_list.push_back(this);
// Push the current link into the home host
applied = true;
host->interpose_list[vmethod_idx] = this;
prev = old_link;
// Link into the chain if any
if (old_link)
child_hosts.clear();
child_next.clear();
if (old_link && old_link->host == host)
{
// If the old link is home, just push into the plain chain
assert(old_link->next == NULL);
old_link->next = this;
prev = old_link;
// Child links belong to the topmost local entry
child_hosts.swap(old_link->child_hosts);
child_next.swap(old_link->child_next);
}
else
{
// If creating a new local chain, find children with same vmethod
find_child_hosts(host, old_ptr);
if (old_link)
{
// Enter the child chain set
assert(old_link->child_hosts.count(host));
old_link->child_hosts.erase(host);
old_link->child_next.insert(this);
// Subtract our own children from the parent's sets
for (auto it = child_next.begin(); it != child_next.end(); ++it)
old_link->child_next.erase(*it);
for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
old_link->child_hosts.erase(*it);
}
}
// Chain subclass hooks
for (auto it = child_next.begin(); it != child_next.end(); ++it)
{
auto nlink = *it;
assert(nlink->saved_chain == old_ptr && nlink->prev == old_link);
nlink->set_chain(interpose_method);
nlink->prev = this;
}
// Chain passive subclass hosts
for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
{
auto nhost = *it;
assert(nhost->interpose_list[vmethod_idx] == old_link);
nhost->set_vmethod_ptr(vmethod_idx, interpose_method);
nhost->interpose_list[vmethod_idx] = this;
}
return true;
@ -225,25 +401,57 @@ void VMethodInterposeLinkBase::remove()
if (!is_applied())
return;
// Remove from the list in the identity
for (int i = host->interpose_list.size()-1; i >= 0; i--)
if (host->interpose_list[i] == this)
vector_erase_at(host->interpose_list, i);
// Remove from the chain
// Remove the link from prev to this
if (prev)
{
if (prev->host == host)
prev->next = next;
else
{
prev->child_next.erase(this);
if (next)
prev->child_next.insert(next);
}
}
if (next)
{
next->set_chain(saved_chain);
next->prev = prev;
assert(child_next.empty() && child_hosts.empty());
}
else
{
// Remove from the list in the identity and vtable
host->interpose_list[vmethod_idx] = prev;
host->set_vmethod_ptr(vmethod_idx, saved_chain);
for (auto it = child_next.begin(); it != child_next.end(); ++it)
{
auto nlink = *it;
assert(nlink->saved_chain == interpose_method && nlink->prev == this);
nlink->set_chain(saved_chain);
nlink->prev = prev;
if (prev)
prev->child_next.insert(nlink);
}
for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
{
auto nhost = *it;
assert(nhost->interpose_list[vmethod_idx] == this);
nhost->interpose_list[vmethod_idx] = prev;
nhost->set_vmethod_ptr(vmethod_idx, saved_chain);
if (prev)
prev->child_hosts.insert(nhost);
}
}
applied = false;
prev = next = NULL;
child_next.clear();
child_hosts.clear();
set_chain(NULL);
}

@ -303,7 +303,7 @@ namespace DFHack
void *vtable_ptr;
friend class VMethodInterposeLinkBase;
std::vector<VMethodInterposeLinkBase*> interpose_list;
std::map<int,VMethodInterposeLinkBase*> interpose_list;
protected:
virtual void doInit(Core *core);

@ -128,7 +128,9 @@ namespace DFHack
{
PS_UNLOADED,
PS_LOADED,
PS_BROKEN
PS_BROKEN,
PS_LOADING,
PS_UNLOADING
};
friend class PluginManager;
friend class RPCService;

@ -134,21 +134,31 @@ namespace DFHack
1) Allow multiple hooks into the same vmethod
2) Auto-remove hooks when a plugin is unloaded.
*/
friend class virtual_identity;
virtual_identity *host; // Class with the vtable
int vmethod_idx;
void *interpose_method; // Pointer to the code of the interposing method
void *chain_mptr; // Pointer to the chain field below
bool applied;
void *saved_chain; // Previous pointer to the code
VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method
// inherited vtable members
std::set<virtual_identity*> child_hosts;
std::set<VMethodInterposeLinkBase*> child_next;
void set_chain(void *chain);
void on_host_delete(virtual_identity *host);
VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id);
void find_child_hosts(virtual_identity *cur, void *vmptr);
public:
VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr);
~VMethodInterposeLinkBase();
bool is_applied() { return saved_chain != NULL; }
bool is_applied() { return applied; }
bool apply();
void remove();
};

@ -92,6 +92,11 @@ DFHACK_EXPORT bool Read (const uint32_t index, t_building & building);
*/
DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map <uint32_t, std::string> & btypes);
/**
* Sets the owner unit for the building.
*/
DFHACK_EXPORT bool setOwner(df::building *building, df::unit *owner);
/**
* Find the building located at the specified tile.
* Does not work on civzones.

@ -55,8 +55,6 @@ namespace DFHack
*/
namespace Gui
{
inline df::viewscreen *getCurViewscreen() { return Core::getTopViewscreen(); }
DFHACK_EXPORT std::string getFocusString(df::viewscreen *top);
// Full-screen item details view
@ -99,6 +97,9 @@ namespace DFHack
/*
* Cursor and window coords
*/
DFHACK_EXPORT df::coord getViewportPos();
DFHACK_EXPORT df::coord getCursorPos();
DFHACK_EXPORT bool getViewCoords (int32_t &x, int32_t &y, int32_t &z);
DFHACK_EXPORT bool setViewCoords (const int32_t x, const int32_t y, const int32_t z);
@ -113,7 +114,11 @@ namespace DFHack
* Gui screens
*/
/// Get the current top-level view-screen
DFHACK_EXPORT df::viewscreen * GetCurrentScreen();
DFHACK_EXPORT df::viewscreen *getCurViewscreen(bool skip_dismissed = false);
inline std::string getCurFocus(bool skip_dismissed = false) {
return getFocusString(getCurViewscreen(skip_dismissed));
}
/// get the size of the window buffer
DFHACK_EXPORT bool getWindowSize(int32_t & width, int32_t & height);

@ -258,7 +258,7 @@ inline df::tile_occupancy *getTileOccupancy(df::coord pos) {
/**
* Returns biome info about the specified world region.
*/
DFHACK_EXPORT df::world_data::T_region_map *getRegionBiome(df::coord2d rgn_pos);
DFHACK_EXPORT df::region_map_entry *getRegionBiome(df::coord2d rgn_pos);
/**
* Returns biome world region coordinates for the given tile within given block.

@ -112,7 +112,7 @@ namespace DFHack
// Push and remove viewscreens
DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL);
DFHACK_EXPORT void dismiss(df::viewscreen *screen);
DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false);
DFHACK_EXPORT bool isDismissed(df::viewscreen *screen);
}
@ -136,7 +136,10 @@ namespace DFHack
virtual bool key_conflict(df::interface_key key);
virtual bool is_lua_screen() { return false; }
virtual std::string getFocusString() = 0;
virtual void onShow() {};
virtual void onDismiss() {};
};
class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen {
@ -166,5 +169,8 @@ namespace DFHack
virtual void help();
virtual void resize(int w, int h);
virtual void feed(std::set<df::interface_key> *keys);
virtual void onShow();
virtual void onDismiss();
};
}

@ -104,11 +104,21 @@ end
-- Trivial classes
function rawset_default(target,source)
for k,v in pairs(source) do
if rawget(target,k) == nil then
rawset(target,k,v)
end
end
end
function defclass(class,parent)
class = class or {}
rawset(class, '__index', rawget(class, '__index') or class)
rawset_default(class, { __index = class })
if parent then
setmetatable(class, parent)
else
rawset_default(class, { init_fields = rawset_default })
end
return class
end
@ -153,14 +163,6 @@ function xyz2pos(x,y,z)
end
end
function rawset_default(target,source)
for k,v in pairs(source) do
if rawget(target,k) == nil then
rawset(target,k,v)
end
end
end
function safe_index(obj,idx,...)
if obj == nil or idx == nil then
return nil
@ -202,12 +204,6 @@ function dfhack.buildings.getSize(bld)
return bld.x2+1-x, bld.y2+1-y, bld.centerx-x, bld.centery-y
end
dfhack.screen.__index = dfhack.screen
function dfhack.screen:__tostring()
return "<lua viewscreen: "..tostring(self._native)..">"
end
-- Interactive
local print_banner = true

@ -4,6 +4,8 @@ local _ENV = mkmodule('gui')
local dscreen = dfhack.screen
USE_GRAPHICS = dscreen.inGraphicsMode()
CLEAR_PEN = {ch=32,fg=0,bg=0}
function simulateInput(screen,...)
@ -16,7 +18,7 @@ function simulateInput(screen,...)
error('Invalid keycode: '..arg)
end
end
if type(arg) == 'number' then
if type(kv) == 'number' then
keys[#keys+1] = kv
end
end
@ -52,6 +54,9 @@ function inset(rect,dx1,dy1,dx2,dy2)
rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1)
)
end
function is_in_rect(rect,x,y)
return x and y and x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2
end
local function to_pen(default, pen, bg, bold)
if pen == nil then
@ -201,10 +206,17 @@ end
-- Base screen object --
------------------------
Screen = defclass(Screen, dfhack.screen)
Screen = defclass(Screen)
Screen.text_input_mode = false
function Screen:init()
self:updateLayout()
return self
end
Screen.isDismissed = dscreen.isDismissed
function Screen:isShown()
return self._native ~= nil
end
@ -213,6 +225,18 @@ function Screen:isActive()
return self:isShown() and not self:isDismissed()
end
function Screen:invalidate()
dscreen.invalidate()
end
function Screen:getWindowSize()
return dscreen.getWindowSize()
end
function Screen:getMousePos()
return dscreen.getMousePos()
end
function Screen:renderParent()
if self._native and self._native.parent then
self._native.parent:render()
@ -232,9 +256,7 @@ function Screen:show(below)
error("This screen is already on display")
end
self:onAboutToShow(below)
if dscreen.show(self, below) then
self:onShown()
else
if not dscreen.show(self, below) then
error('Could not show screen')
end
end
@ -242,17 +264,27 @@ end
function Screen:onAboutToShow()
end
function Screen:onShown()
function Screen:onShow()
self:updateLayout()
end
function Screen:dismiss()
if self._native and not dscreen.isDismissed(self) then
if self._native then
dscreen.dismiss(self)
self:onDismissed()
end
end
function Screen:onDismissed()
function Screen:onDismiss()
end
function Screen:onDestroy()
end
function Screen:onResize(w,h)
self:updateLayout()
end
function Screen:updateLayout()
end
------------------------
@ -321,7 +353,8 @@ local function hint_coord(gap,hint)
end
end
function FramedScreen:updateFrameSize(sw,sh)
function FramedScreen:updateFrameSize()
local sw, sh = dscreen.getWindowSize()
local iw, ih = sw-2, sh-2
local width = math.min(self.frame_width or iw, iw)
local height = math.min(self.frame_height or ih, ih)
@ -331,8 +364,21 @@ function FramedScreen:updateFrameSize(sw,sh)
self.frame_opaque = (gw == 0 and gh == 0)
end
function FramedScreen:onResize(w,h)
self:updateFrameSize(w,h)
function FramedScreen:updateLayout()
self:updateFrameSize()
end
function FramedScreen:getWindowSize()
local rect = self.frame_rect
return rect.width, rect.height
end
function FramedScreen:getMousePos()
local rect = self.frame_rect
local x,y = dscreen.getMousePos()
if is_in_rect(rect,x,y) then
return x-rect.x1, y-rect.y1
end
end
function FramedScreen:onRender()
@ -351,4 +397,7 @@ function FramedScreen:onRender()
self:onRenderBody(Painter.new(rect))
end
function FramedScreen:onRenderBody(dc)
end
return _ENV

@ -3,7 +3,12 @@
local _ENV = mkmodule('gui.dwarfmode')
local gui = require('gui')
local utils = require('utils')
local dscreen = dfhack.screen
local g_cursor = df.global.cursor
local g_sel_rect = df.global.selection_rect
local world_map = df.global.world.map
AREA_MAP_WIDTH = 23
@ -41,8 +46,8 @@ function getPanelLayout()
end
function getCursorPos()
if df.global.cursor.x ~= -30000 then
return copyall(df.global.cursor)
if g_cursor ~= -30000 then
return copyall(g_cursor)
end
end
@ -54,6 +59,51 @@ function clearCursorPos()
df.global.cursor = xyz2pos(nil)
end
function getSelection()
local p1, p2
if g_sel_rect.start_x ~= -30000 then
p1 = xyz2pos(g_sel_rect.start_x, g_sel_rect.start_y, g_sel_rect.start_z)
end
if g_sel_rect.end_x ~= -30000 then
p2 = xyz2pos(g_sel_rect.end_x, g_sel_rect.end_y, g_sel_rect.end_z)
end
return p1, p2
end
function setSelectionStart(pos)
g_sel_rect.start_x = pos.x
g_sel_rect.start_y = pos.y
g_sel_rect.start_z = pos.z
end
function setSelectionEnd(pos)
g_sel_rect.end_x = pos.x
g_sel_rect.end_y = pos.y
g_sel_rect.end_z = pos.z
end
function clearSelection()
g_sel_rect.start_x = -30000
g_sel_rect.start_y = -30000
g_sel_rect.start_z = -30000
g_sel_rect.end_x = -30000
g_sel_rect.end_y = -30000
g_sel_rect.end_z = -30000
end
function getSelectionRange(p1, p2)
local r1 = xyz2pos(
math.min(p1.x, p2.x), math.min(p1.y, p2.y), math.min(p1.z, p2.z)
)
local r2 = xyz2pos(
math.max(p1.x, p2.x), math.max(p1.y, p2.y), math.max(p1.z, p2.z)
)
local sz = xyz2pos(
r2.x - r1.x + 1, r2.y - r1.y + 1, r2.z - r1.z + 1
)
return r1, sz, r2
end
Viewport = defclass(Viewport)
function Viewport.make(map,x,y,z)
@ -179,14 +229,6 @@ function DwarfOverlay:updateLayout()
self.df_layout = getPanelLayout()
end
function DwarfOverlay:onShown()
self:updateLayout()
end
function DwarfOverlay:onResize(w,h)
self:updateLayout()
end
function DwarfOverlay:getViewport(old_vp)
if old_vp then
return old_vp:resize(self.df_layout)
@ -195,6 +237,18 @@ function DwarfOverlay:getViewport(old_vp)
end
end
function DwarfOverlay:moveCursorTo(cursor,viewport)
setCursorPos(cursor)
self:getViewport(viewport):reveal(cursor, 5, 0, 10):set()
end
function DwarfOverlay:selectBuilding(building,cursor,viewport)
cursor = cursor or utils.getBuildingCenter(building)
df.global.world.selected_building = building
self:moveCursorTo(cursor, viewport)
end
function DwarfOverlay:propagateMoveKeys(keys)
for code,_ in pairs(MOVEMENT_KEYS) do
if keys[code] then
@ -238,6 +292,14 @@ end
MenuOverlay = defclass(MenuOverlay, DwarfOverlay)
function MenuOverlay:updateLayout()
DwarfOverlay.updateLayout(self)
self.frame_rect = self.df_layout.menu
end
MenuOverlay.getWindowSize = gui.FramedScreen.getWindowSize
MenuOverlay.getMousePos = gui.FramedScreen.getMousePos
function MenuOverlay:onAboutToShow(below)
DwarfOverlay.onAboutToShow(self,below)
@ -249,7 +311,6 @@ end
function MenuOverlay:onRender()
self:renderParent()
self:updateLayout()
local menu = self.df_layout.menu
if menu then

@ -373,6 +373,14 @@ function call_with_string(obj,methodname,...)
)
end
function getBuildingName(building)
return call_with_string(building, 'getName')
end
function getBuildingCenter(building)
return xyz2pos(building.centerx, building.centery, building.z)
end
-- Ask a yes-no question
function prompt_yes_no(msg,default)
local prompt = msg

@ -49,6 +49,7 @@ using namespace DFHack;
#include "df/ui_look_list.h"
#include "df/d_init.h"
#include "df/item.h"
#include "df/unit.h"
#include "df/job.h"
#include "df/job_item.h"
#include "df/general_ref_building_holderst.h"
@ -177,6 +178,44 @@ bool Buildings::ReadCustomWorkshopTypes(map <uint32_t, string> & btypes)
return true;
}
bool Buildings::setOwner(df::building *bld, df::unit *unit)
{
CHECK_NULL_POINTER(bld);
if (!bld->is_room)
return false;
if (bld->owner == unit)
return true;
if (bld->owner)
{
auto &blist = bld->owner->owned_buildings;
vector_erase_at(blist, linear_index(blist, bld));
if (auto spouse = df::unit::find(bld->owner->relations.spouse_id))
{
auto &blist = spouse->owned_buildings;
vector_erase_at(blist, linear_index(blist, bld));
}
}
bld->owner = unit;
if (unit)
{
unit->owned_buildings.push_back(bld);
if (auto spouse = df::unit::find(unit->relations.spouse_id))
{
auto &blist = spouse->owned_buildings;
if (bld->canUseSpouseRoom() && linear_index(blist, bld) < 0)
blist.push_back(bld);
}
}
return true;
}
df::building *Buildings::findAtTile(df::coord pos)
{
auto occ = Maps::getTileOccupancy(pos);

@ -983,17 +983,36 @@ void Gui::showPopupAnnouncement(std::string message, int color, bool bright)
world->status.popups.push_back(popup);
}
df::viewscreen * Gui::GetCurrentScreen()
df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed)
{
df::viewscreen * ws = &gview->view;
while(ws)
{
if(ws->child)
while (ws && ws->child)
ws = ws->child;
else
if (skip_dismissed)
{
while (ws && Screen::isDismissed(ws) && ws->parent)
ws = ws->parent;
}
return ws;
}
return 0;
df::coord Gui::getViewportPos()
{
if (!df::global::window_x || !df::global::window_y || !df::global::window_z)
return df::coord(0,0,0);
return df::coord(*df::global::window_x, *df::global::window_y, *df::global::window_z);
}
df::coord Gui::getCursorPos()
{
using df::global::cursor;
if (!cursor)
return df::coord();
return df::coord(cursor->x, cursor->y, cursor->z);
}
bool Gui::getViewCoords (int32_t &x, int32_t &y, int32_t &z)

@ -57,6 +57,7 @@ using namespace std;
#include "df/builtin_mats.h"
#include "df/block_square_event_grassst.h"
#include "df/z_level_flags.h"
#include "df/region_map_entry.h"
using namespace DFHack;
using namespace df::enums;
@ -166,7 +167,7 @@ df::tile_occupancy *Maps::getTileOccupancy(int32_t x, int32_t y, int32_t z)
return block ? &block->occupancy[x&15][y&15] : NULL;
}
df::world_data::T_region_map *Maps::getRegionBiome(df::coord2d rgn_pos)
df::region_map_entry *Maps::getRegionBiome(df::coord2d rgn_pos)
{
auto data = world->world_data;
if (!data)

@ -235,14 +235,26 @@ bool Screen::show(df::viewscreen *screen, df::viewscreen *before)
if (screen->child)
screen->child->parent = screen;
if (dfhack_viewscreen::is_instance(screen))
static_cast<dfhack_viewscreen*>(screen)->onShow();
return true;
}
void Screen::dismiss(df::viewscreen *screen)
void Screen::dismiss(df::viewscreen *screen, bool to_first)
{
CHECK_NULL_POINTER(screen);
if (screen->breakdown_level != interface_breakdown_types::NONE)
return;
if (to_first)
screen->breakdown_level = interface_breakdown_types::TOFIRST;
else
screen->breakdown_level = interface_breakdown_types::STOPSCREEN;
if (dfhack_viewscreen::is_instance(screen))
static_cast<dfhack_viewscreen*>(screen)->onDismiss();
}
bool Screen::isDismissed(df::viewscreen *screen)
@ -261,6 +273,8 @@ static std::set<df::viewscreen*> dfhack_screens;
dfhack_viewscreen::dfhack_viewscreen() : text_input_mode(false)
{
dfhack_screens.insert(this);
last_size = Screen::getWindowSize();
}
dfhack_viewscreen::~dfhack_viewscreen()
@ -576,3 +590,15 @@ void dfhack_lua_viewscreen::feed(std::set<df::interface_key> *keys)
lua_pushlightuserdata(Lua::Core::State, keys);
safe_call_lua(do_input, 1, 0);
}
void dfhack_lua_viewscreen::onShow()
{
lua_pushstring(Lua::Core::State, "onShow");
safe_call_lua(do_notify, 1, 0);
}
void dfhack_lua_viewscreen::onDismiss()
{
lua_pushstring(Lua::Core::State, "onDismiss");
safe_call_lua(do_notify, 1, 0);
}

@ -1 +1 @@
Subproject commit 1eeaa08360c39a9a2d811544c2443309adc1a8f1
Subproject commit 328a8dbdc7d9e1e838798abf79861cc18a387e3f

@ -81,7 +81,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(weather weather.cpp)
DFHACK_PLUGIN(colonies colonies.cpp)
DFHACK_PLUGIN(mode mode.cpp)
DFHACK_PLUGIN(liquids liquids.cpp Brushes.h)
DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua)
DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h)
DFHACK_PLUGIN(tubefill tubefill.cpp)
DFHACK_PLUGIN(autodump autodump.cpp)

@ -19,6 +19,7 @@
#include "df/world_data.h"
#include "df/world_geo_biome.h"
#include "df/world_geo_layer.h"
#include "df/region_map_entry.h"
using namespace DFHack;
using namespace df::enums;

@ -18,3 +18,6 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp)
DFHACK_PLUGIN(rprobe rprobe.cpp)
DFHACK_PLUGIN(nestboxes nestboxes.cpp)
DFHACK_PLUGIN(vshook vshook.cpp)
IF(UNIX)
DFHACK_PLUGIN(ref-index ref-index.cpp)
ENDIF()

@ -0,0 +1,149 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <modules/Gui.h>
#include <modules/Screen.h>
#include <vector>
#include <cstdio>
#include <stack>
#include <string>
#include <cmath>
#include <VTableInterpose.h>
#include "df/item.h"
#include "df/unit.h"
#include "df/world.h"
#include "df/general_ref_item.h"
#include "df/general_ref_unit.h"
using std::vector;
using std::string;
using std::stack;
using namespace DFHack;
using df::global::gps;
DFHACK_PLUGIN("ref-index");
#define global_id id
template<class T>
T get_from_global_id_vector(int32_t id, const std::vector<T> &vect, int32_t *cache)
{
size_t size = vect.size();
int32_t start=0;
int32_t end=(int32_t)size-1;
// Check the cached location. If it is a match, this provides O(1) lookup.
// Otherwise it works like one binsearch iteration.
if (size_t(*cache) < size)
{
T cptr = vect[*cache];
if (cptr->global_id == id)
return cptr;
if (cptr->global_id < id)
start = *cache+1;
else
end = *cache-1;
}
// Regular binsearch. The end check provides O(1) caching for missing item.
if (start <= end && vect[end]->global_id >= id)
{
do {
int32_t mid=(start+end)>>1;
T cptr=vect[mid];
if(cptr->global_id==id)
{
*cache = mid;
return cptr;
}
else if(cptr->global_id>id)end=mid-1;
else start=mid+1;
} while(start<=end);
}
*cache = end+1;
return NULL;
}
template<class T> T *find_object(int32_t id, int32_t *cache);
template<> df::item *find_object<df::item>(int32_t id, int32_t *cache) {
return get_from_global_id_vector(id, df::global::world->items.all, cache);
}
template<> df::unit *find_object<df::unit>(int32_t id, int32_t *cache) {
return get_from_global_id_vector(id, df::global::world->units.all, cache);
}
template<class T>
struct CachedRef {
int32_t id;
int32_t cache;
CachedRef(int32_t id = -1) : id(id), cache(-1) {}
T *target() { return find_object<T>(id, &cache); }
};
#ifdef LINUX_BUILD
struct item_hook : df::general_ref_item {
typedef df::general_ref_item interpose_base;
DEFINE_VMETHOD_INTERPOSE(df::item*, getItem, ())
{
// HUGE HACK: ASSUMES THERE ARE 4 USABLE BYTES AFTER THE OBJECT
// This actually is true with glibc allocator due to granularity.
return find_object<df::item>(item_id, 1+&item_id);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(item_hook, getItem);
struct unit_hook : df::general_ref_unit {
typedef df::general_ref_unit interpose_base;
DEFINE_VMETHOD_INTERPOSE(df::unit*, getUnit, ())
{
// HUGE HACK: ASSUMES THERE ARE 4 USABLE BYTES AFTER THE OBJECT
// This actually is true with glibc allocator due to granularity.
return find_object<df::unit>(unit_id, 1+&unit_id);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(unit_hook, getUnit);
command_result hook_refs(color_ostream &out, vector <string> & parameters)
{
auto &hook = INTERPOSE_HOOK(item_hook, getItem);
if (hook.is_applied())
{
hook.remove();
INTERPOSE_HOOK(unit_hook, getUnit).remove();
}
else
{
hook.apply();
INTERPOSE_HOOK(unit_hook, getUnit).apply();
}
if (hook.is_applied())
out.print("Hook is applied.\n");
else
out.print("Hook is not applied.\n");
return CR_OK;
}
#endif
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
#ifdef LINUX_BUILD
commands.push_back(PluginCommand("hook-refs","Inject O(1) cached lookup into general refs.",hook_refs));
#endif
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}

@ -27,6 +27,7 @@ using namespace std;
#include "df/world_region_details.h"
#include "df/world_geo_biome.h"
#include "df/world_geo_layer.h"
#include "df/region_map_entry.h"
#include "df/inclusion_type.h"
#include "df/viewscreen_choose_start_sitest.h"
@ -79,7 +80,7 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
if (parameters.size() == 2)
{
if (parameters[0] == "wet")
if (parameters[0] == "rai")
set_field = 0;
else if (parameters[0] == "veg")
set_field = 1;
@ -87,7 +88,7 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
set_field = 2;
else if (parameters[0] == "evi")
set_field = 3;
else if (parameters[0] == "hil")
else if (parameters[0] == "dra")
set_field = 4;
else if (parameters[0] == "sav")
set_field = 5;
@ -113,11 +114,11 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
{
coord2d rg = screen->biome_rgn[i];
df::world_data::T_region_map* rd = &data->region_map[rg.x][rg.y];
auto rd = &data->region_map[rg.x][rg.y];
if (set && i == to_set) {
if (set_field == 0)
rd->wetness = set_val;
rd->rainfall = set_val;
else if (set_field == 1)
rd->vegetation = set_val;
else if (set_field == 2)
@ -125,11 +126,11 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
else if (set_field == 3)
rd->evilness = set_val;
else if (set_field == 4)
rd->hilliness = set_val;
rd->drainage = set_val;
else if (set_field == 5)
rd->savagery = set_val;
else if (set_field == 6)
rd->saltiness = set_val;
rd->salinity = set_val;
}
out << i << ": x = " << rg.x << ", y = " << rg.y;
@ -140,13 +141,13 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
" landmass_id: " << rd->landmass_id <<
" flags: " << hex << rd->flags.as_int() << dec << endl;
out <<
"wet: " << rd->wetness << " " <<
"rai: " << rd->rainfall << " " <<
"veg: " << rd->vegetation << " " <<
"tem: " << rd->temperature << " " <<
"evi: " << rd->evilness << " " <<
"hil: " << rd->hilliness << " " <<
"dra: " << rd->drainage << " " <<
"sav: " << rd->savagery << " " <<
"sal: " << rd->saltiness;
"sal: " << rd->salinity;
int32_t *p = (int32_t *)rd;
int c = sizeof(*rd) / sizeof(int32_t);

@ -27,6 +27,7 @@
#include <set>
#include <cstdlib>
#include <sstream>
#include <memory>
using std::vector;
using std::string;
using std::endl;
@ -41,6 +42,7 @@ using std::set;
#include "modules/Gui.h"
#include "TileTypes.h"
#include "modules/MapCache.h"
#include "LuaTools.h"
#include "Brushes.h"
using namespace MapExtras;
using namespace DFHack;
@ -50,7 +52,6 @@ CommandHistory liquids_hist;
command_result df_liquids (color_ostream &out, vector <string> & parameters);
command_result df_liquids_here (color_ostream &out, vector <string> & parameters);
command_result df_liquids_execute (color_ostream &out);
DFHACK_PLUGIN("liquids");
@ -74,13 +75,79 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK;
}
// static stuff to be remembered between sessions
static string brushname = "point";
static string mode="magma";
static string flowmode="f+";
static string _setmode ="s.";
static unsigned int amount = 7;
static int width = 1, height = 1, z_levels = 1;
enum BrushType {
B_POINT, B_RANGE, B_BLOCK, B_COLUMN, B_FLOOD
};
static const char *brush_name[] = {
"point", "range", "block", "column", "flood", NULL
};
enum PaintMode {
P_WATER, P_MAGMA, P_OBSIDIAN, P_OBSIDIAN_FLOOR,
P_RIVER_SOURCE, P_FLOW_BITS, P_WCLEAN
};
static const char *paint_mode_name[] = {
"water", "magma", "obsidian", "obsidian_floor",
"riversource", "flowbits", "wclean", NULL
};
enum ModifyMode {
M_INC, M_KEEP, M_DEC
};
static const char *modify_mode_name[] = {
"+", ".", "-", NULL
};
enum PermaflowMode {
PF_KEEP, PF_NONE,
PF_NORTH, PF_SOUTH, PF_EAST, PF_WEST,
PF_NORTHEAST, PF_NORTHWEST, PF_SOUTHEAST, PF_SOUTHWEST
};
static const char *permaflow_name[] = {
".", "-", "N", "S", "E", "W",
"NE", "NW", "SE", "SW", NULL
};
#define X(name) tile_liquid_flow_dir::name
static const df::tile_liquid_flow_dir permaflow_id[] = {
X(none), X(none), X(north), X(south), X(east), X(west),
X(northeast), X(northwest), X(southeast), X(southwest)
};
#undef X
struct OperationMode {
BrushType brush;
PaintMode paint;
ModifyMode flowmode;
ModifyMode setmode;
PermaflowMode permaflow;
unsigned int amount;
df::coord size;
OperationMode() :
brush(B_POINT), paint(P_MAGMA),
flowmode(M_INC), setmode(M_KEEP), permaflow(PF_KEEP), amount(7),
size(1,1,1)
{}
} cur_mode;
command_result df_liquids_execute(color_ostream &out);
command_result df_liquids_execute(color_ostream &out, OperationMode &mode, df::coord pos);
static void print_prompt(std::ostream &str, OperationMode &cur_mode)
{
str <<"[" << paint_mode_name[cur_mode.paint] << ":" << brush_name[cur_mode.brush];
if (cur_mode.brush == B_RANGE)
str << "(w" << cur_mode.size.x << ":h" << cur_mode.size.y << ":z" << cur_mode.size.z << ")";
str << ":" << cur_mode.amount << ":f" << modify_mode_name[cur_mode.flowmode]
<< ":s" << modify_mode_name[cur_mode.setmode]
<< ":pf" << permaflow_name[cur_mode.permaflow]
<< "]";
}
command_result df_liquids (color_ostream &out_, vector <string> & parameters)
{
@ -117,10 +184,8 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
string input = "";
std::stringstream str;
str <<"[" << mode << ":" << brushname;
if (brushname == "range")
str << "(w" << width << ":h" << height << ":z" << z_levels << ")";
str << ":" << amount << ":" << flowmode << ":" << _setmode << "]#";
print_prompt(str, cur_mode);
str << "# ";
if(out.lineedit(str.str(),input,liquids_hist) == -1)
return CR_FAILURE;
liquids_hist.add(input);
@ -147,6 +212,10 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
<< "f+ - make the spawned liquid flow" << endl
<< "f. - don't change flow state (read state in flow mode)" << endl
<< "f- - make the spawned liquid static" << endl
<< "Permaflow (only for water):" << endl
<< "pf. - don't change permaflow state" << endl
<< "pf- - make the spawned liquid static" << endl
<< "pf[NS][EW] - make the spawned liquid permanently flow" << endl
<< "0-7 - set liquid amount" << endl
<< "Brush:" << endl
<< "point - single tile [p]" << endl
@ -168,38 +237,39 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
}
else if(command == "m")
{
mode = "magma";
cur_mode.paint = P_MAGMA;
}
else if(command == "o")
{
mode = "obsidian";
cur_mode.paint = P_OBSIDIAN;
}
else if(command == "of")
{
mode = "obsidian_floor";
cur_mode.paint = P_OBSIDIAN_FLOOR;
}
else if(command == "w")
{
mode = "water";
cur_mode.paint = P_WATER;
}
else if(command == "f")
{
mode = "flowbits";
cur_mode.paint = P_FLOW_BITS;
}
else if(command == "rs")
{
mode = "riversource";
cur_mode.paint = P_RIVER_SOURCE;
}
else if(command == "wclean")
{
mode = "wclean";
cur_mode.paint = P_WCLEAN;
}
else if(command == "point" || command == "p")
{
brushname = "point";
cur_mode.brush = B_POINT;
}
else if(command == "range" || command == "r")
{
int width, height, z_levels;
command_result res = parseRectangle(out, commands, 1, commands.size(),
width, height, z_levels);
if (res != CR_OK)
@ -209,24 +279,26 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
if (width == 1 && height == 1 && z_levels == 1)
{
brushname = "point";
cur_mode.brush = B_POINT;
cur_mode.size = df::coord(1, 1, 1);
}
else
{
brushname = "range";
cur_mode.brush = B_RANGE;
cur_mode.size = df::coord(width, height, z_levels);
}
}
else if(command == "block")
{
brushname = "block";
cur_mode.brush = B_BLOCK;
}
else if(command == "column")
{
brushname = "column";
cur_mode.brush = B_COLUMN;
}
else if(command == "flood")
{
brushname = "flood";
cur_mode.brush = B_FLOOD;
}
else if(command == "q")
{
@ -234,45 +306,59 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
}
else if(command == "f+")
{
flowmode = "f+";
cur_mode.flowmode = M_INC;
}
else if(command == "f-")
{
flowmode = "f-";
cur_mode.flowmode = M_DEC;
}
else if(command == "f.")
{
flowmode = "f.";
cur_mode.flowmode = M_KEEP;
}
else if(command == "s+")
{
_setmode = "s+";
cur_mode.setmode = M_INC;
}
else if(command == "s-")
{
_setmode = "s-";
cur_mode.setmode = M_DEC;
}
else if(command == "s.")
{
_setmode = "s.";
cur_mode.setmode = M_KEEP;
}
else if (command.size() > 2 && memcmp(command.c_str(), "pf", 2) == 0)
{
auto *tail = command.c_str()+2;
for (int pm = PF_KEEP; pm <= PF_SOUTHWEST; pm++)
{
if (strcmp(tail, permaflow_name[pm]) != 0)
continue;
cur_mode.permaflow = PermaflowMode(pm);
tail = NULL;
break;
}
if (tail)
out << command << " : invalid permaflow mode" << endl;
}
// blah blah, bad code, bite me.
else if(command == "0")
amount = 0;
cur_mode.amount = 0;
else if(command == "1")
amount = 1;
cur_mode.amount = 1;
else if(command == "2")
amount = 2;
cur_mode.amount = 2;
else if(command == "3")
amount = 3;
cur_mode.amount = 3;
else if(command == "4")
amount = 4;
cur_mode.amount = 4;
else if(command == "5")
amount = 5;
cur_mode.amount = 5;
else if(command == "6")
amount = 6;
cur_mode.amount = 6;
else if(command == "7")
amount = 7;
cur_mode.amount = 7;
else if(command.empty())
{
df_liquids_execute(out);
@ -298,78 +384,75 @@ command_result df_liquids_here (color_ostream &out, vector <string> & parameters
}
out.print("Run liquids-here with these parameters: ");
out << "[" << mode << ":" << brushname;
if (brushname == "range")
out << "(w" << width << ":h" << height << ":z" << z_levels << ")";
out << ":" << amount << ":" << flowmode << ":" << _setmode << "]\n";
print_prompt(out, cur_mode);
out << endl;
return df_liquids_execute(out);
}
command_result df_liquids_execute(color_ostream &out)
{
// create brush type depending on old parameters
Brush * brush;
CoreSuspender suspend;
if (brushname == "point")
auto cursor = Gui::getCursorPos();
if (!cursor.isValid())
{
brush = new RectangleBrush(1,1,1,0,0,0);
//width = 1;
//height = 1;
//z_levels = 1;
out.printerr("Can't get cursor coords! Make sure you have a cursor active in DF.\n");
return CR_WRONG_USAGE;
}
else if (brushname == "range")
{
brush = new RectangleBrush(width,height,z_levels,0,0,0);
auto rv = df_liquids_execute(out, cur_mode, cursor);
if (rv == CR_OK)
out << "OK" << endl;
return rv;
}
else if(brushname == "block")
command_result df_liquids_execute(color_ostream &out, OperationMode &cur_mode, df::coord cursor)
{
brush = new BlockBrush();
}
else if(brushname == "column")
// create brush type depending on old parameters
Brush *brush;
switch (cur_mode.brush)
{
case B_POINT:
brush = new RectangleBrush(1,1,1,0,0,0);
break;
case B_RANGE:
brush = new RectangleBrush(cur_mode.size.x,cur_mode.size.y,cur_mode.size.z,0,0,0);
break;
case B_BLOCK:
brush = new BlockBrush();
break;
case B_COLUMN:
brush = new ColumnBrush();
}
else if(brushname == "flood")
{
break;
case B_FLOOD:
brush = new FloodBrush(&Core::getInstance());
}
else
{
break;
default:
// this should never happen!
out << "Old brushtype is invalid! Resetting to point brush.\n";
brushname = "point";
width = 1;
height = 1;
z_levels = 1;
brush = new RectangleBrush(width,height,z_levels,0,0,0);
cur_mode.brush = B_POINT;
brush = new RectangleBrush(1,1,1,0,0,0);
}
CoreSuspender suspend;
std::auto_ptr<Brush> brush_ref(brush);
do
{
if (!Maps::IsValid())
{
out << "Can't see any DF map loaded." << endl;
break;;
}
int32_t x,y,z;
if(!Gui::getCursorCoords(x,y,z))
{
out << "Can't get cursor coords! Make sure you have a cursor active in DF." << endl;
break;
return CR_FAILURE;
}
out << "cursor coords: " << x << "/" << y << "/" << z << endl;
MapCache mcache;
DFHack::DFCoord cursor(x,y,z);
coord_vec all_tiles = brush->points(mcache,cursor);
out << "working..." << endl;
// Force the game to recompute its walkability cache
df::global::world->reindex_pathfinding = true;
if(mode == "obsidian")
switch (cur_mode.paint)
{
case P_OBSIDIAN:
{
coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end())
@ -383,8 +466,9 @@ command_result df_liquids_execute(color_ostream &out)
mcache.setDesignationAt(*iter, des);
iter ++;
}
break;
}
if(mode == "obsidian_floor")
case P_OBSIDIAN_FLOOR:
{
coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end())
@ -392,8 +476,9 @@ command_result df_liquids_execute(color_ostream &out)
mcache.setTiletypeAt(*iter, findRandomVariant(tiletype::LavaFloor1));
iter ++;
}
break;
}
else if(mode == "riversource")
case P_RIVER_SOURCE:
{
coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end())
@ -413,8 +498,9 @@ command_result df_liquids_execute(color_ostream &out)
iter++;
}
break;
}
else if(mode=="wclean")
case P_WCLEAN:
{
coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end())
@ -426,8 +512,11 @@ command_result df_liquids_execute(color_ostream &out)
mcache.setDesignationAt(current,des);
iter++;
}
break;
}
else if(mode== "magma" || mode== "water" || mode == "flowbits")
case P_MAGMA:
case P_WATER:
case P_FLOW_BITS:
{
set <Block *> seen_blocks;
coord_vec::iterator iter = all_tiles.begin();
@ -442,6 +531,7 @@ command_result df_liquids_execute(color_ostream &out)
iter ++;
continue;
}
auto raw_block = block->getRaw();
df::tile_designation des = mcache.designationAt(current);
df::tiletype tt = mcache.tiletypeAt(current);
// don't put liquids into places where they don't belong...
@ -450,30 +540,29 @@ command_result df_liquids_execute(color_ostream &out)
iter++;
continue;
}
if(mode != "flowbits")
if(cur_mode.paint != P_FLOW_BITS)
{
unsigned old_amount = des.bits.flow_size;
unsigned new_amount = old_amount;
df::tile_liquid old_liquid = des.bits.liquid_type;
df::tile_liquid new_liquid = old_liquid;
// Compute new liquid type and amount
if(_setmode == "s.")
{
new_amount = amount;
}
else if(_setmode == "s+")
switch (cur_mode.setmode)
{
if(old_amount < amount)
new_amount = amount;
}
else if(_setmode == "s-")
{
if (old_amount > amount)
new_amount = amount;
case M_KEEP:
new_amount = cur_mode.amount;
break;
case M_INC:
if(old_amount < cur_mode.amount)
new_amount = cur_mode.amount;
break;
case M_DEC:
if (old_amount > cur_mode.amount)
new_amount = cur_mode.amount;
}
if (mode == "magma")
if (cur_mode.paint == P_MAGMA)
new_liquid = tile_liquid::Magma;
else if (mode == "water")
else if (cur_mode.paint == P_WATER)
new_liquid = tile_liquid::Water;
// Store new amount and type
des.bits.flow_size = new_amount;
@ -502,40 +591,77 @@ command_result df_liquids_execute(color_ostream &out)
// request flow engine updates
block->enableBlockUpdates(new_amount != old_amount, new_liquid != old_liquid);
}
if (cur_mode.permaflow != PF_KEEP && raw_block)
{
auto &flow = raw_block->liquid_flow[current.x&15][current.y&15];
flow.bits.perm_flow_dir = permaflow_id[cur_mode.permaflow];
flow.bits.temp_flow_timer = 0;
}
seen_blocks.insert(block);
iter++;
}
set <Block *>::iterator biter = seen_blocks.begin();
while (biter != seen_blocks.end())
{
if(flowmode == "f+")
switch (cur_mode.flowmode)
{
case M_INC:
(*biter)->enableBlockUpdates(true);
}
else if(flowmode == "f-")
{
break;
case M_DEC:
if (auto block = (*biter)->getRaw())
{
block->flags.bits.update_liquid = false;
block->flags.bits.update_liquid_twice = false;
}
}
else
break;
case M_KEEP:
{
auto bflags = (*biter)->BlockFlags();
out << "flow bit 1 = " << bflags.bits.update_liquid << endl;
out << "flow bit 2 = " << bflags.bits.update_liquid_twice << endl;
}
}
biter ++;
}
break;
}
if(mcache.WriteAll())
out << "OK" << endl;
else
}
if(!mcache.WriteAll())
{
out << "Something failed horribly! RUN!" << endl;
} while (0);
return CR_FAILURE;
}
// cleanup
delete brush;
return CR_OK;
}
static int paint(lua_State *L)
{
df::coord pos;
OperationMode mode;
lua_settop(L, 8);
Lua::CheckDFAssign(L, &pos, 1);
if (!pos.isValid())
luaL_argerror(L, 1, "invalid cursor position");
mode.brush = (BrushType)luaL_checkoption(L, 2, NULL, brush_name);
mode.paint = (PaintMode)luaL_checkoption(L, 3, NULL, paint_mode_name);
mode.amount = luaL_optint(L, 4, 7);
if (mode.amount < 0 || mode.amount > 7)
luaL_argerror(L, 4, "invalid liquid amount");
if (!lua_isnil(L, 5))
Lua::CheckDFAssign(L, &mode.size, 5);
mode.setmode = (ModifyMode)luaL_checkoption(L, 6, ".", modify_mode_name);
mode.flowmode = (ModifyMode)luaL_checkoption(L, 7, "+", modify_mode_name);
mode.permaflow = (PermaflowMode)luaL_checkoption(L, 8, ".", permaflow_name);
lua_pushboolean(L, df_liquids_execute(*Lua::GetOutput(L), mode, pos));
return 1;
}
DFHACK_PLUGIN_LUA_COMMANDS {
DFHACK_LUA_COMMAND(paint),
DFHACK_LUA_END
};

@ -0,0 +1,11 @@
local _ENV = mkmodule('plugins.liquids')
--[[
Native functions:
* paint(pos,brush,paint,amount,size,setmode,flowmode)
--]]
return _ENV

@ -11,6 +11,7 @@
#include <vector>
#include <string>
#include <set>
#include <algorithm>
#include <VTableInterpose.h>
#include "df/world.h"
@ -247,17 +248,69 @@ struct UnitInfo
int8_t color;
};
#define FILTER_NONWORKERS 0x0001
#define FILTER_NONDWARVES 0x0002
#define FILTER_NONCIV 0x0004
#define FILTER_ANIMALS 0x0008
#define FILTER_LIVING 0x0010
#define FILTER_DEAD 0x0020
enum altsort_mode {
ALTSORT_NAME,
ALTSORT_PROFESSION,
ALTSORT_MAX
};
bool descending;
df::job_skill sort_skill;
df::unit_labor sort_labor;
bool sortByName (const UnitInfo *d1, const UnitInfo *d2)
{
if (descending)
return (d1->name > d2->name);
else
return (d1->name < d2->name);
}
bool sortByProfession (const UnitInfo *d1, const UnitInfo *d2)
{
if (descending)
return (d1->profession > d2->profession);
else
return (d1->profession < d2->profession);
}
bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2)
{
if (sort_skill != job_skill::NONE)
{
df::unit_skill *s1 = binsearch_in_vector<df::unit_skill,df::enum_field<df::job_skill,int16_t>>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill);
df::unit_skill *s2 = binsearch_in_vector<df::unit_skill,df::enum_field<df::job_skill,int16_t>>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill);
int l1 = s1 ? s1->rating : 0;
int l2 = s2 ? s2->rating : 0;
int e1 = s1 ? s1->experience : 0;
int e2 = s2 ? s2->experience : 0;
if (descending)
{
if (l1 != l2)
return l1 > l2;
if (e1 != e2)
return e1 > e2;
}
else
{
if (l1 != l2)
return l1 < l2;
if (e1 != e2)
return e1 < e2;
}
}
if (sort_labor != unit_labor::NONE)
{
if (descending)
return d1->unit->status.labors[sort_labor] > d2->unit->status.labors[sort_labor];
else
return d1->unit->status.labors[sort_labor] < d2->unit->status.labors[sort_labor];
}
return sortByName(d1, d2);
}
class viewscreen_unitlaborsst : public dfhack_viewscreen {
public:
static viewscreen_unitlaborsst *create (char pushtype, df::viewscreen *scr = NULL);
void feed(set<df::interface_key> *events);
void render();
@ -267,86 +320,41 @@ public:
std::string getFocusString() { return "unitlabors"; }
viewscreen_unitlaborsst();
viewscreen_unitlaborsst(vector<df::unit*> &src);
~viewscreen_unitlaborsst() { };
protected:
vector<UnitInfo *> units;
int filter;
altsort_mode altsort;
int first_row, sel_row;
int first_column, sel_column;
int height, name_width, prof_width, labors_width;
// bool descending;
// int sort_skill;
// int sort_labor;
void readUnits ();
void calcSize ();
};
viewscreen_unitlaborsst::viewscreen_unitlaborsst()
viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector<df::unit*> &src)
{
filter = FILTER_LIVING;
first_row = sel_row = 0;
first_column = sel_column = 0;
calcSize();
readUnits();
}
void viewscreen_unitlaborsst::readUnits ()
for (size_t i = 0; i < src.size(); i++)
{
for (size_t i = 0; i < units.size(); i++)
delete units[i];
units.clear();
UnitInfo *cur = new UnitInfo;
for (size_t i = 0; i < world->units.active.size(); i++)
{
df::unit *unit = world->units.active[i];
df::unit *unit = src[i];
cur->unit = unit;
cur->allowEdit = true;
if (unit->race != ui->race_id)
{
cur->allowEdit = false;
if (!(filter & FILTER_NONDWARVES))
continue;
}
if (unit->civ_id != ui->civ_id)
{
cur->allowEdit = false;
if (!(filter & FILTER_NONCIV))
continue;
}
if (unit->flags1.bits.dead)
{
cur->allowEdit = false;
if (!(filter & FILTER_DEAD))
continue;
}
else
{
if (!(filter & FILTER_LIVING))
continue;
}
if (!ENUM_ATTR(profession, can_assign_labor, unit->profession))
{
cur->allowEdit = false;
if (!(filter & FILTER_NONWORKERS))
continue;
}
if (!unit->name.first_name.length())
{
if (!(filter & FILTER_ANIMALS))
continue;
}
cur->name = Translation::TranslateName(&unit->name, false);
cur->transname = Translation::TranslateName(&unit->name, true);
@ -354,9 +362,13 @@ void viewscreen_unitlaborsst::readUnits ()
cur->color = Units::getProfessionColor(unit);
units.push_back(cur);
cur = new UnitInfo;
}
delete cur;
std::sort(units.begin(), units.end(), sortByName);
altsort = ALTSORT_NAME;
first_row = sel_row = 0;
first_column = sel_column = 0;
calcSize();
}
void viewscreen_unitlaborsst::calcSize()
@ -421,8 +433,6 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
return;
}
// TODO - allow modifying filters
if (!units.size())
return;
@ -501,9 +511,64 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
unit->status.labors[col.labor] = !unit->status.labors[col.labor];
}
// TODO: add sorting
if (events->count(interface_key::SECONDSCROLL_UP) || events->count(interface_key::SECONDSCROLL_DOWN))
{
descending = events->count(interface_key::SECONDSCROLL_UP);
sort_skill = columns[sel_column].skill;
sort_labor = columns[sel_column].labor;
std::sort(units.begin(), units.end(), sortBySkill);
}
if (events->count(interface_key::SECONDSCROLL_PAGEUP) || events->count(interface_key::SECONDSCROLL_PAGEDOWN))
{
descending = events->count(interface_key::SECONDSCROLL_PAGEUP);
switch (altsort)
{
case ALTSORT_NAME:
std::sort(units.begin(), units.end(), sortByName);
break;
case ALTSORT_PROFESSION:
std::sort(units.begin(), units.end(), sortByProfession);
break;
}
}
if (events->count(interface_key::CHANGETAB))
{
switch (altsort)
{
case ALTSORT_NAME:
altsort = ALTSORT_PROFESSION;
break;
case ALTSORT_PROFESSION:
altsort = ALTSORT_NAME;
break;
}
}
if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, parent))
{
if (events->count(interface_key::UNITJOB_VIEW) || events->count(interface_key::UNITJOB_ZOOM_CRE))
{
for (int i = 0; i < unitlist->units[unitlist->page].size(); i++)
{
if (unitlist->units[unitlist->page][i] == units[sel_row]->unit)
{
unitlist->cursor_pos[unitlist->page] = i;
unitlist->feed(events);
if (Screen::isDismissed(unitlist))
Screen::dismiss(this);
break;
}
}
}
}
}
void OutputString(int8_t color, int &x, int y, const std::string &text)
{
Screen::paintString(Screen::Pen(' ', color, 0), x, y, text);
x += text.length();
}
void viewscreen_unitlaborsst::render()
{
if (Screen::isDismissed(this))
@ -528,6 +593,7 @@ void viewscreen_unitlaborsst::render()
fg = 0;
bg = 7;
}
Screen::paintTile(Screen::Pen(columns[col_offset].label[0], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 1);
Screen::paintTile(Screen::Pen(columns[col_offset].label[1], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 2);
}
@ -537,6 +603,7 @@ void viewscreen_unitlaborsst::render()
int row_offset = row + first_row;
if (row_offset >= units.size())
break;
UnitInfo *cur = units[row_offset];
df::unit *unit = cur->unit;
int8_t fg = 15, bg = 0;
@ -554,7 +621,8 @@ void viewscreen_unitlaborsst::render()
profession.resize(prof_width);
fg = cur->color;
bg = 0;
Screen::paintString(Screen::Pen(' ', fg, bg), 1 + prof_width + 1, 3 + row, profession);
Screen::paintString(Screen::Pen(' ', fg, bg), 1 + name_width + 1, 3 + row, profession);
// Print unit's skills and labor assignments
for (int col = 0; col < labors_width; col++)
@ -562,11 +630,9 @@ void viewscreen_unitlaborsst::render()
int col_offset = col + first_column;
fg = 15;
bg = 0;
char c = 0xFA;
if ((col_offset == sel_column) && (row_offset == sel_row))
fg = 9;
if ((columns[col_offset].labor != unit_labor::NONE) && (unit->status.labors[columns[col_offset].labor]))
bg = 7;
char c = '-';
if (columns[col_offset].skill != job_skill::NONE)
{
df::unit_skill *skill = binsearch_in_vector<df::unit_skill,df::enum_field<df::job_skill,int16_t>>(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill);
@ -577,7 +643,20 @@ void viewscreen_unitlaborsst::render()
level = NUM_SKILL_LEVELS - 1;
c = skill_levels[level].abbrev;
}
else
c = '-';
}
if (columns[col_offset].labor != unit_labor::NONE)
{
if (unit->status.labors[columns[col_offset].labor])
{
bg = 7;
if (columns[col_offset].skill == job_skill::NONE)
c = 0xF9;
}
}
else
bg = 4;
Screen::paintTile(Screen::Pen(c, fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 3 + row);
}
}
@ -586,15 +665,21 @@ void viewscreen_unitlaborsst::render()
if (cur != NULL)
{
df::unit *unit = cur->unit;
string str = cur->transname;
if (str.length())
str += ", ";
str += cur->profession;
str += ":";
int x = 1;
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->transname);
x += cur->transname.length();
Screen::paintString(Screen::Pen(' ', 15, 0), 1, 3 + height + 2, str);
int y = 1 + str.length() + 1;
if (cur->transname.length())
{
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ", ");
x += 2;
}
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->profession);
x += cur->profession.length();
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ": ");
x += 2;
string str;
if (columns[sel_column].skill == job_skill::NONE)
{
str = ENUM_ATTR_STR(unit_labor, caption, columns[sel_column].labor);
@ -602,7 +687,6 @@ void viewscreen_unitlaborsst::render()
str += " Enabled";
else
str += " Not Enabled";
Screen::paintString(Screen::Pen(' ', 9, 0), y, 3 + height + 2, str);
}
else
{
@ -618,11 +702,45 @@ void viewscreen_unitlaborsst::render()
}
else
str = stl_sprintf("Not %s (0/500)", ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill));
Screen::paintString(Screen::Pen(' ', 9, 0), y, 3 + height + 2, str);
}
Screen::paintString(Screen::Pen(' ', 9, 0), x, 3 + height + 2, str);
}
// TODO - print command help info
int x = 1;
OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key
OutputString(15, x, gps->dimy - 3, ": Toggle labor, ");
OutputString(10, x, gps->dimy - 3, "v"); // UNITJOB_VIEW key
OutputString(15, x, gps->dimy - 3, ": ViewCre, ");
OutputString(10, x, gps->dimy - 3, "c"); // UNITJOB_ZOOM_CRE key
OutputString(15, x, gps->dimy - 3, ": Zoom-Cre, ");
OutputString(10, x, gps->dimy - 3, "Esc"); // LEAVESCREEN key
OutputString(15, x, gps->dimy - 3, ": Done");
x = 1;
OutputString(10, x, gps->dimy - 2, "+"); // SECONDSCROLL_DOWN key
OutputString(10, x, gps->dimy - 2, "-"); // SECONDSCROLL_UP key
OutputString(15, x, gps->dimy - 2, ": Sort by Skill, ");
OutputString(10, x, gps->dimy - 2, "*"); // SECONDSCROLL_PAGEDOWN key
OutputString(10, x, gps->dimy - 2, "/"); // SECONDSCROLL_PAGEUP key
OutputString(15, x, gps->dimy - 2, ": Sort by (");
OutputString(10, x, gps->dimy - 2, "Tab"); // CHANGETAB key
OutputString(15, x, gps->dimy - 2, ") ");
switch (altsort)
{
case ALTSORT_NAME:
OutputString(15, x, gps->dimy - 2, "Name");
break;
case ALTSORT_PROFESSION:
OutputString(15, x, gps->dimy - 2, "Profession");
break;
default:
OutputString(15, x, gps->dimy - 2, "Unknown");
break;
}
}
struct unitlist_hook : df::viewscreen_unitlistst
@ -633,10 +751,12 @@ struct unitlist_hook : df::viewscreen_unitlistst
{
if (input->count(interface_key::UNITVIEW_PRF_PROF))
{
Screen::dismiss(this);
Screen::show(new viewscreen_unitlaborsst());
if (units[page].size())
{
Screen::show(new viewscreen_unitlaborsst(units[page]));
return;
}
}
INTERPOSE_NEXT(feed)(input);
}
};

@ -27,6 +27,7 @@ using namespace std;
#include "df/world.h"
#include "df/world_raws.h"
#include "df/building_def.h"
#include "df/region_map_entry.h"
using std::vector;
using std::string;
@ -224,8 +225,7 @@ command_result df_probe (color_ostream &out, vector <string> & parameters)
int bx = clip_range(block.region_pos.x + (offset % 3) - 1, 0, world->world_data->world_width-1);
int by = clip_range(block.region_pos.y + (offset / 3) - 1, 0, world->world_data->world_height-1);
df::world_data::T_region_map* biome =
&world->world_data->region_map[bx][by];
auto biome = &world->world_data->region_map[bx][by];
int sav = biome->savagery;
int evi = biome->evilness;

@ -25,8 +25,12 @@ using namespace std;
#include "df/world.h"
#include "df/world_data.h"
#include "df/world_region_details.h"
#include "df/world_region_feature.h"
#include "df/world_geo_biome.h"
#include "df/world_geo_layer.h"
#include "df/world_underground_region.h"
#include "df/feature_init.h"
#include "df/region_map_entry.h"
#include "df/inclusion_type.h"
#include "df/viewscreen_choose_start_sitest.h"
@ -108,8 +112,9 @@ struct compare_pair_second
}
};
static void printMatdata(color_ostream &con, const matdata &data)
static void printMatdata(color_ostream &con, const matdata &data, bool only_z = false)
{
if (!only_z)
con << std::setw(9) << data.count;
if(data.lower_z != data.upper_z)
@ -225,73 +230,155 @@ static coord2d biome_delta[] = {
coord2d(-1,-1), coord2d(0,-1), coord2d(1,-1)
};
static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen,
bool showHidden, bool showValue)
{
if (!world || !world->world_data)
struct EmbarkTileLayout {
coord2d biome_off, biome_pos;
df::region_map_entry *biome;
int elevation, max_soil_depth;
int min_z, base_z;
std::map<int, float> penalty;
};
bool estimate_underground(color_ostream &out, EmbarkTileLayout &tile, df::world_region_details *details, int x, int y)
{
out.printerr("World data is not available.\n");
return CR_FAILURE;
}
// Find actual biome
int bv = clip_range(details->biome[x][y] & 15, 1, 9);
tile.biome_off = biome_delta[bv-1];
df::world_data *data = world->world_data;
coord2d cur_region = screen->region_pos;
int d_idx = linear_index(data->region_details, &df::world_region_details::pos, cur_region);
auto cur_details = vector_get(data->region_details, d_idx);
int bx = clip_range(details->pos.x + tile.biome_off.x, 0, data->world_width-1);
int by = clip_range(details->pos.y + tile.biome_off.y, 0, data->world_height-1);
tile.biome_pos = coord2d(bx, by);
tile.biome = &data->region_map[bx][by];
// Compute surface elevation
tile.elevation = (
details->elevation[x][y] + details->elevation[x][y+1] +
details->elevation[x+1][y] + details->elevation[x+1][y+1]
) / 4;
tile.max_soil_depth = std::max((154-tile.biome->elevation)/5,0);
tile.base_z = tile.elevation;
tile.penalty.clear();
auto &features = details->features[x][y];
// Collect global feature layer depths and apply penalties
std::map<int, int> layer_bottom, layer_top;
bool sea_found = false;
for (size_t i = 0; i < features.size(); i++)
{
auto feature = features[i];
auto layer = df::world_underground_region::find(feature->layer);
if (!layer || feature->min_z == -30000) continue;
layer_bottom[layer->layer_depth] = feature->min_z;
layer_top[layer->layer_depth] = feature->max_z;
tile.base_z = std::min(tile.base_z, (int)feature->min_z);
float penalty = 1.0f;
switch (layer->type) {
case df::world_underground_region::Cavern:
penalty = 0.75f;
break;
case df::world_underground_region::MagmaSea:
sea_found = true;
tile.min_z = feature->min_z;
for (int i = feature->min_z; i <= feature->max_z; i++)
tile.penalty[i] = 0.2 + 0.6f*(i-feature->min_z)/(feature->max_z-feature->min_z+1);
break;
case df::world_underground_region::Underworld:
penalty = 0.0f;
break;
}
if (!cur_details)
if (penalty != 1.0f)
{
out.printerr("Current region details are not available.\n");
return CR_FAILURE;
for (int i = feature->min_z; i <= feature->max_z; i++)
tile.penalty[i] = penalty;
}
}
// Compute biomes
std::map<coord2d, int> biomes;
if (screen->biome_highlighted)
if (!sea_found)
{
out.print("Processing one embark tile of biome F%d.\n\n", screen->biome_idx+1);
biomes[screen->biome_rgn[screen->biome_idx]]++;
out.printerr("Could not find magma sea.\n");
return false;
}
else
{
for (int x = screen->embark_pos_min.x; x <= screen->embark_pos_max.x; x++)
// Scan for big local features and apply their penalties
for (size_t i = 0; i < features.size(); i++)
{
for (int y = screen->embark_pos_min.y; y <= screen->embark_pos_max.y; y++)
auto feature = features[i];
auto lfeature = Maps::getLocalInitFeature(details->pos, feature->feature_idx);
if (!lfeature)
continue;
switch (lfeature->getType())
{
int bv = clip_range(cur_details->biome[x][y], 1, 9);
biomes[cur_region + biome_delta[bv-1]]++;
case feature_type::pit:
case feature_type::magma_pool:
case feature_type::volcano:
for (int i = layer_bottom[lfeature->end_depth];
i <= layer_top[lfeature->start_depth]; i++)
tile.penalty[i] = std::min(0.4f, map_find(tile.penalty, i, 1.0f));
break;
default:
break;
}
}
return true;
}
// Compute material maps
MatMap layerMats;
MatMap veinMats;
void add_materials(EmbarkTileLayout &tile, matdata &data, float amount, int min_z, int max_z)
{
for (int z = min_z; z <= max_z; z++)
data.add(z, int(map_find(tile.penalty, z, 1)*amount));
}
for (auto biome_it = biomes.begin(); biome_it != biomes.end(); ++biome_it)
bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &layerMats, MatMap &veinMats)
{
int bx = clip_range(biome_it->first.x, 0, data->world_width-1);
int by = clip_range(biome_it->first.y, 0, data->world_height-1);
auto &region = data->region_map[bx][by];
df::world_geo_biome *geo_biome = df::world_geo_biome::find(region.geo_index);
using namespace geo_layer_type;
df::world_geo_biome *geo_biome = df::world_geo_biome::find(tile.biome->geo_index);
if (!geo_biome)
{
out.printerr("Region geo-biome not found: (%d,%d)\n", bx, by);
return CR_FAILURE;
out.printerr("Region geo-biome not found: (%d,%d)\n",
tile.biome_pos.x, tile.biome_pos.y);
return false;
}
int cnt = biome_it->second;
// soil depth increases by 1 every 5 levels below 150
int top_z_level = tile.elevation - tile.max_soil_depth;
for (unsigned i = 0; i < geo_biome->layers.size(); i++)
{
auto layer = geo_biome->layers[i];
switch (layer->type)
{
case SOIL:
case SOIL_OCEAN:
case SOIL_SAND:
top_z_level += layer->top_height - layer->bottom_height + 1;
break;
default:;
}
}
layerMats[layer->mat_index].add(layer->bottom_height, 0);
top_z_level = std::max(top_z_level, tile.elevation)-1;
int level_cnt = layer->top_height - layer->bottom_height + 1;
int layer_size = 48*48*cnt*level_cnt;
for (unsigned i = 0; i < geo_biome->layers.size(); i++)
{
auto layer = geo_biome->layers[i];
int top_z = std::min(layer->top_height + top_z_level, tile.elevation-1);
int bottom_z = std::max(layer->bottom_height + top_z_level, tile.min_z);
if (i+1 == geo_biome->layers.size()) // stretch layer if needed
bottom_z = tile.min_z;
if (top_z < bottom_z)
continue;
float layer_size = 48*48;
int sums[ENUM_LAST_ITEM(inclusion_type)+1] = { 0 };
@ -304,7 +391,7 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos
// TODO: find out how to estimate the real density
// this code assumes that vein_unk_38 is the weight
// used when choosing the vein material
int size = layer->vein_unk_38[j]*cnt*level_cnt;
float size = float(layer->vein_unk_38[j]);
df::inclusion_type type = layer->vein_type[j];
switch (type)
@ -325,16 +412,65 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos
break;
default:
// shouldn't actually happen
size = cnt*level_cnt;
size = 1;
}
veinMats[layer->vein_mat[j]].add(layer->bottom_height, 0);
veinMats[layer->vein_mat[j]].add(layer->top_height, size);
layer_size -= size;
add_materials(tile, veinMats[layer->vein_mat[j]], size, bottom_z, top_z);
}
layerMats[layer->mat_index].add(layer->top_height, std::max(0,layer_size));
add_materials(tile, layerMats[layer->mat_index], layer_size, bottom_z, top_z);
}
return true;
}
static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen,
bool showHidden, bool showValue)
{
if (!world || !world->world_data)
{
out.printerr("World data is not available.\n");
return CR_FAILURE;
}
df::world_data *data = world->world_data;
coord2d cur_region = screen->region_pos;
int d_idx = linear_index(data->region_details, &df::world_region_details::pos, cur_region);
auto cur_details = vector_get(data->region_details, d_idx);
if (!cur_details)
{
out.printerr("Current region details are not available.\n");
return CR_FAILURE;
}
// Compute material maps
MatMap layerMats;
MatMap veinMats;
matdata world_bottom;
// Compute biomes
std::map<coord2d, int> biomes;
/*if (screen->biome_highlighted)
{
out.print("Processing one embark tile of biome F%d.\n\n", screen->biome_idx+1);
biomes[screen->biome_rgn[screen->biome_idx]]++;
}*/
for (int x = screen->embark_pos_min.x; x <= screen->embark_pos_max.x; x++)
{
for (int y = screen->embark_pos_min.y; y <= screen->embark_pos_max.y; y++)
{
EmbarkTileLayout tile;
if (!estimate_underground(out, tile, cur_details, x, y) ||
!estimate_materials(out, tile, layerMats, veinMats))
return CR_FAILURE;
world_bottom.add(tile.base_z, 0);
world_bottom.add(tile.elevation-1, 0);
}
}
@ -348,7 +484,10 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos
mats->Finish();
}
out << "Warning: the above data is only a very rough estimate." << std::endl;
out << "Embark depth: " << (world_bottom.upper_z-world_bottom.lower_z+1) << " ";
printMatdata(out, world_bottom, true);
out << std::endl << "Warning: the above data is only a very rough estimate." << std::endl;
return CR_OK;
}
@ -536,6 +675,8 @@ command_result prospector (color_ostream &con, vector <string> & parameters)
case tiletype_material::LAVA_STONE:
// TODO ?
break;
default:
break;
}
}
}

@ -213,8 +213,7 @@ Find the raws name of the plant under cursor
p df.world.raws.plants.all[plant.mat_index].id
Dig a channel under the cursor
df.map_designation_at(df.cursor).dig = :Channel
df.map_block_at(df.cursor).flags.designated = true
df.map_tile_at(df.cursor).dig(:Channel)
Spawn 2/7 magma on the tile of the dwarf nicknamed 'hotfeet'
hot = df.unit_citizens.find { |u| u.name.nickname == 'hotfeet' }

@ -279,9 +279,9 @@ module DFHack
job = Job.cpp_new
refbuildingholder = GeneralRefBuildingHolderst.cpp_new
job.job_type = :DestroyBuilding
refbuildingholder.building_id = building.id
refbuildingholder.building_id = bld.id
job.references << refbuildingholder
building.jobs << job
bld.jobs << job
job_link job
job
end

@ -188,6 +188,11 @@ module DFHack
"#<MapTile pos=[#@x, #@y, #@z] shape=#{shape} tilemat=#{tilemat} material=#{mat_info.token}>"
end
def dig(mode=:Default)
designation.dig = mode
mapblock.flags.designated = true
end
def spawn_liquid(quantity, is_magma=false, flowing=true)
designation.flow_size = quantity
designation.liquid_type = (is_magma ? :Magma : :Water)

@ -666,9 +666,9 @@ module DFHack
@_tg = tg
end
field(:_ptr, 0) { number 32, false }
field(:_prev, 4) { number 32, false }
field(:_next, 8) { number 32, false }
field(:_ptr, 0) { pointer }
field(:_prev, 4) { pointer }
field(:_next, 8) { pointer }
def item
# With the current xml structure, currently _tg designate
@ -682,22 +682,24 @@ module DFHack
def item=(v)
#addr = _ptr
#raise 'null pointer' if addr == 0
#raise 'null pointer' if not addr
#@_tg.at(addr)._set(v)
raise 'null pointer'
end
def prev
addr = _prev
return if addr == 0
return if not addr
@_tg._at(addr)._get
end
def next
addr = _next
return if addr == 0
return if not addr
@_tg._at(addr)._get
end
alias next= _next=
alias prev= _prev=
include Enumerable
def each

@ -4,6 +4,7 @@
#include "Export.h"
#include "PluginManager.h"
#include "VersionInfo.h"
#include "MemAccess.h"
#include "DataDefs.h"
#include "df/global_objects.h"
@ -597,6 +598,45 @@ static VALUE rb_dfmemory_write_float(VALUE self, VALUE addr, VALUE val)
return Qtrue;
}
// return memory permissions at address (eg "rx", nil if unmapped)
static VALUE rb_dfmemory_check(VALUE self, VALUE addr)
{
void *ptr = (void*)rb_num2ulong(addr);
std::vector<t_memrange> ranges;
Core::getInstance().p->getMemRanges(ranges);
unsigned i = 0;
while (i < ranges.size() && ranges[i].end <= ptr)
i++;
if (i >= ranges.size() || ranges[i].start > ptr || !ranges[i].valid)
return Qnil;
std::string perm = "";
if (ranges[i].read)
perm += "r";
if (ranges[i].write)
perm += "w";
if (ranges[i].execute)
perm += "x";
if (ranges[i].shared)
perm += "s";
return rb_str_new(perm.c_str(), perm.length());
}
// memory write (tmp override page permissions, eg patch code)
static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw)
{
int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0));
bool ret;
ret = Core::getInstance().p->patchMemory((void*)rb_num2ulong(addr),
rb_string_value_ptr(&raw), strlen);
return ret ? Qtrue : Qfalse;
}
// stl::string
static VALUE rb_dfmemory_stlstring_new(VALUE self)
@ -875,6 +915,8 @@ static void ruby_bind_dfhack(void) {
rb_define_singleton_method(rb_cDFHack, "memory_write_int16", RUBY_METHOD_FUNC(rb_dfmemory_write_int16), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_int32", RUBY_METHOD_FUNC(rb_dfmemory_write_int32), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_float", RUBY_METHOD_FUNC(rb_dfmemory_write_float), 2);
rb_define_singleton_method(rb_cDFHack, "memory_check", RUBY_METHOD_FUNC(rb_dfmemory_check), 1);
rb_define_singleton_method(rb_cDFHack, "memory_patch", RUBY_METHOD_FUNC(rb_dfmemory_patch), 2);
rb_define_singleton_method(rb_cDFHack, "memory_stlstring_new", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_new), 0);
rb_define_singleton_method(rb_cDFHack, "memory_stlstring_delete", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_delete), 1);

@ -41,31 +41,44 @@ module DFHack
# returns an Array of all units that are current fort citizen (dwarves, on map, not hostile)
def unit_citizens
race = ui.race_id
civ = ui.civ_id
world.units.active.find_all { |u|
u.race == race and u.civ_id == civ and !u.flags1.dead and !u.flags1.merchant and
unit_iscitizen(u)
}
end
def unit_iscitizen(u)
u.race == ui.race_id and u.civ_id == ui.civ_id and !u.flags1.dead and !u.flags1.merchant and
!u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and
!u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and
u.mood != :Berserk
# TODO check curse ; currently this should keep vampires, but may include werebeasts
}
end
# list workers (citizen, not crazy / child / inmood / noble)
def unit_workers
unit_citizens.find_all { |u|
world.units.active.find_all { |u|
unit_isworker(u)
}
end
def unit_isworker(u)
unit_iscitizen(u) and
u.mood == :None and
u.profession != :CHILD and
u.profession != :BABY and
# TODO MENIAL_WORK_EXEMPTION_SPOUSE
!unit_entitypositions(u).find { |pos| pos.flags[:MENIAL_WORK_EXEMPTION] }
}
end
# list currently idle workers
def unit_idlers
unit_workers.find_all { |u|
world.units.active.find_all { |u|
unit_isidler(u)
}
end
def unit_isidler(u)
unit_isworker(u) and
# current_job includes eat/drink/sleep/pickupequip
!u.job.current_job and
# filter 'attend meeting'
@ -74,15 +87,14 @@ module DFHack
u.military.squad_index == -1 and
# filter 'on break'
not u.status.misc_traits.find { |t| t.id == :OnBreak }
}
end
def unit_entitypositions(unit)
list = []
return list if not hf = world.history.figures.binsearch(unit.hist_figure_id)
return list if not hf = unit.hist_figure_tg
hf.entity_links.each { |el|
next if el._rtti_classname != :histfig_entity_link_positionst
next if not ent = world.entities.all.binsearch(el.entity_id)
next if not ent = el.entity_tg
next if not pa = ent.positions.assignments.binsearch(el.assignment_id)
next if not pos = ent.positions.own.binsearch(pa.position_id)
list << pos

@ -1 +1 @@
Subproject commit 5d4f06d785f8a9933679fe3caa12c18215e9674d
Subproject commit 2a62ba5ed2607f4dbf0473e77502d4e19c19678e

@ -13,6 +13,7 @@
#include "MiscUtils.h"
#include "DataDefs.h"
#include <VTableInterpose.h>
#include "df/ui.h"
#include "df/world.h"
#include "df/squad.h"
@ -26,6 +27,8 @@
#include "df/death_info.h"
#include "df/criminal_case.h"
#include "df/unit_inventory_item.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/squad_order_trainst.h"
#include <stdlib.h>
@ -67,6 +70,13 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" (humans, elves) can be put to work, but you can't assign rooms\n"
" to them and they don't show up in DwarfTherapist because the\n"
" game treats them like pets.\n"
" tweak stable-cursor [disable]\n"
" Keeps exact position of dwarfmode cursor during exits to main menu.\n"
" E.g. allows switching between t/q/k/d without losing position.\n"
" tweak patrol-duty [disable]\n"
" Causes 'Train' orders to no longer be considered 'patrol duty' so\n"
" soldiers will stop getting unhappy thoughts. Does NOT fix the problem\n"
" when soldiers go off-duty (i.e. civilian).\n"
));
return CR_OK;
}
@ -76,9 +86,6 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out)
return CR_OK;
}
static command_result lair(color_ostream &out, std::vector<std::string> & params);
// to be called by tweak-fixmigrant
// units forced into the fort by removing the flags do not own their clothes
// which has the result that they drop all their clothes and become unhappy because they are naked
@ -136,6 +143,65 @@ command_result fix_clothing_ownership(color_ostream &out, df::unit* unit)
return CR_OK;
}
/*
* Save or restore cursor position on change to/from main dwarfmode menu.
*/
static df::coord last_view, last_cursor;
struct stable_cursor_hook : df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
bool was_default = (ui->main.mode == df::ui_sidebar_mode::Default);
df::coord view = Gui::getViewportPos();
df::coord cursor = Gui::getCursorPos();
INTERPOSE_NEXT(feed)(input);
bool is_default = (ui->main.mode == df::ui_sidebar_mode::Default);
df::coord cur_cursor = Gui::getCursorPos();
if (is_default && !was_default)
{
last_view = view; last_cursor = cursor;
}
else if (!is_default && was_default &&
Gui::getViewportPos() == last_view &&
last_cursor.isValid() && cur_cursor.isValid())
{
Gui::setCursorCoords(last_cursor.x, last_cursor.y, last_cursor.z);
// Force update of ui state
set<df::interface_key> tmp;
tmp.insert(interface_key::CURSOR_DOWN_Z);
INTERPOSE_NEXT(feed)(&tmp);
tmp.clear();
tmp.insert(interface_key::CURSOR_UP_Z);
INTERPOSE_NEXT(feed)(&tmp);
}
else if (cur_cursor.isValid())
{
last_cursor = df::coord();
}
}
};
struct patrol_duty_hook : df::squad_order_trainst
{
typedef df::squad_order_trainst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, isPatrol, ())
{
return false;
}
};
IMPLEMENT_VMETHOD_INTERPOSE(stable_cursor_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(patrol_duty_hook, isPatrol);
static command_result tweak(color_ostream &out, vector <string> &parameters)
{
CoreSuspender suspend;
@ -234,6 +300,22 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
unit->profession2 = df::profession::TRADER;
return fix_clothing_ownership(out, unit);
}
else if (cmd == "stable-cursor")
{
auto &hook = INTERPOSE_HOOK(stable_cursor_hook, feed);
if (vector_get(parameters, 1) == "disable")
hook.remove();
else
hook.apply();
}
else if (cmd == "patrol-duty")
{
auto &hook = INTERPOSE_HOOK(patrol_duty_hook, isPatrol);
if (vector_get(parameters, 1) == "disable")
hook.remove();
else
hook.apply();
}
else
return CR_WRONG_USAGE;

@ -17,6 +17,6 @@ local screen = mkinstance(gui.FramedScreen, {
self:dismiss()
end
end
})
}):init()
screen:show()

@ -0,0 +1,263 @@
-- Interface front-end for liquids plugin.
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local liquids = require('plugins.liquids')
local sel_rect = df.global.selection_rect
local brushes = {
{ tag = 'range', caption = 'Rectangle', range = true },
{ tag = 'block', caption = '16x16 block' },
{ tag = 'column', caption = 'Column' },
{ tag = 'flood', caption = 'Flood' },
}
local paints = {
{ tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'w' },
{ tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'l' },
{ tag = 'obsidian', caption = 'Obsidian Wall' },
{ tag = 'obsidian_floor', caption = 'Obsidian Floor' },
{ tag = 'riversource', caption = 'River Source' },
{ tag = 'flowbits', caption = 'Flow Updates', flow = true },
{ tag = 'wclean', caption = 'Clean Salt/Stagnant' },
}
local flowbits = {
{ tag = '+', caption = 'Enable Updates' },
{ tag = '-', caption = 'Disable Updates' },
{ tag = '.', caption = 'Keep Updates' },
}
local setmode = {
{ tag = '.', caption = 'Set Exactly' },
{ tag = '+', caption = 'Only Increase' },
{ tag = '-', caption = 'Only Decrease' },
}
local permaflows = {
{ tag = '.', caption = "Keep Permaflow" },
{ tag = '-', caption = 'Remove Permaflow' },
{ tag = 'N', caption = 'Set Permaflow N' },
{ tag = 'S', caption = 'Set Permaflow S' },
{ tag = 'E', caption = 'Set Permaflow E' },
{ tag = 'W', caption = 'Set Permaflow W' },
{ tag = 'NE', caption = 'Set Permaflow NE' },
{ tag = 'NW', caption = 'Set Permaflow NW' },
{ tag = 'SE', caption = 'Set Permaflow SE' },
{ tag = 'SW', caption = 'Set Permaflow SW' },
}
Toggle = defclass(Toggle)
function Toggle:init(items)
self:init_fields{
items = items,
selected = 1
}
return self
end
function Toggle:get()
return self.items[self.selected]
end
function Toggle:render(dc)
local item = self:get()
if item then
dc:string(item.caption)
if item.key then
dc:string(" ("):string(item.key, COLOR_LIGHTGREEN):string(")")
end
else
dc:string('NONE', COLOR_RED)
end
end
function Toggle:step(delta)
if #self.items > 1 then
delta = delta or 1
self.selected = 1 + (self.selected + delta - 1) % #self.items
end
end
LiquidsUI = defclass(LiquidsUI, guidm.MenuOverlay)
LiquidsUI.focus_path = 'liquids'
function LiquidsUI:init()
self:init_fields{
brush = mkinstance(Toggle):init(brushes),
paint = mkinstance(Toggle):init(paints),
flow = mkinstance(Toggle):init(flowbits),
set = mkinstance(Toggle):init(setmode),
permaflow = mkinstance(Toggle):init(permaflows),
amount = 7,
}
guidm.MenuOverlay.init(self)
return self
end
function LiquidsUI:onDestroy()
guidm.clearSelection()
end
function render_liquid(dc, block, x, y)
local dsgn = block.designation[x%16][y%16]
if dsgn.flow_size > 0 then
if dsgn.liquid_type == df.tile_liquid.Magma then
dc:pen(COLOR_RED):string("Magma")
else
dc:pen(COLOR_BLUE)
if dsgn.water_stagnant then dc:string("Stagnant ") end
if dsgn.water_salt then dc:string("Salty ") end
dc:string("Water")
end
dc:string(" ["..dsgn.flow_size.."/7]")
else
dc:string('No Liquid')
end
end
local permaflow_abbr = {
north = 'N', south = 'S', east = 'E', west = 'W',
northeast = 'NE', northwest = 'NW', southeast = 'SE', southwest = 'SW'
}
function render_flow_state(dc, block, x, y)
local flow = block.liquid_flow[x%16][y%16]
if block.flags.update_liquid then
dc:string("Updating", COLOR_GREEN)
else
dc:string("Static")
end
dc:string(", ")
if flow.perm_flow_dir ~= 0 then
local tag = df.tile_liquid_flow_dir[flow.perm_flow_dir]
dc:string("Permaflow "..(permaflow_abbr[tag] or tag), COLOR_CYAN)
elseif flow.temp_flow_timer > 0 then
dc:string("Flowing "..flow.temp_flow_timer, COLOR_GREEN)
else
dc:string("No Flow")
end
end
function LiquidsUI:onRenderBody(dc)
dc:clear():seek(1,1):string("Paint Liquids Cheat", COLOR_WHITE)
local cursor = guidm.getCursorPos()
local block = dfhack.maps.getTileBlock(cursor)
if block then
local x, y = pos2xyz(cursor)
local tile = block.tiletype[x%16][y%16]
dc:seek(2,3):string(df.tiletype.attrs[tile].caption, COLOR_CYAN)
dc:newline(2):pen(COLOR_DARKGREY)
render_liquid(dc, block, x, y)
dc:newline(2):pen(COLOR_DARKGREY)
render_flow_state(dc, block, x, y)
else
dc:seek(2,3):string("No map data", COLOR_RED):advance(0,2)
end
dc:newline():pen(COLOR_GREY)
dc:newline(1):string("b", COLOR_LIGHTGREEN):string(": ")
self.brush:render(dc)
dc:newline(1):string("p", COLOR_LIGHTGREEN):string(": ")
self.paint:render(dc)
local paint = self.paint:get()
dc:newline()
if paint.liquid then
dc:newline(1):string("Amount: "..self.amount)
dc:advance(1):string("("):string("-+", COLOR_LIGHTGREEN):string(")")
dc:newline(3):string("s", COLOR_LIGHTGREEN):string(": ")
self.set:render(dc)
else
dc:advance(0,2)
end
dc:newline()
if paint.flow then
dc:newline(1):string("f", COLOR_LIGHTGREEN):string(": ")
self.flow:render(dc)
dc:newline(1):string("r", COLOR_LIGHTGREEN):string(": ")
self.permaflow:render(dc)
else
dc:advance(0,2)
end
dc:newline():newline(1):pen(COLOR_WHITE)
dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ")
dc:string("Enter", COLOR_LIGHTGREEN):string(": Paint")
end
function LiquidsUI:onInput(keys)
local paint = self.paint:get()
local liquid = paint.liquid
if keys.CUSTOM_B then
self.brush:step()
elseif keys.CUSTOM_P then
self.paint:step()
elseif liquid and keys.SECONDSCROLL_UP then
self.amount = math.max(0, self.amount-1)
elseif liquid and keys.SECONDSCROLL_DOWN then
self.amount = math.min(7, self.amount+1)
elseif liquid and keys.CUSTOM_S then
self.set:step()
elseif paint.flow and keys.CUSTOM_F then
self.flow:step()
elseif paint.flow and keys.CUSTOM_R then
self.permaflow:step()
elseif keys.LEAVESCREEN then
if guidm.getSelection() then
guidm.clearSelection()
return
end
self:dismiss()
self:sendInputToParent('CURSOR_DOWN_Z')
self:sendInputToParent('CURSOR_UP_Z')
elseif keys.SELECT then
local cursor = guidm.getCursorPos()
local sp = guidm.getSelection()
local size = nil
if self.brush:get().range then
if not sp then
guidm.setSelectionStart(cursor)
return
else
guidm.clearSelection()
cursor, size = guidm.getSelectionRange(cursor, sp)
end
else
guidm.clearSelection()
end
liquids.paint(
cursor,
self.brush:get().tag, self.paint:get().tag,
self.amount, size,
self.set:get().tag, self.flow:get().tag,
self.permaflow:get().tag
)
elseif self:propagateMoveKeys(keys) then
return
elseif keys.D_LOOK_ARENA_WATER then
self.paint.selected = 1
elseif keys.D_LOOK_ARENA_MAGMA then
self.paint.selected = 2
end
end
if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/LookAround') then
qerror("This script requires the main dwarfmode view in 'k' mode")
end
local list = mkinstance(LiquidsUI):init()
list:show()

@ -4,22 +4,13 @@ local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
function getBuildingName(building)
return utils.call_with_string(building, 'getName')
end
function getBuildingCenter(building)
return xyz2pos(building.centerx, building.centery, building.z)
end
function listMechanismLinks(building)
local lst = {}
local function push(item, mode)
if item then
lst[#lst+1] = {
obj = item, mode = mode,
name = getBuildingName(item),
center = getBuildingCenter(item)
name = utils.getBuildingName(item)
}
end
end
@ -52,26 +43,27 @@ MechanismList = defclass(MechanismList, guidm.MenuOverlay)
MechanismList.focus_path = 'mechanisms'
function MechanismList.new(building)
local self = {
links = {},
selected = 1
function MechanismList:init(building)
self:init_fields{
links = {}, selected = 1
}
return mkinstance(MechanismList, self):init(building)
guidm.MenuOverlay.init(self)
self:fillList(building)
return self
end
function MechanismList:init(building)
function MechanismList:fillList(building)
local links = listMechanismLinks(building)
links[1].viewport = self:getViewport()
links[1].cursor = guidm.getCursorPos()
self.old_viewport = self:getViewport()
self.old_cursor = guidm.getCursorPos()
if #links <= 1 then
links[1].mode = 'none'
end
self.links = links
self.selected = 1
return self
end
local colors = {
@ -103,22 +95,10 @@ function MechanismList:onRenderBody(dc)
dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch")
end
function MechanismList:zoomToLink(link,back)
df.global.world.selected_building = link.obj
if back then
guidm.setCursorPos(link.cursor)
self:getViewport(link.viewport):set()
else
guidm.setCursorPos(link.center)
self:getViewport():reveal(link.center, 5, 0, 10):set()
end
end
function MechanismList:changeSelected(delta)
if #self.links <= 1 then return end
self.selected = 1 + (self.selected + delta - 1) % #self.links
self:zoomToLink(self.links[self.selected])
self:selectBuilding(self.links[self.selected].obj)
end
function MechanismList:onInput(keys)
@ -129,11 +109,11 @@ function MechanismList:onInput(keys)
elseif keys.LEAVESCREEN then
self:dismiss()
if self.selected ~= 1 then
self:zoomToLink(self.links[1], true)
self:selectBuilding(self.links[1].obj, self.old_cursor, self.old_view)
end
elseif keys.SELECT_ALL then
if self.selected > 1 then
self:init(self.links[self.selected].obj)
self:fillList(self.links[self.selected].obj)
end
elseif keys.SELECT then
self:dismiss()
@ -142,13 +122,10 @@ function MechanismList:onInput(keys)
end
end
if not df.viewscreen_dwarfmodest:is_instance(dfhack.gui.getCurViewscreen()) then
qerror("This script requires the main dwarfmode view")
end
if df.global.ui.main.mode ~= df.ui_sidebar_mode.QueryBuilding then
qerror("This script requires the 'q' interface mode")
if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some' then
qerror("This script requires the main dwarfmode view in 'q' mode")
end
local list = MechanismList.new(df.global.world.selected_building)
local list = mkinstance(MechanismList):init(df.global.world.selected_building)
list:show()
list:changeSelected(1)

@ -0,0 +1,246 @@
-- Browses rooms owned by a unit.
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local room_type_table = {
[df.building_bedst] = { token = 'bed', qidx = 2, tile = 233 },
[df.building_tablest] = { token = 'table', qidx = 3, tile = 209 },
[df.building_chairst] = { token = 'chair', qidx = 4, tile = 210 },
[df.building_coffinst] = { token = 'coffin', qidx = 5, tile = 48 },
}
local room_quality_table = {
{ 1, 'Meager Quarters', 'Meager Dining Room', 'Meager Office', 'Grave' },
{ 100, 'Modest Quarters', 'Modest Dining Room', 'Modest Office', "Servant's Burial Chamber" },
{ 250, 'Quarters', 'Dining Room', 'Office', 'Burial Chamber' },
{ 500, 'Decent Quarters', 'Decent Dining Room', 'Decent Office', 'Tomb' },
{ 1000, 'Fine Quarters', 'Fine Dining Room', 'Splendid Office', 'Fine Tomb' },
{ 1500, 'Great Bedroom', 'Great Dining Room', 'Throne Room', 'Mausoleum' },
{ 2500, 'Grand Bedroom', 'Grand Dining Room', 'Opulent Throne Room', 'Grand Mausoleum' },
{ 10000, 'Royal Bedroom', 'Royal Dining Room', 'Royal Throne Room', 'Royal Mausoleum' }
}
function getRoomName(building, unit)
local info = room_type_table[building._type]
if not info or not building.is_room then
return utils.getBuildingName(building)
end
local quality = building:getRoomValue(unit)
local row = room_quality_table[1]
for _,v in ipairs(room_quality_table) do
if v[1] <= quality then
row = v
else
break
end
end
return row[info.qidx]
end
function makeRoomEntry(bld, unit, is_spouse)
local info = room_type_table[bld._type] or {}
return {
obj = bld,
token = info.token or '?',
tile = info.tile or '?',
caption = getRoomName(bld, unit),
can_use = (not is_spouse or bld:canUseSpouseRoom()),
owner = unit
}
end
function listRooms(unit, spouse)
local rv = {}
for _,v in pairs(unit.owned_buildings) do
if v.owner == unit then
rv[#rv+1] = makeRoomEntry(v, unit, spouse)
end
end
return rv
end
function concat_lists(...)
local rv = {}
for i = 1,select('#',...) do
local v = select(i,...)
if v then
for _,x in ipairs(v) do rv[#rv+1] = x end
end
end
return rv
end
RoomList = defclass(RoomList, guidm.MenuOverlay)
RoomList.focus_path = 'room-list'
function RoomList:init(unit)
local base_bld = df.global.world.selected_building
self:init_fields{
unit = unit, base_building = base_bld,
items = {}, selected = 1,
own_rooms = {}, spouse_rooms = {}
}
guidm.MenuOverlay.init(self)
self.old_viewport = self:getViewport()
self.old_cursor = guidm.getCursorPos()
if unit then
self.own_rooms = listRooms(unit)
self.spouse = df.unit.find(unit.relations.spouse_id)
if self.spouse then
self.spouse_rooms = listRooms(self.spouse, unit)
end
self.items = concat_lists(self.own_rooms, self.spouse_rooms)
end
if base_bld then
for i,v in ipairs(self.items) do
if v.obj == base_bld then
self.selected = i
v.tile = 26
goto found
end
end
self.base_item = makeRoomEntry(base_bld, unit)
self.base_item.owner = unit
self.base_item.old_owner = base_bld.owner
self.base_item.tile = 26
self.items = concat_lists({self.base_item}, self.items)
::found::
end
return self
end
local sex_char = { [0] = 12, [1] = 11 }
function drawUnitName(dc, unit)
dc:pen(COLOR_GREY)
if unit then
local color = dfhack.units.getProfessionColor(unit)
dc:char(sex_char[unit.sex] or '?'):advance(1):pen(color)
local vname = dfhack.units.getVisibleName(unit)
if vname and vname.has_name then
dc:string(dfhack.TranslateName(vname)..', ')
end
dc:string(dfhack.units.getProfessionName(unit))
else
dc:string("No Owner Assigned")
end
end
function drawRoomEntry(dc, entry, selected)
local color = COLOR_GREEN
if not entry.can_use then
color = COLOR_RED
elseif entry.obj.owner ~= entry.owner or not entry.owner then
color = COLOR_CYAN
end
dc:pen{fg = color, bold = (selected == entry)}
dc:char(entry.tile):advance(1):string(entry.caption)
end
function can_modify(sel_item)
return sel_item and sel_item.owner
and sel_item.can_use and not sel_item.owner.flags1.dead
end
function RoomList:onRenderBody(dc)
local sel_item = self.items[self.selected]
dc:clear():seek(1,1)
drawUnitName(dc, self.unit)
if self.base_item then
dc:newline():newline(2)
drawRoomEntry(dc, self.base_item, sel_item)
end
if #self.own_rooms > 0 then
dc:newline()
for _,v in ipairs(self.own_rooms) do
dc:newline(2)
drawRoomEntry(dc, v, sel_item)
end
end
if #self.spouse_rooms > 0 then
dc:newline():newline(1)
drawUnitName(dc, self.spouse)
dc:newline()
for _,v in ipairs(self.spouse_rooms) do
dc:newline(2)
drawRoomEntry(dc, v, sel_item)
end
end
if self.unit and #self.own_rooms == 0 and #self.spouse_rooms == 0 then
dc:newline():newline(2):string("No already assigned rooms.", COLOR_LIGHTRED)
end
dc:newline():newline(1):pen(COLOR_WHITE)
dc:string("Esc", COLOR_LIGHTGREEN):string(": Back")
if can_modify(sel_item) then
dc:string(", "):string("Enter", COLOR_LIGHTGREEN)
if sel_item.obj.owner == sel_item.owner then
dc:string(": Unassign")
else
dc:string(": Assign")
end
end
end
function RoomList:changeSelected(delta)
if #self.items <= 1 then return end
self.selected = 1 + (self.selected + delta - 1) % #self.items
self:selectBuilding(self.items[self.selected].obj)
end
function RoomList:onInput(keys)
local sel_item = self.items[self.selected]
if keys.SECONDSCROLL_UP then
self:changeSelected(-1)
elseif keys.SECONDSCROLL_DOWN then
self:changeSelected(1)
elseif keys.LEAVESCREEN then
self:dismiss()
if self.base_building then
if not sel_item or self.base_building ~= sel_item.obj then
self:selectBuilding(self.base_building, self.old_cursor, self.old_view)
end
if self.unit and self.base_building.owner == self.unit then
df.global.ui_building_in_assign = false
end
end
elseif keys.SELECT then
if can_modify(sel_item) then
local owner = sel_item.owner
if sel_item.obj.owner == owner then
owner = sel_item.old_owner
end
dfhack.buildings.setOwner(sel_item.obj, owner)
end
elseif self:simulateViewScroll(keys) then
return
end
end
local focus = dfhack.gui.getCurFocus()
if focus == 'dwarfmode/QueryBuilding/Some' then
local base = df.global.world.selected_building
mkinstance(RoomList):init(base.owner):show()
elseif focus == 'dwarfmode/QueryBuilding/Some/Assign/Unit' then
local unit = df.global.ui_building_assign_units[df.global.ui_building_item_cursor]
mkinstance(RoomList):init(unit):show()
else
qerror("This script requires the main dwarfmode view in 'q' mode")
end