mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
fix(racing): put racers going the wrong way back on the track
The client works out on its own when its racer goes the wrong way and shows a 6 second countdown, but it only moves the car when the server sends RacingSetPlayerResetInfo, which DLU never did for this. The server now follows the reset planes the way the client's LWORacingControlComponent does (1.10.64): each path waypoint is a plane facing along its rotation; the racer starts between planes 0 and 1, moves forward when in front of the upcoming plane and back when behind the last one (CheckUpcomingResetPlane @ 0x00c7edf0, CheckLastResetPlane @ 0x00c7f1a0, CrossResetPlaneBackward @ 0x00cba380). Driving back through a second plane in a row starts the client's countdown (UpdateWrongWayCount @ 0x00be5c10, 6 seconds); going forward through a plane ends it. When it runs out, the racer gets the same reset as an unsmashed reset: reset info for their furthest point and RacingResetPlayerToLastReset. Resets sent for smashes keep the planes in step as well. Fixes issue 764. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -371,6 +371,13 @@ void RacingControlComponent::OnRequestDie(Entity* player, const std::u16string&
|
||||
resetInfo.respawnPos = racingPlayer.respawnPosition;
|
||||
resetInfo.upcomingPlane = racingPlayer.respawnIndex + 1;
|
||||
resetInfo.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
for (auto& current : m_RacingPlayers) {
|
||||
if (current.playerID != racingPlayer.playerID) continue;
|
||||
current.lastPlane = resetInfo.furthestResetPlane;
|
||||
current.upcomingPlane = resetInfo.upcomingPlane;
|
||||
current.wrongWayCount = 0;
|
||||
current.wrongWayTime = -1.0f;
|
||||
}
|
||||
|
||||
DestroyableComponent::Resurrect(*vehicle);
|
||||
auto* destroyableComponent = vehicle->GetComponent<DestroyableComponent>();
|
||||
@@ -392,6 +399,10 @@ void RacingControlComponent::OnRequestDie(Entity* player, const std::u16string&
|
||||
resetInfo.respawnPos = racingPlayer.respawnPosition;
|
||||
resetInfo.upcomingPlane = racingPlayer.respawnIndex + 1;
|
||||
resetInfo.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
racingPlayer.lastPlane = resetInfo.furthestResetPlane;
|
||||
racingPlayer.upcomingPlane = resetInfo.upcomingPlane;
|
||||
racingPlayer.wrongWayCount = 0;
|
||||
racingPlayer.wrongWayTime = -1.0f;
|
||||
|
||||
GameMessages::RacingResetPlayerToLastReset resetPlayer;
|
||||
resetPlayer.target = m_Parent->GetObjectID();
|
||||
@@ -804,6 +815,8 @@ void RacingControlComponent::Update(float deltaTime) {
|
||||
|
||||
if (m_Finished != 0) Game::entityManager->SerializeEntity(m_Parent);
|
||||
|
||||
if (path && player.lap < m_NumberOfLaps) UpdateWrongWay(player, *vehicle, *path, deltaTime);
|
||||
|
||||
// Loop through all the waypoints and see if the player has reached a
|
||||
// new checkpoint
|
||||
uint32_t respawnIndex = 0;
|
||||
@@ -944,3 +957,70 @@ bool RacingControlComponent::MsgConfigureRacingControl(const GameMessages::Confi
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RacingControlComponent::StepResetPlanes(RacingPlayerInfo& player, const std::vector<PathWaypoint>& waypoints, const NiPoint3& position) {
|
||||
// How long the client counts down (its "UpdateRaceWrongWay" timer, 6 in 1.10.64) before a wrong way racer should
|
||||
// be put back
|
||||
constexpr float WRONG_WAY_SECONDS = 6.0f;
|
||||
constexpr float EPSILON = 1e-5f;
|
||||
|
||||
const auto count = static_cast<uint32_t>(waypoints.size());
|
||||
if (count == 0) return;
|
||||
|
||||
// Which side of a reset plane the car is on: the plane goes through the waypoint, facing along its rotation
|
||||
// (ClassifyPointAgainstFacingPlane @ 010e2b80 in 1.10.64). 1 = in front, 2 = behind, 3 = on it.
|
||||
const auto side = [&](const uint32_t index) {
|
||||
const auto& waypoint = waypoints[index];
|
||||
const float distance = QuatUtils::Forward(waypoint.rotation).DotProduct(position - waypoint.position);
|
||||
return distance > EPSILON ? 1 : distance < -EPSILON ? 2 : 3;
|
||||
};
|
||||
|
||||
// Behind the last plane: driven back through it (LWORacingControlComponent::CheckLastResetPlane @ 00c7f1a0, CrossResetPlaneBackward @ 00cba380). The second plane in a row makes the
|
||||
// client show its wrong way countdown (UpdateWrongWayCount @ 00be5c10).
|
||||
bool wentBack = false;
|
||||
for (uint32_t guard = 0; guard < count && player.lastPlane < count && side(player.lastPlane) == 2; guard++) {
|
||||
wentBack = true;
|
||||
player.wrongWayCount++;
|
||||
if (player.wrongWayCount == 2) player.wrongWayTime = WRONG_WAY_SECONDS;
|
||||
player.upcomingPlane = player.lastPlane;
|
||||
player.lastPlane = player.lastPlane == 0 ? count - 1 : player.lastPlane - 1;
|
||||
}
|
||||
if (wentBack) return;
|
||||
|
||||
// In front of the upcoming plane: driven forward through it, which ends going the wrong way (CheckUpcomingResetPlane @ 00c7edf0)
|
||||
for (uint32_t guard = 0; guard < count && player.upcomingPlane < count && side(player.upcomingPlane) == 1; guard++) {
|
||||
player.wrongWayCount = 0;
|
||||
player.wrongWayTime = -1.0f;
|
||||
player.lastPlane = player.upcomingPlane;
|
||||
player.upcomingPlane = (player.upcomingPlane + 1) % count;
|
||||
}
|
||||
}
|
||||
|
||||
void RacingControlComponent::UpdateWrongWay(RacingPlayerInfo& player, const Entity& vehicle, const Path& path, const float deltaTime) {
|
||||
StepResetPlanes(player, path.pathWaypoints, vehicle.GetPosition());
|
||||
|
||||
if (player.wrongWayTime < 0.0f) return;
|
||||
player.wrongWayTime -= deltaTime;
|
||||
if (player.wrongWayTime > 0.0f) return;
|
||||
|
||||
// Out of time: back to the last reset point, the way a reset without a smash does it (the client moves the car,
|
||||
// clears its wrong way state and hides the countdown when it gets the reset info)
|
||||
GameMessages::RacingSetPlayerResetInfo resetInfo;
|
||||
resetInfo.target = m_Parent->GetObjectID();
|
||||
resetInfo.currentLap = static_cast<int32_t>(player.lap);
|
||||
resetInfo.furthestResetPlane = player.respawnIndex;
|
||||
resetInfo.playerID = player.playerID;
|
||||
resetInfo.respawnPos = player.respawnPosition;
|
||||
resetInfo.upcomingPlane = player.respawnIndex + 1;
|
||||
resetInfo.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
|
||||
GameMessages::RacingResetPlayerToLastReset resetPlayer;
|
||||
resetPlayer.target = m_Parent->GetObjectID();
|
||||
resetPlayer.playerID = player.playerID;
|
||||
resetPlayer.Send(UNASSIGNED_SYSTEM_ADDRESS);
|
||||
|
||||
player.lastPlane = resetInfo.furthestResetPlane;
|
||||
player.upcomingPlane = resetInfo.upcomingPlane;
|
||||
player.wrongWayCount = 0;
|
||||
player.wrongWayTime = -1.0f;
|
||||
}
|
||||
|
||||
@@ -99,11 +99,28 @@ struct RacingPlayerInfo {
|
||||
* Unused
|
||||
*/
|
||||
std::chrono::milliseconds raceTime;
|
||||
|
||||
/**
|
||||
* The reset planes (path waypoints) the player is between, and how many planes they have driven back through
|
||||
* since they last went forward through one, tracked the way the client tracks them
|
||||
* (LWORacingControlComponent::HandlePlayerAddedToRace @ 00cba0c0 starts them at 0 and 1).
|
||||
*/
|
||||
uint32_t lastPlane = 0;
|
||||
uint32_t upcomingPlane = 1;
|
||||
uint32_t wrongWayCount = 0;
|
||||
|
||||
/**
|
||||
* Seconds left until a player going the wrong way is put back on the track, negative when not counting down
|
||||
*/
|
||||
float wrongWayTime = -1.0f;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component that's attached to a manager entity in each race zone that loads player vehicles, keep scores, etc.
|
||||
*/
|
||||
struct Path;
|
||||
struct PathWaypoint;
|
||||
|
||||
class RacingControlComponent final : public ActivityComponent {
|
||||
public:
|
||||
static constexpr eReplicaComponentType ComponentType = eReplicaComponentType::RACING_CONTROL;
|
||||
@@ -119,6 +136,18 @@ public:
|
||||
*/
|
||||
void OnPlayerLoaded(Entity* player);
|
||||
|
||||
/**
|
||||
* Moves the player's reset planes as they drive through them and puts them back on the track when they have been
|
||||
* going the wrong way for as long as the client counts down.
|
||||
*/
|
||||
void UpdateWrongWay(RacingPlayerInfo& player, const Entity& vehicle, const Path& path, float deltaTime);
|
||||
|
||||
/**
|
||||
* Moves the player's reset planes for the car being at position, like the client does each frame, and starts the
|
||||
* wrong way countdown when they have driven back through a second plane.
|
||||
*/
|
||||
static void StepResetPlanes(RacingPlayerInfo& player, const std::vector<PathWaypoint>& waypoints, const NiPoint3& position);
|
||||
|
||||
/**
|
||||
* Initalize the player's vehicle.
|
||||
*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
set(DCOMPONENTS_TESTS
|
||||
"DeletionRestrictionTests.cpp"
|
||||
"VendorBuybackTests.cpp"
|
||||
"RacingWrongWayTests.cpp"
|
||||
"DestroyableComponentTests.cpp"
|
||||
"PetComponentTests.cpp"
|
||||
"SimplePhysicsComponentTests.cpp"
|
||||
|
||||
48
tests/dGameTests/dComponentsTests/RacingWrongWayTests.cpp
Normal file
48
tests/dGameTests/dComponentsTests/RacingWrongWayTests.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#include "GameMessages.h"
|
||||
#include "RacingControlComponent.h"
|
||||
#include "ZoneFile.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
namespace {
|
||||
// A straight track along +Z with planes every 10 units, all facing along the track
|
||||
std::vector<PathWaypoint> StraightTrack() {
|
||||
std::vector<PathWaypoint> waypoints(5);
|
||||
for (size_t i = 0; i < waypoints.size(); i++) waypoints[i].position = NiPoint3(0.0f, 0.0f, 10.0f * static_cast<float>(i));
|
||||
return waypoints;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(RacingWrongWayTests, DrivingForwardMovesThePlanes) {
|
||||
const auto track = StraightTrack();
|
||||
RacingPlayerInfo player{};
|
||||
RacingControlComponent::StepResetPlanes(player, track, NiPoint3(0.0f, 0.0f, 5.0f));
|
||||
EXPECT_EQ(player.lastPlane, 0u);
|
||||
EXPECT_EQ(player.upcomingPlane, 1u);
|
||||
RacingControlComponent::StepResetPlanes(player, track, NiPoint3(0.0f, 0.0f, 25.0f));
|
||||
EXPECT_EQ(player.lastPlane, 2u);
|
||||
EXPECT_EQ(player.upcomingPlane, 3u);
|
||||
EXPECT_EQ(player.wrongWayCount, 0u);
|
||||
EXPECT_LT(player.wrongWayTime, 0.0f);
|
||||
}
|
||||
|
||||
TEST(RacingWrongWayTests, TheSecondPlaneBackStartsTheCountdown) {
|
||||
const auto track = StraightTrack();
|
||||
RacingPlayerInfo player{};
|
||||
player.lastPlane = 3;
|
||||
player.upcomingPlane = 4;
|
||||
RacingControlComponent::StepResetPlanes(player, track, NiPoint3(0.0f, 0.0f, 25.0f));
|
||||
EXPECT_EQ(player.wrongWayCount, 1u);
|
||||
EXPECT_LT(player.wrongWayTime, 0.0f);
|
||||
RacingControlComponent::StepResetPlanes(player, track, NiPoint3(0.0f, 0.0f, 15.0f));
|
||||
EXPECT_EQ(player.wrongWayCount, 2u);
|
||||
EXPECT_EQ(player.lastPlane, 1u);
|
||||
EXPECT_EQ(player.upcomingPlane, 2u);
|
||||
EXPECT_FLOAT_EQ(player.wrongWayTime, 6.0f);
|
||||
|
||||
// Turning around and going through a plane again ends it
|
||||
RacingControlComponent::StepResetPlanes(player, track, NiPoint3(0.0f, 0.0f, 21.0f));
|
||||
EXPECT_EQ(player.wrongWayCount, 0u);
|
||||
EXPECT_LT(player.wrongWayTime, 0.0f);
|
||||
EXPECT_EQ(player.lastPlane, 2u);
|
||||
}
|
||||
Reference in New Issue
Block a user