fix: security vulnerabilities (#1980)

* fix: security vulnerabilities

Tested that all functions related to the touched files work

will test sqlite on a CI build

* fix failing test

* ai feedback

* add buffer size checking

* use c_str

* dont log session key

* Try this for a mac definition

* be quiet apple
This commit is contained in:
David Markowitz
2026-06-07 20:59:11 -07:00
committed by GitHub
parent f6c9a27a2b
commit a156a8fcba
109 changed files with 806 additions and 514 deletions

View File

@@ -80,7 +80,7 @@ else() # Build from source
-DWITH_EXTERNAL_ZLIB=ON -DWITH_EXTERNAL_ZLIB=ON
-DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR} -DOPENSSL_ROOT_DIR=${OPENSSL_ROOT_DIR}
-DCMAKE_C_FLAGS=-w # disable zlib warnings -DCMAKE_C_FLAGS=-w # disable zlib warnings
-DCMAKE_CXX_FLAGS=-D_GLIBCXX_USE_CXX11_ABI=0\ -include\ cstdint) -DCMAKE_CXX_FLAGS=-D_GLIBCXX_USE_CXX11_ABI=0\ -Wno-inconsistent-missing-override\ -include\ cstdint)
else() else()
set(MARIADB_EXTRA_CMAKE_ARGS set(MARIADB_EXTRA_CMAKE_ARGS
-DCMAKE_C_FLAGS=-w # disable zlib warnings -DCMAKE_C_FLAGS=-w # disable zlib warnings

View File

@@ -3,6 +3,7 @@
#include "MessageType/Chat.h" #include "MessageType/Chat.h"
#include "BitStreamUtils.h" #include "BitStreamUtils.h"
#include "Game.h" #include "Game.h"
#include "dConfig.h"
#include "Logger.h" #include "Logger.h"
#include "eObjectBits.h" #include "eObjectBits.h"
@@ -72,8 +73,8 @@ void ChatIgnoreList::AddIgnore(Packet* packet) {
return; return;
} }
constexpr int32_t MAX_IGNORES = 32; const int32_t MAX_IGNORES = Game::config->GetValue("max_ignores", 32);
if (receiver.ignoredPlayers.size() > MAX_IGNORES) { if (receiver.ignoredPlayers.size() >= MAX_IGNORES) {
LOG_DEBUG("Player %llu has too many ignores", playerId); LOG_DEBUG("Player %llu has too many ignores", playerId);
return; return;
} }

View File

@@ -435,6 +435,11 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
inStream.IgnoreBytes(4); inStream.IgnoreBytes(4);
inStream.Read(channel); inStream.Read(channel);
inStream.Read(size); inStream.Read(size);
if (size > MAX_MESSAGE_LENGTH) {
LOG("Received a probably spoofed chat message, ignoring msg");
return;
}
inStream.IgnoreBytes(77); inStream.IgnoreBytes(77);
LUWString message(size); LUWString message(size);
@@ -479,6 +484,11 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
if (channel != eChatChannel::PRIVATE_CHAT) LOG("WARNING: Received Private chat with the wrong channel!"); if (channel != eChatChannel::PRIVATE_CHAT) LOG("WARNING: Received Private chat with the wrong channel!");
inStream.Read(size); inStream.Read(size);
if (size > MAX_MESSAGE_LENGTH) {
LOG("Received a probably spoofed chat message, ignoring msg");
return;
}
inStream.IgnoreBytes(77); inStream.IgnoreBytes(77);
inStream.Read(LUReceiverName); inStream.Read(LUReceiverName);

View File

@@ -176,6 +176,7 @@ LWOOBJID PlayerContainer::GetId(const std::u16string& playerName) {
return toReturn; return toReturn;
} }
// TODO Make this a pointer again or do something to make it so you cant edit the LWOOBJID_EMPTY entry? it should be ignored in all cases anyways though...
PlayerData& PlayerContainer::GetPlayerDataMutable(const LWOOBJID& playerID) { PlayerData& PlayerContainer::GetPlayerDataMutable(const LWOOBJID& playerID) {
return m_Players.contains(playerID) ? m_Players[playerID] : m_Players[LWOOBJID_EMPTY]; return m_Players.contains(playerID) ? m_Players[playerID] : m_Players[LWOOBJID_EMPTY];
} }

View File

@@ -3,6 +3,7 @@
#include <stdexcept> #include <stdexcept>
#include "Amf3.h" #include "Amf3.h"
#include "StringifiedEnum.h"
/** /**
* AMF3 Reference document https://rtmp.veriskope.com/pdf/amf3-file-format-spec.pdf * AMF3 Reference document https://rtmp.veriskope.com/pdf/amf3-file-format-spec.pdf
@@ -53,7 +54,7 @@ std::unique_ptr<AMFBaseValue> AMFDeserialize::Read(RakNet::BitStream& inStream)
case eAmf::VectorObject: case eAmf::VectorObject:
[[fallthrough]]; [[fallthrough]];
case eAmf::Dictionary: case eAmf::Dictionary:
throw marker; throw std::invalid_argument(StringifiedEnum::ToString(marker).data());
default: default:
throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast<int32_t>(marker))); throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast<int32_t>(marker)));
} }
@@ -88,6 +89,11 @@ const std::string AMFDeserialize::ReadString(RakNet::BitStream& inStream) {
// Right shift by 1 bit to get index if reference or size of next string if value // Right shift by 1 bit to get index if reference or size of next string if value
length = length >> 1; length = length >> 1;
if (isReference) { if (isReference) {
constexpr int32_t maxStringSize = 1024 * 1024;
if (length > maxStringSize) {
LOG("1MB string attempted to be allocated in AMF deserialize, possible spoof, aborting deserialize.");
throw std::invalid_argument("1MB string attempted to be allocated in AMF deserialize, possible spoof, aborting deserialize.");
}
std::string value(length, 0); std::string value(length, 0);
inStream.Read(&value[0], length); inStream.Read(&value[0], length);
// Empty strings are never sent by reference // Empty strings are never sent by reference
@@ -117,6 +123,12 @@ std::unique_ptr<AMFArrayValue> AMFDeserialize::ReadAmfArray(RakNet::BitStream& i
if (key.size() == 0) break; if (key.size() == 0) break;
arrayValue->Insert(key, Read(inStream)); arrayValue->Insert(key, Read(inStream));
} }
constexpr int32_t maxArraySize = 10'000;
if (sizeOfDenseArray > maxArraySize) {
LOG("Someone sent 10,000 dense array entries, probably a bad packet.");
throw std::invalid_argument("Someone sent 10,000 dense array entries, probably a bad packet.");
}
// Finally read dense portion // Finally read dense portion
for (uint32_t i = 0; i < sizeOfDenseArray; i++) { for (uint32_t i = 0; i < sizeOfDenseArray; i++) {
arrayValue->Insert(i, Read(inStream)); arrayValue->Insert(i, Read(inStream));

View File

@@ -52,8 +52,7 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
if (actualUncompressedSize != -1) { if (actualUncompressedSize != -1) {
uint32_t previousSize = completeUncompressedModel.size(); uint32_t previousSize = completeUncompressedModel.size();
completeUncompressedModel.append(reinterpret_cast<char*>(uncompressedChunk.get())); completeUncompressedModel.append(reinterpret_cast<char*>(uncompressedChunk.get()), actualUncompressedSize);
completeUncompressedModel.resize(previousSize + actualUncompressedSize);
} else { } else {
LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, model.id, err); LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, model.id, err);
break; break;

View File

@@ -308,8 +308,9 @@ std::vector<std::string> GeneralUtils::GetSqlFileNamesFromFolder(const std::stri
for (const auto& t : std::filesystem::directory_iterator(folder)) { for (const auto& t : std::filesystem::directory_iterator(folder)) {
if (t.is_directory() || t.is_symlink()) continue; if (t.is_directory() || t.is_symlink()) continue;
auto filename = t.path().filename().string(); auto filename = t.path().filename().string();
const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0)); // Ensure the file has a name in the format of xxxxxxxx_anything_goes_here.sql
filenames.emplace(index, std::move(filename)); const auto migrationNumber = TryParse<uint32_t>(GeneralUtils::SplitString(filename, '_').at(0));
if (migrationNumber.has_value()) filenames.emplace(migrationNumber.value(), std::move(filename));
} }
// Now sort the map by the oldest migration. // Now sort the map by the oldest migration.

View File

@@ -205,6 +205,12 @@ namespace GeneralUtils {
return isParsed ? static_cast<T>(result) : std::optional<T>{}; return isParsed ? static_cast<T>(result) : std::optional<T>{};
} }
// A version of TryParse that will return `errorVal` if `str` failed to parse.
template <Numeric T>
[[nodiscard]] T TryParse(std::string_view str, const T errorVal) {
return TryParse<T>(str).value_or(errorVal);
}
template<typename T> template<typename T>
requires(!Numeric<T>) requires(!Numeric<T>)
[[nodiscard]] std::optional<T> TryParse(std::string_view str); [[nodiscard]] std::optional<T> TryParse(std::string_view str);
@@ -237,6 +243,12 @@ namespace GeneralUtils {
return std::nullopt; return std::nullopt;
} }
// A version of TryParse that will return `errorVal` if `str` failed to parse.
template <std::floating_point T>
[[nodiscard]] T TryParse(std::string_view str, const T errorVal) {
return TryParse<T>(str).value_or(errorVal);
}
#endif #endif
/** /**
@@ -258,6 +270,11 @@ namespace GeneralUtils {
return z ? std::make_optional<T>(x.value(), y.value(), z.value()) : std::nullopt; return z ? std::make_optional<T>(x.value(), y.value(), z.value()) : std::nullopt;
} }
// Alternative overload of TryParse with a default value
[[nodiscard]] inline NiPoint3 TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ, const NiPoint3 errorVal) {
return TryParse<NiPoint3>(strX, strY, strZ).value_or(errorVal);
}
/** /**
* The TryParse overload for handling NiPoint3 by passing a span of three strings * The TryParse overload for handling NiPoint3 by passing a span of three strings
* @param str The string vector representing the X, Y, and Z coordinates * @param str The string vector representing the X, Y, and Z coordinates
@@ -268,6 +285,11 @@ namespace GeneralUtils {
return (str.size() == 3) ? TryParse<T>(str[0], str[1], str[2]) : std::nullopt; return (str.size() == 3) ? TryParse<T>(str[0], str[1], str[2]) : std::nullopt;
} }
// Alternative overload of TryParse with a default value
[[nodiscard]] inline NiPoint3 TryParse(const std::span<const std::string> str, const NiPoint3 errorVal) {
return TryParse<NiPoint3>(str).value_or(errorVal);
}
template <typename T> template <typename T>
std::u16string to_u16string(const T value) { std::u16string to_u16string(const T value) {
return GeneralUtils::ASCIIToUTF16(std::to_string(value)); return GeneralUtils::ASCIIToUTF16(std::to_string(value));

View File

@@ -53,16 +53,21 @@ Lxfml::Result Lxfml::NormalizePositionOnlyFirstPart(const std::string_view data)
continue; continue;
} }
auto x = GeneralUtils::TryParse<float>(split[9]).value(); try {
auto y = GeneralUtils::TryParse<float>(split[10]).value(); auto x = GeneralUtils::TryParse<float>(split[9]).value();
auto z = GeneralUtils::TryParse<float>(split[11]).value(); auto y = GeneralUtils::TryParse<float>(split[10]).value();
if (x < lowest.x) lowest.x = x; auto z = GeneralUtils::TryParse<float>(split[11]).value();
if (y < lowest.y) lowest.y = y; if (x < lowest.x) lowest.x = x;
if (z < lowest.z) lowest.z = z; if (y < lowest.y) lowest.y = y;
if (z < lowest.z) lowest.z = z;
if (highest.x < x) highest.x = x; if (highest.x < x) highest.x = x;
if (highest.y < y) highest.y = y; if (highest.y < y) highest.y = y;
if (highest.z < z) highest.z = z; if (highest.z < z) highest.z = z;
} catch (std::exception& e) {
LOG("Failed to parse a split value of either (%s), (%s), or (%s).", split[9].c_str(), split[10].c_str(), split[11].c_str());
return toReturn; // Early return since we failed to parse this lxfml.
}
} }
auto delta = (highest - lowest) / 2.0f; auto delta = (highest - lowest) / 2.0f;

View File

@@ -45,6 +45,12 @@ Sd0::Sd0(std::istream& buffer) {
uint32_t bufferSize = buffer.tellg(); uint32_t bufferSize = buffer.tellg();
buffer.seekg(0, std::ios::beg); buffer.seekg(0, std::ios::beg);
WriteSize(firstChunk, bufferSize); WriteSize(firstChunk, bufferSize);
// its expected that if we got here, we got an old sd0 buffer where we ignored the sd0 part
// that means this can be at most the compressed chunk limit.
if (bufferSize > MAX_UNCOMPRESSED_CHUNK_SIZE) {
LOG("Possible bad chunk size of %i specified, rejecting.", bufferSize);
return;
}
firstChunk.resize(firstChunk.size() + bufferSize); firstChunk.resize(firstChunk.size() + bufferSize);
auto* dataStart = reinterpret_cast<char*>(firstChunk.data() + GetDataOffset(true)); auto* dataStart = reinterpret_cast<char*>(firstChunk.data() + GetDataOffset(true));
if (!buffer.read(dataStart, bufferSize)) { if (!buffer.read(dataStart, bufferSize)) {
@@ -71,6 +77,12 @@ Sd0::Sd0(std::istream& buffer) {
WriteSize(chunk, chunkSize); WriteSize(chunk, chunkSize);
// Assuming a good buffer that is large enough to take up 2 zlib buffers
// any buffer should be compressed enough to take up less size than its uncompressed counterpart
if (chunkSize > MAX_UNCOMPRESSED_CHUNK_SIZE) {
LOG("Possible bad chunk size of %i specified, rejecting.", chunkSize);
break;
}
chunk.resize(chunkSize + dataOffset); chunk.resize(chunkSize + dataOffset);
auto* dataStart = reinterpret_cast<char*>(chunk.data() + dataOffset); auto* dataStart = reinterpret_cast<char*>(chunk.data() + dataOffset);
if (!buffer.read(dataStart, chunkSize)) { if (!buffer.read(dataStart, chunkSize)) {
@@ -95,6 +107,11 @@ void Sd0::FromData(const uint8_t* data, size_t bufferSize) {
startOffset, numToCopy, startOffset, numToCopy,
compressedChunk.data(), compressedChunk.size()); compressedChunk.data(), compressedChunk.size());
if (compressedSize == -1) {
LOG("Failed to compress chunk, aborting");
break;
}
auto& chunk = m_Chunks.emplace_back(); auto& chunk = m_Chunks.emplace_back();
bool firstBuffer = m_Chunks.size() == 1; bool firstBuffer = m_Chunks.size() == 1;
auto dataOffset = GetDataOffset(firstBuffer); auto dataOffset = GetDataOffset(firstBuffer);
@@ -119,6 +136,12 @@ std::string Sd0::GetAsStringUncompressed() const {
auto dataOffset = GetDataOffset(first); auto dataOffset = GetDataOffset(first);
first = false; first = false;
const auto chunkSize = chunk.size(); const auto chunkSize = chunk.size();
if (chunkSize <= static_cast<size_t>(dataOffset)) {
LOG("Bad chunkSize for data, aborting");
toReturn = "";
totalSize = 0;
break;
}
auto oldSize = toReturn.size(); auto oldSize = toReturn.size();
toReturn.resize(oldSize + MAX_UNCOMPRESSED_CHUNK_SIZE); toReturn.resize(oldSize + MAX_UNCOMPRESSED_CHUNK_SIZE);
@@ -128,6 +151,13 @@ std::string Sd0::GetAsStringUncompressed() const {
reinterpret_cast<uint8_t*>(toReturn.data()) + oldSize, MAX_UNCOMPRESSED_CHUNK_SIZE, reinterpret_cast<uint8_t*>(toReturn.data()) + oldSize, MAX_UNCOMPRESSED_CHUNK_SIZE,
error); error);
if (uncompressedSize == -1) {
LOG("Failed to decompress chunk, aborting");
toReturn = "";
totalSize = 0;
break;
}
totalSize += uncompressedSize; totalSize += uncompressedSize;
} }

View File

@@ -3,12 +3,12 @@
#include "zlib.h" #include "zlib.h"
namespace ZCompression { namespace ZCompression {
int32_t GetMaxCompressedLength(int32_t nLenSrc) { uint32_t GetMaxCompressedLength(uint32_t nLenSrc) {
int32_t n16kBlocks = (nLenSrc + 16383) / 16384; // round up any fraction of a block uint32_t n16kBlocks = (nLenSrc + 16383) / 16384; // round up any fraction of a block
return (nLenSrc + 6 + (n16kBlocks * 5)); return (nLenSrc + 6 + (n16kBlocks * 5));
} }
int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst) { int32_t Compress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst) {
z_stream zInfo = { 0 }; z_stream zInfo = { 0 };
zInfo.total_in = zInfo.avail_in = nLenSrc; zInfo.total_in = zInfo.avail_in = nLenSrc;
zInfo.total_out = zInfo.avail_out = nLenDst; zInfo.total_out = zInfo.avail_out = nLenDst;
@@ -27,7 +27,7 @@ namespace ZCompression {
return(nRet); return(nRet);
} }
int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr) { int32_t Decompress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst, int32_t& nErr) {
// Get the size of the decompressed data // Get the size of the decompressed data
z_stream zInfo = { 0 }; z_stream zInfo = { 0 };
zInfo.total_in = zInfo.avail_in = nLenSrc; zInfo.total_in = zInfo.avail_in = nLenSrc;

View File

@@ -3,10 +3,10 @@
#include <cstdint> #include <cstdint>
namespace ZCompression { namespace ZCompression {
int32_t GetMaxCompressedLength(int32_t nLenSrc); uint32_t GetMaxCompressedLength(uint32_t nLenSrc);
int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst); int32_t Compress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst);
int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr); int32_t Decompress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst, int32_t& nErr);
} }

View File

@@ -7,7 +7,7 @@
#include "zlib.h" #include "zlib.h"
constexpr uint32_t CRC32_INIT = 0xFFFFFFFF; constexpr uint32_t CRC32_INIT = 0xFFFFFFFF;
constexpr auto NULL_TERMINATOR = std::string_view{"\0\0\0", 4}; constexpr auto NULL_TERMINATOR = std::string_view{ "\0\0\0", 4 };
AssetManager::AssetManager(const std::filesystem::path& path) { AssetManager::AssetManager(const std::filesystem::path& path) {
if (!std::filesystem::is_directory(path)) { if (!std::filesystem::is_directory(path)) {
@@ -25,7 +25,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
if (!std::filesystem::exists(m_Path / ".." / "versions")) { if (!std::filesystem::exists(m_Path / ".." / "versions")) {
throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded."); throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded.");
} }
m_AssetBundleType = eAssetBundleType::Packed; m_AssetBundleType = eAssetBundleType::Packed;
m_RootPath = (m_Path / ".."); m_RootPath = (m_Path / "..");
@@ -34,7 +34,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
if (!std::filesystem::exists(m_Path / ".." / ".." / "versions")) { if (!std::filesystem::exists(m_Path / ".." / ".." / "versions")) {
throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded."); throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded.");
} }
m_AssetBundleType = eAssetBundleType::Packed; m_AssetBundleType = eAssetBundleType::Packed;
m_RootPath = (m_Path / ".." / ".."); m_RootPath = (m_Path / ".." / "..");
@@ -54,15 +54,15 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
} }
switch (m_AssetBundleType) { switch (m_AssetBundleType) {
case eAssetBundleType::Packed: { case eAssetBundleType::Packed: {
this->LoadPackIndex(); this->LoadPackIndex();
break; break;
} }
case eAssetBundleType::None: case eAssetBundleType::None:
[[fallthrough]]; [[fallthrough]];
case eAssetBundleType::Unpacked: { case eAssetBundleType::Unpacked: {
break; break;
} }
} }
} }
@@ -79,7 +79,7 @@ bool AssetManager::HasFile(std::string fixedName) const {
std::replace(fixedName.begin(), fixedName.end(), '\\', '/'); std::replace(fixedName.begin(), fixedName.end(), '\\', '/');
if (std::filesystem::exists(m_ResPath / fixedName)) return true; if (std::filesystem::exists(m_ResPath / fixedName)) return true;
if (this->m_AssetBundleType == eAssetBundleType::Unpacked) return false; if (this->m_AssetBundleType == eAssetBundleType::Unpacked || !m_PackIndex) return false;
std::replace(fixedName.begin(), fixedName.end(), '/', '\\'); std::replace(fixedName.begin(), fixedName.end(), '/', '\\');
if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName; if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName;
@@ -145,8 +145,12 @@ bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) co
} }
const auto& pack = this->m_PackIndex->GetPacks().at(packIndex); const auto& pack = this->m_PackIndex->GetPacks().at(packIndex);
const bool success = pack.ReadFileFromPack(crc, data, len); bool success = false;
try {
success = pack.ReadFileFromPack(crc, data, len);
} catch (std::exception& e) {
LOG("Failed to read file %s from pack file", fixedName.c_str());
}
return success; return success;
} }

View File

@@ -46,6 +46,7 @@ bool Pack::HasFile(const uint32_t crc) const {
} }
bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) const { bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) const {
const auto pathStr = m_FilePath.string();
// Time for some wacky C file reading for speed reasons // Time for some wacky C file reading for speed reasons
PackRecord pkRecord{}; PackRecord pkRecord{};
@@ -65,16 +66,21 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons
bool isCompressed = (pkRecord.m_IsCompressed & 0xff) > 0; bool isCompressed = (pkRecord.m_IsCompressed & 0xff) > 0;
auto inPackSize = isCompressed ? pkRecord.m_CompressedSize : pkRecord.m_UncompressedSize; auto inPackSize = isCompressed ? pkRecord.m_CompressedSize : pkRecord.m_UncompressedSize;
FILE* file; FILE* file = nullptr;
#ifdef _WIN32 #ifdef _WIN32
fopen_s(&file, m_FilePath.string().c_str(), "rb"); fopen_s(&file, pathStr.c_str(), "rb");
#elif __APPLE__ #elif __APPLE__
// macOS has 64bit file IO by default // macOS has 64bit file IO by default
file = fopen(m_FilePath.string().c_str(), "rb"); file = fopen(pathStr.c_str(), "rb");
#else #else
file = fopen64(m_FilePath.string().c_str(), "rb"); file = fopen64(pathStr.c_str(), "rb");
#endif #endif
if (!file) {
LOG("No file found for path %s", pathStr.c_str());
throw std::runtime_error("Could not find file " + pathStr);
}
fseek(file, pos, SEEK_SET); fseek(file, pos, SEEK_SET);
if (!isCompressed) { if (!isCompressed) {
@@ -102,14 +108,18 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons
int32_t readInData = fread(&size, sizeof(uint32_t), 1, file); int32_t readInData = fread(&size, sizeof(uint32_t), 1, file);
pos += 4; // Move pointer position 4 to the right pos += 4; // Move pointer position 4 to the right
char* chunk = static_cast<char*>(malloc(size)); std::unique_ptr<char[]> chunk(new char[size]);
int32_t readInData2 = fread(chunk, sizeof(int8_t), size, file); int32_t readInData2 = fread(chunk.get(), sizeof(int8_t), size, file);
pos += size; // Move pointer position the amount of bytes read to the right pos += size; // Move pointer position the amount of bytes read to the right
int32_t err; int32_t err;
currentReadPos += ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err); const auto countToRead = ZCompression::Decompress(reinterpret_cast<uint8_t*>(chunk.get()), size, reinterpret_cast<uint8_t*>(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err);
if (countToRead == -1) {
LOG("Error decompressing zlib data from file %s", pathStr.c_str());
throw std::runtime_error("Error decompressing zlib data from file " + pathStr);
}
currentReadPos += countToRead;
free(chunk);
} }
*data = decompressedData; *data = decompressedData;

View File

@@ -84,3 +84,7 @@ void dConfig::ProcessLine(const std::string& line) {
this->m_ConfigValues.insert(std::make_pair(key, value)); this->m_ConfigValues.insert(std::make_pair(key, value));
} }
std::string dConfig::GetValue(const std::string& key, const char* emptyValue) {
return GetValue(key, std::string(emptyValue));
};

View File

@@ -5,6 +5,8 @@
#include <map> #include <map>
#include <string> #include <string>
#include "GeneralUtils.h"
class dConfig { class dConfig {
public: public:
dConfig(const std::string& filepath); dConfig(const std::string& filepath);
@@ -22,6 +24,14 @@ public:
*/ */
const std::string& GetValue(std::string key); const std::string& GetValue(std::string key);
// Gets a value from the config and returns the parsed value, or the default value should parsing have failed.
template<typename T>
T GetValue(const std::string& key, const T emptyValue = T()) {
return GeneralUtils::TryParse<T>(GetValue(key)).value_or(emptyValue);
}
std::string GetValue(const std::string& key, const char* emptyValue);
/** /**
* Loads the config from a file * Loads the config from a file
*/ */
@@ -43,3 +53,9 @@ private:
std::vector<std::function<void()>> m_ConfigHandlers; std::vector<std::function<void()>> m_ConfigHandlers;
std::string m_ConfigFilePath; std::string m_ConfigFilePath;
}; };
template<>
inline std::string dConfig::GetValue(const std::string& key, const std::string emptyValue) {
const auto& value = GetValue(key);
return value.empty() ? emptyValue : value;
};

