LegoBuildingManager Round 2

* 100% match most of the remaining methods, only
  Tickle and FUN_10030630 remain.

* The interesting finding is that the curious
  members of the LegoBuildingManager form a short
  embedded fixed-length array used to store info
  about current animation of buildings.

* I saw that you removed the SetY which I had
  added to MxMatrix. Agree that this method
  doesn't make sense on MxMatrix, however I've
  added it back to Matrix4. I see the pattern of
  setting / getting the Y component used in enough
  places that I doubt they were just hoping they
  remembered the subscript correctly every time.
  Let me know if you agree or still don't think it
  makes sense to include.
This commit is contained in:
Mark Langen 2024-05-08 23:40:52 -07:00
parent 5c8249f334
commit eac6c87ea0
12 changed files with 264 additions and 53 deletions

View File

@ -3,11 +3,14 @@
#include "decomp.h"
#include "mxcore.h"
#include "misc/legotypes.h"
class LegoEntity;
class LegoROI;
class LegoStorage;
class LegoWorld;
class LegoCacheSound;
class LegoPathBoundary;
// SIZE 0x2c
struct LegoBuildingInfo {
@ -18,20 +21,20 @@ struct LegoBuildingInfo {
c_bit4 = 0x08
};
LegoEntity* m_entity; // 0x00
const char* m_hausName; // 0x04
MxU32 m_cycle1; // 0x08
MxU32 m_cycle2; // 0x0c
MxU8 m_cycle3; // 0x10
MxS8 m_unk0x11; // 0x11
MxS8 m_initialUnk0x11; // 0x12 = initial value loaded to m_unk0x11
MxU8 m_flags; // 0x13
float m_unk0x014; // 0x14
const char* m_unk0x18; // 0x18
float m_x; // 0x1c
float m_y; // 0x20
float m_z; // 0x24
undefined* m_unk0x28; // 0x28
LegoEntity* m_entity; // 0x00
const char* m_hausName; // 0x04
MxU32 m_cycle1; // 0x08
MxU32 m_cycle2; // 0x0c
MxU8 m_cycle3; // 0x10
MxS8 m_unk0x11; // 0x11
MxS8 m_initialUnk0x11; // 0x12 = initial value loaded to m_unk0x11
MxU8 m_flags; // 0x13
float m_unk0x014; // 0x14
const char* m_unk0x18; // 0x18
float m_x; // 0x1c
float m_y; // 0x20
float m_z; // 0x24
LegoPathBoundary* m_pathBoundary; // 0x28
};
// VTABLE: LEGO1 0x100d6f50
@ -64,30 +67,40 @@ class LegoBuildingManager : public MxCore {
MxBool FUN_1002fe40(LegoEntity* p_entity);
MxBool FUN_1002fe80(LegoEntity* p_entity);
MxBool FUN_1002fed0(LegoEntity* p_entity);
MxU32 GetBuildingEntityId(LegoEntity* p_entity);
MxU32 FUN_1002ff40(LegoEntity*, MxBool);
MxBool FUN_10030000(LegoEntity* p_entity);
MxBool FUN_10030030(MxS32 p_index);
MxBool FUN_10030110(LegoBuildingInfo* p_data);
void ScheduleAnimation(LegoEntity*, MxU32 p_length, MxBool p_haveSound, MxBool);
void FUN_10030590();
void AdjustHeight(MxS32 p_index);
MxResult FUN_10030630();
LegoBuildingInfo* GetInfoArray(int* p_length);
void FUN_100307b0(LegoEntity*, MxS32);
static void FUN_10030800();
// SYNTHETIC: LEGO1 0x1002f940
// LegoBuildingManager::`scalar deleting destructor'
struct AnimEntry {
LegoEntity* m_entity;
LegoROI* m_roi;
LegoTime m_time;
float m_float;
MxBool m_muted;
};
private:
static char* g_customizeAnimFile;
MxU8 m_nextVariant; // 0x08
MxU8 m_unk0x09; // 0x09
undefined4 m_unk0x0c; // 0x0c
undefined4 m_unk0x10; // 0x10
undefined4 m_unk0x14; // 0x14
undefined4 m_unk0x18; // 0x18
undefined4 m_unk0x1c; // 0x1c
MxU8 m_unk0x20; // 0x20
undefined4 m_unk0x24; // 0x24
MxU8 m_unk0x28; // 0x28
undefined4 m_unk0x2c; // 0x2c
MxU8 m_nextVariant; // 0x08
MxU8 m_unk0x09; // 0x09
AnimEntry* m_entries[5]; // 0x0c
MxS8 m_numEntries; // 0x20
LegoCacheSound* m_sound; // 0x24
MxBool m_unk0x28; // 0x28
LegoWorld* m_world; // 0x2c
};
#endif // LEGOBUILDINGMANAGER_H

