Merge branch 'develop' into cmake-cleanup
Conflicts: library/CMakeLists.txt plugins/CMakeLists.txtdevelop
						commit
						ac5a54c8db
					
				| @ -0,0 +1,232 @@ | ||||
| /*
 | ||||
| https://github.com/DFHack/dfhack
 | ||||
| Copyright (c) 2009-2018 DFHack Team | ||||
| 
 | ||||
| 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 | ||||
| #ifndef CL_MOD_PERSISTENCE | ||||
| #define CL_MOD_PERSISTENCE | ||||
| 
 | ||||
| /**
 | ||||
|  * \defgroup grp_persistence Persistence: code related to saving and loading data | ||||
|  * @ingroup grp_modules | ||||
|  */ | ||||
| 
 | ||||
| #include <fstream> | ||||
| #include <memory> | ||||
| #include <string> | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "Export.h" | ||||
| #include "Error.h" | ||||
| 
 | ||||
| namespace DFHack | ||||
| { | ||||
|     class Core; | ||||
| 
 | ||||
|     namespace Persistence | ||||
|     { | ||||
|         struct LegacyData; | ||||
|         class Internal; | ||||
|     } | ||||
| 
 | ||||
|     class DFHACK_EXPORT PersistentDataItem { | ||||
|         size_t index; | ||||
|         std::shared_ptr<Persistence::LegacyData> data; | ||||
| 
 | ||||
|     public: | ||||
|         static const int NumInts = 7; | ||||
| 
 | ||||
|         bool isValid() const; | ||||
|         size_t get_index() const | ||||
|         { | ||||
|             CHECK_INVALID_ARGUMENT(isValid()); | ||||
|             return index; | ||||
|         } | ||||
|         int entry_id() const { return isValid() ? int(index) + 100 : 0; } | ||||
| 
 | ||||
|         int raw_id() const { return isValid() ? -int(index) - 100 : 0; } | ||||
| 
 | ||||
|         const std::string &key() const; | ||||
| 
 | ||||
|         std::string &val(); | ||||
|         const std::string &val() const; | ||||
|         int &ival(int i); | ||||
|         int ival(int i) const; | ||||
| 
 | ||||
|         // Pack binary data into string field.
 | ||||
|         // Since DF serialization chokes on NUL bytes,
 | ||||
|         // use bit magic to ensure none of the bytes is 0.
 | ||||
|         // Choose the lowest bit for padding so that
 | ||||
|         // sign-extend can be used normally.
 | ||||
| 
 | ||||
|         size_t data_size() const | ||||
|         { | ||||
|             return val().size(); | ||||
|         } | ||||
| 
 | ||||
|         bool check_data(size_t off, size_t sz = 1) const | ||||
|         { | ||||
|             return (data_size() >= off + sz); | ||||
|         } | ||||
|         void ensure_data(size_t off, size_t sz = 0) | ||||
|         { | ||||
|             if (data_size() < off + sz) | ||||
|             { | ||||
|                 val().resize(off + sz, '\x01'); | ||||
|             } | ||||
|         } | ||||
|         template<size_t N> | ||||
|         uint8_t (&pdata(size_t off))[N] | ||||
|         { | ||||
|             ensure_data(off, N); | ||||
|             typedef uint8_t array[N]; | ||||
|             return *(array *)(val().data() + off); | ||||
|         } | ||||
|         template<size_t N> | ||||
|         const uint8_t (&pdata(size_t off) const)[N] | ||||
|         { | ||||
|             CHECK_INVALID_ARGUMENT(check_data(off, N)); | ||||
|             typedef const uint8_t array[N]; | ||||
|             return *(array *)(val().data() + off); | ||||
|         } | ||||
|         template<size_t N> | ||||
|         const uint8_t (&cpdata(size_t off) const)[N] | ||||
|         { | ||||
|             return pdata<N>(off); | ||||
|         } | ||||
| 
 | ||||
|         static const size_t int7_size = 1; | ||||
|         uint8_t get_uint7(size_t off) const | ||||
|         { | ||||
|             auto p = pdata<int7_size>(off); | ||||
|             return p[0] >> 1; | ||||
|         } | ||||
|         int8_t get_int7(size_t off) const | ||||
|         { | ||||
|             return int8_t(get_uint7(off)); | ||||
|         } | ||||
|         void set_uint7(size_t off, uint8_t val) | ||||
|         { | ||||
|             auto p = pdata<int7_size>(off); | ||||
|             p[0] = uint8_t((val << 1) | 1); | ||||
|         } | ||||
|         void set_int7(size_t off, int8_t val) | ||||
|         { | ||||
|             set_uint7(off, uint8_t(val)); | ||||
|         } | ||||
| 
 | ||||
|         static const size_t int28_size = 4; | ||||
|         uint32_t get_uint28(size_t off) const | ||||
|         { | ||||
|             auto p = pdata<int28_size>(off); | ||||
|             return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((p[3]&~1U)<<20); | ||||
|         } | ||||
|         int32_t get_int28(size_t off) const | ||||
|         { | ||||
|             return int32_t(get_uint28(off)); | ||||
|         } | ||||
|         void set_uint28(size_t off, uint32_t val) | ||||
|         { | ||||
|             auto p = pdata<int28_size>(off); | ||||
|             p[0] = uint8_t((val<<1) | 1); | ||||
|             p[1] = uint8_t((val>>6) | 1); | ||||
|             p[2] = uint8_t((val>>13) | 1); | ||||
|             p[3] = uint8_t((val>>20) | 1); | ||||
|         } | ||||
|         void set_int28(size_t off, int32_t val) | ||||
|         { | ||||
|             set_uint28(off, val); | ||||
|         } | ||||
| 
 | ||||
|         PersistentDataItem() : index(0), data(nullptr) {} | ||||
|         PersistentDataItem(size_t index, const std::shared_ptr<Persistence::LegacyData> &data) | ||||
|             : index(index), data(data) {} | ||||
|     }; | ||||
|     namespace Persistence | ||||
|     { | ||||
|         class Internal | ||||
|         { | ||||
|             static void clear(); | ||||
|             static void save(); | ||||
|             static void load(); | ||||
|             friend class ::DFHack::Core; | ||||
|         }; | ||||
| 
 | ||||
|         // Returns a new PersistentDataItem with the specified key.
 | ||||
|         // If there is no world loaded or the key is empty, returns an invalid item.
 | ||||
|         DFHACK_EXPORT PersistentDataItem addItem(const std::string &key); | ||||
|         // Returns an existing PersistentDataItem with the specified key.
 | ||||
|         // If "added" is not null and there is no such item, a new item is returned and
 | ||||
|         // the bool value is set to true. If "added" is not null and an item is found or
 | ||||
|         // no new item can be created, the bool value is set to false.
 | ||||
|         DFHACK_EXPORT PersistentDataItem getByKey(const std::string &key, bool *added = nullptr); | ||||
|         // Returns an existing PersistentDataItem with the specified index.
 | ||||
|         // If there is no world loaded or the index is empty, returns an invalid item.
 | ||||
|         DFHACK_EXPORT PersistentDataItem getByIndex(size_t index); | ||||
|         // If the item is invalid, returns false. Otherwise, deletes the item and returns
 | ||||
|         // true. All references to the item are invalid as soon as this function returns.
 | ||||
|         DFHACK_EXPORT bool deleteItem(const PersistentDataItem &item); | ||||
|         // Fills the vector with references to each persistent item.
 | ||||
|         DFHACK_EXPORT void getAll(std::vector<PersistentDataItem> &vec); | ||||
|         // Fills the vector with references to each persistent item with a key that is
 | ||||
|         // greater than or equal to "min" and less than "max".
 | ||||
|         DFHACK_EXPORT void getAllByKeyRange(std::vector<PersistentDataItem> &vec, const std::string &min, const std::string &max); | ||||
|         // Fills the vector with references to each persistent item with a key that is
 | ||||
|         // equal to the given key.
 | ||||
|         DFHACK_EXPORT void getAllByKey(std::vector<PersistentDataItem> &vec, const std::string &key); | ||||
| 
 | ||||
| #if defined(__GNUC__) && __GNUC__ < 5 | ||||
|         // file stream move constructors are missing in libstdc++ before version 5.
 | ||||
|         template<typename T> | ||||
|         class DFHACK_EXPORT gcc_4_fstream_shim : public T | ||||
|         { | ||||
|             const std::string file; | ||||
|         public: | ||||
|             explicit gcc_4_fstream_shim() : T(), file() {} | ||||
|             explicit gcc_4_fstream_shim(const std::string &file) : T(file), file() {} | ||||
|             gcc_4_fstream_shim(gcc_4_fstream_shim<T> && s) : T(), file(s.file) | ||||
|             { | ||||
|                 if (!file.empty()) | ||||
|                 { | ||||
|                     T::open(file.c_str()); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
| #define FSTREAM(x) gcc_4_fstream_shim<x> | ||||
| #else | ||||
| #define FSTREAM(x) x | ||||
| #endif | ||||
| 
 | ||||
|         // Returns an input stream that data can be read from. If no world is currently loaded,
 | ||||
|         // or no data has been saved with the specified key, the stream is invalid and acts
 | ||||
|         // as if the file is empty.
 | ||||
|         DFHACK_EXPORT FSTREAM(std::ifstream) readSaveData(const std::string &key); | ||||
|         // Returns an output stream that data can be saved to. If no world is currently loaded,
 | ||||
|         // an invalid stream is returned, and writing to it has no effect.
 | ||||
|         DFHACK_EXPORT FSTREAM(std::ofstream) writeSaveData(const std::string &key); | ||||
| 
 | ||||
| #undef FSTREAM | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #endif | ||||
| @ -0,0 +1,420 @@ | ||||
| /*
 | ||||
| https://github.com/peterix/dfhack
 | ||||
| Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) | ||||
| 
 | ||||
| This software is provided 'as-is', without any express or implied | ||||
| warranty. In no event will the authors be held liable for any | ||||
| damages arising from the use of this software. | ||||
| 
 | ||||
| Permission is granted to anyone to use this software for any | ||||
| purpose, including commercial applications, and to alter it and | ||||
| redistribute it freely, subject to the following restrictions: | ||||
| 
 | ||||
| 1. The origin of this software must not be misrepresented; you must | ||||
| not claim that you wrote the original software. If you use this | ||||
| software in a product, an acknowledgment in the product documentation | ||||
| would be appreciated but is not required. | ||||
| 
 | ||||
| 2. Altered source versions must be plainly marked as such, and | ||||
| must not be misrepresented as being the original software. | ||||
| 
 | ||||
| 3. This notice may not be removed or altered from any source | ||||
| distribution. | ||||
| */ | ||||
| 
 | ||||
| #include "Internal.h" | ||||
| #include <array> | ||||
| #include <map> | ||||
| #include <json/json.h> | ||||
| 
 | ||||
| #include "Core.h" | ||||
| #include "DataDefs.h" | ||||
| #include "modules/Persistence.h" | ||||
| #include "modules/World.h" | ||||
| 
 | ||||
| #include "df/historical_figure.h" | ||||
| 
 | ||||
| using namespace DFHack; | ||||
| 
 | ||||
| static std::vector<std::shared_ptr<Persistence::LegacyData>> legacy_data; | ||||
| static std::multimap<std::string, size_t> index_cache; | ||||
| 
 | ||||
| struct Persistence::LegacyData | ||||
| { | ||||
|     const std::string key; | ||||
|     std::string str_value; | ||||
|     std::array<int, PersistentDataItem::NumInts> int_values; | ||||
| 
 | ||||
|     explicit LegacyData(const std::string &key) : key(key) | ||||
|     { | ||||
|         for (int i = 0; i < PersistentDataItem::NumInts; i++) | ||||
|         { | ||||
|             int_values.at(i) = -1; | ||||
|         } | ||||
|     } | ||||
|     explicit LegacyData(Json::Value &json) : key(json["k"].asString()) | ||||
|     { | ||||
|         str_value = json["s"].asString(); | ||||
|         for (int i = 0; i < PersistentDataItem::NumInts; i++) | ||||
|         { | ||||
|             int_values.at(i) = json["i"][i].asInt(); | ||||
|         } | ||||
|     } | ||||
|     explicit LegacyData(const df::language_name &name) : key(name.first_name) | ||||
|     { | ||||
|         str_value = name.nickname; | ||||
|         for (int i = 0; i < PersistentDataItem::NumInts; i++) | ||||
|         { | ||||
|             int_values.at(i) = name.words[i]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     Json::Value toJSON() | ||||
|     { | ||||
|         Json::Value json(Json::objectValue); | ||||
|         json["k"] = key; | ||||
|         json["s"] = str_value; | ||||
|         Json::Value ints(Json::arrayValue); | ||||
|         for (int i = 0; i < PersistentDataItem::NumInts; i++) | ||||
|         { | ||||
|             ints[i] = int_values.at(i); | ||||
|         } | ||||
|         json["i"] = std::move(ints); | ||||
| 
 | ||||
|         return std::move(json); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| const std::string &PersistentDataItem::key() const | ||||
| { | ||||
|     CHECK_INVALID_ARGUMENT(isValid()); | ||||
|     return data->key; | ||||
| } | ||||
| 
 | ||||
| std::string &PersistentDataItem::val() | ||||
| { | ||||
|     CHECK_INVALID_ARGUMENT(isValid()); | ||||
|     return data->str_value; | ||||
| } | ||||
| const std::string &PersistentDataItem::val() const | ||||
| { | ||||
|     CHECK_INVALID_ARGUMENT(isValid()); | ||||
|     return data->str_value; | ||||
| } | ||||
| int &PersistentDataItem::ival(int i) | ||||
| { | ||||
|     CHECK_INVALID_ARGUMENT(isValid()); | ||||
|     CHECK_INVALID_ARGUMENT(i >= 0 && i < NumInts); | ||||
|     return data->int_values.at(i); | ||||
| } | ||||
| int PersistentDataItem::ival(int i) const | ||||
| { | ||||
|     CHECK_INVALID_ARGUMENT(isValid()); | ||||
|     CHECK_INVALID_ARGUMENT(i >= 0 && i < NumInts); | ||||
|     return data->int_values.at(i); | ||||
| } | ||||
| 
 | ||||
| bool PersistentDataItem::isValid() const | ||||
| { | ||||
|     if (data == nullptr) | ||||
|         return false; | ||||
| 
 | ||||
|     CoreSuspender suspend; | ||||
| 
 | ||||
|     if (legacy_data.size() <= index) | ||||
|         return false; | ||||
| 
 | ||||
|     return legacy_data.at(index) == data; | ||||
| } | ||||
| 
 | ||||
| void Persistence::Internal::clear() | ||||
| { | ||||
|     CoreSuspender suspend; | ||||
| 
 | ||||
|     legacy_data.clear(); | ||||
|     index_cache.clear(); | ||||
| } | ||||
| 
 | ||||
| void Persistence::Internal::save() | ||||
| { | ||||
|     CoreSuspender suspend; | ||||
| 
 | ||||
|     Json::Value json(Json::arrayValue); | ||||
|     for (size_t i = 0; i < legacy_data.size(); i++) | ||||
|     { | ||||
|         if (legacy_data.at(i) != nullptr) | ||||
|         { | ||||
|             while (json.size() < i) | ||||
|             { | ||||
|                 json[json.size()] = Json::Value(); | ||||
|             } | ||||
| 
 | ||||
|             json[int(i)] = legacy_data.at(i)->toJSON(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     auto file = writeSaveData("legacy-data"); | ||||
|     file << json; | ||||
| } | ||||
| 
 | ||||
| static void convertHFigs() | ||||
| { | ||||
|     auto &figs = df::historical_figure::get_vector(); | ||||
| 
 | ||||
|     auto src = figs.begin(); | ||||
|     while (src != figs.end() && (*src)->id > -100) | ||||
|     { | ||||
|         ++src; | ||||
|     } | ||||
| 
 | ||||
|     if (src == figs.end()) | ||||
|     { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     auto dst = src; | ||||
|     while (src != figs.end()) | ||||
|     { | ||||
|         auto fig = *src; | ||||
|         if (fig->id > -100) | ||||
|         { | ||||
|             *dst = *src; | ||||
|             ++dst; | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if (fig->name.has_name && !fig->name.first_name.empty()) | ||||
|             { | ||||
|                 size_t index = size_t(-fig->id - 100); | ||||
|                 if (legacy_data.size() <= index) | ||||
|                 { | ||||
|                     legacy_data.resize(index + 1); | ||||
|                 } | ||||
|                 legacy_data.at(index) = std::shared_ptr<Persistence::LegacyData>(new Persistence::LegacyData(fig->name)); | ||||
|             } | ||||
|             delete fig; | ||||
|         } | ||||
|         ++src; | ||||
|     } | ||||
| 
 | ||||
|     figs.erase(dst, figs.end()); | ||||
| } | ||||
| 
 | ||||
| void Persistence::Internal::load() | ||||
| { | ||||
|     CoreSuspender suspend; | ||||
| 
 | ||||
|     clear(); | ||||
| 
 | ||||
|     auto file = readSaveData("legacy-data"); | ||||
|     Json::Value json; | ||||
|     try | ||||
|     { | ||||
|         file >> json; | ||||
|     } | ||||
|     catch (std::exception &) | ||||
|     { | ||||
|         // empty file?
 | ||||
|     } | ||||
| 
 | ||||
|     if (json.isArray()) | ||||
|     { | ||||
|         legacy_data.resize(json.size()); | ||||
|         for (size_t i = 0; i < legacy_data.size(); i++) | ||||
|         { | ||||
|             if (json[int(i)].isObject()) | ||||
|             { | ||||
|                 legacy_data.at(i) = std::shared_ptr<LegacyData>(new LegacyData(json[int(i)])); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     convertHFigs(); | ||||
| 
 | ||||
|     for (size_t i = 0; i < legacy_data.size(); i++) | ||||
|     { | ||||
|         if (legacy_data.at(i) == nullptr) | ||||
|         { | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         index_cache.insert(std::make_pair(legacy_data.at(i)->key, i)); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| PersistentDataItem Persistence::addItem(const std::string &key) | ||||
| { | ||||
|     if (key.empty() || !Core::getInstance().isWorldLoaded()) | ||||
|         return PersistentDataItem(); | ||||
| 
 | ||||
|     CoreSuspender suspend; | ||||
| 
 | ||||
|     size_t index = 0; | ||||
|     while (index < legacy_data.size() && legacy_data.at(index) != nullptr) | ||||
|     { | ||||
|         index++; | ||||
|     } | ||||
| 
 | ||||
|     auto ptr = std::shared_ptr<LegacyData>(new LegacyData(key)); | ||||
| 
 | ||||
|     if (index == legacy_data.size()) | ||||
|     { | ||||
|         legacy_data.push_back(ptr); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         legacy_data.at(index) = ptr; | ||||
|     } | ||||
| 
 | ||||
|     index_cache.insert(std::make_pair(key, index)); | ||||
| 
 | ||||
|     return PersistentDataItem(index, ptr); | ||||
| } | ||||
| 
 | ||||
| PersistentDataItem Persistence::getByKey(const std::string &key, bool *added) | ||||
| { | ||||
|     CoreSuspender suspend; | ||||
| 
 | ||||
|     auto it = index_cache.find(key); | ||||
| 
 | ||||
|     if (added) | ||||
|     { | ||||
|         *added = it == index_cache.end(); | ||||
|     } | ||||
| 
 | ||||
|     if (it != index_cache.end()) | ||||
|     { | ||||
|         return PersistentDataItem(it->second, legacy_data.at(it->second)); | ||||
|     } | ||||
| 
 | ||||
|     if (!added) | ||||
|     { | ||||
|         return PersistentDataItem(); | ||||
|     } | ||||
| 
 | ||||
|     return addItem(key); | ||||
| } | ||||
| 
 | ||||
| PersistentDataItem Persistence::getByIndex(size_t index) | ||||
| { | ||||
|     CoreSuspender suspend; | ||||
| 
 | ||||
|     if (index < legacy_data.size() && legacy_data.at(index) != nullptr) | ||||
|     { | ||||
|         return PersistentDataItem(index, legacy_data.at(index)); | ||||
|     } | ||||
| 
 | ||||
|     return PersistentDataItem(); | ||||
| } | ||||
| 
 | ||||
| bool Persistence::deleteItem(const PersistentDataItem &item) | ||||
| { | ||||
|     CoreSuspender suspend; | ||||
| 
 | ||||
|     if (!item.isValid()) | ||||
|     { | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     size_t index = item.get_index(); | ||||
|     auto range = index_cache.equal_range(item.key()); | ||||
|     for (auto it = range.first; it != range.second; ++it) | ||||
|     { | ||||
|         if (it->second == index) | ||||
|         { | ||||
|             index_cache.erase(it); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     legacy_data.at(index) = nullptr; | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| void Persistence::getAll(std::vector<PersistentDataItem> &vec) | ||||
| { | ||||
|     vec.clear(); | ||||
| 
 | ||||
|     CoreSuspender suspend; | ||||
| 
 | ||||
|     for (size_t i = 0; i < legacy_data.size(); i++) | ||||
|     { | ||||
|         if (legacy_data.at(i) != nullptr) | ||||
|         { | ||||
|             vec.push_back(PersistentDataItem(i, legacy_data.at(i))); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Persistence::getAllByKeyRange(std::vector<PersistentDataItem> &vec, const std::string &min, const std::string &max) | ||||
| { | ||||
|     vec.clear(); | ||||
| 
 | ||||
|     CoreSuspender suspend; | ||||
| 
 | ||||
|     auto begin = index_cache.lower_bound(min); | ||||
|     auto end = index_cache.lower_bound(max); | ||||
|     for (auto it = begin; it != end; ++it) | ||||
|     { | ||||
|         vec.push_back(PersistentDataItem(it->second, legacy_data.at(it->second))); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void Persistence::getAllByKey(std::vector<PersistentDataItem> &vec, const std::string &key) | ||||
| { | ||||
|     vec.clear(); | ||||
| 
 | ||||
|     CoreSuspender suspend; | ||||
| 
 | ||||
|     auto range = index_cache.equal_range(key); | ||||
|     for (auto it = range.first; it != range.second; ++it) | ||||
|     { | ||||
|         vec.push_back(PersistentDataItem(it->second, legacy_data.at(it->second))); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static std::string filterSaveFileName(std::string s) | ||||
| { | ||||
|     for (auto &ch : s) | ||||
|     { | ||||
|         if (!isalnum(ch) && ch != '-' && ch != '_') | ||||
|         { | ||||
|             ch = '_'; | ||||
|         } | ||||
|     } | ||||
|     return s; | ||||
| } | ||||
| 
 | ||||
| static std::string getSaveFilePath(const std::string &world, const std::string &name) | ||||
| { | ||||
|     return "data/save/" + world + "/dfhack-" + filterSaveFileName(name) + ".dat"; | ||||
| } | ||||
| 
 | ||||
| #if defined(__GNUC__) && __GNUC__ < 5 | ||||
| // file stream move constructors are missing in libstdc++ before version 5.
 | ||||
| #define FSTREAM(x) Persistence::gcc_4_fstream_shim<x> | ||||
| #else | ||||
| #define FSTREAM(x) x | ||||
| #endif | ||||
| FSTREAM(std::ifstream) Persistence::readSaveData(const std::string &name) | ||||
| { | ||||
|     if (!Core::getInstance().isWorldLoaded()) | ||||
|     { | ||||
|         // No world loaded - return unopened stream.
 | ||||
|         return FSTREAM(std::ifstream)(); | ||||
|     } | ||||
| 
 | ||||
|     return FSTREAM(std::ifstream)(getSaveFilePath(World::ReadWorldFolder(), name)); | ||||
| } | ||||
| 
 | ||||
| FSTREAM(std::ofstream) Persistence::writeSaveData(const std::string &name) | ||||
| { | ||||
|     if (!Core::getInstance().isWorldLoaded()) | ||||
|     { | ||||
|         // No world loaded - return unopened stream.
 | ||||
|         return FSTREAM(std::ofstream)(); | ||||
|     } | ||||
| 
 | ||||
|     return FSTREAM(std::ofstream)(getSaveFilePath("current", name)); | ||||
| } | ||||
| #undef FSTREAM | ||||
| @ -1 +1 @@ | ||||
| Subproject commit c192f9798b5d134e777b1f37c8ebc0df4bd53bda | ||||
| Subproject commit 4388fbfb8f51be41777406c6e7c518f738c195c7 | ||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								| @ -0,0 +1,11 @@ | ||||
| local _ENV = mkmodule('plugins.map-render') | ||||
| 
 | ||||
| --[[ | ||||
| 
 | ||||
|  Native functions: | ||||
| 
 | ||||
|  * render_map_rect(x,y,z,w,h) | ||||
| 
 | ||||
| --]] | ||||
| 
 | ||||
| return _ENV | ||||
| @ -0,0 +1,125 @@ | ||||
| #include "Core.h" | ||||
| #include "Console.h" | ||||
| #include "Export.h" | ||||
| #include "PluginManager.h" | ||||
| #include "VersionInfo.h" | ||||
| #include "VTableInterpose.h" | ||||
| #include "LuaTools.h" | ||||
| 
 | ||||
| #include "DataDefs.h" | ||||
| 
 | ||||
| #include "df/viewscreen_dwarfmodest.h" | ||||
| #include "df/init.h" | ||||
| #include "df/renderer.h" | ||||
| #include "df/graphic.h" | ||||
| #include "df/enabler.h" | ||||
| #include "df/map_renderer.h" | ||||
| 
 | ||||
| using std::string; | ||||
| using std::vector; | ||||
| using namespace DFHack; | ||||
| using namespace df::enums; | ||||
| 
 | ||||
| DFHACK_PLUGIN("map-render"); | ||||
| REQUIRE_GLOBAL(window_x) | ||||
| REQUIRE_GLOBAL(window_y) | ||||
| REQUIRE_GLOBAL(window_z) | ||||
| REQUIRE_GLOBAL_NO_USE(gps) | ||||
| REQUIRE_GLOBAL_NO_USE(enabler) | ||||
| REQUIRE_GLOBAL_NO_USE(twbt_render_map) | ||||
| REQUIRE_GLOBAL(init) | ||||
| 
 | ||||
| #ifdef WIN32 | ||||
|     // On Windows there's no parameter pointing to the map_renderer structure
 | ||||
|     typedef void(_stdcall *RENDER_MAP)(int); | ||||
| 
 | ||||
|     RENDER_MAP _render_map; | ||||
| 
 | ||||
|     void render_map(){ _render_map(0); } | ||||
| #else | ||||
| REQUIRE_GLOBAL(map_renderer) | ||||
| 
 | ||||
|     typedef void(*RENDER_MAP)(void*, int); | ||||
| 
 | ||||
|     RENDER_MAP _render_map; | ||||
| 
 | ||||
|     void render_map(){ _render_map(map_renderer,0); } | ||||
| #endif | ||||
| static int render_map_rect(lua_State* L) | ||||
| { | ||||
|     CoreSuspender suspender; | ||||
| 
 | ||||
|     int x = luaL_checkint(L, 1); | ||||
|     int y = luaL_checkint(L, 2); | ||||
|     int z = luaL_checkint(L, 3); | ||||
|     int w = luaL_checkint(L, 4); | ||||
|     int h = luaL_checkint(L, 5); | ||||
|     uint8_t *s = df::global::gps->screen; | ||||
|     //backup state
 | ||||
|     //TODO: figure out if we can replace screen with other pointer. That way it could be a bit more tidy
 | ||||
|     int32_t win_h = df::global::gps->dimy; | ||||
|     int32_t was_x = *window_x; | ||||
|     int32_t was_y = *window_y; | ||||
|     int32_t was_z = *window_z; | ||||
|     int32_t gx = init->display.grid_x; | ||||
|     int32_t gy = init->display.grid_y; | ||||
|     init->display.grid_x = w+1; | ||||
|     init->display.grid_y = h+1; | ||||
|     *window_x = x; | ||||
|     *window_y = y; | ||||
|     *window_z = z; | ||||
|     //force full redraw
 | ||||
|     df::global::gps->force_full_display_count = 1; | ||||
|     //this modifies screen so it REALLY wants to redraw stuff
 | ||||
|     for (int ty = 0; ty < h; ty++) | ||||
|     for (int tx = 0; tx < w; tx++) | ||||
|     { | ||||
|         for (int i = 0; i < 4; i++) | ||||
|         { | ||||
|             int t = (tx + 1)*win_h + ty + 1; | ||||
|             s[t * 4 + i] = 0; | ||||
|         } | ||||
|     } | ||||
|     render_map(); | ||||
|     //restore state
 | ||||
|     *window_x = was_x; | ||||
|     *window_y = was_y; | ||||
|     *window_z = was_z; | ||||
|     init->display.grid_x = gx; | ||||
|     init->display.grid_y = gy; | ||||
| 
 | ||||
|     lua_createtable(L,w*h*4,0); | ||||
| 
 | ||||
|     int counter = 0; | ||||
|     for (int ty = 0; ty < h; ty++) | ||||
|     for (int tx = 0; tx < w; tx++) | ||||
|     { | ||||
|         for (int i = 0; i < 4;i++) | ||||
|         { | ||||
|             int t = (tx + 1)*win_h + ty + 1; | ||||
|             lua_pushnumber(L, s[t*4+i]); | ||||
|             lua_rawseti(L, -2, counter); | ||||
|             counter++; | ||||
|         } | ||||
|     } | ||||
|     return 1; | ||||
| } | ||||
| 
 | ||||
| DFHACK_PLUGIN_LUA_COMMANDS{ | ||||
|     DFHACK_LUA_COMMAND(render_map_rect), | ||||
|     DFHACK_LUA_END | ||||
| }; | ||||
| 
 | ||||
| DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands) | ||||
| { | ||||
|     auto addr =reinterpret_cast<RENDER_MAP>(Core::getInstance().vinfo->getAddress("twbt_render_map")); | ||||
|     if (addr == nullptr) | ||||
|         return CR_FAILURE; | ||||
|     _render_map = addr; | ||||
|     return CR_OK; | ||||
| } | ||||
| 
 | ||||
| DFhackCExport command_result plugin_shutdown(color_ostream &out) | ||||
| { | ||||
|     return CR_OK; | ||||
| } | ||||
| @ -1 +1 @@ | ||||
| Subproject commit 189d0d8f63b5d34ac3f779254b6ab5ff4763459a | ||||
| Subproject commit 8ef283377c9830fb932ea888d89b551873af36cf | ||||
		Loading…
	
		Reference in New Issue