View File

@@ -16,8 +16,8 @@
// These are the same define, but they mean two different things in different contexts // These are the same define, but they mean two different things in different contexts
// so a different define to distinguish what calculation is happening will help clarity. // so a different define to distinguish what calculation is happening will help clarity.
#define FRAMES_TO_MS(x) 1000 / x #define FRAMES_TO_MS(x) (1000 / (x))
#define MS_TO_FRAMES(x) 1000 / x #define MS_TO_FRAMES(x) (1000 / (x))
//=========== FRAME TIMINGS =========== //=========== FRAME TIMINGS ===========
constexpr uint32_t highFramerate = 60; constexpr uint32_t highFramerate = 60;

View File

@@ -154,8 +154,8 @@ std::map<LOT, uint32_t> CDItemComponentTable::ParseCraftingCurrencies(const CDIt
// Checking for 2 here, not sure what to do when there's more stuff than expected // Checking for 2 here, not sure what to do when there's more stuff than expected
if (amountSplit.size() == 2) { if (amountSplit.size() == 2) {
currencies.insert({ currencies.insert({
std::stoull(amountSplit[0]), GeneralUtils::TryParse<LOT>(amountSplit[0], LOT_NULL),
std::stoi(amountSplit[1]) GeneralUtils::TryParse<uint32_t>(amountSplit[1], 0)
}); });
} }
} }

View File

