From cc1bc148d06f382b35e31ba63c1683fca241884a Mon Sep 17 00:00:00 2001
From: Nathan M Gilbert <tahgtahv@gmail.com>
Date: Mon, 19 Feb 2024 09:24:30 -0500
Subject: [PATCH] Implement HistoryBook::ReadyWorld (#579)

* Implement HistoryBook::ReadyWorld

* Fix names and annotations

* WIP improvements

* Remove unnecessary padding/pack

* Remove packing

* Style

* Use countof

* Fix signed

* Fixes

* Remove duplicate annotation

* Rename

---------

Co-authored-by: Christian Semmler <mail@csemmler.com>
---
 LEGO1/lego/legoomni/include/historybook.h     |  7 +-
 LEGO1/lego/legoomni/include/legogamestate.h   | 41 ++++++---
 .../legoomni/src/common/legogamestate.cpp     | 16 +++-
 .../legoomni/src/infocenter/infocenter.cpp    |  8 +-
 LEGO1/lego/legoomni/src/isle/historybook.cpp  | 89 +++++++++++++++++--
 LEGO1/omni/include/mxstillpresenter.h         |  2 +-
 LEGO1/omni/src/video/mxstillpresenter.cpp     |  2 +-
 7 files changed, 136 insertions(+), 29 deletions(-)

diff --git a/LEGO1/lego/legoomni/include/historybook.h b/LEGO1/lego/legoomni/include/historybook.h
index 3bdc4acf..4e8c9501 100644
--- a/LEGO1/lego/legoomni/include/historybook.h
+++ b/LEGO1/lego/legoomni/include/historybook.h
@@ -4,6 +4,7 @@
 #include "decomp.h"
 #include "legogamestate.h"
 #include "legoworld.h"
+#include "mxstillpresenter.h"
 
 // VTABLE: LEGO1 0x100da328
 // SIZE 0x3e4
@@ -36,9 +37,9 @@ public:
 
 private:
 	LegoGameState::Area m_transitionDestination; // 0xf8
-	undefined m_unk0xfc[104];                    // 0xfc
-	undefined m_unk0x164[560];                   // 0x164
-	undefined m_unk0x394[80];                    // 0x394
+	MxStillPresenter* m_alphabet[26];            // 0xfc
+	MxStillPresenter* m_names[20][7];            // 0x164
+	MxStillPresenter* m_scores[20];              // 0x394
 };
 
 #endif // HISTORYBOOK_H
diff --git a/LEGO1/lego/legoomni/include/legogamestate.h b/LEGO1/lego/legoomni/include/legogamestate.h
index e4c725eb..f893f154 100644
--- a/LEGO1/lego/legoomni/include/legogamestate.h
+++ b/LEGO1/lego/legoomni/include/legogamestate.h
@@ -86,6 +86,32 @@ public:
 		e_unk66 = 66
 	};
 
+	// SIZE 0x0c
+	struct ScoreName {
+		ScoreName* operator=(const ScoreName* p_other);
+
+		MxS16 m_letters[7]; // 0x00
+	};
+
+	// SIZE 0x2c
+	struct ScoreItem {
+		undefined2 m_unk0x00; // 0x00
+		MxU8 m_state[25];     // 0x02
+		ScoreName m_name;     // 0x1c
+		undefined2 m_unk0x2a; // 0x2a
+	};
+
+	// SIZE 0x372
+	struct Scores {
+		void WriteScoreHistory();
+		void FUN_1003ccf0(LegoFile&);
+
+		inline ScoreItem* GetScore(MxS16 p_index) { return p_index >= m_count ? NULL : &m_scores[p_index]; }
+
+		MxS16 m_count;          // 0x00
+		ScoreItem m_scores[20]; // 0x02
+	};
+
 	LegoGameState();
 	~LegoGameState();
 
@@ -109,6 +135,7 @@ public:
 	inline Area GetPreviousArea() { return m_previousArea; }
 	inline MxU32 GetUnknown0x41c() { return m_unk0x41c; }
 	inline Area GetUnknown0x42c() { return m_unk0x42c; }
