From 757df96c0dae77ec70a92e7ffddf6cd4f6f29dba Mon Sep 17 00:00:00 2001
From: jonschz <17198703+jonschz@users.noreply.github.com>
Date: Sat, 17 Aug 2024 20:30:39 +0200
Subject: [PATCH] Implement/match LegoRaceMap (#1087)

* Implement/match LegoRaceMap

* Fix CI, address review comments

---------

Co-authored-by: jonschz <jonschz@users.noreply.github.com>
---
 .../lego/legoomni/include/legoinputmanager.h  |   2 +
 LEGO1/lego/legoomni/include/legomain.h        |   3 +
 LEGO1/lego/legoomni/include/legoracemap.h     |  49 +++++--
 .../lego/legoomni/include/legovideomanager.h  |   1 +
 LEGO1/lego/legoomni/src/common/misc.cpp       |   2 +
 LEGO1/lego/legoomni/src/race/legoracemap.cpp  | 123 ++++++++++++++++--
 .../legoomni/src/video/legovideomanager.cpp   |  22 ++++
 LEGO1/omni/include/mxdsobject.h               |   4 +
 LEGO1/omni/include/mxpresenter.h              |   3 +
 LEGO1/omni/include/mxpresenterlist.h          |  11 ++
 tools/ncc/skip.yml                            |   1 +
 11 files changed, 200 insertions(+), 21 deletions(-)

diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h
index 545a948f..859d1a64 100644
--- a/LEGO1/lego/legoomni/include/legoinputmanager.h
+++ b/LEGO1/lego/legoomni/include/legoinputmanager.h
@@ -118,7 +118,9 @@ public:
 		m_unk0x336 = FALSE;
 	}
 
+	// FUNCTION: BETA10 0x10031ba0
 	LegoControlManager* GetControlManager() { return m_controlManager; }
+
 	LegoWorld* GetWorld() { return m_world; }
 	LegoCameraController* GetCamera() { return m_camera; }
 
diff --git a/LEGO1/lego/legoomni/include/legomain.h b/LEGO1/lego/legoomni/include/legomain.h
index a1c35500..75df917d 100644
--- a/LEGO1/lego/legoomni/include/legomain.h
+++ b/LEGO1/lego/legoomni/include/legomain.h
@@ -122,7 +122,10 @@ public:
 
 	LegoVideoManager* GetVideoManager() { return (LegoVideoManager*) m_videoManager; }
 	LegoSoundManager* GetSoundManager() { return (LegoSoundManager*) m_soundManager; }
+
+	// FUNCTION: BETA10 0x1009e7a0
 	LegoInputManager* GetInputManager() { return m_inputManager; }
+
 	LegoTextureContainer* GetTextureContainer() { return m_textureContainer; }
 	ViewLODListManager* GetViewLODListManager() { return m_viewLODListManager; }
 	LegoWorld* GetCurrentWorld() { return m_currentWorld; }
diff --git a/LEGO1/lego/legoomni/include/legoracemap.h b/LEGO1/lego/legoomni/include/legoracemap.h
index 3cbd7739..1380cd0c 100644
--- a/LEGO1/lego/legoomni/include/legoracemap.h
+++ b/LEGO1/lego/legoomni/include/legoracemap.h
@@ -3,10 +3,17 @@
 
 #include "legoraceactor.h"
 
+class MxControlPresenter;
+class MxStillPresenter;
+
 // VTABLE: LEGO1 0x100d8858 LegoRaceActor
 // VTABLE: LEGO1 0x100d8860 LegoAnimActor
 // VTABLE: LEGO1 0x100d8870 LegoPathActor
 // VTABLE: LEGO1 0x100d893c LegoRaceMap