View File

@ -37,6 +37,7 @@ class LegoCacheSound : public MxCore {
MxResult FUN_10006a30(const char* p_str, MxBool);
void FUN_10006b80();
void FUN_10006be0();
void FUN_10006cb0(undefined4, undefined4);
// SYNTHETIC: LEGO1 0x10006610
// LegoCacheSound::`scalar deleting destructor'

View File

@ -56,6 +56,7 @@ class LegoPathController : public MxCore {
void FUN_100468f0(LegoAnimPresenter* p_presenter);
void FUN_10046930(LegoAnimPresenter* p_presenter);
MxResult FUN_10046b30(LegoPathBoundary** p_path, MxS32& p_value);
LegoPathBoundary* GetPathBoundary(const char* p_name);
void Enable(MxBool p_enable);
void FUN_10046bb0(LegoWorld* p_world);
@ -64,10 +65,10 @@ class LegoPathController : public MxCore {
undefined4 m_unk0x0c; // 0x0c
undefined4 m_unk0x10; // 0x10
undefined4 m_unk0x14; // 0x14
MxS16 m_numL; // 0x18
MxS16 m_numE; // 0x1a
MxS16 m_numN; // 0x1c
MxS16 m_numT; // 0x1e
MxU16 m_numL; // 0x18
MxU16 m_numE; // 0x1a
MxU16 m_numN; // 0x1c
MxU16 m_numT; // 0x1e
map<undefined*, undefined*, LegoPathControllerComparator> m_pfsE; // 0x20
map<undefined*, undefined*, LegoPathControllerComparator> m_unk0x30; // 0x30
};

View File

@ -20,6 +20,7 @@ class LegoUnknown100d5778 {
void Destroy();
undefined4 FUN_100118e0(LPDIRECTSOUNDBUFFER p_dsBuffer);
void FUN_10011ca0();
MxS32 FUN_10011cf0(undefined4, undefined4);
// SYNTHETIC: LEGO1 0x10011650
// LegoUnknown100d5778::`scalar deleting destructor'

View File

@ -83,6 +83,7 @@ class LegoWorld : public LegoEntity {
void FUN_1001fc80(IslePathActor* p_actor);
void FUN_1001fda0(LegoAnimPresenter* p_presenter);
void FUN_1001fe90(LegoAnimPresenter* p_presenter);
LegoPathBoundary* FindPathBoundary(const char* p_name);
void AddPath(LegoPathController* p_controller);
MxResult GetCurrPathInfo(LegoPathBoundary** p_path, MxS32& p_value);
MxCore* Find(const char* p_class, const char* p_name);

View File

@ -153,3 +153,9 @@ void LegoCacheSound::FUN_10006be0()
void LegoCacheSound::FUN_10006cd0(undefined4, undefined4)
{
}
// FUNCTION: LEGO1 0x10006cb0
void LegoCacheSound::FUN_10006cb0(undefined4 p_unk1, undefined4 p_unk2)
{
m_unk0x10.FUN_10011cf0(p_unk1, p_unk2);
}

View File

@ -81,3 +81,10 @@ void LegoUnknown100d5778::FUN_10011ca0()
{
// TODO
}
// STUB: LEGO1 0x10011cf0
MxS32 LegoUnknown100d5778::FUN_10011cf0(undefined4, undefined4)
{
// TODO
return 0;
}

View File

@ -1,17 +1,24 @@
#include "legobuildingmanager.h"
#include "3dmanager/lego3dmanager.h"
#include "legocachesoundmanager.h"
#include "legoentity.h"
#include "legopathboundary.h"
#include "legosoundmanager.h"
#include "legovideomanager.h"
#include "legoworld.h"
#include "misc.h"
#include "misc/legostorage.h"
#include "mxmisc.h"
#include "mxticklemanager.h"
#include "mxtimer.h"
DECOMP_SIZE_ASSERT(LegoBuildingManager, 0x30)
DECOMP_SIZE_ASSERT(LegoBuildingInfo, 0x2c)
DECOMP_SIZE_ASSERT(LegoBuildingManager::AnimEntry, 0x14)
// GLOBAL: LEGO1 0x100f3410
const char* g_buildingDataHausName[5] = {
const char* g_buildingInfoHausName[5] = {
"haus1",
"haus4",
"haus5",
@ -21,7 +28,7 @@ const char* g_buildingDataHausName[5] = {
// clang-format off
// GLOBAL: LEGO1 0x100f3428
float g_buildingDataDownshiftScale[16] = {
float g_buildingInfoDownshiftScale[16] = {
0.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 1.0f,
@ -29,7 +36,7 @@ float g_buildingDataDownshiftScale[16] = {
};
// GLOBAL: LEGO1 0x100f3468
MxU8 g_buildingDataDownshift[16] = {
MxU8 g_buildingInfoDownshift[16] = {
5, 5, 5, 5,
3, 5, 5, 5,
3, 5, 5, 5,
@ -188,6 +195,22 @@ LegoBuildingInfo g_buildingInfoInit[16] = {
// GLOBAL: LEGO1 0x100f3738
MxU32 g_buildingCycle1Length = 6;
// GLOBAL: LEGO1 0x100f373c
MxU32 g_cycleLengthOffset1 = 0x3c;
// GLOBAL: LEGO1 0x100f3740
MxU32 g_cycleLengthOffset3 = 0x42;
// clang-format off
// GLOBAL: LEGO1 0x100f3788
MxU32 g_buildingEntityId[16] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x46, 0x49, 0x4c,
0x4f, 0x52, 0x55, 0x00,
};
// clang-format on
// GLOBAL: LEGO1 0x100f37c8
char* LegoBuildingManager::g_customizeAnimFile = NULL;
@ -227,9 +250,9 @@ void LegoBuildingManager::Init()
m_nextVariant = 0;
m_unk0x09 = 0;
m_unk0x20 = 0;
m_unk0x24 = 0;
m_unk0x28 = 0;
m_numEntries = 0;
m_sound = NULL;
m_unk0x28 = FALSE;
}
// FUNCTION: LEGO1 0x1002fa00
@ -244,15 +267,15 @@ void LegoBuildingManager::FUN_1002fa00()
}
if (g_buildingManagerConfig <= 1) {
LegoEntity* entity = (LegoEntity*) world->Find("MxEntity", g_buildingDataHausName[0]);
LegoEntity* entity = (LegoEntity*) world->Find("MxEntity", g_buildingInfoHausName[0]);
if (entity) {
entity->GetROI()->SetVisibility(TRUE);
m_unk0x09 = 0;
}
}
else {
for (i = 0; i < _countof(g_buildingDataHausName); i++) {
LegoEntity* entity = (LegoEntity*) world->Find("MxEntity", g_buildingDataHausName[i]);
for (i = 0; i < _countof(g_buildingInfoHausName); i++) {
LegoEntity* entity = (LegoEntity*) world->Find("MxEntity", g_buildingInfoHausName[i]);
if (entity) {
entity->GetROI()->SetVisibility(m_nextVariant == i);
}
@ -274,16 +297,27 @@ void LegoBuildingManager::UpdatePosition(MxS32 p_index, LegoWorld* p_world)
LegoROI* roi = entity->GetROI();
AdjustHeight(p_index);
MxMatrix mat = roi->GetLocal2World();
mat[3][1] = g_buildingInfo[p_index].m_unk0x014;
mat.SetY(g_buildingInfo[p_index].m_unk0x014);
roi->FUN_100a46b0(mat);
VideoManager()->Get3DManager()->Moved(*roi);
}
}
// STUB: LEGO1 0x1002fb30
// FUNCTION: LEGO1 0x1002fb30
void LegoBuildingManager::FUN_1002fb30()
{
// TODO
for (MxU32 i = 0; i < _countof(g_buildingInfo); i++) {
g_buildingInfo[i].m_entity = NULL;
}
m_unk0x09 = 0;
// Weird if statement but the obvious != 0 does not produce
// the 100% match this does.
if ((MxU32) m_numEntries >= 0) {
for (MxU32 i = 0; i < m_numEntries; i++) {
delete m_entries[i];
}
}
m_numEntries = 0;
}
// FUNCTION: LEGO1 0x1002fb80
@ -364,14 +398,14 @@ MxResult LegoBuildingManager::Read(LegoStorage* p_storage)
void LegoBuildingManager::AdjustHeight(MxS32 p_index)
{
if (g_buildingInfo[p_index].m_unk0x11 > 0) {
float value = g_buildingDataDownshift[p_index] - g_buildingInfo[p_index].m_unk0x11;
float value = g_buildingInfoDownshift[p_index] - g_buildingInfo[p_index].m_unk0x11;
g_buildingInfo[p_index].m_unk0x014 =
g_buildingInfoInit[p_index].m_unk0x014 - value * g_buildingDataDownshiftScale[p_index];
g_buildingInfoInit[p_index].m_unk0x014 - value * g_buildingInfoDownshiftScale[p_index];
}
else if (g_buildingInfo[p_index].m_unk0x11 == 0) {
float value = g_buildingDataDownshift[p_index] - g_buildingInfo[p_index].m_unk0x11;
float value = g_buildingInfoDownshift[p_index] - g_buildingInfo[p_index].m_unk0x11;
g_buildingInfo[p_index].m_unk0x014 =
g_buildingInfoInit[p_index].m_unk0x014 - value * g_buildingDataDownshiftScale[p_index];
g_buildingInfoInit[p_index].m_unk0x014 - value * g_buildingInfoDownshiftScale[p_index];
if (g_buildingInfo[p_index].m_entity != NULL) {
LegoROI* roi = g_buildingInfo[p_index].m_entity->GetROI();
@ -416,12 +450,12 @@ MxBool LegoBuildingManager::IncrementVariant(LegoEntity* p_entity)
if (info != NULL && info->m_flags & LegoBuildingInfo::c_bit1 && info->m_unk0x11 == -1) {
LegoROI* roi = p_entity->GetROI();
if (++m_nextVariant >= _countof(g_buildingDataHausName)) {
if (++m_nextVariant >= _countof(g_buildingInfoHausName)) {
m_nextVariant = 0;
}
roi->SetVisibility(FALSE);
info->m_hausName = g_buildingDataHausName[m_nextVariant];
info->m_hausName = g_buildingInfoHausName[m_nextVariant];
UpdatePosition(12, CurrentWorld());
if (info->m_entity != NULL) {
@ -494,10 +528,29 @@ MxBool LegoBuildingManager::FUN_1002fed0(LegoEntity* p_entity)
return result;
}
// STUB: LEGO1 0x1002ff40
MxU32 LegoBuildingManager::FUN_1002ff40(LegoEntity*, MxBool)
// FUNCTION: LEGO1 0x1002ff00
MxU32 LegoBuildingManager::GetBuildingEntityId(LegoEntity* p_entity)
{
// TODO
LegoBuildingInfo* data = GetInfo(p_entity);
if (data != NULL && (data->m_flags & 0x4) != 0) {
return g_buildingEntityId[data - g_buildingInfo] + data->m_cycle2;
}
return 0;
}
// FUNCTION: LEGO1 0x1002ff40
MxU32 LegoBuildingManager::FUN_1002ff40(LegoEntity* p_entity, MxBool p_state)
{
LegoBuildingInfo* data = GetInfo(p_entity);
if (data == NULL || (data->m_flags & 0x2) == 0) {
return 0;
}
if (p_state) {
return data->m_cycle3 + g_cycleLengthOffset3;
}
if (data != NULL) {
return data->m_cycle1 + g_cycleLengthOffset1;
}
return 0;
}
@ -550,15 +603,104 @@ MxBool LegoBuildingManager::FUN_10030110(LegoBuildingInfo* p_data)
return FALSE;
}
// FUNCTION: LEGO1 0x10030150
void LegoBuildingManager::ScheduleAnimation(LegoEntity* p_entity, MxU32 p_length, MxBool p_haveSound, MxBool p_extra)
{
m_world = CurrentWorld();
if (p_haveSound) {
m_sound = SoundManager()->GetCacheSoundManager()->FUN_1003d170("bcrash");
m_sound->FUN_10006cb0(0x23, 0x3c);
}
if (m_numEntries == 0) {
m_unk0x28 = p_extra;
TickleManager()->RegisterClient(this, 0x32);
}
m_entries[m_numEntries] = new AnimEntry;
AnimEntry* entry = m_entries[m_numEntries];
m_numEntries++;
entry->m_entity = p_entity;
entry->m_roi = p_entity->GetROI();
entry->m_time = Timer()->GetTime() + p_length + 1000;
entry->m_float = entry->m_roi->GetLocal2World().GetY();
entry->m_muted = (p_haveSound == FALSE);
FUN_100307b0(p_entity, -2);
}
// STUB: LEGO1 0x10030220
MxResult LegoBuildingManager::Tickle()
{
// TODO
// WIP, included some of this to understand the AnimEntry array.
LegoTime time = Timer()->GetTime();
if (m_numEntries != 0) {
if (m_numEntries > 0) {
for (MxS32 i = 0; i < m_numEntries; i++) {
AnimEntry* entry = m_entries[i];
if (entry->m_time <= time) {
// Code to animate and play sounds
}
}
}
}
else {
TickleManager()->UnregisterClient(this);
}
return SUCCESS;
}
// STUB: LEGO1 0x10030590
// FUNCTION: LEGO1 0x10030590
void LegoBuildingManager::FUN_10030590()
{
// TODO
for (MxS32 i = 0; i < _countof(g_buildingInfo); i++) {
g_buildingInfo[i].m_unk0x11 = -1;
g_buildingInfo[i].m_initialUnk0x11 = -1;
AdjustHeight(i);
if (g_buildingInfo[i].m_entity != NULL) {
LegoROI* roi = g_buildingInfo[i].m_entity->GetROI();
MxMatrix matrix = roi->GetLocal2World();
matrix.SetY(g_buildingInfo[i].m_unk0x014);
roi->FUN_100a46b0(matrix);
VideoManager()->Get3DManager()->GetLego3DView()->Moved(*roi);
}
}
}
// FUNCTION: LEGO1 0x10030630
MxResult LegoBuildingManager::FUN_10030630()
{
return SUCCESS;
}
// FUNCTION: LEGO1 0x10030790
LegoBuildingInfo* LegoBuildingManager::GetInfoArray(int* p_length)
{
if (m_unk0x09 == 0) {
FUN_10030630();
}
*p_length = _countof(g_buildingInfo);
return g_buildingInfo;
}
// FUNCTION: LEGO1 0x100307b0
void LegoBuildingManager::FUN_100307b0(LegoEntity* p_entity, MxS32 p_adjust)
{
LegoBuildingInfo* data = GetInfo(p_entity);
if (data != NULL) {
if (data->m_unk0x11 < 0) {
data->m_unk0x11 = g_buildingInfoDownshift[data - g_buildingInfo];
}
if (data->m_unk0x11 > 0) {
data->m_unk0x11 += p_adjust;
if (data->m_unk0x11 <= 1 && p_adjust < 0) {
data->m_unk0x11 = 0;
}
}
}
}
// FUNCTION: LEGO1 0x10030800
void LegoBuildingManager::FUN_10030800()
{
for (MxS32 i = 0; i < _countof(g_buildingInfo); i++) {
g_buildingInfo[i].m_initialUnk0x11 = g_buildingInfo[i].m_unk0x11;
}
}

View File

@ -358,6 +358,23 @@ void LegoWorld::AddPath(LegoPathController* p_controller)
m_list0x68.Append(p_controller);
}
// FUNCTION: LEGO1 0x10020020
LegoPathBoundary* LegoWorld::FindPathBoundary(const char* p_name)
{
LegoPathControllerListCursor cursor(&m_list0x68);
LegoPathController* controller;
while (cursor.Next(controller)) {
LegoPathBoundary* boundary = controller->GetPathBoundary(p_name);
if (boundary) {
return boundary;
}
}
return NULL;
}
// FUNCTION: LEGO1 0x10020120
MxResult LegoWorld::GetCurrPathInfo(LegoPathBoundary** p_path, MxS32& p_value)
{

View File

@ -1,5 +1,7 @@
#include "legopathcontroller.h"
#include "legopathboundary.h"
DECOMP_SIZE_ASSERT(LegoPathController, 0x40)
// STUB: LEGO1 0x10044f40
@ -65,6 +67,17 @@ MxResult LegoPathController::FUN_10046b30(LegoPathBoundary** p_path, MxS32& p_va
return SUCCESS;
}
// FUNCTION: LEGO1 0x10046b50
LegoPathBoundary* LegoPathController::GetPathBoundary(const char* p_name)
{
for (MxS32 i = 0; i < m_numL; i++) {
if (strcmpi(m_unk0x08[i].GetName(), p_name) == 0) {
return &m_unk0x08[i];
}
}
return NULL;
}
// STUB: LEGO1 0x10046bb0
void LegoPathController::FUN_10046bb0(LegoWorld* p_world)
{

View File

@ -17,6 +17,7 @@ class LegoWEGEdge : public LegoWEEdge {
inline LegoU32 GetFlag0x10() { return m_unk0x0c & 0x10 ? FALSE : TRUE; }
inline Mx4DPointFloat* GetUnknown0x14() { return &m_unk0x14; }
inline Mx4DPointFloat* GetEdgeNormal(int index) { return &m_edgeNormals[index]; }
inline LegoChar* GetName() { return m_name; }
// SYNTHETIC: LEGO1 0x1009a7e0
// LegoWEGEdge::`scalar deleting destructor'

View File

@ -147,6 +147,14 @@ class Matrix4 {
float* operator[](int idx) { return m_data[idx]; }
const float* operator[](int idx) const { return m_data[idx]; }
inline void SetX(float p_x) { m_data[3][0] = p_x; }
inline void SetY(float p_y) { m_data[3][1] = p_y; }
inline void SetZ(float p_z) { m_data[3][2] = p_z; }
inline float GetX() const { return m_data[3][0]; }
inline float GetY() const { return m_data[3][1]; }
inline float GetZ() const { return m_data[3][2]; }
protected:
float (*m_data)[4];
};