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