2021-12-05 17:54:36 +00:00
/**
* Thanks to Simon for his early research on the racing system .
*/
# include "RacingControlComponent.h"
# include "CharacterComponent.h"
# include "DestroyableComponent.h"
# include "EntityManager.h"
# include "GameMessages.h"
# include "InventoryComponent.h"
# include "Item.h"
# include "MissionComponent.h"
# include "ModuleAssemblyComponent.h"
# include "PossessableComponent.h"
# include "PossessorComponent.h"
2022-02-05 11:28:17 +00:00
# include "eRacingTaskParam.h"
2021-12-05 17:54:36 +00:00
# include "Spawner.h"
2023-12-04 15:20:41 +00:00
# include "HavokVehiclePhysicsComponent.h"
2021-12-05 17:54:36 +00:00
# include "dServer.h"
# include "dZoneManager.h"
# include "dConfig.h"
2023-01-07 05:17:05 +00:00
# include "Loot.h"
2021-12-05 17:54:36 +00:00
# include "eMissionTaskType.h"
2023-05-09 08:42:11 +00:00
# include "LeaderboardManager.h"
2023-05-08 09:38:08 +00:00
# include "dZoneManager.h"
# include "CDActivitiesTable.h"
2024-05-26 02:59:15 +00:00
# include "eStateChangeType.h"
2023-10-21 23:31:55 +00:00
# include <ctime>
2021-12-05 17:54:36 +00:00
# ifndef M_PI
# define M_PI 3.14159265358979323846264338327950288
# endif
RacingControlComponent : : RacingControlComponent ( Entity * parent )
: Component ( parent ) {
m_PathName = u " MainPath " ;
m_RemainingLaps = 3 ;
m_LeadingPlayer = LWOOBJID_EMPTY ;
m_RaceBestTime = 0 ;
m_RaceBestLap = 0 ;
m_Started = false ;
m_StartTimer = 0 ;
m_Loaded = false ;
m_LoadedPlayers = 0 ;
m_LoadTimer = 0 ;
m_Finished = 0 ;
m_StartTime = 0 ;
m_EmptyTimer = 0 ;
m_SoloRacing = Game : : config - > GetValue ( " solo_racing " ) = = " 1 " ;
2022-07-28 13:39:57 +00:00
2023-05-08 09:38:08 +00:00
m_MainWorld = 1200 ;
2021-12-05 17:54:36 +00:00
const auto worldID = Game : : server - > GetZoneID ( ) ;
2023-11-24 09:46:26 +00:00
if ( Game : : zoneManager - > CheckIfAccessibleZone ( ( worldID / 10 ) * 10 ) ) m_MainWorld = ( worldID / 10 ) * 10 ;
2022-07-28 13:39:57 +00:00
2023-05-08 09:38:08 +00:00
m_ActivityID = 42 ;
2024-02-09 05:40:43 +00:00
CDActivitiesTable * activitiesTable = CDClientManager : : GetTable < CDActivitiesTable > ( ) ;
2023-05-08 09:38:08 +00:00
std : : vector < CDActivities > activities = activitiesTable - > Query ( [ = ] ( CDActivities entry ) { return ( entry . instanceMapID = = worldID ) ; } ) ;
for ( CDActivities activity : activities ) m_ActivityID = activity . ActivityID ;
2021-12-05 17:54:36 +00:00
}
RacingControlComponent : : ~ RacingControlComponent ( ) { }
void RacingControlComponent : : OnPlayerLoaded ( Entity * player ) {
2023-10-09 00:38:48 +00:00
auto * inventoryComponent = player - > GetComponent < InventoryComponent > ( ) ;
if ( ! inventoryComponent ) {
return ;
}
2021-12-05 17:54:36 +00:00
2023-10-09 00:38:48 +00:00
auto * vehicle = inventoryComponent - > FindItemByLot ( 8092 ) ;
2021-12-05 17:54:36 +00:00
2023-10-09 00:38:48 +00:00
// If the race has already started, send the player back to the main world.
if ( m_Loaded | | ! vehicle ) {
2024-01-12 17:39:51 +00:00
auto * characterComponent = player - > GetComponent < CharacterComponent > ( ) ;
if ( characterComponent ) characterComponent - > SendToZone ( m_MainWorld ) ;
2021-12-05 17:54:36 +00:00
return ;
}
m_LoadedPlayers + + ;
2024-05-26 02:59:15 +00:00
// not live accurate to stun the player but prevents them from using skills during the race that are not meant to be used.
GameMessages : : SendSetStunned ( player - > GetObjectID ( ) , eStateChangeType : : PUSH , player - > GetSystemAddress ( ) , LWOOBJID_EMPTY , true , true , true , true , true , true , true , true , true ) ;
2023-10-21 23:31:55 +00:00
LOG ( " Loading player %i " ,
2021-12-05 17:54:36 +00:00
m_LoadedPlayers ) ;
2023-10-09 00:38:48 +00:00
m_LobbyPlayers . push_back ( player - > GetObjectID ( ) ) ;
2021-12-05 17:54:36 +00:00
}
void RacingControlComponent : : LoadPlayerVehicle ( Entity * player ,
2022-12-22 13:16:18 +00:00
uint32_t positionNumber , bool initialLoad ) {
2021-12-05 17:54:36 +00:00
// Load the player's vehicle.
if ( player = = nullptr ) {
return ;
}
auto * inventoryComponent = player - > GetComponent < InventoryComponent > ( ) ;
if ( inventoryComponent = = nullptr ) {
return ;
}
// Find the player's vehicle.
auto * item = inventoryComponent - > FindItemByLot ( 8092 ) ;
if ( item = = nullptr ) {
2023-10-21 23:31:55 +00:00
LOG ( " Failed to find item " ) ;
2024-01-12 17:39:51 +00:00
auto * characterComponent = player - > GetComponent < CharacterComponent > ( ) ;
if ( characterComponent ) {
2023-10-09 00:38:48 +00:00
m_LoadedPlayers - - ;
2024-01-12 17:39:51 +00:00
characterComponent - > SendToZone ( m_MainWorld ) ;
2023-10-09 00:38:48 +00:00
}
2021-12-05 17:54:36 +00:00
return ;
2023-11-24 09:46:26 +00:00
2021-12-05 17:54:36 +00:00
}
// Calculate the vehicle's starting position.
2023-07-17 22:55:33 +00:00
auto * path = Game : : zoneManager - > GetZone ( ) - > GetPath (
2021-12-05 17:54:36 +00:00
GeneralUtils : : UTF16ToWTF8 ( m_PathName ) ) ;
2023-07-15 20:56:33 +00:00
auto spawnPointEntities = Game : : entityManager - > GetEntitiesByLOT ( 4843 ) ;
2024-01-29 07:53:12 +00:00
auto startPosition = NiPoint3Constant : : ZERO ;
auto startRotation = NiQuaternionConstant : : IDENTITY ;
2022-12-22 13:16:18 +00:00
const std : : string placementAsString = std : : to_string ( positionNumber ) ;
for ( auto entity : spawnPointEntities ) {
if ( ! entity ) continue ;
if ( entity - > GetVarAsString ( u " placement " ) = = placementAsString ) {
startPosition = entity - > GetPosition ( ) ;
startRotation = entity - > GetRotation ( ) ;
break ;
}
}
2021-12-05 17:54:36 +00:00
// Make sure the player is at the correct position.
GameMessages : : SendTeleport ( player - > GetObjectID ( ) , startPosition ,
2023-06-18 07:00:36 +00:00
startRotation , player - > GetSystemAddress ( ) , true ) ;
2021-12-05 17:54:36 +00:00
// Spawn the vehicle entity.
EntityInfo info { } ;
info . lot = 8092 ;
info . pos = startPosition ;
info . rot = startRotation ;
info . spawnerID = m_Parent - > GetObjectID ( ) ;
auto * carEntity =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > CreateEntity ( info , nullptr , m_Parent ) ;
2021-12-05 17:54:36 +00:00
// Make the vehicle a child of the racing controller.
m_Parent - > AddChild ( carEntity ) ;
auto * destroyableComponent =
carEntity - > GetComponent < DestroyableComponent > ( ) ;
// Setup the vehicle stats.
if ( destroyableComponent ! = nullptr ) {
destroyableComponent - > SetMaxImagination ( 60 ) ;
destroyableComponent - > SetImagination ( 0 ) ;
}
// Setup the vehicle as being possessed by the player.
auto * possessableComponent =
carEntity - > GetComponent < PossessableComponent > ( ) ;
if ( possessableComponent ! = nullptr ) {
possessableComponent - > SetPossessor ( player - > GetObjectID ( ) ) ;
}
// Load the vehicle's assemblyPartLOTs for display.
auto * moduleAssemblyComponent =
carEntity - > GetComponent < ModuleAssemblyComponent > ( ) ;
if ( moduleAssemblyComponent ) {
moduleAssemblyComponent - > SetSubKey ( item - > GetSubKey ( ) ) ;
moduleAssemblyComponent - > SetUseOptionalParts ( false ) ;
for ( auto * config : item - > GetConfig ( ) ) {
if ( config - > GetKey ( ) = = u " assemblyPartLOTs " ) {
moduleAssemblyComponent - > SetAssemblyPartsLOTs (
GeneralUtils : : ASCIIToUTF16 ( config - > GetValueAsString ( ) ) ) ;
}
}
}
// Setup the player as possessing the vehicle.
auto * possessorComponent = player - > GetComponent < PossessorComponent > ( ) ;
if ( possessorComponent ! = nullptr ) {
possessorComponent - > SetPossessable ( carEntity - > GetObjectID ( ) ) ;
2022-07-09 03:25:15 +00:00
possessorComponent - > SetPossessableType ( ePossessionType : : ATTACHED_VISIBLE ) ; // for racing it's always Attached_Visible
2021-12-05 17:54:36 +00:00
}
// Set the player's current activity as racing.
auto * characterComponent = player - > GetComponent < CharacterComponent > ( ) ;
if ( characterComponent ! = nullptr ) {
characterComponent - > SetIsRacing ( true ) ;
}
// Init the player's racing entry.
if ( initialLoad ) {
m_RacingPlayers . push_back (
{ player - > GetObjectID ( ) ,
carEntity - > GetObjectID ( ) ,
static_cast < uint32_t > ( m_RacingPlayers . size ( ) ) ,
false ,
{ } ,
startPosition ,
startRotation ,
0 ,
0 ,
0 ,
0 } ) ;
2023-11-24 09:46:26 +00:00
m_AllPlayersReady = false ;
2021-12-05 17:54:36 +00:00
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
// Construct and serialize everything when done.
2022-07-28 13:39:57 +00:00
2023-07-15 20:56:33 +00:00
Game : : entityManager - > ConstructEntity ( carEntity ) ;
Game : : entityManager - > SerializeEntity ( player ) ;
Game : : entityManager - > SerializeEntity ( m_Parent ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
GameMessages : : SendRacingSetPlayerResetInfo (
m_Parent - > GetObjectID ( ) , 0 , 0 , player - > GetObjectID ( ) , startPosition , 1 ,
UNASSIGNED_SYSTEM_ADDRESS ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
const auto playerID = player - > GetObjectID ( ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
// Reset the player to the start position during downtime, in case something
// went wrong.
m_Parent - > AddCallbackTimer ( 1 , [ this , playerID ] ( ) {
2023-07-15 20:56:33 +00:00
auto * player = Game : : entityManager - > GetEntity ( playerID ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
if ( player = = nullptr ) {
return ;
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
GameMessages : : SendRacingResetPlayerToLastReset (
m_Parent - > GetObjectID ( ) , playerID , UNASSIGNED_SYSTEM_ADDRESS ) ;
} ) ;
2022-07-28 13:39:57 +00:00
2022-01-24 22:02:56 +00:00
GameMessages : : SendSetJetPackMode ( player , false ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
// Set the vehicle's state.
GameMessages : : SendNotifyVehicleOfRacingObject ( carEntity - > GetObjectID ( ) ,
m_Parent - > GetObjectID ( ) ,
UNASSIGNED_SYSTEM_ADDRESS ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
GameMessages : : SendVehicleSetWheelLockState ( carEntity - > GetObjectID ( ) , false ,
initialLoad ,
UNASSIGNED_SYSTEM_ADDRESS ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
// Make sure everything has the correct position.
GameMessages : : SendTeleport ( player - > GetObjectID ( ) , startPosition ,
2023-06-18 07:00:36 +00:00
startRotation , player - > GetSystemAddress ( ) , true ) ;
2021-12-05 17:54:36 +00:00
GameMessages : : SendTeleport ( carEntity - > GetObjectID ( ) , startPosition ,
2023-06-18 07:00:36 +00:00
startRotation , player - > GetSystemAddress ( ) , true ) ;
2021-12-05 17:54:36 +00:00
}
void RacingControlComponent : : OnRacingClientReady ( Entity * player ) {
// Notify the other players that this player is ready.
for ( auto & racingPlayer : m_RacingPlayers ) {
if ( racingPlayer . playerID ! = player - > GetObjectID ( ) ) {
if ( racingPlayer . playerLoaded ) {
GameMessages : : SendRacingPlayerLoaded (
m_Parent - > GetObjectID ( ) , racingPlayer . playerID ,
racingPlayer . vehicleID , UNASSIGNED_SYSTEM_ADDRESS ) ;
}
continue ;
}
racingPlayer . playerLoaded = true ;
GameMessages : : SendRacingPlayerLoaded (
m_Parent - > GetObjectID ( ) , racingPlayer . playerID ,
racingPlayer . vehicleID , UNASSIGNED_SYSTEM_ADDRESS ) ;
}
2023-07-15 20:56:33 +00:00
Game : : entityManager - > SerializeEntity ( m_Parent ) ;
2021-12-05 17:54:36 +00:00
}
void RacingControlComponent : : OnRequestDie ( Entity * player ) {
// Sent by the client when they collide with something which should smash
// them.
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
for ( auto & racingPlayer : m_RacingPlayers ) {
if ( racingPlayer . playerID ! = player - > GetObjectID ( ) ) {
continue ;
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
auto * vehicle =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( racingPlayer . vehicleID ) ;
2022-07-28 13:39:57 +00:00
2023-03-05 20:34:59 +00:00
if ( ! vehicle ) return ;
2022-07-28 13:39:57 +00:00
2021-12-17 16:37:03 +00:00
if ( ! racingPlayer . noSmashOnReload ) {
racingPlayer . smashedTimes + + ;
2023-03-05 20:34:59 +00:00
GameMessages : : SendDie ( vehicle , vehicle - > GetObjectID ( ) , LWOOBJID_EMPTY , true ,
2023-05-02 22:39:21 +00:00
eKillType : : VIOLENT , u " " , 0 , 0 , 90.0f , false , true , 0 ) ;
2023-03-05 20:34:59 +00:00
auto * destroyableComponent = vehicle - > GetComponent < DestroyableComponent > ( ) ;
uint32_t respawnImagination = 0 ;
// Reset imagination to half its current value, rounded up to the nearest value divisible by 10, as it was done in live.
// Do not actually change the value yet. Do that on respawn.
if ( destroyableComponent ) {
respawnImagination = static_cast < int32_t > ( ceil ( destroyableComponent - > GetImagination ( ) / 2.0f / 10.0f ) ) * 10.0f ;
GameMessages : : SendSetResurrectRestoreValues ( vehicle , - 1 , - 1 , respawnImagination ) ;
}
2022-07-28 13:39:57 +00:00
2023-03-05 20:34:59 +00:00
// Respawn the player in 2 seconds, as was done in live. Not sure if this value is in a setting somewhere else...
2024-01-02 07:53:00 +00:00
vehicle - > AddCallbackTimer ( 2.0f , [ = , this ] ( ) {
2023-03-05 20:34:59 +00:00
if ( ! vehicle | | ! this - > m_Parent ) return ;
GameMessages : : SendRacingResetPlayerToLastReset (
m_Parent - > GetObjectID ( ) , racingPlayer . playerID ,
UNASSIGNED_SYSTEM_ADDRESS ) ;
GameMessages : : SendVehicleStopBoost ( vehicle , player - > GetSystemAddress ( ) , true ) ;
GameMessages : : SendRacingSetPlayerResetInfo (
m_Parent - > GetObjectID ( ) , racingPlayer . lap ,
racingPlayer . respawnIndex , player - > GetObjectID ( ) ,
racingPlayer . respawnPosition , racingPlayer . respawnIndex + 1 ,
UNASSIGNED_SYSTEM_ADDRESS ) ;
GameMessages : : SendResurrect ( vehicle ) ;
auto * destroyableComponent = vehicle - > GetComponent < DestroyableComponent > ( ) ;
// Reset imagination to half its current value, rounded up to the nearest value divisible by 10, as it was done in live.
if ( destroyableComponent ) destroyableComponent - > SetImagination ( respawnImagination ) ;
2023-07-15 20:56:33 +00:00
Game : : entityManager - > SerializeEntity ( vehicle ) ;
2023-11-24 09:46:26 +00:00
} ) ;
2023-03-05 20:34:59 +00:00
auto * characterComponent = player - > GetComponent < CharacterComponent > ( ) ;
if ( characterComponent ! = nullptr ) {
characterComponent - > UpdatePlayerStatistic ( RacingTimesWrecked ) ;
}
} else {
GameMessages : : SendRacingSetPlayerResetInfo (
m_Parent - > GetObjectID ( ) , racingPlayer . lap ,
racingPlayer . respawnIndex , player - > GetObjectID ( ) ,
racingPlayer . respawnPosition , racingPlayer . respawnIndex + 1 ,
UNASSIGNED_SYSTEM_ADDRESS ) ;
GameMessages : : SendRacingResetPlayerToLastReset (
m_Parent - > GetObjectID ( ) , racingPlayer . playerID ,
UNASSIGNED_SYSTEM_ADDRESS ) ;
2021-12-05 17:54:36 +00:00
}
}
}
void RacingControlComponent : : OnRacingPlayerInfoResetFinished ( Entity * player ) {
// When the player has respawned.
for ( auto & racingPlayer : m_RacingPlayers ) {
if ( racingPlayer . playerID ! = player - > GetObjectID ( ) ) {
continue ;
}
auto * vehicle =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( racingPlayer . vehicleID ) ;
2021-12-05 17:54:36 +00:00
if ( vehicle = = nullptr ) {
return ;
}
racingPlayer . noSmashOnReload = false ;
return ;
}
}
2023-05-09 05:40:00 +00:00
void RacingControlComponent : : HandleMessageBoxResponse ( Entity * player , int32_t button , const std : : string & id ) {
2021-12-05 17:54:36 +00:00
auto * data = GetPlayerData ( player - > GetObjectID ( ) ) ;
if ( data = = nullptr ) {
return ;
}
if ( id = = " rewardButton " ) {
2023-05-09 08:42:11 +00:00
if ( data - > collectedRewards ) return ;
2021-12-05 17:54:36 +00:00
data - > collectedRewards = true ;
// Calculate the score, different loot depending on player count
2023-07-22 00:37:31 +00:00
auto playersRating = m_LoadedPlayers ;
2023-11-24 09:46:26 +00:00
if ( m_LoadedPlayers = = 1 & & m_SoloRacing ) {
2023-07-22 00:37:31 +00:00
playersRating * = 2 ;
}
2021-12-05 17:54:36 +00:00
2023-11-24 09:46:26 +00:00
const auto score = playersRating * 10 + data - > finished ;
2023-10-09 20:33:22 +00:00
Loot : : GiveActivityLoot ( player , m_Parent , m_ActivityID , score ) ;
2021-12-05 17:54:36 +00:00
// Giving rewards
GameMessages : : SendNotifyRacingClient (
m_Parent - > GetObjectID ( ) , 2 , 0 , LWOOBJID_EMPTY , u " " ,
player - > GetObjectID ( ) , UNASSIGNED_SYSTEM_ADDRESS ) ;
2023-05-10 09:05:56 +00:00
} else if ( ( id = = " ACT_RACE_EXIT_THE_RACE? " | | id = = " Exit " ) & & button = = m_ActivityExitConfirm ) {
2023-07-15 20:56:33 +00:00
auto * vehicle = Game : : entityManager - > GetEntity ( data - > vehicleID ) ;
2021-12-05 17:54:36 +00:00
if ( vehicle = = nullptr ) {
return ;
}
// Exiting race
GameMessages : : SendNotifyRacingClient (
m_Parent - > GetObjectID ( ) , 3 , 0 , LWOOBJID_EMPTY , u " " ,
player - > GetObjectID ( ) , UNASSIGNED_SYSTEM_ADDRESS ) ;
2024-01-12 17:39:51 +00:00
auto * characterComponent = player - > GetComponent < CharacterComponent > ( ) ;
2021-12-05 17:54:36 +00:00
2024-01-12 17:39:51 +00:00
if ( characterComponent ) characterComponent - > SendToZone ( m_MainWorld ) ;
2021-12-05 17:54:36 +00:00
vehicle - > Kill ( ) ;
}
}
2024-02-27 07:25:44 +00:00
void RacingControlComponent : : Serialize ( RakNet : : BitStream & outBitStream , bool bIsInitialUpdate ) {
2021-12-05 17:54:36 +00:00
// BEGIN Scripted Activity
2024-02-27 07:25:44 +00:00
outBitStream . Write1 ( ) ;
2021-12-05 17:54:36 +00:00
2024-02-27 07:25:44 +00:00
outBitStream . Write < uint32_t > ( m_RacingPlayers . size ( ) ) ;
2021-12-05 17:54:36 +00:00
for ( const auto & player : m_RacingPlayers ) {
2024-02-27 07:25:44 +00:00
outBitStream . Write ( player . playerID ) ;
outBitStream . Write ( player . data [ 0 ] ) ;
if ( player . finished ! = 0 ) outBitStream . Write < float > ( player . raceTime ) ;
else outBitStream . Write ( player . data [ 1 ] ) ;
if ( player . finished ! = 0 ) outBitStream . Write < float > ( player . bestLapTime ) ;
else outBitStream . Write ( player . data [ 2 ] ) ;
if ( player . finished = = 1 ) outBitStream . Write < float > ( 1.0f ) ;
else outBitStream . Write ( player . data [ 3 ] ) ;
outBitStream . Write ( player . data [ 4 ] ) ;
outBitStream . Write ( player . data [ 5 ] ) ;
outBitStream . Write ( player . data [ 6 ] ) ;
outBitStream . Write ( player . data [ 7 ] ) ;
outBitStream . Write ( player . data [ 8 ] ) ;
outBitStream . Write ( player . data [ 9 ] ) ;
2021-12-05 17:54:36 +00:00
}
// END Scripted Activity
2024-02-27 07:25:44 +00:00
outBitStream . Write1 ( ) ;
outBitStream . Write < uint16_t > ( m_RacingPlayers . size ( ) ) ;
2021-12-05 17:54:36 +00:00
2024-02-27 07:25:44 +00:00
outBitStream . Write ( ! m_AllPlayersReady ) ;
2023-11-24 09:46:26 +00:00
if ( ! m_AllPlayersReady ) {
int32_t numReady = 0 ;
2021-12-05 17:54:36 +00:00
for ( const auto & player : m_RacingPlayers ) {
2024-02-27 07:25:44 +00:00
outBitStream . Write1 ( ) ; // Has more player data
outBitStream . Write ( player . playerID ) ;
outBitStream . Write ( player . vehicleID ) ;
outBitStream . Write ( player . playerIndex ) ;
outBitStream . Write ( player . playerLoaded ) ;
2023-11-24 09:46:26 +00:00
if ( player . playerLoaded ) numReady + + ;
2021-12-05 17:54:36 +00:00
}
2024-02-27 07:25:44 +00:00
outBitStream . Write0 ( ) ; // No more data
2023-11-24 09:46:26 +00:00
if ( numReady = = m_RacingPlayers . size ( ) ) m_AllPlayersReady = true ;
2021-12-05 17:54:36 +00:00
}
2024-02-27 07:25:44 +00:00
outBitStream . Write ( ! m_RacingPlayers . empty ( ) ) ;
2021-12-05 17:54:36 +00:00
if ( ! m_RacingPlayers . empty ( ) ) {
for ( const auto & player : m_RacingPlayers ) {
2023-11-24 09:46:26 +00:00
if ( player . finished = = 0 ) continue ;
2024-02-27 07:25:44 +00:00
outBitStream . Write1 ( ) ; // Has more date
2021-12-05 17:54:36 +00:00
2024-02-27 07:25:44 +00:00
outBitStream . Write ( player . playerID ) ;
outBitStream . Write ( player . finished ) ;
2021-12-05 17:54:36 +00:00
}
2024-02-27 07:25:44 +00:00
outBitStream . Write0 ( ) ; // No more data
2021-12-05 17:54:36 +00:00
}
2024-02-27 07:25:44 +00:00
outBitStream . Write ( bIsInitialUpdate ) ;
2023-11-24 09:46:26 +00:00
if ( bIsInitialUpdate ) {
2024-02-27 07:25:44 +00:00
outBitStream . Write ( m_RemainingLaps ) ;
outBitStream . Write < uint16_t > ( m_PathName . size ( ) ) ;
2023-11-24 09:46:26 +00:00
for ( const auto character : m_PathName ) {
2024-02-27 07:25:44 +00:00
outBitStream . Write ( character ) ;
2023-11-24 09:46:26 +00:00
}
2021-12-05 17:54:36 +00:00
}
2024-02-27 07:25:44 +00:00
outBitStream . Write ( ! m_RacingPlayers . empty ( ) ) ;
2023-11-24 09:46:26 +00:00
if ( ! m_RacingPlayers . empty ( ) ) {
for ( const auto & player : m_RacingPlayers ) {
if ( player . finished = = 0 ) continue ;
2024-02-27 07:25:44 +00:00
outBitStream . Write1 ( ) ; // Has more data
outBitStream . Write ( player . playerID ) ;
outBitStream . Write < float > ( player . bestLapTime ) ;
outBitStream . Write < float > ( player . raceTime ) ;
2023-11-24 09:46:26 +00:00
}
2021-12-05 17:54:36 +00:00
2024-02-27 07:25:44 +00:00
outBitStream . Write0 ( ) ; // No more data
2023-11-24 09:46:26 +00:00
}
2021-12-05 17:54:36 +00:00
}
RacingPlayerInfo * RacingControlComponent : : GetPlayerData ( LWOOBJID playerID ) {
for ( auto & player : m_RacingPlayers ) {
if ( player . playerID = = playerID ) {
return & player ;
}
}
return nullptr ;
}
void RacingControlComponent : : Update ( float deltaTime ) {
// This method is a mess.
// Pre-load routine
if ( ! m_Loaded ) {
// Check if any players has disconnected before loading in
for ( size_t i = 0 ; i < m_LobbyPlayers . size ( ) ; i + + ) {
auto * playerEntity =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( m_LobbyPlayers [ i ] ) ;
2021-12-05 17:54:36 +00:00
if ( playerEntity = = nullptr ) {
- - m_LoadedPlayers ;
m_LobbyPlayers . erase ( m_LobbyPlayers . begin ( ) + i ) ;
return ;
}
}
if ( m_LoadedPlayers > = 2 | | ( m_LoadedPlayers = = 1 & & m_SoloRacing ) ) {
m_LoadTimer + = deltaTime ;
} else {
m_EmptyTimer + = deltaTime ;
}
// If a player happens to be left alone for more then 30 seconds without
// anyone else loading in, send them back to the main world
if ( m_EmptyTimer > = 30 ) {
for ( const auto player : m_LobbyPlayers ) {
auto * playerEntity =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( player ) ;
2021-12-05 17:54:36 +00:00
if ( playerEntity = = nullptr ) {
continue ;
}
2024-01-12 17:39:51 +00:00
auto * characterComponent = playerEntity - > GetComponent < CharacterComponent > ( ) ;
2021-12-05 17:54:36 +00:00
2024-01-12 17:39:51 +00:00
if ( characterComponent ) characterComponent - > SendToZone ( m_MainWorld ) ;
2021-12-05 17:54:36 +00:00
}
m_LobbyPlayers . clear ( ) ;
}
// From the first 2 players loading in the rest have a max of 15 seconds
// to load in, can raise this if it's too low
if ( m_LoadTimer > = 15 ) {
2023-10-21 23:31:55 +00:00
LOG ( " Loading all players... " ) ;
2021-12-05 17:54:36 +00:00
2022-12-22 13:16:18 +00:00
for ( size_t positionNumber = 0 ; positionNumber < m_LobbyPlayers . size ( ) ; positionNumber + + ) {
2023-10-21 23:31:55 +00:00
LOG ( " Loading player now! " ) ;
2021-12-05 17:54:36 +00:00
auto * player =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( m_LobbyPlayers [ positionNumber ] ) ;
2021-12-05 17:54:36 +00:00
if ( player = = nullptr ) {
return ;
}
2022-07-28 13:39:57 +00:00
2023-10-21 23:31:55 +00:00
LOG ( " Loading player now NOW! " ) ;
2021-12-05 17:54:36 +00:00
2022-12-22 13:16:18 +00:00
LoadPlayerVehicle ( player , positionNumber + 1 , true ) ;
2021-12-05 17:54:36 +00:00
2023-11-24 09:46:26 +00:00
Game : : entityManager - > SerializeEntity ( m_Parent ) ;
2021-12-05 17:54:36 +00:00
}
m_Loaded = true ;
}
return ;
}
// The players who will be participating have loaded
if ( ! m_Started ) {
// Check if anyone has disconnected during this period
for ( size_t i = 0 ; i < m_RacingPlayers . size ( ) ; i + + ) {
2023-07-15 20:56:33 +00:00
auto * playerEntity = Game : : entityManager - > GetEntity (
2021-12-05 17:54:36 +00:00
m_RacingPlayers [ i ] . playerID ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
if ( playerEntity = = nullptr ) {
m_RacingPlayers . erase ( m_RacingPlayers . begin ( ) + i ) ;
- - m_LoadedPlayers ;
return ;
}
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
// If less then 2 players are left, send the rest back to the main world
if ( m_LoadedPlayers < 2 & & ! ( m_LoadedPlayers = = 1 & & m_SoloRacing ) ) {
for ( const auto player : m_LobbyPlayers ) {
auto * playerEntity =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( player ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
if ( playerEntity = = nullptr ) {
continue ;
}
2022-07-28 13:39:57 +00:00
2024-01-12 17:39:51 +00:00
auto * characterComponent = playerEntity - > GetComponent < CharacterComponent > ( ) ;
2022-07-28 13:39:57 +00:00
2024-01-12 17:39:51 +00:00
if ( characterComponent ) characterComponent - > SendToZone ( m_MainWorld ) ;
2021-12-05 17:54:36 +00:00
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
return ;
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
// Check if all players have send a ready message
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
int32_t readyPlayers = 0 ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
for ( const auto & player : m_RacingPlayers ) {
if ( player . playerLoaded ) {
+ + readyPlayers ;
}
}
if ( readyPlayers > = m_LoadedPlayers ) {
// Setup for racing
if ( m_StartTimer = = 0 ) {
GameMessages : : SendNotifyRacingClient (
m_Parent - > GetObjectID ( ) , 1 , 0 , LWOOBJID_EMPTY , u " " ,
LWOOBJID_EMPTY , UNASSIGNED_SYSTEM_ADDRESS ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
for ( const auto & player : m_RacingPlayers ) {
auto * vehicle =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( player . vehicleID ) ;
2021-12-05 17:54:36 +00:00
auto * playerEntity =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( player . playerID ) ;
2021-12-05 17:54:36 +00:00
if ( vehicle ! = nullptr & & playerEntity ! = nullptr ) {
GameMessages : : SendTeleport (
player . playerID , player . respawnPosition ,
player . respawnRotation ,
2023-06-18 07:00:36 +00:00
playerEntity - > GetSystemAddress ( ) , true ) ;
2021-12-05 17:54:36 +00:00
vehicle - > SetPosition ( player . respawnPosition ) ;
vehicle - > SetRotation ( player . respawnRotation ) ;
auto * destroyableComponent =
vehicle - > GetComponent < DestroyableComponent > ( ) ;
if ( destroyableComponent ! = nullptr ) {
destroyableComponent - > SetImagination ( 0 ) ;
}
2022-07-28 13:39:57 +00:00
2023-07-15 20:56:33 +00:00
Game : : entityManager - > SerializeEntity ( vehicle ) ;
Game : : entityManager - > SerializeEntity (
2021-12-05 17:54:36 +00:00
playerEntity ) ;
}
}
// Spawn imagination pickups
2023-07-17 22:55:33 +00:00
auto * minSpawner = Game : : zoneManager - > GetSpawnersByName (
2021-12-05 17:54:36 +00:00
" ImaginationSpawn_Min " ) [ 0 ] ;
2023-07-17 22:55:33 +00:00
auto * medSpawner = Game : : zoneManager - > GetSpawnersByName (
2021-12-05 17:54:36 +00:00
" ImaginationSpawn_Med " ) [ 0 ] ;
2023-07-17 22:55:33 +00:00
auto * maxSpawner = Game : : zoneManager - > GetSpawnersByName (
2021-12-05 17:54:36 +00:00
" ImaginationSpawn_Max " ) [ 0 ] ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
minSpawner - > Activate ( ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
if ( m_LoadedPlayers > 2 ) {
medSpawner - > Activate ( ) ;
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
if ( m_LoadedPlayers > 4 ) {
maxSpawner - > Activate ( ) ;
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
// Reset players to their start location, without smashing them
for ( auto & player : m_RacingPlayers ) {
auto * vehicleEntity =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( player . vehicleID ) ;
2021-12-05 17:54:36 +00:00
auto * playerEntity =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( player . playerID ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
if ( vehicleEntity = = nullptr | | playerEntity = = nullptr ) {
continue ;
}
player . noSmashOnReload = true ;
OnRequestDie ( playerEntity ) ;
}
}
// This 6 seconds seems to be hardcoded in the client, start race
// after that amount of time
else if ( m_StartTimer > = 6 ) {
// Activate the players movement
for ( auto & player : m_RacingPlayers ) {
auto * vehicleEntity =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( player . vehicleID ) ;
2021-12-05 17:54:36 +00:00
auto * playerEntity =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( player . playerID ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
if ( vehicleEntity = = nullptr | | playerEntity = = nullptr ) {
continue ;
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
GameMessages : : SendVehicleUnlockInput (
player . vehicleID , false , UNASSIGNED_SYSTEM_ADDRESS ) ;
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
// Start the race
GameMessages : : SendActivityStart ( m_Parent - > GetObjectID ( ) ,
UNASSIGNED_SYSTEM_ADDRESS ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
m_Started = true ;
2022-07-28 13:39:57 +00:00
2023-10-21 23:31:55 +00:00
LOG ( " Starting race " ) ;
2022-07-28 13:39:57 +00:00
2023-07-15 20:56:33 +00:00
Game : : entityManager - > SerializeEntity ( m_Parent ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
m_StartTime = std : : time ( nullptr ) ;
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
m_StartTimer + = deltaTime ;
} else {
m_StartTimer = 0 ;
}
return ;
}
// Race routines
2023-07-17 22:55:33 +00:00
auto * path = Game : : zoneManager - > GetZone ( ) - > GetPath (
2021-12-05 17:54:36 +00:00
GeneralUtils : : UTF16ToWTF8 ( m_PathName ) ) ;
for ( auto & player : m_RacingPlayers ) {
2023-07-15 20:56:33 +00:00
auto * vehicle = Game : : entityManager - > GetEntity ( player . vehicleID ) ;
2021-12-05 17:54:36 +00:00
auto * playerEntity =
2023-07-15 20:56:33 +00:00
Game : : entityManager - > GetEntity ( player . playerID ) ;
2021-12-05 17:54:36 +00:00
if ( vehicle = = nullptr | | playerEntity = = nullptr ) {
continue ;
}
const auto vehiclePosition = vehicle - > GetPosition ( ) ;
// If the player is this far below the map, safe to assume they should
// be smashed by death plane
if ( vehiclePosition . y < - 500 ) {
GameMessages : : SendDie ( vehicle , m_Parent - > GetObjectID ( ) ,
2023-05-02 22:39:21 +00:00
LWOOBJID_EMPTY , true , eKillType : : VIOLENT , u " " , 0 , 0 , 0 ,
2021-12-05 17:54:36 +00:00
true , false , 0 ) ;
OnRequestDie ( playerEntity ) ;
continue ;
}
2023-11-24 09:46:26 +00:00
if ( m_Finished ! = 0 ) Game : : entityManager - > SerializeEntity ( m_Parent ) ;
2021-12-05 17:54:36 +00:00
// Loop through all the waypoints and see if the player has reached a
// new checkpoint
uint32_t respawnIndex = 0 ;
for ( const auto & waypoint : path - > pathWaypoints ) {
if ( player . lap = = 3 ) {
break ;
}
if ( player . respawnIndex = = respawnIndex ) {
+ + respawnIndex ;
continue ;
}
const auto & position = waypoint . position ;
2023-12-28 04:18:20 +00:00
if ( std : : abs ( static_cast < int > ( respawnIndex ) - static_cast < int > ( player . respawnIndex ) ) > 10 & &
2021-12-05 17:54:36 +00:00
player . respawnIndex ! = path - > pathWaypoints . size ( ) - 1 ) {
+ + respawnIndex ;
continue ;
}
if ( Vector3 : : DistanceSquared ( position , vehiclePosition ) > 50 * 50 ) {
+ + respawnIndex ;
continue ;
}
// Only go upwards, except if we've lapped
// Not sure how we are supposed to check if they've reach a
// checkpoint, within 50 units seems safe
if ( ! ( respawnIndex > player . respawnIndex | |
player . respawnIndex = = path - > pathWaypoints . size ( ) - 1 ) ) {
+ + respawnIndex ;
continue ;
}
// Some offset up to make they don't fall through the terrain on a
// respawn, seems to fix itself to the track anyhow
2024-05-10 14:22:26 +00:00
if ( waypoint . racing . isResetNode ) {
player . respawnPosition = position + NiPoint3Constant : : UNIT_Y * 5 ;
player . respawnRotation = vehicle - > GetRotation ( ) ;
}
2021-12-05 17:54:36 +00:00
player . respawnIndex = respawnIndex ;
// Reached the start point, lapped
if ( respawnIndex = = 0 ) {
2022-07-28 14:27:06 +00:00
time_t lapTime = std : : time ( nullptr ) - ( player . lap = = 0 ? m_StartTime : player . lapTime ) ;
2021-12-05 17:54:36 +00:00
// Cheating check
if ( lapTime < 40 ) {
continue ;
}
player . lap + + ;
player . lapTime = std : : time ( nullptr ) ;
if ( player . bestLapTime = = 0 | | player . bestLapTime > lapTime ) {
player . bestLapTime = lapTime ;
2023-10-21 23:31:55 +00:00
LOG ( " Best lap time (%llu) " , lapTime ) ;
2021-12-05 17:54:36 +00:00
}
auto * missionComponent =
playerEntity - > GetComponent < MissionComponent > ( ) ;
if ( missionComponent ! = nullptr ) {
2022-02-05 11:28:17 +00:00
// Progress lap time tasks
2023-12-28 04:18:20 +00:00
missionComponent - > Progress ( eMissionTaskType : : RACING , ( lapTime ) * 1000 , static_cast < LWOOBJID > ( eRacingTaskParam : : LAP_TIME ) ) ;
2021-12-05 17:54:36 +00:00
if ( player . lap = = 3 ) {
m_Finished + + ;
player . finished = m_Finished ;
const auto raceTime =
( std : : time ( nullptr ) - m_StartTime ) ;
player . raceTime = raceTime ;
2023-10-21 23:31:55 +00:00
LOG ( " Completed time %llu, %llu " ,
2021-12-05 17:54:36 +00:00
raceTime , raceTime * 1000 ) ;
2023-07-22 06:18:51 +00:00
LeaderboardManager : : SaveScore ( playerEntity - > GetObjectID ( ) , m_ActivityID , static_cast < float > ( player . raceTime ) , static_cast < float > ( player . bestLapTime ) , static_cast < float > ( player . finished = = 1 ) ) ;
2021-12-05 17:54:36 +00:00
// Entire race time
2023-12-28 04:18:20 +00:00
missionComponent - > Progress ( eMissionTaskType : : RACING , ( raceTime ) * 1000 , static_cast < LWOOBJID > ( eRacingTaskParam : : TOTAL_TRACK_TIME ) ) ;
2021-12-05 17:54:36 +00:00
2024-06-11 09:29:25 +00:00
missionComponent - > Progress ( eMissionTaskType : : RACING , 0 , static_cast < LWOOBJID > ( eRacingTaskParam : : COMPETED_IN_RACE ) ) ; // Progress task for competing in a race
missionComponent - > Progress ( eMissionTaskType : : RACING , player . smashedTimes , static_cast < LWOOBJID > ( eRacingTaskParam : : SAFE_DRIVER ) ) ; // Finish a race without being smashed.
// If solo racing is enabled OR if there are 3 players in the race, progress placement tasks.
if ( m_SoloRacing | | m_RacingPlayers . size ( ) > 2 ) {
missionComponent - > Progress ( eMissionTaskType : : RACING , player . finished , static_cast < LWOOBJID > ( eRacingTaskParam : : FINISH_WITH_PLACEMENT ) ) ; // Finish in 1st place on a race
if ( player . finished = = 1 ) {
missionComponent - > Progress ( eMissionTaskType : : RACING , Game : : zoneManager - > GetZone ( ) - > GetWorldID ( ) , static_cast < LWOOBJID > ( eRacingTaskParam : : FIRST_PLACE_MULTIPLE_TRACKS ) ) ; // Finish in 1st place on multiple tracks.
missionComponent - > Progress ( eMissionTaskType : : RACING , Game : : zoneManager - > GetZone ( ) - > GetWorldID ( ) , static_cast < LWOOBJID > ( eRacingTaskParam : : WIN_RACE_IN_WORLD ) ) ; // Finished first place in specific world.
}
if ( player . finished = = m_RacingPlayers . size ( ) ) {
missionComponent - > Progress ( eMissionTaskType : : RACING , Game : : zoneManager - > GetZone ( ) - > GetWorldID ( ) , static_cast < LWOOBJID > ( eRacingTaskParam : : LAST_PLACE_FINISH ) ) ; // Finished first place in specific world.
}
}
2022-02-05 11:28:17 +00:00
auto * characterComponent = playerEntity - > GetComponent < CharacterComponent > ( ) ;
2021-12-05 17:54:36 +00:00
if ( characterComponent ! = nullptr ) {
2022-02-05 11:28:17 +00:00
characterComponent - > TrackRaceCompleted ( m_Finished = = 1 ) ;
2021-12-05 17:54:36 +00:00
}
}
}
2023-10-21 23:31:55 +00:00
LOG ( " Lapped (%i) in (%llu) " , player . lap ,
2021-12-05 17:54:36 +00:00
lapTime ) ;
}
2023-10-21 23:31:55 +00:00
LOG ( " Reached point (%i)/(%i) " , player . respawnIndex ,
2021-12-05 17:54:36 +00:00
path - > pathWaypoints . size ( ) ) ;
break ;
}
}
}