+// VTABLE: BETA10 0x101be4dc LegoRaceActor
+// VTABLE: BETA10 0x101be4e0 LegoAnimActor
+// VTABLE: BETA10 0x101be4f8 LegoPathActor
+// VTABLE: BETA10 0x101be5e8 LegoRaceMap
 // SIZE 0x1b4
 class LegoRaceMap : public virtual LegoRaceActor {
 public:
@@ -28,17 +35,37 @@ public:
 	// LegoRaceMap::`scalar deleting destructor'
 
 private:
-	MxBool m_unk0x08;     // 0x08
-	void* m_unk0x0c;      // 0x0c
-	undefined4 m_unk0x10; // 0x10
-	float m_unk0x14;      // 0x14
-	float m_unk0x18;      // 0x18
-	float m_unk0x1c;      // 0x1c
-	float m_unk0x20;      // 0x20
-	float m_unk0x24;      // 0x24
-	float m_unk0x28;      // 0x28
-	float m_unk0x2c;      // 0x2c
-	undefined4 m_unk0x30; // 0x30
+	MxBool m_unk0x08;                   // 0x08
+	MxStillPresenter* m_stillPresenter; // 0x0c
+
+	// variable name verified by BETA10 0x100ca82b
+	MxControlPresenter* m_Map_Ctl; // 0x10
+
+	// likely an x-offset of the race map in world space
+	float m_unk0x14; // 0x14
+	// inversely scales the map in x direction (either convert world->screen space or to control the size)
+	float m_unk0x18; // 0x18
+	// likely a y-offset of the race map in world space
+	float m_unk0x1c; // 0x1c
+	// inversely scales the map in y direction (either convert world->screen space or to control the size)
+	float m_unk0x20; // 0x20
+	// scales the map in x direction (either convert world->screen space or to change the size)
+	float m_unk0x24; // 0x24
+	// scales the map in y direction (either convert world->screen space or to change the size)
+	float m_unk0x28; // 0x28
+	// likely an x-offset of the race map in screen space
+	float m_unk0x2c; // 0x2c
+	// likely a y-offset of the race map in screen space
+	float m_unk0x30; // 0x30
 };
 
+// GLOBAL: LEGO1 0x100d8848
+// LegoRaceMap::`vbtable'
+
+// GLOBAL: LEGO1 0x100d8840
+// LegoRaceMap::`vbtable'{for `LegoAnimActor'}
+
+// GLOBAL: LEGO1 0x100d8830
+// LegoRaceMap::`vbtable'{for `LegoRaceActor'}
+
 #endif // LEGORACEMAP_H
diff --git a/LEGO1/lego/legoomni/include/legovideomanager.h b/LEGO1/lego/legoomni/include/legovideomanager.h
index 9381a055..c077422c 100644
--- a/LEGO1/lego/legoomni/include/legovideomanager.h
+++ b/LEGO1/lego/legoomni/include/legovideomanager.h
@@ -44,6 +44,7 @@ public:
 	void SetSkyColor(float p_red, float p_green, float p_blue);
 	void OverrideSkyColor(MxBool p_shouldOverride);
 	MxResult ResetPalette(MxBool p_ignoreSkyColor);
+	MxPresenter* GetPresenterByActionObjectName(const char* p_char);
 
 	void FUN_1007c520();
 
diff --git a/LEGO1/lego/legoomni/src/common/misc.cpp b/LEGO1/lego/legoomni/src/common/misc.cpp
index 05dcf678..8d035c74 100644
--- a/LEGO1/lego/legoomni/src/common/misc.cpp
+++ b/LEGO1/lego/legoomni/src/common/misc.cpp
@@ -43,8 +43,10 @@ LegoInputManager* InputManager()
 }
 
 // FUNCTION: LEGO1 0x10015750
+// FUNCTION: BETA10 0x100e48dc
 LegoControlManager* ControlManager()
 {
+	assert(LegoOmni::GetInstance());
 	return LegoOmni::GetInstance()->GetInputManager()->GetControlManager();
 }
 
diff --git a/LEGO1/lego/legoomni/src/race/legoracemap.cpp b/LEGO1/lego/legoomni/src/race/legoracemap.cpp
index 710e9140..950c344b 100644
--- a/LEGO1/lego/legoomni/src/race/legoracemap.cpp
+++ b/LEGO1/lego/legoomni/src/race/legoracemap.cpp
@@ -1,40 +1,143 @@
 #include "legoracemap.h"
 
+#include "define.h"
 #include "legocontrolmanager.h"
+#include "legovideomanager.h"
+#include "legoworld.h"
 #include "misc.h"
+#include "mxcontrolpresenter.h"
+#include "mxstillpresenter.h"
+#include "mxutilities.h"
 
 DECOMP_SIZE_ASSERT(LegoRaceMap, 0x1b4)
 
 // FUNCTION: LEGO1 0x1005d0d0
+// FUNCTION: BETA10 0x100ca2c0
 LegoRaceMap::LegoRaceMap()
 {
 	m_unk0x08 = FALSE;
-	m_unk0x0c = NULL;
-	m_unk0x10 = 0;
+	m_stillPresenter = NULL;
+	m_Map_Ctl = 0;
 	ControlManager()->Register(this);
 }
 
-// STUB: LEGO1 0x1005d2b0
+// FUNCTION: LEGO1 0x1005d2b0
+// FUNCTION: BETA10 0x100ca48c
 LegoRaceMap::~LegoRaceMap()
 {
-	// TODO
+	ControlManager()->Unregister(this);
 }
 