@@ -93,13 +93,14 @@ std::vector<CDMissions> CDMissionsTable::Query(std::function<bool(CDMissions)> p
} }
const CDMissions* CDMissionsTable::GetPtrByMissionID(uint32_t missionID) const { const CDMissions* CDMissionsTable::GetPtrByMissionID(uint32_t missionID) const {
const CDMissions* toReturn = &Default;
for (const auto& entry : GetEntries()) { for (const auto& entry : GetEntries()) {
if (entry.id == missionID) { if (entry.id == missionID) {
return const_cast<CDMissions*>(&entry); toReturn = &entry;
} }
} }
return &Default; return toReturn;
} }
const CDMissions& CDMissionsTable::GetByMissionID(uint32_t missionID, bool& found) const { const CDMissions& CDMissionsTable::GetByMissionID(uint32_t missionID, bool& found) const {

View File

@@ -66,6 +66,7 @@ public:
// Queries the table with a custom "where" clause // Queries the table with a custom "where" clause
std::vector<CDMissions> Query(std::function<bool(CDMissions)> predicate); std::vector<CDMissions> Query(std::function<bool(CDMissions)> predicate);
// Cannot be null.
const CDMissions* GetPtrByMissionID(uint32_t missionID) const; const CDMissions* GetPtrByMissionID(uint32_t missionID) const;
const CDMissions& GetByMissionID(uint32_t missionID, bool& found) const; const CDMissions& GetByMissionID(uint32_t missionID, bool& found) const;

View File

@@ -69,7 +69,7 @@ const CDObjects& CDObjectsTable::GetByID(const uint32_t lot) {
entry.name = tableData.getStringField("name", ""); entry.name = tableData.getStringField("name", "");
UNUSED(entry.placeable = tableData.getIntField("placeable", -1)); UNUSED(entry.placeable = tableData.getIntField("placeable", -1));
entry.type = tableData.getStringField("type", ""); entry.type = tableData.getStringField("type", "");
UNUSED(ntry.description = tableData.getStringField(4, "")); UNUSED(entry.description = tableData.getStringField(4, ""));
UNUSED(entry.localize = tableData.getIntField("localize", -1)); UNUSED(entry.localize = tableData.getIntField("localize", -1));
UNUSED(entry.npcTemplateID = tableData.getIntField("npcTemplateID", -1)); UNUSED(entry.npcTemplateID = tableData.getIntField("npcTemplateID", -1));
UNUSED(entry.displayName = tableData.getStringField("displayName", "")); UNUSED(entry.displayName = tableData.getStringField("displayName", ""));

View File

@@ -56,7 +56,7 @@ CDRailActivatorComponent CDRailActivatorComponentTable::GetEntryByID(int32_t id)
std::pair<uint32_t, std::u16string> CDRailActivatorComponentTable::EffectPairFromString(std::string& str) { std::pair<uint32_t, std::u16string> CDRailActivatorComponentTable::EffectPairFromString(std::string& str) {
const auto split = GeneralUtils::SplitString(str, ':'); const auto split = GeneralUtils::SplitString(str, ':');
if (split.size() == 2) { if (split.size() == 2) {
return { std::stoi(split.at(0)), GeneralUtils::ASCIIToUTF16(split.at(1)) }; return { GeneralUtils::TryParse(split.at(0), 0), GeneralUtils::ASCIIToUTF16(split.at(1)) };
} }
return {}; return {};

View File

@@ -34,6 +34,7 @@ public:
}; };
struct PropertyEntranceResult { struct PropertyEntranceResult {
// This is the number of entries that are in the query IF it were ran without a limit.
int32_t totalEntriesMatchingQuery{}; int32_t totalEntriesMatchingQuery{};
// The entries that match the query. This should only contain up to 12 entries. // The entries that match the query. This should only contain up to 12 entries.
std::vector<IProperty::Info> entries; std::vector<IProperty::Info> entries;
@@ -48,7 +49,7 @@ public:
// Get the properties for the given property lookup params. // Get the properties for the given property lookup params.
// This is expected to return a result set of up to 12 properties // This is expected to return a result set of up to 12 properties
// so as not to transfer too much data at once. // so as not to transfer too much data at once.
virtual std::optional<IProperty::PropertyEntranceResult> GetProperties(const PropertyLookup& params) = 0; virtual IProperty::PropertyEntranceResult GetProperties(const PropertyLookup& params) = 0;
// Update the property moderation info for the given property id. // Update the property moderation info for the given property id.
virtual void UpdatePropertyModerationInfo(const IProperty::Info& info) = 0; virtual void UpdatePropertyModerationInfo(const IProperty::Info& info) = 0;

View File

@@ -127,7 +127,7 @@ public:
std::string GetBehavior(const LWOOBJID behaviorId) override; std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID characterId) override; void RemoveBehavior(const LWOOBJID characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override; IProperty::PropertyEntranceResult GetProperties(const IProperty::PropertyLookup& params) override;
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override; std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override; std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override;
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override; std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override;

View File

@@ -48,7 +48,7 @@ std::vector<MailInfo> MySQLDatabase::GetMailForPlayer(const LWOOBJID characterId
} }
std::optional<MailInfo> MySQLDatabase::GetMail(const uint64_t mailId) { std::optional<MailInfo> MySQLDatabase::GetMail(const uint64_t mailId) {
auto res = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId); auto res = ExecuteSelect("SELECT attachment_lot, attachment_count, receiver_id FROM mail WHERE id=? LIMIT 1;", mailId);
if (!res->next()) { if (!res->next()) {
return std::nullopt; return std::nullopt;
@@ -57,6 +57,7 @@ std::optional<MailInfo> MySQLDatabase::GetMail(const uint64_t mailId) {
MailInfo toReturn; MailInfo toReturn;
toReturn.itemLOT = res->getInt("attachment_lot"); toReturn.itemLOT = res->getInt("attachment_lot");
toReturn.itemCount = res->getInt("attachment_count"); toReturn.itemCount = res->getInt("attachment_count");
toReturn.receiverId = res->getUInt64("receiver_id");
return toReturn; return toReturn;
} }

View File

@@ -18,8 +18,8 @@ IProperty::Info ReadPropertyInfo(PreparedStmtResultSet& result) {
return info; return info;
} }
std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) { IProperty::PropertyEntranceResult MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) {
std::optional<IProperty::PropertyEntranceResult> result; IProperty::PropertyEntranceResult result;
std::string query; std::string query;
PreparedStmtResultSet properties; PreparedStmtResultSet properties;
@@ -73,8 +73,7 @@ std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(co
params.playerId params.playerId
); );
if (count->next()) { if (count->next()) {
if (!result) result = IProperty::PropertyEntranceResult(); result.totalEntriesMatchingQuery = count->getUInt("count");
result->totalEntriesMatchingQuery = count->getUInt("count");
} }
} else { } else {
if (params.sortChoice == SORT_TYPE_REPUTATION) { if (params.sortChoice == SORT_TYPE_REPUTATION) {
@@ -127,14 +126,12 @@ std::optional<IProperty::PropertyEntranceResult> MySQLDatabase::GetProperties(co
params.playerSort params.playerSort
); );
if (count->next()) { if (count->next()) {
if (!result) result = IProperty::PropertyEntranceResult(); result.totalEntriesMatchingQuery = count->getUInt("count");
result->totalEntriesMatchingQuery = count->getUInt("count");
} }
} }
while (properties->next()) { while (properties->next()) {
if (!result) result = IProperty::PropertyEntranceResult(); result.entries.push_back(ReadPropertyInfo(properties));
result->entries.push_back(ReadPropertyInfo(properties));
} }
return result; return result;

View File

@@ -111,7 +111,7 @@ public:
std::string GetBehavior(const LWOOBJID behaviorId) override; std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID characterId) override; void RemoveBehavior(const LWOOBJID characterId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override; IProperty::PropertyEntranceResult GetProperties(const IProperty::PropertyLookup& params) override;
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override; std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override;
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override; std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override;
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override; std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override;
@@ -170,91 +170,91 @@ private:
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const std::string_view param) { inline void SetParam(PreppedStmtRef stmt, const int index, const std::string_view param) {
LOG("%s", param.data()); LOG_DEBUG("%s", param.data());
stmt.bind(index, param.data()); stmt.bind(index, param.data());
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const char* param) { inline void SetParam(PreppedStmtRef stmt, const int index, const char* param) {
LOG("%s", param); LOG_DEBUG("%s", param);
stmt.bind(index, param); stmt.bind(index, param);
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const std::string param) { inline void SetParam(PreppedStmtRef stmt, const int index, const std::string param) {
LOG("%s", param.c_str()); LOG_DEBUG("%s", param.c_str());
stmt.bind(index, param.c_str()); stmt.bind(index, param.c_str());
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const int8_t param) { inline void SetParam(PreppedStmtRef stmt, const int index, const int8_t param) {
LOG("%u", param); LOG_DEBUG("%u", param);
stmt.bind(index, param); stmt.bind(index, param);
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const uint8_t param) { inline void SetParam(PreppedStmtRef stmt, const int index, const uint8_t param) {
LOG("%d", param); LOG_DEBUG("%d", param);
stmt.bind(index, param); stmt.bind(index, param);
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const int16_t param) { inline void SetParam(PreppedStmtRef stmt, const int index, const int16_t param) {
LOG("%u", param); LOG_DEBUG("%u", param);
stmt.bind(index, param); stmt.bind(index, param);
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const uint16_t param) { inline void SetParam(PreppedStmtRef stmt, const int index, const uint16_t param) {
LOG("%d", param); LOG_DEBUG("%d", param);
stmt.bind(index, param); stmt.bind(index, param);
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const uint32_t param) { inline void SetParam(PreppedStmtRef stmt, const int index, const uint32_t param) {
LOG("%u", param); LOG_DEBUG("%u", param);
stmt.bind(index, static_cast<int32_t>(param)); stmt.bind(index, static_cast<int32_t>(param));
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const int32_t param) { inline void SetParam(PreppedStmtRef stmt, const int index, const int32_t param) {
LOG("%d", param); LOG_DEBUG("%d", param);
stmt.bind(index, param); stmt.bind(index, param);
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const int64_t param) { inline void SetParam(PreppedStmtRef stmt, const int index, const int64_t param) {
LOG("%llu", param); LOG_DEBUG("%llu", param);
stmt.bind(index, static_cast<sqlite_int64>(param)); stmt.bind(index, static_cast<sqlite_int64>(param));
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const uint64_t param) { inline void SetParam(PreppedStmtRef stmt, const int index, const uint64_t param) {
LOG("%llu", param); LOG_DEBUG("%llu", param);
stmt.bind(index, static_cast<sqlite_int64>(param)); stmt.bind(index, static_cast<sqlite_int64>(param));
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const float param) { inline void SetParam(PreppedStmtRef stmt, const int index, const float param) {
LOG("%f", param); LOG_DEBUG("%f", param);
stmt.bind(index, param); stmt.bind(index, param);
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const double param) { inline void SetParam(PreppedStmtRef stmt, const int index, const double param) {
LOG("%f", param); LOG_DEBUG("%f", param);
stmt.bind(index, param); stmt.bind(index, param);
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const bool param) { inline void SetParam(PreppedStmtRef stmt, const int index, const bool param) {
LOG("%d", param); LOG_DEBUG("%d", param);
stmt.bind(index, param); stmt.bind(index, param);
} }
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const std::istream* param) { inline void SetParam(PreppedStmtRef stmt, const int index, const std::istream* param) {
LOG("Blob"); LOG_DEBUG("Blob");
// This is the one time you will ever see me use const_cast. // This is the one time you will ever see me use const_cast.
std::stringstream stream; std::stringstream stream;
stream << param->rdbuf(); stream << param->rdbuf();
@@ -264,10 +264,10 @@ inline void SetParam(PreppedStmtRef stmt, const int index, const std::istream* p
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional<uint32_t> param) { inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional<uint32_t> param) {
if (param) { if (param) {
LOG("%d", param.value()); LOG_DEBUG("%d", param.value());
stmt.bind(index, static_cast<int>(param.value())); stmt.bind(index, static_cast<int>(param.value()));
} else { } else {
LOG("Null"); LOG_DEBUG("Null");
stmt.bindNull(index); stmt.bindNull(index);
} }
} }
@@ -275,10 +275,10 @@ inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional<u
template<> template<>
inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional<LWOOBJID> param) { inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional<LWOOBJID> param) {
if (param) { if (param) {
LOG("%d", param.value()); LOG_DEBUG("%d", param.value());
stmt.bind(index, static_cast<sqlite_int64>(param.value())); stmt.bind(index, static_cast<sqlite_int64>(param.value()));
} else { } else {
LOG("Null"); LOG_DEBUG("Null");
stmt.bindNull(index); stmt.bindNull(index);
} }
} }

View File

@@ -47,7 +47,7 @@ std::vector<MailInfo> SQLiteDatabase::GetMailForPlayer(const LWOOBJID characterI
} }
std::optional<MailInfo> SQLiteDatabase::GetMail(const uint64_t mailId) { std::optional<MailInfo> SQLiteDatabase::GetMail(const uint64_t mailId) {
auto [_, res] = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId); auto [_, res] = ExecuteSelect("SELECT attachment_lot, attachment_count, receiver_id FROM mail WHERE id=? LIMIT 1;", mailId);
if (res.eof()) { if (res.eof()) {
return std::nullopt; return std::nullopt;
@@ -56,6 +56,7 @@ std::optional<MailInfo> SQLiteDatabase::GetMail(const uint64_t mailId) {
MailInfo toReturn; MailInfo toReturn;
toReturn.itemLOT = res.getIntField("attachment_lot"); toReturn.itemLOT = res.getIntField("attachment_lot");
toReturn.itemCount = res.getIntField("attachment_count"); toReturn.itemCount = res.getIntField("attachment_count");
toReturn.receiverId = res.getInt64Field("receiver_id");
return toReturn; return toReturn;
} }

View File

@@ -18,8 +18,8 @@ IProperty::Info ReadPropertyInfo(CppSQLite3Query& propertyEntry) {
return toReturn; return toReturn;
} }
std::optional<IProperty::PropertyEntranceResult> SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) { IProperty::PropertyEntranceResult SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) {
std::optional<IProperty::PropertyEntranceResult> result; IProperty::PropertyEntranceResult result;
std::string query; std::string query;
std::pair<CppSQLite3Statement, CppSQLite3Query> propertiesRes; std::pair<CppSQLite3Statement, CppSQLite3Query> propertiesRes;
@@ -73,8 +73,7 @@ std::optional<IProperty::PropertyEntranceResult> SQLiteDatabase::GetProperties(c
params.playerId params.playerId
); );
if (!count.eof()) { if (!count.eof()) {
result = IProperty::PropertyEntranceResult(); result.totalEntriesMatchingQuery = count.getIntField("count");
result->totalEntriesMatchingQuery = count.getIntField("count");
} }
} else { } else {
if (params.sortChoice == SORT_TYPE_REPUTATION) { if (params.sortChoice == SORT_TYPE_REPUTATION) {
@@ -127,15 +126,13 @@ std::optional<IProperty::PropertyEntranceResult> SQLiteDatabase::GetProperties(c
params.playerSort params.playerSort
); );
if (!count.eof()) { if (!count.eof()) {
result = IProperty::PropertyEntranceResult(); result.totalEntriesMatchingQuery = count.getIntField("count");
result->totalEntriesMatchingQuery = count.getIntField("count");
} }
} }
auto& [_, properties] = propertiesRes; auto& [_, properties] = propertiesRes;
if (!properties.eof() && !result.has_value()) result = IProperty::PropertyEntranceResult();
while (!properties.eof()) { while (!properties.eof()) {
result->entries.push_back(ReadPropertyInfo(properties)); result.entries.push_back(ReadPropertyInfo(properties));
properties.nextRow(); properties.nextRow();
} }

View File

@@ -90,7 +90,7 @@ class TestSQLDatabase : public GameDatabase {
std::string GetBehavior(const LWOOBJID behaviorId) override; std::string GetBehavior(const LWOOBJID behaviorId) override;
void RemoveBehavior(const LWOOBJID behaviorId) override; void RemoveBehavior(const LWOOBJID behaviorId) override;
void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override;
std::optional<IProperty::PropertyEntranceResult> GetProperties(const IProperty::PropertyLookup& params) override { return {}; }; IProperty::PropertyEntranceResult GetProperties(const IProperty::PropertyLookup& params) override { return {}; };
std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override { return {}; }; std::vector<ILeaderboard::Entry> GetDescendingLeaderboard(const uint32_t activityId) override { return {}; };
std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override { return {}; }; std::vector<ILeaderboard::Entry> GetAscendingLeaderboard(const uint32_t activityId) override { return {}; };
std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override { return {}; }; std::vector<ILeaderboard::Entry> GetNsLeaderboard(const uint32_t activityId) override { return {}; };

View File

@@ -112,6 +112,7 @@ public:
uint16_t GetNetworkId() const; uint16_t GetNetworkId() const;
// Cannot return nullptr.
Entity* GetOwner() const; Entity* GetOwner() const;
const NiPoint3& GetDefaultPosition() const; const NiPoint3& GetDefaultPosition() const;

View File

@@ -30,6 +30,21 @@ void AirMovementBehavior::Sync(BehaviorContext* context, RakNet::BitStream& bitS
return; return;
} }
// So a player can't send an arbitrary behaviorID in a modified client and cast any behavior on any air behavior
Behavior* toSync = nullptr;
if (m_GroundAction->GetBehaviorID() == behaviorId) {
toSync = m_GroundAction;
} else if (m_HitAction->GetBehaviorID() == behaviorId) {
toSync = m_HitAction;
} else if (m_HitActionEnemy->GetBehaviorID() == behaviorId) {
toSync = m_HitActionEnemy;
} else if (m_TimeoutAction->GetBehaviorID() == behaviorId) {
toSync = m_TimeoutAction;
} else {
LOG("Invalid Air Movement Behavior sync for behaviorID %i on behavior %i", behaviorId, m_behaviorId);
return;
}
LWOOBJID target{}; LWOOBJID target{};
if (!bitStream.Read(target)) { if (!bitStream.Read(target)) {
@@ -37,15 +52,17 @@ void AirMovementBehavior::Sync(BehaviorContext* context, RakNet::BitStream& bitS
return; return;
} }
auto* behavior = CreateBehavior(behaviorId);
if (Game::entityManager->GetEntity(target) != nullptr) { if (Game::entityManager->GetEntity(target) != nullptr) {
branch.target = target; branch.target = target;
} }
behavior->Handle(context, bitStream, branch); toSync->Handle(context, bitStream, branch);
} }
void AirMovementBehavior::Load() { void AirMovementBehavior::Load() {
this->m_Timeout = (GetFloat("timeout_ms") / 1000.0f); m_Timeout = (GetFloat("timeout_ms") / 1000.0f);
m_GroundAction = GetAction("ground_action");
m_HitAction = GetAction("hit_action");
m_HitActionEnemy = GetAction("hit_action_enemy");
m_TimeoutAction = GetAction("timeout_action");
} }

View File

@@ -15,4 +15,9 @@ public:
void Load() override; void Load() override;
private: private:
float m_Timeout; float m_Timeout;
Behavior* m_GroundAction{};
Behavior* m_HitAction{};
Behavior* m_HitActionEnemy{};
Behavior* m_TimeoutAction{};
}; };

View File

@@ -42,6 +42,7 @@ void AreaOfEffectBehavior::Handle(BehaviorContext* context, RakNet::BitStream& b
LWOOBJID target{}; LWOOBJID target{};
if (!bitStream.Read(target)) { if (!bitStream.Read(target)) {
LOG("failed to read in target %i from bitStream, aborting target Handle!", i); LOG("failed to read in target %i from bitStream, aborting target Handle!", i);
continue;
}; };
targets.push_back(target); targets.push_back(target);
} }

View File

@@ -68,7 +68,7 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit
} }
if (isBlocked) { if (isBlocked) {
destroyableComponent->SetAttacksToBlock(std::min(destroyableComponent->GetAttacksToBlock() - 1, 0U)); destroyableComponent->SetAttacksToBlock(std::max<int32_t>(static_cast<int32_t>(destroyableComponent->GetAttacksToBlock() - 1), 0));
Game::entityManager->SerializeEntity(targetEntity); Game::entityManager->SerializeEntity(targetEntity);
this->m_OnFailBlocked->Handle(context, bitStream, branch); this->m_OnFailBlocked->Handle(context, bitStream, branch);
return; return;
@@ -103,9 +103,10 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit
return; return;
} }
uint32_t totalDamageDealt = armorDamageDealt + healthDamageDealt; uint64_t totalDamageDealt = armorDamageDealt + healthDamageDealt;
// A value that's too large may be a cheating attempt, so we set it to MIN // A value that's too large may be a cheating attempt, so we set it to MIN
// Can't overflow here either because should we somehow get to a 64 bit number it'll be clamped to a sane value.
if (totalDamageDealt > this->m_MaxDamage) { if (totalDamageDealt > this->m_MaxDamage) {
totalDamageDealt = this->m_MinDamage; totalDamageDealt = this->m_MinDamage;
} }

View File

@@ -48,15 +48,13 @@ void BlockBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branc
return; return;
} }
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>(); auto* const destroyableComponent = entity->GetComponent<DestroyableComponent>();
destroyableComponent->SetAttacksToBlock(this->m_numAttacksCanBlock); if (destroyableComponent) {
// ??? what is going on here?
if (destroyableComponent == nullptr) { destroyableComponent->SetAttacksToBlock(this->m_numAttacksCanBlock);
return; destroyableComponent->SetAttacksToBlock(0);
} }
destroyableComponent->SetAttacksToBlock(0);
} }
void BlockBehavior::Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) { void BlockBehavior::Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) {

View File

@@ -11,6 +11,11 @@ void ChainBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bitStrea
return; return;
} }
if (chainIndex == 0) {
LOG("Received invalid chain index of 0 for behavior %i.", m_behaviorId);
return;
}
chainIndex--; chainIndex--;
if (chainIndex < this->m_behaviors.size()) { if (chainIndex < this->m_behaviors.size()) {

View File

@@ -7,6 +7,7 @@
void JetPackBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bit_stream, const BehaviorBranchContext branch) { void JetPackBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bit_stream, const BehaviorBranchContext branch) {
auto* entity = Game::entityManager->GetEntity(branch.target); auto* entity = Game::entityManager->GetEntity(branch.target);
if (!entity) return;
GameMessages::SendSetJetPackMode(entity, true, this->m_BypassChecks, this->m_EnableHover, this->m_effectId, this->m_Airspeed, this->m_MaxAirspeed, this->m_VerticalVelocity, this->m_WarningEffectID); GameMessages::SendSetJetPackMode(entity, true, this->m_BypassChecks, this->m_EnableHover, this->m_effectId, this->m_Airspeed, this->m_MaxAirspeed, this->m_VerticalVelocity, this->m_WarningEffectID);
@@ -21,6 +22,7 @@ void JetPackBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bit_st
void JetPackBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branch) { void JetPackBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branch) {
auto* entity = Game::entityManager->GetEntity(branch.target); auto* entity = Game::entityManager->GetEntity(branch.target);
if (!entity) return;
GameMessages::SendSetJetPackMode(entity, false); GameMessages::SendSetJetPackMode(entity, false);

View File

@@ -17,6 +17,11 @@ void SwitchMultipleBehavior::Handle(BehaviorContext* context, RakNet::BitStream&
return; return;
}; };
if (m_behaviors.empty()) {
LOG("No behaviors were loaded for %i, aborting call.", m_behaviorId);
return;
}
uint32_t trigger = 0; uint32_t trigger = 0;
for (unsigned int i = 0; i < this->m_behaviors.size(); i++) { for (unsigned int i = 0; i < this->m_behaviors.size(); i++) {

View File

@@ -478,6 +478,7 @@ std::vector<LWOOBJID> BaseCombatAIComponent::GetTargetWithinAggroRange() const {
for (auto id : m_Parent->GetTargetsInPhantom()) { for (auto id : m_Parent->GetTargetsInPhantom()) {
auto* other = Game::entityManager->GetEntity(id); auto* other = Game::entityManager->GetEntity(id);
if (!other) continue;
const auto distance = Vector3::DistanceSquared(m_Parent->GetPosition(), other->GetPosition()); const auto distance = Vector3::DistanceSquared(m_Parent->GetPosition(), other->GetPosition());

View File

@@ -450,19 +450,10 @@ const std::vector<BuffParameter>& BuffComponent::GetBuffParameters(int32_t buffI
param.value = result.getFloatField("NumberValue"); param.value = result.getFloatField("NumberValue");
param.effectId = result.getIntField("EffectID"); param.effectId = result.getIntField("EffectID");
if (!result.fieldIsNull("StringValue")) { for (const auto& str : GeneralUtils::SplitString(result.getStringField("StringValue"), ',')) {
std::istringstream stream(result.getStringField("StringValue")); if (str.empty()) continue;
std::string token; const auto value = GeneralUtils::TryParse<float>(str);
if (value) param.values.push_back(value.value());
while (std::getline(stream, token, ',')) {
try {
const auto value = std::stof(token);
param.values.push_back(value);
} catch (std::invalid_argument& exception) {
LOG("Failed to parse value (%s): (%s)!", token.c_str(), exception.what());
}
}
} }
parameters.push_back(param); parameters.push_back(param);

View File

@@ -797,8 +797,14 @@ std::string CharacterComponent::StatisticsToString() const {
return result.str(); return result.str();
} }
uint64_t CharacterComponent::GetStatisticFromSplit(std::vector<std::string> split, uint32_t index) { uint64_t CharacterComponent::GetStatisticFromSplit(const std::vector<std::string>& split, const uint32_t index) {
return split.size() > index ? std::stoull(split.at(index)) : 0; uint64_t toReturn = 0;
if (index < split.size()) {
const auto parsed = GeneralUtils::TryParse<uint64_t>(split[index]);
if (parsed) toReturn = *parsed;
}
return toReturn;
} }
ZoneStatistics& CharacterComponent::GetZoneStatisticsForMap(LWOMAPID mapID) { ZoneStatistics& CharacterComponent::GetZoneStatisticsForMap(LWOMAPID mapID) {

View File

@@ -450,7 +450,7 @@ private:
* @param index the statistics ID in the string * @param index the statistics ID in the string
* @return the integer value of this statistic, parsed from the string * @return the integer value of this statistic, parsed from the string
*/ */
static uint64_t GetStatisticFromSplit(std::vector<std::string> split, uint32_t index); static uint64_t GetStatisticFromSplit(const std::vector<std::string>& split, const uint32_t index);
/** /**
* Gets all the statistics for a certain map, if it doesn't exist, it creates empty stats * Gets all the statistics for a certain map, if it doesn't exist, it creates empty stats
@@ -526,6 +526,7 @@ private:
/** /**
* Total amount of meters traveled by this character * Total amount of meters traveled by this character
* Should be a double and then truncated so decimals can be tracked
*/ */
uint64_t m_MetersTraveled; uint64_t m_MetersTraveled;

View File

@@ -793,7 +793,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType
std::vector<Entity*> scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY); std::vector<Entity*> scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY);
for (Entity* scriptEntity : scriptedActs) { for (Entity* scriptEntity : scriptedActs) {
if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds if (!zoneControl || scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
scriptEntity->GetScript()->OnPlayerDied(scriptEntity, m_Parent); scriptEntity->GetScript()->OnPlayerDied(scriptEntity, m_Parent);
} }
} }
@@ -964,6 +964,8 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) {
if (m_Parent->IsPlayer()) { if (m_Parent->IsPlayer()) {
//remove hardcore_lose_uscore_on_death_percent from the player's uscore: //remove hardcore_lose_uscore_on_death_percent from the player's uscore:
auto* character = m_Parent->GetComponent<CharacterComponent>(); auto* character = m_Parent->GetComponent<CharacterComponent>();
if (!character) return;
auto uscore = character->GetUScore(); auto uscore = character->GetUScore();
auto uscoreToLose = static_cast<uint64_t>(uscore * (Game::entityManager->GetHardcoreLoseUscoreOnDeathPercent() / 100.0f)); auto uscoreToLose = static_cast<uint64_t>(uscore * (Game::entityManager->GetHardcoreLoseUscoreOnDeathPercent() / 100.0f));

View File

@@ -966,8 +966,9 @@ void InventoryComponent::EquipScripts(Item* equippedItem) {
auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name); auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name);
if (!itemScript) { if (!itemScript) {
LOG("null script?"); LOG("null script?");
} else {
itemScript->OnFactionTriggerItemEquipped(m_Parent, equippedItem->GetId());
} }
itemScript->OnFactionTriggerItemEquipped(m_Parent, equippedItem->GetId());
} }
} }
@@ -981,8 +982,9 @@ void InventoryComponent::UnequipScripts(Item* unequippedItem) {
auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name); auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name);
if (!itemScript) { if (!itemScript) {
LOG("null script?"); LOG("null script?");
} else {
itemScript->OnFactionTriggerItemUnequipped(m_Parent, unequippedItem->GetId());
} }
itemScript->OnFactionTriggerItemUnequipped(m_Parent, unequippedItem->GetId());
} }
} }
@@ -1633,7 +1635,7 @@ void InventoryComponent::LoadPetXml(const tinyxml2::XMLDocument& document) {
DatabasePet databasePet; DatabasePet databasePet;
databasePet.lot = lot; databasePet.lot = lot;
databasePet.moderationState = moderationStatus; databasePet.moderationState = moderationStatus;
databasePet.name = std::string(name); databasePet.name = name ? name : "";
SetDatabasePet(id, databasePet); SetDatabasePet(id, databasePet);

View File

@@ -22,7 +22,7 @@ public:
void NextLUPExhibit(); void NextLUPExhibit();
private: private:
float m_UpdateTimer = 0.0f; float m_UpdateTimer = 0.0f;
std::array<LOT, 4> m_LUPExhibits = { 11121, 11295, 11423, 11979 }; const std::array<LOT, 4> m_LUPExhibits = { 11121, 11295, 11423, 11979 };
uint8_t m_LUPExhibitIndex = 0; uint8_t m_LUPExhibitIndex = 0;
bool m_DirtyLUPExhibit = true; bool m_DirtyLUPExhibit = true;
}; };

View File

@@ -449,45 +449,48 @@ void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
if (mis == nullptr) return; if (mis == nullptr) return;
auto* cur = mis->FirstChildElement("cur");
auto* done = mis->FirstChildElement("done"); auto* done = mis->FirstChildElement("done");
if (done) {
auto* doneM = done->FirstChildElement(); auto* doneM = done->FirstChildElement();
while (doneM) { while (doneM) {
int missionId; int missionId;
doneM->QueryAttribute("id", &missionId); doneM->QueryAttribute("id", &missionId);
auto* mission = new Mission(this, missionId); auto* mission = new Mission(this, missionId);
mission->LoadFromXmlDone(*doneM); mission->LoadFromXmlDone(*doneM);
doneM = doneM->NextSiblingElement(); doneM = doneM->NextSiblingElement();
m_Missions.insert_or_assign(missionId, mission); m_Missions.insert_or_assign(missionId, mission);
}
auto* currentM = cur->FirstChildElement();
uint32_t missionOrder{};
while (currentM) {
int missionId;
currentM->QueryAttribute("id", &missionId);
auto* mission = m_Missions.contains(missionId) ? m_Missions[missionId] : new Mission(this, missionId);
mission->LoadFromXmlCur(*currentM);
if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) {
mission->SetUniqueMissionOrderID(missionOrder);
if (missionOrder > m_LastUsedMissionOrderUID) m_LastUsedMissionOrderUID = missionOrder;
} }
}
auto* cur = mis->FirstChildElement("cur");
if (cur) {
auto* currentM = cur->FirstChildElement();
currentM = currentM->NextSiblingElement(); uint32_t missionOrder{};
while (currentM) {
int missionId;
m_Missions.insert_or_assign(missionId, mission); currentM->QueryAttribute("id", &missionId);
auto* mission = m_Missions.contains(missionId) ? m_Missions[missionId] : new Mission(this, missionId);
mission->LoadFromXmlCur(*currentM);
if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) {
mission->SetUniqueMissionOrderID(missionOrder);
if (missionOrder > m_LastUsedMissionOrderUID) m_LastUsedMissionOrderUID = missionOrder;
}
currentM = currentM->NextSiblingElement();
m_Missions.insert_or_assign(missionId, mission);
}
} }
} }

View File

