Merge branch 'develop' of https://github.com/DFHack/dfhack into embark-assistant

develop
PatrikLundell 2020-05-05 10:02:02 +02:00
commit fb1dc1aea6
131 changed files with 5123 additions and 10818 deletions

@ -0,0 +1,106 @@
name: Build
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-18.04
steps:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install \
libsdl-image1.2-dev \
libsdl-ttf2.0-dev \
libsdl1.2-dev \
libxml-libxml-perl \
libxml-libxslt-perl \
lua5.3 \
ninja-build \
zlib1g-dev
sudo pip3 install --system sphinx
- name: Clone DFHack
uses: actions/checkout@v1
with:
submodules: true
- name: Set up environment
run: |
echo export DF_VERSION="$(sh travis/get-df-version.sh)" >> "$HOME/.df-env"
echo export DF_FOLDER="$HOME/DF/$DF_VERSION/df_linux" >> "$HOME/.df-env"
- name: Download DF
run: |
source "$HOME/.df-env"
sh travis/download-df.sh
- name: Build docs
run: |
sphinx-build -qW -j3 . docs/html
- name: Upload docs
uses: actions/upload-artifact@v1
with:
name: docs
path: docs/html
- name: Build DFHack
run: |
source "$HOME/.df-env"
cmake \
-S . \
-B build-ci \
-G Ninja \
-DDFHACK_BUILD_ARCH=64 \
-DBUILD_DOCS:BOOL=ON \
-DBUILD_TESTS:BOOL=ON \
-DCMAKE_INSTALL_PREFIX="$DF_FOLDER"
ninja -C build-ci install
- name: Run tests
run: |
source "$HOME/.df-env"
export TERM=dumb
mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init
script -qe -c "python travis/run-tests.py --headless --keep-status \"$DF_FOLDER\""
python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt"
mkdir -p artifacts
cp "$DF_FOLDER/test_status.json" "$DF_FOLDER"/*.log artifacts
- name: Upload test artifacts
uses: actions/upload-artifact@v1
if: success() || failure()
with:
name: test-artifacts
path: artifacts
lint:
runs-on: ubuntu-18.04
steps:
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install \
lua5.3 \
ruby
- name: Clone DFHack
uses: actions/checkout@v1
with:
submodules: true
- name: Check whitespace
run: |
python travis/lint.py
- name: Check Authors.rst
run: |
python travis/authors-rst.py
- name: Check for missing documentation
run: |
python travis/script-docs.py
- name: Check Lua syntax
run: |
python travis/script-syntax.py --ext=lua --cmd="luac5.3 -p"
- name: Check Ruby syntax
run: |
python travis/script-syntax.py --ext=rb --cmd="ruby -c"
check-pr:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Check that PR is based on develop branch
env:
BASE_BRANCH: ${{ github.base_ref }}
run: |
echo "PR base branch: $BASE_BRANCH"
test "$BASE_BRANCH" = develop

3
.gitignore vendored

@ -51,6 +51,9 @@ build/CPack*Config.cmake
*.swp
.vimrc
# VSCode files
.vscode
# ctags file
tags

2
.gitmodules vendored

@ -15,4 +15,4 @@
url = ../../DFHack/scripts.git
[submodule "depends/jsoncpp"]
path = depends/jsoncpp-sub
url = ../../open-source-parsers/jsoncpp.git
url = ../../DFHack/jsoncpp.git

@ -1,66 +0,0 @@
sudo: false
language: cpp
cache:
pip: true
directories:
- $HOME/DF-travis
- $HOME/lua53
addons:
apt:
packages: &default_packages
- libsdl-image1.2-dev
- libsdl-ttf2.0-dev
- libsdl1.2-dev
- libxml-libxml-perl
- libxml-libxslt-perl
- ninja-build
- zlib1g-dev
matrix:
include:
- env: GCC_VERSION=4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- *default_packages
- gcc-4.8
- g++-4.8
before_install:
- export DF_VERSION=$(sh travis/get-df-version.sh)
- export DF_FOLDER="$HOME/DF-travis/$DF_VERSION/df_linux"
- pip install --user "sphinx==1.4" "requests[security]"
- sh travis/build-lua.sh
- sh travis/download-df.sh
- echo "export DFHACK_HEADLESS=1" >> "$HOME/.dfhackrc"
- echo "export DFHACK_DISABLE_CONSOLE=1" >> "$HOME/.dfhackrc"
script:
- export PATH="$PATH:$HOME/lua53/bin"
- git tag tmp-travis-build
- sh travis/git-info.sh
- sphinx-build -qW -j3 . docs/html
- python travis/pr-check-base.py
- python travis/lint.py
- python travis/authors-rst.py
- python travis/script-docs.py
- python travis/script-syntax.py --ext=lua --cmd="luac5.3 -p"
- python travis/script-syntax.py --ext=rb --cmd="ruby -c"
- mkdir build-travis
- cd build-travis
- cmake .. -G Ninja -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DDFHACK_BUILD_ARCH=64 -DBUILD_DOCS:BOOL=ON -DCMAKE_INSTALL_PREFIX="$DF_FOLDER"
- ninja -j3 install
- mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init
- cd ..
- cp travis/dfhack_travis.init "$DF_FOLDER"/
- python travis/run-tests.py "$DF_FOLDER"
- python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt"
before_cache:
- cat "$DF_FOLDER/stderr.log"
- rm -rf "$DF_FOLDER"
notifications:
email: false
irc:
channels:
- "chat.freenode.net#dfhack"
on_success: change
on_failure: always

@ -10,6 +10,12 @@ if("${CMAKE_GENERATOR}" STREQUAL Ninja)
endif()
endif()
if(NOT("${CMAKE_VERSION}" VERSION_LESS 3.12))
# make ZLIB_ROOT work in CMake >= 3.12
# https://cmake.org/cmake/help/git-stage/policy/CMP0074.html
cmake_policy(SET CMP0074 NEW)
endif()
# Set up build types
if(CMAKE_CONFIGURATION_TYPES)
set(CMAKE_CONFIGURATION_TYPES "Release;RelWithDebInfo" CACHE STRING "List of supported configuration types" FORCE)
@ -173,8 +179,8 @@ if(NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhack
endif()
# set up versioning.
set(DF_VERSION "0.44.12")
set(DFHACK_RELEASE "r3")
set(DF_VERSION "0.47.04")
set(DFHACK_RELEASE "r1")
set(DFHACK_PRERELEASE FALSE)
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")
@ -413,7 +419,6 @@ file(WRITE "${CMAKE_BINARY_DIR}/dfhack_setarch.txt" ${DFHACK_SETARCH})
install(FILES "${CMAKE_BINARY_DIR}/dfhack_setarch.txt" DESTINATION "${DFHACK_DATA_DESTINATION}")
install(DIRECTORY dfhack-config/ DESTINATION dfhack-config/default)
install(DIRECTORY test DESTINATION "${DFHACK_DATA_DESTINATION}")
# build the plugins
if(BUILD_PLUGINS)
@ -474,6 +479,12 @@ if(BUILD_DOCS)
install(FILES "README.html" DESTINATION "${DFHACK_DATA_DESTINATION}")
endif()
option(BUILD_TESTS "Include tests (currently just installs Lua tests into the scripts folder)" OFF)
if(BUILD_TESTS)
install(DIRECTORY ${dfhack_SOURCE_DIR}/test
DESTINATION ${DFHACK_DATA_DESTINATION}/scripts)
endif()
# Packaging with CPack!
set(DFHACK_PACKAGE_SUFFIX "")
if(UNIX)
@ -496,7 +507,7 @@ if(DFHACK_BUILD_ID STREQUAL "")
else()
set(DFHACK_BUILD_ID_PACKAGE "${DFHACK_BUILD_ID}-")
endif()
set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${DFHACK_BUILD_ID_PACKAGE}${DFHACK_PACKAGE_PLATFORM_NAME}-${DFHACK_BUILD_ARCH}${DFHACK_PACKAGE_SUFFIX}")
set(CPACK_PACKAGE_FILE_NAME "dfhack-${DFHACK_VERSION}-${DFHACK_BUILD_ID_PACKAGE}${DFHACK_PACKAGE_PLATFORM_NAME}-${DFHACK_BUILD_ARCH}bit${DFHACK_PACKAGE_SUFFIX}")
include(CPack)
option(DFHACK_INCLUDE_CORE "Download and include Dwarf Fortress core files in DFHack. Useful for local testing, but should not be used in releases." OFF)
@ -540,3 +551,9 @@ endif()
# Store old build arch
set(DFHACK_BUILD_ARCH_PREV "${DFHACK_BUILD_ARCH}" CACHE STRING "Previous build architecture" FORCE)
option(BUILD_SIZECHECK "Build the sizecheck library, for research" OFF)
if(BUILD_SIZECHECK)
add_subdirectory(depends/sizecheck)
add_dependencies(dfhack sizecheck)
endif()

@ -1,6 +1,6 @@
# DFHack Readme
[![Build Status](https://travis-ci.org/DFHack/dfhack.svg?branch=develop)](https://travis-ci.org/DFHack/dfhack)
[![Build Status](https://github.com/DFHack/dfhack/workflows/Build/badge.svg?event=push)](https://github.com/DFHack/dfhack/actions?query=workflow%3ABuild)
[![Documentation Status](https://readthedocs.org/projects/dfhack/badge)](https://dfhack.readthedocs.org)
[![License](https://img.shields.io/badge/license-ZLib-blue.svg)](https://en.wikipedia.org/wiki/Zlib_License)

@ -174,7 +174,7 @@ def all_keybinds_documented():
plugin_binds = set(re.findall(':dfhack-keybind:`(.*?)`', f.read()))
undocumented_binds = configured_binds - script_commands - plugin_binds
if undocumented_binds:
raise ValueError('The following DFHack commands have undocumented'
raise ValueError('The following DFHack commands have undocumented '
'keybindings: {}'.format(sorted(undocumented_binds)))

@ -1 +1 @@
Subproject commit 6a9153d053a250be34996b3fd86ac1166c3e17cb
Subproject commit 8340c07802078d905e60e294211a1807ec6f0161

@ -146,7 +146,7 @@ list(APPEND LIBPROTOBUF_FULL_SRCS ${LIBPROTOBUF_LITE_SRCS})
if(CMAKE_COMPILER_IS_GNUCC)
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -Wno-sign-compare")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-result -Wno-unused-local-typedefs -Wno-misleading-indentation")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-result -Wno-unused-local-typedefs -Wno-misleading-indentation -Wno-class-memaccess")
elseif(MSVC)
# Disable warnings for integer conversion to smaller type
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4267")

@ -32,6 +32,7 @@
// Based on original Protocol Buffers design by
// Sanjay Ghemawat, Jeff Dean, and others.
#include <fstream>
#include <stack>
#include <google/protobuf/stubs/hash.h>

@ -0,0 +1,6 @@
project(sizecheck)
add_library(sizecheck SHARED sizecheck.cpp)
ide_folder(sizecheck "Depends")
install(TARGETS sizecheck
LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION}
RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION})

@ -0,0 +1,76 @@
// adapted from https://github.com/mifki/df-sizecheck/blob/master/b.cpp
// usage:
// linux: PRELOAD_LIB=hack/libsizecheck.so ./dfhack
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <memory>
using namespace std;
const uint32_t MAGIC = 0xdfdf4ac8;
bool initialized = false;
int perturb = -1;
void init() {
#ifndef _LINUX
if (getenv("MALLOC_PERTURB_")) {
perturb = atoi(getenv("MALLOC_PERTURB_"));
}
#endif
initialized = true;
}
#ifdef _WIN32
static int posix_memalign(void **ptr, size_t alignment, size_t size)
{
if ((*ptr = _aligned_malloc(size, alignment)))
{
return 0;
}
return errno;
}
#endif
void* alloc(size_t n) {
if (!initialized) {
init();
}
void* addr;
if (posix_memalign(&addr, 32, n + 16) != 0) {
return addr;
}
memset(addr, 0, 16);
*(size_t*)addr = n;
*(uint32_t*)((uint8_t*)addr + 8) = MAGIC;
if (perturb > 0) {
memset((uint8_t*)addr + 16, ~(perturb & 0xff), n);
}
return (uint8_t*)addr + 16;
}
void dealloc(void* addr) {
if (!initialized) {
init();
}
if (uintptr_t(addr) % 32 == 16 && *(uint32_t*)((uint8_t*)addr - 8) == MAGIC) {
addr = (void*)((uint8_t*)addr - 16);
memset((uint8_t*)addr + 16, perturb & 0xff, *(size_t*)addr);
}
free(addr);
}
void* operator new (size_t n, const nothrow_t& tag) {
return alloc(n);
}
void* operator new (size_t n) {
return alloc(n);
}
void operator delete (void* addr) {
return dealloc(addr);
}

@ -1,5 +1,5 @@
project(dfhack-tinythread)
add_library(dfhack-tinythread STATIC EXCLUDE_FROM_ALL tinythread.cpp tinythread.h fast_mutex.h)
add_library(dfhack-tinythread STATIC EXCLUDE_FROM_ALL tinythread.cpp tinythread.h)
if(UNIX)
target_link_libraries(dfhack-tinythread pthread)
endif()

@ -1,249 +0,0 @@
/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; -*-
Copyright (c) 2010-2012 Marcus Geelnard
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.
*/
#ifndef _FAST_MUTEX_H_
#define _FAST_MUTEX_H_
/// @file
// Which platform are we on?
#if !defined(_TTHREAD_PLATFORM_DEFINED_)
#if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__)
#define _TTHREAD_WIN32_
#else
#define _TTHREAD_POSIX_
#endif
#define _TTHREAD_PLATFORM_DEFINED_
#endif
// Check if we can support the assembly language level implementation (otherwise
// revert to the system API)
#if (defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))) || \
(defined(_MSC_VER) && (defined(_M_IX86) /*|| defined(_M_X64)*/)) || \
(defined(__GNUC__) && (defined(__ppc__)))
#define _FAST_MUTEX_ASM_
#else
#define _FAST_MUTEX_SYS_
#endif
#if defined(_TTHREAD_WIN32_)
#define NOMINMAX
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#define __UNDEF_LEAN_AND_MEAN
#endif
#include <windows.h>
#ifdef __UNDEF_LEAN_AND_MEAN
#undef WIN32_LEAN_AND_MEAN
#undef __UNDEF_LEAN_AND_MEAN
#endif
#else
#ifdef _FAST_MUTEX_ASM_
#include <sched.h>
#else
#include <pthread.h>
#endif
#endif
namespace tthread {
/// Fast mutex class.
/// This is a mutual exclusion object for synchronizing access to shared
/// memory areas for several threads. It is similar to the tthread::mutex class,
/// but instead of using system level functions, it is implemented as an atomic
/// spin lock with very low CPU overhead.
///
/// The \c fast_mutex class is NOT compatible with the \c condition_variable
/// class (however, it IS compatible with the \c lock_guard class). It should
/// also be noted that the \c fast_mutex class typically does not provide
/// as accurate thread scheduling as a the standard \c mutex class does.
///
/// Because of the limitations of the class, it should only be used in
/// situations where the mutex needs to be locked/unlocked very frequently.
///
/// @note The "fast" version of this class relies on inline assembler language,
/// which is currently only supported for 32/64-bit Intel x86/AMD64 and
/// PowerPC architectures on a limited number of compilers (GNU g++ and MS
/// Visual C++).
/// For other architectures/compilers, system functions are used instead.
class fast_mutex {
public:
/// Constructor.
#if defined(_FAST_MUTEX_ASM_)
fast_mutex() : mLock(0) {}
#else
fast_mutex()
{
#if defined(_TTHREAD_WIN32_)
InitializeCriticalSection(&mHandle);
#elif defined(_TTHREAD_POSIX_)
pthread_mutex_init(&mHandle, NULL);
#endif
}
#endif
#if !defined(_FAST_MUTEX_ASM_)
/// Destructor.
~fast_mutex()
{
#if defined(_TTHREAD_WIN32_)
DeleteCriticalSection(&mHandle);
#elif defined(_TTHREAD_POSIX_)
pthread_mutex_destroy(&mHandle);
#endif
}
#endif
/// Lock the mutex.
/// The method will block the calling thread until a lock on the mutex can
/// be obtained. The mutex remains locked until \c unlock() is called.
/// @see lock_guard
inline void lock()
{
#if defined(_FAST_MUTEX_ASM_)
bool gotLock;
do {
gotLock = try_lock();
if(!gotLock)
{
#if defined(_TTHREAD_WIN32_)
Sleep(0);
#elif defined(_TTHREAD_POSIX_)
sched_yield();
#endif
}
} while(!gotLock);
#else
#if defined(_TTHREAD_WIN32_)
EnterCriticalSection(&mHandle);
#elif defined(_TTHREAD_POSIX_)
pthread_mutex_lock(&mHandle);
#endif
#endif
}
/// Try to lock the mutex.
/// The method will try to lock the mutex. If it fails, the function will
/// return immediately (non-blocking).
/// @return \c true if the lock was acquired, or \c false if the lock could
/// not be acquired.
inline bool try_lock()
{
#if defined(_FAST_MUTEX_ASM_)
int oldLock;
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
asm volatile (
"movl $1,%%eax\n\t"
"xchg %%eax,%0\n\t"
"movl %%eax,%1\n\t"
: "=m" (mLock), "=m" (oldLock)
:
: "%eax", "memory"
);
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
int *ptrLock = &mLock;
__asm {
mov eax,1
mov ecx,ptrLock
xchg eax,[ecx]
mov oldLock,eax
}
#elif defined(__GNUC__) && (defined(__ppc__))
int newLock = 1;
asm volatile (
"\n1:\n\t"
"lwarx %0,0,%1\n\t"
"cmpwi 0,%0,0\n\t"
"bne- 2f\n\t"
"stwcx. %2,0,%1\n\t"
"bne- 1b\n\t"
"isync\n"
"2:\n\t"
: "=&r" (oldLock)
: "r" (&mLock), "r" (newLock)
: "cr0", "memory"
);
#endif
return (oldLock == 0);
#else
#if defined(_TTHREAD_WIN32_)
return TryEnterCriticalSection(&mHandle) ? true : false;
#elif defined(_TTHREAD_POSIX_)
return (pthread_mutex_trylock(&mHandle) == 0) ? true : false;
#endif
#endif
}
/// Unlock the mutex.
/// If any threads are waiting for the lock on this mutex, one of them will
/// be unblocked.
inline void unlock()
{
#if defined(_FAST_MUTEX_ASM_)
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
asm volatile (
"movl $0,%%eax\n\t"
"xchg %%eax,%0\n\t"
: "=m" (mLock)
:
: "%eax", "memory"
);
#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
int *ptrLock = &mLock;
__asm {
mov eax,0
mov ecx,ptrLock
xchg eax,[ecx]
}
#elif defined(__GNUC__) && (defined(__ppc__))
asm volatile (
"sync\n\t" // Replace with lwsync where possible?
: : : "memory"
);
mLock = 0;
#endif
#else
#if defined(_TTHREAD_WIN32_)
LeaveCriticalSection(&mHandle);
#elif defined(_TTHREAD_POSIX_)
pthread_mutex_unlock(&mHandle);
#endif
#endif
}
private:
#if defined(_FAST_MUTEX_ASM_)
int mLock;
#else
#if defined(_TTHREAD_WIN32_)
CRITICAL_SECTION mHandle;
#elif defined(_TTHREAD_POSIX_)
pthread_mutex_t mHandle;
#endif
#endif
};
}
#endif // _FAST_MUTEX_H_

@ -46,7 +46,6 @@ freely, subject to the following restrictions:
/// @li tthread::recursive_mutex
/// @li tthread::condition_variable
/// @li tthread::lock_guard
/// @li tthread::fast_mutex
///
/// @section misc_sec Miscellaneous
/// The following special keywords are available: #thread_local.

@ -245,6 +245,7 @@ enable \
dwarfmonitor \
mousequery \
autogems \
autodump \
automelt \
autotrade \
buildingplan \

