Merge remote-tracking branch 'angavrilov/master'

develop
Kelly Martin 2012-09-22 13:07:00 -05:00
commit b0bec4c4d4
26 changed files with 3124 additions and 1987 deletions

@ -777,6 +777,10 @@ Gui module
last case, the highlighted *contained item* is returned, not last case, the highlighted *contained item* is returned, not
the container itself. the container itself.
* ``dfhack.gui.getSelectedBuilding([silent])``
Returns the building selected via *'q'*, *'t'*, *'k'* or *'i'*.
* ``dfhack.gui.showAnnouncement(text,color[,is_bright])`` * ``dfhack.gui.showAnnouncement(text,color[,is_bright])``
Adds a regular announcement with given text, color, and brightness. Adds a regular announcement with given text, color, and brightness.
@ -1463,6 +1467,14 @@ Supported callbacks and fields are:
If this method is omitted, the screen is dismissed on receival of the ``LEAVESCREEN`` key. If this method is omitted, the screen is dismissed on receival of the ``LEAVESCREEN`` key.
* ``function screen:onGetSelectedUnit()``
* ``function screen:onGetSelectedItem()``
* ``function screen:onGetSelectedJob()``
* ``function screen:onGetSelectedBuilding()``
Implement these to provide a return value for the matching
``dfhack.gui.getSelected...`` function.
Internal API Internal API
------------ ------------
@ -1819,6 +1831,86 @@ function:
argument specifies the indentation step size in spaces. For argument specifies the indentation step size in spaces. For
the other arguments see the original documentation link above. the other arguments see the original documentation link above.
class
=====
Implements a trivial single-inheritance class system.
* ``Foo = defclass(Foo[, ParentClass])``
Defines or updates class Foo. The ``Foo = defclass(Foo)`` syntax
is needed so that when the module or script is reloaded, the
class identity will be preserved through the preservation of
global variable values.
The ``defclass`` function is defined as a stub in the global
namespace, and using it will auto-load the class module.
* ``Class.super``
This class field is set by defclass to the parent class, and
allows a readable ``Class.super.method(self, ...)`` syntax for
calling superclass methods.
* ``Class.ATTRS { foo = xxx, bar = yyy }``
Declares certain instance fields to be attributes, i.e. auto-initialized
from fields in the table used as the constructor argument. If omitted,
they are initialized with the default values specified in this declaration.
If the default value should be *nil*, use ``ATTRS { foo = DEFAULT_NIL }``.
* ``new_obj = Class{ foo = arg, bar = arg, ... }``
Calling the class as a function creates and initializes a new instance.
Initialization happens in this order:
1. An empty instance table is created, and its metatable set.
2. The ``preinit`` method is called via ``invoke_before`` (see below)
with the table used as argument to the class. This method is intended
for validating and tweaking that argument table.
3. Declared ATTRS are initialized from the argument table or their default values.
4. The ``init`` method is called via ``invoke_after`` with the argument table.
This is the main constructor method.
5. The ``postinit`` method is called via ``invoke_after`` with the argument table.
Place code that should be called after the object is fully constructed here.
Predefined instance methods:
* ``instance:assign{ foo = xxx }``
Assigns all values in the input table to the matching instance fields.
* ``instance:callback(method_name, [args...])``
Returns a closure that invokes the specified method of the class,
properly passing in self, and optionally a number of initial arguments too.
The arguments given to the closure are appended to these.
* ``instance:invoke_before(method_name, args...)``
Navigates the inheritance chain of the instance starting from the most specific
class, and invokes the specified method with the arguments if it is defined in
that specific class. Equivalent to the following definition in every class::
function Class:invoke_before(method, ...)
if rawget(Class, method) then
rawget(Class, method)(self, ...)
end
Class.super.invoke_before(method, ...)
end
* ``instance:invoke_after(method_name, args...)``
Like invoke_before, only the method is called after the recursive call to super,
i.e. invocations happen in the parent to child order.
These two methods are inspired by the Common Lisp before and after methods, and
are intended for implementing similar protocols for certain things. The class
library itself uses them for constructors.
To avoid confusion, these methods cannot be redefined.
======= =======
Plugins Plugins

