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 "Player.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"
# include "VehiclePhysicsComponent.h"
# 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-08 09:38:08 +00:00
# include "dZoneManager.h"
# include "CDActivitiesTable.h"
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 " ;
2023-05-09 15:58:57 +00:00
m_DirtyPathName = true ;
2021-12-05 17:54:36 +00:00
m_RemainingLaps = 3 ;
2023-05-09 15:58:57 +00:00
m_DirtyRaceInfo = true ;
2021-12-05 17:54:36 +00:00
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-05-08 09:38:08 +00:00
if ( dZoneManager : : Instance ( ) - > 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 ;
CDActivitiesTable * activitiesTable = CDClientManager : : Instance ( ) . GetTable < CDActivitiesTable > ( ) ;
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 ) {
// If the race has already started, send the player back to the main world.
if ( m_Loaded ) {
auto * playerInstance = dynamic_cast < Player * > ( player ) ;
playerInstance - > SendToZone ( m_MainWorld ) ;
return ;
}
const auto objectID = player - > GetObjectID ( ) ;
m_LoadedPlayers + + ;
2022-07-25 02:26:51 +00:00
Game : : logger - > Log ( " RacingControlComponent " , " Loading player %i " ,
2021-12-05 17:54:36 +00:00
m_LoadedPlayers ) ;
m_LobbyPlayers . push_back ( objectID ) ;
2023-05-09 15:58:57 +00:00
m_DirtyLoadPlayer = true ;
2021-12-05 17:54:36 +00:00
}
2023-05-09 15:58:57 +00:00
void RacingControlComponent : : LoadPlayerVehicle ( Entity * player , uint32_t positionNumber , bool initialLoad ) {
2021-12-05 17:54:36 +00:00
// Load the player's vehicle.
2023-05-09 15:58:57 +00:00
if ( ! player ) return ;
2021-12-05 17:54:36 +00:00
// Find the player's vehicle.
2023-05-09 15:58:57 +00:00
auto * inventoryComponent = player - > GetComponent < InventoryComponent > ( ) ;
if ( ! inventoryComponent ) return ;
2021-12-05 17:54:36 +00:00
auto * item = inventoryComponent - > FindItemByLot ( 8092 ) ;
2023-05-09 15:58:57 +00:00
if ( ! item ) {
2022-07-25 02:26:51 +00:00
Game : : logger - > Log ( " RacingControlComponent " , " Failed to find item " ) ;
2021-12-05 17:54:36 +00:00
return ;
}
// Calculate the vehicle's starting position.
2023-05-09 15:58:57 +00:00
auto * path = dZoneManager : : Instance ( ) - > GetZone ( ) - > GetPath ( GeneralUtils : : UTF16ToWTF8 ( m_PathName ) ) ;
2021-12-05 17:54:36 +00:00
2022-12-22 13:16:18 +00:00
auto spawnPointEntities = EntityManager : : Instance ( ) - > GetEntitiesByLOT ( 4843 ) ;
auto startPosition = NiPoint3 : : ZERO ;
auto startRotation = NiQuaternion : : IDENTITY ;
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.
2023-05-09 15:58:57 +00:00
GameMessages : : SendTeleport (
player - > GetObjectID ( ) ,
startPosition ,
startRotation ,
player - > GetSystemAddress ( ) ,
true , 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 ( ) ;
2023-05-09 15:58:57 +00:00
auto * carEntity = EntityManager : : Instance ( ) - > 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 ) ;
2023-05-09 15:58:57 +00:00
auto * destroyableComponent = carEntity - > GetComponent < DestroyableComponent > ( ) ;
2021-12-05 17:54:36 +00:00
// Setup the vehicle stats.
2023-05-09 15:58:57 +00:00
if ( destroyableComponent ) {
2021-12-05 17:54:36 +00:00
destroyableComponent - > SetMaxImagination ( 60 ) ;
destroyableComponent - > SetImagination ( 0 ) ;
}
// Setup the vehicle as being possessed by the player.
2023-05-09 15:58:57 +00:00
auto * possessableComponent = carEntity - > GetComponent < PossessableComponent > ( ) ;
2021-12-05 17:54:36 +00:00
2023-05-09 15:58:57 +00:00
if ( possessableComponent ) possessableComponent - > SetPossessor ( player - > GetObjectID ( ) ) ;
2021-12-05 17:54:36 +00:00
// Load the vehicle's assemblyPartLOTs for display.
2023-05-09 15:58:57 +00:00
auto * moduleAssemblyComponent = carEntity - > GetComponent < ModuleAssemblyComponent > ( ) ;
2021-12-05 17:54:36 +00:00
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 > ( ) ;
2023-05-09 15:58:57 +00:00
if ( possessorComponent ) {
2021-12-05 17:54:36 +00:00
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 > ( ) ;
2023-05-09 15:58:57 +00:00
if ( characterComponent ) characterComponent - > SetIsRacing ( true ) ;
2021-12-05 17:54:36 +00:00
// Init the player's racing entry.
if ( initialLoad ) {
m_RacingPlayers . push_back (
2023-05-09 15:58:57 +00:00
{
player - > GetObjectID ( ) ,
carEntity - > GetObjectID ( ) ,
static_cast < uint32_t > ( m_RacingPlayers . size ( ) ) ,
false , { } , startPosition , startRotation , 0 , 0 , 0 , 0
}
) ;
m_DirtyLoadPlayer = true ;
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
2021-12-05 17:54:36 +00:00
EntityManager : : Instance ( ) - > ConstructEntity ( carEntity ) ;
EntityManager : : Instance ( ) - > SerializeEntity ( player ) ;
EntityManager : : Instance ( ) - > SerializeEntity ( m_Parent ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
GameMessages : : SendRacingSetPlayerResetInfo (
2023-05-09 15:58:57 +00:00
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-05-09 15:58:57 +00:00
auto * player = EntityManager : : Instance ( ) - > GetEntity ( playerID ) ;
if ( ! player ) return ;
GameMessages : : SendRacingResetPlayerToLastReset (
m_Parent - > GetObjectID ( ) , playerID ,
UNASSIGNED_SYSTEM_ADDRESS
) ;
2021-12-05 17:54:36 +00:00
}
2023-05-09 15:58:57 +00:00
) ;
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.
2023-05-09 15:58:57 +00:00
GameMessages : : SendNotifyVehicleOfRacingObject (
carEntity - > GetObjectID ( ) ,
2021-12-05 17:54:36 +00:00
m_Parent - > GetObjectID ( ) ,
2023-05-09 15:58:57 +00:00
UNASSIGNED_SYSTEM_ADDRESS
) ;
2022-07-28 13:39:57 +00:00
2023-05-09 15:58:57 +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.
2023-05-09 15:58:57 +00:00
GameMessages : : SendTeleport (
player - > GetObjectID ( ) , startPosition ,
startRotation , player - > GetSystemAddress ( ) ,
true , true
) ;
GameMessages : : SendTeleport (
carEntity - > GetObjectID ( ) , startPosition ,
startRotation , player - > GetSystemAddress ( ) ,
true , 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 (
2023-05-09 15:58:57 +00:00
m_Parent - > GetObjectID ( ) ,
racingPlayer . playerID ,
racingPlayer . vehicleID ,
UNASSIGNED_SYSTEM_ADDRESS
) ;
2021-12-05 17:54:36 +00:00
}
continue ;
}
racingPlayer . playerLoaded = true ;
GameMessages : : SendRacingPlayerLoaded (
2023-05-09 15:58:57 +00:00
m_Parent - > GetObjectID ( ) ,
racingPlayer . playerID ,
racingPlayer . vehicleID ,
UNASSIGNED_SYSTEM_ADDRESS
) ;
2021-12-05 17:54:36 +00:00
}
EntityManager : : Instance ( ) - > SerializeEntity ( m_Parent ) ;
}
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 ) {
2023-05-09 15:58:57 +00:00
if ( racingPlayer . playerID ! = player - > GetObjectID ( ) ) continue ;
2022-07-28 13:39:57 +00:00
2023-05-09 15:58:57 +00:00
auto * vehicle = EntityManager : : Instance ( ) - > 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-05-09 15:58:57 +00:00
GameMessages : : SendDie (
vehicle , vehicle - > GetObjectID ( ) ,
LWOOBJID_EMPTY , true ,
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...
vehicle - > AddCallbackTimer ( 2.0f , [ = ] ( ) {
if ( ! vehicle | | ! this - > m_Parent ) return ;
GameMessages : : SendRacingResetPlayerToLastReset (
2023-05-09 15:58:57 +00:00
m_Parent - > GetObjectID ( ) ,
racingPlayer . playerID ,
UNASSIGNED_SYSTEM_ADDRESS
) ;
2023-03-05 20:34:59 +00:00
GameMessages : : SendVehicleStopBoost ( vehicle , player - > GetSystemAddress ( ) , true ) ;
GameMessages : : SendRacingSetPlayerResetInfo (
m_Parent - > GetObjectID ( ) , racingPlayer . lap ,
racingPlayer . respawnIndex , player - > GetObjectID ( ) ,
racingPlayer . respawnPosition , racingPlayer . respawnIndex + 1 ,
2023-05-09 15:58:57 +00:00
UNASSIGNED_SYSTEM_ADDRESS
) ;
2023-03-05 20:34:59 +00:00
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 ) ;
EntityManager : : Instance ( ) - > SerializeEntity ( vehicle ) ;
} ) ;
auto * characterComponent = player - > GetComponent < CharacterComponent > ( ) ;
2023-05-09 15:58:57 +00:00
if ( characterComponent ) characterComponent - > UpdatePlayerStatistic ( RacingTimesWrecked ) ;
2023-03-05 20:34:59 +00:00
} else {
GameMessages : : SendRacingSetPlayerResetInfo (
m_Parent - > GetObjectID ( ) , racingPlayer . lap ,
racingPlayer . respawnIndex , player - > GetObjectID ( ) ,
racingPlayer . respawnPosition , racingPlayer . respawnIndex + 1 ,
2023-05-09 15:58:57 +00:00
UNASSIGNED_SYSTEM_ADDRESS
) ;
2023-03-05 20:34:59 +00:00
GameMessages : : SendRacingResetPlayerToLastReset (
2023-05-09 15:58:57 +00:00
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 ) {
2023-05-09 15:58:57 +00:00
if ( racingPlayer . playerID ! = player - > GetObjectID ( ) ) continue ;
auto * vehicle = EntityManager : : Instance ( ) - > GetEntity ( racingPlayer . vehicleID ) ;
if ( ! vehicle ) return ;
2021-12-05 17:54:36 +00:00
racingPlayer . noSmashOnReload = false ;
return ;
}
}
2023-05-09 15:58:57 +00:00
void RacingControlComponent : : HandleMessageBoxResponse ( Entity * player , const std : : string & id ) {
2021-12-05 17:54:36 +00:00
auto * data = GetPlayerData ( player - > GetObjectID ( ) ) ;
2023-05-09 15:58:57 +00:00
if ( ! data ) return ;
2021-12-05 17:54:36 +00:00
if ( id = = " rewardButton " ) {
if ( data - > collectedRewards ) {
return ;
}
data - > collectedRewards = true ;
// Calculate the score, different loot depending on player count
const auto score = m_LoadedPlayers * 10 + data - > finished ;
2021-12-20 10:25:45 +00:00
LootGenerator : : Instance ( ) . GiveActivityLoot ( player , m_Parent , m_ActivityID , score ) ;
2021-12-05 17:54:36 +00:00
// Giving rewards
GameMessages : : SendNotifyRacingClient (
2023-05-09 15:58:57 +00:00
m_Parent - > GetObjectID ( ) , 2 , 0 ,
LWOOBJID_EMPTY , u " " ,
player - > GetObjectID ( ) ,
UNASSIGNED_SYSTEM_ADDRESS
) ;
2021-12-05 17:54:36 +00:00
auto * missionComponent = player - > GetComponent < MissionComponent > ( ) ;
2023-05-09 15:58:57 +00:00
if ( ! missionComponent ) return ;
2022-02-05 11:28:17 +00:00
missionComponent - > Progress ( eMissionTaskType : : RACING , 0 , ( LWOOBJID ) eRacingTaskParam : : COMPETED_IN_RACE ) ; // Progress task for competing in a race
missionComponent - > Progress ( eMissionTaskType : : RACING , data - > smashedTimes , ( 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_LoadedPlayers > 2 ) {
missionComponent - > Progress ( eMissionTaskType : : RACING , data - > finished , ( LWOOBJID ) eRacingTaskParam : : FINISH_WITH_PLACEMENT ) ; // Finish in 1st place on a race
2022-04-18 08:04:29 +00:00
if ( data - > finished = = 1 ) {
missionComponent - > Progress ( eMissionTaskType : : RACING , dZoneManager : : Instance ( ) - > GetZone ( ) - > GetWorldID ( ) , ( LWOOBJID ) eRacingTaskParam : : FIRST_PLACE_MULTIPLE_TRACKS ) ; // Finish in 1st place on multiple tracks.
missionComponent - > Progress ( eMissionTaskType : : RACING , dZoneManager : : Instance ( ) - > GetZone ( ) - > GetWorldID ( ) , ( LWOOBJID ) eRacingTaskParam : : WIN_RACE_IN_WORLD ) ; // Finished first place in specific world.
}
if ( data - > finished = = m_LoadedPlayers ) {
missionComponent - > Progress ( eMissionTaskType : : RACING , dZoneManager : : Instance ( ) - > GetZone ( ) - > GetWorldID ( ) , ( LWOOBJID ) eRacingTaskParam : : LAST_PLACE_FINISH ) ; // Finished first place in specific world.
}
2021-12-05 17:54:36 +00:00
}
} else if ( id = = " ACT_RACE_EXIT_THE_RACE? " | | id = = " Exit " ) {
2023-05-09 15:58:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " , " exiting race " ) ;
2021-12-05 17:54:36 +00:00
auto * vehicle = EntityManager : : Instance ( ) - > GetEntity ( data - > vehicleID ) ;
2023-05-09 15:58:57 +00:00
if ( ! vehicle ) return ;
2021-12-05 17:54:36 +00:00
// Exiting race
GameMessages : : SendNotifyRacingClient (
2023-05-09 15:58:57 +00:00
m_Parent - > GetObjectID ( ) , 3 , 0 ,
LWOOBJID_EMPTY , u " " ,
player - > GetObjectID ( ) ,
UNASSIGNED_SYSTEM_ADDRESS
) ;
2021-12-05 17:54:36 +00:00
auto * playerInstance = dynamic_cast < Player * > ( player ) ;
playerInstance - > SendToZone ( m_MainWorld ) ;
vehicle - > Kill ( ) ;
}
}
2023-05-09 15:58:57 +00:00
void RacingControlComponent : : Serialize ( RakNet : : BitStream * outBitStream , bool bIsInitialUpdate , unsigned int & flags ) {
2021-12-05 17:54:36 +00:00
// BEGIN Scripted Activity
outBitStream - > Write1 ( ) ;
outBitStream - > Write ( static_cast < uint32_t > ( m_RacingPlayers . size ( ) ) ) ;
for ( const auto & player : m_RacingPlayers ) {
outBitStream - > Write ( player . playerID ) ;
for ( int i = 0 ; i < 10 ; i + + ) {
outBitStream - > Write ( player . data [ i ] ) ;
}
}
// END Scripted Activity
2023-05-09 15:58:57 +00:00
// BEGIN Base Race Control
2021-12-05 17:54:36 +00:00
2023-05-09 15:58:57 +00:00
outBitStream - > Write ( m_DirtyLobby | | bIsInitialUpdate ) ;
if ( m_DirtyLobby | | bIsInitialUpdate ) {
outBitStream - > Write ( static_cast < uint16_t > ( m_LobbyPlayers . size ( ) ) ) ;
m_DirtyLobby = false ;
}
2021-12-05 17:54:36 +00:00
2023-05-09 15:58:57 +00:00
outBitStream - > Write ( m_DirtyLoadPlayer | | bIsInitialUpdate ) ;
if ( m_DirtyLoadPlayer | | bIsInitialUpdate ) {
2021-12-05 17:54:36 +00:00
for ( const auto & player : m_RacingPlayers ) {
2023-05-09 15:58:57 +00:00
if ( player . playerLoaded ) {
outBitStream - > Write1 ( ) ; // Has more data
outBitStream - > Write ( player . playerID ) ;
outBitStream - > Write ( player . vehicleID ) ;
outBitStream - > Write ( player . rank ) ;
outBitStream - > Write ( player . playerLoaded ) ;
}
2021-12-05 17:54:36 +00:00
}
outBitStream - > Write0 ( ) ; // No more data
2023-05-09 15:58:57 +00:00
m_DirtyLoadPlayer = false ;
2021-12-05 17:54:36 +00:00
}
2023-05-09 15:58:57 +00:00
outBitStream - > Write ( m_DirtyRank | | bIsInitialUpdate ) ;
if ( m_DirtyRank | | bIsInitialUpdate ) {
2021-12-05 17:54:36 +00:00
for ( const auto & player : m_RacingPlayers ) {
2023-05-09 15:58:57 +00:00
outBitStream - > Write1 ( ) ; // Has more data
2021-12-05 17:54:36 +00:00
outBitStream - > Write ( player . playerID ) ;
2023-05-09 15:58:57 +00:00
outBitStream - > Write ( player . rank ) ;
2021-12-05 17:54:36 +00:00
}
outBitStream - > Write0 ( ) ; // No more data
2023-05-09 15:58:57 +00:00
m_DirtyRank = false ;
2021-12-05 17:54:36 +00:00
}
2023-05-09 15:58:57 +00:00
// END Base Race Control
// BEGIN Race Control
2021-12-05 17:54:36 +00:00
2023-05-09 15:58:57 +00:00
outBitStream - > Write ( m_DirtyRaceInfo | | bIsInitialUpdate ) ;
if ( m_DirtyRaceInfo | | bIsInitialUpdate ) {
outBitStream - > Write ( m_RemainingLaps ) ;
2021-12-05 17:54:36 +00:00
2023-05-09 15:58:57 +00:00
if ( m_DirtyPathName ) {
outBitStream - > Write ( static_cast < uint16_t > ( m_PathName . size ( ) ) ) ;
for ( const auto character : m_PathName ) {
outBitStream - > Write ( character ) ;
}
m_DirtyPathName = false ;
} else {
// once we set the path, we don't need to send it every time unless it changes
outBitStream - > Write < uint16_t > ( 0 ) ;
}
m_DirtyRaceInfo = false ;
2021-12-05 17:54:36 +00:00
}
2023-05-09 15:58:57 +00:00
outBitStream - > Write ( m_DirtyEndOfRaceInfo | | bIsInitialUpdate ) ;
if ( m_DirtyEndOfRaceInfo | | bIsInitialUpdate ) {
for ( const auto & player : m_RacingPlayers ) {
if ( player . finished > 0 ) {
outBitStream - > Write1 ( ) ; // Has more data
outBitStream - > Write ( player . playerID ) ;
outBitStream - > Write ( player . bestLapTime ) ;
outBitStream - > Write ( player . raceTime ) ;
}
}
outBitStream - > Write0 ( ) ; // No more data
m_DirtyEndOfRaceInfo = false ;
}
// END Race Control
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 + + ) {
2023-05-09 15:58:57 +00:00
auto * playerEntity = EntityManager : : Instance ( ) - > GetEntity ( m_LobbyPlayers [ i ] ) ;
if ( ! playerEntity ) {
2021-12-05 17:54:36 +00:00
- - m_LoadedPlayers ;
m_LobbyPlayers . erase ( m_LobbyPlayers . begin ( ) + i ) ;
2023-05-09 15:58:57 +00:00
m_DirtyLoadPlayer = true ;
2021-12-05 17:54:36 +00:00
return ;
}
}
2023-05-09 15:58:57 +00:00
if ( m_LoadedPlayers > = 2 | | ( m_LoadedPlayers = = 1 & & m_SoloRacing ) ) m_LoadTimer + = deltaTime ;
else m_EmptyTimer + = deltaTime ;
2021-12-05 17:54:36 +00:00
// 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 ) {
2023-05-09 15:58:57 +00:00
auto * playerEntity = EntityManager : : Instance ( ) - > GetEntity ( player ) ;
if ( playerEntity ) continue ;
2021-12-05 17:54:36 +00:00
auto * playerInstance = dynamic_cast < Player * > ( playerEntity ) ;
playerInstance - > SendToZone ( m_MainWorld ) ;
}
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-05-09 15:58:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " , " Loading all players... " ) ;
2022-12-22 13:16:18 +00:00
for ( size_t positionNumber = 0 ; positionNumber < m_LobbyPlayers . size ( ) ; positionNumber + + ) {
2023-05-09 15:58:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " , " Loading player now! " ) ;
2022-07-28 13:39:57 +00:00
2023-05-09 15:58:57 +00:00
auto * player = EntityManager : : Instance ( ) - > GetEntity ( m_LobbyPlayers [ positionNumber ] ) ;
if ( ! player ) return ;
2021-12-05 17:54:36 +00:00
2023-05-09 15:58:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " , " Loading player now NOW! " ) ;
2022-12-22 13:16:18 +00:00
LoadPlayerVehicle ( player , positionNumber + 1 , true ) ;
2021-12-05 17:54:36 +00:00
m_Loaded = true ;
}
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-05-09 15:58:57 +00:00
auto * playerEntity = EntityManager : : Instance ( ) - > GetEntity ( m_RacingPlayers [ i ] . playerID ) ;
2022-07-28 13:39:57 +00:00
2023-05-09 15:58:57 +00:00
if ( ! playerEntity ) {
2021-12-05 17:54:36 +00:00
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 ) {
2023-05-09 15:58:57 +00:00
auto * playerEntity = EntityManager : : Instance ( ) - > GetEntity ( player ) ;
if ( ! playerEntity ) continue ;
2021-12-05 17:54:36 +00:00
auto * playerInstance = dynamic_cast < Player * > ( playerEntity ) ;
playerInstance - > SendToZone ( m_MainWorld ) ;
}
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
int32_t readyPlayers = 0 ;
for ( const auto & player : m_RacingPlayers ) {
2023-05-09 15:58:57 +00:00
if ( player . playerLoaded ) + + readyPlayers ;
2021-12-05 17:54:36 +00:00
}
if ( readyPlayers > = m_LoadedPlayers ) {
// Setup for racing
if ( m_StartTimer = = 0 ) {
GameMessages : : SendNotifyRacingClient (
m_Parent - > GetObjectID ( ) , 1 , 0 , LWOOBJID_EMPTY , u " " ,
2023-05-09 15:58:57 +00:00
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 ) {
2023-05-09 15:58:57 +00:00
auto * vehicle = EntityManager : : Instance ( ) - > GetEntity ( player . vehicleID ) ;
auto * playerEntity = EntityManager : : Instance ( ) - > GetEntity ( player . playerID ) ;
if ( vehicle & & playerEntity ) {
2021-12-05 17:54:36 +00:00
GameMessages : : SendTeleport (
player . playerID , player . respawnPosition ,
player . respawnRotation ,
2023-05-09 15:58:57 +00:00
playerEntity - > GetSystemAddress ( ) , true , true
) ;
2021-12-05 17:54:36 +00:00
vehicle - > SetPosition ( player . respawnPosition ) ;
vehicle - > SetRotation ( player . respawnRotation ) ;
2023-05-09 15:58:57 +00:00
auto * destroyableComponent = vehicle - > GetComponent < DestroyableComponent > ( ) ;
if ( destroyableComponent ) destroyableComponent - > SetImagination ( 0 ) ;
2021-12-05 17:54:36 +00:00
EntityManager : : Instance ( ) - > SerializeEntity ( vehicle ) ;
2023-05-09 15:58:57 +00:00
EntityManager : : Instance ( ) - > SerializeEntity ( playerEntity ) ;
2021-12-05 17:54:36 +00:00
}
}
// Spawn imagination pickups
2023-05-09 15:58:57 +00:00
auto * minSpawner = dZoneManager : : Instance ( ) - > GetSpawnersByName ( " ImaginationSpawn_Min " ) [ 0 ] ;
auto * medSpawner = dZoneManager : : Instance ( ) - > GetSpawnersByName ( " ImaginationSpawn_Med " ) [ 0 ] ;
auto * maxSpawner = dZoneManager : : Instance ( ) - > GetSpawnersByName ( " ImaginationSpawn_Max " ) [ 0 ] ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
minSpawner - > Activate ( ) ;
2023-05-09 15:58:57 +00:00
if ( m_LoadedPlayers > 2 ) medSpawner - > Activate ( ) ;
if ( m_LoadedPlayers > 4 ) maxSpawner - > Activate ( ) ;
2021-12-05 17:54:36 +00:00
// Reset players to their start location, without smashing them
for ( auto & player : m_RacingPlayers ) {
2023-05-09 15:58:57 +00:00
auto * vehicleEntity = EntityManager : : Instance ( ) - > GetEntity ( player . vehicleID ) ;
auto * playerEntity = EntityManager : : Instance ( ) - > GetEntity ( player . playerID ) ;
if ( ! vehicleEntity | | ! playerEntity ) continue ;
2021-12-05 17:54:36 +00:00
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 ) {
2023-05-09 15:58:57 +00:00
auto * vehicleEntity = EntityManager : : Instance ( ) - > GetEntity ( player . vehicleID ) ;
auto * playerEntity = EntityManager : : Instance ( ) - > GetEntity ( player . playerID ) ;
if ( ! vehicleEntity | | ! playerEntity ) continue ;
GameMessages : : SendVehicleUnlockInput ( player . vehicleID , false , UNASSIGNED_SYSTEM_ADDRESS ) ;
2021-12-05 17:54:36 +00:00
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
// Start the race
2023-05-09 15:58:57 +00:00
GameMessages : : SendActivityStart ( m_Parent - > GetObjectID ( ) , UNASSIGNED_SYSTEM_ADDRESS ) ;
2021-12-05 17:54:36 +00:00
m_Started = true ;
2022-07-25 02:26:51 +00:00
Game : : logger - > Log ( " RacingControlComponent " , " Starting race " ) ;
2021-12-05 17:54:36 +00:00
EntityManager : : Instance ( ) - > SerializeEntity ( m_Parent ) ;
m_StartTime = std : : time ( nullptr ) ;
}
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
m_StartTimer + = deltaTime ;
2023-05-09 15:58:57 +00:00
} else m_StartTimer = 0 ;
2021-12-05 17:54:36 +00:00
return ;
}
// Race routines
2023-05-09 15:58:57 +00:00
auto * path = dZoneManager : : Instance ( ) - > GetZone ( ) - > GetPath ( GeneralUtils : : UTF16ToWTF8 ( m_PathName ) ) ;
2021-12-05 17:54:36 +00:00
for ( auto & player : m_RacingPlayers ) {
auto * vehicle = EntityManager : : Instance ( ) - > GetEntity ( player . vehicleID ) ;
2023-05-09 15:58:57 +00:00
auto * playerEntity = EntityManager : : Instance ( ) - > GetEntity ( player . playerID ) ;
2021-12-05 17:54:36 +00:00
2023-05-09 15:58:57 +00:00
if ( ! vehicle | | ! playerEntity ) continue ;
2021-12-05 17:54:36 +00:00
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 ) {
2023-05-09 15:58:57 +00:00
GameMessages : : SendDie (
vehicle , m_Parent - > GetObjectID ( ) ,
LWOOBJID_EMPTY , true , eKillType : : VIOLENT ,
u " " , 0 , 0 , 0 , true , false , 0
) ;
2021-12-05 17:54:36 +00:00
OnRequestDie ( playerEntity ) ;
continue ;
}
// 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 ) {
2023-05-09 15:58:57 +00:00
if ( player . lap = = 3 ) break ;
2021-12-05 17:54:36 +00:00
if ( player . respawnIndex = = respawnIndex ) {
+ + respawnIndex ;
continue ;
}
const auto & position = waypoint . position ;
if ( std : : abs ( ( int ) respawnIndex - ( int ) player . respawnIndex ) > 10 & &
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
2023-05-09 15:58:57 +00:00
if ( ! ( respawnIndex > player . respawnIndex | | player . respawnIndex = = path - > pathWaypoints . size ( ) - 1 ) ) {
2021-12-05 17:54:36 +00:00
+ + 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
player . respawnPosition = position + NiPoint3 : : UNIT_Y * 5 ;
player . respawnRotation = vehicle - > GetRotation ( ) ;
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
2023-05-09 15:58:57 +00:00
if ( lapTime < 40 ) continue ;
2021-12-05 17:54:36 +00:00
player . lap + + ;
player . lapTime = std : : time ( nullptr ) ;
if ( player . bestLapTime = = 0 | | player . bestLapTime > lapTime ) {
player . bestLapTime = lapTime ;
2023-05-09 15:58:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " , " Best lap time (%llu) " , lapTime ) ;
2021-12-05 17:54:36 +00:00
}
2023-05-09 15:58:57 +00:00
auto * missionComponent = playerEntity - > GetComponent < MissionComponent > ( ) ;
if ( missionComponent ) {
2022-02-05 11:28:17 +00:00
// Progress lap time tasks
missionComponent - > Progress ( eMissionTaskType : : RACING , ( lapTime ) * 1000 , ( LWOOBJID ) eRacingTaskParam : : LAP_TIME ) ;
2021-12-05 17:54:36 +00:00
if ( player . lap = = 3 ) {
m_Finished + + ;
player . finished = m_Finished ;
// Entire race time
2023-05-09 15:58:57 +00:00
const auto raceTime = ( std : : time ( nullptr ) - m_StartTime ) ;
player . raceTime = raceTime ;
Game : : logger - > Log ( " RacingControlComponent " , " Completed time %llu, %llu " , raceTime , raceTime * 1000 ) ;
2022-02-05 11:28:17 +00:00
missionComponent - > Progress ( eMissionTaskType : : RACING , ( raceTime ) * 1000 , ( LWOOBJID ) eRacingTaskParam : : TOTAL_TRACK_TIME ) ;
2021-12-05 17:54:36 +00:00
2022-02-05 11:28:17 +00:00
auto * characterComponent = playerEntity - > GetComponent < CharacterComponent > ( ) ;
2023-05-09 15:58:57 +00:00
if ( characterComponent ) characterComponent - > TrackRaceCompleted ( m_Finished = = 1 ) ;
m_DirtyEndOfRaceInfo = true ;
2021-12-05 17:54:36 +00:00
}
}
2023-05-09 15:58:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " , " Lapped (%i) in (%llu) " , player . lap , lapTime ) ;
2021-12-05 17:54:36 +00:00
}
2023-05-09 15:58:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " , " Reached point (%i)/(%i) " , player . respawnIndex , path - > pathWaypoints . size ( ) ) ;
2021-12-05 17:54:36 +00:00
break ;
}
}
}
std : : string RacingControlComponent : : FormatTimeString ( time_t time ) {
int32_t min = time / 60 ;
time - = min * 60 ;
int32_t sec = time ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
std : : string minText ;
std : : string secText ;
2022-07-28 13:39:57 +00:00
2023-05-09 15:58:57 +00:00
if ( min < = 0 ) minText = " 0 " ;
else minText = std : : to_string ( min ) ;
2022-07-28 13:39:57 +00:00
2023-05-09 15:58:57 +00:00
if ( sec < = 0 ) secText = " 00 " ;
else if ( sec < = 9 ) secText = " 0 " + std : : to_string ( sec ) ;
else secText = std : : to_string ( sec ) ;
2022-07-28 13:39:57 +00:00
2021-12-05 17:54:36 +00:00
return minText + " : " + secText + " .00 " ;
}