-// STUB: LEGO1 0x1005d310
+// GLOBAL: LEGO1 0x1010208c
+// STRING: LEGO1 0x10101f88
+const char* g_mapLocator = "MAP_LOCATOR";
+
+// GLOBAL: LEGO1 0x10102090
+// STRING: LEGO1 0x10101f78
+const char* g_mapGeometry = "MAP_GEOMETRY";
+
+// FUNCTION: LEGO1 0x1005d310
+// FUNCTION: BETA10 0x100ca543
 void LegoRaceMap::ParseAction(char* p_extra)
 {
-	// TODO
+	char value[256];
+
+	if (KeyValueStringParse(value, g_mapLocator, p_extra)) {
+		// variable name verified by BETA10 0x100ca5ac
+		MxStillPresenter* p = (MxStillPresenter*) VideoManager()->GetPresenterByActionObjectName(value);
+
+		assert(p);
+		p->Enable(FALSE);
+		m_stillPresenter = p;
+	}
+
+	if (KeyValueStringParse(value, g_mapGeometry, p_extra)) {
+		char* token = strtok(value, g_parseExtraTokens);
+		if (token != NULL) {
+			m_unk0x14 = atof(token);
+		}
+
+		token = strtok(NULL, g_parseExtraTokens);
+		if (token != NULL) {
+			m_unk0x18 = atof(token);
+		}
+
+		token = strtok(NULL, g_parseExtraTokens);
+		if (token != NULL) {
+			m_unk0x1c = atof(token);
+		}
+
+		token = strtok(NULL, g_parseExtraTokens);
+		if (token != NULL) {
+			m_unk0x20 = atof(token);
+		}
+
+		token = strtok(NULL, g_parseExtraTokens);
+		if (token != NULL) {
+			m_unk0x24 = atof(token);
+		}
+
+		token = strtok(NULL, g_parseExtraTokens);
+		if (token != NULL) {
+			m_unk0x28 = atof(token);
+		}
+
+		token = strtok(NULL, g_parseExtraTokens);
+		if (token != NULL) {
+			m_unk0x2c = atof(token);
+		}
+
+		token = strtok(NULL, g_parseExtraTokens);
+		if (token != NULL) {
+			m_unk0x30 = atof(token);
+		}
+	}
+
+	LegoWorld* currentWorld = CurrentWorld();
+
+	if (currentWorld) {
+		// STRING: LEGO1 0x100f67bc
+		const char* mapCtl = "Map_Ctl";
+
+		m_Map_Ctl = (MxControlPresenter*) currentWorld->Find("MxControlPresenter", mapCtl);
+		assert(m_Map_Ctl);
+	}
 }
 
 // FUNCTION: LEGO1 0x1005d4b0
+// FUNCTION: BETA10 0x100ca849
 void LegoRaceMap::FUN_1005d4b0()
 {
-	// TODO
+	if (m_unk0x08) {
+		short xPos = (GetWorldPosition()[0] - m_unk0x14) / m_unk0x18 * m_unk0x24;
+		short yPos = (GetWorldPosition()[2] - m_unk0x1c) / m_unk0x20 * m_unk0x28;
+
+		m_stillPresenter->SetPosition(xPos + m_unk0x2c, m_unk0x30 - yPos);
+	}
 }
 
-// STUB: LEGO1 0x1005d550
+// FUNCTION: LEGO1 0x1005d550
+// FUNCTION: BETA10 0x100ca92d
 MxLong LegoRaceMap::Notify(MxParam& p_param)
 {
-	// TODO
-	return 0;
+	if (!m_stillPresenter) {
+		return 1;
+	}
+
+	if (((MxNotificationParam&) p_param).GetNotification() == c_notificationControl &&
+		m_Map_Ctl->GetAction()->GetObjectId() ==
+			((LegoControlManagerNotificationParam&) p_param).GetClickedObjectId()) {
+
+		if (((LegoControlManagerNotificationParam&) p_param).GetUnknown0x28() == 1) {
+			m_unk0x08 = TRUE;
+			FUN_1005d4b0();
+			m_stillPresenter->Enable(TRUE);
+		}
+		else {
+			m_unk0x08 = FALSE;
+			m_stillPresenter->Enable(FALSE);
+		}
+	}
+
+	return 1;
 }
diff --git a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp
index 21147b1f..9a41de96 100644
--- a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp
+++ b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp
@@ -409,6 +409,28 @@ MxPresenter* LegoVideoManager::GetPresenterAt(MxS32 p_x, MxS32 p_y)
 	return NULL;
 }
 
