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 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)`` * ``dfhack.gui.getFocusString(viewscreen)``
Returns a string representation of the current focus position Returns a string representation of the current focus position
in the ui. The string has a "screen/foo/bar/baz..." format. 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])`` * ``dfhack.gui.getSelectedWorkshopJob([silent])``
When a job is selected in *'q'* mode, returns the job, else 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. 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 Items module
------------ ------------
@ -1027,6 +1041,11 @@ Burrows module
Buildings 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)`` * ``dfhack.buildings.getSize(building)``
Returns *width, height, centerx, centery*. 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. Displays the given screen, possibly placing it below a different one.
The screen must not be already shown. Returns *true* if success. 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. 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)`` * ``dfhack.screen.isDismissed(screen)``
@ -1312,10 +1332,22 @@ Supported callbacks and fields are:
Initialized by ``show`` with a reference to the backing viewscreen Initialized by ``show`` with a reference to the backing viewscreen
object, and removed again when the object is deleted. 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()`` * ``function screen:onDestroy()``
Called from the destructor when the viewscreen is deleted. 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()`` * ``function screen:onRender()``
Called when the viewscreen should paint itself. This is the only context 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"> <div class="section" id="gui-module">
<h3><a class="toc-backref" href="#id18">Gui module</a></h3> <h3><a class="toc-backref" href="#id18">Gui module</a></h3>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.gui.getCurViewscreen()</tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.getCurViewscreen([skip_dismissed])</span></tt></p>
<p>Returns the viewscreen that is current in the core.</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>
<li><p class="first"><tt class="docutils literal">dfhack.gui.getFocusString(viewscreen)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.gui.getFocusString(viewscreen)</tt></p>
<p>Returns a string representation of the current focus position <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> in the ui. The string has a &quot;screen/foo/bar/baz...&quot; format.</p>
</li> </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> <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 <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> 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> <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> <p>Retrieves the profession name for the given race/caste using raws.</p>
</li> </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> </ul>
</div> </div>
<div class="section" id="items-module"> <div class="section" id="items-module">
@ -1233,6 +1244,10 @@ burrows, or the presence of invaders.</p>
<div class="section" id="buildings-module"> <div class="section" id="buildings-module">
<h3><a class="toc-backref" href="#id24">Buildings module</a></h3> <h3><a class="toc-backref" href="#id24">Buildings module</a></h3>
<ul> <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> <li><p class="first"><tt class="docutils literal">dfhack.buildings.getSize(building)</tt></p>
<p>Returns <em>width, height, centerx, centery</em>.</p> <p>Returns <em>width, height, centerx, centery</em>.</p>
</li> </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. <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> The screen must not be already shown. Returns <em>true</em> if success.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.screen.dismiss(screen)</tt></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.</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>
<li><p class="first"><tt class="docutils literal">dfhack.screen.isDismissed(screen)</tt></p> <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> <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 <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> object, and removed again when the object is deleted.</p>
</li> </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> <li><p class="first"><tt class="docutils literal">function screen:onDestroy()</tt></p>
<p>Called from the destructor when the viewscreen is deleted.</p> <p>Called from the destructor when the viewscreen is deleted.</p>
</li> </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> <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 <p>Called when the viewscreen should paint itself. This is the only context
where the above painting functions work correctly.</p> where the above painting functions work correctly.</p>

@ -45,3 +45,16 @@ keybinding add Shift-G "job-material GLASS_GREEN"
# browse linked mechanisms # browse linked mechanisms
keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/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! // FIXME: copypasta, push into a method!
if(df::global::ui && df::global::gview) 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) && if (strict_virtual_cast<df::viewscreen_dwarfmodest>(ws) &&
df::global::ui->main.mode != ui_sidebar_mode::Hotkeys && df::global::ui->main.mode != ui_sidebar_mode::Hotkeys &&
df::global::ui->main.hotkeys[idx].cmd == df::ui_hotkey::T_cmd::None) 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() virtual_identity::~virtual_identity()
{ {
// Remove interpose entries, so that they don't try accessing this object later // Remove interpose entries, so that they don't try accessing this object later
for (int i = interpose_list.size()-1; i >= 0; i--) for (auto it = interpose_list.begin(); it != interpose_list.end(); ++it)
interpose_list[i]->remove(); if (it->second)
it->second->on_host_delete(this);
interpose_list.clear();
} }
/* Vtable name to identity lookup. */ /* Vtable name to identity lookup. */

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

@ -186,14 +186,17 @@ Plugin::~Plugin()
bool Plugin::load(color_ostream &con) bool Plugin::load(color_ostream &con)
{ {
RefAutolock lock(access);
if(state == PS_BROKEN)
{
return false;
}
else if(state == PS_LOADED)
{ {
return true; RefAutolock lock(access);
if(state == PS_LOADED)
{
return true;
}
else if(state != PS_UNLOADED)
{
return false;
}
state = PS_LOADING;
} }
// enter suspend // enter suspend
CoreSuspender suspend; CoreSuspender suspend;
@ -202,6 +205,7 @@ bool Plugin::load(color_ostream &con)
if(!plug) if(!plug)
{ {
con.printerr("Can't load plugin %s\n", filename.c_str()); con.printerr("Can't load plugin %s\n", filename.c_str());
RefAutolock lock(access);
state = PS_BROKEN; state = PS_BROKEN;
return false; 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()); con.printerr("Plugin %s has no name or version.\n", filename.c_str());
ClosePlugin(plug); ClosePlugin(plug);
RefAutolock lock(access);
state = PS_BROKEN; state = PS_BROKEN;
return false; 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" con.printerr("Plugin %s was not built for this version of DFHack.\n"
"Plugin: %s, DFHack: %s\n", *plug_name, *plug_version, DFHACK_VERSION); "Plugin: %s, DFHack: %s\n", *plug_name, *plug_version, DFHACK_VERSION);
ClosePlugin(plug); ClosePlugin(plug);
RefAutolock lock(access);
state = PS_BROKEN; state = PS_BROKEN;
return false; return false;
} }
RefAutolock lock(access);
plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init"); plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init");
if(!plugin_init) if(!plugin_init)
{ {
@ -273,8 +280,11 @@ bool Plugin::unload(color_ostream &con)
} }
// wait for all calls to finish // wait for all calls to finish
access->wait(); access->wait();
state = PS_UNLOADING;
access->unlock();
// enter suspend // enter suspend
CoreSuspender suspend; CoreSuspender suspend;
access->lock();
// notify plugin about shutdown, if it has a shutdown function // notify plugin about shutdown, if it has a shutdown function
command_result cr = CR_OK; command_result cr = CR_OK;
if(plugin_shutdown) 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*)); 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) void VMethodInterposeLinkBase::set_chain(void *chain)
{ {
saved_chain = 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) 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), : 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) if (vmethod_idx < 0 || interpose_method == NULL)
{ {
@ -179,6 +246,75 @@ VMethodInterposeLinkBase::~VMethodInterposeLinkBase()
remove(); 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() bool VMethodInterposeLinkBase::apply()
{ {
if (is_applied()) if (is_applied())
@ -188,33 +324,73 @@ bool VMethodInterposeLinkBase::apply()
// Retrieve the current vtable entry // Retrieve the current vtable entry
void *old_ptr = host->get_vmethod_ptr(vmethod_idx); void *old_ptr = host->get_vmethod_ptr(vmethod_idx);
assert(old_ptr != NULL); VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx];
// Check if there are other interpose entries for the same slot assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr));
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;
old_link = host->interpose_list[i];
assert(old_link->next == NULL && old_ptr == old_link->interpose_method);
break;
}
// Apply the new method ptr // Apply the new method ptr
set_chain(old_ptr);
if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) if (!host->set_vmethod_ptr(vmethod_idx, interpose_method))
{
set_chain(NULL);
return false; return false;
}
set_chain(old_ptr); // Push the current link into the home host
host->interpose_list.push_back(this); applied = true;
host->interpose_list[vmethod_idx] = this;
prev = old_link;
// Link into the chain if any child_hosts.clear();
if (old_link) 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; 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; return true;
@ -225,25 +401,57 @@ void VMethodInterposeLinkBase::remove()
if (!is_applied()) if (!is_applied())
return; return;
// Remove from the list in the identity // Remove the link from prev to this
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
if (prev) if (prev)
prev->next = next; {
if (prev->host == host)
prev->next = next;
else
{
prev->child_next.erase(this);
if (next)
prev->child_next.insert(next);
}
}
if (next) if (next)
{ {
next->set_chain(saved_chain); next->set_chain(saved_chain);
next->prev = prev; next->prev = prev;
assert(child_next.empty() && child_hosts.empty());
} }
else else
{ {
// Remove from the list in the identity and vtable
host->interpose_list[vmethod_idx] = prev;
host->set_vmethod_ptr(vmethod_idx, saved_chain); host->set_vmethod_ptr(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; prev = next = NULL;
child_next.clear();
child_hosts.clear();
set_chain(NULL); set_chain(NULL);
} }

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

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

@ -134,21 +134,31 @@ namespace DFHack
1) Allow multiple hooks into the same vmethod 1) Allow multiple hooks into the same vmethod
2) Auto-remove hooks when a plugin is unloaded. 2) Auto-remove hooks when a plugin is unloaded.
*/ */
friend class virtual_identity;
virtual_identity *host; // Class with the vtable virtual_identity *host; // Class with the vtable
int vmethod_idx; int vmethod_idx;
void *interpose_method; // Pointer to the code of the interposing method void *interpose_method; // Pointer to the code of the interposing method
void *chain_mptr; // Pointer to the chain field below void *chain_mptr; // Pointer to the chain field below
bool applied;
void *saved_chain; // Previous pointer to the code void *saved_chain; // Previous pointer to the code
VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method 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 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: public:
VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr); VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr);
~VMethodInterposeLinkBase(); ~VMethodInterposeLinkBase();
bool is_applied() { return saved_chain != NULL; } bool is_applied() { return applied; }
bool apply(); bool apply();
void remove(); 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); 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. * Find the building located at the specified tile.
* Does not work on civzones. * Does not work on civzones.

