Merge branch 'master' of http://github.com/peterix/dfhack
commit
96abc903ab
@ -0,0 +1,249 @@
|
||||
/*
|
||||
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*));
|
||||
}
|
||||
|
||||
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),
|
||||
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();
|
||||
}
|
||||
|
||||
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);
|
||||
assert(old_ptr != NULL);
|
||||
|
||||
// Check if there are other interpose entries for the same slot
|
||||
VMethodInterposeLinkBase *old_link = NULL;
|
||||
|
||||
for (int i = host->interpose_list.size()-1; i >= 0; i--)
|
||||
{
|
||||
if (host->interpose_list[i]->vmethod_idx != vmethod_idx)
|
||||
continue;
|
||||
|
||||
old_link = host->interpose_list[i];
|
||||
assert(old_link->next == NULL && old_ptr == old_link->interpose_method);
|
||||
break;
|
||||
}
|
||||
|
||||
// Apply the new method ptr
|
||||
if (!host->set_vmethod_ptr(vmethod_idx, interpose_method))
|
||||
return false;
|
||||
|
||||
set_chain(old_ptr);
|
||||
host->interpose_list.push_back(this);
|
||||
|
||||
// Link into the chain if any
|
||||
if (old_link)
|
||||
{
|
||||
old_link->next = this;
|
||||
prev = old_link;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VMethodInterposeLinkBase::remove()
|
||||
{
|
||||
if (!is_applied())
|
||||
return;
|
||||
|
||||
// Remove from the list in the identity
|
||||
for (int i = host->interpose_list.size()-1; i >= 0; i--)
|
||||
if (host->interpose_list[i] == this)
|
||||
vector_erase_at(host->interpose_list, i);
|
||||
|
||||
// Remove from the chain
|
||||
if (prev)
|
||||
prev->next = next;
|
||||
|
||||
if (next)
|
||||
{
|
||||
next->set_chain(saved_chain);
|
||||
next->prev = prev;
|
||||
}
|
||||
else
|
||||
{
|
||||
host->set_vmethod_ptr(vmethod_idx, saved_chain);
|
||||
}
|
||||
|
||||
prev = next = NULL;
|
||||
set_chain(NULL);
|
||||
}
|
@ -0,0 +1,173 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
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
|
||||
|
||||
void *saved_chain; // Previous pointer to the code
|
||||
VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method
|
||||
|
||||
void set_chain(void *chain);
|
||||
public:
|
||||
VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr);
|
||||
~VMethodInterposeLinkBase();
|
||||
|
||||
bool is_applied() { return saved_chain != NULL; }
|
||||
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,400 @@
|
||||
-- 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(arg) == '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: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,279 @@
|
||||
-- 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 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 df.global.cursor.x ~= -30000 then
|
||||
return copyall(df.global.cursor)
|
||||
end
|
||||
end
|
||||
|
||||
function setCursorPos(cursor)
|
||||
df.global.cursor = cursor
|
||||
end
|
||||
|
||||
function clearCursorPos()
|
||||
df.global.cursor = xyz2pos(nil)
|
||||
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 ad38c5e96b05fedf16114fd16bd463e933f13582
|
||||
Subproject commit abcb667bc832048552d8cbc8f4830936f8b63399
|
@ -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 +0,0 @@
|
||||
as -anl --32 -o functions.o functions.asm
|
@ -1,23 +0,0 @@
|
||||
.intel_syntax
|
||||
push eax
|
||||
push ebp
|
||||
push esp
|
||||
push esi
|
||||
push edi
|
||||
push edx
|
||||
push ecx
|
||||
push ebx
|
||||
push eax
|
||||
mov eax,[esp+36]
|
||||
push eax
|
||||
function:
|
||||
call 0xdeadbee0
|
||||
function2:
|
||||
mov [0xdeadbeef],eax
|
||||
pop eax
|
||||
function3:
|
||||
jmp [0xdeadbeef]
|
||||
|
||||
|
||||
|
||||
|
Binary file not shown.
@ -1,68 +0,0 @@
|
||||
onfunction=onfunction or {}
|
||||
function onfunction.install()
|
||||
ModData=engine.installMod("dfusion/onfunction/functions.o","functions",4)
|
||||
modpos=ModData.pos
|
||||
modsize=ModData.size
|
||||
onfunction.pos=modpos
|
||||
trgpos=engine.getpushvalue()
|
||||
print(string.format("Function installed in:%x function to call is: %x",modpos,trgpos))
|
||||
local firstpos=modpos+engine.FindMarker(ModData,"function")
|
||||
engine.poked(firstpos,trgpos-firstpos-4) --call Lua-Onfunction
|
||||
onfunction.fpos=modpos+engine.FindMarker(ModData,"function3")
|
||||
engine.poked(modpos+engine.FindMarker(ModData,"function2"),modpos+modsize)
|
||||
engine.poked(onfunction.fpos,modpos+modsize)
|
||||
SetExecute(modpos)
|
||||
onfunction.calls={}
|
||||
onfunction.functions={}
|
||||
onfunction.names={}
|
||||
onfunction.hints={}
|
||||
end
|
||||
function OnFunction(values)
|
||||
--[=[print("Onfunction called!")
|
||||
print("Data:")
|
||||
for k,v in pairs(values) do
|
||||
print(string.format("%s=%x",k,v))
|
||||
end
|
||||
print("stack:")
|
||||
for i=0,3 do
|
||||
print(string.format("%d %x",i,engine.peekd(values.esp+i*4)))
|
||||
end
|
||||
--]=]
|
||||
if onfunction.functions[values.ret] ~=nil then
|
||||
onfunction.functions[values.ret](values)
|
||||
end
|
||||
|
||||
return onfunction.calls[values.ret] --returns real function to call
|
||||
end
|
||||
function onfunction.patch(addr)
|
||||
|
||||
if(engine.peekb(addr)~=0xe8) then
|
||||
error("Incorrect address, not a function call")
|
||||
else
|
||||
onfunction.calls[addr+5]=addr+engine.peekd(addr+1)+5 --adds real function to call
|
||||
engine.poked(addr+1,engine.getmod("functions")-addr-5)
|
||||
end
|
||||
end
|
||||
function onfunction.AddFunction(addr,name,hints)
|
||||
onfunction.patch(addr)
|
||||
onfunction.names[name]=addr+5
|
||||
if hints~=nil then
|
||||
onfunction.hints[name]=hints
|
||||
end
|
||||
end
|
||||
function onfunction.ReadHint(values,name,hintname)
|
||||
local hints=onfunction.hints[name]
|
||||
if hints ==nil then return nil end
|
||||
local hint=hints[hintname]
|
||||
if type(hint)=="string" then return values[hint] end
|
||||
local off=hint.off or 0
|
||||
return engine.peek(off+values[hint.reg],hints[hintname].rtype)
|
||||
end
|
||||
function onfunction.SetCallback(name,func)
|
||||
if onfunction.names[name]==nil then
|
||||
error("No such function:"..name)
|
||||
else
|
||||
onfunction.functions[onfunction.names[name]]=func
|
||||
end
|
||||
end
|
||||
|
@ -1,16 +0,0 @@
|
||||
if WINDOWS then --windows function defintions
|
||||
--[=[onfunction.AddFunction(0x55499D+offsets.base(),"Move") --on creature move found with "watch mem=xcoord"
|
||||
onfunction.AddFunction(0x275933+offsets.base(),"Die",{creature="edi"}) --on creature death? found by watching dead flag then stepping until new function
|
||||
onfunction.AddFunction(0x2c1834+offsets.base(),"CreateCreature",{protocreature="eax"}) --arena
|
||||
onfunction.AddFunction(0x349640+offsets.base(),"AddItem",{item="esp"}) --or esp
|
||||
onfunction.AddFunction(0x26e840+offsets.base(),"Dig_Create",{item_type="esp"}) --esp+8 -> material esp->block type
|
||||
onfunction.AddFunction(0x3d4301+offsets.base(),"Make_Item",{item_type="esp"})
|
||||
onfunction.AddFunction(0x5af826+offsets.base(),"Hurt",{target="esi",attacker={off=0x74,rtype=DWORD,reg="esp"}})
|
||||
onfunction.AddFunction(0x3D5886+offsets.base(),"Flip",{building="esi"})
|
||||
onfunction.AddFunction(0x35E340+offsets.base(),"ItemCreate")--]=]
|
||||
--onfunction.AddFunction(0x4B34B6+offsets.base(),"ReactionFinish") --esp item. Ecx creature, edx? 0.34.07
|
||||
onfunction.AddFunction(0x72aB6+offsets.base(),"Die",{creature="edi"}) --0.34.07
|
||||
else --linux
|
||||
--[=[onfunction.AddFunction(0x899befe+offsets.base(),"Move") -- found out by attaching watch...
|
||||
onfunction.AddFunction(0x850eecd+offsets.base(),"Die",{creature="ebx"}) -- same--]=]
|
||||
end
|
@ -1,15 +0,0 @@
|
||||
mypos=engine.getmod("functions")
|
||||
function DeathMsg(values)
|
||||
local name
|
||||
local u=engine.cast(df.unit,values[onfunction.hints["Die"].creature])
|
||||
|
||||
print(u.name.first_name.." died")
|
||||
end
|
||||
if mypos then
|
||||
print("Onfunction already installed")
|
||||
--onfunction.patch(0x189dd6+offsets.base())
|
||||
else
|
||||
onfunction.install()
|
||||
dofile("dfusion/onfunction/locations.lua")
|
||||
onfunction.SetCallback("Die",DeathMsg)
|
||||
end
|
@ -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");
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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,661 @@
|
||||
// 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 <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, "Tr"},
|
||||
{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, "Pu"},
|
||||
{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, "Fi"},
|
||||
{profession::WRESTLER, unit_labor::NONE, job_skill::RANGED_COMBAT, "Ar"},
|
||||
|
||||
{profession::RECRUIT, unit_labor::NONE, job_skill::LEADERSHIP, "Le"},
|
||||
{profession::RECRUIT, unit_labor::NONE, job_skill::TEACHING, "Te"},
|
||||
{profession::RECRUIT, unit_labor::NONE, job_skill::KNOWLEDGE_ACQUISITION, "Lr"},
|
||||
{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, "Aw"},
|
||||
{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, "Ly"},
|
||||
{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"},
|
||||
{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;
|
||||
};
|
||||
|
||||
#define FILTER_NONWORKERS 0x0001
|
||||
#define FILTER_NONDWARVES 0x0002
|
||||
#define FILTER_NONCIV 0x0004
|
||||
#define FILTER_ANIMALS 0x0008
|
||||
#define FILTER_LIVING 0x0010
|
||||
#define FILTER_DEAD 0x0020
|
||||
|
||||
class viewscreen_unitlaborsst : public dfhack_viewscreen {
|
||||
public:
|
||||
static viewscreen_unitlaborsst *create (char pushtype, df::viewscreen *scr = NULL);
|
||||
|
||||
void feed(set<df::interface_key> *events);
|
||||
|
||||
void render();
|
||||
void resize(int w, int h) { calcSize(); }
|
||||
|
||||
void help() { }
|
||||
|
||||
std::string getFocusString() { return "unitlabors"; }
|
||||
|
||||
viewscreen_unitlaborsst();
|
||||
~viewscreen_unitlaborsst() { };
|
||||
|
||||
protected:
|
||||
vector<UnitInfo *> units;
|
||||
int filter;
|
||||
|
||||
int first_row, sel_row;
|
||||
int first_column, sel_column;
|
||||
|
||||
int height, name_width, prof_width, labors_width;
|
||||
|
||||
// bool descending;
|
||||
// int sort_skill;
|
||||
// int sort_labor;
|
||||
|
||||
void readUnits ();
|
||||
void calcSize ();
|
||||
};
|
||||
|
||||
viewscreen_unitlaborsst::viewscreen_unitlaborsst()
|
||||
{
|
||||
filter = FILTER_LIVING;
|
||||
first_row = sel_row = 0;
|
||||
first_column = sel_column = 0;
|
||||
calcSize();
|
||||
readUnits();
|
||||
}
|
||||
|
||||
void viewscreen_unitlaborsst::readUnits ()
|
||||
{
|
||||
for (size_t i = 0; i < units.size(); i++)
|
||||
delete units[i];
|
||||
units.clear();
|
||||
|
||||
UnitInfo *cur = new UnitInfo;
|
||||
for (size_t i = 0; i < world->units.active.size(); i++)
|
||||
{
|
||||
df::unit *unit = world->units.active[i];
|
||||
cur->unit = unit;
|
||||
cur->allowEdit = true;
|
||||
|
||||
if (unit->race != ui->race_id)
|
||||
{
|
||||
cur->allowEdit = false;
|
||||
if (!(filter & FILTER_NONDWARVES))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unit->civ_id != ui->civ_id)
|
||||
{
|
||||
cur->allowEdit = false;
|
||||
if (!(filter & FILTER_NONCIV))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unit->flags1.bits.dead)
|
||||
{
|
||||
cur->allowEdit = false;
|
||||
if (!(filter & FILTER_DEAD))
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(filter & FILTER_LIVING))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ENUM_ATTR(profession, can_assign_labor, unit->profession))
|
||||
{
|
||||
cur->allowEdit = false;
|
||||
if (!(filter & FILTER_NONWORKERS))
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!unit->name.first_name.length())
|
||||
{
|
||||
if (!(filter & FILTER_ANIMALS))
|
||||
continue;
|
||||
}
|
||||
|
||||
cur->name = Translation::TranslateName(&unit->name, false);
|
||||
cur->transname = Translation::TranslateName(&unit->name, true);
|
||||
cur->profession = Units::getProfessionName(unit);
|
||||
cur->color = Units::getProfessionColor(unit);
|
||||
|
||||
units.push_back(cur);
|
||||
cur = new UnitInfo;
|
||||
}
|
||||
delete cur;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// TODO - allow modifying filters
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
// TODO: add sorting
|
||||
}
|
||||
|
||||
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 + prof_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;
|
||||
if ((col_offset == sel_column) && (row_offset == sel_row))
|
||||
fg = 9;
|
||||
if ((columns[col_offset].labor != unit_labor::NONE) && (unit->status.labors[columns[col_offset].labor]))
|
||||
bg = 7;
|
||||
char c = '-';
|
||||
if (columns[col_offset].skill != job_skill::NONE)
|
||||
{
|
||||
df::unit_skill *skill = binsearch_in_vector<df::unit_skill,df::enum_field<df::job_skill,int16_t>>(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill);
|
||||
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;
|
||||
}
|
||||
}
|
||||
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;
|
||||
string str = cur->transname;
|
||||
if (str.length())
|
||||
str += ", ";
|
||||
str += cur->profession;
|
||||
str += ":";
|
||||
|
||||
Screen::paintString(Screen::Pen(' ', 15, 0), 1, 3 + height + 2, str);
|
||||
int y = 1 + str.length() + 1;
|
||||
|
||||
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";
|
||||
Screen::paintString(Screen::Pen(' ', 9, 0), y, 3 + height + 2, str);
|
||||
}
|
||||
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), y, 3 + height + 2, str);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO - print command help info
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
Screen::dismiss(this);
|
||||
Screen::show(new viewscreen_unitlaborsst());
|
||||
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;
|
||||
}
|
@ -0,0 +1,199 @@
|
||||
module DFHack
|
||||
class MaterialInfo
|
||||
attr_accessor :mat_type, :mat_index
|
||||
attr_accessor :mode, :material, :creature, :figure, :plant, :inorganic
|
||||
def initialize(what, idx=nil)
|
||||
case what
|
||||
when Integer
|
||||
@mat_type, @mat_index = what, idx
|
||||
decode_type_index
|
||||
when String
|
||||
decode_string(what)
|
||||
else
|
||||
@mat_type, @mat_index = what.mat_type, what.mat_index
|
||||
decode_type_index
|
||||
end
|
||||
end
|
||||
|
||||
CREATURE_BASE = 19
|
||||
FIGURE_BASE = CREATURE_BASE+200
|
||||
PLANT_BASE = FIGURE_BASE+200
|
||||
END_BASE = PLANT_BASE+200
|
||||
|
||||
# interpret the mat_type and mat_index fields
|
||||
def decode_type_index
|
||||
if @mat_index < 0 or @mat_type >= END_BASE
|
||||
@mode = :Builtin
|
||||
@material = df.world.raws.mat_table.builtin[@mat_type]
|
||||
|
||||
elsif @mat_type >= PLANT_BASE
|
||||
@mode = :Plant
|
||||
@plant = df.world.raws.plants.all[@mat_index]
|
||||
@material = @plant.material[@mat_type-PLANT_BASE] if @plant
|
||||
|
||||
elsif @mat_type >= FIGURE_BASE
|
||||
@mode = :Figure
|
||||
@figure = df.world.history.figures.binsearch(@mat_index)
|
||||
@creature = df.world.raws.creatures.all[@figure.race] if @figure
|
||||
@material = @creature.material[@mat_type-FIGURE_BASE] if @creature
|
||||
|
||||
elsif @mat_type >= CREATURE_BASE
|
||||
@mode = :Creature
|
||||
@creature = df.world.raws.creatures.all[@mat_index]
|
||||
@material = @creature.material[@mat_type-CREATURE_BASE] if @creature
|
||||
|
||||
elsif @mat_type > 0
|
||||
@mode = :Builtin
|
||||
@material = df.world.raws.mat_table.builtin[@mat_type]
|
||||
|
||||
elsif @mat_type == 0
|
||||
@mode = :Inorganic
|
||||
@inorganic = df.world.raws.inorganics[@mat_index]
|
||||
@material = @inorganic.material if @inorganic
|
||||
end
|
||||
end
|
||||
|
||||
def decode_string(str)
|
||||
parts = str.split(':')
|
||||
case parts[0].chomp('_MAT')
|
||||
when 'INORGANIC', 'STONE', 'METAL'
|
||||
decode_string_inorganic(parts)
|
||||
when 'PLANT'
|
||||
decode_string_plant(parts)
|
||||
when 'CREATURE'
|
||||
if parts[3] and parts[3] != 'NONE'
|
||||
decode_string_figure(parts)
|
||||
else
|
||||
decode_string_creature(parts)
|
||||
end
|
||||
when 'INVALID'
|
||||
@mat_type = parts[1].to_i
|
||||
@mat_index = parts[2].to_i
|
||||
else
|
||||
decode_string_builtin(parts)
|
||||
end
|
||||
end
|
||||
|
||||
def decode_string_inorganic(parts)
|
||||
@@inorganics_index ||= (0...df.world.raws.inorganics.length).inject({}) { |h, i| h.update df.world.raws.inorganics[i].id => i }
|
||||
|
||||
@mode = :Inorganic
|
||||
@mat_type = 0
|
||||
|
||||
if parts[1] and parts[1] != 'NONE'
|
||||
@mat_index = @@inorganics_index[parts[1]]
|
||||
raise "invalid inorganic token #{parts.join(':').inspect}" if not @mat_index
|
||||
@inorganic = df.world.raws.inorganics[@mat_index]
|
||||
@material = @inorganic.material
|
||||
end
|
||||
end
|
||||
|
||||
def decode_string_builtin(parts)
|
||||
@@builtins_index ||= (1...df.world.raws.mat_table.builtin.length).inject({}) { |h, i| b = df.world.raws.mat_table.builtin[i] ; b ? h.update(b.id => i) : h }
|
||||
|
||||
@mode = :Builtin
|
||||
@mat_index = -1
|
||||
@mat_type = @@builtins_index[parts[0]]
|
||||
raise "invalid builtin token #{parts.join(':').inspect}" if not @mat_type
|
||||
@material = df.world.raws.mat_table.builtin[@mat_type]
|
||||
|
||||
if parts[0] == 'COAL' and parts[1]
|
||||
@mat_index = ['COKE', 'CHARCOAL'].index(parts[1]) || -1
|
||||
end
|
||||
end
|
||||
|
||||
def decode_string_creature(parts)
|
||||
@@creatures_index ||= (0...df.world.raws.creatures.all.length).inject({}) { |h, i| h.update df.world.raws.creatures.all[i].creature_id => i }
|
||||
|
||||
@mode = :Creature
|
||||
|
||||
if parts[1] and parts[1] != 'NONE'
|
||||
@mat_index = @@creatures_index[parts[1]]
|
||||
raise "invalid creature token #{parts.join(':').inspect}" if not @mat_index
|
||||
@creature = df.world.raws.creatures.all[@mat_index]
|
||||
end
|
||||
|
||||
if @creature and parts[2] and parts[2] != 'NONE'
|
||||
@mat_type = @creature.material.index { |m| m.id == parts[2] }
|
||||
@material = @creature.material[@mat_type]
|
||||
@mat_type += CREATURE_BASE
|
||||
end
|
||||
end
|
||||
|
||||
def decode_string_figure(parts)
|
||||
@mode = :Figure
|
||||
@mat_index = parts[3].to_i
|
||||
@figure = df.world.history.figures.binsearch(@mat_index)
|
||||
raise "invalid creature histfig #{parts.join(':').inspect}" if not @figure
|
||||
|
||||
@creature = df.world.raws.creatures.all[@figure.race]
|
||||
if parts[1] and parts[1] != 'NONE'
|
||||
raise "invalid histfig race #{parts.join(':').inspect}" if @creature.creature_id != parts[1]
|
||||
end
|
||||
|
||||
if @creature and parts[2] and parts[2] != 'NONE'
|
||||
@mat_type = @creature.material.index { |m| m.id == parts[2] }
|
||||
@material = @creature.material[@mat_type]
|
||||
@mat_type += FIGURE_BASE
|
||||
end
|
||||
end
|
||||
|
||||
def decode_string_plant(parts)
|
||||
@@plants_index ||= (0...df.world.raws.plants.all.length).inject({}) { |h, i| h.update df.world.raws.plants.all[i].id => i }
|
||||
|
||||
@mode = :Plant
|
||||
|
||||
if parts[1] and parts[1] != 'NONE'
|
||||
@mat_index = @@plants_index[parts[1]]
|
||||
raise "invalid plant token #{parts.join(':').inspect}" if not @mat_index
|
||||
@plant = df.world.raws.plants.all[@mat_index]
|
||||
end
|
||||
|
||||
if @plant and parts[2] and parts[2] != 'NONE'
|
||||
@mat_type = @plant.material.index { |m| m.id == parts[2] }
|
||||
raise "invalid plant type #{parts.join(':').inspect}" if not @mat_type
|
||||
@material = @plant.material[@mat_type]
|
||||
@mat_type += PLANT_BASE
|
||||
end
|
||||
end
|
||||
|
||||
# delete the caches of raws id => index used in decode_string
|
||||
def self.flush_raws_cache
|
||||
@@inorganics_index = @@plants_index = @@creatures_index = @@builtins_index = nil
|
||||
end
|
||||
|
||||
def token
|
||||
out = []
|
||||
case @mode
|
||||
when :Builtin
|
||||
out << (@material ? @material.id : 'NONE')
|
||||
out << (['COKE', 'CHARCOAL'][@mat_index] || 'NONE') if @material and @material.id == 'COAL' and @mat_index >= 0
|
||||
when :Inorganic
|
||||
out << 'INORGANIC'
|
||||
out << @inorganic.id if @inorganic
|
||||
when :Plant
|
||||
out << 'PLANT_MAT'
|
||||
out << @plant.id if @plant
|
||||
out << @material.id if @plant and @material
|
||||
when :Creature, :Figure
|
||||
out << 'CREATURE_MAT'
|
||||
out << @creature.creature_id if @creature
|
||||
out << @material.id if @creature and @material
|
||||
out << @figure.id.to_s if @creature and @material and @figure
|
||||
else
|
||||
out << 'INVALID'
|
||||
out << @mat_type.to_s
|
||||
out << @mat_index.to_s
|
||||
end
|
||||
out.join(':')
|
||||
end
|
||||
|
||||
def to_s ; token ; end
|
||||
end
|
||||
|
||||
class << self
|
||||
def decode_mat(what, idx=nil)
|
||||
MaterialInfo.new(what, idx)
|
||||
end
|
||||
end
|
||||
end
|
File diff suppressed because it is too large
Load Diff
@ -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,20 @@
|
||||
# fix doors that are frozen in 'open' state
|
||||
|
||||
# door is stuck in open state if the map occupancy flag incorrectly indicates
|
||||
# that an unit is present (and creatures will prone to pass through)
|
||||
|
||||
count = 0
|
||||
df.world.buildings.all.each { |bld|
|
||||
# for all doors
|
||||
next if bld._rtti_classname != :building_doorst
|
||||
# check if it is open
|
||||
next if bld.close_timer == 0
|
||||
# check if occupancy is set
|
||||
occ = df.map_occupancy_at(bld.x1, bld.y1, bld.z)
|
||||
next if not occ.unit
|
||||
# check if an unit is present
|
||||
next if df.world.units.active.find { |u| u.pos.x == bld.x1 and u.pos.y == bld.y1 and u.pos.z == bld.z }
|
||||
count += 1
|
||||
occ.unit = false
|
||||
}
|
||||
puts "unstuck #{count} doors"
|
@ -0,0 +1,41 @@
|
||||
function fixnaked()
|
||||
local total_fixed = 0
|
||||
local total_removed = 0
|
||||
|
||||
for fnUnitCount,fnUnit in ipairs(df.global.world.units.all) do
|
||||
if fnUnit.race == df.global.ui.race_id then
|
||||
local listEvents = fnUnit.status.recent_events
|
||||
--for lkey,lvalue in pairs(listEvents) do
|
||||
-- print(df.unit_thought_type[lvalue.type],lvalue.type,lvalue.age,lvalue.subtype,lvalue.severity)
|
||||
--end
|
||||
|
||||
local found = 1
|
||||
local fixed = 0
|
||||
while found == 1 do
|
||||
local events = fnUnit.status.recent_events
|
||||
found = 0
|
||||
for k,v in pairs(events) do
|
||||
if v.type == df.unit_thought_type.Uncovered
|
||||
or v.type == df.unit_thought_type.NoShirt
|
||||
or v.type == df.unit_thought_type.NoShoes
|
||||
or v.type == df.unit_thought_type.NoCloak
|
||||
or v.type == df.unit_thought_type.OldClothing
|
||||
or v.type == df.unit_thought_type.TatteredClothing
|
||||
or v.type == df.unit_thought_type.RottedClothing then
|
||||
events:erase(k)
|
||||
found = 1
|
||||
total_removed = total_removed + 1
|
||||
fixed = 1
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if fixed == 1 then
|
||||
total_fixed = total_fixed + 1
|
||||
print(total_fixed, total_removed, dfhack.TranslateName(dfhack.units.getVisibleName(fnUnit)))
|
||||
end
|
||||
end
|
||||
end
|
||||
print("Total Fixed: "..total_fixed)
|
||||
end
|
||||
fixnaked()
|
@ -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,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
|
@ -0,0 +1,56 @@
|
||||
# create an infinite magma source at the cursor
|
||||
|
||||
$magma_sources ||= []
|
||||
|
||||
case $script_args[0]
|
||||
when 'here'
|
||||
$magma_onupdate ||= df.onupdate_register(12) {
|
||||
# called every 12 game ticks (100x a dwarf day)
|
||||
if $magma_sources.empty?
|
||||
df.onupdate_unregister($magma_onupdate)
|
||||
$magma_onupdate = nil
|
||||
end
|
||||
|
||||
$magma_sources.each { |x, y, z|
|
||||
if tile = df.map_tile_at(x, y, z) and tile.shape_passableflow
|
||||
des = tile.designation
|
||||
tile.spawn_magma(des.flow_size + 1) if des.flow_size < 7
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
if df.cursor.x != -30000
|
||||
if tile = df.map_tile_at(df.cursor)
|
||||
if tile.shape_passableflow
|
||||
$magma_sources << [df.cursor.x, df.cursor.y, df.cursor.z]
|
||||
else
|
||||
puts "Impassable tile: I'm afraid I can't do that, Dave"
|
||||
end
|
||||
else
|
||||
puts "Unallocated map block - build something here first"
|
||||
end
|
||||
else
|
||||
puts "Please put the game cursor where you want a magma source"
|
||||
end
|
||||
|
||||
when 'delete-here'
|
||||
$magma_sources.delete [df.cursor.x, df.cursor.y, df.cursor.z]
|
||||
|
||||
when 'stop'
|
||||
$magma_sources.clear
|
||||
|
||||
else
|
||||
puts <<EOS
|
||||
Creates a new infinite magma source at the cursor.
|
||||
|
||||
Arguments:
|
||||
here - create a new source at the current cursor position
|
||||
(call multiple times for higher flow)
|
||||
delete-here - delete the source under the cursor
|
||||
stop - delete all created magma sources
|
||||
EOS
|
||||
|
||||
if $magma_sources.first
|
||||
puts '', 'Current magma sources:', $magma_sources.map { |s| " #{s.inspect}" }
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue