GH-3229 fix copy seed button not working for newer worlds

Added the `optional-bare` library and refactored NBT reading
code to support this change.
This commit is contained in:
Petr Mrázek
2021-02-06 15:58:03 +01:00
parent f8ca96a335
commit 1868e0ccf1
10 changed files with 728 additions and 76 deletions

View File

@ -32,7 +32,36 @@
#include <QCoreApplication>
QString gameTypeToString(GameType type)
#include <nonstd/optional>
using nonstd::optional;
using nonstd::nullopt;
GameType::GameType(nonstd::optional<int> original):
original(original)
{
if(!original) {
return;
}
switch(*original) {
case 0:
type = GameType::Survival;
break;
case 1:
type = GameType::Creative;
break;
case 2:
type = GameType::Adventure;
break;
case 3:
type = GameType::Spectator;
break;
default:
break;
}
}
QString GameType::toTranslatedString() const
{
switch (type)
{
@ -47,7 +76,31 @@ QString gameTypeToString(GameType type)
default:
break;
}
return QObject::tr("Unknown");
if(original) {
return QCoreApplication::translate("GameType", "Unknown (%1)").arg(*original);
}
return QCoreApplication::translate("GameType", "Undefined");
}
QString GameType::toLogString() const
{
switch (type)
{
case GameType::Survival:
return "Survival";
case GameType::Creative:
return "Creative";
case GameType::Adventure:
return "Adventure";
case GameType::Spectator:
return "Spectator";
default:
break;
}
if(original) {
return QString("Unknown (%1)").arg(*original);
}
return "Undefined";
}
std::unique_ptr <nbt::tag_compound> parseLevelDat(QByteArray data)
@ -58,15 +111,22 @@ std::unique_ptr <nbt::tag_compound> parseLevelDat(QByteArray data)
return nullptr;
}
std::istringstream foo(std::string(output.constData(), output.size()));
auto pair = nbt::io::read_compound(foo);
try {
auto pair = nbt::io::read_compound(foo);
if(pair.first != "")
if(pair.first != "")
return nullptr;
if(pair.second == nullptr)
return nullptr;
return std::move(pair.second);
}
catch (const nbt::io::input_error &e)
{
qWarning() << "Unable to parse level.dat:" << e.what();
return nullptr;
if(pair.second == nullptr)
return nullptr;
return std::move(pair.second);
}
}
QByteArray serializeLevelDat(nbt::tag_compound * levelInfo)
@ -288,14 +348,16 @@ bool World::rename(const QString &newName)
return true;
}
static QString read_string (nbt::value& parent, const char * name, const QString & fallback = QString())
namespace {
optional<QString> read_string (nbt::value& parent, const char * name)
{
try
{
auto &namedValue = parent.at(name);
if(namedValue.get_type() != nbt::tag_type::String)
{
return fallback;
return nullopt;
}
auto & tag_str = namedValue.as<nbt::tag_string>();
return QString::fromStdString(tag_str.get());
@ -303,25 +365,25 @@ static QString read_string (nbt::value& parent, const char * name, const QString
catch (const std::out_of_range &e)
{
// fallback for old world formats
qWarning() << "String NBT tag" << name << "could not be found. Defaulting to" << fallback;
return fallback;
qWarning() << "String NBT tag" << name << "could not be found.";
return nullopt;
}
catch (const std::bad_cast &e)
{
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to string. Defaulting to" << fallback;
return fallback;
qWarning() << "NBT tag" << name << "could not be converted to string.";
return nullopt;
}
}
static int64_t read_long (nbt::value& parent, const char * name, const int64_t & fallback = 0)
optional<int64_t> read_long (nbt::value& parent, const char * name)
{
try
{
auto &namedValue = parent.at(name);
if(namedValue.get_type() != nbt::tag_type::Long)
{
return fallback;
return nullopt;
}
auto & tag_str = namedValue.as<nbt::tag_long>();
return tag_str.get();
@ -329,25 +391,25 @@ static int64_t read_long (nbt::value& parent, const char * name, const int64_t &
catch (const std::out_of_range &e)
{
// fallback for old world formats
qWarning() << "Long NBT tag" << name << "could not be found. Defaulting to" << fallback;
return fallback;
qWarning() << "Long NBT tag" << name << "could not be found.";
return nullopt;
}
catch (const std::bad_cast &e)
{
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to long. Defaulting to" << fallback;
return fallback;
qWarning() << "NBT tag" << name << "could not be converted to long.";
return nullopt;
}
}
static int read_int (nbt::value& parent, const char * name, const int & fallback = 0)
optional<int> read_int (nbt::value& parent, const char * name)
{
try
{
auto &namedValue = parent.at(name);
if(namedValue.get_type() != nbt::tag_type::Int)
{
return fallback;
return nullopt;
}
auto & tag_str = namedValue.as<nbt::tag_int>();
return tag_str.get();
@ -355,62 +417,72 @@ static int read_int (nbt::value& parent, const char * name, const int & fallback
catch (const std::out_of_range &e)
{
// fallback for old world formats
qWarning() << "Int NBT tag" << name << "could not be found. Defaulting to" << fallback;
return fallback;
qWarning() << "Int NBT tag" << name << "could not be found.";
return nullopt;
}
catch (const std::bad_cast &e)
{
// type mismatch
qWarning() << "NBT tag" << name << "could not be converted to int. Defaulting to" << fallback;
return fallback;
qWarning() << "NBT tag" << name << "could not be converted to int.";
return nullopt;
}
}
GameType read_gametype(nbt::value& parent, const char * name) {
return GameType(read_int(parent, name));
}
}
void World::loadFromLevelDat(QByteArray data)
{
try
auto levelData = parseLevelDat(data);
if(!levelData)
{
auto levelData = parseLevelDat(data);
if(!levelData)
{
is_valid = false;
return;
}
auto &val = levelData->at("Data");
is_valid = val.get_type() == nbt::tag_type::Compound;
if(!is_valid)
return;
m_actualName = read_string(val, "LevelName", m_folderName);
int64_t temp = read_long(val, "LastPlayed", 0);
if(temp == 0)
{
m_lastPlayed = levelDatTime;
}
else
{
m_lastPlayed = QDateTime::fromMSecsSinceEpoch(temp);
}
int GameType_val = read_int(val, "GameType", 0);
m_gameType = (GameType) GameType_val;
m_randomSeed = read_long(val, "RandomSeed", 0);
qDebug() << "World Name:" << m_actualName;
qDebug() << "Last Played:" << m_lastPlayed.toString();
qDebug() << "Seed:" << m_randomSeed;
qDebug() << "GameMode:" << GameType_val;
}
catch (const nbt::io::input_error &e)
{
qWarning() << "Unable to load" << m_folderName << ":" << e.what();
is_valid = false;
return;
}
nbt::value * valPtr = nullptr;
try {
valPtr = &levelData->at("Data");
}
catch (const std::out_of_range &e) {
qWarning() << "Unable to read NBT tags from " << m_folderName << ":" << e.what();
is_valid = false;
return;
}
nbt::value &val = *valPtr;
is_valid = val.get_type() == nbt::tag_type::Compound;
if(!is_valid)
return;
auto name = read_string(val, "LevelName");
m_actualName = name ? *name : m_folderName;
auto timestamp = read_long(val, "LastPlayed");
m_lastPlayed = timestamp ? QDateTime::fromMSecsSinceEpoch(*timestamp) : levelDatTime;
m_gameType = read_gametype(val, "GameType");
optional<int64_t> randomSeed;
try {
auto &WorldGen_val = val.at("WorldGenSettings");
randomSeed = read_long(WorldGen_val, "seed");
}
catch (std::out_of_range) {}
if(!randomSeed) {
randomSeed = read_long(val, "RandomSeed");
}
m_randomSeed = randomSeed ? *randomSeed : 0;
qDebug() << "World Name:" << m_actualName;
qDebug() << "Last Played:" << m_lastPlayed.toString();
if(randomSeed) {
qDebug() << "Seed:" << *randomSeed;
}
qDebug() << "GameType:" << m_gameType.toLogString();
}
bool World::replace(World &with)

View File

@ -16,17 +16,27 @@
#pragma once
#include <QFileInfo>
#include <QDateTime>
#include <nonstd/optional>
#include "multimc_logic_export.h"
enum class GameType
{
Survival,
Creative,
Adventure,
Spectator
struct MULTIMC_LOGIC_EXPORT GameType {
GameType() = default;
GameType (nonstd::optional<int> original);
QString toTranslatedString() const;
QString toLogString() const;
enum
{
Unknown = -1,
Survival = 0,
Creative,
Adventure,
Spectator
} type = Unknown;
nonstd::optional<int> original;
};
QString MULTIMC_LOGIC_EXPORT gameTypeToString(GameType type);
class MULTIMC_LOGIC_EXPORT World
{
@ -98,6 +108,6 @@ protected:
QDateTime levelDatTime;
QDateTime m_lastPlayed;
int64_t m_randomSeed = 0;
GameType m_gameType = GameType::Survival;
GameType m_gameType;
bool is_valid = false;
};

View File

@ -175,7 +175,7 @@ QVariant WorldList::data(const QModelIndex &index, int role) const
return world.name();
case GameModeColumn:
return gameTypeToString(world.gameType());
return world.gameType().toTranslatedString();
case LastPlayedColumn:
return world.lastPlayed();