@ -55,8 +55,6 @@ namespace DFHack
*/ */
namespace Gui namespace Gui
{ {
inline df::viewscreen *getCurViewscreen() { return Core::getTopViewscreen(); }
DFHACK_EXPORT std::string getFocusString(df::viewscreen *top); DFHACK_EXPORT std::string getFocusString(df::viewscreen *top);
// Full-screen item details view // Full-screen item details view
@ -99,6 +97,9 @@ namespace DFHack
/* /*
* Cursor and window coords * 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 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); DFHACK_EXPORT bool setViewCoords (const int32_t x, const int32_t y, const int32_t z);
@ -113,7 +114,11 @@ namespace DFHack
* Gui screens * Gui screens
*/ */
/// Get the current top-level view-screen /// 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 /// get the size of the window buffer
DFHACK_EXPORT bool getWindowSize(int32_t & width, int32_t & height); 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. * 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. * Returns biome world region coordinates for the given tile within given block.

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

@ -104,11 +104,21 @@ end
-- Trivial classes -- 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) function defclass(class,parent)
class = class or {} class = class or {}
rawset(class, '__index', rawget(class, '__index') or class) rawset_default(class, { __index = class })
if parent then if parent then
setmetatable(class, parent) setmetatable(class, parent)
else
rawset_default(class, { init_fields = rawset_default })
end end
return class return class
end end
@ -153,14 +163,6 @@ function xyz2pos(x,y,z)
end end
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,...) function safe_index(obj,idx,...)
if obj == nil or idx == nil then if obj == nil or idx == nil then
return nil 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 return bld.x2+1-x, bld.y2+1-y, bld.centerx-x, bld.centery-y
end end
dfhack.screen.__index = dfhack.screen
function dfhack.screen:__tostring()
return "<lua viewscreen: "..tostring(self._native)..">"
end
-- Interactive -- Interactive
local print_banner = true local print_banner = true

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

@ -3,7 +3,12 @@
local _ENV = mkmodule('gui.dwarfmode') local _ENV = mkmodule('gui.dwarfmode')
local gui = require('gui') local gui = require('gui')
local utils = require('utils')
local dscreen = dfhack.screen 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 local world_map = df.global.world.map
AREA_MAP_WIDTH = 23 AREA_MAP_WIDTH = 23
@ -41,8 +46,8 @@ function getPanelLayout()
end end
function getCursorPos() function getCursorPos()
if df.global.cursor.x ~= -30000 then if g_cursor ~= -30000 then
return copyall(df.global.cursor) return copyall(g_cursor)
end end
end end
@ -54,6 +59,51 @@ function clearCursorPos()
df.global.cursor = xyz2pos(nil) df.global.cursor = xyz2pos(nil)
end 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) Viewport = defclass(Viewport)
function Viewport.make(map,x,y,z) function Viewport.make(map,x,y,z)
@ -179,14 +229,6 @@ function DwarfOverlay:updateLayout()
self.df_layout = getPanelLayout() self.df_layout = getPanelLayout()
end end
function DwarfOverlay:onShown()
self:updateLayout()
end
function DwarfOverlay:onResize(w,h)
self:updateLayout()
end
function DwarfOverlay:getViewport(old_vp) function DwarfOverlay:getViewport(old_vp)
if old_vp then if old_vp then
return old_vp:resize(self.df_layout) return old_vp:resize(self.df_layout)
@ -195,6 +237,18 @@ function DwarfOverlay:getViewport(old_vp)
end end
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) function DwarfOverlay:propagateMoveKeys(keys)
for code,_ in pairs(MOVEMENT_KEYS) do for code,_ in pairs(MOVEMENT_KEYS) do
if keys[code] then if keys[code] then
@ -238,6 +292,14 @@ end
MenuOverlay = defclass(MenuOverlay, DwarfOverlay) 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) function MenuOverlay:onAboutToShow(below)
DwarfOverlay.onAboutToShow(self,below) DwarfOverlay.onAboutToShow(self,below)
@ -249,7 +311,6 @@ end
function MenuOverlay:onRender() function MenuOverlay:onRender()
self:renderParent() self:renderParent()
self:updateLayout()
local menu = self.df_layout.menu local menu = self.df_layout.menu
if menu then if menu then

@ -373,6 +373,14 @@ function call_with_string(obj,methodname,...)
) )
end 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 -- Ask a yes-no question
function prompt_yes_no(msg,default) function prompt_yes_no(msg,default)
local prompt = msg local prompt = msg

