Merge remote-tracking branch 'origin/main' into chore--mount-imporvements

# Conflicts:
#	dGame/dGameMessages/GameMessages.h
This commit is contained in:
Aaron Kimbrell
2026-06-21 00:25:10 -05:00
122 changed files with 1801 additions and 1506 deletions

View File

@@ -22,6 +22,7 @@
#include "eMatchUpdate.h"
#include "ServiceType.h"
#include "MessageType/Chat.h"
#include "ObjectIDManager.h"
#include "CDCurrencyTableTable.h"
#include "CDActivityRewardsTable.h"
@@ -29,6 +30,11 @@
#include "LeaderboardManager.h"
#include "CharacterComponent.h"
#include "Amf3.h"
#include <ranges>
namespace {
const ActivityInstance g_EmptyInstance{ nullptr, CDActivities{} };
}
ActivityComponent::ActivityComponent(Entity* parent, int32_t componentID) : Component(parent, componentID) {
RegisterMsg(&ActivityComponent::OnGetObjectReportInfo);
@@ -71,9 +77,9 @@ void ActivityComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsIniti
if (m_DirtyActivityInfo) {
outBitStream.Write<uint32_t>(m_ActivityPlayers.size());
if (!m_ActivityPlayers.empty()) {
for (const auto& activityPlayer : m_ActivityPlayers) {
outBitStream.Write<LWOOBJID>(activityPlayer->playerID);
for (const auto& activityValue : activityPlayer->values) {
for (const auto& [playerID, values] : m_ActivityPlayers) {
outBitStream.Write<LWOOBJID>(playerID);
for (const auto& activityValue : values) {
outBitStream.Write<float_t>(activityValue);
}
}
@@ -111,78 +117,81 @@ void ActivityComponent::PlayerJoin(Entity* player) {
if (HasLobby()) {
PlayerJoinLobby(player);
} else if (!IsPlayedBy(player)) {
auto* instance = NewInstance();
instance->AddParticipant(player);
NewInstance().AddParticipant(player);
}
}
void ActivityComponent::PlayerJoinLobby(Entity* player) {
if (!m_Parent->HasComponent(eReplicaComponentType::QUICK_BUILD))
GameMessages::SendMatchResponse(player, player->GetSystemAddress(), 0); // tell the client they joined a lobby
LobbyPlayer* newLobbyPlayer = new LobbyPlayer();
newLobbyPlayer->entityID = player->GetObjectID();
Lobby* playerLobby = nullptr;
LobbyPlayer newLobbyPlayer{};
newLobbyPlayer.entityID = player->GetObjectID();
LWOOBJID playerLobbyID = LWOOBJID_EMPTY;
auto* character = player->GetCharacter();
if (character != nullptr)
character->SetLastNonInstanceZoneID(Game::zoneManager->GetZone()->GetWorldID());
for (Lobby* lobby : m_Queue) {
if (lobby->players.size() < m_ActivityInfo.maxTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby->players.size() < m_ActivityInfo.maxTeams) {
for (auto& [lobbyID, lobby] : m_Queue) {
if (lobby.players.size() < m_ActivityInfo.maxTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby.players.size() < m_ActivityInfo.maxTeams) {
// If an empty slot in an existing lobby is found
lobby->players.push_back(newLobbyPlayer);
playerLobby = lobby;
lobby.players.push_back(newLobbyPlayer);
playerLobbyID = lobbyID;
// Update the joining player on players already in the lobby, and update players already in the lobby on the joining player
std::string matchUpdateJoined = "player=9:" + std::to_string(player->GetObjectID()) + "\nplayerName=0:" + player->GetCharacter()->GetName();
for (LobbyPlayer* joinedPlayer : lobby->players) {
auto* entity = joinedPlayer->GetEntity();
LDFData<LWOOBJID> playerLDF("player", player->GetObjectID());
LDFData<std::string> playerName("playerName", player->GetCharacter()->GetName());
std::string matchUpdateJoined = playerLDF.GetString() + "\n" + playerName.GetString();
for (const auto& joinedPlayer : lobby.players) {
auto* const entity = joinedPlayer.GetEntity();
if (entity == nullptr) {
continue;
}
std::string matchUpdate = "player=9:" + std::to_string(entity->GetObjectID()) + "\nplayerName=0:" + entity->GetCharacter()->GetName();
LDFData<LWOOBJID> entityLDF("player", entity->GetObjectID());
LDFData<std::string> entityName("playerName", entity->GetCharacter()->GetName());
std::string matchUpdate = entityLDF.GetString() + "\n" + entityName.GetString();
GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchUpdate, eMatchUpdate::PLAYER_ADDED);
PlayerReady(entity, joinedPlayer->ready);
PlayerReady(entity, joinedPlayer.ready);
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateJoined, eMatchUpdate::PLAYER_ADDED);
}
break;
}
}
if (!playerLobby) {
if (playerLobbyID == LWOOBJID_EMPTY) {
// If all lobbies are full
playerLobby = new Lobby();
playerLobby->players.push_back(newLobbyPlayer);
playerLobby->timer = m_ActivityInfo.waitTime / 1000;
m_Queue.push_back(playerLobby);
playerLobbyID = ObjectIDManager::GenerateObjectID();
auto& newLobby = m_Queue[playerLobbyID];
newLobby.players.push_back(newLobbyPlayer);
newLobby.timer = m_ActivityInfo.waitTime / 1000;
}
const auto& lobby = m_Queue[playerLobbyID];
if (m_ActivityInfo.maxTeamSize != 1 && playerLobby->players.size() >= m_ActivityInfo.minTeamSize || m_ActivityInfo.maxTeamSize == 1 && playerLobby->players.size() >= m_ActivityInfo.minTeams) {
if (m_ActivityInfo.maxTeamSize != 1 && lobby.players.size() >= m_ActivityInfo.minTeamSize || m_ActivityInfo.maxTeamSize == 1 && lobby.players.size() >= m_ActivityInfo.minTeams) {
// Update the joining player on the match timer
std::string matchTimerUpdate = "time=3:" + std::to_string(playerLobby->timer);
GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_READY);
LDFData<float> matchTimer("time", lobby.timer);
GameMessages::SendMatchUpdate(player, player->GetSystemAddress(), matchTimer.GetString(), eMatchUpdate::PHASE_WAIT_READY);
}
}
void ActivityComponent::PlayerLeave(LWOOBJID playerID) {
// Removes the player from a lobby and notifies the others, not applicable for non-lobby instances
for (Lobby* lobby : m_Queue) {
for (int i = 0; i < lobby->players.size(); ++i) {
if (lobby->players[i]->entityID == playerID) {
std::string matchUpdateLeft = "player=9:" + std::to_string(playerID);
for (LobbyPlayer* lobbyPlayer : lobby->players) {
auto* entity = lobbyPlayer->GetEntity();
for (auto& lobby : m_Queue | std::views::values) {
for (int i = 0; i < lobby.players.size(); i++) {
const auto& player = lobby.players[i];
if (player.entityID == playerID) {
LDFData<LWOOBJID> matchUpdateLeft("player", playerID);
for (const auto& lobbyPlayer : lobby.players) {
auto* const entity = lobbyPlayer.GetEntity();
if (entity == nullptr)
continue;
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateLeft, eMatchUpdate::PLAYER_REMOVED);
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchUpdateLeft.GetString(), eMatchUpdate::PLAYER_REMOVED);
}
delete lobby->players[i];
lobby->players[i] = nullptr;
lobby->players.erase(lobby->players.begin() + i);
lobby.players.erase(lobby.players.begin() + i);
return;
}
@@ -191,85 +200,79 @@ void ActivityComponent::PlayerLeave(LWOOBJID playerID) {
}
void ActivityComponent::Update(float deltaTime) {
std::vector<Lobby*> lobbiesToRemove{};
std::vector<LWOOBJID> lobbiesToRemove{};
// Ticks all the lobbies, not applicable for non-instance activities
for (Lobby* lobby : m_Queue) {
for (LobbyPlayer* player : lobby->players) {
auto* entity = player->GetEntity();
for (auto& [lobbyID, lobby] : m_Queue) {
for (const auto& player : lobby.players) {
const auto* const entity = player.GetEntity();
if (entity == nullptr) {
PlayerLeave(player->entityID);
PlayerLeave(player.entityID);
return;
}
}
if (lobby->players.empty()) {
lobbiesToRemove.push_back(lobby);
if (lobby.players.empty()) {
lobbiesToRemove.push_back(lobbyID);
continue;
}
// Update the match time for all players
if (m_ActivityInfo.maxTeamSize != 1 && lobby->players.size() >= m_ActivityInfo.minTeamSize
|| m_ActivityInfo.maxTeamSize == 1 && lobby->players.size() >= m_ActivityInfo.minTeams) {
if (lobby->timer == m_ActivityInfo.waitTime / 1000) {
for (LobbyPlayer* joinedPlayer : lobby->players) {
auto* entity = joinedPlayer->GetEntity();
if (m_ActivityInfo.maxTeamSize != 1 && lobby.players.size() >= m_ActivityInfo.minTeamSize
|| m_ActivityInfo.maxTeamSize == 1 && lobby.players.size() >= m_ActivityInfo.minTeams) {
if (lobby.timer == m_ActivityInfo.waitTime / 1000) {
for (const auto& joinedPlayer : lobby.players) {
auto* const entity = joinedPlayer.GetEntity();
if (entity == nullptr)
continue;
std::string matchTimerUpdate = "time=3:" + std::to_string(lobby->timer);
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_READY);
LDFData<float> matchTimerUpdate("time", lobby.timer);
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate.GetString(), eMatchUpdate::PHASE_WAIT_READY);
}
}
lobby->timer -= deltaTime;
lobby.timer -= deltaTime;
}
bool lobbyReady = true;
for (LobbyPlayer* player : lobby->players) {
if (player->ready) continue;
for (const auto& player : lobby.players) {
if (player.ready) continue;
lobbyReady = false;
}
// If everyone's ready, jump the timer
if (lobbyReady && lobby->timer > m_ActivityInfo.startDelay / 1000) {
lobby->timer = m_ActivityInfo.startDelay / 1000;
if (lobbyReady && lobby.timer > m_ActivityInfo.startDelay / 1000) {
lobby.timer = m_ActivityInfo.startDelay / 1000;
// Update players in lobby on switch to start delay
std::string matchTimerUpdate = "time=3:" + std::to_string(lobby->timer);
for (LobbyPlayer* player : lobby->players) {
auto* entity = player->GetEntity();
LDFData<float> matchTimerUpdate("time", lobby.timer);
for (const auto& player : lobby.players) {
auto* const entity = player.GetEntity();
if (entity == nullptr)
continue;
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate, eMatchUpdate::PHASE_WAIT_START);
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchTimerUpdate.GetString(), eMatchUpdate::PHASE_WAIT_START);
}
}
// The timer has elapsed, start the instance
if (lobby->timer <= 0.0f) {
if (lobby.timer <= 0.0f) {
LOG("Setting up instance.");
ActivityInstance* instance = NewInstance();
LoadPlayersIntoInstance(instance, lobby->players);
instance->StartZone();
lobbiesToRemove.push_back(lobby);
auto& instance = NewInstance();
LoadPlayersIntoInstance(instance, lobby.players);
instance.StartZone();
lobbiesToRemove.push_back(lobbyID);
}
}
while (!lobbiesToRemove.empty()) {
RemoveLobby(lobbiesToRemove.front());
lobbiesToRemove.erase(lobbiesToRemove.begin());
for (const auto id : lobbiesToRemove) {
RemoveLobby(id);
}
}
void ActivityComponent::RemoveLobby(Lobby* lobby) {
for (int i = 0; i < m_Queue.size(); ++i) {
if (m_Queue[i] == lobby) {
m_Queue.erase(m_Queue.begin() + i);
return;
}
}
void ActivityComponent::RemoveLobby(const LWOOBJID lobbyID) {
if (m_Queue.contains(lobbyID)) m_Queue.erase(lobbyID);
}
bool ActivityComponent::HasLobby() const {
@@ -278,9 +281,9 @@ bool ActivityComponent::HasLobby() const {
}
bool ActivityComponent::PlayerIsInQueue(Entity* player) {
for (Lobby* lobby : m_Queue) {
for (LobbyPlayer* lobbyPlayer : lobby->players) {
if (player->GetObjectID() == lobbyPlayer->entityID) return true;
for (const auto& lobby : m_Queue | std::views::values) {
for (const auto& lobbyPlayer : lobby.players) {
if (player->GetObjectID() == lobbyPlayer.entityID) return true;
}
}
@@ -288,8 +291,8 @@ bool ActivityComponent::PlayerIsInQueue(Entity* player) {
}
bool ActivityComponent::IsPlayedBy(Entity* player) const {
for (const auto* instance : this->m_Instances) {
for (const auto* instancePlayer : instance->GetParticipants()) {
for (const auto& instance : m_Instances) {
for (const auto* instancePlayer : instance.GetParticipants()) {
if (instancePlayer != nullptr && instancePlayer->GetObjectID() == player->GetObjectID())
return true;
}
@@ -299,8 +302,8 @@ bool ActivityComponent::IsPlayedBy(Entity* player) const {
}
bool ActivityComponent::IsPlayedBy(LWOOBJID playerID) const {
for (const auto* instance : this->m_Instances) {
for (const auto* instancePlayer : instance->GetParticipants()) {
for (const auto& instance : m_Instances) {
for (const auto* instancePlayer : instance.GetParticipants()) {
if (instancePlayer != nullptr && instancePlayer->GetObjectID() == playerID)
return true;
}
@@ -329,136 +332,95 @@ bool ActivityComponent::TakeCost(Entity* player) const {
}
void ActivityComponent::PlayerReady(Entity* player, bool bReady) {
for (Lobby* lobby : m_Queue) {
for (LobbyPlayer* lobbyPlayer : lobby->players) {
if (lobbyPlayer->entityID == player->GetObjectID()) {
for (auto& lobby : m_Queue | std::views::values) {
for (auto& lobbyPlayer : lobby.players) {
if (lobbyPlayer.entityID == player->GetObjectID()) {
lobbyPlayer->ready = bReady;
lobbyPlayer.ready = bReady;
// Update players in lobby on player being ready
std::string matchReadyUpdate = "player=9:" + std::to_string(player->GetObjectID());
LDFData<LWOOBJID> matchReadyUpdate("player", player->GetObjectID());
eMatchUpdate readyStatus = eMatchUpdate::PLAYER_READY;
if (!bReady) readyStatus = eMatchUpdate::PLAYER_NOT_READY;
for (LobbyPlayer* otherPlayer : lobby->players) {
auto* entity = otherPlayer->GetEntity();
for (const auto& otherPlayer : lobby.players) {
auto* const entity = otherPlayer.GetEntity();
if (entity == nullptr)
continue;
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchReadyUpdate, readyStatus);
GameMessages::SendMatchUpdate(entity, entity->GetSystemAddress(), matchReadyUpdate.GetString(), readyStatus);
}
}
}
}
}
ActivityInstance* ActivityComponent::NewInstance() {
auto* instance = new ActivityInstance(m_Parent, m_ActivityInfo);
m_Instances.push_back(instance);
return instance;
ActivityInstance& ActivityComponent::NewInstance() {
m_Instances.push_back(ActivityInstance(m_Parent, m_ActivityInfo));
return m_Instances.back();
}
void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const {
for (LobbyPlayer* player : lobby) {
auto* entity = player->GetEntity();
void ActivityComponent::LoadPlayersIntoInstance(ActivityInstance& instance, const std::vector<LobbyPlayer>& lobby) const {
for (const auto& player : lobby) {
auto* const entity = player.GetEntity();
if (entity == nullptr || !CheckCost(entity)) {
continue;
}
instance->AddParticipant(entity);
instance.AddParticipant(entity);
}
}
const std::vector<ActivityInstance*>& ActivityComponent::GetInstances() const {
return m_Instances;
}
ActivityInstance* ActivityComponent::GetInstance(const LWOOBJID playerID) {
for (const auto* instance : GetInstances()) {
for (const auto* participant : instance->GetParticipants()) {
const ActivityInstance& ActivityComponent::GetInstance(const LWOOBJID playerID) const {
for (const auto& instance : m_Instances) {
for (const auto* participant : instance.GetParticipants()) {
if (participant->GetObjectID() == playerID)
return const_cast<ActivityInstance*>(instance);
return instance;
}
}
return nullptr;
return g_EmptyInstance;
}
void ActivityComponent::ClearInstances() {
for (ActivityInstance* instance : m_Instances) {
delete instance;
}
m_Instances.clear();
}
ActivityPlayer* ActivityComponent::GetActivityPlayerData(LWOOBJID playerID) {
for (auto* activityData : m_ActivityPlayers) {
if (activityData->playerID == playerID) {
return activityData;
}
}
return nullptr;
bool ActivityComponent::PlayerHasActivityData(LWOOBJID playerID) const {
return m_ActivityPlayers.contains(playerID);
}
void ActivityComponent::RemoveActivityPlayerData(LWOOBJID playerID) {
for (size_t i = 0; i < m_ActivityPlayers.size(); i++) {
if (m_ActivityPlayers[i]->playerID == playerID) {
delete m_ActivityPlayers[i];
m_ActivityPlayers[i] = nullptr;
m_ActivityPlayers.erase(m_ActivityPlayers.begin() + i);
m_DirtyActivityInfo = true;
Game::entityManager->SerializeEntity(m_Parent);
return;
}
}
}
ActivityPlayer* ActivityComponent::AddActivityPlayerData(LWOOBJID playerID) {
auto* data = GetActivityPlayerData(playerID);
if (data != nullptr)
return data;
m_ActivityPlayers.push_back(new ActivityPlayer{ playerID, {} });
m_ActivityPlayers.erase(playerID);
m_DirtyActivityInfo = true;
Game::entityManager->SerializeEntity(m_Parent);
return GetActivityPlayerData(playerID);
}
float_t ActivityComponent::GetActivityValue(LWOOBJID playerID, uint32_t index) {
auto value = -1.0f;
float_t ActivityComponent::GetActivityValue(LWOOBJID playerID, uint32_t index) const {
float value = -1.0f;
auto* data = GetActivityPlayerData(playerID);
if (data != nullptr) {
value = data->values[std::min(index, static_cast<uint32_t>(9))];
const auto& data = m_ActivityPlayers.find(playerID);
if (data != m_ActivityPlayers.cend()) {
value = data->second[std::min(index, static_cast<uint32_t>(9))];
}
LOG_DEBUG("Player %llu has score %f at index %i", playerID, value, index);
return value;
}
void ActivityComponent::SetActivityValue(LWOOBJID playerID, uint32_t index, float_t value) {
auto* data = AddActivityPlayerData(playerID);
if (data != nullptr) {
data->values[std::min(index, static_cast<uint32_t>(9))] = value;
}
auto& data = m_ActivityPlayers[playerID];
data[std::min(index, static_cast<uint32_t>(9))] = value;
LOG_DEBUG("%llu index %i has score of %f", playerID, index, value);
m_DirtyActivityInfo = true;
Game::entityManager->SerializeEntity(m_Parent);
}
void ActivityComponent::PlayerRemove(LWOOBJID playerID) {
for (auto* instance : GetInstances()) {
auto participants = instance->GetParticipants();
for (int i = 0; i < m_Instances.size(); i++) {
auto& instance = m_Instances[i];
auto participants = instance.GetParticipants();
for (const auto* participant : participants) {
if (participant != nullptr && participant->GetObjectID() == playerID) {
instance->RemoveParticipant(participant);
instance.RemoveParticipant(participant);
RemoveActivityPlayerData(playerID);
// If the instance is empty after the delete of the participant, delete the instance too
if (instance->GetParticipants().empty()) {
m_Instances.erase(std::find(m_Instances.begin(), m_Instances.end(), instance));
delete instance;
if (instance.GetParticipants().empty()) {
m_Instances.erase(m_Instances.begin() + i);
}
return;
}
@@ -595,14 +557,13 @@ bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo&
auto& instances = activityInfo.PushDebug("Instances: " + std::to_string(m_Instances.size()));
size_t i = 0;
for (const auto& activityInstance : m_Instances) {
if (!activityInstance) continue;
auto& instance = instances.PushDebug("Instance " + std::to_string(i++));
instance.PushDebug<AMFIntValue>("Score") = activityInstance->GetScore();
instance.PushDebug<AMFIntValue>("Next Zone Clone ID") = activityInstance->GetNextZoneCloneID();
instance.PushDebug<AMFIntValue>("Score") = activityInstance.GetScore();
instance.PushDebug<AMFIntValue>("Next Zone Clone ID") = activityInstance.GetNextZoneCloneID();
{
auto& activityInfo = instance.PushDebug("Activity Info");
const auto& instanceActInfo = activityInstance->GetActivityInfo();
const auto& instanceActInfo = activityInstance.GetActivityInfo();
activityInfo.PushDebug<AMFIntValue>("ActivityID") = instanceActInfo.ActivityID;
activityInfo.PushDebug<AMFIntValue>("locStatus") = instanceActInfo.locStatus;
activityInfo.PushDebug<AMFIntValue>("instanceMapID") = instanceActInfo.instanceMapID;
@@ -625,7 +586,7 @@ bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo&
}
auto& participants = instance.PushDebug("Participants");
for (const auto* participant : activityInstance->GetParticipants()) {
for (const auto* participant : activityInstance.GetParticipants()) {
if (!participant) continue;
auto* character = participant->GetCharacter();
if (!character) continue;
@@ -635,38 +596,36 @@ bool ActivityComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo&
auto& queue = activityInfo.PushDebug("Queue");
i = 0;
for (const auto& lobbyQueue : m_Queue) {
for (const auto& lobbyQueue : m_Queue | std::views::values) {
auto& lobby = queue.PushDebug("Lobby " + std::to_string(i++));
lobby.PushDebug<AMFDoubleValue>("Timer") = lobbyQueue->timer;
lobby.PushDebug<AMFDoubleValue>("Timer") = lobbyQueue.timer;
auto& players = lobby.PushDebug("Players");
for (const auto* player : lobbyQueue->players) {
if (!player) continue;
auto* playerEntity = player->GetEntity();
for (const auto& player : lobbyQueue.players) {
const auto* const playerEntity = player.GetEntity();
if (!playerEntity) continue;
auto* character = playerEntity->GetCharacter();
if (!character) continue;
players.PushDebug<AMFStringValue>(std::to_string(playerEntity->GetObjectID()) + ": " + character->GetName()) = player->ready ? "Ready" : "Not Ready";
players.PushDebug<AMFStringValue>(std::to_string(playerEntity->GetObjectID()) + ": " + character->GetName()) = player.ready ? "Ready" : "Not Ready";
}
}
auto& activityPlayers = activityInfo.PushDebug("Activity Players");
for (const auto* activityPlayer : m_ActivityPlayers) {
if (!activityPlayer) continue;
auto* const activityPlayerEntity = Game::entityManager->GetEntity(activityPlayer->playerID);
for (const auto& [playerID, playerScores] : m_ActivityPlayers) {
auto* const activityPlayerEntity = Game::entityManager->GetEntity(playerID);
if (!activityPlayerEntity) continue;
auto* character = activityPlayerEntity->GetCharacter();
if (!character) continue;
auto& playerData = activityPlayers.PushDebug(std::to_string(activityPlayer->playerID) + " " + character->GetName());
auto& playerData = activityPlayers.PushDebug(std::to_string(playerID) + " " + character->GetName());
auto& scores = playerData.PushDebug("Scores");
for (size_t i = 0; i < 10; ++i) {
scores.PushDebug<AMFDoubleValue>(std::to_string(i)) = activityPlayer->values[i];
scores.PushDebug<AMFDoubleValue>(std::to_string(i)) = playerScores[i];
}
}
activityInfo.PushDebug<AMFIntValue>("ActivityID") = m_ActivityID;
return true;
}

View File

@@ -8,14 +8,15 @@
#include "eReplicaComponentType.h"
#include "CDActivitiesTable.h"
#include <array>
namespace GameMessages {
class GameMsg;
};
/**
* Represents an instance of an activity, having participants and score
*/
/**
* Represents an instance of an activity, having participants and score
*/
class ActivityInstance {
public:
ActivityInstance(Entity* parent, CDActivities activityInfo) { m_Parent = parent; m_ActivityInfo = activityInfo; };
@@ -104,7 +105,7 @@ struct LobbyPlayer {
/**
* The ID of the entity that is in the lobby
*/
LWOOBJID entityID;
LWOOBJID entityID = LWOOBJID_EMPTY;
/**
* Whether or not the entity is ready
@@ -126,12 +127,12 @@ struct Lobby {
/**
* The lobby of players
*/
std::vector<LobbyPlayer*> players;
std::vector<LobbyPlayer> players;
/**
* The timer that determines when the activity should start
*/
float timer;
float timer{};
};
/**
@@ -142,12 +143,12 @@ struct ActivityPlayer {
/**
* The entity that the score is tracked for
*/
LWOOBJID playerID;
LWOOBJID playerID{};
/**
* The list of score for this entity
*/
float values[10];
float values[10]{};
};
/**
@@ -194,13 +195,13 @@ public:
* @param instance the instance to load the players into
* @param lobby the players to load into the instance
*/
void LoadPlayersIntoInstance(ActivityInstance* instance, const std::vector<LobbyPlayer*>& lobby) const;
void LoadPlayersIntoInstance(ActivityInstance& instance, const std::vector<LobbyPlayer>& lobby) const;
/**
* Removes a lobby from the activity manager
* @param lobby the lobby to remove
*/
void RemoveLobby(Lobby* lobby);
void RemoveLobby(const LWOOBJID lobbyID);
/**
* Marks a player as (un)ready in a lobby
@@ -246,7 +247,7 @@ public:
*/
bool IsPlayedBy(LWOOBJID playerID) const;
/**
/**
* Checks if the entity has enough cost to play this activity
* @param player the entity to check
* @return true if the entity has enough cost to play this activity, false otherwise
@@ -271,20 +272,14 @@ public:
* Creates a new instance for this activity
* @return a new instance for this activity
*/
ActivityInstance* NewInstance();
/**
* Returns all the currently active instances of this activity
* @return all the currently active instances of this activity
*/
const std::vector<ActivityInstance*>& GetInstances() const;
ActivityInstance& NewInstance();
/**
* Returns the instance that some entity is currently playing in
* @param playerID the entity to check for
* @return if any, the instance that the entity is currently in
*/
ActivityInstance* GetInstance(const LWOOBJID playerID);
const ActivityInstance& GetInstance(const LWOOBJID playerID) const;
/**
* @brief Reloads the config settings for this component
@@ -292,23 +287,12 @@ public:
*/
void ReloadConfig();
/**
* Removes all the instances
*/
void ClearInstances();
/**
* Returns all the score for the players that are currently playing this activity
* @return
*/
std::vector<ActivityPlayer*> GetActivityPlayers() { return m_ActivityPlayers; };
/**
* Returns activity data for a specific entity (e.g. score and such).
* @param playerID the entity to get data for
* @return the activity data (score) for the passed player in this activity, if it exists
*/
ActivityPlayer* GetActivityPlayerData(LWOOBJID playerID);
bool PlayerHasActivityData(LWOOBJID playerID) const;
/**
* Sets some score value for an entity
@@ -324,7 +308,7 @@ public:
* @param index the index to get score for
* @return activity score for the passed parameters
*/
float_t GetActivityValue(LWOOBJID playerID, uint32_t index);
float_t GetActivityValue(LWOOBJID playerID, uint32_t index) const;
/**
* Removes activity score tracking for some entity
@@ -332,13 +316,6 @@ public:
*/
void RemoveActivityPlayerData(LWOOBJID playerID);
/**
* Adds activity score tracking for some entity
* @param playerID the entity to add the activity score for
* @return the created entry
*/
ActivityPlayer* AddActivityPlayerData(LWOOBJID playerID);
/**
* Sets the mapID that this activity points to
* @param mapID the map ID to set
@@ -346,7 +323,6 @@ public:
void SetInstanceMapID(uint32_t mapID) { m_ActivityInfo.instanceMapID = mapID; };
private:
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& msg);
/**
* The database information for this activity
@@ -356,17 +332,17 @@ private:
/**
* All the active instances of this activity
*/
std::vector<ActivityInstance*> m_Instances;
std::vector<ActivityInstance> m_Instances;
/**
* The current lobbies for this activity
*/
std::vector<Lobby*> m_Queue;
std::map<LWOOBJID, Lobby> m_Queue;
/**
* All the activity score for the players in this activity
*/
std::vector<ActivityPlayer*> m_ActivityPlayers;
std::map<LWOOBJID, std::array<float, 10>> m_ActivityPlayers;
/**
* The activity id

View File

@@ -13,6 +13,8 @@
#include "CDClientDatabase.h"
#include "CDClientManager.h"
#include "CDObjectSkillsTable.h"
#include "CDSkillBehaviorTable.h"
#include "DestroyableComponent.h"
#include <algorithm>
@@ -23,7 +25,6 @@
#include "SkillComponent.h"
#include "QuickBuildComponent.h"
#include "DestroyableComponent.h"
#include "Metrics.hpp"
#include "CDComponentsRegistryTable.h"
#include "CDPhysicsComponentTable.h"
#include "dNavMesh.h"
@@ -44,7 +45,7 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t compo
//Grab the aggro information from BaseCombatAI:
auto componentQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius FROM BaseCombatAIComponent WHERE id = ?;");
"SELECT aggroRadius, tetherSpeed, pursuitSpeed, softTetherRadius, hardTetherRadius, minRoundLength, maxRoundLength, combatRoundLength FROM BaseCombatAIComponent WHERE id = ?;");
componentQuery.bind(1, static_cast<int>(componentID));
auto componentResult = componentQuery.execQuery();
@@ -64,44 +65,37 @@ BaseCombatAIComponent::BaseCombatAIComponent(Entity* parent, const int32_t compo
if (!componentResult.fieldIsNull("hardTetherRadius"))
m_HardTetherRadius = componentResult.getFloatField("hardTetherRadius");
m_MinRoundLength = componentResult.getFloatField("minRoundLength");
m_MaxRoundLength = componentResult.getFloatField("maxRoundLength");
m_CombatRoundLength = componentResult.getFloatField("combatRoundLength");
}
componentResult.finalize();
// Get aggro and tether radius from settings and use this if it is present. Only overwrite the
// radii if it is greater than the one in the database.
if (m_Parent) {
auto aggroRadius = m_Parent->GetVar<float>(u"aggroRadius");
m_AggroRadius = aggroRadius != 0 ? aggroRadius : m_AggroRadius;
auto tetherRadius = m_Parent->GetVar<float>(u"tetherRadius");
m_HardTetherRadius = tetherRadius != 0 ? tetherRadius : m_HardTetherRadius;
}
m_AggroRadius = m_Parent->HasVar(u"aggroRadius") ? m_Parent->GetVar<float>(u"aggroRadius") : m_AggroRadius;
m_HardTetherRadius = m_Parent->HasVar(u"tetherRadius") ? m_Parent->GetVar<float>(u"tetherRadius") : m_HardTetherRadius;
/*
* Find skills
*/
auto skillQuery = CDClientDatabase::CreatePreppedStmt(
"SELECT skillID, cooldown, behaviorID FROM SkillBehavior WHERE skillID IN (SELECT skillID FROM ObjectSkills WHERE objectTemplate = ?);");
skillQuery.bind(1, static_cast<int>(parent->GetLOT()));
for (const auto objectSkill : CDClientManager::GetTable<CDObjectSkillsTable>()->Get(parent->GetLOT())) {
const auto skillBehavior = CDClientManager::GetTable<CDSkillBehaviorTable>()->GetSkillByID(objectSkill.skillID);
if (skillBehavior.skillID == objectSkill.skillID) {
const auto skillId = skillBehavior.skillID;
auto result = skillQuery.execQuery();
const auto abilityCooldown = skillBehavior.cooldown;
while (!result.eof()) {
const auto skillId = static_cast<uint32_t>(result.getIntField("skillID"));
const auto behaviorId = skillBehavior.behaviorID;
const auto abilityCooldown = static_cast<float>(result.getFloatField("cooldown"));
const auto combatWeight = objectSkill.AICombatWeight;
const auto behaviorId = static_cast<uint32_t>(result.getIntField("behaviorID"));
auto* behavior = Behavior::CreateBehavior(behaviorId);
auto* behavior = Behavior::CreateBehavior(behaviorId);
AiSkillEntry entry = { .skillId = skillId, .cooldown = 0.0f, .abilityCooldown = abilityCooldown, .behavior = behavior, .combatWeight = combatWeight };
std::stringstream behaviorQuery;
AiSkillEntry entry = { skillId, 0, abilityCooldown, behavior };
m_SkillEntries.push_back(entry);
result.nextRow();
m_SkillEntries.push_back(entry);
}
}
Stun(1.0f);
@@ -211,8 +205,10 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
}
if (stunnedThisFrame) {
m_MovementAI->Stop();
if (!m_MovementAI->IsPaused()) m_MovementAI->Pause();
// in this case we just become unstunned so check if we paused and resume if we did
if (!m_Stunned && m_MovementAI->IsPaused()) m_MovementAI->Resume();
return;
}
@@ -247,10 +243,12 @@ void BaseCombatAIComponent::Update(const float deltaTime) {
void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
bool hasSkillToCast = false;
int32_t maxSkillWeights = 0;
for (auto& entry : m_SkillEntries) {
if (entry.cooldown > 0.0f) {
entry.cooldown -= deltaTime;
} else {
maxSkillWeights += entry.combatWeight;
hasSkillToCast = true;
}
}
@@ -318,12 +316,14 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
SetAiState(AiState::aggro);
} else {
SetAiState(AiState::idle);
if (m_MovementAI) m_MovementAI->SetMaxSpeed(1.0f);
}
if (!hasSkillToCast) return;
if (m_Target == LWOOBJID_EMPTY) {
SetAiState(AiState::idle);
if (m_MovementAI) m_MovementAI->SetMaxSpeed(1.0f);
return;
}
@@ -334,14 +334,23 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
LookAt(target->GetPosition());
}
for (auto i = 0; i < m_SkillEntries.size(); ++i) {
auto entry = m_SkillEntries.at(i);
// Roll to find which skill we'll try to cast
auto randomizedWeight = GeneralUtils::GenerateRandomNumber<int32_t>(0, maxSkillWeights);
if (entry.cooldown > 0) {
for (auto& entry : m_SkillEntries) {
// Skill isn't cooled off yet
if (entry.cooldown > 0.0f) {
continue;
}
const auto result = skillComponent->CalculateBehavior(entry.skillId, entry.behavior->m_behaviorId, LWOOBJID_EMPTY);
randomizedWeight -= entry.combatWeight;
// if the weight is still greater than 0 continue to the next rolled skill
if (randomizedWeight > 0) {
continue;
}
const auto result = skillComponent->CalculateBehavior(entry.skillId, entry.behavior->m_behaviorId, GetTarget());
if (result.success) {
if (m_MovementAI != nullptr) {
@@ -356,8 +365,6 @@ void BaseCombatAIComponent::CalculateCombat(const float deltaTime) {
entry.cooldown = entry.abilityCooldown + m_SkillTime;
m_SkillEntries[i] = entry;
break;
}
}
@@ -619,6 +626,11 @@ void BaseCombatAIComponent::Wander() {
return;
}
// If we have a path to follow we should almost certainly do that instead of wandering.
if (m_MovementAI->HasPath()) {
return;
}
m_MovementAI->SetHaltDistance(0);
const auto& info = m_MovementAI->GetInfo();
@@ -747,8 +759,8 @@ void BaseCombatAIComponent::SetTetherSpeed(float value) {
m_TetherSpeed = value;
}
void BaseCombatAIComponent::Stun(const float time) {
if (m_StunImmune || m_StunTime > time) {
void BaseCombatAIComponent::Stun(const float time, const bool force) {
if (!force && (m_StunImmune || m_StunTime > time)) {
return;
}
@@ -863,12 +875,12 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport
// roundInfo.PushDebug<AMFDoubleValue>("Combat Start Delay") = m_CombatStartDelay;
std::string curState;
switch (m_State) {
case idle: curState = "Idling"; break;
case aggro: curState = "Aggroed"; break;
case tether: curState = "Returning to Tether"; break;
case spawn: curState = "Spawn"; break;
case dead: curState = "Dead"; break;
default: curState = "Unknown or Undefined"; break;
case idle: curState = "Idling"; break;
case aggro: curState = "Aggroed"; break;
case tether: curState = "Returning to Tether"; break;
case spawn: curState = "Spawn"; break;
case dead: curState = "Dead"; break;
default: curState = "Unknown or Undefined"; break;
}
cmptType.PushDebug<AMFStringValue>("Current Combat State") = curState;
@@ -906,8 +918,16 @@ bool BaseCombatAIComponent::MsgGetObjectReportInfo(GameMessages::GetObjectReport
}
auto& ignoredThreats = cmptType.PushDebug("Temp Ignored Threats");
for (const auto& [id, threat] : m_ThreatEntries) {
for (const auto& [id, threat] : m_RemovedThreatList) {
ignoredThreats.PushDebug<AMFDoubleValue>(std::to_string(id) + " - Time") = threat;
}
auto& skillInfo = cmptType.PushDebug("Skill Info");
for (const auto& skill : m_SkillEntries) {
auto& skillDebug = skillInfo.PushDebug("Skill ID " + std::to_string(skill.skillId));
skillDebug.PushDebug<AMFDoubleValue>("Cooldown") = skill.cooldown;
skillDebug.PushDebug<AMFDoubleValue>("Ability Cooldown") = skill.abilityCooldown;
skillDebug.PushDebug<AMFIntValue>("AI Combat Weight") = skill.combatWeight;
}
return true;
}

View File

@@ -33,13 +33,15 @@ enum class AiState : uint32_t {
*/
struct AiSkillEntry
{
uint32_t skillId;
uint32_t skillId{};
float cooldown;
float cooldown{};
float abilityCooldown;
float abilityCooldown{};
Behavior* behavior;
Behavior* behavior{};
int32_t combatWeight{};
};
/**
@@ -181,8 +183,9 @@ public:
/**
* Stuns the entity for a certain amount of time, will not work if the entity is stun immune
* @param time the time to stun the entity, if stunnable
* @param force whether or not to force the stun and ignore checks
*/
void Stun(float time);
void Stun(float time, const bool force = false);
/**
* Gets the radius that will cause this entity to get aggro'd, causing a target chase
@@ -236,6 +239,8 @@ public:
bool MsgGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
void SetStartingPosition(const NiPoint3& pos) { m_StartPosition = pos; }
private:
/**
* Returns the current target or the target that currently is the largest threat to this entity
@@ -394,9 +399,17 @@ private:
*/
bool m_DirtyStateOrTarget = false;
// Min amount of time to remain as in combat after casting a skill
float m_MinRoundLength = 0.0f;
// max amount of time to remain as in combat after casting a skill
float m_MaxRoundLength = 0.0f;
// The amount of time the entity will be forced to tether for
float m_ForcedTetherTime = 0.0f;
float m_CombatRoundLength = 0.0f;
// The amount of time a removed threat will be ignored for.
std::map<LWOOBJID, float> m_RemovedThreatList;

View File

@@ -24,6 +24,7 @@
#include "WorldPackets.h"
#include "MessageType/Game.h"
#include <ctime>
#include <ranges>
CharacterComponent::CharacterComponent(Entity* parent, const int32_t componentID, Character* character, const SystemAddress& systemAddress) : Component(parent, componentID) {
m_Character = character;
@@ -491,7 +492,7 @@ Item* CharacterComponent::RocketEquip(Entity* player) {
if (!rocket) return rocket;
// build and define the rocket config
for (LDFBaseData* data : rocket->GetConfig()) {
for (const auto& data : rocket->GetConfig().values | std::views::values) {
if (data->GetKey() == u"assemblyPartLOTs") {
std::string newRocketStr = data->GetValueAsString() + ";";
GeneralUtils::ReplaceInString(newRocketStr, "+", ";");

View File

@@ -881,9 +881,9 @@ void DestroyableComponent::FixStats() {
int32_t currentImagination = destroyableComponent->GetImagination();
// Unequip all items
auto equipped = inventoryComponent->GetEquippedItems();
const auto equipped = inventoryComponent->GetEquippedItems();
for (auto& equippedItem : equipped) {
for (const auto& equippedItem : equipped) {
// Get the item with the item ID
auto* item = inventoryComponent->FindItemById(equippedItem.second.id);
@@ -924,7 +924,7 @@ void DestroyableComponent::FixStats() {
buffComponent->ReApplyBuffs();
// Requip all items
for (auto& equippedItem : equipped) {
for (const auto& equippedItem : equipped) {
// Get the item with the item ID
auto* item = inventoryComponent->FindItemById(equippedItem.second.id);

View File

@@ -173,7 +173,7 @@ void InventoryComponent::AddItem(
const uint32_t count,
eLootSourceType lootSourceType,
eInventoryType inventoryType,
const std::vector<LDFBaseData*>& config,
const LwoNameValue& config,
const LWOOBJID parent,
const bool showFlyingLoot,
bool isModMoveAndEquip,
@@ -204,7 +204,7 @@ void InventoryComponent::AddItem(
auto* inventory = GetInventory(inventoryType);
if (!config.empty() || bound) {
if (!config.values.empty() || bound) {
const auto slot = preferredSlot != -1 && inventory->IsSlotEmpty(preferredSlot) ? preferredSlot : inventory->FindEmptySlot();
if (slot == -1) {
@@ -356,7 +356,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
const auto subkey = item->GetSubKey();
if (subkey == LWOOBJID_EMPTY && item->GetConfig().empty() && (!item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))) {
if (subkey == LWOOBJID_EMPTY && item->GetConfig().values.empty() && (!item->GetBound() || (item->GetBound() && item->GetInfo().isBOP))) {
auto left = std::min<uint32_t>(count, origin->GetLotCount(lot));
while (left > 0) {
@@ -379,11 +379,7 @@ void InventoryComponent::MoveItemToInventory(Item* item, const eInventoryType in
isModMoveAndEquip = false;
}
} else {
std::vector<LDFBaseData*> config;
for (auto* const data : item->GetConfig()) {
config.push_back(data->Copy());
}
const auto config = item->GetConfig();
const auto delta = std::min<uint32_t>(item->GetCount(), count);
@@ -744,18 +740,17 @@ void InventoryComponent::Serialize(RakNet::BitStream& outBitStream, const bool b
outBitStream.Write0();
bool flag = !item.config.empty();
bool flag = !item.config.values.empty();
outBitStream.Write(flag);
if (flag) {
RakNet::BitStream ldfStream;
ldfStream.Write<int32_t>(item.config.size()); // Key count
for (LDFBaseData* data : item.config) {
ldfStream.Write<int32_t>(item.config.values.size()); // Key count
for (const auto& data : item.config.values | std::views::values) {
if (data->GetKey() == u"assemblyPartLOTs") {
std::string newRocketStr = data->GetValueAsString() + ";";
GeneralUtils::ReplaceInString(newRocketStr, "+", ";");
LDFData<std::u16string>* ldf_data = new LDFData<std::u16string>(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(newRocketStr));
ldf_data->WriteToPacket(ldfStream);
delete ldf_data;
LDFData<std::u16string> ldf_data(u"assemblyPartLOTs", GeneralUtils::ASCIIToUTF16(newRocketStr));
ldf_data.WriteToPacket(ldfStream);
} else {
data->WriteToPacket(ldfStream);
}
@@ -782,7 +777,7 @@ void InventoryComponent::Update(float deltaTime) {
}
}
void InventoryComponent::UpdateSlot(const std::string& location, EquippedItem item, bool keepCurrent) {
void InventoryComponent::UpdateSlot(const std::string& location, const EquippedItem& item, bool keepCurrent) {
const auto index = m_Equipped.find(location);
if (index != m_Equipped.end()) {
@@ -1056,7 +1051,7 @@ void InventoryComponent::PushEquippedItems() {
}
void InventoryComponent::PopEquippedItems() {
auto current = m_Equipped;
const auto current = m_Equipped;
for (const auto& pair : current) {
auto* const item = FindItemById(pair.second.id);
@@ -1852,7 +1847,7 @@ bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo
slot.PushDebug<AMFBoolValue>("Bind on equip") = item->GetInfo().isBOE;
slot.PushDebug<AMFBoolValue>("Is currently bound") = item->GetBound();
auto& extra = slot.PushDebug("Extra Info");
for (const auto* const setting : item->GetConfig()) {
for (const auto& setting : item->GetConfig().values | std::views::values) {
if (setting) extra.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString();
}
}
@@ -1868,7 +1863,7 @@ bool InventoryComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo
equipSlot.PushDebug<AMFIntValue>("Slot") = info.slot;
equipSlot.PushDebug<AMFIntValue>("Count") = info.count;
auto& extra = equipSlot.PushDebug("Extra Info");
for (const auto* const setting : info.config) {
for (const auto& setting : info.config.values | std::views::values) {
if (setting) extra.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString();
}
}

View File

@@ -134,7 +134,7 @@ public:
uint32_t count,
eLootSourceType lootSourceType = eLootSourceType::NONE,
eInventoryType inventoryType = INVALID,
const std::vector<LDFBaseData*>& config = {},
const LwoNameValue& config = {},
LWOOBJID parent = LWOOBJID_EMPTY,
bool showFlyingLoot = true,
bool isModMoveAndEquip = false,
@@ -213,7 +213,7 @@ public:
* @param item the item to place
* @param keepCurrent stores the item in an additional temp slot if there's already an item equipped
*/
void UpdateSlot(const std::string& location, EquippedItem item, bool keepCurrent = false);
void UpdateSlot(const std::string& location, const EquippedItem& item, bool keepCurrent = false);
/**
* Removes a slot from the inventory

View File

@@ -221,8 +221,8 @@ void ModelComponent::RemoveBehavior(MoveToInventoryMessage& msg, const bool keep
auto* const inventoryComponent = playerEntity->GetComponent<InventoryComponent>();
if (inventoryComponent && !behavior.GetIsLoot()) {
// config is owned by the item
std::vector<LDFBaseData*> config;
config.push_back(new LDFData<std::string>(u"userModelName", behavior.GetName()));
LwoNameValue config;
config.Insert(u"userModelName", behavior.GetName());
inventoryComponent->AddItem(7965, 1, eLootSourceType::PROPERTY, eInventoryType::BEHAVIORS, config, LWOOBJID_EMPTY, true, false, msg.GetBehaviorId());
}
}

View File

@@ -19,6 +19,12 @@
#include "Amf3.h"
#include "dNavMesh.h"
#include "eWaypointCommandType.h"
#include "StringifiedEnum.h"
#include "SkillComponent.h"
#include "GeneralUtils.h"
#include "RenderComponent.h"
#include "InventoryComponent.h"
namespace {
/**
@@ -31,8 +37,6 @@ MovementAIComponent::MovementAIComponent(Entity* parent, const int32_t component
m_Info = info;
m_AtFinalWaypoint = true;
m_BaseCombatAI = nullptr;
m_BaseCombatAI = m_Parent->GetComponent<BaseCombatAIComponent>();
//Try and fix the insane values:
@@ -60,7 +64,7 @@ MovementAIComponent::MovementAIComponent(Entity* parent, const int32_t component
RegisterMsg(&MovementAIComponent::OnGetObjectReportInfo);
if (!m_Parent->GetComponent<BaseCombatAIComponent>()) SetPath(m_Parent->GetVarAsString(u"attached_path"));
SetPath(m_Parent->GetVarAsString(u"attached_path"));
}
void MovementAIComponent::SetPath(const std::string pathName) {
@@ -125,7 +129,11 @@ void MovementAIComponent::Update(const float deltaTime) {
m_TimeTravelled += deltaTime;
SetPosition(ApproximateLocation());
const auto approxPos = ApproximateLocation();
SetPosition(approxPos);
// Set the AIs new home based on where our current waypoint is IF we're idle, that way we can return to this
// when resuming the pathing after losing aggro while moving the aggro hitbox with us
if (m_BaseCombatAI && m_BaseCombatAI->GetState() == AiState::idle) m_BaseCombatAI->SetStartingPosition(approxPos);
if (m_TimeTravelled < m_TimeToTravel) return;
m_TimeTravelled = 0.0f;
@@ -160,32 +168,43 @@ void MovementAIComponent::Update(const float deltaTime) {
SetRotation(QuatUtils::LookAt(source, m_NextWaypoint));
}
} else {
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1;
if (m_CurrentPath.empty()) {
if (m_Path) {
if (m_Path->pathBehavior == PathBehavior::Loop) {
SetPath(m_Path->pathWaypoints);
} else if (m_Path->pathBehavior == PathBehavior::Bounce) {
m_IsBounced = !m_IsBounced;
std::vector<PathWaypoint> waypoints = m_Path->pathWaypoints;
if (m_IsBounced) std::ranges::reverse(waypoints);
SetPath(waypoints);
} else if (m_Path->pathBehavior == PathBehavior::Once) {
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
// Only try to renew or continue the path if we're in the idle or spawn state and we actually have a combatAI component
if (!m_BaseCombatAI || (m_BaseCombatAI && m_BaseCombatAI->GetState() == AiState::idle)) {
// Check if there are more waypoints in the queue, if so set our next destination to the next waypoint
const auto waypointNum = m_IsBounced ? m_CurrentPath.size() : m_CurrentPathWaypointCount - m_CurrentPath.size() - 1;
RunWaypointCommands(waypointNum);
if (m_CurrentPath.empty()) {
if (m_Path) {
if (m_Path->pathBehavior == PathBehavior::Loop) {
SetPath(m_Path->pathWaypoints);
} else if (m_Path->pathBehavior == PathBehavior::Bounce) {
m_IsBounced = !m_IsBounced;
std::vector<PathWaypoint> waypoints = m_Path->pathWaypoints;
if (m_IsBounced) std::ranges::reverse(waypoints);
SetPath(waypoints);
} else if (m_Path->pathBehavior == PathBehavior::Once) {
// In this case we intended to follow a path and once we've followed it we camp there, otherwise we'd just wander home again.
Stop();
return;
}
} else {
Stop();
if (m_FollowedTarget != LWOOBJID_EMPTY) {
GameMessages::GetPosition getPos;
if (!getPos.Send(m_FollowedTarget)) {
LOG("Target %llu does not exist anymore to follow", m_FollowedTarget);
m_FollowedTarget = LWOOBJID_EMPTY;
} else {
SetDestination(getPos.pos);
}
}
return;
}
} else {
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
Stop();
return;
}
} else {
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
SetDestination(m_CurrentPath.top().position);
SetDestination(m_CurrentPath.top().position);
m_CurrentPath.pop();
m_CurrentPath.pop();
}
}
}
@@ -212,8 +231,7 @@ NiPoint3 MovementAIComponent::GetCurrentWaypoint() const {
NiPoint3 MovementAIComponent::ApproximateLocation() const {
auto source = m_SourcePosition;
if (AtFinalWaypoint()) return source;
if (AtFinalWaypoint()) return m_Parent->GetPosition();
auto destination = m_NextWaypoint;
@@ -423,7 +441,69 @@ NiPoint3 MovementAIComponent::GetDestination() const {
void MovementAIComponent::SetMaxSpeed(const float value) {
if (value == m_MaxSpeed) return;
m_MaxSpeed = value;
m_Acceleration = value / 5;
m_Acceleration = value / 5.0f;
}
void MovementAIComponent::RunWaypointCommands(uint32_t waypointNum) {
m_Parent->GetScript()->OnWaypointReached(m_Parent, waypointNum);
if (!m_Path || waypointNum >= m_Path->pathWaypoints.size()) return;
const auto& commands = m_Path->pathWaypoints[waypointNum].commands;
for (const auto& [command, data] : commands) {
LOG_DEBUG("%s %s %s", StringifiedEnum::ToString(command).data(), m_Path->pathName.c_str(), data.c_str());
const auto dataSplit = GeneralUtils::SplitString(data, ',');
switch (command) {
case eWaypointCommandType::INVALID: break;
case eWaypointCommandType::BOUNCE: break;
case eWaypointCommandType::STOP: Pause(); break;
case eWaypointCommandType::GROUP_EMOTE: break;
case eWaypointCommandType::SET_VARIABLE: break; // Empty in the client
case eWaypointCommandType::CAST_SKILL: {
const auto skill = GeneralUtils::TryParse<uint32_t>(data);
if (skill) {
auto* const skillComponent = m_Parent->GetComponent<SkillComponent>();
if (skillComponent) skillComponent->CastSkill(skill.value());
}
break;
}
case eWaypointCommandType::EQUIP_INVENTORY: {
auto* const inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
if (inventoryComponent) {
// items should always exist
auto* const item = inventoryComponent->GetInventory(eInventoryType::ITEMS)->FindItemBySlot(0);
if (item) inventoryComponent->EquipItem(item);
}
break;
}
case eWaypointCommandType::UNEQUIP_INVENTORY: {
auto* const inventoryComponent = m_Parent->GetComponent<InventoryComponent>();
if (inventoryComponent) {
// items should always exist
auto* const item = inventoryComponent->GetInventory(eInventoryType::ITEMS)->FindItemBySlot(0);
if (item) inventoryComponent->UnEquipItem(item);
}
break;
}
case eWaypointCommandType::DELAY: {
// Pause(GeneralUtils::TryParse<float>(data).value_or(0.0f));
break;
}
case eWaypointCommandType::EMOTE: {
// m_Delay = RenderComponent::GetAnimationTime(m_Parent, data);
// const auto emoteID = GeneralUtils::TryParse<uint32_t>(data);
// if (emoteID) GameMessages::SendPlayEmote(m_Parent->GetObjectID(), emoteID.value(), LWOOBJID_EMPTY, UNASSIGNED_SYSTEM_ADDRESS);
break;
}
case eWaypointCommandType::TELEPORT: break;
case eWaypointCommandType::PATH_SPEED: m_BaseSpeed = GetBaseSpeed(m_Parent->GetLOT()) * GeneralUtils::TryParse<float>(data).value_or(1.0f); break;
case eWaypointCommandType::REMOVE_NPC: break;
case eWaypointCommandType::CHANGE_WAYPOINT: SetPath(dataSplit[0]); break;
case eWaypointCommandType::DELETE_SELF: break;
case eWaypointCommandType::KILL_SELF: m_Parent->Smash(); break;
case eWaypointCommandType::SPAWN_OBJECT: break;
case eWaypointCommandType::PLAY_SOUND: break;
}
}
}
bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
@@ -453,12 +533,13 @@ bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf
movementInfo.PushDebug<AMFBoolValue>("Lock Rotation") = m_LockRotation;
movementInfo.PushDebug<AMFBoolValue>("Paused") = m_Paused;
movementInfo.PushDebug<AMFDoubleValue>("Pulling To Point") = m_PullingToPoint;
movementInfo.PushDebug<AMFBoolValue>("At Final Waypoint") = m_AtFinalWaypoint;
auto& pullPointInfo = movementInfo.PushDebug("Pull Point");
pullPointInfo.PushDebug<AMFDoubleValue>("X") = m_PullPoint.x;
pullPointInfo.PushDebug<AMFDoubleValue>("Y") = m_PullPoint.y;
pullPointInfo.PushDebug<AMFDoubleValue>("Z") = m_PullPoint.z;
// movementInfo.PushDebug<AMFDoubleValue>("Delay") = m_Delay;
auto& waypoints = movementInfo.PushDebug("Interpolated Waypoints");
@@ -483,5 +564,25 @@ bool MovementAIComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInf
pathCopy.pop();
}
movementInfo.PushDebug<AMFStringValue>("Followed Target") = std::to_string(m_FollowedTarget);
return true;
}
void MovementAIComponent::FollowTarget(const LWOOBJID target) {
if (target == LWOOBJID_EMPTY) {
m_FollowedTarget = target;
return;
}
GameMessages::GetPosition getPos;
if (!getPos.Send(target)) {
LOG("Tried to follow target %llu but they don't exist", target);
m_FollowedTarget = LWOOBJID_EMPTY;
return;
}
m_FollowedTarget = target;
SetMaxSpeed(1.0f);
m_CurrentSpeed = 1.0f;
SetDestination(getPos.pos);
}

View File

@@ -31,27 +31,27 @@ struct MovementAIInfo {
/**
* The radius that the entity can wander in
*/
float wanderRadius;
float wanderRadius{};
/**
* The speed at which the entity wanders
*/
float wanderSpeed;
float wanderSpeed{};
/**
* This is only used for the emotes
*/
float wanderChance;
float wanderChance{};
/**
* The min amount of delay before wandering
*/
float wanderDelayMin;
float wanderDelayMin{};
/**
* The max amount of delay before wandering
*/
float wanderDelayMax;
float wanderDelayMax{};
};
/**
@@ -212,8 +212,18 @@ public:
bool IsPaused() const { return m_Paused; }
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
bool HasPath() const { return m_Path != nullptr; }
void FollowTarget(const LWOOBJID target);
private:
/**
* @brief
* Runs the commands on a waypoint if a path exists
*/
void RunWaypointCommands(uint32_t waypointNum);
/**
* Sets the current position of the entity
* @param value the position to set
@@ -329,6 +339,8 @@ private:
// The number of waypoints that were on the path in the call to SetPath
uint32_t m_CurrentPathWaypointCount{ 0 };
LWOOBJID m_FollowedTarget{ LWOOBJID_EMPTY };
};
#endif // MOVEMENTAICOMPONENT_H

View File

@@ -12,7 +12,6 @@
#include "eReplicaComponentType.h"
#include "PhysicsComponent.h"
class LDFBaseData;
class Entity;
class dpEntity;
enum class ePhysicsEffectType : uint32_t ;

View File

@@ -346,10 +346,7 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N
info.spawner = nullptr;
info.spawnerID = spawnerID;
info.spawnerNodeID = 0;
for (auto* setting : item->GetConfig()) {
info.settings.push_back(setting->Copy());
}
info.settings = item->GetConfig();
Entity* newEntity = Game::entityManager->CreateEntity(info);
if (newEntity != nullptr) {
@@ -393,11 +390,11 @@ void PropertyManagementComponent::UpdateModelPosition(const LWOOBJID id, const N
auto* spawner = Game::zoneManager->GetSpawner(spawnerId);
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"modelBehaviors", 0));
info.nodes[0]->config.push_back(new LDFData<LWOOBJID>(u"userModelID", info.spawnerID));
info.nodes[0]->config.push_back(new LDFData<int>(u"modelType", 2));
info.nodes[0]->config.push_back(new LDFData<bool>(u"propertyObjectID", true));
info.nodes[0]->config.push_back(new LDFData<int>(u"componentWhitelist", 1));
info.nodes[0]->config.Insert<LWOOBJID>(u"modelBehaviors", 0);
info.nodes[0]->config.Insert<LWOOBJID>(u"userModelID", info.spawnerID);
info.nodes[0]->config.Insert<int>(u"modelType", 2);
info.nodes[0]->config.Insert<bool>(u"propertyObjectID", true);
info.nodes[0]->config.Insert<int>(u"componentWhitelist", 1);
auto* model = spawner->Spawn();
auto* modelComponent = model->GetComponent<ModelComponent>();
@@ -476,28 +473,19 @@ void PropertyManagementComponent::DeleteModel(const LWOOBJID id, const int delet
if (model->GetLOT() == 14) {
//add it to the inv
std::vector<LDFBaseData*> settings;
LwoNameValue actualConfig;
//fill our settings with BBB gurbage
LDFBaseData* ldfBlueprintID = new LDFData<LWOOBJID>(u"blueprintid", model->GetVar<LWOOBJID>(u"blueprintid"));
LDFBaseData* userModelDesc = new LDFData<std::u16string>(u"userModelDesc", u"A cool model you made!");
LDFBaseData* userModelHasBhvr = new LDFData<bool>(u"userModelHasBhvr", false);
LDFBaseData* userModelID = new LDFData<LWOOBJID>(u"userModelID", model->GetVar<LWOOBJID>(u"userModelID"));
LDFBaseData* userModelMod = new LDFData<bool>(u"userModelMod", false);
LDFBaseData* userModelName = new LDFData<std::u16string>(u"userModelName", u"My Cool Model");
LDFBaseData* propertyObjectID = new LDFData<bool>(u"userModelOpt", true);
LDFBaseData* modelType = new LDFData<int>(u"userModelPhysicsType", 2);
actualConfig.Insert(u"blueprintid", model->GetVar<LWOOBJID>(u"blueprintid"));
actualConfig.Insert(u"userModelDesc", u"A cool model you made!");
actualConfig.Insert(u"userModelHasBhvr", false);
actualConfig.Insert(u"userModelID", model->GetVar<LWOOBJID>(u"userModelID"));
actualConfig.Insert(u"userModelMod", false);
actualConfig.Insert(u"userModelName", u"My Cool Model");
actualConfig.Insert(u"userModelOpt", true);
actualConfig.Insert(u"userModelPhysicsType", 2);
settings.push_back(ldfBlueprintID);
settings.push_back(userModelDesc);
settings.push_back(userModelHasBhvr);
settings.push_back(userModelID);
settings.push_back(userModelMod);
settings.push_back(userModelName);
settings.push_back(propertyObjectID);
settings.push_back(modelType);
inventoryComponent->AddItem(6662, 1, eLootSourceType::DELETION, eInventoryType::MODELS_IN_BBB, settings, LWOOBJID_EMPTY, false, false, spawnerId);
inventoryComponent->AddItem(6662, 1, eLootSourceType::DELETION, eInventoryType::MODELS_IN_BBB, actualConfig, LWOOBJID_EMPTY, false, false, spawnerId);
auto* item = inventoryComponent->FindItemBySubKey(spawnerId);
if (item == nullptr) {
@@ -615,23 +603,23 @@ void PropertyManagementComponent::Load() {
info.spawnerID = databaseModel.id;
std::vector<LDFBaseData*> settings;
LwoNameValue& settings = node->config;
//BBB property models need to have extra stuff set for them:
if (databaseModel.lot == 14) {
LWOOBJID blueprintID = databaseModel.ugcId;
settings.push_back(new LDFData<LWOOBJID>(u"blueprintid", blueprintID));
settings.push_back(new LDFData<int>(u"componentWhitelist", 1));
settings.push_back(new LDFData<int>(u"modelType", 2));
settings.push_back(new LDFData<bool>(u"propertyObjectID", true));
settings.push_back(new LDFData<LWOOBJID>(u"userModelID", databaseModel.id));
settings.Insert<LWOOBJID>(u"blueprintid", blueprintID);
settings.Insert<int>(u"componentWhitelist", 1);
settings.Insert<int>(u"modelType", 2);
settings.Insert<bool>(u"propertyObjectID", true);
settings.Insert<LWOOBJID>(u"userModelID", databaseModel.id);
} else {
settings.push_back(new LDFData<int>(u"modelType", 2));
settings.push_back(new LDFData<LWOOBJID>(u"userModelID", databaseModel.id));
settings.push_back(new LDFData<LWOOBJID>(u"modelBehaviors", 0));
settings.push_back(new LDFData<bool>(u"propertyObjectID", true));
settings.push_back(new LDFData<int>(u"componentWhitelist", 1));
settings.Insert<int>(u"modelType", 2);
settings.Insert<LWOOBJID>(u"userModelID", databaseModel.id);
settings.Insert<LWOOBJID>(u"modelBehaviors", 0);
settings.Insert<bool>(u"propertyObjectID", true);
settings.Insert<int>(u"componentWhitelist", 1);
}
std::ostringstream userModelBehavior;
@@ -646,9 +634,7 @@ void PropertyManagementComponent::Load() {
firstAdded = true;
}
settings.push_back(new LDFData<std::string>(u"userModelBehaviors", userModelBehavior.str()));
node->config = settings;
settings.Insert<std::string>(u"userModelBehaviors", userModelBehavior.str());
const auto spawnerId = Game::zoneManager->MakeSpawner(info);

View File

@@ -4,6 +4,8 @@
#include "ControllablePhysicsComponent.h"
#include "EntityManager.h"
#include "SimplePhysicsComponent.h"
#include "Amf3.h"
#include "dpShapeSphere.h"
const std::unordered_set<LWOOBJID> ProximityMonitorComponent::m_EmptyObjectSet = {};
@@ -12,6 +14,7 @@ ProximityMonitorComponent::ProximityMonitorComponent(Entity* parent, const int32
SetProximityRadius(radiusSmall, "rocketSmall");
SetProximityRadius(radiusLarge, "rocketLarge");
}
RegisterMsg(&ProximityMonitorComponent::OnGetObjectReportInfo);
}
ProximityMonitorComponent::~ProximityMonitorComponent() {
@@ -60,6 +63,31 @@ bool ProximityMonitorComponent::IsInProximity(const std::string& name, LWOOBJID
return collisions.contains(objectID);
}
bool ProximityMonitorComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
auto& proxInfo = reportInfo.info->PushDebug("Proximity Monitor");
for (const auto& [name, entity] : m_ProximitiesData) {
if (!entity) continue;
auto& proxAmf = proxInfo.PushDebug(name);
const auto* const shape = entity->GetShape();
if (shape && shape->GetShapeType() == dpShapeType::Sphere) {
const auto* const sphere = static_cast<const dpShapeSphere*>(shape);
proxAmf.PushDebug<AMFDoubleValue>("Radius") = sphere->GetRadius();
}
proxAmf.PushDebug<AMFBoolValue>("Sleeping") = entity->GetSleeping();
proxAmf.PushDebug<AMFDoubleValue>("Scale") = entity->GetScale();
proxAmf.PushDebug<AMFBoolValue>("Gargantuan") = entity->GetIsGargantuan();
proxAmf.PushDebug<AMFBoolValue>("Static") = entity->GetIsStatic();
proxAmf.PushDebug("Position").PushDebug(entity->GetPosition());
proxAmf.PushDebug("Rotation").PushDebug(entity->GetRotation());
auto& collidingAmf = proxAmf.PushDebug("Colliding Objects");
for (const auto& colliding : entity->GetCurrentlyCollidingObjects()) {
collidingAmf.PushDebug(std::to_string(colliding));
}
}
return true;
}
void ProximityMonitorComponent::Update(float deltaTime) {
for (const auto& prox : m_ProximitiesData) {
if (!prox.second) continue;

View File

@@ -64,6 +64,7 @@ public:
private:
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
/**
* All the proximity sensors for this component, indexed by name
*/

View File

@@ -22,6 +22,8 @@
#include "RenderComponent.h"
#include "CppScripts.h"
#include "StringifiedEnum.h"
#include "Amf3.h"
QuickBuildComponent::QuickBuildComponent(Entity* const entity, const int32_t componentID) : Component{ entity, componentID } {
std::u16string checkPreconditions = entity->GetVar<std::u16string>(u"CheckPrecondition");
@@ -42,6 +44,7 @@ QuickBuildComponent::QuickBuildComponent(Entity* const entity, const int32_t com
}
SpawnActivator();
RegisterMsg(&QuickBuildComponent::OnGetObjectReportInfo);
}
QuickBuildComponent::~QuickBuildComponent() {
@@ -568,3 +571,30 @@ void QuickBuildComponent::AddQuickBuildCompleteCallback(const std::function<void
void QuickBuildComponent::AddQuickBuildStateCallback(const std::function<void(eQuickBuildState state)>& callback) {
m_QuickBuildStateCallbacks.push_back(callback);
}
bool QuickBuildComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo) {
auto& quickbuild = reportInfo.info->PushDebug("Quick Build");
quickbuild.PushDebug<AMFStringValue>("State") = StringifiedEnum::ToString(m_State).data();
quickbuild.PushDebug<AMFDoubleValue>("Timer") = m_Timer;
quickbuild.PushDebug<AMFDoubleValue>("TimerIncomplete") = m_TimerIncomplete;
quickbuild.PushDebug("ActivatorPosition").PushDebug(m_ActivatorPosition);
quickbuild.PushDebug<AMFStringValue>("ActivatorId") = std::to_string(m_ActivatorId);
quickbuild.PushDebug<AMFBoolValue>("ShowResetEffect") = m_ShowResetEffect;
quickbuild.PushDebug<AMFDoubleValue>("Taken") = m_Taken;
quickbuild.PushDebug<AMFDoubleValue>("ResetTime") = m_ResetTime;
quickbuild.PushDebug<AMFDoubleValue>("CompleteTime") = m_CompleteTime;
quickbuild.PushDebug<AMFIntValue>("TakeImagination") = m_TakeImagination;
quickbuild.PushDebug<AMFBoolValue>("Interruptible") = m_Interruptible;
quickbuild.PushDebug<AMFBoolValue>("SelfActivator") = m_SelfActivator;
auto& modules = quickbuild.PushDebug("CustomModules");
for (const auto cmodule : m_CustomModules) modules.PushDebug<AMFIntValue>("Module") = cmodule;
quickbuild.PushDebug<AMFIntValue>("ActivityId") = m_ActivityId;
quickbuild.PushDebug<AMFIntValue>("PostImaginationCost") = m_PostImaginationCost;
quickbuild.PushDebug<AMFDoubleValue>("TimeBeforeSmash") = m_TimeBeforeSmash;
quickbuild.PushDebug<AMFDoubleValue>("TimeBeforeDrain") = m_TimeBeforeDrain;
quickbuild.PushDebug<AMFIntValue>("DrainedImagination") = m_DrainedImagination;
quickbuild.PushDebug<AMFBoolValue>("RepositionPlayer") = m_RepositionPlayer;
quickbuild.PushDebug<AMFDoubleValue>("SoftTimer") = m_SoftTimer;
quickbuild.PushDebug<AMFStringValue>("Builder") = std::to_string(m_Builder);
return true;
}

View File

@@ -261,6 +261,8 @@ public:
m_StateDirty = true;
}
private:
bool OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& reportInfo);
/**
* Whether or not the quickbuild state has been changed since we last serialized it.
*/

View File

@@ -26,6 +26,7 @@
#include "dZoneManager.h"
#include "CDActivitiesTable.h"
#include "eStateChangeType.h"
#include <ranges>
#include <ctime>
#ifndef M_PI
@@ -57,6 +58,7 @@ RacingControlComponent::RacingControlComponent(Entity* parent, const int32_t com
CDActivitiesTable* activitiesTable = CDClientManager::GetTable<CDActivitiesTable>();
std::vector<CDActivities> activities = activitiesTable->Query([=](CDActivities entry) {return (entry.instanceMapID == worldID); });
for (CDActivities activity : activities) m_ActivityID = activity.ActivityID;
RegisterMsg(&RacingControlComponent::MsgConfigureRacingControl);
}
RacingControlComponent::~RacingControlComponent() {}
@@ -178,11 +180,10 @@ void RacingControlComponent::LoadPlayerVehicle(Entity* player,
moduleAssemblyComponent->SetSubKey(item->GetSubKey());
moduleAssemblyComponent->SetUseOptionalParts(false);
for (auto* config : item->GetConfig()) {
if (config->GetKey() == u"assemblyPartLOTs") {
moduleAssemblyComponent->SetAssemblyPartsLOTs(
GeneralUtils::ASCIIToUTF16(config->GetValueAsString()));
}
const auto& lnv = item->GetConfig().values;
const auto itr = lnv.find(u"assemblyPartLOTs");
if (itr != lnv.end()) {
moduleAssemblyComponent->SetAssemblyPartsLOTs(GeneralUtils::ASCIIToUTF16(itr->second->GetValueAsString()));
}
}
@@ -871,10 +872,10 @@ void RacingControlComponent::Update(float deltaTime) {
}
}
void RacingControlComponent::MsgConfigureRacingControl(const GameMessages::ConfigureRacingControl& msg) {
for (const auto& dataUnique : msg.racingSettings) {
bool RacingControlComponent::MsgConfigureRacingControl(const GameMessages::ConfigureRacingControl& msg) {
for (const auto& dataUnique : msg.racingSettings | std::views::values) {
if (!dataUnique) continue;
const auto* const data = dataUnique.get();
const auto* const data = dataUnique.get();
if (data->GetKey() == u"Race_PathName" && data->GetValueType() == LDF_TYPE_UTF_16) {
m_PathName = static_cast<const LDFData<std::u16string>*>(data)->GetValue();
} else if (data->GetKey() == u"activityID" && data->GetValueType() == LDF_TYPE_S32) {
@@ -886,4 +887,5 @@ void RacingControlComponent::MsgConfigureRacingControl(const GameMessages::Confi
m_MinimumPlayersForGroupAchievements = static_cast<const LDFData<int32_t>*>(data)->GetValue();
}
}
return true;
}

View File

@@ -152,7 +152,7 @@ public:
*/
RacingPlayerInfo* GetPlayerData(LWOOBJID playerID);
void MsgConfigureRacingControl(const GameMessages::ConfigureRacingControl& msg);
bool MsgConfigureRacingControl(const GameMessages::ConfigureRacingControl& msg);
private:

View File

@@ -8,6 +8,8 @@
#include "GameMessages.h"
#include "Amf3.h"
#include <ranges>
ScriptComponent::ScriptComponent(Entity* parent, const int32_t componentID, const std::string& scriptName, bool serialized, bool client) : Component(parent, componentID) {
m_Serialized = serialized;
m_Client = client;
@@ -20,7 +22,7 @@ ScriptComponent::ScriptComponent(Entity* parent, const int32_t componentID, cons
void ScriptComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitialUpdate) {
if (bIsInitialUpdate) {
const auto& networkSettings = m_Parent->GetNetworkSettings();
auto hasNetworkSettings = !networkSettings.empty();
auto hasNetworkSettings = !networkSettings.values.empty();
outBitStream.Write(hasNetworkSettings);
if (hasNetworkSettings) {
@@ -28,9 +30,9 @@ void ScriptComponent::Serialize(RakNet::BitStream& outBitStream, bool bIsInitial
// First write the most inner LDF data
RakNet::BitStream ldfData;
ldfData.Write<uint8_t>(0);
ldfData.Write<uint32_t>(networkSettings.size());
ldfData.Write<uint32_t>(networkSettings.values.size());
for (auto* networkSetting : networkSettings) {
for (const auto& networkSetting : networkSettings.values | std::views::values) {
networkSetting->WriteToPacket(ldfData);
}
@@ -56,7 +58,7 @@ bool ScriptComponent::OnGetObjectReportInfo(GameMessages::GetObjectReportInfo& r
auto& scriptInfo = reportInfo.info->PushDebug("Script");
scriptInfo.PushDebug<AMFStringValue>("Script Name") = m_ScriptName.empty() ? "None" : m_ScriptName;
auto& networkSettings = scriptInfo.PushDebug("Network Settings");
for (const auto* const setting : m_Parent->GetNetworkSettings()) {
for (const auto& setting : m_Parent->GetNetworkSettings().values | std::views::values) {
networkSettings.PushDebug<AMFStringValue>(GeneralUtils::UTF16ToWTF8(setting->GetKey())) = setting->GetValueAsString();
}

View File

@@ -125,8 +125,7 @@ void SkillComponent::SyncPlayerProjectile(const LWOOBJID projectileId, RakNet::B
this->m_managedProjectiles.erase(this->m_managedProjectiles.begin() + index);
GameMessages::ActivityNotify notify;
notify.notification.push_back( std::make_unique<LDFData<int32_t>>(u"shot_done", sync_entry.skillId));
notify.notification.Insert<int32_t>(u"shot_done", sync_entry.skillId);
m_Parent->OnActivityNotify(notify);
}