Compare commits

...

23 Commits

Author SHA1 Message Date
Wincent
1c0ca57419 Cache build directory when using docker with BuildKit 2025-04-18 16:03:34 +00:00
David Markowitz
6b0f3a66e9 fix: session flags not being loaded every other world load (#1763)
tested that news screen no longer shows up on every other world load
2025-04-11 09:10:38 -05:00
David Markowitz
f5c212fb86 fix: sys addr for private zones (#1760)
* fix: sys addr for private zones

* Initialize variables in Instance
2025-04-11 09:05:31 -05: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
47 changed files with 937 additions and 621 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

@@ -11,7 +11,12 @@ COPY --chmod=0500 ./build.sh /app/
RUN sed -i 's/MARIADB_CONNECTOR_COMPILE_JOBS__=.*/MARIADB_CONNECTOR_COMPILE_JOBS__=2/' /app/CMakeVariables.txt
RUN ./build.sh
RUN --mount=type=cache,target=/app/build,id=build-cache \
mkdir -p /app/build /tmp/persisted-build && \
cd /app/build && \
cmake .. && \
make -j$(nproc --ignore 1) && \
cp -r /app/build/* /tmp/persisted-build/
FROM debian:12 as runtime
@@ -23,23 +28,23 @@ RUN --mount=type=cache,id=build-apt-cache,target=/var/cache/apt \
rm -rf /var/lib/apt/lists/*
# Grab libraries and load them
COPY --from=build /app/build/mariadbcpp/libmariadbcpp.so /usr/local/lib/
COPY --from=build /tmp/persisted-build/mariadbcpp/libmariadbcpp.so /usr/local/lib/
RUN ldconfig
# Server bins
COPY --from=build /app/build/*Server /app/
COPY --from=build /tmp/persisted-build/*Server /app/
# Necessary suplimentary files
COPY --from=build /app/build/*.ini /app/configs/
COPY --from=build /app/build/vanity/*.* /app/vanity/
COPY --from=build /app/build/navmeshes /app/navmeshes
COPY --from=build /app/build/migrations /app/migrations
COPY --from=build /app/build/*.dcf /app/
COPY --from=build /tmp/persisted-build/*.ini /app/configs/
COPY --from=build /tmp/persisted-build/vanity/*.* /app/vanity/
COPY --from=build /tmp/persisted-build/navmeshes /app/navmeshes
COPY --from=build /tmp/persisted-build/migrations /app/migrations
COPY --from=build /tmp/persisted-build/*.dcf /app/
# backup of config and vanity files to copy to the host incase
# of a mount clobbering the copy from above
COPY --from=build /app/build/*.ini /app/default-configs/
COPY --from=build /app/build/vanity/*.* /app/default-vanity/
COPY --from=build /tmp/persisted-build/*.ini /app/default-configs/
COPY --from=build /tmp/persisted-build/vanity/*.* /app/default-vanity/
# needed as the container runs with the root user
# and therefore sudo doesn't exist

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

@@ -204,6 +204,7 @@ void Character::DoQuickXMLDataParse() {
while (currentChild) {
const auto* temp = currentChild->Attribute("v");
const auto* id = currentChild->Attribute("id");
const auto* si = currentChild->Attribute("si");
if (temp && id) {
uint32_t index = 0;
uint64_t value = 0;
@@ -212,6 +213,9 @@ void Character::DoQuickXMLDataParse() {
value = std::stoull(temp);
m_PlayerFlags.insert(std::make_pair(index, value));
} else if (si) {
auto value = GeneralUtils::TryParse<uint32_t>(si);
if (value) m_SessionFlags.insert(value.value());
}
currentChild = currentChild->NextSiblingElement();
}

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

@@ -828,7 +828,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

@@ -273,6 +273,16 @@ Instance* InstanceManager::FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID
return nullptr;
}
Instance* InstanceManager::FindInstanceWithPrivate(LWOMAPID mapID, LWOINSTANCEID instanceID) {
for (Instance* i : m_Instances) {
if (i && i->GetMapID() == mapID && i->GetInstanceID() == instanceID && !i->GetShutdownComplete() && !i->GetIsShuttingDown()) {
return i;
}
}
return nullptr;
}
Instance* InstanceManager::CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password) {
auto* instance = FindPrivateInstance(password);

View File

@@ -76,25 +76,25 @@ public:
void Shutdown();
private:
std::string m_IP;
uint32_t m_Port;
LWOZONEID m_ZoneID;
int m_MaxClientsSoftCap;
int m_MaxClientsHardCap;
int m_CurrentClientCount;
std::vector<Player> m_Players;
SystemAddress m_SysAddr;
bool m_Ready;
bool m_IsShuttingDown;
std::vector<PendingInstanceRequest> m_PendingRequests;
std::vector<PendingInstanceRequest> m_PendingAffirmations;
std::string m_IP{};
uint32_t m_Port{};
LWOZONEID m_ZoneID{};
int m_MaxClientsSoftCap{};
int m_MaxClientsHardCap{};
int m_CurrentClientCount{};
std::vector<Player> m_Players{};
SystemAddress m_SysAddr{};
bool m_Ready{};
bool m_IsShuttingDown{};
std::vector<PendingInstanceRequest> m_PendingRequests{};
std::vector<PendingInstanceRequest> m_PendingAffirmations{};
uint32_t m_AffirmationTimeout;
uint32_t m_AffirmationTimeout{};
bool m_IsPrivate;
std::string m_Password;
bool m_IsPrivate{};
std::string m_Password{};
bool m_Shutdown;
bool m_Shutdown{};
//Private functions:
};
@@ -125,6 +125,7 @@ public:
Instance* FindInstance(LWOMAPID mapID, bool isFriendTransfer, LWOCLONEID cloneId = 0);
Instance* FindInstance(LWOMAPID mapID, LWOINSTANCEID instanceID);
Instance* FindInstanceWithPrivate(LWOMAPID mapID, LWOINSTANCEID instanceID);
Instance* CreatePrivateInstance(LWOMAPID mapID, LWOCLONEID cloneID, const std::string& password);
Instance* FindPrivateInstance(const std::string& password);

View File

@@ -42,6 +42,14 @@
#include "Server.h"
#include "CDZoneTableTable.h"
#include "eGameMasterLevel.h"
#include "StringifiedEnum.h"
#ifdef DARKFLAME_PLATFORM_UNIX
#include <sys/types.h>
#include <sys/wait.h>
#endif
namespace Game {
Logger* logger = nullptr;
@@ -455,6 +463,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);
}
@@ -543,7 +557,7 @@ void HandlePacket(Packet* packet) {
Instance* in = Game::im->GetInstance(zoneID, false, zoneClone);
for (auto* instance : Game::im->GetInstances()) {
LOG("Instance: %i/%i/%i -> %i", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance == in);
LOG("Instance: %i/%i/%i -> %i %s", instance->GetMapID(), instance->GetCloneID(), instance->GetInstanceID(), instance == in, instance->GetSysAddr().ToString());
}
if (in && !in->GetIsReady()) //Instance not ready, make a pending request
@@ -584,15 +598,10 @@ void HandlePacket(Packet* packet) {
if (!Game::im->IsPortInUse(theirPort)) {
Instance* in = new Instance(theirIP.string, theirPort, theirZoneID, theirInstanceID, 0, 12, 12);
SystemAddress copy;
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
in->SetSysAddr(copy);
in->SetSysAddr(packet->systemAddress);
Game::im->AddInstance(in);
} else {
auto instance = Game::im->FindInstance(
theirZoneID, static_cast<uint16_t>(theirInstanceID));
auto* instance = Game::im->FindInstanceWithPrivate(theirZoneID, static_cast<LWOINSTANCEID>(theirInstanceID));
if (instance) {
instance->SetSysAddr(packet->systemAddress);
}
@@ -600,22 +609,14 @@ void HandlePacket(Packet* packet) {
}
if (theirServerType == ServerType::Chat) {
SystemAddress copy;
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
chatServerMasterPeerSysAddr = copy;
chatServerMasterPeerSysAddr = packet->systemAddress;
}
if (theirServerType == ServerType::Auth) {
SystemAddress copy;
copy.binaryAddress = packet->systemAddress.binaryAddress;
copy.port = packet->systemAddress.port;
authServerMasterPeerSysAddr = copy;
authServerMasterPeerSysAddr = packet->systemAddress;
}
LOG("Received server info, instance: %i port: %i", theirInstanceID, theirPort);
LOG("Received %s server info, instance: %i port: %i", StringifiedEnum::ToString(theirServerType).data(), theirInstanceID, theirPort);
break;
}
@@ -679,7 +680,7 @@ void HandlePacket(Packet* packet) {
if (instance) {
instance->AddPlayer(Player());
} else {
printf("Instance missing? What?");
LOG("Instance missing? What?");
}
break;
}
@@ -720,8 +721,8 @@ void HandlePacket(Packet* packet) {
inStream.Read<char>(character);
password += character;
}
Game::im->CreatePrivateInstance(mapId, cloneId, password.c_str());
auto* newInst = Game::im->CreatePrivateInstance(mapId, cloneId, password.c_str());
LOG("Creating private zone %i/%i/%i with password %s", newInst->GetMapID(), newInst->GetCloneID(), newInst->GetInstanceID(), password.c_str());
break;
}
@@ -822,11 +823,10 @@ void HandlePacket(Packet* packet) {
}
case MessageType::Master::SHUTDOWN_RESPONSE: {
RakNet::BitStream inStream(packet->data, packet->length, false);
uint64_t header = inStream.Read(header);
CINSTREAM_SKIP_HEADER;
auto* instance = Game::im->GetInstanceBySysAddr(packet->systemAddress);
LOG("Got shutdown response from %s", packet->systemAddress.ToString());
if (instance == nullptr) {
return;
}

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