mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-12-13 03:28:28 +00:00
Merge branch 'main' into leaderboards-again
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
#include "eObjectBits.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "ePlayerFlag.h"
|
||||
#include "CDPlayerFlagsTable.h"
|
||||
|
||||
Character::Character(uint32_t id, User* parentUser) {
|
||||
//First load the name, etc:
|
||||
@@ -203,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;
|
||||
@@ -211,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();
|
||||
}
|
||||
@@ -231,6 +236,12 @@ void Character::SetBuildMode(bool buildMode) {
|
||||
}
|
||||
|
||||
void Character::SaveXMLToDatabase() {
|
||||
// Check that we can actually _save_ before saving
|
||||
if (!m_OurEntity) {
|
||||
LOG("%i:%s didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
//For metrics, we'll record the time it took to save:
|
||||
auto start = std::chrono::system_clock::now();
|
||||
|
||||
@@ -277,33 +288,19 @@ void Character::SaveXMLToDatabase() {
|
||||
}
|
||||
|
||||
flags->DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes
|
||||
for (std::pair<uint32_t, uint64_t> flag : m_PlayerFlags) {
|
||||
auto* f = m_Doc.NewElement("f");
|
||||
f->SetAttribute("id", flag.first);
|
||||
|
||||
//Because of the joy that is tinyxml2, it doesn't offer a function to set a uint64 as an attribute.
|
||||
//Only signed 64-bits ints would work.
|
||||
std::string v = std::to_string(flag.second);
|
||||
f->SetAttribute("v", v.c_str());
|
||||
|
||||
flags->LinkEndChild(f);
|
||||
for (const auto& [index, flagBucket] : m_PlayerFlags) {
|
||||
auto* f = flags->InsertNewChildElement("f");
|
||||
f->SetAttribute("id", index);
|
||||
f->SetAttribute("v", flagBucket);
|
||||
}
|
||||
|
||||
// Prevents the news feed from showing up on world transfers
|
||||
if (GetPlayerFlag(ePlayerFlag::IS_NEWS_SCREEN_VISIBLE)) {
|
||||
auto* s = m_Doc.NewElement("s");
|
||||
s->SetAttribute("si", ePlayerFlag::IS_NEWS_SCREEN_VISIBLE);
|
||||
flags->LinkEndChild(s);
|
||||
for (const auto& sessionFlag : m_SessionFlags) {
|
||||
auto* s = flags->InsertNewChildElement("s");
|
||||
s->SetAttribute("si", sessionFlag);
|
||||
}
|
||||
|
||||
SaveXmlRespawnCheckpoints();
|
||||
|
||||
//Call upon the entity to update our xmlDoc:
|
||||
if (!m_OurEntity) {
|
||||
LOG("%i:%s didn't have an entity set while saving! CHARACTER WILL NOT BE SAVED!", this->GetID(), this->GetName().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
m_OurEntity->UpdateXMLDoc(m_Doc);
|
||||
|
||||
WriteToDatabase();
|
||||
@@ -323,8 +320,8 @@ void Character::SetIsNewLogin() {
|
||||
while (currentChild) {
|
||||
auto* nextChild = currentChild->NextSiblingElement();
|
||||
if (currentChild->Attribute("si")) {
|
||||
LOG("Removed session flag (%s) from character %i:%s, saving character to database", currentChild->Attribute("si"), GetID(), GetName().c_str());
|
||||
flags->DeleteChild(currentChild);
|
||||
LOG("Removed isLoggedIn flag from character %i:%s, saving character to database", GetID(), GetName().c_str());
|
||||
WriteToDatabase();
|
||||
}
|
||||
currentChild = nextChild;
|
||||
@@ -357,49 +354,64 @@ void Character::SetPlayerFlag(const uint32_t flagId, const bool value) {
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the index first
|
||||
auto flagIndex = uint32_t(std::floor(flagId / 64));
|
||||
const auto flagEntry = CDPlayerFlagsTable::GetEntry(flagId);
|
||||
|
||||
const auto shiftedValue = 1ULL << flagId % 64;
|
||||
|
||||
auto it = m_PlayerFlags.find(flagIndex);
|
||||
|
||||
// Check if flag index exists
|
||||
if (it != m_PlayerFlags.end()) {
|
||||
// Update the value
|
||||
if (value) {
|
||||
it->second |= shiftedValue;
|
||||
} else {
|
||||
it->second &= ~shiftedValue;
|
||||
}
|
||||
if (flagEntry && flagEntry->sessionOnly) {
|
||||
if (value) m_SessionFlags.insert(flagId);
|
||||
else m_SessionFlags.erase(flagId);
|
||||
} else {
|
||||
if (value) {
|
||||
// Otherwise, insert the value
|
||||
uint64_t flagValue = 0;
|
||||
// Calculate the index first
|
||||
auto flagIndex = uint32_t(std::floor(flagId / 64));
|
||||
|
||||
flagValue |= shiftedValue;
|
||||
const auto shiftedValue = 1ULL << flagId % 64;
|
||||
|
||||
m_PlayerFlags.insert(std::make_pair(flagIndex, flagValue));
|
||||
auto it = m_PlayerFlags.find(flagIndex);
|
||||
|
||||
// Check if flag index exists
|
||||
if (it != m_PlayerFlags.end()) {
|
||||
// Update the value
|
||||
if (value) {
|
||||
it->second |= shiftedValue;
|
||||
} else {
|
||||
it->second &= ~shiftedValue;
|
||||
}
|
||||
} else {
|
||||
if (value) {
|
||||
// Otherwise, insert the value
|
||||
uint64_t flagValue = 0;
|
||||
|
||||
flagValue |= shiftedValue;
|
||||
|
||||
m_PlayerFlags.insert(std::make_pair(flagIndex, flagValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Notify the client that a flag has changed server-side
|
||||
GameMessages::SendNotifyClientFlagChange(m_ObjectID, flagId, value, m_ParentUser->GetSystemAddress());
|
||||
}
|
||||
|
||||
bool Character::GetPlayerFlag(const uint32_t flagId) const {
|
||||
// Calculate the index first
|
||||
const auto flagIndex = uint32_t(std::floor(flagId / 64));
|
||||
using enum ePlayerFlag;
|
||||
|
||||
const auto shiftedValue = 1ULL << flagId % 64;
|
||||
bool toReturn = false; //by def, return false.
|
||||
|
||||
auto it = m_PlayerFlags.find(flagIndex);
|
||||
if (it != m_PlayerFlags.end()) {
|
||||
// Don't set the data if we don't have to
|
||||
return (it->second & shiftedValue) != 0;
|
||||
const auto flagEntry = CDPlayerFlagsTable::GetEntry(flagId);
|
||||
if (flagEntry && flagEntry->sessionOnly) {
|
||||
toReturn = m_SessionFlags.contains(flagId);
|
||||
} else {
|
||||
// Calculate the index first
|
||||
const auto flagIndex = uint32_t(std::floor(flagId / 64));
|
||||
|
||||
const auto shiftedValue = 1ULL << flagId % 64;
|
||||
|
||||
auto it = m_PlayerFlags.find(flagIndex);
|
||||
if (it != m_PlayerFlags.end()) {
|
||||
// Don't set the data if we don't have to
|
||||
toReturn = (it->second & shiftedValue) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false; //by def, return false.
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
void Character::SetRetroactiveFlags() {
|
||||
|
||||
@@ -620,6 +620,12 @@ private:
|
||||
*/
|
||||
uint64_t m_LastLogin{};
|
||||
|
||||
/**
|
||||
* Flags only set for the duration of a session
|
||||
*
|
||||
*/
|
||||
std::set<uint32_t> m_SessionFlags;
|
||||
|
||||
/**
|
||||
* The gameplay flags this character has (not just true values)
|
||||
*/
|
||||
|
||||
@@ -386,6 +386,9 @@ void Entity::Initialize() {
|
||||
if (m_Character) {
|
||||
comp->LoadFromXml(m_Character->GetXMLDoc());
|
||||
} else {
|
||||
// extraInfo overrides. Client ORs the database smashable and the luz smashable.
|
||||
comp->SetIsSmashable(comp->GetIsSmashable() | isSmashable);
|
||||
|
||||
if (componentID > 0) {
|
||||
std::vector<CDDestructibleComponent> destCompData = destCompTable->Query([=](CDDestructibleComponent entry) { return (entry.id == componentID); });
|
||||
|
||||
@@ -420,9 +423,6 @@ void Entity::Initialize() {
|
||||
comp->SetMinCoins(currencyValues[0].minvalue);
|
||||
comp->SetMaxCoins(currencyValues[0].maxvalue);
|
||||
}
|
||||
|
||||
// extraInfo overrides. Client ORs the database smashable and the luz smashable.
|
||||
comp->SetIsSmashable(comp->GetIsSmashable() | isSmashable);
|
||||
}
|
||||
} else {
|
||||
comp->SetHealth(1);
|
||||
@@ -860,6 +860,9 @@ void Entity::Subscribe(LWOOBJID scriptObjId, CppScripts::Script* scriptToAdd, co
|
||||
auto* destroyableComponent = GetComponent<DestroyableComponent>();
|
||||
if (!destroyableComponent) return;
|
||||
destroyableComponent->Subscribe(scriptObjId, scriptToAdd);
|
||||
} else if (notificationName == "PlayerResurrectionFinished") {
|
||||
LOG("Subscribing to PlayerResurrectionFinished");
|
||||
m_Subscriptions[scriptObjId][notificationName] = scriptToAdd;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -868,6 +871,9 @@ void Entity::Unsubscribe(LWOOBJID scriptObjId, const std::string& notificationNa
|
||||
auto* destroyableComponent = GetComponent<DestroyableComponent>();
|
||||
if (!destroyableComponent) return;
|
||||
destroyableComponent->Unsubscribe(scriptObjId);
|
||||
} else if (notificationName == "PlayerResurrectionFinished") {
|
||||
LOG("Unsubscribing from PlayerResurrectionFinished");
|
||||
m_Subscriptions[scriptObjId].erase(notificationName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1511,6 +1517,15 @@ void Entity::OnChildLoaded(GameMessages::ChildLoaded& childLoaded) {
|
||||
GetScript()->OnChildLoaded(*this, childLoaded);
|
||||
}
|
||||
|
||||
void Entity::NotifyPlayerResurrectionFinished(GameMessages::PlayerResurrectionFinished& msg) {
|
||||
for (const auto& [id, scriptList] : m_Subscriptions) {
|
||||
auto it = scriptList.find("PlayerResurrectionFinished");
|
||||
if (it == scriptList.end()) continue;
|
||||
|
||||
it->second->NotifyPlayerResurrectionFinished(*this, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void Entity::RequestActivityExit(Entity* sender, LWOOBJID player, bool canceled) {
|
||||
GetScript()->OnRequestActivityExit(sender, player, canceled);
|
||||
}
|
||||
@@ -1550,7 +1565,7 @@ void Entity::Kill(Entity* murderer, const eKillType killType) {
|
||||
|
||||
m_DieCallbacks.clear();
|
||||
|
||||
//OMAI WA MOU, SHINDERIU
|
||||
//お前はもう死んでいる
|
||||
|
||||
GetScript()->OnDie(this, murderer);
|
||||
|
||||
@@ -2193,7 +2208,7 @@ void Entity::SetRespawnRot(const NiQuaternion& rotation) {
|
||||
|
||||
int32_t Entity::GetCollisionGroup() const {
|
||||
for (const auto* component : m_Components | std::views::values) {
|
||||
auto* compToCheck = dynamic_cast<const PhysicsComponent*>(component);
|
||||
auto* compToCheck = dynamic_cast<const PhysicsComponent*>(component);
|
||||
if (compToCheck) {
|
||||
return compToCheck->GetCollisionGroup();
|
||||
}
|
||||
@@ -2201,3 +2216,17 @@ int32_t Entity::GetCollisionGroup() const {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Entity::HandleMsg(GameMessages::GameMsg& msg) const {
|
||||
bool handled = false;
|
||||
const auto [beg, end] = m_MsgHandlers.equal_range(msg.msgId);
|
||||
for (auto it = beg; it != end; ++it) {
|
||||
if (it->second) handled |= it->second(msg);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
void Entity::RegisterMsg(const MessageType::Game msgId, std::function<bool(GameMessages::GameMsg&)> handler) {
|
||||
m_MsgHandlers.emplace(msgId, handler);
|
||||
}
|
||||
|
||||
@@ -14,11 +14,17 @@
|
||||
#include "Observable.h"
|
||||
|
||||
namespace GameMessages {
|
||||
struct GameMsg;
|
||||
struct ActivityNotify;
|
||||
struct ShootingGalleryFire;
|
||||
struct ChildLoaded;
|
||||
struct PlayerResurrectionFinished;
|
||||
};
|
||||
|
||||
namespace MessageType {
|
||||
enum class Game : uint16_t;
|
||||
}
|
||||
|
||||
namespace Loot {
|
||||
class Info;
|
||||
};
|
||||
@@ -219,6 +225,7 @@ public:
|
||||
void OnActivityNotify(GameMessages::ActivityNotify& notify);
|
||||
void OnShootingGalleryFire(GameMessages::ShootingGalleryFire& notify);
|
||||
void OnChildLoaded(GameMessages::ChildLoaded& childLoaded);
|
||||
void NotifyPlayerResurrectionFinished(GameMessages::PlayerResurrectionFinished& msg);
|
||||
|
||||
void OnMessageBoxResponse(Entity* sender, int32_t button, const std::u16string& identifier, const std::u16string& userData);
|
||||
void OnChoiceBoxResponse(Entity* sender, int32_t button, const std::u16string& buttonIdentifier, const std::u16string& identifier);
|
||||
@@ -314,6 +321,10 @@ public:
|
||||
// Scale will only be communicated to the client when the construction packet is sent
|
||||
void SetScale(const float scale) { m_Scale = scale; };
|
||||
|
||||
void RegisterMsg(const MessageType::Game msgId, std::function<bool(GameMessages::GameMsg&)> handler);
|
||||
|
||||
bool HandleMsg(GameMessages::GameMsg& msg) const;
|
||||
|
||||
/**
|
||||
* @brief The observable for player entity position updates.
|
||||
*/
|
||||
@@ -372,6 +383,11 @@ protected:
|
||||
* Collision
|
||||
*/
|
||||
std::vector<LWOOBJID> m_TargetsInPhantom;
|
||||
|
||||
// objectID of receiver and map of notification name to script
|
||||
std::map<LWOOBJID, std::map<std::string, CppScripts::Script*>> m_Subscriptions;
|
||||
|
||||
std::multimap<MessageType::Game, std::function<bool(GameMessages::GameMsg&)>> m_MsgHandlers;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "Mail.h"
|
||||
#include "ZoneInstanceManager.h"
|
||||
#include "WorldPackets.h"
|
||||
#include "MessageType/Game.h"
|
||||
#include <ctime>
|
||||
|
||||
CharacterComponent::CharacterComponent(Entity* parent, Character* character, const SystemAddress& systemAddress) : Component(parent) {
|
||||
@@ -47,6 +48,45 @@ CharacterComponent::CharacterComponent(Entity* parent, Character* character, con
|
||||
m_CountryCode = 0;
|
||||
m_LastUpdateTimestamp = std::time(nullptr);
|
||||
m_SystemAddress = systemAddress;
|
||||
|
||||
RegisterMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, this, &CharacterComponent::OnRequestServerObjectInfo);
|
||||
}
|
||||
|
||||
bool CharacterComponent::OnRequestServerObjectInfo(GameMessages::GameMsg& msg) {
|
||||
auto& request = static_cast<GameMessages::RequestServerObjectInfo&>(msg);
|
||||
AMFArrayValue response;
|
||||
|
||||
response.Insert("visible", true);
|
||||
response.Insert("objectID", std::to_string(request.targetForReport));
|
||||
response.Insert("serverInfo", true);
|
||||
|
||||
auto& data = *response.InsertArray("data");
|
||||
auto& cmptType = data.PushDebug("Character");
|
||||
|
||||
cmptType.PushDebug<AMFIntValue>("Component ID") = GeneralUtils::ToUnderlying(ComponentType);
|
||||
cmptType.PushDebug<AMFIntValue>("Character's account ID") = m_Character->GetParentUser()->GetAccountID();
|
||||
cmptType.PushDebug<AMFBoolValue>("Last log out time") = m_Character->GetLastLogin();
|
||||
cmptType.PushDebug<AMFDoubleValue>("Seconds played this session") = 0;
|
||||
cmptType.PushDebug<AMFBoolValue>("Editor enabled") = false;
|
||||
cmptType.PushDebug<AMFDoubleValue>("Total number of seconds played") = m_TotalTimePlayed;
|
||||
cmptType.PushDebug<AMFStringValue>("Total currency") = std::to_string(m_Character->GetCoins());
|
||||
cmptType.PushDebug<AMFStringValue>("Currency able to be picked up") = std::to_string(m_DroppedCoins);
|
||||
cmptType.PushDebug<AMFStringValue>("Tooltip flags value") = "0";
|
||||
// visited locations
|
||||
cmptType.PushDebug<AMFBoolValue>("is a GM") = m_GMLevel > eGameMasterLevel::CIVILIAN;
|
||||
cmptType.PushDebug<AMFBoolValue>("Has PVP flag turned on") = m_PvpEnabled;
|
||||
cmptType.PushDebug<AMFIntValue>("GM Level") = GeneralUtils::ToUnderlying(m_GMLevel);
|
||||
cmptType.PushDebug<AMFIntValue>("Editor level") = GeneralUtils::ToUnderlying(m_EditorLevel);
|
||||
cmptType.PushDebug<AMFStringValue>("Guild ID") = "0";
|
||||
cmptType.PushDebug<AMFStringValue>("Guild Name") = "";
|
||||
cmptType.PushDebug<AMFDoubleValue>("Reputation") = m_Reputation;
|
||||
cmptType.PushDebug<AMFIntValue>("Current Activity Type") = GeneralUtils::ToUnderlying(m_CurrentActivity);
|
||||
cmptType.PushDebug<AMFDoubleValue>("Property Clone ID") = m_Character->GetPropertyCloneID();
|
||||
|
||||
GameMessages::SendUIMessageServerToSingleClient("ToggleObjectDebugger", response, m_Parent->GetSystemAddress());
|
||||
|
||||
LOG("Handled!");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CharacterComponent::LandingAnimDisabled(int zoneID) {
|
||||
@@ -55,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;
|
||||
@@ -81,6 +127,8 @@ CharacterComponent::~CharacterComponent() {
|
||||
void CharacterComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
|
||||
|
||||
if (bIsInitialUpdate) {
|
||||
if (!m_Character || !m_Character->GetParentUser()) return;
|
||||
|
||||
outBitStream.Write(m_ClaimCodes[0] != 0);
|
||||
if (m_ClaimCodes[0] != 0) outBitStream.Write(m_ClaimCodes[0]);
|
||||
outBitStream.Write(m_ClaimCodes[1] != 0);
|
||||
@@ -100,7 +148,7 @@ void CharacterComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInit
|
||||
outBitStream.Write(m_Character->GetEyebrows());
|
||||
outBitStream.Write(m_Character->GetEyes());
|
||||
outBitStream.Write(m_Character->GetMouth());
|
||||
outBitStream.Write<uint64_t>(0); //AccountID, trying out if 0 works.
|
||||
outBitStream.Write<uint64_t>(m_Character->GetParentUser()->GetAccountID());
|
||||
outBitStream.Write(m_Character->GetLastLogin()); //Last login
|
||||
outBitStream.Write<uint64_t>(0); //"prop mod last display time"
|
||||
outBitStream.Write<uint64_t>(m_Uscore); //u-score
|
||||
@@ -786,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -323,6 +323,8 @@ public:
|
||||
Character* m_Character;
|
||||
private:
|
||||
|
||||
bool OnRequestServerObjectInfo(GameMessages::GameMsg& msg);
|
||||
|
||||
/**
|
||||
* The map of active venture vision effects
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,10 @@ namespace RakNet {
|
||||
class BitStream;
|
||||
}
|
||||
|
||||
namespace GameMessages {
|
||||
struct GameMsg;
|
||||
}
|
||||
|
||||
class Entity;
|
||||
|
||||
/**
|
||||
@@ -52,6 +56,10 @@ public:
|
||||
|
||||
protected:
|
||||
|
||||
void RegisterMsg(const MessageType::Game msgId, auto* self, const auto handler) {
|
||||
m_Parent->RegisterMsg(msgId, std::bind(handler, self, std::placeholders::_1));
|
||||
}
|
||||
|
||||
/**
|
||||
* The entity that owns this component
|
||||
*/
|
||||
|
||||
@@ -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'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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -84,11 +84,12 @@ dpEntity* PhysicsComponent::CreatePhysicsEntity(eReplicaComponentType type) {
|
||||
} else if (info->physicsAsset == "env\\env_won_fv_gas-blocking-volume.hkx") {
|
||||
toReturn = new dpEntity(m_Parent->GetObjectID(), 390.496826f, 111.467964f, 600.821534f, true);
|
||||
m_Position.y -= (111.467964f * m_Parent->GetDefaultScale()) / 2;
|
||||
} else if (info->physicsAsset == "env\\GFTrack_DeathVolume1_CaveExit.hkx") {
|
||||
// Leaving these out for now since they cause more issues than they solve in racing tracks without proper OBB checks.
|
||||
} /* else if (info->physicsAsset == "env\\GFTrack_DeathVolume1_CaveExit.hkx") {
|
||||
toReturn = new dpEntity(m_Parent->GetObjectID(), 112.416870f, 50.363434f, 87.679268f);
|
||||
} else if (info->physicsAsset == "env\\GFTrack_DeathVolume2_RoadGaps.hkx") {
|
||||
toReturn = new dpEntity(m_Parent->GetObjectID(), 48.386536f, 50.363434f, 259.361755f);
|
||||
} else {
|
||||
} */ else {
|
||||
// LOG_DEBUG("This one is supposed to have %s", info->physicsAsset.c_str());
|
||||
|
||||
//add fallback cube:
|
||||
|
||||
@@ -165,7 +165,9 @@ void PropertyManagementComponent::UpdatePropertyDetails(std::string name, std::s
|
||||
info.id = propertyId;
|
||||
info.name = propertyName;
|
||||
info.description = propertyDescription;
|
||||
|
||||
info.lastUpdatedTime = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
|
||||
Database::Get()->UpdateLastSave(info);
|
||||
Database::Get()->UpdatePropertyDetails(info);
|
||||
|
||||
OnQueryPropertyData(GetOwner(), UNASSIGNED_SYSTEM_ADDRESS);
|
||||
@@ -688,6 +690,10 @@ void PropertyManagementComponent::Save() {
|
||||
|
||||
Database::Get()->RemoveModel(model.id);
|
||||
}
|
||||
IProperty::Info info;
|
||||
info.id = propertyId;
|
||||
info.lastUpdatedTime = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
Database::Get()->UpdateLastSave(info);
|
||||
}
|
||||
|
||||
void PropertyManagementComponent::AddModel(LWOOBJID modelId, LWOOBJID spawnerId) {
|
||||
|
||||
@@ -65,14 +65,7 @@ void QuickBuildComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIni
|
||||
|
||||
outBitStream.Write(false);
|
||||
}
|
||||
// If build state is completed and we've already serialized once in the completed state,
|
||||
// don't serializing this component anymore as this will cause the build to jump again.
|
||||
// If state changes, serialization will begin again.
|
||||
if (!m_StateDirty && m_State == eQuickBuildState::COMPLETED) {
|
||||
outBitStream.Write0();
|
||||
outBitStream.Write0();
|
||||
return;
|
||||
}
|
||||
|
||||
// BEGIN Scripted Activity
|
||||
outBitStream.Write1();
|
||||
|
||||
@@ -90,36 +83,27 @@ void QuickBuildComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIni
|
||||
}
|
||||
// END Scripted Activity
|
||||
|
||||
outBitStream.Write1();
|
||||
outBitStream.Write(m_StateDirty || bIsInitialUpdate);
|
||||
if (m_StateDirty || bIsInitialUpdate) {
|
||||
outBitStream.Write(m_State);
|
||||
|
||||
outBitStream.Write(m_State);
|
||||
outBitStream.Write(m_ShowResetEffect);
|
||||
outBitStream.Write(m_Activator != nullptr);
|
||||
|
||||
outBitStream.Write(m_ShowResetEffect);
|
||||
outBitStream.Write(m_Activator != nullptr);
|
||||
outBitStream.Write(m_Timer);
|
||||
outBitStream.Write(m_TimerIncomplete);
|
||||
|
||||
outBitStream.Write(m_Timer);
|
||||
outBitStream.Write(m_TimerIncomplete);
|
||||
|
||||
if (bIsInitialUpdate) {
|
||||
outBitStream.Write(false);
|
||||
outBitStream.Write(m_ActivatorPosition);
|
||||
outBitStream.Write(m_RepositionPlayer);
|
||||
if (bIsInitialUpdate) {
|
||||
outBitStream.Write(false); // IsChoiceBuild
|
||||
outBitStream.Write(m_ActivatorPosition);
|
||||
outBitStream.Write(m_RepositionPlayer);
|
||||
}
|
||||
m_StateDirty = false;
|
||||
}
|
||||
m_StateDirty = false;
|
||||
}
|
||||
|
||||
void QuickBuildComponent::Update(float deltaTime) {
|
||||
m_Activator = GetActivator();
|
||||
|
||||
// Serialize the quickbuild every so often, fixes the odd bug where the quickbuild is not buildable
|
||||
/*if (m_SoftTimer > 0.0f) {
|
||||
m_SoftTimer -= deltaTime;
|
||||
}
|
||||
else {
|
||||
m_SoftTimer = 5.0f;
|
||||
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
}*/
|
||||
SetActivator(GetActivator());
|
||||
|
||||
switch (m_State) {
|
||||
case eQuickBuildState::OPEN: {
|
||||
@@ -130,12 +114,12 @@ void QuickBuildComponent::Update(float deltaTime) {
|
||||
const bool isSmashGroup = spawner != nullptr ? spawner->GetIsSpawnSmashGroup() : false;
|
||||
|
||||
if (isSmashGroup) {
|
||||
m_TimerIncomplete += deltaTime;
|
||||
ModifyIncompleteTimer(deltaTime);
|
||||
|
||||
// For reset times < 0 this has to be handled manually
|
||||
if (m_TimeBeforeSmash > 0) {
|
||||
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f) {
|
||||
m_ShowResetEffect = true;
|
||||
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f && !m_ShowResetEffect) {
|
||||
SetShowResetEffect(true);
|
||||
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
}
|
||||
@@ -153,21 +137,19 @@ void QuickBuildComponent::Update(float deltaTime) {
|
||||
break;
|
||||
}
|
||||
case eQuickBuildState::COMPLETED: {
|
||||
m_Timer += deltaTime;
|
||||
ModifyTimer(deltaTime);
|
||||
|
||||
// For reset times < 0 this has to be handled manually
|
||||
if (m_ResetTime > 0) {
|
||||
if (m_Timer >= m_ResetTime - 4.0f) {
|
||||
if (!m_ShowResetEffect) {
|
||||
m_ShowResetEffect = true;
|
||||
if (m_Timer >= m_ResetTime - 4.0f && !m_ShowResetEffect) {
|
||||
SetShowResetEffect(true);
|
||||
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
}
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
}
|
||||
|
||||
if (m_Timer >= m_ResetTime) {
|
||||
|
||||
GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 0.0f, false, true);
|
||||
GameMessages::SendDieNoImplCode(m_Parent, LWOOBJID_EMPTY, LWOOBJID_EMPTY, eKillType::VIOLENT, u"", 0.0f, 0.0f, 7.0f, false, true);
|
||||
|
||||
ResetQuickBuild(false);
|
||||
}
|
||||
@@ -185,9 +167,9 @@ void QuickBuildComponent::Update(float deltaTime) {
|
||||
}
|
||||
|
||||
m_TimeBeforeDrain -= deltaTime;
|
||||
m_Timer += deltaTime;
|
||||
m_TimerIncomplete = 0;
|
||||
m_ShowResetEffect = false;
|
||||
ModifyTimer(deltaTime);
|
||||
SetIncompleteTimer(0.0f);
|
||||
SetShowResetEffect(false);
|
||||
|
||||
if (m_TimeBeforeDrain <= 0.0f) {
|
||||
m_TimeBeforeDrain = m_CompleteTime / static_cast<float>(m_TakeImagination);
|
||||
@@ -215,12 +197,12 @@ void QuickBuildComponent::Update(float deltaTime) {
|
||||
break;
|
||||
}
|
||||
case eQuickBuildState::INCOMPLETE: {
|
||||
m_TimerIncomplete += deltaTime;
|
||||
ModifyIncompleteTimer(deltaTime);
|
||||
|
||||
// For reset times < 0 this has to be handled manually
|
||||
if (m_TimeBeforeSmash > 0) {
|
||||
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f) {
|
||||
m_ShowResetEffect = true;
|
||||
if (m_TimerIncomplete >= m_TimeBeforeSmash - 4.0f && !m_ShowResetEffect) {
|
||||
SetShowResetEffect(true);
|
||||
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
}
|
||||
@@ -260,7 +242,7 @@ void QuickBuildComponent::SpawnActivator() {
|
||||
info.spawnerID = m_Parent->GetObjectID();
|
||||
info.pos = m_ActivatorPosition == NiPoint3Constant::ZERO ? m_Parent->GetPosition() : m_ActivatorPosition;
|
||||
|
||||
m_Activator = Game::entityManager->CreateEntity(info, nullptr, m_Parent);
|
||||
SetActivator(Game::entityManager->CreateEntity(info, nullptr, m_Parent));
|
||||
if (m_Activator) {
|
||||
m_ActivatorId = m_Activator->GetObjectID();
|
||||
Game::entityManager->ConstructEntity(m_Activator);
|
||||
@@ -277,7 +259,7 @@ void QuickBuildComponent::DespawnActivator() {
|
||||
|
||||
m_Activator->ScheduleKillAfterUpdate();
|
||||
|
||||
m_Activator = nullptr;
|
||||
SetActivator(nullptr);
|
||||
|
||||
m_ActivatorId = LWOOBJID_EMPTY;
|
||||
}
|
||||
@@ -405,8 +387,7 @@ void QuickBuildComponent::StartQuickBuild(Entity* const user) {
|
||||
GameMessages::SendQuickBuildNotifyState(m_Parent, m_State, eQuickBuildState::BUILDING, user->GetObjectID());
|
||||
GameMessages::SendEnableQuickBuild(m_Parent, true, false, false, eQuickBuildFailReason::NOT_GIVEN, 0.0f, user->GetObjectID());
|
||||
|
||||
m_State = eQuickBuildState::BUILDING;
|
||||
m_StateDirty = true;
|
||||
SetState(eQuickBuildState::BUILDING);
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
|
||||
auto* movingPlatform = m_Parent->GetComponent<MovingPlatformComponent>();
|
||||
@@ -444,9 +425,8 @@ void QuickBuildComponent::CompleteQuickBuild(Entity* const user) {
|
||||
GameMessages::SendTerminateInteraction(user->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
|
||||
|
||||
|
||||
m_State = eQuickBuildState::COMPLETED;
|
||||
m_StateDirty = true;
|
||||
m_Timer = 0.0f;
|
||||
SetState(eQuickBuildState::COMPLETED);
|
||||
SetTimer(0.0f);
|
||||
m_DrainedImagination = 0;
|
||||
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
@@ -526,11 +506,10 @@ void QuickBuildComponent::ResetQuickBuild(const bool failed) {
|
||||
|
||||
GameMessages::SendQuickBuildNotifyState(m_Parent, m_State, eQuickBuildState::RESETTING, LWOOBJID_EMPTY);
|
||||
|
||||
m_State = eQuickBuildState::RESETTING;
|
||||
m_StateDirty = true;
|
||||
m_Timer = 0.0f;
|
||||
m_TimerIncomplete = 0.0f;
|
||||
m_ShowResetEffect = false;
|
||||
SetState(eQuickBuildState::RESETTING);
|
||||
SetTimer(0.0f);
|
||||
SetIncompleteTimer(0.0f);
|
||||
SetShowResetEffect(false);
|
||||
m_DrainedImagination = 0;
|
||||
|
||||
Game::entityManager->SerializeEntity(m_Parent);
|
||||
@@ -563,8 +542,7 @@ void QuickBuildComponent::CancelQuickBuild(Entity* const entity, const eQuickBui
|
||||
GameMessages::SendTerminateInteraction(m_Parent->GetObjectID(), eTerminateType::FROM_INTERACTION, m_Parent->GetObjectID());
|
||||
|
||||
// Now update the component itself
|
||||
m_State = eQuickBuildState::INCOMPLETE;
|
||||
m_StateDirty = true;
|
||||
SetState(eQuickBuildState::INCOMPLETE);
|
||||
|
||||
// Notify scripts and possible subscribers
|
||||
m_Parent->GetScript()->OnQuickBuildNotifyState(m_Parent, m_State);
|
||||
|
||||
@@ -218,6 +218,48 @@ public:
|
||||
* @param skipChecks whether or not to skip the check for the quickbuild not being completed
|
||||
*/
|
||||
void CancelQuickBuild(Entity* const builder, const eQuickBuildFailReason failReason, const bool skipChecks = false);
|
||||
|
||||
void SetState(const eQuickBuildState state) {
|
||||
if (m_State == state) return;
|
||||
m_State = state;
|
||||
m_StateDirty = true;
|
||||
}
|
||||
|
||||
void SetShowResetEffect(const bool value) {
|
||||
if (m_ShowResetEffect == value) return;
|
||||
m_ShowResetEffect = value;
|
||||
m_StateDirty = true;
|
||||
}
|
||||
|
||||
void SetActivator(Entity* const activator) {
|
||||
if (m_Activator == activator) return;
|
||||
m_Activator = activator;
|
||||
m_StateDirty = true;
|
||||
}
|
||||
|
||||
void SetTimer(const float value) {
|
||||
if (m_Timer == value) return;
|
||||
m_Timer = value;
|
||||
m_StateDirty = true;
|
||||
}
|
||||
|
||||
void ModifyTimer(const float value) {
|
||||
if (value == 0.0f) return;
|
||||
m_Timer += value;
|
||||
m_StateDirty = true;
|
||||
}
|
||||
|
||||
void SetIncompleteTimer(const float value) {
|
||||
if (m_TimerIncomplete == value) return;
|
||||
m_TimerIncomplete = value;
|
||||
m_StateDirty = true;
|
||||
}
|
||||
|
||||
void ModifyIncompleteTimer(const float value) {
|
||||
if (value == 0.0f) return;
|
||||
m_TimerIncomplete += value;
|
||||
m_StateDirty = true;
|
||||
}
|
||||
private:
|
||||
/**
|
||||
* Whether or not the quickbuild state has been changed since we last serialized it.
|
||||
|
||||
@@ -37,8 +37,19 @@
|
||||
#include "ePlayerFlag.h"
|
||||
#include "dConfig.h"
|
||||
#include "GhostComponent.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
#include "StringifiedEnum.h"
|
||||
|
||||
namespace {
|
||||
using enum MessageType::Game;
|
||||
using namespace GameMessages;
|
||||
using MessageCreator = std::function<std::unique_ptr<GameMessages::GameMsg>()>;
|
||||
std::map<MessageType::Game, MessageCreator> g_MessageHandlers = {
|
||||
{ REQUEST_SERVER_OBJECT_INFO, []() { return std::make_unique<RequestServerObjectInfo>(); } },
|
||||
{ SHOOTING_GALLERY_FIRE, []() { return std::make_unique<ShootingGalleryFire>(); } },
|
||||
};
|
||||
};
|
||||
|
||||
void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const SystemAddress& sysAddr, LWOOBJID objectID, MessageType::Game messageID) {
|
||||
|
||||
CBITSTREAM;
|
||||
@@ -55,6 +66,24 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
||||
|
||||
if (messageID != MessageType::Game::READY_FOR_UPDATES) LOG_DEBUG("Received GM with ID and name: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
|
||||
|
||||
auto handler = g_MessageHandlers.find(messageID);
|
||||
if (handler != g_MessageHandlers.end()) {
|
||||
auto msg = handler->second();
|
||||
|
||||
// Verify that the system address user is able to use this message.
|
||||
if (msg->requiredGmLevel > eGameMasterLevel::CIVILIAN) {
|
||||
auto* usingEntity = Game::entityManager->GetEntity(usr->GetLoggedInChar());
|
||||
if (!usingEntity || usingEntity->GetGMLevel() < msg->requiredGmLevel) {
|
||||
LOG("User %s (%llu) does not have the required GM level to execute this command.", usingEntity->GetCharacter()->GetName().c_str(), usingEntity->GetObjectID());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
msg->Deserialize(inStream);
|
||||
msg->Handle(*entity, sysAddr);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (messageID) {
|
||||
|
||||
case MessageType::Game::UN_USE_BBB_MODEL: {
|
||||
@@ -104,19 +133,20 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
||||
break;
|
||||
}
|
||||
|
||||
// Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client.
|
||||
// Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client.
|
||||
case MessageType::Game::SELECT_SKILL: {
|
||||
auto var = entity->GetVar<bool>(u"dlu_first_time_load");
|
||||
if (var) {
|
||||
entity->SetVar<bool>(u"dlu_first_time_load", false);
|
||||
InventoryComponent* inventoryComponent = entity->GetComponent<InventoryComponent>();
|
||||
|
||||
|
||||
if (inventoryComponent) inventoryComponent->FixInvisibleItems();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case MessageType::Game::PLAYER_LOADED: {
|
||||
GameMessages::SendPlayerReady(entity, sysAddr);
|
||||
entity->SetPlayerReadyForUpdates();
|
||||
|
||||
auto* ghostComponent = entity->GetComponent<GhostComponent>();
|
||||
@@ -183,7 +213,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
||||
LOG("Player %s (%llu) loaded.", entity->GetCharacter()->GetName().c_str(), entity->GetObjectID());
|
||||
|
||||
// After we've done our thing, tell the client they're ready
|
||||
GameMessages::SendPlayerReady(entity, sysAddr);
|
||||
GameMessages::SendPlayerReady(Game::zoneManager->GetZoneControlObject(), sysAddr);
|
||||
|
||||
if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1"
|
||||
@@ -704,12 +733,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
|
||||
case MessageType::Game::UPDATE_INVENTORY_GROUP_CONTENTS:
|
||||
GameMessages::HandleUpdateInventoryGroupContents(inStream, entity, sysAddr);
|
||||
break;
|
||||
case MessageType::Game::SHOOTING_GALLERY_FIRE: {
|
||||
GameMessages::ShootingGalleryFire fire{};
|
||||
fire.Deserialize(inStream);
|
||||
fire.Handle(*entity, sysAddr);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
LOG_DEBUG("Received Unknown GM with ID: %4i, %s", messageID, StringifiedEnum::ToString(messageID).data());
|
||||
|
||||
@@ -48,8 +48,6 @@
|
||||
#include <chrono>
|
||||
#include "RakString.h"
|
||||
|
||||
#include "httplib.h" //sorry not sorry.
|
||||
|
||||
//CDB includes:
|
||||
#include "CDClientManager.h"
|
||||
#include "CDEmoteTable.h"
|
||||
@@ -845,8 +843,10 @@ void GameMessages::SendDieNoImplCode(Entity* entity, const LWOOBJID& killerID, c
|
||||
|
||||
bitStream.Write(entity->GetObjectID());
|
||||
bitStream.Write(MessageType::Game::DIE);
|
||||
|
||||
bitStream.Write(bClientDeath);
|
||||
bitStream.Write(bSpawnLoot);
|
||||
bitStream.Write<uint32_t>(deathType.size());
|
||||
bitStream.Write(deathType);
|
||||
bitStream.Write(directionRelative_AngleXZ);
|
||||
bitStream.Write(directionRelative_AngleY);
|
||||
@@ -856,7 +856,10 @@ void GameMessages::SendDieNoImplCode(Entity* entity, const LWOOBJID& killerID, c
|
||||
if (killType != eKillType::VIOLENT) bitStream.Write(killType);
|
||||
|
||||
bitStream.Write(killerID);
|
||||
bitStream.Write(lootOwnerID);
|
||||
bitStream.Write(lootOwnerID != LWOOBJID_EMPTY);
|
||||
if (lootOwnerID != LWOOBJID_EMPTY) {
|
||||
bitStream.Write(lootOwnerID);
|
||||
}
|
||||
|
||||
SEND_PACKET_BROADCAST;
|
||||
}
|
||||
@@ -968,6 +971,8 @@ void GameMessages::SendResurrect(Entity* entity) {
|
||||
// and just make sure the client has time to be ready.
|
||||
constexpr float respawnTime = 3.66700005531311f + 0.5f;
|
||||
entity->AddCallbackTimer(respawnTime, [=]() {
|
||||
GameMessages::PlayerResurrectionFinished msg;
|
||||
entity->NotifyPlayerResurrectionFinished(msg);
|
||||
auto* destroyableComponent = entity->GetComponent<DestroyableComponent>();
|
||||
|
||||
if (destroyableComponent != nullptr && entity->GetLOT() == 1) {
|
||||
@@ -6427,4 +6432,16 @@ namespace GameMessages {
|
||||
void ShootingGalleryFire::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
entity.OnShootingGalleryFire(*this);
|
||||
}
|
||||
|
||||
bool RequestServerObjectInfo::Deserialize(RakNet::BitStream& bitStream) {
|
||||
if (!bitStream.Read(bVerbose)) return false;
|
||||
if (!bitStream.Read(clientId)) return false;
|
||||
if (!bitStream.Read(targetForReport)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RequestServerObjectInfo::Handle(Entity& entity, const SystemAddress& sysAddr) {
|
||||
auto* handlingEntity = Game::entityManager->GetEntity(targetForReport);
|
||||
if (handlingEntity) handlingEntity->HandleMsg(*this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "eLootSourceType.h"
|
||||
#include "Brick.h"
|
||||
#include "MessageType/Game.h"
|
||||
#include "eGameMasterLevel.h"
|
||||
|
||||
class AMFBaseValue;
|
||||
class Entity;
|
||||
@@ -50,7 +51,8 @@ enum class eCameraTargetCyclingMode : int32_t {
|
||||
|
||||
namespace GameMessages {
|
||||
struct GameMsg {
|
||||
GameMsg(MessageType::Game gmId) : msgId{ gmId } {}
|
||||
GameMsg(MessageType::Game gmId, eGameMasterLevel lvl) : msgId{ gmId }, requiredGmLevel{ lvl } {}
|
||||
GameMsg(MessageType::Game gmId) : GameMsg(gmId, eGameMasterLevel::CIVILIAN) {}
|
||||
virtual ~GameMsg() = default;
|
||||
void Send(const SystemAddress& sysAddr) const;
|
||||
virtual void Serialize(RakNet::BitStream& bitStream) const {}
|
||||
@@ -58,6 +60,7 @@ namespace GameMessages {
|
||||
virtual void Handle(Entity& entity, const SystemAddress& sysAddr) {};
|
||||
MessageType::Game msgId;
|
||||
LWOOBJID target{ LWOOBJID_EMPTY };
|
||||
eGameMasterLevel requiredGmLevel;
|
||||
};
|
||||
|
||||
class PropertyDataMessage;
|
||||
@@ -765,6 +768,20 @@ namespace GameMessages {
|
||||
LOT templateID{};
|
||||
LWOOBJID childID{};
|
||||
};
|
||||
|
||||
struct PlayerResurrectionFinished : public GameMsg {
|
||||
PlayerResurrectionFinished() : GameMsg(MessageType::Game::PLAYER_RESURRECTION_FINISHED) {}
|
||||
};
|
||||
|
||||
struct RequestServerObjectInfo : public GameMsg {
|
||||
bool bVerbose{};
|
||||
LWOOBJID clientId{};
|
||||
LWOOBJID targetForReport{};
|
||||
|
||||
RequestServerObjectInfo() : GameMsg(MessageType::Game::REQUEST_SERVER_OBJECT_INFO, eGameMasterLevel::DEVELOPER) {}
|
||||
bool Deserialize(RakNet::BitStream& bitStream) override;
|
||||
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
|
||||
};
|
||||
};
|
||||
|
||||
#endif // GAMEMESSAGES_H
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include "Character.h"
|
||||
|
||||
#include "CDMissionEmailTable.h"
|
||||
#include "ChatPackets.h"
|
||||
#include "PlayerManager.h"
|
||||
|
||||
Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) {
|
||||
m_MissionComponent = missionComponent;
|
||||
@@ -65,7 +67,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 +78,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 +138,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 +147,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 ||
|
||||
@@ -345,12 +357,25 @@ void Mission::Complete(const bool yieldRewards) {
|
||||
for (const auto& email : missionEmails) {
|
||||
const auto missionEmailBase = "MissionEmail_" + std::to_string(email.ID) + "_";
|
||||
|
||||
if (email.messageType == 1) {
|
||||
if (email.messageType == 1 /* Send an email to the player */) {
|
||||
const auto subject = "%[" + missionEmailBase + "subjectText]";
|
||||
const auto body = "%[" + missionEmailBase + "bodyText]";
|
||||
const auto sender = "%[" + missionEmailBase + "senderName]";
|
||||
|
||||
Mail::SendMail(LWOOBJID_EMPTY, sender, GetAssociate(), subject, body, email.attachmentLOT, 1);
|
||||
} else if (email.messageType == 2 /* Send an announcement in chat */) {
|
||||
auto* character = entity->GetCharacter();
|
||||
|
||||
ChatPackets::AchievementNotify notify{};
|
||||
notify.missionEmailID = email.ID;
|
||||
notify.earningPlayerID = entity->GetObjectID();
|
||||
notify.earnerName.string = character ? GeneralUtils::ASCIIToUTF16(character->GetName()) : u"";
|
||||
|
||||
// Manual write since it's sent to chat server and not a game client
|
||||
RakNet::BitStream bitstream;
|
||||
notify.WriteHeader(bitstream);
|
||||
notify.Serialize(bitstream);
|
||||
Game::chatServer->Send(&bitstream, HIGH_PRIORITY, RELIABLE, 0, Game::chatSysAddr, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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__
|
||||
|
||||
@@ -777,7 +777,7 @@ void SlashCommandHandler::Startup() {
|
||||
.info = "Crashes the server",
|
||||
.aliases = { "crash", "pumpkin" },
|
||||
.handle = DEVGMCommands::Crash,
|
||||
.requiredLevel = eGameMasterLevel::DEVELOPER
|
||||
.requiredLevel = eGameMasterLevel::OPERATOR
|
||||
};
|
||||
RegisterCommand(CrashCommand);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -1444,4 +1444,13 @@ void SlashCommandHandler::Startup() {
|
||||
.requiredLevel = eGameMasterLevel::CIVILIAN
|
||||
};
|
||||
RegisterCommand(removeIgnoreCommand);
|
||||
|
||||
Command shutdownCommand{
|
||||
.help = "Shuts this world down",
|
||||
.info = "Shuts this world down",
|
||||
.aliases = {"shutdown"},
|
||||
.handle = DEVGMCommands::Shutdown,
|
||||
.requiredLevel = eGameMasterLevel::DEVELOPER
|
||||
};
|
||||
RegisterCommand(shutdownCommand);
|
||||
}
|
||||
|
||||
@@ -1622,4 +1622,10 @@ namespace DEVGMCommands {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Shutdown(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
|
||||
auto* character = entity->GetCharacter();
|
||||
if (character) LOG("Mythran (%s) has shutdown the world", character->GetName().c_str());
|
||||
Game::OnSignal(-1);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -73,6 +73,7 @@ namespace DEVGMCommands {
|
||||
void RollLoot(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void CastSkill(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void DeleteInven(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
void Shutdown(Entity* entity, const SystemAddress& sysAddr, const std::string args);
|
||||
}
|
||||
|
||||
#endif //!DEVGMCOMMANDS_H
|
||||
|
||||
@@ -102,7 +102,7 @@ namespace GMGreaterThanZeroCommands {
|
||||
return;
|
||||
}
|
||||
|
||||
IMail::MailInfo mailInsert;
|
||||
MailInfo mailInsert;
|
||||
mailInsert.senderId = entity->GetObjectID();
|
||||
mailInsert.senderUsername = "Darkflame Universe";
|
||||
mailInsert.receiverId = receiverID;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user