@ -366,14 +366,15 @@ ul.auto-toc {
<li><a class="reference internal" href="#global-environment" id="id32">Global environment</a></li> <li><a class="reference internal" href="#global-environment" id="id32">Global environment</a></li>
<li><a class="reference internal" href="#utils" id="id33">utils</a></li> <li><a class="reference internal" href="#utils" id="id33">utils</a></li>
<li><a class="reference internal" href="#dumper" id="id34">dumper</a></li> <li><a class="reference internal" href="#dumper" id="id34">dumper</a></li>
<li><a class="reference internal" href="#class" id="id35">class</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#plugins" id="id35">Plugins</a><ul> <li><a class="reference internal" href="#plugins" id="id36">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id36">burrows</a></li> <li><a class="reference internal" href="#burrows" id="id37">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id37">sort</a></li> <li><a class="reference internal" href="#sort" id="id38">sort</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#scripts" id="id38">Scripts</a></li> <li><a class="reference internal" href="#scripts" id="id39">Scripts</a></li>
</ul> </ul>
</div> </div>
<p>The current version of DFHack has extensive support for <p>The current version of DFHack has extensive support for
@ -1032,6 +1033,9 @@ a full-screen item view of a container. Note that in the
last case, the highlighted <em>contained item</em> is returned, not last case, the highlighted <em>contained item</em> is returned, not
the container itself.</p> the container itself.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.getSelectedBuilding([silent])</span></tt></p>
<p>Returns the building selected via <em>'q'</em>, <em>'t'</em>, <em>'k'</em> or <em>'i'</em>.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showAnnouncement(text,color[,is_bright])</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showAnnouncement(text,color[,is_bright])</span></tt></p>
<p>Adds a regular announcement with given text, color, and brightness. <p>Adds a regular announcement with given text, color, and brightness.
The is_bright boolean actually seems to invert the brightness.</p> The is_bright boolean actually seems to invert the brightness.</p>
@ -1608,6 +1612,16 @@ options; if multiple interpretations exist, the table will contain multiple keys
</dl> </dl>
<p>If this method is omitted, the screen is dismissed on receival of the <tt class="docutils literal">LEAVESCREEN</tt> key.</p> <p>If this method is omitted, the screen is dismissed on receival of the <tt class="docutils literal">LEAVESCREEN</tt> key.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">function screen:onGetSelectedUnit()</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onGetSelectedItem()</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onGetSelectedJob()</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">function screen:onGetSelectedBuilding()</tt></p>
<p>Implement these to provide a return value for the matching
<tt class="docutils literal"><span class="pre">dfhack.gui.getSelected...</span></tt> function.</p>
</li>
</ul> </ul>
</div> </div>
<div class="section" id="internal-api"> <div class="section" id="internal-api">
@ -1917,16 +1931,88 @@ the other arguments see the original documentation link above.</p>
</li> </li>
</ul> </ul>
</div> </div>
<div class="section" id="class">
<h2><a class="toc-backref" href="#id35">class</a></h2>
<p>Implements a trivial single-inheritance class system.</p>
<ul>
<li><p class="first"><tt class="docutils literal">Foo = defclass(Foo[, ParentClass])</tt></p>
<p>Defines or updates class Foo. The <tt class="docutils literal">Foo = defclass(Foo)</tt> syntax
is needed so that when the module or script is reloaded, the
class identity will be preserved through the preservation of
global variable values.</p>
<p>The <tt class="docutils literal">defclass</tt> function is defined as a stub in the global
namespace, and using it will auto-load the class module.</p>
</li>
<li><p class="first"><tt class="docutils literal">Class.super</tt></p>
<p>This class field is set by defclass to the parent class, and
allows a readable <tt class="docutils literal">Class.super.method(self, <span class="pre">...)</span></tt> syntax for
calling superclass methods.</p>
</li>
<li><p class="first"><tt class="docutils literal">Class.ATTRS { foo = xxx, bar = yyy }</tt></p>
<p>Declares certain instance fields to be attributes, i.e. auto-initialized
from fields in the table used as the constructor argument. If omitted,
they are initialized with the default values specified in this declaration.</p>
<p>If the default value should be <em>nil</em>, use <tt class="docutils literal">ATTRS { foo = DEFAULT_NIL }</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal">new_obj = Class{ foo = arg, bar = arg, ... }</tt></p>
<p>Calling the class as a function creates and initializes a new instance.
Initialization happens in this order:</p>
<ol class="arabic simple">
<li>An empty instance table is created, and its metatable set.</li>
<li>The <tt class="docutils literal">preinit</tt> method is called via <tt class="docutils literal">invoke_before</tt> (see below)
with the table used as argument to the class. This method is intended
for validating and tweaking that argument table.</li>
<li>Declared ATTRS are initialized from the argument table or their default values.</li>
<li>The <tt class="docutils literal">init</tt> method is called via <tt class="docutils literal">invoke_after</tt> with the argument table.
This is the main constructor method.</li>
<li>The <tt class="docutils literal">postinit</tt> method is called via <tt class="docutils literal">invoke_after</tt> with the argument table.
Place code that should be called after the object is fully constructed here.</li>
</ol>
</li>
</ul>
<p>Predefined instance methods:</p>
<ul>
<li><p class="first"><tt class="docutils literal">instance:assign{ foo = xxx }</tt></p>
<p>Assigns all values in the input table to the matching instance fields.</p>
</li>
<li><p class="first"><tt class="docutils literal">instance:callback(method_name, <span class="pre">[args...])</span></tt></p>
<p>Returns a closure that invokes the specified method of the class,
properly passing in self, and optionally a number of initial arguments too.
The arguments given to the closure are appended to these.</p>
</li>
<li><p class="first"><tt class="docutils literal">instance:invoke_before(method_name, <span class="pre">args...)</span></tt></p>
<p>Navigates the inheritance chain of the instance starting from the most specific
class, and invokes the specified method with the arguments if it is defined in
that specific class. Equivalent to the following definition in every class:</p>
<pre class="literal-block">
function Class:invoke_before(method, ...)
if rawget(Class, method) then
rawget(Class, method)(self, ...)
end
Class.super.invoke_before(method, ...)
end
</pre>
</li>
<li><p class="first"><tt class="docutils literal">instance:invoke_after(method_name, <span class="pre">args...)</span></tt></p>
<p>Like invoke_before, only the method is called after the recursive call to super,
i.e. invocations happen in the parent to child order.</p>
<p>These two methods are inspired by the Common Lisp before and after methods, and
are intended for implementing similar protocols for certain things. The class
library itself uses them for constructors.</p>
</li>
</ul>
<p>To avoid confusion, these methods cannot be redefined.</p>
</div>
</div> </div>
<div class="section" id="plugins"> <div class="section" id="plugins">
<h1><a class="toc-backref" href="#id35">Plugins</a></h1> <h1><a class="toc-backref" href="#id36">Plugins</a></h1>
<p>DFHack plugins may export native functions and events <p>DFHack plugins may export native functions and events
to lua contexts. They are automatically imported by to lua contexts. They are automatically imported by
<tt class="docutils literal"><span class="pre">mkmodule('plugins.&lt;name&gt;')</span></tt>; this means that a lua <tt class="docutils literal"><span class="pre">mkmodule('plugins.&lt;name&gt;')</span></tt>; this means that a lua
module file is still necessary for <tt class="docutils literal">require</tt> to read.</p> module file is still necessary for <tt class="docutils literal">require</tt> to read.</p>
<p>The following plugins have lua support.</p> <p>The following plugins have lua support.</p>
<div class="section" id="burrows"> <div class="section" id="burrows">
<h2><a class="toc-backref" href="#id36">burrows</a></h2> <h2><a class="toc-backref" href="#id37">burrows</a></h2>
<p>Implements extended burrow manipulations.</p> <p>Implements extended burrow manipulations.</p>
<p>Events:</p> <p>Events:</p>
<ul> <ul>
@ -1964,13 +2050,13 @@ set is the same as used by the command line.</p>
<p>The lua module file also re-exports functions from <tt class="docutils literal">dfhack.burrows</tt>.</p> <p>The lua module file also re-exports functions from <tt class="docutils literal">dfhack.burrows</tt>.</p>
</div> </div>
<div class="section" id="sort"> <div class="section" id="sort">
<h2><a class="toc-backref" href="#id37">sort</a></h2> <h2><a class="toc-backref" href="#id38">sort</a></h2>
<p>Does not export any native functions as of now. Instead, it <p>Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.</p> calls lua code to perform the actual ordering of list items.</p>
</div> </div>
</div> </div>
<div class="section" id="scripts"> <div class="section" id="scripts">
<h1><a class="toc-backref" href="#id38">Scripts</a></h1> <h1><a class="toc-backref" href="#id39">Scripts</a></h1>
<p>Any files with the .lua extension placed into hack/scripts/* <p>Any files with the .lua extension placed into hack/scripts/*
are automatically used by the DFHack core as commands. The are automatically used by the DFHack core as commands. The
matching command name consists of the name of the file sans matching command name consists of the name of the file sans

@ -30,6 +30,7 @@ DFHack v0.34.11-r2 (UNRELEASED)
New scripts: New scripts:
- fixnaked: removes thoughts about nakedness. - fixnaked: removes thoughts about nakedness.
- setfps: set FPS cap at runtime, in case you want slow motion or speed-up. - setfps: set FPS cap at runtime, in case you want slow motion or speed-up.
- siren: wakes up units, stops breaks and parties - but causes bad thoughts.
- fix/population-cap: run after every migrant wave to prevent exceeding the cap. - fix/population-cap: run after every migrant wave to prevent exceeding the cap.
- fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp. - fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp.
- fix/loyaltycascade: fix units allegiance, eg after ordering a dwarf merchant kill. - fix/loyaltycascade: fix units allegiance, eg after ordering a dwarf merchant kill.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -760,6 +760,7 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, getSelectedJob), WRAPM(Gui, getSelectedJob),
WRAPM(Gui, getSelectedUnit), WRAPM(Gui, getSelectedUnit),
WRAPM(Gui, getSelectedItem), WRAPM(Gui, getSelectedItem),
WRAPM(Gui, getSelectedBuilding),
WRAPM(Gui, showAnnouncement), WRAPM(Gui, showAnnouncement),
WRAPM(Gui, showZoomAnnouncement), WRAPM(Gui, showZoomAnnouncement),
WRAPM(Gui, showPopupAnnouncement), WRAPM(Gui, showPopupAnnouncement),

@ -1814,7 +1814,9 @@ void DFHack::Lua::Core::onUpdate(color_ostream &out)
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
run_timers(out, State, frame_timers, frame[1], ++frame_idx); run_timers(out, State, frame_timers, frame[1], ++frame_idx);
run_timers(out, State, tick_timers, frame[1], world->frame_counter);
if (world)
run_timers(out, State, tick_timers, frame[1], world->frame_counter);
} }
void DFHack::Lua::Core::Init(color_ostream &out) void DFHack::Lua::Core::Init(color_ostream &out)

@ -247,8 +247,9 @@ void VMethodInterposeLinkBase::set_chain(void *chain)
addr_to_method_pointer_(chain_mptr, chain); addr_to_method_pointer_(chain_mptr, 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, int priority)
: 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), priority(priority),
applied(false), 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)
@ -349,15 +350,26 @@ bool VMethodInterposeLinkBase::apply(bool enable)
return false; return false;
// Retrieve the current vtable entry // Retrieve the current vtable entry
void *old_ptr = host->get_vmethod_ptr(vmethod_idx);
VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx]; VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx];
VMethodInterposeLinkBase *next_link = NULL;
while (old_link && old_link->host == host && old_link->priority > priority)
{
next_link = old_link;
old_link = old_link->prev;
}
void *old_ptr = next_link ? next_link->saved_chain : host->get_vmethod_ptr(vmethod_idx);
assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr)); assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr));
// Apply the new method ptr // Apply the new method ptr
set_chain(old_ptr); set_chain(old_ptr);
if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) if (next_link)
{
next_link->set_chain(interpose_method);
}
else if (!host->set_vmethod_ptr(vmethod_idx, interpose_method))
{ {
set_chain(NULL); set_chain(NULL);
return false; return false;
@ -365,8 +377,13 @@ bool VMethodInterposeLinkBase::apply(bool enable)
// Push the current link into the home host // Push the current link into the home host
applied = true; applied = true;
host->interpose_list[vmethod_idx] = this;
prev = old_link; prev = old_link;
next = next_link;
if (next_link)
next_link->prev = this;
else
host->interpose_list[vmethod_idx] = this;
child_hosts.clear(); child_hosts.clear();
child_next.clear(); child_next.clear();
@ -374,13 +391,22 @@ bool VMethodInterposeLinkBase::apply(bool enable)
if (old_link && old_link->host == host) if (old_link && old_link->host == host)
{ {
// If the old link is home, just push into the plain chain // If the old link is home, just push into the plain chain
assert(old_link->next == NULL); assert(old_link->next == next_link);
old_link->next = this; old_link->next = this;
// Child links belong to the topmost local entry // Child links belong to the topmost local entry
child_hosts.swap(old_link->child_hosts); child_hosts.swap(old_link->child_hosts);
child_next.swap(old_link->child_next); child_next.swap(old_link->child_next);
} }
else if (next_link)
{
if (old_link)
{
assert(old_link->child_next.count(next_link));
old_link->child_next.erase(next_link);
old_link->child_next.insert(this);
}
}
else else
{ {
// If creating a new local chain, find children with same vmethod // If creating a new local chain, find children with same vmethod
@ -401,6 +427,8 @@ bool VMethodInterposeLinkBase::apply(bool enable)
} }
} }
assert (!next_link || (child_next.empty() && child_hosts.empty()));
// Chain subclass hooks // Chain subclass hooks
for (auto it = child_next.begin(); it != child_next.end(); ++it) for (auto it = child_next.begin(); it != child_next.end(); ++it)
{ {

@ -87,7 +87,8 @@ namespace DFHack
with code defined by DFHack, while retaining ability to with code defined by DFHack, while retaining ability to
call the original code. The API can be safely used from call the original code. The API can be safely used from
plugins, and multiple hooks for the same vmethod are plugins, and multiple hooks for the same vmethod are
automatically chained in undefined order. automatically chained (subclass before superclass; at same
level highest priority called first; undefined order otherwise).
Usage: Usage:
@ -105,6 +106,8 @@ namespace DFHack
}; };
IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo); IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo);
or
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority);
void init() { void init() {
if (!INTERPOSE_HOOK(my_hack, foo).apply()) if (!INTERPOSE_HOOK(my_hack, foo).apply())
@ -121,9 +124,11 @@ namespace DFHack
static DFHack::VMethodInterposeLink<interpose_base,interpose_ptr_##name> interpose_##name; \ static DFHack::VMethodInterposeLink<interpose_base,interpose_ptr_##name> interpose_##name; \
rtype interpose_fn_##name args rtype interpose_fn_##name args
#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) \ #define IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,priority) \
DFHack::VMethodInterposeLink<class::interpose_base,class::interpose_ptr_##name> \ DFHack::VMethodInterposeLink<class::interpose_base,class::interpose_ptr_##name> \
class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name); class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name, priority);
#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) IMPLEMENT_VMETHOD_INTERPOSE_PRIO(class,name,0)
#define INTERPOSE_NEXT(name) (this->*interpose_##name.chain) #define INTERPOSE_NEXT(name) (this->*interpose_##name.chain)
#define INTERPOSE_HOOK(class, name) (class::interpose_##name) #define INTERPOSE_HOOK(class, name) (class::interpose_##name)
@ -140,6 +145,7 @@ namespace DFHack
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
int priority;
bool applied; bool applied;
void *saved_chain; // Previous pointer to the code void *saved_chain; // Previous pointer to the code
@ -155,7 +161,7 @@ namespace DFHack
VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id); VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id);
void find_child_hosts(virtual_identity *cur, void *vmptr); 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, int priority);
~VMethodInterposeLinkBase(); ~VMethodInterposeLinkBase();
bool is_applied() { return applied; } bool is_applied() { return applied; }
@ -171,12 +177,13 @@ namespace DFHack
operator Ptr () { return chain; } operator Ptr () { return chain; }
template<class Ptr2> template<class Ptr2>
VMethodInterposeLink(Ptr target, Ptr2 src) VMethodInterposeLink(Ptr target, Ptr2 src, int priority)
: VMethodInterposeLinkBase( : VMethodInterposeLinkBase(
&Base::_identity, &Base::_identity,
vmethod_pointer_to_idx(target), vmethod_pointer_to_idx(target),
method_pointer_to_addr(src), method_pointer_to_addr(src),
&chain &chain,
priority
) )
{ src = target; /* check compatibility */ } { src = target; /* check compatibility */ }
}; };

