From 3beb414b55e2e586072c409e5e2fb038e2af3f59 Mon Sep 17 00:00:00 2001
From: David Markowitz <EmosewaMC@gmail.com>
Date: Tue, 10 Dec 2024 19:10:54 -0800
Subject: [PATCH] good enough

the client code for this is a mess and should load everything at once or use non race condition code
---
 dGame/dComponents/InventoryComponent.cpp   | 19 ++++++++++++++++
 dGame/dComponents/InventoryComponent.h     |  2 ++
 dGame/dGameMessages/GameMessageHandler.cpp | 12 +++++++++++
 dGame/dGameMessages/GameMessages.cpp       | 25 ++++++++++++++++++----
 dGame/dGameMessages/GameMessages.h         |  3 +++
 5 files changed, 57 insertions(+), 4 deletions(-)

diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp
index d6883e17..0bea9fe4 100644
--- a/dGame/dComponents/InventoryComponent.cpp
+++ b/dGame/dComponents/InventoryComponent.cpp
@@ -1141,6 +1141,25 @@ void InventoryComponent::AddItemSkills(const LOT lot) {
 	SetSkill(slot, skill);
 }
 
+void InventoryComponent::FixInvisibleItems() {
+	const auto numberItemsLoadedPerFrame = 12.0f;
+	const auto callbackTime = 0.125f;
+	const auto arbitaryInventorySize = 300.0f; // max in live + dlu is less than 300, seems like a good number.
+	auto* const items = GetInventory(eInventoryType::ITEMS);
+	if (!items) return;
+
+	// Add an extra update to make sure the client can see all the items.
+	const auto something = static_cast<int32_t>(std::ceil(items->GetItems().size() / arbitaryInventorySize)) + 1;
+	LOG_DEBUG("Fixing invisible items with %i updates", something);
+
+	for (int32_t i = 1; i < something + 1; i++) {
+		// client loads 12 items every 1/8 seconds, we're adding a small hack to fix invisible inventory items due to closing the news screen too fast.
+		m_Parent->AddCallbackTimer((arbitaryInventorySize / numberItemsLoadedPerFrame) * callbackTime * i, [this]() {
+			GameMessages::SendUpdateInventoryUi(m_Parent->GetObjectID(), m_Parent->GetSystemAddress());
+			});
+	}
+}
+
 void InventoryComponent::RemoveItemSkills(const LOT lot) {
 	const auto info = Inventory::FindItemComponent(lot);
 
diff --git a/dGame/dComponents/InventoryComponent.h b/dGame/dComponents/InventoryComponent.h
index 28158ab5..0055fcea 100644
--- a/dGame/dComponents/InventoryComponent.h
+++ b/dGame/dComponents/InventoryComponent.h
@@ -404,6 +404,8 @@ public:
 	void UpdateGroup(const GroupUpdate& groupUpdate);
 	void RemoveGroup(const std::string& groupId);
 
+	void FixInvisibleItems();
+
 	~InventoryComponent() override;
 
 private:
diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp
index f32d749f..baa3a84e 100644
--- a/dGame/dGameMessages/GameMessageHandler.cpp
+++ b/dGame/dGameMessages/GameMessageHandler.cpp
@@ -104,6 +104,18 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System
 		break;
 	}
 
+	// Currently not actually used for our implementation, however its used right now to get around invisible inventory items in the client.
+	case MessageType::Game::SELECT_SKILL: {
+		auto var = entity->GetVar<bool>(u"dlu_first_time_load");
+		if (var) {
+			entity->SetVar<bool>(u"dlu_first_time_load", false);
+			InventoryComponent* inventoryComponent = entity->GetComponent<InventoryComponent>();
+			
+			if (inventoryComponent) inventoryComponent->FixInvisibleItems();
+		}
+		break;
+	}
+
 	case MessageType::Game::PLAYER_LOADED: {
 		GameMessages::SendRestoreToPostLoadStats(entity, sysAddr);
 		entity->SetPlayerReadyForUpdates();
diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp
index 8e94cee3..b42e7d01 100644
--- a/dGame/dGameMessages/GameMessages.cpp
+++ b/dGame/dGameMessages/GameMessages.cpp
@@ -982,7 +982,7 @@ void GameMessages::SendResurrect(Entity* entity) {
 				destroyableComponent->SetImagination(imaginationToRestore);
 			}
 		}
-	});
+		});
 
 	CBITSTREAM;
 	CMSGHEADER;
@@ -5080,6 +5080,12 @@ void GameMessages::HandleSetFlag(RakNet::BitStream& inStream, Entity* entity) {
 
 	auto character = entity->GetCharacter();
 	if (character) character->SetPlayerFlag(iFlagID, bFlag);
+
+	// This is always set the first time a player loads into a world from character select
+	// and is used to know when to refresh the players inventory items so they show up.
+	if (iFlagID == ePlayerFlag::IS_NEWS_SCREEN_VISIBLE && bFlag) {
+		entity->SetVar<bool>(u"dlu_first_time_load", true);
+	}
 }
 
 void GameMessages::HandleRespondToMission(RakNet::BitStream& inStream, Entity* entity) {
@@ -5147,12 +5153,12 @@ void GameMessages::HandleMissionDialogOK(RakNet::BitStream& inStream, Entity* en
 	}
 
 	if (Game::config->GetValue("allow_players_to_skip_cinematics") != "1"
-	|| !player->GetCharacter()
-	|| !player->GetCharacter()->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS)) return;
+		|| !player->GetCharacter()
+		|| !player->GetCharacter()->GetPlayerFlag(ePlayerFlag::DLU_SKIP_CINEMATICS)) return;
 	player->AddCallbackTimer(0.5f, [player]() {
 		if (!player) return;
 		GameMessages::SendEndCinematic(player->GetObjectID(), u"", player->GetSystemAddress());
-	});
+		});
 }
 
 void GameMessages::HandleRequestLinkedMission(RakNet::BitStream& inStream, Entity* entity) {
@@ -6324,3 +6330,14 @@ void GameMessages::SendForceCameraTargetCycle(Entity* entity, bool bForceCycling
 	auto sysAddr = entity->GetSystemAddress();
 	SEND_PACKET;
 }
+
+
+void GameMessages::SendUpdateInventoryUi(LWOOBJID objectId, const SystemAddress& sysAddr) {
+	CBITSTREAM;
+	CMSGHEADER;
+
+	bitStream.Write(objectId);
+	bitStream.Write(MessageType::Game::UPDATE_INVENTORY_UI);
+	
+	SEND_PACKET;
+}
diff --git a/dGame/dGameMessages/GameMessages.h b/dGame/dGameMessages/GameMessages.h
index 090fcd4b..1dbe5d81 100644
--- a/dGame/dGameMessages/GameMessages.h
+++ b/dGame/dGameMessages/GameMessages.h
@@ -677,6 +677,9 @@ namespace GameMessages {
 	void HandleUpdateInventoryGroup(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
 	void HandleUpdateInventoryGroupContents(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr);
 	void SendForceCameraTargetCycle(Entity* entity, bool bForceCycling, eCameraTargetCyclingMode cyclingMode, LWOOBJID optionalTargetID);
+
+	// This is a client gm however its default values are exactly what we need to get around the invisible inventory item issues.
+	void SendUpdateInventoryUi(LWOOBJID objectId, const SystemAddress& sysAddr);
 };
 
 #endif // GAMEMESSAGES_H