@ -34,6 +34,7 @@ Clayton Hughes
Clément Vuchener cvuchener
Dan Amlund danamlund
Daniel Brooks db48x
David Nilsolm
David Corbett dscorbett
David Seguin dseguin
David Timm dtimm
@ -53,6 +54,7 @@ grubsteak grubsteak
Harlan Playford playfordh
Hayati Ayguen hayguen
Herwig Hochleitner bendlas
Ian S kremlin-
IndigoFenix
James Logsdon jlogsdon
Japa JapaMala
@ -61,7 +63,7 @@ Jeremy Apthorp nornagon
Jim Lisi stonetoad
Jimbo Whales jimbowhales
jimcarreer jimcarreer
jj jjyg jj``
jj jjyg jj\`\`
Joel Meador janxious
John Beisley huin
John Shade gsvslto
@ -80,7 +82,9 @@ Mason11987 Mason11987
Matt Regul mattregul
Matthew Cline
Matthew Lindner mlindner
Matthew Taylor ymber yutna
Max maxthyme Max^TM
McArcady McArcady
melkor217 melkor217
Meneth32
Meph
@ -97,6 +101,7 @@ moversti moversti
napagokc napagokc
Neil Little nmlittle
Nick Rart nickrart comestible
Nicolas Ayala nicolasayala
Nikolay Amiantov abbradar
nocico nocico
Omniclasm
@ -106,11 +111,13 @@ Paul Fenwick pjf
PeridexisErrant PeridexisErrant
Petr Mrázek peterix
Pfhreak Pfhreak
Pierre-David Bélanger pierredavidbelanger
potato
Priit Laes plaes
Putnam Putnam3145
Quietust quietust _Q
Raidau Raidau
Ralph Bisschops ralpha
Ramblurr Ramblurr
rampaging-poet
Raoul van Putten
@ -146,8 +153,10 @@ thefriendlyhacker thefriendlyhacker
TheHologram TheHologram
therahedwig therahedwig
ThiagoLira ThiagoLira
thurin thurin
Tim Walberg twalberg
Timothy Collett danaris
Timur Kelman TymurGubayev
Tom Jobbins TheBloke
Tom Prince
TotallyGatsby TotallyGatsby

@ -2298,16 +2298,19 @@ by spaces.
Options:
:``-t``: Select trees only (exclude shrubs)
:``-s``: Select shrubs only (exclude trees)
:``-c``: Clear designations instead of setting them
:``-x``: Apply selected action to all plants except those specified (invert
:``-t``: Tree: Select trees only (exclude shrubs)
:``-s``: Shrub: Select shrubs only (exclude trees)
:``-f``: Farming: Designate only shrubs that yield seeds for farming. Implies -s
:``-c``: Clear: Clear designations instead of setting them
:``-x``: eXcept: Apply selected action to all plants except those specified (invert
selection)
:``-a``: Select every type of plant (obeys ``-t``/``-s``)
:``-v``: Lists the number of (un)designations per plant
:``-a``: All: Select every type of plant (obeys ``-t``/``-s``/``-f``)
:``-v``: Verbose: Lists the number of (un)designations per plant
:``-n *``: Number: Designate up to * (an integer number) plants of each species
Specifying both ``-t`` and ``-s`` will have no effect. If no plant IDs are specified,
all valid plant IDs will be listed.
Specifying both ``-t`` and ``-s`` or ``-f`` will have no effect. If no plant IDs are
specified, all valid plant IDs will be listed, with ``-t``, ``-s``, and ``-f``
restricting the list to trees, shrubs, and farmable shrubs, respectively.
.. note::
@ -2318,6 +2321,12 @@ all valid plant IDs will be listed.
plant gatherer to walk there and do nothing (except clearing the
designation). See :issue:`1479` for details.
The implementation another known deficiency: it's incapable of detecting that
raw definitions that specify a seed extraction reaction for the structural part
but has no other use for it cannot actually yield any seeds, as the part is
never used (parts of :bug:`6940`, e.g. Red Spinach), even though DF
collects it, unless there's a workshop reaction to do it (which there isn't
in vanilla).
.. _infiniteSky:

@ -33,15 +33,135 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
================================================================================
======== IMPORTANT: rename this, and add a new "future" section, BEFORE ========
======== making a new DFHack release! ========
======== making a new DFHack release, even if the only changes made ========
======== were in submodules with their own changelogs! ========
================================================================================
# Future
## Fixes
- `stocks`: fixed display of titles by using the new ``Items::getBookTitle`` operation before the original one
- Fixed a segfault when attempting to start a headless session with a graphical PRINT_MODE setting
- `labormanager`: fixed handling of new jobs in 0.47
## Ruby
- Updated ``item_find`` and ``building_find`` to use centralized logic that works on more screens
# 0.47.04-r1
## Fixes
- Fixed translation of certain types of in-game names
- Fixed a crash in ``find()`` for some types when no world is loaded
- `autogems`: fixed an issue with binned gems being ignored in linked stockpiles
- `stocks`: fixed display of book titles
- `tweak` embark-profile-name: fixed handling of the native shift+space key
## Misc Improvements
- ``dfhack.init-example``: enabled `autodump`
- `getplants`: added switches for designations for farming seeds and for max number designated per plant
- `manipulator`: added intrigue to displayed skills
- `search`: added support for the fortress mode justice screen
## API
- Added ``Items::getBookTitle`` to get titles of "books". Catches titles buried in improvements, unlike getDescription.
- Added ``Items::getBookTitle`` to get titles of books. Catches titles buried in improvements, unlike getDescription.
## Lua
- ``pairs()`` now returns available class methods for DF types
# 0.47.04-beta1
## Fixes
- Fixed a crash when starting DFHack in headless mode with no terminal
## Misc Improvements
- Added "bit" suffix to downloads (e.g. 64-bit)
- Tests:
- moved from DF folder to hack/scripts folder, and disabled installation by default
- made test runner script more flexible
- `dfhack-run`: added color output support
- `embark-assistant`:
- updated embark aquifer info to show all aquifer kinds present
- added neighbor display, including kobolds (SKULKING) and necro tower count
- updated aquifer search criteria to handle the new variation
- added search criteria for embark initial tree cover
- added search criteria for necro tower count, neighbor civ count, and specific neighbors. Should handle additional entities, but not tested
## Internals
- Improved support for tagged unions, allowing tools to access union fields more safely
- Added separate changelogs in the scripts and df-structures repos
- Moved ``reversing`` scripts to df_misc repo
# 0.47.03-beta1
## New Scripts
- `devel/sc`: checks size of structures
- `devel/visualize-structure`: displays the raw memory of a structure
## Fixes
- @ `adv-max-skills`: fixed for 0.47
- `deep-embark`:
- prevented running in non-fortress modes
- ensured that only the newest wagon is deconstructed
- `full-heal`:
- fixed issues with removing corpses
- fixed resurrection for non-historical figures
- @ `modtools/create-unit`: added handling for arena tame setting
- `teleport`: fixed setting new tile occupancy
## Misc Improvements
- `deep-embark`:
- improved support for using directly from the DFHack console
- added a ``-clear`` option to cancel
- `exportlegends`:
- added identity information
- added creature raw names and flags
- `gui/prerelease-warning`: updated links and information about nightly builds
- `modtools/syndrome-trigger`: enabled simultaneous use of ``-synclass`` and ``-syndrome``
- `repeat`: added ``-list`` option
## Structures
- Dropped support for 0.44.12-0.47.02
- ``abstract_building_type``: added types (and subclasses) new to 0.47
- ``agreement_details_type``: added enum
- ``agreement_details``: added struct type (and many associated data types)
- ``agreement_party``: added struct type
- ``announcement_type``: added types new to 0.47
- ``artifact_claim_type``: added enum
- ``artifact_claim``: added struct type
- ``breath_attack_type``: added ``SHARP_ROCK``
- ``building_offering_placest``: new class
- ``building_type``: added ``OfferingPlace``
- ``creature_interaction_effect``: added subclasses new to 0.47
- ``creature_raw_flags``: identified several more items
- ``creature_raw_flags``: renamed many items to match DF names
- ``caste_raw_flags``: renamed many items to match DF names
- ``d_init``: added settings new to 0.47
- ``entity_name_type``: added ``MERCHANT_COMPANY``, ``CRAFT_GUILD``
- ``entity_position_responsibility``: added values new to 0.47
- ``fortress_type``: added enum
- ``general_ref_type``: added ``UNIT_INTERROGATEE``
- ``ghost_type``: added ``None`` value
- ``goal_type``: added goals types new to 0.47
- ``histfig_site_link``: added subclasses new to 0.47
- ``history_event_collection``: added subtypes new to 0.47
- ``history_event_context``: added lots of new fields
- ``history_event_reason``: added captions for all items
- ``history_event_reason``: added items new to 0.47
- ``history_event_type``: added types for events new to 0.47, as well as corresponding ``history_event`` subclasses (too many to list here)
- ``honors_type``: added struct type
- ``interaction_effect``: added subtypes new to 0.47
- ``interaction_source_experimentst``: added class type
- ``interaction_source_usage_hint``: added values new to 0.47
- ``interface_key``: added items for keys new to 0.47
- ``job_skill``: added ``INTRIGUE``, ``RIDING``
- ``lair_type``: added enum
- ``monument_type``: added enum
- ``next_global_id``: added enum
- ``poetic_form_action``: added ``Beseech``
- ``setup_character_info``: expanded significantly in 0.47
- ``text_system``: added layout for struct
- ``tile_occupancy``: added ``varied_heavy_aquifer``
- ``tool_uses``: added items: ``PLACE_OFFERING``, ``DIVINATION``, ``GAMES_OF_CHANCE``
- ``viewscreen_counterintelligencest``: new class (only layout identified so far)
# 0.44.12-r3

@ -4,6 +4,12 @@ import itertools
import os
import sys
CHANGELOG_PATHS = (
'docs/changelog.txt',
'scripts/changelog.txt',
'library/xml/changelog.txt',
)
CHANGELOG_SECTIONS = [
'New Plugins',
'New Scripts',
@ -83,55 +89,61 @@ class ChangelogEntry(object):
return 'ChangelogEntry(%r, %r)' % (self.feature, self.children)
def parse_changelog():
cur_stable = None
cur_dev = None
cur_section = None
last_entry = None
entries = []
with open('docs/changelog.txt') as f:
multiline = ''
for line_id, line in enumerate(f.readlines()):
line_id += 1
if multiline:
multiline += line
elif '[[[' in line:
multiline = line.replace('[[[', '')
if ']]]' in multiline:
line = multiline.replace(']]]', '')
multiline = ''
elif multiline:
continue
for fpath in CHANGELOG_PATHS:
if not os.path.isfile(fpath):
continue
with open(fpath) as f:
cur_stable = None
cur_dev = None
cur_section = None
last_entry = None
multiline = ''
for line_id, line in enumerate(f.readlines()):
line_id += 1
if multiline:
multiline += line
elif '[[[' in line:
multiline = line.replace('[[[', '')
if ']]]' in multiline:
line = multiline.replace(']]]', '')
multiline = ''
elif multiline:
continue
if not line.strip() or line.startswith('==='):
continue
if not line.strip() or line.startswith('==='):
continue
if line.startswith('##'):
cur_section = line.lstrip('#').strip()
elif line.startswith('#'):
cur_dev = line.lstrip('#').strip().lower()
if ('alpha' not in cur_dev and 'beta' not in cur_dev and
'rc' not in cur_dev):
cur_stable = cur_dev
elif line.startswith('-'):
if not cur_stable or not cur_dev or not cur_section:
raise ValueError(
'changelog.txt:%i: Entry without section' % line_id)
last_entry = ChangelogEntry(line.strip(), cur_section,
cur_stable, cur_dev)
entries.append(last_entry)
elif line.lstrip().startswith('-'):
if not cur_stable or not cur_dev:
raise ValueError(
'changelog.txt:%i: Sub-entry without section' % line_id)
if not last_entry:
raise ValueError(
'changelog.txt:%i: Sub-entry without parent' % line_id)
last_entry.children.append(line.strip('- \n'))
else:
raise ValueError('Invalid line: ' + line)
if line.startswith('##'):
cur_section = line.lstrip('#').strip()
elif line.startswith('#'):
cur_dev = line.lstrip('#').strip().lower()
if ('alpha' not in cur_dev and 'beta' not in cur_dev and
'rc' not in cur_dev):
cur_stable = cur_dev
elif line.startswith('-'):
if not cur_stable or not cur_dev or not cur_section:
raise ValueError(
'%s:%i: Entry without section' % (fpath, line_id))
last_entry = ChangelogEntry(line.strip(), cur_section,
cur_stable, cur_dev)
entries.append(last_entry)
elif line.lstrip().startswith('-'):
if not cur_stable or not cur_dev:
raise ValueError(
'%s:%i: Sub-entry without section' % (fpath, line_id))
if not last_entry:
raise ValueError(
'%s:%i: Sub-entry without parent' % (fpath, line_id))
last_entry.children.append(line.strip('- \n'))
else:
raise ValueError('Invalid line: ' + line)
if not entries:
raise RuntimeError('No changelog files with contents found')
return entries
@ -223,6 +235,7 @@ def generate_changelog(all=False):
dev_entries[entry.dev_version][entry.section].append(entry)
consolidate_changelog(stable_entries)
consolidate_changelog(dev_entries)
print_changelog(versions, stable_entries, 'docs/_auto/news.rst')
print_changelog(versions, dev_entries, 'docs/_auto/news-dev.rst')

@ -79,8 +79,14 @@ set(MAIN_SOURCES
RemoteTools.cpp
)
if(WIN32)
set(CONSOLE_SOURCES Console-windows.cpp)
else()
set(CONSOLE_SOURCES Console-posix.cpp)
endif()
set(MAIN_SOURCES_WINDOWS
Console-windows.cpp
${CONSOLE_SOURCES}
Hooks-windows.cpp
PlugLoad-windows.cpp
Process-windows.cpp
@ -92,21 +98,21 @@ if(WIN32)
endif()
set(MAIN_SOURCES_LINUX
Console-posix.cpp
${CONSOLE_SOURCES}
Hooks-linux.cpp
PlugLoad-posix.cpp
Process-linux.cpp
)
set(MAIN_SOURCES_DARWIN
Console-posix.cpp
${CONSOLE_SOURCES}
Hooks-darwin.cpp
PlugLoad-posix.cpp
Process-darwin.cpp
)
set(MAIN_SOURCES_LINUX_EGGY
Console-linux.cpp
${CONSOLE_SOURCES}
Hooks-egg.cpp
PlugLoad-linux.cpp
Process-linux.cpp
@ -359,7 +365,7 @@ add_library(dfhack SHARED ${PROJECT_SOURCES})
add_dependencies(dfhack generate_proto_core)
add_dependencies(dfhack generate_headers)
add_library(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp Error.cpp ${PROJECT_PROTO_SRCS})
add_library(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp Error.cpp ${PROJECT_PROTO_SRCS} ${CONSOLE_SOURCES})
add_dependencies(dfhack-client dfhack)
add_executable(dfhack-run dfhack-run.cpp)

@ -827,21 +827,29 @@ Console::~Console()
delete d;
}
bool Console::init(bool sharing)
bool Console::init(bool dont_redirect)
{
if(sharing)
{
inited = false;
return false;
}
if (!freopen("stdout.log", "w", stdout))
;
d = new Private();
// make our own weird streams so our IO isn't redirected
d->dfout_C = fopen("/dev/tty", "w");
if (dont_redirect)
{
d->dfout_C = fopen("/dev/stdout", "w");
}
else
{
if (!freopen("stdout.log", "w", stdout))
;
d->dfout_C = fopen("/dev/tty", "w");
if (!d->dfout_C)
{
fprintf(stderr, "could not open tty\n");
d->dfout_C = fopen("/dev/stdout", "w");
return false;
}
}
std::cin.tie(this);
clear();
d->supported_terminal = !isUnsupportedTerm() && isatty(STDIN_FILENO);
d->supported_terminal = !isUnsupportedTerm() && isatty(STDIN_FILENO);
// init the exit mechanism
if (pipe(d->exit_pipe) == -1)
;
@ -856,8 +864,11 @@ bool Console::shutdown(void)
{
if(!d)
return true;
d->reset_color();
lock_guard <recursive_mutex> g(*wlock);
close(d->exit_pipe[1]);
if (d->state != Private::con_lineedit)
inited = false;
return true;
}

@ -1579,6 +1579,12 @@ void Core::fatal (std::string output)
#else
cout << "DFHack fatal error: " << out.str() << std::endl;
#endif
bool is_headless = bool(getenv("DFHACK_HEADLESS"));
if (is_headless)
{
exit('f');
}
}
std::string Core::getHackPath()
@ -1686,14 +1692,21 @@ bool Core::Init()
if (is_headless)
{
#ifdef LINUX_BUILD
auto endwin = (int(*)(void))dlsym(RTLD_DEFAULT, "endwin");
if (endwin)
if (is_text_mode)
{
endwin();
auto endwin = (int(*)(void))dlsym(RTLD_DEFAULT, "endwin");
if (endwin)
{
endwin();
}
else
{
cerr << "endwin(): bind failed" << endl;
}
}
else
{
cerr << "endwin(): bind failed" << endl;
cerr << "Headless mode requires PRINT_MODE:TEXT" << endl;
}
#else
cerr << "Headless mode not supported on Windows" << endl;
@ -1701,7 +1714,6 @@ bool Core::Init()
}
if (is_text_mode && !is_headless)
{
con.init(true);
cerr << "Console is not available. Use dfhack-run to send commands.\n";
if (!is_text_mode)
{

@ -224,14 +224,33 @@ std::string df::buffer_container_identity::getFullName(type_identity *item)
(size > 0 ? stl_sprintf("[%d]", size) : std::string("[]"));
}
union_identity::union_identity(size_t size, TAllocateFn alloc,
compound_identity *scope_parent, const char *dfhack_name,
struct_identity *parent, const struct_field_info *fields)
: struct_identity(size, alloc, scope_parent, dfhack_name, parent, fields)
{
}
virtual_identity::virtual_identity(size_t size, TAllocateFn alloc,
const char *dfhack_name, const char *original_name,
virtual_identity *parent, const struct_field_info *fields)
virtual_identity *parent, const struct_field_info *fields,
bool is_plugin)
: struct_identity(size, alloc, NULL, dfhack_name, parent, fields), original_name(original_name),
vtable_ptr(NULL)
vtable_ptr(NULL), is_plugin(is_plugin)
{
// Plugins are initialized after Init was called, so they need to be added to the name table here
if (is_plugin)
{
doInit(&Core::getInstance());
}
}
/* Vtable name to identity lookup. */
static std::map<std::string, virtual_identity*> name_lookup;
/* Vtable pointer to identity lookup. */
std::map<void*, virtual_identity*> virtual_identity::known;
virtual_identity::~virtual_identity()
{
// Remove interpose entries, so that they don't try accessing this object later
@ -239,13 +258,16 @@ virtual_identity::~virtual_identity()
if (it->second)
it->second->on_host_delete(this);
interpose_list.clear();
}
/* Vtable name to identity lookup. */
static std::map<std::string, virtual_identity*> name_lookup;
// Remove global lookup table entries if we're from a plugin
if (is_plugin)
{
name_lookup.erase(getOriginalName());
/* Vtable pointer to identity lookup. */
std::map<void*, virtual_identity*> virtual_identity::known;
if (vtable_ptr)
known.erase(vtable_ptr);
}
}
void virtual_identity::doInit(Core *core)
{
@ -432,3 +454,112 @@ void DFHack::flagarrayToString(std::vector<std::string> *pvec, const void *p,
}
}
}
static const struct_field_info *find_union_tag_candidate(const struct_field_info *fields, const struct_field_info *union_field)
{
if (union_field->extra && union_field->extra->union_tag_field)
{
auto defined_field_name = union_field->extra->union_tag_field;
for (auto field = fields; field->mode != struct_field_info::END; field++)
{
if (!strcmp(field->name, defined_field_name))
{
return field;
}
}
return nullptr;
}
std::string name(union_field->name);
if (name.length() >= 4 && name.substr(name.length() - 4) == "data")
{
name.erase(name.length() - 4, 4);
name += "type";
for (auto field = fields; field->mode != struct_field_info::END; field++)
{
if (field->name == name)
{
return field;
}
}
}
if (name.length() > 7 &&
name.substr(name.length() - 7) == "_target" &&
fields != union_field &&
(union_field - 1)->name == name.substr(0, name.length() - 7))
{
return union_field - 1;
}
return union_field + 1;
}
const struct_field_info *DFHack::find_union_tag(const struct_field_info *fields, const struct_field_info *union_field)
{
CHECK_NULL_POINTER(fields);
CHECK_NULL_POINTER(union_field);
auto tag_candidate = find_union_tag_candidate(fields, union_field);
if (union_field->mode == struct_field_info::SUBSTRUCT &&
union_field->type &&
union_field->type->type() == IDTYPE_UNION)
{
// union field
if (tag_candidate->mode == struct_field_info::PRIMITIVE &&
tag_candidate->type &&
tag_candidate->type->type() == IDTYPE_ENUM)
{
return tag_candidate;
}
return nullptr;
}
if (union_field->mode != struct_field_info::CONTAINER ||
!union_field->type ||
union_field->type->type() != IDTYPE_CONTAINER)
{
// not a union field or a vector; bail
return nullptr;
}
auto container_type = static_cast<container_identity *>(union_field->type);
if (container_type->getFullName(nullptr) != "vector<void>" ||
!container_type->getItemType() ||
container_type->getItemType()->type() != IDTYPE_UNION)
{
// not a vector of unions
return nullptr;
}
if (tag_candidate->mode != struct_field_info::CONTAINER ||
!tag_candidate->type ||
tag_candidate->type->type() != IDTYPE_CONTAINER)
{
// candidate is not a vector
return nullptr;
}
auto tag_container_type = static_cast<container_identity *>(tag_candidate->type);
if (tag_container_type->getFullName(nullptr) == "vector<void>" &&
tag_container_type->getItemType() &&
tag_container_type->getItemType()->type() == IDTYPE_ENUM)
{
return tag_candidate;
}
auto union_fields = ((struct_identity*)union_field->type)->getFields();
if (tag_container_type->getFullName() == "vector<bool>" &&
union_fields[0].mode != struct_field_info::END &&
union_fields[1].mode != struct_field_info::END &&
union_fields[2].mode == struct_field_info::END)
{
return tag_candidate;
}
return nullptr;
}

@ -12,10 +12,11 @@
namespace {
template<class T>
inline T &_toref(T &r) { return r; }
inline T *_toptr(T &r) { return &r; }
template<class T>
inline T &_toref(T *&p) { return *p; }
inline T *_toptr(T *&p) { return p; }
}
#define _fieldptr(ptr, fn) (ptr) ? _toptr((ptr)->fn) : NULL
#define INIT_GLOBAL_FUNCTION_PREFIX \
DFHack::VersionInfo *global_table_ = DFHack::Core::getInstance().vinfo.get(); \

@ -1922,6 +1922,10 @@ static void Lua::Core::InitCoreContext()
void DFHack::Lua::Core::Reset(color_ostream &out, const char *where)
{
// This can happen if DFHack fails to initialize.
if (!State)
return;
int top = lua_gettop(State);
if (top != 0)

@ -508,8 +508,8 @@ static void read_field(lua_State *state, const struct_field_info *field, void *p
return;
case struct_field_info::CONTAINER:
if (!field->eid || !field->type->isContainer() ||
field->eid == ((container_identity*)field->type)->getIndexEnumType())
if (!field->extra || !field->extra->index_enum || !field->type->isContainer() ||
field->extra->index_enum == ((container_identity*)field->type)->getIndexEnumType())
{
field->type->lua_read(state, 2, ptr);
return;
@ -633,6 +633,20 @@ static int meta_struct_field_reference(lua_State *state)
return 1;
}
/**
* Method: _field for df.global.
*/
static int meta_global_field_reference(lua_State *state)
{
if (lua_gettop(state) != 2)
luaL_error(state, "Usage: object._field(name)");
auto field = (struct_field_info*)find_field(state, 2, "reference");
if (!field)
field_error(state, 2, "builtin property or method", "reference");
field_reference(state, field, *(void**)field->offset);
return 1;
}
/**
* Metamethod: __newindex for structures.
*/
@ -1165,6 +1179,7 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, bo
lua_pop(state, 1);
bool add_to_enum = true;
const struct_field_info *tag_field = nullptr;
// Handle the field
switch (fields[i].mode)
@ -1179,10 +1194,15 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, bo
case struct_field_info::POINTER:
// Skip class-typed pointers within unions and other bad pointers
if ((fields[i].count & 2) != 0 && fields[i].type)
if ((pstruct->type() == IDTYPE_UNION || (fields[i].count & 2) != 0) && fields[i].type)
add_to_enum = false;
break;
case struct_field_info::SUBSTRUCT:
case struct_field_info::CONTAINER:
tag_field = find_union_tag(fields, &fields[i]);
break;
default:
break;
}
@ -1194,6 +1214,17 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, bo
if (add_to_enum)
AssociateId(state, base+3, ++cnt, name.c_str());
if (tag_field)
{
// TODO: handle tagged unions
//
// tagged unions are treated as if they have at most one field,
// with the same name as the corresponding enumeration value.
//
// if no field's name matches the enumeration value's name,
// the tagged union is treated as a structure with no fields.
}
lua_pushlightuserdata(state, (void*)&fields[i]);
lua_setfield(state, base+2, name.c_str());
}
@ -1393,7 +1424,10 @@ void struct_identity::build_metatable(lua_State *state)
void global_identity::build_metatable(lua_State *state)
{
int base = lua_gettop(state);
MakeFieldMetatable(state, this, meta_global_index, meta_global_newindex, true);
SetStructMethod(state, base+1, base+2, meta_global_field_reference, "_field");
SetPtrMethods(state, base+1, base+2);
}
/**
@ -1411,7 +1445,7 @@ static void GetAdHocMetatable(lua_State *state, const struct_field_info *field)
case struct_field_info::CONTAINER:
{
auto ctype = (container_identity*)field->type;
MakeContainerMetatable(state, ctype, ctype->getItemType(), -1, field->eid);
MakeContainerMetatable(state, ctype, ctype->getItemType(), -1, field->extra ? field->extra->index_enum : nullptr);
break;
}
@ -1422,12 +1456,12 @@ static void GetAdHocMetatable(lua_State *state, const struct_field_info *field)
case struct_field_info::STATIC_ARRAY:
MakeContainerMetatable(state, &df::buffer_container_identity::base_instance,
field->type, field->count, field->eid);
field->type, field->count, field->extra ? field->extra->index_enum : nullptr);
break;
case struct_field_info::STL_VECTOR_PTR:
MakeContainerMetatable(state, &df::identity_traits<std::vector<void*> >::identity,
field->type, -1, field->eid);
field->type, -1, field->extra ? field->extra->index_enum : nullptr);
break;
default:

@ -296,6 +296,7 @@ bool LuaWrapper::is_type_compatible(lua_State *state, type_identity *type1, int
}
case IDTYPE_STRUCT:
case IDTYPE_UNION:
case IDTYPE_CLASS:
{
auto b1 = (struct_identity*)type1;
@ -1607,8 +1608,8 @@ static void RenderType(lua_State *state, compound_identity *node)
lua_dup(state);
lua_setfield(state, ix_meta, "__index");
// pairs table
lua_newtable(state);
// pairs table - reuse index table
lua_dup(state);
int ptable = base+4;
lua_pushvalue(state, ptable);
@ -1618,6 +1619,7 @@ static void RenderType(lua_State *state, compound_identity *node)
switch (node->type())
{
case IDTYPE_STRUCT:
case IDTYPE_UNION: // TODO: change this to union-type? what relies on this?
lua_pushstring(state, "struct-type");
lua_setfield(state, ftable, "_kind");
IndexStatics(state, ix_meta, ftable, (struct_identity*)node);

@ -240,7 +240,7 @@ void ServerConnection::Accepted(CActiveSocket* socket)
std::thread{[](CActiveSocket* socket) {
try {
ServerConnection(socket).threadFn();
} catch (BlockedException e) {
} catch (BlockedException &) {
}
}, socket}.detach();
}
@ -478,7 +478,7 @@ void ServerMainImpl::threadFn(std::promise<bool> promise, int port)
ServerConnection::Accepted(client);
client = nullptr;
}
} catch(BlockedException e) {
} catch(BlockedException &) {
if (client)
client->Close();
delete client;

@ -127,7 +127,7 @@ bool DFHack::removeRef(std::vector<df::specific_ref*> &vec, df::specific_ref_typ
for (int i = vec.size()-1; i >= 0; i--)
{
df::specific_ref *ref = vec[i];
if (ref->type != type || ref->object != ptr)
if (ref->type != type || ref->data.object != ptr)
continue;
vector_erase_at(vec, i);

@ -45,6 +45,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include <string>
#include <stdint.h>
#include "Console.h"
#include "RemoteClient.h"
#include <cstdio>
@ -55,11 +56,10 @@ POSSIBILITY OF SUCH DAMAGE.
using namespace DFHack;
using namespace dfproto;
using std::cout;
int main (int argc, char *argv[])
{
color_ostream_wrapper out(cout);
Console out;
if (argc <= 1)
{
@ -85,12 +85,15 @@ int main (int argc, char *argv[])
if (!client.connect())
return 2;
out.init(true);
command_result rv;
if (strcmp(argv[1], "--lua") == 0)
{
if (argc <= 3)
{
out.shutdown();
fprintf(stderr, "Usage: dfhack-run --lua <module> <function> [args...]\n");
return 2;
}
@ -99,6 +102,7 @@ int main (int argc, char *argv[])
if (!run_call.bind(&client, "RunLua"))
{
out.shutdown();
fprintf(stderr, "No RunLua protocol function found.");
return 3;
}
@ -130,6 +134,7 @@ int main (int argc, char *argv[])
}
out.flush();
out.shutdown();
if (rv != CR_OK)
return 1;

@ -135,7 +135,7 @@ namespace DFHack
///dtor, NOT thread-safe
~Console();
/// initialize the console. NOT thread-safe
bool init( bool sharing );
bool init( bool dont_redirect );
/// shutdown the console. NOT thread-safe
bool shutdown( void );

@ -64,7 +64,15 @@ namespace DFHack
IDTYPE_CLASS,
IDTYPE_BUFFER,
IDTYPE_STL_PTR_VECTOR,
IDTYPE_OPAQUE
IDTYPE_OPAQUE,
IDTYPE_UNION
};
// pointer flags (bitfield), stored in the count field of struct_field_info
// if mode is POINTER.
enum pointer_identity_flags {
PTRFLAG_IS_ARRAY = 1,
PTRFLAG_HAS_BAD_POINTERS = 2,
};
typedef void *(*TAllocateFn)(void*,const void*);
@ -158,7 +166,12 @@ namespace DFHack
// Bitfields
struct bitfield_item_info {
// the name of the field, or null if the field is unnamed
const char *name;
// size is positive for defined fields, zero for bits past the end
// of the field, and negative for padding on multi-bit fields
//
// ex. if bits[2].size is -2, then bits[0].size is at least 3
int size;
};
@ -246,6 +259,13 @@ namespace DFHack
virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index);
};
struct struct_field_info_extra {
enum_identity *index_enum;
type_identity *ref_target;
const char *union_tag_field;
const char *union_tag_attr;
};
struct struct_field_info {
enum Mode {
END,
@ -264,7 +284,7 @@ namespace DFHack
size_t offset;
type_identity *type;
size_t count;
enum_identity *eid;
const struct_field_info_extra *extra;
};
class DFHACK_EXPORT struct_identity : public compound_identity {
@ -279,8 +299,8 @@ namespace DFHack
public:
struct_identity(size_t size, TAllocateFn alloc,
compound_identity *scope_parent, const char *dfhack_name,
struct_identity *parent, const struct_field_info *fields);
compound_identity *scope_parent, const char *dfhack_name,
struct_identity *parent, const struct_field_info *fields);
virtual identity_type type() { return IDTYPE_STRUCT; }
@ -305,6 +325,15 @@ namespace DFHack
virtual void build_metatable(lua_State *state);
};
class DFHACK_EXPORT union_identity : public struct_identity {
public:
union_identity(size_t size, TAllocateFn alloc,
compound_identity *scope_parent, const char *dfhack_name,
struct_identity *parent, const struct_field_info *fields);
virtual identity_type type() { return IDTYPE_UNION; }
};
#ifdef _MSC_VER
typedef void *virtual_ptr;
#else
@ -321,6 +350,8 @@ namespace DFHack
void *vtable_ptr;
bool is_plugin;
friend class VMethodInterposeLinkBase;
std::map<int,VMethodInterposeLinkBase*> interpose_list;
@ -337,7 +368,8 @@ namespace DFHack
public:
virtual_identity(size_t size, TAllocateFn alloc,
const char *dfhack_name, const char *original_name,
virtual_identity *parent, const struct_field_info *fields);
virtual_identity *parent, const struct_field_info *fields,
bool is_plugin = false);
~virtual_identity();
virtual identity_type type() { return IDTYPE_CLASS; }
@ -432,7 +464,9 @@ namespace df
using DFHack::virtual_class;
using DFHack::global_identity;
using DFHack::struct_identity;
using DFHack::union_identity;
using DFHack::struct_field_info;
using DFHack::struct_field_info_extra;
using DFHack::bitfield_item_info;
using DFHack::bitfield_identity;
using DFHack::enum_identity;
@ -481,7 +515,7 @@ namespace df
template<class T>
enum_field(enum_field<EnumType,T> ev) : value(IntType(ev.value)) {}
operator EnumType () { return EnumType(value); }
operator EnumType () const { return EnumType(value); }
enum_field<EnumType,IntType> &operator=(EnumType ev) {
value = IntType(ev); return *this;
}
@ -777,6 +811,19 @@ namespace DFHack {
flagarray_to_string<T>(&tmp, val);
return join_strings(sep, tmp);
}
/**
* Finds the tag field for a given union field.
*
* The returned tag field is a primitive enum field or nullptr.
*
* If the union field is a container type, the returned tag field is
* a container of primitive enum types.
*
* As a special case, a container-type union can have a tag field that is
* a bit vector if it has exactly two members.
*/
DFHACK_EXPORT const struct_field_info *find_union_tag(const struct_field_info *fields, const struct_field_info *union_field);
}
#define ENUM_ATTR(enum,attr,val) (df::enum_traits<df::enum>::attrs(val).attr)

@ -132,7 +132,7 @@ namespace LuaWrapper {
* Push the pointer as DF object ref using metatable on the stack.
*/
void push_object_ref(lua_State *state, void *ptr);
void *get_object_ref(lua_State *state, int val_index);
DFHACK_EXPORT void *get_object_ref(lua_State *state, int val_index);
/*
* The system might be extended to carry some simple
@ -165,6 +165,7 @@ namespace LuaWrapper {
bool is_type_compatible(lua_State *state, type_identity *type1, int meta1,
type_identity *type2, int meta2, bool exact_equal);
DFHACK_EXPORT
type_identity *get_object_identity(lua_State *state, int objidx,
const char *ctx, bool allow_type = false,
bool keep_metatable = false);

@ -254,7 +254,8 @@ namespace DFHack
};
void ValidateDescriptionOS() {
my_descriptor->ValidateOS();
if (my_descriptor)
my_descriptor->ValidateOS();
};
uintptr_t getBase();

@ -1,9 +1,9 @@
df::enums::dfhack_knowledge_scholar_flag::dfhack_knowledge_scholar_flag value() const
{
int32_t value = category * 32;
int32_t value = int32_t(flag_type) * 32;
for (int32_t i = 0; i < 32; i++)
{
if (flags.whole & (1 << i))
if (flag_data.whole & (1 << i))
{
value += i;
break;

@ -33,6 +33,7 @@ distribution.
#include <stdint.h>
#include "Export.h"
#include "Module.h"
#include <vector>
namespace DFHack
{
@ -71,8 +72,6 @@ namespace DFHack
class DFHACK_EXPORT Graphic : public Module
{
public:
Graphic();
~Graphic();
bool Finish()
{
return true;
@ -82,8 +81,7 @@ namespace DFHack
DFTileSurface* Call(int x, int y);
private:
struct Private;
Private *d;
std::vector<DFTileSurface* (*)(int, int)> funcs;
};
}

@ -285,8 +285,8 @@ do_print_recurse = function(printfn, value, seen, indent)
return recurse_type_map[t](printfn, value, seen, indent)
end
function printall_recurse(value)
local seen = {}
function printall_recurse(value, seen)
local seen = seen or {}
do_print_recurse(dfhack.println, value, seen, 0)
end

@ -736,9 +736,9 @@ end
function FilteredList:setChoices(choices, pos)
choices = choices or {}
self.choices = choices
self.edit.text = ''
self.list:setChoices(choices, pos)
self.choices = self.list.choices
self.not_found.visible = (#choices == 0)
end

@ -1162,7 +1162,7 @@ bool Buildings::deconstruct(df::building *bld)
{
auto item = ui_look_list->items[i];
if (item->type == df::ui_look_list::T_items::Building &&
item->building == bld)
item->data.Building == bld)
{
vector_erase_at(ui_look_list->items, i);
delete item;

@ -47,40 +47,22 @@ std::unique_ptr<Module> DFHack::createGraphic()
return dts::make_unique<Graphic>();
}
struct Graphic::Private
{
bool Started;
vector<DFTileSurface* (*)(int,int)> funcs;
};
Graphic::Graphic()
{
d = new Private;
d->Started = true;
}
Graphic::~Graphic()
{
delete d;
}
bool Graphic::Register(DFTileSurface* (*func)(int,int))
{
d->funcs.push_back(func);
funcs.push_back(func);
return true;
}
bool Graphic::Unregister(DFTileSurface* (*func)(int,int))
{
if ( d->funcs.empty() ) return false;
if ( funcs.empty() ) return false;
vector<DFTileSurface* (*)(int,int)>::iterator it = d->funcs.begin();
while ( it != d->funcs.end() )
vector<DFTileSurface* (*)(int,int)>::iterator it = funcs.begin();
while ( it != funcs.end() )
{
if ( *it == func )
{
d->funcs.erase(it);
funcs.erase(it);
return true;
}
it++;
@ -92,12 +74,12 @@ bool Graphic::Unregister(DFTileSurface* (*func)(int,int))
// This will return first DFTileSurface it can get (or NULL if theres none)
DFTileSurface* Graphic::Call(int x, int y)
{
if ( d->funcs.empty() ) return NULL;
if ( funcs.empty() ) return NULL;
DFTileSurface* temp = NULL;
vector<DFTileSurface* (*)(int,int)>::iterator it = d->funcs.begin();
while ( it != d->funcs.end() )
vector<DFTileSurface* (*)(int,int)>::iterator it = funcs.begin();
while ( it != funcs.end() )
{
temp = (*it)(x,y);
if ( temp != NULL )

@ -591,7 +591,12 @@ std::string Gui::getFocusString(df::viewscreen *top)
if (!top)
return "";
if (virtual_identity *id = virtual_identity::get(top))
if (dfhack_viewscreen::is_instance(top))
{
auto name = static_cast<dfhack_viewscreen*>(top)->getFocusString();
return name.empty() ? "dfhack" : "dfhack/"+name;
}
else if (virtual_identity *id = virtual_identity::get(top))
{
std::string name = getNameChunk(id, 11, 2);
@ -601,11 +606,6 @@ std::string Gui::getFocusString(df::viewscreen *top)
return name;
}
else if (dfhack_viewscreen::is_instance(top))
{
auto name = static_cast<dfhack_viewscreen*>(top)->getFocusString();
return name.empty() ? "dfhack" : "dfhack/"+name;
}
else
{
Core &core = Core::getInstance();
@ -1064,12 +1064,12 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top)
if (auto item = vector_get(ui_look_list->items, *ui_look_cursor))
{
if (item->type == df::ui_look_list::T_items::Unit)
return item->unit;
return item->data.Unit;
else if (item->type == df::ui_look_list::T_items::Item)
{
if (VIRTUAL_CAST_VAR(corpse, df::item_corpsest, item->item))
if (VIRTUAL_CAST_VAR(corpse, df::item_corpsest, item->data.Item))
return df::unit::find(corpse->unit_id); // loo(k) at corpse
else if (VIRTUAL_CAST_VAR(corpsepiece, df::item_corpsepiecest, item->item))
else if (VIRTUAL_CAST_VAR(corpsepiece, df::item_corpsepiecest, item->data.Item))
return df::unit::find(corpsepiece->unit_id); // loo(k) at corpse piece
}
else if (item->type == df::ui_look_list::T_items::Spatter)
@ -1201,7 +1201,7 @@ df::item *Gui::getAnyItem(df::viewscreen *top)
auto item = vector_get(ui_look_list->items, *ui_look_cursor);
if (item && item->type == df::ui_look_list::T_items::Item)
return item->item;
return item->data.Item;
else
return NULL;
}
@ -1266,7 +1266,7 @@ df::building *Gui::getAnyBuilding(df::viewscreen *top)
auto item = vector_get(ui_look_list->items, *ui_look_cursor);
if (item && item->type == df::ui_look_list::T_items::Building)
return item->building;
return item->data.Building;
else
return NULL;
}

@ -664,7 +664,7 @@ df::coord Items::getPosition(df::item *item)
switch (ref->type)
{
case specific_ref_type::VERMIN_ESCAPED_PET:
return ref->vermin->pos;
return ref->data.vermin->pos;
default:
break;

@ -311,7 +311,7 @@ void DFHack::Job::disconnectJobItem(df::job *job, df::job_item_ref *ref) {
auto ref = item->specific_refs[refIndex];
if (ref->type == df::specific_ref_type::JOB) {
if (ref->job == job) {
if (ref->data.job == job) {
vector_erase_at(item->specific_refs, refIndex);
delete ref;
} else {
@ -579,7 +579,7 @@ bool DFHack::Job::attachJobItem(df::job *job, df::item *item,
auto item_link = new df::specific_ref();
item_link->type = specific_ref_type::JOB;
item_link->job = job;
item_link->data.job = job;
item->specific_refs.push_back(item_link);
auto job_link = new df::job_item_ref();

@ -79,6 +79,8 @@ using df::global::world;
extern bool GetLocalFeature(t_feature &feature, df::coord2d rgn_pos, int32_t index);
constexpr unsigned MapExtras::BiomeInfo::MAX_LAYERS;
const BiomeInfo MapCache::biome_stub = {
df::coord2d(),
-1, -1, -1, -1,

@ -81,7 +81,7 @@ struct Persistence::LegacyData
}
json["i"] = std::move(ints);
return std::move(json);
return json;
}
};

@ -180,14 +180,11 @@ string Translation::TranslateName(const df::language_name * name, bool inEnglish
word.append(*world->raws.language.translations[name->language]->words[name->words[1]]);
addNameWord(out, word);
}
if (name->words[5] >= 0)
{
word.clear();
for (int i = 2; i <= 5; i++)
if (name->words[i] >= 0)
word.append(*world->raws.language.translations[name->language]->words[name->words[i]]);
addNameWord(out, word);
}
word.clear();
for (int i = 2; i <= 5; i++)
if (name->words[i] >= 0)
word.append(*world->raws.language.translations[name->language]->words[name->words[i]]);
addNameWord(out, word);
if (name->words[6] >= 0)
{
word.clear();
@ -201,23 +198,22 @@ string Translation::TranslateName(const df::language_name * name, bool inEnglish
{
word.clear();
if (name->words[0] >= 0)
word.append(world->raws.language.words[name->words[0]]->forms[name->parts_of_speech[0].value]);
word.append(world->raws.language.words[name->words[0]]->forms[name->parts_of_speech[0]]);
if (name->words[1] >= 0)
word.append(world->raws.language.words[name->words[1]]->forms[name->parts_of_speech[1].value]);
word.append(world->raws.language.words[name->words[1]]->forms[name->parts_of_speech[1]]);
addNameWord(out, word);
}
if (name->words[5] >= 0)
if (name->words[2] >= 0 || name->words[3] >= 0 || name->words[4] >= 0 || name->words[5] >= 0)
{
if (out.length() > 0)
out.append(" the");
else
out.append("The");
for (int i = 2; i <= 5; i++)
{
if (name->words[i] >= 0)
addNameWord(out, world->raws.language.words[name->words[i]]->forms[name->parts_of_speech[i].value]);
}
}
for (int i = 2; i <= 5; i++)
{
if (name->words[i] >= 0)
addNameWord(out, world->raws.language.words[name->words[i]]->forms[name->parts_of_speech[i]]);
}
if (name->words[6] >= 0)
{
@ -226,7 +222,7 @@ string Translation::TranslateName(const df::language_name * name, bool inEnglish
else
out.append("Of");
addNameWord(out, world->raws.language.words[name->words[6]]->forms[name->parts_of_speech[6].value]);
addNameWord(out, world->raws.language.words[name->words[6]]->forms[name->parts_of_speech[6]]);
}
}

@ -58,6 +58,7 @@ using namespace std;
#include "df/entity_position_assignment.h"
#include "df/entity_raw.h"
#include "df/entity_raw_flags.h"
#include "df/identity_type.h"
#include "df/game_mode.h"
#include "df/histfig_entity_link_positionst.h"
#include "df/historical_entity.h"
@ -200,13 +201,23 @@ void Units::setNickname(df::unit *unit, std::string nick)
if (auto identity = getFigureIdentity(figure))
{
auto id_hfig = df::historical_figure::find(identity->histfig_id);
df::historical_figure *id_hfig = NULL;
switch (identity->type) {
case df::identity_type::HidingCurse:
case df::identity_type::Identity:
case df::identity_type::FalseIdentity:
break; // We want the nickname to end up in the identity
case df::identity_type::Unk_1: // Guess, but that's how it worked in the past
case df::identity_type::TrueName:
case df::identity_type::Unk_4: // Pure guess, as this is a new case, still unseen
id_hfig = df::historical_figure::find(identity->histfig_id);
break;
}
if (id_hfig)
{
// Even DF doesn't do this bit, because it's apparently
// only used for demons masquerading as gods, so you
// can't ever change their nickname in-game.
Translation::setNickname(&id_hfig->name, nick);
}
else
@ -247,7 +258,7 @@ bool Units::isHidingCurse(df::unit *unit)
if (!unit->job.hunt_target)
{
auto identity = Units::getIdentity(unit);
if (identity && identity->unk_4c == 0)
if (identity && identity->type == df::identity_type::HidingCurse)
return true;
}
@ -722,12 +733,11 @@ double Units::getAge(df::unit *unit, bool true_age)
double birth_time = unit->birth_year + unit->birth_time/year_ticks;
double cur_time = *cur_year + *cur_year_tick / year_ticks;
if (!true_age && unit->curse_year >= 0)
{
if (auto identity = getIdentity(unit))
{
if (identity->histfig_id < 0)
birth_time = identity->birth_year + identity->birth_second/year_ticks;
if (!true_age) {
if (auto identity = getIdentity(unit)) {
if (identity->birth_year != -1) {
birth_time = identity->birth_year + identity->birth_second / year_ticks;
}
}
}
@ -1001,7 +1011,7 @@ int Units::computeMovementSpeed(df::unit *unit)
if (in_magma)
speed *= 2;
if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED))
if (craw->flags.is_set(caste_raw_flags::CAN_SWIM))
{
int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING);
@ -1441,7 +1451,7 @@ int8_t Units::getCasteProfessionColor(int race, int casteid, df::profession pid)
{
if (auto caste = vector_get(creature->caste, casteid))
{
if (caste->flags.is_set(caste_raw_flags::CASTE_COLOR))
if (caste->flags.is_set(caste_raw_flags::HAS_COLOR))
return caste->caste_color[0] + caste->caste_color[2] * 8;
}
return creature->color[0] + creature->color[2] * 8;

@ -1 +1 @@
Subproject commit 4053321b202a29f667d64d824ba8339ec1b1df4f
Subproject commit 0686cfdfc18e9f606f2c296b617d5b3ab6b83889

@ -17,6 +17,12 @@ if ! test -f hack/quarantine-removed; then
echo "quarantine flag removed on $(date); remove this file to re-run" > hack/quarantine-removed
fi
if [ ! -t 0 ]; then
stty() {
return
}
fi
old_tty_settings=$(stty -g)
DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib ./dwarfort.exe "$@"
stty "$old_tty_settings"

@ -48,6 +48,12 @@ $HOME/$RC to affect future DFHack installations:
EOF
fi
if [ ! -t 0 ]; then
stty() {
return
}
fi
# Save current terminal settings
old_tty_settings=$(stty -g)
@ -136,6 +142,10 @@ case "$1" in
strace -f setarch "$setarch_arch" -R env LD_PRELOAD="$PRELOAD_LIB" ./libs/Dwarf_Fortress "$@" 2> strace.log
ret=$?
;;
-x | --exec)
exec setarch "$setarch_arch" -R env LD_PRELOAD="$PRELOAD_LIB" ./libs/Dwarf_Fortress "$@"
# script does not resume
;;
*)
setarch "$setarch_arch" -R env LD_PRELOAD="$PRELOAD_LIB" ./libs/Dwarf_Fortress "$@"
ret=$?

@ -167,6 +167,16 @@ void create_jobs() {
stockpiled.insert(item->id);
piled[item->getMaterialIndex()] += 1;
}
else if (item->flags.bits.container) {
std::vector<df::item*> binneditems;
Items::getContainedItems(item, &binneditems);
for (df::item *it : binneditems) {
if (valid_gem(it)) {
stockpiled.insert(it->id);
piled[it->getMaterialIndex()] += 1;
}
}
}
}
// Decrement current jobs from all linked workshops, not just this one.

@ -35,11 +35,13 @@ using namespace std;
#include "df/unit.h"
#include "df/unit_soul.h"
#include "df/unit_syndrome.h"
#include "df/historical_entity.h"
#include "df/historical_figure.h"
#include "df/historical_figure_info.h"
#include "df/identity.h"
#include "df/language_name.h"
#include "df/syndrome.h"
#include "df/world.h"
#include "df/world_raws.h"
#include "df/incident.h"
@ -53,6 +55,26 @@ DFHACK_PLUGIN("cursecheck");
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(cursor);
enum class curses : int8_t {
None,
Unknown,
Ghost,
Zombie,
Necromancer,
Werebeast,
Vampire
};
std::vector<string> curse_names = {
"none",
"unknown",
"ghost",
"zombie",
"necromancer",
"werebeast",
"vampire"
};
command_result cursecheck (color_ostream &out, vector <string> & parameters);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
@ -68,20 +90,25 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK;
}
std::string determineCurse(df::unit * unit)
curses determineCurse(df::unit * unit)
{
string cursetype = "unknown";
curses cursetype = curses::None;
if (unit->curse_year != -1)
{
cursetype = curses::Unknown;
}
// ghosts: ghostly, duh
// as of DF 34.05 and higher vampire ghosts and the like should not be possible
// if they get reintroduced later it will become necessary to watch 'ghostly' seperately
if(unit->flags3.bits.ghostly)
cursetype = "ghost";
cursetype = curses::Ghost;
// zombies: undead or hate life (according to ag), not bloodsuckers
if( (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE || unit->curse.add_tags1.bits.NOT_LIVING)
&& !unit->curse.add_tags1.bits.BLOODSUCKER )
cursetype = "zombie";
cursetype = curses::Zombie;
// necromancers: alive, don't eat, don't drink, don't age
if(!unit->curse.add_tags1.bits.NOT_LIVING
@ -89,18 +116,25 @@ std::string determineCurse(df::unit * unit)
&& unit->curse.add_tags1.bits.NO_DRINK
&& unit->curse.add_tags2.bits.NO_AGING
)
cursetype = "necromancer";
cursetype = curses::Necromancer;
// werecreatures: alive, DO eat, DO drink, don't age
if(!unit->curse.add_tags1.bits.NOT_LIVING
&& !unit->curse.add_tags1.bits.NO_EAT
&& !unit->curse.add_tags1.bits.NO_DRINK
&& unit->curse.add_tags2.bits.NO_AGING )
cursetype = "werebeast";
// werecreatures: subjected to a were syndrome. The curse effects are active only when
// in were form.
for (size_t i = 0; i < unit->syndromes.active.size(); i++)
{
for (size_t k = 0; k < world->raws.syndromes.all[unit->syndromes.active[i]->type]->syn_class.size(); k++)
{
if (strcmp (world->raws.syndromes.all[unit->syndromes.active[i]->type]->syn_class[k]->c_str(), "WERECURSE") == 0)
{
cursetype = curses::Werebeast;
break;
}
}
}
// vampires: bloodsucker (obvious enough)
if(unit->curse.add_tags1.bits.BLOODSUCKER)
cursetype = "vampire";
cursetype = curses::Vampire;
return cursetype;
}
@ -173,21 +207,20 @@ command_result cursecheck (color_ostream &out, vector <string> & parameters)
continue;
}
// non-cursed creatures have curse_year == -1
if(unit->curse_year != -1)
{
cursecount++;
curses cursetype = determineCurse(unit);
string cursetype = determineCurse(unit);
if (cursetype != curses::None)
{
cursecount++;
if(giveNick)
{
Units::setNickname(unit, cursetype); //"CURSED");
Units::setNickname(unit, curse_names[static_cast<size_t>(cursetype)].c_str()); //"CURSED");
}
if(giveDetails)
if (giveDetails)
{
if(unit->name.has_name)
if (unit->name.has_name)
{
string firstname = unit->name.first_name;
string restofname = Translation::TranslateName(&unit->name, false);
@ -195,7 +228,7 @@ command_result cursecheck (color_ostream &out, vector <string> & parameters)
// if creature has no nickname, restofname will already contain firstname
// no need for double output
if(restofname.compare(0, firstname.length(),firstname) != 0)
if (restofname.compare(0, firstname.length(), firstname) != 0)
out.print("%s ", firstname.c_str());
out.print("%s ", restofname.c_str());
}
@ -215,13 +248,13 @@ command_result cursecheck (color_ostream &out, vector <string> & parameters)
out.print("born in %d, cursed in %d to be a %s. (%s%s%s)\n",
unit->birth_year,
unit->curse_year,
cursetype.c_str(),
curse_names [static_cast<size_t>(cursetype)].c_str(),
// technically most cursed creatures are undead,
// therefore output 'active' if they are not completely dead
unit->flags2.bits.killed ? "deceased" : "active",
unit->flags3.bits.ghostly ? "-ghostly" : "",
missing ? "-missing" : ""
);
);
if (missing)
{

@ -160,14 +160,10 @@ public:
}
void Print()
{
char buffer1[256] = {0};
char buffer2[256] = {0};
for( auto v : m_numbers )
{
sprintf( buffer2, "%s%" PRId64, buffer1, v );
sprintf( buffer1, "%s ", buffer2 );
cout->print( "%" PRId64 " ", v );
}
cout->print( "%s", buffer1 );
}
};

@ -537,7 +537,7 @@ command_result FilterManager::saveConfig(DFHack::color_ostream& out) const noexc
if (!ofs.good())
throw std::runtime_error{"Failed to open configuration file for writing"};
ofs << archive;
} catch(std::exception e) {
} catch(std::exception & e) {
ERR(command, out) << "Serializing filters to '" << configPath << "' failed: "
<< e.what() << std::endl;
return CR_FAILURE;
@ -584,7 +584,7 @@ static command_result parseRegexParam(std::regex& target,
try {
std::regex temp{parameters[pos], defaultRegex};
target = std::move(temp);
} catch(std::regex_error e) {
} catch(std::regex_error & e) {
ERR(command,out) << "Failed to parse regular expression '"
<< parameters[pos] << "'\n";
ERR(command,out) << "Parser message: " << e.what() << std::endl;

@ -26,3 +26,5 @@ dfhack_plugin(zoom zoom.cpp)
if(UNIX)
dfhack_plugin(ref-index ref-index.cpp)
endif()
add_subdirectory(check-structures-sanity)

@ -0,0 +1,8 @@
set(PLUGIN_SRCS
dispatch.cpp
main.cpp
types.cpp
validate.cpp
)
dfhack_plugin(check-structures-sanity ${PLUGIN_SRCS} LINK_LIBRARIES lua)

@ -0,0 +1,160 @@
#pragma once
#include "Console.h"
#include "PluginManager.h"
#include "MemAccess.h"
#include "DataDefs.h"
#include "DataIdentity.h"
#include <deque>
#include <map>
#include <string>
using namespace DFHack;
#ifdef WIN32
#define UNEXPECTED __debugbreak()
#else
#define UNEXPECTED __asm__ volatile ("int $0x03; nop")
#endif
#define PTR_ADD(ptr, offset) reinterpret_cast<const void *>(uintptr_t(ptr) + (offset))
struct QueueItem
{
QueueItem(const std::string &, const void *);
QueueItem(const QueueItem &, const std::string &, const void *);
QueueItem(const QueueItem &, size_t, const void *);
std::string path;
const void *ptr;
};
struct CheckedStructure
{
type_identity *identity;
size_t count;
size_t allocated_count;
enum_identity *eid;
bool ptr_is_array;
bool inside_structure;
CheckedStructure();
explicit CheckedStructure(type_identity *, size_t = 0);
CheckedStructure(type_identity *, size_t, enum_identity *, bool);
CheckedStructure(const struct_field_info *);
size_t full_size() const;
bool has_type_at_offset(const CheckedStructure &, size_t) const;
};
#define MIN_SIZE_FOR_SUGGEST 64
extern std::map<size_t, std::vector<std::string>> known_types_by_size;
void build_size_table();
namespace
{
template<typename T, bool is_pointer = std::is_pointer<T>::value>
struct safe_t
{
typedef T type;
};
template<typename T>
struct safe_t<T, true>
{
typedef void *type;
};
}
class Checker
{
color_ostream & out;
std::vector<t_memrange> mapped;
std::map<const void *, std::pair<std::string, CheckedStructure>> data;
std::deque<QueueItem> queue;
public:
size_t checked_count;
size_t error_count;
size_t maxerrors;
bool maxerrors_reported;
bool enums;
bool sizes;
bool unnamed;
bool failfast;
bool noprogress;
bool maybepointer;
Checker(color_ostream & out);
bool queue_item(const QueueItem & item, CheckedStructure cs);
void queue_globals();
bool process_queue();
bool is_in_global(const QueueItem & item);
bool is_valid_dereference(const QueueItem & item, const CheckedStructure & cs, size_t size, bool quiet);
inline bool is_valid_dereference(const QueueItem & item, const CheckedStructure & cs, bool quiet = false)
{
return is_valid_dereference(item, cs, cs.full_size(), quiet);
}
inline bool is_valid_dereference(const QueueItem & item, size_t size, bool quiet = false)
{
return is_valid_dereference(item, CheckedStructure(df::identity_traits<void *>::get()), size, quiet);
}
template<typename T>
const T validate_and_dereference(const QueueItem & item, bool quiet = false)
{
CheckedStructure cs;
cs.identity = df::identity_traits<typename safe_t<T>::type>::get();
if (!is_valid_dereference(item, cs, quiet))
return T();
return *reinterpret_cast<const T *>(item.ptr);
}
int64_t get_int_value(const QueueItem & item, type_identity *type, bool quiet = false);
const char *get_vtable_name(const QueueItem & item, const CheckedStructure & cs, bool quiet = false);
std::pair<const void *, CheckedStructure> validate_vector_size(const QueueItem & item, const CheckedStructure & cs, bool quiet = false);
size_t get_allocated_size(const QueueItem & item);
#ifndef WIN32
// this function doesn't make sense on windows, where std::string is not pointer-sized.
const std::string *validate_stl_string_pointer(const void *const*);
#endif
static const char *const *get_enum_item_key(enum_identity *identity, int64_t value);
static const char *const *get_enum_item_attr_or_key(enum_identity *identity, int64_t value, const char *attr_name);
private:
color_ostream & fail(int, const QueueItem &, const CheckedStructure &);
void dispatch_item(const QueueItem &, const CheckedStructure &);
void dispatch_single_item(const QueueItem &, const CheckedStructure &);
void dispatch_primitive(const QueueItem &, const CheckedStructure &);
void dispatch_pointer(const QueueItem &, const CheckedStructure &);
void dispatch_container(const QueueItem &, const CheckedStructure &);
void dispatch_ptr_container(const QueueItem &, const CheckedStructure &);
void dispatch_bit_container(const QueueItem &, const CheckedStructure &);
void dispatch_bitfield(const QueueItem &, const CheckedStructure &);
void dispatch_enum(const QueueItem &, const CheckedStructure &);
void dispatch_struct(const QueueItem &, const CheckedStructure &);
void dispatch_field(const QueueItem &, const CheckedStructure &, const struct_field_info *, const struct_field_info *);
void dispatch_class(const QueueItem &, const CheckedStructure &);
void dispatch_buffer(const QueueItem &, const CheckedStructure &);
void dispatch_stl_ptr_vector(const QueueItem &, const CheckedStructure &);
void dispatch_tagged_union(const QueueItem &, const QueueItem &, const CheckedStructure &, const CheckedStructure &, const char *);
void dispatch_tagged_union_vector(const QueueItem &, const QueueItem &, const CheckedStructure &, const CheckedStructure &, const char *);
void dispatch_untagged_union(const QueueItem &, const CheckedStructure &);
void check_unknown_pointer(const QueueItem &);
void check_stl_vector(const QueueItem &, type_identity *, type_identity *);
void check_stl_string(const QueueItem &);
void check_possible_pointer(const QueueItem &, const CheckedStructure &);
friend struct CheckedStructure;
static type_identity *wrap_in_pointer(type_identity *);
static type_identity *wrap_in_stl_ptr_vector(type_identity *);
};
#define FAIL(message) \
do \
{ \
auto & failstream = fail(__LINE__, item, cs); \
failstream << message; \
failstream << COLOR_RESET << std::endl; \
if (failfast) \
UNEXPECTED; \
} \
while (false)

@ -0,0 +1,922 @@
#include "check-structures-sanity.h"
#include "df/large_integer.h"
Checker::Checker(color_ostream & out) :
out(out),
checked_count(0),
error_count(0),
maxerrors(~size_t(0)),
maxerrors_reported(false),
enums(false),
sizes(false),
unnamed(false),
failfast(false),
noprogress(!out.is_console()),
maybepointer(false)
{
Core::getInstance().p->getMemRanges(mapped);
}
color_ostream & Checker::fail(int line, const QueueItem & item, const CheckedStructure & cs)
{
error_count++;
out << COLOR_LIGHTRED << "sanity check failed (line " << line << "): ";
out << COLOR_RESET << (cs.identity ? cs.identity->getFullName() : "?");
out << " (accessed as " << item.path << "): ";
out << COLOR_YELLOW;
if (maxerrors && maxerrors != ~size_t(0))
maxerrors--;
return out;
}
bool Checker::queue_item(const QueueItem & item, CheckedStructure cs)
{
if (!cs.identity)
{
UNEXPECTED;
return false;
}
if (cs.identity->type() == IDTYPE_CLASS)
{
if (cs.count)
{
UNEXPECTED;
}
if (get_vtable_name(item, cs, true))
{
auto actual_identity = virtual_identity::get(reinterpret_cast<virtual_ptr>(const_cast<void *>(item.ptr)));
if (static_cast<virtual_identity *>(cs.identity)->is_subclass(actual_identity))
{
cs.identity = actual_identity;
}
}
}
auto ptr_end = PTR_ADD(item.ptr, cs.full_size());
auto prev = data.lower_bound(item.ptr);
if (prev != data.cbegin() && uintptr_t(prev->first) > uintptr_t(item.ptr))
{
prev--;
}
if (prev != data.cend() && uintptr_t(prev->first) <= uintptr_t(item.ptr) && uintptr_t(prev->first) + prev->second.second.full_size() > uintptr_t(item.ptr))
{
auto offset = uintptr_t(item.ptr) - uintptr_t(prev->first);
if (!prev->second.second.has_type_at_offset(cs, offset))
{
if (offset == 0 && cs.identity == df::identity_traits<void *>::get())
{
FAIL("unknown pointer is " << prev->second.second.identity->getFullName() << ", previously seen at " << prev->second.first);
return false;
}
// TODO
FAIL("TODO: handle merging structures: " << item.path << " overlaps " << prev->second.first << " (backward)");
return false;
}
// we've already checked this structure, or we're currently queued to do so
return false;
}
auto overlap_start = data.lower_bound(item.ptr);
auto overlap_end = data.lower_bound(ptr_end);
for (auto overlap = overlap_start; overlap != overlap_end; overlap++)
{
auto offset = uintptr_t(overlap->first) - uintptr_t(item.ptr);
if (!cs.has_type_at_offset(overlap->second.second, offset))
{
// TODO
FAIL("TODO: handle merging structures: " << overlap->second.first << " overlaps " << item.path << " (forward)");
return false;
}
}
data.erase(overlap_start, overlap_end);
data[item.ptr] = std::make_pair(item.path, cs);
queue.push_back(item);
return true;
}
void Checker::queue_globals()
{
auto fields = df::global::_identity.getFields();
for (auto field = fields; field->mode != struct_field_info::END; field++)
{
if (!field->offset)
{
UNEXPECTED;
continue;
}
// offset is the position of the DFHack pointer to this global.
auto ptr = *reinterpret_cast<const void **>(field->offset);
QueueItem item(stl_sprintf("df.global.%s", field->name), ptr);
CheckedStructure cs(field);
if (!ptr)
{
FAIL("unknown global address");
continue;
}
if (!strcmp(field->name, "enabler"))
{
// don't care about libgraphics as we have the source code
continue;
}
queue_item(item, cs);
}
}
bool Checker::process_queue()
{
if (queue.empty())
{
return false;
}
auto item = std::move(queue.front());
queue.pop_front();
auto cs = data.find(item.ptr);
if (cs == data.end())
{
// happens if pointer is determined to be part of a larger structure
return true;
}
dispatch_item(item, cs->second.second);
return true;
}
void Checker::dispatch_item(const QueueItem & base, const CheckedStructure & cs)
{
if (!is_valid_dereference(base, cs))
{
return;
}
if (!cs.count)
{
dispatch_single_item(base, cs);
return;
}
if (sizes && !cs.inside_structure)
{
if (auto allocated_size = get_allocated_size(base))
{
auto expected_size = cs.identity->byte_size();
if (cs.allocated_count)
expected_size *= cs.allocated_count;
else if (cs.count)
expected_size *= cs.count;
if (cs.identity->type() == IDTYPE_CLASS && get_vtable_name(base, cs, true))
{
if (cs.count)
{
UNEXPECTED;
}
auto virtual_type = virtual_identity::get(static_cast<virtual_ptr>(const_cast<void *>(base.ptr)));
expected_size = virtual_type->byte_size();
}
auto & item = base;
if (allocated_size > expected_size)
{
FAIL("identified structure is too small (expected " << expected_size << " bytes, but there are " << allocated_size << " bytes allocated)");
}
else if (allocated_size < expected_size)
{
FAIL("identified structure is too big (expected " << expected_size << " bytes, but there are " << allocated_size << " bytes allocated)");
}
}
else
{
UNEXPECTED;
}
}
auto ptr = base.ptr;
auto size = cs.identity->byte_size();
for (size_t i = 0; i < cs.count; i++)
{
QueueItem item(base, i, ptr);
dispatch_single_item(item, cs);
ptr = PTR_ADD(ptr, size);
}
}
void Checker::dispatch_single_item(const QueueItem & item, const CheckedStructure & cs)
{
checked_count++;
if (!maxerrors)
{
if (!maxerrors_reported)
{
FAIL("error limit reached. bailing out with " << (queue.size() + 1) << " items remaining in the queue.");
maxerrors_reported = true;
}
queue.clear();
return;
}
switch (cs.identity->type())
{
case IDTYPE_GLOBAL:
case IDTYPE_FUNCTION:
UNEXPECTED;
break;
case IDTYPE_PRIMITIVE:
dispatch_primitive(item, cs);
break;
case IDTYPE_POINTER:
dispatch_pointer(item, cs);
break;
case IDTYPE_CONTAINER:
dispatch_container(item, cs);
break;
case IDTYPE_PTR_CONTAINER:
dispatch_ptr_container(item, cs);
break;
case IDTYPE_BIT_CONTAINER:
dispatch_bit_container(item, cs);
break;
case IDTYPE_BITFIELD:
dispatch_bitfield(item, cs);
break;
case IDTYPE_ENUM:
dispatch_enum(item, cs);
break;
case IDTYPE_STRUCT:
dispatch_struct(item, cs);
break;
case IDTYPE_CLASS:
dispatch_class(item, cs);
break;
case IDTYPE_BUFFER:
dispatch_buffer(item, cs);
break;
case IDTYPE_STL_PTR_VECTOR:
dispatch_stl_ptr_vector(item, cs);
break;
case IDTYPE_OPAQUE:
break;
case IDTYPE_UNION:
dispatch_untagged_union(item, cs);
break;
}
}
void Checker::dispatch_primitive(const QueueItem & item, const CheckedStructure & cs)
{
if (cs.identity == df::identity_traits<std::string>::get())
{
check_stl_string(item);
}
else if (cs.identity == df::identity_traits<char *>::get())
{
// TODO check c strings
UNEXPECTED;
}
else if (cs.identity == df::identity_traits<bool>::get())
{
auto val = *reinterpret_cast<const uint8_t *>(item.ptr);
if (val > 1 && val != 0xd2)
{
FAIL("invalid value for bool: " << int(val));
}
}
else if (auto int_id = dynamic_cast<df::integer_identity_base *>(cs.identity))
{
check_possible_pointer(item, cs);
// TODO check ints?
}
else if (auto float_id = dynamic_cast<df::float_identity_base *>(cs.identity))
{
// TODO check floats?
}
else
{
UNEXPECTED;
}
}
void Checker::dispatch_pointer(const QueueItem & item, const CheckedStructure & cs)
{
auto target_ptr = validate_and_dereference<const void *>(item);
if (!target_ptr)
{
return;
}
#ifdef DFHACK64
if (uintptr_t(target_ptr) == 0xd2d2d2d2d2d2d2d2)
#else
if (uintptr_t(target_ptr) == 0xd2d2d2d2)
#endif
{
return;
}
QueueItem target_item(item.path, target_ptr);
auto target = static_cast<pointer_identity *>(cs.identity)->getTarget();
if (!target)
{
check_unknown_pointer(target_item);
return;
}
CheckedStructure target_cs(target);
// 256 is an arbitrarily chosen size threshold
if (cs.count || target->byte_size() <= 256)
{
// target is small, or we are inside an array of pointers; handle now
if (queue_item(target_item, target_cs))
{
// we insert it into the queue to make sure we're not stuck in a loop
// get it back out of the queue to prevent the queue growing too big
queue.pop_back();
dispatch_item(target_item, target_cs);
}
}
else
{
// target is large and not part of an array; handle later
queue_item(target_item, target_cs);
}
}
void Checker::dispatch_container(const QueueItem & item, const CheckedStructure & cs)
{
auto identity = static_cast<container_identity *>(cs.identity);
auto base_container = identity->getFullName(nullptr);
if (base_container == "vector<void>")
{
check_stl_vector(item, identity->getItemType(), identity->getIndexEnumType());
}
else if (base_container == "deque<void>")
{
// TODO: check deque?
}
else if (base_container == "DfArray<void>")
{
// TODO: check DfArray
}
else
{
UNEXPECTED;
}
}
void Checker::dispatch_ptr_container(const QueueItem & item, const CheckedStructure & cs)
{
auto identity = static_cast<container_identity *>(cs.identity);
auto base_container = identity->getFullName(nullptr);
{
UNEXPECTED;
}
}
void Checker::dispatch_bit_container(const QueueItem & item, const CheckedStructure & cs)
{
auto identity = static_cast<container_identity *>(cs.identity);
auto base_container = identity->getFullName(nullptr);
if (base_container == "BitArray<>")
{
// TODO: check DF bit array
}
else if (base_container == "vector<bool>")
{
// TODO: check stl bit vector
}
else
{
UNEXPECTED;
}
}
void Checker::dispatch_bitfield(const QueueItem & item, const CheckedStructure & cs)
{
check_possible_pointer(item, cs);
if (!enums)
{
return;
}
auto bitfield_type = static_cast<bitfield_identity *>(cs.identity);
uint64_t bitfield_value;
switch (bitfield_type->byte_size())
{
case 1:
bitfield_value = validate_and_dereference<uint8_t>(item);
// don't check for uninitialized; too small to be sure
break;
case 2:
bitfield_value = validate_and_dereference<uint16_t>(item);
if (bitfield_value == 0xd2d2)
{
bitfield_value = 0;
}
break;
case 4:
bitfield_value = validate_and_dereference<uint32_t>(item);
if (bitfield_value == 0xd2d2d2d2)
{
bitfield_value = 0;
}
break;
case 8:
bitfield_value = validate_and_dereference<uint64_t>(item);
if (bitfield_value == 0xd2d2d2d2d2d2d2d2)
{
bitfield_value = 0;
}
break;
default:
UNEXPECTED;
bitfield_value = 0;
break;
}
auto num_bits = bitfield_type->getNumBits();
auto bits = bitfield_type->getBits();
for (int i = 0; i < 64; i++)
{
if (!(num_bits & 1))
{
bitfield_value >>= 1;
continue;
}
bitfield_value >>= 1;
if (i >= num_bits || !bits[i].size)
{
FAIL("bitfield bit " << i << " is out of range");
}
else if (unnamed && bits[i].size > 0 && !bits[i].name)
{
FAIL("bitfield bit " << i << " is unnamed");
}
else if (unnamed && !bits[i + bits[i].size].name)
{
FAIL("bitfield bit " << i << " (part of a field starting at bit " << (i + bits[i].size) << ") is unnamed");
}
}
}
void Checker::dispatch_enum(const QueueItem & item, const CheckedStructure & cs)
{
check_possible_pointer(item, cs);
if (!enums)
{
return;
}
auto enum_type = static_cast<enum_identity *>(cs.identity);
auto enum_value = get_int_value(item, enum_type->getBaseType());
if (enum_type->byte_size() == 2 && uint16_t(enum_value) == 0xd2d2)
{
return;
}
else if (enum_type->byte_size() == 4 && uint32_t(enum_value) == 0xd2d2d2d2)
{
return;
}
else if (enum_type->byte_size() == 8 && uint64_t(enum_value) == 0xd2d2d2d2d2d2d2d2)
{
return;
}
if (is_in_global(item) && enum_value == 0)
{
return;
}
auto enum_name = get_enum_item_key(enum_type, enum_value);
if (!enum_name)
{
FAIL("enum value (" << enum_value << ") is out of range");
return;
}
if (unnamed && !*enum_name)
{
FAIL("enum value (" << enum_value << ") is unnamed");
}
}
void Checker::dispatch_struct(const QueueItem & item, const CheckedStructure & cs)
{
auto identity = static_cast<struct_identity *>(cs.identity);
for (auto p = identity; p; p = p->getParent())
{
auto fields = p->getFields();
if (!fields)
{
continue;
}
for (auto field = fields; field->mode != struct_field_info::END; field++)
{
dispatch_field(item, cs, fields, field);
}
}
}
void Checker::dispatch_field(const QueueItem & item, const CheckedStructure & cs, const struct_field_info *fields, const struct_field_info *field)
{
if (field->mode == struct_field_info::OBJ_METHOD ||
field->mode == struct_field_info::CLASS_METHOD)
{
return;
}
auto field_ptr = PTR_ADD(item.ptr, field->offset);
QueueItem field_item(item, field->name, field_ptr);
CheckedStructure field_cs(field);
auto tag_field = find_union_tag(fields, field);
if (tag_field)
{
auto tag_ptr = PTR_ADD(item.ptr, tag_field->offset);
QueueItem tag_item(item, tag_field->name, tag_ptr);
CheckedStructure tag_cs(tag_field);
auto attr_name = field->extra ? field->extra->union_tag_attr : nullptr;
if (tag_cs.identity->isContainer())
{
dispatch_tagged_union_vector(field_item, tag_item, field_cs, tag_cs, attr_name);
}
else
{
dispatch_tagged_union(field_item, tag_item, field_cs, tag_cs, attr_name);
}
return;
}
dispatch_item(field_item, field_cs);
}
void Checker::dispatch_class(const QueueItem & item, const CheckedStructure & cs)
{
auto vtable_name = get_vtable_name(item, cs);
if (!vtable_name)
{
// bail out now because virtual_identity::get will crash
return;
}
auto base_identity = static_cast<virtual_identity *>(cs.identity);
auto vptr = static_cast<virtual_ptr>(const_cast<void *>(item.ptr));
auto identity = virtual_identity::get(vptr);
if (!identity)
{
FAIL("unidentified subclass of " << base_identity->getFullName() << ": " << vtable_name);
return;
}
if (base_identity != identity && !base_identity->is_subclass(identity))
{
FAIL("expected subclass of " << base_identity->getFullName() << ", but got " << identity->getFullName());
return;
}
if (data.count(item.ptr) && data.at(item.ptr).first == item.path)
{
// TODO: handle cases where this may overlap later data
data.at(item.ptr).second.identity = identity;
}
dispatch_struct(QueueItem(item.path + "<" + identity->getFullName() + ">", item.ptr), CheckedStructure(identity));
}
void Checker::dispatch_buffer(const QueueItem & item, const CheckedStructure & cs)
{
auto identity = static_cast<container_identity *>(cs.identity);
auto item_identity = identity->getItemType();
dispatch_item(item, CheckedStructure(item_identity, identity->byte_size() / item_identity->byte_size(), static_cast<enum_identity *>(identity->getIndexEnumType()), true));
}
void Checker::dispatch_stl_ptr_vector(const QueueItem & item, const CheckedStructure & cs)
{
auto identity = static_cast<container_identity *>(cs.identity);
auto ptr_type = wrap_in_pointer(identity->getItemType());
check_stl_vector(item, ptr_type, identity->getIndexEnumType());
}
void Checker::dispatch_tagged_union(const QueueItem & item, const QueueItem & tag_item, const CheckedStructure & cs, const CheckedStructure & tag_cs, const char *attr_name)
{
if (tag_cs.identity->type() != IDTYPE_ENUM || cs.identity->type() != IDTYPE_UNION)
{
UNEXPECTED;
return;
}
auto union_type = static_cast<union_identity *>(cs.identity);
auto union_data_ptr = reinterpret_cast<const uint8_t *>(item.ptr);
uint8_t padding_byte = *union_data_ptr;
bool all_padding = false;
if (padding_byte == 0x00 || padding_byte == 0xd2 || padding_byte == 0xff)
{
all_padding = true;
for (size_t i = 0; i < union_type->byte_size(); i++)
{
if (union_data_ptr[i] != padding_byte)
{
all_padding = false;
break;
}
}
}
auto tag_identity = static_cast<enum_identity *>(tag_cs.identity);
auto tag_value = get_int_value(tag_item, tag_identity->getBaseType());
if (all_padding && padding_byte == 0xd2)
{
// special case: completely uninitialized
switch (tag_identity->byte_size())
{
case 1:
if (tag_value == 0xd2)
{
return;
}
break;
case 2:
if (tag_value == 0xd2d2)
{
return;
}
break;
case 4:
if (tag_value == 0xd2d2d2d2)
{
return;
}
break;
case 8:
if (uint64_t(tag_value) == 0xd2d2d2d2d2d2d2d2)
{
return;
}
break;
default:
UNEXPECTED;
break;
}
}
auto tag_name = get_enum_item_attr_or_key(tag_identity, tag_value, attr_name);
if (!tag_name)
{
FAIL("tagged union tag (accessed as " << tag_item.path << ") value (" << tag_value << ") not defined in enum " << tag_cs.identity->getFullName());
return;
}
if (!*tag_name)
{
FAIL("tagged union tag (accessed as " << tag_item.path << ") value (" << tag_value << ") is unnamed");
return;
}
for (auto field = union_type->getFields(); field->mode != struct_field_info::END; field++)
{
if (strcmp(field->name, *tag_name))
{
continue;
}
if (field->offset != 0)
{
UNEXPECTED;
}
dispatch_item(QueueItem(item, field->name, item.ptr), field);
return;
}
// don't ask for fields if it's all padding
if (all_padding)
{
return;
}
FAIL("tagged union missing " << *tag_name << " field to match tag (accessed as " << tag_item.path << ") value (" << tag_value << ")");
}
void Checker::dispatch_tagged_union_vector(const QueueItem & item, const QueueItem & tag_item, const CheckedStructure & cs, const CheckedStructure & tag_cs, const char *attr_name)
{
auto union_container_identity = static_cast<container_identity *>(cs.identity);
CheckedStructure union_item_cs(union_container_identity->getItemType());
if (union_container_identity->type() != IDTYPE_CONTAINER)
{
// assume pointer container
union_item_cs.identity = wrap_in_pointer(union_item_cs.identity);
}
auto tag_container_identity = static_cast<container_identity *>(tag_cs.identity);
auto tag_container_base = tag_container_identity->getFullName(nullptr);
if (tag_container_base == "vector<void>")
{
auto vec_union = validate_vector_size(item, union_item_cs);
CheckedStructure tag_item_cs(tag_container_identity->getItemType());
auto vec_tag = validate_vector_size(tag_item, tag_item_cs);
if (!vec_union.first || !vec_tag.first)
{
// invalid vectors (already warned)
return;
}
if (!vec_union.second.count && !vec_tag.second.count)
{
// empty vectors
return;
}
if (vec_union.second.count != vec_tag.second.count)
{
FAIL("tagged union vector is " << vec_union.second.count << " elements, but tag vector (accessed as " << tag_item.path << ") is " << vec_tag.second.count << " elements");
}
for (size_t i = 0; i < vec_union.second.count && i < vec_tag.second.count; i++)
{
dispatch_tagged_union(QueueItem(item, i, vec_union.first), QueueItem(tag_item, i, vec_tag.first), union_item_cs, tag_item_cs, attr_name);
vec_union.first = PTR_ADD(vec_union.first, union_item_cs.identity->byte_size());
vec_tag.first = PTR_ADD(vec_tag.first, tag_item_cs.identity->byte_size());
}
}
else if (tag_container_base == "vector<bool>")
{
// TODO
UNEXPECTED;
}
else
{
UNEXPECTED;
}
}
void Checker::dispatch_untagged_union(const QueueItem & item, const CheckedStructure & cs)
{
// special case for large_integer weirdness
if (cs.identity == df::identity_traits<df::large_integer>::get())
{
// it's 16 bytes on 64-bit linux due to a messy header in libgraphics
// but only the first 8 bytes are ever used
dispatch_primitive(item, CheckedStructure(df::identity_traits<int64_t>::get(), 0, nullptr, cs.inside_structure));
return;
}
UNEXPECTED;
}
void Checker::check_unknown_pointer(const QueueItem & item)
{
const static CheckedStructure cs(nullptr, 0, nullptr, true);
if (auto allocated_size = get_allocated_size(item))
{
FAIL("pointer to a block of " << allocated_size << " bytes of allocated memory");
if (allocated_size >= MIN_SIZE_FOR_SUGGEST && known_types_by_size.count(allocated_size))
{
FAIL("known types of this size: " << join_strings(", ", known_types_by_size.at(allocated_size)));
}
// check recursively if it's the right size for a pointer
// or if it starts with what might be a valid pointer
QueueItem ptr_item(item, "?ptr?", item.ptr);
if (allocated_size == sizeof(void *) || (allocated_size > sizeof(void *) && is_valid_dereference(ptr_item, 1, true)))
{
CheckedStructure ptr_cs(df::identity_traits<void *>::get());
if (queue_item(ptr_item, ptr_cs))
{
queue.pop_back();
dispatch_pointer(ptr_item, ptr_cs);
}
}
}
#ifndef WIN32
else if (auto str = validate_stl_string_pointer(&item.ptr))
{
FAIL("untyped pointer is actually stl-string with value \"" << *str << "\" (length " << str->length() << ")");
}
#endif
else if (auto vtable_name = get_vtable_name(QueueItem(item.path, &item.ptr), cs, true))
{
FAIL("pointer to a vtable: " << vtable_name);
}
else if (sizes)
{
//FAIL("pointer to memory with no size information");
}
}
void Checker::check_stl_vector(const QueueItem & item, type_identity *item_identity, type_identity *eid)
{
auto vec_items = validate_vector_size(item, CheckedStructure(item_identity));
// skip bad pointer vectors
if (item.path.length() > 4 && item.path.substr(item.path.length() - 4) == ".bad" && item_identity->type() == IDTYPE_POINTER)
{
return;
}
if (vec_items.first && vec_items.second.count)
{
QueueItem items_item(item.path, vec_items.first);
queue_item(items_item, vec_items.second);
}
}
void Checker::check_stl_string(const QueueItem & item)
{
const static CheckedStructure cs(df::identity_traits<std::string>::get(), 0, nullptr, true);
#ifdef WIN32
struct string_data
{
union
{
uintptr_t start;
char local_data[16];
};
size_t length;
size_t capacity;
};
#else
struct string_data
{
struct string_data_inner
{
size_t length;
size_t capacity;
int32_t refcount;
} *ptr;
};
#endif
auto string = reinterpret_cast<const string_data *>(item.ptr);
#ifdef WIN32
bool is_local = string->capacity < 16;
const char *start = is_local ? &string->local_data[0] : reinterpret_cast<const char *>(string->start);
ptrdiff_t length = string->length;
ptrdiff_t capacity = string->capacity;
#else
if (!is_valid_dereference(QueueItem(item, "?ptr?", string->ptr), 1))
{
// nullptr is NOT okay here
FAIL("invalid string pointer " << stl_sprintf("%p", string->ptr));
return;
}
if (!is_valid_dereference(QueueItem(item, "?hdr?", string->ptr - 1), sizeof(*string->ptr)))
{
return;
}
const char *start = reinterpret_cast<const char *>(string->ptr);
ptrdiff_t length = (string->ptr - 1)->length;
ptrdiff_t capacity = (string->ptr - 1)->capacity;
#endif
if (length < 0)
{
FAIL("string length is negative (" << length << ")");
}
else if (capacity < 0)
{
FAIL("string capacity is negative (" << capacity << ")");
}
else if (capacity < length)
{
FAIL("string capacity (" << capacity << ") is less than length (" << length << ")");
}
#ifndef WIN32
const std::string empty_string;
auto empty_string_data = reinterpret_cast<const string_data *>(&empty_string);
if (sizes && string->ptr != empty_string_data->ptr)
{
size_t allocated_size = get_allocated_size(QueueItem(item, "?hdr?", string->ptr - 1));
size_t expected_size = sizeof(*string->ptr) + capacity + 1;
if (!allocated_size)
{
FAIL("pointer does not appear to be a string");
}
else if (allocated_size != expected_size)
{
FAIL("allocated string data size (" << allocated_size << ") does not match expected size (" << expected_size << ")");
}
}
#endif
}
void Checker::check_possible_pointer(const QueueItem & item, const CheckedStructure & cs)
{
if (sizes && maybepointer && uintptr_t(item.ptr) % sizeof(void *) == 0)
{
auto ptr = validate_and_dereference<const void *>(item, true);
QueueItem ptr_item(item, "?maybe_pointer?", ptr);
if (ptr && is_valid_dereference(ptr_item, 1, true))
{
check_unknown_pointer(ptr_item);
}
}
}

@ -0,0 +1,150 @@
#include "check-structures-sanity.h"
#include "LuaTools.h"
#include "LuaWrapper.h"
DFHACK_PLUGIN("check-structures-sanity");
static command_result command(color_ostream &, std::vector<std::string> &);
DFhackCExport command_result plugin_init(color_ostream &, std::vector<PluginCommand> & commands)
{
commands.push_back(PluginCommand(
"check-structures-sanity",
"performs a sanity check on df-structures",
command,
false,
"check-structures-sanity [-enums] [-sizes] [-lowmem] [-maxerrors n] [-failfast] [starting_point]\n"
"\n"
"-enums: report unexpected or unnamed enum or bitfield values.\n"
"-sizes: report struct and class sizes that don't match structures. (requires sizecheck)\n"
"-unnamed: report unnamed enum/bitfield values, not just undefined ones.\n"
"-maxerrors n: set the maximum number of errors before bailing out.\n"
"-failfast: crash if any error is encountered. useful only for debugging.\n"
"-maybepointer: report integers that might actually be pointers.\n"
"starting_point: a lua expression or a word like 'screen', 'item', or 'building'. (defaults to df.global)\n"
"\n"
"by default, check-structures-sanity reports invalid pointers, vectors, strings, and vtables."
));
known_types_by_size.clear();
build_size_table();
return CR_OK;
}
bool check_malloc_perturb()
{
struct T_test {
uint32_t data[1024];
};
auto test = new T_test;
bool ret = (test->data[0] == 0xd2d2d2d2);
delete test;
return ret;
}
static command_result command(color_ostream & out, std::vector<std::string> & parameters)
{
if (!check_malloc_perturb())
{
out.printerr("check-structures-sanity: MALLOC_PERTURB_ not set, cannot continue\n");
return CR_FAILURE;
}
CoreSuspender suspend;
Checker checker(out);
// check parameters with values first
#define VAL_PARAM(name, expr_using_value) \
auto name ## _idx = std::find(parameters.begin(), parameters.end(), "-" #name); \
if (name ## _idx != parameters.end()) \
{ \
if (name ## _idx + 1 == parameters.end()) \
{ \
return CR_WRONG_USAGE; \
} \
try \
{ \
auto value = std::move(*(name ## _idx + 1)); \
parameters.erase((name ## _idx + 1)); \
parameters.erase(name ## _idx); \
checker.name = (expr_using_value); \
} \
catch (std::exception & ex) \
{ \
out.printerr("check-structures-sanity: argument to -%s: %s\n", #name, ex.what()); \
return CR_WRONG_USAGE; \
} \
}
VAL_PARAM(maxerrors, std::stoul(value));
#undef VAL_PARAM
#define BOOL_PARAM(name) \
auto name ## _idx = std::find(parameters.begin(), parameters.end(), "-" #name); \
if (name ## _idx != parameters.end()) \
{ \
checker.name = true; \
parameters.erase(name ## _idx); \
}
BOOL_PARAM(enums);
BOOL_PARAM(sizes);
BOOL_PARAM(unnamed);
BOOL_PARAM(failfast);
BOOL_PARAM(noprogress);
BOOL_PARAM(maybepointer);
#undef BOOL_PARAM
if (parameters.size() > 1)
{
return CR_WRONG_USAGE;
}
if (parameters.empty())
{
checker.queue_globals();
}
else
{
using namespace DFHack::Lua;
using namespace DFHack::Lua::Core;
using namespace DFHack::LuaWrapper;
StackUnwinder unwinder(State);
PushModulePublic(out, "utils", "df_expr_to_ref");
Push(parameters.at(0));
if (!SafeCall(out, 1, 1))
{
return CR_FAILURE;
}
if (!lua_touserdata(State, -1))
{
return CR_WRONG_USAGE;
}
QueueItem item(parameters.at(0), get_object_ref(State, -1));
lua_getfield(State, -1, "_type");
lua_getfield(State, -1, "_identity");
auto identity = reinterpret_cast<type_identity *>(lua_touserdata(State, -1));
if (!identity)
{
out.printerr("could not determine type identity\n");
return CR_FAILURE;
}
checker.queue_item(item, CheckedStructure(identity));
}
while (checker.process_queue())
{
if (!checker.noprogress)
{
out << "checked " << checker.checked_count << " fields\r" << std::flush;
}
}
out << "checked " << checker.checked_count << " fields" << std::endl;
return checker.error_count ? CR_FAILURE : CR_OK;
}

@ -0,0 +1,189 @@
#include "check-structures-sanity.h"
QueueItem::QueueItem(const std::string & path, const void *ptr) :
path(path),
ptr(ptr)
{
}
QueueItem::QueueItem(const QueueItem & parent, const std::string & member, const void *ptr) :
QueueItem(parent.path + "." + member, ptr)
{
}
QueueItem::QueueItem(const QueueItem & parent, size_t index, const void *ptr) :
QueueItem(parent.path + stl_sprintf("[%zu]", index), ptr)
{
}
CheckedStructure::CheckedStructure() :
CheckedStructure(nullptr, 0)
{
}
CheckedStructure::CheckedStructure(type_identity *identity, size_t count) :
CheckedStructure(identity, count, nullptr, false)
{
}
CheckedStructure::CheckedStructure(type_identity *identity, size_t count, enum_identity *eid, bool inside_structure) :
identity(identity),
count(count),
allocated_count(0),
eid(eid),
ptr_is_array(false),
inside_structure(inside_structure)
{
}
CheckedStructure::CheckedStructure(const struct_field_info *field) :
CheckedStructure()
{
if (!field || field->mode == struct_field_info::END)
{
UNEXPECTED;
}
identity = field->type;
eid = field->extra ? field->extra->index_enum : nullptr;
inside_structure = true;
switch (field->mode)
{
case struct_field_info::END:
UNEXPECTED;
break;
case struct_field_info::PRIMITIVE:
if (field->count || !field->type)
{
UNEXPECTED;
}
break;
case struct_field_info::STATIC_STRING:
if (!field->count || field->type)
{
UNEXPECTED;
}
identity = df::identity_traits<char>::get();
count = field->count;
break;
case struct_field_info::POINTER:
if (field->count & PTRFLAG_IS_ARRAY)
{
ptr_is_array = true;
}
identity = Checker::wrap_in_pointer(field->type);
break;
case struct_field_info::STATIC_ARRAY:
if (!field->count || !field->type)
{
UNEXPECTED;
}
count = field->count;
break;
case struct_field_info::SUBSTRUCT:
case struct_field_info::CONTAINER:
if (field->count || !field->type)
{
UNEXPECTED;
}
break;
case struct_field_info::STL_VECTOR_PTR:
if (field->count)
{
UNEXPECTED;
}
identity = Checker::wrap_in_stl_ptr_vector(field->type);
break;
case struct_field_info::OBJ_METHOD:
case struct_field_info::CLASS_METHOD:
UNEXPECTED;
break;
}
}
size_t CheckedStructure::full_size() const
{
size_t size = identity->byte_size();
if (count)
{
size *= count;
}
return size;
}
bool CheckedStructure::has_type_at_offset(const CheckedStructure & type, size_t offset) const
{
if (!identity)
return false;
if (offset == 0 && identity == type.identity)
return true;
if (offset >= identity->byte_size() && offset < full_size())
offset %= identity->byte_size();
else if (offset >= identity->byte_size())
return false;
if (identity->type() == IDTYPE_BUFFER)
{
auto target = static_cast<container_identity *>(identity)->getItemType();
return CheckedStructure(target, 0).has_type_at_offset(type, offset % target->byte_size());
}
auto st = dynamic_cast<struct_identity *>(identity);
if (!st)
{
return false;
}
for (auto p = st; p; p = p->getParent())
{
auto fields = p->getFields();
if (!fields)
continue;
for (auto field = fields; field->mode != struct_field_info::END; field++)
{
if (field->mode == struct_field_info::OBJ_METHOD || field->mode == struct_field_info::CLASS_METHOD)
continue;
if (field->offset > offset)
continue;
if (CheckedStructure(field).has_type_at_offset(type, offset - field->offset))
return true;
}
}
return false;
}
type_identity *Checker::wrap_in_stl_ptr_vector(type_identity *base)
{
static std::map<type_identity *, std::unique_ptr<df::stl_ptr_vector_identity>> wrappers;
auto it = wrappers.find(base);
if (it != wrappers.end())
{
return it->second.get();
}
return (wrappers[base] = dts::make_unique<df::stl_ptr_vector_identity>(base, nullptr)).get();
}
type_identity *Checker::wrap_in_pointer(type_identity *base)
{
static std::map<type_identity *, std::unique_ptr<df::pointer_identity>> wrappers;
auto it = wrappers.find(base);
if (it != wrappers.end())
{
return it->second.get();
}
return (wrappers[base] = dts::make_unique<df::pointer_identity>(base)).get();
}
std::map<size_t, std::vector<std::string>> known_types_by_size;
void build_size_table()
{
for (auto & ident : compound_identity::getTopScope())
{
if (ident->byte_size() >= MIN_SIZE_FOR_SUGGEST)
{
known_types_by_size[ident->byte_size()].push_back(ident->getFullName());
}
}
}

@ -0,0 +1,439 @@
#include "check-structures-sanity.h"
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0501
#define WINVER 0x0501
#include <windows.h>
#endif
bool Checker::is_in_global(const QueueItem & item)
{
auto fields = df::global::_identity.getFields();
for (auto field = fields; field->mode != struct_field_info::END; field++)
{
size_t size = CheckedStructure(field).full_size();
auto start = *reinterpret_cast<const void * const*>(field->offset);
auto offset = uintptr_t(item.ptr) - uintptr_t(start);
if (offset < size)
{
return true;
}
}
return false;
}
bool Checker::is_valid_dereference(const QueueItem & item, const CheckedStructure & cs, size_t size, bool quiet)
{
auto base = const_cast<void *>(item.ptr);
if (!base)
{
// cannot dereference null pointer, but not an error
return false;
}
// assumes MALLOC_PERTURB_=45
#ifdef DFHACK64
#define UNINIT_PTR 0xd2d2d2d2d2d2d2d2
#define FAIL_PTR(message) FAIL(stl_sprintf("0x%016zx: ", reinterpret_cast<uintptr_t>(base)) << message)
#else
#define UNINIT_PTR 0xd2d2d2d2
#define FAIL_PTR(message) FAIL(stl_sprintf("0x%08zx: ", reinterpret_cast<uintptr_t>(base)) << message)
#endif
if (uintptr_t(base) == UNINIT_PTR)
{
if (!quiet)
{
FAIL_PTR("uninitialized pointer");
}
return false;
}
bool found = true;
auto expected_start = base;
size_t remaining_size = size;
while (found)
{
found = false;
for (auto & range : mapped)
{
if (!range.isInRange(expected_start))
{
continue;
}
found = true;
if (!range.valid || !range.read)
{
if (!quiet)
{
FAIL_PTR("pointer to invalid memory range");
}
return false;
}
auto expected_end = const_cast<void *>(PTR_ADD(expected_start, remaining_size - 1));
if (size && !range.isInRange(expected_end))
{
auto next_start = PTR_ADD(range.end, 1);
remaining_size -= ptrdiff_t(next_start) - ptrdiff_t(expected_start);
expected_start = const_cast<void *>(next_start);
break;
}
return true;
}
}
if (quiet)
{
return false;
}
if (expected_start == base)
{
FAIL_PTR("pointer not in any mapped range");
}
else
{
FAIL_PTR("pointer exceeds mapped memory bounds (size " << size << ")");
}
return false;
#undef FAIL_PTR
}
int64_t Checker::get_int_value(const QueueItem & item, type_identity *type, bool quiet)
{
if (type == df::identity_traits<int32_t>::get())
{
return validate_and_dereference<int32_t>(item, quiet);
}
else if (type == df::identity_traits<uint32_t>::get())
{
return validate_and_dereference<uint32_t>(item, quiet);
}
else if (type == df::identity_traits<int16_t>::get())
{
return validate_and_dereference<int16_t>(item, quiet);
}
else if (type == df::identity_traits<uint16_t>::get())
{
return validate_and_dereference<uint16_t>(item, quiet);
}
else if (type == df::identity_traits<int64_t>::get())
{
return validate_and_dereference<int64_t>(item, quiet);
}
else if (type == df::identity_traits<uint64_t>::get())
{
return int64_t(validate_and_dereference<uint64_t>(item, quiet));
}
else if (type == df::identity_traits<int8_t>::get())
{
return validate_and_dereference<int8_t>(item, quiet);
}
else if (type == df::identity_traits<uint8_t>::get())
{
return validate_and_dereference<uint8_t>(item, quiet);
}
else
{
UNEXPECTED;
return 0;
}
}
const char *Checker::get_vtable_name(const QueueItem & item, const CheckedStructure & cs, bool quiet)
{
auto vtable = validate_and_dereference<const void *const*>(QueueItem(item, "?vtable?", item.ptr), quiet);
if (!vtable)
return nullptr;
auto info = validate_and_dereference<const char *const*>(QueueItem(item, "?vtable?.info", vtable - 1), quiet);
if (!info)
return nullptr;
#ifdef WIN32
#ifdef DFHACK64
void *base;
if (!RtlPcToFileHeader(const_cast<void *>(reinterpret_cast<const void *>(info)), &base))
return nullptr;
const char *typeinfo = reinterpret_cast<const char *>(base) + reinterpret_cast<const int32_t *>(info)[3];
const char *name = typeinfo + 16;
#else
const char *name = reinterpret_cast<const char *>(info) + 8;
#endif
#else
auto name = validate_and_dereference<const char *>(QueueItem(item, "?vtable?.info.name", info + 1), quiet);
#endif
for (auto & range : mapped)
{
if (!range.isInRange(const_cast<char *>(name)))
{
continue;
}
if (!range.valid || !range.read)
{
if (!quiet)
{
FAIL("pointer to invalid memory range");
}
return nullptr;
}
const char *first_letter = nullptr;
bool letter = false;
for (const char *p = name; ; p++)
{
if (!range.isInRange(const_cast<char *>(p)))
{
return nullptr;
}
if ((*p >= 'a' && *p <= 'z') || *p == '_')
{
if (!letter)
{
first_letter = p;
}
letter = true;
}
else if (!*p)
{
return first_letter;
}
}
}
return nullptr;
}
std::pair<const void *, CheckedStructure> Checker::validate_vector_size(const QueueItem & item, const CheckedStructure & cs, bool quiet)
{
using ret_type = std::pair<const void *, CheckedStructure>;
struct vector_data
{
uintptr_t start;
uintptr_t finish;
uintptr_t end_of_storage;
};
vector_data vector = *reinterpret_cast<const vector_data *>(item.ptr);
ptrdiff_t length = vector.finish - vector.start;
ptrdiff_t capacity = vector.end_of_storage - vector.start;
bool local_ok = true;
auto item_size = cs.identity ? cs.identity->byte_size() : 0;
if (!item_size)
{
item_size = 1;
local_ok = false;
}
if (vector.start > vector.finish)
{
local_ok = false;
if (!quiet)
{
FAIL("vector length is negative (" << (length / ptrdiff_t(item_size)) << ")");
}
}
if (vector.start > vector.end_of_storage)
{
local_ok = false;
if (!quiet)
{
FAIL("vector capacity is negative (" << (capacity / ptrdiff_t(item_size)) << ")");
}
}
else if (vector.finish > vector.end_of_storage)
{
local_ok = false;
if (!quiet)
{
FAIL("vector capacity (" << (capacity / ptrdiff_t(item_size)) << ") is less than its length (" << (length / ptrdiff_t(item_size)) << ")");
}
}
size_t ulength = size_t(length);
size_t ucapacity = size_t(capacity);
if (ulength % item_size != 0)
{
local_ok = false;
if (!quiet)
{
FAIL("vector length is non-integer (" << (ulength / item_size) << " items plus " << (ulength % item_size) << " bytes)");
}
}
if (ucapacity % item_size != 0)
{
local_ok = false;
if (!quiet)
{
FAIL("vector capacity is non-integer (" << (ucapacity / item_size) << " items plus " << (ucapacity % item_size) << " bytes)");
}
}
if (local_ok && capacity && !vector.start)
{
if (!quiet)
{
FAIL("vector has null pointer but capacity " << (capacity / item_size));
}
return ret_type();
}
auto start_ptr = reinterpret_cast<const void *>(vector.start);
if (capacity && !is_valid_dereference(QueueItem(item, "?items?", start_ptr), CheckedStructure(cs.identity, capacity / item_size), quiet))
{
local_ok = false;
}
if (!local_ok)
{
return ret_type();
}
CheckedStructure ret_cs(cs.identity, ulength / item_size);
ret_cs.allocated_count = ucapacity / item_size;
return std::make_pair(start_ptr, ret_cs);
}
size_t Checker::get_allocated_size(const QueueItem & item)
{
if (!sizes)
{
return 0;
}
if (uintptr_t(item.ptr) % 32 != 16)
{
return 0;
}
uint32_t tag = *reinterpret_cast<const uint32_t *>(PTR_ADD(item.ptr, -8));
if (tag == 0xdfdf4ac8)
{
return *reinterpret_cast<const size_t *>(PTR_ADD(item.ptr, -16));
}
return 0;
}
#ifndef WIN32
const std::string *Checker::validate_stl_string_pointer(const void *const* base)
{
std::string empty_string;
if (*base == *reinterpret_cast<void **>(&empty_string))
{
return reinterpret_cast<const std::string *>(base);
}
const struct string_data_inner
{
size_t length;
size_t capacity;
int32_t refcount;
} *str_data = static_cast<const string_data_inner *>(*base) - 1;
if (!is_valid_dereference(QueueItem("str", PTR_ADD(str_data, -16)), 16, true))
{
return nullptr;
}
uint32_t tag = *reinterpret_cast<const uint32_t *>(PTR_ADD(str_data, -8));
if (tag == 0xdfdf4ac8)
{
size_t allocated_size = *reinterpret_cast<const size_t *>(PTR_ADD(str_data, -16));
size_t expected_size = sizeof(*str_data) + str_data->capacity + 1;
if (allocated_size != expected_size)
{
return nullptr;
}
}
else
{
return nullptr;
}
if (str_data->capacity < str_data->length)
{
return nullptr;
}
const char *ptr = reinterpret_cast<const char *>(*base);
for (size_t i = 0; i < str_data->length; i++)
{
if (!*ptr++)
{
return nullptr;
}
}
if (*ptr++)
{
return nullptr;
}
return reinterpret_cast<const std::string *>(base);
}
#endif
const char *const *Checker::get_enum_item_key(enum_identity *identity, int64_t value)
{
return get_enum_item_attr_or_key(identity, value, nullptr);
}
const char *const *Checker::get_enum_item_attr_or_key(enum_identity *identity, int64_t value, const char *attr_name)
{
size_t index;
if (auto cplx = identity->getComplex())
{
auto it = cplx->value_index_map.find(value);
if (it == cplx->value_index_map.cend())
{
return nullptr;
}
index = it->second;
}
else
{
if (value < identity->getFirstItem() || value > identity->getLastItem())
{
return nullptr;
}
index = value - identity->getFirstItem();
}
if (attr_name)
{
auto attrs = identity->getAttrs();
auto attr_type = identity->getAttrType();
if (!attrs || !attr_type)
{
return nullptr;
}
attrs = PTR_ADD(attrs, attr_type->byte_size() * index);
for (auto field = attr_type->getFields(); field->mode != struct_field_info::END; field++)
{
if (!strcmp(field->name, attr_name))
{
return reinterpret_cast<const char *const *>(PTR_ADD(attrs, field->offset));
}
}
return nullptr;
}
return &identity->getKeys()[index];
}

@ -76,7 +76,7 @@ void outputHex(uint8_t *buf,uint8_t *lbuf,size_t len,size_t start,color_ostream
for(size_t i=0;i<len;i+=page_size)
{
//con.gotoxy(1,i/page_size+1);
con.print("0x%08lX ",i+start);
con.print("0x%08zX ",i+start);
for(size_t j=0;(j<page_size) && (i+j<len);j++)
{
if(j%sizeof(void*)==0)
@ -116,8 +116,8 @@ void Deinit()
size_t detect_size(void *addr) {
size_t *size = (size_t*)((char*)addr - 16);
int32_t *tag = (int32_t*)((char*)addr - 8);
if (isAddr(size, memdata.ranges) && *tag == 0x11223344) {
uint32_t *tag = (uint32_t*)((char*)addr - 8);
if (isAddr(size, memdata.ranges) && (*tag == 0x11223344 || *tag == 0xdfdf4ac8)) {
return *size;
}
// default
@ -217,7 +217,8 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out)
{
mymutex->lock();
Deinit();
delete mymutex;
mymutex->unlock();
delete mymutex;
mymutex = nullptr;
return CR_OK;
}

@ -414,7 +414,9 @@ void AnimalHospital::processPatients(color_ostream &out) {
static vector<AnimalHospital*> animal_hospital_zones;
void delete_animal_hospital_vector(color_ostream &out) {
out.print("Clearing all animal hospitals\n");
if (dwarfvet_enabled) {
out.print("Clearing all animal hospitals\n");
}
for (vector<AnimalHospital*>::iterator animal_hospital = animal_hospital_zones.begin(); animal_hospital != animal_hospital_zones.end(); animal_hospital++) {
delete (*animal_hospital);
}

@ -17,7 +17,7 @@ namespace embark_assist {
namespace defs {
// Survey types
//
enum class river_sizes {
enum class river_sizes : int8_t {
None,
Brook,
Stream,
@ -26,8 +26,27 @@ namespace embark_assist {
Major
};
enum class aquifer_sizes : int8_t {
NA,
None,
Light,
None_Light,
Heavy,
None_Heavy,
Light_Heavy,
None_Light_Heavy
};
enum class tree_levels : int8_t {
None,
Very_Scarce,
Scarce,
Woodland,
Heavily_Forested
};
struct mid_level_tile {
bool aquifer = false;
aquifer_sizes aquifer = aquifer_sizes::NA;
bool clay = false;
bool sand = false;
bool flux = false;
@ -40,6 +59,7 @@ namespace embark_assist {
int8_t adamantine_level; // -1 = none, 0 .. 3 = cavern 1 .. magma sea. Currently not used beyond present/absent.
int8_t magma_level; // -1 = none, 0 .. 3 = cavern 3 .. surface/volcano
int8_t biome_offset;
tree_levels trees;
uint8_t savagery_level; // 0 - 2
uint8_t evilness_level; // 0 - 2
std::vector<bool> metals;
@ -51,7 +71,7 @@ namespace embark_assist {
struct region_tile_datum {
bool surveyed = false;
uint16_t aquifer_count = 0;
aquifer_sizes aquifer = aquifer_sizes::NA;
uint16_t clay_count = 0;
uint16_t sand_count = 0;
uint16_t flux_count = 0;
@ -65,6 +85,8 @@ namespace embark_assist {
uint8_t biome_count;
int16_t min_temperature[10]; // Indexed through biome_offset; -30000 = null, Urists - 10000, [0] not used
int16_t max_temperature[10]; // Indexed through biome_offset; -30000 = null, Urists - 10000, [0] not used
tree_levels min_tree_level = embark_assist::defs::tree_levels::Heavily_Forested;
tree_levels max_tree_level = embark_assist::defs::tree_levels::None;
bool blood_rain[10];
bool blood_rain_possible;
bool blood_rain_full;
@ -85,6 +107,8 @@ namespace embark_assist {
std::vector<bool> metals;
std::vector<bool> economics;
std::vector<bool> minerals;
std::vector<int16_t> neighbors; // entity_raw indices
uint8_t necro_neighbors;
mid_level_tile north_row[16];
mid_level_tile south_row[16];
mid_level_tile west_column[16];
@ -121,8 +145,7 @@ namespace embark_assist {
struct site_infos {
bool incursions_processed;
bool aquifer;
bool aquifer_full;
aquifer_sizes aquifer;
uint8_t min_soil;
uint8_t max_soil;
bool flat;
@ -140,6 +163,8 @@ namespace embark_assist {
std::vector<uint16_t> economics;
std::vector<uint16_t> minerals;
// Could add savagery, evilness, and biomes, but DF provides those easily.
std::vector<int16_t> neighbors; // entity_raw indices
uint8_t necro_neighbors;
};
typedef std::vector<sites> site_lists;
@ -176,11 +201,17 @@ namespace embark_assist {
enum class aquifer_ranges : int8_t {
NA = -1,
All,
Present,
Partial,
Not_All,
Absent
None,
At_Most_Light,
None_Plus_Light,
None_Plus_At_Least_Light,
Light,
At_Least_Light,
None_Plus_Heavy,
At_Most_Light_Plus_Heavy,
Light_Plus_Heavy,
None_Light_Heavy,
Heavy
};
enum class river_ranges : int8_t {
@ -263,6 +294,20 @@ namespace embark_assist {
Never
};
enum class tree_ranges : int8_t {
NA = -1,
None,
Very_Scarce, // DF dislays this with a different color but still the "scarce" text
Scarce,
Woodland,
Heavily_Forested
};
struct neighbor {
int16_t entity_raw; // entity_raw
present_absent_ranges present;
};
struct finders {
uint16_t x_dim;
uint16_t y_dim;
@ -296,6 +341,8 @@ namespace embark_assist {
int8_t biome_1; // N/A(-1), df::biome_type
int8_t biome_2; // N/A(-1), df::biome_type
int8_t biome_3; // N/A(-1), df::biome_type
tree_ranges min_trees;
tree_ranges max_trees;
int16_t metal_1; // N/A(-1), 0-max_inorganic;
int16_t metal_2; // N/A(-1), 0-max_inorganic;
int16_t metal_3; // N/A(-1), 0-max_inorganic;
@ -305,6 +352,11 @@ namespace embark_assist {
int16_t mineral_1; // N/A(-1), 0-max_inorganic;
int16_t mineral_2; // N/A(-1), 0-max_inorganic;
int16_t mineral_3; // N/A(-1), 0-max_inorganic;
int8_t min_necro_neighbors; // N/A(-1), 0 - 9, where 9 = 9+
int8_t max_necro_neighbors; // N/A(-1), 0 - 9, where 9 = 9+
int8_t min_civ_neighbors; // N/A(-1), 0 - 9, where 9 = 9+
int8_t max_civ_neighbors; // N/A(-1), 0 - 9, where 9 = 9+
std::vector<neighbor> neighbors;
};
struct match_iterators {

@ -311,7 +311,7 @@ command_result embark_assistant(color_ostream &out, std::vector <std::string> &
for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) {
embark_assist::main::state->survey_results[i][k].surveyed = false;
embark_assist::main::state->survey_results[i][k].aquifer_count = 0;
embark_assist::main::state->survey_results[i][k].aquifer = embark_assist::defs::aquifer_sizes::NA;
embark_assist::main::state->survey_results[i][k].clay_count = 0;
embark_assist::main::state->survey_results[i][k].sand_count = 0;
embark_assist::main::state->survey_results[i][k].flux_count = 0;
@ -319,6 +319,8 @@ command_result embark_assistant(color_ostream &out, std::vector <std::string> &
embark_assist::main::state->survey_results[i][k].max_region_soil = 0;
embark_assist::main::state->survey_results[i][k].max_waterfall = 0;
embark_assist::main::state->survey_results[i][k].river_size = embark_assist::defs::river_sizes::None;
embark_assist::main::state->survey_results[i][k].min_tree_level = embark_assist::defs::tree_levels::Heavily_Forested;
embark_assist::main::state->survey_results[i][k].max_tree_level = embark_assist::defs::tree_levels::None;
for (uint8_t l = 1; l < 10; l++) {
embark_assist::main::state->survey_results[i][k].biome_index[l] = -1;

@ -8,11 +8,14 @@
#include "MemAccess.h"
#include "df/biome_type.h"
#include "df/entity_raw.h"
#include "df/entity_raw_flags.h"
#include "df/inorganic_raw.h"
#include "df/material_flags.h"
#include "df/viewscreen_choose_start_sitest.h"
#include "df/world.h"
#include "df/world_data.h"
#include "df/world_raws.h"
#include "df/world_region_type.h"
#include "df/world_raws.h"
@ -64,6 +67,8 @@ namespace embark_assist {
biome_1,
biome_2,
biome_3,
min_trees,
max_trees,
metal_1,
metal_2,
metal_3,
@ -72,10 +77,15 @@ namespace embark_assist {
economic_3,
mineral_1,
mineral_2,
mineral_3
mineral_3,
min_necro_neighbors,
max_necro_neighbors,
min_civ_neighbors,
max_civ_neighbors,
neighbors
};
fields first_fields = fields::x_dim;
fields last_fields = fields::mineral_3;
fields last_fields = fields::neighbors;
struct display_map_elements {
std::string text;
@ -104,6 +114,11 @@ namespace embark_assist {
const DFHack::Screen::Pen white_pen(' ', COLOR_WHITE);
const DFHack::Screen::Pen lr_pen(' ', COLOR_LIGHTRED);
struct civ_entities {
int16_t id;
std::string description;
};
//==========================================================================================================
struct states {
@ -113,6 +128,7 @@ namespace embark_assist {
uint16_t finder_list_focus;
bool finder_list_active;
uint16_t max_inorganic;
std::vector<civ_entities> civs;
};
static states *state = 0;
@ -152,20 +168,26 @@ namespace embark_assist {
FILE* outfile = fopen(profile_file_name, "w");
fields i = first_fields;
size_t civ = 0;
while (true) {
for (size_t k = 0; k < state->ui[static_cast<int8_t>(i)]->list.size(); k++) {
if (state->ui[static_cast<int8_t>(i)]->current_value == state->ui[static_cast<int8_t>(i)]->list[k].key) {
fprintf(outfile, "[%s:%s]\n", state->finder_list[static_cast<int8_t>(i)].text.c_str(), state->ui[static_cast<int8_t>(i)]->list[k].text.c_str());
if (state->ui[static_cast<int8_t>(i) + civ]->current_value == state->ui[static_cast<int8_t>(i) + civ]->list[k].key) {
fprintf(outfile, "[%s:%s]\n", state->finder_list[static_cast<int8_t>(i) + civ].text.c_str(), state->ui[static_cast<int8_t>(i) + civ]->list[k].text.c_str());
break;
}
}
// fprintf(outfile, "[%s:%i]\n", state->finder_list[static_cast<int8_t>(i)].text.c_str(), state->ui[static_cast<int8_t>(i)]->current_value);
if (i == last_fields) {
break; // done
}
civ++;
i = static_cast <fields>(static_cast<int8_t>(i) + 1);
if (civ == state->civs.size()) {
break; // done
}
}
else {
i = static_cast <fields>(static_cast<int8_t>(i) + 1);
}
}
fclose(outfile);
@ -176,6 +198,7 @@ namespace embark_assist {
void load_profile() {
color_ostream_proxy out(Core::getInstance().getConsole());
FILE* infile = fopen(profile_file_name, "r");
size_t civ = 0;
if (!infile) {
out.printerr("No profile file found at %s\n", profile_file_name);
@ -197,8 +220,8 @@ namespace embark_assist {
for (int k = 1; k < count; k++) {
if (line[k] == ':') {
for (int l = 1; l < k; l++) {
if (state->finder_list[static_cast<int8_t>(i)].text.c_str()[l - 1] != line[l]) {
out.printerr("Token mismatch of %s vs %s\n", line, state->finder_list[static_cast<int8_t>(i)].text.c_str());
if (state->finder_list[static_cast<int8_t>(i) + civ].text.c_str()[l - 1] != line[l]) {
out.printerr("Token mismatch of %s vs %s\n", line, state->finder_list[static_cast<int8_t>(i) + civ].text.c_str());
fclose(infile);
return;
}
@ -206,10 +229,10 @@ namespace embark_assist {
found = false;
for (size_t l = 0; l < state->ui[static_cast<int8_t>(i)]->list.size(); l++) {
for (size_t l = 0; l < state->ui[static_cast<int8_t>(i) + civ]->list.size(); l++) {
for (int m = k + 1; m < count; m++) {
if (state->ui[static_cast<int8_t>(i)]->list[l].text.c_str()[m - (k + 1)] != line[m]) {
if (state->ui[static_cast<int8_t>(i)]->list[l].text.c_str()[m - (k + 1)] == '\0' &&
if (state->ui[static_cast<int8_t>(i) + civ]->list[l].text.c_str()[m - (k + 1)] != line[m]) {
if (state->ui[static_cast<int8_t>(i) + civ]->list[l].text.c_str()[m - (k + 1)] == '\0' &&
line[m] == ']') {
found = true;
}
@ -238,10 +261,15 @@ namespace embark_assist {
}
if (i == last_fields) {
break; // done
}
civ++;
i = static_cast <fields>(static_cast<int8_t>(i) + 1);
if (civ == state->civs.size()) {
break; // done
}
}
else {
i = static_cast <fields>(static_cast<int8_t>(i) + 1);
}
}
fclose(infile);
@ -250,6 +278,7 @@ namespace embark_assist {
infile = fopen(profile_file_name, "r");
i = first_fields;
civ = 0;
while (true) {
if (!fgets(line, count, infile))
@ -262,13 +291,13 @@ namespace embark_assist {
found = false;
for (size_t l = 0; l < state->ui[static_cast<int8_t>(i)]->list.size(); l++) {
for (size_t l = 0; l < state->ui[static_cast<int8_t>(i) + civ]->list.size(); l++) {
for (int m = k + 1; m < count; m++) {
if (state->ui[static_cast<int8_t>(i)]->list[l].text.c_str()[m - (k + 1)] != line[m]) {
if (state->ui[static_cast<int8_t>(i)]->list[l].text.c_str()[m - (k + 1)] == '\0' &&
if (state->ui[static_cast<int8_t>(i) + civ]->list[l].text.c_str()[m - (k + 1)] != line[m]) {
if (state->ui[static_cast<int8_t>(i) + civ]->list[l].text.c_str()[m - (k + 1)] == '\0' &&
line[m] == ']') {
state->ui[static_cast<int8_t>(i)]->current_value = state->ui[static_cast<int8_t>(i)]->list[l].key;
state->ui[static_cast<int8_t>(i)]->current_display_value = l;
state->ui[static_cast<int8_t>(i) + civ]->current_value = state->ui[static_cast<int8_t>(i) + civ]->list[l].key;
state->ui[static_cast<int8_t>(i) + civ]->current_display_value = l;
found = true;
}
@ -285,10 +314,15 @@ namespace embark_assist {
}
if (i == last_fields) {
break; // done
}
civ++;
i = static_cast <fields>(static_cast<int8_t>(i) + 1);
if (civ == state->civs.size()) {
break; // done
}
}
else {
i = static_cast <fields>(static_cast<int8_t>(i) + 1);
}
}
fclose(infile);
@ -308,6 +342,30 @@ namespace embark_assist {
fields i = first_fields;
ui_lists *element;
int16_t controllable_civs = 0;
int16_t max_civs;
for (int16_t i = 0; i < (int16_t)world->raws.entities.size(); i++) {
if (world->raws.entities[i]->flags.is_set(df::entity_raw_flags::CIV_CONTROLLABLE)) controllable_civs++;
}
for (int16_t i = 0; i < (int16_t)world->raws.entities.size(); i++) {
if (!world->raws.entities[i]->flags.is_set(df::entity_raw_flags::LAYER_LINKED) && // Animal people
!world->raws.entities[i]->flags.is_set(df::entity_raw_flags::GENERATED) && // Vault guardians
(controllable_civs > 1 || // Suppress the playable civ when only 1
!world->raws.entities[i]->flags.is_set(df::entity_raw_flags::CIV_CONTROLLABLE))) { // Too much work to change dynamically for modded worlds.
if (world->raws.entities[i]->translation == "") {
state->civs.push_back({ i, world->raws.entities[i]->code }); // Kobolds don't have a translation...
}
else {
state->civs.push_back({ i, world->raws.entities[i]->translation });
}
}
}
max_civs = state->civs.size();
if (controllable_civs > 1) max_civs = max_civs - 1;
while (true) {
element = new ui_lists;
@ -376,28 +434,52 @@ namespace embark_assist {
element->list.push_back({ "N/A", static_cast<int8_t>(k) });
break;
case embark_assist::defs::aquifer_ranges::All:
element->list.push_back({ "All", static_cast<int8_t>(k) });
case embark_assist::defs::aquifer_ranges::None:
element->list.push_back({ "None", static_cast<int8_t>(k) });
break;
case embark_assist::defs::aquifer_ranges::Present:
element->list.push_back({ "Present", static_cast<int8_t>(k) });
case embark_assist::defs::aquifer_ranges::At_Most_Light:
element->list.push_back({ "<= Light", static_cast<int8_t>(k) });
break;
case embark_assist::defs::aquifer_ranges::Partial:
element->list.push_back({ "Partial", static_cast<int8_t>(k) });
case embark_assist::defs::aquifer_ranges::None_Plus_Light:
element->list.push_back({ "None + Light", static_cast<int8_t>(k) });
break;
case embark_assist::defs::aquifer_ranges::Not_All:
element->list.push_back({ "Not All", static_cast<int8_t>(k) });
case embark_assist::defs::aquifer_ranges::None_Plus_At_Least_Light:
element->list.push_back({ "None + >= Light", static_cast<int8_t>(k) });
break;
case embark_assist::defs::aquifer_ranges::Absent:
element->list.push_back({ "Absent", static_cast<int8_t>(k) });
case embark_assist::defs::aquifer_ranges::Light:
element->list.push_back({ "Light", static_cast<int8_t>(k) });
break;
case embark_assist::defs::aquifer_ranges::At_Least_Light:
element->list.push_back({ ">= Light", static_cast<int8_t>(k) });
break;
case embark_assist::defs::aquifer_ranges::None_Plus_Heavy:
element->list.push_back({ "None + Heavy", static_cast<int8_t>(k) });
break;
case embark_assist::defs::aquifer_ranges::At_Most_Light_Plus_Heavy:
element->list.push_back({ "<= Light + Heavy", static_cast<int8_t>(k) });
break;
case embark_assist::defs::aquifer_ranges::Light_Plus_Heavy:
element->list.push_back({ "Light + Heavy", static_cast<int8_t>(k) });
break;
case embark_assist::defs::aquifer_ranges::None_Light_Heavy:
element->list.push_back({ "None + Light + Heavy", static_cast<int8_t>(k) });
break;
case embark_assist::defs::aquifer_ranges::Heavy:
element->list.push_back({ "Heavy", static_cast<int8_t>(k) });
break;
}
if (k == embark_assist::defs::aquifer_ranges::Absent) {
if (k == embark_assist::defs::aquifer_ranges::Heavy) {
break;
}
@ -836,6 +918,47 @@ namespace embark_assist {
}
break;
case fields::min_trees:
case fields::max_trees:
{
embark_assist::defs::tree_ranges k = embark_assist::defs::tree_ranges::NA;
while (true) {
switch (k) {
case embark_assist::defs::tree_ranges::NA:
element->list.push_back({ "N/A", static_cast<int8_t>(k) });
break;
case embark_assist::defs::tree_ranges::None:
element->list.push_back({ "None", static_cast<int8_t>(k) });
break;
case embark_assist::defs::tree_ranges::Very_Scarce:
element->list.push_back({ "Very Scarce", static_cast<int8_t>(k) });
break;
case embark_assist::defs::tree_ranges::Scarce:
element->list.push_back({ "Scarce", static_cast<int8_t>(k) });
break;
case embark_assist::defs::tree_ranges::Woodland:
element->list.push_back({ "Woodland", static_cast<int8_t>(k) });
break;
case embark_assist::defs::tree_ranges::Heavily_Forested:
element->list.push_back({ "Heavily Forested", static_cast<int8_t>(k) });
break;
}
if (k == embark_assist::defs::tree_ranges::Heavily_Forested) {
break;
}
k = static_cast <embark_assist::defs::tree_ranges>(static_cast<int8_t>(k) + 1);
}
}
break;
case fields::metal_1:
case fields::metal_2:
case fields::metal_3:
@ -918,10 +1041,73 @@ namespace embark_assist {
name.clear();
}
break;
case fields::min_necro_neighbors:
case fields::max_necro_neighbors:
for (int16_t k = -1; k <= 16; k++) {
if (k == -1) {
element->list.push_back({ "N/A", k });
}
else {
element->list.push_back({ std::to_string(k), k });
}
}
break;
case fields::min_civ_neighbors:
case fields::max_civ_neighbors:
for (int16_t k = -1; k <= max_civs; k++) {
if (k == -1) {
element->list.push_back({ "N/A", k });
}
else {
element->list.push_back({ std::to_string(k), k });
}
}
break;
case fields::neighbors:
for (size_t l = 0; l < state->civs.size(); l++) {
embark_assist::defs::present_absent_ranges k = embark_assist::defs::present_absent_ranges::NA;
while (true) {
switch (k) {
case embark_assist::defs::present_absent_ranges::NA:
element->list.push_back({ "N/A", static_cast<int8_t>(k) });
break;
case embark_assist::defs::present_absent_ranges::Present:
element->list.push_back({ "Present", static_cast<int8_t>(k) });
break;
case embark_assist::defs::present_absent_ranges::Absent:
element->list.push_back({ "Absent", static_cast<int8_t>(k) });
break;
}
if (k == embark_assist::defs::present_absent_ranges::Absent) {
break;
}
k = static_cast <embark_assist::defs::present_absent_ranges>(static_cast<int8_t>(k) + 1);
}
if (l < state->civs.size() - 1) {
element->current_value = element->list[0].key;
state->ui.push_back(element);
element = new ui_lists;
element->current_display_value = 0;
element->current_index = 0;
element->focus = 0;
}
}
break;
}
element->current_value = element->list[0].key;
state->ui.push_back(element);
switch (i) {
@ -1069,6 +1255,14 @@ namespace embark_assist {
state->finder_list.push_back({ "Biome 3", static_cast<int8_t>(i) });
break;
case fields::min_trees:
state->finder_list.push_back({ "Min Trees", static_cast<int8_t>(i) });
break;
case fields::max_trees:
state->finder_list.push_back({ "Max Trees", static_cast<int8_t>(i) });
break;
case fields::metal_1:
state->finder_list.push_back({ "Metal 1", static_cast<int8_t>(i) });
break;
@ -1104,6 +1298,28 @@ namespace embark_assist {
case fields::mineral_3:
state->finder_list.push_back({ "Mineral 3", static_cast<int8_t>(i) });
break;
case fields::min_necro_neighbors:
state->finder_list.push_back({ "Min Necro Tower", static_cast<int8_t>(i) });
break;
case fields::max_necro_neighbors:
state->finder_list.push_back({ "Max Necro Tower", static_cast<int8_t>(i) });
break;
case fields::min_civ_neighbors:
state->finder_list.push_back({ "Min Near Civs", static_cast<int8_t>(i) });
break;
case fields::max_civ_neighbors:
state->finder_list.push_back({ "Max Near Civs", static_cast<int8_t>(i) });
break;
case fields::neighbors:
for (uint8_t k = 0; k < state->civs.size(); k++) {
state->finder_list.push_back({ state->civs[k].description, (int16_t)(static_cast<int8_t>(i) + k) });
}
break;
}
if (i == last_fields) {
@ -1136,7 +1352,6 @@ namespace embark_assist {
state->ui[static_cast<int8_t>(fields::y_dim)]->current_display_value + 1;
}
//==========================================================================================================
void find() {
@ -1312,6 +1527,14 @@ namespace embark_assist {
finder.biome_3 = state->ui[static_cast<uint8_t>(i)]->current_value;
break;
case fields::min_trees:
finder.min_trees = static_cast<embark_assist::defs::tree_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);
break;
case fields::max_trees:
finder.max_trees = static_cast<embark_assist::defs::tree_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);
break;
case fields::metal_1:
finder.metal_1 = state->ui[static_cast<uint8_t>(i)]->current_value;
break;
@ -1347,6 +1570,28 @@ namespace embark_assist {
case fields::mineral_3:
finder.mineral_3 = state->ui[static_cast<uint8_t>(i)]->current_value;
break;
case fields::min_necro_neighbors:
finder.min_necro_neighbors = state->ui[static_cast<uint8_t>(i)]->current_value;
break;
case fields::max_necro_neighbors:
finder.max_necro_neighbors = state->ui[static_cast<uint8_t>(i)]->current_value;
break;
case fields::min_civ_neighbors:
finder.min_civ_neighbors = state->ui[static_cast<uint8_t>(i)]->current_value;
break;
case fields::max_civ_neighbors:
finder.max_civ_neighbors = state->ui[static_cast<uint8_t>(i)]->current_value;
break;
case fields::neighbors:
for (size_t k = 0; k < state->civs.size(); k++) {
finder.neighbors.push_back({ state->civs[k].id, static_cast<embark_assist::defs::present_absent_ranges>(state->ui[static_cast<uint8_t>(i) + k]->current_value) });
}
break;
}
if (i == last_fields) {
@ -1394,7 +1639,7 @@ namespace embark_assist {
state->finder_list_focus--;
}
else {
state->finder_list_focus = static_cast<uint16_t>(last_fields);
state->finder_list_focus = static_cast<uint16_t>(last_fields) + state->civs.size() - 1;
}
}
else {
@ -1407,7 +1652,7 @@ namespace embark_assist {
} else if (input->count(df::interface_key::STANDARDSCROLL_DOWN)) {
if (state->finder_list_active) {
if (state->finder_list_focus < static_cast<uint16_t>(last_fields)) {
if (state->finder_list_focus < static_cast<uint16_t>(last_fields) + state->civs.size() - 1) {
state->finder_list_focus++;
} else {
state->finder_list_focus = 0;

@ -178,7 +178,7 @@ namespace embark_assist{
help_text.push_back("Clay, if present, including thorugh incursions.");
help_text.push_back("Min and Max soil depth in the embark rectangle, including incursions.");
help_text.push_back("Flat indicator if all the tiles and incursions have the same elevation.");
help_text.push_back("Aquifer indicator: Part(ial) or Full, when present, including incursions.");
help_text.push_back("Aquifer indicator: 'No', 'Lt', 'Hv' for presence/type including incursions.");
help_text.push_back("Waterfall and largest Z level drop if the river has elevation differences");
help_text.push_back("Evil weather, when present: BR = Blood Rain, TS = Temporary Syndrome");
help_text.push_back("PS = Permanent Syndrome, Re = Reanimating, and Th = Thralling. Incursions.");
@ -186,6 +186,7 @@ namespace embark_assist{
help_text.push_back("A list of all metals present in the embark. Not incursions.");
help_text.push_back("A list of all economic minerals present in the embark. Both clays and flux");
help_text.push_back("stones are economic, so they show up here as well. Not incursions.");
help_text.push_back("Neighbors, including Kobolds (SKULKING) (hidden in vanilla) and Towers.");
help_text.push_back("In addition to the above, the Find functionality can also produce blinking");
help_text.push_back("overlays over the Local, Region, and World maps to indicate where");
help_text.push_back("matching embarks are found. The Local display marks the top left corner of");
@ -215,11 +216,7 @@ namespace embark_assist{
help_text.push_back("Present means at least one embark tile has to have this value.");
help_text.push_back("Absent means the feature mustn't exist in any of the embark tiles.");
help_text.push_back("N/A means no restrictions are applied.");
help_text.push_back("The Aquifer criterion introduces some new parameters:");
help_text.push_back("Partial means at least one tile has to have an aquifer, but it also has");
help_text.push_back("to be absent from at least one tile. Not All means an aquifer is tolerated");
help_text.push_back("as long as at least one tile doesn't have one, but it doesn't have to have");
help_text.push_back("any at all.");
help_text.push_back("The Aquifer criterion allows you to search for a number of combinations.");
help_text.push_back("Min/Max rivers should be self explanatory. The Yes and No values of");
help_text.push_back("Clay, etc. means one has to be Present and Absent respectivey.");
help_text.push_back("Min Waterfall Drop finds embarks with drops of at least that number");
@ -238,8 +235,10 @@ namespace embark_assist{
help_text.push_back("The parameters for biomes, regions, etc. all require that the required");
help_text.push_back("feature is Present in the embark, and entering the same value multiple");
help_text.push_back("times does nothing (the first match ticks all requirements off). It can be");
help_text.push_back("noted that all the Economic materials are found in the much longer Mineral");
help_text.push_back("list. Note that Find is a fairly time consuming task (as it is in vanilla).");
help_text.push_back("noted that all the Economic materials are found among the longer Minerals.");
help_text.push_back("Min/Max Necro Towers and Min/Max Near Civs work on neighbor counts, while");
help_text.push_back("the civ entity selections let you choose your specific neighbors.");
help_text.push_back("Note that Find is a fairly time consuming task (as it is in vanilla).");
break;
case pages::Caveats_1:
@ -266,6 +265,10 @@ namespace embark_assist{
help_text.push_back("ones.");
help_text.push_back("");
help_text.push_back("Caveats & technical stuff:");
help_text.push_back("- embark-tools' neutralizing of DF random rectangle placement on world");
help_text.push_back(" tile shifts changes placement info after it's read. This is compensated");
help_text.push_back(" for, but at the cost of incorrect info without it. In tile move back");
help_text.push_back(" and forth causes the correct info to be displayed.");
help_text.push_back("- The plugin does in fact allow for a single, optional case sensitive");
help_text.push_back(" parameter when invoked: 'fileresult'. When this parameter is provided");
help_text.push_back(" The plugin will read the search profile stored to file and immediately");
@ -293,11 +296,10 @@ namespace embark_assist{
case pages::Caveats_2:
Screen::drawBorder(" Embark Assistant Help/Info Caveats 2 Page ");
help_text.push_back("- The site info is deduced by the author, so there may be errors and");
help_text.push_back(" there are probably site types that end up not being identified.");
help_text.push_back("- Aquifer indications are based on the author's belief that they occur");
help_text.push_back(" whenever an aquifer supporting layer is present at a depth of 3 or");
help_text.push_back(" more.");
help_text.push_back(" more. In addition, Toady's description on how Heavy aquifers are");
help_text.push_back(" selected has been used.");
help_text.push_back("- Thralling is determined by whether material interactions causes");
help_text.push_back(" blinking, which the author believes is one of 4 thralling changes.");
help_text.push_back("- The geo information is gathered by code which is essentially a");
@ -320,10 +322,11 @@ namespace embark_assist{
help_text.push_back(" whether to make a second, completing, search.");
help_text.push_back("- Incursions are taken into consideration when looking for Aquifers,");
help_text.push_back(" Clay, Sand, Min Soil when Everywhere, Biomes, Regions, Evil Weather,");
help_text.push_back(" Savagery, Evilness, Freezing and Flatness, but ignored for metals/");
help_text.push_back(" economics/minerals (including Flux and Coal) as any volumes are typically");
help_text.push_back(" too small to be of interest. Rivers, Waterfalls, Spires, and Magma Pools");
help_text.push_back(" are not incursion related features.");
help_text.push_back(" Savagery, Evilness, Freezing and Flatness, and trees, but ignored for");
help_text.push_back(" metals/economics/minerals (including Flux and Coal) as any volumes are");
help_text.push_back(" typically too small to be of interest. Rivers, Waterfalls, Spires, and");
help_text.push_back(" Magma Pools are not incursion related features.");
help_text.push_back("- Neighbor determination makes use of a flag in entities.");
help_text.push_back("- There are special rules for handing of incursions from Lakes and Oceans,");
help_text.push_back(" as well as Mountains into everything that isn't a Lake or Ocean, and the");
help_text.push_back(" rules state that these incursions should be reversed (i.e. 'normal' biomes");
@ -335,7 +338,7 @@ namespace embark_assist{
help_text.push_back(" the N, followed by the one to the W, and lastly the one acting as the");
help_text.push_back(" reference. This means there's a risk embarks with such 'trouble' corners");
help_text.push_back(" may get affected corner(s) evaluated incorrectly.");
help_text.push_back("Version 0.10 2019-09-21");
help_text.push_back("Version 0.11 2020-03-03");
break;
}

File diff suppressed because it is too large Load Diff

@ -1,6 +1,7 @@
#include <modules/Gui.h>
#include "df/coord2d.h"
#include "df/entity_raw.h"
#include "df/inorganic_raw.h"
#include "df/dfhack_material_category.h"
#include "df/interface_key.h"
@ -369,14 +370,50 @@ void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_in
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Flat" });
}
if (site_info->aquifer) {
if (site_info->aquifer_full) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Full Aquifer" });
}
else {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Part. Aquifer" });
if (site_info->aquifer != embark_assist::defs::aquifer_sizes::None) {
std::string none = " ";
std::string light = " ";
std::string heavy = " ";
std::string no = "No ";
std::string lt = "Lt ";
std::string hv = "Hv";
switch (site_info->aquifer) {
case embark_assist::defs::aquifer_sizes::NA:
case embark_assist::defs::aquifer_sizes::None: // Neither of these should appear
break;
case embark_assist::defs::aquifer_sizes::Light:
light = lt;
break;
case embark_assist::defs::aquifer_sizes::None_Light:
none = no;
light = lt;
break;
case embark_assist::defs::aquifer_sizes::Heavy:
heavy = hv;
break;
case embark_assist::defs::aquifer_sizes::None_Heavy:
none = no;
heavy = hv;
break;
case embark_assist::defs::aquifer_sizes::Light_Heavy:
light = lt;
heavy = hv;
break;
case embark_assist::defs::aquifer_sizes::None_Light_Heavy:
none = no;
light = lt;
heavy = hv;
break;
}
state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Aq: " + none + light + heavy });
}
if (site_info->max_waterfall > 0) {
@ -443,6 +480,20 @@ void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_in
for (auto const& i : site_info->economics) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_WHITE), world->raws.inorganics[i]->id });
}
for (uint16_t i = 0; i < site_info->neighbors.size(); i++) {
if (world->raws.entities[site_info->neighbors[i]]->translation == "") {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_YELLOW), world->raws.entities[site_info->neighbors[i]]->code }); // Kobolds have an empty translation field
}
else
{
state->embark_info.push_back({ Screen::Pen(' ', COLOR_YELLOW), world->raws.entities[site_info->neighbors[i]]->translation });
}
}
if (site_info->necro_neighbors > 0) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTRED), "Towers: " + std::to_string(site_info->necro_neighbors) });
}
}
//====================================================================

@ -16,11 +16,14 @@
#include "df/creature_interaction_effect.h"
#include "df/creature_interaction_effect_display_symbolst.h"
#include "df/creature_interaction_effect_type.h"
#include "df/entity_raw.h"
#include "df/feature_init.h"
#include "df/feature_init_deep_special_tubest.h"
#include "df/feature_init_magma_poolst.h"
#include "df/feature_init_volcanost.h"
#include "df/feature_type.h"
#include "df/historical_entity.h"
#include "df/historical_entity_type.h"
#include "df/inorganic_flags.h"
#include "df/inorganic_raw.h"
#include "df/interaction.h"
@ -504,13 +507,7 @@ namespace embark_assist {
int16_t elevation,
uint16_t x,
uint16_t y) {
if (mlt->aquifer) {
site_info->aquifer = true;
}
else {
site_info->aquifer_full = false;
}
site_info->aquifer = static_cast<embark_assist::defs::aquifer_sizes>(static_cast<int8_t>(mlt->aquifer) | static_cast<int8_t>(site_info->aquifer));
if (mlt->soil_depth < site_info->min_soil) {
site_info->min_soil = mlt->soil_depth;
@ -760,6 +757,34 @@ void embark_assist::survey::clear_results(embark_assist::defs::match_results *ma
//=================================================================================
embark_assist::defs::tree_levels tree_level_of(df::world_region_type region_type, int16_t vegetation) {
if (region_type == df::world_region_type::Glacier ||
region_type == df::world_region_type::Lake ||
region_type == df::world_region_type::Mountains ||
region_type == df::world_region_type::Ocean) {
return embark_assist::defs::tree_levels::None;
}
else {
if (vegetation == 0) {
return embark_assist::defs::tree_levels::None;
}
else if (vegetation <= 9) {
return embark_assist::defs::tree_levels::Very_Scarce;
}
else if (vegetation <= 32) {
return embark_assist::defs::tree_levels::Scarce;
}
else if (vegetation <= 65) {
return embark_assist::defs::tree_levels::Woodland;
}
else {
return embark_assist::defs::tree_levels::Heavily_Forested;
}
}
}
//=================================================================================
void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_data *geo_summary,
embark_assist::defs::world_tile_data *survey_results) {
// color_ostream_proxy out(Core::getInstance().getConsole());
@ -777,7 +802,7 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat
uint8_t offset_count = 0;
auto &results = survey_results->at(i).at(k);
results.surveyed = false;
results.aquifer_count = 0;
results.aquifer = embark_assist::defs::aquifer_sizes::NA;
results.clay_count = 0;
results.sand_count = 0;
results.flux_count = 0;
@ -785,6 +810,8 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat
results.min_region_soil = 10;
results.max_region_soil = 0;
results.max_waterfall = 0;
results.min_tree_level = embark_assist::defs::tree_levels::Heavily_Forested;
results.max_tree_level = embark_assist::defs::tree_levels::None;
results.savagery_count[0] = 0;
results.savagery_count[1] = 0;
results.savagery_count[2] = 0;
@ -823,7 +850,16 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat
results.min_temperature[l] = min_temperature(results.max_temperature[l], adjusted.y);
geo_index = world_data->region_map[adjusted.x][adjusted.y].geo_index;
if (!geo_summary->at(geo_index).aquifer_absent) results.aquifer_count++;
if (geo_summary->at(geo_index).aquifer_absent) {
results.aquifer = static_cast<embark_assist::defs::aquifer_sizes>(static_cast<int8_t>(results.aquifer) | 1);
}
else if (world_data->region_map[adjusted.x][adjusted.y].drainage % 20 == 7) {
results.aquifer = static_cast<embark_assist::defs::aquifer_sizes>(static_cast<int8_t>(results.aquifer) | 4);
}
else {
results.aquifer = static_cast<embark_assist::defs::aquifer_sizes>(static_cast<int8_t>(results.aquifer) | 2);
}
if (!geo_summary->at(geo_index).clay_absent) results.clay_count++;
if (!geo_summary->at(geo_index).sand_absent) results.sand_count++;
if (!geo_summary->at(geo_index).flux_absent) results.flux_count++;
@ -848,6 +884,12 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat
if (geo_summary->at(geo_index).possible_economics[m]) results.economics[m] = true;
if (geo_summary->at(geo_index).possible_minerals[m]) results.minerals[m] = true;
}
embark_assist::defs::tree_levels tree_level = tree_level_of(world_data->regions[results.biome_index[l]]->type,
world_data->region_map[adjusted.x][adjusted.y].vegetation);
if (tree_level < results.min_tree_level) results.min_tree_level = tree_level;
if (tree_level > results.max_tree_level) results.max_tree_level = tree_level;
}
else {
results.biome_index[l] = -1;
@ -862,7 +904,6 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat
if (results.biome[l] != -1) results.biome_count++;
}
if (results.aquifer_count == offset_count) results.aquifer_count = 256;
if (results.clay_count == offset_count) results.clay_count = 256;
if (results.sand_count == offset_count) results.sand_count = 256;
if (results.flux_count == offset_count) results.flux_count = 256;
@ -906,6 +947,7 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
uint16_t end_check_l;
uint16_t end_check_m;
uint16_t end_check_n;
bool aquifer;
for (uint16_t i = 0; i < state->max_inorganic; i++) {
tile->metals[i] = 0;
@ -1026,7 +1068,8 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
int16_t layer_shift[16];
int16_t cur_shift = elevation + soil_erosion - 1;
mlt->at(i).at(k).aquifer = false;
aquifer = false;
mlt->at(i).at(k).aquifer = embark_assist::defs::aquifer_sizes::NA;
mlt->at(i).at(k).clay = false;
mlt->at(i).at(k).sand = false;
mlt->at(i).at(k).flux = false;
@ -1129,7 +1172,7 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
}
for (uint16_t m = 0; m < state->coals.size(); m++) {
if (layer->mat_index == state->coals [m]) {
if (layer->mat_index == state->coals[m]) {
mlt->at(i).at(k).coal = true;
break;
}
@ -1162,7 +1205,7 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
}
for (uint16_t n = 0; n < state->coals.size(); n++) {
if (layer->vein_mat [m] == state->coals[n]) {
if (layer->vein_mat[m] == state->coals[n]) {
mlt->at(i).at(k).coal = true;
break;
}
@ -1172,10 +1215,22 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
if (bottom_z <= elevation - 3 &&
world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::AQUIFER)) {
mlt->at(i).at(k).aquifer = true;
aquifer = true;
}
}
}
if (!aquifer) {
mlt->at(i).at(k).aquifer = embark_assist::defs::aquifer_sizes::None;
}
else if (world_data->region_map[adjusted.x][adjusted.y].drainage % 20 == 7) {
mlt->at(i).at(k).aquifer = embark_assist::defs::aquifer_sizes::Heavy;
}
else {
mlt->at(i).at(k).aquifer = embark_assist::defs::aquifer_sizes::Light;
}
mlt->at(i).at(k).trees = tree_level_of(world_data->regions[world_data->region_map[adjusted.x][adjusted.y].region_id]->type,
world_data->region_map[adjusted.x][adjusted.y].vegetation);
}
}
@ -1224,7 +1279,7 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
}
}
survey_results->at(x).at(y).aquifer_count = 0;
survey_results->at(x).at(y).aquifer = embark_assist::defs::aquifer_sizes::NA;
survey_results->at(x).at(y).clay_count = 0;
survey_results->at(x).at(y).sand_count = 0;
survey_results->at(x).at(y).flux_count = 0;
@ -1240,7 +1295,7 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) {
if (mlt->at(i).at(k).aquifer) { survey_results->at(x).at(y).aquifer_count++; }
survey_results->at(x).at(y).aquifer = static_cast<embark_assist::defs::aquifer_sizes>(static_cast<int8_t>(survey_results->at(x).at(y).aquifer) | static_cast<int8_t>(mlt->at(i).at(k).aquifer));
if (mlt->at(i).at(k).clay) { survey_results->at(x).at(y).clay_count++; }
if (mlt->at(i).at(k).sand) { survey_results->at(x).at(y).sand_count++; }
if (mlt->at(i).at(k).flux) { survey_results->at(x).at(y).flux_count++; }
@ -1279,6 +1334,9 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
// reanimating handled separately
// thralling handled separately
if (survey_results->at(x).at(y).min_tree_level > mlt->at(i).at(k).trees) survey_results->at(x).at(y).min_tree_level = mlt->at(i).at(k).trees;
if (survey_results->at(x).at(y).max_tree_level < mlt->at(i).at(k).trees) survey_results->at(x).at(y).max_tree_level = mlt->at(i).at(k).trees;
survey_results->at(x).at(y).savagery_count[mlt->at(i).at(k).savagery_level]++;
survey_results->at(x).at(y).evilness_count[mlt->at(i).at(k).evilness_level]++;
@ -1380,6 +1438,11 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
tile->west_column[i].biome_offset = mlt->at(0).at(i).biome_offset;
tile->east_column[i].biome_offset = mlt->at(15).at(i).biome_offset;
tile->north_row[i].trees = mlt->at(i).at(0).trees;
tile->south_row[i].trees = mlt->at(i).at(15).trees;
tile->west_column[i].trees = mlt->at(0).at(i).trees;
tile->east_column[i].trees = mlt->at(15).at(i).trees;
tile->north_row[i].savagery_level = mlt->at(i).at(0).savagery_level;
tile->south_row[i].savagery_level = mlt->at(i).at(15).savagery_level;
tile->west_column[i].savagery_level = mlt->at(0).at(i).savagery_level;
@ -1415,6 +1478,22 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
}
}
// Focus has to be at the world tile to get neighbor info
//
if (!tile->surveyed) {
for (uint16_t i = 0; i < world->entities.all.size(); i++) {
if (world->entities.all[i]->flags.bits.neighbor) {
if (world->entities.all[i]->type == df::historical_entity_type::SiteGovernment) {
tile->necro_neighbors++;
}
else
{
tile->neighbors.push_back(world->entities.all[i]->entity_raw->index);
}
}
}
}
tile->surveyed = true;
}
@ -2068,7 +2147,7 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles *
bool incursion_processing_failed = false;
df::world_data *world_data = world->world_data;
if (!use_cache) { // For some reason DF scrambles these values on world tile movements (at least in Lua...).
if (!use_cache) { // DF scrambles these values on world tile movements, while embark-tools stabilizes the movement, but its changes to the value are done after we've read them.
state->local_min_x = screen->location.embark_pos_min.x;
state->local_min_y = screen->location.embark_pos_min.y;
state->local_max_x = screen->location.embark_pos_max.x;
@ -2079,8 +2158,7 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles *
state->y = y;
site_info->incursions_processed = true;
site_info->aquifer = false;
site_info->aquifer_full = true;
site_info->aquifer = embark_assist::defs::aquifer_sizes::NA;
site_info->min_soil = 10;
site_info->max_soil = 0;
site_info->flat = true;
@ -2097,15 +2175,11 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles *
site_info->metals.clear();
site_info->economics.clear();
site_info->metals.clear();
site_info->neighbors.clear();
for (uint8_t i = state->local_min_x; i <= state->local_max_x; i++) {
for (uint8_t k = state->local_min_y; k <= state->local_max_y; k++) {
if (mlt->at(i).at(k).aquifer) {
site_info->aquifer = true;
}
else {
site_info->aquifer_full = false;
}
site_info->aquifer = static_cast<embark_assist::defs::aquifer_sizes>(static_cast<int8_t>(site_info->aquifer) | static_cast<int8_t>(mlt->at(i).at(k).aquifer));
if (mlt->at(i).at(k).soil_depth < site_info->min_soil) {
site_info->min_soil = mlt->at(i).at(k).soil_depth;
@ -2441,6 +2515,12 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles *
}
if (incursion_processing_failed) site_info->incursions_processed = false;
for (uint16_t i = 0; i < survey_results->at(x).at(y).neighbors.size(); i++) {
site_info->neighbors.push_back(survey_results->at(x).at(y).neighbors[i]);
}
site_info->necro_neighbors = survey_results->at(x).at(y).necro_neighbors;
}
//=================================================================================

@ -38,16 +38,53 @@ enum class selectability {
Unselected
};
//selectability selectablePlant(color_ostream &out, const df::plant_raw *plant)
selectability selectablePlant(const df::plant_raw *plant)
// Determination of whether seeds can be collected is somewhat messy:
// - Growths of type SEEDS are collected only if they are edible either raw or cooked.
// - Growths of type PLANT_GROWTH are collected provided the STOCKPILE_PLANT_GROWTH
// flag is set.
// The two points above were determined through examination of the DF code, while the ones
// below were determined through examination of the behavior of bugged, working, and
// RAW manipulated shrubs on embarks.
// - If seeds are defined as explicit growths, they are the source of seeds, overriding
// the default STRUCTURAL part as the source.
// - If a growth has the reaction that extracts seeds as a side effect of other
// processing (brewing, eating raw, etc.), this overrides the STRUCTURAL part as the
// source of seeds. However, for some reason it does not produce seeds from eating
// raw growths unless the structural part is collected (at least for shrubs: other
// processing was not examined).
// - If a growth has a (non vanilla) reaction that produces seeds, seeds are produced,
// provided there is something (such as a workshop order) that triggers it.
// The code below is satisfied with detection of a seed producing reaction, and does not
// detect the bugged case where a seed extraction process is defined but doesn't get
// triggered. Such a process can be triggered either as a side effect of other
// processing, or as a workshop reaction, and it would be overkill for this code to
// try to determine if a workshop reaction exists and has been permitted for the played
// race.
// There are two bugged cases of this in the current vanilla RAWs:
// Both Red Spinach and Elephant-Head Amaranth have the seed extraction reaction
// explicitly specified for the structural part, but no other use for it. This causes
// these parts to be collected (a valid reaction is defined), but remain unusable. This
// is one of the issues in bug 6940 on the bug tracker (the others cases are detected and
// result in the plants not being usable for farming or even collectable at all).
//selectability selectablePlant(color_ostream &out, const df::plant_raw *plant, bool farming)
selectability selectablePlant(const df::plant_raw *plant, bool farming)
{
const DFHack::MaterialInfo basic_mat = DFHack::MaterialInfo(plant->material_defs.type_basic_mat, plant->material_defs.idx_basic_mat);
bool outOfSeason = false;
selectability result = selectability::Nonselectable;
if (plant->flags.is_set(plant_raw_flags::TREE))
{
// out.print("%s is a selectable tree\n", plant->id.c_str());
return selectability::Selectable;
if (farming)
{
return selectability::Nonselectable;
}
else
{
return selectability::Selectable;
}
}
else if (plant->flags.is_set(plant_raw_flags::GRASS))
{
@ -55,11 +92,26 @@ selectability selectablePlant(const df::plant_raw *plant)
return selectability::Grass;
}
if (farming && plant->material_defs.type_seed == -1)
{
return selectability::Nonselectable;
}
if (basic_mat.material->flags.is_set(material_flags::EDIBLE_RAW) ||
basic_mat.material->flags.is_set(material_flags::EDIBLE_COOKED))
{
// out.print("%s is edible\n", plant->id.c_str());
return selectability::Selectable;
if (farming)
{
if (basic_mat.material->flags.is_set(material_flags::EDIBLE_RAW))
{
result = selectability::Selectable;
}
}
else
{
return selectability::Selectable;
}
}
if (plant->flags.is_set(plant_raw_flags::THREAD) ||
@ -69,14 +121,28 @@ selectability selectablePlant(const df::plant_raw *plant)
plant->flags.is_set(plant_raw_flags::EXTRACT_STILL_VIAL))
{
// out.print("%s is thread/mill/extract\n", plant->id.c_str());
return selectability::Selectable;
if (farming)
{
result = selectability::Selectable;
}
else
{
return selectability::Selectable;
}
}
if (basic_mat.material->reaction_product.id.size() > 0 ||
basic_mat.material->reaction_class.size() > 0)
{
// out.print("%s has a reaction\n", plant->id.c_str());
return selectability::Selectable;
if (farming)
{
result = selectability::Selectable;
}
else
{
return selectability::Selectable;
}
}
for (size_t i = 0; i < plant->growths.size(); i++)
@ -91,16 +157,37 @@ selectability selectablePlant(const df::plant_raw *plant)
(plant->growths[i]->item_type == df::item_type::PLANT_GROWTH &&
growth_mat.material->flags.is_set(material_flags::LEAF_MAT))) // Will change name to STOCKPILE_PLANT_GROWTH any day now...
{
bool seedSource = plant->growths[i]->item_type == df::item_type::SEEDS;
if (plant->growths[i]->item_type == df::item_type::PLANT_GROWTH)
{
for (size_t k = 0; growth_mat.material->reaction_product.material.mat_type.size(); k++)
{
if (growth_mat.material->reaction_product.material.mat_type[k] == plant->material_defs.type_seed &&
growth_mat.material->reaction_product.material.mat_index[k] == plant->material_defs.idx_seed)
{
seedSource = true;
break;
}
}
}
if (*cur_year_tick >= plant->growths[i]->timing_1 &&
(plant->growths[i]->timing_2 == -1 ||
*cur_year_tick <= plant->growths[i]->timing_2))
{
// out.print("%s has an edible seed or a stockpile growth\n", plant->id.c_str());
return selectability::Selectable;
if (!farming || seedSource)
{
return selectability::Selectable;
}
}
else
{
outOfSeason = true;
if (!farming || seedSource)
{
outOfSeason = true;
}
}
}
}
@ -133,7 +220,7 @@ selectability selectablePlant(const df::plant_raw *plant)
else
{
// out.printerr("%s cannot be gathered\n", plant->id.c_str());
return selectability::Nonselectable;
return result;
}
}
@ -143,8 +230,8 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
std::vector<selectability> plantSelections;
std::vector<size_t> collectionCount;
set<string> plantNames;
bool deselect = false, exclude = false, treesonly = false, shrubsonly = false, all = false, verbose = false;
bool deselect = false, exclude = false, treesonly = false, shrubsonly = false, all = false, verbose = false, farming = false;
size_t maxCount = 999999;
int count = 0;
plantSelections.resize(world->raws.plants.all.size());
@ -160,20 +247,43 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
for (size_t i = 0; i < parameters.size(); i++)
{
if(parameters[i] == "help" || parameters[i] == "?")
if (parameters[i] == "help" || parameters[i] == "?")
return CR_WRONG_USAGE;
else if(parameters[i] == "-t")
else if (parameters[i] == "-t")
treesonly = true;
else if(parameters[i] == "-s")
else if (parameters[i] == "-s")
shrubsonly = true;
else if(parameters[i] == "-c")
else if (parameters[i] == "-c")
deselect = true;
else if(parameters[i] == "-x")
else if (parameters[i] == "-x")
exclude = true;
else if(parameters[i] == "-a")
else if (parameters[i] == "-a")
all = true;
else if(parameters[i] == "-v")
else if (parameters[i] == "-v")
verbose = true;
else if (parameters[i] == "-f")
farming = true;
else if (parameters[i] == "-n")
{
if (parameters.size() > i + 1)
{
maxCount = atoi(parameters[i + 1].c_str());
if (maxCount >= 1)
{
i++; // We've consumed the next parameter, so we need to progress the iterator.
}
else
{
out.printerr("-n requires a positive integer parameter!\n");
return CR_WRONG_USAGE;
}
}
else
{
out.printerr("-n requires a positive integer parameter!\n");
return CR_WRONG_USAGE;
}
}
else
plantNames.insert(parameters[i]);
}
@ -182,6 +292,11 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
out.printerr("Cannot specify both -t and -s at the same time!\n");
return CR_WRONG_USAGE;
}
if (treesonly && farming)
{
out.printerr("Cannot specify both -t and -f at the same time!\n");
return CR_WRONG_USAGE;
}
if (all && exclude)
{
out.printerr("Cannot specify both -a and -x at the same time!\n");
@ -200,14 +315,14 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
df::plant_raw *plant = world->raws.plants.all[i];
if (all)
{
// plantSelections[i] = selectablePlant(out, plant);
plantSelections[i] = selectablePlant(plant);
// plantSelections[i] = selectablePlant(out, plant, farming);
plantSelections[i] = selectablePlant(plant, farming);
}
else if (plantNames.find(plant->id) != plantNames.end())
{
plantNames.erase(plant->id);
// plantSelections[i] = selectablePlant(out, plant);
plantSelections[i] = selectablePlant(plant);
// plantSelections[i] = selectablePlant(out, plant, farming);
plantSelections[i] = selectablePlant(plant, farming);
switch (plantSelections[i])
{
case selectability::Grass:
@ -215,7 +330,14 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
break;
case selectability::Nonselectable:
out.printerr("%s does not have any parts that can be gathered\n", plant->id.c_str());
if (farming)
{
out.printerr("%s does not have any parts that can be gathered for seeds for farming\n", plant->id.c_str());
}
else
{
out.printerr("%s does not have any parts that can be gathered\n", plant->id.c_str());
}
break;
case selectability::OutOfSeason:
@ -255,8 +377,8 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
for (size_t i = 0; i < world->raws.plants.all.size(); i++)
{
df::plant_raw *plant = world->raws.plants.all[i];
// switch (selectablePlant(out, plant))
switch (selectablePlant(plant))
// switch (selectablePlant(out, plant, farming))
switch (selectablePlant(plant, farming))
{
case selectability::Grass:
case selectability::Nonselectable:
@ -264,13 +386,21 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
case selectability::OutOfSeason:
{
out.print("* (shrub) %s - %s is out of season\n", plant->id.c_str(), plant->name.c_str());
if (!treesonly)
{
out.print("* (shrub) %s - %s is out of season\n", plant->id.c_str(), plant->name.c_str());
}
break;
}
case selectability::Selectable:
{
out.print("* (%s) %s - %s\n", plant->flags.is_set(plant_raw_flags::TREE) ? "tree" : "shrub", plant->id.c_str(), plant->name.c_str());
if ((treesonly && plant->flags.is_set(plant_raw_flags::TREE)) ||
(shrubsonly && !plant->flags.is_set(plant_raw_flags::TREE)) ||
(!treesonly && !shrubsonly)) // 'farming' weeds out trees when determining selectability, so no need to test that explicitly
{
out.print("* (%s) %s - %s\n", plant->flags.is_set(plant_raw_flags::TREE) ? "tree" : "shrub", plant->id.c_str(), plant->name.c_str());
}
break;
}
@ -311,6 +441,8 @@ command_result df_getplants (color_ostream &out, vector <string> & parameters)
continue;
if (cur->designation[x][y].bits.hidden)
continue;
if (collectionCount[plant->material] >= maxCount)
continue;
if (deselect && Designations::unmarkPlant(plant))
{
collectionCount[plant->material]++;
@ -350,12 +482,16 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCom
"Options:\n"
" -t - Tree: Select trees only (exclude shrubs)\n"
" -s - Shrub: Select shrubs only (exclude trees)\n"
" -f - Farming: Designate only shrubs that yield seeds for farming. Implies -s\n"
" -c - Clear: Clear designations instead of setting them\n"
" -x - eXcept: Apply selected action to all plants except those specified\n"
" -a - All: Select every type of plant (obeys -t/-s)\n"
" -v - Verbose: lists the number of (un)designations per plant\n"
"Specifying both -t and -s will have no effect.\n"
"If no plant IDs are specified, all valid plant IDs will be listed.\n"
" -a - All: Select every type of plant (obeys -t/-s/-f)\n"
" -v - Verbose: List the number of (un)designations per plant\n"
" -n * - Number: Designate up to * (an integer number) plants of each species\n"
"Specifying both -t and -s or -f will have no effect.\n"
"If no plant IDs are specified, and the -a switch isn't given, all valid plant\n"
"IDs will be listed with -t, -s, and -f restricting the list to trees, shrubs,\n"
"and farmable shrubs, respectively.\n"
));
return CR_OK;
}

@ -1 +1 @@
Subproject commit fbbf9e46458e41707c27f2a4438452a579490db1
Subproject commit e3c49ab017da2dcbeaadccd10e56d07d8f03b4ca

@ -373,6 +373,7 @@ public:
return df::unit_labor::TRAPPER;
case df::building_type::Civzone:
case df::building_type::Nest:
case df::building_type::OfferingPlace:
case df::building_type::Stockpile:
case df::building_type::Weapon:
return df::unit_labor::NONE;
@ -475,6 +476,7 @@ public:
return df::unit_labor::TRAPPER;
case df::building_type::Civzone:
case df::building_type::Nest:
case df::building_type::OfferingPlace:
case df::building_type::RoadDirt:
case df::building_type::Stockpile:
case df::building_type::Weapon:
@ -894,6 +896,10 @@ JobLaborMapper::JobLaborMapper()
job_to_labor_table[df::job_type::PutItemOnDisplay] = jlf_const(df::unit_labor::HAUL_ITEM);
job_to_labor_table[df::job_type::StoreItemInLocation] = jlf_no_labor; // StoreItemInLocation
job_to_labor_table[df::job_type::unk_fake_no_job] = jlf_no_labor; // added for 47.04 - see #1561
job_to_labor_table[df::job_type::InterrogateSubject] = jlf_no_labor; // added for 47.04 - see #1561
job_to_labor_table[df::job_type::unk_fake_no_activity] = jlf_no_labor; // added for 47.04 - see #1561
};
df::unit_labor JobLaborMapper::find_job_labor(df::job* j)

@ -385,6 +385,9 @@ static const dwarf_state dwarf_states[] = {
BUSY /* MakeBracelet */,
BUSY /* MakeGem */,
BUSY /* PutItemOnDisplay */,
OTHER /* unk_fake_no_job */,
OTHER /* InterrogateSubject */,
OTHER /* unk_fake_no_activity */,
};
struct labor_info

@ -242,6 +242,7 @@ const SkillColumn columns[] = {
{16, 3, profession::NONE, unit_labor::NONE, job_skill::FLATTERY, "Fl"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::CONSOLE, "Cs"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::PACIFY, "Pc"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::INTRIGUE, "Sc"},
// Noble
{17, 5, profession::TRADER, unit_labor::NONE, job_skill::APPRAISAL, "Ap"},
{17, 5, profession::ADMINISTRATOR, unit_labor::NONE, job_skill::ORGANIZATION, "Or"},
@ -576,7 +577,7 @@ namespace unit_ops {
for (int i = 0; i < 2; i++)
{
if (name.words[i] >= 0)
ret += world->raws.language.words[name.words[i]]->forms[name.parts_of_speech[i].value];
ret += world->raws.language.words[name.words[i]]->forms[name.parts_of_speech[i]];
}
return Translation::capitalize(ret);
}

@ -798,13 +798,13 @@ static command_result orders_clear_command(color_ostream & out)
{
delete condition;
}
if (order->anon_1)
if (order->items)
{
for (auto anon_1 : *order->anon_1)
for (auto item : *order->items)
{
delete anon_1;
delete item;
}
delete order->anon_1;
delete order->items;
}
delete order;

@ -1693,20 +1693,7 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques
using df::global::cur_year;
using df::global::cur_year_tick;
int year_ticks = 403200;
int birth_time = unit->birth_year * year_ticks + unit->birth_time;
int cur_time = *cur_year * year_ticks + *cur_year_tick;
if (unit->curse_year >= 0)
{
if (auto identity = Units::getIdentity(unit))
{
if (identity->histfig_id < 0)
birth_time = identity->birth_year * year_ticks + identity->birth_second;
}
}
send_unit->set_age(cur_time - birth_time);
send_unit->set_age(Units::getAge(unit, false));
ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color());
send_unit->set_flags1(unit->flags1.whole);

@ -270,7 +270,7 @@ rgbf blend(const rgbf& a,const rgbf& b)
void lightingEngineViewscreen::clear()
{
lightMap.assign(lightMap.size(),rgbf(1,1,1));
tthread::lock_guard<tthread::fast_mutex> guard(myRenderer->dataMutex);
std::lock_guard<std::mutex> guard{myRenderer->dataMutex};
if(lightMap.size()==myRenderer->lightGrid.size())
{
std::swap(myRenderer->lightGrid,lightMap);
@ -299,7 +299,7 @@ void lightingEngineViewscreen::calculate()
}
void lightingEngineViewscreen::updateWindow()
{
tthread::lock_guard<tthread::fast_mutex> guard(myRenderer->dataMutex);
std::lock_guard<std::mutex> guard{myRenderer->dataMutex};
if(lightMap.size()!=myRenderer->lightGrid.size())
{
reinit();

@ -1,11 +1,14 @@
#ifndef RENDERER_LIGHT_INCLUDED
#define RENDERER_LIGHT_INCLUDED
#include "renderer_opengl.hpp"
#include "Types.h"
#include <tuple>
#include <stack>
#pragma once
#include <memory>
#include <mutex>
#include <stack>
#include <tuple>
#include <unordered_map>
#include "renderer_opengl.hpp"
#include "Types.h"
// we are not using boost so let's cheat:
template <class T>
inline void hash_combine(std::size_t & seed, const T & v)
@ -91,7 +94,7 @@ private:
}
void reinitLightGrid(int w,int h)
{
tthread::lock_guard<tthread::fast_mutex> guard(dataMutex);
std::lock_guard<std::mutex> guard{dataMutex};
lightGrid.resize(w*h,rgbf(1,1,1));
}
void reinitLightGrid()
@ -100,7 +103,7 @@ private:
}
public:
tthread::fast_mutex dataMutex;
std::mutex dataMutex;
std::vector<rgbf> lightGrid;
renderer_light(renderer* parent):renderer_wrap(parent),light_adaptation(1)
{
@ -108,12 +111,12 @@ public:
}
virtual void update_tile(int32_t x, int32_t y) {
renderer_wrap::update_tile(x,y);
tthread::lock_guard<tthread::fast_mutex> guard(dataMutex);
std::lock_guard<std::mutex> guard{dataMutex};
colorizeTile(x,y);
};
virtual void update_all() {
renderer_wrap::update_all();
tthread::lock_guard<tthread::fast_mutex> guard(dataMutex);
std::lock_guard<std::mutex> guard{dataMutex};
for (int x = 0; x < df::global::gps->dimx; x++)
for (int y = 0; y < df::global::gps->dimy; y++)
colorizeTile(x,y);
@ -374,4 +377,3 @@ private:
};
rgbf blend(const rgbf& a,const rgbf& b);
rgbf blendMax(const rgbf& a,const rgbf& b);
#endif

@ -1,9 +1,7 @@
//original file from https://github.com/Baughn/Dwarf-Fortress--libgraphics-
#ifndef RENDERER_OPENGL_INCLUDED
#define RENDERER_OPENGL_INCLUDED
#pragma once
#include "tinythread.h"
#include "fast_mutex.h"
#include "Core.h"
#include <VTableInterpose.h>
@ -15,6 +13,7 @@
#include "df/graphic.h"
#include <math.h>
#include <cmath>
#include <mutex>
using df::renderer;
using df::init;
@ -281,7 +280,7 @@ private:
}
void reinitLightGrid(int w,int h)
{
tthread::lock_guard<tthread::fast_mutex> guard(dataMutex);
std::lock_guard<std::mutex> guard{dataMutex};
lightGrid.resize(w*h);
}
void reinitLightGrid()
@ -289,7 +288,7 @@ private:
reinitLightGrid(df::global::gps->dimy,df::global::gps->dimx);
}
public:
tthread::fast_mutex dataMutex;
std::mutex dataMutex;
std::vector<rgbf> lightGrid;
renderer_test(renderer* parent):renderer_wrap(parent)
{
@ -297,14 +296,14 @@ public:
}
virtual void update_tile(int32_t x, int32_t y) {
renderer_wrap::update_tile(x,y);
tthread::lock_guard<tthread::fast_mutex> guard(dataMutex);
std::lock_guard<std::mutex> guard{dataMutex};
colorizeTile(x,y);
//some sort of mutex or sth?
//and then map read
};
virtual void update_all() {
renderer_wrap::update_all();
tthread::lock_guard<tthread::fast_mutex> guard(dataMutex);
std::lock_guard<std::mutex> guard{dataMutex};
for (int x = 0; x < df::global::gps->dimx; x++)
for (int y = 0; y < df::global::gps->dimy; y++)
colorizeTile(x,y);
@ -366,7 +365,7 @@ private:
}
void reinitGrids(int w,int h)
{
tthread::lock_guard<tthread::fast_mutex> guard(dataMutex);
std::lock_guard<std::mutex> guard{dataMutex};
foreOffset.resize(w*h);
foreMult.resize(w*h);
backOffset.resize(w*h);
@ -377,7 +376,7 @@ private:
reinitGrids(df::global::gps->dimy,df::global::gps->dimx);
}
public:
tthread::fast_mutex dataMutex;
std::mutex dataMutex;
std::vector<rgbf> foreOffset,foreMult;
std::vector<rgbf> backOffset,backMult;
inline int xyToTile(int x, int y)
@ -390,14 +389,14 @@ public:
}
virtual void update_tile(int32_t x, int32_t y) {
renderer_wrap::update_tile(x,y);
tthread::lock_guard<tthread::fast_mutex> guard(dataMutex);
std::lock_guard<std::mutex> guard{dataMutex};
overwriteTile(x,y);
//some sort of mutex or sth?
//and then map read
};
virtual void update_all() {
renderer_wrap::update_all();
tthread::lock_guard<tthread::fast_mutex> guard(dataMutex);
std::lock_guard<std::mutex> guard{dataMutex};
for (int x = 0; x < df::global::gps->dimx; x++)
for (int y = 0; y < df::global::gps->dimy; y++)
overwriteTile(x,y);
@ -414,4 +413,3 @@ public:
reinitGrids(w,h);
}
};
#endif

@ -1,27 +1,23 @@
#include <vector>
#include <mutex>
#include <sstream>
#include <string>
#include <vector>
#include <LuaTools.h>
#include <VTableInterpose.h>
#include "Core.h"
#include "Console.h"
#include "Core.h"
#include "Export.h"
#include "LuaTools.h"
#include "PluginManager.h"
#include "VTableInterpose.h"
#include <VTableInterpose.h>
#include "df/renderer.h"
#include "df/enabler.h"
#include "df/renderer.h"
#include "df/viewscreen_dungeonmodest.h"
#include "df/viewscreen_dwarfmodest.h"
#include "renderer_opengl.hpp"
#include "renderer_light.hpp"
#include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_dungeonmodest.h"
#include <sstream>
using df::viewscreen_dungeonmodest;
using df::viewscreen_dwarfmodest;
@ -367,7 +363,7 @@ static command_result rendermax(color_ostream &out, vector <string> & parameters
cur=blue;
renderer_test* r=reinterpret_cast<renderer_test*>(enabler->renderer);
tthread::lock_guard<tthread::fast_mutex> guard(r->dataMutex);
std::lock_guard<std::mutex> guard{r->dataMutex};
int h=gps->dimy;
int w=gps->dimx;
int cx=w/2;

@ -2,16 +2,7 @@ module DFHack
class << self
def building_find(what=:selected, y=nil, z=nil)
if what == :selected
case ui.main.mode
when :LookAround
k = ui_look_list.items[ui_look_cursor]
k.building if k.type == :Building
when :BuildingItems, :QueryBuilding
world.selected_building
when :Zones, :ZonesPenInfo, :ZonesPitInfo, :ZonesHospitalInfo
ui_sidebar_menus.zone.selected
end
return world.buildings.all.binsearch(df.get_selected_building_id)
elsif what.kind_of?(Integer)
# search by building.id
return world.buildings.all.binsearch(what) if not z

@ -4,41 +4,7 @@ module DFHack
# arg similar to unit.rb/unit_find; no arg = 'k' menu
def item_find(what=:selected, y=nil, z=nil)
if what == :selected
case curview._rtti_classname
when :viewscreen_itemst
if ref = curview.entry_ref[curview.cursor_pos]
ref.item_tg if ref.kind_of?(GeneralRefItem)
else
# not a container
curview.item
end
when :viewscreen_storesst # z/stocks
if curview.in_group_mode == 0 and curview.in_right_list == 1
curview.items[curview.item_cursor]
end
else
case ui.main.mode
when :LookAround
k = ui_look_list.items[ui_look_cursor]
case k.type
when :Item
k.item
when :Building
# hilight a constructed bed/coffer
mats = k.building.contained_items.find_all { |i| i.use_mode == 2 }
mats[0].item if mats.length == 1
end
when :BuildingItems
bld = world.selected_building
bld.contained_items[ui_building_item_cursor].item if bld
when :ViewUnits
u = world.units.active[ui_selected_unit]
u.inventory[ui_look_cursor].item if u and u.pos.z == cursor.z and
ui_unit_view_mode.value == :Inventory and u.inventory[ui_look_cursor]
else
ui.follow_item_tg if ui.follow_item != -1
end
end
return world.items.all.binsearch(df.get_selected_item_id)
elsif what.kind_of?(Integer)
# search by id
return world.items.all.binsearch(what) if not z

@ -9,6 +9,8 @@
#include "modules/Gui.h"
#include "df/global_objects.h"
#include "df/building.h"
#include "df/item.h"
#include "df/unit.h"
#include "tinythread.h"
@ -630,6 +632,18 @@ static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr)
return rb_uint2inum(*(uintptr_t*)rb_num2ulong(objptr));
}
static VALUE rb_dfget_selected_building_id(VALUE self)
{
df::building *b = Gui::getAnyBuilding(Core::getTopViewscreen());
return rb_int2inum(b ? b->id : -1);
}
static VALUE rb_dfget_selected_item_id(VALUE self)
{
df::item *i = Gui::getAnyItem(Core::getTopViewscreen());
return rb_int2inum(i ? i->id : -1);
}
static VALUE rb_dfget_selected_unit_id(VALUE self)
{
df::unit *u = Gui::getAnyUnit(Core::getTopViewscreen());
@ -1147,6 +1161,8 @@ static void ruby_bind_dfhack(void) {
rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1);
rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1);
rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1);
rb_define_singleton_method(rb_cDFHack, "get_selected_building_id", RUBY_METHOD_FUNC(rb_dfget_selected_building_id), 0);
rb_define_singleton_method(rb_cDFHack, "get_selected_item_id", RUBY_METHOD_FUNC(rb_dfget_selected_item_id), 0);
rb_define_singleton_method(rb_cDFHack, "get_selected_unit_id", RUBY_METHOD_FUNC(rb_dfget_selected_unit_id), 0);
rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1);
rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1);

@ -25,6 +25,7 @@
#include "df/viewscreen_buildinglistst.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_joblistst.h"
#include "df/viewscreen_justicest.h"
#include "df/viewscreen_kitchenprefst.h"
#include "df/viewscreen_layer_militaryst.h"
#include "df/viewscreen_layer_noblelistst.h"
@ -1822,16 +1823,16 @@ public:
switch (element->type)
{
case elt_type::Item:
if (element->item)
desc = Items::getDescription(element->item, 0, true);
if (element->data.Item)
desc = Items::getDescription(element->data.Item, 0, true);
break;
case elt_type::Unit:
if (element->unit)
desc = get_unit_description(element->unit);
if (element->data.Unit)
desc = get_unit_description(element->data.Unit);
break;
case elt_type::Building:
if (element->building)
element->building->getName(&desc);
if (element->data.Building)
element->data.Building->getName(&desc);
break;
default:
break;
@ -2327,6 +2328,85 @@ IMPLEMENT_HOOKS(df::viewscreen_layer_stone_restrictionst, stone_search);
// END: Stone status screen search
//
//
// START: Justice screen conviction search
//
typedef search_generic<df::viewscreen_justicest, df::unit*> justice_conviction_search_base;
class justice_conviction_search : public justice_conviction_search_base
{
public:
bool can_init (df::viewscreen_justicest *screen)
{
return screen->cur_column == df::viewscreen_justicest::ConvictChoices;
}
string get_element_description (df::unit *unit) const
{
return get_unit_description(unit);
}
void render() const
{
print_search_option(37);
}
vector<df::unit*> *get_primary_list()
{
return &viewscreen->convict_choices;
}
virtual int32_t *get_viewscreen_cursor()
{
return &viewscreen->cursor_right;
}
};
IMPLEMENT_HOOKS(df::viewscreen_justicest, justice_conviction_search);
//
// END: Justice screen conviction search
//
//
// START: Justice screen interrogation search
//
typedef search_generic<df::viewscreen_justicest, df::unit*> justice_interrogation_search_base;
class justice_interrogation_search : public justice_interrogation_search_base
{
public:
bool can_init (df::viewscreen_justicest *screen)
{
return screen->cur_column == df::viewscreen_justicest::InterrogateChoices;
}
string get_element_description (df::unit *unit) const
{
return get_unit_description(unit);
}
void render() const
{
print_search_option(37);
}
vector<df::unit*> *get_primary_list()
{
return &viewscreen->interrogate_choices;
}
virtual int32_t *get_viewscreen_cursor()
{
return &viewscreen->cursor_right;
}
};
IMPLEMENT_HOOKS(df::viewscreen_justicest, justice_interrogation_search);
//
// END: Justice screen conviction search
//
#define SEARCH_HOOKS \
HOOK_ACTION(unitlist_search_hook) \
@ -2350,6 +2430,8 @@ IMPLEMENT_HOOKS(df::viewscreen_layer_stone_restrictionst, stone_search);
HOOK_ACTION(location_assign_occupation_search_hook) \
HOOK_ACTION(kitchen_pref_search_hook) \
HOOK_ACTION(stone_search_hook) \
HOOK_ACTION(justice_conviction_search_hook) \
HOOK_ACTION(justice_interrogation_search_hook) \
DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable)

@ -417,7 +417,7 @@ void StockpileSerializer::serialize_list_itemdef ( FuncWriteExport add_value, s
{
const df::itemdef *a = items.at ( i );
// skip procedurally generated items
if ( a->base_flags.is_set ( 0 ) ) continue;
if ( a->base_flags.is_set ( df::itemdef_flags::GENERATED ) ) continue;
ItemTypeInfo ii;
if ( !ii.decode ( type, i ) ) continue;
add_value ( ii.getToken() );

@ -179,8 +179,8 @@ static map<df::item *, bool> items_in_cages;
static df::job *get_item_job(df::item *item)
{
auto ref = Items::getSpecificRef(item, specific_ref_type::JOB);
if (ref && ref->job)
return ref->job;
if (ref && ref->data.job)
return ref->data.job;
return nullptr;
}
@ -1016,12 +1016,12 @@ private:
if (item->flags.bits.in_job)
{
auto ref = Items::getSpecificRef(item, specific_ref_type::JOB);
if (ref && ref->job)
if (ref && ref->data.job)
{
if (ref->job->job_type == job_type::Eat || ref->job->job_type == job_type::Drink)
if (ref->data.job->job_type == job_type::Eat || ref->data.job->job_type == job_type::Drink)
return pos;
auto unit = Job::getWorker(ref->job);
auto unit = Job::getWorker(ref->data.job);
if (unit)
return unit->pos;
}

@ -1 +1 @@
Subproject commit 4fdb2be54365442b8abea86f21746795f83fbdc2
Subproject commit 5b7e7743a1372b929acb2ccb75080e139ac11691

@ -137,7 +137,7 @@ int getCreatedMetalBars (int32_t idx)
return 0;
}
void selectWord (const df::language_word_table &table, int32_t &word, df::enum_field<df::part_of_speech,int16_t> &part, int mode)
void selectWord (const df::language_word_table &table, int32_t &word, df::part_of_speech &part, int mode)
{
if (table.parts[mode].size())
{
@ -174,7 +174,7 @@ void generateName(df::language_name &output, int language, int mode, const df::l
case 0: case 9: case 10:
if (mode != 9)
{
int32_t word; df::enum_field<df::part_of_speech,int16_t> part;
int32_t word; df::part_of_speech part;
output.first_name.clear();
selectWord(table1, word, part, 2);
if (word >= 0 && size_t(word) < world->raws.language.words.size())

@ -1,22 +1,24 @@
#include "df/viewscreen_setupdwarfgamest.h"
using namespace DFHack;
struct embark_profile_name_hook : df::viewscreen_setupdwarfgamest {
typedef df::viewscreen_setupdwarfgamest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input))
{
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input)) {
int ch = -1;
for (auto it = input->begin(); ch == -1 && it != input->end(); ++it)
for (auto it = input->begin(); ch == -1 && it != input->end(); ++it) {
ch = Screen::keyToChar(*it);
if (in_save_profile && ch >= 32 && ch <= 126)
{
profile_name.push_back((char)ch);
}
else
{
if (input->count(df::interface_key::LEAVESCREEN))
// Intercept all printable characters except space.
// If space is intercepted the shift-space abort key will not work.
if (in_save_profile && ch >= 33 && ch <= 126) {
profile_name.push_back((char)ch);
} else {
if (input->count(df::interface_key::LEAVESCREEN)) {
input->insert(df::interface_key::SETUPGAME_SAVE_PROFILE_ABORT);
}
INTERPOSE_NEXT(feed)(input);
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(embark_profile_name_hook, feed);

@ -1151,10 +1151,10 @@ static bool itemInRealJob(df::item *item)
return false;
auto ref = Items::getSpecificRef(item, specific_ref_type::JOB);
if (!ref || !ref->job)
if (!ref || !ref->data.job)
return true;
return ENUM_ATTR(job_type, type, ref->job->job_type)
return ENUM_ATTR(job_type, type, ref->data.job->job_type)
!= job_type_class::Hauling;
}

@ -1,75 +0,0 @@
.rodata:08773064 t_building_templest
.rodata:08772FE4 t_building_dark_towerst
.rodata:08772EE4 t_building_home_apartment_roomst
.rodata:08772F24 t_building_home_apartmentst
.rodata:08772F64 t_building_home_singlest
.rodata:08773024 t_building_keepst
.rodata:08772FA4 t_building_mead_hallst
.rodata:087730A4 t_building_storest
.rodata:08776784 23building_constructionst
.rodata:087771E4 21building_road_pavedst
.rodata:08777064 20building_road_dirtst
.rodata:08777AC4 15building_roadst
.rodata:08779424 16building_wagonst
.rodata:087792A4 21building_tradedepotst
.rodata:087789C4 19building_workshopst
.rodata:08778E44 18building_furnacest
.rodata:08778244 21building_animaltrapst
.rodata:08778FC4 19building_farmplotst
.rodata:08777644 17building_windowst
.rodata:087777C4 17building_statuest
.rodata:08777944 15building_wellst
.rodata:08777364 17building_coffinst
.rodata:087795A4 15building_shopst
.rodata:087783C4 16building_chairst
.rodata:08777C44 16building_tablest
.rodata:08777C44 14building_bedst
.rodata:08778B44 22building_siegeenginest
.rodata:08776D64 15building_cagest
.rodata:08776EE4 16building_chainst
.rodata:08776184 19building_windmillst
.rodata:08776304 22building_water_wheelst
.rodata:08776004 21building_screw_pumpst
.rodata:08778844 24building_archerytargetst
.rodata:08778544 17building_weaponst
.rodata:087786C4 18building_supportst
.rodata:08776604 24building_axle_verticalst
.rodata:08776484 26building_axle_horizontalst
.rodata:08776BE4 24building_gear_assemblyst
.rodata:08778CC4 15building_trapst
.rodata:08779EA4 21building_bars_floorst
.rodata:0877A024 24building_bars_verticalst
.rodata:0877A324 22building_grate_floorst
.rodata:0877A1A4 21building_grate_wallst
.rodata:0877A4A4 20building_floodgatest
.rodata:08779D24 17building_bridgest
.rodata:08779A24 16building_hatchst
.rodata:08779BA4 15building_doorst
.rodata:08777DC4 21building_armorstandst
.rodata:08777F44 21building_weaponrackst
.rodata:087798A4 18building_cabinetst
.rodata:08779724 14building_boxst
.rodata:08776A64 17building_actualst
.rodata:08779144 18building_civzonest
.rodata:087774E4 20building_stockpilest
0:FFFFFFFF 21building_window_gemst
0:FFFFFFFF 23building_window_glassst
.rodata:08787664 t_building_interactst
.rodata:08788DE4 n_building_selectorst
.rodata:08788E44 n_building_permit_foreign_armorst
.rodata:08788E24 n_building_permit_itemst
.rodata:08788E64 n_building_permit_foreign_siegeammost
.rodata:08788E84 n_building_permit_foreign_weaponst
.rodata:08788EA4 n_building_permit_trapcompst
.rodata:08788F04 n_building_new_jobst
.rodata:08788EC4 n_building_category_selectorst
.rodata:08788EE4 n_building_material_selectorst
0:FFFFFFFF f_building_well_tagst
0:FFFFFFFF E_BUILDING_TEMPLE
0:FFFFFFFF E_BUILDING_KEEP
0:FFFFFFFF f_building_civzone_assignedst
0:FFFFFFFF f_building_triggerst
0:FFFFFFFF f_building_triggertargetst
0:FFFFFFFF f_building_chainst
0:FFFFFFFF f_building_cagedst
0:FFFFFFFF f_building_holderst

@ -1,5 +0,0 @@
FF - retractable
00 - west
01 - east
02 - north
03 - south

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Some files were not shown because too many files have changed in this diff Show More