Merge remote-tracking branch 'q/master'
commit
a8158cb19a
@ -1 +1 @@
|
||||
Subproject commit c85e9fb35d3510c5dcc367056cda3237d77a7add
|
||||
Subproject commit d0b2d0750dc2d529a152eba4f3f519f69ff7eab0
|
@ -0,0 +1,457 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#include "Internal.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "MemAccess.h"
|
||||
#include "Core.h"
|
||||
#include "VersionInfo.h"
|
||||
#include "VTableInterpose.h"
|
||||
|
||||
#include "MiscUtils.h"
|
||||
|
||||
using namespace DFHack;
|
||||
|
||||
/*
|
||||
* Code for accessing method pointers directly. Very compiler-specific.
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
struct MSVC_MPTR {
|
||||
void *method;
|
||||
intptr_t this_shift;
|
||||
};
|
||||
|
||||
bool DFHack::is_vmethod_pointer_(void *pptr)
|
||||
{
|
||||
auto pobj = (MSVC_MPTR*)pptr;
|
||||
if (!pobj->method) return false;
|
||||
|
||||
// MSVC implements pointers to vmethods via thunks.
|
||||
// This expects that they all follow a very specific pattern.
|
||||
auto pval = (unsigned*)pobj->method;
|
||||
switch (pval[0]) {
|
||||
case 0x20FF018BU: // mov eax, [ecx]; jmp [eax]
|
||||
case 0x60FF018BU: // mov eax, [ecx]; jmp [eax+0x??]
|
||||
case 0xA0FF018BU: // mov eax, [ecx]; jmp [eax+0x????????]
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int DFHack::vmethod_pointer_to_idx_(void *pptr)
|
||||
{
|
||||
auto pobj = (MSVC_MPTR*)pptr;
|
||||
if (!pobj->method || pobj->this_shift != 0) return -1;
|
||||
|
||||
auto pval = (unsigned*)pobj->method;
|
||||
switch (pval[0]) {
|
||||
case 0x20FF018BU: // mov eax, [ecx]; jmp [eax]
|
||||
return 0;
|
||||
case 0x60FF018BU: // mov eax, [ecx]; jmp [eax+0x??]
|
||||
return ((int8_t)pval[1])/sizeof(void*);
|
||||
case 0xA0FF018BU: // mov eax, [ecx]; jmp [eax+0x????????]
|
||||
return ((int32_t)pval[1])/sizeof(void*);
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void* DFHack::method_pointer_to_addr_(void *pptr)
|
||||
{
|
||||
if (is_vmethod_pointer_(pptr)) return NULL;
|
||||
auto pobj = (MSVC_MPTR*)pptr;
|
||||
return pobj->method;
|
||||
}
|
||||
|
||||
void DFHack::addr_to_method_pointer_(void *pptr, void *addr)
|
||||
{
|
||||
auto pobj = (MSVC_MPTR*)pptr;
|
||||
pobj->method = addr;
|
||||
pobj->this_shift = 0;
|
||||
}
|
||||
|
||||
#elif defined(__GXX_ABI_VERSION)
|
||||
|
||||
struct GCC_MPTR {
|
||||
intptr_t method;
|
||||
intptr_t this_shift;
|
||||
};
|
||||
|
||||
bool DFHack::is_vmethod_pointer_(void *pptr)
|
||||
{
|
||||
auto pobj = (GCC_MPTR*)pptr;
|
||||
return (pobj->method & 1) != 0;
|
||||
}
|
||||
|
||||
int DFHack::vmethod_pointer_to_idx_(void *pptr)
|
||||
{
|
||||
auto pobj = (GCC_MPTR*)pptr;
|
||||
if ((pobj->method & 1) == 0 || pobj->this_shift != 0)
|
||||
return -1;
|
||||
return (pobj->method-1)/sizeof(void*);
|
||||
}
|
||||
|
||||
void* DFHack::method_pointer_to_addr_(void *pptr)
|
||||
{
|
||||
auto pobj = (GCC_MPTR*)pptr;
|
||||
if ((pobj->method & 1) != 0 || pobj->this_shift != 0)
|
||||
return NULL;
|
||||
return (void*)pobj->method;
|
||||
}
|
||||
|
||||
void DFHack::addr_to_method_pointer_(void *pptr, void *addr)
|
||||
{
|
||||
auto pobj = (GCC_MPTR*)pptr;
|
||||
pobj->method = (intptr_t)addr;
|
||||
pobj->this_shift = 0;
|
||||
}
|
||||
|
||||
#else
|
||||
#error Unknown compiler type
|
||||
#endif
|
||||
|
||||
void *virtual_identity::get_vmethod_ptr(int idx)
|
||||
{
|
||||
assert(idx >= 0);
|
||||
void **vtable = (void**)vtable_ptr;
|
||||
if (!vtable) return NULL;
|
||||
return vtable[idx];
|
||||
}
|
||||
|
||||
bool virtual_identity::set_vmethod_ptr(int idx, void *ptr)
|
||||
{
|
||||
assert(idx >= 0);
|
||||
void **vtable = (void**)vtable_ptr;
|
||||
if (!vtable) return NULL;
|
||||
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;
|
||||
addr_to_method_pointer_(chain_mptr, 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),
|
||||
applied(false), saved_chain(NULL), next(NULL), prev(NULL)
|
||||
{
|
||||
if (vmethod_idx < 0 || interpose_method == NULL)
|
||||
{
|
||||
fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n",
|
||||
vmethod_idx, unsigned(interpose_method));
|
||||
fflush(stderr);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
VMethodInterposeLinkBase::~VMethodInterposeLinkBase()
|
||||
{
|
||||
if (is_applied())
|
||||
remove();
|
||||
}
|
||||
|
||||
VMethodInterposeLinkBase *VMethodInterposeLinkBase::get_first_interpose(virtual_identity *id)
|
||||
{
|
||||
auto item = id->interpose_list[vmethod_idx];
|
||||
if (!item)
|
||||
return NULL;
|
||||
|
||||
if (item->host != id)
|
||||
return NULL;
|
||||
while (item->prev && item->prev->host == id)
|
||||
item = item->prev;
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
void VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr)
|
||||
{
|
||||
auto &children = cur->getChildren();
|
||||
|
||||
for (size_t i = 0; i < children.size(); i++)
|
||||
{
|
||||
auto child = static_cast<virtual_identity*>(children[i]);
|
||||
auto base = get_first_interpose(child);
|
||||
|
||||
if (base)
|
||||
{
|
||||
assert(base->prev == NULL);
|
||||
|
||||
if (base->saved_chain != vmptr)
|
||||
continue;
|
||||
|
||||
child_next.insert(base);
|
||||
}
|
||||
else
|
||||
{
|
||||
void *cptr = child->get_vmethod_ptr(vmethod_idx);
|
||||
if (cptr != vmptr)
|
||||
continue;
|
||||
|
||||
child_hosts.insert(child);
|
||||
find_child_hosts(child, vmptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from)
|
||||
{
|
||||
if (from == host)
|
||||
{
|
||||
// When in own host, fully delete
|
||||
remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, drop the link to that child:
|
||||
assert(child_hosts.count(from) != 0 &&
|
||||
from->interpose_list[vmethod_idx] == this);
|
||||
|
||||
// Find and restore the original vmethod ptr
|
||||
auto last = this;
|
||||
while (last->prev) last = last->prev;
|
||||
|
||||
from->set_vmethod_ptr(vmethod_idx, last->saved_chain);
|
||||
|
||||
// Unlink the chains
|
||||
child_hosts.erase(from);
|
||||
from->interpose_list[vmethod_idx] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool VMethodInterposeLinkBase::apply()
|
||||
{
|
||||
if (is_applied())
|
||||
return true;
|
||||
if (!host->vtable_ptr)
|
||||
return false;
|
||||
|
||||
// Retrieve the current vtable entry
|
||||
void *old_ptr = host->get_vmethod_ptr(vmethod_idx);
|
||||
VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Push the current link into the home host
|
||||
applied = true;
|
||||
host->interpose_list[vmethod_idx] = this;
|
||||
prev = 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
void VMethodInterposeLinkBase::remove()
|
||||
{
|
||||
if (!is_applied())
|
||||
return;
|
||||
|
||||
// Remove the link from prev to this
|
||||
if (prev)
|
||||
{
|
||||
if (prev->host == host)
|
||||
prev->next = next;
|
||||
else
|
||||
{
|
||||
prev->child_next.erase(this);
|
||||
|
||||
if (next)
|
||||
prev->child_next.insert(next);
|
||||
}
|
||||
}
|
||||
|
||||
if (next)
|
||||
{
|
||||
next->set_chain(saved_chain);
|
||||
next->prev = prev;
|
||||
|
||||
assert(child_next.empty() && child_hosts.empty());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove from the list in the identity and vtable
|
||||
host->interpose_list[vmethod_idx] = prev;
|
||||
host->set_vmethod_ptr(vmethod_idx, saved_chain);
|
||||
|
||||
for (auto it = child_next.begin(); it != child_next.end(); ++it)
|
||||
{
|
||||
auto nlink = *it;
|
||||
assert(nlink->saved_chain == interpose_method && nlink->prev == this);
|
||||
nlink->set_chain(saved_chain);
|
||||
nlink->prev = prev;
|
||||
if (prev)
|
||||
prev->child_next.insert(nlink);
|
||||
}
|
||||
|
||||
for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it)
|
||||
{
|
||||
auto nhost = *it;
|
||||
assert(nhost->interpose_list[vmethod_idx] == this);
|
||||
nhost->interpose_list[vmethod_idx] = prev;
|
||||
nhost->set_vmethod_ptr(vmethod_idx, saved_chain);
|
||||
if (prev)
|
||||
prev->child_hosts.insert(nhost);
|
||||
}
|
||||
}
|
||||
|
||||
applied = false;
|
||||
prev = next = NULL;
|
||||
child_next.clear();
|
||||
child_hosts.clear();
|
||||
set_chain(NULL);
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "DataFuncs.h"
|
||||
|
||||
namespace DFHack
|
||||
{
|
||||
template<bool> struct StaticAssert;
|
||||
template<> struct StaticAssert<true> {};
|
||||
|
||||
#define STATIC_ASSERT(condition) { StaticAssert<(condition)>(); }
|
||||
|
||||
/* Wrapping around compiler-specific representation of pointers to methods. */
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define METHOD_POINTER_SIZE (sizeof(void*)*2)
|
||||
#elif defined(__GXX_ABI_VERSION)
|
||||
#define METHOD_POINTER_SIZE (sizeof(void*)*2)
|
||||
#else
|
||||
#error Unknown compiler type
|
||||
#endif
|
||||
|
||||
#define ASSERT_METHOD_POINTER(type) \
|
||||
STATIC_ASSERT(df::return_type<type>::is_method && sizeof(type)==METHOD_POINTER_SIZE);
|
||||
|
||||
DFHACK_EXPORT bool is_vmethod_pointer_(void*);
|
||||
DFHACK_EXPORT int vmethod_pointer_to_idx_(void*);
|
||||
DFHACK_EXPORT void* method_pointer_to_addr_(void*);
|
||||
DFHACK_EXPORT void addr_to_method_pointer_(void*,void*);
|
||||
|
||||
template<class T> bool is_vmethod_pointer(T ptr) {
|
||||
ASSERT_METHOD_POINTER(T);
|
||||
return is_vmethod_pointer_(&ptr);
|
||||
}
|
||||
template<class T> int vmethod_pointer_to_idx(T ptr) {
|
||||
ASSERT_METHOD_POINTER(T);
|
||||
return vmethod_pointer_to_idx_(&ptr);
|
||||
}
|
||||
template<class T> void *method_pointer_to_addr(T ptr) {
|
||||
ASSERT_METHOD_POINTER(T);
|
||||
return method_pointer_to_addr_(&ptr);
|
||||
}
|
||||
template<class T> T addr_to_method_pointer(void *addr) {
|
||||
ASSERT_METHOD_POINTER(T);
|
||||
T rv;
|
||||
addr_to_method_pointer_(&rv, addr);
|
||||
return rv;
|
||||
}
|
||||
|
||||
/* Access to vmethod pointers from the vtable. */
|
||||
|
||||
template<class P>
|
||||
P virtual_identity::get_vmethod_ptr(P selector)
|
||||
{
|
||||
typedef typename df::return_type<P>::class_type host_class;
|
||||
virtual_identity &identity = host_class::_identity;
|
||||
int idx = vmethod_pointer_to_idx(selector);
|
||||
return addr_to_method_pointer<P>(identity.get_vmethod_ptr(idx));
|
||||
}
|
||||
|
||||
/* VMethod interpose API.
|
||||
|
||||
This API allows replacing an entry in the original vtable
|
||||
with code defined by DFHack, while retaining ability to
|
||||
call the original code. The API can be safely used from
|
||||
plugins, and multiple hooks for the same vmethod are
|
||||
automatically chained in undefined order.
|
||||
|
||||
Usage:
|
||||
|
||||
struct my_hack : df::someclass {
|
||||
typedef df::someclass interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) {
|
||||
// If needed by the code, claim the suspend lock.
|
||||
// DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK!
|
||||
// CoreSuspendClaimer suspend;
|
||||
...
|
||||
INTERPOSE_NEXT(foo)(arg) // call the original
|
||||
...
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo);
|
||||
|
||||
void init() {
|
||||
if (!INTERPOSE_HOOK(my_hack, foo).apply())
|
||||
error();
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
INTERPOSE_HOOK(my_hack, foo).remove();
|
||||
}
|
||||
*/
|
||||
|
||||
#define DEFINE_VMETHOD_INTERPOSE(rtype, name, args) \
|
||||
typedef rtype (interpose_base::*interpose_ptr_##name)args; \
|
||||
static DFHack::VMethodInterposeLink<interpose_base,interpose_ptr_##name> interpose_##name; \
|
||||
rtype interpose_fn_##name args
|
||||
|
||||
#define IMPLEMENT_VMETHOD_INTERPOSE(class,name) \
|
||||
DFHack::VMethodInterposeLink<class::interpose_base,class::interpose_ptr_##name> \
|
||||
class::interpose_##name(&class::interpose_base::name, &class::interpose_fn_##name);
|
||||
|
||||
#define INTERPOSE_NEXT(name) (this->*interpose_##name.chain)
|
||||
#define INTERPOSE_HOOK(class, name) (class::interpose_##name)
|
||||
|
||||
class DFHACK_EXPORT VMethodInterposeLinkBase {
|
||||
/*
|
||||
These link objects try to:
|
||||
1) Allow multiple hooks into the same vmethod
|
||||
2) Auto-remove hooks when a plugin is unloaded.
|
||||
*/
|
||||
friend class virtual_identity;
|
||||
|
||||
virtual_identity *host; // Class with the vtable
|
||||
int vmethod_idx;
|
||||
void *interpose_method; // Pointer to the code of the interposing method
|
||||
void *chain_mptr; // Pointer to the chain field below
|
||||
|
||||
bool applied;
|
||||
void *saved_chain; // Previous pointer to the code
|
||||
VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method
|
||||
|
||||
// inherited vtable members
|
||||
std::set<virtual_identity*> child_hosts;
|
||||
std::set<VMethodInterposeLinkBase*> child_next;
|
||||
|
||||
void set_chain(void *chain);
|
||||
void on_host_delete(virtual_identity *host);
|
||||
|
||||
VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id);
|
||||
void find_child_hosts(virtual_identity *cur, void *vmptr);
|
||||
public:
|
||||
VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr);
|
||||
~VMethodInterposeLinkBase();
|
||||
|
||||
bool is_applied() { return applied; }
|
||||
bool apply();
|
||||
void remove();
|
||||
};
|
||||
|
||||
template<class Base, class Ptr>
|
||||
class VMethodInterposeLink : public VMethodInterposeLinkBase {
|
||||
public:
|
||||
Ptr chain;
|
||||
|
||||
operator Ptr () { return chain; }
|
||||
|
||||
template<class Ptr2>
|
||||
VMethodInterposeLink(Ptr target, Ptr2 src)
|
||||
: VMethodInterposeLinkBase(
|
||||
&Base::_identity,
|
||||
vmethod_pointer_to_idx(target),
|
||||
method_pointer_to_addr(src),
|
||||
&chain
|
||||
)
|
||||
{ src = target; /* check compatibility */ }
|
||||
};
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "Export.h"
|
||||
#include "Module.h"
|
||||
#include "BitArray.h"
|
||||
#include "ColorText.h"
|
||||
#include <string>
|
||||
|
||||
#include "DataDefs.h"
|
||||
#include "df/graphic.h"
|
||||
#include "df/viewscreen.h"
|
||||
|
||||
/**
|
||||
* \defgroup grp_screen utilities for painting to the screen
|
||||
* @ingroup grp_screen
|
||||
*/
|
||||
|
||||
namespace DFHack
|
||||
{
|
||||
class Core;
|
||||
|
||||
/**
|
||||
* The Screen module
|
||||
* \ingroup grp_modules
|
||||
* \ingroup grp_screen
|
||||
*/
|
||||
namespace Screen
|
||||
{
|
||||
/// Data structure describing all properties a screen tile can have
|
||||
struct Pen {
|
||||
// Ordinary text symbol
|
||||
char ch;
|
||||
int8_t fg, bg;
|
||||
bool bold;
|
||||
|
||||
// Graphics tile
|
||||
int tile;
|
||||
enum TileMode {
|
||||
AsIs, // Tile colors used without modification
|
||||
CharColor, // The fg/bg pair is used
|
||||
TileColor // The fields below are used
|
||||
} tile_mode;
|
||||
int8_t tile_fg, tile_bg;
|
||||
|
||||
Pen(char ch = 0, int8_t fg = 7, int8_t bg = 0, int tile = 0, bool color_tile = false)
|
||||
: ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)),
|
||||
tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0)
|
||||
{}
|
||||
Pen(char ch, int8_t fg, int8_t bg, bool bold, int tile = 0, bool color_tile = false)
|
||||
: ch(ch), fg(fg), bg(bg), bold(bold),
|
||||
tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0)
|
||||
{}
|
||||
Pen(char ch, int8_t fg, int8_t bg, int tile, int8_t tile_fg, int8_t tile_bg)
|
||||
: ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)),
|
||||
tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg)
|
||||
{}
|
||||
Pen(char ch, int8_t fg, int8_t bg, bool bold, int tile, int8_t tile_fg, int8_t tile_bg)
|
||||
: ch(ch), fg(fg), bg(bg), bold(bold),
|
||||
tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg)
|
||||
{}
|
||||
};
|
||||
|
||||
DFHACK_EXPORT df::coord2d getMousePos();
|
||||
DFHACK_EXPORT df::coord2d getWindowSize();
|
||||
|
||||
/// Returns the state of [GRAPHICS:YES/NO]
|
||||
DFHACK_EXPORT bool inGraphicsMode();
|
||||
|
||||
/// Paint one screen tile with the given pen
|
||||
DFHACK_EXPORT bool paintTile(const Pen &pen, int x, int y);
|
||||
|
||||
/// Paint a string onto the screen. Ignores ch and tile of pen.
|
||||
DFHACK_EXPORT bool paintString(const Pen &pen, int x, int y, const std::string &text);
|
||||
|
||||
/// Fills a rectangle with one pen. Possibly more efficient than a loop over paintTile.
|
||||
DFHACK_EXPORT bool fillRect(const Pen &pen, int x1, int y1, int x2, int y2);
|
||||
|
||||
/// Draws a standard dark gray window border with a title string
|
||||
DFHACK_EXPORT bool drawBorder(const std::string &title);
|
||||
|
||||
/// Wipes the screen to full black
|
||||
DFHACK_EXPORT bool clear();
|
||||
|
||||
/// Requests repaint
|
||||
DFHACK_EXPORT bool invalidate();
|
||||
|
||||
/// Find a loaded graphics tile from graphics raws.
|
||||
DFHACK_EXPORT bool findGraphicsTile(const std::string &page, int x, int y, int *ptile, int *pgs = NULL);
|
||||
|
||||
// Push and remove viewscreens
|
||||
DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL);
|
||||
DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false);
|
||||
DFHACK_EXPORT bool isDismissed(df::viewscreen *screen);
|
||||
}
|
||||
|
||||
class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen {
|
||||
df::coord2d last_size;
|
||||
void check_resize();
|
||||
|
||||
protected:
|
||||
bool text_input_mode;
|
||||
|
||||
public:
|
||||
dfhack_viewscreen();
|
||||
virtual ~dfhack_viewscreen();
|
||||
|
||||
static bool is_instance(df::viewscreen *screen);
|
||||
|
||||
virtual void logic();
|
||||
virtual void render();
|
||||
|
||||
virtual int8_t movies_okay() { return 1; }
|
||||
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 {
|
||||
std::string focus;
|
||||
|
||||
void update_focus(lua_State *L, int idx);
|
||||
|
||||
bool safe_call_lua(int (*pf)(lua_State *), int args, int rvs);
|
||||
static dfhack_lua_viewscreen *get_self(lua_State *L);
|
||||
|
||||
static int do_destroy(lua_State *L);
|
||||
static int do_render(lua_State *L);
|
||||
static int do_notify(lua_State *L);
|
||||
static int do_input(lua_State *L);
|
||||
|
||||
public:
|
||||
dfhack_lua_viewscreen(lua_State *L, int table_idx);
|
||||
virtual ~dfhack_lua_viewscreen();
|
||||
|
||||
static df::viewscreen *get_pointer(lua_State *L, int idx, bool make);
|
||||
|
||||
virtual bool is_lua_screen() { return true; }
|
||||
virtual std::string getFocusString() { return focus; }
|
||||
|
||||
virtual void render();
|
||||
virtual void logic();
|
||||
virtual void help();
|
||||
virtual void resize(int w, int h);
|
||||
virtual void feed(std::set<df::interface_key> *keys);
|
||||
|
||||
virtual void onShow();
|
||||
virtual void onDismiss();
|
||||
};
|
||||
}
|
@ -0,0 +1,403 @@
|
||||
-- Viewscreen implementation utility collection.
|
||||
|
||||
local _ENV = mkmodule('gui')
|
||||
|
||||
local dscreen = dfhack.screen
|
||||
|
||||
USE_GRAPHICS = dscreen.inGraphicsMode()
|
||||
|
||||
CLEAR_PEN = {ch=32,fg=0,bg=0}
|
||||
|
||||
function simulateInput(screen,...)
|
||||
local keys = {}
|
||||
local function push_key(arg)
|
||||
local kv = arg
|
||||
if type(arg) == 'string' then
|
||||
kv = df.interface_key[arg]
|
||||
if kv == nil then
|
||||
error('Invalid keycode: '..arg)
|
||||
end
|
||||
end
|
||||
if type(kv) == 'number' then
|
||||
keys[#keys+1] = kv
|
||||
end
|
||||
end
|
||||
for i = 1,select('#',...) do
|
||||
local arg = select(i,...)
|
||||
if arg ~= nil then
|
||||
local t = type(arg)
|
||||
if type(arg) == 'table' then
|
||||
for k,v in pairs(arg) do
|
||||
if v == true then
|
||||
push_key(k)
|
||||
else
|
||||
push_key(v)
|
||||
end
|
||||
end
|
||||
else
|
||||
push_key(arg)
|
||||
end
|
||||
end
|
||||
end
|
||||
dscreen._doSimulateInput(screen, keys)
|
||||
end
|
||||
|
||||
function mkdims_xy(x1,y1,x2,y2)
|
||||
return { x1=x1, y1=y1, x2=x2, y2=y2, width=x2-x1+1, height=y2-y1+1 }
|
||||
end
|
||||
function mkdims_wh(x1,y1,w,h)
|
||||
return { x1=x1, y1=y1, x2=x1+w-1, y2=y1+h-1, width=w, height=h }
|
||||
end
|
||||
function inset(rect,dx1,dy1,dx2,dy2)
|
||||
return mkdims_xy(
|
||||
rect.x1+dx1, rect.y1+dy1,
|
||||
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
|
||||
return default or {}
|
||||
elseif type(pen) ~= 'table' then
|
||||
return {fg=pen,bg=bg,bold=bold}
|
||||
else
|
||||
return pen
|
||||
end
|
||||
end
|
||||
|
||||
----------------------------
|
||||
-- Clipped painter object --
|
||||
----------------------------
|
||||
|
||||
Painter = defclass(Painter, nil)
|
||||
|
||||
function Painter.new(rect, pen)
|
||||
rect = rect or mkdims_wh(0,0,dscreen.getWindowSize())
|
||||
local self = {
|
||||
x1 = rect.x1, clip_x1 = rect.x1,
|
||||
y1 = rect.y1, clip_y1 = rect.y1,
|
||||
x2 = rect.x2, clip_x2 = rect.x2,
|
||||
y2 = rect.y2, clip_y2 = rect.y2,
|
||||
width = rect.x2-rect.x1+1,
|
||||
height = rect.y2-rect.y1+1,
|
||||
cur_pen = to_pen(nil, pen or COLOR_GREY)
|
||||
}
|
||||
return mkinstance(Painter, self):seek(0,0)
|
||||
end
|
||||
|
||||
function Painter:isValidPos()
|
||||
return self.x >= self.clip_x1 and self.x <= self.clip_x2
|
||||
and self.y >= self.clip_y1 and self.y <= self.clip_y2
|
||||
end
|
||||
|
||||
function Painter:viewport(x,y,w,h)
|
||||
local x1,y1 = self.x1+x, self.y1+y
|
||||
local x2,y2 = x1+w-1, y1+h-1
|
||||
local vp = {
|
||||
-- Logical viewport
|
||||
x1 = x1, y1 = y1, x2 = x2, y2 = y2,
|
||||
width = w, height = h,
|
||||
-- Actual clipping rect
|
||||
clip_x1 = math.max(self.clip_x1, x1),
|
||||
clip_y1 = math.max(self.clip_y1, y1),
|
||||
clip_x2 = math.min(self.clip_x2, x2),
|
||||
clip_y2 = math.min(self.clip_y2, y2),
|
||||
-- Pen
|
||||
cur_pen = self.cur_pen
|
||||
}
|
||||
return mkinstance(Painter, vp):seek(0,0)
|
||||
end
|
||||
|
||||
function Painter:localX()
|
||||
return self.x - self.x1
|
||||
end
|
||||
|
||||
function Painter:localY()
|
||||
return self.y - self.y1
|
||||
end
|
||||
|
||||
function Painter:seek(x,y)
|
||||
if x then self.x = self.x1 + x end
|
||||
if y then self.y = self.y1 + y end
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:advance(dx,dy)
|
||||
if dx then self.x = self.x + dx end
|
||||
if dy then self.y = self.y + dy end
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:newline(dx)
|
||||
self.y = self.y + 1
|
||||
self.x = self.x1 + (dx or 0)
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:pen(pen,...)
|
||||
self.cur_pen = to_pen(self.cur_pen, pen, ...)
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:color(fg,bold,bg)
|
||||
self.cur_pen = copyall(self.cur_pen)
|
||||
self.cur_pen.fg = fg
|
||||
self.cur_pen.bold = bold
|
||||
if bg then self.cur_pen.bg = bg end
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:clear()
|
||||
dscreen.fillRect(CLEAR_PEN, self.clip_x1, self.clip_y1, self.clip_x2, self.clip_y2)
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:fill(x1,y1,x2,y2,pen,bg,bold)
|
||||
if type(x1) == 'table' then
|
||||
x1, y1, x2, y2, pen, bg, bold = x1.x1, x1.y1, x1.x2, x1.y2, y1, x2, y2
|
||||
end
|
||||
x1 = math.max(x1,self.clip_x1)
|
||||
y1 = math.max(y1,self.clip_y1)
|
||||
x2 = math.min(x2,self.clip_x2)
|
||||
y2 = math.min(y2,self.clip_y2)
|
||||
dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2)
|
||||
return self
|
||||
end
|
||||
|
||||
function Painter:char(char,pen,...)
|
||||
if self:isValidPos() then
|
||||
dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char)
|
||||
end
|
||||
return self:advance(1, nil)
|
||||
end
|
||||
|
||||
function Painter:tile(char,tile,pen,...)
|
||||
if self:isValidPos() then
|
||||
dscreen.paintTile(to_pen(self.cur_pen, pen, ...), self.x, self.y, char, tile)
|
||||
end
|
||||
return self:advance(1, nil)
|
||||
end
|
||||
|
||||
function Painter:string(text,pen,...)
|
||||
if self.y >= self.clip_y1 and self.y <= self.clip_y2 then
|
||||
local dx = 0
|
||||
if self.x < self.clip_x1 then
|
||||
dx = self.clip_x1 - self.x
|
||||
end
|
||||
local len = #text
|
||||
if self.x + len - 1 > self.clip_x2 then
|
||||
len = self.clip_x2 - self.x + 1
|
||||
end
|
||||
if len > dx then
|
||||
dscreen.paintString(
|
||||
to_pen(self.cur_pen, pen, ...),
|
||||
self.x+dx, self.y,
|
||||
string.sub(text,dx+1,len)
|
||||
)
|
||||
end
|
||||
end
|
||||
return self:advance(#text, nil)
|
||||
end
|
||||
|
||||
------------------------
|
||||
-- Base screen object --
|
||||
------------------------
|
||||
|
||||
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
|
||||
|
||||
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()
|
||||
else
|
||||
dscreen.clear()
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:sendInputToParent(...)
|
||||
if self._native and self._native.parent then
|
||||
simulateInput(self._native.parent, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:show(below)
|
||||
if self._native then
|
||||
error("This screen is already on display")
|
||||
end
|
||||
self:onAboutToShow(below)
|
||||
if not dscreen.show(self, below) then
|
||||
error('Could not show screen')
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:onAboutToShow()
|
||||
end
|
||||
|
||||
function Screen:onShow()
|
||||
self:updateLayout()
|
||||
end
|
||||
|
||||
function Screen:dismiss()
|
||||
if self._native then
|
||||
dscreen.dismiss(self)
|
||||
end
|
||||
end
|
||||
|
||||
function Screen:onDismiss()
|
||||
end
|
||||
|
||||
function Screen:onDestroy()
|
||||
end
|
||||
|
||||
function Screen:onResize(w,h)
|
||||
self:updateLayout()
|
||||
end
|
||||
|
||||
function Screen:updateLayout()
|
||||
end
|
||||
|
||||
------------------------
|
||||
-- Framed screen object --
|
||||
------------------------
|
||||
|
||||
-- Plain grey-colored frame.
|
||||
GREY_FRAME = {
|
||||
frame_pen = { ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY },
|
||||
title_pen = { fg = COLOR_BLACK, bg = COLOR_WHITE },
|
||||
signature_pen = { fg = COLOR_BLACK, bg = COLOR_GREY },
|
||||
}
|
||||
|
||||
-- The usual boundary used by the DF screens. Often has fancy pattern in tilesets.
|
||||
BOUNDARY_FRAME = {
|
||||
frame_pen = { ch = 0xDB, fg = COLOR_DARKGREY, bg = COLOR_BLACK },
|
||||
title_pen = { fg = COLOR_BLACK, bg = COLOR_GREY },
|
||||
signature_pen = { fg = COLOR_BLACK, bg = COLOR_DARKGREY },
|
||||
}
|
||||
|
||||
GREY_LINE_FRAME = {
|
||||
frame_pen = { ch = 206, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
h_frame_pen = { ch = 205, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
v_frame_pen = { ch = 186, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
lt_frame_pen = { ch = 201, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
lb_frame_pen = { ch = 200, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
rt_frame_pen = { ch = 187, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
rb_frame_pen = { ch = 188, fg = COLOR_GREY, bg = COLOR_BLACK },
|
||||
title_pen = { fg = COLOR_BLACK, bg = COLOR_GREY },
|
||||
signature_pen = { fg = COLOR_DARKGREY, bg = COLOR_BLACK },
|
||||
}
|
||||
|
||||
function paint_frame(x1,y1,x2,y2,style,title)
|
||||
local pen = style.frame_pen
|
||||
dscreen.paintTile(style.lt_frame_pen or pen, x1, y1)
|
||||
dscreen.paintTile(style.rt_frame_pen or pen, x2, y1)
|
||||
dscreen.paintTile(style.lb_frame_pen or pen, x1, y2)
|
||||
dscreen.paintTile(style.rb_frame_pen or pen, x2, y2)
|
||||
dscreen.fillRect(style.t_frame_pen or style.h_frame_pen or pen,x1+1,y1,x2-1,y1)
|
||||
dscreen.fillRect(style.b_frame_pen or style.h_frame_pen or pen,x1+1,y2,x2-1,y2)
|
||||
dscreen.fillRect(style.l_frame_pen or style.v_frame_pen or pen,x1,y1+1,x1,y2-1)
|
||||
dscreen.fillRect(style.r_frame_pen or style.v_frame_pen or pen,x2,y1+1,x2,y2-1)
|
||||
dscreen.paintString(style.signature_pen or style.title_pen or pen,x2-7,y2,"DFHack")
|
||||
|
||||
if title then
|
||||
local x = math.max(0,math.floor((x2-x1-3-#title)/2)) + x1
|
||||
local tstr = ' '..title..' '
|
||||
if #tstr > x2-x1-1 then
|
||||
tstr = string.sub(tstr,1,x2-x1-1)
|
||||
end
|
||||
dscreen.paintString(style.title_pen or pen, x, y1, tstr)
|
||||
end
|
||||
end
|
||||
|
||||
FramedScreen = defclass(FramedScreen, Screen)
|
||||
|
||||
FramedScreen.frame_style = BOUNDARY_FRAME
|
||||
|
||||
local function hint_coord(gap,hint)
|
||||
if hint and hint > 0 then
|
||||
return math.min(hint,gap)
|
||||
elseif hint and hint < 0 then
|
||||
return math.max(0,gap-hint)
|
||||
else
|
||||
return math.floor(gap/2)
|
||||
end
|
||||
end
|
||||
|
||||
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)
|
||||
local gw, gh = iw-width, ih-height
|
||||
local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint)
|
||||
self.frame_rect = mkdims_wh(x1+1,y1+1,width,height)
|
||||
self.frame_opaque = (gw == 0 and gh == 0)
|
||||
end
|
||||
|
||||
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()
|
||||
local rect = self.frame_rect
|
||||
local x1,y1,x2,y2 = rect.x1-1, rect.y1-1, rect.x2+1, rect.y2+1
|
||||
|
||||
if self.frame_opaque then
|
||||
dscreen.clear()
|
||||
else
|
||||
self:renderParent()
|
||||
dscreen.fillRect(CLEAR_PEN,x1,y1,x2,y2)
|
||||
end
|
||||
|
||||
paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title)
|
||||
|
||||
self:onRenderBody(Painter.new(rect))
|
||||
end
|
||||
|
||||
function FramedScreen:onRenderBody(dc)
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,327 @@
|
||||
-- Support for messing with the dwarfmode screen
|
||||
|
||||
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
|
||||
MENU_WIDTH = 30
|
||||
|
||||
function getPanelLayout()
|
||||
local sw, sh = dscreen.getWindowSize()
|
||||
local view_height = sh-2
|
||||
local view_rb = sw-1
|
||||
local area_x2 = sw-AREA_MAP_WIDTH-2
|
||||
local menu_x2 = sw-MENU_WIDTH-2
|
||||
local menu_x1 = area_x2-MENU_WIDTH-1
|
||||
local area_pos = df.global.ui_area_map_width
|
||||
local menu_pos = df.global.ui_menu_width
|
||||
local rv = {}
|
||||
|
||||
if area_pos < 3 then
|
||||
rv.area_map = gui.mkdims_xy(area_x2+1,1,view_rb-1,view_height)
|
||||
view_rb = area_x2
|
||||
end
|
||||
if menu_pos < area_pos or df.global.ui.main.mode ~= 0 then
|
||||
if menu_pos >= area_pos then
|
||||
rv.menu_forced = true
|
||||
menu_pos = area_pos-1
|
||||
end
|
||||
local menu_x = menu_x2
|
||||
if menu_pos < 2 then menu_x = menu_x1 end
|
||||
rv.menu = gui.mkdims_xy(menu_x+1,1,view_rb-1,view_height)
|
||||
view_rb = menu_x
|
||||
end
|
||||
rv.area_pos = area_pos
|
||||
rv.menu_pos = menu_pos
|
||||
rv.map = gui.mkdims_xy(1,1,view_rb-1,view_height)
|
||||
return rv
|
||||
end
|
||||
|
||||
function getCursorPos()
|
||||
if g_cursor ~= -30000 then
|
||||
return copyall(g_cursor)
|
||||
end
|
||||
end
|
||||
|
||||
function setCursorPos(cursor)
|
||||
df.global.cursor = cursor
|
||||
end
|
||||
|
||||
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)
|
||||
local self = gui.mkdims_wh(x,y,map.width,map.height)
|
||||
self.z = z
|
||||
return mkinstance(Viewport, self)
|
||||
end
|
||||
|
||||
function Viewport.get(layout)
|
||||
return Viewport.make(
|
||||
(layout or getPanelLayout()).map,
|
||||
df.global.window_x,
|
||||
df.global.window_y,
|
||||
df.global.window_z
|
||||
)
|
||||
end
|
||||
|
||||
function Viewport:resize(layout)
|
||||
return Viewport.make(
|
||||
(layout or getPanelLayout()).map,
|
||||
self.x1, self.y1, self.z
|
||||
)
|
||||
end
|
||||
|
||||
function Viewport:set()
|
||||
local vp = self:clip()
|
||||
df.global.window_x = vp.x1
|
||||
df.global.window_y = vp.y1
|
||||
df.global.window_z = vp.z
|
||||
return vp
|
||||
end
|
||||
|
||||
function Viewport:clip(x,y,z)
|
||||
return self:make(
|
||||
math.max(0, math.min(x or self.x1, world_map.x_count-self.width)),
|
||||
math.max(0, math.min(y or self.y1, world_map.y_count-self.height)),
|
||||
math.max(0, math.min(z or self.z, world_map.z_count-1))
|
||||
)
|
||||
end
|
||||
|
||||
function Viewport:isVisibleXY(target,gap)
|
||||
gap = gap or 0
|
||||
|
||||
return math.max(target.x-gap,0) >= self.x1
|
||||
and math.min(target.x+gap,world_map.x_count-1) <= self.x2
|
||||
and math.max(target.y-gap,0) >= self.y1
|
||||
and math.min(target.y+gap,world_map.y_count-1) <= self.y2
|
||||
end
|
||||
|
||||
function Viewport:isVisible(target,gap)
|
||||
gap = gap or 0
|
||||
|
||||
return self:isVisibleXY(target,gap) and target.z == self.z
|
||||
end
|
||||
|
||||
function Viewport:centerOn(target)
|
||||
return self:clip(
|
||||
target.x - math.floor(self.width/2),
|
||||
target.y - math.floor(self.height/2),
|
||||
target.z
|
||||
)
|
||||
end
|
||||
|
||||
function Viewport:scrollTo(target,gap)
|
||||
gap = math.max(0, gap or 5)
|
||||
if gap*2 >= math.min(self.width, self.height) then
|
||||
gap = math.floor(math.min(self.width, self.height)/2)
|
||||
end
|
||||
local x = math.min(self.x1, target.x-gap)
|
||||
x = math.max(x, target.x+gap+1-self.width)
|
||||
local y = math.min(self.y1, target.y-gap)
|
||||
y = math.max(y, target.y+gap+1-self.height)
|
||||
return self:clip(x, y, target.z)
|
||||
end
|
||||
|
||||
function Viewport:reveal(target,gap,max_scroll,scroll_gap,scroll_z)
|
||||
gap = math.max(0, gap or 5)
|
||||
if self:isVisible(target, gap) then
|
||||
return self
|
||||
end
|
||||
|
||||
max_scroll = math.max(0, max_scroll or 5)
|
||||
if self:isVisibleXY(target, -max_scroll)
|
||||
and (scroll_z or target.z == self.z) then
|
||||
return self:scrollTo(target, scroll_gap or gap)
|
||||
else
|
||||
return self:centerOn(target)
|
||||
end
|
||||
end
|
||||
|
||||
MOVEMENT_KEYS = {
|
||||
CURSOR_UP = { 0, -1, 0 }, CURSOR_DOWN = { 0, 1, 0 },
|
||||
CURSOR_LEFT = { -1, 0, 0 }, CURSOR_RIGHT = { 1, 0, 0 },
|
||||
CURSOR_UPLEFT = { -1, -1, 0 }, CURSOR_UPRIGHT = { 1, -1, 0 },
|
||||
CURSOR_DOWNLEFT = { -1, 1, 0 }, CURSOR_DOWNRIGHT = { 1, 1, 0 },
|
||||
CURSOR_UP_FAST = { 0, -1, 0, true }, CURSOR_DOWN_FAST = { 0, 1, 0, true },
|
||||
CURSOR_LEFT_FAST = { -1, 0, 0, true }, CURSOR_RIGHT_FAST = { 1, 0, 0, true },
|
||||
CURSOR_UPLEFT_FAST = { -1, -1, 0, true }, CURSOR_UPRIGHT_FAST = { 1, -1, 0, true },
|
||||
CURSOR_DOWNLEFT_FAST = { -1, 1, 0, true }, CURSOR_DOWNRIGHT_FAST = { 1, 1, 0, true },
|
||||
CURSOR_UP_Z = { 0, 0, 1 }, CURSOR_DOWN_Z = { 0, 0, -1 },
|
||||
CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 },
|
||||
}
|
||||
|
||||
function Viewport:scrollByKey(key)
|
||||
local info = MOVEMENT_KEYS[key]
|
||||
if info then
|
||||
local delta = 10
|
||||
if info[4] then delta = 20 end
|
||||
|
||||
return self:clip(
|
||||
self.x1 + delta*info[1],
|
||||
self.y1 + delta*info[2],
|
||||
self.z + info[3]
|
||||
)
|
||||
else
|
||||
return self
|
||||
end
|
||||
end
|
||||
|
||||
DwarfOverlay = defclass(DwarfOverlay, gui.Screen)
|
||||
|
||||
function DwarfOverlay:updateLayout()
|
||||
self.df_layout = getPanelLayout()
|
||||
end
|
||||
|
||||
function DwarfOverlay:getViewport(old_vp)
|
||||
if old_vp then
|
||||
return old_vp:resize(self.df_layout)
|
||||
else
|
||||
return Viewport.get(self.df_layout)
|
||||
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
|
||||
self:sendInputToParent(code)
|
||||
return code
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DwarfOverlay:simulateViewScroll(keys, anchor, no_clip_cursor)
|
||||
local layout = self.df_layout
|
||||
local cursor = getCursorPos()
|
||||
|
||||
anchor = anchor or cursor
|
||||
|
||||
if anchor and keys.A_MOVE_SAME_SQUARE then
|
||||
self:getViewport():centerOn(anchor):set()
|
||||
return 'A_MOVE_SAME_SQUARE'
|
||||
end
|
||||
|
||||
for code,_ in pairs(MOVEMENT_KEYS) do
|
||||
if keys[code] then
|
||||
local vp = self:getViewport():scrollByKey(code)
|
||||
if (cursor and not no_clip_cursor) or no_clip_cursor == false then
|
||||
vp = vp:reveal(anchor,4,20,4,true)
|
||||
end
|
||||
vp:set()
|
||||
|
||||
return code
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function DwarfOverlay:onAboutToShow(below)
|
||||
local screen = dfhack.gui.getCurViewscreen()
|
||||
if below then screen = below.parent end
|
||||
if not df.viewscreen_dwarfmodest:is_instance(screen) then
|
||||
error("This screen requires the main dwarfmode view")
|
||||
end
|
||||
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)
|
||||
|
||||
self:updateLayout()
|
||||
if not self.df_layout.menu then
|
||||
error("The menu panel of dwarfmode is not visible")
|
||||
end
|
||||
end
|
||||
|
||||
function MenuOverlay:onRender()
|
||||
self:renderParent()
|
||||
|
||||
local menu = self.df_layout.menu
|
||||
if menu then
|
||||
-- Paint signature on the frame.
|
||||
dscreen.paintString(
|
||||
{fg=COLOR_BLACK,bg=COLOR_DARKGREY},
|
||||
menu.x1+1, menu.y2+1, "DFHack"
|
||||
)
|
||||
|
||||
self:onRenderBody(gui.Painter.new(menu))
|
||||
end
|
||||
end
|
||||
|
||||
return _ENV
|
@ -0,0 +1,604 @@
|
||||
/*
|
||||
https://github.com/peterix/dfhack
|
||||
Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must
|
||||
not claim that you wrote the original software. If you use this
|
||||
software in a product, an acknowledgment in the product documentation
|
||||
would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
*/
|
||||
|
||||
|
||||
#include "Internal.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
using namespace std;
|
||||
|
||||
#include "modules/Screen.h"
|
||||
#include "MemAccess.h"
|
||||
#include "VersionInfo.h"
|
||||
#include "Types.h"
|
||||
#include "Error.h"
|
||||
#include "ModuleFactory.h"
|
||||
#include "Core.h"
|
||||
#include "PluginManager.h"
|
||||
#include "LuaTools.h"
|
||||
|
||||
#include "MiscUtils.h"
|
||||
|
||||
using namespace DFHack;
|
||||
|
||||
#include "DataDefs.h"
|
||||
#include "df/init.h"
|
||||
#include "df/texture_handler.h"
|
||||
#include "df/tile_page.h"
|
||||
#include "df/interfacest.h"
|
||||
#include "df/enabler.h"
|
||||
|
||||
using namespace df::enums;
|
||||
using df::global::init;
|
||||
using df::global::gps;
|
||||
using df::global::texture;
|
||||
using df::global::gview;
|
||||
using df::global::enabler;
|
||||
|
||||
using Screen::Pen;
|
||||
|
||||
/*
|
||||
* Screen painting API.
|
||||
*/
|
||||
|
||||
df::coord2d Screen::getMousePos()
|
||||
{
|
||||
if (!gps || (enabler && !enabler->tracking_on))
|
||||
return df::coord2d(-1, -1);
|
||||
|
||||
return df::coord2d(gps->mouse_x, gps->mouse_y);
|
||||
}
|
||||
|
||||
df::coord2d Screen::getWindowSize()
|
||||
{
|
||||
if (!gps) return df::coord2d(80, 25);
|
||||
|
||||
return df::coord2d(gps->dimx, gps->dimy);
|
||||
}
|
||||
|
||||
bool Screen::inGraphicsMode()
|
||||
{
|
||||
return init && init->display.flag.is_set(init_display_flags::USE_GRAPHICS);
|
||||
}
|
||||
|
||||
static void doSetTile(const Pen &pen, int index)
|
||||
{
|
||||
auto screen = gps->screen + index*4;
|
||||
screen[0] = uint8_t(pen.ch);
|
||||
screen[1] = uint8_t(pen.fg) & 15;
|
||||
screen[2] = uint8_t(pen.bg) & 15;
|
||||
screen[3] = uint8_t(pen.bold) & 1;
|
||||
gps->screentexpos[index] = pen.tile;
|
||||
gps->screentexpos_addcolor[index] = (pen.tile_mode == Screen::Pen::CharColor);
|
||||
gps->screentexpos_grayscale[index] = (pen.tile_mode == Screen::Pen::TileColor);
|
||||
gps->screentexpos_cf[index] = pen.tile_fg;
|
||||
gps->screentexpos_cbr[index] = pen.tile_bg;
|
||||
}
|
||||
|
||||
bool Screen::paintTile(const Pen &pen, int x, int y)
|
||||
{
|
||||
if (!gps) return false;
|
||||
|
||||
int dimx = gps->dimx, dimy = gps->dimy;
|
||||
if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false;
|
||||
|
||||
doSetTile(pen, x*dimy + y);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text)
|
||||
{
|
||||
if (!gps || y < 0 || y >= gps->dimy) return false;
|
||||
|
||||
Pen tmp(pen);
|
||||
bool ok = false;
|
||||
|
||||
for (size_t i = -std::min(0,x); i < text.size(); i++)
|
||||
{
|
||||
if (x + i >= size_t(gps->dimx))
|
||||
break;
|
||||
|
||||
tmp.ch = text[i];
|
||||
tmp.tile = (pen.tile ? pen.tile + uint8_t(text[i]) : 0);
|
||||
paintTile(tmp, x+i, y);
|
||||
ok = true;
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2)
|
||||
{
|
||||
if (!gps) return false;
|
||||
|
||||
if (x1 < 0) x1 = 0;
|
||||
if (y1 < 0) y1 = 0;
|
||||
if (x2 >= gps->dimx) x2 = gps->dimx-1;
|
||||
if (y2 >= gps->dimy) y2 = gps->dimy-1;
|
||||
if (x1 > x2 || y1 > y2) return false;
|
||||
|
||||
for (int x = x1; x <= x2; x++)
|
||||
{
|
||||
int index = x*gps->dimy;
|
||||
|
||||
for (int y = y1; y <= y2; y++)
|
||||
doSetTile(pen, index+y);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Screen::drawBorder(const std::string &title)
|
||||
{
|
||||
if (!gps) return false;
|
||||
|
||||
int dimx = gps->dimx, dimy = gps->dimy;
|
||||
Pen border(0xDB, 8);
|
||||
Pen text(0, 0, 7);
|
||||
Pen signature(0, 0, 8);
|
||||
|
||||
for (int x = 0; x < dimx; x++)
|
||||
{
|
||||
doSetTile(border, x * dimy + 0);
|
||||
doSetTile(border, x * dimy + dimy - 1);
|
||||
}
|
||||
for (int y = 0; y < dimy; y++)
|
||||
{
|
||||
doSetTile(border, 0 * dimy + y);
|
||||
doSetTile(border, (dimx - 1) * dimy + y);
|
||||
}
|
||||
|
||||
paintString(signature, dimx-8, dimy-1, "DFHack");
|
||||
|
||||
return paintString(text, (dimx - title.length()) / 2, 0, title);
|
||||
}
|
||||
|
||||
bool Screen::clear()
|
||||
{
|
||||
if (!gps) return false;
|
||||
|
||||
return fillRect(Pen(' ',0,0,false), 0, 0, gps->dimx-1, gps->dimy-1);
|
||||
}
|
||||
|
||||
bool Screen::invalidate()
|
||||
{
|
||||
if (!enabler) return false;
|
||||
|
||||
enabler->flag.bits.render = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *ptile, int *pgs)
|
||||
{
|
||||
if (!gps || !texture || x < 0 || y < 0) return false;
|
||||
|
||||
for (size_t i = 0; i < texture->page.size(); i++)
|
||||
{
|
||||
auto page = texture->page[i];
|
||||
if (!page->loaded || page->token != pagename) continue;
|
||||
|
||||
if (x >= page->page_dim_x || y >= page->page_dim_y)
|
||||
break;
|
||||
int idx = y*page->page_dim_x + x;
|
||||
if (size_t(idx) >= page->texpos.size())
|
||||
break;
|
||||
|
||||
if (ptile) *ptile = page->texpos[idx];
|
||||
if (pgs) *pgs = page->texpos_gs[idx];
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Screen::show(df::viewscreen *screen, df::viewscreen *before)
|
||||
{
|
||||
CHECK_NULL_POINTER(screen);
|
||||
CHECK_INVALID_ARGUMENT(!screen->parent && !screen->child);
|
||||
|
||||
if (!gps || !gview) return false;
|
||||
|
||||
df::viewscreen *parent = &gview->view;
|
||||
while (parent && parent->child != before)
|
||||
parent = parent->child;
|
||||
|
||||
if (!parent) return false;
|
||||
|
||||
gps->force_full_display_count += 2;
|
||||
|
||||
screen->child = parent->child;
|
||||
screen->parent = parent;
|
||||
parent->child = screen;
|
||||
if (screen->child)
|
||||
screen->child->parent = screen;
|
||||
|
||||
if (dfhack_viewscreen::is_instance(screen))
|
||||
static_cast<dfhack_viewscreen*>(screen)->onShow();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Screen::dismiss(df::viewscreen *screen, bool to_first)
|
||||
{
|
||||
CHECK_NULL_POINTER(screen);
|
||||
|
||||
if (screen->breakdown_level != interface_breakdown_types::NONE)
|
||||
return;
|
||||
|
||||
if (to_first)
|
||||
screen->breakdown_level = interface_breakdown_types::TOFIRST;
|
||||
else
|
||||
screen->breakdown_level = interface_breakdown_types::STOPSCREEN;
|
||||
|
||||
if (dfhack_viewscreen::is_instance(screen))
|
||||
static_cast<dfhack_viewscreen*>(screen)->onDismiss();
|
||||
}
|
||||
|
||||
bool Screen::isDismissed(df::viewscreen *screen)
|
||||
{
|
||||
CHECK_NULL_POINTER(screen);
|
||||
|
||||
return screen->breakdown_level != interface_breakdown_types::NONE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Base DFHack viewscreen.
|
||||
*/
|
||||
|
||||
static std::set<df::viewscreen*> dfhack_screens;
|
||||
|
||||
dfhack_viewscreen::dfhack_viewscreen() : text_input_mode(false)
|
||||
{
|
||||
dfhack_screens.insert(this);
|
||||
|
||||
last_size = Screen::getWindowSize();
|
||||
}
|
||||
|
||||
dfhack_viewscreen::~dfhack_viewscreen()
|
||||
{
|
||||
dfhack_screens.erase(this);
|
||||
}
|
||||
|
||||
bool dfhack_viewscreen::is_instance(df::viewscreen *screen)
|
||||
{
|
||||
return dfhack_screens.count(screen) != 0;
|
||||
}
|
||||
|
||||
void dfhack_viewscreen::check_resize()
|
||||
{
|
||||
auto size = Screen::getWindowSize();
|
||||
|
||||
if (size != last_size)
|
||||
{
|
||||
last_size = size;
|
||||
resize(size.x, size.y);
|
||||
}
|
||||
}
|
||||
|
||||
void dfhack_viewscreen::logic()
|
||||
{
|
||||
check_resize();
|
||||
|
||||
// Various stuff works poorly unless always repainting
|
||||
Screen::invalidate();
|
||||
}
|
||||
|
||||
void dfhack_viewscreen::render()
|
||||
{
|
||||
check_resize();
|
||||
}
|
||||
|
||||
bool dfhack_viewscreen::key_conflict(df::interface_key key)
|
||||
{
|
||||
if (key == interface_key::OPTIONS)
|
||||
return true;
|
||||
|
||||
if (text_input_mode)
|
||||
{
|
||||
if (key == interface_key::HELP || key == interface_key::MOVIES)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Lua-backed viewscreen.
|
||||
*/
|
||||
|
||||
static int DFHACK_LUA_VS_TOKEN = 0;
|
||||
|
||||
df::viewscreen *dfhack_lua_viewscreen::get_pointer(lua_State *L, int idx, bool make)
|
||||
{
|
||||
df::viewscreen *screen;
|
||||
|
||||
if (lua_istable(L, idx))
|
||||
{
|
||||
if (!Lua::IsCoreContext(L))
|
||||
luaL_error(L, "only the core context can create lua screens");
|
||||
|
||||
lua_rawgetp(L, idx, &DFHACK_LUA_VS_TOKEN);
|
||||
|
||||
if (!lua_isnil(L, -1))
|
||||
{
|
||||
if (make)
|
||||
luaL_error(L, "this screen is already on display");
|
||||
|
||||
screen = (df::viewscreen*)lua_touserdata(L, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!make)
|
||||
luaL_error(L, "this screen is not on display");
|
||||
|
||||
screen = new dfhack_lua_viewscreen(L, idx);
|
||||
}
|
||||
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
else
|
||||
screen = Lua::CheckDFObject<df::viewscreen>(L, idx);
|
||||
|
||||
return screen;
|
||||
}
|
||||
|
||||
bool dfhack_lua_viewscreen::safe_call_lua(int (*pf)(lua_State *), int args, int rvs)
|
||||
{
|
||||
CoreSuspendClaimer suspend;
|
||||
color_ostream_proxy out(Core::getInstance().getConsole());
|
||||
|
||||
auto L = Lua::Core::State;
|
||||
lua_pushcfunction(L, pf);
|
||||
if (args > 0) lua_insert(L, -args-1);
|
||||
lua_pushlightuserdata(L, this);
|
||||
if (args > 0) lua_insert(L, -args-1);
|
||||
|
||||
return Lua::Core::SafeCall(out, args+1, rvs);
|
||||
}
|
||||
|
||||
dfhack_lua_viewscreen *dfhack_lua_viewscreen::get_self(lua_State *L)
|
||||
{
|
||||
auto self = (dfhack_lua_viewscreen*)lua_touserdata(L, 1);
|
||||
lua_rawgetp(L, LUA_REGISTRYINDEX, self);
|
||||
if (!lua_istable(L, -1)) return NULL;
|
||||
return self;
|
||||
}
|
||||
|
||||
int dfhack_lua_viewscreen::do_destroy(lua_State *L)
|
||||
{
|
||||
auto self = get_self(L);
|
||||
if (!self) return 0;
|
||||
|
||||
lua_pushnil(L);
|
||||
lua_rawsetp(L, LUA_REGISTRYINDEX, self);
|
||||
|
||||
lua_pushnil(L);
|
||||
lua_rawsetp(L, -2, &DFHACK_LUA_VS_TOKEN);
|
||||
lua_pushnil(L);
|
||||
lua_setfield(L, -2, "_native");
|
||||
|
||||
lua_getfield(L, -1, "onDestroy");
|
||||
if (lua_isnil(L, -1))
|
||||
return 0;
|
||||
|
||||
lua_pushvalue(L, -2);
|
||||
lua_call(L, 1, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void dfhack_lua_viewscreen::update_focus(lua_State *L, int idx)
|
||||
{
|
||||
lua_getfield(L, idx, "text_input_mode");
|
||||
text_input_mode = lua_toboolean(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
lua_getfield(L, idx, "focus_path");
|
||||
auto str = lua_tostring(L, -1);
|
||||
if (!str) str = "";
|
||||
focus = str;
|
||||
lua_pop(L, 1);
|
||||
|
||||
if (focus.empty())
|
||||
focus = "lua";
|
||||
else
|
||||
focus = "lua/"+focus;
|
||||
}
|
||||
|
||||
int dfhack_lua_viewscreen::do_render(lua_State *L)
|
||||
{
|
||||
auto self = get_self(L);
|
||||
if (!self) return 0;
|
||||
|
||||
lua_getfield(L, -1, "onRender");
|
||||
|
||||
if (lua_isnil(L, -1))
|
||||
{
|
||||
Screen::clear();
|
||||
return 0;
|
||||
}
|
||||
|
||||
lua_pushvalue(L, -2);
|
||||
lua_call(L, 1, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int dfhack_lua_viewscreen::do_notify(lua_State *L)
|
||||
{
|
||||
int args = lua_gettop(L);
|
||||
|
||||
auto self = get_self(L);
|
||||
if (!self) return 0;
|
||||
|
||||
lua_pushvalue(L, 2);
|
||||
lua_gettable(L, -2);
|
||||
if (lua_isnil(L, -1))
|
||||
return 0;
|
||||
|
||||
// self field args table fn -> table fn table args
|
||||
lua_replace(L, 1);
|
||||
lua_copy(L, -1, 2);
|
||||
lua_insert(L, 1);
|
||||
lua_call(L, args-1, 1);
|
||||
|
||||
self->update_focus(L, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dfhack_lua_viewscreen::do_input(lua_State *L)
|
||||
{
|
||||
auto self = get_self(L);
|
||||
if (!self) return 0;
|
||||
|
||||
auto keys = (std::set<df::interface_key>*)lua_touserdata(L, 2);
|
||||
|
||||
lua_getfield(L, -1, "onInput");
|
||||
|
||||
if (lua_isnil(L, -1))
|
||||
{
|
||||
if (keys->count(interface_key::LEAVESCREEN))
|
||||
Screen::dismiss(self);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
lua_pushvalue(L, -2);
|
||||
|
||||
lua_createtable(L, 0, keys->size()+3);
|
||||
|
||||
for (auto it = keys->begin(); it != keys->end(); ++it)
|
||||
{
|
||||
auto key = *it;
|
||||
|
||||
if (auto name = enum_item_raw_key(key))
|
||||
lua_pushstring(L, name);
|
||||
else
|
||||
lua_pushinteger(L, key);
|
||||
|
||||
lua_pushboolean(L, true);
|
||||
lua_rawset(L, -3);
|
||||
|
||||
if (key >= interface_key::STRING_A000 &&
|
||||
key <= interface_key::STRING_A255)
|
||||
{
|
||||
lua_pushinteger(L, key - interface_key::STRING_A000);
|
||||
lua_setfield(L, -2, "_STRING");
|
||||
}
|
||||
}
|
||||
|
||||
if (enabler && enabler->tracking_on)
|
||||
{
|
||||
if (enabler->mouse_lbut) {
|
||||
lua_pushboolean(L, true);
|
||||
lua_setfield(L, -2, "_MOUSE_L");
|
||||
}
|
||||
if (enabler->mouse_rbut) {
|
||||
lua_pushboolean(L, true);
|
||||
lua_setfield(L, -2, "_MOUSE_R");
|
||||
}
|
||||
}
|
||||
|
||||
lua_call(L, 2, 0);
|
||||
self->update_focus(L, -1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
dfhack_lua_viewscreen::dfhack_lua_viewscreen(lua_State *L, int table_idx)
|
||||
{
|
||||
assert(Lua::IsCoreContext(L));
|
||||
|
||||
Lua::PushDFObject(L, (df::viewscreen*)this);
|
||||
lua_setfield(L, table_idx, "_native");
|
||||
lua_pushlightuserdata(L, this);
|
||||
lua_rawsetp(L, table_idx, &DFHACK_LUA_VS_TOKEN);
|
||||
|
||||
lua_pushvalue(L, table_idx);
|
||||
lua_rawsetp(L, LUA_REGISTRYINDEX, this);
|
||||
|
||||
update_focus(L, table_idx);
|
||||
}
|
||||
|
||||
dfhack_lua_viewscreen::~dfhack_lua_viewscreen()
|
||||
{
|
||||
safe_call_lua(do_destroy, 0, 0);
|
||||
}
|
||||
|
||||
void dfhack_lua_viewscreen::render()
|
||||
{
|
||||
if (Screen::isDismissed(this)) return;
|
||||
|
||||
dfhack_viewscreen::render();
|
||||
|
||||
safe_call_lua(do_render, 0, 0);
|
||||
}
|
||||
|
||||
void dfhack_lua_viewscreen::logic()
|
||||
{
|
||||
if (Screen::isDismissed(this)) return;
|
||||
|
||||
dfhack_viewscreen::logic();
|
||||
|
||||
lua_pushstring(Lua::Core::State, "onIdle");
|
||||
safe_call_lua(do_notify, 1, 0);
|
||||
}
|
||||
|
||||
void dfhack_lua_viewscreen::help()
|
||||
{
|
||||
if (Screen::isDismissed(this)) return;
|
||||
|
||||
lua_pushstring(Lua::Core::State, "onHelp");
|
||||
safe_call_lua(do_notify, 1, 0);
|
||||
}
|
||||
|
||||
void dfhack_lua_viewscreen::resize(int w, int h)
|
||||
{
|
||||
if (Screen::isDismissed(this)) return;
|
||||
|
||||
auto L = Lua::Core::State;
|
||||
lua_pushstring(L, "onResize");
|
||||
lua_pushinteger(L, w);
|
||||
lua_pushinteger(L, h);
|
||||
safe_call_lua(do_notify, 3, 0);
|
||||
}
|
||||
|
||||
void dfhack_lua_viewscreen::feed(std::set<df::interface_key> *keys)
|
||||
{
|
||||
if (Screen::isDismissed(this)) return;
|
||||
|
||||
lua_pushlightuserdata(Lua::Core::State, keys);
|
||||
safe_call_lua(do_input, 1, 0);
|
||||
}
|
||||
|
||||
void dfhack_lua_viewscreen::onShow()
|
||||
{
|
||||
lua_pushstring(Lua::Core::State, "onShow");
|
||||
safe_call_lua(do_notify, 1, 0);
|
||||
}
|
||||
|
||||
void dfhack_lua_viewscreen::onDismiss()
|
||||
{
|
||||
lua_pushstring(Lua::Core::State, "onDismiss");
|
||||
safe_call_lua(do_notify, 1, 0);
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 3663b22e25a6772b9f2765caaff81914c131c1cc
|
||||
Subproject commit 328a8dbdc7d9e1e838798abf79861cc18a387e3f
|
@ -1,13 +0,0 @@
|
||||
#ifndef LUA_CONSOLE_H
|
||||
#define LUA_CONSOLE_H
|
||||
#include <Console.h>
|
||||
#include "luamain.h"
|
||||
|
||||
namespace lua
|
||||
{
|
||||
|
||||
void RegisterConsole(lua::state &st);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -1,131 +0,0 @@
|
||||
#include "LuaTools.h"
|
||||
|
||||
#include "lua_Console.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
//TODO error management. Using lua error? or something other?
|
||||
static DFHack::color_ostream* GetConsolePtr(lua::state &st)
|
||||
{
|
||||
return DFHack::Lua::GetOutput(st);
|
||||
}
|
||||
|
||||
static int lua_Console_clear(lua_State *S)
|
||||
{
|
||||
lua::state st(S);
|
||||
DFHack::color_ostream* c=GetConsolePtr(st);
|
||||
c->clear();
|
||||
return 0;
|
||||
}
|
||||
static int lua_Console_gotoxy(lua_State *S)
|
||||
{
|
||||
lua::state st(S);
|
||||
DFHack::color_ostream* c=GetConsolePtr(st);
|
||||
if(c->is_console())
|
||||
{
|
||||
DFHack::Console* con=static_cast<DFHack::Console*>(c);
|
||||
con->gotoxy(st.as<int>(1,1),st.as<int>(1,2));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static int lua_Console_color(lua_State *S)
|
||||
{
|
||||
lua::state st(S);
|
||||
DFHack::color_ostream* c=GetConsolePtr(st);
|
||||
c->color( static_cast<DFHack::Console::color_value>(st.as<int>(-1,1)) );
|
||||
return 0;
|
||||
}
|
||||
static int lua_Console_reset_color(lua_State *S)
|
||||
{
|
||||
lua::state st(S);
|
||||
DFHack::color_ostream* c=GetConsolePtr(st);
|
||||
c->reset_color();
|
||||
return 0;
|
||||
}
|
||||
static int lua_Console_cursor(lua_State *S)
|
||||
{
|
||||
lua::state st(S);
|
||||
DFHack::color_ostream* c=GetConsolePtr(st);
|
||||
if(c->is_console())
|
||||
{
|
||||
DFHack::Console* con=static_cast<DFHack::Console*>(c);
|
||||
con->cursor(st.as<bool>(1));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static int lua_Console_msleep(lua_State *S)
|
||||
{
|
||||
lua::state st(S);
|
||||
DFHack::color_ostream* c=GetConsolePtr(st);
|
||||
if(c->is_console())
|
||||
{
|
||||
DFHack::Console* con=static_cast<DFHack::Console*>(c);
|
||||
con->msleep(st.as<unsigned>(1));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static int lua_Console_get_columns(lua_State *S)
|
||||
{
|
||||
lua::state st(S);
|
||||
DFHack::color_ostream* c=GetConsolePtr(st);
|
||||
if(c->is_console())
|
||||
{
|
||||
DFHack::Console* con=static_cast<DFHack::Console*>(c);
|
||||
st.push(con->get_columns());
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
static int lua_Console_get_rows(lua_State *S)
|
||||
{
|
||||
lua::state st(S);
|
||||
DFHack::color_ostream* c=GetConsolePtr(st);
|
||||
if(c->is_console())
|
||||
{
|
||||
DFHack::Console* con=static_cast<DFHack::Console*>(c);
|
||||
st.push(con->get_rows());
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
static int lua_Console_lineedit(lua_State *S)
|
||||
{
|
||||
lua::state st(S);
|
||||
DFHack::color_ostream* c=GetConsolePtr(st);
|
||||
if(c->is_console())
|
||||
{
|
||||
DFHack::Console* con=static_cast<DFHack::Console*>(c);
|
||||
string ret;
|
||||
DFHack::CommandHistory hist;
|
||||
int i=con->lineedit(st.as<string>(1),ret,hist);
|
||||
st.push(ret);
|
||||
st.push(i);
|
||||
return 2;// dunno if len is needed...
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
const luaL_Reg lua_console_func[]=
|
||||
{
|
||||
{"clear",lua_Console_clear},
|
||||
{"gotoxy",lua_Console_gotoxy},
|
||||
{"color",lua_Console_color},
|
||||
{"reset_color",lua_Console_reset_color},
|
||||
{"cursor",lua_Console_cursor},
|
||||
{"msleep",lua_Console_msleep},
|
||||
{"get_columns",lua_Console_get_columns},
|
||||
{"get_rows",lua_Console_get_rows},
|
||||
{"lineedit",lua_Console_lineedit},
|
||||
{NULL,NULL}
|
||||
};
|
||||
void lua::RegisterConsole(lua::state &st)
|
||||
{
|
||||
st.getglobal("Console");
|
||||
if(st.is<lua::nil>())
|
||||
{
|
||||
st.pop();
|
||||
st.newtable();
|
||||
}
|
||||
|
||||
lua::RegFunctionsLocal(st, lua_console_func);
|
||||
//TODO add color consts
|
||||
st.setglobal("Console");
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <modules/Gui.h>
|
||||
#include <modules/Screen.h>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
|
||||
#include <VTableInterpose.h>
|
||||
#include "df/item.h"
|
||||
#include "df/unit.h"
|
||||
#include "df/world.h"
|
||||
#include "df/general_ref_item.h"
|
||||
#include "df/general_ref_unit.h"
|
||||
|
||||
using std::vector;
|
||||
using std::string;
|
||||
using std::stack;
|
||||
using namespace DFHack;
|
||||
|
||||
using df::global::gps;
|
||||
|
||||
DFHACK_PLUGIN("ref-index");
|
||||
|
||||
#define global_id id
|
||||
|
||||
template<class T>
|
||||
T get_from_global_id_vector(int32_t id, const std::vector<T> &vect, int32_t *cache)
|
||||
{
|
||||
size_t size = vect.size();
|
||||
int32_t start=0;
|
||||
int32_t end=(int32_t)size-1;
|
||||
|
||||
// Check the cached location. If it is a match, this provides O(1) lookup.
|
||||
// Otherwise it works like one binsearch iteration.
|
||||
if (size_t(*cache) < size)
|
||||
{
|
||||
T cptr = vect[*cache];
|
||||
if (cptr->global_id == id)
|
||||
return cptr;
|
||||
if (cptr->global_id < id)
|
||||
start = *cache+1;
|
||||
else
|
||||
end = *cache-1;
|
||||
}
|
||||
|
||||
// Regular binsearch. The end check provides O(1) caching for missing item.
|
||||
if (start <= end && vect[end]->global_id >= id)
|
||||
{
|
||||
do {
|
||||
int32_t mid=(start+end)>>1;
|
||||
|
||||
T cptr=vect[mid];
|
||||
if(cptr->global_id==id)
|
||||
{
|
||||
*cache = mid;
|
||||
return cptr;
|
||||
}
|
||||
else if(cptr->global_id>id)end=mid-1;
|
||||
else start=mid+1;
|
||||
} while(start<=end);
|
||||
}
|
||||
|
||||
*cache = end+1;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
template<class T> T *find_object(int32_t id, int32_t *cache);
|
||||
template<> df::item *find_object<df::item>(int32_t id, int32_t *cache) {
|
||||
return get_from_global_id_vector(id, df::global::world->items.all, cache);
|
||||
}
|
||||
template<> df::unit *find_object<df::unit>(int32_t id, int32_t *cache) {
|
||||
return get_from_global_id_vector(id, df::global::world->units.all, cache);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
struct CachedRef {
|
||||
int32_t id;
|
||||
int32_t cache;
|
||||
CachedRef(int32_t id = -1) : id(id), cache(-1) {}
|
||||
T *target() { return find_object<T>(id, &cache); }
|
||||
};
|
||||
|
||||
#ifdef LINUX_BUILD
|
||||
struct item_hook : df::general_ref_item {
|
||||
typedef df::general_ref_item interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(df::item*, getItem, ())
|
||||
{
|
||||
// HUGE HACK: ASSUMES THERE ARE 4 USABLE BYTES AFTER THE OBJECT
|
||||
// This actually is true with glibc allocator due to granularity.
|
||||
return find_object<df::item>(item_id, 1+&item_id);
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(item_hook, getItem);
|
||||
|
||||
struct unit_hook : df::general_ref_unit {
|
||||
typedef df::general_ref_unit interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(df::unit*, getUnit, ())
|
||||
{
|
||||
// HUGE HACK: ASSUMES THERE ARE 4 USABLE BYTES AFTER THE OBJECT
|
||||
// This actually is true with glibc allocator due to granularity.
|
||||
return find_object<df::unit>(unit_id, 1+&unit_id);
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(unit_hook, getUnit);
|
||||
|
||||
command_result hook_refs(color_ostream &out, vector <string> & parameters)
|
||||
{
|
||||
auto &hook = INTERPOSE_HOOK(item_hook, getItem);
|
||||
if (hook.is_applied())
|
||||
{
|
||||
hook.remove();
|
||||
INTERPOSE_HOOK(unit_hook, getUnit).remove();
|
||||
}
|
||||
else
|
||||
{
|
||||
hook.apply();
|
||||
INTERPOSE_HOOK(unit_hook, getUnit).apply();
|
||||
}
|
||||
|
||||
if (hook.is_applied())
|
||||
out.print("Hook is applied.\n");
|
||||
else
|
||||
out.print("Hook is not applied.\n");
|
||||
return CR_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
#ifdef LINUX_BUILD
|
||||
commands.push_back(PluginCommand("hook-refs","Inject O(1) cached lookup into general refs.",hook_refs));
|
||||
#endif
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <modules/Gui.h>
|
||||
#include <modules/Screen.h>
|
||||
#include <vector>
|
||||
#include <cstdio>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
|
||||
#include <VTableInterpose.h>
|
||||
#include "df/graphic.h"
|
||||
#include "df/viewscreen_titlest.h"
|
||||
|
||||
using std::vector;
|
||||
using std::string;
|
||||
using std::stack;
|
||||
using namespace DFHack;
|
||||
|
||||
using df::global::gps;
|
||||
|
||||
DFHACK_PLUGIN("vshook");
|
||||
|
||||
struct title_hook : df::viewscreen_titlest {
|
||||
typedef df::viewscreen_titlest interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, render, ())
|
||||
{
|
||||
INTERPOSE_NEXT(render)();
|
||||
|
||||
Screen::Pen pen(' ',COLOR_WHITE,COLOR_BLACK);
|
||||
Screen::paintString(pen,0,0,"DFHack " DFHACK_VERSION);
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(title_hook, render);
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
|
||||
{
|
||||
if (gps)
|
||||
{
|
||||
if (!INTERPOSE_HOOK(title_hook, render).apply())
|
||||
out.printerr("Could not interpose viewscreen_titlest::render\n");
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
INTERPOSE_HOOK(title_hook, render).remove();
|
||||
return CR_OK;
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
local _ENV = mkmodule('plugins.liquids')
|
||||
|
||||
--[[
|
||||
|
||||
Native functions:
|
||||
|
||||
* paint(pos,brush,paint,amount,size,setmode,flowmode)
|
||||
|
||||
--]]
|
||||
|
||||
return _ENV
|
@ -0,0 +1,783 @@
|
||||
// Dwarf Manipulator - a Therapist-style labor editor
|
||||
|
||||
#include "Core.h"
|
||||
#include <Console.h>
|
||||
#include <Export.h>
|
||||
#include <PluginManager.h>
|
||||
#include <MiscUtils.h>
|
||||
#include <modules/Screen.h>
|
||||
#include <modules/Translation.h>
|
||||
#include <modules/Units.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
#include <VTableInterpose.h>
|
||||
#include "df/world.h"
|
||||
#include "df/ui.h"
|
||||
#include "df/graphic.h"
|
||||
#include "df/enabler.h"
|
||||
#include "df/viewscreen_unitlistst.h"
|
||||
#include "df/interface_key.h"
|
||||
#include "df/unit.h"
|
||||
#include "df/unit_soul.h"
|
||||
#include "df/unit_skill.h"
|
||||
#include "df/creature_raw.h"
|
||||
#include "df/caste_raw.h"
|
||||
|
||||
using std::set;
|
||||
using std::vector;
|
||||
using std::string;
|
||||
|
||||
using namespace DFHack;
|
||||
using namespace df::enums;
|
||||
|
||||
using df::global::world;
|
||||
using df::global::ui;
|
||||
using df::global::gps;
|
||||
using df::global::enabler;
|
||||
|
||||
DFHACK_PLUGIN("manipulator");
|
||||
|
||||
struct SkillLevel
|
||||
{
|
||||
const char *name;
|
||||
int points;
|
||||
char abbrev;
|
||||
};
|
||||
|
||||
#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'}
|
||||
};
|
||||
|
||||
struct SkillColumn
|
||||
{
|
||||
df::profession profession;
|
||||
df::unit_labor labor;
|
||||
df::job_skill skill;
|
||||
char label[3];
|
||||
bool special; // specified labor is mutually exclusive with all other special labors
|
||||
};
|
||||
|
||||
#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[] = {
|
||||
// Mining
|
||||
{profession::MINER, unit_labor::MINE, job_skill::MINING, "Mi", true},
|
||||
// Woodworking
|
||||
{profession::WOODWORKER, unit_labor::CARPENTER, job_skill::CARPENTRY, "Ca"},
|
||||
{profession::WOODWORKER, unit_labor::BOWYER, job_skill::BOWYER, "Bw"},
|
||||
{profession::WOODWORKER, unit_labor::CUTWOOD, job_skill::WOODCUTTING, "WC", true},
|
||||
// Stoneworking
|
||||
{profession::STONEWORKER, unit_labor::MASON, job_skill::MASONRY, "Ma"},
|
||||
{profession::STONEWORKER, unit_labor::DETAIL, job_skill::DETAILSTONE, "En"},
|
||||
// Hunting/Related
|
||||
{profession::RANGER, unit_labor::ANIMALTRAIN, job_skill::ANIMALTRAIN, "Tn"},
|
||||
{profession::RANGER, unit_labor::ANIMALCARE, job_skill::ANIMALCARE, "Ca"},
|
||||
{profession::RANGER, unit_labor::HUNT, job_skill::SNEAK, "Hu", true},
|
||||
{profession::RANGER, unit_labor::TRAPPER, job_skill::TRAPPING, "Tr"},
|
||||
{profession::RANGER, unit_labor::DISSECT_VERMIN, job_skill::DISSECT_VERMIN, "Di"},
|
||||
// Healthcare
|
||||
{profession::DOCTOR, unit_labor::DIAGNOSE, job_skill::DIAGNOSE, "Di"},
|
||||
{profession::DOCTOR, unit_labor::SURGERY, job_skill::SURGERY, "Su"},
|
||||
{profession::DOCTOR, unit_labor::BONE_SETTING, job_skill::SET_BONE, "Bo"},
|
||||
{profession::DOCTOR, unit_labor::SUTURING, job_skill::SUTURE, "St"},
|
||||
{profession::DOCTOR, unit_labor::DRESSING_WOUNDS, job_skill::DRESS_WOUNDS, "Dr"},
|
||||
{profession::DOCTOR, unit_labor::FEED_WATER_CIVILIANS, job_skill::NONE, "Fd"},
|
||||
{profession::DOCTOR, unit_labor::RECOVER_WOUNDED, job_skill::NONE, "Re"},
|
||||
// Farming/Related
|
||||
{profession::FARMER, unit_labor::BUTCHER, job_skill::BUTCHER, "Bu"},
|
||||
{profession::FARMER, unit_labor::TANNER, job_skill::TANNER, "Ta"},
|
||||
{profession::FARMER, unit_labor::PLANT, job_skill::PLANT, "Gr"},
|
||||
{profession::FARMER, unit_labor::DYER, job_skill::DYER, "Dy"},
|
||||
{profession::FARMER, unit_labor::SOAP_MAKER, job_skill::SOAP_MAKING, "So"},
|
||||
{profession::FARMER, unit_labor::BURN_WOOD, job_skill::WOOD_BURNING, "WB"},
|
||||
{profession::FARMER, unit_labor::POTASH_MAKING, job_skill::POTASH_MAKING, "Po"},
|
||||
{profession::FARMER, unit_labor::LYE_MAKING, job_skill::LYE_MAKING, "Ly"},
|
||||
{profession::FARMER, unit_labor::MILLER, job_skill::MILLING, "Ml"},
|
||||
{profession::FARMER, unit_labor::BREWER, job_skill::BREWING, "Br"},
|
||||
{profession::FARMER, unit_labor::HERBALIST, job_skill::HERBALISM, "He"},
|
||||
{profession::FARMER, unit_labor::PROCESS_PLANT, job_skill::PROCESSPLANTS, "Th"},
|
||||
{profession::FARMER, unit_labor::MAKE_CHEESE, job_skill::CHEESEMAKING, "Ch"},
|
||||
{profession::FARMER, unit_labor::MILK, job_skill::MILK, "Mk"},
|
||||
{profession::FARMER, unit_labor::SHEARER, job_skill::SHEARING, "Sh"},
|
||||
{profession::FARMER, unit_labor::SPINNER, job_skill::SPINNING, "Sp"},
|
||||
{profession::FARMER, unit_labor::COOK, job_skill::COOK, "Co"},
|
||||
{profession::FARMER, unit_labor::PRESSING, job_skill::PRESSING, "Pr"},
|
||||
{profession::FARMER, unit_labor::BEEKEEPING, job_skill::BEEKEEPING, "Be"},
|
||||
// Fishing/Related
|
||||
{profession::FISHERMAN, unit_labor::FISH, job_skill::FISH, "Fi"},
|
||||
{profession::FISHERMAN, unit_labor::CLEAN_FISH, job_skill::PROCESSFISH, "Cl"},
|
||||
{profession::FISHERMAN, unit_labor::DISSECT_FISH, job_skill::DISSECT_FISH, "Di"},
|
||||
// Metalsmithing
|
||||
{profession::METALSMITH, unit_labor::SMELT, job_skill::SMELT, "Fu"},
|
||||
{profession::METALSMITH, unit_labor::FORGE_WEAPON, job_skill::FORGE_WEAPON, "We"},
|
||||
{profession::METALSMITH, unit_labor::FORGE_ARMOR, job_skill::FORGE_ARMOR, "Ar"},
|
||||
{profession::METALSMITH, unit_labor::FORGE_FURNITURE, job_skill::FORGE_FURNITURE, "Bl"},
|
||||
{profession::METALSMITH, unit_labor::METAL_CRAFT, job_skill::METALCRAFT, "Cr"},
|
||||
// Jewelry
|
||||
{profession::JEWELER, unit_labor::CUT_GEM, job_skill::CUTGEM, "Cu"},
|
||||
{profession::JEWELER, unit_labor::ENCRUST_GEM, job_skill::ENCRUSTGEM, "Se"},
|
||||
// Crafts
|
||||
{profession::CRAFTSMAN, unit_labor::LEATHER, job_skill::LEATHERWORK, "Le"},
|
||||
{profession::CRAFTSMAN, unit_labor::WOOD_CRAFT, job_skill::WOODCRAFT, "Wo"},
|
||||
{profession::CRAFTSMAN, unit_labor::STONE_CRAFT, job_skill::STONECRAFT, "St"},
|
||||
{profession::CRAFTSMAN, unit_labor::BONE_CARVE, job_skill::BONECARVE, "Bo"},
|
||||
{profession::CRAFTSMAN, unit_labor::GLASSMAKER, job_skill::GLASSMAKER, "Gl"},
|
||||
{profession::CRAFTSMAN, unit_labor::WEAVER, job_skill::WEAVING, "We"},
|
||||
{profession::CRAFTSMAN, unit_labor::CLOTHESMAKER, job_skill::CLOTHESMAKING, "Cl"},
|
||||
{profession::CRAFTSMAN, unit_labor::EXTRACT_STRAND, job_skill::EXTRACT_STRAND, "Ad"},
|
||||
{profession::CRAFTSMAN, unit_labor::POTTERY, job_skill::POTTERY, "Po"},
|
||||
{profession::CRAFTSMAN, unit_labor::GLAZING, job_skill::GLAZING, "Gl"},
|
||||
{profession::CRAFTSMAN, unit_labor::WAX_WORKING, job_skill::WAX_WORKING, "Wx"},
|
||||
// Engineering
|
||||
{profession::ENGINEER, unit_labor::SIEGECRAFT, job_skill::SIEGECRAFT, "En"},
|
||||
{profession::ENGINEER, unit_labor::SIEGEOPERATE, job_skill::SIEGEOPERATE, "Op"},
|
||||
{profession::ENGINEER, unit_labor::MECHANIC, job_skill::MECHANICS, "Me"},
|
||||
{profession::ENGINEER, unit_labor::OPERATE_PUMP, job_skill::OPERATE_PUMP, "Pu"},
|
||||
// Hauling
|
||||
{profession::STANDARD, unit_labor::HAUL_STONE, job_skill::NONE, "St"},
|
||||
{profession::STANDARD, unit_labor::HAUL_WOOD, job_skill::NONE, "Wo"},
|
||||
{profession::STANDARD, unit_labor::HAUL_ITEM, job_skill::NONE, "It"},
|
||||
{profession::STANDARD, unit_labor::HAUL_BODY, job_skill::NONE, "Bu"},
|
||||
{profession::STANDARD, unit_labor::HAUL_FOOD, job_skill::NONE, "Fo"},
|
||||
{profession::STANDARD, unit_labor::HAUL_REFUSE, job_skill::NONE, "Re"},
|
||||
{profession::STANDARD, unit_labor::HAUL_FURNITURE, job_skill::NONE, "Fu"},
|
||||
{profession::STANDARD, unit_labor::HAUL_ANIMAL, job_skill::NONE, "An"},
|
||||
{profession::STANDARD, unit_labor::PUSH_HAUL_VEHICLE, job_skill::NONE, "Ve"},
|
||||
// Other Jobs
|
||||
{profession::CHILD, unit_labor::ARCHITECT, job_skill::DESIGNBUILDING, "Ar"},
|
||||
{profession::CHILD, unit_labor::ALCHEMIST, job_skill::ALCHEMY, "Al"},
|
||||
{profession::CHILD, unit_labor::CLEAN, job_skill::NONE, "Cl"},
|
||||
// Military
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::WRESTLING, "Wr"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::AXE, "Ax"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::SWORD, "Sw"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::MACE, "Mc"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::HAMMER, "Ha"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::SPEAR, "Sp"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::DAGGER, "Kn"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::CROSSBOW, "Cb"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::BOW, "Bo"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::BLOWGUN, "Bl"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::PIKE, "Pk"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::WHIP, "La"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::ARMOR, "Ar"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::SHIELD, "Sh"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::BITE, "Bi"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::GRASP_STRIKE, "St"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::STANCE_STRIKE, "Ki"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::DODGING, "Do"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::MISC_WEAPON, "Mi"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::MELEE_COMBAT, "Fg"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::RANGED_COMBAT, "Ac"},
|
||||
|
||||
{profession::RECRUIT, unit_labor::NONE, job_skill::LEADERSHIP, "Ld"},
|
||||
{profession::RECRUIT, unit_labor::NONE, job_skill::TEACHING, "Te"},
|
||||
{profession::RECRUIT, unit_labor::NONE, job_skill::KNOWLEDGE_ACQUISITION, "St"},
|
||||
{profession::RECRUIT, unit_labor::NONE, job_skill::DISCIPLINE, "Di"},
|
||||
{profession::RECRUIT, unit_labor::NONE, job_skill::CONCENTRATION, "Co"},
|
||||
{profession::RECRUIT, unit_labor::NONE, job_skill::SITUATIONAL_AWARENESS, "Ob"},
|
||||
{profession::RECRUIT, unit_labor::NONE, job_skill::COORDINATION, "Cr"},
|
||||
{profession::RECRUIT, unit_labor::NONE, job_skill::BALANCE, "Ba"},
|
||||
|
||||
// Social
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::PERSUASION, "Pe"},
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::NEGOTIATION, "Ne"},
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::JUDGING_INTENT, "Ju"},
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::LYING, "Li"},
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::INTIMIDATION, "In"},
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::CONVERSATION, "Cn"},
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::COMEDY, "Cm"},
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::FLATTERY, "Fl"},
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::CONSOLE, "Cs"},
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::PACIFY, "Pc"},
|
||||
|
||||
// Noble
|
||||
{profession::ADMINISTRATOR, unit_labor::NONE, job_skill::APPRAISAL, "Ap"},
|
||||
{profession::ADMINISTRATOR, unit_labor::NONE, job_skill::ORGANIZATION, "Or"},
|
||||
{profession::ADMINISTRATOR, unit_labor::NONE, job_skill::RECORD_KEEPING, "RK"},
|
||||
|
||||
// Miscellaneous
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::THROW, "Th"},
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::CRUTCH_WALK, "CW"},
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::SWIMMING, "Sw"},
|
||||
{profession::STANDARD, unit_labor::NONE, job_skill::KNAPPING, "Kn"},
|
||||
|
||||
{profession::DRUNK, unit_labor::NONE, job_skill::WRITING, "Wr"},
|
||||
{profession::DRUNK, unit_labor::NONE, job_skill::PROSE, "Pr"},
|
||||
{profession::DRUNK, unit_labor::NONE, job_skill::POETRY, "Po"},
|
||||
{profession::DRUNK, unit_labor::NONE, job_skill::READING, "Rd"},
|
||||
{profession::DRUNK, unit_labor::NONE, job_skill::SPEAKING, "Sp"},
|
||||
|
||||
{profession::ADMINISTRATOR, unit_labor::NONE, job_skill::MILITARY_TACTICS, "MT"},
|
||||
{profession::ADMINISTRATOR, unit_labor::NONE, job_skill::TRACKING, "Tr"},
|
||||
{profession::ADMINISTRATOR, unit_labor::NONE, job_skill::MAGIC_NATURE, "Dr"},
|
||||
};
|
||||
|
||||
struct UnitInfo
|
||||
{
|
||||
df::unit *unit;
|
||||
bool allowEdit;
|
||||
string name;
|
||||
string transname;
|
||||
string profession;
|
||||
int8_t color;
|
||||
};
|
||||
|
||||
enum altsort_mode {
|
||||
ALTSORT_NAME,
|
||||
ALTSORT_PROFESSION,
|
||||
ALTSORT_MAX
|
||||
};
|
||||
|
||||
bool descending;
|
||||
df::job_skill sort_skill;
|
||||
df::unit_labor sort_labor;
|
||||
|
||||
bool sortByName (const UnitInfo *d1, const UnitInfo *d2)
|
||||
{
|
||||
if (descending)
|
||||
return (d1->name > d2->name);
|
||||
else
|
||||
return (d1->name < d2->name);
|
||||
}
|
||||
|
||||
bool sortByProfession (const UnitInfo *d1, const UnitInfo *d2)
|
||||
{
|
||||
if (descending)
|
||||
return (d1->profession > d2->profession);
|
||||
else
|
||||
return (d1->profession < d2->profession);
|
||||
}
|
||||
|
||||
bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2)
|
||||
{
|
||||
if (sort_skill != job_skill::NONE)
|
||||
{
|
||||
df::unit_skill *s1 = binsearch_in_vector<df::unit_skill,df::enum_field<df::job_skill,int16_t>>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill);
|
||||
df::unit_skill *s2 = binsearch_in_vector<df::unit_skill,df::enum_field<df::job_skill,int16_t>>(d2->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:
|
||||
void feed(set<df::interface_key> *events);
|
||||
|
||||
void render();
|
||||
void resize(int w, int h) { calcSize(); }
|
||||
|
||||
void help() { }
|
||||
|
||||
std::string getFocusString() { return "unitlabors"; }
|
||||
|
||||
viewscreen_unitlaborsst(vector<df::unit*> &src);
|
||||
~viewscreen_unitlaborsst() { };
|
||||
|
||||
protected:
|
||||
vector<UnitInfo *> units;
|
||||
altsort_mode altsort;
|
||||
|
||||
int first_row, sel_row;
|
||||
int first_column, sel_column;
|
||||
|
||||
int height, name_width, prof_width, labors_width;
|
||||
|
||||
void calcSize ();
|
||||
};
|
||||
|
||||
viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector<df::unit*> &src)
|
||||
{
|
||||
for (size_t i = 0; i < src.size(); 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 (unit->civ_id != ui->civ_id)
|
||||
cur->allowEdit = false;
|
||||
|
||||
if (unit->flags1.bits.dead)
|
||||
cur->allowEdit = false;
|
||||
|
||||
if (!ENUM_ATTR(profession, can_assign_labor, unit->profession))
|
||||
cur->allowEdit = false;
|
||||
|
||||
cur->name = Translation::TranslateName(&unit->name, false);
|
||||
cur->transname = Translation::TranslateName(&unit->name, true);
|
||||
cur->profession = Units::getProfessionName(unit);
|
||||
cur->color = Units::getProfessionColor(unit);
|
||||
|
||||
units.push_back(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()
|
||||
{
|
||||
height = gps->dimy - 10;
|
||||
if (height > units.size())
|
||||
height = units.size();
|
||||
|
||||
name_width = prof_width = labors_width = 0;
|
||||
for (int i = 4; i < gps->dimx; i++)
|
||||
{
|
||||
// 20% for Name, 20% for Profession, 60% for Labors
|
||||
switch ((i - 4) % 5)
|
||||
{
|
||||
case 0: case 2: case 4:
|
||||
labors_width++;
|
||||
break;
|
||||
case 1:
|
||||
name_width++;
|
||||
break;
|
||||
case 3:
|
||||
prof_width++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (labors_width > NUM_COLUMNS)
|
||||
{
|
||||
if (labors_width & 1)
|
||||
name_width++;
|
||||
else
|
||||
prof_width++;
|
||||
labors_width--;
|
||||
}
|
||||
|
||||
// don't adjust scroll position immediately after the window opened
|
||||
if (units.size() == 0)
|
||||
return;
|
||||
|
||||
// if the window grows vertically, scroll upward to eliminate blank rows from the bottom
|
||||
if (first_row > units.size() - height)
|
||||
first_row = units.size() - height;
|
||||
|
||||
// if it shrinks vertically, scroll downward to keep the cursor visible
|
||||
if (first_row < sel_row - height + 1)
|
||||
first_row = sel_row - height + 1;
|
||||
|
||||
// if the window grows horizontally, scroll to the left to eliminate blank columns from the right
|
||||
if (first_column > NUM_COLUMNS - labors_width)
|
||||
first_column = NUM_COLUMNS - labors_width;
|
||||
|
||||
// if it shrinks horizontally, scroll to the right to keep the cursor visible
|
||||
if (first_column < sel_column - labors_width + 1)
|
||||
first_column = sel_column - labors_width + 1;
|
||||
}
|
||||
|
||||
void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
|
||||
{
|
||||
if (events->count(interface_key::LEAVESCREEN))
|
||||
{
|
||||
events->clear();
|
||||
Screen::dismiss(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!units.size())
|
||||
return;
|
||||
|
||||
if (events->count(interface_key::CURSOR_UP) || events->count(interface_key::CURSOR_UPLEFT) || events->count(interface_key::CURSOR_UPRIGHT))
|
||||
sel_row--;
|
||||
if (events->count(interface_key::CURSOR_UP_FAST) || events->count(interface_key::CURSOR_UPLEFT_FAST) || events->count(interface_key::CURSOR_UPRIGHT_FAST))
|
||||
sel_row -= 10;
|
||||
if (events->count(interface_key::CURSOR_DOWN) || events->count(interface_key::CURSOR_DOWNLEFT) || events->count(interface_key::CURSOR_DOWNRIGHT))
|
||||
sel_row++;
|
||||
if (events->count(interface_key::CURSOR_DOWN_FAST) || events->count(interface_key::CURSOR_DOWNLEFT_FAST) || events->count(interface_key::CURSOR_DOWNRIGHT_FAST))
|
||||
sel_row += 10;
|
||||
|
||||
if (sel_row < 0)
|
||||
sel_row = 0;
|
||||
if (sel_row > units.size() - 1)
|
||||
sel_row = units.size() - 1;
|
||||
|
||||
if (sel_row < first_row)
|
||||
first_row = sel_row;
|
||||
if (first_row < sel_row - height + 1)
|
||||
first_row = sel_row - height + 1;
|
||||
|
||||
if (events->count(interface_key::CURSOR_LEFT) || events->count(interface_key::CURSOR_UPLEFT) || events->count(interface_key::CURSOR_DOWNLEFT))
|
||||
sel_column--;
|
||||
if (events->count(interface_key::CURSOR_LEFT_FAST) || events->count(interface_key::CURSOR_UPLEFT_FAST) || events->count(interface_key::CURSOR_DOWNLEFT_FAST))
|
||||
sel_column -= 10;
|
||||
if (events->count(interface_key::CURSOR_RIGHT) || events->count(interface_key::CURSOR_UPRIGHT) || events->count(interface_key::CURSOR_DOWNRIGHT))
|
||||
sel_column++;
|
||||
if (events->count(interface_key::CURSOR_RIGHT_FAST) || events->count(interface_key::CURSOR_UPRIGHT_FAST) || events->count(interface_key::CURSOR_DOWNRIGHT_FAST))
|
||||
sel_column += 10;
|
||||
|
||||
if ((sel_column != 0) && events->count(interface_key::CURSOR_UP_Z))
|
||||
{
|
||||
// go to beginning of current column group; if already at the beginning, go to the beginning of the previous one
|
||||
sel_column--;
|
||||
df::profession cur = columns[sel_column].profession;
|
||||
while ((sel_column > 0) && columns[sel_column - 1].profession == cur)
|
||||
sel_column--;
|
||||
}
|
||||
if ((sel_column != NUM_COLUMNS - 1) && events->count(interface_key::CURSOR_DOWN_Z))
|
||||
{
|
||||
// go to end of current column group; if already at the end, go to the end of the next one
|
||||
sel_column++;
|
||||
df::profession cur = columns[sel_column].profession;
|
||||
while ((sel_column < NUM_COLUMNS - 1) && columns[sel_column + 1].profession == cur)
|
||||
sel_column++;
|
||||
}
|
||||
|
||||
if (sel_column < 0)
|
||||
sel_column = 0;
|
||||
if (sel_column > NUM_COLUMNS - 1)
|
||||
sel_column = NUM_COLUMNS - 1;
|
||||
|
||||
if (sel_column < first_column)
|
||||
first_column = sel_column;
|
||||
if (first_column < sel_column - labors_width + 1)
|
||||
first_column = sel_column - labors_width + 1;
|
||||
|
||||
UnitInfo *cur = units[sel_row];
|
||||
if (events->count(interface_key::SELECT) && (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE))
|
||||
{
|
||||
df::unit *unit = cur->unit;
|
||||
const SkillColumn &col = columns[sel_column];
|
||||
if (col.special)
|
||||
{
|
||||
if (!unit->status.labors[col.labor])
|
||||
{
|
||||
for (int i = 0; i < NUM_COLUMNS; i++)
|
||||
{
|
||||
if ((columns[i].labor != unit_labor::NONE) && columns[i].special)
|
||||
unit->status.labors[columns[i].labor] = false;
|
||||
}
|
||||
}
|
||||
unit->military.pickup_flags.bits.update = true;
|
||||
}
|
||||
unit->status.labors[col.labor] = !unit->status.labors[col.labor];
|
||||
}
|
||||
|
||||
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))
|
||||
return;
|
||||
|
||||
dfhack_viewscreen::render();
|
||||
|
||||
Screen::clear();
|
||||
Screen::drawBorder(" Manage Labors ");
|
||||
|
||||
for (int col = 0; col < labors_width; col++)
|
||||
{
|
||||
int col_offset = col + first_column;
|
||||
if (col_offset >= NUM_COLUMNS)
|
||||
break;
|
||||
|
||||
int8_t fg = Units::getCasteProfessionColor(ui->race_id, -1, columns[col_offset].profession);
|
||||
int8_t bg = 0;
|
||||
|
||||
if (col_offset == sel_column)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
for (int row = 0; row < height; row++)
|
||||
{
|
||||
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;
|
||||
if (row_offset == sel_row)
|
||||
{
|
||||
fg = 0;
|
||||
bg = 7;
|
||||
}
|
||||
|
||||
string name = cur->name;
|
||||
name.resize(name_width);
|
||||
Screen::paintString(Screen::Pen(' ', fg, bg), 1, 3 + row, name);
|
||||
|
||||
string profession = cur->profession;
|
||||
profession.resize(prof_width);
|
||||
fg = cur->color;
|
||||
bg = 0;
|
||||
|
||||
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++)
|
||||
{
|
||||
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].skill != job_skill::NONE)
|
||||
{
|
||||
df::unit_skill *skill = binsearch_in_vector<df::unit_skill,df::enum_field<df::job_skill,int16_t>>(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill);
|
||||
if ((skill != NULL) && (skill->rating || skill->experience))
|
||||
{
|
||||
int level = skill->rating;
|
||||
if (level > NUM_SKILL_LEVELS - 1)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
UnitInfo *cur = units[sel_row];
|
||||
if (cur != NULL)
|
||||
{
|
||||
df::unit *unit = cur->unit;
|
||||
int x = 1;
|
||||
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->transname);
|
||||
x += cur->transname.length();
|
||||
|
||||
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);
|
||||
if (unit->status.labors[columns[sel_column].labor])
|
||||
str += " Enabled";
|
||||
else
|
||||
str += " Not Enabled";
|
||||
}
|
||||
else
|
||||
{
|
||||
df::unit_skill *skill = binsearch_in_vector<df::unit_skill,df::enum_field<df::job_skill,int16_t>>(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill);
|
||||
if (skill)
|
||||
{
|
||||
int level = skill->rating;
|
||||
if (level > NUM_SKILL_LEVELS - 1)
|
||||
level = NUM_SKILL_LEVELS - 1;
|
||||
str = stl_sprintf("%s %s", skill_levels[level].name, ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill));
|
||||
if (level != NUM_SKILL_LEVELS - 1)
|
||||
str += stl_sprintf(" (%d/%d)", skill->experience, skill_levels[level].points);
|
||||
}
|
||||
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), x, 3 + height + 2, str);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
typedef df::viewscreen_unitlistst interpose_base;
|
||||
|
||||
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
|
||||
{
|
||||
if (input->count(interface_key::UNITVIEW_PRF_PROF))
|
||||
{
|
||||
if (units[page].size())
|
||||
{
|
||||
Screen::show(new viewscreen_unitlaborsst(units[page]));
|
||||
return;
|
||||
}
|
||||
}
|
||||
INTERPOSE_NEXT(feed)(input);
|
||||
}
|
||||
};
|
||||
|
||||
IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, feed);
|
||||
|
||||
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
|
||||
{
|
||||
if (gps)
|
||||
{
|
||||
if (!INTERPOSE_HOOK(unitlist_hook, feed).apply())
|
||||
out.printerr("Could not interpose viewscreen_unitlistst::feed\n");
|
||||
}
|
||||
|
||||
return CR_OK;
|
||||
}
|
||||
|
||||
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
|
||||
{
|
||||
INTERPOSE_HOOK(unitlist_hook, feed).remove();
|
||||
return CR_OK;
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 17b653665567a5f1df628217820f76bb0b9c70a5
|
||||
Subproject commit 2a62ba5ed2607f4dbf0473e77502d4e19c19678e
|
@ -0,0 +1,9 @@
|
||||
-- Force a migrants event in next 10 ticks.
|
||||
|
||||
df.global.timed_events:insert('#',{
|
||||
new = true,
|
||||
type = df.timed_event_type.Migrants,
|
||||
season = df.global.cur_season,
|
||||
season_ticks = df.global.cur_season_tick+1,
|
||||
entity = df.historical_entity.find(df.global.ui.civ_id)
|
||||
})
|
@ -0,0 +1,64 @@
|
||||
# script to fix loyalty cascade, when you order your militia to kill friendly units
|
||||
|
||||
def fixunit(unit)
|
||||
return if unit.race != df.ui.race_id or unit.civ_id != df.ui.civ_id
|
||||
links = unit.hist_figure_tg.entity_links
|
||||
fixed = false
|
||||
|
||||
# check if the unit is a civ renegade
|
||||
if i1 = links.index { |l|
|
||||
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
|
||||
l.entity_id == df.ui.civ_id
|
||||
} and i2 = links.index { |l|
|
||||
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
|
||||
l.entity_id == df.ui.civ_id
|
||||
}
|
||||
fixed = true
|
||||
i1, i2 = i2, i1 if i1 > i2
|
||||
links.delete_at i2
|
||||
links.delete_at i1
|
||||
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.civ_id, :link_strength => 100)
|
||||
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.civ_tg.name} again"
|
||||
end
|
||||
|
||||
# check if the unit is a group renegade
|
||||
if i1 = links.index { |l|
|
||||
l.kind_of?(DFHack::HistfigEntityLinkFormerMemberst) and
|
||||
l.entity_id == df.ui.group_id
|
||||
} and i2 = links.index { |l|
|
||||
l.kind_of?(DFHack::HistfigEntityLinkEnemyst) and
|
||||
l.entity_id == df.ui.group_id
|
||||
}
|
||||
fixed = true
|
||||
i1, i2 = i2, i1 if i1 > i2
|
||||
links.delete_at i2
|
||||
links.delete_at i1
|
||||
links << DFHack::HistfigEntityLinkMemberst.cpp_new(:entity_id => df.ui.group_id, :link_strength => 100)
|
||||
df.add_announcement "fixloyalty: #{unit.name} is now a member of #{df.ui.group_tg.name} again"
|
||||
end
|
||||
|
||||
# fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed)
|
||||
if fixed and unit.unknown8.enemy_status_slot != -1
|
||||
i = unit.unknown8.enemy_status_slot
|
||||
unit.unknown8.enemy_status_slot = -1
|
||||
cache = df.world.enemy_status_cache
|
||||
cache.slot_used[i] = false
|
||||
cache.rel_map[i].map! { -1 }
|
||||
cache.rel_map.each { |a| a[i] = -1 }
|
||||
cache.next_slot = i if cache.next_slot > i
|
||||
end
|
||||
|
||||
# return true if we actually fixed the unit
|
||||
fixed
|
||||
end
|
||||
|
||||
count = 0
|
||||
df.unit_citizens.each { |u|
|
||||
count += 1 if fixunit(u)
|
||||
}
|
||||
|
||||
if count > 0
|
||||
puts "loyalty cascade fixed (#{count} dwarves)"
|
||||
else
|
||||
puts "no loyalty cascade found"
|
||||
end
|
@ -0,0 +1,40 @@
|
||||
-- Communicates current population to mountainhomes to avoid cap overshooting.
|
||||
|
||||
-- The reason for population cap problems is that the population value it
|
||||
-- is compared against comes from the last dwarven caravan that successfully
|
||||
-- left for mountainhomes. This script instantly updates it.
|
||||
-- Note that a migration wave can still overshoot the limit by 1-2 dwarves because
|
||||
-- of the last migrant bringing his family. Likewise, king arrival ignores cap.
|
||||
|
||||
local args = {...}
|
||||
|
||||
local ui = df.global.ui
|
||||
local ui_stats = ui.tasks
|
||||
local civ = df.historical_entity.find(ui.civ_id)
|
||||
|
||||
if not civ then
|
||||
qerror('No active fortress.')
|
||||
end
|
||||
|
||||
local civ_stats = civ.activity_stats
|
||||
|
||||
if not civ_stats then
|
||||
if args[1] ~= 'force' then
|
||||
qerror('No caravan report object; use "fix/population-cap force" to create one')
|
||||
end
|
||||
print('Creating an empty statistics structure...')
|
||||
civ.activity_stats = {
|
||||
new = true,
|
||||
created_weapons = { resize = #ui_stats.created_weapons },
|
||||
known_creatures1 = { resize = #ui_stats.known_creatures1 },
|
||||
known_creatures = { resize = #ui_stats.known_creatures },
|
||||
known_plants1 = { resize = #ui_stats.known_plants1 },
|
||||
known_plants = { resize = #ui_stats.known_plants },
|
||||
}
|
||||
civ_stats = civ.activity_stats
|
||||
end
|
||||
|
||||
-- Use max to keep at least some of the original caravan communication idea
|
||||
civ_stats.population = math.max(civ_stats.population, ui_stats.population)
|
||||
|
||||
print('Home civ notified about current population.')
|
@ -0,0 +1,22 @@
|
||||
-- Test lua viewscreens.
|
||||
|
||||
local gui = require 'gui'
|
||||
|
||||
local text = 'Woohoo, lua viewscreen :)'
|
||||
|
||||
local screen = mkinstance(gui.FramedScreen, {
|
||||
frame_style = gui.GREY_LINE_FRAME,
|
||||
frame_title = 'Hello World',
|
||||
frame_width = #text+6,
|
||||
frame_height = 3,
|
||||
onRenderBody = function(self, dc)
|
||||
dc:seek(3,1):string(text, COLOR_LIGHTGREEN)
|
||||
end,
|
||||
onInput = function(self,keys)
|
||||
if keys.LEAVESCREEN or keys.SELECT then
|
||||
self:dismiss()
|
||||
end
|
||||
end
|
||||
}):init()
|
||||
|
||||
screen:show()
|
@ -0,0 +1,263 @@
|
||||
-- Interface front-end for liquids plugin.
|
||||
|
||||
local utils = require 'utils'
|
||||
local gui = require 'gui'
|
||||
local guidm = require 'gui.dwarfmode'
|
||||
|
||||
local liquids = require('plugins.liquids')
|
||||
|
||||
local sel_rect = df.global.selection_rect
|
||||
|
||||
local brushes = {
|
||||
{ tag = 'range', caption = 'Rectangle', range = true },
|
||||
{ tag = 'block', caption = '16x16 block' },
|
||||
{ tag = 'column', caption = 'Column' },
|
||||
{ tag = 'flood', caption = 'Flood' },
|
||||
}
|
||||
|
||||
local paints = {
|
||||
{ tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'w' },
|
||||
{ tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'l' },
|
||||
{ tag = 'obsidian', caption = 'Obsidian Wall' },
|
||||
{ tag = 'obsidian_floor', caption = 'Obsidian Floor' },
|
||||
{ tag = 'riversource', caption = 'River Source' },
|
||||
{ tag = 'flowbits', caption = 'Flow Updates', flow = true },
|
||||
{ tag = 'wclean', caption = 'Clean Salt/Stagnant' },
|
||||
}
|
||||
|
||||
local flowbits = {
|
||||
{ tag = '+', caption = 'Enable Updates' },
|
||||
{ tag = '-', caption = 'Disable Updates' },
|
||||
{ tag = '.', caption = 'Keep Updates' },
|
||||
}
|
||||
|
||||
local setmode = {
|
||||
{ tag = '.', caption = 'Set Exactly' },
|
||||
{ tag = '+', caption = 'Only Increase' },
|
||||
{ tag = '-', caption = 'Only Decrease' },
|
||||
}
|
||||
|
||||
local permaflows = {
|
||||
{ tag = '.', caption = "Keep Permaflow" },
|
||||
{ tag = '-', caption = 'Remove Permaflow' },
|
||||
{ tag = 'N', caption = 'Set Permaflow N' },
|
||||
{ tag = 'S', caption = 'Set Permaflow S' },
|
||||
{ tag = 'E', caption = 'Set Permaflow E' },
|
||||
{ tag = 'W', caption = 'Set Permaflow W' },
|
||||
{ tag = 'NE', caption = 'Set Permaflow NE' },
|
||||
{ tag = 'NW', caption = 'Set Permaflow NW' },
|
||||
{ tag = 'SE', caption = 'Set Permaflow SE' },
|
||||
{ tag = 'SW', caption = 'Set Permaflow SW' },
|
||||
}
|
||||
|
||||
Toggle = defclass(Toggle)
|
||||
|
||||
function Toggle:init(items)
|
||||
self:init_fields{
|
||||
items = items,
|
||||
selected = 1
|
||||
}
|
||||
return self
|
||||
end
|
||||
|
||||
function Toggle:get()
|
||||
return self.items[self.selected]
|
||||
end
|
||||
|
||||
function Toggle:render(dc)
|
||||
local item = self:get()
|
||||
if item then
|
||||
dc:string(item.caption)
|
||||
if item.key then
|
||||
dc:string(" ("):string(item.key, COLOR_LIGHTGREEN):string(")")
|
||||
end
|
||||
else
|
||||
dc:string('NONE', COLOR_RED)
|
||||
end
|
||||
end
|
||||
|
||||
function Toggle:step(delta)
|
||||
if #self.items > 1 then
|
||||
delta = delta or 1
|
||||
self.selected = 1 + (self.selected + delta - 1) % #self.items
|
||||
end
|
||||
end
|
||||
|
||||
LiquidsUI = defclass(LiquidsUI, guidm.MenuOverlay)
|
||||
|
||||
LiquidsUI.focus_path = 'liquids'
|
||||
|
||||
function LiquidsUI:init()
|
||||
self:init_fields{
|
||||
brush = mkinstance(Toggle):init(brushes),
|
||||
paint = mkinstance(Toggle):init(paints),
|
||||
flow = mkinstance(Toggle):init(flowbits),
|
||||
set = mkinstance(Toggle):init(setmode),
|
||||
permaflow = mkinstance(Toggle):init(permaflows),
|
||||
amount = 7,
|
||||
}
|
||||
guidm.MenuOverlay.init(self)
|
||||
return self
|
||||
end
|
||||
|
||||
function LiquidsUI:onDestroy()
|
||||
guidm.clearSelection()
|
||||
end
|
||||
|
||||
function render_liquid(dc, block, x, y)
|
||||
local dsgn = block.designation[x%16][y%16]
|
||||
|
||||
if dsgn.flow_size > 0 then
|
||||
if dsgn.liquid_type == df.tile_liquid.Magma then
|
||||
dc:pen(COLOR_RED):string("Magma")
|
||||
else
|
||||
dc:pen(COLOR_BLUE)
|
||||
if dsgn.water_stagnant then dc:string("Stagnant ") end
|
||||
if dsgn.water_salt then dc:string("Salty ") end
|
||||
dc:string("Water")
|
||||
end
|
||||
dc:string(" ["..dsgn.flow_size.."/7]")
|
||||
else
|
||||
dc:string('No Liquid')
|
||||
end
|
||||
end
|
||||
|
||||
local permaflow_abbr = {
|
||||
north = 'N', south = 'S', east = 'E', west = 'W',
|
||||
northeast = 'NE', northwest = 'NW', southeast = 'SE', southwest = 'SW'
|
||||
}
|
||||
|
||||
function render_flow_state(dc, block, x, y)
|
||||
local flow = block.liquid_flow[x%16][y%16]
|
||||
|
||||
if block.flags.update_liquid then
|
||||
dc:string("Updating", COLOR_GREEN)
|
||||
else
|
||||
dc:string("Static")
|
||||
end
|
||||
dc:string(", ")
|
||||
if flow.perm_flow_dir ~= 0 then
|
||||
local tag = df.tile_liquid_flow_dir[flow.perm_flow_dir]
|
||||
dc:string("Permaflow "..(permaflow_abbr[tag] or tag), COLOR_CYAN)
|
||||
elseif flow.temp_flow_timer > 0 then
|
||||
dc:string("Flowing "..flow.temp_flow_timer, COLOR_GREEN)
|
||||
else
|
||||
dc:string("No Flow")
|
||||
end
|
||||
end
|
||||
|
||||
function LiquidsUI:onRenderBody(dc)
|
||||
dc:clear():seek(1,1):string("Paint Liquids Cheat", COLOR_WHITE)
|
||||
|
||||
local cursor = guidm.getCursorPos()
|
||||
local block = dfhack.maps.getTileBlock(cursor)
|
||||
|
||||
if block then
|
||||
local x, y = pos2xyz(cursor)
|
||||
local tile = block.tiletype[x%16][y%16]
|
||||
|
||||
dc:seek(2,3):string(df.tiletype.attrs[tile].caption, COLOR_CYAN)
|
||||
dc:newline(2):pen(COLOR_DARKGREY)
|
||||
render_liquid(dc, block, x, y)
|
||||
dc:newline(2):pen(COLOR_DARKGREY)
|
||||
render_flow_state(dc, block, x, y)
|
||||
else
|
||||
dc:seek(2,3):string("No map data", COLOR_RED):advance(0,2)
|
||||
end
|
||||
|
||||
dc:newline():pen(COLOR_GREY)
|
||||
|
||||
dc:newline(1):string("b", COLOR_LIGHTGREEN):string(": ")
|
||||
self.brush:render(dc)
|
||||
dc:newline(1):string("p", COLOR_LIGHTGREEN):string(": ")
|
||||
self.paint:render(dc)
|
||||
|
||||
local paint = self.paint:get()
|
||||
|
||||
dc:newline()
|
||||
if paint.liquid then
|
||||
dc:newline(1):string("Amount: "..self.amount)
|
||||
dc:advance(1):string("("):string("-+", COLOR_LIGHTGREEN):string(")")
|
||||
dc:newline(3):string("s", COLOR_LIGHTGREEN):string(": ")
|
||||
self.set:render(dc)
|
||||
else
|
||||
dc:advance(0,2)
|
||||
end
|
||||
|
||||
dc:newline()
|
||||
if paint.flow then
|
||||
dc:newline(1):string("f", COLOR_LIGHTGREEN):string(": ")
|
||||
self.flow:render(dc)
|
||||
dc:newline(1):string("r", COLOR_LIGHTGREEN):string(": ")
|
||||
self.permaflow:render(dc)
|
||||
else
|
||||
dc:advance(0,2)
|
||||
end
|
||||
|
||||
dc:newline():newline(1):pen(COLOR_WHITE)
|
||||
dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ")
|
||||
dc:string("Enter", COLOR_LIGHTGREEN):string(": Paint")
|
||||
end
|
||||
|
||||
function LiquidsUI:onInput(keys)
|
||||
local paint = self.paint:get()
|
||||
local liquid = paint.liquid
|
||||
if keys.CUSTOM_B then
|
||||
self.brush:step()
|
||||
elseif keys.CUSTOM_P then
|
||||
self.paint:step()
|
||||
elseif liquid and keys.SECONDSCROLL_UP then
|
||||
self.amount = math.max(0, self.amount-1)
|
||||
elseif liquid and keys.SECONDSCROLL_DOWN then
|
||||
self.amount = math.min(7, self.amount+1)
|
||||
elseif liquid and keys.CUSTOM_S then
|
||||
self.set:step()
|
||||
elseif paint.flow and keys.CUSTOM_F then
|
||||
self.flow:step()
|
||||
elseif paint.flow and keys.CUSTOM_R then
|
||||
self.permaflow:step()
|
||||
elseif keys.LEAVESCREEN then
|
||||
if guidm.getSelection() then
|
||||
guidm.clearSelection()
|
||||
return
|
||||
end
|
||||
self:dismiss()
|
||||
self:sendInputToParent('CURSOR_DOWN_Z')
|
||||
self:sendInputToParent('CURSOR_UP_Z')
|
||||
elseif keys.SELECT then
|
||||
local cursor = guidm.getCursorPos()
|
||||
local sp = guidm.getSelection()
|
||||
local size = nil
|
||||
if self.brush:get().range then
|
||||
if not sp then
|
||||
guidm.setSelectionStart(cursor)
|
||||
return
|
||||
else
|
||||
guidm.clearSelection()
|
||||
cursor, size = guidm.getSelectionRange(cursor, sp)
|
||||
end
|
||||
else
|
||||
guidm.clearSelection()
|
||||
end
|
||||
liquids.paint(
|
||||
cursor,
|
||||
self.brush:get().tag, self.paint:get().tag,
|
||||
self.amount, size,
|
||||
self.set:get().tag, self.flow:get().tag,
|
||||
self.permaflow:get().tag
|
||||
)
|
||||
elseif self:propagateMoveKeys(keys) then
|
||||
return
|
||||
elseif keys.D_LOOK_ARENA_WATER then
|
||||
self.paint.selected = 1
|
||||
elseif keys.D_LOOK_ARENA_MAGMA then
|
||||
self.paint.selected = 2
|
||||
end
|
||||
end
|
||||
|
||||
if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/LookAround') then
|
||||
qerror("This script requires the main dwarfmode view in 'k' mode")
|
||||
end
|
||||
|
||||
local list = mkinstance(LiquidsUI):init()
|
||||
list:show()
|
@ -0,0 +1,131 @@
|
||||
-- Shows mechanisms linked to the current building.
|
||||
|
||||
local utils = require 'utils'
|
||||
local gui = require 'gui'
|
||||
local guidm = require 'gui.dwarfmode'
|
||||
|
||||
function listMechanismLinks(building)
|
||||
local lst = {}
|
||||
local function push(item, mode)
|
||||
if item then
|
||||
lst[#lst+1] = {
|
||||
obj = item, mode = mode,
|
||||
name = utils.getBuildingName(item)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
push(building, 'self')
|
||||
|
||||
if not df.building_actual:is_instance(building) then
|
||||
return lst
|
||||
end
|
||||
|
||||
local item, tref, tgt
|
||||
for _,v in ipairs(building.contained_items) do
|
||||
item = v.item
|
||||
if df.item_trappartsst:is_instance(item) then
|
||||
tref = dfhack.items.getGeneralRef(item, df.general_ref_type.BUILDING_TRIGGER)
|
||||
if tref then
|
||||
push(tref:getBuilding(), 'trigger')
|
||||
end
|
||||
tref = dfhack.items.getGeneralRef(item, df.general_ref_type.BUILDING_TRIGGERTARGET)
|
||||
if tref then
|
||||
push(tref:getBuilding(), 'target')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return lst
|
||||
end
|
||||
|
||||
MechanismList = defclass(MechanismList, guidm.MenuOverlay)
|
||||
|
||||
MechanismList.focus_path = 'mechanisms'
|
||||
|
||||
function MechanismList:init(building)
|
||||
self:init_fields{
|
||||
links = {}, selected = 1
|
||||
}
|
||||
guidm.MenuOverlay.init(self)
|
||||
self:fillList(building)
|
||||
return self
|
||||
end
|
||||
|
||||
function MechanismList:fillList(building)
|
||||
local links = listMechanismLinks(building)
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
local colors = {
|
||||
self = COLOR_CYAN, none = COLOR_CYAN,
|
||||
trigger = COLOR_GREEN, target = COLOR_GREEN
|
||||
}
|
||||
local icons = {
|
||||
self = 128, none = 63, trigger = 27, target = 26
|
||||
}
|
||||
|
||||
function MechanismList:onRenderBody(dc)
|
||||
dc:clear()
|
||||
dc:seek(1,1):string("Mechanism Links", COLOR_WHITE):newline()
|
||||
|
||||
for i,v in ipairs(self.links) do
|
||||
local pen = { fg=colors[v.mode], bold = (i == self.selected) }
|
||||
dc:newline(1):pen(pen):char(icons[v.mode])
|
||||
dc:advance(1):string(v.name)
|
||||
end
|
||||
|
||||
local nlinks = #self.links
|
||||
|
||||
if nlinks <= 1 then
|
||||
dc:newline():newline(1):string("This building has no links", COLOR_LIGHTRED)
|
||||
end
|
||||
|
||||
dc:newline():newline(1):pen(COLOR_WHITE)
|
||||
dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ")
|
||||
dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch")
|
||||
end
|
||||
|
||||
function MechanismList:changeSelected(delta)
|
||||
if #self.links <= 1 then return end
|
||||
self.selected = 1 + (self.selected + delta - 1) % #self.links
|
||||
self:selectBuilding(self.links[self.selected].obj)
|
||||
end
|
||||
|
||||
function MechanismList:onInput(keys)
|
||||
if keys.SECONDSCROLL_UP then
|
||||
self:changeSelected(-1)
|
||||
elseif keys.SECONDSCROLL_DOWN then
|
||||
self:changeSelected(1)
|
||||
elseif keys.LEAVESCREEN then
|
||||
self:dismiss()
|
||||
if self.selected ~= 1 then
|
||||
self:selectBuilding(self.links[1].obj, self.old_cursor, self.old_view)
|
||||
end
|
||||
elseif keys.SELECT_ALL then
|
||||
if self.selected > 1 then
|
||||
self:fillList(self.links[self.selected].obj)
|
||||
end
|
||||
elseif keys.SELECT then
|
||||
self:dismiss()
|
||||
elseif self:simulateViewScroll(keys) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some' then
|
||||
qerror("This script requires the main dwarfmode view in 'q' mode")
|
||||
end
|
||||
|
||||
local list = mkinstance(MechanismList):init(df.global.world.selected_building)
|
||||
list:show()
|
||||
list:changeSelected(1)
|
@ -0,0 +1,246 @@
|
||||
-- Browses rooms owned by a unit.
|
||||
|
||||
local utils = require 'utils'
|
||||
local gui = require 'gui'
|
||||
local guidm = require 'gui.dwarfmode'
|
||||
|
||||
local room_type_table = {
|
||||
[df.building_bedst] = { token = 'bed', qidx = 2, tile = 233 },
|
||||
[df.building_tablest] = { token = 'table', qidx = 3, tile = 209 },
|
||||
[df.building_chairst] = { token = 'chair', qidx = 4, tile = 210 },
|
||||
[df.building_coffinst] = { token = 'coffin', qidx = 5, tile = 48 },
|
||||
}
|
||||
|
||||
local room_quality_table = {
|
||||
{ 1, 'Meager Quarters', 'Meager Dining Room', 'Meager Office', 'Grave' },
|
||||
{ 100, 'Modest Quarters', 'Modest Dining Room', 'Modest Office', "Servant's Burial Chamber" },
|
||||
{ 250, 'Quarters', 'Dining Room', 'Office', 'Burial Chamber' },
|
||||
{ 500, 'Decent Quarters', 'Decent Dining Room', 'Decent Office', 'Tomb' },
|
||||
{ 1000, 'Fine Quarters', 'Fine Dining Room', 'Splendid Office', 'Fine Tomb' },
|
||||
{ 1500, 'Great Bedroom', 'Great Dining Room', 'Throne Room', 'Mausoleum' },
|
||||
{ 2500, 'Grand Bedroom', 'Grand Dining Room', 'Opulent Throne Room', 'Grand Mausoleum' },
|
||||
{ 10000, 'Royal Bedroom', 'Royal Dining Room', 'Royal Throne Room', 'Royal Mausoleum' }
|
||||
}
|
||||
|
||||
function getRoomName(building, unit)
|
||||
local info = room_type_table[building._type]
|
||||
if not info or not building.is_room then
|
||||
return utils.getBuildingName(building)
|
||||
end
|
||||
|
||||
local quality = building:getRoomValue(unit)
|
||||
local row = room_quality_table[1]
|
||||
for _,v in ipairs(room_quality_table) do
|
||||
if v[1] <= quality then
|
||||
row = v
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
return row[info.qidx]
|
||||
end
|
||||
|
||||
function makeRoomEntry(bld, unit, is_spouse)
|
||||
local info = room_type_table[bld._type] or {}
|
||||
|
||||
return {
|
||||
obj = bld,
|
||||
token = info.token or '?',
|
||||
tile = info.tile or '?',
|
||||
caption = getRoomName(bld, unit),
|
||||
can_use = (not is_spouse or bld:canUseSpouseRoom()),
|
||||
owner = unit
|
||||
}
|
||||
end
|
||||
|
||||
function listRooms(unit, spouse)
|
||||
local rv = {}
|
||||
for _,v in pairs(unit.owned_buildings) do
|
||||
if v.owner == unit then
|
||||
rv[#rv+1] = makeRoomEntry(v, unit, spouse)
|
||||
end
|
||||
end
|
||||
return rv
|
||||
end
|
||||
|
||||
function concat_lists(...)
|
||||
local rv = {}
|
||||
for i = 1,select('#',...) do
|
||||
local v = select(i,...)
|
||||
if v then
|
||||
for _,x in ipairs(v) do rv[#rv+1] = x end
|
||||
end
|
||||
end
|
||||
return rv
|
||||
end
|
||||
|
||||
RoomList = defclass(RoomList, guidm.MenuOverlay)
|
||||
|
||||
RoomList.focus_path = 'room-list'
|
||||
|
||||
function RoomList:init(unit)
|
||||
local base_bld = df.global.world.selected_building
|
||||
|
||||
self:init_fields{
|
||||
unit = unit, base_building = base_bld,
|
||||
items = {}, selected = 1,
|
||||
own_rooms = {}, spouse_rooms = {}
|
||||
}
|
||||
guidm.MenuOverlay.init(self)
|
||||
|
||||
self.old_viewport = self:getViewport()
|
||||
self.old_cursor = guidm.getCursorPos()
|
||||
|
||||
if unit then
|
||||
self.own_rooms = listRooms(unit)
|
||||
self.spouse = df.unit.find(unit.relations.spouse_id)
|
||||
if self.spouse then
|
||||
self.spouse_rooms = listRooms(self.spouse, unit)
|
||||
end
|
||||
self.items = concat_lists(self.own_rooms, self.spouse_rooms)
|
||||
end
|
||||
|
||||
if base_bld then
|
||||
for i,v in ipairs(self.items) do
|
||||
if v.obj == base_bld then
|
||||
self.selected = i
|
||||
v.tile = 26
|
||||
goto found
|
||||
end
|
||||
end
|
||||
self.base_item = makeRoomEntry(base_bld, unit)
|
||||
self.base_item.owner = unit
|
||||
self.base_item.old_owner = base_bld.owner
|
||||
self.base_item.tile = 26
|
||||
self.items = concat_lists({self.base_item}, self.items)
|
||||
::found::
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local sex_char = { [0] = 12, [1] = 11 }
|
||||
|
||||
function drawUnitName(dc, unit)
|
||||
dc:pen(COLOR_GREY)
|
||||
if unit then
|
||||
local color = dfhack.units.getProfessionColor(unit)
|
||||
dc:char(sex_char[unit.sex] or '?'):advance(1):pen(color)
|
||||
|
||||
local vname = dfhack.units.getVisibleName(unit)
|
||||
if vname and vname.has_name then
|
||||
dc:string(dfhack.TranslateName(vname)..', ')
|
||||
end
|
||||
dc:string(dfhack.units.getProfessionName(unit))
|
||||
else
|
||||
dc:string("No Owner Assigned")
|
||||
end
|
||||
end
|
||||
|
||||
function drawRoomEntry(dc, entry, selected)
|
||||
local color = COLOR_GREEN
|
||||
if not entry.can_use then
|
||||
color = COLOR_RED
|
||||
elseif entry.obj.owner ~= entry.owner or not entry.owner then
|
||||
color = COLOR_CYAN
|
||||
end
|
||||
dc:pen{fg = color, bold = (selected == entry)}
|
||||
dc:char(entry.tile):advance(1):string(entry.caption)
|
||||
end
|
||||
|
||||
function can_modify(sel_item)
|
||||
return sel_item and sel_item.owner
|
||||
and sel_item.can_use and not sel_item.owner.flags1.dead
|
||||
end
|
||||
|
||||
function RoomList:onRenderBody(dc)
|
||||
local sel_item = self.items[self.selected]
|
||||
|
||||
dc:clear():seek(1,1)
|
||||
drawUnitName(dc, self.unit)
|
||||
|
||||
if self.base_item then
|
||||
dc:newline():newline(2)
|
||||
drawRoomEntry(dc, self.base_item, sel_item)
|
||||
end
|
||||
if #self.own_rooms > 0 then
|
||||
dc:newline()
|
||||
for _,v in ipairs(self.own_rooms) do
|
||||
dc:newline(2)
|
||||
drawRoomEntry(dc, v, sel_item)
|
||||
end
|
||||
end
|
||||
if #self.spouse_rooms > 0 then
|
||||
dc:newline():newline(1)
|
||||
drawUnitName(dc, self.spouse)
|
||||
|
||||
dc:newline()
|
||||
for _,v in ipairs(self.spouse_rooms) do
|
||||
dc:newline(2)
|
||||
drawRoomEntry(dc, v, sel_item)
|
||||
end
|
||||
end
|
||||
if self.unit and #self.own_rooms == 0 and #self.spouse_rooms == 0 then
|
||||
dc:newline():newline(2):string("No already assigned rooms.", COLOR_LIGHTRED)
|
||||
end
|
||||
|
||||
dc:newline():newline(1):pen(COLOR_WHITE)
|
||||
dc:string("Esc", COLOR_LIGHTGREEN):string(": Back")
|
||||
|
||||
if can_modify(sel_item) then
|
||||
dc:string(", "):string("Enter", COLOR_LIGHTGREEN)
|
||||
if sel_item.obj.owner == sel_item.owner then
|
||||
dc:string(": Unassign")
|
||||
else
|
||||
dc:string(": Assign")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function RoomList:changeSelected(delta)
|
||||
if #self.items <= 1 then return end
|
||||
self.selected = 1 + (self.selected + delta - 1) % #self.items
|
||||
self:selectBuilding(self.items[self.selected].obj)
|
||||
end
|
||||
|
||||
function RoomList:onInput(keys)
|
||||
local sel_item = self.items[self.selected]
|
||||
|
||||
if keys.SECONDSCROLL_UP then
|
||||
self:changeSelected(-1)
|
||||
elseif keys.SECONDSCROLL_DOWN then
|
||||
self:changeSelected(1)
|
||||
elseif keys.LEAVESCREEN then
|
||||
self:dismiss()
|
||||
|
||||
if self.base_building then
|
||||
if not sel_item or self.base_building ~= sel_item.obj then
|
||||
self:selectBuilding(self.base_building, self.old_cursor, self.old_view)
|
||||
end
|
||||
if self.unit and self.base_building.owner == self.unit then
|
||||
df.global.ui_building_in_assign = false
|
||||
end
|
||||
end
|
||||
elseif keys.SELECT then
|
||||
if can_modify(sel_item) then
|
||||
local owner = sel_item.owner
|
||||
if sel_item.obj.owner == owner then
|
||||
owner = sel_item.old_owner
|
||||
end
|
||||
dfhack.buildings.setOwner(sel_item.obj, owner)
|
||||
end
|
||||
elseif self:simulateViewScroll(keys) then
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
local focus = dfhack.gui.getCurFocus()
|
||||
if focus == 'dwarfmode/QueryBuilding/Some' then
|
||||
local base = df.global.world.selected_building
|
||||
mkinstance(RoomList):init(base.owner):show()
|
||||
elseif focus == 'dwarfmode/QueryBuilding/Some/Assign/Unit' then
|
||||
local unit = df.global.ui_building_assign_units[df.global.ui_building_item_cursor]
|
||||
mkinstance(RoomList):init(unit):show()
|
||||
else
|
||||
qerror("This script requires the main dwarfmode view in 'q' mode")
|
||||
end
|
Loading…
Reference in New Issue