Compare commits

...

7 Commits

Author SHA1 Message Date
David Markowitz
ece4119834 fix: wrong activity id for fb race 2025-11-14 22:35:40 -08:00
David Markowitz
ca60787055 fix: ffa -> shared loot for activities (#1925) 2025-10-26 01:01:21 -07:00
David Markowitz
396dcb0465 feat: add logger feature to log on function entry and exit (#1924)
* feat: add logger feature to log on function entry and exit

* i didnt save the file
2025-10-25 14:53:49 -05:00
David Markowitz
6e545eb1b9 Update Loot.cpp (#1923) 2025-10-24 21:53:00 -07:00
David Markowitz
46aac016fd fix: unintended stopping (#1922) 2025-10-23 23:41:16 -05:00
David Markowitz
83823fa64f fix: resurrect not available for non-gms (#1919) 2025-10-20 23:05:22 -07:00
David Markowitz
0dd504c803 feat: behavior states (#1918) 2025-10-20 01:16:36 -05:00
16 changed files with 111 additions and 27 deletions

View File

@@ -477,7 +477,7 @@ TeamData* TeamContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
}
}
newTeam->lootFlag = 1;
newTeam->lootFlag = 0;
TeamStatusUpdate(newTeam);

View File

@@ -96,3 +96,17 @@ bool Logger::GetLogToConsole() const {
}
return toReturn;
}
FuncEntry::FuncEntry(const char* funcName, const char* fileName, const uint32_t line) {
m_FuncName = funcName;
if (!m_FuncName) m_FuncName = "Unknown";
m_Line = line;
m_FileName = fileName;
LOG("--> %s::%s:%i", m_FileName, m_FuncName, m_Line);
}
FuncEntry::~FuncEntry() {
if (!m_FuncName || !m_FileName) return;
LOG("<-- %s::%s:%i", m_FileName, m_FuncName, m_Line);
}

View File

@@ -32,6 +32,19 @@ constexpr const char* GetFileNameFromAbsolutePath(const char* path) {
#define LOG(message, ...) do { auto str_ = FILENAME_AND_LINE; Game::logger->Log(str_, message, ##__VA_ARGS__); } while(0)
#define LOG_DEBUG(message, ...) do { auto str_ = FILENAME_AND_LINE; Game::logger->LogDebug(str_, message, ##__VA_ARGS__); } while(0)
// Place this right at the start of a function. Will log a message when called and then once you leave the function.
#define LOG_ENTRY auto str_ = GetFileNameFromAbsolutePath(__FILE__); FuncEntry funcEntry_(__FUNCTION__, str_, __LINE__)
class FuncEntry {
public:
FuncEntry(const char* funcName, const char* fileName, const uint32_t line);
~FuncEntry();
private:
const char* m_FuncName = nullptr;
const char* m_FileName = nullptr;
uint32_t m_Line = 0;
};
// Writer class for writing data to files.
class Writer {
public:

View File

@@ -10,7 +10,9 @@ void AndBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bitStream,
}
void AndBehavior::Calculate(BehaviorContext* context, RakNet::BitStream& bitStream, const BehaviorBranchContext branch) {
LOG_ENTRY;
for (auto* behavior : this->m_behaviors) {
LOG("%i calculating %i", m_behaviorId, behavior->GetBehaviorID());
behavior->Calculate(context, bitStream, branch);
}
}

View File

@@ -95,4 +95,6 @@ public:
Behavior& operator=(const Behavior& other) = default;
Behavior& operator=(Behavior&& other) = default;
uint32_t GetBehaviorID() const { return m_behaviorId; }
};

View File

@@ -29,19 +29,22 @@ ModelComponent::ModelComponent(Entity* parent, const int32_t componentID) : Comp
bool ModelComponent::OnResetModelToDefaults(GameMessages::GameMsg& msg) {
auto& reset = static_cast<GameMessages::ResetModelToDefaults&>(msg);
for (auto& behavior : m_Behaviors) behavior.HandleMsg(reset);
GameMessages::UnSmash unsmash;
unsmash.target = GetParent()->GetObjectID();
unsmash.duration = 0.0f;
unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS);
if (reset.bResetBehaviors) for (auto& behavior : m_Behaviors) behavior.HandleMsg(reset);
m_Parent->SetPosition(m_OriginalPosition);
m_Parent->SetRotation(m_OriginalRotation);
if (reset.bUnSmash) {
GameMessages::UnSmash unsmash;
unsmash.target = GetParent()->GetObjectID();
unsmash.duration = 0.0f;
unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS);
m_NumActiveUnSmash = 0;
}
if (reset.bResetPos) m_Parent->SetPosition(m_OriginalPosition);
if (reset.bResetRot) m_Parent->SetRotation(m_OriginalRotation);
m_Parent->SetVelocity(NiPoint3Constant::ZERO);
m_Speed = 3.0f;
m_NumListeningInteract = 0;
m_NumActiveUnSmash = 0;
m_NumActiveAttack = 0;
GameMessages::SetFaction set{};

View File

@@ -848,6 +848,11 @@ namespace GameMessages {
struct ResetModelToDefaults : public GameMsg {
ResetModelToDefaults() : GameMsg(MessageType::Game::RESET_MODEL_TO_DEFAULTS) {}
bool bResetPos{ true };
bool bResetRot{ true };
bool bUnSmash{ true };
bool bResetBehaviors{ true };
};
struct EmotePlayed : public GameMsg {

View File

@@ -5,6 +5,7 @@
#include "ControlBehaviorMsgs.h"
#include "tinyxml2.h"
#include "ModelComponent.h"
#include "StringifiedEnum.h"
#include <ranges>
@@ -178,13 +179,32 @@ void PropertyBehavior::Deserialize(const tinyxml2::XMLElement& behavior) {
}
void PropertyBehavior::Update(float deltaTime, ModelComponent& modelComponent) {
for (auto& state : m_States | std::views::values) state.Update(deltaTime, modelComponent);
auto& activeState = GetActiveState();
UpdateResult updateResult{};
activeState.Update(deltaTime, modelComponent, updateResult);
if (updateResult.newState.has_value() && updateResult.newState.value() != m_ActiveState) {
LOG("Behavior %llu is changing from state %s to %s", m_BehaviorId, StringifiedEnum::ToString(m_ActiveState).data(), StringifiedEnum::ToString(updateResult.newState.value()).data());
GameMessages::ResetModelToDefaults resetMsg{};
resetMsg.bResetPos = false;
resetMsg.bResetRot = false;
resetMsg.bUnSmash = false;
modelComponent.OnResetModelToDefaults(resetMsg);
HandleMsg(resetMsg);
m_ActiveState = updateResult.newState.value();
}
}
void PropertyBehavior::OnChatMessageReceived(const std::string& sMessage) {
for (auto& state : m_States | std::views::values) state.OnChatMessageReceived(sMessage);
auto& activeState = GetActiveState();
activeState.OnChatMessageReceived(sMessage);
}
void PropertyBehavior::OnHit() {
for (auto& state : m_States | std::views::values) state.OnHit();
auto& activeState = GetActiveState();
activeState.OnHit();
}
State& PropertyBehavior::GetActiveState() {
DluAssert(m_States.contains(m_ActiveState));
return m_States[m_ActiveState];
}

View File

@@ -1,18 +1,23 @@
#ifndef __PROPERTYBEHAVIOR__H__
#define __PROPERTYBEHAVIOR__H__
#include "BehaviorStates.h"
#include "State.h"
#include <optional>
namespace tinyxml2 {
class XMLElement;
}
enum class BehaviorState : uint32_t;
class AMFArrayValue;
class BehaviorMessageBase;
class ModelComponent;
struct UpdateResult {
std::optional<BehaviorState> newState;
};
/**
* Represents the Entity of a Property Behavior and holds data associated with the behavior
*/
@@ -45,6 +50,7 @@ public:
void OnHit();
private:
State& GetActiveState();
// The current active behavior state. Behaviors can only be in ONE state at a time.
BehaviorState m_ActiveState;

View File

@@ -163,8 +163,8 @@ void State::Deserialize(const tinyxml2::XMLElement& state) {
}
}
void State::Update(float deltaTime, ModelComponent& modelComponent) {
for (auto& strip : m_Strips) strip.Update(deltaTime, modelComponent);
void State::Update(float deltaTime, ModelComponent& modelComponent, UpdateResult& updateResult) {
for (auto& strip : m_Strips) strip.Update(deltaTime, modelComponent, updateResult);
}
void State::OnChatMessageReceived(const std::string& sMessage) {

View File

@@ -9,6 +9,7 @@ namespace tinyxml2 {
class AMFArrayValue;
class ModelComponent;
struct UpdateResult;
class State {
public:
@@ -21,7 +22,7 @@ public:
void Serialize(tinyxml2::XMLElement& state) const;
void Deserialize(const tinyxml2::XMLElement& state);
void Update(float deltaTime, ModelComponent& modelComponent);
void Update(float deltaTime, ModelComponent& modelComponent, UpdateResult& updateResult);
void OnChatMessageReceived(const std::string& sMessage);
void OnHit();

View File

@@ -160,13 +160,14 @@ void Strip::SpawnDrop(LOT dropLOT, Entity& entity) {
}
}
void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent, UpdateResult& updateResult) {
auto& entity = *modelComponent.GetParent();
auto& nextAction = GetNextAction();
auto number = nextAction.GetValueParameterDouble();
auto valueStr = nextAction.GetValueParameterString();
auto numberAsInt = static_cast<int32_t>(number);
auto nextActionType = GetNextAction().GetType();
LOG_DEBUG("Processing Strip Action: %s with number %.2f and string %s", nextActionType.data(), number, valueStr.data());
// TODO replace with switch case and nextActionType with enum
/* BEGIN Move */
@@ -223,7 +224,8 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
unsmash.Send(UNASSIGNED_SYSTEM_ADDRESS);
modelComponent.AddUnSmash();
m_PausedTime = number;
// since it may take time for the message to relay to clients
m_PausedTime = number + 0.5f;
} else if (nextActionType == "Wait") {
m_PausedTime = number;
} else if (nextActionType == "Chat") {
@@ -258,6 +260,21 @@ void Strip::ProcNormalAction(float deltaTime, ModelComponent& modelComponent) {
for (; numberAsInt > 0; numberAsInt--) SpawnDrop(6431, entity); // 1 Armor powerup
}
/* END Gameplay */
/* BEGIN StateMachine */
else if (nextActionType == "ChangeStateHome") {
updateResult.newState = BehaviorState::HOME_STATE;
} else if (nextActionType == "ChangeStateCircle") {
updateResult.newState = BehaviorState::CIRCLE_STATE;
} else if (nextActionType == "ChangeStateSquare") {
updateResult.newState = BehaviorState::SQUARE_STATE;
} else if (nextActionType == "ChangeStateDiamond") {
updateResult.newState = BehaviorState::DIAMOND_STATE;
} else if (nextActionType == "ChangeStateTriangle") {
updateResult.newState = BehaviorState::TRIANGLE_STATE;
} else if (nextActionType == "ChangeStateStar") {
updateResult.newState = BehaviorState::STAR_STATE;
}
/* END StateMachine*/
else {
static std::set<std::string> g_WarnedActions;
if (!g_WarnedActions.contains(nextActionType.data())) {
@@ -330,7 +347,7 @@ bool Strip::CheckMovement(float deltaTime, ModelComponent& modelComponent) {
return moveFinished;
}
void Strip::Update(float deltaTime, ModelComponent& modelComponent) {
void Strip::Update(float deltaTime, ModelComponent& modelComponent, UpdateResult& updateResult) {
// No point in running a strip with only one action.
// Strips are also designed to have 2 actions or more to run.
if (!HasMinimumActions()) return;
@@ -354,9 +371,9 @@ void Strip::Update(float deltaTime, ModelComponent& modelComponent) {
// Check for trigger blocks and if not a trigger block proc this blocks action
if (m_NextActionIndex == 0) {
LOG("Behavior strip started %s", nextAction.GetType().data());
if (nextAction.GetType() == "OnInteract") {
modelComponent.AddInteract();
} else if (nextAction.GetType() == "OnChat") {
// logic here if needed
} else if (nextAction.GetType() == "OnAttack") {
@@ -365,7 +382,7 @@ void Strip::Update(float deltaTime, ModelComponent& modelComponent) {
Game::entityManager->SerializeEntity(entity);
m_WaitingForAction = true;
} else { // should be a normal block
ProcNormalAction(deltaTime, modelComponent);
ProcNormalAction(deltaTime, modelComponent, updateResult);
}
}

View File

@@ -12,6 +12,7 @@ namespace tinyxml2 {
class AMFArrayValue;
class ModelComponent;
struct UpdateResult;
class Strip {
public:
@@ -33,9 +34,9 @@ public:
// Checks the movement logic for whether or not to proceed
// Returns true if the movement can continue, false if it needs to wait more.
bool CheckMovement(float deltaTime, ModelComponent& modelComponent);
void Update(float deltaTime, ModelComponent& modelComponent);
void Update(float deltaTime, ModelComponent& modelComponent, UpdateResult& updateResult);
void SpawnDrop(LOT dropLOT, Entity& entity);
void ProcNormalAction(float deltaTime, ModelComponent& modelComponent);
void ProcNormalAction(float deltaTime, ModelComponent& modelComponent, UpdateResult& updateResult);
void RemoveStates(ModelComponent& modelComponent) const;
// 2 actions are required for strips to work

View File

@@ -355,7 +355,7 @@ void DropLoot(Entity* player, const LWOOBJID source, const std::map<LOT, LootDro
// Coin roll is divided up between the members, rounded up, then dropped for each player
const uint32_t coinRoll = static_cast<uint32_t>(minCoins + GeneralUtils::GenerateRandomNumber<float>(0, 1) * (maxCoins - minCoins));
const auto droppedCoins = team ? std::ceil(coinRoll / team->members.size()) : coinRoll;
const auto droppedCoins = team ? std::ceil(static_cast<float>(coinRoll) / team->members.size()) : coinRoll;
if (team) {
for (auto member : team->members) {
GameMessages::DropClientLoot lootMsg{};

View File

@@ -1052,7 +1052,7 @@ void SlashCommandHandler::Startup() {
.info = "Resurrects the player",
.aliases = { "resurrect" },
.handle = GMZeroCommands::Resurrect,
.requiredLevel = eGameMasterLevel::CIVILIAN
.requiredLevel = eGameMasterLevel::DEVELOPER
};
RegisterCommand(ResurrectCommand);

View File

@@ -20,7 +20,7 @@ void NsRaceServer::OnStartup(Entity* self) {
raceSet.push_back(make_unique<LDFData<std::u16string>>(u"Race_PathName", u"MainPath"));
raceSet.push_back(make_unique<LDFData<int32_t>>(u"Current_Lap", 1));
raceSet.push_back(make_unique<LDFData<int32_t>>(u"Number_of_Laps", 3));
raceSet.push_back(make_unique<LDFData<int32_t>>(u"activityID", 42));
raceSet.push_back(make_unique<LDFData<int32_t>>(u"activityID", 60));
raceSet.push_back(make_unique<LDFData<int32_t>>(u"Place_1", 100));
raceSet.push_back(make_unique<LDFData<int32_t>>(u"Place_2", 90));