mirror of
				https://github.com/DarkflameUniverse/DarkflameServer.git
				synced 2025-10-31 12:41:55 +00:00 
			
		
		
		
	Use better naming
- Remove use of Base. It is implied if you inherit that the class inherited from is a Base. - Fix compilation errors from said change.
This commit is contained in:
		| @@ -1,5 +0,0 @@ | ||||
| #include "BaseRacingControlComponent.h" | ||||
|  | ||||
| BaseRacingControlComponent::BaseRacingControlComponent(Entity* parent, int32_t componentId) : ScriptedActivityComponent(parent, componentId) { | ||||
|  | ||||
| } | ||||
| @@ -1,16 +0,0 @@ | ||||
| #ifndef __BASERACINGCONTROLCOMPONENT__H__ | ||||
| #define __BASERACINGCONTROLCOMPONENT__H__ | ||||
|  | ||||
| #include "ScriptedActivityComponent.h" | ||||
| #include "eReplicaComponentType.h" | ||||
|  | ||||
| class Entity; | ||||
|  | ||||
| class BaseRacingControlComponent : public ScriptedActivityComponent { | ||||
| public: | ||||
| 	inline static const eReplicaComponentType ComponentType = eReplicaComponentType::RACING_CONTROL; | ||||
| 	BaseRacingControlComponent(Entity* parent, int32_t componentId); | ||||
| }; | ||||
|  | ||||
|  | ||||
| #endif  //!__BASERACINGCONTROLCOMPONENT__H__ | ||||
| @@ -1,7 +1,7 @@ | ||||
| set(DGAME_DCOMPONENTS_SOURCES "AchievementVendorComponent.cpp" | ||||
| 	"ActivityComponent.cpp" | ||||
| 	"BaseCombatAIComponent.cpp" | ||||
| 	"BaseRacingControlComponent.cpp" | ||||
| 	"RacingControlComponent.cpp" | ||||
| 	"BouncerComponent.cpp" | ||||
| 	"BuffComponent.cpp" | ||||
| 	"BuildBorderComponent.cpp" | ||||
| @@ -34,7 +34,7 @@ set(DGAME_DCOMPONENTS_SOURCES "AchievementVendorComponent.cpp" | ||||
| 	"PropertyManagementComponent.cpp" | ||||
| 	"PropertyVendorComponent.cpp" | ||||
| 	"ProximityMonitorComponent.cpp" | ||||
| 	"RacingControlComponent.cpp" | ||||
| 	"VehicleRacingControlComponent.cpp" | ||||
| 	"RacingSoundTriggerComponent.cpp" | ||||
| 	"RacingStatsComponent.cpp" | ||||
| 	"RailActivatorComponent.cpp" | ||||
|   | ||||
| @@ -4,6 +4,9 @@ Legend | ||||
| ├~~ Loaded with Parent | ||||
| ├-> SubComponent | ||||
| ├-? idk lol, but related | ||||
|  | ||||
| originalName -> newName | ||||
|  | ||||
| ``` | ||||
|  | ||||
|  | ||||
| @@ -14,8 +17,8 @@ LWOActivityComponent | ||||
| ├── LWOMiniGameControlComponent | ||||
| ├── LWOShootingGalleryComponent | ||||
| ├── LWOScriptedActivityComponent | ||||
| |	└── LWOBaseRacingControlComponent | ||||
| |		├── LWORacingControlComponent | ||||
| |	└── LWOBaseRacingControlComponent -> RacingControlComponent | ||||
| |		├── LWORacingControlComponent -> VehicleRacingControlComponent | ||||
| |		└── LWOGateRushControlComponent | ||||
| LWOBaseCombatAIComponent | ||||
| ├~~ LWOPathfindingControlComponent | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| #include "GateRushControlComponent.h" | ||||
|  | ||||
| GateRushControlComponent::GateRushControlComponent(Entity* parent, int32_t componentId) : BaseRacingControlComponent(parent, componentId) { | ||||
| GateRushControlComponent::GateRushControlComponent(Entity* parent, int32_t componentId) : RacingControlComponent(parent, componentId) { | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,12 +1,12 @@ | ||||
| #ifndef __GATERUSHCONTROLCOMPONENT__H__ | ||||
| #define __GATERUSHCONTROLCOMPONENT__H__ | ||||
|  | ||||
| #include "BaseRacingControlComponent.h" | ||||
| #include "RacingControlComponent.h" | ||||
| #include "eReplicaComponentType.h" | ||||
|  | ||||
| class Entity; | ||||
|  | ||||
| class GateRushControlComponent : public BaseRacingControlComponent { | ||||
| class GateRushControlComponent : public RacingControlComponent { | ||||
| public: | ||||
| 	inline static const eReplicaComponentType ComponentType = eReplicaComponentType::GATE_RUSH_CONTROL; | ||||
| 	GateRushControlComponent(Entity* parent, int32_t componentId); | ||||
|   | ||||
| @@ -1,880 +1,5 @@ | ||||
| /** | ||||
|  * 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" | ||||
| #include "eRacingTaskParam.h" | ||||
| #include "Spawner.h" | ||||
| #include "dServer.h" | ||||
| #include "dZoneManager.h" | ||||
| #include "dConfig.h" | ||||
| #include "Loot.h" | ||||
| #include "eMissionTaskType.h" | ||||
| #include "dZoneManager.h" | ||||
| #include "CDActivitiesTable.h" | ||||
| RacingControlComponent::RacingControlComponent(Entity* parent, int32_t componentId) : ScriptedActivityComponent(parent, componentId) { | ||||
|  | ||||
| #ifndef M_PI | ||||
| #define M_PI 3.14159265358979323846264338327950288 | ||||
| #endif | ||||
|  | ||||
| RacingControlComponent::RacingControlComponent(Entity* parent, int32_t componentId) : BaseRacingControlComponent(parent, componentId) { | ||||
| 	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"; | ||||
|  | ||||
| 	m_MainWorld = 1200; | ||||
| 	const auto worldID = Game::server->GetZoneID(); | ||||
| 	if (dZoneManager::Instance()->CheckIfAccessibleZone((worldID/10)*10)) m_MainWorld = (worldID/10)*10; | ||||
|  | ||||
| 	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; | ||||
| } | ||||
|  | ||||
| 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++; | ||||
|  | ||||
| 	Game::logger->Log("RacingControlComponent", "Loading player %i", | ||||
| 		m_LoadedPlayers); | ||||
|  | ||||
| 	m_LobbyPlayers.push_back(objectID); | ||||
| } | ||||
|  | ||||
| void RacingControlComponent::LoadPlayerVehicle(Entity* player, | ||||
| 	uint32_t positionNumber, bool initialLoad) { | ||||
| 	// 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) { | ||||
| 		Game::logger->Log("RacingControlComponent", "Failed to find item"); | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Calculate the vehicle's starting position. | ||||
|  | ||||
| 	auto* path = dZoneManager::Instance()->GetZone()->GetPath( | ||||
| 		GeneralUtils::UTF16ToWTF8(m_PathName)); | ||||
|  | ||||
| 	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; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Make sure the player is at the correct position. | ||||
|  | ||||
| 	GameMessages::SendTeleport(player->GetObjectID(), startPosition, | ||||
| 		startRotation, player->GetSystemAddress(), true); | ||||
|  | ||||
| 	// Spawn the vehicle entity. | ||||
|  | ||||
| 	EntityInfo info{}; | ||||
| 	info.lot = 8092; | ||||
| 	info.pos = startPosition; | ||||
| 	info.rot = startRotation; | ||||
| 	info.spawnerID = m_ParentEntity->GetObjectID(); | ||||
|  | ||||
| 	auto* carEntity = | ||||
| 		EntityManager::Instance()->CreateEntity(info, nullptr, m_ParentEntity); | ||||
|  | ||||
| 	// Make the vehicle a child of the racing controller. | ||||
| 	m_ParentEntity->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()); | ||||
| 		possessorComponent->SetPossessableType(ePossessionType::ATTACHED_VISIBLE); // for racing it's always Attached_Visible | ||||
| 	} | ||||
|  | ||||
| 	// 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 }); | ||||
| 	} | ||||
|  | ||||
| 	// Construct and serialize everything when done. | ||||
|  | ||||
| 	EntityManager::Instance()->ConstructEntity(carEntity); | ||||
| 	EntityManager::Instance()->SerializeEntity(player); | ||||
| 	EntityManager::Instance()->SerializeEntity(m_ParentEntity); | ||||
|  | ||||
| 	GameMessages::SendRacingSetPlayerResetInfo( | ||||
| 		m_ParentEntity->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_ParentEntity->AddCallbackTimer(1, [this, playerID]() { | ||||
| 		auto* player = EntityManager::Instance()->GetEntity(playerID); | ||||
|  | ||||
| 		if (player == nullptr) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		GameMessages::SendRacingResetPlayerToLastReset( | ||||
| 			m_ParentEntity->GetObjectID(), playerID, UNASSIGNED_SYSTEM_ADDRESS); | ||||
| 		}); | ||||
|  | ||||
| 	GameMessages::SendSetJetPackMode(player, false); | ||||
|  | ||||
| 	// Set the vehicle's state. | ||||
| 	GameMessages::SendNotifyVehicleOfRacingObject(carEntity->GetObjectID(), | ||||
| 		m_ParentEntity->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, | ||||
| 		startRotation, player->GetSystemAddress(), true); | ||||
| 	GameMessages::SendTeleport(carEntity->GetObjectID(), startPosition, | ||||
| 		startRotation, player->GetSystemAddress(), true); | ||||
| } | ||||
|  | ||||
| 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_ParentEntity->GetObjectID(), racingPlayer.playerID, | ||||
| 					racingPlayer.vehicleID, UNASSIGNED_SYSTEM_ADDRESS); | ||||
| 			} | ||||
|  | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		racingPlayer.playerLoaded = true; | ||||
|  | ||||
| 		GameMessages::SendRacingPlayerLoaded( | ||||
| 			m_ParentEntity->GetObjectID(), racingPlayer.playerID, | ||||
| 			racingPlayer.vehicleID, UNASSIGNED_SYSTEM_ADDRESS); | ||||
| 	} | ||||
|  | ||||
| 	EntityManager::Instance()->SerializeEntity(m_ParentEntity); | ||||
| } | ||||
|  | ||||
| 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); | ||||
|  | ||||
| 		if (!vehicle) return; | ||||
|  | ||||
| 		if (!racingPlayer.noSmashOnReload) { | ||||
| 			racingPlayer.smashedTimes++; | ||||
| 			GameMessages::SendDie(vehicle, vehicle->GetObjectID(), LWOOBJID_EMPTY, true, | ||||
| 				eKillType::VIOLENT, u"", 0, 0, 90.0f, false, true, 0); | ||||
|  | ||||
| 			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); | ||||
| 			} | ||||
|  | ||||
| 			// 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_ParentEntity) return; | ||||
| 				GameMessages::SendRacingResetPlayerToLastReset( | ||||
| 					m_ParentEntity->GetObjectID(), racingPlayer.playerID, | ||||
| 					UNASSIGNED_SYSTEM_ADDRESS); | ||||
|  | ||||
| 				GameMessages::SendVehicleStopBoost(vehicle, player->GetSystemAddress(), true); | ||||
|  | ||||
| 				GameMessages::SendRacingSetPlayerResetInfo( | ||||
| 					m_ParentEntity->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); | ||||
| 			}); | ||||
|  | ||||
| 			auto* characterComponent = player->GetComponent<CharacterComponent>(); | ||||
| 			if (characterComponent != nullptr) { | ||||
| 				characterComponent->UpdatePlayerStatistic(RacingTimesWrecked); | ||||
| 			} | ||||
| 		} else { | ||||
| 			GameMessages::SendRacingSetPlayerResetInfo( | ||||
| 				m_ParentEntity->GetObjectID(), racingPlayer.lap, | ||||
| 				racingPlayer.respawnIndex, player->GetObjectID(), | ||||
| 				racingPlayer.respawnPosition, racingPlayer.respawnIndex + 1, | ||||
| 				UNASSIGNED_SYSTEM_ADDRESS); | ||||
| 			GameMessages::SendRacingResetPlayerToLastReset( | ||||
| 				m_ParentEntity->GetObjectID(), racingPlayer.playerID, | ||||
| 				UNASSIGNED_SYSTEM_ADDRESS); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void RacingControlComponent::OnRacingPlayerInfoResetFinished(Entity* player) { | ||||
| 	// When the player has respawned. | ||||
|  | ||||
| 	for (auto& racingPlayer : m_RacingPlayers) { | ||||
| 		if (racingPlayer.playerID != player->GetObjectID()) { | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		auto* vehicle = | ||||
| 			EntityManager::Instance()->GetEntity(racingPlayer.vehicleID); | ||||
|  | ||||
| 		if (vehicle == nullptr) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		racingPlayer.noSmashOnReload = false; | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void RacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t button, const std::string& id) { | ||||
| 	auto* data = GetPlayerData(player->GetObjectID()); | ||||
|  | ||||
| 	if (data == nullptr) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	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; | ||||
|  | ||||
| 		LootGenerator::Instance().GiveActivityLoot(player, m_ParentEntity, m_ActivityID, score); | ||||
|  | ||||
| 		// Giving rewards | ||||
| 		GameMessages::SendNotifyRacingClient( | ||||
| 			m_ParentEntity->GetObjectID(), 2, 0, LWOOBJID_EMPTY, u"", | ||||
| 			player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); | ||||
|  | ||||
| 		auto* missionComponent = player->GetComponent<MissionComponent>(); | ||||
|  | ||||
| 		if (missionComponent == nullptr) return; | ||||
|  | ||||
| 		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 | ||||
| 			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. | ||||
| 			} | ||||
| 		} | ||||
| 	} else if ((id == "ACT_RACE_EXIT_THE_RACE?" || id == "Exit") && button == m_ActivityExitConfirm) { | ||||
| 		auto* vehicle = EntityManager::Instance()->GetEntity(data->vehicleID); | ||||
|  | ||||
| 		if (vehicle == nullptr) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		// Exiting race | ||||
| 		GameMessages::SendNotifyRacingClient( | ||||
| 			m_ParentEntity->GetObjectID(), 3, 0, LWOOBJID_EMPTY, u"", | ||||
| 			player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); | ||||
|  | ||||
| 		auto* playerInstance = dynamic_cast<Player*>(player); | ||||
|  | ||||
| 		playerInstance->SendToZone(m_MainWorld); | ||||
|  | ||||
| 		vehicle->Kill(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void RacingControlComponent::Serialize(RakNet::BitStream* outBitStream, | ||||
| 	bool bIsInitialUpdate, | ||||
| 	unsigned int& flags) { | ||||
| 	// 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 | ||||
|  | ||||
| 	outBitStream->Write1(); // Dirty? | ||||
| 	outBitStream->Write(static_cast<uint16_t>(m_RacingPlayers.size())); | ||||
|  | ||||
| 	outBitStream->Write(!m_RacingPlayers.empty()); | ||||
| 	if (!m_RacingPlayers.empty()) { | ||||
| 		for (const auto& player : m_RacingPlayers) { | ||||
| 			outBitStream->Write1(); // Has more date | ||||
|  | ||||
| 			outBitStream->Write(player.playerID); | ||||
| 			outBitStream->Write(player.vehicleID); | ||||
| 			outBitStream->Write(player.playerIndex); | ||||
| 			outBitStream->Write(player.playerLoaded); | ||||
| 		} | ||||
|  | ||||
| 		outBitStream->Write0(); // No more data | ||||
| 	} | ||||
|  | ||||
| 	outBitStream->Write(!m_RacingPlayers.empty()); | ||||
| 	if (!m_RacingPlayers.empty()) { | ||||
| 		for (const auto& player : m_RacingPlayers) { | ||||
| 			outBitStream->Write1(); // Has more date | ||||
|  | ||||
| 			outBitStream->Write(player.playerID); | ||||
| 			outBitStream->Write<uint32_t>(0); | ||||
| 		} | ||||
|  | ||||
| 		outBitStream->Write0(); // No more data | ||||
| 	} | ||||
|  | ||||
| 	outBitStream->Write1(); // Dirty? | ||||
|  | ||||
| 	outBitStream->Write(m_RemainingLaps); | ||||
|  | ||||
| 	outBitStream->Write(static_cast<uint16_t>(m_PathName.size())); | ||||
| 	for (const auto character : m_PathName) { | ||||
| 		outBitStream->Write(character); | ||||
| 	} | ||||
|  | ||||
| 	outBitStream->Write1(); // ??? | ||||
| 	outBitStream->Write1(); // ??? | ||||
|  | ||||
| 	outBitStream->Write(m_LeadingPlayer); | ||||
| 	outBitStream->Write(m_RaceBestLap); | ||||
| 	outBitStream->Write(m_RaceBestTime); | ||||
| } | ||||
|  | ||||
| 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 = | ||||
| 				EntityManager::Instance()->GetEntity(m_LobbyPlayers[i]); | ||||
|  | ||||
| 			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 = | ||||
| 					EntityManager::Instance()->GetEntity(player); | ||||
|  | ||||
| 				if (playerEntity == nullptr) { | ||||
| 					continue; | ||||
| 				} | ||||
|  | ||||
| 				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) { | ||||
| 			Game::logger->Log("RacingControlComponent", | ||||
| 				"Loading all players..."); | ||||
|  | ||||
| 			for (size_t positionNumber = 0; positionNumber < m_LobbyPlayers.size(); positionNumber++) { | ||||
| 				Game::logger->Log("RacingControlComponent", | ||||
| 					"Loading player now!"); | ||||
|  | ||||
| 				auto* player = | ||||
| 					EntityManager::Instance()->GetEntity(m_LobbyPlayers[positionNumber]); | ||||
|  | ||||
| 				if (player == nullptr) { | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				Game::logger->Log("RacingControlComponent", | ||||
| 					"Loading player now NOW!"); | ||||
|  | ||||
| 				LoadPlayerVehicle(player, positionNumber + 1, true); | ||||
|  | ||||
| 				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++) { | ||||
| 			auto* playerEntity = EntityManager::Instance()->GetEntity( | ||||
| 				m_RacingPlayers[i].playerID); | ||||
|  | ||||
| 			if (playerEntity == nullptr) { | ||||
| 				m_RacingPlayers.erase(m_RacingPlayers.begin() + i); | ||||
|  | ||||
| 				--m_LoadedPlayers; | ||||
|  | ||||
| 				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; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (readyPlayers >= m_LoadedPlayers) { | ||||
| 			// Setup for racing | ||||
| 			if (m_StartTimer == 0) { | ||||
| 				GameMessages::SendNotifyRacingClient( | ||||
| 					m_ParentEntity->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); | ||||
|  | ||||
| 					if (vehicle != nullptr && playerEntity != nullptr) { | ||||
| 						GameMessages::SendTeleport( | ||||
| 							player.playerID, player.respawnPosition, | ||||
| 							player.respawnRotation, | ||||
| 							playerEntity->GetSystemAddress(), true); | ||||
|  | ||||
| 						vehicle->SetPosition(player.respawnPosition); | ||||
| 						vehicle->SetRotation(player.respawnRotation); | ||||
|  | ||||
| 						auto* destroyableComponent = vehicle->GetComponent<DestroyableComponent>(); | ||||
|  | ||||
| 						if (destroyableComponent != nullptr) { | ||||
| 							destroyableComponent->SetImagination(0); | ||||
| 						} | ||||
|  | ||||
| 						EntityManager::Instance()->SerializeEntity(vehicle); | ||||
| 						EntityManager::Instance()->SerializeEntity( | ||||
| 							playerEntity); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// 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; | ||||
| 					} | ||||
|  | ||||
| 					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 = | ||||
| 						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_ParentEntity->GetObjectID(), | ||||
| 					UNASSIGNED_SYSTEM_ADDRESS); | ||||
|  | ||||
| 				m_Started = true; | ||||
|  | ||||
| 				Game::logger->Log("RacingControlComponent", "Starting race"); | ||||
|  | ||||
| 				EntityManager::Instance()->SerializeEntity(m_ParentEntity); | ||||
|  | ||||
| 				m_StartTime = std::time(nullptr); | ||||
| 			} | ||||
|  | ||||
| 			m_StartTimer += deltaTime; | ||||
| 		} else { | ||||
| 			m_StartTimer = 0; | ||||
| 		} | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Race routines | ||||
| 	auto* path = dZoneManager::Instance()->GetZone()->GetPath( | ||||
| 		GeneralUtils::UTF16ToWTF8(m_PathName)); | ||||
|  | ||||
| 	for (auto& player : m_RacingPlayers) { | ||||
| 		auto* vehicle = EntityManager::Instance()->GetEntity(player.vehicleID); | ||||
| 		auto* playerEntity = | ||||
| 			EntityManager::Instance()->GetEntity(player.playerID); | ||||
|  | ||||
| 		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_ParentEntity->GetObjectID(), | ||||
| 				LWOOBJID_EMPTY, true, eKillType::VIOLENT, u"", 0, 0, 0, | ||||
| 				true, false, 0); | ||||
|  | ||||
| 			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) { | ||||
| 			if (player.lap == 3) { | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			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 | ||||
| 			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 | ||||
| 			player.respawnPosition = position + NiPoint3::UNIT_Y * 5; | ||||
| 			player.respawnRotation = vehicle->GetRotation(); | ||||
| 			player.respawnIndex = respawnIndex; | ||||
|  | ||||
| 			// Reached the start point, lapped | ||||
| 			if (respawnIndex == 0) { | ||||
| 				time_t lapTime = std::time(nullptr) - (player.lap == 0 ? m_StartTime : player.lapTime); | ||||
|  | ||||
| 				// Cheating check | ||||
| 				if (lapTime < 40) { | ||||
| 					continue; | ||||
| 				} | ||||
|  | ||||
| 				player.lap++; | ||||
|  | ||||
| 				player.lapTime = std::time(nullptr); | ||||
|  | ||||
| 				if (player.bestLapTime == 0 || player.bestLapTime > lapTime) { | ||||
| 					player.bestLapTime = lapTime; | ||||
|  | ||||
| 					Game::logger->Log("RacingControlComponent", | ||||
| 						"Best lap time (%llu)", lapTime); | ||||
| 				} | ||||
|  | ||||
| 				auto* missionComponent = playerEntity->GetComponent<MissionComponent>(); | ||||
|  | ||||
| 				if (missionComponent != nullptr) { | ||||
|  | ||||
| 					// Progress lap time tasks | ||||
| 					missionComponent->Progress(eMissionTaskType::RACING, (lapTime) * 1000, (LWOOBJID)eRacingTaskParam::LAP_TIME); | ||||
|  | ||||
| 					if (player.lap == 3) { | ||||
| 						m_Finished++; | ||||
| 						player.finished = m_Finished; | ||||
|  | ||||
| 						const auto raceTime = | ||||
| 							(std::time(nullptr) - m_StartTime); | ||||
|  | ||||
| 						player.raceTime = raceTime; | ||||
|  | ||||
| 						Game::logger->Log("RacingControlComponent", | ||||
| 							"Completed time %llu, %llu", | ||||
| 							raceTime, raceTime * 1000); | ||||
|  | ||||
| 						// Entire race time | ||||
| 						missionComponent->Progress(eMissionTaskType::RACING, (raceTime) * 1000, (LWOOBJID)eRacingTaskParam::TOTAL_TRACK_TIME); | ||||
|  | ||||
| 						auto* characterComponent = playerEntity->GetComponent<CharacterComponent>(); | ||||
| 						if (characterComponent != nullptr) { | ||||
| 							characterComponent->TrackRaceCompleted(m_Finished == 1); | ||||
| 						} | ||||
|  | ||||
| 						// TODO: Figure out how to update the GUI leaderboard. | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				Game::logger->Log("RacingControlComponent", | ||||
| 					"Lapped (%i) in (%llu)", player.lap, | ||||
| 					lapTime); | ||||
| 			} | ||||
|  | ||||
| 			Game::logger->Log("RacingControlComponent", | ||||
| 				"Reached point (%i)/(%i)", player.respawnIndex, | ||||
| 				path->pathWaypoints.size()); | ||||
|  | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::string RacingControlComponent::FormatTimeString(time_t time) { | ||||
| 	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"; | ||||
| } | ||||
|   | ||||
| @@ -1,254 +1,16 @@ | ||||
| /** | ||||
|  * Thanks to Simon for his early research on the racing system. | ||||
|  */ | ||||
| #ifndef __RACINGCONTROLCOMPONENT__H__ | ||||
| #define __RACINGCONTROLCOMPONENT__H__ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "BitStream.h" | ||||
| #include "Entity.h" | ||||
| #include "BaseRacingControlComponent.h" | ||||
| #include "ScriptedActivityComponent.h" | ||||
| #include "eReplicaComponentType.h" | ||||
|  | ||||
|  /** | ||||
|   * Information for each player in the race | ||||
|   */ | ||||
| struct RacingPlayerInfo { | ||||
| class Entity; | ||||
|  | ||||
| 	/** | ||||
| 	 * The ID of the player | ||||
| 	 */ | ||||
| 	LWOOBJID playerID; | ||||
|  | ||||
| 	/** | ||||
| 	 * The ID of the car the player is driving | ||||
| 	 */ | ||||
| 	LWOOBJID vehicleID; | ||||
|  | ||||
| 	/** | ||||
| 	 * The index of this player in the list of players | ||||
| 	 */ | ||||
| 	uint32_t playerIndex; | ||||
|  | ||||
| 	/** | ||||
| 	 * Whether the player has finished loading or not | ||||
| 	 */ | ||||
| 	bool playerLoaded; | ||||
|  | ||||
| 	/** | ||||
| 	 * Scripted activity component score | ||||
| 	 */ | ||||
| 	float data[10]{}; | ||||
|  | ||||
| 	/** | ||||
| 	 * Point that the player will respawn at if they smash their car | ||||
| 	 */ | ||||
| 	NiPoint3 respawnPosition; | ||||
|  | ||||
| 	/** | ||||
| 	 * Rotation that the player will respawn at if they smash their car | ||||
| 	 */ | ||||
| 	NiQuaternion respawnRotation; | ||||
|  | ||||
| 	/** | ||||
| 	 * The index in the respawn point the player is now at | ||||
| 	 */ | ||||
| 	uint32_t respawnIndex; | ||||
|  | ||||
| 	/** | ||||
| 	 * The number of laps the player has completed | ||||
| 	 */ | ||||
| 	uint32_t lap; | ||||
|  | ||||
| 	/** | ||||
| 	 * Whether or not the player has finished the race | ||||
| 	 */ | ||||
| 	uint32_t finished; | ||||
|  | ||||
| 	/** | ||||
| 	 * Unused | ||||
| 	 */ | ||||
| 	uint16_t reachedPoints; | ||||
|  | ||||
| 	/** | ||||
| 	 * The fastest lap time of the player | ||||
| 	 */ | ||||
| 	time_t bestLapTime = 0; | ||||
|  | ||||
| 	/** | ||||
| 	 * The current lap time of the player | ||||
| 	 */ | ||||
| 	time_t lapTime = 0; | ||||
|  | ||||
| 	/** | ||||
| 	 * The number of times this player smashed their car | ||||
| 	 */ | ||||
| 	uint32_t smashedTimes = 0; | ||||
|  | ||||
| 	/** | ||||
| 	 * Whether or not the player should be smashed if the game is reloaded | ||||
| 	 */ | ||||
| 	bool noSmashOnReload = false; | ||||
|  | ||||
| 	/** | ||||
| 	 * Whether or not this player has collected their rewards from completing the race | ||||
| 	 */ | ||||
| 	bool collectedRewards = false; | ||||
|  | ||||
| 	/** | ||||
| 	 * Unused | ||||
| 	 */ | ||||
| 	time_t raceTime = 0; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Component that's attached to a manager entity in each race zone that loads player vehicles, keep scores, etc. | ||||
|  */ | ||||
| class RacingControlComponent : public BaseRacingControlComponent { | ||||
| class RacingControlComponent : public ScriptedActivityComponent { | ||||
| public: | ||||
| 	inline static const eReplicaComponentType ComponentType = eReplicaComponentType::RACING_CONTROL; | ||||
|  | ||||
| 	RacingControlComponent(Entity* parentEntity, int32_t componentId); | ||||
| 	~RacingControlComponent(); | ||||
|  | ||||
| 	void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); | ||||
| 	void Update(float deltaTime); | ||||
|  | ||||
| 	/** | ||||
| 	 * Invoked when a player loads into the zone. | ||||
| 	 */ | ||||
| 	void OnPlayerLoaded(Entity* player); | ||||
|  | ||||
| 	/** | ||||
| 	 * Initalize the player's vehicle. | ||||
| 	 * | ||||
| 	 * @param player The player who's vehicle to initialize. | ||||
| 	 * @param initialLoad Is this the first time the player is loading in this race? | ||||
| 	 */ | ||||
| 	void LoadPlayerVehicle(Entity* player, uint32_t positionNumber, bool initialLoad = false); | ||||
|  | ||||
| 	/** | ||||
| 	 * Invoked when the client says it has loaded in. | ||||
| 	 */ | ||||
| 	void OnRacingClientReady(Entity* player); | ||||
|  | ||||
| 	/** | ||||
| 	 * Invoked when the client says it should be smashed. | ||||
| 	 */ | ||||
| 	void OnRequestDie(Entity* player); | ||||
|  | ||||
| 	/** | ||||
| 	 * Invoked when the player has finished respawning. | ||||
| 	 */ | ||||
| 	void OnRacingPlayerInfoResetFinished(Entity* player); | ||||
|  | ||||
| 	/** | ||||
| 	 * Invoked when the player responds to the GUI. | ||||
| 	 */ | ||||
| 	void HandleMessageBoxResponse(Entity* player, int32_t button, const std::string& id); | ||||
|  | ||||
| 	/** | ||||
| 	 * Get the racing data from a player's LWOOBJID. | ||||
| 	 */ | ||||
| 	RacingPlayerInfo* GetPlayerData(LWOOBJID playerID); | ||||
|  | ||||
| 	/** | ||||
| 	 * Formats a time to a string, currently unused | ||||
| 	 * @param time the time to format | ||||
| 	 * @return the time formatted as string | ||||
| 	 */ | ||||
| 	static std::string FormatTimeString(time_t time); | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	/** | ||||
| 	 * The players that are currently racing | ||||
| 	 */ | ||||
| 	std::vector<RacingPlayerInfo> m_RacingPlayers; | ||||
|  | ||||
| 	/** | ||||
| 	 * The paths that are followed for the camera scenes | ||||
| 	 */ | ||||
| 	std::u16string m_PathName; | ||||
|  | ||||
| 	/** | ||||
| 	 * The ID of the activity for participating in this race | ||||
| 	 */ | ||||
| 	uint32_t m_ActivityID; | ||||
|  | ||||
| 	/** | ||||
| 	 * The world the players return to when they finish the race | ||||
| 	 */ | ||||
| 	uint32_t m_MainWorld; | ||||
|  | ||||
| 	/** | ||||
| 	 * The number of laps that are remaining for the winning player | ||||
| 	 */ | ||||
| 	uint16_t m_RemainingLaps; | ||||
|  | ||||
| 	/** | ||||
| 	 * The ID of the player that's currently winning the race | ||||
| 	 */ | ||||
| 	LWOOBJID m_LeadingPlayer; | ||||
|  | ||||
| 	/** | ||||
| 	 * The overall best lap from all the players | ||||
| 	 */ | ||||
| 	float m_RaceBestLap; | ||||
|  | ||||
| 	/** | ||||
| 	 * The overall best time from all the players | ||||
| 	 */ | ||||
| 	float m_RaceBestTime; | ||||
|  | ||||
| 	/** | ||||
| 	 * Whether or not the race has started | ||||
| 	 */ | ||||
| 	bool m_Started; | ||||
|  | ||||
| 	/** | ||||
| 	 * The time left until the race will start | ||||
| 	 */ | ||||
| 	float m_StartTimer; | ||||
|  | ||||
| 	/** | ||||
| 	 * The time left for loading the players | ||||
| 	 */ | ||||
| 	float m_LoadTimer; | ||||
|  | ||||
| 	/** | ||||
| 	 * Whether or not all players have loaded | ||||
| 	 */ | ||||
| 	bool m_Loaded; | ||||
|  | ||||
| 	/** | ||||
| 	 * The number of loaded players | ||||
| 	 */ | ||||
| 	uint32_t m_LoadedPlayers; | ||||
|  | ||||
| 	/** | ||||
| 	 * All the players that are in the lobby, loaded or not | ||||
| 	 */ | ||||
| 	std::vector<LWOOBJID> m_LobbyPlayers; | ||||
|  | ||||
| 	/** | ||||
| 	 * The number of players that have finished the race | ||||
| 	 */ | ||||
| 	uint32_t m_Finished; | ||||
|  | ||||
| 	/** | ||||
| 	 * The time the race was started | ||||
| 	 */ | ||||
| 	time_t m_StartTime; | ||||
|  | ||||
| 	/** | ||||
| 	 * Timer for tracking how long a player was alone in this race | ||||
| 	 */ | ||||
| 	float m_EmptyTimer; | ||||
|  | ||||
| 	bool m_SoloRacing; | ||||
|  | ||||
| 	/** | ||||
| 	 * Value for message box response to know if we are exiting the race via the activity dialogue | ||||
| 	 */ | ||||
| 	const int32_t m_ActivityExitConfirm = 1; | ||||
| 	RacingControlComponent(Entity* parent, int32_t componentId); | ||||
| }; | ||||
|  | ||||
|  | ||||
| #endif  //!__RACINGCONTROLCOMPONENT__H__ | ||||
|   | ||||
							
								
								
									
										880
									
								
								dGame/dComponents/VehicleRacingControlComponent.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										880
									
								
								dGame/dComponents/VehicleRacingControlComponent.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,880 @@ | ||||
| /** | ||||
|  * Thanks to Simon for his early research on the racing system. | ||||
|  */ | ||||
|  | ||||
| #include "VehicleRacingControlComponent.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" | ||||
| #include "eRacingTaskParam.h" | ||||
| #include "Spawner.h" | ||||
| #include "dServer.h" | ||||
| #include "dZoneManager.h" | ||||
| #include "dConfig.h" | ||||
| #include "Loot.h" | ||||
| #include "eMissionTaskType.h" | ||||
| #include "dZoneManager.h" | ||||
| #include "CDActivitiesTable.h" | ||||
|  | ||||
| #ifndef M_PI | ||||
| #define M_PI 3.14159265358979323846264338327950288 | ||||
| #endif | ||||
|  | ||||
| VehicleRacingControlComponent::VehicleRacingControlComponent(Entity* parent, int32_t componentId) : RacingControlComponent(parent, componentId) { | ||||
| 	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"; | ||||
|  | ||||
| 	m_MainWorld = 1200; | ||||
| 	const auto worldID = Game::server->GetZoneID(); | ||||
| 	if (dZoneManager::Instance()->CheckIfAccessibleZone((worldID/10)*10)) m_MainWorld = (worldID/10)*10; | ||||
|  | ||||
| 	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; | ||||
| } | ||||
|  | ||||
| VehicleRacingControlComponent::~VehicleRacingControlComponent() {} | ||||
|  | ||||
| void VehicleRacingControlComponent::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++; | ||||
|  | ||||
| 	Game::logger->Log("VehicleRacingControlComponent", "Loading player %i", | ||||
| 		m_LoadedPlayers); | ||||
|  | ||||
| 	m_LobbyPlayers.push_back(objectID); | ||||
| } | ||||
|  | ||||
| void VehicleRacingControlComponent::LoadPlayerVehicle(Entity* player, | ||||
| 	uint32_t positionNumber, bool initialLoad) { | ||||
| 	// 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) { | ||||
| 		Game::logger->Log("VehicleRacingControlComponent", "Failed to find item"); | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Calculate the vehicle's starting position. | ||||
|  | ||||
| 	auto* path = dZoneManager::Instance()->GetZone()->GetPath( | ||||
| 		GeneralUtils::UTF16ToWTF8(m_PathName)); | ||||
|  | ||||
| 	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; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Make sure the player is at the correct position. | ||||
|  | ||||
| 	GameMessages::SendTeleport(player->GetObjectID(), startPosition, | ||||
| 		startRotation, player->GetSystemAddress(), true); | ||||
|  | ||||
| 	// Spawn the vehicle entity. | ||||
|  | ||||
| 	EntityInfo info{}; | ||||
| 	info.lot = 8092; | ||||
| 	info.pos = startPosition; | ||||
| 	info.rot = startRotation; | ||||
| 	info.spawnerID = m_ParentEntity->GetObjectID(); | ||||
|  | ||||
| 	auto* carEntity = | ||||
| 		EntityManager::Instance()->CreateEntity(info, nullptr, m_ParentEntity); | ||||
|  | ||||
| 	// Make the vehicle a child of the racing controller. | ||||
| 	m_ParentEntity->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()); | ||||
| 		possessorComponent->SetPossessableType(ePossessionType::ATTACHED_VISIBLE); // for racing it's always Attached_Visible | ||||
| 	} | ||||
|  | ||||
| 	// 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 }); | ||||
| 	} | ||||
|  | ||||
| 	// Construct and serialize everything when done. | ||||
|  | ||||
| 	EntityManager::Instance()->ConstructEntity(carEntity); | ||||
| 	EntityManager::Instance()->SerializeEntity(player); | ||||
| 	EntityManager::Instance()->SerializeEntity(m_ParentEntity); | ||||
|  | ||||
| 	GameMessages::SendRacingSetPlayerResetInfo( | ||||
| 		m_ParentEntity->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_ParentEntity->AddCallbackTimer(1, [this, playerID]() { | ||||
| 		auto* player = EntityManager::Instance()->GetEntity(playerID); | ||||
|  | ||||
| 		if (player == nullptr) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		GameMessages::SendRacingResetPlayerToLastReset( | ||||
| 			m_ParentEntity->GetObjectID(), playerID, UNASSIGNED_SYSTEM_ADDRESS); | ||||
| 		}); | ||||
|  | ||||
| 	GameMessages::SendSetJetPackMode(player, false); | ||||
|  | ||||
| 	// Set the vehicle's state. | ||||
| 	GameMessages::SendNotifyVehicleOfRacingObject(carEntity->GetObjectID(), | ||||
| 		m_ParentEntity->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, | ||||
| 		startRotation, player->GetSystemAddress(), true); | ||||
| 	GameMessages::SendTeleport(carEntity->GetObjectID(), startPosition, | ||||
| 		startRotation, player->GetSystemAddress(), true); | ||||
| } | ||||
|  | ||||
| void VehicleRacingControlComponent::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_ParentEntity->GetObjectID(), racingPlayer.playerID, | ||||
| 					racingPlayer.vehicleID, UNASSIGNED_SYSTEM_ADDRESS); | ||||
| 			} | ||||
|  | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		racingPlayer.playerLoaded = true; | ||||
|  | ||||
| 		GameMessages::SendRacingPlayerLoaded( | ||||
| 			m_ParentEntity->GetObjectID(), racingPlayer.playerID, | ||||
| 			racingPlayer.vehicleID, UNASSIGNED_SYSTEM_ADDRESS); | ||||
| 	} | ||||
|  | ||||
| 	EntityManager::Instance()->SerializeEntity(m_ParentEntity); | ||||
| } | ||||
|  | ||||
| void VehicleRacingControlComponent::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); | ||||
|  | ||||
| 		if (!vehicle) return; | ||||
|  | ||||
| 		if (!racingPlayer.noSmashOnReload) { | ||||
| 			racingPlayer.smashedTimes++; | ||||
| 			GameMessages::SendDie(vehicle, vehicle->GetObjectID(), LWOOBJID_EMPTY, true, | ||||
| 				eKillType::VIOLENT, u"", 0, 0, 90.0f, false, true, 0); | ||||
|  | ||||
| 			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); | ||||
| 			} | ||||
|  | ||||
| 			// 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_ParentEntity) return; | ||||
| 				GameMessages::SendRacingResetPlayerToLastReset( | ||||
| 					m_ParentEntity->GetObjectID(), racingPlayer.playerID, | ||||
| 					UNASSIGNED_SYSTEM_ADDRESS); | ||||
|  | ||||
| 				GameMessages::SendVehicleStopBoost(vehicle, player->GetSystemAddress(), true); | ||||
|  | ||||
| 				GameMessages::SendRacingSetPlayerResetInfo( | ||||
| 					m_ParentEntity->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); | ||||
| 			}); | ||||
|  | ||||
| 			auto* characterComponent = player->GetComponent<CharacterComponent>(); | ||||
| 			if (characterComponent != nullptr) { | ||||
| 				characterComponent->UpdatePlayerStatistic(RacingTimesWrecked); | ||||
| 			} | ||||
| 		} else { | ||||
| 			GameMessages::SendRacingSetPlayerResetInfo( | ||||
| 				m_ParentEntity->GetObjectID(), racingPlayer.lap, | ||||
| 				racingPlayer.respawnIndex, player->GetObjectID(), | ||||
| 				racingPlayer.respawnPosition, racingPlayer.respawnIndex + 1, | ||||
| 				UNASSIGNED_SYSTEM_ADDRESS); | ||||
| 			GameMessages::SendRacingResetPlayerToLastReset( | ||||
| 				m_ParentEntity->GetObjectID(), racingPlayer.playerID, | ||||
| 				UNASSIGNED_SYSTEM_ADDRESS); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void VehicleRacingControlComponent::OnRacingPlayerInfoResetFinished(Entity* player) { | ||||
| 	// When the player has respawned. | ||||
|  | ||||
| 	for (auto& racingPlayer : m_RacingPlayers) { | ||||
| 		if (racingPlayer.playerID != player->GetObjectID()) { | ||||
| 			continue; | ||||
| 		} | ||||
|  | ||||
| 		auto* vehicle = | ||||
| 			EntityManager::Instance()->GetEntity(racingPlayer.vehicleID); | ||||
|  | ||||
| 		if (vehicle == nullptr) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		racingPlayer.noSmashOnReload = false; | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void VehicleRacingControlComponent::HandleMessageBoxResponse(Entity* player, int32_t button, const std::string& id) { | ||||
| 	auto* data = GetPlayerData(player->GetObjectID()); | ||||
|  | ||||
| 	if (data == nullptr) { | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	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; | ||||
|  | ||||
| 		LootGenerator::Instance().GiveActivityLoot(player, m_ParentEntity, m_ActivityID, score); | ||||
|  | ||||
| 		// Giving rewards | ||||
| 		GameMessages::SendNotifyRacingClient( | ||||
| 			m_ParentEntity->GetObjectID(), 2, 0, LWOOBJID_EMPTY, u"", | ||||
| 			player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); | ||||
|  | ||||
| 		auto* missionComponent = player->GetComponent<MissionComponent>(); | ||||
|  | ||||
| 		if (missionComponent == nullptr) return; | ||||
|  | ||||
| 		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 | ||||
| 			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. | ||||
| 			} | ||||
| 		} | ||||
| 	} else if ((id == "ACT_RACE_EXIT_THE_RACE?" || id == "Exit") && button == m_ActivityExitConfirm) { | ||||
| 		auto* vehicle = EntityManager::Instance()->GetEntity(data->vehicleID); | ||||
|  | ||||
| 		if (vehicle == nullptr) { | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		// Exiting race | ||||
| 		GameMessages::SendNotifyRacingClient( | ||||
| 			m_ParentEntity->GetObjectID(), 3, 0, LWOOBJID_EMPTY, u"", | ||||
| 			player->GetObjectID(), UNASSIGNED_SYSTEM_ADDRESS); | ||||
|  | ||||
| 		auto* playerInstance = dynamic_cast<Player*>(player); | ||||
|  | ||||
| 		playerInstance->SendToZone(m_MainWorld); | ||||
|  | ||||
| 		vehicle->Kill(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void VehicleRacingControlComponent::Serialize(RakNet::BitStream* outBitStream, | ||||
| 	bool bIsInitialUpdate, | ||||
| 	unsigned int& flags) { | ||||
| 	// 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 | ||||
|  | ||||
| 	outBitStream->Write1(); // Dirty? | ||||
| 	outBitStream->Write(static_cast<uint16_t>(m_RacingPlayers.size())); | ||||
|  | ||||
| 	outBitStream->Write(!m_RacingPlayers.empty()); | ||||
| 	if (!m_RacingPlayers.empty()) { | ||||
| 		for (const auto& player : m_RacingPlayers) { | ||||
| 			outBitStream->Write1(); // Has more date | ||||
|  | ||||
| 			outBitStream->Write(player.playerID); | ||||
| 			outBitStream->Write(player.vehicleID); | ||||
| 			outBitStream->Write(player.playerIndex); | ||||
| 			outBitStream->Write(player.playerLoaded); | ||||
| 		} | ||||
|  | ||||
| 		outBitStream->Write0(); // No more data | ||||
| 	} | ||||
|  | ||||
| 	outBitStream->Write(!m_RacingPlayers.empty()); | ||||
| 	if (!m_RacingPlayers.empty()) { | ||||
| 		for (const auto& player : m_RacingPlayers) { | ||||
| 			outBitStream->Write1(); // Has more date | ||||
|  | ||||
| 			outBitStream->Write(player.playerID); | ||||
| 			outBitStream->Write<uint32_t>(0); | ||||
| 		} | ||||
|  | ||||
| 		outBitStream->Write0(); // No more data | ||||
| 	} | ||||
|  | ||||
| 	outBitStream->Write1(); // Dirty? | ||||
|  | ||||
| 	outBitStream->Write(m_RemainingLaps); | ||||
|  | ||||
| 	outBitStream->Write(static_cast<uint16_t>(m_PathName.size())); | ||||
| 	for (const auto character : m_PathName) { | ||||
| 		outBitStream->Write(character); | ||||
| 	} | ||||
|  | ||||
| 	outBitStream->Write1(); // ??? | ||||
| 	outBitStream->Write1(); // ??? | ||||
|  | ||||
| 	outBitStream->Write(m_LeadingPlayer); | ||||
| 	outBitStream->Write(m_RaceBestLap); | ||||
| 	outBitStream->Write(m_RaceBestTime); | ||||
| } | ||||
|  | ||||
| RacingPlayerInfo* VehicleRacingControlComponent::GetPlayerData(LWOOBJID playerID) { | ||||
| 	for (auto& player : m_RacingPlayers) { | ||||
| 		if (player.playerID == playerID) { | ||||
| 			return &player; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return nullptr; | ||||
| } | ||||
|  | ||||
| void VehicleRacingControlComponent::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 = | ||||
| 				EntityManager::Instance()->GetEntity(m_LobbyPlayers[i]); | ||||
|  | ||||
| 			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 = | ||||
| 					EntityManager::Instance()->GetEntity(player); | ||||
|  | ||||
| 				if (playerEntity == nullptr) { | ||||
| 					continue; | ||||
| 				} | ||||
|  | ||||
| 				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) { | ||||
| 			Game::logger->Log("VehicleRacingControlComponent", | ||||
| 				"Loading all players..."); | ||||
|  | ||||
| 			for (size_t positionNumber = 0; positionNumber < m_LobbyPlayers.size(); positionNumber++) { | ||||
| 				Game::logger->Log("VehicleRacingControlComponent", | ||||
| 					"Loading player now!"); | ||||
|  | ||||
| 				auto* player = | ||||
| 					EntityManager::Instance()->GetEntity(m_LobbyPlayers[positionNumber]); | ||||
|  | ||||
| 				if (player == nullptr) { | ||||
| 					return; | ||||
| 				} | ||||
|  | ||||
| 				Game::logger->Log("VehicleRacingControlComponent", | ||||
| 					"Loading player now NOW!"); | ||||
|  | ||||
| 				LoadPlayerVehicle(player, positionNumber + 1, true); | ||||
|  | ||||
| 				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++) { | ||||
| 			auto* playerEntity = EntityManager::Instance()->GetEntity( | ||||
| 				m_RacingPlayers[i].playerID); | ||||
|  | ||||
| 			if (playerEntity == nullptr) { | ||||
| 				m_RacingPlayers.erase(m_RacingPlayers.begin() + i); | ||||
|  | ||||
| 				--m_LoadedPlayers; | ||||
|  | ||||
| 				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; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if (readyPlayers >= m_LoadedPlayers) { | ||||
| 			// Setup for racing | ||||
| 			if (m_StartTimer == 0) { | ||||
| 				GameMessages::SendNotifyRacingClient( | ||||
| 					m_ParentEntity->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); | ||||
|  | ||||
| 					if (vehicle != nullptr && playerEntity != nullptr) { | ||||
| 						GameMessages::SendTeleport( | ||||
| 							player.playerID, player.respawnPosition, | ||||
| 							player.respawnRotation, | ||||
| 							playerEntity->GetSystemAddress(), true); | ||||
|  | ||||
| 						vehicle->SetPosition(player.respawnPosition); | ||||
| 						vehicle->SetRotation(player.respawnRotation); | ||||
|  | ||||
| 						auto* destroyableComponent = vehicle->GetComponent<DestroyableComponent>(); | ||||
|  | ||||
| 						if (destroyableComponent != nullptr) { | ||||
| 							destroyableComponent->SetImagination(0); | ||||
| 						} | ||||
|  | ||||
| 						EntityManager::Instance()->SerializeEntity(vehicle); | ||||
| 						EntityManager::Instance()->SerializeEntity( | ||||
| 							playerEntity); | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				// 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; | ||||
| 					} | ||||
|  | ||||
| 					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 = | ||||
| 						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_ParentEntity->GetObjectID(), | ||||
| 					UNASSIGNED_SYSTEM_ADDRESS); | ||||
|  | ||||
| 				m_Started = true; | ||||
|  | ||||
| 				Game::logger->Log("VehicleRacingControlComponent", "Starting race"); | ||||
|  | ||||
| 				EntityManager::Instance()->SerializeEntity(m_ParentEntity); | ||||
|  | ||||
| 				m_StartTime = std::time(nullptr); | ||||
| 			} | ||||
|  | ||||
| 			m_StartTimer += deltaTime; | ||||
| 		} else { | ||||
| 			m_StartTimer = 0; | ||||
| 		} | ||||
|  | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// Race routines | ||||
| 	auto* path = dZoneManager::Instance()->GetZone()->GetPath( | ||||
| 		GeneralUtils::UTF16ToWTF8(m_PathName)); | ||||
|  | ||||
| 	for (auto& player : m_RacingPlayers) { | ||||
| 		auto* vehicle = EntityManager::Instance()->GetEntity(player.vehicleID); | ||||
| 		auto* playerEntity = | ||||
| 			EntityManager::Instance()->GetEntity(player.playerID); | ||||
|  | ||||
| 		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_ParentEntity->GetObjectID(), | ||||
| 				LWOOBJID_EMPTY, true, eKillType::VIOLENT, u"", 0, 0, 0, | ||||
| 				true, false, 0); | ||||
|  | ||||
| 			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) { | ||||
| 			if (player.lap == 3) { | ||||
| 				break; | ||||
| 			} | ||||
|  | ||||
| 			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 | ||||
| 			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 | ||||
| 			player.respawnPosition = position + NiPoint3::UNIT_Y * 5; | ||||
| 			player.respawnRotation = vehicle->GetRotation(); | ||||
| 			player.respawnIndex = respawnIndex; | ||||
|  | ||||
| 			// Reached the start point, lapped | ||||
| 			if (respawnIndex == 0) { | ||||
| 				time_t lapTime = std::time(nullptr) - (player.lap == 0 ? m_StartTime : player.lapTime); | ||||
|  | ||||
| 				// Cheating check | ||||
| 				if (lapTime < 40) { | ||||
| 					continue; | ||||
| 				} | ||||
|  | ||||
| 				player.lap++; | ||||
|  | ||||
| 				player.lapTime = std::time(nullptr); | ||||
|  | ||||
| 				if (player.bestLapTime == 0 || player.bestLapTime > lapTime) { | ||||
| 					player.bestLapTime = lapTime; | ||||
|  | ||||
| 					Game::logger->Log("VehicleRacingControlComponent", | ||||
| 						"Best lap time (%llu)", lapTime); | ||||
| 				} | ||||
|  | ||||
| 				auto* missionComponent = playerEntity->GetComponent<MissionComponent>(); | ||||
|  | ||||
| 				if (missionComponent != nullptr) { | ||||
|  | ||||
| 					// Progress lap time tasks | ||||
| 					missionComponent->Progress(eMissionTaskType::RACING, (lapTime) * 1000, (LWOOBJID)eRacingTaskParam::LAP_TIME); | ||||
|  | ||||
| 					if (player.lap == 3) { | ||||
| 						m_Finished++; | ||||
| 						player.finished = m_Finished; | ||||
|  | ||||
| 						const auto raceTime = | ||||
| 							(std::time(nullptr) - m_StartTime); | ||||
|  | ||||
| 						player.raceTime = raceTime; | ||||
|  | ||||
| 						Game::logger->Log("VehicleRacingControlComponent", | ||||
| 							"Completed time %llu, %llu", | ||||
| 							raceTime, raceTime * 1000); | ||||
|  | ||||
| 						// Entire race time | ||||
| 						missionComponent->Progress(eMissionTaskType::RACING, (raceTime) * 1000, (LWOOBJID)eRacingTaskParam::TOTAL_TRACK_TIME); | ||||
|  | ||||
| 						auto* characterComponent = playerEntity->GetComponent<CharacterComponent>(); | ||||
| 						if (characterComponent != nullptr) { | ||||
| 							characterComponent->TrackRaceCompleted(m_Finished == 1); | ||||
| 						} | ||||
|  | ||||
| 						// TODO: Figure out how to update the GUI leaderboard. | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				Game::logger->Log("VehicleRacingControlComponent", | ||||
| 					"Lapped (%i) in (%llu)", player.lap, | ||||
| 					lapTime); | ||||
| 			} | ||||
|  | ||||
| 			Game::logger->Log("VehicleRacingControlComponent", | ||||
| 				"Reached point (%i)/(%i)", player.respawnIndex, | ||||
| 				path->pathWaypoints.size()); | ||||
|  | ||||
| 			break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| std::string VehicleRacingControlComponent::FormatTimeString(time_t time) { | ||||
| 	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"; | ||||
| } | ||||
							
								
								
									
										254
									
								
								dGame/dComponents/VehicleRacingControlComponent.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										254
									
								
								dGame/dComponents/VehicleRacingControlComponent.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,254 @@ | ||||
| /** | ||||
|  * Thanks to Simon for his early research on the racing system. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "BitStream.h" | ||||
| #include "Entity.h" | ||||
| #include "RacingControlComponent.h" | ||||
| #include "eReplicaComponentType.h" | ||||
|  | ||||
|  /** | ||||
|   * Information for each player in the race | ||||
|   */ | ||||
| struct RacingPlayerInfo { | ||||
|  | ||||
| 	/** | ||||
| 	 * The ID of the player | ||||
| 	 */ | ||||
| 	LWOOBJID playerID; | ||||
|  | ||||
| 	/** | ||||
| 	 * The ID of the car the player is driving | ||||
| 	 */ | ||||
| 	LWOOBJID vehicleID; | ||||
|  | ||||
| 	/** | ||||
| 	 * The index of this player in the list of players | ||||
| 	 */ | ||||
| 	uint32_t playerIndex; | ||||
|  | ||||
| 	/** | ||||
| 	 * Whether the player has finished loading or not | ||||
| 	 */ | ||||
| 	bool playerLoaded; | ||||
|  | ||||
| 	/** | ||||
| 	 * Scripted activity component score | ||||
| 	 */ | ||||
| 	float data[10]{}; | ||||
|  | ||||
| 	/** | ||||
| 	 * Point that the player will respawn at if they smash their car | ||||
| 	 */ | ||||
| 	NiPoint3 respawnPosition; | ||||
|  | ||||
| 	/** | ||||
| 	 * Rotation that the player will respawn at if they smash their car | ||||
| 	 */ | ||||
| 	NiQuaternion respawnRotation; | ||||
|  | ||||
| 	/** | ||||
| 	 * The index in the respawn point the player is now at | ||||
| 	 */ | ||||
| 	uint32_t respawnIndex; | ||||
|  | ||||
| 	/** | ||||
| 	 * The number of laps the player has completed | ||||
| 	 */ | ||||
| 	uint32_t lap; | ||||
|  | ||||
| 	/** | ||||
| 	 * Whether or not the player has finished the race | ||||
| 	 */ | ||||
| 	uint32_t finished; | ||||
|  | ||||
| 	/** | ||||
| 	 * Unused | ||||
| 	 */ | ||||
| 	uint16_t reachedPoints; | ||||
|  | ||||
| 	/** | ||||
| 	 * The fastest lap time of the player | ||||
| 	 */ | ||||
| 	time_t bestLapTime = 0; | ||||
|  | ||||
| 	/** | ||||
| 	 * The current lap time of the player | ||||
| 	 */ | ||||
| 	time_t lapTime = 0; | ||||
|  | ||||
| 	/** | ||||
| 	 * The number of times this player smashed their car | ||||
| 	 */ | ||||
| 	uint32_t smashedTimes = 0; | ||||
|  | ||||
| 	/** | ||||
| 	 * Whether or not the player should be smashed if the game is reloaded | ||||
| 	 */ | ||||
| 	bool noSmashOnReload = false; | ||||
|  | ||||
| 	/** | ||||
| 	 * Whether or not this player has collected their rewards from completing the race | ||||
| 	 */ | ||||
| 	bool collectedRewards = false; | ||||
|  | ||||
| 	/** | ||||
| 	 * Unused | ||||
| 	 */ | ||||
| 	time_t raceTime = 0; | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Component that's attached to a manager entity in each race zone that loads player vehicles, keep scores, etc. | ||||
|  */ | ||||
| class VehicleRacingControlComponent : public RacingControlComponent { | ||||
| public: | ||||
| 	inline static const eReplicaComponentType ComponentType = eReplicaComponentType::RACING_CONTROL; | ||||
|  | ||||
| 	VehicleRacingControlComponent(Entity* parentEntity, int32_t componentId); | ||||
| 	~VehicleRacingControlComponent(); | ||||
|  | ||||
| 	void Serialize(RakNet::BitStream* outBitStream, bool bIsInitialUpdate, unsigned int& flags); | ||||
| 	void Update(float deltaTime); | ||||
|  | ||||
| 	/** | ||||
| 	 * Invoked when a player loads into the zone. | ||||
| 	 */ | ||||
| 	void OnPlayerLoaded(Entity* player); | ||||
|  | ||||
| 	/** | ||||
| 	 * Initalize the player's vehicle. | ||||
| 	 * | ||||
| 	 * @param player The player who's vehicle to initialize. | ||||
| 	 * @param initialLoad Is this the first time the player is loading in this race? | ||||
| 	 */ | ||||
| 	void LoadPlayerVehicle(Entity* player, uint32_t positionNumber, bool initialLoad = false); | ||||
|  | ||||
| 	/** | ||||
| 	 * Invoked when the client says it has loaded in. | ||||
| 	 */ | ||||
| 	void OnRacingClientReady(Entity* player); | ||||
|  | ||||
| 	/** | ||||
| 	 * Invoked when the client says it should be smashed. | ||||
| 	 */ | ||||
| 	void OnRequestDie(Entity* player); | ||||
|  | ||||
| 	/** | ||||
| 	 * Invoked when the player has finished respawning. | ||||
| 	 */ | ||||
| 	void OnRacingPlayerInfoResetFinished(Entity* player); | ||||
|  | ||||
| 	/** | ||||
| 	 * Invoked when the player responds to the GUI. | ||||
| 	 */ | ||||
| 	void HandleMessageBoxResponse(Entity* player, int32_t button, const std::string& id); | ||||
|  | ||||
| 	/** | ||||
| 	 * Get the racing data from a player's LWOOBJID. | ||||
| 	 */ | ||||
| 	RacingPlayerInfo* GetPlayerData(LWOOBJID playerID); | ||||
|  | ||||
| 	/** | ||||
| 	 * Formats a time to a string, currently unused | ||||
| 	 * @param time the time to format | ||||
| 	 * @return the time formatted as string | ||||
| 	 */ | ||||
| 	static std::string FormatTimeString(time_t time); | ||||
|  | ||||
| private: | ||||
|  | ||||
| 	/** | ||||
| 	 * The players that are currently racing | ||||
| 	 */ | ||||
| 	std::vector<RacingPlayerInfo> m_RacingPlayers; | ||||
|  | ||||
| 	/** | ||||
| 	 * The paths that are followed for the camera scenes | ||||
| 	 */ | ||||
| 	std::u16string m_PathName; | ||||
|  | ||||
| 	/** | ||||
| 	 * The ID of the activity for participating in this race | ||||
| 	 */ | ||||
| 	uint32_t m_ActivityID; | ||||
|  | ||||
| 	/** | ||||
| 	 * The world the players return to when they finish the race | ||||
| 	 */ | ||||
| 	uint32_t m_MainWorld; | ||||
|  | ||||
| 	/** | ||||
| 	 * The number of laps that are remaining for the winning player | ||||
| 	 */ | ||||
| 	uint16_t m_RemainingLaps; | ||||
|  | ||||
| 	/** | ||||
| 	 * The ID of the player that's currently winning the race | ||||
| 	 */ | ||||
| 	LWOOBJID m_LeadingPlayer; | ||||
|  | ||||
| 	/** | ||||
| 	 * The overall best lap from all the players | ||||
| 	 */ | ||||
| 	float m_RaceBestLap; | ||||
|  | ||||
| 	/** | ||||
| 	 * The overall best time from all the players | ||||
| 	 */ | ||||
| 	float m_RaceBestTime; | ||||
|  | ||||
| 	/** | ||||
| 	 * Whether or not the race has started | ||||
| 	 */ | ||||
| 	bool m_Started; | ||||
|  | ||||
| 	/** | ||||
| 	 * The time left until the race will start | ||||
| 	 */ | ||||
| 	float m_StartTimer; | ||||
|  | ||||
| 	/** | ||||
| 	 * The time left for loading the players | ||||
| 	 */ | ||||
| 	float m_LoadTimer; | ||||
|  | ||||
| 	/** | ||||
| 	 * Whether or not all players have loaded | ||||
| 	 */ | ||||
| 	bool m_Loaded; | ||||
|  | ||||
| 	/** | ||||
| 	 * The number of loaded players | ||||
| 	 */ | ||||
| 	uint32_t m_LoadedPlayers; | ||||
|  | ||||
| 	/** | ||||
| 	 * All the players that are in the lobby, loaded or not | ||||
| 	 */ | ||||
| 	std::vector<LWOOBJID> m_LobbyPlayers; | ||||
|  | ||||
| 	/** | ||||
| 	 * The number of players that have finished the race | ||||
| 	 */ | ||||
| 	uint32_t m_Finished; | ||||
|  | ||||
| 	/** | ||||
| 	 * The time the race was started | ||||
| 	 */ | ||||
| 	time_t m_StartTime; | ||||
|  | ||||
| 	/** | ||||
| 	 * Timer for tracking how long a player was alone in this race | ||||
| 	 */ | ||||
| 	float m_EmptyTimer; | ||||
|  | ||||
| 	bool m_SoloRacing; | ||||
|  | ||||
| 	/** | ||||
| 	 * Value for message box response to know if we are exiting the race via the activity dialogue | ||||
| 	 */ | ||||
| 	const int32_t m_ActivityExitConfirm = 1; | ||||
| }; | ||||
| @@ -25,7 +25,7 @@ | ||||
| #include "CDClientManager.h" | ||||
| #include "CDSkillBehaviorTable.h" | ||||
| #include "SkillComponent.h" | ||||
| #include "RacingControlComponent.h" | ||||
| #include "VehicleRacingControlComponent.h" | ||||
| #include "RequestServerProjectileImpact.h" | ||||
| #include "SyncSkill.h" | ||||
| #include "StartSkill.h" | ||||
| @@ -126,10 +126,8 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream* inStream, const System | ||||
|  | ||||
| 		std::vector<Entity*> racingControllers = EntityManager::Instance()->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); | ||||
| 		for (Entity* racingController : racingControllers) { | ||||
| 			auto* racingComponent = racingController->GetComponent<RacingControlComponent>(); | ||||
| 			if (racingComponent != nullptr) { | ||||
| 				racingComponent->OnPlayerLoaded(entity); | ||||
| 			} | ||||
| 			auto* vehicleRacingControlComponent = racingController->GetComponent<VehicleRacingControlComponent>(); | ||||
| 			if (vehicleRacingControlComponent) vehicleRacingControlComponent->OnPlayerLoaded(entity); | ||||
| 		} | ||||
|  | ||||
| 		Entity* zoneControl = EntityManager::Instance()->GetZoneControlEntity(); | ||||
|   | ||||
| @@ -73,7 +73,7 @@ | ||||
| #include "RenderComponent.h" | ||||
| #include "PossessableComponent.h" | ||||
| #include "PossessorComponent.h" | ||||
| #include "RacingControlComponent.h" | ||||
| #include "VehicleRacingControlComponent.h" | ||||
| #include "RailActivatorComponent.h" | ||||
| #include "LevelProgressionComponent.h" | ||||
|  | ||||
| @@ -3889,11 +3889,9 @@ void GameMessages::HandleMessageBoxResponse(RakNet::BitStream* inStream, Entity* | ||||
| 		scriptedActivityComponent->HandleMessageBoxResponse(userEntity, GeneralUtils::UTF16ToWTF8(identifier)); | ||||
| 	} | ||||
|  | ||||
| 	auto* racingControlComponent = entity->GetComponent<RacingControlComponent>(); | ||||
| 	auto* vehicleRacingControlComponent = entity->GetComponent<VehicleRacingControlComponent>(); | ||||
|  | ||||
| 	if (racingControlComponent != nullptr) { | ||||
| 		racingControlComponent->HandleMessageBoxResponse(userEntity, iButton, GeneralUtils::UTF16ToWTF8(identifier)); | ||||
| 	} | ||||
| 	if (vehicleRacingControlComponent) vehicleRacingControlComponent->HandleMessageBoxResponse(userEntity, iButton, GeneralUtils::UTF16ToWTF8(identifier)); | ||||
|  | ||||
| 	for (auto* shootingGallery : EntityManager::Instance()->GetEntitiesByComponent(eReplicaComponentType::SHOOTING_GALLERY)) { | ||||
| 		shootingGallery->OnMessageBoxResponse(userEntity, iButton, identifier, userData); | ||||
| @@ -4137,13 +4135,11 @@ void GameMessages::HandleRacingClientReady(RakNet::BitStream* inStream, Entity* | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	auto* racingControlComponent = dZoneManager::Instance()->GetZoneControlObject()->GetComponent<RacingControlComponent>(); | ||||
| 	auto* vehicleRacingControlComponent = dZoneManager::Instance()->GetZoneControlObject()->GetComponent<VehicleRacingControlComponent>(); | ||||
|  | ||||
| 	if (racingControlComponent == nullptr) { | ||||
| 		return; | ||||
| 	} | ||||
| 	if (!vehicleRacingControlComponent) return; | ||||
|  | ||||
| 	racingControlComponent->OnRacingClientReady(player); | ||||
| 	vehicleRacingControlComponent->OnRacingClientReady(player); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -4187,23 +4183,20 @@ void GameMessages::HandleRequestDie(RakNet::BitStream* inStream, Entity* entity, | ||||
|  | ||||
| 	auto* zoneController = dZoneManager::Instance()->GetZoneControlObject(); | ||||
|  | ||||
| 	auto* racingControlComponent = zoneController->GetComponent<RacingControlComponent>(); | ||||
| 	auto* vehicleRacingControlComponent = zoneController->GetComponent<VehicleRacingControlComponent>(); | ||||
|  | ||||
| 	Game::logger->Log("HandleRequestDie", "Got die request: %i", entity->GetLOT()); | ||||
|  | ||||
| 	if (racingControlComponent != nullptr) { | ||||
| 		auto* possessableComponent = entity->GetComponent<PossessableComponent>(); | ||||
| 	if (!vehicleRacingControlComponent) return; | ||||
| 	auto* possessableComponent = entity->GetComponent<PossessableComponent>(); | ||||
|  | ||||
| 		if (possessableComponent != nullptr) { | ||||
| 			entity = EntityManager::Instance()->GetEntity(possessableComponent->GetPossessor()); | ||||
| 	if (possessableComponent) { | ||||
| 		entity = EntityManager::Instance()->GetEntity(possessableComponent->GetPossessor()); | ||||
|  | ||||
| 			if (entity == nullptr) { | ||||
| 				return; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		racingControlComponent->OnRequestDie(entity); | ||||
| 		if (!entity) return; | ||||
| 	} | ||||
|  | ||||
| 	vehicleRacingControlComponent->OnRequestDie(entity); | ||||
| } | ||||
|  | ||||
|  | ||||
| @@ -4230,13 +4223,11 @@ void GameMessages::HandleRacingPlayerInfoResetFinished(RakNet::BitStream* inStre | ||||
|  | ||||
| 	auto* zoneController = dZoneManager::Instance()->GetZoneControlObject(); | ||||
|  | ||||
| 	auto* racingControlComponent = zoneController->GetComponent<RacingControlComponent>(); | ||||
| 	auto* vehicleRacingControlComponent = zoneController->GetComponent<VehicleRacingControlComponent>(); | ||||
|  | ||||
| 	Game::logger->Log("HandleRacingPlayerInfoResetFinished", "Got finished: %i", entity->GetLOT()); | ||||
|  | ||||
| 	if (racingControlComponent != nullptr) { | ||||
| 		racingControlComponent->OnRacingPlayerInfoResetFinished(player); | ||||
| 	} | ||||
| 	if (vehicleRacingControlComponent) vehicleRacingControlComponent->OnRacingPlayerInfoResetFinished(player); | ||||
| } | ||||
|  | ||||
| void GameMessages::SendUpdateReputation(const LWOOBJID objectId, const int64_t reputation, const SystemAddress& sysAddr) { | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| #include "PossessableComponent.h" | ||||
| #include "PossessorComponent.h" | ||||
| #include "EntityManager.h" | ||||
| #include "RacingControlComponent.h" | ||||
| #include "VehicleRacingControlComponent.h" | ||||
| #include "dZoneManager.h" | ||||
|  | ||||
| void RaceMaelstromGeiser::OnStartup(Entity* self) { | ||||
| @@ -59,11 +59,9 @@ void RaceMaelstromGeiser::OnProximityUpdate(Entity* self, Entity* entering, std: | ||||
|  | ||||
| 	auto* zoneController = dZoneManager::Instance()->GetZoneControlObject(); | ||||
|  | ||||
| 	auto* racingControlComponent = zoneController->GetComponent<RacingControlComponent>(); | ||||
| 	auto* vehicleRacingControlComponent = zoneController->GetComponent<VehicleRacingControlComponent>(); | ||||
|  | ||||
| 	if (racingControlComponent != nullptr) { | ||||
| 		racingControlComponent->OnRequestDie(player); | ||||
| 	} | ||||
| 	if (vehicleRacingControlComponent) vehicleRacingControlComponent->OnRequestDie(player); | ||||
| } | ||||
|  | ||||
| void RaceMaelstromGeiser::OnTimerDone(Entity* self, std::string timerName) { | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| #include "ActVehicleDeathTrigger.h" | ||||
| #include "PossessableComponent.h" | ||||
| #include "GameMessages.h" | ||||
| #include "RacingControlComponent.h" | ||||
| #include "VehicleRacingControlComponent.h" | ||||
| #include "dZoneManager.h" | ||||
| #include "EntityManager.h" | ||||
| #include "PossessorComponent.h" | ||||
| @@ -44,9 +44,7 @@ void ActVehicleDeathTrigger::OnCollisionPhantom(Entity* self, Entity* target) { | ||||
|  | ||||
| 	auto* zoneController = dZoneManager::Instance()->GetZoneControlObject(); | ||||
|  | ||||
| 	auto* racingControlComponent = zoneController->GetComponent<RacingControlComponent>(); | ||||
| 	auto* vehicleRacingControlComponent = zoneController->GetComponent<VehicleRacingControlComponent>(); | ||||
|  | ||||
| 	if (racingControlComponent != nullptr) { | ||||
| 		racingControlComponent->OnRequestDie(player); | ||||
| 	} | ||||
| 	if (vehicleRacingControlComponent) vehicleRacingControlComponent->OnRequestDie(player); | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 David Markowitz
					David Markowitz