@ -49,6 +49,7 @@ using namespace DFHack;
#include "df/ui_look_list.h" #include "df/ui_look_list.h"
#include "df/d_init.h" #include "df/d_init.h"
#include "df/item.h" #include "df/item.h"
#include "df/unit.h"
#include "df/job.h" #include "df/job.h"
#include "df/job_item.h" #include "df/job_item.h"
#include "df/general_ref_building_holderst.h" #include "df/general_ref_building_holderst.h"
@ -177,6 +178,44 @@ bool Buildings::ReadCustomWorkshopTypes(map <uint32_t, string> & btypes)
return true; 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) df::building *Buildings::findAtTile(df::coord pos)
{ {
auto occ = Maps::getTileOccupancy(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); world->status.popups.push_back(popup);
} }
df::viewscreen * Gui::GetCurrentScreen() df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed)
{ {
df::viewscreen * ws = &gview->view; df::viewscreen * ws = &gview->view;
while(ws) while (ws && ws->child)
ws = ws->child;
if (skip_dismissed)
{ {
if(ws->child) while (ws && Screen::isDismissed(ws) && ws->parent)
ws = ws->child; ws = ws->parent;
else
return ws;
} }
return 0;
return ws;
}
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) 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/builtin_mats.h"
#include "df/block_square_event_grassst.h" #include "df/block_square_event_grassst.h"
#include "df/z_level_flags.h" #include "df/z_level_flags.h"
#include "df/region_map_entry.h"
using namespace DFHack; using namespace DFHack;
using namespace df::enums; 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; 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; auto data = world->world_data;
if (!data) if (!data)

