Add FlagComponent and msg handlers

This commit is contained in:
David Markowitz
2025-01-20 02:53:21 -08:00
parent e4c2eecbc7
commit af2ba5b287
53 changed files with 781 additions and 486 deletions

View File

@@ -22,6 +22,7 @@
#include "eGameMasterLevel.h"
#include "ePlayerFlag.h"
#include "CDPlayerFlagsTable.h"
#include "FlagComponent.h"
Character::Character(uint32_t id, User* parentUser) {
//First load the name, etc:
@@ -37,7 +38,7 @@ Character::~Character() {
m_ParentUser = nullptr;
}
void Character::UpdateInfoFromDatabase() {
void Character::UpdateInfoFromDatabase(bool clearSessionFlags) {
auto charInfo = Database::Get()->GetCharacterInfo(m_ID);
if (charInfo) {
@@ -65,10 +66,17 @@ void Character::UpdateInfoFromDatabase() {
m_OurEntity = nullptr;
m_BuildMode = false;
// This is not done through a game message because our current implementation does this at a point
// in time where an Entity does not exist, so there is no FlagComponent to handle the msg.
if (clearSessionFlags) {
FlagComponent::ClearSessionFlags(m_Doc);
WriteToDatabase();
}
}
void Character::UpdateFromDatabase() {
UpdateInfoFromDatabase();
void Character::UpdateFromDatabase(bool clearSessionFlags) {
UpdateInfoFromDatabase(clearSessionFlags);
}
void Character::DoQuickXMLDataParse() {
@@ -197,25 +205,6 @@ void Character::DoQuickXMLDataParse() {
character->QueryAttribute("lzrz", &m_OriginalRotation.z);
character->QueryAttribute("lzrw", &m_OriginalRotation.w);
}
auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag");
if (flags) {
auto* currentChild = flags->FirstChildElement();
while (currentChild) {
const auto* temp = currentChild->Attribute("v");
const auto* id = currentChild->Attribute("id");
if (temp && id) {
uint32_t index = 0;
uint64_t value = 0;
index = std::stoul(id);
value = std::stoull(temp);
m_PlayerFlags.insert(std::make_pair(index, value));
}
currentChild = currentChild->NextSiblingElement();
}
}
}
void Character::UnlockEmote(int emoteID) {
@@ -276,25 +265,6 @@ void Character::SaveXMLToDatabase() {
character->LinkEndChild(emotes);
}
//Export our flags:
auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag");
if (!flags) {
flags = m_Doc.NewElement("flag"); //Create a flags tag if we don't have one
m_Doc.FirstChildElement("obj")->LinkEndChild(flags); //Link it to the obj tag so we can find next time
}
flags->DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes
for (const auto& [index, flagBucket] : m_PlayerFlags) {
auto* f = flags->InsertNewChildElement("f");
f->SetAttribute("id", index);
f->SetAttribute("v", flagBucket);
}
for (const auto& sessionFlag : m_SessionFlags) {
auto* s = flags->InsertNewChildElement("s");
s->SetAttribute("si", sessionFlag);
}
SaveXmlRespawnCheckpoints();
m_OurEntity->UpdateXMLDoc(m_Doc);
@@ -307,23 +277,6 @@ void Character::SaveXMLToDatabase() {
LOG("%i:%s Saved character to Database in: %fs", this->GetID(), this->GetName().c_str(), elapsed.count());
}
void Character::SetIsNewLogin() {
// If we dont have a flag element, then we cannot have a s element as a child of flag.
auto* flags = m_Doc.FirstChildElement("obj")->FirstChildElement("flag");
if (!flags) return;
auto* currentChild = flags->FirstChildElement();
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);
WriteToDatabase();
}
currentChild = nextChild;
}
}
void Character::WriteToDatabase() {
//Dump our xml into m_XMLData:
tinyxml2::XMLPrinter printer(0, true, 0);
@@ -333,90 +286,6 @@ void Character::WriteToDatabase() {
Database::Get()->UpdateCharacterXml(m_ID, printer.CStr());
}
void Character::SetPlayerFlag(const uint32_t flagId, const bool value) {
// If the flag is already set, we don't have to recalculate it
if (GetPlayerFlag(flagId) == value) return;
if (value) {
// Update the mission component:
auto* player = Game::entityManager->GetEntity(m_ObjectID);
if (player != nullptr) {
auto* missionComponent = player->GetComponent<MissionComponent>();
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::PLAYER_FLAG, flagId);
}
}
}
const auto flagEntry = CDPlayerFlagsTable::GetEntry(flagId);
if (flagEntry && flagEntry->sessionOnly) {
if (value) m_SessionFlags.insert(flagId);
else m_SessionFlags.erase(flagId);
} else {
// Calculate the index first
auto flagIndex = uint32_t(std::floor(flagId / 64));
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;
}
} 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 {
using enum ePlayerFlag;
bool toReturn = false; //by def, return false.
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 toReturn;
}
void Character::SetRetroactiveFlags() {
// Retroactive check for if player has joined a faction to set their 'joined a faction' flag to true.
if (GetPlayerFlag(ePlayerFlag::VENTURE_FACTION) || GetPlayerFlag(ePlayerFlag::ASSEMBLY_FACTION) || GetPlayerFlag(ePlayerFlag::PARADOX_FACTION) || GetPlayerFlag(ePlayerFlag::SENTINEL_FACTION)) {
SetPlayerFlag(ePlayerFlag::JOINED_A_FACTION, true);
}
}
void Character::SaveXmlRespawnCheckpoints() {
//Export our respawn points:
auto* points = m_Doc.FirstChildElement("obj")->FirstChildElement("res");
@@ -473,7 +342,11 @@ void Character::OnZoneLoad() {
if (missionComponent != nullptr) {
// Fix the monument race flag
if (missionComponent->GetMissionState(319) >= eMissionState::READY_TO_COMPLETE) {
SetPlayerFlag(ePlayerFlag::AG_FINISH_LINE_BUILT, true);
GameMessages::SetFlag setFlag{};
setFlag.target = m_ObjectID;
setFlag.iFlagId = ePlayerFlag::AG_FINISH_LINE_BUILT;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
}

View File

@@ -31,7 +31,7 @@ public:
*/
void WriteToDatabase();
void SaveXMLToDatabase();
void UpdateFromDatabase();
void UpdateFromDatabase(bool clearSessionFlags = false);
void SaveXmlRespawnCheckpoints();
void LoadXmlRespawnCheckpoints();
@@ -40,15 +40,6 @@ public:
const tinyxml2::XMLDocument& GetXMLDoc() const { return m_Doc; }
void _setXmlDoc(tinyxml2::XMLDocument& doc) { doc.DeepCopy(&m_Doc); }
/**
* Out of abundance of safety and clarity of what this saves, this is its own function.
*
* Clears the s element from the flag element and saves the xml to the database. Used to prevent the news
* feed from showing up on world transfers.
*
*/
void SetIsNewLogin();
/**
* Gets the database ID of the character
* @return the database ID of the character
@@ -410,32 +401,11 @@ public:
*/
void UnlockEmote(int emoteID);
/**
* Sets a flag for the character, indicating certain parts of the game that have been interacted with. Not to be
* confused with the permissions
* @param flagId the ID of the flag to set
* @param value the value to set for the flag
*/
void SetPlayerFlag(uint32_t flagId, bool value);
/**
* Gets the value for a certain character flag
* @param flagId the ID of the flag to get a value for
* @return the value of the flag given the ID (the default is false, obviously)
*/
bool GetPlayerFlag(uint32_t flagId) const;
/**
* Notifies the character that they're now muted
*/
void SendMuteNotice() const;
/**
* Sets any flags that are meant to have been set that may not have been set due to them being
* missing in a previous patch.
*/
void SetRetroactiveFlags();
/**
* Get the equipped items for this character, only used for character creation
* @return the equipped items for this character on world load
@@ -465,7 +435,7 @@ public:
void _setXmlData(const std::string& xmlData) { m_XMLData = xmlData; }
private:
void UpdateInfoFromDatabase();
void UpdateInfoFromDatabase(bool clearSessionFlags);
/**
* The ID of this character. First 32 bits of the ObjectID.
*/
@@ -620,17 +590,6 @@ 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)
*/
std::unordered_map<uint32_t, uint64_t> m_PlayerFlags;
/**
* The character XML belonging to this character
*/

View File

@@ -84,6 +84,7 @@
#include "GhostComponent.h"
#include "AchievementVendorComponent.h"
#include "VanityUtilities.h"
#include "FlagComponent.h"
// Table includes
#include "CDComponentsRegistryTable.h"
@@ -482,6 +483,8 @@ void Entity::Initialize() {
AddComponent<CharacterComponent>(m_Character, systemAddress)->LoadFromXml(m_Character->GetXMLDoc());
AddComponent<GhostComponent>();
AddComponent<FlagComponent>()->LoadFromXml(m_Character->GetXMLDoc());
}
if (compRegistryTable->GetByIDAndType(m_TemplateID, eReplicaComponentType::INVENTORY) > 0 || m_Character) {

View File

@@ -604,3 +604,10 @@ void EntityManager::FireEventServerSide(Entity* origin, std::string args) {
bool EntityManager::IsExcludedFromGhosting(LOT lot) {
return std::find(m_GhostingExcludedLOTs.begin(), m_GhostingExcludedLOTs.end(), lot) != m_GhostingExcludedLOTs.end();
}
bool EntityManager::SendMsg(GameMessages::GameMsg& msg) {
bool bRet = false;
auto* entity = GetEntity(msg.target);
if (entity) bRet = entity->HandleMsg(msg);
return bRet;
}

View File

@@ -8,6 +8,13 @@
#include "dCommonVars.h"
// Convenience macro to send a message to the entity manager
#define SEND_ENTITY_MSG(msg) Game::entityManager->SendMsg(msg)
namespace GameMessages {
struct GameMsg;
};
class Entity;
class EntityInfo;
class Player;
@@ -72,6 +79,9 @@ public:
const bool GetHardcoreDropinventoryOnDeath() { return m_HardcoreDropinventoryOnDeath; };
const uint32_t GetHardcoreUscoreEnemiesMultiplier() { return m_HardcoreUscoreEnemiesMultiplier; };
// Sends a message to be handled by the receiving entity
bool SendMsg(GameMessages::GameMsg& msg);
private:
void SerializeEntities();
void KillEntities();

View File

@@ -197,6 +197,10 @@ void UserManager::RequestCharacterList(const SystemAddress& sysAddr) {
skillComponent->Reset();
}
GameMessages::ClearSessionFlags msg{};
msg.target = chars[i]->GetObjectID();
SEND_ENTITY_MSG(msg);
Game::entityManager->DestroyEntity(chars[i]->GetEntity());
chars[i]->SaveXMLToDatabase();
@@ -210,8 +214,7 @@ void UserManager::RequestCharacterList(const SystemAddress& sysAddr) {
for (const auto& characterId : Database::Get()->GetAccountCharacterIds(u->GetAccountID())) {
Character* character = new Character(characterId, u);
character->UpdateFromDatabase();
character->SetIsNewLogin();
character->UpdateFromDatabase(true);
chars.push_back(character);
}

View File

@@ -10,6 +10,7 @@ set(DGAME_DCOMPONENTS_SOURCES
"ControllablePhysicsComponent.cpp"
"DestroyableComponent.cpp"
"DonationVendorComponent.cpp"
"FlagComponent.cpp"
"GhostComponent.cpp"
"InventoryComponent.cpp"
"ItemComponent.cpp"

View File

@@ -0,0 +1,199 @@
#include "FlagComponent.h"
#include "CDPlayerFlagsTable.h"
#include "eMissionTaskType.h"
#include "ePlayerFlag.h"
#include "MissionComponent.h"
FlagComponent::FlagComponent(Entity* parent) : Component(parent) {
RegisterMsg(MessageType::Game::SET_FLAG, this, &FlagComponent::OnSetFlag);
RegisterMsg(MessageType::Game::GET_FLAG, this, &FlagComponent::OnGetFlag);
RegisterMsg(MessageType::Game::CLEAR_SESSION_FLAGS, this, &FlagComponent::OnClearSessionFlags);
RegisterMsg(MessageType::Game::SET_RETROACTIVE_FLAGS, this, &FlagComponent::OnSetRetroactiveFlags);
}
bool FlagComponent::OnSetFlag(GameMessages::GameMsg& msg) {
auto& setFlag = static_cast<GameMessages::SetFlag&>(msg);
LOG("Set %i", setFlag.iFlagId);
SetPlayerFlag(setFlag.iFlagId, setFlag.bFlag);
// This is always set the first time a player loads into a world from character select
// and is used to know when to refresh the players inventory items so they show up.
if (setFlag.iFlagId == ePlayerFlag::IS_NEWS_SCREEN_VISIBLE && setFlag.bFlag) {
m_Parent->SetVar<bool>(u"dlu_first_time_load", true);
}
return true;
}
bool FlagComponent::OnGetFlag(GameMessages::GameMsg& msg) {
auto& getFlag = static_cast<GameMessages::GetFlag&>(msg);
LOG("Get %i", getFlag.iFlagId);
getFlag.bFlag = GetPlayerFlag(getFlag.iFlagId);
return true;
}
void FlagComponent::UpdateXml(tinyxml2::XMLDocument& doc) {
if (!doc.FirstChildElement("obj")) return;
auto& obj = *doc.FirstChildElement("obj");
if (!obj.FirstChildElement("flag")) {
obj.InsertNewChildElement("flag");
}
auto& flags = *obj.FirstChildElement("flag");
flags.DeleteChildren(); //Clear it if we have anything, so that we can fill it up again without dupes
// Save our flags
for (const auto& [index, flagBucket] : m_PlayerFlags) {
auto& f = *flags.InsertNewChildElement("f");
f.SetAttribute("id", index);
f.SetAttribute("v", flagBucket);
}
// Save our session flags
for (const auto& sessionFlag : m_SessionFlags) {
auto& s = *flags.InsertNewChildElement("s");
LOG("Saving session flag %i", sessionFlag);
s.SetAttribute("si", sessionFlag);
}
}
void FlagComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) {
if (!doc.FirstChildElement("obj")) return;
auto& obj = *doc.FirstChildElement("obj");
if (!obj.FirstChildElement("flag")) return;
auto& flags = *obj.FirstChildElement("flag");
const auto* currentChild = flags.FirstChildElement("f");
while (currentChild) {
const auto* temp = currentChild->Attribute("v");
const auto* id = currentChild->Attribute("id");
if (temp && id) {
uint32_t index = 0;
uint64_t value = 0;
index = std::stoul(id);
value = std::stoull(temp);
m_PlayerFlags.insert(std::make_pair(index, value));
}
currentChild = currentChild->NextSiblingElement("f");
}
// Now load our session flags
currentChild = flags.FirstChildElement("s");
while (currentChild) {
const auto* temp = currentChild->Attribute("si");
if (temp) {
uint32_t sessionIndex = 0;
sessionIndex = std::stoul(temp);
m_SessionFlags.insert(sessionIndex);
}
currentChild = currentChild->NextSiblingElement("s");
}
}
void FlagComponent::SetPlayerFlag(const uint32_t flagId, const bool value) {
// If the flag is already set, we don't have to recalculate it
if (GetPlayerFlag(flagId) == value) return;
if (value) {
// Update the mission component:
auto* missionComponent = m_Parent->GetComponent<MissionComponent>();
if (missionComponent != nullptr) {
missionComponent->Progress(eMissionTaskType::PLAYER_FLAG, flagId);
}
}
const auto flagEntry = CDPlayerFlagsTable::GetEntry(flagId);
if (flagEntry && flagEntry->sessionOnly) {
if (value) m_SessionFlags.insert(flagId);
else m_SessionFlags.erase(flagId);
} else {
// Calculate the index first
auto flagIndex = uint32_t(std::floor(flagId / 64));
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;
}
} 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_Parent->GetObjectID(), flagId, value, m_Parent->GetSystemAddress());
}
bool FlagComponent::GetPlayerFlag(const uint32_t flagId) const {
bool toReturn = false; //by def, return false.
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 toReturn;
}
bool FlagComponent::OnSetRetroactiveFlags(GameMessages::GameMsg& msg) {
// Retroactive check for if player has joined a faction to set their 'joined a faction' flag to true.
if (GetPlayerFlag(ePlayerFlag::VENTURE_FACTION) ||
GetPlayerFlag(ePlayerFlag::ASSEMBLY_FACTION) ||
GetPlayerFlag(ePlayerFlag::PARADOX_FACTION) ||
GetPlayerFlag(ePlayerFlag::SENTINEL_FACTION)) {
SetPlayerFlag(ePlayerFlag::JOINED_A_FACTION, true);
}
return true;
}
void FlagComponent::ClearSessionFlags(tinyxml2::XMLDocument& doc) {
if (!doc.FirstChildElement("obj")) return;
auto& obj = *doc.FirstChildElement("obj");
if (!obj.FirstChildElement("flag")) return;
auto& flags = *obj.FirstChildElement("flag");
auto* currentChild = flags.FirstChildElement();
while (currentChild) {
auto* nextChild = currentChild->NextSiblingElement();
if (currentChild->Attribute("si")) {
flags.DeleteChild(currentChild);
}
currentChild = nextChild;
}
}

View File

@@ -0,0 +1,59 @@
#ifndef FLAGCOMPONENT_H
#define FLAGCOMPONENT_H
#include <cstdint>
#include <set>
#include <unordered_map>
#include "Component.h"
#include "eReplicaComponentType.h"
class FlagComponent final : public Component {
public:
static const inline eReplicaComponentType ComponentType = eReplicaComponentType::FLAG;
FlagComponent(Entity* parent);
void UpdateXml(tinyxml2::XMLDocument& doc) override;
void LoadFromXml(const tinyxml2::XMLDocument& doc) override;
// Used to clear the save data from a static context where you only have a doc (switching characters)
static void ClearSessionFlags(tinyxml2::XMLDocument& doc);
private:
/**
* Sets a flag for the character, indicating certain parts of the game that have been interacted with. Not to be
* confused with the permissions
* @param flagId the ID of the flag to set
* @param value the value to set for the flag
*/
bool OnSetFlag(GameMessages::GameMsg& msg);
void SetPlayerFlag(const uint32_t flagId, const bool value);
/**
* Gets the value for a certain character flag
* @param flagId the ID of the flag to get a value for
* @return the value of the flag given the ID (the default is false, obviously)
*/
bool OnGetFlag(GameMessages::GameMsg& msg);
bool GetPlayerFlag(const uint32_t flagId) const;
bool OnClearSessionFlags(GameMessages::GameMsg& msg) { m_SessionFlags.clear(); return true; }
/**
* Sets any flags that are meant to have been set that may not have been set due to them being
* missing in a previous patch.
*/
bool OnSetRetroactiveFlags(GameMessages::GameMsg& msg);
/**
* 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)
*/
std::unordered_map<uint32_t, uint64_t> m_PlayerFlags;
};
#endif //!FLAGCOMPONENT_H

View File

@@ -557,7 +557,11 @@ void PetComponent::NotifyTamingBuildSuccess(NiPoint3 position) {
// Triggers the catch a pet missions
if (petFlags.find(m_Parent->GetLOT()) != petFlags.end()) {
tamer->GetCharacter()->SetPlayerFlag(petFlags.at(m_Parent->GetLOT()), true);
GameMessages::SetFlag setFlag{};
setFlag.target = tamer->GetObjectID();
setFlag.iFlagId = petFlags.at(m_Parent->GetLOT());
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
auto* missionComponent = tamer->GetComponent<MissionComponent>();
@@ -847,7 +851,11 @@ void PetComponent::Activate(Item* item, bool registerPet, bool fromTaming) {
Game::entityManager->SerializeEntity(m_Parent);
owner->GetCharacter()->SetPlayerFlag(ePlayerFlag::FIRST_MANUAL_PET_HIBERNATE, true);
GameMessages::SetFlag setFlag{};
setFlag.target = owner->GetObjectID();
setFlag.iFlagId = ePlayerFlag::FIRST_MANUAL_PET_HIBERNATE;
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
if (registerPet) {
GameMessages::SendAddPetToPlayer(m_Owner, 0, GeneralUtils::UTF8ToUTF16(m_Name), m_DatabaseId, m_Parent->GetLOT(), owner->GetSystemAddress());

View File

@@ -481,14 +481,12 @@ void QuickBuildComponent::CompleteQuickBuild(Entity* const user) {
}
// Set flag
auto* character = user->GetCharacter();
if (character != nullptr) {
const auto flagNumber = m_Parent->GetVar<int32_t>(u"quickbuild_single_build_player_flag");
if (flagNumber != 0) {
character->SetPlayerFlag(flagNumber, true);
}
if (m_Parent->HasVar(u"quickbuild_single_build_player_flag")) {
GameMessages::SetFlag setFlag{};
setFlag.target = user->GetObjectID();
setFlag.iFlagId = m_Parent->GetVar<int32_t>(u"quickbuild_single_build_player_flag");
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
}
RenderComponent::PlayAnimation(user, u"rebuild-celebrate", 1.09f);
}

View File

@@ -47,6 +47,7 @@ namespace {
std::map<MessageType::Game, MessageCreator> g_MessageHandlers = {
{ REQUEST_SERVER_OBJECT_INFO, []() { return std::make_unique<RequestServerObjectInfo>(); } },
{ SHOOTING_GALLERY_FIRE, []() { return std::make_unique<ShootingGalleryFire>(); } },
{ SET_FLAG, []() { return std::make_unique<SetFlag>(); } },
};
};
@@ -123,11 +124,6 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
break;
}
case MessageType::Game::SET_FLAG: {
GameMessages::HandleSetFlag(inStream, entity);
break;
}
case MessageType::Game::HAS_BEEN_COLLECTED: {
GameMessages::HandleHasBeenCollected(inStream, entity);
break;
@@ -215,13 +211,17 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
// After we've done our thing, tell the client they're ready
GameMessages::SendPlayerReady(Game::zoneManager->GetZoneControlObject(), sysAddr);
if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1"
|| !entity->GetCharacter()
|| !entity->GetCharacter()->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS)) return;
entity->AddCallbackTimer(0.5f, [entity, sysAddr]() {
if (!entity) return;
GameMessages::SendEndCinematic(entity->GetObjectID(), u"", sysAddr);
});
GameMessages::GetFlag getFlag{};
getFlag.target = entity->GetObjectID();
getFlag.iFlagId = ePlayerFlag::DLU_SKIP_CINEMATICS;
SEND_ENTITY_MSG(getFlag);
if (Game::config->GetValue("allow_players_to_skip_cinematics") == "1" && getFlag.bFlag) {
entity->AddCallbackTimer(0.5f, [entity, sysAddr]() {
if (!entity) return;
GameMessages::SendEndCinematic(entity->GetObjectID(), u"", sysAddr);
});
}
break;
}

View File

@@ -5076,23 +5076,6 @@ void GameMessages::HandleModularBuildConvertModel(RakNet::BitStream& inStream, E
item->SetCount(item->GetCount() - 1, false, false, true, eLootSourceType::QUICKBUILD);
}
void GameMessages::HandleSetFlag(RakNet::BitStream& inStream, Entity* entity) {
bool bFlag{};
int32_t iFlagID{};
inStream.Read(bFlag);
inStream.Read(iFlagID);
auto character = entity->GetCharacter();
if (character) character->SetPlayerFlag(iFlagID, bFlag);
// This is always set the first time a player loads into a world from character select
// and is used to know when to refresh the players inventory items so they show up.
if (iFlagID == ePlayerFlag::IS_NEWS_SCREEN_VISIBLE && bFlag) {
entity->SetVar<bool>(u"dlu_first_time_load", true);
}
}
void GameMessages::HandleRespondToMission(RakNet::BitStream& inStream, Entity* entity) {
int missionID{};
LWOOBJID playerID{};
@@ -5157,13 +5140,17 @@ void GameMessages::HandleMissionDialogOK(RakNet::BitStream& inStream, Entity* en
missionComponent->CompleteMission(missionID);
}
if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1"
|| !player->GetCharacter()
|| !player->GetCharacter()->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS)) return;
player->AddCallbackTimer(0.5f, [player]() {
if (!player) return;
GameMessages::SendEndCinematic(player->GetObjectID(), u"", player->GetSystemAddress());
});
GameMessages::GetFlag getFlag{};
getFlag.target = entity->GetObjectID();
getFlag.iFlagId = ePlayerFlag::DLU_SKIP_CINEMATICS;
SEND_ENTITY_MSG(getFlag);
if (Game::config->GetValue("allow_players_to_skip_cinematics") == "1" && getFlag.bFlag) {
player->AddCallbackTimer(0.5f, [player]() {
if (!player) return;
GameMessages::SendEndCinematic(player->GetObjectID(), u"", player->GetSystemAddress());
});
}
}
void GameMessages::HandleRequestLinkedMission(RakNet::BitStream& inStream, Entity* entity) {
@@ -6443,4 +6430,14 @@ namespace GameMessages {
auto* handlingEntity = Game::entityManager->GetEntity(targetForReport);
if (handlingEntity) handlingEntity->HandleMsg(*this);
}
bool SetFlag::Deserialize(RakNet::BitStream& bitStream) {
if (!bitStream.Read(bFlag)) return false;
if (!bitStream.Read(iFlagId)) return false;
return true;
}
void SetFlag::Handle(Entity& entity, const SystemAddress& sysAddr) {
entity.HandleMsg(*this);
}
}

View File

@@ -634,7 +634,6 @@ namespace GameMessages {
void HandleRequestUse(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandlePlayEmote(RakNet::BitStream& inStream, Entity* entity);
void HandleModularBuildConvertModel(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
void HandleSetFlag(RakNet::BitStream& inStream, Entity* entity);
void HandleRespondToMission(RakNet::BitStream& inStream, Entity* entity);
void HandleMissionDialogOK(RakNet::BitStream& inStream, Entity* entity);
void HandleRequestLinkedMission(RakNet::BitStream& inStream, Entity* entity);
@@ -782,6 +781,30 @@ namespace GameMessages {
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
};
struct SetFlag : public GameMsg {
SetFlag() : GameMsg(MessageType::Game::SET_FLAG) {}
bool Deserialize(RakNet::BitStream& bitStream) override;
void Handle(Entity& entity, const SystemAddress& sysAddr) override;
uint32_t iFlagId{};
bool bFlag{};
};
struct GetFlag : public GameMsg {
GetFlag() : GameMsg(MessageType::Game::GET_FLAG) {}
uint32_t iFlagId{};
bool bFlag{};
};
struct ClearSessionFlags : public GameMsg {
ClearSessionFlags() : GameMsg(MessageType::Game::CLEAR_SESSION_FLAGS) {}
};
struct SetRetroactiveFlags : public GameMsg {
SetRetroactiveFlags() : GameMsg(MessageType::Game::SET_RETROACTIVE_FLAGS) {}
};
};
#endif // GAMEMESSAGES_H

View File

@@ -390,10 +390,15 @@ void Mission::Catchup() {
}
if (type == eMissionTaskType::PLAYER_FLAG) {
for (int32_t target : task->GetAllTargets()) {
const auto flag = GetCharacter()->GetPlayerFlag(target);
GameMessages::GetFlag getFlag{};
getFlag.target = entity->GetObjectID();
if (!flag) {
for (int32_t target : task->GetAllTargets()) {
getFlag.iFlagId = target;
getFlag.bFlag = false;
SEND_ENTITY_MSG(getFlag);
if (!getFlag.bFlag) {
continue;
}

View File

@@ -146,17 +146,22 @@ bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluat
return missionComponent->GetMissionState(value) == eMissionState::AVAILABLE || missionComponent->GetMissionState(value) == eMissionState::COMPLETE_AVAILABLE;
case PreconditionType::OnMission:
if (missionComponent == nullptr) return false;
return missionComponent->GetMissionState(value) == eMissionState::ACTIVE ||
missionComponent->GetMissionState(value) == eMissionState::COMPLETE_ACTIVE ||
missionComponent->GetMissionState(value) == eMissionState::READY_TO_COMPLETE ||
missionComponent->GetMissionState(value) == eMissionState::COMPLETE_READY_TO_COMPLETE;
return missionComponent->GetMissionState(value) == eMissionState::ACTIVE ||
missionComponent->GetMissionState(value) == eMissionState::COMPLETE_ACTIVE ||
missionComponent->GetMissionState(value) == eMissionState::READY_TO_COMPLETE ||
missionComponent->GetMissionState(value) == eMissionState::COMPLETE_READY_TO_COMPLETE;
case PreconditionType::MissionComplete:
if (missionComponent == nullptr) return false;
return missionComponent->GetMissionState(value) >= eMissionState::COMPLETE;
case PreconditionType::PetDeployed:
return false; // TODO
case PreconditionType::HasFlag:
return character->GetPlayerFlag(value);
case PreconditionType::HasFlag: {
GameMessages::GetFlag getFlag{};
getFlag.target = player->GetObjectID();
getFlag.iFlagId = value;
SEND_ENTITY_MSG(getFlag);
return getFlag.bFlag;
}
case PreconditionType::WithinShape:
return true; // Client checks this one
case PreconditionType::InBuild:

View File

@@ -108,11 +108,18 @@ namespace DEVGMCommands {
void ToggleSkipCinematics(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1" && entity->GetGMLevel() < eGameMasterLevel::DEVELOPER) return;
auto* character = entity->GetCharacter();
if (!character) return;
bool current = character->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS);
character->SetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS, !current);
if (!current) {
GameMessages::GetFlag current{};
current.target = entity->GetObjectID();
current.iFlagId = ePlayerFlag::DLU_SKIP_CINEMATICS;
SEND_ENTITY_MSG(current);
GameMessages::SetFlag setFlag{};
setFlag.target = entity->GetObjectID();
setFlag.iFlagId = ePlayerFlag::DLU_SKIP_CINEMATICS;
setFlag.bFlag = !current.bFlag;
SEND_ENTITY_MSG(setFlag);
if (!current.bFlag) {
GameMessages::SendSlashCommandFeedbackText(entity, u"You have elected to skip cinematics. Note that not all cinematics can be skipped, but most will be skipped now.");
} else {
GameMessages::SendSlashCommandFeedbackText(entity, u"Cinematics will no longer be skipped.");
@@ -424,6 +431,9 @@ namespace DEVGMCommands {
void SetFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
GameMessages::SetFlag setFlag{};
setFlag.target = entity->GetObjectID();
if (splitArgs.size() == 1) {
const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(0));
@@ -431,8 +441,9 @@ namespace DEVGMCommands {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag id.");
return;
}
entity->GetCharacter()->SetPlayerFlag(flagId.value(), true);
setFlag.iFlagId = flagId.value();
setFlag.bFlag = true;
SEND_ENTITY_MSG(setFlag);
} else if (splitArgs.size() >= 2) {
const auto flagId = GeneralUtils::TryParse<int32_t>(splitArgs.at(1));
std::string onOffFlag = splitArgs.at(0);
@@ -445,12 +456,17 @@ namespace DEVGMCommands {
ChatPackets::SendSystemMessage(sysAddr, u"Invalid flag type.");
return;
}
entity->GetCharacter()->SetPlayerFlag(flagId.value(), onOffFlag == "on");
setFlag.iFlagId = flagId.value();
setFlag.bFlag = onOffFlag == "on";
SEND_ENTITY_MSG(setFlag);
}
}
void ClearFlag(Entity* entity, const SystemAddress& sysAddr, const std::string args) {
GameMessages::SetFlag setFlag{};
setFlag.target = entity->GetObjectID();
setFlag.bFlag = false;
const auto splitArgs = GeneralUtils::SplitString(args, ' ');
if (splitArgs.empty()) return;
@@ -461,7 +477,8 @@ namespace DEVGMCommands {
return;
}
entity->GetCharacter()->SetPlayerFlag(flagId.value(), false);
setFlag.iFlagId = flagId.value();
SEND_ENTITY_MSG(setFlag);
}
void PlayEffect(Entity* entity, const SystemAddress& sysAddr, const std::string args) {