+// FUNCTION: LEGO1 0x1007c180
+// FUNCTION: BETA10 0x100d6df4
+MxPresenter* LegoVideoManager::GetPresenterByActionObjectName(const char* p_actionObjectName)
+{
+	MxPresenterListCursor cursor(m_presenters);
+	MxPresenter* presenter;
+
+	while (TRUE) {
+		if (!cursor.Prev(presenter)) {
+			return NULL;
+		}
+
+		if (!presenter->GetAction()) {
+			continue;
+		}
+
+		if (strcmpi(presenter->GetAction()->GetObjectName(), p_actionObjectName) == 0) {
+			return presenter;
+		}
+	}
+}
+
 // FUNCTION: LEGO1 0x1007c290
 MxResult LegoVideoManager::RealizePalette(MxPalette* p_pallete)
 {
diff --git a/LEGO1/omni/include/mxdsobject.h b/LEGO1/omni/include/mxdsobject.h
index e291d7d9..3bb0a3ff 100644
--- a/LEGO1/omni/include/mxdsobject.h
+++ b/LEGO1/omni/include/mxdsobject.h
@@ -63,8 +63,12 @@ public:
 	// FUNCTION: BETA10 0x1012efb0
 	const char* GetSourceName() const { return m_sourceName; }
 
+	// FUNCTION: BETA10 0x10028460
 	const char* GetObjectName() const { return m_objectName; }
+
+	// FUNCTION: BETA10 0x10017910
 	MxU32 GetObjectId() { return m_objectId; }
+
 	const MxAtomId& GetAtomId() { return m_atomId; }
 	MxS16 GetUnknown24() { return m_unk0x24; }
 	MxPresenter* GetUnknown28() { return m_unk0x28; }
diff --git a/LEGO1/omni/include/mxpresenter.h b/LEGO1/omni/include/mxpresenter.h
index 15323d2f..a30897d1 100644
--- a/LEGO1/omni/include/mxpresenter.h
+++ b/LEGO1/omni/include/mxpresenter.h
@@ -123,7 +123,10 @@ public:
 	MxS32 GetX() const { return this->m_location.GetX(); }
 	MxS32 GetY() const { return this->m_location.GetY(); }
 	MxS32 GetDisplayZ() const { return this->m_displayZ; }
+
+	// FUNCTION: BETA10 0x10028430
 	MxDSAction* GetAction() const { return this->m_action; }
+
 	void SetAction(MxDSAction* p_action) { m_action = p_action; }
 
 	void SetCompositePresenter(MxCompositePresenter* p_compositePresenter)
diff --git a/LEGO1/omni/include/mxpresenterlist.h b/LEGO1/omni/include/mxpresenterlist.h
index 87430a53..34714def 100644
--- a/LEGO1/omni/include/mxpresenterlist.h
+++ b/LEGO1/omni/include/mxpresenterlist.h
@@ -30,8 +30,10 @@ public:
 // class MxPtrListCursor<MxPresenter>
 
 // VTABLE: LEGO1 0x100d6470
+// SIZE 0x10
 class MxPresenterListCursor : public MxPtrListCursor<MxPresenter> {
 public:
+	// FUNCTION: BETA10 0x1007d130
 	MxPresenterListCursor(MxPresenterList* p_list) : MxPtrListCursor<MxPresenter>(p_list) {}
 };
 
@@ -98,4 +100,13 @@ public:
 // TEMPLATE: LEGO1 0x100225e0
 // MxList<MxPresenter *>::DeleteEntry
 
+// TEMPLATE: BETA10 0x1007d1d0
+// MxPtrListCursor<MxPresenter>::MxPtrListCursor<MxPresenter>
+
+// TEMPLATE: BETA10 0x1007d270
+// MxListCursor<MxPresenter>::MxListCursor<MxPresenter>
+
+// TEMPLATE: BETA10 0x100d9420
+// MxListCursor<MxPresenter>::Prev
+
 #endif // MXPRESENTERLIST_H
diff --git a/tools/ncc/skip.yml b/tools/ncc/skip.yml
index 46229e07..6062e7b1 100644
--- a/tools/ncc/skip.yml
+++ b/tools/ncc/skip.yml
@@ -30,3 +30,4 @@ i_activity: "Allow original naming from beta"
 i_actor: "Allow original naming from beta"
 score: "Allow original naming from beta"
 c_LOCATIONS_NUM: "Allow original naming from beta"
+m_Map_Ctl: "Allow original naming from beta"