@@ -17,7 +17,10 @@ MultiZoneEntranceComponent::MultiZoneEntranceComponent(Entity* parent, const int
MultiZoneEntranceComponent::~MultiZoneEntranceComponent() {} MultiZoneEntranceComponent::~MultiZoneEntranceComponent() {}
void MultiZoneEntranceComponent::OnUse(Entity* originator) { void MultiZoneEntranceComponent::OnUse(Entity* originator) {
auto* rocket = originator->GetComponent<CharacterComponent>()->RocketEquip(originator); auto* const characterComponent = originator->GetComponent<CharacterComponent>();
if (!characterComponent) return;
auto* rocket = characterComponent->RocketEquip(originator);
if (!rocket) return; if (!rocket) return;
// the LUP world menu is just the property menu, the client knows how to handle it // the LUP world menu is just the property menu, the client knows how to handle it
@@ -26,7 +29,7 @@ void MultiZoneEntranceComponent::OnUse(Entity* originator) {
void MultiZoneEntranceComponent::OnSelectWorld(Entity* originator, uint32_t index) { void MultiZoneEntranceComponent::OnSelectWorld(Entity* originator, uint32_t index) {
auto* rocketLaunchpadControlComponent = m_Parent->GetComponent<RocketLaunchpadControlComponent>(); auto* rocketLaunchpadControlComponent = m_Parent->GetComponent<RocketLaunchpadControlComponent>();
if (!rocketLaunchpadControlComponent) return; if (!rocketLaunchpadControlComponent || index >= m_LUPWorlds.size()) return;
rocketLaunchpadControlComponent->Launch(originator, m_LUPWorlds[index], 0); rocketLaunchpadControlComponent->Launch(originator, m_LUPWorlds[index], 0);
} }

View File

@@ -922,7 +922,9 @@ void PetComponent::Deactivate() {
} }
void PetComponent::Release() { void PetComponent::Release() {
auto* inventoryComponent = GetOwner()->GetComponent<InventoryComponent>(); auto* const owner = GetOwner();
if (!owner) return;
auto* const inventoryComponent = owner->GetComponent<InventoryComponent>();
if (inventoryComponent == nullptr) { if (inventoryComponent == nullptr) {
return; return;
@@ -932,9 +934,9 @@ void PetComponent::Release() {
inventoryComponent->RemoveDatabasePet(m_DatabaseId); inventoryComponent->RemoveDatabasePet(m_DatabaseId);
auto* item = inventoryComponent->FindItemBySubKey(m_DatabaseId); auto* const item = inventoryComponent->FindItemBySubKey(m_DatabaseId);
item->SetCount(0, false, false); if (item) item->SetCount(0, false, false);
} }
void PetComponent::Command(const NiPoint3& position, const LWOOBJID source, const int32_t commandType, const int32_t typeId, const bool overrideObey) { void PetComponent::Command(const NiPoint3& position, const LWOOBJID source, const int32_t commandType, const int32_t typeId, const bool overrideObey) {

View File

@@ -58,31 +58,16 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, const int32_t c
} }
if (m_IsRespawnVolume) { if (m_IsRespawnVolume) {
{ const auto respawnPos = GeneralUtils::SplitString(m_Parent->GetVarAsString(u"rspPos"), '\x1f');
auto respawnString = std::stringstream(m_Parent->GetVarAsString(u"rspPos")); m_RespawnPos = GeneralUtils::TryParse(respawnPos, NiPoint3Constant::ZERO);
std::string segment; const auto respawnRot = GeneralUtils::SplitString(m_Parent->GetVarAsString(u"rspRot"), '\x1f');
std::vector<std::string> seglist; m_RespawnRot = respawnRot.size() >= 4 ? NiQuaternion(
GeneralUtils::TryParse(respawnRot[0], 1.0f),
while (std::getline(respawnString, segment, '\x1f')) { GeneralUtils::TryParse(respawnRot[1], 0.0f),
seglist.push_back(segment); GeneralUtils::TryParse(respawnRot[2], 0.0f),
} GeneralUtils::TryParse(respawnRot[3], 0.0f))
: QuatUtils::IDENTITY;
m_RespawnPos = NiPoint3(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2]));
}
{
auto respawnString = std::stringstream(m_Parent->GetVarAsString(u"rspRot"));
std::string segment;
std::vector<std::string> seglist;
while (std::getline(respawnString, segment, '\x1f')) {
seglist.push_back(segment);
}
m_RespawnRot = NiQuaternion(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2]), std::stof(seglist[3]));
}
} }
// HF - RespawnPoints. Legacy respawn entity. // HF - RespawnPoints. Legacy respawn entity.

View File

@@ -131,7 +131,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
const auto lookupResult = Database::Get()->GetProperties(propertyLookup); const auto lookupResult = Database::Get()->GetProperties(propertyLookup);
for (const auto& propertyEntry : lookupResult->entries) { for (const auto& propertyEntry : lookupResult.entries) {
const auto owner = propertyEntry.ownerId; const auto owner = propertyEntry.ownerId;
const auto otherCharacter = Database::Get()->GetCharacterInfo(owner); const auto otherCharacter = Database::Get()->GetCharacterInfo(owner);
if (!otherCharacter.has_value()) { if (!otherCharacter.has_value()) {
@@ -174,5 +174,5 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl
} }
// Query here is to figure out whether or not to display the button to go to the next page or not. // Query here is to figure out whether or not to display the button to go to the next page or not.
GameMessages::SendPropertySelectQuery(m_Parent->GetObjectID(), startIndex, lookupResult->totalEntriesMatchingQuery - (startIndex + numResults) > 0, character->GetPropertyCloneID(), false, true, entries, sysAddr); GameMessages::SendPropertySelectQuery(m_Parent->GetObjectID(), startIndex, lookupResult.totalEntriesMatchingQuery - (startIndex + numResults) > 0, character->GetPropertyCloneID(), false, true, entries, sysAddr);
} }

View File

@@ -107,20 +107,12 @@ std::vector<NiPoint3> PropertyManagementComponent::GetPaths() const {
std::vector<float> points; std::vector<float> points;
std::istringstream stream(result.getStringField("path")); for (const auto& str : GeneralUtils::SplitString(result.getStringField("path"), ' ')) {
std::string token; const auto value = GeneralUtils::TryParse<float>(str);
if (value) points.push_back(value.value());
while (std::getline(stream, token, ' ')) {
try {
auto value = std::stof(token);
points.push_back(value);
} catch (std::invalid_argument& exception) {
LOG("Failed to parse value (%s): (%s)!", token.c_str(), exception.what());
}
} }
for (auto i = 0u; i < points.size(); i += 3) { for (auto i = 0u; i + 2 < points.size(); i += 3) {
paths.emplace_back(points[i], points[i + 1], points[i + 2]); paths.emplace_back(points[i], points[i + 1], points[i + 2]);
} }
@@ -780,15 +772,17 @@ void PropertyManagementComponent::OnQueryPropertyData(Entity* originator, const
privacy = static_cast<char>(this->privacyOption); privacy = static_cast<char>(this->privacyOption);
if (moderatorRequested) { if (moderatorRequested) {
auto moderationInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId); auto moderationInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId);
if (moderationInfo->rejectionReason != "") { if (moderationInfo) {
moderatorRequested = false; if (moderationInfo->rejectionReason != "") {
rejectionReason = moderationInfo->rejectionReason; moderatorRequested = false;
} else if (moderationInfo->rejectionReason == "" && moderationInfo->modApproved == 1) { rejectionReason = moderationInfo->rejectionReason;
moderatorRequested = false; } else if (moderationInfo->rejectionReason == "" && moderationInfo->modApproved == 1) {
rejectionReason = ""; moderatorRequested = false;
} else { rejectionReason = "";
moderatorRequested = true; } else {
rejectionReason = ""; moderatorRequested = true;
rejectionReason = "";
}
} }
} }
} }

View File

@@ -380,7 +380,7 @@ void QuickBuildComponent::StartQuickBuild(Entity* const user) {
m_Builder = user->GetObjectID(); m_Builder = user->GetObjectID();
auto* character = user->GetComponent<CharacterComponent>(); auto* character = user->GetComponent<CharacterComponent>();
character->SetCurrentActivity(eGameActivity::QUICKBUILDING); if (character) character->SetCurrentActivity(eGameActivity::QUICKBUILDING);
Game::entityManager->SerializeEntity(user); Game::entityManager->SerializeEntity(user);

View File

@@ -306,7 +306,7 @@ void RacingControlComponent::OnRequestDie(Entity* player, const std::u16string&
eKillType::VIOLENT, deathType, 0, 0, 90.0f, false, true, 0); eKillType::VIOLENT, deathType, 0, 0, 90.0f, false, true, 0);
auto* destroyableComponent = vehicle->GetComponent<DestroyableComponent>(); auto* destroyableComponent = vehicle->GetComponent<DestroyableComponent>();
uint32_t respawnImagination = 0; int32_t respawnImagination = 0;
// Reset imagination to half its current value, rounded up to the nearest value divisible by 10, as it was done in live. // Reset imagination to half its current value, rounded up to the nearest value divisible by 10, as it was done in live.
// Do not actually change the value yet. Do that on respawn. // Do not actually change the value yet. Do that on respawn.
if (destroyableComponent) { if (destroyableComponent) {

View File

@@ -385,26 +385,30 @@ void TriggerComponent::HandleSetPhysicsVolumeEffect(Entity* targetEntity, std::v
} }
phantomPhysicsComponent->SetPhysicsEffectActive(true); phantomPhysicsComponent->SetPhysicsEffectActive(true);
ePhysicsEffectType effectType = ePhysicsEffectType::PUSH; ePhysicsEffectType effectType = ePhysicsEffectType::PUSH;
std::transform(argArray.at(0).begin(), argArray.at(0).end(), argArray.at(0).begin(), ::tolower); //Transform to lowercase if (!argArray.empty()) {
if (argArray.at(0) == "push") effectType = ePhysicsEffectType::PUSH; std::transform(argArray.at(0).begin(), argArray.at(0).end(), argArray.at(0).begin(), ::tolower); //Transform to lowercase
else if (argArray.at(0) == "attract") effectType = ePhysicsEffectType::ATTRACT; if (argArray.at(0) == "push") effectType = ePhysicsEffectType::PUSH;
else if (argArray.at(0) == "repulse") effectType = ePhysicsEffectType::REPULSE; else if (argArray.at(0) == "attract") effectType = ePhysicsEffectType::ATTRACT;
else if (argArray.at(0) == "gravity") effectType = ePhysicsEffectType::GRAVITY_SCALE; else if (argArray.at(0) == "repulse") effectType = ePhysicsEffectType::REPULSE;
else if (argArray.at(0) == "friction") effectType = ePhysicsEffectType::FRICTION; else if (argArray.at(0) == "gravity") effectType = ePhysicsEffectType::GRAVITY_SCALE;
else if (argArray.at(0) == "friction") effectType = ePhysicsEffectType::FRICTION;
}
phantomPhysicsComponent->SetEffectType(effectType); phantomPhysicsComponent->SetEffectType(effectType);
phantomPhysicsComponent->SetDirectionalMultiplier(std::stof(argArray.at(1))); if (argArray.size() > 1) {
phantomPhysicsComponent->SetDirectionalMultiplier(GeneralUtils::TryParse(argArray.at(1), 0.0f));
}
if (argArray.size() > 4) { if (argArray.size() > 4) {
const NiPoint3 direction = const NiPoint3 direction =
GeneralUtils::TryParse<NiPoint3>(argArray.at(2), argArray.at(3), argArray.at(4)).value_or(NiPoint3Constant::ZERO); GeneralUtils::TryParse(argArray.at(2), argArray.at(3), argArray.at(4), NiPoint3Constant::ZERO);
phantomPhysicsComponent->SetDirection(direction); phantomPhysicsComponent->SetDirection(direction);
} }
if (argArray.size() > 5) { if (argArray.size() > 6) {
const uint32_t min = GeneralUtils::TryParse<uint32_t>(argArray.at(6)).value_or(0); const uint32_t min = GeneralUtils::TryParse(argArray.at(6), 0);
phantomPhysicsComponent->SetMin(min); phantomPhysicsComponent->SetMin(min);
const uint32_t max = GeneralUtils::TryParse<uint32_t>(argArray.at(7)).value_or(0); const uint32_t max = GeneralUtils::TryParse(argArray.at(7), 0);
phantomPhysicsComponent->SetMax(max); phantomPhysicsComponent->SetMax(max);
} }

View File

