Compare commits

...

21 Commits

Author SHA1 Message Date
David Markowitz
3d595ce4ac Add properties to landing anim excluded zones
Check this video for footage of no animation playing on landing in a property.
https://www.youtube.com/watch?v=FYqjZBnuBIg
2025-04-11 00:46:45 -07:00
David Markowitz
99f6cf2d92 fix: pin actions to SHA commits and downgrade cmake to ~3.25 (#1757)
* Update build-and-test.yml

* Update build-and-test.yml

* Update build-and-test.yml

* Update build-and-test.yml

* Update build-and-test.yml

* Update build-and-test.yml

* specify up to 3.31

* Update build-and-test.yml
2025-04-02 11:50:35 -05:00
David Markowitz
bc0f3d9163 fix: mission states being incorrect after world load (#1750) 2025-04-02 08:59:21 -05:00
David Markowitz
20d5a9b6d8 fix: mail claiming item (#1758)
* Update build-and-test.yml

* Update build-and-test.yml

* Update build-and-test.yml

* Update build-and-test.yml

* Update build-and-test.yml

* Update build-and-test.yml

* specify up to 3.31

* fix: mail claiming item

tested that I can claim an item without an error
2025-04-02 08:56:01 -05:00
jadebenn
c490d45fe0 feat: Packed asset bundle improvements (#1754)
* Removed some unneccessary indirection and added const-correctness

* improved packed asset bundle error messages

* rephrase the string_view initialization to satisfy microsoft

* change forward slashes to back slashes and let us never speak of this again

* make crc32b function static

* remove redundant 'static'

---------

Co-authored-by: jadebenn <9892985+jadebenn@users.noreply.github.com>
2025-03-29 14:46:18 -07:00
jadebenn
aa49aaae76 invert sqlite lookup result to fix name in use lookup errors (#1755)
Co-authored-by: jadebenn <9892985+jadebenn@users.noreply.github.com>
2025-03-28 18:12:28 -07:00
David Markowitz
f78baee534 fix the wu man (#1743) 2025-03-28 17:04:35 -05:00
David Markowitz
347fc46f01 check pending names too (#1748) 2025-03-28 17:03:04 -05:00
David Markowitz
d104559cc4 fix: avery npc not having animations anymore (#1751)
* for avery

* remove label
2025-03-17 13:16:56 -05:00
Gie "Max" Vanommeslaeghe
b702843011 Merge pull request #1735 from DarkflameUniverse/mailv2
feat: Mail Re-write and packet/bitstream handler POC
2025-02-18 21:47:13 +01:00
14d7dec6a8 toctou 2025-02-01 02:05:17 -06:00
6eaf0a153e explicit character ID usage 2025-02-01 01:56:57 -06:00
78e52904e5 address feedback 2025-02-01 01:51:46 -06:00
b388b03251 remove fwd decl 2025-02-01 01:21:17 -06:00
David Markowitz
ae37641635 eliminate children (#1741) 2025-01-25 20:47:51 -06:00
566791e647 chore: limit API to only listen on localhost (#1740) 2025-01-25 20:47:12 -06:00
a07d54e513 all tested and working 2025-01-20 00:42:28 -06:00
b01b3cc38d WIP debugging 2025-01-19 19:07:55 -06:00
b7c579fb84 make it compile and cleanup 2025-01-19 16:31:54 -06:00
7b1d6948c3 Overaul, need to test 2025-01-19 00:25:20 -06:00
6cd1310460 First pass 2025-01-08 14:01:09 -06:00
43 changed files with 885 additions and 570 deletions

View File

@@ -16,12 +16,12 @@ jobs:
os: [ windows-2022, ubuntu-22.04, macos-13 ]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@85e6279cec87321a52edac9c87bce653a07cf6c2
with:
submodules: true
- name: Add msbuild to PATH (Windows only)
if: ${{ matrix.os == 'windows-2022' }}
uses: microsoft/setup-msbuild@v2
uses: microsoft/setup-msbuild@767f00a3f09872d96a0cb9fcd5e6a4ff33311330
with:
vs-version: '[17,18)'
msbuild-architecture: x64
@@ -30,12 +30,16 @@ jobs:
run: |
brew install openssl@3
sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
- name: Get CMake 3.x
uses: lukka/get-cmake@28983e0d3955dba2bb0a6810caae0c6cf268ec0c
with:
cmakeVersion: "~3.25.0" # <--= optional, use most recent 3.25.x version
- name: cmake
uses: lukka/run-cmake@v10
uses: lukka/run-cmake@67c73a83a46f86c4e0b96b741ac37ff495478c38
with:
workflowPreset: "ci-${{matrix.os}}"
- name: artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@6027e3dd177782cd8ab9af838c04fd81a07f1d47
with:
name: build-${{matrix.os}}
path: |

View File

@@ -78,7 +78,7 @@ git clone --recursive https://github.com/DarkflameUniverse/DarkflameServer
### Windows packages
Ensure that you have either the [MSVC C++ compiler](https://visualstudio.microsoft.com/vs/features/cplusplus/) (recommended) or the [Clang compiler](https://github.com/llvm/llvm-project/releases/) installed.
You'll also need to download and install [CMake](https://cmake.org/download/) (version <font size="4">**CMake version 3.25**</font> or later!).
You'll also need to download and install [CMake](https://cmake.org/download/) (<font size="4">**version 3.25**</font> up to <font size="4">**version 3.31**</font>!).
### MacOS packages
Ensure you have [brew](https://brew.sh) installed.
@@ -100,7 +100,7 @@ sudo apt install build-essential gcc zlib1g-dev libssl-dev openssl mariadb-serve
```
#### Required CMake version
This project uses <font size="4">**CMake version 3.25**</font> or higher and as such you will need to ensure you have this version installed.
This project uses <font size="4">**CMake version 3.25**</font> up to <font size="4">**version 3.31**</font> and as such you will need to ensure you have this version installed.
You can check your CMake version by using the following command in a terminal.
```bash
cmake --version

View File

@@ -150,11 +150,12 @@ ChatWebAPI::~ChatWebAPI() {
bool ChatWebAPI::Startup() {
// Make listen address
std::string listen_ip = Game::config->GetValue("web_server_listen_ip");
if (listen_ip == "localhost") listen_ip = "127.0.0.1";
// std::string listen_ip = Game::config->GetValue("web_server_listen_ip");
// if (listen_ip == "localhost") listen_ip = "127.0.0.1";
const std::string& listen_port = Game::config->GetValue("web_server_listen_port");
const std::string& listen_address = "http://" + listen_ip + ":" + listen_port;
// const std::string& listen_address = "http://" + listen_ip + ":" + listen_port;
const std::string& listen_address = "http://localhost:" + listen_port;
LOG("Starting web server on %s", listen_address.c_str());
// Create HTTP listener

View File

@@ -6,6 +6,9 @@
#include "zlib.h"
constexpr uint32_t CRC32_INIT = 0xFFFFFFFF;
constexpr auto NULL_TERMINATOR = std::string_view{"\0\0\0", 4};
AssetManager::AssetManager(const std::filesystem::path& path) {
if (!std::filesystem::is_directory(path)) {
throw std::runtime_error("Attempted to load asset bundle (" + path.string() + ") however it is not a valid directory.");
@@ -18,12 +21,20 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
m_RootPath = m_Path;
m_ResPath = (m_Path / "client" / "res");
} else if (std::filesystem::exists(m_Path / ".." / "versions") && std::filesystem::exists(m_Path / "res")) {
} else if (std::filesystem::exists(m_Path / "res" / "pack")) {
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.");
}
m_AssetBundleType = eAssetBundleType::Packed;
m_RootPath = (m_Path / "..");
m_ResPath = (m_Path / "res");
} else if (std::filesystem::exists(m_Path / "pack") && std::filesystem::exists(m_Path / ".." / ".." / "versions")) {
} else if (std::filesystem::exists(m_Path / "pack")) {
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.");
}
m_AssetBundleType = eAssetBundleType::Packed;
m_RootPath = (m_Path / ".." / "..");
@@ -48,6 +59,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
break;
}
case eAssetBundleType::None:
[[fallthrough]];
case eAssetBundleType::Unpacked: {
break;
}
@@ -55,19 +67,10 @@ AssetManager::AssetManager(const std::filesystem::path& path) {
}
void AssetManager::LoadPackIndex() {
m_PackIndex = new PackIndex(m_RootPath);
m_PackIndex = PackIndex(m_RootPath);
}
std::filesystem::path AssetManager::GetResPath() {
return m_ResPath;
}
eAssetBundleType AssetManager::GetAssetBundleType() {
return m_AssetBundleType;
}
bool AssetManager::HasFile(const char* name) {
auto fixedName = std::string(name);
bool AssetManager::HasFile(std::string fixedName) const {
std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); });
// Special case for unpacked client have BrickModels in upper case
@@ -81,8 +84,7 @@ bool AssetManager::HasFile(const char* name) {
std::replace(fixedName.begin(), fixedName.end(), '/', '\\');
if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName;
uint32_t crc = crc32b(0xFFFFFFFF, reinterpret_cast<uint8_t*>(const_cast<char*>(fixedName.c_str())), fixedName.size());
crc = crc32b(crc, reinterpret_cast<Bytef*>(const_cast<char*>("\0\0\0\0")), 4);
const auto crc = crc32b(crc32b(CRC32_INIT, fixedName), NULL_TERMINATOR);
for (const auto& item : this->m_PackIndex->GetPackFileIndices()) {
if (item.m_Crc == crc) {
@@ -93,8 +95,7 @@ bool AssetManager::HasFile(const char* name) {
return false;
}
bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) {
auto fixedName = std::string(name);
bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) const {
std::transform(fixedName.begin(), fixedName.end(), fixedName.begin(), [](uint8_t c) { return std::tolower(c); });
std::replace(fixedName.begin(), fixedName.end(), '\\', '/'); // On the off chance someone has the wrong slashes, force forward slashes
@@ -129,8 +130,7 @@ bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) {
fixedName = "client\\res\\" + fixedName;
}
int32_t packIndex = -1;
uint32_t crc = crc32b(0xFFFFFFFF, reinterpret_cast<uint8_t*>(const_cast<char*>(fixedName.c_str())), fixedName.size());
crc = crc32b(crc, reinterpret_cast<Bytef*>(const_cast<char*>("\0\0\0\0")), 4);
auto crc = crc32b(crc32b(CRC32_INIT, fixedName), NULL_TERMINATOR);
for (const auto& item : this->m_PackIndex->GetPackFileIndices()) {
if (item.m_Crc == crc) {
@@ -144,15 +144,13 @@ bool AssetManager::GetFile(const char* name, char** data, uint32_t* len) {
return false;
}
auto packs = this->m_PackIndex->GetPacks();
auto* pack = packs.at(packIndex);
bool success = pack->ReadFileFromPack(crc, data, len);
const auto& pack = this->m_PackIndex->GetPacks().at(packIndex);
const bool success = pack.ReadFileFromPack(crc, data, len);
return success;
}
AssetStream AssetManager::GetFile(const char* name) {
AssetStream AssetManager::GetFile(const char* name) const {
char* buf; uint32_t len;
bool success = this->GetFile(name, &buf, &len);
@@ -160,23 +158,15 @@ AssetStream AssetManager::GetFile(const char* name) {
return AssetStream(buf, len, success);
}
uint32_t AssetManager::crc32b(uint32_t base, uint8_t* message, size_t l) {
size_t i, j;
uint32_t crc, msb;
crc = base;
for (i = 0; i < l; i++) {
uint32_t AssetManager::crc32b(uint32_t crc, const std::string_view message) {
for (const auto byte : message) {
// xor next byte to upper bits of crc
crc ^= (static_cast<unsigned int>(message[i]) << 24);
for (j = 0; j < 8; j++) { // Do eight times.
msb = crc >> 31;
crc ^= (static_cast<uint32_t>(std::bit_cast<uint8_t>(byte)) << 24);
for (size_t _ = 0; _ < 8; _++) { // Do eight times.
const uint32_t msb = crc >> 31;
crc <<= 1;
crc ^= (0 - msb) & 0x04C11DB7;
}
}
return crc; // don't complement crc on output
}
AssetManager::~AssetManager() {
delete m_PackIndex;
}

View File

@@ -61,23 +61,32 @@ struct AssetStream : std::istream {
class AssetManager {
public:
AssetManager(const std::filesystem::path& path);
~AssetManager();
std::filesystem::path GetResPath();
eAssetBundleType GetAssetBundleType();
[[nodiscard]]
const std::filesystem::path& GetResPath() const {
return m_ResPath;
}
[[nodiscard]]
eAssetBundleType GetAssetBundleType() const {
return m_AssetBundleType;
}
bool HasFile(const char* name);
bool GetFile(const char* name, char** data, uint32_t* len);
AssetStream GetFile(const char* name);
[[nodiscard]]
bool HasFile(std::string name) const;
[[nodiscard]]
bool GetFile(std::string name, char** data, uint32_t* len) const;
[[nodiscard]]
AssetStream GetFile(const char* name) const;
private:
void LoadPackIndex();
// Modified crc algorithm (mpeg2)
// Reference: https://stackoverflow.com/questions/54339800/how-to-modify-crc-32-to-crc-32-mpeg-2
inline uint32_t crc32b(uint32_t base, uint8_t* message, size_t l);
bool m_SuccessfullyLoaded;
static inline uint32_t crc32b(uint32_t crc, std::string_view message);
std::filesystem::path m_Path;
std::filesystem::path m_RootPath;
@@ -85,5 +94,5 @@ private:
eAssetBundleType m_AssetBundleType = eAssetBundleType::None;
PackIndex* m_PackIndex;
std::optional<PackIndex> m_PackIndex;
};

View File

@@ -21,19 +21,20 @@ Pack::Pack(const std::filesystem::path& filePath) {
m_FileStream.seekg(recordCountPos, std::ios::beg);
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_RecordCount);
uint32_t recordCount = 0;
BinaryIO::BinaryRead<uint32_t>(m_FileStream, recordCount);
for (int i = 0; i < m_RecordCount; i++) {
m_Records.reserve(recordCount);
std::generate_n(std::back_inserter(m_Records), recordCount, [&] {
PackRecord record;
BinaryIO::BinaryRead<PackRecord>(m_FileStream, record);
m_Records.push_back(record);
}
return record;
});
m_FileStream.close();
}
bool Pack::HasFile(uint32_t crc) {
bool Pack::HasFile(const uint32_t crc) const {
for (const auto& record : m_Records) {
if (record.m_Crc == crc) {
return true;
@@ -43,7 +44,7 @@ bool Pack::HasFile(uint32_t crc) {
return false;
}
bool Pack::ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) {
bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) const {
// Time for some wacky C file reading for speed reasons
PackRecord pkRecord{};

View File

@@ -24,16 +24,17 @@ struct PackRecord {
class Pack {
public:
Pack(const std::filesystem::path& filePath);
~Pack() = default;
bool HasFile(uint32_t crc);
bool ReadFileFromPack(uint32_t crc, char** data, uint32_t* len);
[[nodiscard]]
bool HasFile(uint32_t crc) const;
[[nodiscard]]
bool ReadFileFromPack(uint32_t crc, char** data, uint32_t* len) const;
private:
std::ifstream m_FileStream;
std::filesystem::path m_FilePath;
char m_Version[7];
uint32_t m_RecordCount;
std::vector<PackRecord> m_Records;
};

View File

@@ -6,38 +6,32 @@
PackIndex::PackIndex(const std::filesystem::path& filePath) {
m_FileStream = std::ifstream(filePath / "versions" / "primary.pki", std::ios::in | std::ios::binary);
uint32_t packPathCount = 0;
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_Version);
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_PackPathCount);
BinaryIO::BinaryRead<uint32_t>(m_FileStream, packPathCount);
m_PackPaths.resize(m_PackPathCount);
m_PackPaths.resize(packPathCount);
for (auto& item : m_PackPaths) {
BinaryIO::ReadString<uint32_t>(m_FileStream, item, BinaryIO::ReadType::String);
}
BinaryIO::BinaryRead<uint32_t>(m_FileStream, m_PackFileIndexCount);
uint32_t packFileIndexCount = 0;
BinaryIO::BinaryRead<uint32_t>(m_FileStream, packFileIndexCount);
for (int i = 0; i < m_PackFileIndexCount; i++) {
m_PackFileIndices.reserve(packFileIndexCount);
std::generate_n(std::back_inserter(m_PackFileIndices), packFileIndexCount, [&] {
PackFileIndex packFileIndex;
BinaryIO::BinaryRead<PackFileIndex>(m_FileStream, packFileIndex);
m_PackFileIndices.push_back(packFileIndex);
}
return packFileIndex;
});
LOG("Loaded pack catalog with %i pack files and %i files", m_PackPaths.size(), m_PackFileIndices.size());
m_Packs.reserve(m_PackPaths.size());
for (auto& item : m_PackPaths) {
std::replace(item.begin(), item.end(), '\\', '/');
auto* pack = new Pack(filePath / item);
m_Packs.push_back(pack);
m_Packs.emplace_back(filePath / item);
}
m_FileStream.close();
}
PackIndex::~PackIndex() {
for (const auto* item : m_Packs) {
delete item;
}
}

View File

@@ -21,20 +21,23 @@ struct PackFileIndex {
class PackIndex {
public:
PackIndex(const std::filesystem::path& filePath);
~PackIndex();
const std::vector<std::string>& GetPackPaths() { return m_PackPaths; }
const std::vector<PackFileIndex>& GetPackFileIndices() { return m_PackFileIndices; }
const std::vector<Pack*>& GetPacks() { return m_Packs; }
[[nodiscard]]
const std::vector<std::string>& GetPackPaths() const { return m_PackPaths; }
[[nodiscard]]
const std::vector<PackFileIndex>& GetPackFileIndices() const { return m_PackFileIndices; }
[[nodiscard]]
const std::vector<Pack>& GetPacks() const { return m_Packs; }
private:
std::ifstream m_FileStream;
uint32_t m_Version;
uint32_t m_PackPathCount;
std::vector<std::string> m_PackPaths;
uint32_t m_PackFileIndexCount;
std::vector<PackFileIndex> m_PackFileIndices;
std::vector<Pack*> m_Packs;
std::vector<Pack> m_Packs;
};

View File

@@ -7,7 +7,8 @@ enum class eConnectionType : uint16_t {
CHAT,
WORLD = 4,
CLIENT,
MASTER
MASTER,
UNKNOWN
};
#endif //!__ECONNECTIONTYPE__H__

View File

@@ -28,7 +28,8 @@ enum eInventoryType : uint32_t {
DONATION,
VAULT_MODELS,
ITEM_SETS, //internal, technically this is BankBehaviors.
INVALID // made up, for internal use!!!, Technically this called the ALL inventory.
INVALID, // made up, for internal use!!!, Technically this called the ALL inventory.
ALL, // Use this to search all inventories instead of a specific one.
};
class InventoryType {

View File

@@ -44,6 +44,8 @@ public:
// Updates the given character ids last login to be right now.
virtual void UpdateLastLoggedInCharacter(const uint32_t characterId) = 0;
virtual bool IsNameInUse(const std::string_view name) = 0;
};
#endif //!__ICHARINFO__H__

View File

@@ -8,27 +8,10 @@
#include "dCommonVars.h"
#include "NiQuaternion.h"
#include "NiPoint3.h"
#include "MailInfo.h"
class IMail {
public:
struct MailInfo {
std::string senderUsername;
std::string recipient;
std::string subject;
std::string body;
uint64_t id{};
uint32_t senderId{};
uint32_t receiverId{};
uint64_t timeSent{};
bool wasRead{};
struct {
LWOOBJID itemID{};
int32_t itemCount{};
LOT itemLOT{};
LWOOBJID itemSubkey{};
};
};
// Insert a new mail into the database.
virtual void InsertNewMail(const MailInfo& mail) = 0;

View File

@@ -79,14 +79,14 @@ public:
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const IMail::MailInfo& mail) override;
void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel(
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) override;
std::vector<IMail::MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<IMail::MailInfo> GetMail(const uint64_t mailId) override;
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override;
@@ -124,8 +124,9 @@ public:
void IncrementTimesPlayed(const uint32_t playerId, const uint32_t gameId) override;
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override;
void DeleteUgcBuild(const LWOOBJID bigId) override;
sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
uint32_t GetAccountCount() override;
bool IsNameInUse(const std::string_view name) override;
sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
private:
// Generic query functions that can be used for any query.

View File

@@ -76,3 +76,9 @@ void MySQLDatabase::SetPendingCharacterName(const uint32_t characterId, const st
void MySQLDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) {
ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ? LIMIT 1", static_cast<uint32_t>(time(NULL)), characterId);
}
bool MySQLDatabase::IsNameInUse(const std::string_view name) {
auto result = ExecuteSelect("SELECT name FROM charinfo WHERE name = ? or pending_name = ? LIMIT 1;", name, name);
return result->next();
}

View File

@@ -1,6 +1,7 @@
#include "MySQLDatabase.h"
void MySQLDatabase::InsertNewMail(const IMail::MailInfo& mail) {
void MySQLDatabase::InsertNewMail(const MailInfo& mail) {
ExecuteInsert(
"INSERT INTO `mail` "
"(`sender_id`, `sender_name`, `receiver_id`, `receiver_name`, `time_sent`, `subject`, `body`, `attachment_id`, `attachment_lot`, `attachment_subkey`, `attachment_count`, `was_read`)"
@@ -18,17 +19,17 @@ void MySQLDatabase::InsertNewMail(const IMail::MailInfo& mail) {
mail.itemCount);
}
std::vector<IMail::MailInfo> MySQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
std::vector<MailInfo> MySQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
auto res = ExecuteSelect(
"SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent"
" FROM mail WHERE receiver_id=? limit ?;",
characterId, numberOfMail);
std::vector<IMail::MailInfo> toReturn;
std::vector<MailInfo> toReturn;
toReturn.reserve(res->rowsCount());
while (res->next()) {
IMail::MailInfo mail;
MailInfo mail;
mail.id = res->getUInt64("id");
mail.subject = res->getString("subject").c_str();
mail.body = res->getString("body").c_str();
@@ -46,14 +47,14 @@ std::vector<IMail::MailInfo> MySQLDatabase::GetMailForPlayer(const uint32_t char
return toReturn;
}
std::optional<IMail::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);
if (!res->next()) {
return std::nullopt;
}
IMail::MailInfo toReturn;
MailInfo toReturn;
toReturn.itemLOT = res->getInt("attachment_lot");
toReturn.itemCount = res->getInt("attachment_count");

View File

@@ -77,14 +77,14 @@ public:
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const IMail::MailInfo& mail) override;
void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel(
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) override;
std::vector<IMail::MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<IMail::MailInfo> GetMail(const uint64_t mailId) override;
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override;
@@ -123,6 +123,7 @@ public:
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override;
void DeleteUgcBuild(const LWOOBJID bigId) override;
uint32_t GetAccountCount() override;
bool IsNameInUse(const std::string_view name) override;
private:
CppSQLite3Statement CreatePreppedStmt(const std::string& query);

View File

@@ -77,3 +77,9 @@ void SQLiteDatabase::SetPendingCharacterName(const uint32_t characterId, const s
void SQLiteDatabase::UpdateLastLoggedInCharacter(const uint32_t characterId) {
ExecuteUpdate("UPDATE charinfo SET last_login = ? WHERE id = ?;", static_cast<uint32_t>(time(NULL)), characterId);
}
bool SQLiteDatabase::IsNameInUse(const std::string_view name) {
auto [_, result] = ExecuteSelect("SELECT name FROM charinfo WHERE name = ? or pending_name = ? LIMIT 1;", name, name);
return !result.eof();
}

View File

@@ -1,6 +1,6 @@
#include "SQLiteDatabase.h"
void SQLiteDatabase::InsertNewMail(const IMail::MailInfo& mail) {
void SQLiteDatabase::InsertNewMail(const MailInfo& mail) {
ExecuteInsert(
"INSERT INTO `mail` "
"(`sender_id`, `sender_name`, `receiver_id`, `receiver_name`, `time_sent`, `subject`, `body`, `attachment_id`, `attachment_lot`, `attachment_subkey`, `attachment_count`, `was_read`)"
@@ -18,16 +18,16 @@ void SQLiteDatabase::InsertNewMail(const IMail::MailInfo& mail) {
mail.itemCount);
}
std::vector<IMail::MailInfo> SQLiteDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
std::vector<MailInfo> SQLiteDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
auto [_, res] = ExecuteSelect(
"SELECT id, subject, body, sender_name, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read, time_sent"
" FROM mail WHERE receiver_id=? limit ?;",
characterId, numberOfMail);
std::vector<IMail::MailInfo> toReturn;
std::vector<MailInfo> toReturn;
while (!res.eof()) {
IMail::MailInfo mail;
MailInfo mail;
mail.id = res.getInt64Field("id");
mail.subject = res.getStringField("subject");
mail.body = res.getStringField("body");
@@ -46,14 +46,14 @@ std::vector<IMail::MailInfo> SQLiteDatabase::GetMailForPlayer(const uint32_t cha
return toReturn;
}
std::optional<IMail::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);
if (res.eof()) {
return std::nullopt;
}
IMail::MailInfo toReturn;
MailInfo toReturn;
toReturn.itemLOT = res.getIntField("attachment_lot");
toReturn.itemCount = res.getIntField("attachment_count");

View File

@@ -184,7 +184,7 @@ void TestSQLDatabase::InsertCheatDetection(const IPlayerCheatDetections::Info& i
}
void TestSQLDatabase::InsertNewMail(const IMail::MailInfo& mail) {
void TestSQLDatabase::InsertNewMail(const MailInfo& mail) {
}
@@ -192,11 +192,11 @@ void TestSQLDatabase::InsertNewUgcModel(std::istringstream& sd0Data, const uint3
}
std::vector<IMail::MailInfo> TestSQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
std::vector<MailInfo> TestSQLDatabase::GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) {
return {};
}
std::optional<IMail::MailInfo> TestSQLDatabase::GetMail(const uint64_t mailId) {
std::optional<MailInfo> TestSQLDatabase::GetMail(const uint64_t mailId) {
return {};
}

View File

@@ -56,14 +56,14 @@ class TestSQLDatabase : public GameDatabase {
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const IMail::MailInfo& mail) override;
void InsertNewMail(const MailInfo& mail) override;
void InsertNewUgcModel(
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) override;
std::vector<IMail::MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<IMail::MailInfo> GetMail(const uint64_t mailId) override;
std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override;
@@ -102,6 +102,8 @@ class TestSQLDatabase : public GameDatabase {
void InsertUgcBuild(const std::string& modules, const LWOOBJID bigId, const std::optional<uint32_t> characterId) override {};
void DeleteUgcBuild(const LWOOBJID bigId) override {};
uint32_t GetAccountCount() override { return 0; };
bool IsNameInUse(const std::string_view name) override { return false; };
};
#endif //!TESTSQLDATABASE_H

View File

@@ -305,13 +305,13 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
LOT shirtLOT = FindCharShirtID(shirtColor, shirtStyle);
LOT pantsLOT = FindCharPantsID(pantsColor);
if (!name.empty() && Database::Get()->GetCharacterInfo(name)) {
if (!name.empty() && Database::Get()->IsNameInUse(name)) {
LOG("AccountID: %i chose unavailable name: %s", u->GetAccountID(), name.c_str());
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::CUSTOM_NAME_IN_USE);
return;
}
if (Database::Get()->GetCharacterInfo(predefinedName)) {
if (Database::Get()->IsNameInUse(predefinedName)) {
LOG("AccountID: %i chose unavailable predefined name: %s", u->GetAccountID(), predefinedName.c_str());
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::PREDEFINED_NAME_IN_USE);
return;
@@ -324,7 +324,7 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
}
//Now that the name is ok, we can get an objectID from Master:
ObjectIDManager::RequestPersistentID([=, this](uint32_t objectID) mutable {
ObjectIDManager::RequestPersistentID([=, this](uint32_t objectID) {
if (Database::Get()->GetCharacterInfo(objectID)) {
LOG("Character object id unavailable, check object_id_tracker!");
WorldPackets::SendCharacterCreationResponse(sysAddr, eCharacterCreationResponse::OBJECT_ID_UNAVAILABLE);
@@ -369,13 +369,14 @@ void UserManager::CreateCharacter(const SystemAddress& sysAddr, Packet* packet)
// If predefined name is invalid, change it to be their object id
// that way more than one player can create characters if the predefined name files are not provided
if (predefinedName == "INVALID") {
auto assignedPredefinedName = predefinedName;
if (assignedPredefinedName == "INVALID") {
std::stringstream nameObjID;
nameObjID << "minifig" << objectID;
predefinedName = nameObjID.str();
assignedPredefinedName = nameObjID.str();
}
std::string_view nameToAssign = !name.empty() && nameOk ? name : predefinedName;
std::string_view nameToAssign = !name.empty() && nameOk ? name : assignedPredefinedName;
std::string pendingName = !name.empty() && !nameOk ? name : "";
ICharInfo::Info info;

View File

@@ -95,15 +95,21 @@ bool CharacterComponent::LandingAnimDisabled(int zoneID) {
case 556:
case 1101:
case 1202:
case 1150:
case 1151:
case 1203:
case 1204:
case 1250:
case 1251:
case 1261:
case 1301:
case 1302:
case 1303:
case 1350:
case 1401:
case 1402:
case 1403:
case 1450:
case 1603:
case 2001:
return true;
@@ -828,7 +834,7 @@ void CharacterComponent::AwardClaimCodes() {
subject << "%[RewardCodes_" << rewardCode << "_subjectText]";
std::ostringstream body;
body << "%[RewardCodes_" << rewardCode << "_bodyText]";
Mail::SendMail(LWOOBJID_EMPTY, "%[MAIL_SYSTEM_NOTIFICATION]", m_Parent, subject.str(), body.str(), attachmentLOT, 1);
Mail::SendMail(m_Parent, subject.str(), body.str(), attachmentLOT, 1);
}
}

View File

@@ -150,11 +150,11 @@ uint32_t InventoryComponent::GetLotCount(const LOT lot) const {
return count;
}
uint32_t InventoryComponent::GetLotCountNonTransfer(LOT lot) const {
uint32_t InventoryComponent::GetLotCountNonTransfer(LOT lot, bool includeVault) const {
uint32_t count = 0;
for (const auto& inventory : m_Inventories) {
if (IsTransferInventory(inventory.second->GetType())) continue;
if (IsTransferInventory(inventory.second->GetType(), includeVault)) continue;
count += inventory.second->GetLotCount(lot);
}
@@ -274,7 +274,7 @@ void InventoryComponent::AddItem(
switch (sourceType) {
case 0:
Mail::SendMail(LWOOBJID_EMPTY, "Darkflame Universe", m_Parent, "Lost Reward", "You received an item and didn&apos;t have room for it.", lot, size);
Mail::SendMail(m_Parent, "%[MAIL_ACTIVITY_OVERFLOW_HEADER]", "%[MAIL_ACTIVITY_OVERFLOW_BODY]", lot, size);
break;
case 1:
@@ -305,21 +305,35 @@ bool InventoryComponent::RemoveItem(const LOT lot, const uint32_t count, eInvent
LOG("Attempted to remove 0 of item (%i) from the inventory!", lot);
return false;
}
if (inventoryType == INVALID) inventoryType = Inventory::FindInventoryTypeForLot(lot);
auto* inventory = GetInventory(inventoryType);
if (!inventory) return false;
if (inventoryType != eInventoryType::ALL) {
if (inventoryType == INVALID) inventoryType = Inventory::FindInventoryTypeForLot(lot);
auto* inventory = GetInventory(inventoryType);
if (!inventory) return false;
auto left = std::min<uint32_t>(count, inventory->GetLotCount(lot));
if (left != count) return false;
auto left = std::min<uint32_t>(count, inventory->GetLotCount(lot));
if (left != count) return false;
while (left > 0) {
auto* item = FindItemByLot(lot, inventoryType, false, ignoreBound);
if (!item) break;
const auto delta = std::min<uint32_t>(left, item->GetCount());
item->SetCount(item->GetCount() - delta, silent);
left -= delta;
while (left > 0) {
auto* item = FindItemByLot(lot, inventoryType, false, ignoreBound);
if (!item) break;
const auto delta = std::min<uint32_t>(left, item->GetCount());
item->SetCount(item->GetCount() - delta, silent);
left -= delta;
}
return true;
} else {
auto left = count;
for (const auto& inventory : m_Inventories | std::views::values) {
while (left > 0 && inventory->GetLotCount(lot) > 0) {
auto* item = inventory->FindItemByLot(lot, false, ignoreBound);
if (!item) break;
const auto delta = std::min<uint32_t>(item->GetCount(), left);
item->SetCount(item->GetCount() - delta, silent);
left -= delta;
}
}
return left == 0;
}
return true;
}
void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType inventory, const uint32_t count, const bool showFlyingLot, bool isModMoveAndEquip, const bool ignoreEquipped, const int32_t preferredSlot) {
@@ -1318,8 +1332,8 @@ BehaviorSlot InventoryComponent::FindBehaviorSlot(const eItemType type) {
}
}
bool InventoryComponent::IsTransferInventory(eInventoryType type) {
return type == VENDOR_BUYBACK || type == VAULT_ITEMS || type == VAULT_MODELS || type == TEMP_ITEMS || type == TEMP_MODELS || type == MODELS_IN_BBB;
bool InventoryComponent::IsTransferInventory(eInventoryType type, bool includeVault) {
return type == VENDOR_BUYBACK || (includeVault && (type == VAULT_ITEMS || type == VAULT_MODELS)) || type == TEMP_ITEMS || type == TEMP_MODELS || type == MODELS_IN_BBB;
}
uint32_t InventoryComponent::FindSkill(const LOT lot) {

View File

@@ -100,7 +100,7 @@ public:
* @param lot the lot to search for
* @return the amount of items this entity possesses of the specified lot
*/
uint32_t GetLotCountNonTransfer(LOT lot) const;
uint32_t GetLotCountNonTransfer(LOT lot, bool includeVault = true) const;
/**
* Returns the items that are currently equipped by this entity
@@ -373,7 +373,7 @@ public:
* @param type the inventory type to check
* @return if the inventory type is a temp inventory
*/
static bool IsTransferInventory(eInventoryType type);
static bool IsTransferInventory(eInventoryType type, bool includeVault = true);
/**
* Finds the skill related to the passed LOT from the ObjectSkills table

View File

@@ -99,17 +99,8 @@ void MissionComponent::AcceptMission(const uint32_t missionId, const bool skipCh
mission->Accept();
this->m_Missions.insert_or_assign(missionId, mission);
if (missionId == 1728) {
//Needs to send a mail
auto address = m_Parent->GetSystemAddress();
Mail::HandleNotificationRequest(address, m_Parent->GetObjectID());
}
}
void MissionComponent::CompleteMission(const uint32_t missionId, const bool skipChecks, const bool yieldRewards) {
// Get the mission first
auto* mission = this->GetMission(missionId);
@@ -521,7 +512,7 @@ void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
auto* mission = new Mission(this, missionId);
mission->LoadFromXml(*doneM);
mission->LoadFromXmlDone(*doneM);
doneM = doneM->NextSiblingElement();
@@ -536,9 +527,9 @@ void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
currentM->QueryAttribute("id", &missionId);
auto* mission = new Mission(this, missionId);
auto* mission = m_Missions.contains(missionId) ? m_Missions[missionId] : new Mission(this, missionId);
mission->LoadFromXml(*currentM);
mission->LoadFromXmlCur(*currentM);
if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) {
mission->SetUniqueMissionOrderID(missionOrder);
@@ -574,20 +565,23 @@ void MissionComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
auto* mission = pair.second;
if (mission) {
const auto complete = mission->IsComplete();
const auto completions = mission->GetCompletions();
auto* m = doc.NewElement("m");
if (complete) {
mission->UpdateXml(*m);
if (completions > 0) {
mission->UpdateXmlDone(*m);
done->LinkEndChild(m);
continue;
if (mission->IsComplete()) continue;
m = doc.NewElement("m");
}
if (mission->IsMission()) m->SetAttribute("o", mission->GetUniqueMissionOrderID());
mission->UpdateXml(*m);
mission->UpdateXmlCur(*m);
cur->LinkEndChild(m);
}

View File

@@ -65,7 +65,7 @@ Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) {
}
}
void Mission::LoadFromXml(const tinyxml2::XMLElement& element) {
void Mission::LoadFromXmlDone(const tinyxml2::XMLElement& element) {
// Start custom XML
if (element.Attribute("state") != nullptr) {
m_State = static_cast<eMissionState>(std::stoul(element.Attribute("state")));
@@ -76,11 +76,15 @@ void Mission::LoadFromXml(const tinyxml2::XMLElement& element) {
m_Completions = std::stoul(element.Attribute("cct"));
m_Timestamp = std::stoul(element.Attribute("cts"));
if (IsComplete()) {
return;
}
}
}
void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) {
// Start custom XML
if (element.Attribute("state") != nullptr) {
m_State = static_cast<eMissionState>(std::stoul(element.Attribute("state")));
}
// End custom XML
auto* task = element.FirstChildElement();
@@ -132,7 +136,7 @@ void Mission::LoadFromXml(const tinyxml2::XMLElement& element) {
}
}
void Mission::UpdateXml(tinyxml2::XMLElement& element) {
void Mission::UpdateXmlDone(tinyxml2::XMLElement& element) {
// Start custom XML
element.SetAttribute("state", static_cast<unsigned int>(m_State));
// End custom XML
@@ -141,15 +145,21 @@ void Mission::UpdateXml(tinyxml2::XMLElement& element) {
element.SetAttribute("id", static_cast<unsigned int>(info.id));
if (m_Completions > 0) {
element.SetAttribute("cct", static_cast<unsigned int>(m_Completions));
element.SetAttribute("cct", static_cast<unsigned int>(m_Completions));
element.SetAttribute("cts", static_cast<unsigned int>(m_Timestamp));
element.SetAttribute("cts", static_cast<unsigned int>(m_Timestamp));
}
if (IsComplete()) {
return;
}
}
void Mission::UpdateXmlCur(tinyxml2::XMLElement& element) {
// Start custom XML
element.SetAttribute("state", static_cast<unsigned int>(m_State));
// End custom XML
element.DeleteChildren();
element.SetAttribute("id", static_cast<unsigned int>(info.id));
if (IsComplete()) return;
for (auto* task : m_Tasks) {
if (task->GetType() == eMissionTaskType::COLLECTION ||

View File

@@ -28,8 +28,13 @@ public:
Mission(MissionComponent* missionComponent, uint32_t missionId);
~Mission();
void LoadFromXml(const tinyxml2::XMLElement& element);
void UpdateXml(tinyxml2::XMLElement& element);
// XML functions to load and save completed mission state to xml
void LoadFromXmlDone(const tinyxml2::XMLElement& element);
void UpdateXmlDone(tinyxml2::XMLElement& element);
// XML functions to load and save current mission state and task data to xml
void LoadFromXmlCur(const tinyxml2::XMLElement& element);
void UpdateXmlCur(tinyxml2::XMLElement& element);
/**
* Returns the ID of this mission

View File

@@ -26,12 +26,279 @@
#include "eMissionTaskType.h"
#include "eReplicaComponentType.h"
#include "eConnectionType.h"
#include "User.h"
#include "StringifiedEnum.h"
namespace {
const std::string DefaultSender = "%[MAIL_SYSTEM_NOTIFICATION]";
}
namespace Mail {
std::map<eMessageID, std::function<std::unique_ptr<MailLUBitStream>()>> g_Handlers = {
{eMessageID::SendRequest, []() {
return std::make_unique<SendRequest>();
}},
{eMessageID::DataRequest, []() {
return std::make_unique<DataRequest>();
}},
{eMessageID::AttachmentCollectRequest, []() {
return std::make_unique<AttachmentCollectRequest>();
}},
{eMessageID::DeleteRequest, []() {
return std::make_unique<DeleteRequest>();
}},
{eMessageID::ReadRequest, []() {
return std::make_unique<ReadRequest>();
}},
{eMessageID::NotificationRequest, []() {
return std::make_unique<NotificationRequest>();
}},
};
void MailLUBitStream::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(messageID);
}
bool MailLUBitStream::Deserialize(RakNet::BitStream& bitstream) {
VALIDATE_READ(bitstream.Read(messageID));
return true;
}
bool SendRequest::Deserialize(RakNet::BitStream& bitStream) {
VALIDATE_READ(mailInfo.Deserialize(bitStream));
return true;
}
void SendRequest::Handle() {
SendResponse response;
auto* character = player->GetCharacter();
if (character && !(character->HasPermission(ePermissionMap::RestrictedMailAccess) || character->GetParentUser()->GetIsMuted())) {
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, INVALID, 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;
}
response.Send(sysAddr);
}
void SendResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(status);
}
void NotificationResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(status);
bitStream.Write<uint64_t>(0); // unused
bitStream.Write<uint64_t>(0); // unused
bitStream.Write(auctionID);
bitStream.Write<uint64_t>(0); // unused
bitStream.Write(mailCount);
bitStream.Write<uint32_t>(0); // packing
}
void DataRequest::Handle() {
const auto* character = player->GetCharacter();
if (!character) return;
auto playerMail = Database::Get()->GetMailForPlayer(character->GetID(), 20);
DataResponse response;
response.playerMail = playerMail;
response.Send(sysAddr);
}
void DataResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(this->throttled);
bitStream.Write<uint16_t>(this->playerMail.size());
bitStream.Write<uint16_t>(0); // packing
for (const auto& mail : this->playerMail) {
mail.Serialize(bitStream);
}
}
bool AttachmentCollectRequest::Deserialize(RakNet::BitStream& bitStream) {
uint32_t unknown;
VALIDATE_READ(bitStream.Read(unknown));
VALIDATE_READ(bitStream.Read(mailID));
VALIDATE_READ(bitStream.Read(playerID));
return true;
}
void AttachmentCollectRequest::Handle() {
AttachmentCollectResponse response;
response.mailID = mailID;
auto inv = player->GetComponent<InventoryComponent>();
if (mailID > 0 && playerID == player->GetObjectID() && inv) {
auto playerMail = Database::Get()->GetMail(mailID);
if (!playerMail) {
response.status = eAttachmentCollectResponse::MailNotFound;
} else if (!inv->HasSpaceForLoot({ {playerMail->itemLOT, playerMail->itemCount} })) {
response.status = eAttachmentCollectResponse::NoSpaceInInventory;
} else {
inv->AddItem(playerMail->itemLOT, playerMail->itemCount, eLootSourceType::MAIL);
Database::Get()->ClaimMailItem(mailID);
response.status = eAttachmentCollectResponse::Success;
}
}
response.Send(sysAddr);
}
void AttachmentCollectResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(status);
bitStream.Write(mailID);
}
bool DeleteRequest::Deserialize(RakNet::BitStream& bitStream) {
int32_t unknown;
VALIDATE_READ(bitStream.Read(unknown));
VALIDATE_READ(bitStream.Read(mailID));
VALIDATE_READ(bitStream.Read(playerID));
return true;
}
void DeleteRequest::Handle() {
DeleteResponse response;
response.mailID = mailID;
auto mailData = Database::Get()->GetMail(mailID);
if (mailData && !(mailData->itemLOT != 0 && mailData->itemCount > 0)) {
Database::Get()->DeleteMail(mailID);
response.status = eDeleteResponse::Success;
} else if (mailData && mailData->itemLOT != 0 && mailData->itemCount > 0) {
response.status = eDeleteResponse::HasAttachments;
} else {
response.status = eDeleteResponse::NotFound;
}
response.Send(sysAddr);
}
void DeleteResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(status);
bitStream.Write(mailID);
}
bool ReadRequest::Deserialize(RakNet::BitStream& bitStream) {
int32_t unknown;
VALIDATE_READ(bitStream.Read(unknown));
VALIDATE_READ(bitStream.Read(mailID));
return true;
}
void ReadRequest::Handle() {
ReadResponse response;
response.mailID = mailID;
if (Database::Get()->GetMail(mailID)) {
response.status = eReadResponse::Success;
Database::Get()->MarkMailRead(mailID);
}
response.Send(sysAddr);
}
void ReadResponse::Serialize(RakNet::BitStream& bitStream) const {
MailLUBitStream::Serialize(bitStream);
bitStream.Write(status);
bitStream.Write(mailID);
}
void NotificationRequest::Handle() {
NotificationResponse response;
auto character = player->GetCharacter();
if (character) {
auto unreadMailCount = Database::Get()->GetUnreadMailCount(character->GetID());
response.status = eNotificationResponse::NewMail;
response.mailCount = unreadMailCount;
}
response.Send(sysAddr);
}
}
// Non Stuct Functions
void Mail::HandleMail(RakNet::BitStream& inStream, const SystemAddress& sysAddr, Entity* player) {
MailLUBitStream data;
if (!data.Deserialize(inStream)) {
LOG_DEBUG("Error Reading Mail header");
return;
}
auto it = g_Handlers.find(data.messageID);
if (it != g_Handlers.end()) {
auto request = it->second();
request->sysAddr = sysAddr;
request->player = player;
if (!request->Deserialize(inStream)) {
LOG_DEBUG("Error Reading Mail Request: %s", StringifiedEnum::ToString(data.messageID).data());
return;
}
request->Handle();
} else {
LOG_DEBUG("Unhandled Mail Request with ID: %i", data.messageID);
}
}
void Mail::SendMail(const Entity* recipient, const std::string& subject, const std::string& body, const LOT attachment,
const uint16_t attachmentCount) {
SendMail(
LWOOBJID_EMPTY,
ServerName,
DefaultSender,
recipient->GetObjectID(),
recipient->GetCharacter()->GetName(),
subject,
@@ -46,7 +313,7 @@ void Mail::SendMail(const LWOOBJID recipient, const std::string& recipientName,
const std::string& body, const LOT attachment, const uint16_t attachmentCount, const SystemAddress& sysAddr) {
SendMail(
LWOOBJID_EMPTY,
ServerName,
DefaultSender,
recipient,
recipientName,
subject,
@@ -75,7 +342,7 @@ void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, const
void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJID recipient,
const std::string& recipientName, const std::string& subject, const std::string& body, const LOT attachment,
const uint16_t attachmentCount, const SystemAddress& sysAddr) {
IMail::MailInfo mailInsert;
MailInfo mailInsert;
mailInsert.senderUsername = senderName;
mailInsert.recipient = recipientName;
mailInsert.subject = subject;
@@ -90,316 +357,7 @@ void Mail::SendMail(const LWOOBJID sender, const std::string& senderName, LWOOBJ
Database::Get()->InsertNewMail(mailInsert);
if (sysAddr == UNASSIGNED_SYSTEM_ADDRESS) return; // TODO: Echo to chat server
SendNotification(sysAddr, 1); //Show the "one new mail" message
}
void Mail::HandleMailStuff(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* entity) {
int mailStuffID = 0;
packet.Read(mailStuffID);
auto returnVal = std::async(std::launch::async, [&packet, &sysAddr, entity, mailStuffID]() {
Mail::MailMessageID stuffID = MailMessageID(mailStuffID);
switch (stuffID) {
case MailMessageID::AttachmentCollect:
Mail::HandleAttachmentCollect(packet, sysAddr, entity);
break;
case MailMessageID::DataRequest:
Mail::HandleDataRequest(packet, sysAddr, entity);
break;
case MailMessageID::MailDelete:
Mail::HandleMailDelete(packet, sysAddr);
break;
case MailMessageID::MailRead:
Mail::HandleMailRead(packet, sysAddr);
break;
case MailMessageID::NotificationRequest:
Mail::HandleNotificationRequest(sysAddr, entity->GetObjectID());
break;
case MailMessageID::Send:
Mail::HandleSendMail(packet, sysAddr, entity);
break;
default:
LOG("Unhandled and possibly undefined MailStuffID: %i", int(stuffID));
}
});
}
void Mail::HandleSendMail(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* entity) {
//std::string subject = GeneralUtils::WStringToString(ReadFromPacket(packet, 50));
//std::string body = GeneralUtils::WStringToString(ReadFromPacket(packet, 400));
//std::string recipient = GeneralUtils::WStringToString(ReadFromPacket(packet, 32));
// Check if the player has restricted mail access
auto* character = entity->GetCharacter();
if (!character) return;
if (character->HasPermission(ePermissionMap::RestrictedMailAccess)) {
// Send a message to the player
ChatPackets::SendSystemMessage(
sysAddr,
u"This character has restricted mail access."
);
Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::AccountIsMuted);
return;
}
LUWString subjectRead(50);
packet.Read(subjectRead);
LUWString bodyRead(400);
packet.Read(bodyRead);
LUWString recipientRead(32);
packet.Read(recipientRead);
const std::string subject = subjectRead.GetAsString();
const std::string body = bodyRead.GetAsString();
//Cleanse recipient:
const std::string recipient = std::regex_replace(recipientRead.GetAsString(), std::regex("[^0-9a-zA-Z]+"), "");
uint64_t unknown64 = 0;
LWOOBJID attachmentID;
uint16_t attachmentCount;
packet.Read(unknown64);
packet.Read(attachmentID);
packet.Read(attachmentCount); //We don't care about the rest of the packet.
uint32_t itemID = static_cast<uint32_t>(attachmentID);
LOT itemLOT = 0;
//Inventory::InventoryType itemType;
int mailCost = Game::zoneManager->GetWorldConfig()->mailBaseFee;
int stackSize = 0;
auto inv = static_cast<InventoryComponent*>(entity->GetComponent(eReplicaComponentType::INVENTORY));
Item* item = nullptr;
if (itemID > 0 && attachmentCount > 0 && inv) {
item = inv->FindItemById(attachmentID);
if (item) {
mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig()->mailPercentAttachmentFee);
stackSize = item->GetCount();
itemLOT = item->GetLot();
} else {
Mail::SendSendResponse(sysAddr, MailSendResponse::AttachmentNotFound);
return;
}
}
//Check if we can even send this mail (negative coins bug):
if (entity->GetCharacter()->GetCoins() - mailCost < 0) {
Mail::SendSendResponse(sysAddr, MailSendResponse::NotEnoughCoins);
return;
}
//Get the receiver's id:
auto receiverID = Database::Get()->GetCharacterInfo(recipient);
if (!receiverID) {
Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::RecipientNotFound);
return;
}
//Check if we have a valid receiver:
if (GeneralUtils::CaseInsensitiveStringCompare(recipient, character->GetName()) || receiverID->id == character->GetID()) {
Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::CannotMailSelf);
return;
} else {
IMail::MailInfo mailInsert;
mailInsert.senderUsername = character->GetName();
mailInsert.recipient = recipient;
mailInsert.subject = subject;
mailInsert.body = body;
mailInsert.senderId = character->GetID();
mailInsert.receiverId = receiverID->id;
mailInsert.itemCount = attachmentCount;
mailInsert.itemID = itemID;
mailInsert.itemLOT = itemLOT;
mailInsert.itemSubkey = LWOOBJID_EMPTY;
Database::Get()->InsertNewMail(mailInsert);
}
Mail::SendSendResponse(sysAddr, Mail::MailSendResponse::Success);
entity->GetCharacter()->SetCoins(entity->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL);
LOG("Seeing if we need to remove item with ID/count/LOT: %i %i %i", itemID, attachmentCount, itemLOT);
if (inv && itemLOT != 0 && attachmentCount > 0 && item) {
LOG("Trying to remove item with ID/count/LOT: %i %i %i", itemID, attachmentCount, itemLOT);
inv->RemoveItem(itemLOT, attachmentCount, INVALID, true);
auto* missionCompoent = entity->GetComponent<MissionComponent>();
if (missionCompoent != nullptr) {
missionCompoent->Progress(eMissionTaskType::GATHER, itemLOT, LWOOBJID_EMPTY, "", -attachmentCount);
}
}
character->SaveXMLToDatabase();
}
void Mail::HandleDataRequest(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* player) {
auto playerMail = Database::Get()->GetMailForPlayer(player->GetCharacter()->GetID(), 20);
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::MAIL);
bitStream.Write(int(MailMessageID::MailData));
bitStream.Write(int(0)); // throttled
bitStream.Write<uint16_t>(playerMail.size()); // size
bitStream.Write<uint16_t>(0);
for (const auto& mail : playerMail) {
bitStream.Write(mail.id); //MailID
const LUWString subject(mail.subject, 50);
bitStream.Write(subject); //subject
const LUWString body(mail.body, 400);
bitStream.Write(body); //body
const LUWString sender(mail.senderUsername, 32);
bitStream.Write(sender); //sender
bitStream.Write(uint32_t(0)); // packing
bitStream.Write(uint64_t(0)); // attachedCurrency
bitStream.Write(mail.itemID); //Attachment ID
LOT lot = mail.itemLOT;
if (lot <= 0) bitStream.Write(LOT(-1));
else bitStream.Write(lot);
bitStream.Write(uint32_t(0)); // packing
bitStream.Write(mail.itemSubkey); // Attachment subKey
bitStream.Write<uint16_t>(mail.itemCount); // Attachment count
bitStream.Write(uint8_t(0)); // subject type (used for auction)
bitStream.Write(uint8_t(0)); // packing
bitStream.Write(uint32_t(0)); // packing
bitStream.Write<uint64_t>(mail.timeSent); // expiration date
bitStream.Write<uint64_t>(mail.timeSent);// send date
bitStream.Write<uint8_t>(mail.wasRead); //was read
bitStream.Write(uint8_t(0)); // isLocalized
bitStream.Write(uint16_t(0)); // packing
bitStream.Write(uint32_t(0)); // packing
}
Game::server->Send(bitStream, sysAddr, false);
}
void Mail::HandleAttachmentCollect(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* player) {
int unknown;
uint64_t mailID;
LWOOBJID playerID;
packet.Read(unknown);
packet.Read(mailID);
packet.Read(playerID);
if (mailID > 0 && playerID == player->GetObjectID()) {
auto playerMail = Database::Get()->GetMail(mailID);
LOT attachmentLOT = 0;
uint32_t attachmentCount = 0;
if (playerMail) {
attachmentLOT = playerMail->itemLOT;
attachmentCount = playerMail->itemCount;
}
auto inv = player->GetComponent<InventoryComponent>();
if (!inv) return;
inv->AddItem(attachmentLOT, attachmentCount, eLootSourceType::MAIL);
Mail::SendAttachmentRemoveConfirm(sysAddr, mailID);
Database::Get()->ClaimMailItem(mailID);
}
}
void Mail::HandleMailDelete(RakNet::BitStream& packet, const SystemAddress& sysAddr) {
int unknown;
uint64_t mailID;
LWOOBJID playerID;
packet.Read(unknown);
packet.Read(mailID);
packet.Read(playerID);
if (mailID > 0) Mail::SendDeleteConfirm(sysAddr, mailID, playerID);
}
void Mail::HandleMailRead(RakNet::BitStream& packet, const SystemAddress& sysAddr) {
int unknown;
uint64_t mailID;
packet.Read(unknown);
packet.Read(mailID);
if (mailID > 0) Mail::SendReadConfirm(sysAddr, mailID);
}
void Mail::HandleNotificationRequest(const SystemAddress& sysAddr, uint32_t objectID) {
auto unreadMailCount = Database::Get()->GetUnreadMailCount(objectID);
if (unreadMailCount > 0) Mail::SendNotification(sysAddr, unreadMailCount);
}
void Mail::SendSendResponse(const SystemAddress& sysAddr, MailSendResponse response) {
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::MAIL);
bitStream.Write(int(MailMessageID::SendResponse));
bitStream.Write(int(response));
Game::server->Send(bitStream, sysAddr, false);
}
void Mail::SendNotification(const SystemAddress& sysAddr, int mailCount) {
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::MAIL);
uint64_t messageType = 2;
uint64_t s1 = 0;
uint64_t s2 = 0;
uint64_t s3 = 0;
uint64_t s4 = 0;
bitStream.Write(messageType);
bitStream.Write(s1);
bitStream.Write(s2);
bitStream.Write(s3);
bitStream.Write(s4);
bitStream.Write(mailCount);
bitStream.Write(int(0)); //Unknown
Game::server->Send(bitStream, sysAddr, false);
}
void Mail::SendAttachmentRemoveConfirm(const SystemAddress& sysAddr, uint64_t mailID) {
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::MAIL);
bitStream.Write(int(MailMessageID::AttachmentCollectConfirm));
bitStream.Write(int(0)); //unknown
bitStream.Write(mailID);
Game::server->Send(bitStream, sysAddr, false);
}
void Mail::SendDeleteConfirm(const SystemAddress& sysAddr, uint64_t mailID, LWOOBJID playerID) {
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::MAIL);
bitStream.Write(int(MailMessageID::MailDeleteConfirm));
bitStream.Write(int(0)); //unknown
bitStream.Write(mailID);
Game::server->Send(bitStream, sysAddr, false);
Database::Get()->DeleteMail(mailID);
}
void Mail::SendReadConfirm(const SystemAddress& sysAddr, uint64_t mailID) {
RakNet::BitStream bitStream;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, MessageType::Client::MAIL);
bitStream.Write(int(MailMessageID::MailReadConfirm));
bitStream.Write(int(0)); //unknown
bitStream.Write(mailID);
Game::server->Send(bitStream, sysAddr, false);
Database::Get()->MarkMailRead(mailID);
NotificationResponse response;
response.status = eNotificationResponse::NewMail;
response.Send(sysAddr);
}

View File

@@ -1,43 +1,210 @@
#pragma once
#ifndef __MAIL_H__
#define __MAIL_H__
#include <cstdint>
#include "BitStream.h"
#include "RakNetTypes.h"
#include "dCommonVars.h"
#include "BitStreamUtils.h"
#include "MailInfo.h"
class Entity;
namespace Mail {
enum class MailMessageID {
Send = 0x00,
SendResponse = 0x01,
DataRequest = 0x03,
MailData = 0x04,
AttachmentCollect = 0x05,
AttachmentCollectConfirm = 0x06,
MailDelete = 0x07,
MailDeleteConfirm = 0x08,
MailRead = 0x09,
MailReadConfirm = 0x0a,
NotificationRequest = 0x0b
enum class eMessageID : uint32_t {
SendRequest = 0,
SendResponse,
NotificationResponse,
DataRequest,
DataResponse,
AttachmentCollectRequest,
AttachmentCollectResponse,
DeleteRequest,
DeleteResponse,
ReadRequest,
ReadResponse,
NotificationRequest,
AuctionCreate,
AuctionCreationResponse,
AuctionCancel,
AuctionCancelResponse,
AuctionList,
AuctionListResponse,
AuctionBid,
AuctionBidResponse,
UnknownError
};
enum class MailSendResponse {
enum class eSendResponse : uint32_t {
Success = 0,
NotEnoughCoins,
AttachmentNotFound,
ItemCannotBeMailed,
CannotMailSelf,
RecipientNotFound,
DifferentFaction,
Unknown,
RecipientDifferentFaction,
UnHandled7,
ModerationFailure,
AccountIsMuted,
UnknownFailure,
SenderAccountIsMuted,
UnHandled10,
RecipientIsIgnored,
UnknownFailure3,
RecipientIsFTP
UnHandled12,
RecipientIsFTP,
UnknownError
};
const std::string ServerName = "Darkflame Universe";
enum class eDeleteResponse : uint32_t {
Success = 0,
HasAttachments,
NotFound,
Throttled,
UnknownError
};
enum class eAttachmentCollectResponse : uint32_t {
Success = 0,
AttachmentNotFound,
NoSpaceInInventory,
MailNotFound,
Throttled,
UnknownError
};
enum class eNotificationResponse : uint32_t {
NewMail = 0,
UnHandled,
AuctionWon,
AuctionSold,
AuctionOutbided,
AuctionExpired,
AuctionCancelled,
AuctionUpdated,
UnknownError
};
enum class eReadResponse : uint32_t {
Success = 0,
UnknownError
};
enum class eAuctionCreateResponse : uint32_t {
Success = 0,
NotEnoughMoney,
ItemNotFound,
ItemNotSellable,
UnknownError
};
enum class eAuctionCancelResponse : uint32_t {
NotFound = 0,
NotYours,
HasBid,
NoLongerExists,
UnknownError
};
struct MailLUBitStream : public LUBitStream {
eMessageID messageID = eMessageID::UnknownError;
SystemAddress sysAddr = UNASSIGNED_SYSTEM_ADDRESS;
Entity* player = nullptr;
MailLUBitStream() = default;
MailLUBitStream(eMessageID _messageID) : LUBitStream(eConnectionType::CLIENT, MessageType::Client::MAIL), messageID{_messageID} {};
virtual void Serialize(RakNet::BitStream& bitStream) const override;
virtual bool Deserialize(RakNet::BitStream& bitStream) override;
virtual void Handle() override {};
};
struct SendRequest : public MailLUBitStream {
MailInfo mailInfo;
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle() override;
};
struct SendResponse :public MailLUBitStream {
eSendResponse status = eSendResponse::UnknownError;
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct NotificationResponse : public MailLUBitStream {
eNotificationResponse status = eNotificationResponse::UnknownError;
LWOOBJID auctionID = LWOOBJID_EMPTY;
uint32_t mailCount = 1;
NotificationResponse() : MailLUBitStream(eMessageID::NotificationResponse) {};
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct DataRequest : public MailLUBitStream {
bool Deserialize(RakNet::BitStream& bitStream) override { return true; };
void Handle() override;
};
struct DataResponse : public MailLUBitStream {
uint32_t throttled = 0;
std::vector<MailInfo> playerMail;
DataResponse() : MailLUBitStream(eMessageID::DataResponse) {};
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct AttachmentCollectRequest : public MailLUBitStream {
uint64_t mailID = 0;
LWOOBJID playerID = LWOOBJID_EMPTY;
AttachmentCollectRequest() : MailLUBitStream(eMessageID::AttachmentCollectRequest) {};
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle() override;
};
struct AttachmentCollectResponse : public MailLUBitStream {
eAttachmentCollectResponse status = eAttachmentCollectResponse::UnknownError;
uint64_t mailID = 0;
AttachmentCollectResponse() : MailLUBitStream(eMessageID::AttachmentCollectResponse) {};
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct DeleteRequest : public MailLUBitStream {
uint64_t mailID = 0;
LWOOBJID playerID = LWOOBJID_EMPTY;
DeleteRequest() : MailLUBitStream(eMessageID::DeleteRequest) {};
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle() override;
};
struct DeleteResponse : public MailLUBitStream {
eDeleteResponse status = eDeleteResponse::UnknownError;
uint64_t mailID = 0;
DeleteResponse() : MailLUBitStream(eMessageID::DeleteResponse) {};
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct ReadRequest : public MailLUBitStream {
uint64_t mailID = 0;
ReadRequest() : MailLUBitStream(eMessageID::ReadRequest) {};
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle() override;
};
struct ReadResponse : public MailLUBitStream {
uint64_t mailID = 0;
eReadResponse status = eReadResponse::UnknownError;
ReadResponse() : MailLUBitStream(eMessageID::ReadResponse) {};
void Serialize(RakNet::BitStream& bitStream) const override;
};
struct NotificationRequest : public MailLUBitStream {
NotificationRequest() : MailLUBitStream(eMessageID::NotificationRequest) {};
bool Deserialize(RakNet::BitStream& bitStream) override { return true; };
void Handle() override;
};
void HandleMail(RakNet::BitStream& inStream, const SystemAddress& sysAddr, Entity* player);
void SendMail(
const Entity* recipient,
@@ -78,18 +245,6 @@ namespace Mail {
uint16_t attachmentCount,
const SystemAddress& sysAddr
);
void HandleMailStuff(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* entity);
void HandleSendMail(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* entity);
void HandleDataRequest(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* player);
void HandleAttachmentCollect(RakNet::BitStream& packet, const SystemAddress& sysAddr, Entity* player);
void HandleMailDelete(RakNet::BitStream& packet, const SystemAddress& sysAddr);
void HandleMailRead(RakNet::BitStream& packet, const SystemAddress& sysAddr);
void HandleNotificationRequest(const SystemAddress& sysAddr, uint32_t objectID);
void SendSendResponse(const SystemAddress& sysAddr, MailSendResponse response);
void SendNotification(const SystemAddress& sysAddr, int mailCount);
void SendAttachmentRemoveConfirm(const SystemAddress& sysAddr, uint64_t mailID);
void SendDeleteConfirm(const SystemAddress& sysAddr, uint64_t mailID, LWOOBJID playerID);
void SendReadConfirm(const SystemAddress& sysAddr, uint64_t mailID);
};
#endif // !__MAIL_H__

View File

@@ -996,7 +996,7 @@ void SlashCommandHandler::Startup() {
Command RequestMailCountCommand{
.help = "Gets the players mail count",
.info = "Sends notification with number of unread messages in the player's mailbox",
.aliases = { "requestmailcount" },
.aliases = { "requestmailcount", "checkmail" },
.handle = GMZeroCommands::RequestMailCount,
.requiredLevel = eGameMasterLevel::CIVILIAN
};

View File

@@ -102,7 +102,7 @@ namespace GMGreaterThanZeroCommands {
return;
}
IMail::MailInfo mailInsert;
MailInfo mailInsert;
mailInsert.senderId = entity->GetObjectID();
mailInsert.senderUsername = "Darkflame Universe";
mailInsert.receiverId = receiverID;

View File

@@ -12,6 +12,7 @@
#include "VanityUtilities.h"
#include "WorldPackets.h"
#include "ZoneInstanceManager.h"
#include "Database.h"
// Components
#include "BuffComponent.h"
@@ -216,7 +217,10 @@ namespace GMZeroCommands {
}
void RequestMailCount(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
Mail::HandleNotificationRequest(entity->GetSystemAddress(), entity->GetObjectID());
Mail::NotificationResponse response;
response.status = Mail::eNotificationResponse::NewMail;
response.mailCount = Database::Get()->GetUnreadMailCount(entity->GetCharacter()->GetID());
response.Send(sysAddr);
}
void InstanceInfo(Entity* entity, const SystemAddress& sysAddr, const std::string args) {

View File

@@ -43,6 +43,13 @@
#include "CDZoneTableTable.h"
#include "eGameMasterLevel.h"
#ifdef DARKFLAME_PLATFORM_UNIX
#include <sys/types.h>
#include <sys/wait.h>
#endif
namespace Game {
Logger* logger = nullptr;
dServer* server = nullptr;
@@ -455,6 +462,12 @@ int main(int argc, char** argv) {
}
}
#ifdef DARKFLAME_PLATFORM_UNIX
// kill off dead zombie instances
int status{};
waitpid(static_cast<pid_t>(-1), &status, WNOHANG);
#endif
t += std::chrono::milliseconds(masterFrameDelta);
std::this_thread::sleep_until(t);
}

30
dNet/BitStreamUtils.cpp Normal file
View File

@@ -0,0 +1,30 @@
#include "BitStreamUtils.h"
#include "dServer.h"
#include "BitStream.h"
#include "PacketUtils.h"
void LUBitStream::WriteHeader(RakNet::BitStream& bitStream) const {
bitStream.Write<MessageID>(ID_USER_PACKET_ENUM);
bitStream.Write(this->connectionType);
bitStream.Write(this->internalPacketID);
bitStream.Write<uint8_t>(0); // padding
}
bool LUBitStream::ReadHeader(RakNet::BitStream& bitStream) {
MessageID messageID;
bitStream.Read(messageID);
if (messageID != ID_USER_PACKET_ENUM) return false;
VALIDATE_READ(bitStream.Read(this->connectionType));
VALIDATE_READ(bitStream.Read(this->internalPacketID));
uint8_t padding;
VALIDATE_READ(bitStream.Read<uint8_t>(padding));
return true;
}
void LUBitStream::Send(const SystemAddress& sysAddr) const {
RakNet::BitStream bitStream;
this->WriteHeader(bitStream);
this->Serialize(bitStream);
Game::server->Send(bitStream, sysAddr, sysAddr == UNASSIGNED_SYSTEM_ADDRESS);
}

View File

@@ -2,12 +2,13 @@
#define __BITSTREAMUTILS__H__
#include "GeneralUtils.h"
#include "MessageIdentifiers.h"
#include "BitStream.h"
#include "MessageIdentifiers.h"
#include "eConnectionType.h"
#include <string>
#include <algorithm>
enum class eConnectionType : uint16_t;
#define VALIDATE_READ(x) do { if (!x) return false; } while (0)
struct LUString {
std::string string;
@@ -45,6 +46,28 @@ struct LUWString {
};
};
struct LUBitStream {
eConnectionType connectionType = eConnectionType::UNKNOWN;
uint32_t internalPacketID = 0xFFFFFFFF;
LUBitStream() = default;
template <typename T>
LUBitStream(eConnectionType connectionType, T internalPacketID) {
this->connectionType = connectionType;
this->internalPacketID = static_cast<uint32_t>(internalPacketID);
}
void WriteHeader(RakNet::BitStream& bitStream) const;
bool ReadHeader(RakNet::BitStream& bitStream);
void Send(const SystemAddress& sysAddr) const;
virtual void Serialize(RakNet::BitStream& bitStream) const {}
virtual bool Deserialize(RakNet::BitStream& bitStream) { return true; }
virtual void Handle() {};
};
namespace BitStreamUtils {
template<typename T>
void WriteHeader(RakNet::BitStream& bitStream, eConnectionType connectionType, T internalPacketID) {
@@ -53,7 +76,6 @@ namespace BitStreamUtils {
bitStream.Write(static_cast<uint32_t>(internalPacketID));
bitStream.Write<uint8_t>(0);
}
}
namespace RakNet {

View File

@@ -1,7 +1,9 @@
set(DNET_SOURCES "AuthPackets.cpp"
"BitStreamUtils.cpp"
"ChatPackets.cpp"
"ClientPackets.cpp"
"dServer.cpp"
"MailInfo.cpp"
"MasterPackets.cpp"
"PacketUtils.cpp"
"WorldPackets.cpp"

63
dNet/MailInfo.cpp Normal file
View File

@@ -0,0 +1,63 @@
#include "MailInfo.h"
#include "BitStream.h"
#include "DluAssert.h"
void MailInfo::Serialize(RakNet::BitStream& bitStream) const {
bitStream.Write(id);
const LUWString subject(this->subject, 50);
bitStream.Write(subject);
const LUWString body(this->body, 400);
bitStream.Write(body);
const LUWString sender(this->senderUsername, 32);
bitStream.Write(sender);
bitStream.Write<uint32_t>(0); // packing
bitStream.Write<uint64_t>(0); // attachedCurrency
bitStream.Write(itemID);
LOT lot = itemLOT;
if (lot <= 0) bitStream.Write<LOT>(LOT_NULL);
else bitStream.Write(lot);
bitStream.Write<uint32_t>(0); // packing
bitStream.Write(itemSubkey);
bitStream.Write(itemCount);
bitStream.Write<uint8_t>(0); // subject type (used for auction)
bitStream.Write<uint8_t>(0); // packing
bitStream.Write<uint32_t>(0); // packing
bitStream.Write<uint64_t>(timeSent); // expiration date
bitStream.Write<uint64_t>(timeSent);// send date
bitStream.Write<uint8_t>(wasRead); // was read
bitStream.Write<uint8_t>(0); // isLocalized
bitStream.Write<uint16_t>(1033); // language code
bitStream.Write<uint32_t>(0); // packing
}
bool MailInfo::Deserialize(RakNet::BitStream& bitStream) {
LUWString subject(50);
VALIDATE_READ(bitStream.Read(subject));
this->subject = subject.GetAsString();
LUWString body(400);
VALIDATE_READ(bitStream.Read(body));
this->body = body.GetAsString();
LUWString recipientName(32);
VALIDATE_READ(bitStream.Read(recipientName));
this->recipient = recipientName.GetAsString();
uint64_t unknown;
VALIDATE_READ(bitStream.Read(unknown));
VALIDATE_READ(bitStream.Read(itemID));
VALIDATE_READ(bitStream.Read(itemCount));
VALIDATE_READ(bitStream.Read(languageCode));
bitStream.IgnoreBytes(4); // padding
DluAssert(bitStream.GetNumberOfUnreadBits() == 0);
return true;
}

34
dNet/MailInfo.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef __MAILINFO_H__
#define __MAILINFO_H__
#include <string>
#include <cstdint>
#include "dCommonVars.h"
namespace RakNet {
class BitStream;
}
struct MailInfo {
std::string senderUsername;
std::string recipient;
std::string subject;
std::string body;
uint64_t id{};
uint32_t senderId{};
uint32_t receiverId{};
uint64_t timeSent{};
bool wasRead{};
uint16_t languageCode{};
struct {
LWOOBJID itemID{};
int16_t itemCount{};
LOT itemLOT{};
LWOOBJID itemSubkey{};
};
void Serialize(RakNet::BitStream& bitStream) const;
bool Deserialize(RakNet::BitStream& bitStream);
};
#endif // __MAILINFO_H__

View File

@@ -16,6 +16,7 @@ void DLUVanityTeleportingObject::OnStartup(Entity* self) {
void DLUVanityTeleportingObject::OnTimerDone(Entity* self, std::string timerName) {
if (timerName == "setupTeleport") {
RenderComponent::PlayAnimation(self, u"interact");
GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportBeam", "teleportBeam");
GameMessages::SendPlayFXEffect(self->GetObjectID(), 6478, u"teleportRings", "teleportRings");
@@ -27,7 +28,6 @@ void DLUVanityTeleportingObject::OnTimerDone(Entity* self, std::string timerName
} else if (timerName == "teleport") {
std::vector<VanityObjectLocation>& locations = m_Object->m_Locations[Game::server->GetZoneID()];
selectLocation:
VanityObjectLocation& newLocation = locations[GeneralUtils::GenerateRandomNumber<size_t>(0, locations.size() - 1)];
// try to get not the same position, but if we get the same one twice, it's fine

View File

@@ -8,16 +8,11 @@ void AmTeapotServer::OnUse(Entity* self, Entity* user) {
auto* inventoryComponent = user->GetComponent<InventoryComponent>();
if (!inventoryComponent) return;
auto* blueFlowerItem = inventoryComponent->FindItemByLot(BLUE_FLOWER_LEAVES, eInventoryType::ITEMS);
if (!blueFlowerItem) {
blueFlowerItem = inventoryComponent->FindItemByLot(BLUE_FLOWER_LEAVES, eInventoryType::VAULT_ITEMS);
if (!blueFlowerItem) return;
}
// The client allows you to use the teapot only if you have a stack of 10 leaves in some inventory somewhere.
if (blueFlowerItem->GetCount() >= 10) {
blueFlowerItem->SetCount(blueFlowerItem->GetCount() - 10);
if (inventoryComponent->GetLotCountNonTransfer(BLUE_FLOWER_LEAVES, false) >= 10) {
inventoryComponent->RemoveItem(BLUE_FLOWER_LEAVES, 10, eInventoryType::ALL);
inventoryComponent->AddItem(WU_S_IMAGINATION_TEA, 1);
}
GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, self->GetObjectID());
}

View File

@@ -833,15 +833,20 @@ void HandlePacket(Packet* packet) {
}
if (packet->data[0] != ID_USER_PACKET_ENUM || packet->length < 4) return;
if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::SERVER) {
if (static_cast<MessageType::Server>(packet->data[3]) == MessageType::Server::VERSION_CONFIRM) {
CINSTREAM;
LUBitStream luBitStream;
luBitStream.ReadHeader(inStream);
if (luBitStream.connectionType == eConnectionType::SERVER) {
if (static_cast<MessageType::Server>(luBitStream.internalPacketID) == MessageType::Server::VERSION_CONFIRM) {
AuthPackets::HandleHandshake(Game::server, packet);
}
}
if (static_cast<eConnectionType>(packet->data[1]) != eConnectionType::WORLD) return;
if (luBitStream.connectionType != eConnectionType::WORLD) return;
switch (static_cast<MessageType::World>(packet->data[3])) {
switch (static_cast<MessageType::World>(luBitStream.internalPacketID)) {
case MessageType::World::VALIDATION: {
CINSTREAM_SKIP_HEADER;
LUWString username;
@@ -1187,11 +1192,7 @@ void HandlePacket(Packet* packet) {
}
case MessageType::World::MAIL: {
RakNet::BitStream bitStream(packet->data, packet->length, false);
// FIXME: Change this to the macro to skip the header...
LWOOBJID space;
bitStream.Read(space);
Mail::HandleMailStuff(bitStream, packet->systemAddress, UserManager::Instance()->GetUser(packet->systemAddress)->GetLastUsedChar()->GetEntity());
Mail::HandleMail(inStream, packet->systemAddress, UserManager::Instance()->GetUser(packet->systemAddress)->GetLastUsedChar()->GetEntity());
break;
}

View File

@@ -9,5 +9,6 @@ max_number_of_friends=50
web_server_enabled=0
web_server_listen_ip=127.0.0.1
# Unused for now
# web_server_listen_ip=127.0.0.1
web_server_listen_port=2005