+	inline Scores* GetScores() { return &m_unk0xa6; }
 
 	inline void SetDirty(MxBool p_dirty) { m_isDirty = p_dirty; }
 	inline void SetCurrentArea(Area p_currentArea) { m_currentArea = p_currentArea; }
@@ -122,14 +149,6 @@ public:
 	void FUN_10039780(MxU8);
 	void FUN_10039940();
 
-	struct ScoreStruct {
-		void WriteScoreHistory();
-		void FUN_1003ccf0(LegoFile&);
-
-		MxU16 m_unk0x00;
-		undefined m_unk0x02[0x2c][20];
-	};
-
 private:
 	void RegisterState(LegoState* p_state);
 	MxResult WriteVariable(LegoStorage* p_stream, MxVariableTable* p_from, const char* p_variableName);
@@ -147,9 +166,9 @@ private:
 	LegoBackgroundColor* m_tempBackgroundColor; // 0x1c
 	LegoFullScreenMovie* m_fullScreenMovie;     // 0x20
 	MxU16 m_unk0x24;                            // 0x24
-	undefined m_unk0x28[128];                   // 0x28
-	ScoreStruct m_unk0xa6;                      // 0xa6
-	undefined m_unk0x41a[2];                    // 0x41a - might be part of the structure at 0xa6
+	undefined m_unk0x26[128];                   // 0x26
+	Scores m_unk0xa6;                           // 0xa6
+	undefined4 m_unk0x418;                      // 0x418
 	undefined4 m_unk0x41c;                      // 0x41c
 	MxBool m_isDirty;                           // 0x420
 	Area m_currentArea;                         // 0x424
diff --git a/LEGO1/lego/legoomni/src/common/legogamestate.cpp b/LEGO1/lego/legoomni/src/common/legogamestate.cpp
index ce379f47..9d81f699 100644
--- a/LEGO1/lego/legoomni/src/common/legogamestate.cpp
+++ b/LEGO1/lego/legoomni/src/common/legogamestate.cpp
@@ -15,8 +15,9 @@
 
 #include <stdio.h>
 
-// Based on the highest dword offset (0x42c) referenced in the constructor.
-// There may be other members that come after.
+DECOMP_SIZE_ASSERT(LegoGameState::ScoreName, 0xe)
+DECOMP_SIZE_ASSERT(LegoGameState::ScoreItem, 0x2c)
+DECOMP_SIZE_ASSERT(LegoGameState::Scores, 0x372)
 DECOMP_SIZE_ASSERT(LegoGameState, 0x430)
 
 // GLOBAL: LEGO1 0x100f3e40
@@ -687,14 +688,21 @@ void LegoGameState::RegisterState(LegoState* p_state)
 	m_stateArray[targetIndex] = p_state;
 }
 
+// FUNCTION: LEGO1 0x1003c710
+LegoGameState::ScoreName* LegoGameState::ScoreName::operator=(const ScoreName* p_other)
+{
+	memcpy(m_letters, p_other->m_letters, sizeof(m_letters));
+	return this;
+}
+
 // STUB: LEGO1 0x1003c870
-void LegoGameState::ScoreStruct::WriteScoreHistory()
+void LegoGameState::Scores::WriteScoreHistory()
 {
 	// TODO
 }
 
 // STUB: LEGO1 0x1003ccf0
-void LegoGameState::ScoreStruct::FUN_1003ccf0(LegoFile&)
+void LegoGameState::Scores::FUN_1003ccf0(LegoFile&)
 {
 	// TODO
 }
diff --git a/LEGO1/lego/legoomni/src/infocenter/infocenter.cpp b/LEGO1/lego/legoomni/src/infocenter/infocenter.cpp
index 02f24a6b..64bd9e58 100644
--- a/LEGO1/lego/legoomni/src/infocenter/infocenter.cpp
+++ b/LEGO1/lego/legoomni/src/infocenter/infocenter.cpp
@@ -111,7 +111,7 @@ MxResult Infocenter::Create(MxDSAction& p_dsAction)
 			if (m_infocenterState->GetNameLetter(i)) {
 				m_infocenterState->GetNameLetter(i)->Enable(TRUE);
 				m_infocenterState->GetNameLetter(i)->SetTickleState(MxPresenter::e_repeating);
-				m_infocenterState->GetNameLetter(i)->VTable0x88(((7 - count) / 2 + i) * 29 + 223, 45);
+				m_infocenterState->GetNameLetter(i)->SetPosition(((7 - count) / 2 + i) * 29 + 223, 45);
 			}
 		}
 	}
