Merge branch 'main' into heightmaps
This commit is contained in:
@@ -1,65 +0,0 @@
|
||||
#include "ContentLUT.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "coders/json.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "files/files.hpp"
|
||||
#include "items/ItemDef.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "files/WorldFiles.hpp"
|
||||
#include "Content.hpp"
|
||||
|
||||
ContentLUT::ContentLUT(
|
||||
const ContentIndices* indices, size_t blocksCount, size_t itemsCount
|
||||
)
|
||||
: blocks(blocksCount, indices->blocks, BLOCK_VOID, ContentType::BLOCK),
|
||||
items(itemsCount, indices->items, ITEM_VOID, ContentType::ITEM) {
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static constexpr size_t get_entries_count(
|
||||
const ContentUnitIndices<T>& indices, const dv::value& list
|
||||
) {
|
||||
return list != nullptr
|
||||
? std::max(list.size(), indices.count())
|
||||
: indices.count();
|
||||
}
|
||||
|
||||
std::shared_ptr<ContentLUT> ContentLUT::create(
|
||||
const std::shared_ptr<WorldFiles>& worldFiles,
|
||||
const fs::path& filename,
|
||||
const Content* content
|
||||
) {
|
||||
auto worldInfo = worldFiles->readWorldInfo();
|
||||
if (!worldInfo.has_value()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto root = files::read_json(filename);
|
||||
auto& blocklist = root["blocks"];
|
||||
auto& itemlist = root["items"];
|
||||
|
||||
auto* indices = content->getIndices();
|
||||
size_t blocks_c = get_entries_count(indices->blocks, blocklist);
|
||||
size_t items_c = get_entries_count(indices->items, itemlist);
|
||||
|
||||
auto lut = std::make_shared<ContentLUT>(indices, blocks_c, items_c);
|
||||
|
||||
lut->blocks.setup(blocklist, content->blocks);
|
||||
lut->items.setup(itemlist, content->items);
|
||||
|
||||
if (lut->hasContentReorder() || lut->hasMissingContent()) {
|
||||
return lut;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<contententry> ContentLUT::getMissingContent() const {
|
||||
std::vector<contententry> entries;
|
||||
blocks.getMissingContent(entries);
|
||||
items.getMissingContent(entries);
|
||||
return entries;
|
||||
}
|
||||
@@ -21,8 +21,10 @@
|
||||
#include "util/stringutil.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
#include "data/dv_util.hpp"
|
||||
#include "data/StructLayout.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
using namespace data;
|
||||
|
||||
static debug::Logger logger("content-loader");
|
||||
|
||||
@@ -125,6 +127,30 @@ void ContentLoader::fixPackIndices() {
|
||||
}
|
||||
}
|
||||
|
||||
static void perform_user_block_fields(
|
||||
const std::string& blockName, StructLayout& layout
|
||||
) {
|
||||
if (layout.size() > MAX_USER_BLOCK_FIELDS_SIZE) {
|
||||
throw std::runtime_error(
|
||||
util::quote(blockName) +
|
||||
" fields total size exceeds limit (" +
|
||||
std::to_string(layout.size()) + "/" +
|
||||
std::to_string(MAX_USER_BLOCK_FIELDS_SIZE) + ")");
|
||||
}
|
||||
for (const auto& field : layout) {
|
||||
if (field.name.at(0) == '.') {
|
||||
throw std::runtime_error(
|
||||
util::quote(blockName) + " field " + field.name +
|
||||
": user field may not start with '.'");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Field> fields;
|
||||
fields.insert(fields.end(), layout.begin(), layout.end());
|
||||
// add built-in fields here
|
||||
layout = StructLayout::create(fields);
|
||||
}
|
||||
|
||||
void ContentLoader::loadBlock(
|
||||
Block& def, const std::string& name, const fs::path& file
|
||||
) {
|
||||
@@ -259,6 +285,14 @@ void ContentLoader::loadBlock(
|
||||
root.at("ui-layout").get(def.uiLayout);
|
||||
root.at("inventory-size").get(def.inventorySize);
|
||||
root.at("tick-interval").get(def.tickInterval);
|
||||
|
||||
if (root.has("fields")) {
|
||||
def.dataStruct = std::make_unique<StructLayout>();
|
||||
def.dataStruct->deserialize(root["fields"]);
|
||||
|
||||
perform_user_block_fields(def.name, *def.dataStruct);
|
||||
}
|
||||
|
||||
if (def.tickInterval == 0) {
|
||||
def.tickInterval = 1;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
#include "ContentReport.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "coders/json.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "files/files.hpp"
|
||||
#include "items/ItemDef.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "files/WorldFiles.hpp"
|
||||
#include "Content.hpp"
|
||||
|
||||
ContentReport::ContentReport(
|
||||
const ContentIndices* indices,
|
||||
size_t blocksCount,
|
||||
size_t itemsCount,
|
||||
uint regionsVersion
|
||||
)
|
||||
: blocks(blocksCount, indices->blocks, BLOCK_VOID, ContentType::BLOCK),
|
||||
items(itemsCount, indices->items, ITEM_VOID, ContentType::ITEM),
|
||||
regionsVersion(regionsVersion) {
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static constexpr size_t get_entries_count(
|
||||
const ContentUnitIndices<T>& indices, const dv::value& list
|
||||
) {
|
||||
return list != nullptr ? std::max(list.size(), indices.count())
|
||||
: indices.count();
|
||||
}
|
||||
|
||||
static void process_blocks_data(
|
||||
const Content* content, ContentReport& report, const dv::value& root
|
||||
) {
|
||||
for (const auto& [name, map] : root.asObject()) {
|
||||
data::StructLayout layout;
|
||||
layout.deserialize(map);
|
||||
auto def = content->blocks.find(name);
|
||||
if (def == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (def->dataStruct == nullptr) {
|
||||
ContentIssue issue {ContentIssueType::BLOCK_DATA_LAYOUTS_UPDATE};
|
||||
report.issues.push_back(issue);
|
||||
report.dataLoss.push_back(name + ": discard data");
|
||||
continue;
|
||||
}
|
||||
if (layout != *def->dataStruct) {
|
||||
ContentIssue issue {ContentIssueType::BLOCK_DATA_LAYOUTS_UPDATE};
|
||||
report.issues.push_back(issue);
|
||||
report.dataLayoutsUpdated = true;
|
||||
}
|
||||
|
||||
auto incapatibility = layout.checkCompatibility(*def->dataStruct);
|
||||
if (!incapatibility.empty()) {
|
||||
for (const auto& error : incapatibility) {
|
||||
report.dataLoss.push_back(
|
||||
"[" + name + "] field " + error.name + " - " +
|
||||
data::to_string(error.type)
|
||||
);
|
||||
}
|
||||
}
|
||||
report.blocksDataLayouts[name] = std::move(layout);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<ContentReport> ContentReport::create(
|
||||
const std::shared_ptr<WorldFiles>& worldFiles,
|
||||
const fs::path& filename,
|
||||
const Content* content
|
||||
) {
|
||||
auto worldInfo = worldFiles->readWorldInfo();
|
||||
if (!worldInfo.has_value()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto root = files::read_json(filename);
|
||||
// TODO: remove default value 2 in 0.24
|
||||
uint regionsVersion = 2U;
|
||||
root.at("region-version").get(regionsVersion);
|
||||
auto& blocklist = root["blocks"];
|
||||
auto& itemlist = root["items"];
|
||||
|
||||
auto* indices = content->getIndices();
|
||||
size_t blocks_c = get_entries_count(indices->blocks, blocklist);
|
||||
size_t items_c = get_entries_count(indices->items, itemlist);
|
||||
|
||||
auto report = std::make_shared<ContentReport>(
|
||||
indices, blocks_c, items_c, regionsVersion
|
||||
);
|
||||
report->blocks.setup(blocklist, content->blocks);
|
||||
report->items.setup(itemlist, content->items);
|
||||
|
||||
if (root.has("blocks-data")) {
|
||||
process_blocks_data(content, *report, root["blocks-data"]);
|
||||
}
|
||||
|
||||
report->buildIssues();
|
||||
|
||||
if (report->isUpgradeRequired() || report->hasContentReorder() ||
|
||||
report->hasMissingContent() || report->hasUpdatedLayouts()) {
|
||||
return report;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T, class U>
|
||||
static void build_issues(
|
||||
std::vector<ContentIssue>& issues, const ContentUnitLUT<T, U>& report
|
||||
) {
|
||||
auto type = report.getContentType();
|
||||
if (report.hasContentReorder()) {
|
||||
issues.push_back(ContentIssue {ContentIssueType::REORDER, type});
|
||||
}
|
||||
if (report.hasMissingContent()) {
|
||||
issues.push_back(ContentIssue {ContentIssueType::MISSING, type});
|
||||
}
|
||||
}
|
||||
|
||||
void ContentReport::buildIssues() {
|
||||
build_issues(issues, blocks);
|
||||
build_issues(issues, items);
|
||||
|
||||
if (regionsVersion < REGION_FORMAT_VERSION) {
|
||||
for (int layer = REGION_LAYER_VOXELS;
|
||||
layer < REGION_LAYERS_COUNT;
|
||||
layer++) {
|
||||
ContentIssue issue {ContentIssueType::REGION_FORMAT_UPDATE};
|
||||
issue.regionLayer = static_cast<RegionLayerIndex>(layer);
|
||||
issues.push_back(issue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::vector<ContentIssue>& ContentReport::getIssues() const {
|
||||
return issues;
|
||||
}
|
||||
|
||||
std::vector<ContentEntry> ContentReport::getMissingContent() const {
|
||||
std::vector<ContentEntry> entries;
|
||||
blocks.getMissingContent(entries);
|
||||
items.getMissingContent(entries);
|
||||
return entries;
|
||||
}
|
||||
@@ -4,27 +4,49 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "constants.hpp"
|
||||
#include "data/dv.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "Content.hpp"
|
||||
#include "data/StructLayout.hpp"
|
||||
#include "files/world_regions_fwd.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
struct contententry {
|
||||
enum class ContentIssueType {
|
||||
REORDER,
|
||||
MISSING,
|
||||
REGION_FORMAT_UPDATE,
|
||||
BLOCK_DATA_LAYOUTS_UPDATE,
|
||||
};
|
||||
|
||||
struct ContentIssue {
|
||||
ContentIssueType issueType;
|
||||
union {
|
||||
ContentType contentType;
|
||||
RegionLayerIndex regionLayer;
|
||||
};
|
||||
};
|
||||
|
||||
struct ContentEntry {
|
||||
ContentType type;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
class WorldFiles;
|
||||
|
||||
/// @brief Content unit lookup table
|
||||
/// @tparam T index type
|
||||
/// @tparam U unit class
|
||||
template <typename T, class U>
|
||||
class ContentUnitLUT {
|
||||
std::vector<T> indices;
|
||||
std::vector<std::string> names;
|
||||
bool missingContent = false;
|
||||
bool reorderContent = false;
|
||||
/// @brief index that will be used to mark missing unit
|
||||
T missingValue;
|
||||
ContentType type;
|
||||
public:
|
||||
@@ -57,11 +79,11 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
void getMissingContent(std::vector<contententry>& entries) const {
|
||||
void getMissingContent(std::vector<ContentEntry>& entries) const {
|
||||
for (size_t i = 0; i < count(); i++) {
|
||||
if (indices[i] == missingValue) {
|
||||
auto& name = names[i];
|
||||
entries.push_back(contententry {type, name});
|
||||
entries.push_back(ContentEntry {type, name});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,6 +102,9 @@ public:
|
||||
reorderContent = true;
|
||||
}
|
||||
}
|
||||
inline ContentType getContentType() const {
|
||||
return type;
|
||||
}
|
||||
inline size_t count() const {
|
||||
return indices.size();
|
||||
}
|
||||
@@ -91,28 +116,54 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
/// @brief Content indices lookup table or report
|
||||
/// used to convert world with different indices
|
||||
/// @brief Content incapatibility report used to convert world.
|
||||
/// Building with indices.json
|
||||
class ContentLUT {
|
||||
class ContentReport {
|
||||
public:
|
||||
ContentUnitLUT<blockid_t, Block> blocks;
|
||||
ContentUnitLUT<itemid_t, ItemDef> items;
|
||||
uint regionsVersion;
|
||||
|
||||
ContentLUT(const ContentIndices* indices, size_t blocks, size_t items);
|
||||
std::unordered_map<std::string, data::StructLayout> blocksDataLayouts;
|
||||
std::vector<ContentIssue> issues;
|
||||
std::vector<std::string> dataLoss;
|
||||
|
||||
static std::shared_ptr<ContentLUT> create(
|
||||
bool dataLayoutsUpdated = false;
|
||||
|
||||
ContentReport(
|
||||
const ContentIndices* indices,
|
||||
size_t blocks,
|
||||
size_t items,
|
||||
uint regionsVersion
|
||||
);
|
||||
|
||||
static std::shared_ptr<ContentReport> create(
|
||||
const std::shared_ptr<WorldFiles>& worldFiles,
|
||||
const fs::path& filename,
|
||||
const Content* content
|
||||
);
|
||||
|
||||
inline const std::vector<std::string>& getDataLoss() const {
|
||||
return dataLoss;
|
||||
}
|
||||
inline bool hasUpdatedLayouts() {
|
||||
return dataLayoutsUpdated;
|
||||
}
|
||||
|
||||
inline bool hasContentReorder() const {
|
||||
return blocks.hasContentReorder() || items.hasContentReorder();
|
||||
}
|
||||
inline bool hasMissingContent() const {
|
||||
return blocks.hasMissingContent() || items.hasMissingContent();
|
||||
}
|
||||
inline bool isUpgradeRequired() const {
|
||||
return regionsVersion < REGION_FORMAT_VERSION;
|
||||
}
|
||||
inline bool hasDataLoss() const {
|
||||
return !dataLoss.empty();
|
||||
}
|
||||
void buildIssues();
|
||||
|
||||
std::vector<contententry> getMissingContent() const;
|
||||
const std::vector<ContentIssue>& getIssues() const;
|
||||
std::vector<ContentEntry> getMissingContent() const;
|
||||
};
|
||||
Reference in New Issue
Block a user