Merge branch 'develop' into lua-ref-target

develop
lethosor 2020-04-27 23:24:29 -04:00
commit 9e085b66ac
35 changed files with 588 additions and 190 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

@ -1,64 +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
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 -DBUILD_TESTS:BOOL=ON -DCMAKE_INSTALL_PREFIX="$DF_FOLDER"
- ninja -j3 install
- mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init
- cd ..
- python travis/run-tests.py --headless --keep-status "$DF_FOLDER"
- python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt"
- cat "$DF_FOLDER/test_status.json"
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)
@ -174,8 +180,8 @@ endif()
# set up versioning.
set(DF_VERSION "0.47.04")
set(DFHACK_RELEASE "beta1")
set(DFHACK_PRERELEASE TRUE)
set(DFHACK_RELEASE "r1")
set(DFHACK_PRERELEASE FALSE)
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")

@ -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)))

@ -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>

@ -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
@ -116,6 +117,7 @@ Priit Laes plaes
Putnam Putnam3145
Quietust quietust _Q
Raidau Raidau
Ralph Bisschops ralpha
Ramblurr Ramblurr
rampaging-poet
Raoul van Putten

@ -1292,6 +1292,12 @@ Items module
Returns true *x,y,z* of the item, or *nil* if invalid; may be not equal to item.pos if in inventory.
* ``dfhack.items.getBookTitle(item)``
Returns the title of the "book" item, or an empty string if the item isn't a "book" or it doesn't
have a title. A "book" is a codex or a tool item that has page or writings improvements, such as
scrolls and quires.
* ``dfhack.items.getDescription(item, type[, decorate])``
Returns the string description of the item, as produced by the ``getItemDescription``

@ -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:

@ -39,9 +39,24 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
# Future
# 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.
## Lua
- ``pairs()`` now returns available class methods for DF types

@ -235,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')

@ -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(); \

@ -1766,6 +1766,7 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPM(Items, getContainer),
WRAPM(Items, getHolderBuilding),
WRAPM(Items, getHolderUnit),
WRAPM(Items, getBookTitle),
WRAPM(Items, getDescription),
WRAPM(Items, isCasteMaterial),
WRAPM(Items, getSubtypeCount),

@ -68,6 +68,8 @@ namespace DFHack
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,
@ -164,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;
};

@ -159,6 +159,11 @@ DFHACK_EXPORT df::unit *getHolderUnit(df::item *item);
/// Returns the true position of the item.
DFHACK_EXPORT df::coord getPosition(df::item *item);
/// Returns the title of a codex or "tool", either as the codex title or as the title of the
/// first page or writing it has that has a non blank title. An empty string is returned if
/// no title is found (which is the case for everything that isn't a "book").
DFHACK_EXPORT std::string getBookTitle(df::item *item);
/// Returns the description string of the item.
DFHACK_EXPORT std::string getDescription(df::item *item, int type = 0, bool decorate = false);

@ -59,6 +59,8 @@ using namespace std;
#include "df/general_ref_unit_holderst.h"
#include "df/historical_entity.h"
#include "df/item.h"
#include "df/item_bookst.h"
#include "df/item_toolst.h"
#include "df/item_type.h"
#include "df/itemdef_ammost.h"
#include "df/itemdef_armorst.h"
@ -74,6 +76,9 @@ using namespace std;
#include "df/itemdef_toyst.h"
#include "df/itemdef_trapcompst.h"
#include "df/itemdef_weaponst.h"
#include "df/itemimprovement.h"
#include "df/itemimprovement_pagesst.h"
#include "df/itemimprovement_writingst.h"
#include "df/job_item.h"
#include "df/mandate.h"
#include "df/map_block.h"
@ -90,6 +95,7 @@ using namespace std;
#include "df/viewscreen_itemst.h"
#include "df/world.h"
#include "df/world_site.h"
#include "df/written_content.h"
using namespace DFHack;
using namespace df::enums;
@ -658,7 +664,7 @@ df::coord Items::getPosition(df::item *item)
switch (ref->type)
{
case specific_ref_type::VERMIN_ESCAPED_PET:
return ref->data.VERMIN_ESCAPED_PET->pos;
return ref->data.vermin->pos;
default:
break;
@ -681,6 +687,87 @@ static void addQuality(std::string &tmp, int quality)
}
}
// It's not impossible the functionality of this operation is provided by one of the unmapped item functions.
std::string Items::getBookTitle(df::item *item)
{
CHECK_NULL_POINTER(item);
std::string tmp;
if (item->getType() == df::item_type::BOOK)
{
auto book = virtual_cast<df::item_bookst>(item);
if (book->title != "")
{
return book->title;
}
else
{
for (size_t i = 0; i < book->improvements.size(); i++)
{
if (auto page = virtual_cast<df::itemimprovement_pagesst>(book->improvements[i]))
{
for (size_t k = 0; k < page->contents.size(); k++)
{
df::written_content *contents = world->written_contents.all[page->contents[k]];
if (contents->title != "")
{
return contents->title;
}
}
}
else if (auto writing = virtual_cast<df::itemimprovement_writingst>(book->improvements[i]))
{
for (size_t k = 0; k < writing->contents.size(); k++)
{
df::written_content *contents = world->written_contents.all[writing->contents[k]];
if (contents->title != "")
{
return contents->title;
}
}
}
}
}
}
else if (item->getType() == df::item_type::TOOL)
{
auto book = virtual_cast<df::item_toolst>(item);
if (book->hasToolUse(df::tool_uses::CONTAIN_WRITING))
{
for (size_t i = 0; i < book->improvements.size(); i++)
{
if (auto page = virtual_cast<df::itemimprovement_pagesst>(book->improvements[i]))
{
for (size_t k = 0; k < page->contents.size(); k++)
{
df::written_content *contents = world->written_contents.all[page->contents[k]];
if (contents->title != "")
{
return contents->title;
}
}
}
else if (auto writing = virtual_cast<df::itemimprovement_writingst>(book->improvements[i]))
{
for (size_t k = 0; k < writing->contents.size(); k++)
{
df::written_content *contents = world->written_contents.all[writing->contents[k]];
if (contents->title != "")
{
return contents->title;
}
}
}
}
}
}
return "";
}
std::string Items::getDescription(df::item *item, int type, bool decorate)
{
CHECK_NULL_POINTER(item);

@ -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->data.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->data.JOB = job;
item_link->data.job = job;
item->specific_refs.push_back(item_link);
auto job_link = new df::job_item_ref();

@ -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();
@ -206,18 +203,17 @@ string Translation::TranslateName(const df::language_name * name, bool inEnglish
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]]);
}
}
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)
{

@ -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;
}
}
}

