From 11d7455cad316340c71f0ae060e4e1104a0cdea1 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 10 Nov 2026 13:36:25 -0600 Subject: [PATCH] fix(shooting gallery): moving platform targets run on their mover, as live sg_cannon.lua puts a moving platform target (the subs and the kraken) on its path with SetCurrentPath{ pathName, startPoint = 0 }; its SetPathingSpeed is read by nothing on a moving platform, so these run their paths at the waypoints' speeds (10, 15 and 35 in the zone), as the client's mover does. Live constructed them with the path and the mover waiting at waypoint 0 (9) and serialized the mover at each waypoint (9, then 2). They had a movement AI and a made up PlatformResync instead, and the kraken a speed chosen to look right, which goes back to the script's. PlatformResync's constructor that overrode its arguments for a few LOTs (the FV tree platform 9483 among them) has no user left and is gone; a resync request is answered by movers only. Co-Authored-By: Claude Opus 5.5 --- dGame/dGameMessages/MovementMessages.cpp | 41 ++++--------------- dGame/dGameMessages/MovementMessages.h | 5 --- .../ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp | 40 ++++++++++-------- .../RemainingMessagesTests.cpp | 11 ++++- 4 files changed, 40 insertions(+), 57 deletions(-) diff --git a/dGame/dGameMessages/MovementMessages.cpp b/dGame/dGameMessages/MovementMessages.cpp index cede9c002..2056430fd 100644 --- a/dGame/dGameMessages/MovementMessages.cpp +++ b/dGame/dGameMessages/MovementMessages.cpp @@ -50,26 +50,6 @@ namespace GameMessages { return true; } - PlatformResync::PlatformResync(const Entity& entity, bool bStopAtDesiredWaypoint, int32_t iIndex, int32_t iDesiredWaypointIndex, - int32_t nextIndex, eMovementPlatformState movementState, bool special) : PlatformResync() { - const auto lot = entity.GetLOT(); - - if (lot == 12341 || lot == 5027 || lot == 5028 || lot == 14335 || lot == 14447 || lot == 14449 || lot == 11306 || lot == 11308 || lot == 9483) { - iDesiredWaypointIndex = (lot == 11306 || lot == 11308) ? 1 : 0; - iIndex = lot == 9483 ? 1 : 0; - nextIndex = lot == 9483 && !special ? 1 : 0; - bStopAtDesiredWaypoint = true; - movementState = lot == 9483 && !special ? eMovementPlatformState::Stopped : eMovementPlatformState::Stationary; - } - - target = entity.GetObjectID(); - this->bStopAtDesiredWaypoint = bStopAtDesiredWaypoint; - this->iIndex = iIndex; - this->iDesiredWaypointIndex = iDesiredWaypointIndex; - this->iNextIndex = nextIndex; - this->eState = movementState; - } - void PlatformResync::Serialize(RakNet::BitStream& bitStream) const { bitStream.Write(bReverse); bitStream.Write(bStopAtDesiredWaypoint); @@ -121,21 +101,18 @@ namespace GameMessages { } void RequestPlatformResync::Handle(Entity& entity, const SystemAddress& sysAddr) { - // A mover answers with where the server has it (live: travelling 2 with its percent and position, or 25 at a - // waypoint with the time waited) + // A mover answers with where the server has it (live: travelling 2 with its percent and position, or waiting at + // a waypoint with the time waited) auto* const platform = entity.GetComponent(); - if (platform && platform->IsMover()) { - PlatformResync resync; - resync.target = entity.GetObjectID(); - platform->FillResync(resync); - resync.Send(sysAddr); + if (!platform || !platform->IsMover()) { + // Nothing else has waypoints to resync to: the client's simple mover reads them from its own two point path + // (LWOPlatformSimpleMover::Resync 0x00bf9ec0, a crash without one) and would set off on a travelling state return; } - // A platform without a path has no waypoints to resync to; the client's simple mover reads them anyway and - // crashes (LWOPlatformSimpleMover::Resync 0x00bf9ec0 -> Path::GetWaypointAtIndex on a null path) - if (platform && !platform->HasPath()) return; - if (entity.GetLOT() == 6267 || entity.GetLOT() == 16141) return; - PlatformResync(entity).Send(sysAddr); + PlatformResync resync; + resync.target = entity.GetObjectID(); + platform->FillResync(resync); + resync.Send(sysAddr); } void OrientToAngle::Serialize(RakNet::BitStream& bitStream) const { diff --git a/dGame/dGameMessages/MovementMessages.h b/dGame/dGameMessages/MovementMessages.h index a1a8524e4..11ad03097 100644 --- a/dGame/dGameMessages/MovementMessages.h +++ b/dGame/dGameMessages/MovementMessages.h @@ -49,11 +49,6 @@ namespace GameMessages { // Server -> client, to one client (UNASSIGNED broadcasts). struct PlatformResync : public NetGameMsg { PlatformResync() : NetGameMsg(MessageType::Game::PLATFORM_RESYNC) {} - // What DLU sends for a platform entity: the arguments, overridden for a few LOTs whose paths only have a - // start and an end (special is for the FV tree platform). - PlatformResync(const Entity& entity, bool bStopAtDesiredWaypoint = false, - int32_t iIndex = 0, int32_t iDesiredWaypointIndex = 1, int32_t nextIndex = 1, - eMovementPlatformState movementState = eMovementPlatformState::Moving, bool special = false); void Serialize(RakNet::BitStream& bitStream) const override; bool Deserialize(RakNet::BitStream& bitStream) override; diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index 2a889eb86..b7172380d 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -11,6 +11,7 @@ #include "CharacterComponent.h" #include "SimplePhysicsComponent.h" #include "MovementAIComponent.h" +#include "MovingPlatformComponent.h" #include "MissionComponent.h" #include "Loot.h" #include "InventoryComponent.h" @@ -295,7 +296,17 @@ void SGCannon::DoSpawnTimerFunc(Entity* self, const std::string& name) { auto* enemy = Game::entityManager->CreateEntity(info, nullptr, self); - auto* movementAI = enemy->AddComponent(-1, MovementAIInfo{}); + // sg_cannon.lua (onChildLoaded): a moving platform target gets SetPathingSpeed, which nothing on a moving platform + // reads, and SetCurrentPath{ pathName, startPoint = 0 }: its mover runs the path at the waypoints' speeds, as the + // client's does. Live constructed these targets with the path and the mover waiting at waypoint 0 (9), and + // serialized the mover at each waypoint. The other targets follow their waypoints. + auto* const movingPlatform = toSpawn.isMovingPlatform ? enemy->GetComponent() : nullptr; + MovementAIComponent* movementAI = nullptr; + if (movingPlatform) { + movingPlatform->SetCurrentPath(path->pathName, 0); + } else { + movementAI = enemy->AddComponent(-1, MovementAIInfo{}); + } auto* simplePhysicsComponent = enemy->GetComponent(); if (simplePhysicsComponent) { simplePhysicsComponent->SetPhysicsMotionState(4); @@ -303,20 +314,21 @@ void SGCannon::DoSpawnTimerFunc(Entity* self, const std::string& name) { Game::entityManager->ConstructEntity(enemy); - movementAI->SetMaxSpeed(toSpawn.initialSpeed); - movementAI->SetCurrentSpeed(toSpawn.initialSpeed); - movementAI->SetHaltDistance(0.0f); + if (movementAI) { + movementAI->SetMaxSpeed(toSpawn.initialSpeed); + movementAI->SetCurrentSpeed(toSpawn.initialSpeed); + movementAI->SetHaltDistance(0.0f); - movementAI->SetPath(path->pathWaypoints); + movementAI->SetPath(path->pathWaypoints); + } enemy->AddDieCallback([this, self, enemy, name]() { RegisterHit(self, enemy, name); }); - // Save the enemy and tell it to start pathing + // Save the enemy if (enemy != nullptr) { const_cast&>(self->GetVar>(SpawnedObjects)).push_back(enemy->GetObjectID()); - GameMessages::PlatformResync(*enemy).Send(UNASSIGNED_SYSTEM_ADDRESS); } } } @@ -1104,22 +1116,14 @@ std::vector> SGCannon::GetWaves() { 1.0, false, true }, - // Ness temp fix for dlu where speeds are set to 7 to match a speed closer to live while we work on movingplatform components. + // Ness { std::vector { "Wave_1_Ness_1", "Wave_1_Ness_2", "Wave_2_Ness_1" }, 2565, 10.0, 15.0, true, 10.0, 15.0, - 7.0, 10000, false, 0.0, 7.0, - 7.0, true, true + 2.0, 10000, false, 0.0, 1.0, + 1.0, true, true }, - // // Ness - // { - // std::vector { "Wave_1_Ness_1", "Wave_1_Ness_2", "Wave_2_Ness_1" }, - // 2565, 10.0, 15.0, true, 10.0, 15.0, - // 2.0, 10000, false, 0.0, 1.0, - // 1.0, true, true - // }, - // Friendly 1 { std::vector { "Wave_3_FShip_1", "Wave_3_FShip_2" }, diff --git a/tests/dGameTests/dGameMessagesTests/RemainingMessagesTests.cpp b/tests/dGameTests/dGameMessagesTests/RemainingMessagesTests.cpp index accbdf551..307adb6c9 100644 --- a/tests/dGameTests/dGameMessagesTests/RemainingMessagesTests.cpp +++ b/tests/dGameTests/dGameMessagesTests/RemainingMessagesTests.cpp @@ -89,14 +89,21 @@ TEST_F(RemainingMessagesTests, TeleportMatchesLegacy) { } TEST_F(RemainingMessagesTests, StartPathingAndPlatformResyncMatchLegacy) { - for (const LOT lot : { 999, 12341, 9483, 11306, 6267 }) { + // The legacy sender overrode its arguments for a few LOTs; the message has no such cases, so only others compare + for (const LOT lot : { 999, 6267 }) { info.lot = lot; Entity entity(0x1000000000000001LL, info); ExpectSameAsLegacyBroadcast([&] { LegacyGameMessages::SendStartPathing(&entity); }, [&] { GameMessages::StartPathing msg; msg.target = entity.GetObjectID(); return msg; }()); for (uint32_t mask = 0; mask < 4; mask++) { for (const auto state : { eMovementPlatformState::Moving, eMovementPlatformState::Stationary, eMovementPlatformState::Stopped }) { for (const int32_t index : { 0, 1, 5 }) { - GameMessages::PlatformResync msg(entity, Bit(mask, 0), index, index + 1, index + 2, state, Bit(mask, 1)); + GameMessages::PlatformResync msg; + msg.target = entity.GetObjectID(); + msg.bStopAtDesiredWaypoint = Bit(mask, 0); + msg.iIndex = index; + msg.iDesiredWaypointIndex = index + 1; + msg.iNextIndex = index + 2; + msg.eState = state; ExpectSameAsLegacy([&](const SystemAddress& address) { LegacyGameMessages::SendPlatformResync(&entity, address, Bit(mask, 0), index, index + 1, index + 2, state, Bit(mask, 1)); }, msg);