From ee999ccbdf8c68175195b604ef5988ddda7b4956 Mon Sep 17 00:00:00 2001 From: Pauli Date: Sat, 9 Jun 2018 12:02:42 +0300 Subject: [PATCH 01/66] Implement runtime debug print filtering The runtime debug print filtering support dynamic debug print selection. Tis patch only implements basic core support for filtering. The commands to change the runtime filtering settings will be added in a following patch. But even with only this one can change filtering settings by editing memory using a debugger. It can even be automated by using gdb break point commands. --- library/CMakeLists.txt | 4 + library/Debug.cpp | 186 ++++++++ library/include/Debug.h | 369 ++++++++++++++++ library/include/DebugManager.h | 111 +++++ library/include/Signal.hpp | 782 +++++++++++++++++++++++++++++++++ 5 files changed, 1452 insertions(+) create mode 100644 library/Debug.cpp create mode 100644 library/include/Debug.h create mode 100644 library/include/DebugManager.h create mode 100644 library/include/Signal.hpp diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 390efdcfe..416e42c41 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -28,6 +28,8 @@ include/Core.h include/ColorText.h include/DataDefs.h include/DataIdentity.h +include/Debug.h +include/DebugManager.h include/VTableInterpose.h include/LuaWrapper.h include/LuaTools.h @@ -38,6 +40,7 @@ include/MiscUtils.h include/Module.h include/Pragma.h include/MemAccess.h +include/Signal.hpp include/TileTypes.h include/Types.h include/VersionInfo.h @@ -55,6 +58,7 @@ SET(MAIN_SOURCES Core.cpp ColorText.cpp DataDefs.cpp +Debug.cpp Error.cpp VTableInterpose.cpp LuaWrapper.cpp diff --git a/library/Debug.cpp b/library/Debug.cpp new file mode 100644 index 000000000..aa0cf8cdf --- /dev/null +++ b/library/Debug.cpp @@ -0,0 +1,186 @@ +/** +Copyright © 2018 Pauli + +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. + */ +#define _POSIX_C_SOURCE 200809L +#include "Core.h" + +#include "Debug.h" +#include "DebugManager.h" + +#include +#include +#include + +#ifdef _MSC_VER +static tm* localtime_r(const time_t* time, tm* result) +{ + localtime_s(result, time); + return result; +} +#endif + +namespace DFHack { +DBG_DECLARE(core,debug); + +void DebugManager::registerCategory(DebugCategory& cat) +{ + DEBUG(debug) << "register DebugCategory '" << cat.category() + << "' from '" << cat.plugin() + << "' allowed " << cat.allowed() << std::endl; + std::lock_guard guard(access_mutex_); + push_back(&cat); + categorySignal(CAT_ADD, cat); +} + +void DebugManager::unregisterCategory(DebugCategory& cat) +{ + DEBUG(debug) << "unregister DebugCategory '" << cat.category() + << "' from '" << cat.plugin() + << "' allowed " << cat.allowed() << std::endl; + std::lock_guard guard(access_mutex_); + auto iter = std::find(begin(), end(), &cat); + std::swap(*iter, back()); + pop_back(); + categorySignal(CAT_REMOVE, cat); +} + +DebugRegisterBase::DebugRegisterBase(DebugCategory* cat) +{ + // Make sure Core lives at least as long any DebugCategory to + // allow debug prints until all Debugcategories has been destructed + Core::getInstance(); + DebugManager::getInstance().registerCategory(*cat); +} + +void DebugRegisterBase::unregister(DebugCategory* cat) +{ + DebugManager::getInstance().unregisterCategory(*cat); +} + +static color_value selectColor(const DebugCategory::level msgLevel) +{ + switch(msgLevel) { + case DebugCategory::LTRACE: + return COLOR_GREY; + case DebugCategory::LDEBUG: + return COLOR_LIGHTBLUE; + case DebugCategory::LINFO: + return COLOR_CYAN; + case DebugCategory::LWARNING: + return COLOR_YELLOW; + case DebugCategory::LERROR: + return COLOR_LIGHTRED; + } + return COLOR_WHITE; +} + +#if __GNUC__ +// Allow gcc to optimize tls access. It also makes sure initialized is done as +// early as possible. The early initialization helps to give threads same ids as +// gdb shows. +#define EXEC_ATTR __attribute__((tls_model("initial-exec"))) +#else +#define EXEC_ATTR +#endif + +namespace { +static std::atomic nextId{0}; +static EXEC_ATTR thread_local uint32_t thread_id{nextId.fetch_add(1)+1}; +} + +DebugCategory::ostream_proxy_prefix::ostream_proxy_prefix( + const DebugCategory& cat, + color_ostream& target, + const DebugCategory::level msgLevel) : + color_ostream_proxy(target) +{ + color(selectColor(msgLevel)); + auto now = std::chrono::system_clock::now(); + tm local{}; + //! \todo c++ 2020 will have std::chrono::to_stream(fmt, system_clock::now()) + //! but none implements it yet. + std::time_t now_c = std::chrono::system_clock::to_time_t(now); + auto ms = std::chrono::duration_cast(now.time_since_epoch()) % 1000; + // Output time in format %02H:%02M:%02S.%03ms +#if __GNUC__ < 5 + // Fallback for gcc 4 + char buffer[32]; + size_t sz = strftime(buffer, sizeof(buffer)/sizeof(buffer[0]), + "%T.", localtime_r(&now_c, &local)); + *this << (sz > 0 ? buffer : "HH:MM:SS.") +#else + *this << std::put_time(localtime_r(&now_c, &local),"%T.") +#endif + << std::setfill('0') << std::setw(3) << ms.count() + // Thread id is allocated in the thread creation order to a thread_local + // variable + << ":t" << thread_id + // Output plugin and category names to make it easier to locate where + // the message is coming. It would be easy replaces these with __FILE__ + // and __LINE__ passed from the macro if that would be preferred prefix. + << ':' << cat.plugin() << ':' << cat.category() << ": "; +} + + +DebugCategory::level DebugCategory::allowed() const noexcept +{ + return allowed_.load(std::memory_order_relaxed); +} + +void DebugCategory::allowed(DebugCategory::level value) noexcept +{ + level old = allowed_.exchange(value, std::memory_order_relaxed); + if (old == value) + return; + TRACE(debug) << "modify DebugCategory '" << category() + << "' from '" << plugin() + << "' allowed " << value << std::endl; + auto& manager = DebugManager::getInstance(); + manager.categorySignal(DebugManager::CAT_MODIFIED, *this); +} + +DebugCategory::cstring_ref DebugCategory::category() const noexcept +{ + return category_; +} + +DebugCategory::cstring_ref DebugCategory::plugin() const noexcept +{ + return plugin_; +} + +#if __cplusplus < 201703L && __cpp_lib_atomic_is_always_lock_free < 201603 +//! C++17 has std::atomic::is_always_lock_free for static_assert. Older +//! standards only provide runtime checks if an atomic type is lock free +struct failIfEnumAtomicIsNotLockFree { + failIfEnumAtomicIsNotLockFree() { + std::atomic test; + if (test.is_lock_free()) + return; + std::cerr << __FILE__ << ':' << __LINE__ + << ": error: std::atomic should be lock free. Your compiler reports the atomic requires runtime locks. Either you are using a very old CPU or we need to change code to use integer atomic type." << std::endl; + std::abort(); + } +} failIfEnumAtomicIsNotLockFree; +#endif + +} diff --git a/library/include/Debug.h b/library/include/Debug.h new file mode 100644 index 000000000..976172c8d --- /dev/null +++ b/library/include/Debug.h @@ -0,0 +1,369 @@ +/** +Copyright © 2018 Pauli + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + */ + +#pragma once + +#include "ColorText.h" + +#include +#include "Core.h" + +namespace DFHack { + +/*! \file Debug.h + * Light weight wrappers to runtime debug output filtering. Idea is to add as + * little as possible code compared to debug output without filtering. The + * effect is archived using #TRACE, #DEBUG, #INFO, #WARN and #ERR macros. They + * "return" color_ostream object or reference that can be then used normally for + * either printf or stream style debug output. + * + * Internally macros do inline filtering check which allows compiler to have a + * fast path without debug output only checking unlikely condition. But if + * output is enabled then runtime code will jump to debug printing function + * calls. The macro setup code will also print standardized leading part of + * debug string including time stamp, plugin name and debug category name. + * + * \code{.cpp} + * #include "Debug.h" + * DBG_DECLARE(myplugin,init); + * + * DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) + * { + * command_result rv = CR_OK; + * DEBUG(init, out).print("initializing\n") + * if ((rv = initWork()) != CR_OK) { + * ERR(init, out) << "initWork failed with " + * << rv << " error code" << std::endl; + * return rv; + * } + * return rv + * } + * \endcode + * + * The debug print filtering levels can be changed using debugger. Following + * gdb example would automatically setup core/init and core/render to trace + * level when SDL_init is called. + * + * \code{.unparsed} + * break SDL_init + * commands + * silent + * p DFHack::debug::core::debug_init.allowed_ = 0 + * p DFHack::debug::core::debug_render.allowed_ = 0 + * c + * end + * \endcode + * + */ + +#ifndef __has_cpp_attribute +#define __has_cpp_attribute(x) 0 +#endif + +/*! + * \defgroup debug_branch_prediction Branch prediction helper macros + * Helper macro tells compiler that debug output branch is unlikely and should + * be optimized to cold section of the function. + * \{ + */ +#if __cplusplus >= 202000L || __has_cpp_attribute(likely) +// c++20 will have standard branch prediction hint attributes +#define likely(x) (x) [[likely]] +#define unlikely(x) (x) [[unlikely]] +#elif defined(__GNUC__) +// gcc has builtin functions that give hints to the branch prediction +#define likely(x) (__builtin_expect(!!(x), 1)) +#define unlikely(x) (__builtin_expect(!!(x), 0)) +#else +#define likely(x) (x) +#define unlikely(x) (x) +#endif +//! \} + +#ifdef NDEBUG +//! Reduce minimum compiled in debug levels if NDEBUG is defined +#define DBG_FILTER DFHack::DebugCategory::LINFO +#else +//! Set default compiled in debug levels to include all prints +#define DBG_FILTER DFHack::DebugCategory::LTRACE +#endif + +/*! + * DebugCategory is used to enable and disable debug messages in runtime. + * Declaration and definition are handled by #DBG_DECLARE and #DBG_DEFINE + * macros. Runtime filtering support is handled by #TRACE, #DEBUG, #INFO, #WARN + * and #ERR macros. + */ +class DFHACK_EXPORT DebugCategory final { +public: + //! type helper to maybe make it easier to convert to std::string_view when + //! c++17 can be required. + using cstring = const char*; + using cstring_ref = const char*; + /*! + * Debug level enum for message filtering + */ + enum level { + LTRACE = 0, + LDEBUG = 1, + LINFO = 2, + LWARNING = 3, + LERROR = 4, + }; + + /*! + * \param plugin the name of plugin the category belongs to + * \param category the name of category + * \param defaultLevel optional default filtering level for the category + */ + constexpr DebugCategory(cstring_ref plugin, + cstring_ref category, + level defaultLevel = LWARNING) noexcept : + plugin_{plugin}, + category_{category}, + allowed_{defaultLevel} + {} + + + DebugCategory(const DebugCategory&) = delete; + DebugCategory(DebugCategory&&) = delete; + DebugCategory& operator=(DebugCategory) = delete; + DebugCategory& operator=(DebugCategory&&) = delete; + + /*! + * Used by debug macros to check if message should be printed. + * + * It is defined in the header to allow compiler inline it and make disabled + * state a fast path without function calls. + * + * \param msgLevel the debug message level the following print belongs to + * \return boolean with true indicating that message should be printed + */ + bool isEnabled(const level msgLevel) const noexcept { + const uint32_t intLevel = static_cast(msgLevel); + // Compile time filtering to allow compiling out debug checks prints + // from binary. + return static_cast(DBG_FILTER) <= intLevel && + // Runtime filtering for debug messages + static_cast(allowed_.load(std::memory_order_relaxed)) <= intLevel; + } + + struct DFHACK_EXPORT ostream_proxy_prefix : public color_ostream_proxy { + ostream_proxy_prefix(const DebugCategory& cat, + color_ostream& target, + DebugCategory::level level); + ~ostream_proxy_prefix() + { + flush(); + } + }; + + /*! + * Fetch a steam object proxy object for output. It also adds standard + * message components like time and plugin and category names to the line. + * + * User must make sure that the line is terminated with a line end. + * + * \param msgLevel Specifies the level which next debug message belongs + * \return color_ostream_proxy that can be used to print the message + * \sa DFHack::Core::getConsole() + */ + ostream_proxy_prefix getStream(const level msgLevel) const + { + return {*this,Core::getInstance().getConsole(),msgLevel}; + } + /*! + * Add standard message components to existing output stream object to begin + * a new message line to an shared buffered object. + * + * \param msgLevel Specifies the level which next debug message belongs + * \param target An output stream object where a debug output is printed + * \return color_ostream reference that was passed as second parameter + */ + ostream_proxy_prefix getStream(const level msgLevel, color_ostream& target) const + { + return {*this,target,msgLevel}; + } + + /*! + * \brief Allow management code to set a new filtering level + * Caller must have locked DebugManager::access_mutex_. + */ + void allowed(level value) noexcept; + //! Query current filtering level + level allowed() const noexcept; + //! Query plugin name + cstring_ref plugin() const noexcept; + //! Query category name + cstring_ref category() const noexcept; +private: + + cstring plugin_; + cstring category_; + std::atomic allowed_; +#if __cplusplus >= 201703L || __cpp_lib_atomic_is_always_lock_free >= 201603 + static_assert(std::atomic::is_always_lock_free, + "std::atomic should be lock free. You are using a very old CPU or code needs to use std::atomic"); +#endif +}; + +/** + * Handle actual registering wrong template parameter generated pointer + * calculation. + */ +class DFHACK_EXPORT DebugRegisterBase { +protected: + DebugRegisterBase(DebugCategory* category); + void unregister(DebugCategory* category); +}; + +/** + * Register DebugCategory to DebugManager + */ +template +class DebugRegister final : public DebugRegisterBase { +public: + DebugRegister() : + DebugRegisterBase{category} + {} + ~DebugRegister() { + unregister(category); + } +}; + +#define DBG_NAME(category) debug_ ## category + + +/*! + * Declares a debug category. There must be only a declaration per category. + * Declaration should be in same plugin where it is used. If same category name + * is used in core and multiple plugins they all are changed with same command + * unless user specifies explicitly plugin name. + * + * Must be used in one translation unit only. + * + * \param plugin the name of plugin where debug category is used + * \param category the name of category + * \param level the initial DebugCategory::level filtering level. + */ +#define DBG_DECLARE(plugin,category, ...) \ + namespace debug { namespace plugin { \ + DebugCategory DBG_NAME(category){#plugin,#category,__VA_ARGS__}; \ + DebugRegister<&DBG_NAME(category)> register_ ## category; \ + } } \ + using debug::plugin::DBG_NAME(category) + +/*! + * Can be used to access a shared DBG_DECLARE category. But may not be used from + * static initializer because translation unit order is undefined. + * + * Can be used in shared headers to gain access to one definition from + * DBG_DECLARE. + * \param plugin The plugin name that must match DBG_DECLARE + * \param category The category name that must matnch DBG_DECLARE + */ +#define DBG_EXTERN(plugin,category) \ + namespace debug { namespace plugin { \ + extern DFHack::DebugCategory DBG_NAME(category); \ + } } \ + using debug::plugin::DBG_NAME(category) + +#define DBG_PRINT(category,pred,level,...) \ + if pred(!DFHack::DBG_NAME(category).isEnabled(level)) \ + ; /* nop fast path when debug category is disabled */ \ + else /* else to allow macro use in if-else branches */ \ + DFHack::DBG_NAME(category).getStream(level, ## __VA_ARGS__) \ +/* end of DBG_PRINT */ + +/*! + * Open a line for trace level debug output if enabled + * + * Preferred category for inside loop debug messages or callbacks/methods called + * multiple times per second. Good example would be render or onUpdate methods. + * + * \param category the debug category + * \param optional the optional second parameter is an existing + * color_ostream_proxy object + * \return color_ostream object that can be used for stream output + */ +#define TRACE(category, ...) DBG_PRINT(category, likely, \ + DFHack::DebugCategory::LTRACE, ## __VA_ARGS__) + +/*! + * Open a line for debug level debug output if enabled + * + * Preferred place to use it would be commonly called functions that don't fall + * into trace category. + * + * \param category the debug category + * \param optional the optional second parameter is an existing + * color_ostream_proxy object + * \return color_ostream object that can be used for stream output + */ +#define DEBUG(category, ...) DBG_PRINT(category, likely, \ + DFHack::DebugCategory::LDEBUG, ## __VA_ARGS__) + +/*! + * Open a line for error level debug output if enabled + * + * Important debug messages when some rarely changed state changes. Example + * would be when a debug category filtering level changes. + * + * \param category the debug category + * \param optional the optional second parameter is an existing + * color_ostream_proxy object + * \return color_ostream object that can be used for stream output + */ +#define INFO(category, ...) DBG_PRINT(category, likely, \ + DFHack::DebugCategory::LINFO, ## __VA_ARGS__) + +/*! + * Open a line for warning level debug output if enabled + * + * Warning category is for recoverable errors. This generally signals that + * something unusual happened but there is code handling the error which should + * allow df continue running without issues. + * + * \param category the debug category + * \param optional the optional second parameter is an existing + * color_ostream_proxy object + * \return color_ostream object that can be used for stream output + */ +#define WARN(category, ...) DBG_PRINT(category, unlikely, \ + DFHack::DebugCategory::LWARNING, ## __VA_ARGS__) + +/*! + * Open a line for error level error output if enabled + * + * Errors should be printed only for cases where plugin or dfhack can't recover + * from reported error and it requires manual handling from the user. + * + * \param category the debug category + * \param optional the optional second parameter is an existing + * color_ostream_proxy object + * \return color_ostream object that can be used for stream output + */ +#define ERR(category, ...) DBG_PRINT(category, unlikely, \ + DFHack::DebugCategory::LERROR, ## __VA_ARGS__) + +} diff --git a/library/include/DebugManager.h b/library/include/DebugManager.h new file mode 100644 index 000000000..fb4c635fb --- /dev/null +++ b/library/include/DebugManager.h @@ -0,0 +1,111 @@ +/** + Copyright © 2018 Pauli + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. + */ + +#pragma once + +#include "Export.h" +#include "Signal.hpp" + +#include +#include + +namespace DFHack { + +/*! \file DebugManager.h + * Expose an simple interface to runtime debug output filtering. The management + * interface is separated from output interface because output is required in + * many places while management is expected to be required only in a few places. + */ + +class DebugCategory; + +/*! + * \brief Container holding all registered runtime debug categories + * Singleton DebugManager is a minor extension to std::vector that allows signal + * callbacks to be attached from ui code that manages. + * + * To avoid parallel plugin unload causing issues access to DebugManager must be + * protected by mutex. The access mutex will be taken when + * DFHack::DebugCategory::~DebugCategory performs unregister calls to + * DFHack::DebugManager. The mutex will protect from memory disappearing while + * ui code is accessing or changing the runtime state. + * + * Signal emitting happens from a locked contexts. Taking the + * DFHack::DebugManager::access_mutex_ in a signal callback will results to a + * deadlock. + * + * The interface is extremely simple but enough to implement persistent filter + * states and runtime configuration code in a plugin. + */ +class DFHACK_EXPORT DebugManager : public std::vector { +public: + friend class DebugRegisterBase; + + //! access_mutex_ protects all readers and writers to DFHack::DebugManager + std::mutex access_mutex_; + + //! Different signals that all will be routed to + //! DebugManager::categorySignal + enum signalType { + CAT_ADD, + CAT_REMOVE, + CAT_MODIFIED, + }; + + //! type to help access signal features like Connection and BlockGuard + using categorySignal_t = Signal; + + /*! + * Signal object where callbacks can be connected. Connecting to a class + * method can use a lambda wrapper to the capture object pointer and correctly + * call required method. + * + * Signal is internally serialized allowing multiple threads call it + * freely. + */ + categorySignal_t categorySignal; + + //! Get the singleton object + static DebugManager& getInstance() { + static DebugManager instance; + return instance; + } + + //! Prevent copies + DebugManager(const DebugManager&) = delete; + //! Prevent copies + DebugManager(DebugManager&&) = delete; + //! Prevent copies + DebugManager& operator=(DebugManager) = delete; + //! Prevent copies + DebugManager& operator=(DebugManager&&) = delete; +protected: + DebugManager() = default; + + //! Helper for automatic category registering and signaling + void registerCategory(DebugCategory &); + //! Helper for automatic category unregistering and signaling + void unregisterCategory(DebugCategory &); +private: +}; +} diff --git a/library/include/Signal.hpp b/library/include/Signal.hpp new file mode 100644 index 000000000..acc8f4a0a --- /dev/null +++ b/library/include/Signal.hpp @@ -0,0 +1,782 @@ +/** +Copyright © 2018 Pauli + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +#ifdef __SSE__ +#include +#endif + +namespace DFHack { + +/*! + * Select inline implementation for Signal members + * This requires careful destruction order where all connection has been + * disconnected before Signal::~Signal() + */ +class signal_inline_tag; +/*! + * Select share_ptr managed implementation for Signal members. + * + * If Connection holding object may be deleted without full serialization + * between disconnect and signal emit the holding object must be managed by + * shared_ptr and derive from ConnectedBase. It will also have to pass the + * std::shared_ptr to connect. + + * It uses two way std::weak_ptr reference to guarantee destruction of either + * object doesn't happen when call is made to them. + * + * It is still possible to get a callback call after manual disconnect from + * outside destructor. But without destruction risk the disconnect race can be + * handled by slot implementation side. + */ +class signal_shared_tag; + +/** + * Used for signal_shared_tag holders that may race with destructor triggered + * disconnect and emit from Signal. + */ +class ConnectedBase { +}; + +template +class Signal; + +namespace details { + +template +struct SignalImpl; + +template +struct selectImpl; + +//! Manage callback states in thread safe manner +template +class CallbackHolderImpl; + +template +struct CallbackHolderBase { + using Callback = std::function; + + CallbackHolderBase(const Callback& cb) : + cb_{cb}, + state_{} + {} + + //! Block the connection + void block() noexcept + { + state_ += blocked; + } + + //! Unblock the connection + void unblock() noexcept + { + state_ -= blocked; + } + + //! Check if connection is deleted + bool erased() const noexcept + { + return state_ & deleted; + } + + //! Check if connection is still active (not blocked or erased) + operator bool() const noexcept + { + return !(state_ & ~inCall); + } + +protected: + //! Immutable callback object + const Callback cb_; + using state_t = unsigned; + //! Single shared state as a bitfield to simplify synchronization + //! between state changes. + std::atomic state_; + static constexpr state_t deleted = 0x1 << (sizeof(state_t)*CHAR_BIT - 1); + static constexpr state_t inCall = deleted >> (sizeof(state_t)*CHAR_BIT/2); + static constexpr state_t blocked = 0x1; + static constexpr state_t blockedMask = inCall - 1; + static constexpr state_t inCallMask = (deleted - 1) ^ blockedMask; +}; + +template +class CallbackHolderImpl : + public CallbackHolderBase { + using parent_t = CallbackHolderBase; +public: + using Callback = typename parent_t::Callback; +private: + using state_t = typename parent_t::state_t; + //! Make sure callback pointed object doesn't disappear under us + //! while we call it. + struct CallGuard { + + //! Prevent copies but allow copy elision + CallGuard(const CallGuard&); + + //! Allow implicit conversion to callback for simply syntax + const Callback& operator*() const noexcept + { + return holder_->cb_; + } + + operator bool() const noexcept + { + return *holder_; + } + + //! Mark call not to be called any more + ~CallGuard() { + holder_->state_ -= parent_t::inCall; + } + private: + //! Reference to the connection + CallbackHolderImpl* holder_; + + //! Mark call to be in process + CallGuard(CallbackHolderImpl* holder) : + holder_{holder} + { + holder_->state_ += parent_t::inCall; + } + //! Only allow construction from the CallbackHolderImpl::prepareCall + friend class CallbackHolderImpl; + }; +public: + //! Construct the callback state for a callback + CallbackHolderImpl(const Callback& cb) : + parent_t{cb} + {} + + /*! + * Data race free disconnection for the connection. It spins until + * no more callers to wait. Spinning should be problem as callbacks + * are expected to be simple and fast to execute. + * + * Must not be called from withing callback! + * + * \todo Maybe use monitor instruction to avoid busy wait and call + * std::thread::yield() if wait is longer than expected. + */ + void erase() noexcept + { + state_t oldstate; + state_t newstate; + /** Spin until no callers to this callback */ +spin: + while ((oldstate = parent_t::state_) & parent_t::inCallMask) { + // pause would be portable to all old processors but there + // isn't portable way to generate it without SSE header. +#ifdef __SSE__ + _mm_pause(); +#endif + } + do { + if (oldstate & parent_t::inCallMask) + goto spin; + newstate = oldstate | parent_t::deleted; + } while(!parent_t::state_.compare_exchange_weak(oldstate, newstate)); + } + + //! Return RAII CallGuard to protect race between callback and + //! disconnect. + CallGuard prepareCall() + { + return {this}; + } +}; + +template +class CallbackHolderImpl : + public CallbackHolderBase { + using parent_t = CallbackHolderBase; +public: + using Callback = typename parent_t::Callback; +private: + using state_t = typename parent_t::state_t; + //! Make sure callback pointed object doesn't disappear under us + //! while we call it. + struct CallGuard { + + //! Prevent copies but allow copy elision + CallGuard(const CallGuard&); + + //! Allow implicit conversion to callback for simply syntax + const Callback& operator*() const noexcept + { + return holder_->cb_; + } + + operator bool() const noexcept + { + // If this is not marked erased then weak_ref->lock succeeded or + // the slot isn't managed by shared_ptr + return *holder_; + } + + private: + //! Reference to the connection + CallbackHolderImpl* holder_; + std::shared_ptr strong_ref_; + + //! Mark call to be in process + CallGuard(CallbackHolderImpl* holder) : + holder_{holder}, + strong_ref_{holder->weak_ref_.lock()} + { + } + //! Only allow construction from the CallbackHolderImpl::prepareCall + friend class CallbackHolderImpl; + }; + + std::weak_ptr weak_ref_; + friend CallGuard; +public: + //! Construct the callback state for an automatically synchronized object + CallbackHolderImpl(const Callback& cb, + std::shared_ptr& ref) : + parent_t{cb}, + weak_ref_{ref} + {} + + //! Construct the callback state for an externally synchronized object + CallbackHolderImpl(const Callback& cb) : + parent_t{cb}, + weak_ref_{} + {} + + /*! + * erase from destructor can't happen while we are in call because + */ + void erase() noexcept + { + parent_t::state_ |= parent_t::deleted; + } + + //! Return RAII CallGuard to protect race between callback and + //! disconnect. + CallGuard prepareCall() + { + return {this}; + } +}; + +template +struct SignalImpl : public selectImpl::parent_t { +protected: + using select_t = selectImpl; + using parent_t = typename select_t::parent_t; +public: + using CallbackHolder = CallbackHolderImpl; + using Callback = typename CallbackHolder::Callback; + + //! The container type used to store callbacks + using CallbackContainer = std::list; + struct BlockGuard; + + //! Simple connection class that is required to disconnect from the + //! signal. + struct Connection { + //! Construct a default Connection object but using it will result + //! to undefined behavior unless proper connection is assigned to it + Connection() = default; + + Connection(Connection&& o) : + iter_{o.iter_}, + signal_{} + { + std::swap(signal_, o.signal_); + } + + Connection& operator=(Connection&& o) + { + disconnect(); + iter_ = o.iter_; + std::swap(signal_, o.signal_); + return *this; + } + + Connection(const Connection&) = delete; + Connection& operator=(const Connection&) = delete; + + //! Disconnect from signal + void disconnect() + { + auto s = select_t::lock(signal_); + if (!s) + return; + + s->disconnect(*this); + } + + ~Connection() + { + disconnect(); + } + private: + //! Block the connection temporary + void block() + { + auto s = select_t::lock(signal_); + if (!s) + return; + iter_->block(); + } + + //! Restore blocked connection + void unblock() + { + auto s = select_t::lock(signal_); + if (!s) + return; + iter_->unblock(); + } + + //! Construct connection object + Connection(const typename CallbackContainer::iterator &iter, + typename select_t::weak_ptr ptr) : + iter_{iter}, + signal_{ptr} + {} + + //! std::list iterator that is used to access the callback and allow + //! removal from the list. + typename CallbackContainer::iterator iter_; + //! Reference to signal object + typename select_t::weak_ptr signal_; + friend SignalImpl; + friend BlockGuard; + }; + + /*! + * BlockGuard allows temporary RAII guard managed blocking of a + * connection object. + */ + struct BlockGuard { + /*! + * Block a connection that belongs to signal + * \param connection The connection that will be temporary blocked + */ + BlockGuard(Connection& connection) : + blocked_{&connection} + { + connection.block(); + } + + /*! + * Unblock the temporary blocked connection + */ + ~BlockGuard() + { + blocked_->unblock(); + } + + //! Prevent copies but allow copy elision + BlockGuard(const BlockGuard&); + private: + Connection* blocked_; + }; + + Connection connect(const Callback& f) + { + std::lock_guard lock(access_); + auto iter = callbacks_.emplace(callbacks_.begin(), f); + return {iter, parent_t::shared_from_this()}; + } + + Connection connect(std::shared_ptr c, const Callback& f) + { + std::lock_guard lock(access_); + auto iter = callbacks_.emplace(callbacks_.begin(), f, c); + return {iter, parent_t::shared_from_this()}; + } + + void disconnect(Connection& connection) { + std::lock_guard lock(access_); + if (recursion_) { + deleted_ = true; + connection.iter_->erase(); + } else { + callbacks_.erase(connection.iter_); + } + select_t::reset(connection.signal_); + } + + template + void operator()(Combiner &combiner, Args&&... arg) + { + std::unique_lock lock(access_); + struct RecursionGuard { + SignalImpl* signal_; + std::unique_lock* lock_; + //! Increment access count to make sure disconnect doesn't erase + RecursionGuard(SignalImpl *signal, std::unique_lock* lock) : + signal_{signal}, + lock_{lock} + { + ++signal_->recursion_; + } + + /*! + * Clean up deleted functions in data race free and exception + * safe manner. + */ + ~RecursionGuard() + { + lock_->lock(); + if (--signal_->recursion_ == 0 && signal_->deleted_) { + for (auto iter = signal_->callbacks_.begin(); iter != signal_->callbacks_.end();) { + if (iter->erased()) + iter = signal_->callbacks_.erase(iter); + else + ++iter; + } + signal_->deleted_ = false; + } + } + + } guard{this, &lock}; + // Call begin in locked context to allow data race free iteration + // even if there is parallel inserts to the begin after unlocking. + auto iter = callbacks_.begin(); + lock.unlock(); + for (; iter != callbacks_.end(); ++iter) { + // Quickly skip blocked calls without memory writes + if (!*iter) + continue; + // Protect connection from deletion while we are about to call + // it. + auto cb = iter->prepareCall(); + if (cb) + combiner(*cb, std::forward(arg)...); + } + } + + void operator()(Args&&... arg) + { + auto combiner = [](const Callback& cb, Args&&... arg2) + { + cb(std::forward(arg2)...); + }; + (*this)(combiner,std::forward(arg)...); + } + + ~SignalImpl() { + // Check that callbacks are empty. If this triggers then signal may + // have to be extended to allow automatic disconnection of active + // connections in the destructor. + if (std::is_same::value) + assert(callbacks_.empty() && "It is very likely that this signal should use signal_shared_tag"); + } + + //! Simplify access to pimpl when it is inline + SignalImpl* operator->() { + return this; + } + SignalImpl& operator*() { + return *this; + } + + SignalImpl() = default; +private: + SignalImpl(const SignalImpl&) : + SignalImpl{} + {} + std::mutex access_; + CallbackContainer callbacks_; + int recursion_; + bool deleted_; + friend Signal; +}; + +template +struct selectImpl { + using impl_t = SignalImpl; + using interface_t = Signal; + using type = impl_t; + using weak_ptr = impl_t*; + struct ptr_from_this { + weak_ptr shared_from_this() + { + return static_cast(this); + } + }; + using parent_t = ptr_from_this; + + selectImpl() = default; + + // Disallow copies for inline version. + selectImpl(const selectImpl&) = delete; + selectImpl(selectImpl&&) = delete; + selectImpl& operator=(const selectImpl&) = delete; + selectImpl& operator=(selectImpl&&) = delete; + + + static type make() { + return {}; + } + + static void reset(weak_ptr& ptr) { + ptr = nullptr; + } + + static weak_ptr lock(weak_ptr& ptr) { + return ptr; + } + + static weak_ptr get(interface_t& signal) { + return &signal.pimpl; + } +}; + +template +struct selectImpl { + using impl_t = SignalImpl; + using interface_t = Signal; + using type = std::shared_ptr; + using weak_ptr = std::weak_ptr; + using parent_t = std::enable_shared_from_this; + + // Allow copies for shared version + + static type make() { + return std::make_shared>(); + } + + static void reset(weak_ptr& ptr) { + ptr.reset(); + } + + static type lock(weak_ptr& ptr) { + return ptr.lock(); + } + + static weak_ptr get(interface_t& signal) { + return signal.pimpl; + } +}; +} + +/*! + * As I couldn't figure out which signal library would be a good. Too bad all + * signal libraries seem to be either heavy with unnecessary features or written + * before C++11/14 have become useable targets. That seems to indicate everyone + * is now building signal system with standard components. + * + * Implementation and interface is build around std::function holding delegates + * to a function pointer or a functor. One can put there example lambda function + * that captures this pointer from connect side. The lambda function then calls + * the slot method of object correctly. + * + * It is fairly simple to change the signal signature to directly call methods + * but internally that std::function becomes more complex. The pointer to + * member function is problematic because multiple inheritance requires + * adjustments to this. The lambda capture approach should be easy to use while + * letting compiler optimize method call in the callee side. + * + * DFHack::Signal::Connection is an connection handle. The handle can be used to + * disconnect and block a callback. Connection destructor will automatically + * disconnect from the signal. + * + * DFHack::Signal::BlockGuard is an automatic blocked callback guard object. It + * prevents any signals from calling the slot as long the BlockGuard object is + * alive. Internally it replaces the callback with an empty callback and stores + * the real callback in a member variable. Destructor then puts back the real + * callback. This allows easily recursive BlockGuard work correctly because only + * the first BlockGuard has the real callback. + * + * signal_inline_tag requires careful destruction order where all connection are + * disconnected before signal destruction. The implementation is specifically + * targeting places like static and singleton variables and widget hierarchies. + * It provides data race free connect, disconnect and emit operations. + * + * signal_shared_tag allows a bit more freedom when destroying the Signal. It + * adds data race safety between Connection, BlockGuard and destructor. If + * multiple callers need access to Signal with potential of destruction of + * original owner then callers can use Signal copy constructor to take a strong + * reference managed by shared_ptr or weak_ptr with Signal::weak_from_this(). + * weak_from_this returns an object that forwards call directly to + * implementation when the shared_ptr is created using Signal::lock + * + * \param RT return type is derived from a single signature template argument + * \param Args Variable argument type list that is derived from a signature + * template argument. + * \param tag The tag type which selects between shared_ptr managed pimpl and + * inline member variables. + */ +template +class Signal : protected details::selectImpl { +public: + //! Type of callable that can be connected to the signal. + using Callback = std::function; + +protected: + using select_t = details::selectImpl; + using CallbackContainer = typename select_t::impl_t::CallbackContainer; +public: + using weak_ptr = typename select_t::weak_ptr; + + /*! + * Simple connection class that is required to disconnect from the + * signal. + * \sa SignalImpl::Connection + */ + using Connection = typename select_t::impl_t::Connection; + /*! + * BlockGuard allows temporary RAII guard managed blocking of a + * connection object. + * \sa SignalImpl::BlockGuard + */ + using BlockGuard = typename select_t::impl_t::BlockGuard; + + /*! + * Connect a callback function to the signal + * + * Safe to call from any context as long as SignalImpl destructor can't be + * called simultaneously from other thread. + * + * \param f callable that will connected to the signal + * \return connection handle that can be used to disconnect it + */ + Connection connect(const Callback& f) + { + return pimpl->connect(f); + } + + /*! + * Thread safe connect variant connection and Connected object destruction + * can't race with emit from different threads. + * + * Safe to call from any context as long as SignalImpl destructor can't be + * called simultaneously from other thread. + */ + Connection connect(std::shared_ptr c, const Callback& f) + { + static_assert(std::is_same::value, + "Race free destruction is only possible with signal_shared_tag"); + return pimpl->connect(c, f); + } + + /*! + * Disconnection a callback from slots + * + * signal_inline_tag: + * This may not be called if the callback has been called in same + * thread. If callback should trigger destruction an object then + * deletion must use deferred. This rule prevents issues if other thread + * are trying to call the callback when disconnecting. + * + * signal_shared_tag: + * disconnect can be freely called from anywhere as long as caller holds a + * strong reference to the Signal. Strong reference can be obtained by using + * Connection::disconnect, Signal copy constructor to have a copy of signal + * or weak_ptr from weak_from_this() passed to Signal::lock(). + * + * \param connection the object returned from DFHack::Signal::connect + */ + void disconnect(Connection& connection) + { + pimpl->disconnect(connection); + } + + /*! + * Call all connected callbacks using passed arguments. + * + * signal_inline_tag: + * Must not call operator() from callbacks. + * Must not disconnect called callback from inside callback. Solution often + * is to set just atomic state variables in callback and do actual + * processing including deletion in update handler or logic vmethod. + * + * signal_shared_tag: + * Safe to call from any context as long as SignalImpl destructor can't be + * called simultaneously from other thread. + * Safe to disconnect any connection from callbacks. + * + * \param combiner that calls callbacks and processes return values + * \param arg arguments list defined by template parameter signature. + */ + template + void operator()(Combiner &combiner, Args&&... arg) + { + (*pimpl)(combiner, std::forward(arg)...); + } + + /*! + * Call all connected callbacks using passed arguments. + * + * signal_inline_tag: + * Must not call operator() from callbacks. + * Must not disconnect called callback from inside callback. Solution often + * is to set just atomic state variables in callback and do actual + * processing including deletion in update handler or logic vmethod. + * + * signal_shared_tag: + * Safe to call from any context as long as SignalImpl destructor can't be + * called simultaneously from other thread. + * Safe to disconnect any connection from callbacks. + * + * \param arg arguments list defined by template parameter signature. + */ + void operator()(Args&&... arg) + { + (*pimpl)(std::forward(arg)...); + } + + /*! + * Helper to lock the weak_ptr + */ + static typename select_t::type lock(weak_ptr& ptr) + { + return select_t::lock(ptr); + } + + /*! + * Helper to create a weak reference to pimpl which can be used to access + * pimpl directly. If the tag is signal_shared_tag then it provides race + * free access to Signal when using Signal::lock and checking returned + * shared_ptr. + */ + weak_ptr weak_from_this() noexcept + { + return select_t::get(*this); + } + + Signal() : + pimpl{select_t::make()} + {} +private: + typename select_t::type pimpl; + friend select_t; +}; +} From 8a3a05de242fdede47d832b4e453d7bec8a734fe Mon Sep 17 00:00:00 2001 From: Pauli Date: Sun, 10 Jun 2018 10:17:23 +0300 Subject: [PATCH 02/66] Allow unloading plugins that use std::regex --- library/CMakeLists.txt | 1 + library/CompilerWorkAround.cpp | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 library/CompilerWorkAround.cpp diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 416e42c41..d3bff72df 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -57,6 +57,7 @@ include/wdirent.h SET(MAIN_SOURCES Core.cpp ColorText.cpp +CompilerWorkAround.cpp DataDefs.cpp Debug.cpp Error.cpp diff --git a/library/CompilerWorkAround.cpp b/library/CompilerWorkAround.cpp new file mode 100644 index 000000000..402553bd8 --- /dev/null +++ b/library/CompilerWorkAround.cpp @@ -0,0 +1,32 @@ +#include + +namespace DFHack { +namespace neverCalled { + +/** + * gcc/linstdc++ seems to generate code that links libstdc++ back to first + * shared object using std::regex. To allow plugins unload with std::regex in + * the code we need the std::regex functions inside libdfhack.so. + * + * If your plugin decides to use any overloads that aren't listed here it may + * stay in memory after dlclose. + */ +std::regex stdRegexPluginUnloadWorkaround() +{ + std::regex fake("foo"); + std::string haystack("bar is foo in the world"); + std::regex fake2(std::string("bar")); + if (std::regex_match(haystack, fake)) + std::swap(fake, fake2); + if (std::regex_search(haystack, fake)) + std::swap(fake, fake2); + const char* haystack2 = "foo"; + if (std::regex_match(haystack2, fake)) + std::swap(fake, fake2); + if (std::regex_search(haystack2, fake)) + std::swap(fake, fake2); + return fake; +} + +} +} From 9cfb07f4760f7d99cc95109c06c44be8669bb731 Mon Sep 17 00:00:00 2001 From: Pauli Date: Sun, 10 Jun 2018 10:18:39 +0300 Subject: [PATCH 03/66] Add debug plugin to manage runtime debug filters --- plugins/CMakeLists.txt | 1 + plugins/debug.cpp | 1139 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1140 insertions(+) create mode 100644 plugins/debug.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index fbf458c4f..a05d680ea 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -107,6 +107,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(cursecheck cursecheck.cpp) DFHACK_PLUGIN(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(deramp deramp.cpp) + DFHACK_PLUGIN(debug debug.cpp LINK_LIBRARIES jsoncpp_lib_static) DFHACK_PLUGIN(dig dig.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp) add_subdirectory(diggingInvaders) diff --git a/plugins/debug.cpp b/plugins/debug.cpp new file mode 100644 index 000000000..9464305dd --- /dev/null +++ b/plugins/debug.cpp @@ -0,0 +1,1139 @@ +/** +Copyright © 2018 Pauli + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source + distribution. + */ + +#include "Core.h" +#include "PluginManager.h" +#include "DebugManager.h" +#include "Debug.h" +#include "modules/Filesystem.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +DFHACK_PLUGIN("debug"); + +namespace DFHack { +DBG_DECLARE(debug,filter); +DBG_DECLARE(debug,init); +DBG_DECLARE(debug,command); +DBG_DECLARE(debug,ui); +} + +namespace serialization { + +template +struct nvp : public std::pair { + using parent_t = std::pair; + nvp(const char* name, T& value) : + parent_t{name, &value} + {} +}; + +template +nvp make_nvp(const char* name, T& value) { + return {name, value}; +} + +} + +#define NVP(variable) serialization::make_nvp(#variable, variable) + +namespace Json { +template +typename std::enable_if::value, ET>::type +get(Json::Value& ar, const std::string &key, const ET& default_) +{ + return static_cast(as(ar.get(key, static_cast(default_)))); +} +} + +namespace DFHack { namespace debugPlugin { + +using JsonArchive = Json::Value; + +//! Write a named and type value to Json::Value. enable_if makes sure this is +//! only available for types that Json::Value supports directly. +template::value, int>::type = 0> +JsonArchive& operator<<(JsonArchive& ar, const serialization::nvp& target) +{ + ar[target.first] = *target.second; + return ar; +} + +//! Read a named and typed value from Json::Value +template +JsonArchive& operator>>(JsonArchive& ar, const serialization::nvp& target) +{ + *target.second = Json::get(ar, target.first, T{}); + return ar; +} + +/*! + * Default regex flags optimized for matching speed because objects are stored + * long time in memory. + */ +static constexpr auto defaultRegex = + std::regex::optimize | std::regex::nosubs | std::regex::collate; + +static const char* const commandHelp = + " Manage runtime debug print filters.\n" + "\n" + " debugfilter category [ []]\n" + " List categories matching regular expressions.\n" + " debugfilter filter []\n" + " List active filters or show detailed information for a filter.\n" + " debugfilter set [persistent] [ []]\n" + " Set a filter level to categories matching regular expressions.\n" + " debugfilter unset [ ...]\n" + " Unset filters matching space separated list of ids from 'filter'.\n" + " debugfilter disable [ ...]\n" + " Disable filters matching space separated list of ids from 'filter'.\n" + " debugfilter enable [ ...]\n" + " Enable filters matching space separated list of ids from 'filter'.\n" + " debugfilter help []\n" + " Show detailed help for a command or this help.\n"; +static const char* const commandCategory = + " category [ []]\n" + " List categories with optional filters. Parameters are passed to\n" + " std::regex to limit which once are shown. The first regular\n" + " expression is used to match category and the second is used match\n" + " plugin name.\n"; +static const char* const commandSet = + " set [persistent] [ []]\n" + " Set filtering level for matching categories. 'level' must be one of\n" + " trace, debug, info, warning and error. The 'level' parameter sets\n" + " the lowest message level that will be shown. The command doesn't\n" + " allow filters to disable any error messages.\n" + " Default filter life time is until Dwarf Fortress process exists or\n" + " plugin is unloaded. Passing 'persistent' as second parameter tells\n" + " the plugin to store the filter to dfhack-config. Stored filters\n" + " will be active until always when the plugin is loaded. 'unset'\n" + " command can be used to remove persistent filters.\n" + " Filters are applied FIFO order. The latest filter will override any\n" + " older filter that also matches.\n"; +static const char* const commandFilters = + " filter []\n" + " Show the list of active filters. The first column is 'id' which can\n" + " be used to deactivate filters using 'unset' command.\n" + " Filters are printed in same order as applied - the oldest first.\n"; +static const char* const commandUnset = + " unset [ ...]\n" + " 'unset' takes space separated list of filter ids from 'filter'.\n" + " It will reset any matching category back to the default 'warning'\n" + " level or any other still active matching filter level.\n"; +static const char* const commandDisable = + " disable [ ...]\n" + " 'disable' takes space separated list of filter ids from 'filter'.\n" + " It will reset any matching category back to the default 'warning'\n" + " level or any other still active matching filter level.\n" + " 'disable' will print red filters that were already disabled.\n"; +static const char* const commandEnable = + " enable [ ...]\n" + " 'enable' takes space separated list of filter ids from 'filter'.\n" + " It will reset any matching category back to the default 'warning'\n" + " level or any other still active matching filter level.\n" + " 'enable' will print red filters that were already enabled.\n"; +static const char* const commandHelpDetails = + " help []\n" + " Show help for any of subcommands. Without any parameters it shows\n" + " short help for all subcommands.\n"; + +//! Helper type to hold static dispatch table for subcommands +struct CommandDispatch { + //! Store handler function pointer and help message for commands + struct Command { + using handler_t = command_result(*)(color_ostream&,std::vector&); + Command(handler_t handler, const char* help) : + handler_(handler), + help_(help) + {} + + command_result operator()(color_ostream& out, + std::vector& parameters) const noexcept + { + return handler_(out, parameters); + } + + handler_t handler() const noexcept + { return handler_; } + + const char* help() const noexcept + { return help_; } + private: + handler_t handler_; + const char* help_; + }; + using dispatch_t = const std::map; + //! Name to handler function and help message mapping + static dispatch_t dispatch; +}; + +struct LevelName { + static constexpr auto regex_opt = std::regex::icase | std::regex::optimize | std::regex::nosubs; + LevelName(const std::string& name) : + name_{name}, + match_{name, regex_opt} + {} + + bool match(const std::string& value) const + { + return std::regex_match(value, match_); + } + + operator const std::string&() const noexcept + { + return name_; + } + + const std::string& str() const noexcept + { + return name_; + } + + template + std::string operator+(const T& v) const + { + return name_ + v; + } +private: + std::string name_; + std::regex match_; +}; + +std::string operator+(const std::string& a, const LevelName& b) +{ + return a + static_cast(b); +} + +//! List of DebugCategory::level's in human readable form +static const std::array levelNames{ + LevelName{"Trace"}, + LevelName{"Debug"}, + LevelName{"Info"}, + LevelName{"Warning"}, + LevelName{"Error"}, +}; + +/*! + * Filter applies a runtime filter to matching DFHack::DebugCategory 's. + * Filters are stored in DFHack::debugPlugin::FilterManager which applies + * filters to dynamically added categories. The manager also stores and loads + * persistent Filters from the configuration file. + */ +struct Filter { + explicit Filter(DebugCategory::level level, + const std::string& categoryText, + const std::regex& category, + const std::string& pluginText, + const std::regex& plugin, + bool persistent = true, + bool enabled = true) noexcept; + + explicit Filter(DebugCategory::level level, + const std::string& categoryText, + const std::string& pluginText, + bool persistent = true, + bool enabled = true); + + explicit Filter() = default; + + //! Apply the filter to a category if regex's match + void apply(DFHack::DebugCategory& cat) noexcept; + //! Apply the filter to a category that has been already matched + bool applyAgain(DFHack::DebugCategory& cat) const noexcept; + //! Remove the category from matching count if regex's match + //! \return true if regex's matched + bool remove(const DFHack::DebugCategory& cat) noexcept; + + //! Query if filter is enabled + bool enabled() const noexcept {return enabled_;} + //! Set the enable status of filter + void enabled(bool enable) noexcept; + //! Query if filter is persistent + bool persistent() const noexcept {return persistent_;} + //! Query if the filter level + bool level() const noexcept {return level_;} + //! Query number of matching categories + size_t matches() const noexcept {return matches_;} + //! Add matches count for the initial category filter matching + void addMatch() noexcept {++matches_;} + //! Return the category filter text + const std::string& categoryText() const noexcept {return categoryText_;} + //! Return the plugin filter text + const std::string& pluginText() const noexcept {return pluginText_;} + + //! Load Filter from configuration file. Second parameter would be version + //! number if format changes in future to include more fields. Then new + //! fields would have to be loaded conditionally + void load(JsonArchive& ar, const unsigned int) + { + ar >> NVP(categoryText_) + >> NVP(pluginText_) + >> NVP(enabled_) + >> NVP(level_); + TRACE(filter) << "Loading filter cat: " << categoryText_ + << " plug: " << pluginText_ + << " ena " << enabled_ + << " level: " << level_ + << std::endl; + persistent_ = true; + matches_ = 0; + category_ = std::regex{categoryText_}; + plugin_ = std::regex{pluginText_}; + } + + //! Save the Filter to json configuration file + void save(JsonArchive& ar, const unsigned int) const + { + ar << NVP(categoryText_) + << NVP(pluginText_) + << NVP(enabled_) + << NVP(level_); + } + +private: + std::regex category_; + std::regex plugin_; + DebugCategory::level level_; + size_t matches_; + bool persistent_; + bool enabled_; + std::string categoryText_; + std::string pluginText_; +}; + +Filter::Filter(DebugCategory::level level, + const std::string& categoryText, + const std::regex& category, + const std::string& pluginText, + const std::regex& plugin, + bool persistent, + bool enabled) noexcept : + category_{category}, + plugin_{plugin}, + level_{level}, + matches_{0}, + persistent_{persistent}, + enabled_{enabled}, + categoryText_{categoryText}, + pluginText_{pluginText} +{} + +Filter::Filter(DebugCategory::level level, + const std::string& categoryText, + const std::string& pluginText, + bool persistent, + bool enabled) : + Filter{ + level, + categoryText, + std::regex{categoryText, defaultRegex}, + pluginText, + std::regex{pluginText, defaultRegex}, + persistent, + enabled, + } +{} + +void Filter::enabled(bool enable) noexcept +{ + if (enable == enabled_) + return; + enabled_ = enable; +} + +bool Filter::applyAgain(DebugCategory& cat) const noexcept +{ + if (!enabled_) + return false; + if (!std::regex_search(cat.category(), category_)) + return false; + if (!std::regex_search(cat.category(), plugin_)) + return false; + TRACE(filter) << "apply " << cat.plugin() << ':' << cat.category() << " matches '" + << pluginText() << "' '" << categoryText() << '\'' << std::endl; + cat.allowed(level_); + return true; +} + +void Filter::apply(DebugCategory& cat) noexcept +{ + if (applyAgain(cat)) + ++matches_; +} + +bool Filter::remove(const DebugCategory& cat) noexcept +{ + if (!enabled_) + return false; + if (!std::regex_search(cat.category(), category_)) + return false; + if (!std::regex_search(cat.category(), plugin_)) + return false; + TRACE(filter) << "remove " << cat.plugin() << ':' << cat.category() << " matches '" + << pluginText() << "' '" << categoryText() << std::endl; + --matches_; + return true; +} + +/** + * Storage for enabled and disabled filters. It uses ordered map because common + * case is to iterate filters from oldest to the newest. + * + * Data races: any reader and writer must lock + * DFHack::DebugManager::access_lock_. Sharing DebugManager make sense because + * most of functionality related to filters requires locking DebugManager too. + * Remaining functionality can share same lock because they are rarely called + * functions. + */ +struct FilterManager : public std::map +{ + using parent_t = std::map; + //! Current configuration version implemented by the code + constexpr static Json::UInt configVersion{1}; + //! Path to the configuration file + constexpr static const char* configPath{"dfhack-config/runtime-debug.json"}; + + //! Get reference to the singleton + static FilterManager& getInstance() noexcept + { + static FilterManager instance; + return instance; + } + + //! Add a new filter which gets automatically a new id + template + std::pair emplaceNew( Args&&... args ) { + return emplace(std::piecewise_construct, + std::forward_as_tuple(nextId_++), + std::forward_as_tuple(std::forward(args)...)); + } + + //! Load state from the configuration file + DFHack::command_result loadConfig(DFHack::color_ostream& out) noexcept; + //! Save state to the configuration file + DFHack::command_result saveConfig(DFHack::color_ostream& out) const noexcept; + + //! Connect FilterManager to DFHack::DebugManager::categorySignal + void connectTo(DebugManager::categorySignal_t& signal) noexcept; + //! Temporary block DFHack::DebugManager::categorySignal + DebugManager::categorySignal_t::BlockGuard blockSlot() noexcept; + + ~FilterManager(); + + FilterManager(const FilterManager&) = delete; + FilterManager(FilterManager&&) = delete; + FilterManager& operator=(FilterManager) = delete; + FilterManager& operator=(FilterManager&&) = delete; + + void load(JsonArchive& ar) + { + Json::UInt version = -1; + ar >> serialization::make_nvp("configVersion", version); + if (version > configVersion) { + std::stringstream ss; + ss << "The saved config version (" + << version + << ") is newer than code supported version (" + << configVersion + << ")"; + throw std::runtime_error{ss.str()}; + } + ar >> NVP(nextId_); + JsonArchive& children = ar["filters"]; + for (auto iter = children.begin(); iter != children.end(); ++iter) { + Filter child; + child.load(*iter, version); + std::stringstream ss(iter.name()); + size_t id; + ss >> id; + insert(std::make_pair(id, child)); + } + } + + void save(JsonArchive& ar) const + { + JsonArchive children{Json::objectValue}; + for (const auto& filterPair: *this) { + if (!filterPair.second.persistent()) + continue; + std::stringstream ss; + ss << filterPair.first; + filterPair.second.save(children[ss.str()], configVersion); + } + auto configVersion = FilterManager::configVersion; + ar << NVP(configVersion) + << NVP(nextId_); + ar["filters"] = children; + } + +private: + FilterManager() = default; + //! The next integer to use as id to have each with unique number + Json::UInt64 nextId_; + DebugManager::categorySignal_t::Connection connection_; +}; + +FilterManager::~FilterManager() +{ +} + +command_result FilterManager::loadConfig(DFHack::color_ostream& out) noexcept +{ + nextId_ = 1; + if (!Filesystem::isfile(configPath)) + return CR_OK; + try { + DEBUG(command, out) << "Load config from '" << configPath << "'" << std::endl; + JsonArchive archive; + std::ifstream ifs(configPath); + if (!ifs.good()) + throw std::runtime_error{"Failed to open configuration file for reading"}; + ifs >> archive; + load(archive); + } catch(std::exception& e) { + ERR(command, out) << "Serializing filters from '" << configPath << "' failed: " + << e.what() << std::endl; + return CR_FAILURE; + } + return CR_OK; +} + +command_result FilterManager::saveConfig(DFHack::color_ostream& out) const noexcept +{ + try { + DEBUG(command, out) << "Save config to '" << configPath << "'" << std::endl; + JsonArchive archive; + save(archive); + std::ofstream ofs(configPath); + if (!ofs.good()) + throw std::runtime_error{"Failed to open configuration file for writing"}; + ofs << archive; + } catch(std::exception e) { + ERR(command, out) << "Serializing filters to '" << configPath << "' failed: " + << e.what() << std::endl; + return CR_FAILURE; + } + return CR_OK; +} + +void FilterManager::connectTo(DebugManager::categorySignal_t& signal) noexcept +{ + connection_ = signal.connect( + [this](DebugManager::signalType t, DebugCategory& cat) { + TRACE(filter) << "sig type: " << t << ' ' + << cat.plugin() << ':' + << cat.category() << std::endl; + switch (t) { + case DebugManager::CAT_ADD: + for (auto& filterPair: *this) + filterPair.second.apply(cat); + break; + case DebugManager::CAT_REMOVE: + for (auto& filterPair: *this) + filterPair.second.remove(cat); + break; + case DebugManager::CAT_MODIFIED: + break; + } + }); +} + +DebugManager::categorySignal_t::BlockGuard FilterManager::blockSlot() noexcept +{ + TRACE(filter) << "Temporary disable FilterManager::connection_" << std::endl; + return {connection_}; +} + +//! \brief Helper to parse optional regex string safely +static command_result parseRegexParam(std::regex& target, + color_ostream& out, + std::vector& parameters, + size_t pos) +{ + if (parameters.size() <= pos) + return CR_OK; + try { + std::regex temp{parameters[pos], defaultRegex}; + target = std::move(temp); + } 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; + return CR_WRONG_USAGE; + } + return CR_OK; +} + +/*! + * "Algorithm" to apply category filters based on optional regex parameters. + * \param out The output stream where errors should be written + * \param paramters The list of parameters for the command + * \param pos The position where first optional regular expression parameter is + * \param header The callback after locking DebugManager and before loop + * \param categoryMatch The callback for each matching category + */ +template +static command_result applyCategoryFilters(color_ostream& out, + std::vector& parameters, + size_t pos, Callable1 header, + Callable2 categoryMatch, + Callable3 listComplete) +{ + std::regex pluginRegex{".", defaultRegex}; + std::regex categoryRegex{".", defaultRegex}; + command_result rv = CR_OK; + + DEBUG(command,out) << "applying category filters '" + << (parameters.size() >= pos + 1 ? parameters[pos] : "") + << "' and plugin filter '" + << (parameters.size() >= pos + 2 ? parameters[pos+1] : "") + << '\'' << std::endl; + // Parse parameters + if ((rv = parseRegexParam(pluginRegex, out, parameters, pos)) != CR_OK) + return rv; + if ((rv = parseRegexParam(categoryRegex, out, parameters, pos+1)) != CR_OK) + return rv; + // Lock the manager to have consistent view of categories + auto& manager = DebugManager::getInstance(); + std::lock_guard lock(manager.access_mutex_); + out << std::left; + auto guard = header(manager, categoryRegex, pluginRegex); + for (auto* category: manager) { + DebugCategory::cstring_ref p = category->plugin(); + DebugCategory::cstring_ref c = category->category(); + // Apply filters to the category and plugin names + if (!std::regex_search(c, categoryRegex)) + continue; + if (!std::regex_search(p, pluginRegex)) + continue; + categoryMatch(*category); + } + out << std::flush << std::right; + out.color(COLOR_RESET); + return listComplete(); +} + +static void printCategoryListHeader(color_ostream& out) +{ + // Output the header. + out.color(COLOR_GREEN); + out << std::setw(12) << "Plugin" + << std::setw(12) << "Category" + << std::setw(18) << "Lowest printed" << '\n'; +} + +static void printCategoryListEntry(color_ostream& out, + unsigned& line, + DebugCategory& cat, + DebugCategory::level old = static_cast(-1)) +{ + if ((line & 31) == 0) + printCategoryListHeader(out); + // Output matching categories. + out.color((line++ & 1) == 0 ? COLOR_CYAN : COLOR_LIGHTCYAN); + const std::string& level = (old != static_cast(-1)) ? + levelNames[static_cast(old)] + "->" + + levelNames[static_cast(cat.allowed())] : + levelNames[static_cast(cat.allowed())].str(); + out << std::setw(12) << cat.plugin() + << std::setw(12) << cat.category() + << std::setw(18) << level << '\n'; +} + +//! Handler for debugfilter category +static command_result listCategories(color_ostream& out, + std::vector& parameters) +{ + unsigned line = 0; + return applyCategoryFilters(out, parameters, 1u, + // After parameter parsing + [](DebugManager&, const std::regex&, const std::regex&) { + return 0; + }, + // Per category + [&out, &line](DebugCategory& cat) { + printCategoryListEntry(out, line, cat); + }, + // After list + []() {return CR_OK;}); +} + +//! Type that prints parameter string in center of output stream field +template> +struct center { + using string = std::basic_string; + center(const string& str) : + str_(str) + {} + + const string& str_; +}; + +/*! + * Helper to construct a center object to print fields centered + * \code{.cpp} + * out << std::setw(20) << centered("centered"_s); + * \endcode + */ +template +center centered(const ST& str) +{ + return {str}; +} + +//! c++14 string conversion literal to std::string +std::string operator "" _s(const char* cstr, size_t len) +{ + return {cstr, len}; +} + +//! Output centered string, the stream must be using std::ios::right +//! \sa DFHack::debugPlugin::centered +template +std::basic_ostream& operator<<(std::basic_ostream& os, const center& toCenter) +{ + std::streamsize w = os.width(); + const auto& str = toCenter.str_; + mbstate_t ps{}; + std::streamsize ccount = 0; + auto iter = str.cbegin(); + // Check multibyte character length. It will break when mbrlen find the '\0' + for (;ccount < w; ++ccount) { + const size_t n = std::distance(iter, str.cend()); + using ss_t = std::make_signed::type; + ss_t bytes = mbrlen(&*iter, n, &ps); + if (bytes <= 0) /* Check for errors and null */ + break; + std::advance(iter, bytes); + } + + if (ccount < w) { + // Center the character when there is less than the width + // fillw = w - count + // cw = w - (fillw)/2 = (2w - w + count)/2 + // Extra one for rounding half results up + std::streamsize cw = (w + ccount + 1)/2; + os << std::setw(cw) << str + << std::setw(w - cw) << ""; + } else { + // Truncate characters to the width of field + os.write(&str[0], std::distance(str.begin(), iter)); + // Reset the field width because we wrote the string with write + os << std::setw(0); + } + return os; +} + +static FilterManager::iterator parseFilterId(color_ostream& out, + const std::string& parameter) +{ + unsigned long id = 0; + try { + id = std::stoul(parameter); + } catch(...) { + } + auto& filMan = FilterManager::getInstance(); + auto iter = filMan.find(id); + if (iter == filMan.end()) { + WARN(command,out) << "The optional parameter (" + << parameter << ") must be an filter id." << std::endl; + } + return iter; +} + +static void printFilterListEntry(color_ostream& out, + unsigned line, + color_value lineColor, + size_t id, + const Filter& filter) +{ + if ((line & 31) == 0) { + out.color(COLOR_GREEN); + out << std::setw(4) << "ID" + << std::setw(8) << "enabled" + << std::setw(8) << "persist" + << std::setw(9) << centered("level"_s) + << ' ' + << std::setw(15) << centered("category"_s) + << ' ' + << std::setw(15) << centered("plugin"_s) + << std::setw(8) << "matches" + << '\n'; + } + out.color(lineColor); + out << std::setw(4) << id + << std::setw(8) << centered(filter.enabled() ? "X"_s:""_s) + << std::setw(8) << centered(filter.persistent() ? "X"_s:""_s) + << std::setw(9) << centered(levelNames[filter.level()].str()) + << ' ' + << std::setw(15) << centered(filter.categoryText()) + << ' ' + << std::setw(15) << centered(filter.pluginText()) + << std::setw(8) << filter.matches() + << '\n'; +} + +//! Handler for debugfilter filter +static command_result listFilters(color_ostream& out, + std::vector& parameters) +{ + if (1u < parameters.size()) { + auto& catMan = DebugManager::getInstance(); + std::lock_guard lock(catMan.access_mutex_); + auto iter = parseFilterId(out, parameters[1]); + if (iter == FilterManager::getInstance().end()) + return CR_WRONG_USAGE; + + auto id = iter->first; + Filter& filter = iter->second; + + out << std::left + << std::setw(10) << "ID:" << id << '\n' + << std::setw(10) << "Enabled:" << (filter.enabled() ? "Yes"_s:"No"_s) << '\n' + << std::setw(10) << "Persist:" << (filter.persistent() ? "Yes"_s:"No"_s) << '\n' + << std::setw(10) << "Level:" << levelNames[filter.level()].str() << '\n' + << std::setw(10) << "category:" << filter.categoryText() << '\n' + << std::setw(10) << "plugin:" << filter.pluginText() << '\n' + << std::setw(10) << "matches:" << filter.matches() << '\n' + << std::right + << std::endl; + return CR_OK; + } + auto& catMan = DebugManager::getInstance(); + { + std::lock_guard lock(catMan.access_mutex_); + auto& filMan = FilterManager::getInstance(); + unsigned line = 0; + for (auto& filterPair: filMan) { + size_t id = filterPair.first; + const Filter& filter = filterPair.second; + + color_value c = (line & 1) == 0 ? COLOR_CYAN : COLOR_LIGHTCYAN; + printFilterListEntry(out,line++,c,id,filter); + } + } + out.color(COLOR_RESET); + out.flush(); + return CR_OK; +} + +static const std::string persistent("persistent"); + +//! Handler for debugfilter set +static command_result setFilter(color_ostream& out, + std::vector& parameters) +{ + bool persist = false; + size_t pos = 1u; + if (pos < parameters.size() && + parameters[pos] == persistent) { + pos++; + persist = true; + } + if (pos >= parameters.size()) { + ERR(command,out).print("set requires at least the level parameter\n"); + return CR_WRONG_USAGE; + } + const std::string& level = parameters[pos]; + auto iter = std::find_if(levelNames.begin(), levelNames.end(), + [&level](const LevelName& v) -> bool { + return v.match(level); + }); + if (iter == levelNames.end()) { + ERR(command,out).print("level ('%s') parameter must be one of " + "trace, debug, info, warning, error.\n", + parameters[pos].c_str()); + return CR_WRONG_USAGE; + } + + DebugCategory::level filterLevel = static_cast( + iter - levelNames.begin()); + + unsigned line = 0; + Filter* newFilter = nullptr; + return applyCategoryFilters(out, parameters, pos + 1, + // After parameters parsing + [¶meters, pos, filterLevel,persist,&newFilter](DebugManager&, const std::regex& catRegex, const std::regex& pluginRegex) + { + auto& filMan = FilterManager::getInstance(); + newFilter = &filMan.emplaceNew(filterLevel, + pos+1 < parameters.size()?parameters[pos+1]:".", + catRegex, + pos+2 < parameters.size()?parameters[pos+2]:".", + pluginRegex, + persist).first->second; + return filMan.blockSlot(); + }, + // Per item + [filterLevel, &line, &out, &newFilter](DebugCategory& cat) { + auto old = cat.allowed(); + cat.allowed(filterLevel); + newFilter->addMatch(); + printCategoryListEntry(out, line, cat, old); + }, + // After list + [&out,&persist]() { + if (persist) + return FilterManager::getInstance().saveConfig(out); + return CR_OK; + }); +} + +template +static command_result applyFilterIds(color_ostream& out, + std::vector& parameters, + const char* name, + HighlightRed hlRed, + ListComplete listComplete) +{ + if (1u >= parameters.size()) { + ERR(command,out) << name << " requires at least a filter id" << std::endl; + return CR_WRONG_USAGE; + } + command_result rv = CR_OK; + { + auto& catMan = DebugManager::getInstance(); + std::lock_guard lock(catMan.access_mutex_); + auto& filMan = FilterManager::getInstance(); + unsigned line = 0; + for (size_t pos = 1; pos < parameters.size(); ++pos) { + const std::string& p = parameters[pos]; + auto iter = parseFilterId(out, p); + if (iter == filMan.end()) + continue; + color_value c = (line & 1) == 0 ? COLOR_CYAN : COLOR_LIGHTCYAN; + if (hlRed(iter)) + c = COLOR_RED; + printFilterListEntry(out,line++,c,iter->first,iter->second); + } + rv = listComplete(); + } + out.color(COLOR_RESET); + out.flush(); + return rv; +} + +//! Handler for debugfilter disable +static command_result disableFilter(color_ostream& out, + std::vector& parameters) +{ + std::set modified; + bool mustSave = false; + return applyFilterIds(out,parameters,"disable", + // Per item + [&modified,&mustSave](FilterManager::iterator& iter) -> bool { + Filter& filter = iter->second; + bool enabled = filter.enabled(); + if (enabled == false) + return true; + auto& catMan = DebugManager::getInstance(); + for (DebugCategory* cat: catMan) { + if (filter.remove(*cat)) + modified.emplace(cat); + } + filter.enabled(false); + mustSave = mustSave || filter.persistent(); + return false; + }, + // After list + [&modified,&mustSave,&out]() { + for (DebugCategory* cat: modified) { + // Reset filtering back to default + cat->allowed(DebugCategory::LWARNING); + auto& filMan = FilterManager::getInstance(); + // Reapply all remaining filters + for (auto& filterPair: filMan) + filterPair.second.applyAgain(*cat); + } + if (mustSave) + return FilterManager::getInstance().saveConfig(out); + return CR_OK; + }); +} + +//! Handler for debugfilter enable +static command_result enableFilter(color_ostream& out, + std::vector& parameters) +{ + std::set modified; + bool mustSave = false; + return applyFilterIds(out,parameters,"enable", + // Per item + [&modified,&mustSave](FilterManager::iterator& iter) -> bool { + Filter& filter = iter->second; + bool enabled = filter.enabled(); + if (enabled == true) + return true; + filter.enabled(true); + auto& catMan = DebugManager::getInstance(); + for (DebugCategory* cat: catMan) { + if (filter.applyAgain(*cat)) { + modified.emplace(cat); + filter.addMatch(); + } + } + mustSave = mustSave || filter.persistent(); + return false; + }, + // After list + [&modified,&mustSave,&out]() { + for (DebugCategory* cat: modified) { + // Reset filtering back to default + cat->allowed(DebugCategory::LWARNING); + auto& filMan = FilterManager::getInstance(); + // Reapply all remaining filters + for (auto& filterPair: filMan) + filterPair.second.applyAgain(*cat); + } + if (mustSave) + return FilterManager::getInstance().saveConfig(out); + return CR_OK; + }); +} + +//! Handler for debugfilter unset +static command_result unsetFilter(color_ostream& out, + std::vector& parameters) +{ + std::set modified; + std::vector toErase; + return applyFilterIds(out,parameters,"unset", + // Per item + [&modified, &toErase](FilterManager::iterator& iter) -> bool { + Filter& filter = iter->second; + if (filter.enabled()) { + auto& catMan = DebugManager::getInstance(); + for (DebugCategory* cat: catMan) { + if (filter.remove(*cat)) + modified.emplace(cat); + } + } + toErase.emplace_back(iter); + return false; + }, + // After list + [&modified,&toErase,&out]() { + auto& filMan = FilterManager::getInstance(); + bool mustSave = false; + for (auto iter: toErase) { + mustSave = mustSave || iter->second.persistent(); + filMan.erase(iter); + } + + for (DebugCategory* cat: modified) { + // Reset filtering back to default + cat->allowed(DebugCategory::LWARNING); + // Reapply all remaining filters + for (auto& filterPair: filMan) + filterPair.second.applyAgain(*cat); + } + if (mustSave) + return FilterManager::getInstance().saveConfig(out); + return CR_OK; + }); +} + +using DFHack::debugPlugin::CommandDispatch; + +static command_result printHelp(color_ostream& out, + std::vector& parameters) +{ + const char* help = commandHelp; + auto iter = CommandDispatch::dispatch.end(); + if (1u < parameters.size()) + iter = CommandDispatch::dispatch.find(parameters[1]); + if (iter != CommandDispatch::dispatch.end()) + help = iter->second.help(); + out << help << std::flush; + return CR_OK; +} + +CommandDispatch::dispatch_t CommandDispatch::dispatch { + {"category", {listCategories,commandCategory}}, + {"filter", {listFilters,commandFilters}}, + {"set", {setFilter,commandSet}}, + {"unset", {unsetFilter,commandUnset}}, + {"enable", {enableFilter,commandEnable}}, + {"disable", {disableFilter,commandDisable}}, + {"help", {printHelp,commandHelpDetails}}, +}; + +//! Dispatch command handling to the subcommand or help +static command_result commandDebugFilter(color_ostream& out, + std::vector& parameters) +{ + DEBUG(command,out).print("debugfilter %s, parameter count %zu\n", + parameters.size() > 0 ? parameters[0].c_str() : "", + parameters.size()); + auto handler = printHelp; + auto iter = CommandDispatch::dispatch.end(); + if (0u < parameters.size()) + iter = CommandDispatch::dispatch.find(parameters[0]); + if (iter != CommandDispatch::dispatch.end()) + handler = iter->second.handler(); + return (handler)(out, parameters); +} + +} } /* namespace debug */ + +DFhackCExport DFHack::command_result plugin_init(DFHack::color_ostream& out, + std::vector& commands) +{ + commands.emplace_back( + "debugfilter", + "Manage runtime debug print filters", + DFHack::debugPlugin::commandDebugFilter, + false, + DFHack::debugPlugin::commandHelp); + auto& filMan = DFHack::debugPlugin::FilterManager::getInstance(); + DFHack::command_result rv = DFHack::CR_OK; + if ((rv = filMan.loadConfig(out)) != DFHack::CR_OK) + return rv; + auto& catMan = DFHack::DebugManager::getInstance(); + std::lock_guard lock(catMan.access_mutex_); + for (auto* cat: catMan) { + for (auto& filterPair: filMan) { + DFHack::debugPlugin::Filter& filter = filterPair.second; + filter.apply(*cat); + } + } + INFO(init,out).print("plugin_init with %zu commands, %zu filters and %zu categories\n", + commands.size(), filMan.size(), catMan.size()); + filMan.connectTo(catMan.categorySignal); + return rv; +} + +DFhackCExport DFHack::command_result plugin_shutdown(DFHack::color_ostream& out) +{ + INFO(init,out).print("plugin_shutdown\n"); + return DFHack::CR_OK; +} From 490a8557766bf3459ff85d8e0cb2ea941f311c24 Mon Sep 17 00:00:00 2001 From: Pauli Date: Mon, 2 Jul 2018 18:48:33 +0300 Subject: [PATCH 04/66] Add a test for signal_shared_tag implementation The test cases check that the signal_shared_tag implementation can be used and destructed safely from multiple threads. --- plugins/CMakeLists.txt | 2 + plugins/devel/CMakeLists.txt | 2 +- plugins/devel/kittens.cpp | 178 +++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index a05d680ea..07a94112c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,5 +1,7 @@ INCLUDE(Plugins.cmake) +find_package(Threads) + OPTION(BUILD_STONESENSE "Build stonesense (needs a checkout first)." OFF) if(BUILD_STONESENSE) add_subdirectory (stonesense) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 4fb4b5cf5..879edd5bb 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -9,7 +9,7 @@ DFHACK_PLUGIN(counters counters.cpp) DFHACK_PLUGIN(dumpmats dumpmats.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp) DFHACK_PLUGIN(frozen frozen.cpp) -DFHACK_PLUGIN(kittens kittens.cpp) +DFHACK_PLUGIN(kittens kittens.cpp LINK_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) DFHACK_PLUGIN(memview memview.cpp memutils.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(notes notes.cpp) diff --git a/plugins/devel/kittens.cpp b/plugins/devel/kittens.cpp index 5de94ced3..26a2655e8 100644 --- a/plugins/devel/kittens.cpp +++ b/plugins/devel/kittens.cpp @@ -1,12 +1,16 @@ #include #include +#include #include +#include #include "Console.h" #include "Core.h" +#include "Debug.h" #include "Export.h" #include "MiscUtils.h" #include "PluginManager.h" +#include "Signal.hpp" #include "modules/Gui.h" #include "modules/Items.h" @@ -25,6 +29,10 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(world); +namespace DFHack { +DBG_DECLARE(kittens,command); +} + std::atomic shutdown_flag{false}; std::atomic final_flag{true}; std::atomic timering{false}; @@ -42,6 +50,7 @@ command_result trackmenu (color_ostream &out, vector & parameters); command_result trackpos (color_ostream &out, vector & parameters); command_result trackstate (color_ostream &out, vector & parameters); command_result colormods (color_ostream &out, vector & parameters); +command_result sharedsignal (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { @@ -51,6 +60,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & parameters) return CR_OK; } +struct Connected; +using shared = std::shared_ptr; +using weak = std::weak_ptr; + +static constexpr std::chrono::microseconds delay{1}; + +template +struct ClearMem : public ConnectedBase { + ~ClearMem() + { + memset(reinterpret_cast(this), 0xDE, sizeof(Derived)); + } +}; + +struct Connected : public ClearMem { + using Sig = Signal; + std::array con; + Sig signal; + weak other; + Sig::weak_ptr other_sig; + color_ostream *out; + int id; + uint32_t count; + uint32_t caller; + alignas(64) std::atomic callee; + Connected() = default; + Connected(int id) : + Connected{} + { + this->id = id; + } + void connect(color_ostream& o, shared& b, size_t pos, uint32_t c) + { + out = &o; + count = c*2; + other = b; + other_sig = b->signal.weak_from_this(); + // Externally synchronized object destruction is only safe to this + // connect. + con[pos] = b->signal.connect( + [this](int) { + uint32_t old = callee.fetch_add(1); + assert(old != 0xDEDEDEDE); + std::this_thread::sleep_for(delay); + assert(callee != 0xDEDEDEDE); + }); + // Shared object managed object with possibility of destruction while + // other threads calling emit must pass the shared_ptr to connect. + Connected *bptr = b.get(); + b->con[pos] = signal.connect(b, + [bptr](int) { + uint32_t old = bptr->callee.fetch_add(1); + assert(old != 0xDEDEDEDE); + std::this_thread::sleep_for(delay); + assert(bptr->callee != 0xDEDEDEDE); + }); + } + void reconnect(size_t pos) { + auto b = other.lock(); + if (!b) + return; + // Not required to use Sig::lock because other holds strong reference to + // Signal. But this just shows how weak_ref could be used. + auto sig = Sig::lock(other_sig); + if (!sig) + return; + con[pos] = sig->connect(b, + [this](int) { + uint32_t old = callee.fetch_add(1); + assert(old != 0xDEDEDEDE); + std::this_thread::sleep_for(delay); + assert(callee != 0xDEDEDEDE); + }); + } + void connect(color_ostream& o, shared& a, shared& b,size_t pos, uint32_t c) + { + out = &o; + count = c; + con[pos] = b->signal.connect(a, + [this](int) { + uint32_t old = callee.fetch_add(1); + assert(old != 0xDEDEDEDE); + std::this_thread::sleep_for(delay); + assert(callee != 0xDEDEDEDE); + }); + } + Connected* operator->() noexcept + { + return this; + } + ~Connected() { + INFO(command,*out).print("Connected %d had %d count. " + "It was caller %d times. " + "It was callee %d times.\n", + id, count, caller, callee.load()); + } +}; + +command_result sharedsignal (color_ostream &out, vector & parameters) +{ + using rng_t = std::linear_congruential_engine; + rng_t rng(std::random_device{}()); + size_t count = 10; + if (0 < parameters.size()) { + std::stringstream ss(parameters[0]); + ss >> count; + DEBUG(command, out) << "Parsed " << count + << " from paramters[0] '" << parameters[0] << '\'' << std::endl; + } + + + std::uniform_int_distribution dis(4096,8192); + out << "Running signal_shared_tag destruction test " + << count << " times" << std::endl; + for (size_t nr = 0; nr < count; ++nr) { + std::array t{}; + // Make an object which destruction is protected by std::thread::join() + Connected external{static_cast(t.size())}; + TRACE(command, out) << "begin " << std::endl; + { + int id = 0; + // Make objects that are automatically protected using weak_ptr + // references that are promoted to shared_ptr when Signal is + // accessed. + std::array c = { + std::make_shared(id++), + std::make_shared(id++), + std::make_shared(id++), + std::make_shared(id++), + }; + assert(t.size() == c.size()); + for (unsigned i = 1; i < c.size(); ++i) { + c[0]->connect(out, c[0], c[i], i - 1, dis(rng)); + c[i]->connect(out, c[i], c[0], 0, dis(rng)); + } + external.connect(out, c[1], 1, dis(rng)); + auto thr = [&out](shared c) { + TRACE(command, out) << "Thread " << c->id << " started." << std::endl; + weak ref = c; + for (;c->caller < c->count; ++c->caller) { + c->signal(c->caller); + } + TRACE(command, out) << "Thread " << c->id << " resets shared." << std::endl; + c.reset(); + while((c = ref.lock())) { + ++c->caller; + c->signal(c->caller); + c.reset(); + std::this_thread::sleep_for(delay*25); + } + }; + for (unsigned i = 0; i < c.size(); ++i) { + TRACE(command, out) << "start thread " << i << std::endl; + t[i] = std::thread{thr, c[i]}; + } + } + TRACE(command, out) << "running " << std::endl; + for (;external->caller < external->count; ++external->caller) { + external->signal(external->caller); + external->reconnect(1); + } + TRACE(command, out) << "join " << std::endl; + for (unsigned i = 0; i < t.size(); ++i) + t[i].join(); + } + return CR_OK; +} + command_result kittens (color_ostream &out, vector & parameters) { if (parameters.size() >= 1) From c201cf5b7b3faf44e109484ebdcb84a7a0589346 Mon Sep 17 00:00:00 2001 From: Pauli Date: Wed, 4 Jul 2018 14:54:00 +0300 Subject: [PATCH 05/66] Documentation and Changelog for debug printing and Signal --- docs/Plugins.rst | 96 ++++++++++++++++++++++++++++++++++++++++++++++ docs/changelog.txt | 15 ++++++++ 2 files changed, 111 insertions(+) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 5f41bafed..bbeeb813a 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -392,6 +392,102 @@ Otherwise somewhat similar to `gui/quickcmd`. .. image:: images/command-prompt.png +.. _debug: + +debug +===== +Manager DFHack runtime debug prints. Debug prints are grouped by plugin name, +category name and print level. Levels are ``trace``, ``debug``, ``info``, +``warning`` and ``error``. + +The runtime message printing is controlled using filters. Filters set minimum +visible message to all matching categories. Matching uses regular expression +that allows listing multiple alternative matches or partial name matches. +Persistent filters are stored in ``dfhack-config/runtime-debug.json``. + +Oldest filters are applied first. That means a newer filter can override the +older printing level selection. + +Usage: ``debugfilter [subcommand] [parameters...]`` + +Following subcommands are supported. + +Regular expression syntax +------------------------- + +Syntax is C++ version of ECMA-262 grammar (Javascript regular expression). +Deails of differences can be found from +https://en.cppreference.com/w/cpp/regex/ecmascript + +help +---- +Give overall help or a detailed help for a subcommand. + +Usage: ``debugfilter help [subcommand]`` + +category +-------- +List available debug plugin and category names. + +Usage: ``debugfilter category [plugin regex] [category regex]`` + +The list can be filtered using optional regex parameters. If filters aren't +given then the it uses ``"."`` regex which matches any character. The regex +parameters are good way to test regex before passing them to ``set``. + +filter +------ +List active and passive debug print level changes. + +Usage: ``debugfilter filter [id]`` + +Optional ``id`` parameter is the id listed as first column in the filter list. +If id is given then the command shows information for the given filter only in +multi line format that is better format if filter has long regex. + +set +--- +Creates a new debug filter to set category printing levels. + +Usage: ``debugfilter set [level] [plugin regex] [category regex]`` + +Adds a filter that will be deleted when DF process exists or plugin is unloaded. + +Usage: ``debugfilter set persistent [level] [plugin regex] [category regex]`` + +Stores the filter in the configuration file to until ``unset`` is used to remove +it. + +Level is the minimum debug printing level to show in log. + +* ``trace``: Possibly very noisy messages which can be printed many times per second + +* ``debug``: Messages that happen often but they should happen only a couple of times per second + +* ``info``: Important state changes that happen rarely during normal execution + +* ``warining``: Enabled by default. Shows warnings about unexpected events which code managed to handle correctly. + +* ``error``: Enabled by default. Shows errors which code can't handle without user intervention. + +unset +----- +Delete a space separated list of filters + +Usage: ``debugfilter unset [id...]`` + +disable +------- +Disable a space separated list of filters but keep it in the filter list + +Usage: ``debugfilter disable [id...]`` + +enable +------ +Enable a space sperate list of filters + +Usage: ``debugfilter enable [id...]`` + .. _hotkeys: hotkeys diff --git a/docs/changelog.txt b/docs/changelog.txt index 083697b3b..3bf19b338 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +## New Plugins +- `debug`: manages runtime debug print category filtering + ## Fixes - `fix/dead-units`: fixed script trying to use missing isDiplomat function @@ -56,6 +59,18 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - New functions: - ``Units::isDiplomat(unit)`` - Exposed ``Screen::zoom()`` to C++ (was Lua-only) +- New classes: + - ``Signal`` to C++ only + - ``DebugCategory`` to C++ only (used through new macros) + - ``DebugManager`` to C++ only +- New macros: + - ``DBG_DECLARE`` + - ``DBG_EXTERN`` + - ``TRACE`` + - ``DEBUG`` + - ``INFO`` + - ``WARN`` + - ``ERR`` ## Lua - ``gui.widgets``: ``List:setChoices`` clones ``choices`` for internal table changes From c127ceab964ac75745613dd24d0675ac9d20c825 Mon Sep 17 00:00:00 2001 From: Stoyan Gaydarov Date: Sun, 8 Jul 2018 11:19:19 -0700 Subject: [PATCH 06/66] Use a unique_ptr for VersionInfo to avoid worrying about memory --- library/Process-darwin.cpp | 11 +++-------- library/Process-linux.cpp | 11 +++-------- library/Process-windows.cpp | 7 ++----- library/include/MemAccess.h | 5 +++-- 4 files changed, 11 insertions(+), 23 deletions(-) diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 2091d9a96..58e78d7ce 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -48,7 +48,7 @@ using namespace std; #include using namespace DFHack; -Process::Process(VersionInfoFactory * known_versions) +Process::Process(VersionInfoFactory * known_versions) : identified(false), my_pe(0) { int target_result; @@ -59,10 +59,6 @@ Process::Process(VersionInfoFactory * known_versions) real_path = realpath(path, NULL); } - identified = false; - my_descriptor = 0; - my_pe = 0; - md5wrapper md5; uint32_t length; uint8_t first_kb [1024]; @@ -73,7 +69,7 @@ Process::Process(VersionInfoFactory * known_versions) VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); if(vinfo) { - my_descriptor = new VersionInfo(*vinfo); + my_descriptor.reset(new VersionInfo(*vinfo)); identified = true; } else @@ -112,8 +108,7 @@ Process::Process(VersionInfoFactory * known_versions) Process::~Process() { - // destroy our copy of the memory descriptor - delete my_descriptor; + // Nothing to do here } string Process::doReadClassName (void * vptr) diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index fa06d28e4..f18eff5f8 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -46,7 +46,7 @@ using namespace std; #include using namespace DFHack; -Process::Process(VersionInfoFactory * known_versions) +Process::Process(VersionInfoFactory * known_versions) : identified(false), my_pe(0) { const char * dir_name = "/proc/self/"; const char * exe_link_name = "/proc/self/exe"; @@ -54,10 +54,6 @@ Process::Process(VersionInfoFactory * known_versions) const char * cmdline_name = "/proc/self/cmdline"; int target_result; - identified = false; - my_descriptor = 0; - my_pe = 0; - // valgrind replaces readlink for /proc/self/exe, but not open. char self_exe[1024]; memset(self_exe, 0, sizeof(self_exe)); @@ -77,7 +73,7 @@ Process::Process(VersionInfoFactory * known_versions) VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); if(vinfo) { - my_descriptor = new VersionInfo(*vinfo); + my_descriptor.reset(new VersionInfo(*vinfo)); identified = true; } else @@ -116,8 +112,7 @@ Process::Process(VersionInfoFactory * known_versions) Process::~Process() { - // destroy our copy of the memory descriptor - delete my_descriptor; + // Nothing to do here } string Process::doReadClassName (void * vptr) diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index 3a1da0ac4..aa20ec8b8 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -62,13 +62,11 @@ namespace DFHack char * base; }; } -Process::Process(VersionInfoFactory * factory) +Process::Process(VersionInfoFactory * factory) : identified(false) { HMODULE hmod = NULL; DWORD needed; bool found = false; - identified = false; - my_descriptor = NULL; d = new PlatformSpecific(); // open process @@ -102,7 +100,7 @@ Process::Process(VersionInfoFactory * factory) { identified = true; // give the process a data model and memory layout fixed for the base of first module - my_descriptor = new VersionInfo(*vinfo); + my_descriptor.reset(new VersionInfo(*vinfo)); my_descriptor->rebaseTo(getBase()); } else @@ -115,7 +113,6 @@ Process::Process(VersionInfoFactory * factory) Process::~Process() { // destroy our rebased copy of the memory descriptor - delete my_descriptor; if(d->sections != NULL) free(d->sections); } diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 3022688f9..da94e81df 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -33,6 +33,7 @@ distribution. #include #include #include +#include namespace DFHack { @@ -248,7 +249,7 @@ namespace DFHack /// get the symbol table extension of this process VersionInfo *getDescriptor() { - return my_descriptor; + return my_descriptor.get(); }; uintptr_t getBase(); /// get the DF Process ID @@ -291,7 +292,7 @@ namespace DFHack std::string getMD5() { return my_md5; } private: - VersionInfo * my_descriptor; + std::unique_ptr my_descriptor; PlatformSpecific *d; bool identified; uint32_t my_pid; From b5ddde8475ac08294cf133a6c7077aa03c323884 Mon Sep 17 00:00:00 2001 From: Stoyan Gaydarov Date: Sun, 8 Jul 2018 11:34:12 -0700 Subject: [PATCH 07/66] Use a shared_ptr to avoid having to manage VersionInfo vector memory --- library/Process-darwin.cpp | 2 +- library/Process-linux.cpp | 2 +- library/Process-windows.cpp | 2 +- library/VersionInfoFactory.cpp | 17 ++++++----------- library/include/VersionInfoFactory.h | 8 +++++--- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 58e78d7ce..614432584 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -66,7 +66,7 @@ Process::Process(VersionInfoFactory * known_versions) : identified(false), my_pe // get hash of the running DF process my_md5 = md5.getHashFromFile(real_path, length, (char *) first_kb); // create linux process, add it to the vector - VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); + const VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); if(vinfo) { my_descriptor.reset(new VersionInfo(*vinfo)); diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index f18eff5f8..37e55865e 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -70,7 +70,7 @@ Process::Process(VersionInfoFactory * known_versions) : identified(false), my_pe // get hash of the running DF process my_md5 = md5.getHashFromFile(self_exe_name, length, (char *) first_kb); // create linux process, add it to the vector - VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); + const VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); if(vinfo) { my_descriptor.reset(new VersionInfo(*vinfo)); diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index aa20ec8b8..a45afa619 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -95,7 +95,7 @@ Process::Process(VersionInfoFactory * factory) : identified(false) return; } my_pe = d->pe_header.FileHeader.TimeDateStamp; - VersionInfo* vinfo = factory->getVersionInfoByPETimestamp(my_pe); + const VersionInfo* vinfo = factory->getVersionInfoByPETimestamp(my_pe); if(vinfo) { identified = true; diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index c895a1510..7a15a2d81 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -52,31 +52,26 @@ VersionInfoFactory::~VersionInfoFactory() void VersionInfoFactory::clear() { - // for each stored version, delete - for(size_t i = 0; i < versions.size();i++) - { - delete versions[i]; - } versions.clear(); error = false; } -VersionInfo * VersionInfoFactory::getVersionInfoByMD5(string hash) +const VersionInfo * VersionInfoFactory::getVersionInfoByMD5(string hash) { for(size_t i = 0; i < versions.size();i++) { if(versions[i]->hasMD5(hash)) - return versions[i]; + return versions[i].get(); } return 0; } -VersionInfo * VersionInfoFactory::getVersionInfoByPETimestamp(uintptr_t timestamp) +const VersionInfo * VersionInfoFactory::getVersionInfoByPETimestamp(uintptr_t timestamp) { for(size_t i = 0; i < versions.size();i++) { if(versions[i]->hasPE(timestamp)) - return versions[i]; + return versions[i].get(); } return 0; } @@ -230,8 +225,8 @@ bool VersionInfoFactory::loadFile(string path_to_xml) const char *name = pMemInfo->Attribute("name"); if(name) { - VersionInfo *version = new VersionInfo(); - ParseVersion( pMemInfo , version ); + auto version = std::make_shared(); + ParseVersion( pMemInfo , version.get() ); versions.push_back(version); } } diff --git a/library/include/VersionInfoFactory.h b/library/include/VersionInfoFactory.h index 8a2cabdc3..a03c11aa1 100644 --- a/library/include/VersionInfoFactory.h +++ b/library/include/VersionInfoFactory.h @@ -25,6 +25,8 @@ distribution. #pragma once +#include + #include "Pragma.h" #include "Export.h" @@ -39,9 +41,9 @@ namespace DFHack ~VersionInfoFactory(); bool loadFile( std::string path_to_xml); bool isInErrorState() const {return error;}; - VersionInfo * getVersionInfoByMD5(std::string md5string); - VersionInfo * getVersionInfoByPETimestamp(uintptr_t timestamp); - std::vector versions; + const VersionInfo * getVersionInfoByMD5(std::string md5string); + const VersionInfo * getVersionInfoByPETimestamp(uintptr_t timestamp); + std::vector> versions; // trash existing list void clear(); private: From 12c8046f90dd59ef7f5f54ec79bb856b1eb7d2c5 Mon Sep 17 00:00:00 2001 From: Stoyan Gaydarov Date: Sun, 8 Jul 2018 12:04:53 -0700 Subject: [PATCH 08/66] Some memory management changes for Core --- library/Core.cpp | 16 +++++++--------- library/LuaApi.cpp | 4 ++-- library/include/Core.h | 5 +++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 69ef5a132..d4db9bb08 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1518,7 +1518,7 @@ Core::~Core() } Core::Core() : - d{new Private}, + d(new Private), script_path_mutex{}, HotkeyMutex{}, HotkeyCond{}, @@ -1630,10 +1630,10 @@ bool Core::Init() fatal(out.str()); return false; } - p = new DFHack::Process(vif); - vinfo = p->getDescriptor(); + std::unique_ptr local_p(new DFHack::Process(vif)); + vinfo = local_p->getDescriptor(); - if(!vinfo || !p->isIdentified()) + if(!vinfo || !local_p->isIdentified()) { if (!Version::git_xml_match()) { @@ -1664,11 +1664,10 @@ bool Core::Init() fatal("Not a known DF version.\n"); } errorstate = true; - delete p; - p = NULL; return false; } cerr << "Version: " << vinfo->getVersion() << endl; + p = std::move(local_p); #if defined(_WIN32) const OSType expected = OS_WINDOWS; @@ -2321,8 +2320,7 @@ int Core::Shutdown ( void ) } allModules.clear(); memset(&(s_mods), 0, sizeof(s_mods)); - delete d; - d = nullptr; + d.reset(); return -1; } @@ -2751,7 +2749,7 @@ void ClassNameCheck::getKnownClassNames(std::vector &names) MemoryPatcher::MemoryPatcher(Process *p_) : p(p_) { if (!p) - p = Core::getInstance().p; + p = Core::getInstance().p.get(); } MemoryPatcher::~MemoryPatcher() diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index a435c7a8e..2840aa5b9 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2521,7 +2521,7 @@ static const LuaWrapper::FunctionReg dfhack_internal_module[] = { static int internal_getmd5(lua_State *L) { - auto p = Core::getInstance().p; + auto& p = Core::getInstance().p; if (p->getDescriptor()->getOS() == OS_WINDOWS) luaL_error(L, "process MD5 not available on Windows"); lua_pushstring(L, p->getMD5().c_str()); @@ -2530,7 +2530,7 @@ static int internal_getmd5(lua_State *L) static int internal_getPE(lua_State *L) { - auto p = Core::getInstance().p; + auto& p = Core::getInstance().p; if (p->getDescriptor()->getOS() != OS_WINDOWS) luaL_error(L, "process PE timestamp not available on non-Windows"); lua_pushinteger(L, p->getPE()); diff --git a/library/include/Core.h b/library/include/Core.h index 529633ff2..9ea24f15a 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -30,6 +30,7 @@ distribution. #include #include #include +#include #include #include "Console.h" #include "modules/Graphic.h" @@ -191,7 +192,7 @@ namespace DFHack DFHack::Console &getConsole() { return con; } - DFHack::Process * p; + std::unique_ptr p; DFHack::VersionInfo * vinfo; DFHack::Windows::df_window * screen_window; @@ -209,7 +210,7 @@ namespace DFHack ~Core(); struct Private; - Private *d; + std::unique_ptr d; bool Init(); int Update (void); From 6cfd987c0d9bb4a36167e0a6fd9817f7478c9b79 Mon Sep 17 00:00:00 2001 From: Stoyan Gaydarov Date: Sun, 8 Jul 2018 12:05:34 -0700 Subject: [PATCH 09/66] Remove an outdated comment, with c++11 enabled the code is thread safe --- library/include/Core.h | 1 - 1 file changed, 1 deletion(-) diff --git a/library/include/Core.h b/library/include/Core.h index 9ea24f15a..d83ee9aea 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -136,7 +136,6 @@ namespace DFHack /// Get the single Core instance or make one. static Core& getInstance() { - // FIXME: add critical section for thread safety here. static Core instance; return instance; } From 699f864110c17ecc0fde36474aa950f175e0f535 Mon Sep 17 00:00:00 2001 From: Stoyan Gaydarov Date: Sun, 8 Jul 2018 16:10:01 -0700 Subject: [PATCH 10/66] use dts::make_unique instead of new --- library/Core.cpp | 3 ++- library/Process-darwin.cpp | 3 ++- library/Process-linux.cpp | 3 ++- library/Process-windows.cpp | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index d4db9bb08..e18b03140 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -42,6 +42,7 @@ using namespace std; #include "Core.h" #include "DataDefs.h" #include "Console.h" +#include "MiscUtils.h" #include "Module.h" #include "VersionInfoFactory.h" #include "VersionInfo.h" @@ -1518,7 +1519,7 @@ Core::~Core() } Core::Core() : - d(new Private), + d(dts::make_unique()), script_path_mutex{}, HotkeyMutex{}, HotkeyCond{}, diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 614432584..13657ca17 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -42,6 +42,7 @@ using namespace std; #include #include "MemAccess.h" #include "Memory.h" +#include "MiscUtils.h" #include "VersionInfoFactory.h" #include "VersionInfo.h" #include "Error.h" @@ -69,7 +70,7 @@ Process::Process(VersionInfoFactory * known_versions) : identified(false), my_pe const VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); if(vinfo) { - my_descriptor.reset(new VersionInfo(*vinfo)); + my_descriptor = dts::make_unique(*vinfo); identified = true; } else diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 37e55865e..c5dea6b46 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -40,6 +40,7 @@ using namespace std; #include #include "MemAccess.h" #include "Memory.h" +#include "MiscUtils.h" #include "VersionInfoFactory.h" #include "VersionInfo.h" #include "Error.h" @@ -73,7 +74,7 @@ Process::Process(VersionInfoFactory * known_versions) : identified(false), my_pe const VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); if(vinfo) { - my_descriptor.reset(new VersionInfo(*vinfo)); + my_descriptor = dts::make_unique(*vinfo); identified = true; } else diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index a45afa619..eee7c4feb 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -44,6 +44,7 @@ using namespace std; #include "Error.h" #include "MemAccess.h" #include "Memory.h" +#include "MiscUtils.h" using namespace DFHack; namespace DFHack { @@ -100,7 +101,7 @@ Process::Process(VersionInfoFactory * factory) : identified(false) { identified = true; // give the process a data model and memory layout fixed for the base of first module - my_descriptor.reset(new VersionInfo(*vinfo)); + my_descriptor = dts::make_unique(*vinfo); my_descriptor->rebaseTo(getBase()); } else From 6f90273bb6a81a5bb778c9d64a8a635dc8658ffe Mon Sep 17 00:00:00 2001 From: Stoyan Gaydarov Date: Sun, 8 Jul 2018 15:50:14 -0700 Subject: [PATCH 11/66] More usage of smart pointers throughout core and version info. --- library/Core.cpp | 25 +++++-------------------- library/DataStatics.cpp | 2 +- library/Process-darwin.cpp | 7 +++---- library/Process-linux.cpp | 7 +++---- library/Process-windows.cpp | 7 +++---- library/VersionInfoFactory.cpp | 20 ++++++++++---------- library/include/Core.h | 4 ++-- library/include/MemAccess.h | 16 +++++++++++----- library/include/VersionInfo.h | 15 +++++++++++++++ library/include/VersionInfoFactory.h | 6 +++--- 10 files changed, 56 insertions(+), 53 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index e18b03140..52705af1a 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1532,10 +1532,7 @@ Core::Core() : { // init the console. This must be always the first step! plug_mgr = 0; - vif = 0; - p = 0; errorstate = false; - vinfo = 0; started = false; memset(&(s_mods), 0, sizeof(s_mods)); @@ -1614,24 +1611,24 @@ bool Core::Init() #else const char * path = "hack\\symbols.xml"; #endif - vif = new DFHack::VersionInfoFactory(); + auto local_vif = dts::make_unique(); cerr << "Identifying DF version.\n"; try { - vif->loadFile(path); + local_vif->loadFile(path); } catch(Error::All & err) { std::stringstream out; out << "Error while reading symbols.xml:\n"; out << err.what() << std::endl; - delete vif; - vif = NULL; errorstate = true; fatal(out.str()); return false; } - std::unique_ptr local_p(new DFHack::Process(vif)); + vif = std::move(local_vif); + auto local_p = dts::make_unique(*vif); + local_p->ValidateDescriptionOS(); vinfo = local_p->getDescriptor(); if(!vinfo || !local_p->isIdentified()) @@ -1670,18 +1667,6 @@ bool Core::Init() cerr << "Version: " << vinfo->getVersion() << endl; p = std::move(local_p); -#if defined(_WIN32) - const OSType expected = OS_WINDOWS; -#elif defined(_DARWIN) - const OSType expected = OS_APPLE; -#else - const OSType expected = OS_LINUX; -#endif - if (expected != vinfo->getOS()) { - cerr << "OS mismatch; resetting to " << int(expected) << endl; - vinfo->setOS(expected); - } - // Init global object pointers df::global::InitGlobals(); diff --git a/library/DataStatics.cpp b/library/DataStatics.cpp index 1e4b21be9..31c8c0200 100644 --- a/library/DataStatics.cpp +++ b/library/DataStatics.cpp @@ -17,7 +17,7 @@ namespace { } #define INIT_GLOBAL_FUNCTION_PREFIX \ - DFHack::VersionInfo *global_table_ = DFHack::Core::getInstance().vinfo; \ + DFHack::VersionInfo *global_table_ = DFHack::Core::getInstance().vinfo.get(); \ void * tmp_; #define INIT_GLOBAL_FUNCTION_ITEM(type,name) \ diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 13657ca17..4e1abc108 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -42,14 +42,13 @@ using namespace std; #include #include "MemAccess.h" #include "Memory.h" -#include "MiscUtils.h" #include "VersionInfoFactory.h" #include "VersionInfo.h" #include "Error.h" #include using namespace DFHack; -Process::Process(VersionInfoFactory * known_versions) : identified(false), my_pe(0) +Process::Process(const VersionInfoFactory& known_versions) : identified(false), my_pe(0) { int target_result; @@ -67,10 +66,10 @@ Process::Process(VersionInfoFactory * known_versions) : identified(false), my_pe // get hash of the running DF process my_md5 = md5.getHashFromFile(real_path, length, (char *) first_kb); // create linux process, add it to the vector - const VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); + auto vinfo = known_versions.getVersionInfoByMD5(my_md5); if(vinfo) { - my_descriptor = dts::make_unique(*vinfo); + my_descriptor = std::make_shared(*vinfo); identified = true; } else diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index c5dea6b46..a7f09a7f6 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -40,14 +40,13 @@ using namespace std; #include #include "MemAccess.h" #include "Memory.h" -#include "MiscUtils.h" #include "VersionInfoFactory.h" #include "VersionInfo.h" #include "Error.h" #include using namespace DFHack; -Process::Process(VersionInfoFactory * known_versions) : identified(false), my_pe(0) +Process::Process(const VersionInfoFactory& known_versions) : identified(false), my_pe(0) { const char * dir_name = "/proc/self/"; const char * exe_link_name = "/proc/self/exe"; @@ -71,10 +70,10 @@ Process::Process(VersionInfoFactory * known_versions) : identified(false), my_pe // get hash of the running DF process my_md5 = md5.getHashFromFile(self_exe_name, length, (char *) first_kb); // create linux process, add it to the vector - const VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); + auto vinfo = known_versions.getVersionInfoByMD5(my_md5); if(vinfo) { - my_descriptor = dts::make_unique(*vinfo); + my_descriptor = std::make_shared(*vinfo); identified = true; } else diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index eee7c4feb..9e3b7d1fb 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -44,7 +44,6 @@ using namespace std; #include "Error.h" #include "MemAccess.h" #include "Memory.h" -#include "MiscUtils.h" using namespace DFHack; namespace DFHack { @@ -63,7 +62,7 @@ namespace DFHack char * base; }; } -Process::Process(VersionInfoFactory * factory) : identified(false) +Process::Process(const VersionInfoFactory& factory) : identified(false) { HMODULE hmod = NULL; DWORD needed; @@ -96,12 +95,12 @@ Process::Process(VersionInfoFactory * factory) : identified(false) return; } my_pe = d->pe_header.FileHeader.TimeDateStamp; - const VersionInfo* vinfo = factory->getVersionInfoByPETimestamp(my_pe); + auto vinfo = factory.getVersionInfoByPETimestamp(my_pe); if(vinfo) { identified = true; // give the process a data model and memory layout fixed for the base of first module - my_descriptor = dts::make_unique(*vinfo); + my_descriptor = std::make_shared(*vinfo); my_descriptor->rebaseTo(getBase()); } else diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index 7a15a2d81..4202df5bb 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -56,24 +56,24 @@ void VersionInfoFactory::clear() error = false; } -const VersionInfo * VersionInfoFactory::getVersionInfoByMD5(string hash) +std::shared_ptr VersionInfoFactory::getVersionInfoByMD5(string hash) const { - for(size_t i = 0; i < versions.size();i++) + for (const auto& version : versions) { - if(versions[i]->hasMD5(hash)) - return versions[i].get(); + if(version->hasMD5(hash)) + return version; } - return 0; + return nullptr; } -const VersionInfo * VersionInfoFactory::getVersionInfoByPETimestamp(uintptr_t timestamp) +std::shared_ptr VersionInfoFactory::getVersionInfoByPETimestamp(uintptr_t timestamp) const { - for(size_t i = 0; i < versions.size();i++) + for (const auto& version : versions) { - if(versions[i]->hasPE(timestamp)) - return versions[i].get(); + if(version->hasPE(timestamp)) + return version; } - return 0; + return nullptr; } void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) diff --git a/library/include/Core.h b/library/include/Core.h index d83ee9aea..8e70f928f 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -192,7 +192,7 @@ namespace DFHack DFHack::Console &getConsole() { return con; } std::unique_ptr p; - DFHack::VersionInfo * vinfo; + std::shared_ptr vinfo; DFHack::Windows::df_window * screen_window; static void print(const char *format, ...) Wformat(printf,1,2); @@ -235,7 +235,7 @@ namespace DFHack struct Cond; // FIXME: shouldn't be kept around like this - DFHack::VersionInfoFactory * vif; + std::unique_ptr vif; // Module storage struct { diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index da94e81df..7f3be74df 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -35,9 +35,10 @@ distribution. #include #include +#include "VersionInfo.h" + namespace DFHack { - struct VersionInfo; class Process; //class Window; class DFVector; @@ -79,7 +80,7 @@ namespace DFHack { public: /// this is the single most important destructor ever. ~px - Process(VersionInfoFactory * known_versions); + Process(const VersionInfoFactory& known_versions); ~Process(); /// read a 8-byte integer uint64_t readQuad(const void * address) @@ -247,10 +248,15 @@ namespace DFHack void getMemRanges(std::vector & ranges ); /// get the symbol table extension of this process - VersionInfo *getDescriptor() + std::shared_ptr getDescriptor() { - return my_descriptor.get(); + return my_descriptor; + }; + + void ValidateDescriptionOS() { + my_descriptor->ValidateOS(); }; + uintptr_t getBase(); /// get the DF Process ID int getPID(); @@ -292,7 +298,7 @@ namespace DFHack std::string getMD5() { return my_md5; } private: - std::unique_ptr my_descriptor; + std::shared_ptr my_descriptor; PlatformSpecific *d; bool identified; uint32_t my_pid; diff --git a/library/include/VersionInfo.h b/library/include/VersionInfo.h index dc959f1db..0d069c00c 100644 --- a/library/include/VersionInfo.h +++ b/library/include/VersionInfo.h @@ -26,6 +26,7 @@ distribution. #pragma once #include +#include #include #include #include @@ -169,5 +170,19 @@ namespace DFHack { return OS; }; + + void ValidateOS() { +#if defined(_WIN32) + const OSType expected = OS_WINDOWS; +#elif defined(_DARWIN) + const OSType expected = OS_APPLE; +#else + const OSType expected = OS_LINUX; +#endif + if (expected != getOS()) { + std::cerr << "OS mismatch; resetting to " << int(expected) << std::endl; + setOS(expected); + } + } }; } diff --git a/library/include/VersionInfoFactory.h b/library/include/VersionInfoFactory.h index a03c11aa1..a6b1a17d5 100644 --- a/library/include/VersionInfoFactory.h +++ b/library/include/VersionInfoFactory.h @@ -41,12 +41,12 @@ namespace DFHack ~VersionInfoFactory(); bool loadFile( std::string path_to_xml); bool isInErrorState() const {return error;}; - const VersionInfo * getVersionInfoByMD5(std::string md5string); - const VersionInfo * getVersionInfoByPETimestamp(uintptr_t timestamp); - std::vector> versions; + std::shared_ptr getVersionInfoByMD5(std::string md5string) const; + std::shared_ptr getVersionInfoByPETimestamp(uintptr_t timestamp) const; // trash existing list void clear(); private: + std::vector> versions; void ParseVersion (TiXmlElement* version, VersionInfo* mem); bool error; }; From 38cccdb0f42ad3228431b9f3d502c5c536a47c2e Mon Sep 17 00:00:00 2001 From: Stoyan Gaydarov Date: Sun, 8 Jul 2018 20:48:39 -0700 Subject: [PATCH 12/66] Update the module create calls to return unique_ptrs --- library/Core.cpp | 10 +++------- library/include/Core.h | 2 +- library/include/ModuleFactory.h | 10 +++++----- library/modules/Graphic.cpp | 5 +++-- library/modules/Materials.cpp | 5 ++--- library/modules/Notes.cpp | 5 +++-- 6 files changed, 17 insertions(+), 20 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 52705af1a..5eec30489 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2300,10 +2300,6 @@ int Core::Shutdown ( void ) plug_mgr = 0; } // invalidate all modules - for(size_t i = 0 ; i < allModules.size(); i++) - { - delete allModules[i]; - } allModules.clear(); memset(&(s_mods), 0, sizeof(s_mods)); d.reset(); @@ -2826,9 +2822,9 @@ TYPE * Core::get##TYPE() \ if(errorstate) return NULL;\ if(!s_mods.p##TYPE)\ {\ - Module * mod = create##TYPE();\ - s_mods.p##TYPE = (TYPE *) mod;\ - allModules.push_back(mod);\ + std::unique_ptr mod = create##TYPE();\ + s_mods.p##TYPE = (TYPE *) mod.get();\ + allModules.push_back(std::move(mod));\ }\ return s_mods.p##TYPE;\ } diff --git a/library/include/Core.h b/library/include/Core.h index 8e70f928f..0fec2774d 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -243,7 +243,7 @@ namespace DFHack Notes * pNotes; Graphic * pGraphic; } s_mods; - std::vector allModules; + std::vector> allModules; DFHack::PluginManager * plug_mgr; std::vector script_paths[2]; diff --git a/library/include/ModuleFactory.h b/library/include/ModuleFactory.h index 87c9a726f..5c3c149a2 100644 --- a/library/include/ModuleFactory.h +++ b/library/include/ModuleFactory.h @@ -27,13 +27,13 @@ distribution. #ifndef MODULE_FACTORY_H_INCLUDED #define MODULE_FACTORY_H_INCLUDED +#include + namespace DFHack { class Module; - Module* createGui(); - Module* createWorld(); - Module* createMaterials(); - Module* createNotes(); - Module* createGraphic(); + std::unique_ptr createMaterials(); + std::unique_ptr createNotes(); + std::unique_ptr createGraphic(); } #endif diff --git a/library/modules/Graphic.cpp b/library/modules/Graphic.cpp index e965bc7e1..1d84926e3 100644 --- a/library/modules/Graphic.cpp +++ b/library/modules/Graphic.cpp @@ -36,14 +36,15 @@ using namespace std; #include "Error.h" #include "VersionInfo.h" #include "MemAccess.h" +#include "MiscUtils.h" #include "ModuleFactory.h" #include "Core.h" using namespace DFHack; -Module* DFHack::createGraphic() +std::unique_ptr DFHack::createGraphic() { - return new Graphic(); + return dts::make_unique(); } struct Graphic::Private diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index a8f10d7d2..51d717d05 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -592,12 +592,11 @@ bool DFHack::isStoneInorganic(int material) return true; } -Module* DFHack::createMaterials() +std::unique_ptr DFHack::createMaterials() { - return new Materials(); + return dts::make_unique(); } - Materials::Materials() { } diff --git a/library/modules/Notes.cpp b/library/modules/Notes.cpp index b5215102f..04fb59e4d 100644 --- a/library/modules/Notes.cpp +++ b/library/modules/Notes.cpp @@ -33,6 +33,7 @@ using namespace std; #include "Types.h" #include "Error.h" #include "MemAccess.h" +#include "MiscUtils.h" #include "ModuleFactory.h" #include "Core.h" #include "modules/Notes.h" @@ -40,9 +41,9 @@ using namespace std; #include "df/ui.h" using namespace DFHack; -Module* DFHack::createNotes() +std::unique_ptr DFHack::createNotes() { - return new Notes(); + return dts::make_unique(); } // FIXME: not even a wrapper now From 143b557ad99a12a936f0c4595e149ca7ecedbb2b Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Sun, 5 Aug 2018 17:10:41 +0200 Subject: [PATCH 13/66] Added embark-assistant world match indication --- docs/changelog.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index fd53e4f4c..17ed0c25f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -40,7 +40,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Fixes - `building-hacks`: fixed error when dealing with custom animation tables - `devel/test-perlin`: fixed Lua error (``math.pow()``) -- `embark-assistant`: fixed crash when entering finder with a 16x16 embark selected, and added 16 to dimension choices +- `embark-assistant`: + - fixed crash when entering finder with a 16x16 embark selected, and added 16 to dimension choices + - added match indicator display on the right ("World") map - `labormanager`: - stopped assigning labors to ineligible dwarves, pets, etc. - stopped assigning invalid labors From 70630cfd923b3c01adcae0b5c3979507f0f18c5e Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Sun, 5 Aug 2018 17:11:26 +0200 Subject: [PATCH 14/66] Added embark-assistant world match indication --- plugins/embark-assistant/embark-assistant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp index 728d96f58..cfd44a30b 100644 --- a/plugins/embark-assistant/embark-assistant.cpp +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -143,7 +143,7 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector Date: Sun, 5 Aug 2018 17:11:39 +0200 Subject: [PATCH 15/66] Added embark-assistant world match indication --- plugins/embark-assistant/matcher.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index 28c72b76f..b2509336b 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -1518,11 +1518,11 @@ uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iter preliminary_matches = preliminary_world_match(survey_results, &iterator->finder, match_results); if (preliminary_matches == 0) { - out.printerr("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); + out.printerr("matcher::find: Preliminarily matching World Tiles: %i\n", preliminary_matches); return 0; } else { - out.print("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); + out.print("matcher::find: Preliminarily matching World Tiles: %i\n", preliminary_matches); } while (screen->location.region_pos.x != 0 || screen->location.region_pos.y != 0) { From f7fadaab377d1aab744a3327bc1359c383fdba05 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Sun, 5 Aug 2018 17:11:47 +0200 Subject: [PATCH 16/66] Added embark-assistant world match indication --- plugins/embark-assistant/overlay.cpp | 62 ++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/plugins/embark-assistant/overlay.cpp b/plugins/embark-assistant/overlay.cpp index 19615cac5..c0f5e0c8c 100644 --- a/plugins/embark-assistant/overlay.cpp +++ b/plugins/embark-assistant/overlay.cpp @@ -48,7 +48,7 @@ namespace embark_assist { std::vector embark_info; - Screen::Pen region_match_grid[16][16]; + Screen::Pen local_match_grid[16][16]; pen_column *world_match_grid = nullptr; uint16_t match_count = 0; @@ -60,26 +60,38 @@ namespace embark_assist { //==================================================================== -/* // Attempt to replicate the DF logic for sizing the right world map. This - // code seems to compute the values correctly, but the author hasn't been - // able to apply them at the same time as DF does to 100%. - // DF seems to round down on 0.5 values. - df::coord2d world_dimension_size(uint16_t available_screen, uint16_t map_size) { + // Logic for sizing the World map to the right. + df::coord2d world_dimension_size(uint16_t available_screen, uint16_t map_size) { uint16_t result; for (uint16_t factor = 1; factor < 17; factor++) { - result = map_size / factor; - if ((map_size - result * factor) * 2 != factor) { - result = (map_size + factor / 2) / factor; - } + result = ceil (double (map_size - 1) / factor); if (result <= available_screen) { - return {result, factor}; + if (factor == 1 && + map_size <= available_screen) { + return{ uint16_t(result + 1), factor }; + } + else if ((map_size == 129 && // Weird exceptions where the last row/column goes unused. + (factor == 6 || + factor == 7)) || + (map_size == 257 && + (factor == 5 || + factor == 11 || + factor == 12 || + factor == 14 || + factor == 15))) { + return{ uint16_t(result - 1), factor }; + } + else + { + return{ result, factor }; + } } } return{16, 16}; // Should never get here. } -*/ + //==================================================================== class ViewscreenOverlay : public df::viewscreen_choose_start_sitest @@ -155,6 +167,7 @@ namespace embark_assist { Screen::Pen pen_lr(' ', COLOR_LIGHTRED); Screen::Pen pen_w(' ', COLOR_WHITE); + Screen::Pen pen_g(' ', COLOR_GREY); Screen::paintString(pen_lr, width - 28, 20, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_I).c_str(), false); Screen::paintString(pen_w, width - 27, 20, ": Embark Assistant Info", false); @@ -166,6 +179,7 @@ namespace embark_assist { Screen::paintString(pen_w, width - 27, 23, ": Quit Embark Assistant", false); Screen::paintString(pen_w, width - 28, 25, "Matching World Tiles:", false); Screen::paintString(empty_pen, width - 6, 25, to_string(state->match_count), false); + Screen::paintString(pen_g, width - 28, 26, "(Those on the Region Map)", false); if (height > 25) { // Mask the vanilla DF find help as it's overridden. Screen::paintString(pen_w, 50, height - 2, " ", false); @@ -221,13 +235,25 @@ namespace embark_assist { for (uint8_t i = 0; i < 16; i++) { for (uint8_t k = 0; k < 16; k++) { - if (state->region_match_grid[i][k].ch) { - Screen::paintTile(state->region_match_grid[i][k], i + 1, k + 2); + if (state->local_match_grid[i][k].ch) { + Screen::paintTile(state->local_match_grid[i][k], i + 1, k + 2); } } } -/* // Stuff for trying to replicate the DF right world map sizing logic. Close, but not there. + uint16_t l_width = width - 30 - (ceil(double_t(width) / 2) - 5) + 1; // Horizontal space available for world map. + uint16_t l_height = height - 8 - 2 + 1; // Vertical space available for world map. + df::coord2d size_factor_x = world_dimension_size(l_width, world->worldgen.worldgen_parms.dim_x); + df::coord2d size_factor_y = world_dimension_size(l_height, world->worldgen.worldgen_parms.dim_y); + + for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { + for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { + if (state->world_match_grid[i][k].ch) { + Screen::paintTile(state->world_match_grid[i][k], width / 2 - 5 + min(size_factor_x.x - 1, i / size_factor_x.y), 2 + min(size_factor_y.x - 1, k / size_factor_y.y)); + } + } + } + /* // Stuff for trying to replicate the DF right world map sizing logic. Close, but not there. Screen::Pen pen(' ', COLOR_YELLOW); // Boundaries of the top level world map Screen::paintString(pen, width / 2 - 5, 2, "X", false); // Marks UL corner of right world map. Constant @@ -390,11 +416,11 @@ void embark_assist::overlay::set_mid_level_tile_match(embark_assist::defs::mlt_m for (uint8_t i = 0; i < 16; i++) { for (uint8_t k = 0; k < 16; k++) { if (mlt_matches[i][k]) { - state->region_match_grid[i][k] = green_x_pen; + state->local_match_grid[i][k] = green_x_pen; } else { - state->region_match_grid[i][k] = empty_pen; + state->local_match_grid[i][k] = empty_pen; } } } @@ -411,7 +437,7 @@ void embark_assist::overlay::clear_match_results() { for (uint8_t i = 0; i < 16; i++) { for (uint8_t k = 0; k < 16; k++) { - state->region_match_grid[i][k] = empty_pen; + state->local_match_grid[i][k] = empty_pen; } } } From 136eb0f03a074b22fe6a01b14ddd829ebe4d2160 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Sun, 5 Aug 2018 17:23:03 +0200 Subject: [PATCH 17/66] Added embark-assistant world match indication --- plugins/embark-assistant/help_ui.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp index 7c0763a71..0eae32c6b 100644 --- a/plugins/embark-assistant/help_ui.cpp +++ b/plugins/embark-assistant/help_ui.cpp @@ -171,8 +171,8 @@ namespace embark_assist{ 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."); help_text.push_back("In addition to the above, the Find functionality can also produce blinking"); - help_text.push_back("overlays over the region map and the middle world map to indicate where"); - help_text.push_back("matching embarks are found. The region display marks the top left corner of"); + 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"); help_text.push_back("a matching embark rectangle as a matching tile."); break; @@ -264,7 +264,7 @@ namespace embark_assist{ help_text.push_back(" reaching caverns that have been removed at world gen to fail to be"); help_text.push_back(" generated at all. It's likely this bug also affects magma pools."); help_text.push_back(" This plugin does not address this but scripts can correct it."); - help_text.push_back("Version 0.5 2018-07-13"); + help_text.push_back("Version 0.6 2018-08-05"); break; } From aa6182a1eebfbc132ae0abfe0f1ee6aca9d7715c Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Sun, 5 Aug 2018 17:28:49 +0200 Subject: [PATCH 18/66] Added embark-assistant world match indication --- plugins/embark-assistant/overlay.cpp | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/plugins/embark-assistant/overlay.cpp b/plugins/embark-assistant/overlay.cpp index c0f5e0c8c..bacd0b2d6 100644 --- a/plugins/embark-assistant/overlay.cpp +++ b/plugins/embark-assistant/overlay.cpp @@ -253,23 +253,6 @@ namespace embark_assist { } } } - /* // Stuff for trying to replicate the DF right world map sizing logic. Close, but not there. - Screen::Pen pen(' ', COLOR_YELLOW); - // Boundaries of the top level world map - Screen::paintString(pen, width / 2 - 5, 2, "X", false); // Marks UL corner of right world map. Constant -// Screen::paintString(pen, width - 30, 2, "X", false); // Marks UR corner of right world map area. -// Screen::paintString(pen, width / 2 - 5, height - 8, "X", false); // BL corner of right world map area. -// Screen::paintString(pen, width - 30, height - 8, "X", false); // BR corner of right world map area. - - uint16_t l_width = width - 30 - (width / 2 - 5) + 1; // Horizontal space available for right world map. - uint16_t l_height = height - 8 - 2 + 1; // Vertical space available for right world map. - df::coord2d size_factor_x = world_dimension_size(l_width, world->worldgen.worldgen_parms.dim_x); - df::coord2d size_factor_y = world_dimension_size(l_height, world->worldgen.worldgen_parms.dim_y); - - Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2, "X", false); - Screen::paintString(pen, width / 2 - 5, 2 + size_factor_y.x - 1, "X", false); - Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2 + size_factor_y.x - 1, "X", false); - */ } if (state->matching) { From c75a4fe8eebd3f8ef6b57f094e4c56c29f70eb9e Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Mon, 6 Aug 2018 11:52:22 +0200 Subject: [PATCH 19/66] Used taleden's world map size algorithm --- plugins/embark-assistant/overlay.cpp | 39 +++++----------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/plugins/embark-assistant/overlay.cpp b/plugins/embark-assistant/overlay.cpp index bacd0b2d6..63c70996c 100644 --- a/plugins/embark-assistant/overlay.cpp +++ b/plugins/embark-assistant/overlay.cpp @@ -61,35 +61,12 @@ namespace embark_assist { //==================================================================== // Logic for sizing the World map to the right. - df::coord2d world_dimension_size(uint16_t available_screen, uint16_t map_size) { - uint16_t result; + df::coord2d world_dimension_size(uint16_t map_size, uint16_t region_size) { + uint16_t factor = (map_size - 1 + region_size - 1) / region_size; + uint16_t result = (map_size + ((factor - 1) / 2)) / factor; + if (result > region_size) { result = region_size; } - for (uint16_t factor = 1; factor < 17; factor++) { - result = ceil (double (map_size - 1) / factor); - - if (result <= available_screen) { - if (factor == 1 && - map_size <= available_screen) { - return{ uint16_t(result + 1), factor }; - } - else if ((map_size == 129 && // Weird exceptions where the last row/column goes unused. - (factor == 6 || - factor == 7)) || - (map_size == 257 && - (factor == 5 || - factor == 11 || - factor == 12 || - factor == 14 || - factor == 15))) { - return{ uint16_t(result - 1), factor }; - } - else - { - return{ result, factor }; - } - } - } - return{16, 16}; // Should never get here. + return{ result, factor}; } //==================================================================== @@ -241,10 +218,8 @@ namespace embark_assist { } } - uint16_t l_width = width - 30 - (ceil(double_t(width) / 2) - 5) + 1; // Horizontal space available for world map. - uint16_t l_height = height - 8 - 2 + 1; // Vertical space available for world map. - df::coord2d size_factor_x = world_dimension_size(l_width, world->worldgen.worldgen_parms.dim_x); - df::coord2d size_factor_y = world_dimension_size(l_height, world->worldgen.worldgen_parms.dim_y); + df::coord2d size_factor_x = world_dimension_size(world->worldgen.worldgen_parms.dim_x, width / 2 - 24); + df::coord2d size_factor_y = world_dimension_size(world->worldgen.worldgen_parms.dim_y, height - 9); for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) { for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) { From 19a575df3ad11036dc6973c9fd5861af1eb5fa3c Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 9 Aug 2018 06:18:01 -0500 Subject: [PATCH 20/66] labormanager: assign more dwarves to cleaning I am fairly certain that Toady changed how cleaning jobs are spawned; this makes it so that cleaning actually happens with labormanager. --- plugins/labormanager/labormanager.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 7e42afd64..5f0003b75 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -1911,6 +1911,11 @@ public: labors_changed = true; } } + else if (l == df::unit_labor::CLEAN && best_score < 0) + { + if (Units::isValidLabor((*bestdwarf)->dwarf, l)) + set_labor(*bestdwarf, l, true); + } else if ((*bestdwarf)->state == IDLE) { if (Units::isValidLabor((*bestdwarf)->dwarf, l)) @@ -1961,6 +1966,10 @@ public: { set_labor(*d, l, true); } + + if (score < 0) + set_labor(*d, df::unit_labor::CLEAN, true); + if ((*d)->using_labor != df::unit_labor::NONE && (score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) && default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) @@ -1990,6 +1999,8 @@ public: set_labor(canary_dwarf, l, true); } + set_labor(canary_dwarf, df::unit_labor::CLEAN, true); + /* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ set_labor(canary_dwarf, df::unit_labor::REMOVE_CONSTRUCTION, true); From 039fb3bc6b5e7e3431ec22bc2ff69722b86ebd27 Mon Sep 17 00:00:00 2001 From: billw2012 Date: Fri, 10 Aug 2018 20:42:34 +0100 Subject: [PATCH 21/66] labormanager: add option to disable the management of a labor. Also switching to case insensitive labor name matching. --- plugins/labormanager/labormanager.cpp | 67 +++++++++++++++++++-------- 1 file changed, 48 insertions(+), 19 deletions(-) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 7e42afd64..55cbc8c0e 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -100,6 +100,11 @@ enum ConfigFlags { CF_ALLOW_HUNTING = 4, }; +// Value of 0 for max dwarfs means uncapped. +const int MAX_DWARFS_NONE = 0; +// Value < 0 for max dwarfs means don't manager the labor. +const int MAX_DWARFS_DISABLE = -1; + // Here go all the command declarations... // mostly to allow having the mandatory stuff on top of the file and commands on the bottom @@ -390,16 +395,18 @@ struct labor_info int idle_dwarfs; int busy_dwarfs; - int priority() { return config.ival(1); } + int priority() const { return config.ival(1); } void set_priority(int priority) { config.ival(1) = priority; } - int maximum_dwarfs() { return config.ival(2); } + bool is_disabled() const { return maximum_dwarfs() == MAX_DWARFS_DISABLE; } + int maximum_dwarfs() const { return config.ival(2); } void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(2) = maximum_dwarfs; } - int time_since_last_assigned() + int time_since_last_assigned() const { return (*df::global::cur_year - config.ival(3)) * 403200 + *df::global::cur_year_tick - config.ival(4); } + void mark_assigned() { config.ival(3) = (*df::global::cur_year); config.ival(4) = (*df::global::cur_year_tick); @@ -412,7 +419,6 @@ enum tools_enum { TOOLS_MAX }; - struct labor_default { int priority; @@ -841,6 +847,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector \n" " Set max number of dwarves assigned to a labor.\n" + " labormanager max disable\n" + " Don't attempt to assign any dwarves to a labor.\n" " labormanager max none\n" " Unrestrict the number of dwarves assigned to a labor.\n" " labormanager priority \n" @@ -944,7 +952,7 @@ private: private: void set_labor(dwarf_info_t* dwarf, df::unit_labor labor, bool value) { - if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) + if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor) && !labor_infos[labor].is_disabled()) { if (!Units::isValidLabor(dwarf->dwarf, labor)) { @@ -954,7 +962,6 @@ private: ENUM_KEY_STR(unit_labor, labor).c_str()); return; } - bool old = dwarf->dwarf->status.labors[labor]; dwarf->dwarf->status.labors[labor] = value; if (old != value) @@ -1782,9 +1789,13 @@ public: if (l == df::unit_labor::NONE) continue; - if (labor_infos[l].maximum_dwarfs() > 0 && - i->second > labor_infos[l].maximum_dwarfs()) - i->second = labor_infos[l].maximum_dwarfs(); + const int user_specified_max_dwarfs = labor_infos[l].maximum_dwarfs(); + + // Allow values less than 0, they will disable this labor. + if (user_specified_max_dwarfs != MAX_DWARFS_NONE && i->second > user_specified_max_dwarfs) + { + i->second = user_specified_max_dwarfs; + } int priority = labor_infos[l].priority(); @@ -2172,25 +2183,41 @@ void print_labor(df::unit_labor labor, color_ostream &out) out << labor_name << ": "; for (int i = 0; i < 20 - (int)labor_name.length(); i++) out << ' '; - out << "priority " << labor_infos[labor].priority() - << ", maximum " << labor_infos[labor].maximum_dwarfs() - << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs (" - << labor_infos[labor].busy_dwarfs << " busy, " - << labor_infos[labor].idle_dwarfs << " idle)" + const auto& labor_info = labor_infos[labor]; + if (labor_info.is_disabled()) + { + out << "DISABLED"; + } + else + { + out << "priority " << labor_info.priority(); + + if (labor_info.maximum_dwarfs() == MAX_DWARFS_NONE) + out << ", no maximum"; + else + out << ", maximum " << labor_info.maximum_dwarfs(); + } + + out << ", currently " << labor_info.active_dwarfs << " dwarfs (" + << labor_info.busy_dwarfs << " busy, " + << labor_info.idle_dwarfs << " idle)" << endl; } -df::unit_labor lookup_labor_by_name(std::string& name) +df::unit_labor lookup_labor_by_name(std::string name) { - df::unit_labor labor = df::unit_labor::NONE; + // We should accept incorrect casing, there is no ambiguity. + std::transform(name.begin(), name.end(), name.begin(), ::toupper); FOR_ENUM_ITEMS(unit_labor, test_labor) { if (name == ENUM_KEY_STR(unit_labor, test_labor)) - labor = test_labor; + { + return test_labor; + } } - return labor; + return df::unit_labor::NONE; } DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) @@ -2250,7 +2277,9 @@ command_result labormanager(color_ostream &out, std::vector & para int v; if (parameters[2] == "none") - v = 0; + v = MAX_DWARFS_NONE; + else if (parameters[2] == "disable") + v = MAX_DWARFS_DISABLE; else v = atoi(parameters[2].c_str()); From 76b8f4af0ed59ac486a55e1c9e9acb3ab2875e3a Mon Sep 17 00:00:00 2001 From: billw2012 Date: Fri, 10 Aug 2018 21:25:40 +0100 Subject: [PATCH 22/66] Fixing tab --- plugins/labormanager/labormanager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 55cbc8c0e..d7a2f6f44 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -2197,8 +2197,7 @@ void print_labor(df::unit_labor labor, color_ostream &out) else out << ", maximum " << labor_info.maximum_dwarfs(); } - - out << ", currently " << labor_info.active_dwarfs << " dwarfs (" + out << ", currently " << labor_info.active_dwarfs << " dwarfs (" << labor_info.busy_dwarfs << " busy, " << labor_info.idle_dwarfs << " idle)" << endl; From a404ab3096e7fb981ee7fd92445f703c397eecac Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Aug 2018 10:21:44 -0500 Subject: [PATCH 23/66] nestboxes: clean up & update description --- plugins/devel/nestboxes.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/devel/nestboxes.cpp b/plugins/devel/nestboxes.cpp index 634735f78..4276bef38 100644 --- a/plugins/devel/nestboxes.cpp +++ b/plugins/devel/nestboxes.cpp @@ -68,9 +68,11 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector & parameters) { CoreSuspender suspend; - bool clean = false; - int dump_count = 0; - int good_egg = 0; if (parameters.size() == 1) { if (parameters[0] == "enable") From c840321edf48c30721ea2eec6afb2a9372d5834f Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Aug 2018 10:23:15 -0500 Subject: [PATCH 24/66] move nestboxes out of devel --- plugins/CMakeLists.txt | 1 + plugins/devel/CMakeLists.txt | 1 - plugins/{devel => }/nestboxes.cpp | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename plugins/{devel => }/nestboxes.cpp (100%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 951a1d2d6..208da7125 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -138,6 +138,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(mode mode.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp) + DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(orders orders.cpp LINK_LIBRARIES jsoncpp_lib_static) DFHACK_PLUGIN(pathable pathable.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(petcapRemover petcapRemover.cpp) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 4fb4b5cf5..9478ba805 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -11,7 +11,6 @@ DFHACK_PLUGIN(eventExample eventExample.cpp) DFHACK_PLUGIN(frozen frozen.cpp) DFHACK_PLUGIN(kittens kittens.cpp) DFHACK_PLUGIN(memview memview.cpp memutils.cpp LINK_LIBRARIES lua) -DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(notes notes.cpp) DFHACK_PLUGIN(onceExample onceExample.cpp) DFHACK_PLUGIN(renderer-msg renderer-msg.cpp) diff --git a/plugins/devel/nestboxes.cpp b/plugins/nestboxes.cpp similarity index 100% rename from plugins/devel/nestboxes.cpp rename to plugins/nestboxes.cpp From 750b3cb8850a3ce186666e0b57c4922387402d8c Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Aug 2018 10:30:53 -0500 Subject: [PATCH 25/66] the tabmonster strikes again --- plugins/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 208da7125..0a21fc270 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -137,8 +137,8 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(mode mode.cpp) - DFHACK_PLUGIN(mousequery mousequery.cpp) - DFHACK_PLUGIN(nestboxes nestboxes.cpp) + DFHACK_PLUGIN(mousequery mousequery.cpp) + DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(orders orders.cpp LINK_LIBRARIES jsoncpp_lib_static) DFHACK_PLUGIN(pathable pathable.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(petcapRemover petcapRemover.cpp) From 784c3b1590d7db8c43cb2445afcbe31bc4333a77 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Aug 2018 10:32:50 -0500 Subject: [PATCH 26/66] a pox on invisible whitespace --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 0a21fc270..d0e7df34b 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -137,7 +137,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(mode mode.cpp) - DFHACK_PLUGIN(mousequery mousequery.cpp) + DFHACK_PLUGIN(mousequery mousequery.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(orders orders.cpp LINK_LIBRARIES jsoncpp_lib_static) DFHACK_PLUGIN(pathable pathable.cpp LINK_LIBRARIES lua) From 6275905644921414fd1997afbe6724fb2594a65c Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Thu, 16 Aug 2018 11:33:59 -0500 Subject: [PATCH 27/66] nestboxes: Use buildings.others[NEST_BOX] Should minimize performance impact (not that it was much before) --- plugins/nestboxes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/nestboxes.cpp b/plugins/nestboxes.cpp index 4276bef38..b527322c2 100644 --- a/plugins/nestboxes.cpp +++ b/plugins/nestboxes.cpp @@ -8,6 +8,7 @@ #include "df/ui.h" #include "df/building_nest_boxst.h" #include "df/building_type.h" +#include "df/buildings_other_id.h" #include "df/global_objects.h" #include "df/item.h" #include "df/unit.h" @@ -37,7 +38,7 @@ static void eggscan(color_ostream &out) { CoreSuspender suspend; - for (df::building *build : world->buildings.all) + for (df::building *build : world->buildings.other[df::buildings_other_id::NEST_BOX]) { auto type = build->getType(); if (df::enums::building_type::NestBox == type) From 866379204144f3ac84fa59ad8e4612c9adaef56c Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Sat, 18 Aug 2018 18:01:30 +0200 Subject: [PATCH 28/66] Made cancel state sensitive --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 17ed0c25f..d87131be7 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -43,6 +43,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `embark-assistant`: - fixed crash when entering finder with a 16x16 embark selected, and added 16 to dimension choices - added match indicator display on the right ("World") map + - changed 'c'ancel to abort find if it's under way and clear results if not, allowing use of partial surveys. - `labormanager`: - stopped assigning labors to ineligible dwarves, pets, etc. - stopped assigning invalid labors From 2ed6469be8e4753c3c9fd8e58e8db0286b616fb8 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Sat, 18 Aug 2018 18:02:19 +0200 Subject: [PATCH 29/66] Made cancel state sensitive --- plugins/embark-assistant/help_ui.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp index 0eae32c6b..395cbc25f 100644 --- a/plugins/embark-assistant/help_ui.cpp +++ b/plugins/embark-assistant/help_ui.cpp @@ -127,8 +127,8 @@ namespace embark_assist{ help_text.push_back("Main screen control keys used by the Embark Assistant:"); help_text.push_back("i: Info/Help. Brings up this display."); help_text.push_back("f: Brings up the Find Embark screen. See the Find page for more information."); - help_text.push_back("c: Clears the results of a Find operation, and also cancels an operation if"); - help_text.push_back(" one is under way."); + help_text.push_back("c: Clears the results of a Find operation, or cancels an operation if one is"); + help_text.push_back(" under way (at which time a second 'c' clears it)."); help_text.push_back("q: Quits the Embark Assistant and brings you back to the vanilla DF interface."); help_text.push_back(" It can be noted that the Embark Assistant automatically cancels itself"); help_text.push_back(" when DF leaves the embark screen either through Abort Game or by"); @@ -264,7 +264,7 @@ namespace embark_assist{ help_text.push_back(" reaching caverns that have been removed at world gen to fail to be"); help_text.push_back(" generated at all. It's likely this bug also affects magma pools."); help_text.push_back(" This plugin does not address this but scripts can correct it."); - help_text.push_back("Version 0.6 2018-08-05"); + help_text.push_back("Version 0.7 2018-08-18"); break; } From 73e7ffff837d7339deb362098e24827946e38f6a Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Sat, 18 Aug 2018 18:03:00 +0200 Subject: [PATCH 30/66] Made cancel state sensitive --- plugins/embark-assistant/overlay.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/embark-assistant/overlay.cpp b/plugins/embark-assistant/overlay.cpp index 63c70996c..75e6258a3 100644 --- a/plugins/embark-assistant/overlay.cpp +++ b/plugins/embark-assistant/overlay.cpp @@ -103,9 +103,13 @@ namespace embark_assist { state->embark_update(); } else if (input->count(df::interface_key::CUSTOM_C)) { - state->match_active = false; - state->matching = false; - state->clear_match_callback(); + if (state->matching) { + state->matching = false; + } + else { + state->match_active = false; + state->clear_match_callback(); + } } else if (input->count(df::interface_key::CUSTOM_F)) { if (!state->match_active && !state->matching) { From 239d4a8c466286b76f572cf96243f49d75866f8d Mon Sep 17 00:00:00 2001 From: billw2012 Date: Sun, 26 Aug 2018 13:38:03 +0100 Subject: [PATCH 31/66] Attempt to full exclude disabled labors from all relevant calculations. --- plugins/labormanager/labormanager.cpp | 127 +++++++++++++++----------- 1 file changed, 73 insertions(+), 54 deletions(-) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index d7a2f6f44..a3fd7db4a 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -1016,7 +1016,7 @@ private: df::unit_labor labor = labor_mapper->find_job_labor(j); - if (labor != df::unit_labor::NONE) + if (labor != df::unit_labor::NONE && !labor_infos[labor].is_disabled()) { labor_needed[labor]++; if (worker == -1) @@ -1143,7 +1143,7 @@ private: { df::item* item = *i; - if (item->flags.bits.dump) + if (item->flags.bits.dump && !labor_infos[df::unit_labor::HAUL_REFUSE].is_disabled()) labor_needed[df::unit_labor::HAUL_REFUSE]++; if (item->flags.whole & bad_flags.whole) @@ -1443,7 +1443,7 @@ private: FOR_ENUM_ITEMS(unit_labor, labor) { - if (labor == df::unit_labor::NONE) + if (labor == df::unit_labor::NONE || labor_infos[labor].is_disabled()) continue; df::job_skill skill = labor_to_skill[labor]; @@ -1463,7 +1463,7 @@ private: { FOR_ENUM_ITEMS(unit_labor, labor) { - if (labor == unit_labor::NONE) + if (labor == unit_labor::NONE || labor_infos[labor].is_disabled()) continue; if (Units::isValidLabor(dwarf->dwarf, labor)) set_labor(dwarf, labor, false); @@ -1707,67 +1707,79 @@ public: if (l == df::unit_labor::NONE) continue; - int before = labor_needed[l]; - - labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); + if (!labor_infos[l].is_disabled()) + { + int before = labor_needed[l]; - if (default_labor_infos[l].tool != TOOL_NONE) - labor_needed[l] = std::min(labor_needed[l], tool_count[default_labor_infos[l].tool] - tool_in_use[default_labor_infos[l].tool]); + labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); - if (print_debug && before != labor_needed[l]) - out.print("labor %s reduced from %d to %d\n", ENUM_KEY_STR(unit_labor, l).c_str(), before, labor_needed[l]); + if (default_labor_infos[l].tool != TOOL_NONE) + labor_needed[l] = std::min(labor_needed[l], tool_count[default_labor_infos[l].tool] - tool_in_use[default_labor_infos[l].tool]); + if (print_debug && before != labor_needed[l]) + out.print("labor %s reduced from %d to %d\n", ENUM_KEY_STR(unit_labor, l).c_str(), before, labor_needed[l]); + } + else + { + labor_needed[l] = 0; + } } /* assign food haulers for rotting food items */ - - if (priority_food > 0 && labor_infos[df::unit_labor::HAUL_FOOD].idle_dwarfs > 0) - priority_food = 1; - - if (print_debug) - out.print("priority food count = %d\n", priority_food); - - while (!available_dwarfs.empty() && priority_food > 0) + if (!labor_infos[df::unit_labor::HAUL_FOOD].is_disabled()) { - std::list::iterator bestdwarf = available_dwarfs.begin(); + if (priority_food > 0 && labor_infos[df::unit_labor::HAUL_FOOD].idle_dwarfs > 0) + priority_food = 1; - int best_score = INT_MIN; + if (print_debug) + out.print("priority food count = %d\n", priority_food); - for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) + while (!available_dwarfs.empty() && priority_food > 0) { - dwarf_info_t* d = (*k); + std::list::iterator bestdwarf = available_dwarfs.begin(); + + int best_score = INT_MIN; - if (Units::isValidLabor(d->dwarf, df::unit_labor::HAUL_FOOD)) + for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) { - int score = score_labor(d, df::unit_labor::HAUL_FOOD); + dwarf_info_t* d = (*k); - if (score > best_score) + if (Units::isValidLabor(d->dwarf, df::unit_labor::HAUL_FOOD)) { - bestdwarf = k; - best_score = score; + int score = score_labor(d, df::unit_labor::HAUL_FOOD); + + if (score > best_score) + { + bestdwarf = k; + best_score = score; + } } } - } - if (best_score > INT_MIN) - { - if (print_debug) - out.print("LABORMANAGER: assign \"%s\" labor %s score=%d (priority food)\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, df::unit_labor::HAUL_FOOD).c_str(), best_score); - - FOR_ENUM_ITEMS(unit_labor, l) + if (best_score > INT_MIN) { - if (l == df::unit_labor::NONE) - continue; - if (Units::isValidLabor((*bestdwarf)->dwarf, l)) - set_labor(*bestdwarf, l, l == df::unit_labor::HAUL_FOOD); + if (print_debug) + out.print("LABORMANAGER: assign \"%s\" labor %s score=%d (priority food)\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, df::unit_labor::HAUL_FOOD).c_str(), best_score); + + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + if (Units::isValidLabor((*bestdwarf)->dwarf, l)) + set_labor(*bestdwarf, l, l == df::unit_labor::HAUL_FOOD); + } + + available_dwarfs.erase(bestdwarf); + priority_food--; } + else + break; - available_dwarfs.erase(bestdwarf); - priority_food--; } - else - break; - + } + else + { + priority_food = 0; } if (print_debug) @@ -1786,12 +1798,11 @@ public: for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { df::unit_labor l = i->first; - if (l == df::unit_labor::NONE) + if (l == df::unit_labor::NONE || labor_infos[l].is_disabled()) continue; const int user_specified_max_dwarfs = labor_infos[l].maximum_dwarfs(); - // Allow values less than 0, they will disable this labor. if (user_specified_max_dwarfs != MAX_DWARFS_NONE && i->second > user_specified_max_dwarfs) { i->second = user_specified_max_dwarfs; @@ -1951,7 +1962,7 @@ public: FOR_ENUM_ITEMS(unit_labor, l) { - if (l == df::unit_labor::NONE) + if (l == df::unit_labor::NONE || labor_infos[l].is_disabled()) continue; if (l == (*d)->using_labor) continue; @@ -2002,12 +2013,17 @@ public: } /* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ - - set_labor(canary_dwarf, df::unit_labor::REMOVE_CONSTRUCTION, true); + if (!labor_infos[df::unit_labor::REMOVE_CONSTRUCTION].is_disabled()) + { + set_labor(canary_dwarf, df::unit_labor::REMOVE_CONSTRUCTION, true); + } /* Set HAUL_WATER so we can detect ponds that need to be filled ponds. */ - set_labor(canary_dwarf, df::unit_labor::HAUL_WATER, true); + if (!labor_infos[df::unit_labor::HAUL_WATER].is_disabled()) + { + set_labor(canary_dwarf, df::unit_labor::HAUL_WATER, true); + } if (print_debug) out.print("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str()); @@ -2027,7 +2043,7 @@ public: { FOR_ENUM_ITEMS(unit_labor, l) { - if (l == df::unit_labor::NONE) + if (l == df::unit_labor::NONE || labor_infos[l].is_disabled()) continue; if (Units::isValidLabor((*d)->dwarf, l)) @@ -2059,13 +2075,16 @@ public: } } - set_labor(*d, df::unit_labor::PULL_LEVER, true); + if (!labor_infos[df::unit_labor::PULL_LEVER].is_disabled()) + { + set_labor(*d, df::unit_labor::PULL_LEVER, true); + } if (any) continue; FOR_ENUM_ITEMS(unit_labor, l) { - if (l == df::unit_labor::NONE) + if (l == df::unit_labor::NONE || labor_infos[l].is_disabled()) continue; if (to_assign[l] > 0 || l == df::unit_labor::CLEAN) @@ -2086,7 +2105,7 @@ public: FOR_ENUM_ITEMS(unit_labor, l) { - if (l == df::unit_labor::NONE) + if (l == df::unit_labor::NONE || labor_infos[l].is_disabled()) continue; tools_enum t = default_labor_infos[l].tool; From 0809de2d56ca68a2fe88c286fc8a54a6ced166b4 Mon Sep 17 00:00:00 2001 From: ImgBotApp Date: Thu, 30 Aug 2018 06:18:07 +0000 Subject: [PATCH 32/66] [ImgBot] optimizes images *Total -- 141.35kb -> 100.12kb (29.17%) /reversing/doc/building-facing/screw/4.png -- 2.68kb -> 1.46kb (45.67%) /reversing/doc/building-facing/screw/2-windows.png -- 4.59kb -> 2.54kb (44.59%) /docs/images/hotkeys.png -- 56.84kb -> 31.62kb (44.38%) /reversing/doc/building-facing/screw/3.png -- 2.62kb -> 1.48kb (43.58%) /reversing/doc/building-facing/screw/2.png -- 2.68kb -> 1.53kb (43.18%) /reversing/doc/building-facing/screw/1.png -- 2.66kb -> 1.56kb (41.58%) /reversing/doc/building-facing/horizontal_axle/1.png -- 2.74kb -> 1.62kb (40.83%) /reversing/doc/building-facing/horizontal_axle/2.png -- 2.78kb -> 1.66kb (40.32%) /reversing/doc/building-facing/waterwheel/linux.png -- 3.94kb -> 2.76kb (29.89%) /reversing/doc/building-facing/horizontal_axle/2-windows.png -- 14.74kb -> 12.88kb (12.63%) /reversing/doc/building-facing/bridge/linux1.png -- 13.42kb -> 11.85kb (11.7%) /reversing/doc/building-facing/bridge/windows.png -- 15.11kb -> 13.71kb (9.32%) /reversing/doc/building-facing/waterwheel/windows.png -- 16.52kb -> 15.46kb (6.45%) --- docs/images/hotkeys.png | Bin 58207 -> 32376 bytes .../doc/building-facing/bridge/linux1.png | Bin 13739 -> 12132 bytes .../doc/building-facing/bridge/windows.png | Bin 15477 -> 14034 bytes .../doc/building-facing/horizontal_axle/1.png | Bin 2802 -> 1658 bytes .../horizontal_axle/2-windows.png | Bin 15097 -> 13191 bytes .../doc/building-facing/horizontal_axle/2.png | Bin 2847 -> 1699 bytes reversing/doc/building-facing/screw/1.png | Bin 2727 -> 1593 bytes .../doc/building-facing/screw/2-windows.png | Bin 4703 -> 2606 bytes reversing/doc/building-facing/screw/2.png | Bin 2749 -> 1562 bytes reversing/doc/building-facing/screw/3.png | Bin 2687 -> 1516 bytes reversing/doc/building-facing/screw/4.png | Bin 2746 -> 1492 bytes .../doc/building-facing/waterwheel/linux.png | Bin 4038 -> 2831 bytes .../building-facing/waterwheel/windows.png | Bin 16920 -> 15829 bytes 13 files changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/images/hotkeys.png b/docs/images/hotkeys.png index bdf9c76b8187d9254c801da9314c1bd0a1da4f3b..524ce9a52f45de4e5dbdc7e0a9e0cf708ee928b3 100644 GIT binary patch literal 32376 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

({L45L zXS1pGt8!1n-ja(s&VDzfSD#jTqj=%w*R1&*Q`ADzwyQ^*oy^(HS*NUZW|v@?fXADq zEfsGTy?)U&ah6Yo;{?ZojSu+_vM1_t6~557V!OHT_lu23Ih6EXx=b)xTqdO=nD)U^ z^Yc#)UXvX%Mp7p?DXN*DnKfa?%lf1{``J=?dv4UR=o@Es2s-n|rm^U!xeIQr&lA|Z zxUM2kZ~hz08+Xnei9008>KNm;!ZBd`)t;1{x0fGhIJoV-hfMsR9Mgo}%l9{*os{wC$Q9-r>Ob1)1#y3s(7lk{oox3!j%EGUwAvfX-@KT< z^s`inz?5b8`!1}wU47l^@6Mx*{Kt>C?3DP%d&kK6yZG;%Lvbl(4p-6^pJ7{GGfj?t zO}W|&vjaS@`%KerZ|LR7;7Aq@w^_B&+nMFiVW*bG)n_#3avDk%2~7H6BfLf+z{I8Z z=!=Zu9%qy&sq%TOCNJ$%iOSP&B3Rc zXY@2gE`7RmAykqhb4SSskr=BAv!-j>>t`0EGEHq;G*e0;O>eJnnr`=*zOz?$t^7Pa zlOaOmY4CH8n@1!AFC4nd^>{(+d7j=`2F}NPECUVp)NLs%WVDSb_%CuPYWc8%v&E2Sl~RP zOEF?*!Pg1Xlw+ZzxDo|w|-&Hxggtf zyWU)}>^2QJJH0SC{qzjZ%ceX0CL2ypd$n=)TF26PQ?h3`t(djqVcDfSPmLlEUpr#y zoq8vSH%jWrF~8aRi`CCh4dRVbRzEjMh}Wl$ZQa8*Ha&0VshqJ{89VLVf2_KYu5KIe#4XS#0iRy;9#L7Q$&(T(hg znVigXfAL&2FgS3>N^bgxnY(X3yyPgnWs%_8zS_kTd*&<#XJ9#Q|6g?Eiu z#9M@U&AJ74mB@Y;5Hj&de4@jhn6Z{KnIn}Wh{J`$*s_sdk-hfiKcyoJWpCXUR1@Cu z=t8Ue6y-S!WSi8Pgq(!GX>We(|8DwJf4PDee}sSY-;_W6rnrP>n(eORGM|rD?YU4c z@<$-$+KdV92U6YV@w9mI8mlE_GR&%;pSQcsee?Vq>TjZTgtO#Nu~|Fp)_Cx8W67EG zHrIEtZ{@u;bKC8+Hm5G8{bP>)`aQKfvRp;_(X%_dHHD9*z1n#Ah5R;at{ABvlY)nr zTw|It+?NO>9q0+loZ+y;@quH9(*h@tnR{M&3Og;E#gOi*oGamZnB$g358KQ-!7h`6 z#3fvfCr-2}^@J#HaB1XtUFC6osiGdA3x~Pbwf+Ukzxte~-^>%9XRC}hVEtq-Q(sYYsjD1GawyWPR7HPh$I`n!y&u#f@ zb5*wJ?Ur%-x9N9!)AboYrFJ~?Iv==s>29`lc}Fjsl*3Zu8ONO!zI*wcgw_@hne5)cbcVoG5KNkyUPVxTMLdhu35@!ruJ zGcFqN#tO7=$WZY)9B#vPEsS~N)phIH)<>B9N_F2m=Ygq%v-pgO`n<6Tdd62Sh#D`B z+qh_<)UKqv?U^6CelCu)?cTgNZm-v)2d#nn+I;ad1r0VVj@M%`uojw@;4rgooef*x zwF{xFWk=o=w7OlLl(%#LNv#9Z$39@bWfHVA;H- zW)>Sy&z1*mJRDIwLJBHsW*2J6Sx36{&DUFOnvr-V_0gN9%ic%sIQsaLb;`Upfu6jx z*A;|zYICWcTde6W%_18t*DLlgPn$!pJMW@Fiv!CHcT2aqIU5WW-_=DfV$6Ce%aIi6 zy_M%FM`gzJx5`oc*Sa^Dq|cOVR$ZO)Ea{F=aPw8AJ4V5WFYZY-@?;j;mwe!YfJy4S zcZ=H;dN|Hq*k=4y;#^G8YR=`w8_w^Nzj1!2)TRsKhhC}Fh~?dq5nfZU-MuBWhS9Ju z{^X9#XGIP@VZAf0;+?0mZMy00b3$r;zo@bJ$p~J%#mmoLe$c!i)p?ruI+3P;cNt<% zE+P>ETiX0*EM4gN`+`8InJr)VWpURp&&{jc{>TU%DXDgQ#-husxY$%+=7xto8AlyY z_h`&qF!gVbSRO;;`{@Usw^`j*XgJ^a@}!;LkKc*~M;D%!bv?zR*P`2^eK%b~PI}dC zBZujSPw;PJG=J`J;n4TSosO$Z&1>9v%%e9r=C9$j`fGZJx5qi`%KJ4LvK%FaFE%M> z+z@Lyc&0=QTJKPaIXu z_#t5&^_)$&N9v)P$h-6hXP(cy^Y^ksXXy-u&e}6y*8gr?kot{ranv`V6B-$*Ow-uH zcb~mc_h-+Y2GO_9TmJu8Gw-I|^UWz|-*(L2Il5 zNjY8P(wtvD+b5!Ez0b~{iME_|w^}%QR_Ih#w6itr-Sp~Z`qOQu`X}bTj-OU-wpZ(W zUR~(Bn?Jhl?0u-ZGyCr6qUW=ii+%_1etPZrw$rQH%}<_UH$TC(^^d=F>P_ZX{4) zj}k=m+8(4jPm`VKY~g(3fJabT$;m#An+sA8on;f=^yX>H<}WVd0d0Y6G*WVnPbWU% zQslgyzwLU?>*7tXW=&26KAPkfvnu0| zm{XhdmM<+b3y-zvuz0>v{^s9&J4iaG*ric?ac)NYUxR~J+ly@N(!TK4&Qw@&+Gfq^ z6|PdZ#Qs&bN2)W0dx#to*k$(W86x`{lZEEpwlT(jOW`W`SpGNZ;FF)t7 zl~OsDDRDUC>XE5t3ASq+o%>tgm8U&9<4_R!wr5V}1mnNAHk3~9F;Y(baVJ=0n?aKq z*Zk7URXt6vjeM`Gbog9%F7^&Q(dqUyXNK|ZTqbclZ*f+a&y}W|_4#Kdh<$BOSi$Ih zSg2{LB;%FGhTM)Ki<>`YJd}~q-cetyGS6MM?%WN37N3}PowYLCzZ}e%!aLpa)xVhr zou?HFUn;r1UG{Ku_QXjqHNu}2=U)7*F~fDfbGO@(P4gX%Km4?0nek(jck#~DXT>>T zeggU=)?+uFGx3O)(dpUyE|}+SdDOK{b=RMe+sA+BJdn_sk~}+y!!<$1Aa5hv zy4FkSXKY@EnjMb`yRkNC^#|JA(?J`nM*m#Cx#?nS^BjrRt2JS+A-!rDl8YV&0OpVuH(1^OR=8Dte z9M7^h)sA(|3rfT8{`z#syS~$|-PFICg-N{W=%i?$2S&=#hAjp)X&FtD7iXnpCz5M?j_nE8y*hIwVQr*SpXO?cyV7OQ(dZ>fbNUO>v z_K%FCYTIePbmc8Cwj~@n5wR{TLxRcXdO(Zd(wo0Bo(WD7m|^nZjB`bo?v9`D&J<5k zW;lDbY@57Jf9{NZ0*M)|oS~CV?Gj&z8&(aJFR%+t{}Gg^**19`}~%oP6hM!o2-AoK4^Qj{;hXy~(esRg2H>4qfH7)M7;07zWHCE=(?-vtjmm$A`xL3l1Kt`*!M#cFeW(L*n0sz_q=@hq6la7oFT}|_hA0^vKFL`}UA*pHFybp`-CstkRuic`^ zt$40QC-yr-&%I2Gm)VCrZMa&_2WNhG(YK&8`2ow?yuu4k7LFb#+19<-9Q3YZ8s|*2 zm{-b9b2uHOHWeK1)i~m&?6#xi0?SN3+n}Bd#S?A6-)DbgoX(q7(8WA$Z^(rhvy5%G zMO$^tf1f??+0T3{y?L7PqE>5OIiW2DHx-($S^S-OYr)d}>6fRScyUvEY0^Piqs*Jk zMsKINn})10Q%wGN`1aS9`t^5q{i@uVeOSBb{yZ+>$uKvltulY~do_{mNTK>MycH2#kd9@YIYpOC9x2@~l^yvKT)T4)Qr#xM3 zow{`14dp!tKOKL2Ld>mk(M1{Q1351?IBIi624?JJP2~QX@&86%WShVO#!SJcYqioT z^LV4m*IzBz6efQ?^TMr!rfW(py1e?{0(~Wx!dFZf^e!4*xp`Xf`HN2+;opK>syUV1 zeh2hWg!q# z@>@OiP*_PYU$HVD_k7R8sZCNj0tGn{jwhS~45#ZV2^<%AwCrW!q4cX4^z%ZR>=t{A z7(QxQ(CYo$U{>jB&gdPCtZrM5JvgQ;^~RvcOz?P^vnW$8{n*=J?*!BEesQPGt(7wt z@1K9;{FxI9+n5{qf3I-lPh8D2JtpZ(-ZD1N$7PyF)9(~isw|M3ZYot}V=`mm+?@+K z?9Q8s&NschVd+DyT?^esHnsU&n_$X1Kl5^cFqd)MijyoVy3Q{)Kg>JAWBaW^(AB?e z_U58nGwd$N|0;W5Cb{~5JIrhGIrN7!@Gf#8*-q4zx zs;9aoarNH=A1`f--?`g@Kn580c zWRly3BKPA;Hz#gbbAe;V@0|wUHz$1i96$4X?aDVD(?xqLN;};5SXn8@ecK#2ckTtz z+H=}+A2-ead?;gj>qWivBdggnK5R0-exl|Y_u{y7C&Kf4Y9cve^wcYME>39gX*lYn z9RG8N`Adr_Z0nfrNLQ&`J`l?J@ch!JT}{_4gI1rj{QBeoxBhHt9R-~SMy4_E%q1P2 zou|vboVCqk{^LugG26^?=k1hM+-I_VNzJ64oNVvhIwlx|_&ar*+C0ds;9%OZCwEVq z?wy#!cM6=HuTS}T@vPaI=l#zdP5K4ImoW<5-ki|NCf+jr1@}#Z{5+AyD(=sP)+}np zo==p1TN=0Qi8fX=s_HA+;=Es~ucTHu{c&-|j^B@%gu4XR6#TBf^D^Hgk7bb~^Y(rE zS8HWV1HT9NAAO;@`Rvn4$zN|I%s(x7!be`}K*@jgGnecPg_bh%o}LntapK}*Gmrl0 z{|~D+Syn9B`PC}#-#a&6LMS?2MU~I5IxekYep}OWUA(acQ@d+nL;Ji_dpFJlD*j^zgjF)S0KR8!e8q zczKx>Sv{9fP4HJ!8UqJhZWgXeeNY-O`5{<+|Ei$sg`Z%N&b zX0;oqe{88Q4nO+u++6m@r`g`#{rjob?Q~z(P3fAHU+xyi&Y!DM)856&mX~%()I`H= z{{F7~wud>zDqJyU3hu{Unx}2+v~!;Jw`SgN$+a23e|gS+_i<@6^VjF=e&5?(vZyVc zcZcw^6CEqN7YVNYx&8E!)bFoPR?BQL$YfDuHw}zWN_AS<~S+I>f~{tbHDH= zu>=$Dhp*0EHIi<8(YHMH+S?t1$1fW_UuSCc{@&MTLe4eY&amhi@7j9SMz7*PTvf+; z=a-!Zc?=t0ZG6!tRVDQLUZ8;b_8pO|ZvTqhw=VK#&z%s{>8uk(th$^}yKt&xHpK)_S)Bn%w7nNG@SsY_sDJkFq<5 z6^DbxwzL#(fmH|2&7QFG^`+Va9SU~d=JJc{s}}8d@P8$qAusW8^IwbiICmd~qcJpDo7gTS_e%D69<7Va_y8_({rUh>oOh+;O+A*o+8+T9P# zH7Ha+!Cw0R(T1geU6m(t#H>DBHv9b6m*;i4`YxuwNqhWVaH0Id(;aymn$BNTT;cY+ z^T6^%q2qqr8Ov{y8>_Oln(q(%W>7PQP29K*1T^e~Twf7G7simFhNckw6O51g8^*0xd=Y zUqqPVJ1$?-$^Lby>cdvHi{a7?rvwgN;Cm>zaFfsB6t-`dSx&!5Q@bJedFQs1QrpyS zZ4m9x>7Au~*Kkv)jjt8|Tpd|9UxUR_Yky~505zM_SDIX&TqC{3U{iBK>E51||=S)U;*tep?77eLQ(=Sb!YJ5xd0h@ljMvE#Z>xNSt?OP@k zL~wJmZ_{B}8$W-gBlrEqTi@>#PhHc-ap3M70Rw?OW^ZpOSI?^r{C4b{&F`i+_x{g$ zbLaP(H#auNZ+m}F{dUmxn|po&ugQ$`MxfDb5GxT%iDFe=U*0`KmCq5 zB2ND5ydQ=~*4{2=h||c}zRmf6`xf)tYj3arz2p1N=}PDKtNfl?QHKZ$8&;&c9t|xrOKH?ZEYK4{SMJ*!A{KyZhS1pLTzn^>|C=ZM~a2 zN|)!{*b{p#_g31P>(8aPy_wP*wO!M0+3c^+H-ui_9C|%l?&`F;4RzmU{5pF}{?_vw z+irhhmfkS$#;>*3JjVT1TTE-;oVAg2y59OVr|Ww1@0k6m?Q1UQ#;v}+FYR$yRNtyb^qy4^Lx{$|NA!a z&(pd7Pu9!-aliNHg>mKY$@)+4{;yG%ulY8&=Eu%oKJ)8;Ev@-5|NPUv^?$?f{rn;P z^EUhc3Fh}}{p0@sw*GYM{=JFe_p8F=DnG|o{_Oq!WU2Yq^XB#w#C!Rlwwl`+%9YL-xtG^HkGniSHf8$VeHFLArEl~7eeqvW-)8k{o;QX^3cLD4Yxc~mx6S(fXtu?> z{}-NLJTGv1k=*M;A8-8U>RY%@zQ3N)cHM%~u#k1Xll#8j{}OTh_tST?Z~d&P?St?mzL3dHsoPfAvpq ztJObwtybUd+kTbXTWWS+d*5-r^XZxM(yxtQ_as*5-kmmINc$#yTyrz;E_wD%0cmyqkN$Qg+%DcOe>=PH z{J!a5zdm%mx#v0S%`LwxZ}L<>V7U-tRYC=Qr2NyqN?AoDe zC#CMrGsy5QJ(+T{p_>3nX)TH{?vSCTWefbzKv65>Eq?@ z$|k~#1f}M<^v*Sil#2VrzSH2^he@ZOHg)SwX6x0T_)9!a=c|<2v?_6vX`f%2Pbz+G z9wP2_m#x<)a&MdX>7U|RyN^wul`0iiv-9O?n^oL;UB9|+{Pb|{xc&5y*$$^%nZn%} zzt>N2o4)Gwx+%w|{|}5+6<_(nGGphpg}pDY=Xmd_?N#P2iC<8sd{;j_=AJ^{^LN4L zw!K*VlKphZs~@hX)9d*(zsI}YPW*l_Z~5lEAI)whr@Sy^DBi;(GyTO4m)u3Qoxl3~ zx-I#3`9C+`S)Tdy@!t=lziIW)H=KR<@#yU%!tp$!UJwvEBC8u@(P$=AOU# zZ_e8-$8G=pJ#b&-#YSh_y7Ol~_HQ@bT&uOa_Sf#DZN9&+rd|;5`*<`wMtOQ%>E4(+ z-{75(eu))@UcUD1_N$MnmnAkHetz)->xCb6oBC?oJf{CUyfotj$4k>aA-_cWC*3>j z?(u%%;icCnC|)Xl;Gp^HcKEI@Z!~4QcX|ERi(QhtT<_AZWbw?K`&NTCXV2RHI&EKo zw81B_*e1!^d1v~B%z9rR+BI80!z}(%Po+-((}I;Mc{}f~+8lF#Q}Um;Ki)pQ_3VP4 z@~^T#eHX{PfDF{$j_by_%fc41b+!&WQEhP*=0CoNe3k&C`y5EsMM?zi;!} z;4Qhk_N~s((XPLe+p%rs(vR)0qcm#kocGk;V!K_|E7yKTJbU)OPlv^OXFq1QXnVGE zTIx6bd-As5F1YmDZ@a8B+pN#pvx{H#4T}ZEw-afBz1r zUca;A^VuzXDr;;$8u!=Vi-_mbb>9E1{77E$7vqDQEZ6nQoqd0S_g1{Hj`Y{!ue~4b zW+aPse^>nV^X0}v=N;?WBrUFeD?jwcc-r-UZ|aWSE@Zuy`O>CO^VQj^w;NBdxh=Q) zP2sMX`t^Fdm(7eRDh=NGW0jg^K-tNw7G)M~7i@}pAHPZ~TFLzR1%K)E?AspPo9A_u z-!Zyl#&!C}Q{VcDr$26Xp1JK{QQ7>n=M9+cH+THpd31mJkGThaT{Qp3{JXR>@BPv6 zyBgwuH+Gur`^1*{rTz8OL#NkIZkryf%&WIE>Q($7cgz2aw3o&`)?N~~*#1Oi!NaFP z+ZAKfo(lzTm9{KPGRR47x|X^1{G_|T=S_aJadOh+vvZjZqgsX6MQzOz1cQmpiQLN3 zosO@k7{5NTZZCVv3D6R6+)rN^L2KF1%w|;KE{_cpzQdX|531jJaq2EJ#gl zy0-G;(q`w=i@e#|r}P;w%7bg3;xTxFh(HG^3 zO8@gt=4SN+*5c^$JCACg?8poXvb%bxRA76%oBG0YogFWZh;y+|eb}y0Z@0GQbja58 zj~!pk5Ei`H$^YJ)wfS29c{~4a_TMK>@t>=me09dj&1b)7JUm_X@vh|yl`EElf^&a3 zJzQY)N8ri%<$)P9SAD76!y#L&q-6E+VeQI<3%~B}e15u7JZ@s=X0g*Pk=-eO&VZKZ zGu_<#zw-0bwdh9dPR)F^CSbPH=enKo@fr8@*Ym-A5--9Nwcmbyqs{kAtBq3c70tBC zpXoDe^)sERd(ZFH5dZry>$2wxu)VE^98-5N9({UU{jB=bop-AC=Pvx|l>6ywXgee* z^7`pr(cm(GPRhZ$)8%4|=J+51ZiOx>8T&4`o?ykKR{ z6?H!Q!mMotOLti&|L0zRVrl)Jb0tEE_^R_*`KBgl=IOQHZ~ywNm^s~3%U%ZV**Vu= z@NY`}UTn0Z#_yQG*5d!&MW^y#d#%0yyeDJrl$CxLcD>PJ(^XnDOGkuH>oC9i)VbeJ zb6x%Y`RA*cZFkn|o7C@cIu1@573){jU!1xr_dR!zO<`i+-Shi&md_3K&zc)5KL6sc zZl8I(ebgQoM|}=IA;KH;+hUQcOUPqT0RA|5WPbT+r_zOHt*0hG`}=7^fOL7}{&xkf zec&knP*~MS z*qh52X1&=`8Dv+xFT^Ka;qfy4DR+y+XWd<=)y_`4I63|Fy4v%ehrRzL zSaFuVE4qIKlxaIU{8AR4dF(V>ZSC$;tMmUmzpF?}@4J5gXM|MzO_!{>VZU{RS9Klt zn>Oi_W>C<=Q_D|%yZd`d-DB_I^$_hV)HqV*#BXhRcgz3Zw|@VpALp)LZEke$=MUqZ zi#(-QmY$Lx(`n}M zRTZ>OPOyXK$fr=mCe@5@XHoBLZaW3J}y^0!kN54)dkc(ms1?XpulpPT#a z1#4AWG)rMhWlV-~LP%@PzsHW1zq2Q9-ye9t>rZU$$IFhH_k)|St@pD%%(Ax5`E*gv ztzE(AR+c-WIb1PW`^oBfzEk#ff{zzBPt50&o8tFZV2hvQgv!x1hq9j@IEWGp zFKkkLeShPf1zWS%f04D);&#l|ZWaRvD3rOpPUa1lxVkA(TV~50L0vXout%*p zTaEg;bK9nbp9q&)Wp}9{^{|-R_I+13=~+6S=8WWt0$*lQvWW{6mppROit+!70_PLB`5E3!c{Whux1T?a%m`|H1t?ul?I!lTT?Y$ID;6XfHU|=Fjz>!l;C_T++6oqKQomdc&XwU|^ZeDUPbdRxmm z*QXzjoYv9tB1Pzwjouc+a@i{jS_{4kw%9FhUGTJRO|syYoX8dD*80!VUvlA7**$&! zt3SVH-U?ZEZvFS$J3iN0E3JzBv;XUb`}=gcukLjEnm4od-F8JKC99h);E>L`aOlNU z-iU(>N>7N3$sb%GS|$Ab^r~BG^0)6zT_3w|)2Va2ol5H>Q=fOPc($qjz1@Dh%zINq zo=;cbs;s1B6$v)vj=oM=YTo;Q+RrZ?>`^Yb?)bg;cD?ZS&vw!C^Ch?Y+GS4XpZYs> z>fG|ySARC0Iwu@>HuS~&`{#b=?A2a*PTxPv-NmJ36F7VFTwc%`kh*`xjYHyM&)L={ zrcSiTk?mfP`rLWdrXKCLpB5JFU$g1+x`4UyVK*z4R_)~a`}g(qOGNJU%GOi z)pT9|Rp*b#tF7!0*ae8oBa)7OtZy(S!Z_R0Uf;bj;e?s~l7Yd&xIy7xI3etpeM zKh<~lcS^!X3CV;WGqdaqR@-MaO{=|p?z`K&ZH1?uFKsE@`FGc)ZH4pOpWQWzez(2w zb?nbuw<_ZoRw=Er{Imc2h5P$;xv#dqT3hq=-6v48nhj1?`xaakb$mVLa=o_o_czlI zE`4fLk#cR?+ucj@{84IOY4kSCcxHH8^cXV{{iGmW>!SpR} z&Kfk8%i3%{_dfOh=JTgtbYAY+#CbX8%8!-LiVm^$>iMA-56!oC*Dqc8%9%I(gCkF> ztl*Ycb86Sw{66GuRQPPqiJnTM#A7ixH~){!EIIjfW%!h1+j4LB)Rg|bCDW&T*M4d7 zn*RS2Ye)kv;Ha7@R*rZuB|)NC;s$%^6BdHXuV#i*UOaG8`(E2f82a(`@dHa z@%#RLE}XtPOaAJ^h;_3k{S02=Te=~5#kyr1f>*pNkm_v07;MasUu-bq@S+In{Bdcm(0_DJa;Pp zS7zMP<-Yp!k6GsP*ll_%efL?N&JFh1m-^Rg|GnJD)1%*Q`gyDM$LW?|8&VhUa{8Ki z>dl_`ch`4*&YL}X=3K6`yC<*pK0W`_)te{Rf4|)}?fdQaTM1lORnP8DEh~EM^mWD` zv!6eL_gPs#&3=3>blJWOd*`L{GA;W$V?Sw13~%X?H$8 zXfAr*TtChF-;38UW{S!DkZB?5%eQ>4n>p91bRk&i{ifi3M%JO*eD@Vi zpZjU*&6By4kNAbo{eJq6=9PPXS55t0-2F5@@3@fGz_#|hX~y2Qj<0ihqw0VETJXzt z--QKN*&JUtb?aSQ@YQ_h=kxq0WBF~@^atLk`r2#$Tr&BBX z?EKvR6|R1>XSG&oU1jR?SkKa*(^mEb*w;nQGW+@J(^lSw>-YYxw{HJ5c_Xrl*?Q^9&B2#y zI_y=i{(hQyLo@SU>hdj5r_G)8?DDkT!7J=C_X${sZaY_`UVd+V@$w6s-aWalepdM6 zL}5??Be}xyb@zP!Q@ihX?)iQ8{HJ%<@2|{%dRo4ITAA%H7v-q+dw)*5v+g0+;wXu; zyWTdkgQ89G`rU}Vj@qDFXL|n3EvpvyPo7wRN6Y%X*Q(WLjHb>lSH1c)dYO^a-yf-& zx1Ov%ecfz{(dqVApSH4ZzgNp+>pyAP*^{UILg%jMu~wbBvwwzD?xi!k--N_|wzF0} z8{Rhe(zNzhZ>E^YC#nD6dERf~E|oH`S>-o1L)go-Lh{tN`v&ct%eUQEs!S{7+2-KX z*9ZI-T5SlvWFshR39iNuF4lf>dVc*x{{6ol?f!hr{`q0{`KO21+fQC!_b1Tm%L~n| z=kjlO#9a;8`srPD%GJByPo4e6p}+szteDG7JYR`EeLUagsr5>)RmG<#-x3MB`c$L( zO-xWv z{qKTa-}%M+WNCQ(IAyuGKd-7NETI^8l4R58W}NpH8|EsvZY{d~J`)U|25FKy9OxxXpz z{<!JN=W+Y*nd;j`+~}n)#cCsmU;eJ88Ex1 zbcR;&d%wEK6})QE^QKwv{JgNL+-ueLr=DMDa9vI2y1M)GwtK0|17d~hSNZI<-&vY@ z`$zB!z07$XeRIH7%Y;papT3^AKV`rFcZ}W7XYsF`e?EE5|8%ze{;LbF`ew}y-9B&U z)Zb@Zy;H6&TKY@E^24F-qL)*{m&OTRjrqrE;NQ`CB6WFy?8LcFrB_e-EwoZvbP8NC z^Jq%z#{d7){O9v+f76t|!Oqv%U)AzP-S;^DE_P|y+s)@DGN0eCA)J0@DrcW9`@G4= zrpL{zat+yhv*73iR~Nl{uB{6vPq%NqCBs+GwN+7RRTtQ`4+2v^UG29&;T~VFC13y5 z|L61R@+XeJdM$5~A)0x?>hJdSllPdIg1z0$yem zKknw=|0~As&!_vJ`t|K5@!S9RXuei{Y2ONuthq&Qpms!qNdG*Aqsg1Ly}zUURm#k7 z?;?{=y=^-dt9s_H(z^0|V#N8A2jsRV7ryCxx@&Fn-hat2D=hwj>~VL7+7j{eQ~UfU zf7kDu#=q~vhOa;CpERHUryO7VznN|Ayr9?DmtQqo9PaFO+L&#%u63FB-Cw-H>sJVT zSJA!S8MW_M&FQDfzqg$J{W-+)_@f&;U)zYi-u`Lr?}*&X7azPU-?L~C#vVyFMjwsJv01Dc=NTDkTzSu$s0E$BqMCx%<^xm zKEJkG0bD(t`80P0xKI(;o#U~>_G!6J-`$V@K3m<}{=VSqm%qwNN=ilG&I4QDr3GIf zJ63+rzPN#P|B2nNymhm$@11|;*Hz8*84FAo?9efiam&7NOY^ymy5s3-Kku0RY?-#z z>FvvapM7)f<+V10MpK@jYrnN4wQjC{{Op?2y4n@K`>eF%XD_iTzn44NKO?fda{rn2 z-*5jY&D$HFxAXL~Y4=XH`#pUbZXl^;J}-7)%YWAc8Q zx~{a==JTtKAcgC~ZQ$O*$>aI+^Y7Vxf9{<7_gh`;zHfQbN=izLggwFW`ayy>%3glT z_xcMHmfHQW`!sdh*7u_9Yh`zTn;E!<@vu_hgolKlg%lXx-OaG5} zI=funIX7n8x%K7G|5jPQFM56F_=V4_&g`D_N^7&vinY3%pDr}}`Qz>6XStCtcAnRJ zy`5h(#2@T?a07rxGFmbI@B0NcKQ=#ch&*nWccIAX+C;7>&(G_Y?5a5}A{6zl;FtBy zAIG)s@A)lJB{$1otN7{aa#UM#SFADm^OXDS?oU@EGjBQE0^PRca(r{Zv%-aEFMQ(q z`D=Rfr;GZNm{VncS1CvBoX#h=-%)#|@1%g=r@j>$eeMr^abss{UG2i3ON_$zRc$|4 z%VWF#)E@1X=XAf{7MbO*wVJ2?r_<+kCb`b3o~1u;frc38a)8^5&e|p!vNjs>-A{hF zowoY=*?zHT`x=dmw-&2FNoCTl9Zy@IygseFIOSW#&E#kD`)f+`B3JZAr=O0=eEoUq z&C2~Y@wWq{L8a!M^`BpVzPj}3_lMsmhrBy=T`fK~^_BprJEQ@&>F_6(wdc$-K#uP` zpCNd0;?V|Y7Z;b1TCglf6i4X)D+N!jOfnv(Nqqa{cyxhlNm7CPB#CXHK0RMND3+I9 zay%_z7G-7(={#Tb)Bq1h*ziTo&)88^J6mGQqtopDM>?)e{B!>I!!K|3Uvq7>pDBAf zxmf3VuI%ft!h(W=7bl8?+5no@7PJOHdQV%{c&lZ+y|y4!hF!N*MNjsNe}J?4nyZCO z(yz>U>vyjDR%~P;cKlfE^NkVvr{>>1o;CZD)t$H2_rCXcbabrP1xl=1% z-Y(zsUS3d8Ft8L{LicnuUt2DI^VO%1(^q@Ro6fYg)7!6i<=*`7r~g`&+g&-P`}uXv&s$Gce}3(p`{`=r&s!>2 zLG5CvJ9d9k|AI1hO=;LZvpiW)cyy|P12L#s%<1ZLsdW#!Z}3NjJz>{v(JAIkRLh73 zW!WW`KeetLTf6{gzcy8hnUVtHqza%thFRUWU@x;RELai?g%@4}x;Hf{Nn>Rb!X?4Tl9-vnF~Edm!sQ!+sz z8U_kcTfr@LwYO$&3U0opz@oz&y+`t#K#ugmWH zs`l$$IhQ{&aWK*^&=l5v&tYwa`8)6Cyzp|}W{%P5F_~q^RYg}8GCO6y9R&GB&wWFg$ zBODZuY9d<{gtz$pe;YY-0qe{KTfay0M%8=lUF7sfc;|)A*wQ&KT7T&t*;YP%ZH|0M zM5#>h_wgcZ9uA zIj7IPb!FG#m&NNUCD%Hs7jC;JzN-9r?q2QqSjG0uIlk%ry|#A$HWt0U`zc`V)9b%;bN6bmJQsUz z{rA_Gt~l>A%ZruzIqm!DNzZDx*^Utp(-#nk{L6Yi{ zT{XFvHLlFJ2X#clZ&q$UGfmx=|LpQyP`O{S2wVy%WGIH*yuB-W=7Q3ww=ayNoZZ4^ zyuWm4F1z5iZSQVrf0d4%1h(>})|F$|Zku_&oqX!D#hlb^qyIZp&i>A+&5)cQKmW?U zfSsq;dxP?f=qmjqdhb8Ip7d<;sY$Q6&bB{^R*R1fid?}9F6)#)WnHjA#@woB@6UYC z;MI}TX6`!vDtRZ5l%?OcH#d~ON`;*l7i;(1^=DInbojz5?cCiH4o7ZN``qun@aF_j zPiM)uh2Y^o&s4?C+dKcHYFUH5mtm)J-{|M7NvA-gLmeG{BH&1Gn$9;5)NtKcJ^zb* zoZ8>EsWXrNJM4aX+1uSu_Haurj|)_6^*eH>a=+e69TkXG%QhcB_VhZav()-(@+}qc zkmdK&pdMDMNYuUyd*Yv6mw#8Y&jZSh0l{48tl&-oX9bJ4<8-uD+? zTKZk!Z({YEKf6HP zmxX2vt2nOi{`~Lrg+1|cEA&F*JNB=+@4xU@!0aWvPF-sU&-*O-6#!|4xq{MX+7h<4 z?@dE$*F8Md`XMw0G@qLKc$(~`ebx1u;aARQ++MqXP27IZb8G7pUx@~ueLCMlzEk+6 z@-x2fg>sS3xkB;yB_xY@TQSkJA7tb1)<=$4ETg^Yw zZ!g>I16TJQXNc@w5W2sr8PvY`{_u-z)H1EV7e1c;XS4cOvF)9wpv?Z_h#;tW@$%OC zhy>Mm0 z;m;F$CvLa@vpuHLS$kz^)!fagug~qhly8F$perAB=Q+v6EyOeL*UAd+U8ZWEcy<}6sY?D%u zS3``!Ue(+8Nb2UU-wizLXGlt#dv{%Z$E<%M@_DSn)>m_i_r1CEeGZSTwQF0?Or>Ub z|4EIh!l|G8_Fnq&$$nW}pk1b)ANSUcU$^-N{a*5^=1%IJqU!a$*=mXy0!56T{F+8b&+VEm6!tB6!t~VEUb!OfBL?F7{%LV@2B5NzdAK)zxDT; zn@b(DBaWIRzlfNj6%6f4G+^}AG z|AkQG?#$UGFU{}V*}FHpo;T)ick1VFcZ+`I*XwV6m2tsnI^Q(^P>x8!nEZ;D^6S0# zE;6|zeDlJ|tf0Bk@2;zE-RP9-eCONL*l#I`{oXHb?0CA+Y}*>6+)1zg{HiJS%v}>8 zz5UGk&#mBI%*9S!P>Ejb`1rJua+IsYSt$!6<3*)xZd+c>3HC30Qya$CJ5Q(ixX(nz zRiREYh0eUecRhx>RZoSg=by0`)zY`-}8nq@|JPG zru$83T&#BTz{SZar~PW&Ue7|CO6}F(A7qw$f34*zr(gXN>F@6K`LyPQx*JdXDW`kq z$DupAi!0WA(bSu6rz)DZ@YSx@3W6YTu@hVuHA}PS-P{?@);~|D+0EamzxlY2nfz(1 z*LOZI6?@mQvGTawOS6I&5ndTh*XDxU#FcCAZ(4k7pF8)b+HI;UZB*iSpZXFknYVx6 z60>)ori4y?eE6{!C}~653(qELub;Sf_c!XY^WpA0 zS5@Uy_<&2g3~;0KL7mu+^+zuhwXDfB;^`N?{AfXG;}xr?Jyx6QPM2lgUb}yb=Bs?U z+hV*E(y!%DtK!x7+PkRc&*Uc;Jd0PXG2+s#SNXO5b6xBTU0FG~U{EPw6$LH@rpFZa z=G@%s-_|p8)v^;0HU^*hF8Kb|{}_`+UqKh(?DD<6B=q^cRpEbbxX+8)9e6JLf(5t) znDk~F=hp413$3=_(=UHrvv8Z|pVUvf@l*fxmzT|dQ>SXne|C53!d<7Xh|l_d`tp_E zsXuSEyaF{bSEPac6I1dt^qF!5vzzaf#*L1tlT(iGOMkj%*Vm=j*88v4%{I@QC?;Fw z@tNfL8=pDk;; zduF=mbg%CRmp=1rc~-qeV9T|f8#?COUaI-tE(?{~ax2*-`>){c5`&UOvzGaWo?7el zwN>Wjzy9=p`$Eg^iLN?*{a(@SC)x6=KA+|*p9c!bEb!cz?_X~HiC3k!d9=qhFLi!@ z@L5~a++RFXeQ%d>zI&6WCmXYvch)w?6R)()w`osG+fh<*md)~g(=5p?Tw8fhZtsbo zzW#0JiVOclXF=v`x19{1o_0_7bog{|m&e7_8&uIRo0q0~=lY`3>H9A2inh;ep3S9I z<9lso`ai#{zijg_9$MO*Yq;pD@2suw*Ez0U9=Rg!a5rc)uewOe%9D4h`q|%3rDc-w zv&091h@xGROOKZR7-|aj0{?0#p)<9s(0h2dZqMNU&S^hud z9kjl~PmV_#9Pg9CLwD!aUJsC~Pg`q}dHHI`r;v#|qdr_;K3B4~|JtqI=%ta?YYnFJ ze!HS_EqY$ucF$MlNf#Eyyj#I{7`%qzstdS-m%7 z=N7eHvHE((Dy`!5o!kIniM+S>HU-O9o?=<+9lTy+y{C(d`pOI0N`iudve&`cEq#7? zyj*S3)|lIioR5|2TAwn0ZFcJa+@f7qtlplvKXXCrMV&i^0*T|z^zm(6;b^7X3*%~L@+0pT0hZv|a^t1M^Mn>%2HG5+& z?{Pk6oNb!%m(6i?namcGl!s{&JGSor+Y?qAYn7p1{U}lWd&&{NyvUBZpQhf{yaFnB z{5(I`S*q<{x6FFd@0{fUcP}O^50DLvYrO6P>UZt~hvS_EPute!TsYKm&5cF3y88RO zOM8;nXYG}2oBO7-eeRvfg4R1zrzS2BkiDT9GQDl?AEWwvbMNe$bq6Bl)9xAY7$@pd<2 z=f&)Gd}X>v<7?NaOOtuat)9531mrJYQnt{jyy*2^v$~zp;Tey#&6-S3pI*mUbD(i8~n_D^JKg4EPt)VQd(bTdAPW^9N!A+X`i)xZa(Gs zyg2pi+uupOPFoYVKe7(gMXLrC4{H=xZ;q)CR?ob&tlK0rTE%}&oc{Aza0y)GXZ7aE zwANE^pKM#{wG%YKAyroN{O_&{DYxgmz?`}RPjPXby{(yhJ3xBXL8tYdpb?@Na5HyK z^!|-D`>L8vGOq{E+P1wabK5Pg{dRl%uKYUta%*buGQE~HucJ$^xZnA|ceTyGGgd33 z_XoZbeVPnUM?Y_cEWNWSc!eIXb?d8|GOnw0U%Z*J%_by$`I4Uk)<4hJmT_HOnz-EQ zX_;1tc$RdQvuVTw(}^7&9ew%Wy!BacN5s6C&7QMLU*((huf4m)+0XK`4tR;xZ2hZF zCk}0G-LXz!;p>mfuV;Df28~;7oNW7X!{lnCq~$fatAg@wo~)lYZLU+PoptLgKbJDE zS>I2;5m~kRRLw%OGOw)Yj=7Vbac|ui92C9(j9;GZ)dZA;@G7=qrOnp7?kz8@BBDy9 zleUDsHhVuMIBWh}wzYmU7qIr*?{zz8yFc-j=+mX@(vVCUFP8P#Z=u!hp7V2^zIy)A zymIW*@_^aG6YWK_Ca>oUPJMOvq(1l7gDIyke_Fo8YQN4Ew#mBRU;lkxBE+#*O-NAi zVj;La6}FDtD zS_sLB>%ckjmFcf$wzY4iHh3x7x=Fm=_GV)6>nl^E_8U58Z#K#7y}kSK=g3#f&ViOt zT~$O*Z@s)xx~3_qI~=*Um&b2T>^8}cS25q#oOfx7@AVhPQCCH`fU1^`4nF}%)JVnZ z`LDaLcDAc)>CWW0)9w~=>B-t`{q{cP?y}rVtBUh8uddv_<;{&?=kW64>hHU*mKgKg z*p?gJx5R2YwAmz@_y6i%$jFaqR(z=Ys{5}>lya0ojWgN%;6m|vY{gESjd`>0+$h~) zQoQEo8pqY!*wz}qnxiYd?ahR5wrrle-!U&O-CkD|Jl*com07$&>u2qAKW+b!cP>Z! zDW^%dc0T=i>qlzl&)T+Yg%*FK7uH?=Fnx8Fz2}{Ot9HMCRkkM|H0mn|wXIY-POpFc zT`k{TJjSz*UCX(py5I4_s=vQ?ZOZ(e_wv?$7pw2u)B5(dXT6ZpIKSq;%5!sY*8k{V zTmC%!A|G{4A2gb@>YH%XI;(BRSG_8evP?tFgwTI}y7*EbkmO7@spB5mQseBB^&Z{lW?j1ti7@Z@JluICD8<)3x} z&Huc+_R}fXnQLqL?~>M6cPpdcp9-Jezdt7Q*NNp9%5MG&u-<+umC$ z{PkA%xG(tFq@Z`25^r?2dG1thsrXq{uB{?gonKy0zSVXrcjoD}<+p!4&5P}bey{%= zWlRb*K;6DL;C@}?LbGo>?d7vxpWofT@OkF5$)`SD-I)p&ik1a8!AeTN3D?MR_2UNx z7p#8&Y1>qMzu2haO^BpL)uu1ffr_^6eUnOOZ}&Xht?#{e(UwVmN`F6XdCN65y7kp( z&&@dt&9>d!=9x7=B(4J-3hMXETCcs`zBP55&CAM#mER4&?76V#w?x+CO{H~vt>4?9 zH-EqR>%vq}a(;0G+~ySFiF$rze?ZyW`na~<*^17mmV5ob^zl;l4~fYAsk>t;K7v*) z_16nYTCMcCyiDt9*uq~mPN#pRE;L)`^fl~O*}heJ)5G@t`@1XPc0goM>^``iRiNo6 z?^W6+^ZipH{iTWE;fa%+QGPd0@kX)7O?db?{nUj|nMMg$SY8I(gf(B|+xcMCC6E@2}_H$~#r{DtOZDyz}R}nep)%b}P@MzIJ~52ehURG{|-lyo$?GCF5wK z_VY^zV}HjRC`Scem=*EXk^9ZbrgT9bL}$zwhFSIVG?s@IclMJ(L5 zZU*rujH8@SEuVcpbLVtEvo*`S*+Cr@#9*A`>ovPT7u6~EFs4pk;_tchi-eKa zwcJ~3y!xy5?dAjJ5kW!d#8{_ecCrkp7kR#>KWzRau9vdyYt}J)rJOys_R@mFuQmPP zMwlS98y9Slan)}1x|~}Z-MiTOCoOVZdTD9=#*#O-E@A8M>wMMUWAU|%>*ROIDN8<0 zsClb(**|p0-|+8Ne9Pa121lM}Sj24yuU$O>o&XZziCTW$GTJQXuBPm7p34oUA-BFu zsO0YYI(hl^yeU=UCVt>P)awf;%U0;9tehJ?Z+&n4v}yO#Gi2K@?-zZweZrF5l{xnA z&Z*m8SKO%s1=Oma;DBOnzP57fd87Hqv+l0l|4QAw^qsP<^|vYN;p?Y=mNJ2K&A`Tj zyJi-D+oypB8dfdR`g;^$6X-y01Y-E&c&25Ou1@@85@?ey@77M>}3thsw4==9gl=WG9M^8B1PQ~S!j+As5ezxf2}6_g}_ zi-R4Gr$04all*k%RsUtU%P2)k|CNU{YZZ@e85K(G`9I9`FHWpQqRxp0%G@l3)^Q_cX#5Pa(UMb<*4KrM}MDJ>i1U)Xc}zpJpeYo;>gO+rO3j z=YWbzKO1np|5z>KV&`doejZ^^fQ3N1QAUodC$s4sH=b>haW(D2qA58$l1_jU{i0cV z;0o_L%i6U?&dU`o!?G{@;)N~&4_pUIN@pv@r`w;)?Vo?q zYts7I)L$#Pw*P+>cN{dAoZWg$F8lcBZC6>;KYiSKyH02Oe;2zt7x)bJSE+bK|F!oO zb*p;LY%Du1XSwxUrMr5@Pj&r;lV=7*dIVW-w~3eDucN#F`~@q%<@MTE@)fu3-JO&A z+al^tuFjK+yX}u7_dTw*k@qj({rTCfevmtTo4wafN;i8uZQI||#W^?j z#h?38`&U>$Lsb*G)(6iaJ*)_r{OxH)!5(@4e|tc^unKUGKl4lb>y*81s}@blwA}Wl zF7DNpy&rC{tQFYqyJ9bBQTErmozc&^xAJOUx%c+6DDs?=;K9}~K zzNWhMWO(|{+)stK+oyrnDDLl#2la=h?ao{Je-~ z-UgN9=a|=@u;kUVG@F0XD|r4Dvyi;gF?oA$AJZ>??R2NA-ZFflS>E1Sxh#3D^4q_i z-rm{t>cRJD@M=kqRpqDO7T4QdF+00QKfW3~02=SH5VV#8vchD$r*Z!L-=7z5tE=9x z7ytVebU5Lhu%O_@PH=mu_-b!%>UZw--ailhE_ze{Q+H+k$540uj7U#)MeO<6|8c>z zv&Y|W-}HIi;kBE;E_`lRe{)IY_A{bc@4*Whe>(m723|7vG+K?f{^yd)=xN|_*=N^( zI_2Im0!=GjIj6t+jSXmZLP7DeRz61&y&^b-)w3(es9+{FT8bs4RocY=i&H$7xrlT-VTVI zWfi}0TjtuOU?pcHl1H}==5)$ePRw%dP>>AQaa_x6}V=hu^s(MkfnCk4}V zk9>Ozno^5hxU1&v+M3)yyOb?KwzWUw*4$FFurfSoZuGn>YV!3n;fqYJ+P>$y`ZIOv zTy@YI9dK)JS@QaYpRT#jKdrp`-@M1~(pHAD8_F4$r{#f7O_mydxK$4^tY1+@?ADx>~w@;vYSId3LtmU>>> z?lt$_gLZ0z)-q+@4k_O|HAFtjEE6<11uaI_{dGUF`~SbNYy1AZmwEsHyLINJZ7*Kb zyLJ27*i2OUcLtPgQoz~frQHf2yB}ZJZ+?1qT{8Nrz1{!7S*0&dE^vK$LpXO{!Tg#Z zn?YWj44!vd_I$#{PY<8lpZvCc|LKnx#qB*m_wAK{_FQ7X1(+O1)cn6HxBq?1?OQCj z<$1D#cQsE;>*PyJQckxQwzX%hcDvtBGQ z4GEKMon}AhpEJ0D85%gXuJ1@$$hC=a?>FAke;u)Yen&?~M;|x|`99xw^wT%>_>+^* z|2zC~;hxXy*FW7gdzZ}Y1)`78Mqw3OZ|$z#<@EL4p0(vqS9?D$nC_qSoA>Mc>ybZi z?I@kPQs3nL^yQ%$mD_Eu9GmuZwRVUbsC+-(4oa!M$L$V3$(OI!?0;A5zgN5eO=J0! z_y7L{t;@Xm@A(m$a_b z@2lFs?m|sR?&K?GD{a;~o&NpxUClmFTOBlYs$^9ME)R{Aqa;o`-MA&%x^}VDmOqnE zUGPl9R?UBIownM`zEveRKB#g9$k^Lz@xNz4!rc>37;K z{<_ts@PTt>&42b!H}-u$o7z(U|B3g`YcW<8tJv0VmD(~Z`#{l^CksMXB<&+9-enZ^8>zlmd`#-;Q*Bz3_*85pI&x_mcxGwLi-En3A%nMfkPt>@bwm->(Gzj~1$LaQ8 z9`G6IwfbA;h-Sr$X8t-G{{C9%Hak$aVfyl^`9A|HFM&Fhzt4l3_b+~c7j)&hfO&_|RocYP0m6(hu+F*W~6ke7v$ba^~#BIhA#>H@DRHi}dPGUR1bviT6*A zmnKy^*4*0|Diu?hiK=^cnJwE%Y55v0{+!ywA9wBf+JFAZsN7V7`z^v7G)^G`gzzI9U6^SG*s zZz|uWN><-C+8W{&es21srN*hRKmUF*NiBRiY8+%<+O^u|_pRMVzt?RwI{xvI^n35e z+Os{@veoa2nOFU~bmqqodyPIXtWNp#cl)UwyQiHz7d~&gcGR5-sEdi8y?XXG^?Y~! zsUJ)0&lNtnT^}>@Ip!`;pgj{l}?6uOF#hKe<|dpZfNkSoQ5Wk=m!P+beR<|F_xZ z_xAm#O5f&tp_y`&*ZgE(di>Oho8O(-_`LP{(>-qC9&5d4-&0us_i^pc+T&_l1M}|v z>CuV(J;Ubzck?Bl$G4%alKOXJ%i1?J|JEB-{o8B#ZSR+5bLub3Z+3jW?XC2aZMms6-5T(;RId93^W zAKz!TKhu-X3D-}G?7yqd{_k7(&V9+V(dI^f9C~!!{zu5W`=8uWvcJsuc_``5y}t*d z>!)^pzppm=%8}psQ#rriRjQ7wcU_lx+X&U^zS|p@e!5q`Z~E8zpEk$Y|NnKIwe79q z)>m<56W`Q*Rh2CK{HJH4x&8#%A98D_nJ+_4NiTzA6#xJEwLs=aMA4tuvzv>8uh#{= zUUQ5$<#spwlJ{>qFO{Ejd1+ogY2(YCwMCwnUk9OX(dChfRh<8?B;ei;zfJo@fBKxh z_5IR<(A@_sH@!R{_tGSgsK4Ztr!D66K#TdqK*AEpmnd^KR~QeHC9AiIy3Yc%$O8SN!rgJoUA-?(41v zrKjG%J|V>$>ihevLEfADmn(`sv#l&Y>57(6axVOeI6PHbcuQUE@@+PoQzyqgLY5{o@dtGg;lYfQQ|BXFs{sH&NJt>u;4atl-wZma5@bGrd$^R0BXanE;?n4L!Ehkx|4t-UI>B`@Q?>hZ^` zuY29)u6PNV0-qZLZu&Z#X4EcR_)3;HT(4!l-?KD{^lKAMXK(Y^>}R{l>e~eM>uaW0 z@k*l=6pj~u#W-KR&+o<8vc8WaYWek_d)M6G6dNV}_4LOtzkGxCUp$HyI9zONjn7-T zn}OE!{Ng+N;zoMMnwvX9rQ*)KzX<9qDlM7}>*}v6K3BOZ^|{bqxnzonQr?{+cQHJg{{sbNvy0PHvzBcV}wTx8z z#imA&YTQ-}+AoHzXHcIBYAT)mV3AR~;NhvMoKfv}N;mMow*t-RxVWgV2g&j_i8)l*2%Yx#cbbwLx7(oy9nKmNF! zy)^GHH`-qKo+}H!&TU)uAH3R&!}0a?eI@zn%Prob($C*4LDQIX4lh6s7T=j{x-F~LgBIjo*))$M z>VNquNzizG(be9!MK+&~WLf^+!-f`H0$T)D?Ymg;^q1%9DrmU~-P+m%UOT?FO|ksH8R4;){yU*zxPq z#Gi7oedCMPfTGOzpuSGp1xOe}_U}N$%O8>xjUiD6bJ>LjU)Q#+`k%3*NU=r~)zwpu zIKGaTS{I}7`WQ59rf7rg4qoHC|Q#h&E;66pvn*JgLm?~19Hl3!%p z+)z5*WZ$1vXyr1rP>h8XiX2g;=WOG*y}PAf>((>9PFdaK|D{f&gexjH_Y}{M`E?w< z@)F^RN}mgwN2&E=>z%3Qd}{e?Cgao_KO#5fp5OebJ$n{f(^pes%ei&e1Ipgj-)mX@ zsIz!U%B2`d!^(MYZhYSpQ+c-+Eo*SGul+2zC2qn)VS!E0W==3II#mOWeJf4ysPLuU zjLa2MpcCiU-n_VC+M%Z{;W4M7qwz1Efcw=O%Vlfg4^P!DzJBn9an$)!2QCJue7KQ$ zY{I9^O{L%KcD`Bl8m*uZ$p$TNZ*SASem&|MtK;f(mOEZQuzMLBqgd;8OtRL!XR`0@ z^3zAdW9FhZ2Gd&D*5;c+cjDg_*pfBBbRB1CEPKy{b!+xqtIpgoMFZWNq`Z{~5=k2^pCT-9A z?Kk_qx|Tl$%|~n4*8b02@yipm)W23>OI>YLZe`kCwVmrim&^3qZgqo93qyK0S0M+6 z6!Avw&sgzG{?JtB)LKDMi8=Grms`D{G3>~*)+ZMGubWo<3bfe+*?Gs<*2W90i;22+ zo@H(Dxe3d!=S)~Vdu#tz<3$UuDxfyjdiRzX7*Bf2zV`p+1z)GItzDg$GyU;I^{~@7 zpF_)`izmUY%H;gEkBi&1Ys_9hk9c;;5t?uyrCsmdy}!++*6j=4@%RD1R}r5ZGvhUToc3J__&Tv|b^WUPmH$`CZ#h$w zXb3B)!$1XfT0&-P&9B3bm9M2IZm$=p{GL9M-!!9kvE%9vm|2hkm-8Enem+^<{OORo zlKy}9Cs)tA-n(+)>r;L9rLc2EAcM%h{f>*D$j|?)vHSnOy>F`Dr&oTHSJM9*{baK| zzt{48&;rcG5?n9b{WbN(@Bjb8w(a>gGpG8o_RXK)$~XOQZvJ%tJ)c*59IE2j-(shC z&#%#yj{CPgr|PTm&0oK!Z+bmf{^|Yua#Oz9N!zYk3Obg+K^^xXU!P3wEV+{*U--TUV8(q>4UCxQp4?l~VnaXkK? z|F`-N_Ld(WI##|@zQ@xHuF5n5L8hb~IKlPvd^!8mtLI(fe#bmnAK$g-XYPw{@6MVe zKq}v0w3ApCCTlukwXnm0X8vnI<+E)|D*CD)7*@9b45>{r}tPHINBo@7ku5^wo2Zh>*}$8#nYMUN{X67>{VV^hJ?O`y}oUY&ULb@tO=vvM z5zo>jmS45=zLs}K%lEd}%+qYqr7zXrR`Fe3u+>>AwhXNqEB$?4^Zn!doNrv(6qk2l z*T&fI8X2W;^P8?kK{Gb=q@7bH8GlzS5w=_I(k5^FOzC*5 z8y(Xjv$GdHVY}6?l}oMqxBB5L`&aj0#lMQyeLamgD%vEYH2CgWS@vjg-X~`bz-jiy z5=eU`E>1B%bH%R*;uF9BSO0YHywdxBDxaSI=kr>APil*wt?@3&ww9guB zR zzwe)G-m5#|S&%xQ6r65&T6BAIY&TZzy|VkkBDBO-+txR+fB%ojZ-4$)-~9V+`=;0V z_gAcc`uKeP6fxN-za8uU)shyy^YY%H8kGGIFm>XkBpCu{q4$c+=M#!P!sm z{1QiNaJ{t5m{POCG5dDBy}3~HwXG6ojg+IzGS*HkKA%4WH5`{+pK$Th#dP~q>$dNk zI5)p`u3)Uy4ejce4@%csN$Gm>H zzS%x{e=%xz=ISkPu0%(bwb`-dy|1eSEei!Fmk#I&T|BbUit&HmFZem>QO|tWeU~9M7;XdPALc7+tk3U6xbpP%~R=e4`+(CLpCOTfdlD?6I6>2riH zT#)L%r7gtATshMDYoW)_65~asFXf@t88oA8RF2va^FiJH*`1WP0 zhgz8cGf%VkH?*h6RQjU2M!Lq0+kD>vUV{SVp7%*JXD2>b*Bo8;c5>X|(`VkDnbYe@jm^-!D}8w|JuYK7pT) zmN#G8xt?w9{W%|_BSGuua>G^3x4qUg-}YV)v_ctsf8BV)7N>uo?>bgKy|1MIH~2|w zzf0Ve0W5EVn{=1g?})Mc z`AXdK!$1E`@9)Z|oWFhl<>UTO@ALV*ZY$|$m}V}%W#zbfmOJP`GtsQ#O~uOt{!RoR zs`S8b;jdL3z6;A*K?f}r{0Ihb)Dx7I2e*~>w)aizxBKr|_W!N)&7aT0H$DEUpK^V6 z{Ok``mr3i%+Wg(Vsp@Z<@VZ6b?A0Ed`F4~PG(*nH6MfXaT3K*gUrB}arN7Z-Tvuf^ z%RbGQ=LDTU(ssLj?w{wGH&52b-F-79L|hBJ&SNpS`FOdwVCJ94o8?oEOUIx3cHMq@ z?)uuadG(*3{$ElW&KtFSPN4g>oI6Tqt-l$WA2BdLYG8i&hRXB!2wU^R9IdMYZa!S` z?q5yjukz&qwNVgTxvqwqTerTtv?=)3PbbjgProv+Xg^Tl>ki)2&gS@f@%g$bbMtFe zt?&N{a=s?M>Gj(DP0CTruiBliNe_FS_jjr9MTX4FwF^F;`E~YcS@m}l#iQFew|<4} z=UljJiP7&n&$qFce|pWu>01}OVy}Jv@_=X`=!*59!Js{#E98RrRc$|)zPu*2?9=nN zDX0Hd?Z5MBc|h!?J*WK^8r2tpH-BEd2+pG~ZA*G;{yaVZtT^&V&$WBy`=&w8vqYkQHkFK?WQD{5o|`eX8o!)?2$$TVJi7 zJ@?W%^{G!+Z=U>HX0m<$@|x3arwm$eg&*H@+OPH0+mIFK=9a5RizhAL@>$P%sV*cY z^g+WCm-jDm`uF#(W93WXlLuru?0-L8Q1W7_=B)Mo8$kZ_n4izxVOnT*F=ux1uRBE(StVWUL%7`(Eyx)68`4MbG0ibAP_8 zeRl41FW<6@hUy|GO?dC79BD|F5pfMPOmuKCdL8}VEtnCjHeiK*+(s?Lj_aaPx#b<(7hx6ga5>z{k%*{!#G zvZwFqRk_Kh<;?YIz0W=0r1HOaJ}JyrslM&|==Cpl!6`A(5%=7JHa+cHq|voVU$nM+FQ;;={LX**wa0Yzmv5Ecc43C5dGNn0JD*wRfAZau z^_K0`t;e&LNO!qU@Yr}7;+m5;a*pVqFrR7lMkIybImoE;x1{F8`aMR{o}D@0j3@1$ z+f^elsVT=HC?Iaqjiw1xdVGU6RZ4rFoTd$NNBTU4v&qc!SAKqexm?}K@CSIRmtdxd%FH9#c!wT7M~7oocm@^HvguZsUC^{CcXKq zmE0fxOzrmdt+#(FSWlYLefHdLmZdIsCvP>?y!o^8##@!L>&NxNp>EItyP-HDLpCvR z>-_6^4;sz~>Yd)+8dlhIVb=6d+EI`I>sq97wGkXAR6OT2SMPM)}=lJir)Vr7+KQAAxW4T$+ zvP1L4@>LsxC;W{$;G1Z+`;U*JrcV4_O@00K4in@(QtU;wF6SxjcUiKC&oyw$W{8`A z>TL1bw8HA-`)b3E3t2bTh_2N;E-&W4Y)#Y2DTZlEOMB(~y~Cyj{XTbha-N2wBe$pd ze`Y1k6J>r1{M*l*Q(v^{-&NMimZy_d@7aCg*!0(9f=J5hEEU$9=gL%CpB_GSPGr)d zPwC9tesrk2Ji6)Ep7Pq)_4mpZ&VeFNa}+@NM1A3ftP4w6*G6sbYkS_e^r7WZladfY zUb#qz?&;G5ZuiB4tW8f)%v`QeZ38lw)9?F_Iq&}2r`_4O^Jj|37JsE`t_{HxYy~GZ z<%ISwQHU-*=;!#&0Aahm*c*|Q{&sDCDL9UyC-bfp1a#7C)%j&n4jaf z3Jut>hSZGO|$Ddlq=VqJFXL>WGCw9{AxjuGlba*_M z&nlldWmSzhXo2DxMbBkKLrw6XyCJRa(?;ah#$G1s6+gotb z9i!XN)UIFmU8fs(&o{|lc}MY^yyK_ar|vZSF8D0@dG36Jn0WL2=??{;3AeqDz6mPF zes`o#cAwxMVaMv>6{4pZTDjXN$Y!?6?d4vD&u^VwF5uaDN>z0e>rb|yN1THq?ADx7 z)7ZJqrP939R#ZjXO)FZjTKcLyyDP&QTY@; z!%y~6^DeBq-!W}ngzsMV)F6EWfs_k}p0AF`Xcc@S9wOE-x9QEt2_jRbtC)#(8DYJ~e40=gA)}58u58v5(%}zUk)%Bk9|D`Rq@d3u^D!B&+YXiBwV5nm%RK zo@h1A6W^MV($9t(C3(-2zaUP(=N7cb$aDED{==a3tD^08=v1r1?&z{rc8O0D_cdkv zKUuHQRkTiENoCFh-iquVN))A)mgVrp5YSj z`3)DcZfB?{3I zb`$%T7~I!7J~!X|eX{y>F@Dd)pDuQj4%OT>^4w-CH))kt{`8xgXW}P4xm%=ieHuq} z%@JWGN3-o~HvLSgJ#^~src=*ET?5aBHktJE)~KDI>;F3<9CsvLBw%C)&D{OT1`8D~KBxAA5d&dAe}&mvf4X z_V>>iYxPz+mAWyKyhg*Jr`FU{i zmv4-VV%0nBo_^Q(6X)GJ4OXvtF@Q@Stu2uHSHv|?3v6M3P3ecb&Mvzn6CSQ^zW>8X z`##oL6kjK1{s5`>}TOj>iiByt(URWPZx) ziXv*@Kyd6n;qNY7?kE`9KI3sr*b*mikefkfs7^!5kX8pCy{dj@P;3&AvGf1psi*99 z!a!<3?Uj{k;Gn&8#ECmzSfsI(N2%LV(P_0A_gYn*E$h+?MOB-{HeJZNx-Iz0OzOhc)!~ATss)i71*5)3W~@zEXliiy!fAIz$UIj9hs@`Q49Aep16G@45&x{;1+ zZzqDxS>KC8*VkhN!fRuuK z46T9P>lUl-Kfg+0i-Ng%AS54aJ1E~h zE`PXFXri-xhS++YrDb#12P|9)^^X_N z1rEK=H%+c*IiH85y{OHi% zawb$)Fv>T2>rzl#`$gww&GJLwwiL&@RMpfg8?*bhUeAF>pT<@3@DnPhydx**p7>s} zvdfC~igk1AHAS(|-;pu0NgwK?1fnMXznFDzneEj4TV6kdpI@4B(tP8MS*b^^eY&c% zW!Kj=o6f-!Lr`sDtB=vo3N!2c>8v-klKD^1=Ub63#l5y!XVt#c!qN!`ud^q&gms^` z4*2;jH^idWu6yn5%r8rBPBE@iTdSeF+%FuF?3XI7E8skR`^aX-jk|YzbE({}bE3{_ z!8*;)&n`2Hg~o5*VRgV|pWTU6SEVi2HeS%0WjLQ}*}0{AP8MsVGqqf^dUfURswri# z*q*YPIed@G9rurAy>-}P#oSonQb&b243LSV+*eMx#&aLC0ovPO(d4TKe4Apg!Wx!q-!g${d{q z6NUcuhc&C|W;~r$YGSf{Mq8gySH{P#psedZxq>@k#&!v%igB+!th1`X^tD0qKC=^Z zL>7kb4>XPnjB;ysds3b9^s>XVpQ)->wk2Ob;3W!5p`cuLF7?@l3s$EVaVc@fOSfFG z%H&>K#XI}br%78)6pYU+EBF6O?^))0I_%<8Swt8t+HvQM(`$)tEy2iVSz!wn5X|r?#WQ#6Z0Xy)qDwH+(J4FOngsK@EZ6lJD-q>p;mqSs+~)f_ z1S1WdRtE-0ZQRwv)~~Zf@yr52R<{D%FF%~5tP5BDoExSw*TLM``mpFTHmL=*&)Il1wz;>;4ND)b?lX&%-x;5=znk?} z#;>zw%k`JpT@jU3{`UR2@Xh#+zU$Ka}Sia zi#mR$-(F5KSflec+rzh~4NGo+zuhMzFTVS3SFZc7cg-IqoNk%Db-(rXRlkgLr|)Zt znU6&-Z`=R&w$kx={gv(OY*!?kU$}hXB^TT6n7-Y`UNNB^?>+t>dSt!r{f+Zqte3sj z{=04OHmj1U7GI7FOL_}^Oq~9EU*mKC6uv(<%-^~le|dEG^WcTH;>CBCthI1C*88m@ zZ+E&kYbN!pN+g`RM!SfH!yi%QD6?->bA-1qIzu0F({pV|M zR|H<37P8(^c~$KC#)Wi3+FL=~ za`~rE->yC`_I%z!`)fgIvfQ1`8gVkmjxBzl-^Y2pwUb$2uJuK>Li3CbACj)H6-wLI zpV`U0E8cbP!KvZF)3w(xTxYv(={no>i}S)`&maF%^{UQPzPj#RndtPdDbf6W?B9;s z-H1QOZmpJlzWK(+;|j&03-|0gf8KQBPs8>86R-2s&--wz@vX;g$B4gQpKq1z7u@pw z<$>o>?sf0Zlq6Q0KjV_iF2A?mdVBR#(eU8&a@Q|j4gbC{d;NR&>)$0^YJV@k*pPVB zL`Qr6CO7{DcOJbAv8dGZond?bj$EZ#^L?ua*%lEy&e$Jb;M?H8obgwoaI@XQ{2vFc zXIR$D6>mOo6RcL-ZvSnv)e^at-mhk!eBhVLaN8^>Uf`l#i?Ur1|1Z@A{FjcqoVd}r zCI33JyyRp4!aY+~DZa7c=wg@Nc=T|et#z4}u>GU?EBf`4w%-tJ+#?yiGEaK(-x0f)tgEZt z$@C=B+)N#9z8u{6!qheQPr`uiIdYGY`*Zrc+dM{?;U+hW5lnw zTNN_*Ki^p|7k7V;UEyyXwduyo^k2yT>fRS!YxrT?%JXmJWha}nz0r3$zLn>z`?0rg zAD=y)nQ*?4MNa%z_V=6f`&(x?ADiu;u9IftcwCp|wtkz)Hh!kt-;&PAILr8F`rk5n z>vdbm{?fiJy!QF>-z}<+AG5d1tM6H_@cYBYr#m78clCv>iS*a54O{zlUigQdoA;PV zh3ENH{{5U}t~T%Q4>LpFKRZ7^>v_)bR(W2)`YrhnzP+*7_SmVM|G<2-Pq#(w>qx?R8T{eJPT`rTsI^8498Zr}ZW;l3D0d31rC8CzZX z{p$TIU)R0+&dhJ?#Bck{qW^tn)uWfsUz}_`fBEux-DTGWDebe^CiqzU=bfm~Z3hqApAVaL z!~Kt;#d#$|{tMgWqt(9cJa70>BO&JC0kMs@KR*bTeV*=l=JlK{nU8NP9Gwb}rOP9Wegl(I7Ex%%-*{7eozb-X@ zIrVz^CF%DyKGt@BCY9B0oByq*x@VtN&7^Oin^?-68bLw6Lx>GJGuSE?#Jyf81m(xYt0v0 z)#v<@-TdC7Sc&VD0Y8IX#^r2tuAC3Q`TjLc`T1>u)djs{j7#RfnA|(%_5u0dyS^T1 zt4&#BRPR{4Iz<0X@j*NJHx_0z()W6Q*GOXniifqVr z%ecBGa`L-fzwKsOybgc%e{|-Ti@Kge=L&o*vz4V`CKynAI#5ly97RGXEd_?0@;p|Nm1J&KlS`Et&H?PvSfGXLEu0k3P>W zW$e117pQ!FSozYnf+hd6MceZLd%5EcXAGZlsU9=35ihMQ^{!1^vhA;vu=e3fz2sZd z3~$dcyU~AJa-Q`y@qc&X%+_rao3T5I?}-(AO3$)?HE%9{-cx_p&npsc*=(L`Udi{= zZ^qG&26Np+{Farx`yF7t@8WigkG(VAnHo&4D*P{a{e}6X{0zSDyc?q}rasPLd+Imq zaP5O_YUdWs`6yG}Vg6^QMN;qYu78gY3!S;DP{TUo`HJ|czRv8+>}Sp|@G%Zg>kF#- z_hi=NhO$MazmFeSFJG9kZr1PI8|#&sZh1=|eysUqr;PRNBDMQYteflHALsv+pH=lc z*5tSU_sf6m?OnG2{mQxP@x0bAv-jJ%8P|Ug*P8WH`jp0utYeoKpHIlT{x&P?$|BjT zxi{v#GRbi()ysZ$aRKL5Gm~YvzG???U&&5c5xHLR>Kbmn#onz}3mjXdw!W6uW87-v zGjn70zGHE#_5OakWK~kw8&z^q)%0ake4N+w_?YH%`#p9XY5KguP&_WE`}^ATU%~4G zi?go$DayLIj(h9tD$&xNtN3FpYwKT~e7yJBu|&0LK0T}rMF*$wGRRbi$+$fdG<)1W zhuQ%(LTj7r!#G ze^xr_^_OQSvd^ns+O|cSv2wrvho?LDPJI5&ZQkuVpZYpm=>wmwJ0uVBBww1rGp}7R zbFrrN>V~3C#{=vBg-Dqf%K1JluK2e8pU?JxAM1Y2ub#KKdY;yDb+5R^qWX0{v+rBG zYtG8&vzxQni>+dgk`(E;oJm0^XFW%3BpDByE%FEcfyszKi zU-s_4_xrlt?Pc$7JIl-bnPg}8iMO`w5yNje^W+2jRxtlx-hA8EJGNT#%GZa1i^?k( z{C{+a<9A0{jr$kA>F=M+z5jgf{paT%%v|1a@A0O&F*kR|NdDPy{`S9p)+zDZ4)*PO z*!{Om_Tje*Bl|~Ao-0_PI%*`{2JnPWs(} zdoo-1?yRiH&r37xH`l2XHt2Tw-+My1_D|a3TGQ@d7b6V(AMHzAXfxT(e_yBgQS-w8 z6&pLuHQu>P-J78xwL`9wxy8buyLg?lZM@j6J@);{XQs?%e!yyRoqNLzIor%_lJ$K0 z`-;`hn4FL3SbXNe^WM3ySw&l=xkKb9KQBstzAs_o>^|46Iz<~7JTrIReB!`t#r*2- z^fTS+1x6Jz0(&AhROeQ9r|H~SnkyrnX}$HW{{vTpTkf)#*7a)ie?qoYG!V|rWD*7 zV=8)n*VJQ8H!J>`iu%s?XVuwp?8C<65eD{$i@P&7h~5`4cl?;UZR3f`K3w_q_yo&13E@6ou^54vj_i1zWWiTw`EM22EwMQJ znf)!#gU1hQGY%D>KOXO5_Uc%~8KsG@MK)^7pSYpmW?blV>{Er>hZw(E?#avUyCvuU zQ}*5URo~?C_wN^c|Nm9E`)RuSOY?GD*ZupyNB(*{w}116U6Z@g{!V-Sp+&5vT`)@5 zc44Tv(`m72!=}}pH@6u+bt~D~v^ufphS0>PoGYvBPW*N{Bc7i0y(KJHENbfuC$T7T zmzHVj8|o&mxsY|c^;TKqg;&0J6}MFJFIt^1;dTA1|Ko}#2M?DV9=3-!Z-ysL7Qfgb zJ15>Pf+gS}+d*MwmkHmc8Z879lO6>;I?7S^zghDCUUqr82hDah0s#-1S+2D1^1Uq( z78zF`dod+A(QXFUx6PS#Zf>_Za&G5^f4DXM?_SGui|lN!?X%f#{$WS{1Iri3zr3IC z$2`OFjKDTtX{)YFmp^G}ukLW`o#(>(IByZ><$CYS&;Od={nTFl(w|%GQj_|r7aK#@ zU)q!_e^D(=G`~((G%8|3qGO9|z=cDqS5G)`zfELpJ1Y6BVV^_Ag81DxHYEQStol5= z_RFz*`Y*Shw_h0k{mr85c6VL#-~a7ky`H}1_wV&DE|}nk!w=TKO~ieWmsEy{EXIo0iq}*8V$r{>1)MT%6mc-+Znb=BvNy z72}P*o74aPJNb_xch5=Q{b!pS1)3R}e^kv6pDq8vOPJ{l>r?pxdBqC~UleXAJaFMy z#IuOEi8qOxJEupu`1tPdmy1FzjZauAGO;~>+#j3a8Sk3Yc|a&YE%%WBt*P5`m&Cl< z%6VIzrsae@8-7@Gm z`!?s$ylk;u!9R8+ILeAzR|KOjvX1@uQP$&WtMsMjZVSF-cf2ce z{_}f1*ZaFU3~N8!{(5w$;F3G@)R~qSp7AxDf3K=(^@Q8s#KbJhHWhSqeK0iP=oX6W zd9X@_WwW}8;Dx;O&uY{G8@Jw|CYVlbsg^y-1sJ*?Z%S$ufnnQhqrO~q=+XUWD?%>TyT!= zkUygE?9zMfXWYfC8?BnoU+gjbu|P#@+lCL5SjEK>X3VdX`MkXPpNsv9h6{&2o;RD{ zG+TS-j;d4^u~$dcKiqrW$`Rq8a{A-fgN8GYJf3Ma-}Tzs5R>P8vl7;&-Syo3zrz0P z@6U(7y#D{);_ki&nZFL#cE9+xd)|V`@;gUA7g_JO^L@O{U3%fwe_h!t3Riy%SzpNA z@8j7LY4tMI zEsK)0D;Hj!Z1p{5VQH-WtB2fcvwJUWdZfkY_~@ZkoW|{+E9{SJCtXocI_CODvCgf> zW#a1dZWS|J?Yg=4{OekAfSo}{>d7-7i{pi?xA%lQI4Df3O%&ak^>lmgIm3X5wknlx zDmw!1K4)jyqFS-<(z@1-8~MNQHh*B)Y$ubyAb-Efu7&ptc1?VF-@ciRSI*P9t$*3W znXFT$8c%c+Kfi3_<-En3#sBrs{{4IROZ)D5%XZ7fJNK`-oWA8zy?yZOT z)wZO3Y;lzkYZh=*+H~)E=*`=EvhMsl>Uith$+%m)s`+#8eP#UqTlUtcdwf+-AAf&w zR=@1>xqSPjciZKYM-F4bxa(Q!*#6;=e%d!;krC ztXm3?*Y%h4U07DmS0s6A|MT4Q+Zsd9RfSoLv{0S&W-JVH`u$} zzip$iZq^PjWf?=W9^vA2eTU{}Q7h$67`AL!q33j3H1fsz?k(C*r^Wo5qNj78O5FaM zgQ;}t`snnQYgTptJfEMNyE1e8!;;9!VkPhY@?ZL|S21DP+1<9A9-R8Lo@Iu%(w-Ma z;qn_i9)S|(rUvsxOi4}R%?$H@xr?_&1blYN{UjrGl>3=a90%Xw@Hv0{?>r5z|Ltr1 zD@~Gho1OEAWsh~2u+Lh*wAXb0zVuH?d`=hCK4tFFpR%Fjn^;${ul%Jy{{KF@wYDx& z-?HJtQ>)5_vpbI}>i_$YX(x7PUc{$k&wB;AKSXEBJa9D^yLV4%&d#6PO&Vufd@q}2 z@xI(7^PcRP_daq@g&!RLcjNk*$@-^yCJ3LIQ&nu)C;lrr{cqRt0^|NfwqBhS$1_c$ z;nO~Ns)=2`^#9<^AGegxcj|9C<}>qnZh`D2^Mm>IYxMt}DKuKLqjui!N0Bq6^Zm~3 znUVgI`TX)PjjrA5PKqqKjL$kdPbk%!=zsbv(IqPMujz=+w@8;Kec#uuD17s5!v?1_ zk1DF-nOiEp9na`fK6c``@}=p~dmiWX=|Af^I@Pq^;@P|lk9IZ*Y<(2{t8H~u#GizR zk9F6mY}@NPYx|Fbi;f9*Ui@+R>XnyM4*zwvKl{dL=6{aXKf{_@-Zzp0%4XUnbo?W|79 z!lSwISIap{U)0H(X5O7BWs)oV&}d<#be4ume)5s-g|5-hih|1`jel%g#O52d=?7Eb z!nAc0V%t{7|JbF-GBxJsrv{DY{vU_M%Q%d7Zfac}B_30=Qh(DV@hINj3$NaAtZmfU z(sp6%o(o%B9hNq92*^Z-1pHXE$SKmuCH}CoQ^&+sd$BL~ri#Uf%($M!pELW0?5p^h zAI1Mq{LI1f<+SYa%)+oU`~|m|w$11Nw$EUL#w11Yv;)o1Uzr9oA-~V?heiz|Te&?&m->(~ksw$3ZZ+X(npLuvUd%cJg zcenDEZdRohp=|D76MFwYy87^&e*fiXcYm*_d7i$d?0BA5ZMOQS&UR(|HawWXMy zd*=5Sx?<7qYrm`3mVDXp^VM7(o5b zV);6Uwn=;}owDt%ou7Vf&7~OG&j-xb^~`-z6r*bGr~l5*?Y5nbmwrvblx>?5Zh!hG zc<((+Vel7yxx?%>nF4(~R<#zcx?oj3A-SWNsfp=1Ys;yA&kRrbr*}jPRg}1MZ!U<9 z&bis-S@6C5cYeR)t4)r38A_*a+~IZl%o0{^u?x4hX1`RA-Bq&iTyxr`IhKc4dY(SL z^0TPzi~R3*{8yhgm-0He%NMjz=kP7wXdAP3N zZki_$kXOeObLi{LeMR=(r&}}jUyR@KcI*C|H@rSHKQeu~MabUB%>Kjks_?ajUmvv} zU489WX;OXt90_j`x!I=YzD8HxIm7nh(CN0`+i{&6ULJkh<}veq^0&h_xf@^iZA*N1 z`O52`)AEnmuvsFnToIN=Nski@8nrt-+IlyZuRM;{`un8_Y6xOD;LQq7oIN7{q)N8_yzg- zJr`ehi|;l(e&hJ&%CgS4pL|zbpEZ5PjZb-U507pYzFjwSqxYYWI|XOjAJI$V&t06q ztUgZOH~xbA<@Q{)eb@6Id={zIxPQQ~FFE+#cSS4vXAzN~Ctv#9zTKB+Mpg3h2eR(< z_fn;gAKYVRaO}CO*7J{RZ~x+2d;gnOZq3WmTYpYPZ}~dkI^*{1_m{l$-z=GaziR%r z{d>FP>WbBKE1&Ytn>~H!|Jgpbe?0tW+&%4ru)ppS_xTlFa>h1qKR&tq<;Udn6*gaa zemAu8?VDSx{M+?aWtUv-AJ4L-R(FCMzTlm`t) z{(GEQ_26Lpi}(Ehl=b-kJXU=CU*LUrRp5PLfBhxP=f}Emi|d-2FN)lf(z?_0;G?Ij z&z$96C4BLE>1!K<=S}r?5`T>D%UR~HsX7-{x8i5#7U`zv?G?#CYNC}2eqIt?x}`>R zKiiZkA73A?s#|;G%yYB2-PPD1@y zs=Tpc#=5Dm8f*PdR3_dJVLE=hFW)h*La?nmnCp9+h+{41;kS!#pI27zkDg=jmi?<^ z$Ghjfu6az4lkWSdW!zbsnmQ#u#wS^#V~uI{wF_;$(l6BS?k-=Rcli0`#r@}&*uA|K z*e#~JqVV&zXRlip6n2|knZYXI5^*^!cV^j@Ev433Y`3;7e>S~*f!Or&CDU@P15cOP zq04Z4_Tlxs-!e~seGvR4?8=5r?yL(tEFZ3G|DnIWYP#vdUDHj?=8Ighr(juWeB^&AHa~`^;aLcvk!TUw0?{KKs}8|LlTD zwG^mfl? zye%mGd+Vp?+RN;Y%?-c88Gobc*~K#p@0S~^Ta-yuzy7u?f9mrUmwqN zxHIdcXZ)Ri5hp%=Pr7w@4tHIR;xmEx9Zo&_JZr9g{Vm>`YrjtLa$U^hSM0y;HhecV z?EUdI`TUpAUunOde+~SlSvyH0Kc~;vTmM+4-IF_q{>(_8dg=4=+4U=>pKdg~ac=he z6B}LTzpXo6qr`80?8L^KjpwKPg$LbU7asWg-Swb$?drhg^Y)(YRoE3*%v}2J=INNb z2Sew%-HG4QyZ%*dO=fO!>5J#j<6Z3cKS=t;y5mEV)&E2BRU6(mbvyFz3iz?~d(Nq| zQ&sO@Z>ZgCRaeM(z4BkHo%n_Ne}~vo#by${`=*LN3i|L}et&>Qvj#id(cSEkKn z3t4?H)#q?ldu6P+(sP!Axc`k$r9-aY?67?{W9N^9&)GjdJN#V6VRz}P$E6JGIV)td zPp((C_;3B;=b877bE^ZE%k5q9yY%l_-k5^c+JG>-rM-H~a^hz6d=uAT<=I!%yt_2i zG=p8lC_>?>q2(D5HDh`67n)}A&F`ig&ichC?^@f|yWpO4LGU`0SH>#6zm_h&yx@$F z)U>&-k~mGV8dVt*Vh{zFTnj>}MIcHR+lzP6qP z`*7#JE}Fff!FFfhDIU|_JC!N4G1FlSew4FdxMTavfC z3&Vd9T(EcfWCjKX&H|6fVg?4@P7r1cn9`)mz@V4r>EaktaqI2eo5eE6=RSAz64ALe zZ{zVPw%cqz^@YElsylI7rKbWTlL6;F6^}2GwfD_0>~NG}aTQ=V_+9R{ zzZ-HiDR6A+yH&ND?}m47?zi3P9IH-q?=OAq$5FJc<+@x-lS1^K57FlD{(Snp-(kY$ zqlYe6X4JF7B5si)FO?_kw8icOp?g*WbRiFKp|Nnng-;Z3Bb1J`b7j^5x{` zd$iP~mSjI$Ib-s^BX72MlnQ2LesoE8j#R(sQ=Bh-`f|#yT|Wex=IBb!tPs*Tu)r?e z|4Os(TgyzNi@d^?*L^}hH?tb+OxL*O{_XTjvyM5@1{3dkt@Sl&Rh5pMr*?VgPV3U^ zH*ZGYd~oByK9B$N8X|XoYu$Ws+DnIiuxn2B6k)8C~_+{xe$)2=}IoT`Dhg_U!^o+N5XPQ#Y zvM^O6K7m`;)2&QvcD^f;@2D;M9H?6)-(mXbn9Q=On>SZJ71-L~b5(U)i_fgRWjgmn zPo%WEEdTbb)}*L4aJhPZ-xU2mk!gN>@kZxf+D`fL++5 zGbgUPR2A;pQk1o)sP6y7FZp@)>sL&iCLvE9lMoIy-&&w8hrUOeswY-IFsD|IC`VEbVDUcD$Jn4f_6ou*H+y{2)z)QaReYP=_-E>;8>aO?Zv}kJ*;!LpRM7h8>ysZp zJ0Cr|aUt_;wleck|Mzt(HFqUmI{o?OU43O{*Rzu^Pq|q>Yn?*9@hfkWw`)!^BwI_{ z=(|ZyNp@Iv^r3X7C+P z>FSSfRJZ45Xzg3%7e8mu#5=|(yHlz^zDX@QuTq=4BDrPBi>YfYo`2@ zt4kbQ>wZ;>%J*tpyonD8k34_w&v!lE`%hf$q;5pMuiSWgfy}S8jLdseKmNV2p4alZ zw{cy)gBr&s>6y#&R!p_}_#xw&Q_&~K4HcGFmGzsp)FhRxjG1#jRejm8Pohme?yI#`?~DK4{S?r`9Dqkd|X>u z&xF%2*FU=~`tzrd$IR|v^Aq6`_dA!#^1S(T^LYG6v9KF=4xFocy5aN!nZ5bfeb!gl zb3L9gOKLwfVR)${SWlct6*`H63&b*y@u~_)&(V3l_ zlb0GhFTMHnX0-Ft$z7*|_g%by_rG{+%d(U&&n~_2b?ve$K4reDGI_~P|8Ks66Thr{ z!ZkNnYp2t4?N1WmFI}+nv1^3ZA`N|{ddFlZfI(${536Vaq{)M%HJ!*xv_NR!_CRg>3+TOfid%*9kbv6e|?s2|Gg-= zy{U5!X`k)QzIUTLU4Q4>r*9ToMyUrq|M5k5WpRm_>n^4K!jt;*_VT=!xv1?mai2}C z_Eqzv-un9;1G9Qr#IBYvnl^84<;Szz=e1@+o%y3W0Ti-b@{Gs6JF^%$PB?p_r0m(W zWs{nx*C>ByYuEqXp4ZbKugxDnZ}yH=(<2w=Bz*eJ{y(aFQ={kEN%1z-k~iY!`OP=> zcz#>w-(scM&rxS5T~(Dx@pwB|idXL$-}$F}=l528yY%_=A}jlyua0h>o}Qlf@-FB9 z@M&6`Z&X&kx%TPC+OD#7*FRsB^jY5*%*?B?XYn|lyj{h@aF6}8?xu^krf$>a zypV9yb!$)bb44z<#nX+oPi!%&j`nYU+Wqg-*^eJzpFHUO^JDb#W$Wh8+mkh6zJ~m) zfBF3XBf38=%se~k>SLc*_8*@&zI{6LOJ$MM^F+DZyD~+Si-Q0M)=cUf~;<7hp^@&G+D{cxC;hC17|Kgp? zS^f(N58oEdFl%rS>2b?vOf};=_UFjfjF&-)pWfX$vNh@Fr4JvL2CJ_N?>QIHbLQW_ z$@bS4C8dO||8Ri)smJq-s{5QucC|WNTrM4V@}3tjmToNm4zN}UM@@f9;-Jc)bJbi#YwJ3j9&=j}(otoC%3DWyxFM`}T!811P@~KG4{l4+3 zF?~xHCu}Kj51-)qu~;t9V9f@`os)dj--lc|^JD6h3ri1ve4YOMz3$mND`wOilzsX> z|IbP>-eZ2RMD4`lPRY+)Y&v`If*+T2z8#9F==4`tFOjoyU7xZmV(+3W@xJk^Pt?C# z;-I!ydPRkL+S@%wb?K>p{9LVHPdyyJZl&0?!;@B2)UOd@J-p8@xOeyV$Q_zncAZao zts$}X&5NqKe|n3jEz9reiJK*1S^n)w?&CR6f2`E6`@B3Vq;G!Ge#6}_j&7d*{P}w} zdx?2xW7l{$LhAlaULrj0U!Q$7a;b32ms^yuLip>R&{;m>es@>zcALcAU-vtIYlh0s z3mwwgJB@xXS64b+{Ih!+xY+>Gx_r{fWwl=0)FvA*N|5;SEcVZfxVxs);%dIUoS%I$ zqH3d{`L!Znm+Ah`^rOy#s#1TR>A^dvy4{&^I7HvtdaI1mn)D`x*=y5xZ7JCQ?pMa2SBH*#J^$|S>Br~) z?T-BW?p`>z@O3+>{kJ-#i)TPu1klX(^UU8}uP+_?+uE6Z`6#zK*YmSkyDAbBAKiM| z+Aoc0O@Yd8$(ieP>>3o(xxv-X%U72UulD9o?-Ex$QTZr7e*$OI8~0^1A01twp1h!8 z_P-gtmBx<#VHV$0n(pOXn9NzZ$uZyT$w{`oGbgA^e@b&cRV9L-%4%%;x`Hlo+q~=yW^lQ>A?|H|F>M_#_x__elM?)0Oa!AD;u`6<|8$e-2~`z0Q88P+Qv6HWf7RNqs)bjE&(=Us*CL_mZD|CFQ{O}PibULPGa56Uw) ze|nstWH5sx==gzzAS+2Pmi8Z0QVKLAWxQC}g&P(B%?a#05a6^YMkCT=daZaZ|DRaR z_tUhGGOKR1Iy&P+;40IN%&C$GOAHTAIx3-Vth2G~Nce(zPIiw@xBvawp)daJ=?{0Y zkDUC5PtLaQ{lodcz;vEn{gZg0srswEINN4(<+s}2>R0B$&Ng!#)HsS1W?m_n z)Yx_4WVH317GXxexXz%GY>AK07yoguw|n(m`s|)Hs#9l$U;6ZEZGq!#{}|68ht3r& zSHGyYES}}>Y*cqbL$$!$`QXA97WJE3rBb~lr+imt4qBkHy3cGfm&oL&lx=Cx`K3|bpr)7NA zyjoY->u1-Ct~FMdb-mv-y+dP?kcR4!#V@=&CvZ%8WU}bVb55z)td*NIPjv?0SNbz` z^1GH-%O*Ftgu4W~=c*O5CTTCN`PpHw&g8je=MRx3?Mi>p#@&E8)6N3i+SrnmoNCqM1oe>Y*NuK%(I>s+^$4c>ESaPGRy z=&$y|kKJ}=gSBi)I@7$82_-vsh{_uMad4V8hvToM+`ejc@lPLW?VQTz7byKPc-~)G zB=qr_>wc~C|L64oI#Q<|ulPt=IjFMgjmj(iBS#KSov~lx{WGmo?nzxu@lW-S?FjOD z#W3YNOINcM=ej8h@{vcT9`Wv4_0HmDbW)7rv8j*#EqcIz^8JKgE7pjwT)$$EL6yp_ z_DwSqj&0Bv|HxIl>X3oZ%aX4bYfCIQ>g$PrdbUD+MyP#KWVy`Oqz~^s56tDOTC!c^ z!8wilhT&EZ{AN~#Z>?Px=6cW|dYNvSdJ@mMmb;x%5>ks_9SE6q{o2tJR-LAkRx-`` zp{C|wYUQ~7LtsvihqCM0B@>SA{aNOl^J$OBq95|l{us1P|9|k@lUtf){iZw}cAWo3yRh^41Cdqfs-x;c(KNM~j~Ab$#j(B(pNg z`W-eKN*j}H}>5ymJqQGfY@2>oI*r38AdsW!`tny?>5uOPvx33ed zbx_-!a;dn=WbMM2oL8OHlBciUXD9sJ%kZG5=i;6>;hS$rcutwLLFM;L*=g4PHNQ2= z=d~=J;}eqoTHmGS=Zbi)}~(VpL52G%Une7ZdSi>Qd+6tzVvI)U;>MMTv(0*~xJ7W!sl(WObn z+Ipf90^A`BJ9e>K_NK@`b(m!B`Dlst%_sS-clVThkBuvtR5Ypm$=Oz$W9ueuV%J~t z=Ad!!QmrdI^H)BaAzZVp>ZgxF@ZJ+kzD4mg1)Cf?aVEtpRDZgap=`l&rg>?ja% ze|hv}ieS9nscO!;jmP~zD7JjoFuknrwk(jNcVEt<`32|BYc4q28LXRfNlEx{Yhd3O zL$Axvlj@GPSUsJ4*|aO*Yp<7_P^x6(ixe^Ez8jM_6-+a$^Y~iQBK*p-i$Cg*hi%nE z--6{oCO25ieps^9eEFf6T>R|pn;?6d5bU#_PbmxrUjmcllRByDHA zpn{^yGN(>#RQa<;?Dmcx56wia@Egw0$|XN|&bfR1v!`0!zL|`RQq+ySeol#46mfL< z^V9&ri=t0mUKNNmT0Ha*%&h6|IHNnIP{YkP?!v-}C5zaE?63R1*{3YLuXxJZO;cy6 z+`2!Rv(lLDe78;TP4}16yFT1j)wrnPYxgNXF!r9O>daYUtjVTl&L;QAhyS**vK3$T z?(^R{=H~wO0qvPTmJ2fKpXo8adOH1)^z@JebGc4vy8iZI{HdR@^s`G{YISNv(zD4I z4!PAlx>9^n_eASW0aw#6DoX`I_2w*!oH{+Yt3Z4Gm&QL?V2T#tvSleL~uIc>S$E24@?%`HWEt}orgoNT(C90+#?^UcsS(GI^jcoY?f774mVcM`=ow0xBJ&p zNtQs*uAR473&q-s-p^mL-eKLOQy*79-{<{Z;hz6IC%<`j_M5Ej>p6TNWt=zlY46TS}lI*CU(lpiAOnvZl1A_?MCVpu1eA4b9VS> zt!|mzp^@n~t0^dM)z{?}4x7E~{zf%Tiwc}uvUaKKEg7zA)vUI?T2h`{1FlVE3i5j} z;gXk^ROUN2kp=TSCOq&;334#(J~;K+kA9o3Bf@>@lcHOfHi|s=3d$Eb!Q!Y8Y^!Q& zHS3(P`yBN~|E@A!VU@{$XZ%oEJ9}bO^u<;#?{ke@fpWa=U7wZ~3F^OOoAOrBJz{B5 zyi47Opd_{BGnSrBU(7YpGRugsGj5{AY`-@XKWAO=STWI^-(!()$J7&Qt1iv4Qd8rM z-TEkavfLtV*@AQz{*xy{AK9BdQe~N`aN=5u*ER2djQ>;={ry>EPBzTWZ<{nHI;a0g zBg?}huKk7+I7J?HT39H4d(X)%!8qx~v^FcJOBpp$PlDB+oNQZqH|^v*i<=wV@2GU< zuitxfgG%9KmGxVA`x{uYs#vE+8a{MVtLoj5C#N`jgR$_;B?^Teh4yiu-aeXJUG%7P z)3QmdF0(aqTyF;$>DUKpe7W1%+?rPy)*WrQw(tE0-@-=@g8vIFAF7=E=+(;1qGyY<<*PcE%1uJO zCqh_L-x`TFJh`~IPHn;YB?~O-9ov*Xwp{2aUM}*!LCSBHIeUszGi#$y>9fi6K2PD= zF*)mbXR)fQ&eBxlO&WHc9h25i4Eno^e@E)s?0|a$!NI39+`X>#wQKS#>m18iuvb`A zS|v%d!>Rkp^?#G!e46v-wTO(+bN!aRlf+lVa`)G~6`c80Bfiu{XW2KGms{N@{e9}g z<)Pult-fx_wAE8Ysy|4*)@t+-U@@9G;Z)_SGwOaD8^R5rM_fJX{@t4O&XSvY1nO~R0uBk|VqHOAaaPHKyrI%MtF-UCO#4z*B z+4;p&I$MehcX7>JvFN+PpG%&X-g^}?Kg!8pyZp@jAN}fonK@tG&6uJi#oDa8ymwK; zjHvgSA6?kjZ&|B4vGA+j>Aq)d*>h(5s5zQ`)C}eHu95aiQ+%y(%6IL8qRxMf-fo&J z1=SZ6rJq#Gw^{8ZvW{b;N=E04MK3}fMFcmS)Uhz_+1Ecr7X$SxpS#(sba6enk#Lu^Q=Oq8l`*8 z4329m2$$S6lc{#{p&R^r?R+A?ZwZOt`0mh&`4+`Ne>~K5R5Y!7d3w`DB8$N?OL&Nk01?7qZ!@YTBu~=&0^s+-Le^`j?=y zOJ{QirUbaS>GZZ)`!;)-*-wxv2^8>dnihRhD>HF|J4>fc?4t?RzK`40CT#hvc~+5e zY1Hka)7q{R4)gOFGM@DP%MlP|r+A?A`2_E84|rxQ`S~T+d70GWga#jl$;l^<2uU^9 z+DE?r;d85IQC;Eb4M)nqaO#B|I?*~w*(>Jg%qs^cC4Kabk`6I)cWH2(5Vgr(zZ{{MD%?(BQFcYPF}ZN6`} zq`SJT`QGiNzn{(h%l_vZs8QjdwtUjcM^AfV>vM`fM_EcZeAIkrub0r-;I+BC|M|6z z&3ArFZ_mGd=jO(}>_tnq<-YrKbs2x3?3e59K5KiM5?|dmH0Lk=9F+@W4^5Za4W;4!&+9-6mKXnLkKXO5#=$gm#+NTAi%YpCI_|o+ zvyWT!vh$Qkqm5rqIVlLAU~w!{6@J(yx8j7m?<YfORMYJD+el;-U5f!u_T^`Fq5fDf8?q*OmJ%`KRs8xh!O&&nr`g zieF}`!KL{epek{)o7@D>O=2_G2(UJ9^f^0q+uA6WtSr&2;;>t@GCyT;J-xtjdUn>f ztoY36`^PVy-uAlXrqOFBwXM}GMvmV;%#0FcJ)AQ$YyQaxt$F{>nZNnJc-FSb0@dA_ z+gALFnm4zpRP*wiZ%(1n^HOJr2L~FgDx1NhkaB0?^%|o@HkK1tG>m3;H6_OMrJh{! zZ{oRs_g6Zm82zli6P0pl&h(@6UrS!IO1X4<(~9Yd|F1op>{&d=(>V71o_8rhf7+R5 zIxL%Y;>zt^8WN`4`b94uy7%g$<;~rnUimF^YbnZLnYn7ip1AXgS1<2cr53DTe1N-o zZS2`2r&nw>Kd*F1PCy~}b*)-(puvs5>ZgDCT)UaQ=bNs}&AH2uMb4{j>~^hL)|JTI zYc3wooAT<$J#M45Y(}Ly@3%_Md?loR*eYdHg-DOqhvYJrrJO0NUI<0b^bzHamYr$y zH_&=j_49r2=Kj7=W*z+}c-=eRLO&6nz4BMi&YZpcM_FmR+~XS+&4(f+_#0<%Jw5R5 z)RogG-W*$>>a}F0f!Ni^eb){8+)9f-=Xfpq*BldmJZ^{Yxw09%%HG(Pb*$!}xnUk> zQ_hxoCzqvs`r_U%`=so>)t$>b^RM^Wok)E3^3L|%zdvkrZd?`5KY_CeG%hvG0hGuT zI8vBqDu63;urSk1m~fK6cG$Yp>5PFIj82ubFjw!=`il0>0iW6;Ek$SoSOMNR9~4>j^8TMev9g zH$hS%uX=F)+(ii{_IAPf%E^We5YcO&X8O1u+%V;QNVN{zF#%st`ZaP4czrTucgE`; z-A(iKS?6o*au2Xkrb7L1~`_TthnwoJf(C zz4!Xwx7Fu--{;=_(S26>{@tF%llwsB%iD14{x_dNLmg5lpSw9ZEL3wL#@$=ua?JV}(kG(6+WaKDt;z~~Kj}yC-I=?hHD%Q=A!ehE`KFwWKEAz82j@5YC|L_rI zpXQ);;#-Q5+{C8yEJg=E`J`A$C0@U*e!e&OMgOZaqNhzXd=)2TicUOvM%yc8r-nq; z-^LlU+jmxOsOe2vbwcj-1WscG2Q|>B>+N;vM*r*MUQIDRsk^u6#ic8nZ&HH(%mdYa z6F8f~xM!|Wc*DqO1dh8%Z`s?`j?1n(EbDt~3obdo1$;kOd)NB=!$zy6_NuPjcEc|!hH zHGl%&al%#>P@J+jPE!xgUYsx^YI66~Z4sH>Q@06hiKl~NyM+_W7lrP( zp7=ULqG^tLw;Q;!j*v@fa!~VjIcQLAd4dJvVpB7&wlt*^pfFMpW-;2T!_{_kqc%94 z6;Aw2$*L6L3I6=o095yc{k&sil6XPu6C?+3oX6=a0X4-@-~_~7h-3^750JPcI2^!) z09a9qYvH>qFIRf=ud}Nw`1 zANbwf|0g5!Ta>0Hr)7}$=IeI*{%Ab#yLLI|B#m!$pr^y$vm4)f|S z4c8z2n)UPkW%cuGj! zkyG}Zi#z|XQ!`((hyQ=jq9IXr`!&cnI`4IiY~{|KYw$UzcDbPJ*}<#F{W_W&_a1Xp zi`Z9~QuXUe<a z*0j#Akn+t}Rrp)O3a1;)OrSyxR02Dwb-Nxk*m_P}J9p!yZH3d8|G%<&e=bYy&ZPHj z%|_8%FI}^WnzM74QN8lB^YXSYHt#s|)->>p6IpfQ z1y$H2tF&|Uziho1AAYrOoumJ+e^c$(oVhP^?p##No0Ls;v9nfA{W<%)zCBmfWxtN5 zg)=#f8hy4(?6fixY*}F(;;XioDO_=8jQiol{ItSfw+^rFS9E`M=F8R%=F!hHUa#$H z3j8QEfioq^+{inBUg6zLaretcvuyK4Pp{c!e8DrNN#V8&ICE(K@TuyYC6amWDKsCj z{odZ}qYyk<_xz2>^jAgSj2zW6LMN|$8ffq(M&r!c!*}*fi`bWBKl|j#(s=DEj>t`o zJ_0r15(89fKpHsEnhIOpbwcmgEw%p+YM?+>fT{*%&k3AOYa~If5gqMZ-faPwqi3&_ zRK5IWn|WICHZ!s2o6$)o`FF#QhVevYt@3f*y`GcV)?>wG?KESLD}Tf$a5BxDBgwj0 zslDfC;N!f7rth?GZHeHydN;`R^R-%a#c%PwyT79plI4nG^G$p9~_{QClptTmJTyB?> zZb|Wnhm`NM+W1I3QeCXs>Y|lh@X_=onLpcp`OMm}>QT`I&ZZmz*2R`7NpIGy{#*3z z;>2sml2#aUxhbCriA}m(^|>eAp}|Ms$rcTX()qK)FCJZ)cWcJR>XdEKCzUozNFlXop1T-BXG?)(Kh&X>7RlP{{_;O zW$k533f|6228W)5+H0ee#sae1=XKA-N6pw@@@xukU%Fqcl(gMTTe-D21a5CE+bSb@ z7F@NaWa{kveR%ov^XL8a`uXQe$=jJGwm&}dVdc)1PcN(M@A&1+w$@5o6lusXU&}%C3Wij4Zq^x)5^`p?%yhZi|^wIxiC3x>>WjQF-Ct z-TiazDid=u>NL!At$X>;)|-?U{(qOu?Cw;$=59{fpL5mwV{U!uFW>k3F1!7&i$CT6 zyf}C4d%E%Ca|YZeSR6afr+i(vXWu+!{S^<{jE2@T(^-5x7`>vsM3&-4Gk{qgaCu*RSJNgwNX-krbi@3)=bzG=6|`mT<8 zdgH(psw|4L(NN%&Q6~@oy7$bLt3GQu-& zWR)wKR;}9mH!%J9;+sFSv^gO2ajll-7-_3sQQbo`UK}O*Hc%gt_;r+*16xp&Te$pM~`1Tz3Zi? zSZTTZ|KI1_4Ugcx<_`I86j!x4pulu+8_w0MS z_dK+Ie(&~?IBUDP(--Y4QJU-YJmmSxyE?1&xY}5Sv+tX)f7kiuqWKMHzmSce%S)~8 zH@@!;nO?h)$L`wq!UmtGk9-tuBLnZ+9^beBq^&{G%k;aQ-?zWhUZS47a3Qb#-J8<$ zy65bXwyWH(U-&CJzwC#N-T)>mp*lSH}}hv z2LoQdleYg6eZIZE>iOLJf4;2!GHct<_kZqgoxjI&|3ltyw=%CyIkz&ruvD(>_ms7s zpedjY94)VQ@9N9; zSEt)-wJ!Ul9^Iew=jy!rA1Alv{+(I7vj5klGcUT=W zBT-4%`7PJWlM}{N3FA z`qDqM-zu+Pr@cg7GJARC<*v>yk)m@MO39mVXmYhBpEz=3;S&R+&^x^SNB1Yb=ggUU z{z{5_R_R1d`?>yo;a7e~f6M#vb6>pdi8mj7O4^^@yK`e>=KgyBH(%B-{=E0jkL~|6 zZ#&fAdGKku|Gd|#m)y&ye$GirS`n*gYWU;Tu1mYN7Qep6C)*$QyZwFb$F6+45Mbm>3)^>9}=K8LG)U{TpcmJU&k!$2wo9|BC z#645OdVSc!^GD>Koyz$hJNe|19sSj@Omo+08Sy?{UVO4pZ13l|^V082{uR~dy}tO7 z?|Y~0yUDlv{Ql%je?Qy)&(V#G3m<1g;``r{neP71IVnkN?w)8fce}Rr?E2{9&(Zg* zKHQpRzkTc9uT!@jx@G!wp7YKpKU|Kl&)>4&X#2X{l*7_#*~=q?n_Z?|T4mOKb#X$- zpVhU;XF4ofbvPpAVeHnFIV%PIi%q{j<9+6rBAnb)^;_I}|M$(hv-^GCa>c#rjjR42 zkoTAWzFm>Up`y2!)z6#SUwYYP!zOjcfAfUSjb*~`^Q<RNJQi5`cL2Q@7uL3_utiRo#p0+*Zele%@Rx4IpMC+v9ygy49J?sNOe{Hmne>+aV*zug}fTl`fz`_bFC${Q7fw+1hHarMfBL%(LRzMiST@p|SP$7Mlf zTEfz%OSj*<_wCuUb@}^#&j0@Y`I)$AecrU0CyvDJUdep?QEu+^`1sF9XKmAui!NSX z`}X3F;{l~4_$N6Ql6H{NOih;Wwt0!mX zzrQFjU0QmUzq@%b=jzow1EYwF^5Fm9_q~t&KI{F~Z~xxc?*H&^ z*})SFjG^W8*S*L1zwNBr{q6aq#fO(#2Ct5ZUia|e&YYwz+l&7nw3~h}?y>2$bu(sa zr7e4tI&Gp?8Y8$DwCfjtQS~nV^0zl$TZ%nSFMIl?;Ed`qo4*&&_3g90+y0xsh;MbQ zuDjS~aL*_5Zm!j)`?s$bFIk;@zCC((?vizVwzanIeQ&-?Rj_?q8|w_MTG`LP6ezD) zU;Y2xW3x*S_rCk*JSRB&It#>prTeHMGxkU*ITx=&;)@IG;gd_!Q?LiLc=tlaa z&URrIOaDKwd5s(==$>S8 literal 13739 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 diff --git a/reversing/doc/building-facing/bridge/windows.png b/reversing/doc/building-facing/bridge/windows.png index 38adf3d41b6fd019b78df399985f239d2e6d5536..916e5e21cb4c8b8377630d72808db2e4e08d2351 100644 GIT binary patch literal 14034 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 KelF{r5}E*1Gr?K_ literal 15477 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 diff --git a/reversing/doc/building-facing/horizontal_axle/1.png b/reversing/doc/building-facing/horizontal_axle/1.png index b1a664d70e6be178bef6d6cf3656626fc66b55a7..5710650ffe7449b20af93ee20dc96bbc2f0b57b1 100644 GIT binary patch delta 1648 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 F0st`|U9SKD diff --git a/reversing/doc/building-facing/horizontal_axle/2-windows.png b/reversing/doc/building-facing/horizontal_axle/2-windows.png index 905e98be0a02428d46ddfe4a63e3a60847ae4e57..18643992e00684e4a53ec58d8f84ccae891a2e1d 100644 GIT binary patch literal 13191 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 literal 15097 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 literal 2847 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 RV_;xl@O1TaS?83{1OUrtUlRZT diff --git a/reversing/doc/building-facing/screw/1.png b/reversing/doc/building-facing/screw/1.png index 216f050da9547c660e8773e40d3020924a16780c..3d7dac56d2ccd0f6ca4db5b9516b00f271ec537b 100644 GIT binary patch delta 1568 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 KT-G@yGywoQyIKYS diff --git a/reversing/doc/building-facing/screw/2-windows.png b/reversing/doc/building-facing/screw/2-windows.png index 26e41d32710ffce532a168ab2b34cb6b6d458df9..ff24bc91d820559166ef67eb22089e7f2a46a9b0 100644 GIT binary patch literal 2606 zcmeAS@N?(olHy`uVBq!ia0y~yU^Hi7V3cNIW?*3OO}gj9z`($g?&#~tz_78O`%fY( z0|SFXvPY0F14ES>14Ba#1H&%{28M;_2(k{)j`4O;Byp_Jz_644f>UE{-7;x8BZ;UiUlUl+?P9rc;~3j4`~M#f+w)xi6=W~=JP$bSU zndu+n5=9OLUWFJIK?MeeOH9|Eh`)Zn&(vSPYQfJIPUAG613i+PcOQuT=jh`iut8 zvjN5i>+kM7)M#R|W%u&7-}4nFkzqtiO@_&Wcm+LcP|ZwdSR=H!9coT6~xIb#`HzhN-jW3d?^}dcz!}1R6UO zt~Ex>=3NVMU#-w`{8sk4$p(iFWe)%KJsC2mWpQ_b&?>8Lr(4TXvd%qZ+ptV==CVtl zw(d8Xu0P}RBu=)Ax1uKa)b7Zx^4tFK&iq*^LAn+b*i6InGJCfy)129Ht3ZhNq{)Yf z*99-Um!%aywqB<;bKODDn2A0H`yQ{Fl=-eFEGt89H{bT)NhhO}*FN=}@lc}g;f+&Q z41`}+h<&Z^RlhuM>Phc)r7Lf#B`%-Y7di8r_+tP zZL@E$o0@rOs%-sn&r4bFu5aEp)w5Q7U25vDr1O!NC;P;=ypEo^kKH^i_1CS8RBd5U zAZ|?gwEvH|^c11r0Vg-NosDjvJC##}{R_|ehps2LY@hn$?9aM~npM{}{1dXiKYe#+ z+lAv{`rM7?et(DkLD!_r ztmv6BB0K8pwtP}KQ^0e)ASYF8W~ZK)+MV>&Q)gVnRWDb3-jw1bb9&0m&b$Wc;27pf zn#RIA{dce|cYj!P(&Tu-%$YCR=1B*~h-zPsGg3`k6n$@};10%+yKm>u zcG=?3e|GonY0`Lvwd%79R9Pr&oVtT>A1{#0iNRpJl|EdCwm>X@tb{n<_5)_6)sk_C~|5sDUniodv5n^ z)69OJ`RA;j%~F3BGKrB<>d@}T3Jxz%y?s2*xnaX;w!))^*R2evN6vK0`IVLK{os)J z*9p%3TPNqOE=MZ-}2i3tbg_9P(>fCw$pla9Y8SAEco}J%(TXWq6 zw#4c+bA^lf55E-&w7pyb}-|5yR@z%*Mo}J%X&z~ax%Hr9w=ZE$mEpvD+cKpuThxcz?{PsYqZ|BRMAM)c@zNVziUDwKvWY_;MHIo`ohb=48@-7LAtW*5++e7Pg|C7{1Cqt&m{5uOu zv^?dpMh`Q3cX}BFE!)v@Y*yF~8_vU_|95RZ7NaVm86I^vs=@1Z@}JDeW>a3;4QYX5g1gul98*-&c;GRk*q=#y~ZHAM2MVXE)D#?Y%5i`Q6`nx-Jdq@oxDVMX5WEGZpovr%L^*Mz31_} z#TKJC^UA^2_;u%9k98*-{x)}cGvjmJp(A@s>`ppxJ^#CX;%%)jNru16S3dXI`n=ET zS<#NYwJ!p`y}!C3ykWKHvYnd`-n`u+*!D8w+k2Cf4ZLDn|4(iJ1ykR{f7?==jHjv! zl>ITkG9z1K9iw*ev{OaJMRx*{*Dc+_mNauy$TWpoWgpH}`)I znP#`*wc3tdkEAvji3Ua9i@LsH-43=jKCL0sre~kw)zX{S^1r^|KVvO_)>{6%X*~=K z45}rr5hW>!C8<`)MX8A;sSHL2h8DU87P`g;AqHkvhUQkr7TN{|Rt5&kujT5YXvob^ a$xN%nt>Iet)kzEt3=E#GelF{r5}E*s&fSy% literal 4703 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 diff --git a/reversing/doc/building-facing/screw/2.png b/reversing/doc/building-facing/screw/2.png index e5e5d62e93581684f26fda022bdb65012cac4cbb..fe33f93745b18b77d18ab975892a4c37fcd5d2d0 100644 GIT binary patch delta 1537 zcmdlhI*Vt5L>3b>0|Ud<_sP!~7#LX69eo`c7&i8E|4C$JU|>*4_6YK2V5m}KU}$Jz zVEDzrz|io5fuYoZf#FpG1B2BJ1_tqhIlBUF7#J9M0(?ST|NsBbz`y{)4F3(i+;|um z7#Neh-CY;_Dc=`NX!AY?M&d+K0-b`qkCB+pKx|C^d-rD$!FYB+#*{sl- z+Ele;&BLGb;xF1VZkBXl?mnvIDWEx($z_URW#6^PXLIa%#0}KWFAX{NWM{{L(|eoG zHQdbE7ptan`{*Lc!nQ3=brX*7nE(Dk)Qq!Y-D#U=pM7@L&QoBb)!I)sp_-FUX`cIa zZT=&!mHvhMHcOsKyBjxWS>3rATK)cb#i74!O}EUCC)>nmH6_1U z%g-9ha(u_tT!j^rl}^w1HrFsJKdEt#*R)zh#PHqhm@l!O9fw()ye2U$U)Aowxa}=- zOyD)a`g80}T?HF^J6S_l2`uMiF|B5QuFrL9caKAW@uIo0>`hS{cd2OHy`87EiSzr@ zrl@1bEo50(ua)oG`cKmmZA*Y}Uy z{5G-gjOF|4yR1!7YU>}*d9AvZ^aq z7xe!TR$cMbbVu{Hi3@hHU6(JJvfv(P%Jjmh*BXv**WHYK{d@l81!{M$b90q>OkA{T zk=6>w(BsKG5$9Wz_tZ;my4JZsqN^vFb!Kw+bH?Wn-YQw!O|#_6>apRMXq)$DY4Vr3 zd2^#{S;|GOWK5aFH8CiW>*o$8KRK!1rmAnUX%+9n=1VNyKDYSq@q%QmaDJN{hC@|p1xdZc0s)M3g6r|8NqF0_RYx-3e?U-A)nB}U@c94IkEckbuW$K;crd#>TSWHE4 zw(MTd659CnR&(^$YsZ^r{Y{$a$#<^);lk2$FSDcfFn)hx{k9O&l6=+~T$yNBtFWTm;;i8(1+}&3!^#yTt~{Ar^Sw#< ztxXJ>$kVe=~hgnb%mC? literal 2749 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 Kb6Mw<&;$Uc{B6qs diff --git a/reversing/doc/building-facing/screw/3.png b/reversing/doc/building-facing/screw/3.png index 8233b59098730acd81bc66062a0b6755a64ce37d..9352f734a6b0b19d187adc191a5141ffbb1f4275 100644 GIT binary patch delta 1490 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 YzTBaFPSM|g3=9kmp00i_>zopr0Gz<-JOBUy diff --git a/reversing/doc/building-facing/screw/4.png b/reversing/doc/building-facing/screw/4.png index 9f490b1a2c31b60ee9e462c252d667a01876d2cd..bb7f9d94261dc1b16b91056b1a8c410e5f30be63 100644 GIT binary patch delta 1466 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 Jvd$@?2>_Ix0%QOH literal 4038 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 E0Je71_y7O^ literal 16920 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 From fefef2e121698fdb672a911cbffd7d13976fc3d8 Mon Sep 17 00:00:00 2001 From: billw2012 Date: Fri, 31 Aug 2018 20:53:06 +0100 Subject: [PATCH 33/66] Deprioritize dwarves with unmanged labors assigned, some renaming from disabled to unmanaged. --- plugins/labormanager/labormanager.cpp | 62 ++++++++++++++++----------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index a3fd7db4a..419432870 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -103,7 +103,7 @@ enum ConfigFlags { // Value of 0 for max dwarfs means uncapped. const int MAX_DWARFS_NONE = 0; // Value < 0 for max dwarfs means don't manager the labor. -const int MAX_DWARFS_DISABLE = -1; +const int MAX_DWARFS_UNMANAGED = -1; // Here go all the command declarations... @@ -398,7 +398,7 @@ struct labor_info int priority() const { return config.ival(1); } void set_priority(int priority) { config.ival(1) = priority; } - bool is_disabled() const { return maximum_dwarfs() == MAX_DWARFS_DISABLE; } + bool is_unmanaged() const { return maximum_dwarfs() == MAX_DWARFS_UNMANAGED; } int maximum_dwarfs() const { return config.ival(2); } void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(2) = maximum_dwarfs; } @@ -530,10 +530,12 @@ struct dwarf_info_t bool has_children; bool armed; + int unmanaged_labors_assigned; + df::unit_labor using_labor; dwarf_info_t(df::unit* dw) : dwarf(dw), state(OTHER), - clear_all(false), high_skill(0), has_children(false), armed(false), using_labor(df::unit_labor::NONE) + clear_all(false), high_skill(0), has_children(false), armed(false), using_labor(df::unit_labor::NONE), unmanaged_labors_assigned(0) { for (int e = TOOL_NONE; e < TOOLS_MAX; e++) has_tool[e] = false; @@ -847,8 +849,11 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector \n" " Set max number of dwarves assigned to a labor.\n" + " labormanager max unmanaged\n" " labormanager max disable\n" - " Don't attempt to assign any dwarves to a labor.\n" + " Don't attempt to manage this labor.\n" + " Any dwarves with unmanaged labors assigned will be less\n" + " likely to have managed labors assigned to them.\n" " labormanager max none\n" " Unrestrict the number of dwarves assigned to a labor.\n" " labormanager priority \n" @@ -865,8 +870,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector = 0 && labor <= ENUM_LAST_ITEM(unit_labor) && !labor_infos[labor].is_disabled()) + if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor) && !labor_infos[labor].is_unmanaged()) { if (!Units::isValidLabor(dwarf->dwarf, labor)) { @@ -1016,7 +1021,7 @@ private: df::unit_labor labor = labor_mapper->find_job_labor(j); - if (labor != df::unit_labor::NONE && !labor_infos[labor].is_disabled()) + if (labor != df::unit_labor::NONE && !labor_infos[labor].is_unmanaged()) { labor_needed[labor]++; if (worker == -1) @@ -1143,7 +1148,7 @@ private: { df::item* item = *i; - if (item->flags.bits.dump && !labor_infos[df::unit_labor::HAUL_REFUSE].is_disabled()) + if (item->flags.bits.dump && !labor_infos[df::unit_labor::HAUL_REFUSE].is_unmanaged()) labor_needed[df::unit_labor::HAUL_REFUSE]++; if (item->flags.whole & bad_flags.whole) @@ -1389,6 +1394,8 @@ private: dwarf->state = state; + dwarf->unmanaged_labors_assigned = 0; + FOR_ENUM_ITEMS(unit_labor, l) { if (l == df::unit_labor::NONE) @@ -1396,6 +1403,8 @@ private: if (dwarf->dwarf->status.labors[l]) if (state == IDLE) labor_infos[l].idle_dwarfs++; + if (labor_infos[l].is_unmanaged()) + dwarf->unmanaged_labors_assigned++; } @@ -1443,7 +1452,7 @@ private: FOR_ENUM_ITEMS(unit_labor, labor) { - if (labor == df::unit_labor::NONE || labor_infos[labor].is_disabled()) + if (labor == df::unit_labor::NONE || labor_infos[labor].is_unmanaged()) continue; df::job_skill skill = labor_to_skill[labor]; @@ -1463,7 +1472,7 @@ private: { FOR_ENUM_ITEMS(unit_labor, labor) { - if (labor == unit_labor::NONE || labor_infos[labor].is_disabled()) + if (labor == unit_labor::NONE || labor_infos[labor].is_unmanaged()) continue; if (Units::isValidLabor(dwarf->dwarf, labor)) set_labor(dwarf, labor, false); @@ -1579,6 +1588,9 @@ private: score -= Units::computeMovementSpeed(d->dwarf); + // significantly disfavor dwarves who have unmanaged labors assigned + score -= 1000 * d->unmanaged_labors_assigned; + return score; } @@ -1707,7 +1719,7 @@ public: if (l == df::unit_labor::NONE) continue; - if (!labor_infos[l].is_disabled()) + if (!labor_infos[l].is_unmanaged()) { int before = labor_needed[l]; @@ -1726,7 +1738,7 @@ public: } /* assign food haulers for rotting food items */ - if (!labor_infos[df::unit_labor::HAUL_FOOD].is_disabled()) + if (!labor_infos[df::unit_labor::HAUL_FOOD].is_unmanaged()) { if (priority_food > 0 && labor_infos[df::unit_labor::HAUL_FOOD].idle_dwarfs > 0) priority_food = 1; @@ -1798,7 +1810,7 @@ public: for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { df::unit_labor l = i->first; - if (l == df::unit_labor::NONE || labor_infos[l].is_disabled()) + if (l == df::unit_labor::NONE || labor_infos[l].is_unmanaged()) continue; const int user_specified_max_dwarfs = labor_infos[l].maximum_dwarfs(); @@ -1962,7 +1974,7 @@ public: FOR_ENUM_ITEMS(unit_labor, l) { - if (l == df::unit_labor::NONE || labor_infos[l].is_disabled()) + if (l == df::unit_labor::NONE || labor_infos[l].is_unmanaged()) continue; if (l == (*d)->using_labor) continue; @@ -2013,14 +2025,14 @@ public: } /* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ - if (!labor_infos[df::unit_labor::REMOVE_CONSTRUCTION].is_disabled()) + if (!labor_infos[df::unit_labor::REMOVE_CONSTRUCTION].is_unmanaged()) { set_labor(canary_dwarf, df::unit_labor::REMOVE_CONSTRUCTION, true); } /* Set HAUL_WATER so we can detect ponds that need to be filled ponds. */ - if (!labor_infos[df::unit_labor::HAUL_WATER].is_disabled()) + if (!labor_infos[df::unit_labor::HAUL_WATER].is_unmanaged()) { set_labor(canary_dwarf, df::unit_labor::HAUL_WATER, true); } @@ -2043,7 +2055,7 @@ public: { FOR_ENUM_ITEMS(unit_labor, l) { - if (l == df::unit_labor::NONE || labor_infos[l].is_disabled()) + if (l == df::unit_labor::NONE || labor_infos[l].is_unmanaged()) continue; if (Units::isValidLabor((*d)->dwarf, l)) @@ -2075,7 +2087,7 @@ public: } } - if (!labor_infos[df::unit_labor::PULL_LEVER].is_disabled()) + if (!labor_infos[df::unit_labor::PULL_LEVER].is_unmanaged()) { set_labor(*d, df::unit_labor::PULL_LEVER, true); } @@ -2084,7 +2096,7 @@ public: FOR_ENUM_ITEMS(unit_labor, l) { - if (l == df::unit_labor::NONE || labor_infos[l].is_disabled()) + if (l == df::unit_labor::NONE || labor_infos[l].is_unmanaged()) continue; if (to_assign[l] > 0 || l == df::unit_labor::CLEAN) @@ -2105,7 +2117,7 @@ public: FOR_ENUM_ITEMS(unit_labor, l) { - if (l == df::unit_labor::NONE || labor_infos[l].is_disabled()) + if (l == df::unit_labor::NONE || labor_infos[l].is_unmanaged()) continue; tools_enum t = default_labor_infos[l].tool; @@ -2203,9 +2215,9 @@ void print_labor(df::unit_labor labor, color_ostream &out) for (int i = 0; i < 20 - (int)labor_name.length(); i++) out << ' '; const auto& labor_info = labor_infos[labor]; - if (labor_info.is_disabled()) + if (labor_info.is_unmanaged()) { - out << "DISABLED"; + out << "UNMANAGED"; } else { @@ -2296,8 +2308,8 @@ command_result labormanager(color_ostream &out, std::vector & para if (parameters[2] == "none") v = MAX_DWARFS_NONE; - else if (parameters[2] == "disable") - v = MAX_DWARFS_DISABLE; + else if (parameters[2] == "disable" || parameters[2] == "unmanaged") + v = MAX_DWARFS_UNMANAGED; else v = atoi(parameters[2].c_str()); From 368f8d05041d6253c1b12d4e62d532c8a308b487 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 24 Nov 2018 14:52:26 -0500 Subject: [PATCH 34/66] Remove and replace checks for OnBreak --- library/xml | 2 +- plugins/autohauler.cpp | 5 +---- plugins/autolabor.cpp | 10 +--------- plugins/dwarfmonitor.cpp | 10 +++++----- plugins/ruby/unit.rb | 4 ++-- plugins/search.cpp | 5 ++--- plugins/siege-engine.cpp | 2 +- scripts | 2 +- 8 files changed, 14 insertions(+), 26 deletions(-) diff --git a/library/xml b/library/xml index de83a453d..c588b17ed 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit de83a453d7e55aa48ffc92c8f4c147b1a1acd525 +Subproject commit c588b17edba27d4cada78bedf36ec45f427461a5 diff --git a/plugins/autohauler.cpp b/plugins/autohauler.cpp index c43ec110e..40d7452c7 100644 --- a/plugins/autohauler.cpp +++ b/plugins/autohauler.cpp @@ -879,11 +879,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) // Account for the military else if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession)) dwarf_info[dwarf].state = MILITARY; - // Account for dwarves on break or migrants - // DF leaves the OnBreak trait type on some dwarves while they're not actually on break - // Since they have no current job, they will default to IDLE + // Account for incoming migrants else if (is_migrant) - // Dwarf is unemployed with null job { dwarf_info[dwarf].state = OTHER; } diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 09a756642..0fb46a3b6 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1202,14 +1202,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) { - bool is_on_break = false; - - for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++) - { - if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) - is_on_break = true; - } - if (Units::isBaby(dwarfs[dwarf]) || Units::isChild(dwarfs[dwarf]) || dwarfs[dwarf]->profession == profession::DRUNK) @@ -1220,7 +1212,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) dwarf_info[dwarf].state = MILITARY; else if (dwarfs[dwarf]->job.current_job == NULL) { - if (is_on_break) + if (Units::getMiscTrait(dwarfs[dwarf], misc_trait_type::Migrant)) dwarf_info[dwarf].state = OTHER; else if (dwarfs[dwarf]->specific_refs.size() > 0) dwarf_info[dwarf].state = OTHER; diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 471850025..29d0198b3 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -1742,11 +1742,11 @@ static void add_work_history(df::unit *unit, activity_type type) static bool is_at_leisure(df::unit *unit) { - for (auto p = unit->status.misc_traits.begin(); p < unit->status.misc_traits.end(); p++) - { - if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) - return true; - } + if (Units::getMiscTrait(unit, misc_trait_type::Migrant)) + return true; + + if (!unit->job.current_job && Units::getMainSocialActivity(unit)) + return true; return false; } diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 13c01095d..848e0f471 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -252,8 +252,8 @@ module DFHack not u.specific_refs.find { |s| s.type == :ACTIVITY } and # filter soldiers (TODO check schedule) u.military.squad_id == -1 and - # filter 'on break' - not u.status.misc_traits.find { |t| t.id == :OnBreak } + # filter incoming migrants + not u.status.misc_traits.find { |t| t.id == :Migrant } end def unit_entitypositions(unit) diff --git a/plugins/search.cpp b/plugins/search.cpp index b43daa942..452e8cc23 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -1122,10 +1122,9 @@ private: return ""; for (auto p = unit->status.misc_traits.begin(); p < unit->status.misc_traits.end(); p++) { - if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) + if ((*p)->id == misc_trait_type::Migrant) { - int i = (*p)->value; - return ".on break"; + return ".new arrival.migrant"; } } diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index 3df9edf29..30a582c0d 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -1492,7 +1492,7 @@ static void releaseTiredWorker(EngineInfo *engine, df::job *job, df::unit *worke if (unit == worker || unit->job.current_job || !unit->status.labors[unit_labor::SIEGEOPERATE] || - !Units::isCitizen(unit) || Units::getMiscTrait(unit, misc_trait_type::OnBreak) || + !Units::isCitizen(unit) || Units::getMiscTrait(unit, misc_trait_type::Migrant) || isTired(unit) || !Maps::canWalkBetween(job->pos, unit->pos)) continue; diff --git a/scripts b/scripts index 87cc0fcad..7deb13c68 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 87cc0fcad57a3a8ff7b558be9f3523101ac2f69d +Subproject commit 7deb13c681b011d9a87782b9cdf9eec6e6acf7c1 From b913076451e886f69f06742ad66b1a90161cfb4b Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 24 Nov 2018 17:09:24 -0500 Subject: [PATCH 35/66] Update xml, stonesense, changelog Merged DFHack/df-structures#296 and (modified) attached changelog entry --- docs/changelog.txt | 11 +++++++++++ library/xml | 2 +- plugins/stonesense | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index fd53e4f4c..901ae8b67 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -63,6 +63,17 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## Lua - ``utils``: new ``OrderedTable`` class +## Structures +- ``musical_form``: named voices field +- ``musical_form_instruments``: named minimum_required and maximum_permitted +- ``dance_form``: named musical_form_id and musical_written_content_id +- ``written_content``: named poetic_form +- ``activity_event_performancest``: renamed poem as written_content_id +- ``incident_sub6_performance``: made performance_event an enum +- ``incident_sub6_performance.participants``: named performance_event and role_index +- ``incident_sub6_performance``: named poetic_form_id, musical_form_id, and dance_form_id + + ================================================================================ # 0.44.12-r1 diff --git a/library/xml b/library/xml index c588b17ed..fa6e818b3 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit c588b17edba27d4cada78bedf36ec45f427461a5 +Subproject commit fa6e818b34235b9fe10abc37d06eba27a932c870 diff --git a/plugins/stonesense b/plugins/stonesense index 4a1953f27..4a0f63e04 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 4a1953f27c6acd1ceeb2d5447613c106e708c26c +Subproject commit 4a0f63e044d02532c948d67780dfd128fbd6d043 From 32aaa37070d88ed00e25371fc2e764d0c9c50233 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Tue, 4 Dec 2018 14:54:28 +0100 Subject: [PATCH 36/66] Added coal search to embark-assistant --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index d87131be7..24120c2a2 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -44,6 +44,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - fixed crash when entering finder with a 16x16 embark selected, and added 16 to dimension choices - added match indicator display on the right ("World") map - changed 'c'ancel to abort find if it's under way and clear results if not, allowing use of partial surveys. + - Added Coal as a search criterion, as well as a coal indication as current embark selection info. - `labormanager`: - stopped assigning labors to ineligible dwarves, pets, etc. - stopped assigning invalid labors From 5f6376e76e8c993f2df06bac274d1d9bffbc2638 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Tue, 4 Dec 2018 14:55:16 +0100 Subject: [PATCH 37/66] Added coal search to embark-assistant --- plugins/embark-assistant/defs.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/embark-assistant/defs.h b/plugins/embark-assistant/defs.h index 43f2ddc0a..f7a9ed680 100644 --- a/plugins/embark-assistant/defs.h +++ b/plugins/embark-assistant/defs.h @@ -28,6 +28,7 @@ namespace embark_assist { bool clay = false; bool sand = false; bool flux = false; + bool coal = false; int8_t soil_depth; int8_t offset; int16_t elevation; @@ -51,6 +52,7 @@ namespace embark_assist { uint16_t clay_count = 0; uint16_t sand_count = 0; uint16_t flux_count = 0; + uint16_t coal_count = 0; uint8_t min_region_soil = 10; uint8_t max_region_soil = 0; bool waterfall = false; @@ -90,6 +92,7 @@ namespace embark_assist { bool clay_absent = true; bool sand_absent = true; bool flux_absent = true; + bool coal_absent = true; std::vector possible_metals; std::vector possible_economics; std::vector possible_minerals; @@ -113,6 +116,7 @@ namespace embark_assist { bool clay; bool sand; bool flux; + bool coal; std::vector metals; std::vector economics; std::vector minerals; @@ -253,6 +257,7 @@ namespace embark_assist { present_absent_ranges clay; present_absent_ranges sand; present_absent_ranges flux; + present_absent_ranges coal; soil_ranges soil_min; all_present_ranges soil_min_everywhere; soil_ranges soil_max; From efeb0504ccd112eb8fb9d19941560d183aa4d2d0 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Tue, 4 Dec 2018 14:55:59 +0100 Subject: [PATCH 38/66] Added coal search to embark-assistant --- plugins/embark-assistant/finder_ui.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp index 5578150ea..5c7cc9046 100644 --- a/plugins/embark-assistant/finder_ui.cpp +++ b/plugins/embark-assistant/finder_ui.cpp @@ -44,6 +44,7 @@ namespace embark_assist { clay, sand, flux, + coal, soil_min, soil_min_everywhere, soil_max, @@ -508,6 +509,7 @@ namespace embark_assist { case fields::clay: case fields::sand: case fields::flux: + case fields::coal: { embark_assist::defs::present_absent_ranges k = embark_assist::defs::present_absent_ranges::NA; while (true) { @@ -993,6 +995,10 @@ namespace embark_assist { state->finder_list.push_back({ "Flux", static_cast(i) }); break; + case fields::coal: + state->finder_list.push_back({ "Coal", static_cast(i) }); + break; + case fields::soil_min: state->finder_list.push_back({ "Min Soil", static_cast(i) }); break; @@ -1228,6 +1234,11 @@ namespace embark_assist { static_cast(state->ui[static_cast(i)]->current_value); break; + case fields::coal: + finder.coal = + static_cast(state->ui[static_cast(i)]->current_value); + break; + case fields::soil_min: finder.soil_min = static_cast(state->ui[static_cast(i)]->current_value); From 1ef7f0746caa76544ce7a5455c99b46e7db0a471 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Tue, 4 Dec 2018 14:56:55 +0100 Subject: [PATCH 39/66] Added coal search to embark-assistant --- plugins/embark-assistant/help_ui.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp index 395cbc25f..e92ae67c0 100644 --- a/plugins/embark-assistant/help_ui.cpp +++ b/plugins/embark-assistant/help_ui.cpp @@ -257,14 +257,13 @@ namespace embark_assist{ help_text.push_back("- The geo information is gathered by code which is essentially a"); help_text.push_back(" copy of parts of prospector's code adapted for this plugin."); help_text.push_back("- Clay determination is made by finding the reaction MAKE_CLAY_BRICKS."); - help_text.push_back(" Flux determination is made by finding the reaction PIG_IRON_MAKING."); - help_text.push_back("- Right world map overlay not implemented as author has failed to"); - help_text.push_back(" emulate the sizing logic exactly."); + help_text.push_back("- Flux determination is made by finding the reaction PIG_IRON_MAKING."); + help_text.push_back("- Coal is detected by finding COAL producing reactions on minerals."); help_text.push_back("- There's currently a DF bug (#0010267) that causes adamantine spires"); help_text.push_back(" reaching caverns that have been removed at world gen to fail to be"); help_text.push_back(" generated at all. It's likely this bug also affects magma pools."); help_text.push_back(" This plugin does not address this but scripts can correct it."); - help_text.push_back("Version 0.7 2018-08-18"); + help_text.push_back("Version 0.8 2018-12-04"); break; } From 8f9cbfeafdf46a6bd2ff01e47dc8ffb5446a3b1c Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Tue, 4 Dec 2018 14:57:23 +0100 Subject: [PATCH 40/66] Added coal search to embark-assistant --- plugins/embark-assistant/matcher.cpp | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index b2509336b..7b3d385cb 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -46,6 +46,7 @@ namespace embark_assist { bool clay_found = false; bool sand_found = false; bool flux_found = false; + bool coal_found = false; uint8_t max_soil = 0; bool uneven = false; int16_t min_temperature = survey_results->at(x).at(y).min_temperature[mlt->at(start_x).at(start_y).biome_offset]; @@ -174,6 +175,12 @@ namespace embark_assist { flux_found = true; } + // Coal + if (mlt->at(i).at(k).coal) { + if (finder->coal == embark_assist::defs::present_absent_ranges::Absent) return false; + coal_found = true; + } + // Min Soil if (finder->soil_min != embark_assist::defs::soil_ranges::NA && mlt->at(i).at(k).soil_depth < static_cast(finder->soil_min) && @@ -335,6 +342,9 @@ namespace embark_assist { // Flux if (finder->flux == embark_assist::defs::present_absent_ranges::Present && !flux_found) return false; + // Coal + if (finder->coal == embark_assist::defs::present_absent_ranges::Present && !coal_found) return false; + // Min Soil if (finder->soil_min != embark_assist::defs::soil_ranges::NA && finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::Present && @@ -571,6 +581,7 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Present: if (tile->clay_count == 0) return false; break; + case embark_assist::defs::present_absent_ranges::Absent: if (tile->clay_count > 256 - embark_size) return false; break; @@ -584,6 +595,7 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Present: if (tile->sand_count == 0) return false; break; + case embark_assist::defs::present_absent_ranges::Absent: if (tile->sand_count > 256 - embark_size) return false; break; @@ -597,11 +609,26 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Present: if (tile->flux_count == 0) return false; break; + case embark_assist::defs::present_absent_ranges::Absent: if (tile->flux_count > 256 - embark_size) return false; break; } + // Coal + switch (finder->coal) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->coal_count == 0) return false; + break; + + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->coal_count > 256 - embark_size) return false; + break; + } + // Soil Min switch (finder->soil_min) { case embark_assist::defs::soil_ranges::NA: @@ -1027,6 +1054,7 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Present: if (tile->clay_count == 0) return false; break; + case embark_assist::defs::present_absent_ranges::Absent: if (tile->clay_count == 256) return false; break; @@ -1040,6 +1068,7 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Present: if (tile->sand_count == 0) return false; break; + case embark_assist::defs::present_absent_ranges::Absent: if (tile->sand_count == 256) return false; break; @@ -1053,11 +1082,26 @@ namespace embark_assist { case embark_assist::defs::present_absent_ranges::Present: if (tile->flux_count == 0) return false; break; + case embark_assist::defs::present_absent_ranges::Absent: if (tile->flux_count == 256) return false; break; } + // Coal + switch (finder->coal) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->coal_count == 0) return false; + break; + + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->coal_count == 256) return false; + break; + } + // Soil Min switch (finder->soil_min) { case embark_assist::defs::soil_ranges::NA: From 0916d69373df6e02ef00cb11d427698e76d0f202 Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Tue, 4 Dec 2018 14:57:44 +0100 Subject: [PATCH 41/66] Added coal search to embark-assistant --- plugins/embark-assistant/overlay.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/embark-assistant/overlay.cpp b/plugins/embark-assistant/overlay.cpp index 75e6258a3..c25859ccb 100644 --- a/plugins/embark-assistant/overlay.cpp +++ b/plugins/embark-assistant/overlay.cpp @@ -339,6 +339,10 @@ void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_in state->embark_info.push_back({ Screen::Pen(' ', COLOR_RED), "Clay" }); } + if (site_info->coal) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_GREY), "Coal" }); + } + state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Soil " + std::to_string(site_info->min_soil) + " - " + std::to_string(site_info->max_soil) }); if (site_info->flat) { From ef57295c028870ae9d4e1946bbdf869a314eb29e Mon Sep 17 00:00:00 2001 From: PatrikLundell Date: Tue, 4 Dec 2018 14:58:10 +0100 Subject: [PATCH 42/66] Added coal search to embark-assistant --- plugins/embark-assistant/survey.cpp | 60 +++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index 4071d78b3..f19f3cd4a 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -10,6 +10,7 @@ #include "modules/Materials.h" #include "DataDefs.h" +#include "df/builtin_mats.h" #include "df/coord2d.h" #include "df/creature_interaction_effect.h" #include "df/creature_interaction_effect_display_symbolst.h" @@ -34,6 +35,9 @@ #include "df/interaction_target_materialst.h" #include "df/material_common.h" #include "df/reaction.h" +#include "df/reaction_product.h" +#include "df/reaction_product_itemst.h" +#include "df/reaction_product_type.h" #include "df/region_map_entry.h" #include "df/syndrome.h" #include "df/viewscreen.h" @@ -66,6 +70,7 @@ namespace embark_assist { struct states { uint16_t clay_reaction = -1; uint16_t flux_reaction = -1; + std::vector coals; uint16_t x; uint16_t y; uint8_t local_min_x; @@ -104,6 +109,19 @@ namespace embark_assist { out.printerr("The reaction 'PIG_IRON_MAKING' was not found, so flux can't be identified.\n"); } + for (uint16_t i = 0; i < world->raws.inorganics.size(); i++) { + for (uint16_t k = 0; k < world->raws.inorganics[i]->economic_uses.size(); k++) { + for (uint16_t l = 0; l < world->raws.reactions.reactions[world->raws.inorganics[i]->economic_uses[k]]->products.size(); l++) { + df::reaction_product_itemst *product = static_cast(world->raws.reactions.reactions[world->raws.inorganics[i]->economic_uses[k]]->products[l]); + + if (product->mat_type == df::builtin_mats::COAL) { + state->coals.push_back(i); + break; + } + } + } + } + for (uint16_t i = 0; i < world_data->geo_biomes.size(); i++) { geo_summary->at(i).possible_metals.resize(state->max_inorganic); geo_summary->at(i).possible_economics.resize(state->max_inorganic); @@ -154,6 +172,13 @@ namespace embark_assist { } } + for (uint16_t l = 0; l < state->coals.size(); l++) { + if (layer->mat_index == state->coals[l]) { + geo_summary->at(i).coal_absent = false; + break; + } + } + size = (uint16_t)layer->vein_mat.size(); for (uint16_t l = 0; l < size; l++) { @@ -176,6 +201,14 @@ namespace embark_assist { geo_summary->at(i).flux_absent = false; } } + + for (uint16_t m = 0; m < state->coals.size(); m++) { + if (vein== state->coals[m]) { + geo_summary->at(i).coal_absent = false; + break; + } + } + } } @@ -531,6 +564,7 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat results.clay_count = 0; results.sand_count = 0; results.flux_count = 0; + results.coal_count = 0; results.min_region_soil = 10; results.max_region_soil = 0; results.waterfall = false; @@ -576,6 +610,7 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat 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++; + if (!geo_summary->at(geo_index).coal_absent) results.coal_count++; if (geo_summary->at(geo_index).soil_size < results.min_region_soil) results.min_region_soil = geo_summary->at(geo_index).soil_size; @@ -614,6 +649,8 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat 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; + if (results.coal_count == offset_count) results.coal_count = 256; + for (uint8_t l = 0; l < 3; l++) { if (results.savagery_count[l] == offset_count) results.savagery_count[l] = 256; if (results.evilness_count[l] == offset_count) results.evilness_count[l] = 256; @@ -776,6 +813,8 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data mlt->at(i).at(k).clay = false; mlt->at(i).at(k).sand = false; mlt->at(i).at(k).flux = false; + mlt->at(i).at(k).coal = false; + if (max_soil_depth == 0) { mlt->at(i).at(k).soil_depth = 0; } @@ -869,6 +908,13 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data mlt->at(i).at(k).flux = true; } } + + for (uint16_t m = 0; m < state->coals.size(); m++) { + if (layer->mat_index == state->coals [m]) { + mlt->at(i).at(k).coal = true; + break; + } + } } end_check_m = static_cast(layer->vein_mat.size()); @@ -895,6 +941,13 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data mlt->at(i).at(k).flux = true; } } + + for (uint16_t n = 0; n < state->coals.size(); n++) { + if (layer->vein_mat [m] == state->coals[n]) { + mlt->at(i).at(k).coal = true; + break; + } + } } } @@ -911,6 +964,7 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data 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; + survey_results->at(x).at(y).coal_count = 0; survey_results->at(x).at(y).min_region_soil = 10; survey_results->at(x).at(y).max_region_soil = 0; survey_results->at(x).at(y).savagery_count[0] = 0; @@ -929,6 +983,7 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data 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++; } + if (mlt->at(i).at(k).coal) { survey_results->at(x).at(y).coal_count++; } if (mlt->at(i).at(k).soil_depth < survey_results->at(x).at(y).min_region_soil) { survey_results->at(x).at(y).min_region_soil = mlt->at(i).at(k).soil_depth; @@ -1170,6 +1225,7 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles * site_info->clay = false; site_info->sand = false; site_info->flux = false; + site_info->coal = false; site_info->metals.clear(); site_info->economics.clear(); site_info->metals.clear(); @@ -1222,6 +1278,10 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles * site_info->flux = true; } + if (mlt->at(i).at(k).coal) { + site_info->coal = true; + } + for (uint16_t l = 0; l < state->max_inorganic; l++) { metals[l] = metals[l] || mlt->at(i).at(k).metals[l]; economics[l] = economics[l] || mlt->at(i).at(k).economics[l]; From dfbb2416d9c5f0964202564d6abe0f30a076390f Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 20 Dec 2018 21:29:48 -0500 Subject: [PATCH 43/66] Update docs for nestboxes (#1395) --- docs/Plugins.rst | 8 ++++++++ docs/changelog.txt | 3 +++ 2 files changed, 11 insertions(+) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 62ddf29e6..ee2c56e02 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -1728,6 +1728,14 @@ Subcommands: :import NAME: Imports manager orders from a file named ``dfhack-config/orders/NAME.json``. :clear: Deletes all manager orders in the current embark. +.. _nestboxes: + +nestboxes +========= + +Automatically scan for and forbid fertile eggs incubating in a nestbox. +Toggle status with `enable` or `disable`. + ================ Map modification ================ diff --git a/docs/changelog.txt b/docs/changelog.txt index 901ae8b67..586d21311 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +## New Plugins +- `nestboxes`: automatically scan for and forbid fertile eggs incubating in a nestbox + ## Fixes - `building-hacks`: fixed error when dealing with custom animation tables - `devel/test-perlin`: fixed Lua error (``math.pow()``) From 0ccfc8be35aec38313932f6967bc4cd4806c4b60 Mon Sep 17 00:00:00 2001 From: Lethosor Date: Thu, 27 Dec 2018 16:21:24 -0500 Subject: [PATCH 44/66] Fix a few typos in docs and reorganize a bit --- docs/Plugins.rst | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index bbeeb813a..6eb6326bb 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -396,28 +396,24 @@ Otherwise somewhat similar to `gui/quickcmd`. debug ===== -Manager DFHack runtime debug prints. Debug prints are grouped by plugin name, +Manager for DFHack runtime debug prints. Debug prints are grouped by plugin name, category name and print level. Levels are ``trace``, ``debug``, ``info``, ``warning`` and ``error``. -The runtime message printing is controlled using filters. Filters set minimum -visible message to all matching categories. Matching uses regular expression -that allows listing multiple alternative matches or partial name matches. -Persistent filters are stored in ``dfhack-config/runtime-debug.json``. +The runtime message printing is controlled using filters. Filters set the +visible messages of all matching categories. Matching uses regular expression syntax, +which allows listing multiple alternative matches or partial name matches. +This syntax is a C++ version of the ECMA-262 grammar (Javascript regular expressions). +Details of differences can be found at +https://en.cppreference.com/w/cpp/regex/ecmascript +Persistent filters are stored in ``dfhack-config/runtime-debug.json``. Oldest filters are applied first. That means a newer filter can override the older printing level selection. Usage: ``debugfilter [subcommand] [parameters...]`` -Following subcommands are supported. - -Regular expression syntax -------------------------- - -Syntax is C++ version of ECMA-262 grammar (Javascript regular expression). -Deails of differences can be found from -https://en.cppreference.com/w/cpp/regex/ecmascript +The following subcommands are supported: help ---- @@ -466,7 +462,7 @@ Level is the minimum debug printing level to show in log. * ``info``: Important state changes that happen rarely during normal execution -* ``warining``: Enabled by default. Shows warnings about unexpected events which code managed to handle correctly. +* ``warning``: Enabled by default. Shows warnings about unexpected events which code managed to handle correctly. * ``error``: Enabled by default. Shows errors which code can't handle without user intervention. From 32edeffc3fec6a04ec45b75fe9540e4c4970c576 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 27 Dec 2018 16:31:57 -0500 Subject: [PATCH 45/66] Remove unused find_package(Threads) --- plugins/CMakeLists.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6eb4ae6ff..284922cc1 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,7 +1,5 @@ INCLUDE(Plugins.cmake) -find_package(Threads) - OPTION(BUILD_STONESENSE "Build stonesense (needs a checkout first)." OFF) if(BUILD_STONESENSE) add_subdirectory (stonesense) From e74946f62e7a0f7509a4c4a76bc9a3b7ca79cc79 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 27 Dec 2018 17:37:13 -0500 Subject: [PATCH 46/66] Update xml and related changelog entries --- docs/changelog.txt | 37 ++++++++++++++++++++++++++++++++----- library/xml | 2 +- 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 380636198..d6853be31 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -45,12 +45,14 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `building-hacks`: fixed error when dealing with custom animation tables - `devel/test-perlin`: fixed Lua error (``math.pow()``) - `embark-assistant`: fixed crash when entering finder with a 16x16 embark selected, and added 16 to dimension choices +- `embark-skills`: fixed missing ``skill_points_remaining`` field - `labormanager`: - stopped assigning labors to ineligible dwarves, pets, etc. - stopped assigning invalid labors - added support for crafting jobs that use pearl - `prospector`: (also affected `embark-tools`) - fixed a crash when prospecting an unusable site (ocean, mountains, etc.) with a large default embark size in d_init.txt (e.g. 16x16) - `siege-engine`: fixed a few Lua errors (``math.pow()``, ``unit.relationship_ids``) +- `tweak`: fixed ``hotkey-clear`` ## Misc Improvements - `devel/export-dt-ini`: added viewscreen offsets for DT 40.1.2 @@ -72,14 +74,39 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``utils``: new ``OrderedTable`` class ## Structures -- ``musical_form``: named voices field -- ``musical_form_instruments``: named minimum_required and maximum_permitted -- ``dance_form``: named musical_form_id and musical_written_content_id -- ``written_content``: named poetic_form +- Win32: added missing vtables for ``viewscreen_storesst`` and ``squad_order_rescue_hfst`` - ``activity_event_performancest``: renamed poem as written_content_id -- ``incident_sub6_performance``: made performance_event an enum +- ``dance_form``: named musical_form_id and musical_written_content_id - ``incident_sub6_performance.participants``: named performance_event and role_index +- ``incident_sub6_performance``: made performance_event an enum - ``incident_sub6_performance``: named poetic_form_id, musical_form_id, and dance_form_id +- ``musical_form_instruments``: named minimum_required and maximum_permitted +- ``musical_form``: named voices field +- ``poetic_form``: identified many fields and related enum/bitfield types +- ``setup_character_info``: identified ``skill_points_remaining`` (for `embark-skills`) +- ``unit_thought_type``: added new expulsion thoughts from 0.44.12 +- ``viewscreen_layer_militaryst``: identified ``equip.assigned.assigned_items`` +- ``world_data``: added ``mountain_peak_flags`` type, including ``is_volcano`` +- ``written_content``: named poetic_form +- ``unit_action.attack``: identified ``attack_skill`` +- ``unit_action.attack``: added ``lightly_tap`` and ``spar_report`` flags +- ``misc_trait_type``: removed ``LikesOutdoors``, ``Hardened``, ``TimeSinceBreak``, ``OnBreak`` (all unused by DF) +- ``unit_personality``: identified ``stress_drain``, ``stress_boost``, ``likes_outdoors``, ``combat_hardened`` +- ``plant_tree_tile``: gave connection bits more meaningful names (e.g. ``connection_east`` instead of ``thick_branches_1``) +- ``plant_tree_info``: identified ``extent_east``, etc. +- ``ui``: fixed alignment of ``main`` and ``squads`` (fixes `tweak` hotkey-clear and DF-AI) +- ``ui.main``: identified ``fortress_site`` +- ``ui.squads``: identified ``kill_rect_targets_scroll`` +- ``world_site``: identified names and/or types of some fields +- ``world_history``: identified names and/or types of some fields +- ``viewscreen_setupadventurest``: identified some nemesis and personality fields, and ``page.ChooseHistfig`` +- ``unit_storage_status``: newly identified type, stores noble holdings information (used in ``viewscreen_layer_noblelistst``) +- ``viewscreen_layer_noblelistst``: identified ``storage_status`` (see ``unit_storage_status`` type) +- ``viewscreen_layer_arena_creaturest``: identified item- and name-related fields +- ``viewscreen_new_regionst``: identified ``rejection_msg``, ``raw_folder``, ``load_world_params`` +- ``viewscreen_new_regionst``: changed many ``int8_t`` fields to ``bool`` +- ``unit_flags3``: identified ``marked_for_gelding`` +- ``body_part_status``: identified ``gelded`` ## API - New debug features related to `debug` plugin: diff --git a/library/xml b/library/xml index fa6e818b3..44215836d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit fa6e818b34235b9fe10abc37d06eba27a932c870 +Subproject commit 44215836d5b57c3722b126aaf481f652385f3a23 From f8dd215012534a8d6527bc01772f2365a4048fdf Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 27 Dec 2018 17:57:55 -0500 Subject: [PATCH 47/66] Update scripts and related changelog entries --- docs/changelog.txt | 29 ++++++++++++++++++++++++++++- scripts | 2 +- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index d6853be31..d99eabb7f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -41,11 +41,25 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `debug`: manages runtime debug print category filtering - `nestboxes`: automatically scan for and forbid fertile eggs incubating in a nestbox +## New Scripts +- `devel/query`: searches for field names in DF objects +- `extinguish`: puts out fires +- `tame`: sets tamed/trained status of animals + ## Fixes - `building-hacks`: fixed error when dealing with custom animation tables - `devel/test-perlin`: fixed Lua error (``math.pow()``) - `embark-assistant`: fixed crash when entering finder with a 16x16 embark selected, and added 16 to dimension choices - `embark-skills`: fixed missing ``skill_points_remaining`` field +- `full-heal`: + - stopped wagon resurrection + - fixed a minor issue with post-resurrection hostility +- `gui/companion-order`: + - fixed issues with printing coordinates + - fixed issues with move command + - fixed cheat commands (and removed "Power up", which was broken) +- `gui/gm-editor`: fixed reinterpret cast (``r``) +- `gui/pathable`: fixed error when sidebar is hidden with ``Tab`` - `labormanager`: - stopped assigning labors to ineligible dwarves, pets, etc. - stopped assigning invalid labors @@ -55,12 +69,25 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `tweak`: fixed ``hotkey-clear`` ## Misc Improvements -- `devel/export-dt-ini`: added viewscreen offsets for DT 40.1.2 +- `armoks-blessing`: improved documentation to list all available arguments +- `devel/export-dt-ini`: + - added viewscreen offsets for DT 40.1.2 + - added item base flags offset + - added needs offsets - `embark-assistant`: - added match indicator display on the right ("World") map - changed 'c'ancel to abort find if it's under way and clear results if not, allowing use of partial surveys. - added Coal as a search criterion, as well as a coal indication as current embark selection info. +- `full-heal`: + - added ``-all``, ``-all_civ`` and ``-all_citizens`` arguments + - added module support + - now removes historical figure death dates and ghost data +- `growcrops`: added ``all`` argument to grow all crops +- `gui/load-screen`: improved documentation - `labormanager`: now takes nature value into account when assigning jobs +- `open-legends`: added warning about risk of save corruption and improved related documentation +- `points`: added support when in ``viewscreen_setupdwarfgamest`` and improved error messages +- `siren`: removed break handling (relevant ``misc_trait_type`` was no longer used - see "Structures" section) ## Internals - Linux/macOS: changed recommended build backend from Make to Ninja (Make builds will be significantly slower now) diff --git a/scripts b/scripts index 7deb13c68..a242a7cd8 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 7deb13c681b011d9a87782b9cdf9eec6e6acf7c1 +Subproject commit a242a7cd81ae7046146f86c3103360449e1d9ca8 From 9fe24e1b3fe01fcbb15e982967464971b3921b85 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 27 Dec 2018 19:39:43 -0500 Subject: [PATCH 48/66] Update changelog and bump version to r2 --- CMakeLists.txt | 2 +- docs/Authors.rst | 2 ++ docs/changelog.txt | 7 ++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e05e5cf2b..689618637 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -168,7 +168,7 @@ endif() # set up versioning. set(DF_VERSION "0.44.12") -set(DFHACK_RELEASE "r1") +set(DFHACK_RELEASE "r2") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") diff --git a/docs/Authors.rst b/docs/Authors.rst index 7233e558a..ba3a00b35 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -23,6 +23,7 @@ Bearskie Bearskie belal jimhester Ben Lubar BenLubar Ben Rosser TC01 +billw2012 billw2012 brndd brndd burneddi Bumber Bumber64 Caldfir caldfir @@ -68,6 +69,7 @@ Kromtec Kromtec Kurik Amudnil Lethosor lethosor Mason11987 Mason11987 +Matt Regul mattregul Matthew Cline Matthew Lindner mlindner Max maxthyme Max^TM diff --git a/docs/changelog.txt b/docs/changelog.txt index d99eabb7f..4db4072f0 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +# 0.44.12-r2 + ## New Plugins - `debug`: manages runtime debug print category filtering - `nestboxes`: automatically scan for and forbid fertile eggs incubating in a nestbox @@ -64,6 +66,8 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - stopped assigning labors to ineligible dwarves, pets, etc. - stopped assigning invalid labors - added support for crafting jobs that use pearl + - fixed issues causing cleaning jobs to not be assigned + - added support for disabling management of specific labors - `prospector`: (also affected `embark-tools`) - fixed a crash when prospecting an unusable site (ocean, mountains, etc.) with a large default embark size in d_init.txt (e.g. 16x16) - `siege-engine`: fixed a few Lua errors (``math.pow()``, ``unit.relationship_ids``) - `tweak`: fixed ``hotkey-clear`` @@ -93,9 +97,10 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - Linux/macOS: changed recommended build backend from Make to Ninja (Make builds will be significantly slower now) - Added a usable unit test framework for basic tests, and a few basic tests - Core: various thread safety and memory management improvements -- Fixed cmake build dependencies for generated header files +- Fixed CMake build dependencies for generated header files - Fixed custom ``CMAKE_CXX_FLAGS`` not being passed to plugins - Changed ``plugins/CMakeLists.custom.txt`` to be ignored by git and created (if needed) at build time instead +- Added ``CMakeSettings.json`` with intellisense support ## Lua - ``utils``: new ``OrderedTable`` class From 315852a2513679ca1afe0d53b34974cefd946f89 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 27 Dec 2018 19:46:36 -0500 Subject: [PATCH 49/66] labormanager: fix -Wreorder warning --- plugins/labormanager/labormanager.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index d779e036a..e416dcf63 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -535,7 +535,8 @@ struct dwarf_info_t df::unit_labor using_labor; dwarf_info_t(df::unit* dw) : dwarf(dw), state(OTHER), - clear_all(false), high_skill(0), has_children(false), armed(false), using_labor(df::unit_labor::NONE), unmanaged_labors_assigned(0) + clear_all(false), high_skill(0), has_children(false), armed(false), + unmanaged_labors_assigned(0), using_labor(df::unit_labor::NONE) { for (int e = TOOL_NONE; e < TOOLS_MAX; e++) has_tool[e] = false; @@ -2233,7 +2234,7 @@ void print_labor(df::unit_labor labor, color_ostream &out) else { out << "priority " << labor_info.priority(); - + if (labor_info.maximum_dwarfs() == MAX_DWARFS_NONE) out << ", no maximum"; else From af0d569afd9ad29a34631360e9968624695e34b3 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 29 Dec 2018 00:15:45 -0500 Subject: [PATCH 50/66] check-rpc: add -> append --- travis/check-rpc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/travis/check-rpc.py b/travis/check-rpc.py index 7b771ee67..ecddafa9f 100644 --- a/travis/check-rpc.py +++ b/travis/check-rpc.py @@ -67,9 +67,9 @@ for plugin_name in actual: io = methods[m] if m in expected[plugin_name]: if expected[plugin_name][m] != io: - wrong.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + wrong.append('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) else: - missing.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + missing.append('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) if len(missing) > 0: print('Incomplete documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Add the following lines:') @@ -98,7 +98,7 @@ for plugin_name in expected: for m in methods: io = methods[m] if m not in actual[plugin_name]: - missing.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + missing.append('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) if len(missing) > 0: print('Incorrect documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Remove the following lines:') From b08ccd001ebe0edd8a2beea73be41f270a35ec7e Mon Sep 17 00:00:00 2001 From: lethosor Date: Sun, 30 Dec 2018 17:47:10 -0500 Subject: [PATCH 51/66] travis: Always clear DF folder --- .travis.yml | 6 ++---- travis/download-df.sh | 27 ++++++++++++++++----------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index c90276540..98534a11a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ matrix: - g++-4.8 before_install: - export DF_VERSION=$(sh travis/get-df-version.sh) -- export DF_FOLDER="$HOME/DF-travis/$DF_VERSION" +- 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 @@ -56,9 +56,7 @@ script: - python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt" before_cache: - cat "$DF_FOLDER/stderr.log" -- rm -rf "$DF_FOLDER/hack" -- rm -rf "$DF_FOLDER/dfhack-config" -- rm -f "$DF_FOLDER"/*.log +- rm -rf "$DF_FOLDER" notifications: email: false irc: diff --git a/travis/download-df.sh b/travis/download-df.sh index 20dc3dbd4..44426de54 100755 --- a/travis/download-df.sh +++ b/travis/download-df.sh @@ -15,26 +15,31 @@ cd "$DF_FOLDER" if [ -f receipt ]; then if [ "$selfmd5" != "$(cat receipt)" ]; then echo "download-df.sh changed; removing DF" + rm receipt else echo "Already downloaded $DF_VERSION" - exit 0 fi fi -rm -rif "$tardest" df_linux +if [ ! -f receipt ]; then + rm -f "$tardest" + minor=$(echo "$DF_VERSION" | cut -d. -f2) + patch=$(echo "$DF_VERSION" | cut -d. -f3) + url="http://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2" + echo Downloading + wget "$url" -O "$tardest" +fi -minor=$(echo "$DF_VERSION" | cut -d. -f2) -patch=$(echo "$DF_VERSION" | cut -d. -f3) -url="http://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2" +rm -rf df_linux +mkdir df_linux -echo Downloading -wget "$url" -O "$tardest" echo Extracting -tar xf "$tardest" --strip-components=1 +tar xf "$tardest" --strip-components=1 -C df_linux echo Changing settings -echo '' >> "$DF_FOLDER/data/init/init.txt" -echo '[PRINT_MODE:TEXT]' >> "$DF_FOLDER/data/init/init.txt" -echo '[SOUND:NO]' >> "$DF_FOLDER/data/init/init.txt" +echo '' >> "$DF_FOLDER/df_linux/data/init/init.txt" +echo '[PRINT_MODE:TEXT]' >> "$DF_FOLDER/df_linux/data/init/init.txt" +echo '[SOUND:NO]' >> "$DF_FOLDER/df_linux/data/init/init.txt" echo Done echo "$selfmd5" > receipt +ls From 8063717503a4d97c3b8bf90e543dc99b3122c8a5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 2 Jan 2019 17:05:28 -0500 Subject: [PATCH 52/66] get-df-version: make filter more strict --- travis/get-df-version.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/travis/get-df-version.sh b/travis/get-df-version.sh index 13d317d2e..24386252d 100755 --- a/travis/get-df-version.sh +++ b/travis/get-df-version.sh @@ -1,4 +1,4 @@ #!/bin/sh cd "$(dirname "$0")" cd .. -grep DF_VERSION CMakeLists.txt | perl -ne 'print "$&\n" if /[\d\.]+/' +grep -i 'set(DF_VERSION' CMakeLists.txt | perl -ne 'print "$&\n" if /[\d\.]+/' From 13dfa130d72a7370d0f1e0c17f9abf3a794c54cc Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 2 Jan 2019 17:34:08 -0500 Subject: [PATCH 53/66] Add more diagnostics to run-tests.py --- travis/run-tests.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/travis/run-tests.py b/travis/run-tests.py index ae48074b5..a66320f37 100644 --- a/travis/run-tests.py +++ b/travis/run-tests.py @@ -14,6 +14,9 @@ os.chdir(sys.argv[1]) if os.path.exists(test_stage): os.remove(test_stage) +print(os.getcwd()) +print(os.listdir('.')) + tries = 0 while True: tries += 1 @@ -33,3 +36,5 @@ while True: process = subprocess.Popen([dfhack], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) process.communicate() + if process.returncode != 0: + print('DF exited with ' + repr(process.returncode)) From 966389703796d497894288370d94b1533a869e3b Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 2 Jan 2019 18:40:34 -0500 Subject: [PATCH 54/66] Fix inconsistency resulting in nested df_linux folders --- travis/download-df.sh | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/travis/download-df.sh b/travis/download-df.sh index 44426de54..cedb843b9 100755 --- a/travis/download-df.sh +++ b/travis/download-df.sh @@ -11,6 +11,8 @@ echo "DF_VERSION: $DF_VERSION" echo "DF_FOLDER: $DF_FOLDER" mkdir -p "$DF_FOLDER" cd "$DF_FOLDER" +# back out of df_linux +cd .. if [ -f receipt ]; then if [ "$selfmd5" != "$(cat receipt)" ]; then @@ -36,9 +38,9 @@ mkdir df_linux echo Extracting tar xf "$tardest" --strip-components=1 -C df_linux echo Changing settings -echo '' >> "$DF_FOLDER/df_linux/data/init/init.txt" -echo '[PRINT_MODE:TEXT]' >> "$DF_FOLDER/df_linux/data/init/init.txt" -echo '[SOUND:NO]' >> "$DF_FOLDER/df_linux/data/init/init.txt" +echo '' >> "df_linux/data/init/init.txt" +echo '[PRINT_MODE:TEXT]' >> "df_linux/data/init/init.txt" +echo '[SOUND:NO]' >> "df_linux/data/init/init.txt" echo Done echo "$selfmd5" > receipt From 703e1b8a0cc700ec45fe638582db20dd55338660 Mon Sep 17 00:00:00 2001 From: lethosor Date: Wed, 2 Jan 2019 19:15:19 -0500 Subject: [PATCH 55/66] Consolidate cd commands --- travis/download-df.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/travis/download-df.sh b/travis/download-df.sh index cedb843b9..4eae6f9b9 100755 --- a/travis/download-df.sh +++ b/travis/download-df.sh @@ -10,9 +10,8 @@ cd "$(dirname "$0")" echo "DF_VERSION: $DF_VERSION" echo "DF_FOLDER: $DF_FOLDER" mkdir -p "$DF_FOLDER" -cd "$DF_FOLDER" # back out of df_linux -cd .. +cd "$DF_FOLDER/.." if [ -f receipt ]; then if [ "$selfmd5" != "$(cat receipt)" ]; then From 17d60c5a1ffdfee32babbd299eae4932374a48d5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Fri, 4 Jan 2019 10:51:54 -0500 Subject: [PATCH 56/66] Mention custom profession folder and clean up docs a bit --- docs/Plugins.rst | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 5fc30fa65..4d122bb51 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -561,16 +561,19 @@ directly to the main dwarf mode screen. Professions ----------- -The manipulator plugin supports saving Professions: a named set of Labors labors that can be -quickly applied to one or multiple Dwarves. +The manipulator plugin supports saving professions: a named set of labors that can be +quickly applied to one or multiple dwarves. -To save a Profession highlight a Dwarf and press :kbd:`P`. The Profession will be saved using -the Custom Profession Name of the Dwarf, or the default for that Dwarf if no Custom Profession -Name has been set. +To save a profession, highlight a dwarf and press :kbd:`P`. The profession will be saved using +the custom profession name of the dwarf, or the default for that dwarf if no custom profession +name has been set. -To apply a Profession either highlight a single Dwarf, or select multiple with :kbd:`x`, and press -:kbd:`p` to select the Profession to apply. All labors for the selected Dwarves will be reset to -the labors of the chosen Profession. +To apply a profession, either highlight a single dwarf or select multiple with +:kbd:`x`, and press :kbd:`p` to select the profession to apply. All labors for +the selected dwarves will be reset to the labors of the chosen profession. + +Professions are saved as human-readable text files in the "professions" folder +within the DF folder, and can be edited or deleted there. .. comment - the link target "search" is reserved for the Sphinx search page .. _search-plugin: From 6c266075de0bcf53d3bb7fb3c7582acf9c684da0 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 15 Jan 2019 21:06:49 -0500 Subject: [PATCH 57/66] Console-posix: fix crash with prompts longer than screen width Also add an extra fallback check around substr Fixes #1425 --- library/Console-posix.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/library/Console-posix.cpp b/library/Console-posix.cpp index da786aaac..696672861 100644 --- a/library/Console-posix.cpp +++ b/library/Console-posix.cpp @@ -480,7 +480,7 @@ namespace DFHack { char seq[64]; int cols = get_columns(); - int plen = prompt.size(); + int plen = prompt.size() % cols; int len = raw_buffer.size(); int begin = 0; int cooked_cursor = raw_cursor; @@ -493,7 +493,15 @@ namespace DFHack } if (plen+len > cols) len -= plen+len - cols; - std::string mbstr = toLocaleMB(raw_buffer.substr(begin,len)); + std::string mbstr; + try { + mbstr = toLocaleMB(raw_buffer.substr(begin,len)); + } + catch (std::out_of_range&) { + // fallback check in case begin is still out of range + // (this behaves badly but at least doesn't crash) + mbstr = toLocaleMB(raw_buffer); + } /* Cursor to left edge */ snprintf(seq,64,"\x1b[1G"); if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; From 3a9fea9c52b70997ed2a1185759cf2b9819d507d Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 28 Mar 2019 11:09:46 -0400 Subject: [PATCH 58/66] Update stonesense --- docs/Authors.rst | 1 + docs/changelog.txt | 3 +++ plugins/stonesense | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/Authors.rst b/docs/Authors.rst index ba3a00b35..963d65fde 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -51,6 +51,7 @@ gchristopher gchristopher grubsteak grubsteak Harlan Playford playfordh Hayati Ayguen hayguen +Herwig Hochleitner bendlas IndigoFenix James Logsdon jlogsdon Japa JapaMala diff --git a/docs/changelog.txt b/docs/changelog.txt index 4db4072f0..17c766f75 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,9 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # Future +## Internals +- Fixed some OpenGL build issues with `stonesense` + # 0.44.12-r2 ## New Plugins diff --git a/plugins/stonesense b/plugins/stonesense index 4a0f63e04..c5333b176 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 4a0f63e044d02532c948d67780dfd128fbd6d043 +Subproject commit c5333b1762328be83b9f21f5956c06ffc15d426b From c11f2b5ffa7434afca44e9ad44ea61d2f1aec37c Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 28 Mar 2019 14:01:20 -0400 Subject: [PATCH 59/66] Update stonesense (fix non-Linux builds) --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index c5333b176..6b4df3995 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit c5333b1762328be83b9f21f5956c06ffc15d426b +Subproject commit 6b4df3995f53e0579fc590a78d68e54fc2ca2b81 From 9c403f509dbe6bfefca7a03c5734d424215054dc Mon Sep 17 00:00:00 2001 From: Josh Cooper Date: Mon, 15 Apr 2019 16:21:54 -0700 Subject: [PATCH 60/66] [Release] cxxrandom v2.0.1 --- plugins/lua/cxxrandom.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/cxxrandom.lua b/plugins/lua/cxxrandom.lua index 1521c4c9e..4d2d59da9 100644 --- a/plugins/lua/cxxrandom.lua +++ b/plugins/lua/cxxrandom.lua @@ -188,7 +188,7 @@ function num_sequence:new(a,b) else error("Invalid arguments - a: " .. tostring(a) .. " and b: " .. tostring(b)) end - print("seqID:"..o.seqID) + --print("seqID:"..o.seqID) setmetatable(o,self) return o end From e5eade1ad7a0acc924e4e7390986dd5fb99f8c94 Mon Sep 17 00:00:00 2001 From: lethosor Date: Tue, 30 Apr 2019 16:53:09 -0400 Subject: [PATCH 61/66] Make gui.dwarfmode.{get_movement_delta,get_hotkey_target} public --- library/lua/gui/dwarfmode.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index fa2d41410..886e0c4df 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -226,7 +226,7 @@ MOVEMENT_KEYS = { CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, } -local function get_movement_delta(key, delta, big_step) +function get_movement_delta(key, delta, big_step) local info = MOVEMENT_KEYS[key] if info then if info[4] then @@ -243,7 +243,7 @@ for i,v in ipairs(df.global.ui.main.hotkeys) do HOTKEY_KEYS['D_HOTKEY'..(i+1)] = v end -local function get_hotkey_target(key) +function get_hotkey_target(key) local hk = HOTKEY_KEYS[key] if hk and hk.cmd == df.ui_hotkey.T_cmd.Zoom then return xyz2pos(hk.x, hk.y, hk.z) From 32a0ab967946aa4a15f80470bd91c87b45de4789 Mon Sep 17 00:00:00 2001 From: lethosor Date: Sat, 11 May 2019 23:34:59 -0400 Subject: [PATCH 62/66] Update submodules --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 44215836d..2be1fc4af 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 44215836d5b57c3722b126aaf481f652385f3a23 +Subproject commit 2be1fc4afea4d3345b9b76d0f27f56087ac9b6e0 diff --git a/scripts b/scripts index a242a7cd8..c8394fa75 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a242a7cd81ae7046146f86c3103360449e1d9ca8 +Subproject commit c8394fa75ff20abcdcdbe975dbf157d21882172e From f2bd697d64a13233889b8d0b6aa9cc716e14ab60 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 13 May 2019 19:21:57 -0400 Subject: [PATCH 63/66] mousequery: give explicit feedback when enabling/disabling sub-features The behavior of this plugin is somewhat unintuitive - "mousequery edge" disables the edge-scrolling feature instead of enabling it. This should avoid confusion without breaking compatibility with existing init scripts. --- plugins/mousequery.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index d9bba78b6..5aab0b584 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -836,26 +836,35 @@ static command_result mousequery_cmd(color_ostream &out, vector & param else if (cmd[0] == 'p') { plugin_enabled = (state == "enable"); + out << "mousequery: plugin " << (plugin_enabled ? "enabled" : "disabled") << endl; } else if (cmd[0] == 'r') { rbutton_enabled = (state == "enable"); + out << "mousequery: rbutton " << (rbutton_enabled ? "enabled" : "disabled") << endl; } else if (cmd[0] == 't') { tracking_enabled = (state == "enable"); - if (!tracking_enabled) + if (!tracking_enabled) { + out << "mousequery: edge scrolling disabled" << endl; active_scrolling = false; + } + out << "mousequery: tracking " << (tracking_enabled ? "enabled" : "disabled") << endl; } else if (cmd[0] == 'e') { active_scrolling = (state == "enable"); - if (active_scrolling) + if (active_scrolling) { + out << "mousequery: tracking enabled" << endl; tracking_enabled = true; + } + out << "mousequery: edge scrolling " << (active_scrolling ? "enabled" : "disabled") << endl; } else if (cmd[0] == 'l') { live_view = (state == "enable"); + out << "mousequery: live view " << (live_view ? "enabled" : "disabled") << endl; } else if (cmd == "drag") { From dd9c433f17bbeab7415ad8e7c5c75d24c88ea025 Mon Sep 17 00:00:00 2001 From: lethosor Date: Mon, 13 May 2019 19:38:24 -0400 Subject: [PATCH 64/66] Use BYPRODUCTS to keep ninja builds from re-running codegen every time This partially reverts f02466de8a783be0c5c06f42485a42f5266693aa, but behavior should be the same under MSVC, which that commit attempted to fix. From https://cmake.org/cmake/help/v3.14/command/add_custom_command.html: > The `BYPRODUCTS` option is ignored on non-Ninja generators except to mark > byproducts `GENERATED`. Since `$GENERATED_HDRS` are already marked generated, this change should have no effect on non-Ninja generators. --- library/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index a9255ae00..d64b7429f 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -264,10 +264,14 @@ FILE(GLOB GENERATE_INPUT_SCRIPTS ${dfapi_SOURCE_DIR}/xml/*.pm ${dfapi_SOURCE_DIR FILE(GLOB GENERATE_INPUT_XMLS ${dfapi_SOURCE_DIR}/xml/df.*.xml) set(CODEGEN_OUT ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml) -LIST(APPEND CODEGEN_OUT ${GENERATED_HDRS}) +IF(NOT("${CMAKE_GENERATOR}" STREQUAL Ninja)) + # use BYPRODUCTS instead under Ninja to avoid rebuilds + LIST(APPEND CODEGEN_OUT ${GENERATED_HDRS}) +ENDIF() ADD_CUSTOM_COMMAND( OUTPUT ${CODEGEN_OUT} + BYPRODUCTS ${GENERATED_HDRS} COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/xml/codegen.pl ${CMAKE_CURRENT_SOURCE_DIR}/xml ${CMAKE_CURRENT_SOURCE_DIR}/include/df From c3b06b81c7b672b0c9fb56a19a6b845d082f28e5 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 16 May 2019 23:22:11 -0400 Subject: [PATCH 65/66] mousequery: use map dimensions to determine edge scrolling locations TWBT modifies the map dimensions, so using the window dimensions to handle edge scrolling produces the wrong behavior when using a larger map tileset than text tileset. --- plugins/mousequery.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 5aab0b584..fb6c1e4c5 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -572,12 +572,11 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest static decltype(enabler->clock) last_t = 0; auto dims = Gui::getDwarfmodeViewDims(); - auto right_margin = (dims.menu_x1 > 0) ? dims.menu_x1 : gps->dimx; int32_t mx, my; auto mpos = get_mouse_pos(mx, my); bool mpos_valid = mpos.x != -30000 && mpos.y != -30000 && mpos.z != -30000; - if (mx < 1 || mx > right_margin - 2 || my < 1 || my > gps->dimy - 2) + if (mx < 1 || mx > dims.map_x2 || my < 1 || my > dims.map_y2) mpos_valid = false; // Check if in lever binding mode @@ -683,7 +682,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest return; } - if (mx > right_margin - scroll_buffer) + if (mx > dims.map_x2 - scroll_buffer) { sendKey(interface_key::CURSOR_RIGHT); return; @@ -695,7 +694,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest return; } - if (my > gps->dimy - scroll_buffer) + if (my > dims.map_y2 - scroll_buffer) { sendKey(interface_key::CURSOR_DOWN); return; From 92717a7f71335b4b17c54bab034a3da861957b94 Mon Sep 17 00:00:00 2001 From: lethosor Date: Thu, 16 May 2019 23:30:03 -0400 Subject: [PATCH 66/66] mousequery: Fix some more instances of map boundary checks --- plugins/mousequery.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index fb6c1e4c5..618252ad3 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -369,8 +369,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest // Can't check limits earlier as we must be sure we are in query or default mode // (so we can clear the button down flag) auto dims = Gui::getDwarfmodeViewDims(); - int right_bound = (dims.menu_x1 > 0) ? dims.menu_x1 - 2 : gps->dimx - 2; - if (mx < 1 || mx > right_bound || my < 1 || my > gps->dimy - 2) + if (mx < 1 || mx > dims.map_x2 || my < 1 || my > dims.map_y2) return false; if (ui->main.mode == df::ui_sidebar_mode::Zones || @@ -435,13 +434,13 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest if (mx < scroll_trigger_x) sendKey(interface_key::CURSOR_LEFT_FAST); - if (mx > ((dims.menu_x1 > 0) ? dims.menu_x1 : gps->dimx) - scroll_trigger_x) + if (mx > dims.map_x2 - scroll_trigger_x) sendKey(interface_key::CURSOR_RIGHT_FAST); if (my < scroll_trigger_y) sendKey(interface_key::CURSOR_UP_FAST); - if (my > gps->dimy - scroll_trigger_y) + if (my > dims.map_y2 - scroll_trigger_y) sendKey(interface_key::CURSOR_DOWN_FAST); } @@ -731,9 +730,9 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest if (shouldTrack()) { if (delta_t <= scroll_delay && (mx < scroll_buffer || - mx > dims.menu_x1 - scroll_buffer || + mx > dims.map_x2 - scroll_buffer || my < scroll_buffer || - my > gps->dimy - scroll_buffer)) + my > dims.map_y2 - scroll_buffer)) { return; }