@ -235,14 +235,26 @@ bool Screen::show(df::viewscreen *screen, df::viewscreen *before)
if (screen->child) if (screen->child)
screen->child->parent = screen; screen->child->parent = screen;
if (dfhack_viewscreen::is_instance(screen))
static_cast<dfhack_viewscreen*>(screen)->onShow();
return true; return true;
} }
void Screen::dismiss(df::viewscreen *screen) void Screen::dismiss(df::viewscreen *screen, bool to_first)
{ {
CHECK_NULL_POINTER(screen); CHECK_NULL_POINTER(screen);
screen->breakdown_level = interface_breakdown_types::STOPSCREEN; 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) 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_viewscreen::dfhack_viewscreen() : text_input_mode(false)
{ {
dfhack_screens.insert(this); dfhack_screens.insert(this);
last_size = Screen::getWindowSize();
} }
dfhack_viewscreen::~dfhack_viewscreen() 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); lua_pushlightuserdata(Lua::Core::State, keys);
safe_call_lua(do_input, 1, 0); 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(weather weather.cpp)
DFHACK_PLUGIN(colonies colonies.cpp) DFHACK_PLUGIN(colonies colonies.cpp)
DFHACK_PLUGIN(mode mode.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(tiletypes tiletypes.cpp Brushes.h)
DFHACK_PLUGIN(tubefill tubefill.cpp) DFHACK_PLUGIN(tubefill tubefill.cpp)
DFHACK_PLUGIN(autodump autodump.cpp) DFHACK_PLUGIN(autodump autodump.cpp)

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

@ -18,3 +18,6 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp)
DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp)
DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp)
DFHACK_PLUGIN(vshook vshook.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_region_details.h"
#include "df/world_geo_biome.h" #include "df/world_geo_biome.h"
#include "df/world_geo_layer.h" #include "df/world_geo_layer.h"
#include "df/region_map_entry.h"
#include "df/inclusion_type.h" #include "df/inclusion_type.h"
#include "df/viewscreen_choose_start_sitest.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.size() == 2)
{ {
if (parameters[0] == "wet") if (parameters[0] == "rai")
set_field = 0; set_field = 0;
else if (parameters[0] == "veg") else if (parameters[0] == "veg")
set_field = 1; set_field = 1;
@ -87,7 +88,7 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
set_field = 2; set_field = 2;
else if (parameters[0] == "evi") else if (parameters[0] == "evi")
set_field = 3; set_field = 3;
else if (parameters[0] == "hil") else if (parameters[0] == "dra")
set_field = 4; set_field = 4;
else if (parameters[0] == "sav") else if (parameters[0] == "sav")
set_field = 5; set_field = 5;
@ -113,11 +114,11 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
{ {
coord2d rg = screen->biome_rgn[i]; 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 && i == to_set) {
if (set_field == 0) if (set_field == 0)
rd->wetness = set_val; rd->rainfall = set_val;
else if (set_field == 1) else if (set_field == 1)
rd->vegetation = set_val; rd->vegetation = set_val;
else if (set_field == 2) else if (set_field == 2)
@ -125,11 +126,11 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
else if (set_field == 3) else if (set_field == 3)
rd->evilness = set_val; rd->evilness = set_val;
else if (set_field == 4) else if (set_field == 4)
rd->hilliness = set_val; rd->drainage = set_val;
else if (set_field == 5) else if (set_field == 5)
rd->savagery = set_val; rd->savagery = set_val;
else if (set_field == 6) else if (set_field == 6)
rd->saltiness = set_val; rd->salinity = set_val;
} }
out << i << ": x = " << rg.x << ", y = " << rg.y; 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 << " landmass_id: " << rd->landmass_id <<
" flags: " << hex << rd->flags.as_int() << dec << endl; " flags: " << hex << rd->flags.as_int() << dec << endl;
out << out <<
"wet: " << rd->wetness << " " << "rai: " << rd->rainfall << " " <<
"veg: " << rd->vegetation << " " << "veg: " << rd->vegetation << " " <<
"tem: " << rd->temperature << " " << "tem: " << rd->temperature << " " <<
"evi: " << rd->evilness << " " << "evi: " << rd->evilness << " " <<
"hil: " << rd->hilliness << " " << "dra: " << rd->drainage << " " <<
"sav: " << rd->savagery << " " << "sav: " << rd->savagery << " " <<
"sal: " << rd->saltiness; "sal: " << rd->salinity;
int32_t *p = (int32_t *)rd; int32_t *p = (int32_t *)rd;
int c = sizeof(*rd) / sizeof(int32_t); int c = sizeof(*rd) / sizeof(int32_t);

@ -27,6 +27,7 @@
#include <set> #include <set>
#include <cstdlib> #include <cstdlib>
#include <sstream> #include <sstream>
#include <memory>
using std::vector; using std::vector;
using std::string; using std::string;
using std::endl; using std::endl;
@ -41,6 +42,7 @@ using std::set;
#include "modules/Gui.h" #include "modules/Gui.h"
#include "TileTypes.h" #include "TileTypes.h"
#include "modules/MapCache.h" #include "modules/MapCache.h"
#include "LuaTools.h"
#include "Brushes.h" #include "Brushes.h"
using namespace MapExtras; using namespace MapExtras;
using namespace DFHack; using namespace DFHack;
@ -50,7 +52,6 @@ CommandHistory liquids_hist;
command_result df_liquids (color_ostream &out, vector <string> & parameters); 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_here (color_ostream &out, vector <string> & parameters);
command_result df_liquids_execute (color_ostream &out);
DFHACK_PLUGIN("liquids"); DFHACK_PLUGIN("liquids");
@ -74,13 +75,79 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK; return CR_OK;
} }
// static stuff to be remembered between sessions enum BrushType {
static string brushname = "point"; B_POINT, B_RANGE, B_BLOCK, B_COLUMN, B_FLOOD
static string mode="magma"; };
static string flowmode="f+";
static string _setmode ="s."; static const char *brush_name[] = {
static unsigned int amount = 7; "point", "range", "block", "column", "flood", NULL
static int width = 1, height = 1, z_levels = 1; };
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) 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 = ""; string input = "";
std::stringstream str; std::stringstream str;
str <<"[" << mode << ":" << brushname; print_prompt(str, cur_mode);
if (brushname == "range") str << "# ";
str << "(w" << width << ":h" << height << ":z" << z_levels << ")";
str << ":" << amount << ":" << flowmode << ":" << _setmode << "]#";
if(out.lineedit(str.str(),input,liquids_hist) == -1) if(out.lineedit(str.str(),input,liquids_hist) == -1)
return CR_FAILURE; return CR_FAILURE;
liquids_hist.add(input); 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+ - make the spawned liquid flow" << endl
<< "f. - don't change flow state (read state in flow mode)" << endl << "f. - don't change flow state (read state in flow mode)" << endl
<< "f- - make the spawned liquid static" << 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 << "0-7 - set liquid amount" << endl
<< "Brush:" << endl << "Brush:" << endl
<< "point - single tile [p]" << endl << "point - single tile [p]" << endl
@ -168,38 +237,39 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
} }
else if(command == "m") else if(command == "m")
{ {
mode = "magma"; cur_mode.paint = P_MAGMA;
} }
else if(command == "o") else if(command == "o")
{ {
mode = "obsidian"; cur_mode.paint = P_OBSIDIAN;
} }
else if(command == "of") else if(command == "of")
{ {
mode = "obsidian_floor"; cur_mode.paint = P_OBSIDIAN_FLOOR;
} }
else if(command == "w") else if(command == "w")
{ {
mode = "water"; cur_mode.paint = P_WATER;
} }
else if(command == "f") else if(command == "f")
{ {
mode = "flowbits"; cur_mode.paint = P_FLOW_BITS;
} }
else if(command == "rs") else if(command == "rs")
{ {
mode = "riversource"; cur_mode.paint = P_RIVER_SOURCE;
} }
else if(command == "wclean") else if(command == "wclean")
{ {
mode = "wclean"; cur_mode.paint = P_WCLEAN;
} }
else if(command == "point" || command == "p") else if(command == "point" || command == "p")
{ {
brushname = "point"; cur_mode.brush = B_POINT;
} }
else if(command == "range" || command == "r") else if(command == "range" || command == "r")
{ {
int width, height, z_levels;
command_result res = parseRectangle(out, commands, 1, commands.size(), command_result res = parseRectangle(out, commands, 1, commands.size(),
width, height, z_levels); width, height, z_levels);
if (res != CR_OK) 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) if (width == 1 && height == 1 && z_levels == 1)
{ {
brushname = "point"; cur_mode.brush = B_POINT;
cur_mode.size = df::coord(1, 1, 1);
} }
else else
{ {
brushname = "range"; cur_mode.brush = B_RANGE;
cur_mode.size = df::coord(width, height, z_levels);
} }
} }
else if(command == "block") else if(command == "block")
{ {
brushname = "block"; cur_mode.brush = B_BLOCK;
} }
else if(command == "column") else if(command == "column")
{ {
brushname = "column"; cur_mode.brush = B_COLUMN;
} }
else if(command == "flood") else if(command == "flood")
{ {
brushname = "flood"; cur_mode.brush = B_FLOOD;
} }
else if(command == "q") else if(command == "q")
{ {
@ -234,45 +306,59 @@ command_result df_liquids (color_ostream &out_, vector <string> & parameters)
} }
else if(command == "f+") else if(command == "f+")
{ {
flowmode = "f+"; cur_mode.flowmode = M_INC;
} }
else if(command == "f-") else if(command == "f-")
{ {
flowmode = "f-"; cur_mode.flowmode = M_DEC;
} }
else if(command == "f.") else if(command == "f.")
{ {
flowmode = "f."; cur_mode.flowmode = M_KEEP;
} }
else if(command == "s+") else if(command == "s+")
{ {
_setmode = "s+"; cur_mode.setmode = M_INC;
} }
else if(command == "s-") else if(command == "s-")
{ {
_setmode = "s-"; cur_mode.setmode = M_DEC;
} }
else if(command == "s.") 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. // blah blah, bad code, bite me.
else if(command == "0") else if(command == "0")
amount = 0; cur_mode.amount = 0;
else if(command == "1") else if(command == "1")
amount = 1; cur_mode.amount = 1;
else if(command == "2") else if(command == "2")
amount = 2; cur_mode.amount = 2;
else if(command == "3") else if(command == "3")
amount = 3; cur_mode.amount = 3;
else if(command == "4") else if(command == "4")
amount = 4; cur_mode.amount = 4;
else if(command == "5") else if(command == "5")
amount = 5; cur_mode.amount = 5;
else if(command == "6") else if(command == "6")
amount = 6; cur_mode.amount = 6;
else if(command == "7") else if(command == "7")
amount = 7; cur_mode.amount = 7;
else if(command.empty()) else if(command.empty())
{ {
df_liquids_execute(out); 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.print("Run liquids-here with these parameters: ");
out << "[" << mode << ":" << brushname; print_prompt(out, cur_mode);
if (brushname == "range") out << endl;
out << "(w" << width << ":h" << height << ":z" << z_levels << ")";
out << ":" << amount << ":" << flowmode << ":" << _setmode << "]\n";
return df_liquids_execute(out); return df_liquids_execute(out);
} }
command_result df_liquids_execute(color_ostream &out) command_result df_liquids_execute(color_ostream &out)
{ {
// create brush type depending on old parameters CoreSuspender suspend;
Brush * brush;
if (brushname == "point") auto cursor = Gui::getCursorPos();
if (!cursor.isValid())
{ {
brush = new RectangleBrush(1,1,1,0,0,0); out.printerr("Can't get cursor coords! Make sure you have a cursor active in DF.\n");
//width = 1; return CR_WRONG_USAGE;
//height = 1;
//z_levels = 1;
} }
else if (brushname == "range")
{ auto rv = df_liquids_execute(out, cur_mode, cursor);
brush = new RectangleBrush(width,height,z_levels,0,0,0); if (rv == CR_OK)
} out << "OK" << endl;
else if(brushname == "block") return rv;
}
command_result df_liquids_execute(color_ostream &out, OperationMode &cur_mode, df::coord cursor)
{
// 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(); brush = new BlockBrush();
} break;
else if(brushname == "column") case B_COLUMN:
{
brush = new ColumnBrush(); brush = new ColumnBrush();
} break;
else if(brushname == "flood") case B_FLOOD:
{
brush = new FloodBrush(&Core::getInstance()); brush = new FloodBrush(&Core::getInstance());
} break;
else default:
{
// this should never happen! // this should never happen!
out << "Old brushtype is invalid! Resetting to point brush.\n"; out << "Old brushtype is invalid! Resetting to point brush.\n";
brushname = "point"; cur_mode.brush = B_POINT;
width = 1; brush = new RectangleBrush(1,1,1,0,0,0);
height = 1;
z_levels = 1;
brush = new RectangleBrush(width,height,z_levels,0,0,0);
} }
CoreSuspender suspend; std::auto_ptr<Brush> brush_ref(brush);
do if (!Maps::IsValid())
{ {
if (!Maps::IsValid()) out << "Can't see any DF map loaded." << endl;
{ return CR_FAILURE;
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;
}
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 MapCache mcache;
df::global::world->reindex_pathfinding = true; coord_vec all_tiles = brush->points(mcache,cursor);
if(mode == "obsidian") // Force the game to recompute its walkability cache
df::global::world->reindex_pathfinding = true;
switch (cur_mode.paint)
{
case P_OBSIDIAN:
{ {
coord_vec::iterator iter = all_tiles.begin(); coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end()) while (iter != all_tiles.end())
@ -383,8 +466,9 @@ command_result df_liquids_execute(color_ostream &out)
mcache.setDesignationAt(*iter, des); mcache.setDesignationAt(*iter, des);
iter ++; iter ++;
} }
break;
} }
if(mode == "obsidian_floor") case P_OBSIDIAN_FLOOR:
{ {
coord_vec::iterator iter = all_tiles.begin(); coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end()) while (iter != all_tiles.end())
@ -392,8 +476,9 @@ command_result df_liquids_execute(color_ostream &out)
mcache.setTiletypeAt(*iter, findRandomVariant(tiletype::LavaFloor1)); mcache.setTiletypeAt(*iter, findRandomVariant(tiletype::LavaFloor1));
iter ++; iter ++;
} }
break;
} }
else if(mode == "riversource") case P_RIVER_SOURCE:
{ {
coord_vec::iterator iter = all_tiles.begin(); coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end()) while (iter != all_tiles.end())
@ -413,8 +498,9 @@ command_result df_liquids_execute(color_ostream &out)
iter++; iter++;
} }
break;
} }
else if(mode=="wclean") case P_WCLEAN:
{ {
coord_vec::iterator iter = all_tiles.begin(); coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end()) while (iter != all_tiles.end())
@ -426,8 +512,11 @@ command_result df_liquids_execute(color_ostream &out)
mcache.setDesignationAt(current,des); mcache.setDesignationAt(current,des);
iter++; iter++;
} }
break;
} }
else if(mode== "magma" || mode== "water" || mode == "flowbits") case P_MAGMA:
case P_WATER:
case P_FLOW_BITS:
{ {
set <Block *> seen_blocks; set <Block *> seen_blocks;
coord_vec::iterator iter = all_tiles.begin(); coord_vec::iterator iter = all_tiles.begin();
@ -442,6 +531,7 @@ command_result df_liquids_execute(color_ostream &out)
iter ++; iter ++;
continue; continue;
} }
auto raw_block = block->getRaw();
df::tile_designation des = mcache.designationAt(current); df::tile_designation des = mcache.designationAt(current);
df::tiletype tt = mcache.tiletypeAt(current); df::tiletype tt = mcache.tiletypeAt(current);
// don't put liquids into places where they don't belong... // don't put liquids into places where they don't belong...
@ -450,30 +540,29 @@ command_result df_liquids_execute(color_ostream &out)
iter++; iter++;
continue; continue;
} }
if(mode != "flowbits") if(cur_mode.paint != P_FLOW_BITS)
{ {
unsigned old_amount = des.bits.flow_size; unsigned old_amount = des.bits.flow_size;
unsigned new_amount = old_amount; unsigned new_amount = old_amount;
df::tile_liquid old_liquid = des.bits.liquid_type; df::tile_liquid old_liquid = des.bits.liquid_type;
df::tile_liquid new_liquid = old_liquid; df::tile_liquid new_liquid = old_liquid;
// Compute new liquid type and amount // Compute new liquid type and amount
if(_setmode == "s.") switch (cur_mode.setmode)
{
new_amount = amount;
}
else if(_setmode == "s+")
{
if(old_amount < amount)
new_amount = amount;
}
else if(_setmode == "s-")
{ {
if (old_amount > amount) case M_KEEP:
new_amount = amount; 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; new_liquid = tile_liquid::Magma;
else if (mode == "water") else if (cur_mode.paint == P_WATER)
new_liquid = tile_liquid::Water; new_liquid = tile_liquid::Water;
// Store new amount and type // Store new amount and type
des.bits.flow_size = new_amount; des.bits.flow_size = new_amount;
@ -502,40 +591,77 @@ command_result df_liquids_execute(color_ostream &out)
// request flow engine updates // request flow engine updates
block->enableBlockUpdates(new_amount != old_amount, new_liquid != old_liquid); 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); seen_blocks.insert(block);
iter++; iter++;
} }
set <Block *>::iterator biter = seen_blocks.begin(); set <Block *>::iterator biter = seen_blocks.begin();
while (biter != seen_blocks.end()) while (biter != seen_blocks.end())
{ {
if(flowmode == "f+") switch (cur_mode.flowmode)
{ {
case M_INC:
(*biter)->enableBlockUpdates(true); (*biter)->enableBlockUpdates(true);
} break;
else if(flowmode == "f-") case M_DEC:
{
if (auto block = (*biter)->getRaw()) if (auto block = (*biter)->getRaw())
{ {
block->flags.bits.update_liquid = false; block->flags.bits.update_liquid = false;
block->flags.bits.update_liquid_twice = false; block->flags.bits.update_liquid_twice = false;
} }
} break;
else case M_KEEP:
{ {
auto bflags = (*biter)->BlockFlags(); auto bflags = (*biter)->BlockFlags();
out << "flow bit 1 = " << bflags.bits.update_liquid << endl; out << "flow bit 1 = " << bflags.bits.update_liquid << endl;
out << "flow bit 2 = " << bflags.bits.update_liquid_twice << endl; out << "flow bit 2 = " << bflags.bits.update_liquid_twice << endl;
}
} }
biter ++; biter ++;
} }
break;
} }
if(mcache.WriteAll()) }
out << "OK" << endl;
else if(!mcache.WriteAll())
out << "Something failed horribly! RUN!" << endl; {
} while (0); out << "Something failed horribly! RUN!" << endl;
return CR_FAILURE;
}
// cleanup
delete brush;
return CR_OK; 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 <vector>
#include <string> #include <string>
#include <set> #include <set>
#include <algorithm>
#include <VTableInterpose.h> #include <VTableInterpose.h>
#include "df/world.h" #include "df/world.h"
@ -41,36 +42,36 @@ DFHACK_PLUGIN("manipulator");
struct SkillLevel struct SkillLevel
{ {
const char *name; const char *name;
int points; int points;
char abbrev; char abbrev;
}; };
#define NUM_SKILL_LEVELS (sizeof(skill_levels) / sizeof(SkillLevel)) #define NUM_SKILL_LEVELS (sizeof(skill_levels) / sizeof(SkillLevel))
// The various skill rankings. Zero skill is hardcoded to "Not" and '-'. // The various skill rankings. Zero skill is hardcoded to "Not" and '-'.
const SkillLevel skill_levels[] = { const SkillLevel skill_levels[] = {
{"Dabbling", 500, '0'}, {"Dabbling", 500, '0'},
{"Novice", 600, '1'}, {"Novice", 600, '1'},
{"Adequate", 700, '2'}, {"Adequate", 700, '2'},
{"Competent", 800, '3'}, {"Competent", 800, '3'},
{"Skilled", 900, '4'}, {"Skilled", 900, '4'},
{"Proficient", 1000, '5'}, {"Proficient", 1000, '5'},
{"Talented", 1100, '6'}, {"Talented", 1100, '6'},
{"Adept", 1200, '7'}, {"Adept", 1200, '7'},
{"Expert", 1300, '8'}, {"Expert", 1300, '8'},
{"Professional",1400, '9'}, {"Professional",1400, '9'},
{"Accomplished",1500, 'A'}, {"Accomplished",1500, 'A'},
{"Great", 1600, 'B'}, {"Great", 1600, 'B'},
{"Master", 1700, 'C'}, {"Master", 1700, 'C'},
{"High Master", 1800, 'D'}, {"High Master", 1800, 'D'},
{"Grand Master",1900, 'E'}, {"Grand Master",1900, 'E'},
{"Legendary", 2000, 'U'}, {"Legendary", 2000, 'U'},
{"Legendary+1", 2100, 'V'}, {"Legendary+1", 2100, 'V'},
{"Legendary+2", 2200, 'W'}, {"Legendary+2", 2200, 'W'},
{"Legendary+3", 2300, 'X'}, {"Legendary+3", 2300, 'X'},
{"Legendary+4", 2400, 'Y'}, {"Legendary+4", 2400, 'Y'},
{"Legendary+5", 0, 'Z'} {"Legendary+5", 0, 'Z'}
}; };
struct SkillColumn struct SkillColumn
@ -82,7 +83,7 @@ struct SkillColumn
bool special; // specified labor is mutually exclusive with all other special labors bool special; // specified labor is mutually exclusive with all other special labors
}; };
#define NUM_COLUMNS (sizeof(columns) / sizeof(SkillColumn)) #define NUM_COLUMNS (sizeof(columns) / sizeof(SkillColumn))
// All of the skill/labor columns we want to display. Includes profession (for color), labor, skill, and 2 character label // All of the skill/labor columns we want to display. Includes profession (for color), labor, skill, and 2 character label
const SkillColumn columns[] = { const SkillColumn columns[] = {
@ -247,17 +248,69 @@ struct UnitInfo
int8_t color; int8_t color;
}; };
#define FILTER_NONWORKERS 0x0001 enum altsort_mode {
#define FILTER_NONDWARVES 0x0002 ALTSORT_NAME,
#define FILTER_NONCIV 0x0004 ALTSORT_PROFESSION,
#define FILTER_ANIMALS 0x0008 ALTSORT_MAX
#define FILTER_LIVING 0x0010 };
#define FILTER_DEAD 0x0020
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 { class viewscreen_unitlaborsst : public dfhack_viewscreen {
public: public:
static viewscreen_unitlaborsst *create (char pushtype, df::viewscreen *scr = NULL);
void feed(set<df::interface_key> *events); void feed(set<df::interface_key> *events);
void render(); void render();
@ -267,86 +320,41 @@ public:
std::string getFocusString() { return "unitlabors"; } std::string getFocusString() { return "unitlabors"; }
viewscreen_unitlaborsst(); viewscreen_unitlaborsst(vector<df::unit*> &src);
~viewscreen_unitlaborsst() { }; ~viewscreen_unitlaborsst() { };
protected: protected:
vector<UnitInfo *> units; vector<UnitInfo *> units;
int filter; altsort_mode altsort;
int first_row, sel_row; int first_row, sel_row;
int first_column, sel_column; int first_column, sel_column;
int height, name_width, prof_width, labors_width; int height, name_width, prof_width, labors_width;
// bool descending;
// int sort_skill;
// int sort_labor;
void readUnits ();
void calcSize (); void calcSize ();
}; };
viewscreen_unitlaborsst::viewscreen_unitlaborsst() viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector<df::unit*> &src)
{ {
filter = FILTER_LIVING; for (size_t i = 0; i < src.size(); i++)
first_row = sel_row = 0;
first_column = sel_column = 0;
calcSize();
readUnits();
}
void viewscreen_unitlaborsst::readUnits ()
{
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]; UnitInfo *cur = new UnitInfo;
df::unit *unit = src[i];
cur->unit = unit; cur->unit = unit;
cur->allowEdit = true; cur->allowEdit = true;
if (unit->race != ui->race_id) if (unit->race != ui->race_id)
{
cur->allowEdit = false; cur->allowEdit = false;
if (!(filter & FILTER_NONDWARVES))
continue;
}
if (unit->civ_id != ui->civ_id) if (unit->civ_id != ui->civ_id)
{
cur->allowEdit = false; cur->allowEdit = false;
if (!(filter & FILTER_NONCIV))
continue;
}
if (unit->flags1.bits.dead) if (unit->flags1.bits.dead)
{
cur->allowEdit = false; cur->allowEdit = false;
if (!(filter & FILTER_DEAD))
continue;
}
else
{
if (!(filter & FILTER_LIVING))
continue;
}
if (!ENUM_ATTR(profession, can_assign_labor, unit->profession)) if (!ENUM_ATTR(profession, can_assign_labor, unit->profession))
{
cur->allowEdit = false; 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->name = Translation::TranslateName(&unit->name, false);
cur->transname = Translation::TranslateName(&unit->name, true); cur->transname = Translation::TranslateName(&unit->name, true);
@ -354,9 +362,13 @@ void viewscreen_unitlaborsst::readUnits ()
cur->color = Units::getProfessionColor(unit); cur->color = Units::getProfessionColor(unit);
units.push_back(cur); 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() void viewscreen_unitlaborsst::calcSize()
@ -421,8 +433,6 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
return; return;
} }
// TODO - allow modifying filters
if (!units.size()) if (!units.size())
return; return;
@ -501,9 +511,64 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
unit->status.labors[col.labor] = !unit->status.labors[col.labor]; 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() void viewscreen_unitlaborsst::render()
{ {
if (Screen::isDismissed(this)) if (Screen::isDismissed(this))
@ -528,6 +593,7 @@ void viewscreen_unitlaborsst::render()
fg = 0; fg = 0;
bg = 7; 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[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); 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; int row_offset = row + first_row;
if (row_offset >= units.size()) if (row_offset >= units.size())
break; break;
UnitInfo *cur = units[row_offset]; UnitInfo *cur = units[row_offset];
df::unit *unit = cur->unit; df::unit *unit = cur->unit;
int8_t fg = 15, bg = 0; int8_t fg = 15, bg = 0;
@ -554,7 +621,8 @@ void viewscreen_unitlaborsst::render()
profession.resize(prof_width); profession.resize(prof_width);
fg = cur->color; fg = cur->color;
bg = 0; 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 // Print unit's skills and labor assignments
for (int col = 0; col < labors_width; col++) for (int col = 0; col < labors_width; col++)
@ -562,11 +630,9 @@ void viewscreen_unitlaborsst::render()
int col_offset = col + first_column; int col_offset = col + first_column;
fg = 15; fg = 15;
bg = 0; bg = 0;
char c = 0xFA;
if ((col_offset == sel_column) && (row_offset == sel_row)) if ((col_offset == sel_column) && (row_offset == sel_row))
fg = 9; 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) 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); 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; level = NUM_SKILL_LEVELS - 1;
c = skill_levels[level].abbrev; 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); 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) if (cur != NULL)
{ {
df::unit *unit = cur->unit; df::unit *unit = cur->unit;
string str = cur->transname; int x = 1;
if (str.length()) Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->transname);
str += ", "; x += cur->transname.length();
str += cur->profession;
str += ":";
Screen::paintString(Screen::Pen(' ', 15, 0), 1, 3 + height + 2, str); if (cur->transname.length())
int y = 1 + str.length() + 1; {
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) if (columns[sel_column].skill == job_skill::NONE)
{ {
str = ENUM_ATTR_STR(unit_labor, caption, columns[sel_column].labor); str = ENUM_ATTR_STR(unit_labor, caption, columns[sel_column].labor);
@ -602,7 +687,6 @@ void viewscreen_unitlaborsst::render()
str += " Enabled"; str += " Enabled";
else else
str += " Not Enabled"; str += " Not Enabled";
Screen::paintString(Screen::Pen(' ', 9, 0), y, 3 + height + 2, str);
} }
else else
{ {
@ -618,11 +702,45 @@ void viewscreen_unitlaborsst::render()
} }
else else
str = stl_sprintf("Not %s (0/500)", ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill)); 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 struct unitlist_hook : df::viewscreen_unitlistst
@ -633,9 +751,11 @@ struct unitlist_hook : df::viewscreen_unitlistst
{ {
if (input->count(interface_key::UNITVIEW_PRF_PROF)) if (input->count(interface_key::UNITVIEW_PRF_PROF))
{ {
Screen::dismiss(this); if (units[page].size())
Screen::show(new viewscreen_unitlaborsst()); {
return; Screen::show(new viewscreen_unitlaborsst(units[page]));
return;
}
} }
INTERPOSE_NEXT(feed)(input); INTERPOSE_NEXT(feed)(input);
} }