@ -1 +1 @@
Subproject commit 596e3032417fb3d8dbe7d62a544ddd415ae2d89f
Subproject commit 0792fc0202fb6a04bfdaa262bc36a3b14c8581e5

@ -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.

@ -67,6 +67,11 @@ bool Checker::queue_item(const QueueItem & item, CheckedStructure cs)
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;

@ -129,7 +129,6 @@ bool CheckedStructure::has_type_at_offset(const CheckedStructure & type, size_t
auto st = dynamic_cast<struct_identity *>(identity);
if (!st)
{
UNEXPECTED;
return false;
}

@ -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;
}

@ -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"},

@ -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);

@ -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"
@ -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() );

@ -30,7 +30,7 @@
#include "df/ui_advmode.h"
DFHACK_PLUGIN("stocks");
#define PLUGIN_VERSION 0.12
#define PLUGIN_VERSION 0.13
REQUIRE_GLOBAL(world);
@ -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->data.JOB)
return ref->data.JOB;
if (ref && ref->data.job)
return ref->data.job;
return nullptr;
}
@ -248,7 +248,11 @@ static string get_keywords(df::item *item)
static string get_item_label(df::item *item, bool trim = false)
{
auto label = Items::getDescription(item, 0, false);
auto label = Items::getBookTitle(item);
if (label == "")
{
label = Items::getDescription(item, 0, false);
}
if (trim && item->getType() == item_type::BIN)
{
auto pos = label.find("<#");
@ -562,7 +566,11 @@ class StockListColumn : public ListColumn<T>
if (!ListColumn<T>::showEntry(entry, search_tokens))
return false;
string item_name = toLower(Items::getDescription(entry->elem->entries[0], 0, false));
string item_name = toLower(Items::getBookTitle(entry->elem->entries[0]));
if (item_name == "")
{
item_name = toLower(Items::getDescription(entry->elem->entries[0], 0, false));
}
if ((match_start || match_end) && raw_search.size() > item_name.size())
return false;
@ -1008,12 +1016,12 @@ private:
if (item->flags.bits.in_job)
{
auto ref = Items::getSpecificRef(item, specific_ref_type::JOB);
if (ref && ref->data.JOB)
if (ref && ref->data.job)
{
if (ref->data.JOB->job_type == job_type::Eat || ref->data.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->data.JOB);
auto unit = Job::getWorker(ref->data.job);
if (unit)
return unit->pos;
}

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

@ -1 +1 @@
Subproject commit 8618cd0b0a17935fe07f329b249726cda61f5bdf
Subproject commit 2079b9fb69b8b4db48aa35ec54a96f5cca7cc8ef

@ -1,2 +0,0 @@
#!/bin/sh
git log --pretty="commit %h (parents: %p): %s" -1

@ -1,18 +0,0 @@
import os, sys
repo = os.environ.get('TRAVIS_REPO_SLUG', 'dfhack/dfhack').lower()
branch = os.environ.get('TRAVIS_BRANCH', 'master')
try:
pr_id = int(os.environ.get('TRAVIS_PULL_REQUEST', 'false'))
except ValueError:
print('Not a pull request')
sys.exit(0)
print('Pull request %s#%i' % (repo, pr_id))
if repo != 'dfhack/dfhack':
print('Not in dfhack/dfhack')
sys.exit(0)
if branch != 'develop':
print('Not based on develop branch')
sys.exit(1)
else:
print('Ok')
sys.exit(0)