@@ -596,12 +596,12 @@ MxU8 Infocenter::HandleMouseMove(MxS32 p_x, MxS32 p_y)
 			m_unk0x11c->SetDisplayZ(1000);
 			VideoManager()->SortPresenterList();
 			m_unk0x11c->Enable(TRUE);
-			m_unk0x11c->VTable0x88(p_x, p_y);
+			m_unk0x11c->SetPosition(p_x, p_y);
 
 			m_unk0x11c->SetDisplayZ(oldDisplayZ);
 		}
 		else {
-			m_unk0x11c->VTable0x88(p_x, p_y);
+			m_unk0x11c->SetPosition(p_x, p_y);
 		}
 
 		FUN_10070d10(p_x, p_y);
@@ -1259,7 +1259,7 @@ void Infocenter::UpdateFrameHot(MxBool p_display)
 		VideoManager()->SortPresenterList();
 
 		m_frameHotBitmap->Enable(TRUE);
-		m_frameHotBitmap->VTable0x88(x, y);
+		m_frameHotBitmap->SetPosition(x, y);
 		m_frameHotBitmap->SetDisplayZ(originalDisplayZ);
 	}
 	else {
diff --git a/LEGO1/lego/legoomni/src/isle/historybook.cpp b/LEGO1/lego/legoomni/src/isle/historybook.cpp
index 2e6691a7..41a49aed 100644
--- a/LEGO1/lego/legoomni/src/isle/historybook.cpp
+++ b/LEGO1/lego/legoomni/src/isle/historybook.cpp
@@ -1,5 +1,6 @@
 #include "historybook.h"
 
+#include "jukebox.h"
 #include "legocontrolmanager.h"
 #include "legoinputmanager.h"
 #include "legoomni.h"
@@ -12,9 +13,9 @@ DECOMP_SIZE_ASSERT(HistoryBook, 0x3e4)
 // FUNCTION: LEGO1 0x100822f0
 HistoryBook::HistoryBook()
 {
-	memset(m_unk0xfc, NULL, sizeof(m_unk0xfc));
-	memset(m_unk0x164, NULL, sizeof(m_unk0x164));
-	memset(m_unk0x394, NULL, sizeof(m_unk0x394));
+	memset(m_alphabet, NULL, sizeof(m_alphabet));
+	memset(m_names, NULL, sizeof(m_names));
+	memset(m_scores, NULL, sizeof(m_scores));
 	NotificationManager()->Register(this);
 }
 
@@ -61,10 +62,88 @@ MxLong HistoryBook::Notify(MxParam& p_param)
 	return 0;
 }
 