@ -27,6 +27,7 @@ using namespace std;
#include "df/world.h" #include "df/world.h"
#include "df/world_raws.h" #include "df/world_raws.h"
#include "df/building_def.h" #include "df/building_def.h"
#include "df/region_map_entry.h"
using std::vector; using std::vector;
using std::string; 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 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); 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 = auto biome = &world->world_data->region_map[bx][by];
&world->world_data->region_map[bx][by];
int sav = biome->savagery; int sav = biome->savagery;
int evi = biome->evilness; int evi = biome->evilness;

@ -25,8 +25,12 @@ using namespace std;
#include "df/world.h" #include "df/world.h"
#include "df/world_data.h" #include "df/world_data.h"
#include "df/world_region_details.h" #include "df/world_region_details.h"
#include "df/world_region_feature.h"
#include "df/world_geo_biome.h" #include "df/world_geo_biome.h"
#include "df/world_geo_layer.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/inclusion_type.h"
#include "df/viewscreen_choose_start_sitest.h" #include "df/viewscreen_choose_start_sitest.h"
@ -108,9 +112,10 @@ 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)
{ {
con << std::setw(9) << data.count; if (!only_z)
con << std::setw(9) << data.count;
if(data.lower_z != data.upper_z) if(data.lower_z != data.upper_z)
con <<" Z:" << std::setw(4) << data.lower_z << ".." << data.upper_z << std::endl; con <<" Z:" << std::setw(4) << data.lower_z << ".." << data.upper_z << std::endl;
@ -225,116 +230,247 @@ static coord2d biome_delta[] = {
coord2d(-1,-1), coord2d(0,-1), coord2d(1,-1) coord2d(-1,-1), coord2d(0,-1), coord2d(1,-1)
}; };
static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen, struct EmbarkTileLayout {
bool showHidden, bool showValue) 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)
{ {
if (!world || !world->world_data) // 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;
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++)
{ {
out.printerr("World data is not available.\n"); auto feature = features[i];
return CR_FAILURE; 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 (penalty != 1.0f)
{
for (int i = feature->min_z; i <= feature->max_z; i++)
tile.penalty[i] = penalty;
}
} }
df::world_data *data = world->world_data; if (!sea_found)
coord2d cur_region = screen->region_pos; {
int d_idx = linear_index(data->region_details, &df::world_region_details::pos, cur_region); out.printerr("Could not find magma sea.\n");
auto cur_details = vector_get(data->region_details, d_idx); return false;
}
if (!cur_details) // Scan for big local features and apply their penalties
for (size_t i = 0; i < features.size(); i++)
{ {
out.printerr("Current region details are not available.\n"); auto feature = features[i];
return CR_FAILURE; auto lfeature = Maps::getLocalInitFeature(details->pos, feature->feature_idx);
if (!lfeature)
continue;
switch (lfeature->getType())
{
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;
}
} }
// Compute biomes return true;
std::map<coord2d, int> biomes; }
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));
}
bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &layerMats, MatMap &veinMats)
{
using namespace geo_layer_type;
df::world_geo_biome *geo_biome = df::world_geo_biome::find(tile.biome->geo_index);
if (screen->biome_highlighted) if (!geo_biome)
{ {
out.print("Processing one embark tile of biome F%d.\n\n", screen->biome_idx+1); out.printerr("Region geo-biome not found: (%d,%d)\n",
biomes[screen->biome_rgn[screen->biome_idx]]++; tile.biome_pos.x, tile.biome_pos.y);
return false;
} }
else
// 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++)
{ {
for (int x = screen->embark_pos_min.x; x <= screen->embark_pos_max.x; x++) auto layer = geo_biome->layers[i];
switch (layer->type)
{ {
for (int y = screen->embark_pos_min.y; y <= screen->embark_pos_max.y; y++) case SOIL:
{ case SOIL_OCEAN:
int bv = clip_range(cur_details->biome[x][y], 1, 9); case SOIL_SAND:
biomes[cur_region + biome_delta[bv-1]]++; top_z_level += layer->top_height - layer->bottom_height + 1;
} break;
default:;
} }
} }
// Compute material maps top_z_level = std::max(top_z_level, tile.elevation)-1;
MatMap layerMats;
MatMap veinMats;
for (auto biome_it = biomes.begin(); biome_it != biomes.end(); ++biome_it) for (unsigned i = 0; i < geo_biome->layers.size(); i++)
{ {
int bx = clip_range(biome_it->first.x, 0, data->world_width-1); auto layer = geo_biome->layers[i];
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);
if (!geo_biome) 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);
out.printerr("Region geo-biome not found: (%d,%d)\n", bx, by); if (i+1 == geo_biome->layers.size()) // stretch layer if needed
return CR_FAILURE; 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 };
int cnt = biome_it->second; for (unsigned j = 0; j < layer->vein_mat.size(); j++)
if (is_valid_enum_item<df::inclusion_type>(layer->vein_type[j]))
sums[layer->vein_type[j]] += layer->vein_unk_38[j];
for (unsigned i = 0; i < geo_biome->layers.size(); i++) for (unsigned j = 0; j < layer->vein_mat.size(); j++)
{ {
auto layer = geo_biome->layers[i]; // 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
float size = float(layer->vein_unk_38[j]);
df::inclusion_type type = layer->vein_type[j];
layerMats[layer->mat_index].add(layer->bottom_height, 0); switch (type)
{
case inclusion_type::VEIN:
// 3 veins of 80 tiles avg
size = size * 80 * 3 / sums[type];
break;
case inclusion_type::CLUSTER:
// 1 cluster of 700 tiles avg
size = size * 700 * 1 / sums[type];
break;
case inclusion_type::CLUSTER_SMALL:
size = size * 6 * 7 / sums[type];
break;
case inclusion_type::CLUSTER_ONE:
size = size * 1 * 5 / sums[type];
break;
default:
// shouldn't actually happen
size = 1;
}
int level_cnt = layer->top_height - layer->bottom_height + 1; layer_size -= size;
int layer_size = 48*48*cnt*level_cnt;
int sums[ENUM_LAST_ITEM(inclusion_type)+1] = { 0 }; add_materials(tile, veinMats[layer->vein_mat[j]], size, bottom_z, top_z);
}
for (unsigned j = 0; j < layer->vein_mat.size(); j++) add_materials(tile, layerMats[layer->mat_index], layer_size, bottom_z, top_z);
if (is_valid_enum_item<df::inclusion_type>(layer->vein_type[j])) }
sums[layer->vein_type[j]] += layer->vein_unk_38[j];
for (unsigned j = 0; j < layer->vein_mat.size(); j++) return true;
{ }
// 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;
df::inclusion_type type = layer->vein_type[j];
switch (type) static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen,
{ bool showHidden, bool showValue)
case inclusion_type::VEIN: {
// 3 veins of 80 tiles avg if (!world || !world->world_data)
size = size * 80 * 3 / sums[type]; {
break; out.printerr("World data is not available.\n");
case inclusion_type::CLUSTER: return CR_FAILURE;
// 1 cluster of 700 tiles avg }
size = size * 700 * 1 / sums[type];
break; df::world_data *data = world->world_data;
case inclusion_type::CLUSTER_SMALL: coord2d cur_region = screen->region_pos;
size = size * 6 * 7 / sums[type]; int d_idx = linear_index(data->region_details, &df::world_region_details::pos, cur_region);
break; auto cur_details = vector_get(data->region_details, d_idx);
case inclusion_type::CLUSTER_ONE:
size = size * 1 * 5 / sums[type];
break;
default:
// shouldn't actually happen
size = cnt*level_cnt;
}
veinMats[layer->vein_mat[j]].add(layer->bottom_height, 0); if (!cur_details)
veinMats[layer->vein_mat[j]].add(layer->top_height, size); {
out.printerr("Current region details are not available.\n");
return CR_FAILURE;
}
layer_size -= size; // Compute material maps
} MatMap layerMats;
MatMap veinMats;
matdata world_bottom;
layerMats[layer->mat_index].add(layer->top_height, std::max(0,layer_size)); // 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(); 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; return CR_OK;
} }
@ -536,6 +675,8 @@ command_result prospector (color_ostream &con, vector <string> & parameters)
case tiletype_material::LAVA_STONE: case tiletype_material::LAVA_STONE:
// TODO ? // TODO ?
break; 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 p df.world.raws.plants.all[plant.mat_index].id
Dig a channel under the cursor Dig a channel under the cursor
df.map_designation_at(df.cursor).dig = :Channel df.map_tile_at(df.cursor).dig(:Channel)
df.map_block_at(df.cursor).flags.designated = true
Spawn 2/7 magma on the tile of the dwarf nicknamed 'hotfeet' Spawn 2/7 magma on the tile of the dwarf nicknamed 'hotfeet'
hot = df.unit_citizens.find { |u| u.name.nickname == 'hotfeet' } hot = df.unit_citizens.find { |u| u.name.nickname == 'hotfeet' }

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

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

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