@@ -61,6 +61,11 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
User* usr = UserManager::Instance()->GetUser(sysAddr); User* usr = UserManager::Instance()->GetUser(sysAddr);
if (!usr) {
LOG("Failed to find a logged in user for (%llu), aborting GM: %4i, %s!", objectID, messageID, StringifiedEnum::ToString(messageID).data());
return;
}
if (!entity) { if (!entity) {
LOG("Failed to find associated entity (%llu), aborting GM: %4i, %s!", objectID, messageID, StringifiedEnum::ToString(messageID).data()); LOG("Failed to find associated entity (%llu), aborting GM: %4i, %s!", objectID, messageID, StringifiedEnum::ToString(messageID).data());
return; return;
@@ -76,7 +81,8 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
if (msg->requiredGmLevel > eGameMasterLevel::CIVILIAN) { if (msg->requiredGmLevel > eGameMasterLevel::CIVILIAN) {
auto* usingEntity = Game::entityManager->GetEntity(usr->GetLoggedInChar()); auto* usingEntity = Game::entityManager->GetEntity(usr->GetLoggedInChar());
if (!usingEntity || usingEntity->GetGMLevel() < msg->requiredGmLevel) { if (!usingEntity || usingEntity->GetGMLevel() < msg->requiredGmLevel) {
LOG("User %s (%llu) does not have the required GM level to execute this command.", usingEntity->GetCharacter()->GetName().c_str(), usingEntity->GetObjectID()); if (usingEntity) LOG("User %s (%llu) does not have the required GM level to execute this command.", usingEntity->GetCharacter()->GetName().c_str(), usingEntity->GetObjectID());
else LOG("ObjectID %llu tried to use a gm required message.", usr->GetLoggedInChar());
return; return;
} }
} }
@@ -167,8 +173,8 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
GameMessages::SendRestoreToPostLoadStats(entity, sysAddr); GameMessages::SendRestoreToPostLoadStats(entity, sysAddr);
auto* destroyable = entity->GetComponent<DestroyableComponent>(); auto* const destroyable = entity->GetComponent<DestroyableComponent>();
destroyable->SetImagination(destroyable->GetImagination()); if (destroyable) destroyable->SetImagination(destroyable->GetImagination());
Game::entityManager->SerializeEntity(entity); Game::entityManager->SerializeEntity(entity);
std::vector<Entity*> racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); std::vector<Entity*> racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL);
@@ -186,7 +192,7 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
std::vector<Entity*> scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPT); std::vector<Entity*> scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPT);
for (Entity* scriptEntity : scriptedActs) { for (Entity* scriptEntity : scriptedActs) {
if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds if (!zoneControl || scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds
scriptEntity->GetScript()->OnPlayerLoaded(scriptEntity, entity); scriptEntity->GetScript()->OnPlayerLoaded(scriptEntity, entity);
} }
} }
@@ -332,9 +338,9 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
if (behaviorId > 0) { if (behaviorId > 0) {
auto bs = RakNet::BitStream(reinterpret_cast<unsigned char*>(&startSkill.sBitStream[0]), startSkill.sBitStream.size(), false); auto bs = RakNet::BitStream(reinterpret_cast<unsigned char*>(&startSkill.sBitStream[0]), startSkill.sBitStream.size(), false);
auto* skillComponent = entity->GetComponent<SkillComponent>(); auto* const skillComponent = entity->GetComponent<SkillComponent>();
success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, bs, startSkill.optionalTargetID, startSkill.skillID); if (skillComponent) success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, bs, startSkill.optionalTargetID, startSkill.skillID);
if (success && entity->GetCharacter()) { if (success && entity->GetCharacter()) {
DestroyableComponent* destComp = entity->GetComponent<DestroyableComponent>(); DestroyableComponent* destComp = entity->GetComponent<DestroyableComponent>();
@@ -387,9 +393,9 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
if (usr != nullptr) { if (usr != nullptr) {
auto bs = RakNet::BitStream(reinterpret_cast<unsigned char*>(&sync.sBitStream[0]), sync.sBitStream.size(), false); auto bs = RakNet::BitStream(reinterpret_cast<unsigned char*>(&sync.sBitStream[0]), sync.sBitStream.size(), false);
auto* skillComponent = entity->GetComponent<SkillComponent>(); auto* const skillComponent = entity->GetComponent<SkillComponent>();
skillComponent->SyncPlayerSkill(sync.uiSkillHandle, sync.uiBehaviorHandle, bs); if (skillComponent) skillComponent->SyncPlayerSkill(sync.uiSkillHandle, sync.uiBehaviorHandle, bs);
} }
EchoSyncSkill echo = EchoSyncSkill(); EchoSyncSkill echo = EchoSyncSkill();

View File

@@ -2137,6 +2137,7 @@ void GameMessages::HandleUpdatePropertyOrModelForFilterCheck(RakNet::BitStream&
inStream.Read(worldId); inStream.Read(worldId);
inStream.Read(descriptionLength); inStream.Read(descriptionLength);
if (descriptionLength > MAX_MESSAGE_LENGTH) return;
for (uint32_t i = 0; i < descriptionLength; ++i) { for (uint32_t i = 0; i < descriptionLength; ++i) {
uint16_t character; uint16_t character;
inStream.Read(character); inStream.Read(character);
@@ -2144,6 +2145,7 @@ void GameMessages::HandleUpdatePropertyOrModelForFilterCheck(RakNet::BitStream&
} }
inStream.Read(nameLength); inStream.Read(nameLength);
if (nameLength > MAX_MESSAGE_LENGTH) return;
for (uint32_t i = 0; i < nameLength; ++i) { for (uint32_t i = 0; i < nameLength; ++i) {
uint16_t character; uint16_t character;
inStream.Read(character); inStream.Read(character);
@@ -2474,9 +2476,15 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
uint32_t sd0Size; uint32_t sd0Size;
inStream.Read(sd0Size); inStream.Read(sd0Size);
std::unique_ptr<char[]> sd0Data(new char[sd0Size]);
if (sd0Data == nullptr) return; // For the sake of letting players make models as big as they want, only reject if we cant allocate the required memory.
std::unique_ptr<char[]> sd0Data;
try {
sd0Data.reset(new char[sd0Size]);
} catch (std::exception& e) {
LOG("Failed to allocate sd0 of size %u", sd0Size);
return;
}
inStream.ReadAlignedBytes(reinterpret_cast<unsigned char*>(sd0Data.get()), sd0Size); inStream.ReadAlignedBytes(reinterpret_cast<unsigned char*>(sd0Data.get()), sd0Size);
@@ -2652,6 +2660,7 @@ void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream& inStream, Entit
inStream.Read(startIndex); inStream.Read(startIndex);
inStream.Read(filterTextLength); inStream.Read(filterTextLength);
if (filterTextLength > MAX_MESSAGE_LENGTH) return;
for (auto i = 0u; i < filterTextLength; i++) { for (auto i = 0u; i < filterTextLength; i++) {
char c; char c;
inStream.Read(c); inStream.Read(c);
@@ -3040,6 +3049,7 @@ void GameMessages::HandleVerifyAck(RakNet::BitStream& inStream, Entity* entity,
uint32_t sBitStreamLength = 0; uint32_t sBitStreamLength = 0;
inStream.Read(sBitStreamLength); inStream.Read(sBitStreamLength);
if (sBitStreamLength > MAX_MESSAGE_LENGTH) return;
for (uint64_t k = 0; k < sBitStreamLength; k++) { for (uint64_t k = 0; k < sBitStreamLength; k++) {
uint8_t character; uint8_t character;
inStream.Read(character); inStream.Read(character);
@@ -3261,6 +3271,7 @@ void GameMessages::HandleClientTradeUpdate(RakNet::BitStream& inStream, Entity*
inStream.Read(currency); inStream.Read(currency);
inStream.Read(itemCount); inStream.Read(itemCount);
if (itemCount > MAX_MESSAGE_LENGTH) return;
LOG("Trade update from (%llu) -> (%llu), (%i)", entity->GetObjectID(), currency, itemCount); LOG("Trade update from (%llu) -> (%llu), (%i)", entity->GetObjectID(), currency, itemCount);
@@ -3673,6 +3684,7 @@ void GameMessages::HandleRequestSetPetName(RakNet::BitStream& inStream, Entity*
inStream.Read(nameLength); inStream.Read(nameLength);
if (nameLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < nameLength; i++) { for (size_t i = 0; i < nameLength; i++) {
char16_t character; char16_t character;
inStream.Read(character); inStream.Read(character);
@@ -3742,6 +3754,7 @@ void GameMessages::HandleMessageBoxResponse(RakNet::BitStream& inStream, Entity*
inStream.Read(iButton); inStream.Read(iButton);
inStream.Read(identifierLength); inStream.Read(identifierLength);
if (identifierLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < identifierLength; i++) { for (size_t i = 0; i < identifierLength; i++) {
char16_t character; char16_t character;
inStream.Read(character); inStream.Read(character);
@@ -3749,6 +3762,7 @@ void GameMessages::HandleMessageBoxResponse(RakNet::BitStream& inStream, Entity*
} }
inStream.Read(userDataLength); inStream.Read(userDataLength);
if (userDataLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < userDataLength; i++) { for (size_t i = 0; i < userDataLength; i++) {
char16_t character; char16_t character;
inStream.Read(character); inStream.Read(character);
@@ -3796,6 +3810,7 @@ void GameMessages::HandleChoiceBoxRespond(RakNet::BitStream& inStream, Entity* e
std::u16string identifier; std::u16string identifier;
inStream.Read(buttonIdentifierLength); inStream.Read(buttonIdentifierLength);
if (buttonIdentifierLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < buttonIdentifierLength; i++) { for (size_t i = 0; i < buttonIdentifierLength; i++) {
char16_t character; char16_t character;
inStream.Read(character); inStream.Read(character);
@@ -3805,6 +3820,7 @@ void GameMessages::HandleChoiceBoxRespond(RakNet::BitStream& inStream, Entity* e
inStream.Read(iButton); inStream.Read(iButton);
inStream.Read(identifierLength); inStream.Read(identifierLength);
if (identifierLength > MAX_MESSAGE_LENGTH) return;
for (size_t i = 0; i < identifierLength; i++) { for (size_t i = 0; i < identifierLength; i++) {
char16_t character; char16_t character;
inStream.Read(character); inStream.Read(character);
@@ -4158,7 +4174,13 @@ void GameMessages::HandleUpdatePropertyPerformanceCost(RakNet::BitStream& inStre
return; return;
} }
Database::Get()->UpdatePerformanceCost(zone->GetZoneID(), performanceCost); const auto* const propertyManagementComponent = entity->GetComponent<PropertyManagementComponent>();
const auto* const ownerEntity = propertyManagementComponent ? propertyManagementComponent->GetOwner() : nullptr;
const auto* const character = ownerEntity ? ownerEntity->GetCharacter() : nullptr;
const auto& zoneID = zone->GetZoneID();
if (character && character->GetPropertyCloneID() == zoneID.GetCloneID()) {
Database::Get()->UpdatePerformanceCost(zoneID, performanceCost);
}
} }
void GameMessages::HandleVehicleNotifyHitImaginationServer(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { void GameMessages::HandleVehicleNotifyHitImaginationServer(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) {
@@ -4783,13 +4805,19 @@ void GameMessages::HandleParseChatMessage(RakNet::BitStream& inStream, Entity* e
uint32_t wsStringLength; uint32_t wsStringLength;
inStream.Read(wsStringLength); inStream.Read(wsStringLength);
if (wsStringLength > MAX_MESSAGE_LENGTH) {
LOG("Max message length reached, capping message.");
wsStringLength = MAX_MESSAGE_LENGTH;
}
for (uint32_t i = 0; i < wsStringLength; ++i) { for (uint32_t i = 0; i < wsStringLength; ++i) {
uint16_t character; uint16_t character;
inStream.Read(character); inStream.Read(character);
wsString.push_back(character); wsString.push_back(character);
} }
if (wsString[0] == L'/') { if (!wsString.empty() && wsString[0] == L'/') {
SlashCommandHandler::HandleChatCommand(wsString, entity, sysAddr); SlashCommandHandler::HandleChatCommand(wsString, entity, sysAddr);
} }
} }
@@ -4806,6 +4834,7 @@ void GameMessages::HandleFireEventServerSide(RakNet::BitStream& inStream, Entity
LWOOBJID senderID{}; LWOOBJID senderID{};
inStream.Read(argsLength); inStream.Read(argsLength);
if (argsLength > MAX_MESSAGE_LENGTH) return;
for (uint32_t i = 0; i < argsLength; ++i) { for (uint32_t i = 0; i < argsLength; ++i) {
uint16_t character; uint16_t character;
inStream.Read(character); inStream.Read(character);
@@ -5436,7 +5465,7 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity*
std::vector<LOT> modList; std::vector<LOT> modList;
auto& oldPartList = character->GetVar<std::string>(u"currentModifiedBuild"); auto& oldPartList = character->GetVar<std::string>(u"currentModifiedBuild");
bool everyPieceSwapped = !oldPartList.empty(); // If the player didn't put a build in initially, then they should not get this achievement. bool everyPieceSwapped = !oldPartList.empty(); // If the player didn't put a build in initially, then they should not get this achievement.
if (count >= 3) { if (count >= 3 && count < 8) {
std::u16string modules; std::u16string modules;
for (uint32_t k = 0; k < count; k++) { for (uint32_t k = 0; k < count; k++) {
@@ -5733,6 +5762,7 @@ void GameMessages::HandleMatchRequest(RakNet::BitStream& inStream, Entity* entit
inStream.Read(activator); inStream.Read(activator);
inStream.Read(playerChoicesLen); inStream.Read(playerChoicesLen);
if (playerChoicesLen > MAX_MESSAGE_LENGTH) return;
for (uint32_t i = 0; i < playerChoicesLen; ++i) { for (uint32_t i = 0; i < playerChoicesLen; ++i) {
uint16_t character; uint16_t character;
inStream.Read(character); inStream.Read(character);
@@ -5890,7 +5920,7 @@ void GameMessages::HandlePlayerRailArrivedNotification(RakNet::BitStream& inStre
const SystemAddress& sysAddr) { const SystemAddress& sysAddr) {
uint32_t pathNameLength; uint32_t pathNameLength;
inStream.Read(pathNameLength); inStream.Read(pathNameLength);
if (pathNameLength > MAX_MESSAGE_LENGTH) return;
std::u16string pathName; std::u16string pathName;
for (auto k = 0; k < pathNameLength; k++) { for (auto k = 0; k < pathNameLength; k++) {
uint16_t c; uint16_t c;

View File

@@ -75,7 +75,8 @@ uint32_t Inventory::GetLotCount(const LOT lot) const {
} }
void Inventory::SetSize(const uint32_t value) { void Inventory::SetSize(const uint32_t value) {
free += static_cast<int32_t>(value) - static_cast<int32_t>(size); const auto delta = static_cast<int32_t>(value) - static_cast<int32_t>(size);
free = static_cast<uint32_t>(std::max(0, static_cast<int32_t>(free) + delta));
size = value; size = value;

View File

@@ -401,7 +401,8 @@ void Item::Disassemble(const eInventoryType inventoryType) {
const auto deliminator = '+'; const auto deliminator = '+';
while (std::getline(ssData, token, deliminator)) { while (std::getline(ssData, token, deliminator)) {
const auto modLot = std::stoi(token.substr(2, token.size() - 1)); if (token.size() <= 2) continue; // invalid token, must have size of at least 3.
const auto modLot = GeneralUtils::TryParse(token.substr(2, token.size() - 1), LOT_NULL);
modArray.push_back(modLot); modArray.push_back(modLot);
} }
@@ -440,7 +441,10 @@ void Item::DisassembleModel(uint32_t numToDismantle) {
std::vector<std::string> renderAssetSplit = GeneralUtils::SplitString(renderAsset, '/'); std::vector<std::string> renderAssetSplit = GeneralUtils::SplitString(renderAsset, '/');
if (renderAssetSplit.empty()) return; if (renderAssetSplit.empty()) return;
std::string lxfmlPath = "BrickModels" + lxfmlFolderName + "/" + GeneralUtils::SplitString(renderAssetSplit.back(), '.').at(0) + ".lxfml"; const auto renderAssetSplitSplit = GeneralUtils::SplitString(renderAssetSplit.back(), '.');
if (renderAssetSplitSplit.empty()) return;
std::string lxfmlPath = "BrickModels" + lxfmlFolderName + "/" + renderAssetSplitSplit[0] + ".lxfml";
auto file = Game::assetManager->GetFile(lxfmlPath.c_str()); auto file = Game::assetManager->GetFile(lxfmlPath.c_str());
if (!file) { if (!file) {

View File

@@ -128,8 +128,8 @@ void ItemSet::OnEquip(const LOT lot) {
return; return;
} }
auto* skillComponent = m_InventoryComponent->GetParent()->GetComponent<SkillComponent>(); auto [skillComponent, missionComponent] = m_InventoryComponent->GetParent()->GetComponentsMut<SkillComponent, MissionComponent>();
auto* missionComponent = m_InventoryComponent->GetParent()->GetComponent<MissionComponent>(); if (!skillComponent || !missionComponent) return; // Nothing to do here if these are null
for (const auto skill : skillSet) { for (const auto skill : skillSet) {
auto* skillTable = CDClientManager::GetTable<CDSkillBehaviorTable>(); auto* skillTable = CDClientManager::GetTable<CDSkillBehaviorTable>();

View File

@@ -74,23 +74,19 @@ Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) {
void Mission::LoadFromXmlDone(const tinyxml2::XMLElement& element) { void Mission::LoadFromXmlDone(const tinyxml2::XMLElement& element) {
// Start custom XML // Start custom XML
if (element.Attribute("state") != nullptr) { m_State = static_cast<eMissionState>(element.UnsignedAttribute("state"));
m_State = static_cast<eMissionState>(std::stoul(element.Attribute("state")));
}
// End custom XML // End custom XML
if (element.Attribute("cct") != nullptr) { m_Completions = element.UnsignedAttribute("cct");
m_Completions = std::stoul(element.Attribute("cct"));
m_Timestamp = std::stoul(element.Attribute("cts")); m_Timestamp = element.UnsignedAttribute("cts");
}
} }
void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) { void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) {
const auto* const character = GetCharacter(); const auto* const character = GetCharacter();
// Start custom XML // Start custom XML
if (element.Attribute("state") != nullptr) { if (element.Attribute("state") != nullptr) {
m_State = static_cast<eMissionState>(std::stoul(element.Attribute("state"))); m_State = static_cast<eMissionState>(element.IntAttribute("state", -1));
} }
// End custom XML // End custom XML
@@ -106,7 +102,7 @@ void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) {
const auto type = curTask->GetType(); const auto type = curTask->GetType();
auto value = std::stoul(task->Attribute("v")); auto value = task->UnsignedAttribute("v");
curTask->SetProgress(value, false); curTask->SetProgress(value, false);
task = task->NextSiblingElement(); task = task->NextSiblingElement();
@@ -114,7 +110,7 @@ void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) {
if (type == eMissionTaskType::COLLECTION || type == eMissionTaskType::VISIT_PROPERTY) { if (type == eMissionTaskType::COLLECTION || type == eMissionTaskType::VISIT_PROPERTY) {
std::vector<uint32_t> uniques; std::vector<uint32_t> uniques;
while (task != nullptr && value > 0) { while (task != nullptr && value > 0) {
const auto unique = std::stoul(task->Attribute("v")); const auto unique = task->UnsignedAttribute("v");
uniques.push_back(unique); uniques.push_back(unique);

View File

@@ -17,50 +17,63 @@ PropertyBehavior::PropertyBehavior(bool _isTemplated) {
isTemplated = _isTemplated; isTemplated = _isTemplated;
} }
bool CheckStateRange(const BehaviorState state) {
return state >= BehaviorState::HOME_STATE && state <= BehaviorState::STAR_STATE;
}
template<> template<>
void PropertyBehavior::HandleMsg(AddStripMessage& msg) { void PropertyBehavior::HandleMsg(AddStripMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId(); m_LastEditedState = msg.GetActionContext().GetStateId();
}; };
template<> template<>
void PropertyBehavior::HandleMsg(AddActionMessage& msg) { void PropertyBehavior::HandleMsg(AddActionMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId(); m_LastEditedState = msg.GetActionContext().GetStateId();
}; };
template<> template<>
void PropertyBehavior::HandleMsg(RearrangeStripMessage& msg) { void PropertyBehavior::HandleMsg(RearrangeStripMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId(); m_LastEditedState = msg.GetActionContext().GetStateId();
}; };
template<> template<>
void PropertyBehavior::HandleMsg(UpdateActionMessage& msg) { void PropertyBehavior::HandleMsg(UpdateActionMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId(); m_LastEditedState = msg.GetActionContext().GetStateId();
}; };
template<> template<>
void PropertyBehavior::HandleMsg(UpdateStripUiMessage& msg) { void PropertyBehavior::HandleMsg(UpdateStripUiMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId(); m_LastEditedState = msg.GetActionContext().GetStateId();
}; };
template<> template<>
void PropertyBehavior::HandleMsg(RemoveStripMessage& msg) { void PropertyBehavior::HandleMsg(RemoveStripMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId(); m_LastEditedState = msg.GetActionContext().GetStateId();
}; };
template<> template<>
void PropertyBehavior::HandleMsg(RemoveActionsMessage& msg) { void PropertyBehavior::HandleMsg(RemoveActionsMessage& msg) {
if (!CheckStateRange(msg.GetActionContext().GetStateId())) return;
m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetActionContext().GetStateId(); m_LastEditedState = msg.GetActionContext().GetStateId();
}; };
template<> template<>
void PropertyBehavior::HandleMsg(SplitStripMessage& msg) { void PropertyBehavior::HandleMsg(SplitStripMessage& msg) {
if (!CheckStateRange(msg.GetSourceActionContext().GetStateId())) return;
if (!CheckStateRange(msg.GetDestinationActionContext().GetStateId())) return;
m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg);
m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetDestinationActionContext().GetStateId(); m_LastEditedState = msg.GetDestinationActionContext().GetStateId();
@@ -68,6 +81,8 @@ void PropertyBehavior::HandleMsg(SplitStripMessage& msg) {
template<> template<>
void PropertyBehavior::HandleMsg(MigrateActionsMessage& msg) { void PropertyBehavior::HandleMsg(MigrateActionsMessage& msg) {
if (!CheckStateRange(msg.GetSourceActionContext().GetStateId())) return;
if (!CheckStateRange(msg.GetDestinationActionContext().GetStateId())) return;
m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg);
m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetDestinationActionContext().GetStateId(); m_LastEditedState = msg.GetDestinationActionContext().GetStateId();
@@ -75,6 +90,8 @@ void PropertyBehavior::HandleMsg(MigrateActionsMessage& msg) {
template<> template<>
void PropertyBehavior::HandleMsg(MergeStripsMessage& msg) { void PropertyBehavior::HandleMsg(MergeStripsMessage& msg) {
if (!CheckStateRange(msg.GetSourceActionContext().GetStateId())) return;
if (!CheckStateRange(msg.GetDestinationActionContext().GetStateId())) return;
m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg);
m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg);
m_LastEditedState = msg.GetDestinationActionContext().GetStateId(); m_LastEditedState = msg.GetDestinationActionContext().GetStateId();

View File

@@ -66,13 +66,9 @@ const BrickList& BrickDatabase::GetBricks(const LxfmlPath& lxfmlPath) {
std::string materialString(materialList); std::string materialString(materialList);
const auto materials = GeneralUtils::SplitString(materialString, ','); const auto materials = GeneralUtils::SplitString(materialString, ',');
if (!materials.empty()) { brick.materialID = GeneralUtils::TryParse(materials[0], 0);
brick.materialID = std::stoi(materials[0]);
} else {
brick.materialID = 0;
}
} else if (materialID != nullptr) { } else if (materialID != nullptr) {
brick.materialID = std::stoi(materialID); brick.materialID = GeneralUtils::TryParse(materialID, 0);
} else { } else {
brick.materialID = 0; // This is bad, makes it so the minigame can't be played brick.materialID = 0; // This is bad, makes it so the minigame can't be played
} }

View File

@@ -54,17 +54,23 @@ void LogAndSaveFailedAntiCheatCheck(const LWOOBJID& id, const SystemAddress& sys
// If player exists and entity exists in world, use both for logging info. // If player exists and entity exists in world, use both for logging info.
if (entity && player) { if (entity && player) {
const auto* const playerChar = player->GetCharacter();
const auto& playerName = playerChar ? playerChar->GetName() : "(null player character)";
const auto* const entityChar = entity->GetCharacter();
const auto& entityName = entityChar ? entityChar->GetName() : "(null entity character)";
LOG("Player (%s) (%llu) at system address (%s) with sending player (%s) (%llu) does not match their own.", LOG("Player (%s) (%llu) at system address (%s) with sending player (%s) (%llu) does not match their own.",
player->GetCharacter()->GetName().c_str(), player->GetObjectID(), playerName.c_str(), player->GetObjectID(),
sysAddr.ToString(), sysAddr.ToString(),
entity->GetCharacter()->GetName().c_str(), entity->GetObjectID()); entityName.c_str(), entity->GetObjectID());
if (player->GetCharacter()) toReport = player->GetCharacter()->GetParentUser(); if (playerChar) toReport = playerChar->GetParentUser();
// In the case that the target entity id did not exist, just log the player info. // In the case that the target entity id did not exist, just log the player info.
} else if (player) { } else if (player) {
const auto* const playerChar = player->GetCharacter();
const auto& playerName = playerChar ? playerChar->GetName() : "(null player character)";
LOG("Player (%s) (%llu) at system address (%s) with sending player (%llu) does not match their own.", LOG("Player (%s) (%llu) at system address (%s) with sending player (%llu) does not match their own.",
player->GetCharacter()->GetName().c_str(), player->GetObjectID(), playerName.c_str(), player->GetObjectID(),
sysAddr.ToString(), id); sysAddr.ToString(), id);
if (player->GetCharacter()) toReport = player->GetCharacter()->GetParentUser(); if (playerChar) toReport = playerChar->GetParentUser();
// In the rare case that the player does not exist, just log the system address and who the target id was. // In the rare case that the player does not exist, just log the system address and who the target id was.
} else { } else {
LOG("Player at system address (%s) with sending player (%llu) does not match their own.", LOG("Player at system address (%s) with sending player (%llu) does not match their own.",
@@ -76,8 +82,11 @@ void LogAndSaveFailedAntiCheatCheck(const LWOOBJID& id, const SystemAddress& sys
auto* user = UserManager::Instance()->GetUser(sysAddr); auto* user = UserManager::Instance()->GetUser(sysAddr);
if (user) { if (user) {
const auto* const lastChar = user->GetLastUsedChar();
const auto& lastName = lastChar ? lastChar->GetName() : "(null last char)";
const auto lastObjID = lastChar ? lastChar->GetObjectID() : LWOOBJID_EMPTY;
LOG("User at system address (%s) (%s) (%llu) sent a packet as (%llu) which is not an id they own.", LOG("User at system address (%s) (%s) (%llu) sent a packet as (%llu) which is not an id they own.",
sysAddr.ToString(), user->GetLastUsedChar()->GetName().c_str(), user->GetLastUsedChar()->GetObjectID(), id); sysAddr.ToString(), lastName.c_str(), lastObjID, id);
// Can't know sending player. Just log system address for IP banning. // Can't know sending player. Just log system address for IP banning.
} else { } else {
LOG("No user found for system address (%s).", sysAddr.ToString()); LOG("No user found for system address (%s).", sysAddr.ToString());

View File

@@ -326,7 +326,7 @@ void DropRegularLoot(Team& team, GameMessages::DropClientLoot& lootMsg, const bo
void DropLoot(Entity* player, const LWOOBJID source, const std::map<LOT, LootDropInfo>& rolledItems, uint32_t minCoins, uint32_t maxCoins, const bool noTeamLootOnDeath) { void DropLoot(Entity* player, const LWOOBJID source, const std::map<LOT, LootDropInfo>& rolledItems, uint32_t minCoins, uint32_t maxCoins, const bool noTeamLootOnDeath) {
player = player->GetOwner(); // if the owner is overwritten, we collect that here player = player->GetOwner(); // if the owner is overwritten, we collect that here
const auto playerID = player->GetObjectID(); const auto playerID = player->GetObjectID();
if (!player || !player->IsPlayer()) { if (!player->IsPlayer()) {
LOG("Trying to drop loot for non-player %llu:%i", playerID, player->GetLOT()); LOG("Trying to drop loot for non-player %llu:%i", playerID, player->GetLOT());
return; return;
} }

View File

@@ -74,69 +74,74 @@ namespace Mail {
void SendRequest::Handle() { void SendRequest::Handle() {
SendResponse response; SendResponse response;
auto* character = player->GetCharacter(); auto* character = player->GetCharacter();
const bool restrictMailOnMute = UserManager::Instance()->GetMuteRestrictMail() && character->GetParentUser()->GetIsMuted(); if (!character) {
const bool restrictedMailAccess = character->HasPermission(ePermissionMap::RestrictedMailAccess); response.status = eSendResponse::UnknownError;
if (character && !(restrictedMailAccess || restrictMailOnMute)) {
mailInfo.recipient = std::regex_replace(mailInfo.recipient, std::regex("[^0-9a-zA-Z]+"), "");
auto receiverID = Database::Get()->GetCharacterInfo(mailInfo.recipient);
if (!receiverID) {
response.status = eSendResponse::RecipientNotFound;
} else if (GeneralUtils::CaseInsensitiveStringCompare(mailInfo.recipient, character->GetName()) || receiverID->id == character->GetID()) {
response.status = eSendResponse::CannotMailSelf;
} else {
uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee;
uint32_t stackSize = 0;
auto inventoryComponent = player->GetComponent<InventoryComponent>();
Item* item = nullptr;
bool hasAttachment = mailInfo.itemID != 0 && mailInfo.itemCount > 0;
if (hasAttachment) {
item = inventoryComponent->FindItemById(mailInfo.itemID);
if (item) {
mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig().mailPercentAttachmentFee);
mailInfo.itemLOT = item->GetLot();
}
}
if (hasAttachment && !item) {
response.status = eSendResponse::AttachmentNotFound;
} else if (player->GetCharacter()->GetCoins() - mailCost < 0) {
response.status = eSendResponse::NotEnoughCoins;
} else {
bool removeSuccess = true;
// Remove coins and items from the sender
player->GetCharacter()->SetCoins(player->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL);
if (inventoryComponent && hasAttachment && item) {
removeSuccess = inventoryComponent->RemoveItem(mailInfo.itemLOT, mailInfo.itemCount, ALL, true);
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent && removeSuccess) missionComponent->Progress(eMissionTaskType::GATHER, mailInfo.itemLOT, LWOOBJID_EMPTY, "", -mailInfo.itemCount);
}
// we passed all the checks, now we can actully send the mail
if (removeSuccess) {
mailInfo.senderId = character->GetID();
mailInfo.senderUsername = character->GetName();
mailInfo.receiverId = receiverID->id;
mailInfo.itemSubkey = LWOOBJID_EMPTY;
//clear out the attachementID
mailInfo.itemID = 0;
Database::Get()->InsertNewMail(mailInfo);
response.status = eSendResponse::Success;
character->SaveXMLToDatabase();
} else {
response.status = eSendResponse::AttachmentNotFound;
}
}
}
} else { } else {
response.status = eSendResponse::SenderAccountIsMuted; const bool restrictMailOnMute = UserManager::Instance()->GetMuteRestrictMail() && character->GetParentUser()->GetIsMuted();
const bool restrictedMailAccess = character->HasPermission(ePermissionMap::RestrictedMailAccess);
if (character && !(restrictedMailAccess || restrictMailOnMute)) {
mailInfo.recipient = std::regex_replace(mailInfo.recipient, std::regex("[^0-9a-zA-Z]+"), "");
auto receiverID = Database::Get()->GetCharacterInfo(mailInfo.recipient);
if (!receiverID) {
response.status = eSendResponse::RecipientNotFound;
} else if (GeneralUtils::CaseInsensitiveStringCompare(mailInfo.recipient, character->GetName()) || receiverID->id == character->GetID()) {
response.status = eSendResponse::CannotMailSelf;
} else {
uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee;
uint32_t stackSize = 0;
auto inventoryComponent = player->GetComponent<InventoryComponent>();
Item* item = nullptr;
bool hasAttachment = mailInfo.itemID != 0 && mailInfo.itemCount > 0;
if (hasAttachment) {
item = inventoryComponent->FindItemById(mailInfo.itemID);
if (item) {
mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig().mailPercentAttachmentFee);
mailInfo.itemLOT = item->GetLot();
}
}
if (hasAttachment && !item) {
response.status = eSendResponse::AttachmentNotFound;
} else if (player->GetCharacter()->GetCoins() - mailCost < 0) {
response.status = eSendResponse::NotEnoughCoins;
} else {
bool removeSuccess = true;
// Remove coins and items from the sender
player->GetCharacter()->SetCoins(player->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL);
if (inventoryComponent && hasAttachment && item) {
removeSuccess = inventoryComponent->RemoveItem(mailInfo.itemLOT, mailInfo.itemCount, ALL, true);
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent && removeSuccess) missionComponent->Progress(eMissionTaskType::GATHER, mailInfo.itemLOT, LWOOBJID_EMPTY, "", -mailInfo.itemCount);
}
// we passed all the checks, now we can actully send the mail
if (removeSuccess) {
mailInfo.senderId = character->GetID();
mailInfo.senderUsername = character->GetName();
mailInfo.receiverId = receiverID->id;
mailInfo.itemSubkey = LWOOBJID_EMPTY;
//clear out the attachementID
mailInfo.itemID = 0;
Database::Get()->InsertNewMail(mailInfo);
response.status = eSendResponse::Success;
character->SaveXMLToDatabase();
} else {
response.status = eSendResponse::AttachmentNotFound;
}
}
}
} else {
response.status = eSendResponse::SenderAccountIsMuted;
}
} }
LOG("Finished send with status %s", StringifiedEnum::ToString(response.status).data()); LOG("Finished send with status %s", StringifiedEnum::ToString(response.status).data());
response.Send(sysAddr); response.Send(sysAddr);
} }
@@ -193,7 +198,7 @@ namespace Mail {
if (mailID > 0 && playerID == player->GetObjectID() && inv) { if (mailID > 0 && playerID == player->GetObjectID() && inv) {
auto playerMail = Database::Get()->GetMail(mailID); auto playerMail = Database::Get()->GetMail(mailID);
if (!playerMail) { if (!playerMail || playerMail->receiverId != player->GetObjectID()) {
response.status = eAttachmentCollectResponse::MailNotFound; response.status = eAttachmentCollectResponse::MailNotFound;
} else if (!inv->HasSpaceForLoot({ {playerMail->itemLOT, playerMail->itemCount} })) { } else if (!inv->HasSpaceForLoot({ {playerMail->itemLOT, playerMail->itemCount} })) {
response.status = eAttachmentCollectResponse::NoSpaceInInventory; response.status = eAttachmentCollectResponse::NoSpaceInInventory;
@@ -225,15 +230,21 @@ namespace Mail {
DeleteResponse response; DeleteResponse response;
response.mailID = mailID; response.mailID = mailID;
auto mailData = Database::Get()->GetMail(mailID); const auto mailData = Database::Get()->GetMail(mailID);
if (mailData && !(mailData->itemLOT > 0 && mailData->itemCount > 0)) { response.status = eDeleteResponse::NotFound;
Database::Get()->DeleteMail(mailID); if (mailData) {
response.status = eDeleteResponse::Success; if (mailData->receiverId != playerID) {
} else if (mailData && mailData->itemLOT > 0 && mailData->itemCount > 0) { LOG("Player %llu attempted to delete mail owned by %llu. Possible spoof?", playerID, mailData->receiverId);
response.status = eDeleteResponse::HasAttachments; } else {
} else { if (!(mailData->itemLOT > 0 && mailData->itemCount > 0)) {
response.status = eDeleteResponse::NotFound; Database::Get()->DeleteMail(mailID);
response.status = eDeleteResponse::Success;
} else if (mailData->itemLOT > 0 && mailData->itemCount > 0) {
response.status = eDeleteResponse::HasAttachments;
}
}
} }
LOG("DeleteRequest status %s", StringifiedEnum::ToString(response.status).data()); LOG("DeleteRequest status %s", StringifiedEnum::ToString(response.status).data());
response.Send(sysAddr); response.Send(sysAddr);
} }
@@ -253,11 +264,19 @@ namespace Mail {
void ReadRequest::Handle() { void ReadRequest::Handle() {
ReadResponse response; ReadResponse response;
response.status = eReadResponse::UnknownError;
response.mailID = mailID; response.mailID = mailID;
if (Database::Get()->GetMail(mailID)) { const auto mail = Database::Get()->GetMail(mailID);
response.status = eReadResponse::Success; if (mail) {
Database::Get()->MarkMailRead(mailID); if (mail->receiverId == player->GetObjectID()) {
response.status = eReadResponse::Success;
Database::Get()->MarkMailRead(mailID);
} else {
LOG("Player %llu tried to mark mail read for player %llu", mail->receiverId, player->GetObjectID());
}
} else {
LOG("No mail by ID %llu found to mark as read.", mailID);
} }
LOG("ReadRequest %s", StringifiedEnum::ToString(response.status).data()); LOG("ReadRequest %s", StringifiedEnum::ToString(response.status).data());

View File

@@ -114,11 +114,13 @@ bool Precondition::Check(Entity* player, bool evaluateCosts) const {
bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluateCosts) const { bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluateCosts) const {
auto* missionComponent = player->GetComponent<MissionComponent>();
auto* inventoryComponent = player->GetComponent<InventoryComponent>();
auto* destroyableComponent = player->GetComponent<DestroyableComponent>();
auto* levelComponent = player->GetComponent<LevelProgressionComponent>();
auto* character = player->GetCharacter(); auto* character = player->GetCharacter();
auto [missionComponent, inventoryComponent, destroyableComponent, levelComponent] =
player->GetComponentsMut<const MissionComponent, /* not const */ InventoryComponent, const DestroyableComponent, const LevelProgressionComponent>();
if (!missionComponent || !inventoryComponent || !destroyableComponent || !levelComponent || !character) {
return false;
}
Mission* mission; Mission* mission;

View File

@@ -825,7 +825,7 @@ namespace DEVGMCommands {
} }
const auto numberToSpawnOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[1]); const auto numberToSpawnOptional = GeneralUtils::TryParse<uint32_t>(splitArgs[1]);
if (!numberToSpawnOptional && numberToSpawnOptional.value() > 0) { if (!numberToSpawnOptional) {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn."); ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn.");
return; return;
} }
@@ -833,7 +833,7 @@ namespace DEVGMCommands {
// Must spawn within a radius of at least 0.0f // Must spawn within a radius of at least 0.0f
const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse<float>(splitArgs[2]); const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse<float>(splitArgs[2]);
if (!radiusToSpawnWithinOptional && radiusToSpawnWithinOptional.value() < 0.0f) { if (!radiusToSpawnWithinOptional || radiusToSpawnWithinOptional.value() < 0.0f) {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within."); ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within.");
return; return;
} }
@@ -1133,6 +1133,10 @@ namespace DEVGMCommands {
} }
const auto& password = splitArgs[2]; const auto& password = splitArgs[2];
if (password.length() >= 50) {
ChatPackets::SendSystemMessage(sysAddr, u"Password is too long.");
return;
}
ZoneInstanceManager::Instance()->CreatePrivateZone(Game::server, zone.value(), clone.value(), password); ZoneInstanceManager::Instance()->CreatePrivateZone(Game::server, zone.value(), clone.value(), password);

View File

@@ -187,8 +187,13 @@ namespace GMZeroCommands {
auto splitArgs = GeneralUtils::SplitString(args, ' '); auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return; if (splitArgs.empty()) return;
ChatPackets::SendSystemMessage(sysAddr, u"Requesting private map...");
const auto& password = splitArgs[0]; const auto& password = splitArgs[0];
if (password.length() >= 50) {
ChatPackets::SendSystemMessage(sysAddr, u"Password is too long.");
return;
}
ChatPackets::SendSystemMessage(sysAddr, u"Requesting private map...");
ZoneInstanceManager::Instance()->RequestPrivateZone(Game::server, false, password, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { ZoneInstanceManager::Instance()->RequestPrivateZone(Game::server, false, password, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) {
LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort);

View File

@@ -328,20 +328,14 @@ int main(int argc, char** argv) {
} }
Game::randomEngine = std::mt19937(time(0)); Game::randomEngine = std::mt19937(time(0));
uint32_t maxClients = 999; uint32_t maxClients = Game::config->GetValue("max_clients", 999);
uint32_t ourPort = 2000; uint32_t ourPort = Game::config->GetValue("master_server_port", 2000);
std::string ourIP = "localhost"; std::string ourIP = Game::config->GetValue("external_ip", "localhost");
const auto maxClientsString = Game::config->GetValue("max_clients");
if (!maxClientsString.empty()) maxClients = std::stoi(maxClientsString);
const auto masterServerPortString = Game::config->GetValue("master_server_port");
if (!masterServerPortString.empty()) ourPort = std::atoi(masterServerPortString.c_str());
const auto externalIPString = Game::config->GetValue("external_ip");
if (!externalIPString.empty()) ourIP = externalIPString;
char salt[BCRYPT_HASHSIZE]; char salt[BCRYPT_HASHSIZE];
char hash[BCRYPT_HASHSIZE]; char hash[BCRYPT_HASHSIZE];
const auto& cfgPassword = Game::config->GetValue("master_password"); const auto& cfgPassword = Game::config->GetValue<std::string>("master_password", "3.25DARKFLAME1");
int res = GenerateBCryptPassword(!cfgPassword.empty() ? cfgPassword : "3.25DARKFLAME1", 13, salt, hash); int res = GenerateBCryptPassword(cfgPassword, 13, salt, hash);
assert(res == 0); assert(res == 0);
Game::server = new dServer(ourIP, ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServiceType::MASTER, Game::config, &Game::lastSignal, hash); Game::server = new dServer(ourIP, ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServiceType::MASTER, Game::config, &Game::lastSignal, hash);
@@ -535,8 +529,7 @@ void HandlePacket(Packet* packet) {
case MessageType::Master::REQUEST_ZONE_TRANSFER: { case MessageType::Master::REQUEST_ZONE_TRANSFER: {
LOG("Received zone transfer req"); LOG("Received zone transfer req");
RakNet::BitStream inStream(packet->data, packet->length, false); CINSTREAM_SKIP_HEADER;
uint64_t header = inStream.Read(header);
uint64_t requestID = 0; uint64_t requestID = 0;
uint8_t mythranShift = false; uint8_t mythranShift = false;
uint32_t zoneID = 0; uint32_t zoneID = 0;
@@ -575,8 +568,7 @@ void HandlePacket(Packet* packet) {
//This is here because otherwise we'd have to include IM in //This is here because otherwise we'd have to include IM in
//non-master servers. This packet allows us to add World //non-master servers. This packet allows us to add World
//servers back if master crashed //servers back if master crashed
RakNet::BitStream inStream(packet->data, packet->length, false); CINSTREAM_SKIP_HEADER;
uint64_t header = inStream.Read(header);
uint32_t theirPort = 0; uint32_t theirPort = 0;
uint32_t theirZoneID = 0; uint32_t theirZoneID = 0;
@@ -602,13 +594,13 @@ void HandlePacket(Packet* packet) {
instance->SetSysAddr(packet->systemAddress); instance->SetSysAddr(packet->systemAddress);
} }
} }
break; break;
case ServiceType::CHAT: case ServiceType::CHAT:
chatServerMasterPeerSysAddr = packet->systemAddress; chatServerMasterPeerSysAddr = packet->systemAddress;
break; break;
case ServiceType::AUTH: case ServiceType::AUTH:
authServerMasterPeerSysAddr = packet->systemAddress; authServerMasterPeerSysAddr = packet->systemAddress;
break; break;
default: default:
// We just ignore any other server type // We just ignore any other server type
break; break;
@@ -664,8 +656,7 @@ void HandlePacket(Packet* packet) {
} }
case MessageType::Master::PLAYER_ADDED: { case MessageType::Master::PLAYER_ADDED: {
RakNet::BitStream inStream(packet->data, packet->length, false); CINSTREAM_SKIP_HEADER;
uint64_t header = inStream.Read(header);
LWOMAPID theirZoneID = 0; LWOMAPID theirZoneID = 0;
LWOINSTANCEID theirInstanceID = 0; LWOINSTANCEID theirInstanceID = 0;
@@ -684,8 +675,7 @@ void HandlePacket(Packet* packet) {
} }
case MessageType::Master::PLAYER_REMOVED: { case MessageType::Master::PLAYER_REMOVED: {
RakNet::BitStream inStream(packet->data, packet->length, false); CINSTREAM_SKIP_HEADER;
uint64_t header = inStream.Read(header);
LWOMAPID theirZoneID = 0; LWOMAPID theirZoneID = 0;
LWOINSTANCEID theirInstanceID = 0; LWOINSTANCEID theirInstanceID = 0;
@@ -702,8 +692,7 @@ void HandlePacket(Packet* packet) {
} }
case MessageType::Master::CREATE_PRIVATE_ZONE: { case MessageType::Master::CREATE_PRIVATE_ZONE: {
RakNet::BitStream inStream(packet->data, packet->length, false); CINSTREAM_SKIP_HEADER;
uint64_t header = inStream.Read(header);
uint32_t mapId; uint32_t mapId;
LWOCLONEID cloneId; LWOCLONEID cloneId;
@@ -714,6 +703,8 @@ void HandlePacket(Packet* packet) {
uint32_t len; uint32_t len;
inStream.Read<uint32_t>(len); inStream.Read<uint32_t>(len);
len = std::min<uint32_t>(len, 50); // cap the master password at 50 characters
for (uint32_t i = 0; len > i; i++) { for (uint32_t i = 0; len > i; i++) {
char character; char character;
inStream.Read<char>(character); inStream.Read<char>(character);
@@ -726,8 +717,7 @@ void HandlePacket(Packet* packet) {
} }
case MessageType::Master::REQUEST_PRIVATE_ZONE: { case MessageType::Master::REQUEST_PRIVATE_ZONE: {
RakNet::BitStream inStream(packet->data, packet->length, false); CINSTREAM_SKIP_HEADER;
uint64_t header = inStream.Read(header);
uint64_t requestID = 0; uint64_t requestID = 0;
uint8_t mythranShift = false; uint8_t mythranShift = false;
@@ -739,6 +729,7 @@ void HandlePacket(Packet* packet) {
uint32_t len; uint32_t len;
inStream.Read<uint32_t>(len); inStream.Read<uint32_t>(len);
len = std::min<uint32_t>(len, 50);
for (uint32_t i = 0; i < len; i++) { for (uint32_t i = 0; i < len; i++) {
char character; inStream.Read<char>(character); char character; inStream.Read<char>(character);
@@ -761,8 +752,7 @@ void HandlePacket(Packet* packet) {
} }
case MessageType::Master::WORLD_READY: { case MessageType::Master::WORLD_READY: {
RakNet::BitStream inStream(packet->data, packet->length, false); CINSTREAM_SKIP_HEADER;
uint64_t header = inStream.Read(header);
LWOMAPID zoneID; LWOMAPID zoneID;
LWOINSTANCEID instanceID; LWOINSTANCEID instanceID;
@@ -785,8 +775,7 @@ void HandlePacket(Packet* packet) {
} }
case MessageType::Master::PREP_ZONE: { case MessageType::Master::PREP_ZONE: {
RakNet::BitStream inStream(packet->data, packet->length, false); CINSTREAM_SKIP_HEADER;
uint64_t header = inStream.Read(header);
int32_t zoneID; int32_t zoneID;
inStream.Read(zoneID); inStream.Read(zoneID);
@@ -801,8 +790,7 @@ void HandlePacket(Packet* packet) {
} }
case MessageType::Master::AFFIRM_TRANSFER_RESPONSE: { case MessageType::Master::AFFIRM_TRANSFER_RESPONSE: {
RakNet::BitStream inStream(packet->data, packet->length, false); CINSTREAM_SKIP_HEADER;
uint64_t header = inStream.Read(header);
uint64_t requestID; uint64_t requestID;
@@ -876,7 +864,7 @@ int ShutdownSequence(int32_t signal) {
if (!instance->GetIsReady()) { if (!instance->GetIsReady()) {
Game::im->RemoveInstance(instance); Game::im->RemoveInstance(instance);
} }
instance->SetIsShuttingDown(true); instance->SetIsShuttingDown(true);
} }

View File

@@ -37,6 +37,10 @@ dNavMesh::~dNavMesh() {
void dNavMesh::LoadNavmesh() { void dNavMesh::LoadNavmesh() {
struct FPClose {
FILE* fp{};
~FPClose() { if (fp) fclose(fp); }
} fp;
std::string path = (BinaryPathFinder::GetBinaryDir() / "navmeshes/" / (std::to_string(m_ZoneId) + ".bin")).string(); std::string path = (BinaryPathFinder::GetBinaryDir() / "navmeshes/" / (std::to_string(m_ZoneId) + ".bin")).string();
@@ -44,55 +48,48 @@ void dNavMesh::LoadNavmesh() {
return; return;
} }
FILE* fp;
#ifdef _WIN32 #ifdef _WIN32
fopen_s(&fp, path.c_str(), "rb"); fopen_s(&fp.fp, path.c_str(), "rb");
#elif __APPLE__ #elif __APPLE__
// macOS has 64bit file IO by default // macOS has 64bit file IO by default
fp = fopen(path.c_str(), "rb"); fp.fp = fopen(path.c_str(), "rb");
#else #else
fp = fopen64(path.c_str(), "rb"); fp.fp = fopen64(path.c_str(), "rb");
#endif #endif
if (!fp) { if (!fp.fp) {
return; return;
} }
// Read header. // Read header.
NavMeshSetHeader header; NavMeshSetHeader header;
size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, fp); size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, fp.fp);
if (readLen != 1) { if (readLen != 1) {
fclose(fp);
return; return;
} }
if (header.magic != NAVMESHSET_MAGIC) { if (header.magic != NAVMESHSET_MAGIC) {
fclose(fp);
return; return;
} }
if (header.version != NAVMESHSET_VERSION) { if (header.version != NAVMESHSET_VERSION) {
fclose(fp);
return; return;
} }
dtNavMesh* mesh = dtAllocNavMesh(); dtNavMesh* mesh = dtAllocNavMesh();
if (!mesh) { if (!mesh) {
fclose(fp);
return; return;
} }
dtStatus status = mesh->init(&header.params); dtStatus status = mesh->init(&header.params);
if (dtStatusFailed(status)) { if (dtStatusFailed(status)) {
fclose(fp);
return; return;
} }
// Read tiles. // Read tiles.
for (int i = 0; i < header.numTiles; ++i) { for (int i = 0; i < header.numTiles; ++i) {
NavMeshTileHeader tileHeader; NavMeshTileHeader tileHeader;
readLen = fread(&tileHeader, sizeof(tileHeader), 1, fp); readLen = fread(&tileHeader, sizeof(tileHeader), 1, fp.fp);
if (readLen != 1) return; if (readLen != 1) return;
if (!tileHeader.tileRef || !tileHeader.dataSize) if (!tileHeader.tileRef || !tileHeader.dataSize)
@@ -101,14 +98,15 @@ void dNavMesh::LoadNavmesh() {
unsigned char* data = static_cast<unsigned char*>(dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM)); unsigned char* data = static_cast<unsigned char*>(dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM));
if (!data) break; if (!data) break;
memset(data, 0, tileHeader.dataSize); memset(data, 0, tileHeader.dataSize);
readLen = fread(data, tileHeader.dataSize, 1, fp); readLen = fread(data, tileHeader.dataSize, 1, fp.fp);
if (readLen != 1) return; if (readLen != 1) {
dtFree(data);
return;
}
mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0); mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0);
} }
fclose(fp);
m_NavMesh = mesh; m_NavMesh = mesh;
} }
@@ -187,13 +185,18 @@ std::vector<NiPoint3> dNavMesh::GetPath(const NiPoint3& startPos, const NiPoint3
path.push_back(startPos); //insert the start pos path.push_back(startPos); //insert the start pos
// Linearly interpolate between these two points: // Linearly interpolate between these two points:
for (int i = 0; i < numPoints; i++) { const auto yDist = endPos.y - startPos.y;
NiPoint3 newPoint{ startPos }; // Ensure we cannot divide by zero
const auto xDist = endPos.x - startPos.x;
if (xDist != 0.0f) {
for (int i = 0; i < numPoints; i++) {
NiPoint3 newPoint{ startPos };
newPoint.x += speed; newPoint.x += speed;
newPoint.y = newPoint.y + (((endPos.y - startPos.y) / (endPos.x - startPos.x)) * (newPoint.x - startPos.x)); newPoint.y = newPoint.y + ((yDist / xDist) * (newPoint.x - startPos.x));
path.push_back(newPoint); path.push_back(newPoint);
}
} }
path.push_back(endPos); //finally insert our end pos path.push_back(endPos); //finally insert our end pos
@@ -212,16 +215,16 @@ std::vector<NiPoint3> dNavMesh::GetPath(const NiPoint3& startPos, const NiPoint3
ePos[2] = endPos.z; ePos[2] = endPos.z;
dtStatus pathFindStatus; dtStatus pathFindStatus;
dtPolyRef startRef; dtPolyRef startRef{};
dtPolyRef endRef; dtPolyRef endRef{};
float polyPickExt[3] = { 32.0f, 32.0f, 32.0f }; float polyPickExt[3] = { 32.0f, 32.0f, 32.0f };
dtQueryFilter filter{}; dtQueryFilter filter{};
//Find our start poly //Find our start poly
m_NavQuery->findNearestPoly(sPos, polyPickExt, &filter, &startRef, 0); const auto startResult = m_NavQuery->findNearestPoly(sPos, polyPickExt, &filter, &startRef, 0);
//Find our end poly //Find our end poly
m_NavQuery->findNearestPoly(ePos, polyPickExt, &filter, &endRef, 0); const auto endResult = m_NavQuery->findNearestPoly(ePos, polyPickExt, &filter, &endRef, 0);
pathFindStatus = DT_FAILURE; pathFindStatus = DT_FAILURE;
int m_nstraightPath = 0; int m_nstraightPath = 0;
@@ -232,7 +235,7 @@ std::vector<NiPoint3> dNavMesh::GetPath(const NiPoint3& startPos, const NiPoint3
dtPolyRef m_straightPathPolys[MAX_POLYS]; dtPolyRef m_straightPathPolys[MAX_POLYS];
int m_straightPathOptions = 0; int m_straightPathOptions = 0;
if (startRef && endRef) { if (dtStatusSucceed(startResult) && dtStatusSucceed(endResult)) {
m_NavQuery->findPath(startRef, endRef, sPos, ePos, &filter, m_polys, &m_npolys, MAX_POLYS); m_NavQuery->findPath(startRef, endRef, sPos, ePos, &filter, m_polys, &m_npolys, MAX_POLYS);
if (m_npolys) { if (m_npolys) {

View File

@@ -18,6 +18,7 @@ RawChunk::RawChunk(std::ifstream& stream) {
// We can just skip the rest of the data so we can read the next chunks, we don't need anymore data // We can just skip the rest of the data so we can read the next chunks, we don't need anymore data
// Possible overflow here? TODO make reasonable upper bound or confirm big numbers arent necessary to have
uint32_t colorMapSize; uint32_t colorMapSize;
BinaryIO::BinaryRead(stream, colorMapSize); BinaryIO::BinaryRead(stream, colorMapSize);
stream.seekg(static_cast<uint32_t>(stream.tellg()) + (colorMapSize * colorMapSize * 4)); stream.seekg(static_cast<uint32_t>(stream.tellg()) + (colorMapSize * colorMapSize * 4));

View File

@@ -157,8 +157,7 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) {
} }
//If we aren't running in live mode, then only GMs are allowed to enter: //If we aren't running in live mode, then only GMs are allowed to enter:
const auto& closedToNonDevs = Game::config->GetValue("closed_to_non_devs"); if (Game::config->GetValue<bool>("closed_to_non_devs", false) && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) {
if (closedToNonDevs.size() > 0 && bool(std::stoi(closedToNonDevs)) && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) {
stamps.emplace_back(eStamps::GM_REQUIRED, 1); stamps.emplace_back(eStamps::GM_REQUIRED, 1);
AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "The server is currently only open to developers.", "", 2001, username, stamps); AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "The server is currently only open to developers.", "", 2001, username, stamps);
return; return;

View File

@@ -108,6 +108,7 @@ ChatModerationRequest ClientPackets::HandleChatModerationRequest(Packet* packet)
uint16_t messageLength; uint16_t messageLength;
inStream.Read(messageLength); inStream.Read(messageLength);
if (messageLength > MAX_MESSAGE_LENGTH) return request;
for (uint32_t i = 0; i < messageLength; ++i) { for (uint32_t i = 0; i < messageLength; ++i) {
uint16_t character; uint16_t character;
inStream.Read(character); inStream.Read(character);

View File

@@ -77,7 +77,8 @@ void MasterPackets::SendZoneTransferResponse(dServer* server, const SystemAddres
void MasterPackets::HandleServerInfo(Packet* packet) { void MasterPackets::HandleServerInfo(Packet* packet) {
RakNet::BitStream inStream(packet->data, packet->length, false); RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header); uint64_t header{};
inStream.Read(header);
uint32_t theirPort = 0; uint32_t theirPort = 0;
uint32_t theirZoneID = 0; uint32_t theirZoneID = 0;

View File

@@ -103,12 +103,9 @@ void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, int64_t rep
//Compress the data before sending: //Compress the data before sending:
const uint32_t reservedSize = ZCompression::GetMaxCompressedLength(data.GetNumberOfBytesUsed()); const uint32_t reservedSize = ZCompression::GetMaxCompressedLength(data.GetNumberOfBytesUsed());
uint8_t* compressedData = new uint8_t[reservedSize]; auto compressedData = std::make_unique<uint8_t[]>(reservedSize);
// TODO There should be better handling here for not enough memory... size_t size = ZCompression::Compress(data.GetData(), data.GetNumberOfBytesUsed(), compressedData.get(), reservedSize);
if (!compressedData) return;
size_t size = ZCompression::Compress(data.GetData(), data.GetNumberOfBytesUsed(), compressedData, reservedSize);
assert(size <= reservedSize); assert(size <= reservedSize);
@@ -123,11 +120,10 @@ void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, int64_t rep
* an assertion is done to prevent bad data from being saved or sent. * an assertion is done to prevent bad data from being saved or sent.
*/ */
#pragma warning(disable:6385) // C6385 Reading invalid data from 'compressedData'. #pragma warning(disable:6385) // C6385 Reading invalid data from 'compressedData'.
bitStream.WriteAlignedBytes(compressedData, size); bitStream.WriteAlignedBytes(compressedData.get(), size);
#pragma warning(default:6385) #pragma warning(default:6385)
SEND_PACKET; SEND_PACKET;
delete[] compressedData;
LOG("Sent CreateCharacter for ID: %llu", player); LOG("Sent CreateCharacter for ID: %llu", player);
} }

View File

@@ -233,13 +233,11 @@ bool dServer::Startup() {
} }
void dServer::UpdateMaximumMtuSize() { void dServer::UpdateMaximumMtuSize() {
auto maxMtuSize = mConfig->GetValue("maximum_mtu_size"); mPeer->SetMTUSize(mConfig->GetValue<int32_t>("maximum_mtu_size", 1228));
mPeer->SetMTUSize(maxMtuSize.empty() ? 1228 : std::stoi(maxMtuSize));
} }
void dServer::UpdateBandwidthLimit() { void dServer::UpdateBandwidthLimit() {
auto newBandwidth = mConfig->GetValue("maximum_outgoing_bandwidth"); mPeer->SetPerConnectionOutgoingBandwidthLimit(mConfig->GetValue<int32_t>("maximum_outgoing_bandwidth", 0));
mPeer->SetPerConnectionOutgoingBandwidthLimit(!newBandwidth.empty() ? std::stoi(newBandwidth) : 0);
} }
void dServer::Shutdown() { void dServer::Shutdown() {

View File

@@ -109,6 +109,7 @@ void dpEntity::SetPosition(const NiPoint3& newPos) {
} }
void dpEntity::SetRotation(const NiQuaternion& newRot) { void dpEntity::SetRotation(const NiQuaternion& newRot) {
if (!m_CollisionShape) return;
m_Rotation = newRot; m_Rotation = newRot;
if (m_CollisionShape->GetShapeType() == dpShapeType::Box) { if (m_CollisionShape->GetShapeType() == dpShapeType::Box) {
@@ -118,6 +119,7 @@ void dpEntity::SetRotation(const NiQuaternion& newRot) {
} }
void dpEntity::SetScale(float newScale) { void dpEntity::SetScale(float newScale) {
if (!m_CollisionShape) return;
m_Scale = newScale; m_Scale = newScale;
if (m_CollisionShape->GetShapeType() == dpShapeType::Box) { if (m_CollisionShape->GetShapeType() == dpShapeType::Box) {

View File

@@ -87,7 +87,7 @@ void dpWorld::Shutdown() {
} }
bool dpWorld::IsLoaded() { bool dpWorld::IsLoaded() {
return m_NavMesh->IsNavmeshLoaded(); return m_NavMesh && m_NavMesh->IsNavmeshLoaded();
} }
void dpWorld::StepWorld(float deltaTime) { void dpWorld::StepWorld(float deltaTime) {

View File

@@ -36,13 +36,14 @@ void AmSkullkinDrill::OnStartup(Entity* self) {
Entity* AmSkullkinDrill::GetStandObj(Entity* self) { Entity* AmSkullkinDrill::GetStandObj(Entity* self) {
const auto& myGroup = self->GetGroups(); const auto& myGroup = self->GetGroups();
if (myGroup.empty()) { if (myGroup.empty() || myGroup[0].empty()) {
return nullptr; return nullptr;
} }
const auto& group = myGroup[0];
std::string groupName = "Drill_Stand_"; std::string groupName = "Drill_Stand_";
groupName.push_back(myGroup[0][myGroup[0].size() - 1]); groupName.push_back(group.back());
const auto standObjs = Game::entityManager->GetEntitiesInGroup(groupName); const auto standObjs = Game::entityManager->GetEntitiesInGroup(groupName);

View File

@@ -7,9 +7,14 @@
void Binoculars::OnUse(Entity* self, Entity* user) { void Binoculars::OnUse(Entity* self, Entity* user) {
const auto number = self->GetVarAsString(u"number"); const auto number = self->GetVarAsString(u"number");
int32_t flag = std::stoi(std::to_string(Game::server->GetZoneID()).substr(0, 2) + number); const int32_t flag = GeneralUtils::TryParse(std::to_string(Game::server->GetZoneID()).substr(0, 2) + number, 0);
if (user->GetCharacter()->GetPlayerFlag(flag) == false) { GameMessages::GetFlag flagMsg;
user->GetCharacter()->SetPlayerFlag(flag, true); flagMsg.target = user->GetObjectID();
flagMsg.flagID = flag;
flagMsg.Send();
if (!flagMsg.flag) {
auto* const character = user->GetCharacter();
if (character) character->SetPlayerFlag(flag, true);
GameMessages::SendFireEventClientSide(self->GetObjectID(), user->GetSystemAddress(), u"achieve", LWOOBJID_EMPTY, 0, -1, LWOOBJID_EMPTY); GameMessages::SendFireEventClientSide(self->GetObjectID(), user->GetSystemAddress(), u"achieve", LWOOBJID_EMPTY, 0, -1, LWOOBJID_EMPTY);
} }
} }

View File

@@ -33,14 +33,18 @@ void StoryBoxInteractServer::OnUse(Entity* self, Entity* user) {
const auto storyText = self->GetVarAsString(u"storyText"); const auto storyText = self->GetVarAsString(u"storyText");
if (storyText.length() > 2) { if (storyText.length() > 2) {
auto storyValue = GeneralUtils::TryParse<uint32_t>(storyText.substr(storyText.length() - 2)); auto storyValue = GeneralUtils::TryParse<uint32_t>(storyText.substr(storyText.length() - 2));
if(!storyValue) return; if (!storyValue) return;
int32_t boxFlag = self->GetVar<int32_t>(u"altFlagID"); int32_t boxFlag = self->GetVar<int32_t>(u"altFlagID");
if (boxFlag <= 0) { if (boxFlag <= 0) {
boxFlag = (10000 + Game::server->GetZoneID() + storyValue.value()); boxFlag = (10000 + Game::server->GetZoneID() + storyValue.value());
} }
GameMessages::GetFlag flagMsg;
if (user->GetCharacter()->GetPlayerFlag(boxFlag) == false) { flagMsg.target = user->GetObjectID();
user->GetCharacter()->SetPlayerFlag(boxFlag, true); flagMsg.flagID = boxFlag;
flagMsg.Send();
if (!flagMsg.flag) {
auto* const character = user->GetCharacter();
if (character) character->SetPlayerFlag(boxFlag, true);
GameMessages::SendFireEventClientSide(self->GetObjectID(), user->GetSystemAddress(), u"achieve", LWOOBJID_EMPTY, 0, -1, LWOOBJID_EMPTY); GameMessages::SendFireEventClientSide(self->GetObjectID(), user->GetSystemAddress(), u"achieve", LWOOBJID_EMPTY, 0, -1, LWOOBJID_EMPTY);
} }
} }

View File

@@ -26,7 +26,8 @@ void NsConcertChoiceBuildManager::SpawnCrate(Entity* self) {
const auto splitGroup = GeneralUtils::SplitString(group, '_'); const auto splitGroup = GeneralUtils::SplitString(group, '_');
if (splitGroup.size() < 2) if (splitGroup.size() < 2)
return; return;
const auto groupNumber = std::stoi(splitGroup.at(1)); const auto groupNumber = GeneralUtils::TryParse(splitGroup.at(1), -1);
if (groupNumber == -1) return;
EntityInfo info{}; EntityInfo info{};
info.lot = crate.lot; info.lot = crate.lot;

View File

@@ -33,7 +33,8 @@ void NtParadoxPanelServer::OnUse(Entity* self, Entity* user) {
const auto flag = self->GetVar<int32_t>(u"flag"); const auto flag = self->GetVar<int32_t>(u"flag");
player->GetCharacter()->SetPlayerFlag(flag, true); auto* const character = player->GetCharacter();
if (character) character->SetPlayerFlag(flag, true);
RenderComponent::PlayAnimation(player, u"rebuild-celebrate"); RenderComponent::PlayAnimation(player, u"rebuild-celebrate");

View File

@@ -413,7 +413,8 @@ void ZoneAgProperty::BaseOnFireEventServerSide(Entity* self, Entity* sender, std
if (player == nullptr) if (player == nullptr)
return; return;
player->GetCharacter()->SetPlayerFlag(self->GetVar<int32_t>(defeatedProperyFlag), true); auto* const character = player->GetCharacter();
if (character) character->SetPlayerFlag(self->GetVar<int32_t>(defeatedProperyFlag), true);
GameMessages::SendNotifyClientObject(self->GetObjectID(), u"PlayCinematic", 0, 0, GameMessages::SendNotifyClientObject(self->GetObjectID(), u"PlayCinematic", 0, 0,
LWOOBJID_EMPTY, destroyedCinematic, UNASSIGNED_SYSTEM_ADDRESS); LWOOBJID_EMPTY, destroyedCinematic, UNASSIGNED_SYSTEM_ADDRESS);

View File

@@ -15,7 +15,7 @@ void VeMissionConsole::OnUse(Entity* self, Entity* user) {
// The flag to set is 101<number> // The flag to set is 101<number>
const auto flagNumber = self->GetVar<std::u16string>(m_NumberVariable); const auto flagNumber = self->GetVar<std::u16string>(m_NumberVariable);
const int32_t flag = std::stoi("101" + GeneralUtils::UTF16ToWTF8(flagNumber)); const int32_t flag = GeneralUtils::TryParse("101" + GeneralUtils::UTF16ToWTF8(flagNumber), 0);
auto* character = user->GetCharacter(); auto* character = user->GetCharacter();
if (character != nullptr) { if (character != nullptr) {

View File

@@ -13,7 +13,10 @@ void CavePrisonCage::OnStartup(Entity* self) {
return; return;
} }
auto* spawner = Game::zoneManager->GetSpawnersByName("PrisonCounterweight_0" + GeneralUtils::UTF16ToWTF8(myNum))[0]; const auto spawners = Game::zoneManager->GetSpawnersByName("PrisonCounterweight_0" + GeneralUtils::UTF16ToWTF8(myNum));
if (spawners.empty()) return;
auto* spawner = spawners[0];
self->SetVar<Spawner*>(u"CWSpawner", spawner); self->SetVar<Spawner*>(u"CWSpawner", spawner);
@@ -21,6 +24,7 @@ void CavePrisonCage::OnStartup(Entity* self) {
} }
void CavePrisonCage::Setup(Entity* self, Spawner* spawner) { void CavePrisonCage::Setup(Entity* self, Spawner* spawner) {
if (!spawner) return;
SpawnCounterweight(self, spawner); SpawnCounterweight(self, spawner);
NiPoint3 mypos = self->GetPosition(); NiPoint3 mypos = self->GetPosition();
@@ -62,6 +66,8 @@ void CavePrisonCage::OnQuickBuildNotifyState(Entity* self, eQuickBuildState stat
} }
void CavePrisonCage::SpawnCounterweight(Entity* self, Spawner* spawner) { void CavePrisonCage::SpawnCounterweight(Entity* self, Spawner* spawner) {
if (!spawner) return;
spawner->Reset(); spawner->Reset();
auto* counterweight = spawner->Spawn(); auto* counterweight = spawner->Spawn();
@@ -164,7 +170,8 @@ void CavePrisonCage::OnTimerDone(Entity* self, std::string timerName) {
const auto flagNum = 2020 + self->GetVarAs<int32_t>(u"myNumber"); const auto flagNum = 2020 + self->GetVarAs<int32_t>(u"myNumber");
// Set the flag on the builder character // Set the flag on the builder character
builder->GetCharacter()->SetPlayerFlag(flagNum, true); auto* const character = builder->GetCharacter();
if (character) character->SetPlayerFlag(flagNum, true);
// Setup a timer named 'VillagerEscape' to be triggered in 5 seconds // Setup a timer named 'VillagerEscape' to be triggered in 5 seconds
self->AddTimer("VillagerEscape", 5.0f); self->AddTimer("VillagerEscape", 5.0f);

View File

@@ -408,7 +408,7 @@ void NjMonastryBossInstance::SummonWave(Entity* self, Entity* frakjaw) {
// Stop the music for the first, fourth and fifth wave // Stop the music for the first, fourth and fifth wave
const auto wave = self->GetVar<uint32_t>(WaveNumberVariable); const auto wave = self->GetVar<uint32_t>(WaveNumberVariable);
if (wave >= 1 || wave < (m_Waves.size() - 1)) { if (wave >= 1 && wave < (m_Waves.size() - 1)) {
GameMessages::SendNotifyClientObject(self->GetObjectID(), StopMusicNotification, 0, 0, GameMessages::SendNotifyClientObject(self->GetObjectID(), StopMusicNotification, 0, 0,
LWOOBJID_EMPTY, AudioWaveAudio + std::to_string(wave - 1), LWOOBJID_EMPTY, AudioWaveAudio + std::to_string(wave - 1),
UNASSIGNED_SYSTEM_ADDRESS); UNASSIGNED_SYSTEM_ADDRESS);

View File

@@ -96,7 +96,7 @@ void BaseConsoleTeleportServer::TransferPlayer(Entity* self, Entity* player, int
auto* characterComponent = player->GetComponent<CharacterComponent>(); auto* characterComponent = player->GetComponent<CharacterComponent>();
if (characterComponent) characterComponent->SendToZone(std::stoi(GeneralUtils::UTF16ToWTF8(teleportZone))); if (characterComponent) characterComponent->SendToZone(GeneralUtils::TryParse(GeneralUtils::UTF16ToWTF8(teleportZone), 0));
UpdatePlayerTable(self, player, false); UpdatePlayerTable(self, player, false);
} }

View File

@@ -127,12 +127,15 @@ void BasePropertyServer::BasePlayerLoaded(Entity* self, Entity* player) {
if (player->GetObjectID() != propertyOwner) if (player->GetObjectID() != propertyOwner)
return; return;
} else { } else {
const auto defeatedFlag = player->GetCharacter()->GetPlayerFlag(self->GetVar<int32_t>(defeatedProperyFlag)); GameMessages::GetFlag flagMsg;
flagMsg.target = player->GetObjectID();
flagMsg.flagID = self->GetVar<int32_t>(defeatedProperyFlag);
flagMsg.Send();
self->SetNetworkVar(UnclaimedVariable, true); self->SetNetworkVar(UnclaimedVariable, true);
self->SetVar<LWOOBJID>(PlayerIDVariable, player->GetObjectID()); self->SetVar<LWOOBJID>(PlayerIDVariable, player->GetObjectID());
if (!defeatedFlag) { if (!flagMsg.flag) {
StartMaelstrom(self, player); StartMaelstrom(self, player);
SpawnSpots(self); SpawnSpots(self);
GameMessages::SendPlay2DAmbientSound(player, GUIDMaelstrom); GameMessages::SendPlay2DAmbientSound(player, GUIDMaelstrom);

View File

@@ -124,21 +124,23 @@ void BaseRandomServer::NotifySpawnerOfDeath(Entity* self, Spawner* spawner) {
return; return;
} }
const auto& sectionName = spawnerName.substr(0, spawnerName.size() - 7); if (spawnerName.size() >= 7) {
const auto& sectionName = spawnerName.substr(0, spawnerName.size() - 7);
const auto variableName = u"mobsDead" + GeneralUtils::ASCIIToUTF16(sectionName); const auto variableName = u"mobsDead" + GeneralUtils::ASCIIToUTF16(sectionName);
auto mobDeathCount = self->GetVar<int32_t>(variableName); auto mobDeathCount = self->GetVar<int32_t>(variableName);
mobDeathCount++; mobDeathCount++;
if (mobDeathCount >= mobDeathResetNumber) { if (mobDeathCount >= mobDeathResetNumber) {
const auto& zoneInfo = GeneralUtils::SplitString(sectionName, '_'); const auto& zoneInfo = GeneralUtils::SplitString(sectionName, '_');
SpawnSection(self, sectionName, sectionMultipliers[zoneInfo[sectionIDConst - 1]]); SpawnSection(self, sectionName, sectionMultipliers[zoneInfo[sectionIDConst - 1]]);
}
self->SetVar(variableName, mobDeathCount);
} }
self->SetVar(variableName, mobDeathCount);
} }
void BaseRandomServer::NamedEnemyDeath(Entity* self, Spawner* spawner) { void BaseRandomServer::NamedEnemyDeath(Entity* self, Spawner* spawner) {

View File

@@ -37,7 +37,7 @@ public:
void NamedTimerDone(Entity* self, const std::string& timerName); void NamedTimerDone(Entity* self, const std::string& timerName);
protected: protected:
std::vector<int32_t> namedMobs = { const std::vector<int32_t> namedMobs = {
11988, // Ronin 11988, // Ronin
11984, // Spiderling 11984, // Spiderling
12654, // Horsemen 12654, // Horsemen

View File

@@ -24,7 +24,8 @@ void BaseSurvivalServer::BasePlayerLoaded(Entity* self, Entity* player) {
if (waitingIter != state.waitingPlayers.end() || playersIter != state.players.end()) { if (waitingIter != state.waitingPlayers.end() || playersIter != state.players.end()) {
auto* characterComponent = player->GetComponent<CharacterComponent>(); auto* characterComponent = player->GetComponent<CharacterComponent>();
if (characterComponent) characterComponent->SendToZone(player->GetCharacter()->GetLastNonInstanceZoneID()); const auto* const character = player->GetCharacter();
if (characterComponent && character) characterComponent->SendToZone(character->GetLastNonInstanceZoneID());
return; return;
} }

View File

@@ -73,7 +73,7 @@ void NtFactionSpyServer::OnCinematicUpdate(Entity* self, Entity* sender, eCinema
// Make sure we have a path of type <root>_<index> // Make sure we have a path of type <root>_<index>
if (pathSplit.size() >= 2) { if (pathSplit.size() >= 2) {
auto pathRoot = pathSplit.at(0); auto pathRoot = pathSplit.at(0);
auto pathIndex = std::stoi(GeneralUtils::UTF16ToWTF8(pathSplit.at(1))) - 1; auto pathIndex = GeneralUtils::TryParse(GeneralUtils::UTF16ToWTF8(pathSplit.at(1)), -1) - 1;
const auto& dialogueTable = self->GetVar<std::vector<SpyDialogue>>(m_SpyDialogueTableVariable); const auto& dialogueTable = self->GetVar<std::vector<SpyDialogue>>(m_SpyDialogueTableVariable);
// Make sure we're listening to the root we're interested in // Make sure we're listening to the root we're interested in

View File

@@ -57,7 +57,7 @@ bool SpawnPetBaseServer::CheckNumberOfPets(Entity* self, Entity* user) {
if (petID.empty()) if (petID.empty())
continue; continue;
const auto* spawnedPet = Game::entityManager->GetEntity(std::stoull(petID)); const auto* spawnedPet = Game::entityManager->GetEntity(GeneralUtils::TryParse(petID, LWOOBJID_EMPTY));
if (spawnedPet == nullptr) if (spawnedPet == nullptr)
continue; continue;

View File

@@ -24,8 +24,11 @@ void AgFans::OnStartup(Entity* self) {
} }
void AgFans::ToggleFX(Entity* self, bool hit) { void AgFans::ToggleFX(Entity* self, bool hit) {
std::string fanGroup = self->GetGroups()[0]; const auto groups = self->GetGroups();
std::vector<Entity*> fanVolumes = Game::entityManager->GetEntitiesInGroup(fanGroup); std::string fanGroup = groups.empty() ? "" : groups[0];
const auto fanVolumes = Game::entityManager->GetEntitiesInGroup(fanGroup);
const auto fxObjs = Game::entityManager->GetEntitiesInGroup(fanGroup + "fx");
auto* const fxObj = fxObjs.empty() ? nullptr : fxObjs[0];
auto* renderComponent = static_cast<RenderComponent*>(self->GetComponent(eReplicaComponentType::RENDER)); auto* renderComponent = static_cast<RenderComponent*>(self->GetComponent(eReplicaComponentType::RENDER));
@@ -47,8 +50,7 @@ void AgFans::ToggleFX(Entity* self, bool hit) {
volumePhys->SetPhysicsEffectActive(false); volumePhys->SetPhysicsEffectActive(false);
Game::entityManager->SerializeEntity(volume); Game::entityManager->SerializeEntity(volume);
if (!hit) { if (!hit) {
Entity* fxObj = Game::entityManager->GetEntitiesInGroup(fanGroup + "fx")[0]; if (fxObj) RenderComponent::PlayAnimation(fxObj, u"trigger");
RenderComponent::PlayAnimation(fxObj, u"trigger");
} }
} }
} else if (!self->GetVar<bool>(u"on") && self->GetVar<bool>(u"alive")) { } else if (!self->GetVar<bool>(u"on") && self->GetVar<bool>(u"alive")) {
@@ -63,8 +65,7 @@ void AgFans::ToggleFX(Entity* self, bool hit) {
volumePhys->SetPhysicsEffectActive(true); volumePhys->SetPhysicsEffectActive(true);
Game::entityManager->SerializeEntity(volume); Game::entityManager->SerializeEntity(volume);
if (!hit) { if (!hit) {
Entity* fxObj = Game::entityManager->GetEntitiesInGroup(fanGroup + "fx")[0]; if (fxObj) RenderComponent::PlayAnimation(fxObj, u"idle");
RenderComponent::PlayAnimation(fxObj, u"idle");
} }
} }
} }

View File

@@ -6,6 +6,7 @@
void FvBrickPuzzleServer::OnStartup(Entity* self) { void FvBrickPuzzleServer::OnStartup(Entity* self) {
const auto myGroup = GeneralUtils::UTF16ToWTF8(self->GetVar<std::u16string>(u"spawner_name")); const auto myGroup = GeneralUtils::UTF16ToWTF8(self->GetVar<std::u16string>(u"spawner_name"));
if (myGroup.size() <= 10) return;
const auto pipeNum = GeneralUtils::TryParse<int32_t>(myGroup.substr(10, 1)); const auto pipeNum = GeneralUtils::TryParse<int32_t>(myGroup.substr(10, 1));
if (!pipeNum) return; if (!pipeNum) return;
@@ -17,6 +18,7 @@ void FvBrickPuzzleServer::OnStartup(Entity* self) {
void FvBrickPuzzleServer::OnDie(Entity* self, Entity* killer) { void FvBrickPuzzleServer::OnDie(Entity* self, Entity* killer) {
const auto myGroup = GeneralUtils::UTF16ToWTF8(self->GetVar<std::u16string>(u"spawner_name")); const auto myGroup = GeneralUtils::UTF16ToWTF8(self->GetVar<std::u16string>(u"spawner_name"));
if (myGroup.size() <= 10) return;
const auto pipeNum = GeneralUtils::TryParse<int32_t>(myGroup.substr(10, 1)); const auto pipeNum = GeneralUtils::TryParse<int32_t>(myGroup.substr(10, 1));
if (!pipeNum) return; if (!pipeNum) return;

View File

@@ -9,9 +9,12 @@ void FvFacilityBrick::OnStartup(Entity* self) {
} }
void FvFacilityBrick::OnNotifyObject(Entity* self, Entity* sender, const std::string& name, int32_t param1, int32_t param2) { void FvFacilityBrick::OnNotifyObject(Entity* self, Entity* sender, const std::string& name, int32_t param1, int32_t param2) {
auto* brickSpawner = Game::zoneManager->GetSpawnersByName("ImaginationBrick")[0]; const auto brickObjs = Game::zoneManager->GetSpawnersByName("ImaginationBrick");
auto* bugSpawner = Game::zoneManager->GetSpawnersByName("MaelstromBug")[0]; auto* const brickSpawner = brickObjs.empty() ? nullptr : brickObjs[0];
auto* canisterSpawner = Game::zoneManager->GetSpawnersByName("BrickCanister")[0]; const auto bugObjs = Game::zoneManager->GetSpawnersByName("MaelstromBug");
auto* const bugSpawner = bugObjs.empty() ? nullptr : bugObjs[0];
const auto canisterObjs = Game::zoneManager->GetSpawnersByName("BrickCanister");
auto* const canisterSpawner = canisterObjs.empty() ? nullptr : canisterObjs[0];
if (name == "ConsoleLeftUp") { if (name == "ConsoleLeftUp") {
GameMessages::SendStopFXEffect(self, true, "LeftPipeOff"); GameMessages::SendStopFXEffect(self, true, "LeftPipeOff");
@@ -62,7 +65,7 @@ void FvFacilityBrick::OnNotifyObject(Entity* self, Entity* sender, const std::st
canisterSpawner->Reset(); canisterSpawner->Reset();
canisterSpawner->Deactivate(); canisterSpawner->Deactivate();
} else if (self->GetVar<bool>(u"ConsoleLEFTActive") || self->GetVar<bool>(u"ConsoleRIGHTActive")) { } else if (self->GetVar<bool>(u"ConsoleLEFTActive") || self->GetVar<bool>(u"ConsoleRIGHTActive")) {
brickSpawner->Activate(); if (brickSpawner) brickSpawner->Activate();
auto* object = Game::entityManager->GetEntitiesInGroup("Brick")[0]; auto* object = Game::entityManager->GetEntitiesInGroup("Brick")[0];
@@ -70,17 +73,25 @@ void FvFacilityBrick::OnNotifyObject(Entity* self, Entity* sender, const std::st
GameMessages::SendStopFXEffect(object, true, "bluebrick"); GameMessages::SendStopFXEffect(object, true, "bluebrick");
} }
bugSpawner->Reset(); if (bugSpawner) {
bugSpawner->Deactivate(); bugSpawner->Reset();
bugSpawner->Deactivate();
}
canisterSpawner->Reset(); if (canisterSpawner) {
canisterSpawner->Activate(); canisterSpawner->Reset();
canisterSpawner->Activate();
}
} else { } else {
brickSpawner->Reset(); if (brickSpawner) {
brickSpawner->Deactivate(); brickSpawner->Reset();
brickSpawner->Deactivate();
}
bugSpawner->Reset(); if (bugSpawner) {
bugSpawner->Activate(); bugSpawner->Reset();
bugSpawner->Activate();
}
} }
} }

View File

@@ -241,7 +241,7 @@ void SGCannon::GameOverTimerFunc(Entity* self) {
void SGCannon::DoSpawnTimerFunc(Entity* self, const std::string& name) { void SGCannon::DoSpawnTimerFunc(Entity* self, const std::string& name) {
if (self->GetVar<bool>(GameStartedVariable)) { if (self->GetVar<bool>(GameStartedVariable)) {
const auto spawnNumber = static_cast<uint32_t>(std::stoi(name.substr(7))); const auto spawnNumber = GeneralUtils::TryParse(name.substr(7), 0);
const auto& activeSpawns = self->GetVar<std::vector<SGEnemy>>(ActiveSpawnsVariable); const auto& activeSpawns = self->GetVar<std::vector<SGEnemy>>(ActiveSpawnsVariable);
if (activeSpawns.size() <= spawnNumber) { if (activeSpawns.size() <= spawnNumber) {
LOG_DEBUG("Trying to spawn %i when spawns size is only %i", spawnNumber, activeSpawns.size()); LOG_DEBUG("Trying to spawn %i when spawns size is only %i", spawnNumber, activeSpawns.size());

View File

@@ -41,7 +41,9 @@ void NsConcertQuickBuild::OnStartup(Entity* self) {
return; return;
// Get the manager of the crate of this quick build // Get the manager of the crate of this quick build
const auto groupNumber = std::stoi(splitGroup.at(3)); const auto groupNumber = GeneralUtils::TryParse(splitGroup.at(3), -1);
if (groupNumber == -1) return;
const auto managerObjects = Game::entityManager->GetEntitiesInGroup("CB_" + std::to_string(groupNumber)); const auto managerObjects = Game::entityManager->GetEntitiesInGroup("CB_" + std::to_string(groupNumber));
if (managerObjects.empty()) if (managerObjects.empty())
return; return;

Some files were not shown because too many files have changed in this diff Show More