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"
2023-01-22 23:38:47 +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"
2023-01-22 23:38:47 +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"
2021-12-05 17:54:36 +00:00
# ifndef M_PI
# define M_PI 3.14159265358979323846264338327950288
# endif
2022-07-28 13:39:57 +00:00
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 " ;
2023-05-08 09:38:08 +00:00
m_MainWorld = 1200 ;
2022-07-28 13:39:57 +00:00
const auto worldID = Game : : server - > GetZoneID ( ) ;
2023-06-22 02:46:01 +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 ( ) { }
2022-07-28 13:39:57 +00:00
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 ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
playerInstance - > SendToZone ( m_MainWorld ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
return ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
const auto objectID = player - > GetObjectID ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
m_LoadedPlayers + + ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " , " Loading player %i " ,
m_LoadedPlayers ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
m_LobbyPlayers . push_back ( objectID ) ;
2021-12-05 17:54:36 +00:00
}
2022-07-28 13:39:57 +00:00
void RacingControlComponent : : LoadPlayerVehicle ( Entity * player ,
2022-12-22 13:16:18 +00:00
uint32_t positionNumber , bool initialLoad ) {
2022-07-28 13:39:57 +00:00
// Load the player's vehicle.
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( player = = nullptr ) {
return ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
auto * inventoryComponent = player - > GetComponent < InventoryComponent > ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( inventoryComponent = = nullptr ) {
return ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Find the player's vehicle.
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
auto * item = inventoryComponent - > FindItemByLot ( 8092 ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( item = = nullptr ) {
Game : : logger - > Log ( " RacingControlComponent " , " Failed to find item " ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
return ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Calculate the vehicle's starting position.
2021-12-05 17:54:36 +00:00
2022-07-28 13:39: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
2022-07-28 13:39:57 +00:00
// Make sure the player is at the correct position.
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
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
2022-07-28 13:39:57 +00:00
// Spawn the vehicle entity.
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
EntityInfo info { } ;
info . lot = 8092 ;
info . pos = startPosition ;
info . rot = startRotation ;
info . spawnerID = m_Parent - > GetObjectID ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
auto * carEntity =
EntityManager : : Instance ( ) - > CreateEntity ( info , nullptr , m_Parent ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Make the vehicle a child of the racing controller.
m_Parent - > AddChild ( carEntity ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
auto * destroyableComponent =
carEntity - > GetComponent < DestroyableComponent > ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Setup the vehicle stats.
if ( destroyableComponent ! = nullptr ) {
destroyableComponent - > SetMaxImagination ( 60 ) ;
destroyableComponent - > SetImagination ( 0 ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Setup the vehicle as being possessed by the player.
auto * possessableComponent =
carEntity - > GetComponent < PossessableComponent > ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( possessableComponent ! = nullptr ) {
possessableComponent - > SetPossessor ( player - > GetObjectID ( ) ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Load the vehicle's assemblyPartLOTs for display.
auto * moduleAssemblyComponent =
carEntity - > GetComponent < ModuleAssemblyComponent > ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( moduleAssemblyComponent ) {
moduleAssemblyComponent - > SetSubKey ( item - > GetSubKey ( ) ) ;
moduleAssemblyComponent - > SetUseOptionalParts ( false ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
for ( auto * config : item - > GetConfig ( ) ) {
if ( config - > GetKey ( ) = = u " assemblyPartLOTs " ) {
moduleAssemblyComponent - > SetAssemblyPartsLOTs (
GeneralUtils : : ASCIIToUTF16 ( config - > GetValueAsString ( ) ) ) ;
}
}
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Setup the player as possessing the vehicle.
auto * possessorComponent = player - > GetComponent < PossessorComponent > ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( possessorComponent ! = nullptr ) {
possessorComponent - > SetPossessable ( carEntity - > GetObjectID ( ) ) ;
possessorComponent - > SetPossessableType ( ePossessionType : : ATTACHED_VISIBLE ) ; // for racing it's always Attached_Visible
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Set the player's current activity as racing.
auto * characterComponent = player - > GetComponent < CharacterComponent > ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( characterComponent ! = nullptr ) {
characterComponent - > SetIsRacing ( true ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// 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 } ) ;
}
// Construct and serialize everything when done.
EntityManager : : Instance ( ) - > ConstructEntity ( carEntity ) ;
EntityManager : : Instance ( ) - > SerializeEntity ( player ) ;
EntityManager : : Instance ( ) - > SerializeEntity ( m_Parent ) ;
GameMessages : : SendRacingSetPlayerResetInfo (
m_Parent - > GetObjectID ( ) , 0 , 0 , player - > GetObjectID ( ) , startPosition , 1 ,
UNASSIGNED_SYSTEM_ADDRESS ) ;
const auto playerID = player - > GetObjectID ( ) ;
// Reset the player to the start position during downtime, in case something
// went wrong.
m_Parent - > AddCallbackTimer ( 1 , [ this , playerID ] ( ) {
auto * player = EntityManager : : Instance ( ) - > GetEntity ( playerID ) ;
if ( player = = nullptr ) {
return ;
}
GameMessages : : SendRacingResetPlayerToLastReset (
m_Parent - > GetObjectID ( ) , playerID , UNASSIGNED_SYSTEM_ADDRESS ) ;
} ) ;
GameMessages : : SendSetJetPackMode ( player , false ) ;
// Set the vehicle's state.
GameMessages : : SendNotifyVehicleOfRacingObject ( carEntity - > GetObjectID ( ) ,
m_Parent - > GetObjectID ( ) ,
UNASSIGNED_SYSTEM_ADDRESS ) ;
GameMessages : : SendVehicleSetWheelLockState ( carEntity - > GetObjectID ( ) , false ,
initialLoad ,
UNASSIGNED_SYSTEM_ADDRESS ) ;
// Make sure everything has the correct position.
GameMessages : : SendTeleport ( player - > GetObjectID ( ) , startPosition ,
2023-06-18 07:00:36 +00:00
startRotation , player - > GetSystemAddress ( ) , true ) ;
2022-07-28 13:39:57 +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
}
2022-07-28 13:39:57 +00:00
void RacingControlComponent : : OnRacingClientReady ( Entity * player ) {
// Notify the other players that this player is ready.
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
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 ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
continue ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
racingPlayer . playerLoaded = true ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
GameMessages : : SendRacingPlayerLoaded (
m_Parent - > GetObjectID ( ) , racingPlayer . playerID ,
racingPlayer . vehicleID , UNASSIGNED_SYSTEM_ADDRESS ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
EntityManager : : Instance ( ) - > SerializeEntity ( m_Parent ) ;
2021-12-05 17:54:36 +00:00
}
2022-07-28 13:39:57 +00:00
void RacingControlComponent : : OnRequestDie ( Entity * player ) {
// Sent by the client when they collide with something which should smash
// them.
for ( auto & racingPlayer : m_RacingPlayers ) {
if ( racingPlayer . playerID ! = player - > GetObjectID ( ) ) {
continue ;
}
auto * vehicle =
EntityManager : : Instance ( ) - > GetEntity ( racingPlayer . vehicleID ) ;
2023-03-05 20:34:59 +00:00
if ( ! vehicle ) return ;
2022-07-28 13:39:57 +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...
vehicle - > AddCallbackTimer ( 2.0f , [ = ] ( ) {
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 ) ;
EntityManager : : Instance ( ) - > SerializeEntity ( vehicle ) ;
2023-06-22 02:46:01 +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 ) ;
2022-07-28 13:39:57 +00:00
}
}
2021-12-05 17:54:36 +00:00
}
2022-07-28 13:39:57 +00:00
void RacingControlComponent : : OnRacingPlayerInfoResetFinished ( Entity * player ) {
// When the player has respawned.
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
for ( auto & racingPlayer : m_RacingPlayers ) {
if ( racingPlayer . playerID ! = player - > GetObjectID ( ) ) {
continue ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
auto * vehicle =
EntityManager : : Instance ( ) - > GetEntity ( racingPlayer . vehicleID ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( vehicle = = nullptr ) {
return ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
racingPlayer . noSmashOnReload = false ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
return ;
}
2021-12-05 17:54:36 +00:00
}
2023-05-09 05:40:00 +00:00
void RacingControlComponent : : HandleMessageBoxResponse ( Entity * player , int32_t button , const std : : string & id ) {
2022-07-28 13:39:57 +00:00
auto * data = GetPlayerData ( player - > GetObjectID ( ) ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( data = = nullptr ) {
return ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( id = = " rewardButton " ) {
2023-05-09 08:42:11 +00:00
if ( data - > collectedRewards ) return ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
data - > collectedRewards = true ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Calculate the score, different loot depending on player count
const auto score = m_LoadedPlayers * 10 + data - > finished ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
LootGenerator : : Instance ( ) . GiveActivityLoot ( player , m_Parent , m_ActivityID , score ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Giving rewards
GameMessages : : SendNotifyRacingClient (
m_Parent - > GetObjectID ( ) , 2 , 0 , LWOOBJID_EMPTY , u " " ,
player - > GetObjectID ( ) , UNASSIGNED_SYSTEM_ADDRESS ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
auto * missionComponent = player - > GetComponent < MissionComponent > ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( missionComponent = = nullptr ) return ;
2022-02-05 11:28:17 +00:00
2023-01-22 23:38:47 +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.
2022-02-05 11:28:17 +00:00
2022-07-28 13:39:57 +00:00
// If solo racing is enabled OR if there are 3 players in the race, progress placement tasks.
if ( m_SoloRacing | | m_LoadedPlayers > 2 ) {
2023-01-22 23:38:47 +00:00
missionComponent - > Progress ( eMissionTaskType : : RACING , data - > finished , ( LWOOBJID ) eRacingTaskParam : : FINISH_WITH_PLACEMENT ) ; // Finish in 1st place on a race
2022-07-28 13:39:57 +00:00
if ( data - > finished = = 1 ) {
2023-01-22 23:38:47 +00:00
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.
2022-07-28 13:39:57 +00:00
}
if ( data - > finished = = m_LoadedPlayers ) {
2023-01-22 23:38:47 +00:00
missionComponent - > Progress ( eMissionTaskType : : RACING , dZoneManager : : Instance ( ) - > GetZone ( ) - > GetWorldID ( ) , ( LWOOBJID ) eRacingTaskParam : : LAST_PLACE_FINISH ) ; // Finished first place in specific world.
2022-07-28 13:39:57 +00:00
}
}
2023-05-10 09:05:56 +00:00
} else if ( ( id = = " ACT_RACE_EXIT_THE_RACE? " | | id = = " Exit " ) & & button = = m_ActivityExitConfirm ) {
2022-07-28 13:39:57 +00:00
auto * vehicle = EntityManager : : Instance ( ) - > GetEntity ( data - > vehicleID ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( vehicle = = nullptr ) {
return ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Exiting race
GameMessages : : SendNotifyRacingClient (
m_Parent - > GetObjectID ( ) , 3 , 0 , LWOOBJID_EMPTY , u " " ,
player - > GetObjectID ( ) , UNASSIGNED_SYSTEM_ADDRESS ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
auto * playerInstance = dynamic_cast < Player * > ( player ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
playerInstance - > SendToZone ( m_MainWorld ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
vehicle - > Kill ( ) ;
}
2021-12-05 17:54:36 +00:00
}
2022-07-28 13:39:57 +00:00
void RacingControlComponent : : Serialize ( RakNet : : BitStream * outBitStream ,
bool bIsInitialUpdate ,
unsigned int & flags ) {
// BEGIN Scripted Activity
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write1 ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write ( static_cast < uint32_t > ( m_RacingPlayers . size ( ) ) ) ;
for ( const auto & player : m_RacingPlayers ) {
outBitStream - > Write ( player . playerID ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
for ( int i = 0 ; i < 10 ; i + + ) {
outBitStream - > Write ( player . data [ i ] ) ;
}
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// END Scripted Activity
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write1 ( ) ; // Dirty?
outBitStream - > Write ( static_cast < uint16_t > ( m_RacingPlayers . size ( ) ) ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write ( ! m_RacingPlayers . empty ( ) ) ;
if ( ! m_RacingPlayers . empty ( ) ) {
for ( const auto & player : m_RacingPlayers ) {
outBitStream - > Write1 ( ) ; // Has more date
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write ( player . playerID ) ;
outBitStream - > Write ( player . vehicleID ) ;
outBitStream - > Write ( player . playerIndex ) ;
outBitStream - > Write ( player . playerLoaded ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write0 ( ) ; // No more data
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write ( ! m_RacingPlayers . empty ( ) ) ;
if ( ! m_RacingPlayers . empty ( ) ) {
for ( const auto & player : m_RacingPlayers ) {
outBitStream - > Write1 ( ) ; // Has more date
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write ( player . playerID ) ;
outBitStream - > Write < uint32_t > ( 0 ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write0 ( ) ; // No more data
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write1 ( ) ; // Dirty?
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write ( m_RemainingLaps ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write ( static_cast < uint16_t > ( m_PathName . size ( ) ) ) ;
for ( const auto character : m_PathName ) {
outBitStream - > Write ( character ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write1 ( ) ; // ???
outBitStream - > Write1 ( ) ; // ???
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
outBitStream - > Write ( m_LeadingPlayer ) ;
outBitStream - > Write ( m_RaceBestLap ) ;
outBitStream - > Write ( m_RaceBestTime ) ;
2021-12-05 17:54:36 +00:00
}
2022-07-28 13:39:57 +00:00
RacingPlayerInfo * RacingControlComponent : : GetPlayerData ( LWOOBJID playerID ) {
for ( auto & player : m_RacingPlayers ) {
if ( player . playerID = = playerID ) {
return & player ;
}
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
return nullptr ;
2021-12-05 17:54:36 +00:00
}
void RacingControlComponent : : Update ( float deltaTime ) {
2022-07-28 13:39:57 +00:00
// This method is a mess.
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// 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 =
EntityManager : : Instance ( ) - > GetEntity ( m_LobbyPlayers [ i ] ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( playerEntity = = nullptr ) {
- - m_LoadedPlayers ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
m_LobbyPlayers . erase ( m_LobbyPlayers . begin ( ) + i ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
return ;
}
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39: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
2022-07-28 13:39:57 +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 ) {
auto * playerEntity =
EntityManager : : Instance ( ) - > GetEntity ( player ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( playerEntity = = nullptr ) {
continue ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
auto * playerInstance = dynamic_cast < Player * > ( playerEntity ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
playerInstance - > SendToZone ( m_MainWorld ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
m_LobbyPlayers . clear ( ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// 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 ) {
Game : : logger - > Log ( " RacingControlComponent " ,
" 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 + + ) {
2022-07-28 13:39:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " ,
" Loading player now! " ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
auto * player =
2022-12-22 13:16:18 +00:00
EntityManager : : Instance ( ) - > GetEntity ( m_LobbyPlayers [ positionNumber ] ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( player = = nullptr ) {
return ;
}
Game : : logger - > Log ( " RacingControlComponent " ,
" 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
2022-07-28 13:39:57 +00:00
m_Loaded = true ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
m_Loaded = true ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
return ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// 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 + + ) {
auto * playerEntity = EntityManager : : Instance ( ) - > GetEntity (
m_RacingPlayers [ i ] . playerID ) ;
if ( playerEntity = = nullptr ) {
m_RacingPlayers . erase ( m_RacingPlayers . begin ( ) + i ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
- - m_LoadedPlayers ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
return ;
}
}
// 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 =
EntityManager : : Instance ( ) - > GetEntity ( player ) ;
if ( playerEntity = = nullptr ) {
continue ;
}
auto * playerInstance = dynamic_cast < Player * > ( playerEntity ) ;
playerInstance - > SendToZone ( m_MainWorld ) ;
}
return ;
}
// Check if all players have send a ready message
int32_t readyPlayers = 0 ;
for ( const auto & player : m_RacingPlayers ) {
if ( player . playerLoaded ) {
+ + readyPlayers ;
}
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
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 ) ;
for ( const auto & player : m_RacingPlayers ) {
auto * vehicle =
EntityManager : : Instance ( ) - > GetEntity ( player . vehicleID ) ;
auto * playerEntity =
EntityManager : : Instance ( ) - > GetEntity ( player . playerID ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +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
2022-07-28 13:39:57 +00:00
vehicle - > SetPosition ( player . respawnPosition ) ;
vehicle - > SetRotation ( player . respawnRotation ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
auto * destroyableComponent =
vehicle - > GetComponent < DestroyableComponent > ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( destroyableComponent ! = nullptr ) {
destroyableComponent - > SetImagination ( 0 ) ;
}
EntityManager : : Instance ( ) - > SerializeEntity ( vehicle ) ;
EntityManager : : Instance ( ) - > SerializeEntity (
playerEntity ) ;
}
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Spawn imagination pickups
auto * minSpawner = dZoneManager : : Instance ( ) - > GetSpawnersByName (
" ImaginationSpawn_Min " ) [ 0 ] ;
auto * medSpawner = dZoneManager : : Instance ( ) - > GetSpawnersByName (
" ImaginationSpawn_Med " ) [ 0 ] ;
auto * maxSpawner = dZoneManager : : Instance ( ) - > GetSpawnersByName (
" ImaginationSpawn_Max " ) [ 0 ] ;
minSpawner - > Activate ( ) ;
if ( m_LoadedPlayers > 2 ) {
medSpawner - > Activate ( ) ;
}
if ( m_LoadedPlayers > 4 ) {
maxSpawner - > Activate ( ) ;
}
// Reset players to their start location, without smashing them
for ( auto & player : m_RacingPlayers ) {
auto * vehicleEntity =
EntityManager : : Instance ( ) - > GetEntity ( player . vehicleID ) ;
auto * playerEntity =
EntityManager : : Instance ( ) - > GetEntity ( player . playerID ) ;
if ( vehicleEntity = = nullptr | | playerEntity = = nullptr ) {
continue ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
player . noSmashOnReload = true ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
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 =
EntityManager : : Instance ( ) - > GetEntity ( player . vehicleID ) ;
auto * playerEntity =
EntityManager : : Instance ( ) - > GetEntity ( player . playerID ) ;
if ( vehicleEntity = = nullptr | | playerEntity = = nullptr ) {
continue ;
}
GameMessages : : SendVehicleUnlockInput (
player . vehicleID , false , UNASSIGNED_SYSTEM_ADDRESS ) ;
}
// Start the race
GameMessages : : SendActivityStart ( m_Parent - > GetObjectID ( ) ,
UNASSIGNED_SYSTEM_ADDRESS ) ;
m_Started = true ;
Game : : logger - > Log ( " RacingControlComponent " , " Starting race " ) ;
EntityManager : : Instance ( ) - > SerializeEntity ( m_Parent ) ;
m_StartTime = std : : time ( nullptr ) ;
}
m_StartTimer + = deltaTime ;
} else {
m_StartTimer = 0 ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
return ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// Race routines
auto * path = dZoneManager : : Instance ( ) - > GetZone ( ) - > GetPath (
GeneralUtils : : UTF16ToWTF8 ( m_PathName ) ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
for ( auto & player : m_RacingPlayers ) {
auto * vehicle = EntityManager : : Instance ( ) - > GetEntity ( player . vehicleID ) ;
auto * playerEntity =
EntityManager : : Instance ( ) - > GetEntity ( player . playerID ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( vehicle = = nullptr | | playerEntity = = nullptr ) {
continue ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
const auto vehiclePosition = vehicle - > GetPosition ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// 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 ,
2022-07-28 13:39:57 +00:00
true , false , 0 ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
OnRequestDie ( playerEntity ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
continue ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +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 ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( player . respawnIndex = = respawnIndex ) {
+ + respawnIndex ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
continue ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
const auto & position = waypoint . position ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( std : : abs ( ( int ) respawnIndex - ( int ) player . respawnIndex ) > 10 & &
player . respawnIndex ! = path - > pathWaypoints . size ( ) - 1 ) {
+ + respawnIndex ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
continue ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( Vector3 : : DistanceSquared ( position , vehiclePosition ) > 50 * 50 ) {
+ + respawnIndex ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
continue ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// 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 ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
continue ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// 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 ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// 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
2022-07-28 13:39:57 +00:00
// Cheating check
if ( lapTime < 40 ) {
continue ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
player . lap + + ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
player . lapTime = std : : time ( nullptr ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( player . bestLapTime = = 0 | | player . bestLapTime > lapTime ) {
player . bestLapTime = lapTime ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " ,
" Best lap time (%llu) " , lapTime ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
auto * missionComponent =
playerEntity - > GetComponent < MissionComponent > ( ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( missionComponent ! = nullptr ) {
2022-02-05 11:28:17 +00:00
2022-07-28 13:39:57 +00:00
// Progress lap time tasks
2023-01-22 23:38:47 +00:00
missionComponent - > Progress ( eMissionTaskType : : RACING , ( lapTime ) * 1000 , ( LWOOBJID ) eRacingTaskParam : : LAP_TIME ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
if ( player . lap = = 3 ) {
m_Finished + + ;
player . finished = m_Finished ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
const auto raceTime =
( std : : time ( nullptr ) - m_StartTime ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
player . raceTime = raceTime ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " ,
" Completed time %llu, %llu " ,
raceTime , raceTime * 1000 ) ;
2021-12-05 17:54:36 +00:00
2023-06-22 02:46:01 +00:00
LeaderboardManager : : SaveScore ( playerEntity - > GetObjectID ( ) , m_ActivityID , static_cast < float > ( player . bestLapTime ) , static_cast < float > ( player . raceTime ) , static_cast < float > ( player . finished = = 1 ) ) ;
2022-07-28 13:39:57 +00:00
// Entire race time
2023-01-22 23:38:47 +00:00
missionComponent - > Progress ( eMissionTaskType : : RACING , ( raceTime ) * 1000 , ( LWOOBJID ) eRacingTaskParam : : TOTAL_TRACK_TIME ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
auto * characterComponent = playerEntity - > GetComponent < CharacterComponent > ( ) ;
if ( characterComponent ! = nullptr ) {
characterComponent - > TrackRaceCompleted ( m_Finished = = 1 ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
// TODO: Figure out how to update the GUI leaderboard.
}
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " ,
" Lapped (%i) in (%llu) " , player . lap ,
lapTime ) ;
}
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
Game : : logger - > Log ( " RacingControlComponent " ,
" Reached point (%i)/(%i) " , player . respawnIndex ,
path - > pathWaypoints . size ( ) ) ;
2021-12-05 17:54:36 +00:00
2022-07-28 13:39:57 +00:00
break ;
}
}
2021-12-05 17:54:36 +00:00
}
std : : string RacingControlComponent : : FormatTimeString ( time_t time ) {
2022-07-28 13:39:57 +00:00
int32_t min = time / 60 ;
time - = min * 60 ;
int32_t sec = time ;
std : : string minText ;
std : : string secText ;
if ( min < = 0 ) {
minText = " 0 " ;
} else {
minText = std : : to_string ( min ) ;
}
if ( sec < = 0 ) {
secText = " 00 " ;
} else if ( sec < = 9 ) {
secText = " 0 " + std : : to_string ( sec ) ;
} else {
secText = std : : to_string ( sec ) ;
}
return minText + " : " + secText + " .00 " ;
2021-12-05 17:54:36 +00:00
}