@ -4,6 +4,7 @@
#include "Export.h" #include "Export.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "VersionInfo.h" #include "VersionInfo.h"
#include "MemAccess.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "df/global_objects.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 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 // stl::string
static VALUE rb_dfmemory_stlstring_new(VALUE self) 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_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_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_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_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); rb_define_singleton_method(rb_cDFHack, "memory_stlstring_delete", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_delete), 1);

@ -41,48 +41,60 @@ module DFHack
# returns an Array of all units that are current fort citizen (dwarves, on map, not hostile) # returns an Array of all units that are current fort citizen (dwarves, on map, not hostile)
def unit_citizens def unit_citizens
race = ui.race_id
civ = ui.civ_id
world.units.active.find_all { |u| 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)
!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 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) # list workers (citizen, not crazy / child / inmood / noble)
def unit_workers def unit_workers
unit_citizens.find_all { |u| world.units.active.find_all { |u|
u.mood == :None and unit_isworker(u)
u.profession != :CHILD and
u.profession != :BABY and
# TODO MENIAL_WORK_EXEMPTION_SPOUSE
!unit_entitypositions(u).find { |pos| pos.flags[:MENIAL_WORK_EXEMPTION] }
} }
end 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 # list currently idle workers
def unit_idlers def unit_idlers
unit_workers.find_all { |u| world.units.active.find_all { |u|
# current_job includes eat/drink/sleep/pickupequip unit_isidler(u)
!u.job.current_job and
# filter 'attend meeting'
not u.specific_refs.find { |s| s.type == :ACTIVITY } and
# filter soldiers (TODO check schedule)
u.military.squad_index == -1 and
# filter 'on break'
not u.status.misc_traits.find { |t| t.id == :OnBreak }
} }
end end
def unit_isidler(u)
unit_isworker(u) and
# current_job includes eat/drink/sleep/pickupequip
!u.job.current_job and
# filter 'attend meeting'
not u.specific_refs.find { |s| s.type == :ACTIVITY } and
# filter soldiers (TODO check schedule)
u.military.squad_index == -1 and
# filter 'on break'
not u.status.misc_traits.find { |t| t.id == :OnBreak }
end
def unit_entitypositions(unit) def unit_entitypositions(unit)
list = [] 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| hf.entity_links.each { |el|
next if el._rtti_classname != :histfig_entity_link_positionst 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 pa = ent.positions.assignments.binsearch(el.assignment_id)
next if not pos = ent.positions.own.binsearch(pa.position_id) next if not pos = ent.positions.own.binsearch(pa.position_id)
list << pos list << pos

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

@ -13,6 +13,7 @@
#include "MiscUtils.h" #include "MiscUtils.h"
#include "DataDefs.h" #include "DataDefs.h"
#include <VTableInterpose.h>
#include "df/ui.h" #include "df/ui.h"
#include "df/world.h" #include "df/world.h"
#include "df/squad.h" #include "df/squad.h"
@ -26,6 +27,8 @@
#include "df/death_info.h" #include "df/death_info.h"
#include "df/criminal_case.h" #include "df/criminal_case.h"
#include "df/unit_inventory_item.h" #include "df/unit_inventory_item.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/squad_order_trainst.h"
#include <stdlib.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" " (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" " to them and they don't show up in DwarfTherapist because the\n"
" game treats them like pets.\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; return CR_OK;
} }
@ -76,9 +86,6 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out)
return CR_OK; return CR_OK;
} }
static command_result lair(color_ostream &out, std::vector<std::string> & params);
// to be called by tweak-fixmigrant // to be called by tweak-fixmigrant
// units forced into the fort by removing the flags do not own their clothes // 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 // 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; 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) static command_result tweak(color_ostream &out, vector <string> &parameters)
{ {
CoreSuspender suspend; CoreSuspender suspend;
@ -234,6 +300,22 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
unit->profession2 = df::profession::TRADER; unit->profession2 = df::profession::TRADER;
return fix_clothing_ownership(out, unit); 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 else
return CR_WRONG_USAGE; return CR_WRONG_USAGE;

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