@ -91,6 +91,10 @@ namespace DFHack
DFHACK_EXPORT bool any_item_hotkey(df::viewscreen *top); DFHACK_EXPORT bool any_item_hotkey(df::viewscreen *top);
DFHACK_EXPORT df::item *getSelectedItem(color_ostream &out, bool quiet = false); DFHACK_EXPORT df::item *getSelectedItem(color_ostream &out, bool quiet = false);
// A building is selected via 'q', 't' or 'i' (civzone)
DFHACK_EXPORT bool any_building_hotkey(df::viewscreen *top);
DFHACK_EXPORT df::building *getSelectedBuilding(color_ostream &out, bool quiet = false);
// Show a plain announcement, or a titan-style popup message // Show a plain announcement, or a titan-style popup message
DFHACK_EXPORT void showAnnouncement(std::string message, int color = 7, bool bright = true); DFHACK_EXPORT void showAnnouncement(std::string message, int color = 7, bool bright = true);
DFHACK_EXPORT void showZoomAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true); DFHACK_EXPORT void showZoomAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true);

@ -33,6 +33,14 @@ distribution.
#include "df/graphic.h" #include "df/graphic.h"
#include "df/viewscreen.h" #include "df/viewscreen.h"
namespace df
{
struct job;
struct item;
struct unit;
struct building;
}
/** /**
* \defgroup grp_screen utilities for painting to the screen * \defgroup grp_screen utilities for painting to the screen
* @ingroup grp_screen * @ingroup grp_screen
@ -134,6 +142,7 @@ namespace DFHack
virtual ~dfhack_viewscreen(); virtual ~dfhack_viewscreen();
static bool is_instance(df::viewscreen *screen); static bool is_instance(df::viewscreen *screen);
static dfhack_viewscreen *try_cast(df::viewscreen *screen);
virtual void logic(); virtual void logic();
virtual void render(); virtual void render();
@ -146,6 +155,10 @@ namespace DFHack
virtual std::string getFocusString() = 0; virtual std::string getFocusString() = 0;
virtual void onShow() {}; virtual void onShow() {};
virtual void onDismiss() {}; virtual void onDismiss() {};
virtual df::unit *getSelectedUnit() { return NULL; }
virtual df::item *getSelectedItem() { return NULL; }
virtual df::job *getSelectedJob() { return NULL; }
virtual df::building *getSelectedBuilding() { return NULL; }
}; };
class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen { class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen {
@ -178,5 +191,10 @@ namespace DFHack
virtual void onShow(); virtual void onShow();
virtual void onDismiss(); virtual void onDismiss();
virtual df::unit *getSelectedUnit();
virtual df::item *getSelectedItem();
virtual df::job *getSelectedJob();
virtual df::building *getSelectedBuilding();
}; };
} }

@ -53,6 +53,7 @@ using namespace DFHack;
#include "df/viewscreen_dungeon_monsterstatusst.h" #include "df/viewscreen_dungeon_monsterstatusst.h"
#include "df/viewscreen_joblistst.h" #include "df/viewscreen_joblistst.h"
#include "df/viewscreen_unitlistst.h" #include "df/viewscreen_unitlistst.h"
#include "df/viewscreen_buildinglistst.h"
#include "df/viewscreen_itemst.h" #include "df/viewscreen_itemst.h"
#include "df/viewscreen_layer.h" #include "df/viewscreen_layer.h"
#include "df/viewscreen_layer_workshop_profilest.h" #include "df/viewscreen_layer_workshop_profilest.h"
@ -691,6 +692,8 @@ df::job *Gui::getSelectedJob(color_ostream &out, bool quiet)
return job; return job;
} }
else if (auto dfscreen = dfhack_viewscreen::try_cast(top))
return dfscreen->getSelectedJob();
else else
return getSelectedWorkshopJob(out, quiet); return getSelectedWorkshopJob(out, quiet);
} }
@ -781,6 +784,9 @@ static df::unit *getAnyUnit(df::viewscreen *top)
return NULL; return NULL;
} }
if (auto dfscreen = dfhack_viewscreen::try_cast(top))
return dfscreen->getSelectedUnit();
if (!Gui::dwarfmode_hotkey(top)) if (!Gui::dwarfmode_hotkey(top))
return NULL; return NULL;
@ -875,6 +881,9 @@ static df::item *getAnyItem(df::viewscreen *top)
return NULL; return NULL;
} }
if (auto dfscreen = dfhack_viewscreen::try_cast(top))
return dfscreen->getSelectedItem();
if (!Gui::dwarfmode_hotkey(top)) if (!Gui::dwarfmode_hotkey(top))
return NULL; return NULL;
@ -933,6 +942,70 @@ df::item *Gui::getSelectedItem(color_ostream &out, bool quiet)
return item; return item;
} }
static df::building *getAnyBuilding(df::viewscreen *top)
{
using namespace ui_sidebar_mode;
using df::global::ui;
using df::global::ui_look_list;
using df::global::ui_look_cursor;
using df::global::world;
using df::global::ui_sidebar_menus;
if (auto screen = strict_virtual_cast<df::viewscreen_buildinglistst>(top))
return vector_get(screen->buildings, screen->cursor);
if (auto dfscreen = dfhack_viewscreen::try_cast(top))
return dfscreen->getSelectedBuilding();
if (!Gui::dwarfmode_hotkey(top))
return NULL;
switch (ui->main.mode) {
case LookAround:
{
if (!ui_look_list || !ui_look_cursor)
return NULL;
auto item = vector_get(ui_look_list->items, *ui_look_cursor);
if (item && item->type == df::ui_look_list::T_items::Building)
return item->building;
else
return NULL;
}
case QueryBuilding:
case BuildingItems:
{
return world->selected_building;
}
case Zones:
case ZonesPenInfo:
case ZonesPitInfo:
case ZonesHospitalInfo:
{
if (ui_sidebar_menus)
return ui_sidebar_menus->zone.selected;
return NULL;
}
default:
return NULL;
}
}
bool Gui::any_building_hotkey(df::viewscreen *top)
{
return getAnyBuilding(top) != NULL;
}
df::building *Gui::getSelectedBuilding(color_ostream &out, bool quiet)
{
df::building *building = getAnyBuilding(Core::getTopViewscreen());
if (!building && !quiet)
out.printerr("No building is selected in the UI.\n");
return building;
}
// //
static void doShowAnnouncement( static void doShowAnnouncement(

@ -50,6 +50,10 @@ using namespace DFHack;
#include "df/tile_page.h" #include "df/tile_page.h"
#include "df/interfacest.h" #include "df/interfacest.h"
#include "df/enabler.h" #include "df/enabler.h"
#include "df/unit.h"
#include "df/item.h"
#include "df/job.h"
#include "df/building.h"
using namespace df::enums; using namespace df::enums;
using df::global::init; using df::global::init;
@ -322,6 +326,11 @@ bool dfhack_viewscreen::is_instance(df::viewscreen *screen)
return dfhack_screens.count(screen) != 0; return dfhack_screens.count(screen) != 0;
} }
dfhack_viewscreen *dfhack_viewscreen::try_cast(df::viewscreen *screen)
{
return is_instance(screen) ? static_cast<dfhack_viewscreen*>(screen) : NULL;
}
void dfhack_viewscreen::check_resize() void dfhack_viewscreen::check_resize()
{ {
auto size = Screen::getWindowSize(); auto size = Screen::getWindowSize();
@ -637,3 +646,35 @@ void dfhack_lua_viewscreen::onDismiss()
lua_pushstring(Lua::Core::State, "onDismiss"); lua_pushstring(Lua::Core::State, "onDismiss");
safe_call_lua(do_notify, 1, 0); safe_call_lua(do_notify, 1, 0);
} }
df::unit *dfhack_lua_viewscreen::getSelectedUnit()
{
Lua::StackUnwinder frame(Lua::Core::State);
lua_pushstring(Lua::Core::State, "onGetSelectedUnit");
safe_call_lua(do_notify, 1, 1);
return Lua::GetDFObject<df::unit>(Lua::Core::State, -1);
}
df::item *dfhack_lua_viewscreen::getSelectedItem()
{
Lua::StackUnwinder frame(Lua::Core::State);
lua_pushstring(Lua::Core::State, "onGetSelectedItem");
safe_call_lua(do_notify, 1, 1);
return Lua::GetDFObject<df::item>(Lua::Core::State, -1);
}
df::job *dfhack_lua_viewscreen::getSelectedJob()
{
Lua::StackUnwinder frame(Lua::Core::State);
lua_pushstring(Lua::Core::State, "onGetSelectedJob");
safe_call_lua(do_notify, 1, 1);
return Lua::GetDFObject<df::job>(Lua::Core::State, -1);
}
df::building *dfhack_lua_viewscreen::getSelectedBuilding()
{
Lua::StackUnwinder frame(Lua::Core::State);
lua_pushstring(Lua::Core::State, "onGetSelectedBuilding");
safe_call_lua(do_notify, 1, 1);
return Lua::GetDFObject<df::building>(Lua::Core::State, -1);
}

@ -1 +1 @@
Subproject commit 8a78bfa218817765b0a80431e0cf25435ffb2179 Subproject commit d52c7181fb439a5fead143188d17d659d82e7f89

@ -397,7 +397,7 @@ static void enable_hooks(bool enable)
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{ {
switch (event) { switch (event) {
case SC_MAP_LOADED: case SC_WORLD_LOADED:
if (find_reactions(out)) if (find_reactions(out))
{ {
out.print("Detected spatter add reactions - enabling plugin.\n"); out.print("Detected spatter add reactions - enabling plugin.\n");
@ -406,7 +406,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
else else
enable_hooks(false); enable_hooks(false);
break; break;
case SC_MAP_UNLOADED: case SC_WORLD_UNLOADED:
enable_hooks(false); enable_hooks(false);
reactions.clear(); reactions.clear();
products.clear(); products.clear();
@ -420,8 +420,8 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{ {
if (Core::getInstance().isMapLoaded()) if (Core::getInstance().isWorldLoaded())
plugin_onstatechange(out, SC_MAP_LOADED); plugin_onstatechange(out, SC_WORLD_LOADED);
return CR_OK; return CR_OK;
} }

@ -331,6 +331,12 @@ class viewscreen_unitlaborsst : public dfhack_viewscreen {
public: public:
void feed(set<df::interface_key> *events); void feed(set<df::interface_key> *events);
void logic() {
dfhack_viewscreen::logic();
if (do_refresh_names)
refreshNames();
}
void render(); void render();
void resize(int w, int h) { calcSize(); } void resize(int w, int h) { calcSize(); }
@ -338,23 +344,27 @@ public:
std::string getFocusString() { return "unitlabors"; } std::string getFocusString() { return "unitlabors"; }
viewscreen_unitlaborsst(vector<df::unit*> &src); df::unit *getSelectedUnit();
viewscreen_unitlaborsst(vector<df::unit*> &src, int cursor_pos);
~viewscreen_unitlaborsst() { }; ~viewscreen_unitlaborsst() { };
protected: protected:
vector<UnitInfo *> units; vector<UnitInfo *> units;
altsort_mode altsort; altsort_mode altsort;
bool do_refresh_names;
int first_row, sel_row, num_rows; int first_row, sel_row, num_rows;
int first_column, sel_column; int first_column, sel_column;
int col_widths[DISP_COLUMN_MAX]; int col_widths[DISP_COLUMN_MAX];
int col_offsets[DISP_COLUMN_MAX]; int col_offsets[DISP_COLUMN_MAX];
void refreshNames();
void calcSize (); void calcSize ();
}; };
viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector<df::unit*> &src) viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector<df::unit*> &src, int cursor_pos)
{ {
for (size_t i = 0; i < src.size(); i++) for (size_t i = 0; i < src.size(); i++)
{ {
@ -375,19 +385,44 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector<df::unit*> &src)
if (!ENUM_ATTR(profession, can_assign_labor, unit->profession)) if (!ENUM_ATTR(profession, can_assign_labor, unit->profession))
cur->allowEdit = false; cur->allowEdit = false;
cur->name = Translation::TranslateName(&unit->name, false);
cur->transname = Translation::TranslateName(&unit->name, true);
cur->profession = Units::getProfessionName(unit);
cur->color = Units::getProfessionColor(unit); cur->color = Units::getProfessionColor(unit);
units.push_back(cur); units.push_back(cur);
} }
std::sort(units.begin(), units.end(), sortByName);
altsort = ALTSORT_NAME; altsort = ALTSORT_NAME;
first_row = sel_row = 0;
first_column = sel_column = 0; first_column = sel_column = 0;
refreshNames();
first_row = 0;
sel_row = cursor_pos;
calcSize(); calcSize();
// recalculate first_row to roughly match the original layout
first_row = 0;
while (first_row < sel_row - num_rows + 1)
first_row += num_rows + 2;
// make sure the selection stays visible
if (first_row > sel_row)
first_row = sel_row - num_rows + 1;
// don't scroll beyond the end
if (first_row > units.size() - num_rows)
first_row = units.size() - num_rows;
}
void viewscreen_unitlaborsst::refreshNames()
{
do_refresh_names = false;
for (size_t i = 0; i < units.size(); i++)
{
UnitInfo *cur = units[i];
df::unit *unit = cur->unit;
cur->name = Translation::TranslateName(&unit->name, false);
cur->transname = Translation::TranslateName(&unit->name, true);
cur->profession = Units::getProfessionName(unit);
}
} }
void viewscreen_unitlaborsst::calcSize() void viewscreen_unitlaborsst::calcSize()
@ -463,16 +498,25 @@ void viewscreen_unitlaborsst::calcSize()
void viewscreen_unitlaborsst::feed(set<df::interface_key> *events) void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
{ {
if (events->count(interface_key::LEAVESCREEN)) bool leave_all = events->count(interface_key::LEAVESCREEN_ALL);
if (leave_all || events->count(interface_key::LEAVESCREEN))
{ {
events->clear(); events->clear();
Screen::dismiss(this); Screen::dismiss(this);
if (leave_all)
{
events->insert(interface_key::LEAVESCREEN);
parent->feed(events);
}
return; return;
} }
if (!units.size()) if (!units.size())
return; return;
if (do_refresh_names)
refreshNames();
if (events->count(interface_key::CURSOR_UP) || events->count(interface_key::CURSOR_UPLEFT) || events->count(interface_key::CURSOR_UPRIGHT)) if (events->count(interface_key::CURSOR_UP) || events->count(interface_key::CURSOR_UPLEFT) || events->count(interface_key::CURSOR_UPRIGHT))
sel_row--; sel_row--;
if (events->count(interface_key::CURSOR_UP_FAST) || events->count(interface_key::CURSOR_UPLEFT_FAST) || events->count(interface_key::CURSOR_UPRIGHT_FAST)) if (events->count(interface_key::CURSOR_UP_FAST) || events->count(interface_key::CURSOR_UPLEFT_FAST) || events->count(interface_key::CURSOR_UPRIGHT_FAST))
@ -528,7 +572,11 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
if (first_column < sel_column - col_widths[DISP_COLUMN_LABORS] + 1) if (first_column < sel_column - col_widths[DISP_COLUMN_LABORS] + 1)
first_column = sel_column - col_widths[DISP_COLUMN_LABORS] + 1; first_column = sel_column - col_widths[DISP_COLUMN_LABORS] + 1;
// handle mouse input int input_row = sel_row;
int input_column = sel_column;
int input_sort = altsort;
// Translate mouse input to appropriate keyboard input
if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1) if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1)
{ {
int click_header = DISP_COLUMN_MAX; // group ID of the column header clicked int click_header = DISP_COLUMN_MAX; // group ID of the column header clicked
@ -560,34 +608,44 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
case DISP_COLUMN_HAPPINESS: case DISP_COLUMN_HAPPINESS:
if (enabler->mouse_lbut || enabler->mouse_rbut) if (enabler->mouse_lbut || enabler->mouse_rbut)
{ {
descending = enabler->mouse_lbut; input_sort = ALTSORT_HAPPINESS;
std::sort(units.begin(), units.end(), sortByHappiness); if (enabler->mouse_lbut)
events->insert(interface_key::SECONDSCROLL_PAGEUP);
if (enabler->mouse_rbut)
events->insert(interface_key::SECONDSCROLL_PAGEDOWN);
} }
break; break;
case DISP_COLUMN_NAME: case DISP_COLUMN_NAME:
if (enabler->mouse_lbut || enabler->mouse_rbut) if (enabler->mouse_lbut || enabler->mouse_rbut)
{ {
descending = enabler->mouse_rbut; input_sort = ALTSORT_NAME;
std::sort(units.begin(), units.end(), sortByName); if (enabler->mouse_lbut)
events->insert(interface_key::SECONDSCROLL_PAGEDOWN);
if (enabler->mouse_rbut)
events->insert(interface_key::SECONDSCROLL_PAGEUP);
} }
break; break;
case DISP_COLUMN_PROFESSION: case DISP_COLUMN_PROFESSION:
if (enabler->mouse_lbut || enabler->mouse_rbut) if (enabler->mouse_lbut || enabler->mouse_rbut)
{ {
descending = enabler->mouse_rbut; input_sort = ALTSORT_PROFESSION;
std::sort(units.begin(), units.end(), sortByProfession); if (enabler->mouse_lbut)
events->insert(interface_key::SECONDSCROLL_PAGEDOWN);
if (enabler->mouse_rbut)
events->insert(interface_key::SECONDSCROLL_PAGEUP);
} }
break; break;
case DISP_COLUMN_LABORS: case DISP_COLUMN_LABORS:
if (enabler->mouse_lbut || enabler->mouse_rbut) if (enabler->mouse_lbut || enabler->mouse_rbut)
{ {
descending = enabler->mouse_lbut; input_column = click_labor;
sort_skill = columns[click_labor].skill; if (enabler->mouse_lbut)
sort_labor = columns[click_labor].labor; events->insert(interface_key::SECONDSCROLL_UP);
std::sort(units.begin(), units.end(), sortBySkill); if (enabler->mouse_rbut)
events->insert(interface_key::SECONDSCROLL_DOWN);
} }
break; break;
} }
@ -603,12 +661,12 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
// left-click to view, right-click to zoom // left-click to view, right-click to zoom
if (enabler->mouse_lbut) if (enabler->mouse_lbut)
{ {
sel_row = click_unit; input_row = click_unit;
events->insert(interface_key::UNITJOB_VIEW); events->insert(interface_key::UNITJOB_VIEW);
} }
if (enabler->mouse_rbut) if (enabler->mouse_rbut)
{ {
sel_row = click_unit; input_row = click_unit;
events->insert(interface_key::UNITJOB_ZOOM_CRE); events->insert(interface_key::UNITJOB_ZOOM_CRE);
} }
break; break;
@ -617,21 +675,28 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
// left-click to toggle, right-click to just highlight // left-click to toggle, right-click to just highlight
if (enabler->mouse_lbut || enabler->mouse_rbut) if (enabler->mouse_lbut || enabler->mouse_rbut)
{ {
sel_row = click_unit;
sel_column = click_labor;
if (enabler->mouse_lbut) if (enabler->mouse_lbut)
{
input_row = click_unit;
input_column = click_labor;
events->insert(interface_key::SELECT); events->insert(interface_key::SELECT);
}
if (enabler->mouse_rbut)
{
sel_row = click_unit;
sel_column = click_labor;
}
} }
break; break;
} }
enabler->mouse_lbut = enabler->mouse_rbut = 0; enabler->mouse_lbut = enabler->mouse_rbut = 0;
} }
UnitInfo *cur = units[sel_row]; UnitInfo *cur = units[input_row];
if (events->count(interface_key::SELECT) && (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE)) if (events->count(interface_key::SELECT) && (cur->allowEdit) && (columns[input_column].labor != unit_labor::NONE))
{ {
df::unit *unit = cur->unit; df::unit *unit = cur->unit;
const SkillColumn &col = columns[sel_column]; const SkillColumn &col = columns[input_column];
bool newstatus = !unit->status.labors[col.labor]; bool newstatus = !unit->status.labors[col.labor];
if (col.special) if (col.special)
{ {
@ -650,7 +715,7 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
if (events->count(interface_key::SELECT_ALL) && (cur->allowEdit)) if (events->count(interface_key::SELECT_ALL) && (cur->allowEdit))
{ {
df::unit *unit = cur->unit; df::unit *unit = cur->unit;
const SkillColumn &col = columns[sel_column]; const SkillColumn &col = columns[input_column];
bool newstatus = !unit->status.labors[col.labor]; bool newstatus = !unit->status.labors[col.labor];
for (int i = 0; i < NUM_COLUMNS; i++) for (int i = 0; i < NUM_COLUMNS; i++)
{ {
@ -675,15 +740,15 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
if (events->count(interface_key::SECONDSCROLL_UP) || events->count(interface_key::SECONDSCROLL_DOWN)) if (events->count(interface_key::SECONDSCROLL_UP) || events->count(interface_key::SECONDSCROLL_DOWN))
{ {
descending = events->count(interface_key::SECONDSCROLL_UP); descending = events->count(interface_key::SECONDSCROLL_UP);
sort_skill = columns[sel_column].skill; sort_skill = columns[input_column].skill;
sort_labor = columns[sel_column].labor; sort_labor = columns[input_column].labor;
std::sort(units.begin(), units.end(), sortBySkill); std::sort(units.begin(), units.end(), sortBySkill);
} }
if (events->count(interface_key::SECONDSCROLL_PAGEUP) || events->count(interface_key::SECONDSCROLL_PAGEDOWN)) if (events->count(interface_key::SECONDSCROLL_PAGEUP) || events->count(interface_key::SECONDSCROLL_PAGEDOWN))
{ {
descending = events->count(interface_key::SECONDSCROLL_PAGEUP); descending = events->count(interface_key::SECONDSCROLL_PAGEUP);
switch (altsort) switch (input_sort)
{ {
case ALTSORT_NAME: case ALTSORT_NAME:
std::sort(units.begin(), units.end(), sortByName); std::sort(units.begin(), units.end(), sortByName);
@ -718,12 +783,14 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
{ {
for (int i = 0; i < unitlist->units[unitlist->page].size(); i++) for (int i = 0; i < unitlist->units[unitlist->page].size(); i++)
{ {
if (unitlist->units[unitlist->page][i] == units[sel_row]->unit) if (unitlist->units[unitlist->page][i] == units[input_row]->unit)
{ {
unitlist->cursor_pos[unitlist->page] = i; unitlist->cursor_pos[unitlist->page] = i;
unitlist->feed(events); unitlist->feed(events);
if (Screen::isDismissed(unitlist)) if (Screen::isDismissed(unitlist))
Screen::dismiss(this); Screen::dismiss(this);
else
do_refresh_names = true;
break; break;
} }
} }
@ -954,6 +1021,14 @@ void viewscreen_unitlaborsst::render()
} }
} }
df::unit *viewscreen_unitlaborsst::getSelectedUnit()
{
// This query might be from the rename plugin
do_refresh_names = true;
return units[sel_row]->unit;
}
struct unitlist_hook : df::viewscreen_unitlistst struct unitlist_hook : df::viewscreen_unitlistst
{ {
typedef df::viewscreen_unitlistst interpose_base; typedef df::viewscreen_unitlistst interpose_base;
@ -964,7 +1039,7 @@ struct unitlist_hook : df::viewscreen_unitlistst
{ {
if (units[page].size()) if (units[page].size())
{ {
Screen::show(new viewscreen_unitlaborsst(units[page])); Screen::show(new viewscreen_unitlaborsst(units[page], cursor_pos[page]));
return; return;
} }
} }

@ -200,7 +200,7 @@ DFHACK_PLUGIN_LUA_FUNCTIONS {
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{ {
switch (event) { switch (event) {
case SC_MAP_LOADED: case SC_WORLD_LOADED:
{ {
auto pworld = Core::getInstance().getWorld(); auto pworld = Core::getInstance().getWorld();
bool enable = pworld->GetPersistentData("power-meter/enabled").isValid(); bool enable = pworld->GetPersistentData("power-meter/enabled").isValid();
@ -212,7 +212,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
} }
} }
break; break;
case SC_MAP_UNLOADED: case SC_WORLD_UNLOADED:
enable_hooks(false); enable_hooks(false);
break; break;
default: default:
@ -224,8 +224,8 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{ {
if (Core::getInstance().isMapLoaded()) if (Core::getInstance().isWorldLoaded())
plugin_onstatechange(out, SC_MAP_LOADED); plugin_onstatechange(out, SC_WORLD_LOADED);
return CR_OK; return CR_OK;
} }

@ -10,9 +10,11 @@
#include "modules/Translation.h" #include "modules/Translation.h"
#include "modules/Units.h" #include "modules/Units.h"
#include "modules/World.h" #include "modules/World.h"
#include "modules/Screen.h"
#include <VTableInterpose.h> #include <VTableInterpose.h>
#include "df/ui.h" #include "df/ui.h"
#include "df/ui_sidebar_menus.h"
#include "df/world.h" #include "df/world.h"
#include "df/squad.h" #include "df/squad.h"
#include "df/unit.h" #include "df/unit.h"
@ -27,6 +29,8 @@
#include "df/building_furnacest.h" #include "df/building_furnacest.h"
#include "df/building_trapst.h" #include "df/building_trapst.h"
#include "df/building_siegeenginest.h" #include "df/building_siegeenginest.h"
#include "df/building_civzonest.h"
#include "df/viewscreen_dwarfmodest.h"
#include "RemoteServer.h" #include "RemoteServer.h"
#include "rename.pb.h" #include "rename.pb.h"
@ -43,6 +47,7 @@ using namespace df::enums;
using namespace dfproto; using namespace dfproto;
using df::global::ui; using df::global::ui;
using df::global::ui_sidebar_menus;
using df::global::world; using df::global::world;
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event);
@ -66,8 +71,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" (a building must be highlighted via 'q')\n" " (a building must be highlighted via 'q')\n"
)); ));
if (Core::getInstance().isMapLoaded()) if (Core::getInstance().isWorldLoaded())
plugin_onstatechange(out, SC_MAP_LOADED); plugin_onstatechange(out, SC_WORLD_LOADED);
} }
return CR_OK; return CR_OK;
@ -78,10 +83,10 @@ static void init_buildings(bool enable);
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{ {
switch (event) { switch (event) {
case SC_MAP_LOADED: case SC_WORLD_LOADED:
init_buildings(true); init_buildings(true);
break; break;
case SC_MAP_UNLOADED: case SC_WORLD_UNLOADED:
init_buildings(false); init_buildings(false);
break; break;
default: default:
@ -105,7 +110,8 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
BUILDING('w', building_workshopst, NULL) \ BUILDING('w', building_workshopst, NULL) \
BUILDING('e', building_furnacest, NULL) \ BUILDING('e', building_furnacest, NULL) \
BUILDING('T', building_trapst, NULL) \ BUILDING('T', building_trapst, NULL) \
BUILDING('i', building_siegeenginest, NULL) BUILDING('i', building_siegeenginest, NULL) \
BUILDING('Z', building_civzonest, "Zone")
#define BUILDING(code, cname, tag) \ #define BUILDING(code, cname, tag) \
struct cname##_hook : df::cname { \ struct cname##_hook : df::cname { \
@ -124,10 +130,36 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
INTERPOSE_NEXT(getName)(buf); \ INTERPOSE_NEXT(getName)(buf); \
} \ } \
}; \ }; \
IMPLEMENT_VMETHOD_INTERPOSE(cname##_hook, getName); IMPLEMENT_VMETHOD_INTERPOSE_PRIO(cname##_hook, getName, 100);
KNOWN_BUILDINGS KNOWN_BUILDINGS
#undef BUILDING #undef BUILDING
struct dwarf_render_zone_hook : df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (ui->main.mode == ui_sidebar_mode::Zones &&
ui_sidebar_menus && ui_sidebar_menus->zone.selected &&
!ui_sidebar_menus->zone.selected->name.empty())
{
auto dims = Gui::getDwarfmodeViewDims();
int width = dims.menu_x2 - dims.menu_x1 - 1;
Screen::Pen pen(' ',COLOR_WHITE);
Screen::fillRect(pen, dims.menu_x1, dims.y1+1, dims.menu_x2, dims.y1+1);
std::string name;
ui_sidebar_menus->zone.selected->getName(&name);
Screen::paintString(pen, dims.menu_x1+1, dims.y1+1, name.substr(0, width));
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(dwarf_render_zone_hook, render);
static char getBuildingCode(df::building *bld) static char getBuildingCode(df::building *bld)
{ {
CHECK_NULL_POINTER(bld); CHECK_NULL_POINTER(bld);
@ -142,6 +174,9 @@ KNOWN_BUILDINGS
static bool enable_building_rename(char code, bool enable) static bool enable_building_rename(char code, bool enable)
{ {
if (code == 'Z')
INTERPOSE_HOOK(dwarf_render_zone_hook, render).apply(enable);
switch (code) { switch (code) {
#define BUILDING(code, cname, tag) \ #define BUILDING(code, cname, tag) \
case code: return INTERPOSE_HOOK(cname##_hook, getName).apply(enable); case code: return INTERPOSE_HOOK(cname##_hook, getName).apply(enable);
@ -154,6 +189,8 @@ KNOWN_BUILDINGS
static void disable_building_rename() static void disable_building_rename()
{ {
INTERPOSE_HOOK(dwarf_render_zone_hook, render).remove();
#define BUILDING(code, cname, tag) \ #define BUILDING(code, cname, tag) \
INTERPOSE_HOOK(cname##_hook, getName).remove(); INTERPOSE_HOOK(cname##_hook, getName).remove();
KNOWN_BUILDINGS KNOWN_BUILDINGS
@ -357,10 +394,11 @@ static command_result rename(color_ostream &out, vector <string> &parameters)
if (parameters.size() != 2) if (parameters.size() != 2)
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
if (ui->main.mode != ui_sidebar_mode::QueryBuilding) df::building *bld = Gui::getSelectedBuilding(out, true);
if (!bld)
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
if (!renameBuilding(world->selected_building, parameters[1])) if (!renameBuilding(bld, parameters[1]))
{ {
out.printerr("This type of building is not supported.\n"); out.printerr("This type of building is not supported.\n");
return CR_FAILURE; return CR_FAILURE;

@ -1821,6 +1821,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
{ {
switch (event) { switch (event) {
case SC_MAP_LOADED: case SC_MAP_LOADED:
if (!gamemode || *gamemode == game_mode::DWARF)
{ {
auto pworld = Core::getInstance().getWorld(); auto pworld = Core::getInstance().getWorld();
bool enable = pworld->GetPersistentData("siege-engine/enabled").isValid(); bool enable = pworld->GetPersistentData("siege-engine/enabled").isValid();

@ -972,7 +972,7 @@ static void enable_hooks(bool enable)
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{ {
switch (event) { switch (event) {
case SC_MAP_LOADED: case SC_WORLD_LOADED:
if (find_engines()) if (find_engines())
{ {
out.print("Detected steam engine workshops - enabling plugin.\n"); out.print("Detected steam engine workshops - enabling plugin.\n");
@ -981,7 +981,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
else else
enable_hooks(false); enable_hooks(false);
break; break;
case SC_MAP_UNLOADED: case SC_WORLD_UNLOADED:
enable_hooks(false); enable_hooks(false);
engines.clear(); engines.clear();
break; break;
@ -994,8 +994,8 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{ {
if (Core::getInstance().isMapLoaded()) if (Core::getInstance().isWorldLoaded())
plugin_onstatechange(out, SC_MAP_LOADED); plugin_onstatechange(out, SC_WORLD_LOADED);
return CR_OK; return CR_OK;
} }

@ -1 +1 @@
Subproject commit 37a823541538023b9f3d0d1e8039cf32851de68d Subproject commit 2a62ba5ed2607f4dbf0473e77502d4e19c19678e

@ -803,6 +803,19 @@ end
-- cur_year_tick -- cur_year_tick
-- --
function stop_autosave()
if is_known 'd_init' then
local f = df.global.d_init.flags4
if f.AUTOSAVE_SEASONAL or f.AUTOSAVE_YEARLY then
f.AUTOSAVE_SEASONAL = false
f.AUTOSAVE_YEARLY = false
print('Disabled seasonal and yearly autosave.')
end
else
dfhack.printerr('Could not disable autosave!')
end
end
local function find_cur_year_tick() local function find_cur_year_tick()
local zone local zone
if os_type == 'windows' then if os_type == 'windows' then
@ -815,6 +828,8 @@ local function find_cur_year_tick()
return return
end end
stop_autosave()
local addr = zone:find_counter([[ local addr = zone:find_counter([[
Searching for cur_year_tick. Please exit to main dwarfmode Searching for cur_year_tick. Please exit to main dwarfmode
menu, then do as instructed below:]], menu, then do as instructed below:]],
@ -824,6 +839,79 @@ menu, then do as instructed below:]],
ms.found_offset('cur_year_tick', addr) ms.found_offset('cur_year_tick', addr)
end end
--
-- cur_season_tick
--
function step_n_frames(cnt)
local world = df.global.world
local ctick = world.frame_counter
local more = ''
while world.frame_counter-ctick < cnt do
print(" Please step the game "..(cnt-world.frame_counter+ctick)..more.." frames.")
more = ' more'
if not utils.prompt_yes_no(' Done?', true) then
return nil
end
end
return world.frame_counter-ctick
end
local function find_cur_season_tick()
if not (is_known 'cur_year_tick') then
dfhack.printerr('Cannot search for cur_season_tick - prerequisites missing.')
return
end
stop_autosave()
local addr = searcher:find_interactive([[
Searching for cur_season_tick. Please exit to main dwarfmode
menu, then do as instructed below:]],
'int32_t',
function(ccursor)
if ccursor > 0 then
if not step_n_frames(10) then
return false
end
end
return true, math.floor(((df.global.cur_year_tick+10)%100800)/10)
end
)
ms.found_offset('cur_season_tick', addr)
end
--
-- cur_season
--
local function find_cur_season()
if not (is_known 'cur_year_tick' and is_known 'cur_season_tick') then
dfhack.printerr('Cannot search for cur_season - prerequisites missing.')
return
end
stop_autosave()
local addr = searcher:find_interactive([[
Searching for cur_season. Please exit to main dwarfmode
menu, then do as instructed below:]],
'int8_t',
function(ccursor)
if ccursor > 0 then
local cst = df.global.cur_season_tick
df.global.cur_season_tick = 10079
df.global.cur_year_tick = df.global.cur_year_tick + (10079-cst)*10
if not step_n_frames(10) then
return false
end
end
return true, math.floor((df.global.cur_year_tick+10)/100800)%4
end
)
ms.found_offset('cur_season', addr)
end
-- --
-- process_jobs -- process_jobs
-- --
@ -839,6 +927,8 @@ end
local function find_process_jobs() local function find_process_jobs()
local zone = get_process_zone() or searcher local zone = get_process_zone() or searcher
stop_autosave()
local addr = zone:find_menu_cursor([[ local addr = zone:find_menu_cursor([[
Searching for process_jobs. Please do as instructed below:]], Searching for process_jobs. Please do as instructed below:]],
'int8_t', 'int8_t',
@ -856,6 +946,8 @@ end
local function find_process_dig() local function find_process_dig()
local zone = get_process_zone() or searcher local zone = get_process_zone() or searcher
stop_autosave()
local addr = zone:find_menu_cursor([[ local addr = zone:find_menu_cursor([[
Searching for process_dig. Please do as instructed below:]], Searching for process_dig. Please do as instructed below:]],
'int8_t', 'int8_t',
@ -879,6 +971,8 @@ local function find_pause_state()
end end
zone = zone or searcher zone = zone or searcher
stop_autosave()
local addr = zone:find_menu_cursor([[ local addr = zone:find_menu_cursor([[
Searching for pause_state. Please do as instructed below:]], Searching for pause_state. Please do as instructed below:]],
'int8_t', 'int8_t',
@ -930,6 +1024,8 @@ print('\nUnpausing globals:\n')
exec_finder(find_cur_year, 'cur_year') exec_finder(find_cur_year, 'cur_year')
exec_finder(find_cur_year_tick, 'cur_year_tick') exec_finder(find_cur_year_tick, 'cur_year_tick')
exec_finder(find_cur_season_tick, 'cur_season_tick')
exec_finder(find_cur_season, 'cur_season')
exec_finder(find_process_jobs, 'process_jobs') exec_finder(find_process_jobs, 'process_jobs')
exec_finder(find_process_dig, 'process_dig') exec_finder(find_process_dig, 'process_dig')
exec_finder(find_pause_state, 'pause_state') exec_finder(find_pause_state, 'pause_state')

@ -0,0 +1,178 @@
-- Injects new reaction, item and building defs into the world.
-- The savegame contains a list of the relevant definition tokens in
-- the right order, but all details are read from raws every time.
-- This allows just adding stub definitions, and simply saving and
-- reloading the game.
local utils = require 'utils'
local raws = df.global.world.raws
print[[
WARNING: THIS SCRIPT CAN PERMANENLY DAMAGE YOUR SAVE.
This script attempts to inject new raw objects into your
world. If the injected references do not match the actual
edited raws, your save will refuse to load, or load but crash.
]]
if not utils.prompt_yes_no('Did you make a backup?') then
qerror('Not backed up.')
end
df.global.pause_state = true
local changed = false
function inject_reaction(name)
for _,v in ipairs(raws.reactions) do
if v.code == name then
print('Reaction '..name..' already exists.')
return
end
end
print('Injecting reaction '..name)
changed = true
raws.reactions:insert('#', {
new = true,
code = name,
name = 'Dummy reaction '..name,
index = #raws.reactions,
})
end
local building_types = {
workshop = { df.building_def_workshopst, raws.buildings.workshops },
furnace = { df.building_def_furnacest, raws.buildings.furnaces },
}
function inject_building(btype, name)
for _,v in ipairs(raws.buildings.all) do
if v.code == name then
print('Building '..name..' already exists.')
return
end
end
print('Injecting building '..name)
changed = true
local typeinfo = building_types[btype]
local id = raws.buildings.next_id
raws.buildings.next_id = id+1
raws.buildings.all:insert('#', {
new = typeinfo[1],
code = name,
name = 'Dummy '..btype..' '..name,
id = id,
})
typeinfo[2]:insert('#', raws.buildings.all[#raws.buildings.all-1])
end
local itemdefs = raws.itemdefs
local item_types = {
weapon = { df.itemdef_weaponst, itemdefs.weapons, 'weapon_type' },
trainweapon = { df.itemdef_weaponst, itemdefs.weapons, 'training_weapon_type' },
pick = { df.itemdef_weaponst, itemdefs.weapons, 'digger_type' },
trapcomp = { df.itemdef_trapcompst, itemdefs.trapcomps, 'trapcomp_type' },
toy = { df.itemdef_toyst, itemdefs.toys, 'toy_type' },
tool = { df.itemdef_toolst, itemdefs.tools, 'tool_type' },
instrument = { df.itemdef_instrumentst, itemdefs.instruments, 'instrument_type' },
armor = { df.itemdef_armorst, itemdefs.armor, 'armor_type' },
ammo = { df.itemdef_ammost, itemdefs.ammo, 'ammo_type' },
siegeammo = { df.itemdef_siegeammost, itemdefs.siege_ammo, 'siegeammo_type' },
gloves = { df.itemdef_glovest, itemdefs.gloves, 'gloves_type' },
shoes = { df.itemdef_shoest, itemdefs.shoes, 'shoes_type' },
shield = { df.itemdef_shieldst, itemdefs.shields, 'shield_type' },
helm = { df.itemdef_helmst, itemdefs.helms, 'helm_type' },
pants = { df.itemdef_pantsst, itemdefs.pants, 'pants_type' },
food = { df.itemdef_foodst, itemdefs.food },
}
function add_to_civ(entity, bvec, id)
for _,v in ipairs(entity.resources[bvec]) do
if v == id then
return
end
end
entity.resources[bvec]:insert('#', id)
end
function add_to_dwarf_civs(btype, id)
local typeinfo = item_types[btype]
if not typeinfo[3] then
print('Not adding to civs.')
end
for _,entity in ipairs(df.global.world.entities.all) do
if entity.race == df.global.ui.race_id then
add_to_civ(entity, typeinfo[3], id)
end
end
end
function inject_item(btype, name)
for _,v in ipairs(itemdefs.all) do
if v.id == name then
print('Itemdef '..name..' already exists.')
return
end
end
print('Injecting item '..name)
changed = true
local typeinfo = item_types[btype]
local vec = typeinfo[2]
local id = #vec
vec:insert('#', {
new = typeinfo[1],
id = name,
subtype = id,
name = name,
name_plural = name,
})
itemdefs.all:insert('#', vec[id])
add_to_dwarf_civs(btype, id)
end
local args = {...}
local mode = nil
local ops = {}
for _,kv in ipairs(args) do
if mode and string.match(kv, '^[%u_]+$') then
table.insert(ops, curry(mode, kv))
elseif kv == 'reaction' then
mode = inject_reaction
elseif building_types[kv] then
mode = curry(inject_building, kv)
elseif item_types[kv] then
mode = curry(inject_item, kv)
else
qerror('Invalid option: '..kv)
end
end
if #ops > 0 then
print('')
for _,v in ipairs(ops) do
v()
end
end
if changed then
print('\nNow without unpausing save and reload the game to re-read raws.')
else
print('\nNo changes made.')
end

@ -13,10 +13,12 @@ local function verify_mode(expected)
end end
end end
if string.match(focus, '^dwarfmode/QueryBuilding/Some') then local unit = dfhack.gui.getSelectedUnit(true)
local building = dfhack.gui.getSelectedBuilding(true)
if building and (not unit or mode == 'building') then
verify_mode('building') verify_mode('building')
local building = df.global.world.selected_building
if plugin.canRenameBuilding(building) then if plugin.canRenameBuilding(building) then
dlg.showInputPrompt( dlg.showInputPrompt(
'Rename Building', 'Rename Building',
@ -30,9 +32,7 @@ if string.match(focus, '^dwarfmode/QueryBuilding/Some') then
'Cannot rename this type of building.', COLOR_LIGHTRED 'Cannot rename this type of building.', COLOR_LIGHTRED
) )
end end
elseif dfhack.gui.getSelectedUnit(true) then elseif unit then
local unit = dfhack.gui.getSelectedUnit(true)
if mode == 'unit-profession' then if mode == 'unit-profession' then
dlg.showInputPrompt( dlg.showInputPrompt(
'Rename Unit', 'Rename Unit',

@ -74,6 +74,10 @@ function SiegeEngine:onDestroy()
end end
end end
function SiegeEngine:onGetSelectedBuilding()
return df.global.world.selected_building
end
function SiegeEngine:showCursor(enable) function SiegeEngine:showCursor(enable)
local cursor = guidm.getCursorPos() local cursor = guidm.getCursorPos()
if cursor and not enable then if cursor and not enable then

@ -0,0 +1,109 @@
-- Wakes up the sleeping, breaks up parties and stops breaks.
local utils = require 'utils'
local args = {...}
local burrows = {}
local bnames = {}
if not dfhack.isMapLoaded() then
qerror('Map is not loaded.')
end
for _,v in ipairs(args) do
local b = dfhack.burrows.findByName(v)
if not b then
qerror('Unknown burrow: '..v)
end
table.insert(bnames, v)
table.insert(burrows, b)
end
function is_in_burrows(pos)
if #burrows == 0 then
return true
end
for _,v in ipairs(burrows) do
if dfhack.burrows.isAssignedTile(v, pos) then
return true
end
end
end
function add_thought(unit, code)
for _,v in ipairs(unit.status.recent_events) do
if v.type == code then
v.age = 0
return
end
end
unit.status.recent_events:insert('#', { new = true, type = code })
end
function wake_unit(unit)
local job = unit.job.current_job
if not job or job.job_type ~= df.job_type.Sleep then
return
end
if job.completion_timer > 0 then
unit.counters.unconscious = 0
add_thought(unit, df.unit_thought_type.SleepNoiseWake)
elseif job.completion_timer < 0 then
add_thought(unit, df.unit_thought_type.Tired)
end
job.pos:assign(unit.pos)
job.completion_timer = 0
unit.path.dest:assign(unit.pos)
unit.path.path.x:resize(0)
unit.path.path.y:resize(0)
unit.path.path.z:resize(0)
unit.counters.job_counter = 0
end
function stop_break(unit)
local counter = dfhack.units.getMiscTrait(unit, df.misc_trait_type.OnBreak)
if counter then
counter.id = df.misc_trait_type.TimeSinceBreak
counter.value = 100800 - 30*1200
add_thought(unit, df.unit_thought_type.Tired)
end
end
-- Stop rest
for _,v in ipairs(df.global.world.units.active) do
local x,y,z = dfhack.units.getPosition(v)
if x and not dfhack.units.isDead(v) and is_in_burrows(xyz2pos(x,y,z)) then
wake_unit(v)
stop_break(v)
end
end
-- Stop parties
for _,v in ipairs(df.global.ui.parties) do
local pos = utils.getBuildingCenter(v.location)
if is_in_burrows(pos) then
v.timer = 0
for _, u in ipairs(v.units) do
add_thought(unit, df.unit_thought_type.Tired)
end
end
end
local place = 'the halls and tunnels'
if #bnames > 0 then
if #bnames == 1 then
place = bnames[1]
else
place = table.concat(bnames,', ',1,#bnames-1)..' and '..bnames[#bnames]
end
end
dfhack.gui.showAnnouncement(
'A loud siren sounds throughout '..place..', waking the sleeping and startling the awake.',
COLOR_BROWN, true
)