diff --git a/LUA_API.rst b/LUA_API.rst
index 76f6454f4..a7dab21b0 100644
--- a/LUA_API.rst
+++ b/LUA_API.rst
@@ -724,15 +724,20 @@ can be omitted.
Gui module
----------
-* ``dfhack.gui.getCurViewscreen()``
+* ``dfhack.gui.getCurViewscreen([skip_dismissed])``
- Returns the viewscreen that is current in the core.
+ Returns the topmost viewscreen. If ``skip_dismissed`` is *true*,
+ ignores screens already marked to be removed.
* ``dfhack.gui.getFocusString(viewscreen)``
Returns a string representation of the current focus position
in the ui. The string has a "screen/foo/bar/baz..." format.
+* ``dfhack.gui.getCurFocus([skip_dismissed])``
+
+ Returns the focus string of the current viewscreen.
+
* ``dfhack.gui.getSelectedWorkshopJob([silent])``
When a job is selected in *'q'* mode, returns the job, else
@@ -875,6 +880,15 @@ Units module
Retrieves the profession name for the given race/caste using raws.
+* ``dfhack.units.getProfessionColor(unit[,ignore_noble])``
+
+ Retrieves the color associated with the profession, using noble assignments
+ or raws. The ``ignore_noble`` boolean disables the use of noble positions.
+
+* ``dfhack.units.getCasteProfessionColor(race,caste,prof_id)``
+
+ Retrieves the profession color for the given race/caste using raws.
+
Items module
------------
@@ -1027,6 +1041,11 @@ Burrows module
Buildings module
----------------
+* ``dfhack.buildings.setOwner(item,unit)``
+
+ Replaces the owner of the building. If unit is *nil*, removes ownership.
+ Returns *false* in case of error.
+
* ``dfhack.buildings.getSize(building)``
Returns *width, height, centerx, centery*.
@@ -1291,9 +1310,10 @@ Screens are managed with the following functions:
Displays the given screen, possibly placing it below a different one.
The screen must not be already shown. Returns *true* if success.
-* ``dfhack.screen.dismiss(screen)``
+* ``dfhack.screen.dismiss(screen[,to_first])``
Marks the screen to be removed when the game enters its event loop.
+ If ``to_first`` is *true*, all screens up to the first one will be deleted.
* ``dfhack.screen.isDismissed(screen)``
@@ -1312,10 +1332,22 @@ Supported callbacks and fields are:
Initialized by ``show`` with a reference to the backing viewscreen
object, and removed again when the object is deleted.
+* ``function screen:onShow()``
+
+ Called by ``dfhack.screen.show`` if successful.
+
+* ``function screen:onDismiss()``
+
+ Called by ``dfhack.screen.dismiss`` if successful.
+
* ``function screen:onDestroy()``
Called from the destructor when the viewscreen is deleted.
+* ``function screen:onResize(w, h)``
+
+ Called before ``onRender`` or ``onIdle`` when the window size has changed.
+
* ``function screen:onRender()``
Called when the viewscreen should paint itself. This is the only context
diff --git a/Lua API.html b/Lua API.html
index c4ab9c8c4..b9f09cf96 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -987,13 +987,17 @@ can be omitted.
-dfhack.gui.getCurViewscreen()
-Returns the viewscreen that is current in the core.
+dfhack.gui.getCurViewscreen([skip_dismissed])
+Returns the topmost viewscreen. If skip_dismissed is true,
+ignores screens already marked to be removed.
dfhack.gui.getFocusString(viewscreen)
Returns a string representation of the current focus position
in the ui. The string has a "screen/foo/bar/baz..." format.
+dfhack.gui.getCurFocus([skip_dismissed])
+Returns the focus string of the current viewscreen.
+
dfhack.gui.getSelectedWorkshopJob([silent])
When a job is selected in 'q' mode, returns the job, else
prints error unless silent and returns nil.
@@ -1109,6 +1113,13 @@ or raws. The ignore_noble boolean disables the
dfhack.units.getCasteProfessionName(race,caste,prof_id[,plural])
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.
+
@@ -1233,6 +1244,10 @@ burrows, or the presence of invaders.
+dfhack.buildings.setOwner(item,unit)
+Replaces the owner of the building. If unit is nil, removes ownership.
+Returns false in case of error.
+
dfhack.buildings.getSize(building)
Returns width, height, centerx, centery.
@@ -1465,8 +1480,9 @@ interface screens added by dfhack should bear the "DFHack" signature.<
Displays the given screen, possibly placing it below a different one.
The screen must not be already shown. Returns true if success.
-dfhack.screen.dismiss(screen)
-Marks the screen to be removed when the game enters its event loop.
+dfhack.screen.dismiss(screen[,to_first])
+Marks the screen to be removed when the game enters its event loop.
+If to_first is true, all screens up to the first one will be deleted.
dfhack.screen.isDismissed(screen)
Checks if the screen is already marked for removal.
@@ -1482,9 +1498,18 @@ that delegates all processing to methods stored in that table.
Initialized by show with a reference to the backing viewscreen
object, and removed again when the object is deleted.
+function screen:onShow()
+Called by dfhack.screen.show if successful.
+
+function screen:onDismiss()
+Called by dfhack.screen.dismiss if successful.
+
function screen:onDestroy()
Called from the destructor when the viewscreen is deleted.
+function screen:onResize(w, h)
+Called before onRender or onIdle when the window size has changed.
+
function screen:onRender()
Called when the viewscreen should paint itself. This is the only context
where the above painting functions work correctly.
diff --git a/dfhack.init-example b/dfhack.init-example
index f5f40196c..380bdd04f 100644
--- a/dfhack.init-example
+++ b/dfhack.init-example
@@ -45,3 +45,16 @@ keybinding add Shift-G "job-material GLASS_GREEN"
# browse linked mechanisms
keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms
+
+# browse rooms of same owner
+keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work
+
+# interface for the liquids plugin
+keybinding add Alt-L@dwarfmode/LookAround gui/liquids
+
+###################
+# UI logic tweaks #
+###################
+
+# stabilize the cursor of dwarfmode when switching menus
+tweak stable-cursor
diff --git a/library/Core.cpp b/library/Core.cpp
index a61fef4e9..6a0dea7c2 100644
--- a/library/Core.cpp
+++ b/library/Core.cpp
@@ -1260,7 +1260,7 @@ bool Core::ncurses_wgetch(int in, int & out)
// FIXME: copypasta, push into a method!
if(df::global::ui && df::global::gview)
{
- df::viewscreen * ws = Gui::GetCurrentScreen();
+ df::viewscreen * ws = Gui::getCurViewscreen();
if (strict_virtual_cast(ws) &&
df::global::ui->main.mode != ui_sidebar_mode::Hotkeys &&
df::global::ui->main.hotkeys[idx].cmd == df::ui_hotkey::T_cmd::None)
diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp
index d6604cdb3..341164441 100644
--- a/library/DataDefs.cpp
+++ b/library/DataDefs.cpp
@@ -218,8 +218,10 @@ virtual_identity::virtual_identity(size_t size, TAllocateFn alloc,
virtual_identity::~virtual_identity()
{
// Remove interpose entries, so that they don't try accessing this object later
- for (int i = interpose_list.size()-1; i >= 0; i--)
- interpose_list[i]->remove();
+ for (auto it = interpose_list.begin(); it != interpose_list.end(); ++it)
+ if (it->second)
+ it->second->on_host_delete(this);
+ interpose_list.clear();
}
/* Vtable name to identity lookup. */
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index d25da8087..6dfb2f354 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -77,6 +77,7 @@ distribution.
#include "df/job_material_category.h"
#include "df/burrow.h"
#include "df/building_civzonest.h"
+#include "df/region_map_entry.h"
#include
#include
@@ -749,6 +750,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = {
static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, getCurViewscreen),
WRAPM(Gui, getFocusString),
+ WRAPM(Gui, getCurFocus),
WRAPM(Gui, getSelectedWorkshopJob),
WRAPM(Gui, getSelectedJob),
WRAPM(Gui, getSelectedUnit),
@@ -814,6 +816,8 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getAge),
WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName),
+ WRAPM(Units, getProfessionColor),
+ WRAPM(Units, getCasteProfessionColor),
{ NULL, NULL }
};
@@ -928,8 +932,7 @@ static int maps_getRegionBiome(lua_State *L)
static int maps_getTileBiomeRgn(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
- Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos));
- return 1;
+ return Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos));
}
static const luaL_Reg dfhack_maps_funcs[] = {
@@ -984,6 +987,7 @@ static bool buildings_containsTile(df::building *bld, int x, int y, bool room) {
}
static const LuaWrapper::FunctionReg dfhack_buildings_module[] = {
+ WRAPM(Buildings, setOwner),
WRAPM(Buildings, allocInstance),
WRAPM(Buildings, checkFreeTiles),
WRAPM(Buildings, countExtentTiles),
diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp
index d8b9ff27d..ceb644e60 100644
--- a/library/PluginManager.cpp
+++ b/library/PluginManager.cpp
@@ -186,14 +186,17 @@ Plugin::~Plugin()
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
CoreSuspender suspend;
@@ -202,6 +205,7 @@ bool Plugin::load(color_ostream &con)
if(!plug)
{
con.printerr("Can't load plugin %s\n", filename.c_str());
+ RefAutolock lock(access);
state = PS_BROKEN;
return false;
}
@@ -211,6 +215,7 @@ bool Plugin::load(color_ostream &con)
{
con.printerr("Plugin %s has no name or version.\n", filename.c_str());
ClosePlugin(plug);
+ RefAutolock lock(access);
state = PS_BROKEN;
return false;
}
@@ -219,9 +224,11 @@ bool Plugin::load(color_ostream &con)
con.printerr("Plugin %s was not built for this version of DFHack.\n"
"Plugin: %s, DFHack: %s\n", *plug_name, *plug_version, DFHACK_VERSION);
ClosePlugin(plug);
+ RefAutolock lock(access);
state = PS_BROKEN;
return false;
}
+ RefAutolock lock(access);
plugin_init = (command_result (*)(color_ostream &, std::vector &)) LookupPlugin(plug, "plugin_init");
if(!plugin_init)
{
@@ -273,8 +280,11 @@ bool Plugin::unload(color_ostream &con)
}
// wait for all calls to finish
access->wait();
+ state = PS_UNLOADING;
+ access->unlock();
// enter suspend
CoreSuspender suspend;
+ access->lock();
// notify plugin about shutdown, if it has a shutdown function
command_result cr = CR_OK;
if(plugin_shutdown)
diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp
index 3725ccba7..04c436ba7 100644
--- a/library/VTableInterpose.cpp
+++ b/library/VTableInterpose.cpp
@@ -154,6 +154,73 @@ bool virtual_identity::set_vmethod_ptr(int idx, void *ptr)
return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*));
}
+/*
+ VMethod interposing data structures.
+
+ In order to properly support adding and removing hooks,
+ it is necessary to track them. This is what this class
+ is for. The task is further complicated by propagating
+ hooks to child classes that use exactly the same original
+ vmethod implementation.
+
+ Every applied link contains in the saved_chain field a
+ pointer to the next vmethod body that should be called
+ by the hook the link represents. This is the actual
+ control flow structure that needs to be maintained.
+
+ There also are connections between link objects themselves,
+ which constitute the bookkeeping for doing that. Finally,
+ every link is associated with a fixed virtual_identity host,
+ which represents the point in the class hierarchy where
+ the hook is applied.
+
+ When there are no subclasses (i.e. only one host), the
+ structures look like this:
+
+ +--------------+ +------------+
+ | link1 |-next------->| link2 |-next=NULL
+ |s_c: original |<-------prev-|s_c: $link1 |<--+
+ +--------------+ +------------+ |
+ |
+ host->interpose_list[vmethod_idx] ------+
+ vtable: $link2
+
+ The original vtable entry is stored in the saved_chain of the
+ first link. The interpose_list map points to the last one.
+ The hooks are called in order: link2 -> link1 -> original.
+
+ When there are subclasses that use the same vmethod, but don't
+ hook it, the topmost link gets a set of the child_hosts, and
+ the hosts have the link added to their interpose_list:
+
+ +--------------+ +----------------+
+ | link0 @host0 |<--+-interpose_list-| host1 |
+ | |-child_hosts-+----->| vtable: $link |
+ +--------------+ | | +----------------+
+ | |
+ | | +----------------+
+ +-interpose_list-| host2 |
+ +----->| vtable: $link |
+ +----------------+
+
+ When a child defines its own hook, the child_hosts link is
+ severed and replaced with a child_next pointer to the new
+ hook. The hook still points back the chain with prev.
+ All child links to subclasses of host2 are migrated from
+ link1 to link2.
+
+ +--------------+-next=NULL +--------------+-next=NULL
+ | link1 @host1 |-child_next------->| link2 @host2 |-child_*--->subclasses
+ | |<-------------prev-|s_c: $link1 |
+ +--------------+<-------+ +--------------+<-------+
+ | |
+ +--------------+ | +--------------+ |
+ | host1 |-i_list-+ | host2 |-i_list-+
+ |vtable: $link1| |vtable: $link2|
+ +--------------+ +--------------+
+
+ */
+
void VMethodInterposeLinkBase::set_chain(void *chain)
{
saved_chain = chain;
@@ -162,7 +229,7 @@ void VMethodInterposeLinkBase::set_chain(void *chain)
VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr)
: host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method), chain_mptr(chain_mptr),
- saved_chain(NULL), next(NULL), prev(NULL)
+ applied(false), saved_chain(NULL), next(NULL), prev(NULL)
{
if (vmethod_idx < 0 || interpose_method == NULL)
{
@@ -179,6 +246,75 @@ VMethodInterposeLinkBase::~VMethodInterposeLinkBase()
remove();
}
+VMethodInterposeLinkBase *VMethodInterposeLinkBase::get_first_interpose(virtual_identity *id)
+{
+ auto item = id->interpose_list[vmethod_idx];
+ if (!item)
+ return NULL;
+
+ if (item->host != id)
+ return NULL;
+ while (item->prev && item->prev->host == id)
+ item = item->prev;
+
+ return item;
+}
+
+void VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr)
+{
+ auto &children = cur->getChildren();
+
+ for (size_t i = 0; i < children.size(); i++)
+ {
+ auto child = static_cast(children[i]);
+ auto base = get_first_interpose(child);
+
+ if (base)
+ {
+ assert(base->prev == NULL);
+
+ if (base->saved_chain != vmptr)
+ continue;
+
+ child_next.insert(base);
+ }
+ else
+ {
+ void *cptr = child->get_vmethod_ptr(vmethod_idx);
+ if (cptr != vmptr)
+ continue;
+
+ child_hosts.insert(child);
+ find_child_hosts(child, vmptr);
+ }
+ }
+}
+
+void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from)
+{
+ if (from == host)
+ {
+ // When in own host, fully delete
+ remove();
+ }
+ else
+ {
+ // Otherwise, drop the link to that child:
+ assert(child_hosts.count(from) != 0 &&
+ from->interpose_list[vmethod_idx] == this);
+
+ // Find and restore the original vmethod ptr
+ auto last = this;
+ while (last->prev) last = last->prev;
+
+ from->set_vmethod_ptr(vmethod_idx, last->saved_chain);
+
+ // Unlink the chains
+ child_hosts.erase(from);
+ from->interpose_list[vmethod_idx] = NULL;
+ }
+}
+
bool VMethodInterposeLinkBase::apply()
{
if (is_applied())
@@ -188,33 +324,73 @@ bool VMethodInterposeLinkBase::apply()
// Retrieve the current vtable entry
void *old_ptr = host->get_vmethod_ptr(vmethod_idx);
- assert(old_ptr != NULL);
+ VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx];
- // Check if there are other interpose entries for the same slot
- VMethodInterposeLinkBase *old_link = NULL;
-
- for (int i = host->interpose_list.size()-1; i >= 0; i--)
- {
- if (host->interpose_list[i]->vmethod_idx != vmethod_idx)
- continue;
-
- old_link = host->interpose_list[i];
- assert(old_link->next == NULL && old_ptr == old_link->interpose_method);
- break;
- }
+ assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr));
// Apply the new method ptr
+ set_chain(old_ptr);
+
if (!host->set_vmethod_ptr(vmethod_idx, interpose_method))
+ {
+ set_chain(NULL);
return false;
+ }
- set_chain(old_ptr);
- host->interpose_list.push_back(this);
+ // Push the current link into the home host
+ applied = true;
+ host->interpose_list[vmethod_idx] = this;
+ prev = old_link;
- // Link into the chain if any
- if (old_link)
+ child_hosts.clear();
+ child_next.clear();
+
+ if (old_link && old_link->host == host)
{
+ // If the old link is home, just push into the plain chain
+ assert(old_link->next == NULL);
old_link->next = this;
- prev = old_link;
+
+ // Child links belong to the topmost local entry
+ child_hosts.swap(old_link->child_hosts);
+ child_next.swap(old_link->child_next);
+ }
+ else
+ {
+ // If creating a new local chain, find children with same vmethod
+ find_child_hosts(host, old_ptr);
+
+ if (old_link)
+ {
+ // Enter the child chain set
+ assert(old_link->child_hosts.count(host));
+ old_link->child_hosts.erase(host);
+ old_link->child_next.insert(this);
+
+ // Subtract our own children from the parent's sets
+ for (auto it = child_next.begin(); it != child_next.end(); ++it)
+ old_link->child_next.erase(*it);
+ for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
+ old_link->child_hosts.erase(*it);
+ }
+ }
+
+ // Chain subclass hooks
+ for (auto it = child_next.begin(); it != child_next.end(); ++it)
+ {
+ auto nlink = *it;
+ assert(nlink->saved_chain == old_ptr && nlink->prev == old_link);
+ nlink->set_chain(interpose_method);
+ nlink->prev = this;
+ }
+
+ // Chain passive subclass hosts
+ for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
+ {
+ auto nhost = *it;
+ assert(nhost->interpose_list[vmethod_idx] == old_link);
+ nhost->set_vmethod_ptr(vmethod_idx, interpose_method);
+ nhost->interpose_list[vmethod_idx] = this;
}
return true;
@@ -225,25 +401,57 @@ void VMethodInterposeLinkBase::remove()
if (!is_applied())
return;
- // Remove from the list in the identity
- for (int i = host->interpose_list.size()-1; i >= 0; i--)
- if (host->interpose_list[i] == this)
- vector_erase_at(host->interpose_list, i);
-
- // Remove from the chain
+ // Remove the link from prev to this
if (prev)
- 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)
{
next->set_chain(saved_chain);
next->prev = prev;
+
+ assert(child_next.empty() && child_hosts.empty());
}
else
{
+ // Remove from the list in the identity and vtable
+ host->interpose_list[vmethod_idx] = prev;
host->set_vmethod_ptr(vmethod_idx, saved_chain);
+
+ for (auto it = child_next.begin(); it != child_next.end(); ++it)
+ {
+ auto nlink = *it;
+ assert(nlink->saved_chain == interpose_method && nlink->prev == this);
+ nlink->set_chain(saved_chain);
+ nlink->prev = prev;
+ if (prev)
+ prev->child_next.insert(nlink);
+ }
+
+ for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
+ {
+ auto nhost = *it;
+ assert(nhost->interpose_list[vmethod_idx] == this);
+ nhost->interpose_list[vmethod_idx] = prev;
+ nhost->set_vmethod_ptr(vmethod_idx, saved_chain);
+ if (prev)
+ prev->child_hosts.insert(nhost);
+ }
}
+ applied = false;
prev = next = NULL;
+ child_next.clear();
+ child_hosts.clear();
set_chain(NULL);
}
diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h
index ccb29b0e7..591a0c3ff 100644
--- a/library/include/DataDefs.h
+++ b/library/include/DataDefs.h
@@ -303,7 +303,7 @@ namespace DFHack
void *vtable_ptr;
friend class VMethodInterposeLinkBase;
- std::vector interpose_list;
+ std::map interpose_list;
protected:
virtual void doInit(Core *core);
diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h
index 25b05ad40..38f0e2e50 100644
--- a/library/include/PluginManager.h
+++ b/library/include/PluginManager.h
@@ -128,7 +128,9 @@ namespace DFHack
{
PS_UNLOADED,
PS_LOADED,
- PS_BROKEN
+ PS_BROKEN,
+ PS_LOADING,
+ PS_UNLOADING
};
friend class PluginManager;
friend class RPCService;
diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h
index bb7a37ce8..c9482f82c 100644
--- a/library/include/VTableInterpose.h
+++ b/library/include/VTableInterpose.h
@@ -134,21 +134,31 @@ namespace DFHack
1) Allow multiple hooks into the same vmethod
2) Auto-remove hooks when a plugin is unloaded.
*/
+ friend class virtual_identity;
virtual_identity *host; // Class with the vtable
int vmethod_idx;
void *interpose_method; // Pointer to the code of the interposing method
void *chain_mptr; // Pointer to the chain field below
+ bool applied;
void *saved_chain; // Previous pointer to the code
VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method
+ // inherited vtable members
+ std::set child_hosts;
+ std::set child_next;
+
void set_chain(void *chain);
+ void on_host_delete(virtual_identity *host);
+
+ VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id);
+ void find_child_hosts(virtual_identity *cur, void *vmptr);
public:
VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr);
~VMethodInterposeLinkBase();
- bool is_applied() { return saved_chain != NULL; }
+ bool is_applied() { return applied; }
bool apply();
void remove();
};
diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h
index 6e0a22052..639df6865 100644
--- a/library/include/modules/Buildings.h
+++ b/library/include/modules/Buildings.h
@@ -92,6 +92,11 @@ DFHACK_EXPORT bool Read (const uint32_t index, t_building & building);
*/
DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map & btypes);
+/**
+ * Sets the owner unit for the building.
+ */
+DFHACK_EXPORT bool setOwner(df::building *building, df::unit *owner);
+
/**
* Find the building located at the specified tile.
* Does not work on civzones.
diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h
index e7155c436..58f222419 100644
--- a/library/include/modules/Gui.h
+++ b/library/include/modules/Gui.h
@@ -55,8 +55,6 @@ namespace DFHack
*/
namespace Gui
{
- inline df::viewscreen *getCurViewscreen() { return Core::getTopViewscreen(); }
-
DFHACK_EXPORT std::string getFocusString(df::viewscreen *top);
// Full-screen item details view
@@ -99,6 +97,9 @@ namespace DFHack
/*
* Cursor and window coords
*/
+ DFHACK_EXPORT df::coord getViewportPos();
+ DFHACK_EXPORT df::coord getCursorPos();
+
DFHACK_EXPORT bool getViewCoords (int32_t &x, int32_t &y, int32_t &z);
DFHACK_EXPORT bool setViewCoords (const int32_t x, const int32_t y, const int32_t z);
@@ -113,7 +114,11 @@ namespace DFHack
* Gui screens
*/
/// Get the current top-level view-screen
- DFHACK_EXPORT df::viewscreen * GetCurrentScreen();
+ DFHACK_EXPORT df::viewscreen *getCurViewscreen(bool skip_dismissed = false);
+
+ inline std::string getCurFocus(bool skip_dismissed = false) {
+ return getFocusString(getCurViewscreen(skip_dismissed));
+ }
/// get the size of the window buffer
DFHACK_EXPORT bool getWindowSize(int32_t & width, int32_t & height);
diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h
index e63eef733..e6e9682eb 100644
--- a/library/include/modules/Maps.h
+++ b/library/include/modules/Maps.h
@@ -258,7 +258,7 @@ inline df::tile_occupancy *getTileOccupancy(df::coord pos) {
/**
* Returns biome info about the specified world region.
*/
-DFHACK_EXPORT df::world_data::T_region_map *getRegionBiome(df::coord2d rgn_pos);
+DFHACK_EXPORT df::region_map_entry *getRegionBiome(df::coord2d rgn_pos);
/**
* Returns biome world region coordinates for the given tile within given block.
diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h
index ce3f32ed2..492e1eecc 100644
--- a/library/include/modules/Screen.h
+++ b/library/include/modules/Screen.h
@@ -112,7 +112,7 @@ namespace DFHack
// Push and remove viewscreens
DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL);
- DFHACK_EXPORT void dismiss(df::viewscreen *screen);
+ DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false);
DFHACK_EXPORT bool isDismissed(df::viewscreen *screen);
}
@@ -136,7 +136,10 @@ namespace DFHack
virtual bool key_conflict(df::interface_key key);
virtual bool is_lua_screen() { return false; }
+
virtual std::string getFocusString() = 0;
+ virtual void onShow() {};
+ virtual void onDismiss() {};
};
class DFHACK_EXPORT dfhack_lua_viewscreen : public dfhack_viewscreen {
@@ -166,5 +169,8 @@ namespace DFHack
virtual void help();
virtual void resize(int w, int h);
virtual void feed(std::set *keys);
+
+ virtual void onShow();
+ virtual void onDismiss();
};
}
diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua
index 5699e8a20..2cbd019a6 100644
--- a/library/lua/dfhack.lua
+++ b/library/lua/dfhack.lua
@@ -104,11 +104,21 @@ end
-- Trivial classes
+function rawset_default(target,source)
+ for k,v in pairs(source) do
+ if rawget(target,k) == nil then
+ rawset(target,k,v)
+ end
+ end
+end
+
function defclass(class,parent)
class = class or {}
- rawset(class, '__index', rawget(class, '__index') or class)
+ rawset_default(class, { __index = class })
if parent then
setmetatable(class, parent)
+ else
+ rawset_default(class, { init_fields = rawset_default })
end
return class
end
@@ -153,14 +163,6 @@ function xyz2pos(x,y,z)
end
end
-function rawset_default(target,source)
- for k,v in pairs(source) do
- if rawget(target,k) == nil then
- rawset(target,k,v)
- end
- end
-end
-
function safe_index(obj,idx,...)
if obj == nil or idx == nil then
return nil
@@ -202,12 +204,6 @@ function dfhack.buildings.getSize(bld)
return bld.x2+1-x, bld.y2+1-y, bld.centerx-x, bld.centery-y
end
-dfhack.screen.__index = dfhack.screen
-
-function dfhack.screen:__tostring()
- return ""
-end
-
-- Interactive
local print_banner = true
diff --git a/library/lua/gui.lua b/library/lua/gui.lua
index f9a45548f..9e189ea13 100644
--- a/library/lua/gui.lua
+++ b/library/lua/gui.lua
@@ -4,6 +4,8 @@ local _ENV = mkmodule('gui')
local dscreen = dfhack.screen
+USE_GRAPHICS = dscreen.inGraphicsMode()
+
CLEAR_PEN = {ch=32,fg=0,bg=0}
function simulateInput(screen,...)
@@ -16,7 +18,7 @@ function simulateInput(screen,...)
error('Invalid keycode: '..arg)
end
end
- if type(arg) == 'number' then
+ if type(kv) == 'number' then
keys[#keys+1] = kv
end
end
@@ -52,6 +54,9 @@ function inset(rect,dx1,dy1,dx2,dy2)
rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1)
)
end
+function is_in_rect(rect,x,y)
+ return x and y and x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2
+end
local function to_pen(default, pen, bg, bold)
if pen == nil then
@@ -201,10 +206,17 @@ end
-- Base screen object --
------------------------
-Screen = defclass(Screen, dfhack.screen)
+Screen = defclass(Screen)
Screen.text_input_mode = false
+function Screen:init()
+ self:updateLayout()
+ return self
+end
+
+Screen.isDismissed = dscreen.isDismissed
+
function Screen:isShown()
return self._native ~= nil
end
@@ -213,6 +225,18 @@ function Screen:isActive()
return self:isShown() and not self:isDismissed()
end
+function Screen:invalidate()
+ dscreen.invalidate()
+end
+
+function Screen:getWindowSize()
+ return dscreen.getWindowSize()
+end
+
+function Screen:getMousePos()
+ return dscreen.getMousePos()
+end
+
function Screen:renderParent()
if self._native and self._native.parent then
self._native.parent:render()
@@ -232,9 +256,7 @@ function Screen:show(below)
error("This screen is already on display")
end
self:onAboutToShow(below)
- if dscreen.show(self, below) then
- self:onShown()
- else
+ if not dscreen.show(self, below) then
error('Could not show screen')
end
end
@@ -242,17 +264,27 @@ end
function Screen:onAboutToShow()
end
-function Screen:onShown()
+function Screen:onShow()
+ self:updateLayout()
end
function Screen:dismiss()
- if self._native and not dscreen.isDismissed(self) then
+ if self._native then
dscreen.dismiss(self)
- self:onDismissed()
end
end
-function Screen:onDismissed()
+function Screen:onDismiss()
+end
+
+function Screen:onDestroy()
+end
+
+function Screen:onResize(w,h)
+ self:updateLayout()
+end
+
+function Screen:updateLayout()
end
------------------------
@@ -321,7 +353,8 @@ local function hint_coord(gap,hint)
end
end
-function FramedScreen:updateFrameSize(sw,sh)
+function FramedScreen:updateFrameSize()
+ local sw, sh = dscreen.getWindowSize()
local iw, ih = sw-2, sh-2
local width = math.min(self.frame_width or iw, iw)
local height = math.min(self.frame_height or ih, ih)
@@ -331,8 +364,21 @@ function FramedScreen:updateFrameSize(sw,sh)
self.frame_opaque = (gw == 0 and gh == 0)
end
-function FramedScreen:onResize(w,h)
- self:updateFrameSize(w,h)
+function FramedScreen:updateLayout()
+ self:updateFrameSize()
+end
+
+function FramedScreen:getWindowSize()
+ local rect = self.frame_rect
+ return rect.width, rect.height
+end
+
+function FramedScreen:getMousePos()
+ local rect = self.frame_rect
+ local x,y = dscreen.getMousePos()
+ if is_in_rect(rect,x,y) then
+ return x-rect.x1, y-rect.y1
+ end
end
function FramedScreen:onRender()
@@ -351,4 +397,7 @@ function FramedScreen:onRender()
self:onRenderBody(Painter.new(rect))
end
+function FramedScreen:onRenderBody(dc)
+end
+
return _ENV
diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua
index dde5225a1..1f7ae1b03 100644
--- a/library/lua/gui/dwarfmode.lua
+++ b/library/lua/gui/dwarfmode.lua
@@ -3,7 +3,12 @@
local _ENV = mkmodule('gui.dwarfmode')
local gui = require('gui')
+local utils = require('utils')
+
local dscreen = dfhack.screen
+
+local g_cursor = df.global.cursor
+local g_sel_rect = df.global.selection_rect
local world_map = df.global.world.map
AREA_MAP_WIDTH = 23
@@ -41,8 +46,8 @@ function getPanelLayout()
end
function getCursorPos()
- if df.global.cursor.x ~= -30000 then
- return copyall(df.global.cursor)
+ if g_cursor ~= -30000 then
+ return copyall(g_cursor)
end
end
@@ -54,6 +59,51 @@ function clearCursorPos()
df.global.cursor = xyz2pos(nil)
end
+function getSelection()
+ local p1, p2
+ if g_sel_rect.start_x ~= -30000 then
+ p1 = xyz2pos(g_sel_rect.start_x, g_sel_rect.start_y, g_sel_rect.start_z)
+ end
+ if g_sel_rect.end_x ~= -30000 then
+ p2 = xyz2pos(g_sel_rect.end_x, g_sel_rect.end_y, g_sel_rect.end_z)
+ end
+ return p1, p2
+end
+
+function setSelectionStart(pos)
+ g_sel_rect.start_x = pos.x
+ g_sel_rect.start_y = pos.y
+ g_sel_rect.start_z = pos.z
+end
+
+function setSelectionEnd(pos)
+ g_sel_rect.end_x = pos.x
+ g_sel_rect.end_y = pos.y
+ g_sel_rect.end_z = pos.z
+end
+
+function clearSelection()
+ g_sel_rect.start_x = -30000
+ g_sel_rect.start_y = -30000
+ g_sel_rect.start_z = -30000
+ g_sel_rect.end_x = -30000
+ g_sel_rect.end_y = -30000
+ g_sel_rect.end_z = -30000
+end
+
+function getSelectionRange(p1, p2)
+ local r1 = xyz2pos(
+ math.min(p1.x, p2.x), math.min(p1.y, p2.y), math.min(p1.z, p2.z)
+ )
+ local r2 = xyz2pos(
+ math.max(p1.x, p2.x), math.max(p1.y, p2.y), math.max(p1.z, p2.z)
+ )
+ local sz = xyz2pos(
+ r2.x - r1.x + 1, r2.y - r1.y + 1, r2.z - r1.z + 1
+ )
+ return r1, sz, r2
+end
+
Viewport = defclass(Viewport)
function Viewport.make(map,x,y,z)
@@ -179,14 +229,6 @@ function DwarfOverlay:updateLayout()
self.df_layout = getPanelLayout()
end
-function DwarfOverlay:onShown()
- self:updateLayout()
-end
-
-function DwarfOverlay:onResize(w,h)
- self:updateLayout()
-end
-
function DwarfOverlay:getViewport(old_vp)
if old_vp then
return old_vp:resize(self.df_layout)
@@ -195,6 +237,18 @@ function DwarfOverlay:getViewport(old_vp)
end
end
+function DwarfOverlay:moveCursorTo(cursor,viewport)
+ setCursorPos(cursor)
+ self:getViewport(viewport):reveal(cursor, 5, 0, 10):set()
+end
+
+function DwarfOverlay:selectBuilding(building,cursor,viewport)
+ cursor = cursor or utils.getBuildingCenter(building)
+
+ df.global.world.selected_building = building
+ self:moveCursorTo(cursor, viewport)
+end
+
function DwarfOverlay:propagateMoveKeys(keys)
for code,_ in pairs(MOVEMENT_KEYS) do
if keys[code] then
@@ -238,6 +292,14 @@ end
MenuOverlay = defclass(MenuOverlay, DwarfOverlay)
+function MenuOverlay:updateLayout()
+ DwarfOverlay.updateLayout(self)
+ self.frame_rect = self.df_layout.menu
+end
+
+MenuOverlay.getWindowSize = gui.FramedScreen.getWindowSize
+MenuOverlay.getMousePos = gui.FramedScreen.getMousePos
+
function MenuOverlay:onAboutToShow(below)
DwarfOverlay.onAboutToShow(self,below)
@@ -249,7 +311,6 @@ end
function MenuOverlay:onRender()
self:renderParent()
- self:updateLayout()
local menu = self.df_layout.menu
if menu then
diff --git a/library/lua/utils.lua b/library/lua/utils.lua
index 009bdf985..19a4e6f6a 100644
--- a/library/lua/utils.lua
+++ b/library/lua/utils.lua
@@ -373,6 +373,14 @@ function call_with_string(obj,methodname,...)
)
end
+function getBuildingName(building)
+ return call_with_string(building, 'getName')
+end
+
+function getBuildingCenter(building)
+ return xyz2pos(building.centerx, building.centery, building.z)
+end
+
-- Ask a yes-no question
function prompt_yes_no(msg,default)
local prompt = msg
diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp
index 8ec60e55b..d1aed8979 100644
--- a/library/modules/Buildings.cpp
+++ b/library/modules/Buildings.cpp
@@ -49,6 +49,7 @@ using namespace DFHack;
#include "df/ui_look_list.h"
#include "df/d_init.h"
#include "df/item.h"
+#include "df/unit.h"
#include "df/job.h"
#include "df/job_item.h"
#include "df/general_ref_building_holderst.h"
@@ -177,6 +178,44 @@ bool Buildings::ReadCustomWorkshopTypes(map & btypes)
return true;
}
+bool Buildings::setOwner(df::building *bld, df::unit *unit)
+{
+ CHECK_NULL_POINTER(bld);
+
+ if (!bld->is_room)
+ return false;
+ if (bld->owner == unit)
+ return true;
+
+ if (bld->owner)
+ {
+ auto &blist = bld->owner->owned_buildings;
+ vector_erase_at(blist, linear_index(blist, bld));
+
+ if (auto spouse = df::unit::find(bld->owner->relations.spouse_id))
+ {
+ auto &blist = spouse->owned_buildings;
+ vector_erase_at(blist, linear_index(blist, bld));
+ }
+ }
+
+ bld->owner = unit;
+
+ if (unit)
+ {
+ unit->owned_buildings.push_back(bld);
+
+ if (auto spouse = df::unit::find(unit->relations.spouse_id))
+ {
+ auto &blist = spouse->owned_buildings;
+ if (bld->canUseSpouseRoom() && linear_index(blist, bld) < 0)
+ blist.push_back(bld);
+ }
+ }
+
+ return true;
+}
+
df::building *Buildings::findAtTile(df::coord pos)
{
auto occ = Maps::getTileOccupancy(pos);
diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp
index 9d3ee96eb..0f28860bf 100644
--- a/library/modules/Gui.cpp
+++ b/library/modules/Gui.cpp
@@ -983,17 +983,36 @@ void Gui::showPopupAnnouncement(std::string message, int color, bool bright)
world->status.popups.push_back(popup);
}
-df::viewscreen * Gui::GetCurrentScreen()
+df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed)
{
df::viewscreen * ws = &gview->view;
- while(ws)
+ while (ws && ws->child)
+ ws = ws->child;
+
+ if (skip_dismissed)
{
- if(ws->child)
- ws = ws->child;
- else
- return ws;
+ while (ws && Screen::isDismissed(ws) && ws->parent)
+ ws = ws->parent;
}
- 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)
diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp
index 3ab156d77..4107680b0 100644
--- a/library/modules/Maps.cpp
+++ b/library/modules/Maps.cpp
@@ -57,6 +57,7 @@ using namespace std;
#include "df/builtin_mats.h"
#include "df/block_square_event_grassst.h"
#include "df/z_level_flags.h"
+#include "df/region_map_entry.h"
using namespace DFHack;
using namespace df::enums;
@@ -166,7 +167,7 @@ df::tile_occupancy *Maps::getTileOccupancy(int32_t x, int32_t y, int32_t z)
return block ? &block->occupancy[x&15][y&15] : NULL;
}
-df::world_data::T_region_map *Maps::getRegionBiome(df::coord2d rgn_pos)
+df::region_map_entry *Maps::getRegionBiome(df::coord2d rgn_pos)
{
auto data = world->world_data;
if (!data)
diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp
index 9b6839a40..c2377f2ca 100644
--- a/library/modules/Screen.cpp
+++ b/library/modules/Screen.cpp
@@ -235,14 +235,26 @@ bool Screen::show(df::viewscreen *screen, df::viewscreen *before)
if (screen->child)
screen->child->parent = screen;
+ if (dfhack_viewscreen::is_instance(screen))
+ static_cast(screen)->onShow();
+
return true;
}
-void Screen::dismiss(df::viewscreen *screen)
+void Screen::dismiss(df::viewscreen *screen, bool to_first)
{
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(screen)->onDismiss();
}
bool Screen::isDismissed(df::viewscreen *screen)
@@ -261,6 +273,8 @@ static std::set dfhack_screens;
dfhack_viewscreen::dfhack_viewscreen() : text_input_mode(false)
{
dfhack_screens.insert(this);
+
+ last_size = Screen::getWindowSize();
}
dfhack_viewscreen::~dfhack_viewscreen()
@@ -576,3 +590,15 @@ void dfhack_lua_viewscreen::feed(std::set *keys)
lua_pushlightuserdata(Lua::Core::State, keys);
safe_call_lua(do_input, 1, 0);
}
+
+void dfhack_lua_viewscreen::onShow()
+{
+ lua_pushstring(Lua::Core::State, "onShow");
+ safe_call_lua(do_notify, 1, 0);
+}
+
+void dfhack_lua_viewscreen::onDismiss()
+{
+ lua_pushstring(Lua::Core::State, "onDismiss");
+ safe_call_lua(do_notify, 1, 0);
+}
diff --git a/library/xml b/library/xml
index 1eeaa0836..328a8dbdc 160000
--- a/library/xml
+++ b/library/xml
@@ -1 +1 @@
-Subproject commit 1eeaa08360c39a9a2d811544c2443309adc1a8f1
+Subproject commit 328a8dbdc7d9e1e838798abf79861cc18a387e3f
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 023cd6e83..a2e520178 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -81,7 +81,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(weather weather.cpp)
DFHACK_PLUGIN(colonies colonies.cpp)
DFHACK_PLUGIN(mode mode.cpp)
- DFHACK_PLUGIN(liquids liquids.cpp Brushes.h)
+ DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua)
DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h)
DFHACK_PLUGIN(tubefill tubefill.cpp)
DFHACK_PLUGIN(autodump autodump.cpp)
diff --git a/plugins/changelayer.cpp b/plugins/changelayer.cpp
index 317a0fa36..3ab1899af 100644
--- a/plugins/changelayer.cpp
+++ b/plugins/changelayer.cpp
@@ -19,6 +19,7 @@
#include "df/world_data.h"
#include "df/world_geo_biome.h"
#include "df/world_geo_layer.h"
+#include "df/region_map_entry.h"
using namespace DFHack;
using namespace df::enums;
diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt
index 8274accfb..134d5cb67 100644
--- a/plugins/devel/CMakeLists.txt
+++ b/plugins/devel/CMakeLists.txt
@@ -18,3 +18,6 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp)
DFHACK_PLUGIN(rprobe rprobe.cpp)
DFHACK_PLUGIN(nestboxes nestboxes.cpp)
DFHACK_PLUGIN(vshook vshook.cpp)
+IF(UNIX)
+DFHACK_PLUGIN(ref-index ref-index.cpp)
+ENDIF()
diff --git a/plugins/devel/ref-index.cpp b/plugins/devel/ref-index.cpp
new file mode 100644
index 000000000..686f6918b
--- /dev/null
+++ b/plugins/devel/ref-index.cpp
@@ -0,0 +1,149 @@
+#include "Core.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#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
+T get_from_global_id_vector(int32_t id, const std::vector &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 T *find_object(int32_t id, int32_t *cache);
+template<> df::item *find_object(int32_t id, int32_t *cache) {
+ return get_from_global_id_vector(id, df::global::world->items.all, cache);
+}
+template<> df::unit *find_object(int32_t id, int32_t *cache) {
+ return get_from_global_id_vector(id, df::global::world->units.all, cache);
+}
+
+template
+struct CachedRef {
+ int32_t id;
+ int32_t cache;
+ CachedRef(int32_t id = -1) : id(id), cache(-1) {}
+ T *target() { return find_object(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(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(unit_id, 1+&unit_id);
+ }
+};
+
+IMPLEMENT_VMETHOD_INTERPOSE(unit_hook, getUnit);
+
+command_result hook_refs(color_ostream &out, vector & 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 &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;
+}
diff --git a/plugins/devel/rprobe.cpp b/plugins/devel/rprobe.cpp
index 7a091a962..805489d5e 100644
--- a/plugins/devel/rprobe.cpp
+++ b/plugins/devel/rprobe.cpp
@@ -27,6 +27,7 @@ using namespace std;
#include "df/world_region_details.h"
#include "df/world_geo_biome.h"
#include "df/world_geo_layer.h"
+#include "df/region_map_entry.h"
#include "df/inclusion_type.h"
#include "df/viewscreen_choose_start_sitest.h"
@@ -79,7 +80,7 @@ command_result rprobe (color_ostream &out, vector & parameters)
if (parameters.size() == 2)
{
- if (parameters[0] == "wet")
+ if (parameters[0] == "rai")
set_field = 0;
else if (parameters[0] == "veg")
set_field = 1;
@@ -87,7 +88,7 @@ command_result rprobe (color_ostream &out, vector & parameters)
set_field = 2;
else if (parameters[0] == "evi")
set_field = 3;
- else if (parameters[0] == "hil")
+ else if (parameters[0] == "dra")
set_field = 4;
else if (parameters[0] == "sav")
set_field = 5;
@@ -113,11 +114,11 @@ command_result rprobe (color_ostream &out, vector & parameters)
{
coord2d rg = screen->biome_rgn[i];
- df::world_data::T_region_map* rd = &data->region_map[rg.x][rg.y];
+ auto rd = &data->region_map[rg.x][rg.y];
if (set && i == to_set) {
if (set_field == 0)
- rd->wetness = set_val;
+ rd->rainfall = set_val;
else if (set_field == 1)
rd->vegetation = set_val;
else if (set_field == 2)
@@ -125,11 +126,11 @@ command_result rprobe (color_ostream &out, vector & parameters)
else if (set_field == 3)
rd->evilness = set_val;
else if (set_field == 4)
- rd->hilliness = set_val;
+ rd->drainage = set_val;
else if (set_field == 5)
rd->savagery = set_val;
else if (set_field == 6)
- rd->saltiness = set_val;
+ rd->salinity = set_val;
}
out << i << ": x = " << rg.x << ", y = " << rg.y;
@@ -140,13 +141,13 @@ command_result rprobe (color_ostream &out, vector & parameters)
" landmass_id: " << rd->landmass_id <<
" flags: " << hex << rd->flags.as_int() << dec << endl;
out <<
- "wet: " << rd->wetness << " " <<
+ "rai: " << rd->rainfall << " " <<
"veg: " << rd->vegetation << " " <<
"tem: " << rd->temperature << " " <<
"evi: " << rd->evilness << " " <<
- "hil: " << rd->hilliness << " " <<
+ "dra: " << rd->drainage << " " <<
"sav: " << rd->savagery << " " <<
- "sal: " << rd->saltiness;
+ "sal: " << rd->salinity;
int32_t *p = (int32_t *)rd;
int c = sizeof(*rd) / sizeof(int32_t);
diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp
index b036e4fa8..6df530a92 100644
--- a/plugins/liquids.cpp
+++ b/plugins/liquids.cpp
@@ -27,6 +27,7 @@
#include
#include
#include
+#include
using std::vector;
using std::string;
using std::endl;
@@ -41,6 +42,7 @@ using std::set;
#include "modules/Gui.h"
#include "TileTypes.h"
#include "modules/MapCache.h"
+#include "LuaTools.h"
#include "Brushes.h"
using namespace MapExtras;
using namespace DFHack;
@@ -50,7 +52,6 @@ CommandHistory liquids_hist;
command_result df_liquids (color_ostream &out, vector & parameters);
command_result df_liquids_here (color_ostream &out, vector & parameters);
-command_result df_liquids_execute (color_ostream &out);
DFHACK_PLUGIN("liquids");
@@ -74,13 +75,79 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK;
}
-// static stuff to be remembered between sessions
-static string brushname = "point";
-static string mode="magma";
-static string flowmode="f+";
-static string _setmode ="s.";
-static unsigned int amount = 7;
-static int width = 1, height = 1, z_levels = 1;
+enum BrushType {
+ B_POINT, B_RANGE, B_BLOCK, B_COLUMN, B_FLOOD
+};
+
+static const char *brush_name[] = {
+ "point", "range", "block", "column", "flood", NULL
+};
+
+enum PaintMode {
+ P_WATER, P_MAGMA, P_OBSIDIAN, P_OBSIDIAN_FLOOR,
+ P_RIVER_SOURCE, P_FLOW_BITS, P_WCLEAN
+};
+
+static const char *paint_mode_name[] = {
+ "water", "magma", "obsidian", "obsidian_floor",
+ "riversource", "flowbits", "wclean", NULL
+};
+
+enum ModifyMode {
+ M_INC, M_KEEP, M_DEC
+};
+
+static const char *modify_mode_name[] = {
+ "+", ".", "-", NULL
+};
+
+enum PermaflowMode {
+ PF_KEEP, PF_NONE,
+ PF_NORTH, PF_SOUTH, PF_EAST, PF_WEST,
+ PF_NORTHEAST, PF_NORTHWEST, PF_SOUTHEAST, PF_SOUTHWEST
+};
+
+static const char *permaflow_name[] = {
+ ".", "-", "N", "S", "E", "W",
+ "NE", "NW", "SE", "SW", NULL
+};
+
+#define X(name) tile_liquid_flow_dir::name
+static const df::tile_liquid_flow_dir permaflow_id[] = {
+ X(none), X(none), X(north), X(south), X(east), X(west),
+ X(northeast), X(northwest), X(southeast), X(southwest)
+};
+#undef X
+
+struct OperationMode {
+ BrushType brush;
+ PaintMode paint;
+ ModifyMode flowmode;
+ ModifyMode setmode;
+ PermaflowMode permaflow;
+ unsigned int amount;
+ df::coord size;
+
+ OperationMode() :
+ brush(B_POINT), paint(P_MAGMA),
+ flowmode(M_INC), setmode(M_KEEP), permaflow(PF_KEEP), amount(7),
+ size(1,1,1)
+ {}
+} cur_mode;
+
+command_result df_liquids_execute(color_ostream &out);
+command_result df_liquids_execute(color_ostream &out, OperationMode &mode, df::coord pos);
+
+static void print_prompt(std::ostream &str, OperationMode &cur_mode)
+{
+ str <<"[" << paint_mode_name[cur_mode.paint] << ":" << brush_name[cur_mode.brush];
+ if (cur_mode.brush == B_RANGE)
+ str << "(w" << cur_mode.size.x << ":h" << cur_mode.size.y << ":z" << cur_mode.size.z << ")";
+ str << ":" << cur_mode.amount << ":f" << modify_mode_name[cur_mode.flowmode]
+ << ":s" << modify_mode_name[cur_mode.setmode]
+ << ":pf" << permaflow_name[cur_mode.permaflow]
+ << "]";
+}
command_result df_liquids (color_ostream &out_, vector & parameters)
{
@@ -117,10 +184,8 @@ command_result df_liquids (color_ostream &out_, vector & parameters)
string input = "";
std::stringstream str;
- str <<"[" << mode << ":" << brushname;
- if (brushname == "range")
- str << "(w" << width << ":h" << height << ":z" << z_levels << ")";
- str << ":" << amount << ":" << flowmode << ":" << _setmode << "]#";
+ print_prompt(str, cur_mode);
+ str << "# ";
if(out.lineedit(str.str(),input,liquids_hist) == -1)
return CR_FAILURE;
liquids_hist.add(input);
@@ -147,6 +212,10 @@ command_result df_liquids (color_ostream &out_, vector & parameters)
<< "f+ - make the spawned liquid flow" << endl
<< "f. - don't change flow state (read state in flow mode)" << endl
<< "f- - make the spawned liquid static" << endl
+ << "Permaflow (only for water):" << endl
+ << "pf. - don't change permaflow state" << endl
+ << "pf- - make the spawned liquid static" << endl
+ << "pf[NS][EW] - make the spawned liquid permanently flow" << endl
<< "0-7 - set liquid amount" << endl
<< "Brush:" << endl
<< "point - single tile [p]" << endl
@@ -168,38 +237,39 @@ command_result df_liquids (color_ostream &out_, vector & parameters)
}
else if(command == "m")
{
- mode = "magma";
+ cur_mode.paint = P_MAGMA;
}
else if(command == "o")
{
- mode = "obsidian";
+ cur_mode.paint = P_OBSIDIAN;
}
else if(command == "of")
{
- mode = "obsidian_floor";
+ cur_mode.paint = P_OBSIDIAN_FLOOR;
}
else if(command == "w")
{
- mode = "water";
+ cur_mode.paint = P_WATER;
}
else if(command == "f")
{
- mode = "flowbits";
+ cur_mode.paint = P_FLOW_BITS;
}
else if(command == "rs")
{
- mode = "riversource";
+ cur_mode.paint = P_RIVER_SOURCE;
}
else if(command == "wclean")
{
- mode = "wclean";
+ cur_mode.paint = P_WCLEAN;
}
else if(command == "point" || command == "p")
{
- brushname = "point";
+ cur_mode.brush = B_POINT;
}
else if(command == "range" || command == "r")
{
+ int width, height, z_levels;
command_result res = parseRectangle(out, commands, 1, commands.size(),
width, height, z_levels);
if (res != CR_OK)
@@ -209,24 +279,26 @@ command_result df_liquids (color_ostream &out_, vector & parameters)
if (width == 1 && height == 1 && z_levels == 1)
{
- brushname = "point";
+ cur_mode.brush = B_POINT;
+ cur_mode.size = df::coord(1, 1, 1);
}
else
{
- brushname = "range";
+ cur_mode.brush = B_RANGE;
+ cur_mode.size = df::coord(width, height, z_levels);
}
}
else if(command == "block")
{
- brushname = "block";
+ cur_mode.brush = B_BLOCK;
}
else if(command == "column")
{
- brushname = "column";
+ cur_mode.brush = B_COLUMN;
}
else if(command == "flood")
{
- brushname = "flood";
+ cur_mode.brush = B_FLOOD;
}
else if(command == "q")
{
@@ -234,45 +306,59 @@ command_result df_liquids (color_ostream &out_, vector & parameters)
}
else if(command == "f+")
{
- flowmode = "f+";
+ cur_mode.flowmode = M_INC;
}
else if(command == "f-")
{
- flowmode = "f-";
+ cur_mode.flowmode = M_DEC;
}
else if(command == "f.")
{
- flowmode = "f.";
+ cur_mode.flowmode = M_KEEP;
}
else if(command == "s+")
{
- _setmode = "s+";
+ cur_mode.setmode = M_INC;
}
else if(command == "s-")
{
- _setmode = "s-";
+ cur_mode.setmode = M_DEC;
}
else if(command == "s.")
{
- _setmode = "s.";
+ cur_mode.setmode = M_KEEP;
+ }
+ else if (command.size() > 2 && memcmp(command.c_str(), "pf", 2) == 0)
+ {
+ auto *tail = command.c_str()+2;
+ for (int pm = PF_KEEP; pm <= PF_SOUTHWEST; pm++)
+ {
+ if (strcmp(tail, permaflow_name[pm]) != 0)
+ continue;
+ cur_mode.permaflow = PermaflowMode(pm);
+ tail = NULL;
+ break;
+ }
+ if (tail)
+ out << command << " : invalid permaflow mode" << endl;
}
// blah blah, bad code, bite me.
else if(command == "0")
- amount = 0;
+ cur_mode.amount = 0;
else if(command == "1")
- amount = 1;
+ cur_mode.amount = 1;
else if(command == "2")
- amount = 2;
+ cur_mode.amount = 2;
else if(command == "3")
- amount = 3;
+ cur_mode.amount = 3;
else if(command == "4")
- amount = 4;
+ cur_mode.amount = 4;
else if(command == "5")
- amount = 5;
+ cur_mode.amount = 5;
else if(command == "6")
- amount = 6;
+ cur_mode.amount = 6;
else if(command == "7")
- amount = 7;
+ cur_mode.amount = 7;
else if(command.empty())
{
df_liquids_execute(out);
@@ -298,78 +384,75 @@ command_result df_liquids_here (color_ostream &out, vector & parameters
}
out.print("Run liquids-here with these parameters: ");
- out << "[" << mode << ":" << brushname;
- if (brushname == "range")
- out << "(w" << width << ":h" << height << ":z" << z_levels << ")";
- out << ":" << amount << ":" << flowmode << ":" << _setmode << "]\n";
+ print_prompt(out, cur_mode);
+ out << endl;
return df_liquids_execute(out);
}
command_result df_liquids_execute(color_ostream &out)
{
- // create brush type depending on old parameters
- Brush * brush;
+ CoreSuspender suspend;
- if (brushname == "point")
+ auto cursor = Gui::getCursorPos();
+ if (!cursor.isValid())
{
- brush = new RectangleBrush(1,1,1,0,0,0);
- //width = 1;
- //height = 1;
- //z_levels = 1;
+ out.printerr("Can't get cursor coords! Make sure you have a cursor active in DF.\n");
+ return CR_WRONG_USAGE;
}
- else if (brushname == "range")
- {
- brush = new RectangleBrush(width,height,z_levels,0,0,0);
- }
- else if(brushname == "block")
+
+ auto rv = df_liquids_execute(out, cur_mode, cursor);
+ if (rv == CR_OK)
+ out << "OK" << endl;
+ 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();
- }
- else if(brushname == "column")
- {
+ break;
+ case B_COLUMN:
brush = new ColumnBrush();
- }
- else if(brushname == "flood")
- {
+ break;
+ case B_FLOOD:
brush = new FloodBrush(&Core::getInstance());
- }
- else
- {
+ break;
+ default:
// this should never happen!
out << "Old brushtype is invalid! Resetting to point brush.\n";
- brushname = "point";
- width = 1;
- height = 1;
- z_levels = 1;
- brush = new RectangleBrush(width,height,z_levels,0,0,0);
+ cur_mode.brush = B_POINT;
+ brush = new RectangleBrush(1,1,1,0,0,0);
}
- CoreSuspender suspend;
+ std::auto_ptr brush_ref(brush);
- do
+ if (!Maps::IsValid())
{
- if (!Maps::IsValid())
- {
- out << "Can't see any DF map loaded." << endl;
- break;;
- }
- int32_t x,y,z;
- if(!Gui::getCursorCoords(x,y,z))
- {
- out << "Can't get cursor coords! Make sure you have a cursor active in DF." << endl;
- break;
- }
- 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;
+ out << "Can't see any DF map loaded." << endl;
+ return CR_FAILURE;
+ }
- // Force the game to recompute its walkability cache
- df::global::world->reindex_pathfinding = true;
+ MapCache mcache;
+ 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();
while (iter != all_tiles.end())
@@ -383,8 +466,9 @@ command_result df_liquids_execute(color_ostream &out)
mcache.setDesignationAt(*iter, des);
iter ++;
}
+ break;
}
- if(mode == "obsidian_floor")
+ case P_OBSIDIAN_FLOOR:
{
coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end())
@@ -392,8 +476,9 @@ command_result df_liquids_execute(color_ostream &out)
mcache.setTiletypeAt(*iter, findRandomVariant(tiletype::LavaFloor1));
iter ++;
}
+ break;
}
- else if(mode == "riversource")
+ case P_RIVER_SOURCE:
{
coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end())
@@ -413,8 +498,9 @@ command_result df_liquids_execute(color_ostream &out)
iter++;
}
+ break;
}
- else if(mode=="wclean")
+ case P_WCLEAN:
{
coord_vec::iterator iter = all_tiles.begin();
while (iter != all_tiles.end())
@@ -426,8 +512,11 @@ command_result df_liquids_execute(color_ostream &out)
mcache.setDesignationAt(current,des);
iter++;
}
+ break;
}
- else if(mode== "magma" || mode== "water" || mode == "flowbits")
+ case P_MAGMA:
+ case P_WATER:
+ case P_FLOW_BITS:
{
set seen_blocks;
coord_vec::iterator iter = all_tiles.begin();
@@ -442,6 +531,7 @@ command_result df_liquids_execute(color_ostream &out)
iter ++;
continue;
}
+ auto raw_block = block->getRaw();
df::tile_designation des = mcache.designationAt(current);
df::tiletype tt = mcache.tiletypeAt(current);
// don't put liquids into places where they don't belong...
@@ -450,30 +540,29 @@ command_result df_liquids_execute(color_ostream &out)
iter++;
continue;
}
- if(mode != "flowbits")
+ if(cur_mode.paint != P_FLOW_BITS)
{
unsigned old_amount = des.bits.flow_size;
unsigned new_amount = old_amount;
df::tile_liquid old_liquid = des.bits.liquid_type;
df::tile_liquid new_liquid = old_liquid;
// Compute new liquid type and amount
- if(_setmode == "s.")
- {
- new_amount = amount;
- }
- else if(_setmode == "s+")
- {
- if(old_amount < amount)
- new_amount = amount;
- }
- else if(_setmode == "s-")
+ switch (cur_mode.setmode)
{
- if (old_amount > amount)
- new_amount = amount;
+ case M_KEEP:
+ new_amount = cur_mode.amount;
+ break;
+ case M_INC:
+ if(old_amount < cur_mode.amount)
+ new_amount = cur_mode.amount;
+ break;
+ case M_DEC:
+ if (old_amount > cur_mode.amount)
+ new_amount = cur_mode.amount;
}
- if (mode == "magma")
+ if (cur_mode.paint == P_MAGMA)
new_liquid = tile_liquid::Magma;
- else if (mode == "water")
+ else if (cur_mode.paint == P_WATER)
new_liquid = tile_liquid::Water;
// Store new amount and type
des.bits.flow_size = new_amount;
@@ -502,40 +591,77 @@ command_result df_liquids_execute(color_ostream &out)
// request flow engine updates
block->enableBlockUpdates(new_amount != old_amount, new_liquid != old_liquid);
}
+ if (cur_mode.permaflow != PF_KEEP && raw_block)
+ {
+ auto &flow = raw_block->liquid_flow[current.x&15][current.y&15];
+ flow.bits.perm_flow_dir = permaflow_id[cur_mode.permaflow];
+ flow.bits.temp_flow_timer = 0;
+ }
seen_blocks.insert(block);
iter++;
}
set ::iterator biter = seen_blocks.begin();
while (biter != seen_blocks.end())
{
- if(flowmode == "f+")
+ switch (cur_mode.flowmode)
{
+ case M_INC:
(*biter)->enableBlockUpdates(true);
- }
- else if(flowmode == "f-")
- {
+ break;
+ case M_DEC:
if (auto block = (*biter)->getRaw())
{
block->flags.bits.update_liquid = false;
block->flags.bits.update_liquid_twice = false;
}
- }
- else
- {
- auto bflags = (*biter)->BlockFlags();
- out << "flow bit 1 = " << bflags.bits.update_liquid << endl;
- out << "flow bit 2 = " << bflags.bits.update_liquid_twice << endl;
+ break;
+ case M_KEEP:
+ {
+ auto bflags = (*biter)->BlockFlags();
+ out << "flow bit 1 = " << bflags.bits.update_liquid << endl;
+ out << "flow bit 2 = " << bflags.bits.update_liquid_twice << endl;
+ }
}
biter ++;
}
+ break;
}
- if(mcache.WriteAll())
- out << "OK" << endl;
- else
- out << "Something failed horribly! RUN!" << endl;
- } while (0);
+ }
+
+ if(!mcache.WriteAll())
+ {
+ out << "Something failed horribly! RUN!" << endl;
+ return CR_FAILURE;
+ }
- // cleanup
- delete brush;
return CR_OK;
}
+
+static int paint(lua_State *L)
+{
+ df::coord pos;
+ OperationMode mode;
+
+ lua_settop(L, 8);
+ Lua::CheckDFAssign(L, &pos, 1);
+ if (!pos.isValid())
+ luaL_argerror(L, 1, "invalid cursor position");
+ mode.brush = (BrushType)luaL_checkoption(L, 2, NULL, brush_name);
+ mode.paint = (PaintMode)luaL_checkoption(L, 3, NULL, paint_mode_name);
+ mode.amount = luaL_optint(L, 4, 7);
+ if (mode.amount < 0 || mode.amount > 7)
+ luaL_argerror(L, 4, "invalid liquid amount");
+ if (!lua_isnil(L, 5))
+ Lua::CheckDFAssign(L, &mode.size, 5);
+ mode.setmode = (ModifyMode)luaL_checkoption(L, 6, ".", modify_mode_name);
+ mode.flowmode = (ModifyMode)luaL_checkoption(L, 7, "+", modify_mode_name);
+ mode.permaflow = (PermaflowMode)luaL_checkoption(L, 8, ".", permaflow_name);
+
+ lua_pushboolean(L, df_liquids_execute(*Lua::GetOutput(L), mode, pos));
+ return 1;
+}
+
+DFHACK_PLUGIN_LUA_COMMANDS {
+ DFHACK_LUA_COMMAND(paint),
+ DFHACK_LUA_END
+};
diff --git a/plugins/lua/liquids.lua b/plugins/lua/liquids.lua
new file mode 100644
index 000000000..22ce4da35
--- /dev/null
+++ b/plugins/lua/liquids.lua
@@ -0,0 +1,11 @@
+local _ENV = mkmodule('plugins.liquids')
+
+--[[
+
+ Native functions:
+
+ * paint(pos,brush,paint,amount,size,setmode,flowmode)
+
+--]]
+
+return _ENV
\ No newline at end of file
diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp
index 434ba08c8..71b1fc907 100644
--- a/plugins/manipulator.cpp
+++ b/plugins/manipulator.cpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
#include
#include "df/world.h"
@@ -41,36 +42,36 @@ DFHACK_PLUGIN("manipulator");
struct SkillLevel
{
- const char *name;
- int points;
- char abbrev;
+ const char *name;
+ int points;
+ 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 '-'.
const SkillLevel skill_levels[] = {
- {"Dabbling", 500, '0'},
- {"Novice", 600, '1'},
- {"Adequate", 700, '2'},
- {"Competent", 800, '3'},
- {"Skilled", 900, '4'},
- {"Proficient", 1000, '5'},
- {"Talented", 1100, '6'},
- {"Adept", 1200, '7'},
- {"Expert", 1300, '8'},
- {"Professional",1400, '9'},
- {"Accomplished",1500, 'A'},
- {"Great", 1600, 'B'},
- {"Master", 1700, 'C'},
- {"High Master", 1800, 'D'},
- {"Grand Master",1900, 'E'},
- {"Legendary", 2000, 'U'},
- {"Legendary+1", 2100, 'V'},
- {"Legendary+2", 2200, 'W'},
- {"Legendary+3", 2300, 'X'},
- {"Legendary+4", 2400, 'Y'},
- {"Legendary+5", 0, 'Z'}
+ {"Dabbling", 500, '0'},
+ {"Novice", 600, '1'},
+ {"Adequate", 700, '2'},
+ {"Competent", 800, '3'},
+ {"Skilled", 900, '4'},
+ {"Proficient", 1000, '5'},
+ {"Talented", 1100, '6'},
+ {"Adept", 1200, '7'},
+ {"Expert", 1300, '8'},
+ {"Professional",1400, '9'},
+ {"Accomplished",1500, 'A'},
+ {"Great", 1600, 'B'},
+ {"Master", 1700, 'C'},
+ {"High Master", 1800, 'D'},
+ {"Grand Master",1900, 'E'},
+ {"Legendary", 2000, 'U'},
+ {"Legendary+1", 2100, 'V'},
+ {"Legendary+2", 2200, 'W'},
+ {"Legendary+3", 2300, 'X'},
+ {"Legendary+4", 2400, 'Y'},
+ {"Legendary+5", 0, 'Z'}
};
struct SkillColumn
@@ -82,7 +83,7 @@ struct SkillColumn
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
const SkillColumn columns[] = {
@@ -247,17 +248,69 @@ struct UnitInfo
int8_t color;
};
-#define FILTER_NONWORKERS 0x0001
-#define FILTER_NONDWARVES 0x0002
-#define FILTER_NONCIV 0x0004
-#define FILTER_ANIMALS 0x0008
-#define FILTER_LIVING 0x0010
-#define FILTER_DEAD 0x0020
+enum altsort_mode {
+ ALTSORT_NAME,
+ ALTSORT_PROFESSION,
+ ALTSORT_MAX
+};
+
+bool descending;
+df::job_skill sort_skill;
+df::unit_labor sort_labor;
+
+bool sortByName (const UnitInfo *d1, const UnitInfo *d2)
+{
+ if (descending)
+ return (d1->name > d2->name);
+ else
+ return (d1->name < d2->name);
+}
+
+bool sortByProfession (const UnitInfo *d1, const UnitInfo *d2)
+{
+ if (descending)
+ return (d1->profession > d2->profession);
+ else
+ return (d1->profession < d2->profession);
+}
+
+bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2)
+{
+ if (sort_skill != job_skill::NONE)
+ {
+ df::unit_skill *s1 = binsearch_in_vector>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill);
+ df::unit_skill *s2 = binsearch_in_vector>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill);
+ int l1 = s1 ? s1->rating : 0;
+ int l2 = s2 ? s2->rating : 0;
+ int e1 = s1 ? s1->experience : 0;
+ int e2 = s2 ? s2->experience : 0;
+ if (descending)
+ {
+ if (l1 != l2)
+ return l1 > l2;
+ if (e1 != e2)
+ return e1 > e2;
+ }
+ else
+ {
+ if (l1 != l2)
+ return l1 < l2;
+ if (e1 != e2)
+ return e1 < e2;
+ }
+ }
+ if (sort_labor != unit_labor::NONE)
+ {
+ if (descending)
+ return d1->unit->status.labors[sort_labor] > d2->unit->status.labors[sort_labor];
+ else
+ return d1->unit->status.labors[sort_labor] < d2->unit->status.labors[sort_labor];
+ }
+ return sortByName(d1, d2);
+}
class viewscreen_unitlaborsst : public dfhack_viewscreen {
public:
- static viewscreen_unitlaborsst *create (char pushtype, df::viewscreen *scr = NULL);
-
void feed(set *events);
void render();
@@ -267,86 +320,41 @@ public:
std::string getFocusString() { return "unitlabors"; }
- viewscreen_unitlaborsst();
+ viewscreen_unitlaborsst(vector &src);
~viewscreen_unitlaborsst() { };
protected:
vector units;
- int filter;
+ altsort_mode altsort;
int first_row, sel_row;
int first_column, sel_column;
int height, name_width, prof_width, labors_width;
-// bool descending;
-// int sort_skill;
-// int sort_labor;
-
- void readUnits ();
void calcSize ();
};
-viewscreen_unitlaborsst::viewscreen_unitlaborsst()
+viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src)
{
- filter = FILTER_LIVING;
- first_row = sel_row = 0;
- first_column = sel_column = 0;
- calcSize();
- readUnits();
-}
-
-void viewscreen_unitlaborsst::readUnits ()
-{
- for (size_t i = 0; i < units.size(); i++)
- delete units[i];
- units.clear();
-
- UnitInfo *cur = new UnitInfo;
- for (size_t i = 0; i < world->units.active.size(); i++)
+ for (size_t i = 0; i < src.size(); i++)
{
- df::unit *unit = world->units.active[i];
+ UnitInfo *cur = new UnitInfo;
+ df::unit *unit = src[i];
cur->unit = unit;
cur->allowEdit = true;
if (unit->race != ui->race_id)
- {
cur->allowEdit = false;
- if (!(filter & FILTER_NONDWARVES))
- continue;
- }
if (unit->civ_id != ui->civ_id)
- {
cur->allowEdit = false;
- if (!(filter & FILTER_NONCIV))
- continue;
- }
if (unit->flags1.bits.dead)
- {
cur->allowEdit = false;
- if (!(filter & FILTER_DEAD))
- continue;
- }
- else
- {
- if (!(filter & FILTER_LIVING))
- continue;
- }
if (!ENUM_ATTR(profession, can_assign_labor, unit->profession))
- {
cur->allowEdit = false;
- if (!(filter & FILTER_NONWORKERS))
- continue;
- }
-
- if (!unit->name.first_name.length())
- {
- if (!(filter & FILTER_ANIMALS))
- continue;
- }
cur->name = Translation::TranslateName(&unit->name, false);
cur->transname = Translation::TranslateName(&unit->name, true);
@@ -354,9 +362,13 @@ void viewscreen_unitlaborsst::readUnits ()
cur->color = Units::getProfessionColor(unit);
units.push_back(cur);
- cur = new UnitInfo;
}
- delete cur;
+ std::sort(units.begin(), units.end(), sortByName);
+
+ altsort = ALTSORT_NAME;
+ first_row = sel_row = 0;
+ first_column = sel_column = 0;
+ calcSize();
}
void viewscreen_unitlaborsst::calcSize()
@@ -421,8 +433,6 @@ void viewscreen_unitlaborsst::feed(set *events)
return;
}
- // TODO - allow modifying filters
-
if (!units.size())
return;
@@ -501,9 +511,64 @@ void viewscreen_unitlaborsst::feed(set *events)
unit->status.labors[col.labor] = !unit->status.labors[col.labor];
}
- // TODO: add sorting
+ if (events->count(interface_key::SECONDSCROLL_UP) || events->count(interface_key::SECONDSCROLL_DOWN))
+ {
+ descending = events->count(interface_key::SECONDSCROLL_UP);
+ sort_skill = columns[sel_column].skill;
+ sort_labor = columns[sel_column].labor;
+ std::sort(units.begin(), units.end(), sortBySkill);
+ }
+
+ if (events->count(interface_key::SECONDSCROLL_PAGEUP) || events->count(interface_key::SECONDSCROLL_PAGEDOWN))
+ {
+ descending = events->count(interface_key::SECONDSCROLL_PAGEUP);
+ switch (altsort)
+ {
+ case ALTSORT_NAME:
+ std::sort(units.begin(), units.end(), sortByName);
+ break;
+ case ALTSORT_PROFESSION:
+ std::sort(units.begin(), units.end(), sortByProfession);
+ break;
+ }
+ }
+ if (events->count(interface_key::CHANGETAB))
+ {
+ switch (altsort)
+ {
+ case ALTSORT_NAME:
+ altsort = ALTSORT_PROFESSION;
+ break;
+ case ALTSORT_PROFESSION:
+ altsort = ALTSORT_NAME;
+ break;
+ }
+ }
+
+ if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, parent))
+ {
+ if (events->count(interface_key::UNITJOB_VIEW) || events->count(interface_key::UNITJOB_ZOOM_CRE))
+ {
+ for (int i = 0; i < unitlist->units[unitlist->page].size(); i++)
+ {
+ if (unitlist->units[unitlist->page][i] == units[sel_row]->unit)
+ {
+ unitlist->cursor_pos[unitlist->page] = i;
+ unitlist->feed(events);
+ if (Screen::isDismissed(unitlist))
+ Screen::dismiss(this);
+ break;
+ }
+ }
+ }
+ }
}
+void OutputString(int8_t color, int &x, int y, const std::string &text)
+{
+ Screen::paintString(Screen::Pen(' ', color, 0), x, y, text);
+ x += text.length();
+}
void viewscreen_unitlaborsst::render()
{
if (Screen::isDismissed(this))
@@ -528,6 +593,7 @@ void viewscreen_unitlaborsst::render()
fg = 0;
bg = 7;
}
+
Screen::paintTile(Screen::Pen(columns[col_offset].label[0], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 1);
Screen::paintTile(Screen::Pen(columns[col_offset].label[1], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 2);
}
@@ -537,6 +603,7 @@ void viewscreen_unitlaborsst::render()
int row_offset = row + first_row;
if (row_offset >= units.size())
break;
+
UnitInfo *cur = units[row_offset];
df::unit *unit = cur->unit;
int8_t fg = 15, bg = 0;
@@ -554,7 +621,8 @@ void viewscreen_unitlaborsst::render()
profession.resize(prof_width);
fg = cur->color;
bg = 0;
- Screen::paintString(Screen::Pen(' ', fg, bg), 1 + prof_width + 1, 3 + row, profession);
+
+ Screen::paintString(Screen::Pen(' ', fg, bg), 1 + name_width + 1, 3 + row, profession);
// Print unit's skills and labor assignments
for (int col = 0; col < labors_width; col++)
@@ -562,11 +630,9 @@ void viewscreen_unitlaborsst::render()
int col_offset = col + first_column;
fg = 15;
bg = 0;
+ char c = 0xFA;
if ((col_offset == sel_column) && (row_offset == sel_row))
fg = 9;
- if ((columns[col_offset].labor != unit_labor::NONE) && (unit->status.labors[columns[col_offset].labor]))
- bg = 7;
- char c = '-';
if (columns[col_offset].skill != job_skill::NONE)
{
df::unit_skill *skill = binsearch_in_vector>(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill);
@@ -577,7 +643,20 @@ void viewscreen_unitlaborsst::render()
level = NUM_SKILL_LEVELS - 1;
c = skill_levels[level].abbrev;
}
+ else
+ c = '-';
+ }
+ if (columns[col_offset].labor != unit_labor::NONE)
+ {
+ if (unit->status.labors[columns[col_offset].labor])
+ {
+ bg = 7;
+ if (columns[col_offset].skill == job_skill::NONE)
+ c = 0xF9;
+ }
}
+ else
+ bg = 4;
Screen::paintTile(Screen::Pen(c, fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 3 + row);
}
}
@@ -586,15 +665,21 @@ void viewscreen_unitlaborsst::render()
if (cur != NULL)
{
df::unit *unit = cur->unit;
- string str = cur->transname;
- if (str.length())
- str += ", ";
- str += cur->profession;
- str += ":";
+ int x = 1;
+ Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->transname);
+ x += cur->transname.length();
- Screen::paintString(Screen::Pen(' ', 15, 0), 1, 3 + height + 2, str);
- int y = 1 + str.length() + 1;
+ if (cur->transname.length())
+ {
+ Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ", ");
+ x += 2;
+ }
+ Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->profession);
+ x += cur->profession.length();
+ Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ": ");
+ x += 2;
+ string str;
if (columns[sel_column].skill == job_skill::NONE)
{
str = ENUM_ATTR_STR(unit_labor, caption, columns[sel_column].labor);
@@ -602,7 +687,6 @@ void viewscreen_unitlaborsst::render()
str += " Enabled";
else
str += " Not Enabled";
- Screen::paintString(Screen::Pen(' ', 9, 0), y, 3 + height + 2, str);
}
else
{
@@ -618,11 +702,45 @@ void viewscreen_unitlaborsst::render()
}
else
str = stl_sprintf("Not %s (0/500)", ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill));
- Screen::paintString(Screen::Pen(' ', 9, 0), y, 3 + height + 2, str);
}
+ Screen::paintString(Screen::Pen(' ', 9, 0), x, 3 + height + 2, str);
}
- // TODO - print command help info
+ int x = 1;
+ OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key
+ OutputString(15, x, gps->dimy - 3, ": Toggle labor, ");
+
+ OutputString(10, x, gps->dimy - 3, "v"); // UNITJOB_VIEW key
+ OutputString(15, x, gps->dimy - 3, ": ViewCre, ");
+
+ OutputString(10, x, gps->dimy - 3, "c"); // UNITJOB_ZOOM_CRE key
+ OutputString(15, x, gps->dimy - 3, ": Zoom-Cre, ");
+
+ OutputString(10, x, gps->dimy - 3, "Esc"); // LEAVESCREEN key
+ OutputString(15, x, gps->dimy - 3, ": Done");
+
+ x = 1;
+ OutputString(10, x, gps->dimy - 2, "+"); // SECONDSCROLL_DOWN key
+ OutputString(10, x, gps->dimy - 2, "-"); // SECONDSCROLL_UP key
+ OutputString(15, x, gps->dimy - 2, ": Sort by Skill, ");
+
+ OutputString(10, x, gps->dimy - 2, "*"); // SECONDSCROLL_PAGEDOWN key
+ OutputString(10, x, gps->dimy - 2, "/"); // SECONDSCROLL_PAGEUP key
+ OutputString(15, x, gps->dimy - 2, ": Sort by (");
+ OutputString(10, x, gps->dimy - 2, "Tab"); // CHANGETAB key
+ OutputString(15, x, gps->dimy - 2, ") ");
+ switch (altsort)
+ {
+ case ALTSORT_NAME:
+ OutputString(15, x, gps->dimy - 2, "Name");
+ break;
+ case ALTSORT_PROFESSION:
+ OutputString(15, x, gps->dimy - 2, "Profession");
+ break;
+ default:
+ OutputString(15, x, gps->dimy - 2, "Unknown");
+ break;
+ }
}
struct unitlist_hook : df::viewscreen_unitlistst
@@ -633,9 +751,11 @@ struct unitlist_hook : df::viewscreen_unitlistst
{
if (input->count(interface_key::UNITVIEW_PRF_PROF))
{
- Screen::dismiss(this);
- Screen::show(new viewscreen_unitlaborsst());
- return;
+ if (units[page].size())
+ {
+ Screen::show(new viewscreen_unitlaborsst(units[page]));
+ return;
+ }
}
INTERPOSE_NEXT(feed)(input);
}
diff --git a/plugins/probe.cpp b/plugins/probe.cpp
index 2ae6846d5..45ef1bbfb 100644
--- a/plugins/probe.cpp
+++ b/plugins/probe.cpp
@@ -27,6 +27,7 @@ using namespace std;
#include "df/world.h"
#include "df/world_raws.h"
#include "df/building_def.h"
+#include "df/region_map_entry.h"
using std::vector;
using std::string;
@@ -224,8 +225,7 @@ command_result df_probe (color_ostream &out, vector & parameters)
int bx = clip_range(block.region_pos.x + (offset % 3) - 1, 0, world->world_data->world_width-1);
int by = clip_range(block.region_pos.y + (offset / 3) - 1, 0, world->world_data->world_height-1);
- df::world_data::T_region_map* biome =
- &world->world_data->region_map[bx][by];
+ auto biome = &world->world_data->region_map[bx][by];
int sav = biome->savagery;
int evi = biome->evilness;
diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp
index e2f1e9534..5eab897c0 100644
--- a/plugins/prospector.cpp
+++ b/plugins/prospector.cpp
@@ -25,8 +25,12 @@ using namespace std;
#include "df/world.h"
#include "df/world_data.h"
#include "df/world_region_details.h"
+#include "df/world_region_feature.h"
#include "df/world_geo_biome.h"
#include "df/world_geo_layer.h"
+#include "df/world_underground_region.h"
+#include "df/feature_init.h"
+#include "df/region_map_entry.h"
#include "df/inclusion_type.h"
#include "df/viewscreen_choose_start_sitest.h"
@@ -108,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)
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)
};
-static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen,
- bool showHidden, bool showValue)
+struct EmbarkTileLayout {
+ coord2d biome_off, biome_pos;
+ df::region_map_entry *biome;
+ int elevation, max_soil_depth;
+ int min_z, base_z;
+ std::map 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 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");
- return CR_FAILURE;
+ auto feature = features[i];
+ auto layer = df::world_underground_region::find(feature->layer);
+ if (!layer || feature->min_z == -30000) continue;
+
+ layer_bottom[layer->layer_depth] = feature->min_z;
+ layer_top[layer->layer_depth] = feature->max_z;
+ tile.base_z = std::min(tile.base_z, (int)feature->min_z);
+
+ float penalty = 1.0f;
+ switch (layer->type) {
+ case df::world_underground_region::Cavern:
+ penalty = 0.75f;
+ break;
+ case df::world_underground_region::MagmaSea:
+ sea_found = true;
+ tile.min_z = feature->min_z;
+ for (int i = feature->min_z; i <= feature->max_z; i++)
+ tile.penalty[i] = 0.2 + 0.6f*(i-feature->min_z)/(feature->max_z-feature->min_z+1);
+ break;
+ case df::world_underground_region::Underworld:
+ penalty = 0.0f;
+ break;
+ }
+
+ if (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;
- coord2d cur_region = screen->region_pos;
- int d_idx = linear_index(data->region_details, &df::world_region_details::pos, cur_region);
- auto cur_details = vector_get(data->region_details, d_idx);
+ if (!sea_found)
+ {
+ out.printerr("Could not find magma sea.\n");
+ 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");
- return CR_FAILURE;
+ auto feature = features[i];
+ 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
- std::map biomes;
+ return true;
+}
+
+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);
- biomes[screen->biome_rgn[screen->biome_idx]]++;
+ out.printerr("Region geo-biome not found: (%d,%d)\n",
+ 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++)
- {
- int bv = clip_range(cur_details->biome[x][y], 1, 9);
- biomes[cur_region + biome_delta[bv-1]]++;
- }
+ case SOIL:
+ case SOIL_OCEAN:
+ case SOIL_SAND:
+ top_z_level += layer->top_height - layer->bottom_height + 1;
+ break;
+ default:;
}
}
- // Compute material maps
- MatMap layerMats;
- MatMap veinMats;
+ top_z_level = std::max(top_z_level, tile.elevation)-1;
- 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);
- int by = clip_range(biome_it->first.y, 0, data->world_height-1);
- auto ®ion = data->region_map[bx][by];
- df::world_geo_biome *geo_biome = df::world_geo_biome::find(region.geo_index);
+ auto layer = geo_biome->layers[i];
- if (!geo_biome)
- {
- out.printerr("Region geo-biome not found: (%d,%d)\n", bx, by);
- return CR_FAILURE;
- }
+ int top_z = std::min(layer->top_height + top_z_level, tile.elevation-1);
+ int bottom_z = std::max(layer->bottom_height + top_z_level, tile.min_z);
+ if (i+1 == geo_biome->layers.size()) // stretch layer if needed
+ bottom_z = tile.min_z;
+ if (top_z < bottom_z)
+ continue;
+
+ float layer_size = 48*48;
+
+ int sums[ENUM_LAST_ITEM(inclusion_type)+1] = { 0 };
- int cnt = biome_it->second;
+ for (unsigned j = 0; j < layer->vein_mat.size(); j++)
+ if (is_valid_enum_item(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;
- int layer_size = 48*48*cnt*level_cnt;
+ layer_size -= size;
- 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++)
- if (is_valid_enum_item(layer->vein_type[j]))
- sums[layer->vein_type[j]] += layer->vein_unk_38[j];
+ add_materials(tile, layerMats[layer->mat_index], layer_size, bottom_z, top_z);
+ }
- for (unsigned j = 0; j < layer->vein_mat.size(); j++)
- {
- // 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];
+ return true;
+}
- 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 = cnt*level_cnt;
- }
+static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen,
+ bool showHidden, bool showValue)
+{
+ if (!world || !world->world_data)
+ {
+ out.printerr("World data is not available.\n");
+ return CR_FAILURE;
+ }
+
+ df::world_data *data = world->world_data;
+ coord2d cur_region = screen->region_pos;
+ int d_idx = linear_index(data->region_details, &df::world_region_details::pos, cur_region);
+ auto cur_details = vector_get(data->region_details, d_idx);
- veinMats[layer->vein_mat[j]].add(layer->bottom_height, 0);
- veinMats[layer->vein_mat[j]].add(layer->top_height, size);
+ if (!cur_details)
+ {
+ 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 biomes;
+
+ /*if (screen->biome_highlighted)
+ {
+ out.print("Processing one embark tile of biome F%d.\n\n", screen->biome_idx+1);
+ biomes[screen->biome_rgn[screen->biome_idx]]++;
+ }*/
+
+ for (int x = screen->embark_pos_min.x; x <= screen->embark_pos_max.x; x++)
+ {
+ for (int y = screen->embark_pos_min.y; y <= screen->embark_pos_max.y; y++)
+ {
+ EmbarkTileLayout tile;
+ if (!estimate_underground(out, tile, cur_details, x, y) ||
+ !estimate_materials(out, tile, layerMats, veinMats))
+ return CR_FAILURE;
+
+ world_bottom.add(tile.base_z, 0);
+ world_bottom.add(tile.elevation-1, 0);
}
}
@@ -348,7 +484,10 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos
mats->Finish();
}
- out << "Warning: the above data is only a very rough estimate." << std::endl;
+ out << "Embark depth: " << (world_bottom.upper_z-world_bottom.lower_z+1) << " ";
+ printMatdata(out, world_bottom, true);
+
+ out << std::endl << "Warning: the above data is only a very rough estimate." << std::endl;
return CR_OK;
}
@@ -536,6 +675,8 @@ command_result prospector (color_ostream &con, vector & parameters)
case tiletype_material::LAVA_STONE:
// TODO ?
break;
+ default:
+ break;
}
}
}
diff --git a/plugins/ruby/README b/plugins/ruby/README
index 8a473f332..c9a84fb37 100644
--- a/plugins/ruby/README
+++ b/plugins/ruby/README
@@ -213,8 +213,7 @@ Find the raws name of the plant under cursor
p df.world.raws.plants.all[plant.mat_index].id
Dig a channel under the cursor
- df.map_designation_at(df.cursor).dig = :Channel
- df.map_block_at(df.cursor).flags.designated = true
+ df.map_tile_at(df.cursor).dig(:Channel)
Spawn 2/7 magma on the tile of the dwarf nicknamed 'hotfeet'
hot = df.unit_citizens.find { |u| u.name.nickname == 'hotfeet' }
diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb
index ab029ac24..af152e198 100644
--- a/plugins/ruby/building.rb
+++ b/plugins/ruby/building.rb
@@ -279,9 +279,9 @@ module DFHack
job = Job.cpp_new
refbuildingholder = GeneralRefBuildingHolderst.cpp_new
job.job_type = :DestroyBuilding
- refbuildingholder.building_id = building.id
+ refbuildingholder.building_id = bld.id
job.references << refbuildingholder
- building.jobs << job
+ bld.jobs << job
job_link job
job
end
diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb
index c99d5b88d..dccea7291 100644
--- a/plugins/ruby/map.rb
+++ b/plugins/ruby/map.rb
@@ -188,6 +188,11 @@ module DFHack
"#"
end
+ def dig(mode=:Default)
+ designation.dig = mode
+ mapblock.flags.designated = true
+ end
+
def spawn_liquid(quantity, is_magma=false, flowing=true)
designation.flow_size = quantity
designation.liquid_type = (is_magma ? :Magma : :Water)
diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb
index 3507508e1..0cee6426f 100644
--- a/plugins/ruby/ruby-autogen-defs.rb
+++ b/plugins/ruby/ruby-autogen-defs.rb
@@ -666,9 +666,9 @@ module DFHack
@_tg = tg
end
- field(:_ptr, 0) { number 32, false }
- field(:_prev, 4) { number 32, false }
- field(:_next, 8) { number 32, false }
+ field(:_ptr, 0) { pointer }
+ field(:_prev, 4) { pointer }
+ field(:_next, 8) { pointer }
def item
# With the current xml structure, currently _tg designate
@@ -682,22 +682,24 @@ module DFHack
def item=(v)
#addr = _ptr
- #raise 'null pointer' if addr == 0
+ #raise 'null pointer' if not addr
#@_tg.at(addr)._set(v)
raise 'null pointer'
end
def prev
addr = _prev
- return if addr == 0
+ return if not addr
@_tg._at(addr)._get
end
def next
addr = _next
- return if addr == 0
+ return if not addr
@_tg._at(addr)._get
end
+ alias next= _next=
+ alias prev= _prev=
include Enumerable
def each
diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp
index 1391faa44..482714d2a 100644
--- a/plugins/ruby/ruby.cpp
+++ b/plugins/ruby/ruby.cpp
@@ -4,6 +4,7 @@
#include "Export.h"
#include "PluginManager.h"
#include "VersionInfo.h"
+#include "MemAccess.h"
#include "DataDefs.h"
#include "df/global_objects.h"
@@ -597,6 +598,45 @@ static VALUE rb_dfmemory_write_float(VALUE self, VALUE addr, VALUE val)
return Qtrue;
}
+// return memory permissions at address (eg "rx", nil if unmapped)
+static VALUE rb_dfmemory_check(VALUE self, VALUE addr)
+{
+ void *ptr = (void*)rb_num2ulong(addr);
+ std::vector ranges;
+ Core::getInstance().p->getMemRanges(ranges);
+
+ unsigned i = 0;
+ while (i < ranges.size() && ranges[i].end <= ptr)
+ i++;
+
+ if (i >= ranges.size() || ranges[i].start > ptr || !ranges[i].valid)
+ return Qnil;
+
+ std::string perm = "";
+ if (ranges[i].read)
+ perm += "r";
+ if (ranges[i].write)
+ perm += "w";
+ if (ranges[i].execute)
+ perm += "x";
+ if (ranges[i].shared)
+ perm += "s";
+
+ return rb_str_new(perm.c_str(), perm.length());
+}
+
+// memory write (tmp override page permissions, eg patch code)
+static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw)
+{
+ int strlen = FIX2INT(rb_funcall(raw, rb_intern("length"), 0));
+ bool ret;
+
+ ret = Core::getInstance().p->patchMemory((void*)rb_num2ulong(addr),
+ rb_string_value_ptr(&raw), strlen);
+
+ return ret ? Qtrue : Qfalse;
+}
+
// stl::string
static VALUE rb_dfmemory_stlstring_new(VALUE self)
@@ -875,6 +915,8 @@ static void ruby_bind_dfhack(void) {
rb_define_singleton_method(rb_cDFHack, "memory_write_int16", RUBY_METHOD_FUNC(rb_dfmemory_write_int16), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_int32", RUBY_METHOD_FUNC(rb_dfmemory_write_int32), 2);
rb_define_singleton_method(rb_cDFHack, "memory_write_float", RUBY_METHOD_FUNC(rb_dfmemory_write_float), 2);
+ rb_define_singleton_method(rb_cDFHack, "memory_check", RUBY_METHOD_FUNC(rb_dfmemory_check), 1);
+ rb_define_singleton_method(rb_cDFHack, "memory_patch", RUBY_METHOD_FUNC(rb_dfmemory_patch), 2);
rb_define_singleton_method(rb_cDFHack, "memory_stlstring_new", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_new), 0);
rb_define_singleton_method(rb_cDFHack, "memory_stlstring_delete", RUBY_METHOD_FUNC(rb_dfmemory_stlstring_delete), 1);
diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb
index ebcf249da..1a619c5ce 100644
--- a/plugins/ruby/unit.rb
+++ b/plugins/ruby/unit.rb
@@ -41,48 +41,60 @@ module DFHack
# returns an Array of all units that are current fort citizen (dwarves, on map, not hostile)
def unit_citizens
- race = ui.race_id
- civ = ui.civ_id
world.units.active.find_all { |u|
- u.race == race and u.civ_id == civ and !u.flags1.dead and !u.flags1.merchant and
- !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
+ unit_iscitizen(u)
}
end
+ def unit_iscitizen(u)
+ u.race == ui.race_id and u.civ_id == ui.civ_id and !u.flags1.dead and !u.flags1.merchant and
+ !u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and
+ !u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and
+ u.mood != :Berserk
+ # TODO check curse ; currently this should keep vampires, but may include werebeasts
+ end
+
# list workers (citizen, not crazy / child / inmood / noble)
def unit_workers
- unit_citizens.find_all { |u|
- 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] }
+ world.units.active.find_all { |u|
+ unit_isworker(u)
}
end
+ def unit_isworker(u)
+ unit_iscitizen(u) and
+ u.mood == :None and
+ u.profession != :CHILD and
+ u.profession != :BABY and
+ # TODO MENIAL_WORK_EXEMPTION_SPOUSE
+ !unit_entitypositions(u).find { |pos| pos.flags[:MENIAL_WORK_EXEMPTION] }
+ end
+
# list currently idle workers
def unit_idlers
- unit_workers.find_all { |u|
- # 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 }
+ world.units.active.find_all { |u|
+ unit_isidler(u)
}
end
+ def unit_isidler(u)
+ unit_isworker(u) and
+ # current_job includes eat/drink/sleep/pickupequip
+ !u.job.current_job and
+ # filter 'attend meeting'
+ 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)
list = []
- return list if not hf = world.history.figures.binsearch(unit.hist_figure_id)
+ return list if not hf = unit.hist_figure_tg
hf.entity_links.each { |el|
next if el._rtti_classname != :histfig_entity_link_positionst
- next if not ent = world.entities.all.binsearch(el.entity_id)
+ next if not ent = el.entity_tg
next if not pa = ent.positions.assignments.binsearch(el.assignment_id)
next if not pos = ent.positions.own.binsearch(pa.position_id)
list << pos
diff --git a/plugins/stonesense b/plugins/stonesense
index 5d4f06d78..2a62ba5ed 160000
--- a/plugins/stonesense
+++ b/plugins/stonesense
@@ -1 +1 @@
-Subproject commit 5d4f06d785f8a9933679fe3caa12c18215e9674d
+Subproject commit 2a62ba5ed2607f4dbf0473e77502d4e19c19678e
diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp
index 2daa9063b..591125c5e 100644
--- a/plugins/tweak.cpp
+++ b/plugins/tweak.cpp
@@ -13,6 +13,7 @@
#include "MiscUtils.h"
#include "DataDefs.h"
+#include
#include "df/ui.h"
#include "df/world.h"
#include "df/squad.h"
@@ -26,6 +27,8 @@
#include "df/death_info.h"
#include "df/criminal_case.h"
#include "df/unit_inventory_item.h"
+#include "df/viewscreen_dwarfmodest.h"
+#include "df/squad_order_trainst.h"
#include
@@ -67,6 +70,13 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector & params);
-
-
// to be called by tweak-fixmigrant
// units forced into the fort by removing the flags do not own their clothes
// which has the result that they drop all their clothes and become unhappy because they are naked
@@ -136,6 +143,65 @@ command_result fix_clothing_ownership(color_ostream &out, df::unit* unit)
return CR_OK;
}
+/*
+ * Save or restore cursor position on change to/from main dwarfmode menu.
+ */
+
+static df::coord last_view, last_cursor;
+
+struct stable_cursor_hook : df::viewscreen_dwarfmodest
+{
+ typedef df::viewscreen_dwarfmodest interpose_base;
+
+ DEFINE_VMETHOD_INTERPOSE(void, feed, (set *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 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 ¶meters)
{
CoreSuspender suspend;
@@ -234,6 +300,22 @@ static command_result tweak(color_ostream &out, vector ¶meters)
unit->profession2 = df::profession::TRADER;
return fix_clothing_ownership(out, unit);
}
+ else if (cmd == "stable-cursor")
+ {
+ auto &hook = INTERPOSE_HOOK(stable_cursor_hook, feed);
+ if (vector_get(parameters, 1) == "disable")
+ hook.remove();
+ else
+ hook.apply();
+ }
+ else if (cmd == "patrol-duty")
+ {
+ auto &hook = INTERPOSE_HOOK(patrol_duty_hook, isPatrol);
+ if (vector_get(parameters, 1) == "disable")
+ hook.remove();
+ else
+ hook.apply();
+ }
else
return CR_WRONG_USAGE;
diff --git a/scripts/gui/hello-world.lua b/scripts/gui/hello-world.lua
index b67e50124..80986bbf6 100644
--- a/scripts/gui/hello-world.lua
+++ b/scripts/gui/hello-world.lua
@@ -17,6 +17,6 @@ local screen = mkinstance(gui.FramedScreen, {
self:dismiss()
end
end
-})
+}):init()
screen:show()
diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua
new file mode 100644
index 000000000..869cac908
--- /dev/null
+++ b/scripts/gui/liquids.lua
@@ -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()
diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua
index fe45d4acd..6b4b4042b 100644
--- a/scripts/gui/mechanisms.lua
+++ b/scripts/gui/mechanisms.lua
@@ -4,22 +4,13 @@ local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
-function getBuildingName(building)
- return utils.call_with_string(building, 'getName')
-end
-
-function getBuildingCenter(building)
- return xyz2pos(building.centerx, building.centery, building.z)
-end
-
function listMechanismLinks(building)
local lst = {}
local function push(item, mode)
if item then
lst[#lst+1] = {
obj = item, mode = mode,
- name = getBuildingName(item),
- center = getBuildingCenter(item)
+ name = utils.getBuildingName(item)
}
end
end
@@ -52,26 +43,27 @@ MechanismList = defclass(MechanismList, guidm.MenuOverlay)
MechanismList.focus_path = 'mechanisms'
-function MechanismList.new(building)
- local self = {
- links = {},
- selected = 1
+function MechanismList:init(building)
+ self:init_fields{
+ links = {}, selected = 1
}
- return mkinstance(MechanismList, self):init(building)
+ guidm.MenuOverlay.init(self)
+ self:fillList(building)
+ return self
end
-function MechanismList:init(building)
+function MechanismList:fillList(building)
local links = listMechanismLinks(building)
- links[1].viewport = self:getViewport()
- links[1].cursor = guidm.getCursorPos()
+ self.old_viewport = self:getViewport()
+ self.old_cursor = guidm.getCursorPos()
+
if #links <= 1 then
links[1].mode = 'none'
end
self.links = links
self.selected = 1
- return self
end
local colors = {
@@ -103,22 +95,10 @@ function MechanismList:onRenderBody(dc)
dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch")
end
-function MechanismList:zoomToLink(link,back)
- df.global.world.selected_building = link.obj
-
- if back then
- guidm.setCursorPos(link.cursor)
- self:getViewport(link.viewport):set()
- else
- guidm.setCursorPos(link.center)
- self:getViewport():reveal(link.center, 5, 0, 10):set()
- end
-end
-
function MechanismList:changeSelected(delta)
if #self.links <= 1 then return end
self.selected = 1 + (self.selected + delta - 1) % #self.links
- self:zoomToLink(self.links[self.selected])
+ self:selectBuilding(self.links[self.selected].obj)
end
function MechanismList:onInput(keys)
@@ -129,11 +109,11 @@ function MechanismList:onInput(keys)
elseif keys.LEAVESCREEN then
self:dismiss()
if self.selected ~= 1 then
- self:zoomToLink(self.links[1], true)
+ self:selectBuilding(self.links[1].obj, self.old_cursor, self.old_view)
end
elseif keys.SELECT_ALL then
if self.selected > 1 then
- self:init(self.links[self.selected].obj)
+ self:fillList(self.links[self.selected].obj)
end
elseif keys.SELECT then
self:dismiss()
@@ -142,13 +122,10 @@ function MechanismList:onInput(keys)
end
end
-if not df.viewscreen_dwarfmodest:is_instance(dfhack.gui.getCurViewscreen()) then
- qerror("This script requires the main dwarfmode view")
-end
-if df.global.ui.main.mode ~= df.ui_sidebar_mode.QueryBuilding then
- qerror("This script requires the 'q' interface mode")
+if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some' then
+ qerror("This script requires the main dwarfmode view in 'q' mode")
end
-local list = MechanismList.new(df.global.world.selected_building)
+local list = mkinstance(MechanismList):init(df.global.world.selected_building)
list:show()
list:changeSelected(1)
diff --git a/scripts/gui/room-list.lua b/scripts/gui/room-list.lua
new file mode 100644
index 000000000..a4507466f
--- /dev/null
+++ b/scripts/gui/room-list.lua
@@ -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