-// STUB: LEGO1 0x100826f0
+inline void SetColor(MxStillPresenter* p_presenter, MxU8 p_color, MxU8* p_colors, MxU32 p_x, MxU32 p_y)
+{
+	if (p_color) {
+		for (MxS32 lax = 0; lax < 4; lax++) {
+			if (p_presenter->GetAlphaMask() != NULL) {
+				memset(NULL, p_colors[p_color - 1], 4);
+			}
+			else {
+				memset(p_presenter->GetBitmap()->GetStart(p_x, p_y + lax), p_colors[p_color - 1], 4);
+			}
+		}
+	}
+}
+
+// FUNCTION: LEGO1 0x100826f0
 void HistoryBook::ReadyWorld()
 {
-	// TODO
+	LegoWorld::ReadyWorld();
+	GameState()->GetScores()->WriteScoreHistory();
+
+	char bitmap[] = "A_Bitmap";
+	for (MxS16 i = 0; i < 26; i++) {
+		m_alphabet[i] = (MxStillPresenter*) Find("MxStillPresenter", bitmap);
+		bitmap[0]++;
+	}
+
+	MxStillPresenter* scoreboxMaster = (MxStillPresenter*) Find("MxStillPresenter", "ScoreBox");
+	MxU8 scoreColors[3] =
+		{0x76, 0x4c, 0x38}; // yellow - #FFB900, blue - #00548C, red - #CB1220, background - #CECECE, border - #74818B
+	MxS32 scoreY = 0x79;
+
+	for (MxS16 scoreIndex = 0; scoreIndex < GameState()->GetScores()->m_count; scoreIndex++) {
+		LegoGameState::ScoreItem* score = GameState()->GetScores()->GetScore(scoreIndex);
+
+		MxStillPresenter** scorebox = &m_scores[scoreIndex];
+		*scorebox = scoreboxMaster->Clone();
+
+		MxS32 scoreX = 0x90;
+		if (scoreIndex >= 10) {
+			if (scoreIndex == 10) {
+				scoreY = 0x79;
+			}
+
+			scoreX = 0x158;
+		}
+
+		MxS32 scoreboxX = 1;
+		MxS32 scoreboxRow = 5;
+		MxU8* scoreState = score->m_state;
+
+		for (; scoreboxRow > 0; scoreboxRow--) {
+			for (MxS32 scoreBoxColumn = 0, scoreboxY = 1; scoreBoxColumn < 5; scoreBoxColumn++, scoreboxY += 5) {
+				SetColor(*scorebox, scoreState[scoreBoxColumn], scoreColors, scoreboxX, scoreboxY);
+			}
+
+			scoreState += 5;
+			scoreboxX += 5;
+		}
+
+		(*scorebox)->Enable(TRUE);
+		(*scorebox)->SetTickleState(MxPresenter::e_repeating);
+		(*scorebox)->SetPosition(scoreX + 0xa1, scoreY);
+
+		for (MxS16 letterIndex = 0; letterIndex < (MxS16) _countof(m_names[0]);) {
+			MxS16 letter = score->m_name.m_letters[letterIndex];
+
+			if (letter == -1) {
+				break;
+			}
+
+			MxS16 nameIndex = letterIndex++;
+			m_names[scoreIndex][nameIndex] = m_alphabet[letter]->Clone();
+			m_names[scoreIndex][nameIndex]->Enable(TRUE);
+			m_names[scoreIndex][nameIndex]->SetTickleState(MxPresenter::e_repeating);
+			m_names[scoreIndex][nameIndex]->SetPosition(scoreX, scoreY);
+			scoreX += 0x17;
+		}
+
+		scoreY += 0x1b;
+	}
+
+	PlayMusic(JukeBox::e_informationCenter);
 }
 
 // FUNCTION: LEGO1 0x10082a10
diff --git a/LEGO1/omni/include/mxstillpresenter.h b/LEGO1/omni/include/mxstillpresenter.h
index 2a64aa09..0fb92862 100644
--- a/LEGO1/omni/include/mxstillpresenter.h
+++ b/LEGO1/omni/include/mxstillpresenter.h
@@ -40,7 +40,7 @@ public:
 	void NextFrame() override;                        // vtable+0x64
 	void LoadFrame(MxStreamChunk* p_chunk) override;  // vtable+0x68
 	void RealizePalette() override;                   // vtable+0x70
-	virtual void VTable0x88(MxS32 p_x, MxS32 p_y);    // vtable+0x88
+	virtual void SetPosition(MxS32 p_x, MxS32 p_y);   // vtable+0x88
 	virtual MxStillPresenter* Clone();                // vtable+0x8c
 
 private:
diff --git a/LEGO1/omni/src/video/mxstillpresenter.cpp b/LEGO1/omni/src/video/mxstillpresenter.cpp
index e36297c6..049ad48c 100644
--- a/LEGO1/omni/src/video/mxstillpresenter.cpp
+++ b/LEGO1/omni/src/video/mxstillpresenter.cpp
@@ -148,7 +148,7 @@ void MxStillPresenter::RepeatingTickle()
 }
 
 // FUNCTION: LEGO1 0x100ba040
-void MxStillPresenter::VTable0x88(MxS32 p_x, MxS32 p_y)
+void MxStillPresenter::SetPosition(MxS32 p_x, MxS32 p_y)
 {
 	MxS32 x = m_location.GetX();
 	MxS32 y = m_location.GetY();