Commit finished code changes

This commit is contained in:
jonschz 2024-11-01 07:49:44 +01:00
parent 115343f6e5
commit 14824ac196
18 changed files with 192 additions and 52 deletions

View File

@ -6,6 +6,7 @@
#include "legoworld.h"
class LegoCarBuildAnimPresenter;
class LegoControlManagerNotificationParam;
class LegoEventNotificationParam;
class MxControlPresenter;
class MxStillPresenter;
@ -21,6 +22,7 @@ class LegoVehicleBuildState : public LegoState {
e_entering = 1,
e_unknown2 = 2,
e_cutscene = 3,
e_unknown4 = 4,
e_exiting = 6
};
@ -117,14 +119,16 @@ class LegoCarBuild : public LegoWorld {
undefined4 FUN_100244e0(MxLong p_x, MxLong p_y);
undefined4 FUN_100246e0(MxLong p_x, MxLong p_y);
MxS32 FUN_10024850(MxLong p_x, MxLong p_y);
undefined4 FUN_10024890(LegoEventNotificationParam* p_param);
undefined4 FUN_10024890(MxParam* p_param2);
undefined4 FUN_10024c20(LegoEventNotificationParam* p_param);
void FUN_10024ef0();
void FUN_10024f30();
void FUN_10024f50();
void FUN_10024f70(MxBool p_enabled);
void SetPresentersEnabled(MxBool p_enabled);
void TogglePresentersEnabled();
void FUN_100250e0(MxBool p_param);
void FUN_10025350(MxS32 p_param);
void FUN_10025450();
undefined4 FUN_10025720(undefined4 p_param1);
MxS32 FUN_10025d70();
@ -197,7 +201,8 @@ class LegoCarBuild : public LegoWorld {
// variable name verified by BETA10 0x1006b219
LegoVehicleBuildState* m_buildState; // 0x32c
undefined4 m_unk0x330; // 0x330
// variable name verified by BETA10 0x1006d742
undefined4 m_carId; // 0x330
// variable name verified by BETA10 0x1006cba7
LegoGameState::Area m_destLocation; // 0x334

View File

@ -77,15 +77,33 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter {
MxBool StringEqualsShelf(const LegoChar* p_string);
MxBool StringEndsOnY(const LegoChar* p_string);
MxBool StringEndsOnZero(const LegoChar* p_string);
const LegoChar* GetWiredNameByPartName(const LegoChar* p_name);
void FUN_10079dc0(const LegoChar* p_param1, MxS16 p_param2);
// FUNCTION: BETA10 0x10070180
void SetUnknown0xbc(undefined2 p_unk0xbc) { m_unk0xbc = p_unk0xbc; }
// FUNCTION: BETA10 0x100703b0
MxMatrix& GetUnknown0xe0() { return m_unk0xe0; }
MxBool StringEndsOnW(LegoChar* p_param);
MxBool StringEndsOnYOrN(const LegoChar* p_string);
const BoundingSphere& FUN_10079e20();
// FUNCTION: BETA10 0x100703e0
const LegoChar* GetWiredNameOfLastPlacedPart() { return m_parts[m_placedPartCount].m_wiredName; }
MxS16 GetNumberOfParts() { return m_numberOfParts; }
MxS16 GetPlacedPartCount() { return m_placedPartCount; }
// FUNCTION: BETA10 0x10070270
MxBool AllPartsPlaced()
{
// this function differs in BETA10
return m_placedPartCount == m_numberOfParts;
}
// SYNTHETIC: LEGO1 0x10078660
// LegoCarBuildAnimPresenter::`scalar deleting destructor'
@ -118,8 +136,6 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter {
// name verified by BETA10 0x10070d63
LegoChar* m_mainSourceId; // 0x14c
friend class LegoCarBuild;
};
#endif // LEGOCARBUILDPRESENTER_H

View File

@ -121,7 +121,9 @@ class LegoInputManager : public MxPresenter {
// FUNCTION: BETA10 0x10031ba0
LegoControlManager* GetControlManager() { return m_controlManager; }
// FUNCTION: BETA10 0x10017870
LegoWorld* GetWorld() { return m_world; }
LegoCameraController* GetCamera() { return m_camera; }
void ProcessEvents();

View File

@ -34,7 +34,7 @@ class MxTransitionManager : public MxCore {
virtual MxResult GetDDrawSurfaceFromVideoManager(); // vtable+0x14
enum TransitionType {
e_notTransitioning = 0,
e_idle = 0, // name verified by BETA10 0x100ec4e6
e_noAnimation,
e_dissolve,
e_mosaic,
@ -45,7 +45,7 @@ class MxTransitionManager : public MxCore {
MxResult StartTransition(TransitionType p_animationType, MxS32 p_speed, MxBool p_doCopy, MxBool p_playMusicInAnim);
TransitionType GetTransitionType() { return m_transitionType; }
TransitionType GetTransitionType() { return m_mode; }
// SYNTHETIC: LEGO1 0x1004b9e0
// MxTransitionManager::`scalar deleting destructor'
@ -68,13 +68,16 @@ class MxTransitionManager : public MxCore {
FlagBitfield m_copyFlags; // 0x20
undefined4 m_unk0x24; // 0x24
FlagBitfield m_unk0x28; // 0x28
TransitionType m_transitionType; // 0x2c
LPDIRECTDRAWSURFACE m_ddSurface; // 0x30
MxU16 m_animationTimer; // 0x34
MxU16 m_columnOrder[640]; // 0x36
MxU16 m_randomShift[480]; // 0x536
MxULong m_systemTime; // 0x8f8
MxS32 m_animationSpeed; // 0x8fc
// name verified by BETA10 0x100ec4e6
TransitionType m_mode; // 0x2c
LPDIRECTDRAWSURFACE m_ddSurface; // 0x30
MxU16 m_animationTimer; // 0x34
MxU16 m_columnOrder[640]; // 0x36
MxU16 m_randomShift[480]; // 0x536
MxULong m_systemTime; // 0x8f8
MxS32 m_animationSpeed; // 0x8fc
};
#endif // MXTRANSITIONMANAGER_H

View File

@ -1,5 +1,6 @@
#include "legocarbuild.h"
#include "copter_actions.h"
#include "dunebuggy.h"
#include "helicopter.h"
#include "jetski.h"
@ -26,6 +27,12 @@
#include <isle.h>
#include <vec.h>
// names and values verified by BETA10 0x1006d742
#define Helicopter_Actor 1
#define DuneBugy_Actor 2
#define Jetski_Actor 3
#define RaceCar_Actor 4
DECOMP_SIZE_ASSERT(LegoCarBuild, 0x34c)
DECOMP_SIZE_ASSERT(LegoVehicleBuildState, 0x50)
@ -87,12 +94,27 @@ MxBool LegoCarBuild::VTable0x5c()
return TRUE;
}
// STUB: LEGO1 0x10022a80
// STUB: BETA10 0x1006aea3
// FUNCTION: LEGO1 0x10022a80
// FUNCTION: BETA10 0x1006aea3
LegoCarBuild::~LegoCarBuild()
{
// TODO
// ...
m_unk0x100 = 0;
m_unk0x110 = NULL;
if (m_unk0x258) {
m_unk0x258->SetUnknown0xbc(0);
m_unk0x258->SetTickleState(MxPresenter::e_idle);
m_unk0x258 = NULL;
}
ControlManager()->Unregister(this);
TickleManager()->UnregisterClient(this);
if (InputManager()->GetWorld() == this) {
InputManager()->ClearWorld();
}
InputManager()->UnRegister(this);
NotificationManager()->Unregister(this);
}
@ -117,22 +139,22 @@ MxResult LegoCarBuild::Create(MxDSAction& p_dsAction)
if (m_atomId == *g_copterScript) {
buildStateClassName = "LegoCopterBuildState";
GameState()->SetCurrentArea(LegoGameState::e_copterbuild);
m_unk0x330 = 1;
m_carId = Helicopter_Actor;
}
else if (m_atomId == *g_dunecarScript) {
buildStateClassName = "LegoDuneCarBuildState";
GameState()->SetCurrentArea(LegoGameState::e_dunecarbuild);
m_unk0x330 = 2;
m_carId = DuneBugy_Actor;
}
else if (m_atomId == *g_jetskiScript) {
buildStateClassName = "LegoJetskiBuildState";
GameState()->SetCurrentArea(LegoGameState::e_jetskibuild);
m_unk0x330 = 3;
m_carId = Jetski_Actor;
}
else if (m_atomId == *g_racecarScript) {
buildStateClassName = "LegoRaceCarBuildState";
GameState()->SetCurrentArea(LegoGameState::e_racecarbuild);
m_unk0x330 = 4;
m_carId = Helicopter_Actor;
}
LegoGameState* gameState = GameState();
@ -429,7 +451,7 @@ MxResult LegoCarBuild::Tickle()
DWORD time = timeGetTime();
DWORD dTime = (time - m_unk0x10c) / 100;
if (m_unk0x330 == 4) {
if (m_carId == Helicopter_Actor) {
switch (m_unk0x10a) {
// TODO: Work out constants
case 500:
@ -452,7 +474,7 @@ MxResult LegoCarBuild::Tickle()
return SUCCESS;
}
}
else if (m_unk0x330 == 3) {
else if (m_carId == Jetski_Actor) {
switch (m_unk0x10a) {
case 500:
LEGOCARBUILD_TICKLE_CASE(291, 291, 311, "Exit_Ctl")
@ -473,7 +495,7 @@ MxResult LegoCarBuild::Tickle()
// return SUCCESS;
}
}
else if (m_unk0x330 == 2) {
else if (m_carId == DuneBugy_Actor) {
switch (m_unk0x10a) {
case 500:
LEGOCARBUILD_TICKLE_CASE(155, 155, 175, "Exit_Ctl")
@ -490,7 +512,7 @@ MxResult LegoCarBuild::Tickle()
return SUCCESS;
}
}
else if (m_unk0x330 == 1) {
else if (m_carId == Helicopter_Actor) {
switch (m_unk0x10a) {
case 500:
LEGOCARBUILD_TICKLE_CASE(185, 185, 205, "Exit_Ctl")
@ -575,7 +597,7 @@ MxLong LegoCarBuild::Notify(MxParam& p_param)
break;
case c_notificationControl:
result = FUN_10024890((LegoEventNotificationParam*) &p_param);
result = FUN_10024890(&p_param);
if (result == 1) {
m_unk0x109 = 0;
@ -624,7 +646,7 @@ void LegoCarBuild::ReadyWorld()
InitPresenters();
if (BackgroundAudioManager()->GetEnabled()) {
InvokeAction(Extra::ActionType::e_start, *g_jukeboxScript, FUN_10025ee0(m_unk0x330), NULL);
InvokeAction(Extra::ActionType::e_start, *g_jukeboxScript, FUN_10025ee0(m_carId), NULL);
m_buildState->m_animationState = LegoVehicleBuildState::e_unknown2;
NotificationManager()->Send(this, MxNotificationParam());
}
@ -636,8 +658,8 @@ void LegoCarBuild::ReadyWorld()
// FUNCTION: LEGO1 0x100243a0
void LegoCarBuild::FUN_100243a0()
{
switch (m_unk0x330) {
case 1:
switch (m_carId) {
case Helicopter_Actor:
if (GameState()->GetCurrentAct() == LegoGameState::Act::e_act2) {
m_destLocation = LegoGameState::Area::e_act3script;
TransitionManager()->StartTransition(MxTransitionManager::TransitionType::e_mosaic, 50, FALSE, FALSE);
@ -648,15 +670,15 @@ void LegoCarBuild::FUN_100243a0()
TransitionManager()->StartTransition(MxTransitionManager::TransitionType::e_mosaic, 50, FALSE, FALSE);
break;
}
case 2:
case DuneBugy_Actor:
m_destLocation = LegoGameState::Area::e_garadoor;
TransitionManager()->StartTransition(MxTransitionManager::TransitionType::e_mosaic, 50, FALSE, FALSE);
break;
case 3:
case Jetski_Actor:
m_destLocation = LegoGameState::Area::e_unk17;
TransitionManager()->StartTransition(MxTransitionManager::TransitionType::e_mosaic, 50, FALSE, FALSE);
break;
case 4:
case RaceCar_Actor:
m_destLocation = LegoGameState::Area::e_unk20;
TransitionManager()->StartTransition(MxTransitionManager::TransitionType::e_mosaic, 50, FALSE, FALSE);
}
@ -788,7 +810,7 @@ undefined4 LegoCarBuild::FUN_10024c20(LegoEventNotificationParam* p_param)
switch (m_buildState->m_animationState) {
case 4:
entity = (LegoEntity*) Find(m_atomId, m_unk0x330);
entity = (LegoEntity*) Find(m_atomId, m_carId);
if (entity && entity->GetROI()) {
@ -837,7 +859,7 @@ undefined4 LegoCarBuild::FUN_10024c20(LegoEventNotificationParam* p_param)
assert(destWorld);
m_buildState->m_animationState = LegoVehicleBuildState::e_exiting;
if (m_unk0x258->m_numberOfParts != m_unk0x258->m_placedPartCount) {
if (m_unk0x258->AllPartsPlaced()) {
FUN_100243a0();
}
else {
@ -852,17 +874,17 @@ undefined4 LegoCarBuild::FUN_10024c20(LegoEventNotificationParam* p_param)
case 2:
MxU32 jukeboxScript;
switch (m_unk0x330) {
case 1:
switch (m_carId) {
case Helicopter_Actor:
jukeboxScript = JukeboxScript::c_HelicopterBuild_Music;
break;
case 2:
case DuneBugy_Actor:
jukeboxScript = JukeboxScript::c_DuneCarBuild_Music;
break;
case 3:
case Jetski_Actor:
jukeboxScript = JukeboxScript::c_JetskiBuild_Music;
break;
case 4:
case RaceCar_Actor:
jukeboxScript = JukeboxScript::c_RaceCarBuild_Music;
}
@ -893,6 +915,14 @@ void LegoCarBuild::FUN_10024ef0()
FUN_10015820(FALSE, 7);
}
// FUNCTION: LEGO1 0x10024f30
// FUNCTION: BETA10 0x1006dfa0
void LegoCarBuild::FUN_10024f30()
{
FUN_10022f30();
m_unk0x258->SetUnknown0xbc(2);
}
// FUNCTION: LEGO1 0x10024f50
// FUNCTION: BETA10 0x1006dfce
void LegoCarBuild::FUN_10024f50()
@ -973,13 +1003,64 @@ void LegoCarBuild::FUN_100250e0(MxBool p_enabled)
}
}
// STUB: LEGO1 0x10025450
// STUB: BETA10 0x1006e599
void LegoCarBuild::FUN_10025450()
// STUB: LEGO1 0x10025350
// STUB: BETA10 0x1006e3c0
void LegoCarBuild::FUN_10025350(MxS32 p_param)
{
// TODO
}
// FUNCTION: LEGO1 0x10025450
// FUNCTION: BETA10 0x1006e599
void LegoCarBuild::FUN_10025450()
{
m_unk0x12c = m_unk0x110->GetLocal2World();
m_unk0x1c0 = m_unk0x12c;
Vector3 local_c(m_unk0x1c0[3]);
local_c = Vector3(m_unk0x258->GetUnknown0xe0()[3]);
// This looks odd, but it improves the LEGO1 match while breaking the BETA10 match.
// I don't know whether this is due to compiler entropy.
// Feel free to replace unk0x178 -> m_unk0x178 and remove this variable if it improves the LEGO1 match
// in the future.
MxMatrix* unk0x178 = &m_unk0x178;
*unk0x178 = m_unk0x12c;
if (m_unk0x258->PartIsPlaced(m_unk0x110->GetName())) {
m_unk0x2a4 = Vector4(m_unk0x110->GetWorldPosition());
if (!m_unk0x2d4) {
m_unk0x2bc = m_unk0x2a4;
m_unk0x208 = m_unk0x12c;
m_unk0x2a4[0] += m_unk0x1c0[3][0] - m_unk0x208[3][0];
m_unk0x2a4[1] += m_unk0x1c0[3][1] - m_unk0x208[3][1];
m_unk0x2a4[2] += m_unk0x1c0[3][2] - m_unk0x208[3][2];
}
*unk0x178 = m_unk0x1c0;
}
else {
const LegoChar* wiredName;
if (!m_unk0x258->FUN_10079c30(m_unk0x110->GetName())) {
wiredName = m_unk0x258->GetWiredNameByPartName(m_unk0x110->GetName());
}
else {
wiredName = m_unk0x258->GetWiredNameOfLastPlacedPart();
}
LegoROI* parentROI = (LegoROI*) m_unk0x110->GetParentROI();
m_unk0x208 = parentROI->FindChildROI(wiredName, parentROI)->GetLocal2World();
m_unk0x2bc = Vector4(parentROI->FindChildROI(wiredName, parentROI)->GetWorldPosition());
m_unk0x2a4 = Vector4(m_unk0x110->GetWorldPosition());
m_unk0x2a4[2] += (m_unk0x1c0[3][2] - m_unk0x12c[3][2]);
m_unk0x178[3][2] = m_unk0x1c0[3][2];
}
}
// FUNCTION: LEGO1 0x100256c0
// FUNCTION: BETA10 0x1006e96c
void LegoCarBuild::Enable(MxBool p_enable)
@ -999,6 +1080,7 @@ void LegoCarBuild::Enable(MxBool p_enable)
}
// STUB: LEGO1 0x10025720
// STUB: BETA10 0x1006e9df
undefined4 LegoCarBuild::FUN_10025720(undefined4 p_param1)
{
// TODO
@ -1057,7 +1139,7 @@ void LegoCarBuild::FUN_10025e40()
MxBool LegoCarBuild::Escape()
{
BackgroundAudioManager()->Init();
MxS32 targetEntityId = FUN_10025ee0(m_unk0x330);
MxS32 targetEntityId = FUN_10025ee0(m_carId);
InvokeAction(Extra::ActionType::e_stop, *g_jukeboxScript, targetEntityId, NULL);
DeleteObjects(&m_atomId, 500, 999);

View File

@ -627,6 +627,20 @@ MxBool LegoCarBuildAnimPresenter::StringEndsOnZero(const LegoChar* p_string)
return (p_string[strlen(p_string) - 2] != '0');
}
// FUNCTION: LEGO1 0x10079d60
// FUNCTION: BETA10 0x1007284c
const LegoChar* LegoCarBuildAnimPresenter::GetWiredNameByPartName(const LegoChar* p_name)
{
for (MxS16 i = 0; i < m_numberOfParts; i++) {
if (strcmpi(p_name, m_parts[i].m_name) == 0) {
return m_parts[i].m_wiredName;
}
}
return NULL;
}
// FUNCTION: LEGO1 0x10079e20
// FUNCTION: BETA10 0x10072959
const BoundingSphere& LegoCarBuildAnimPresenter::FUN_10079e20()

View File

@ -238,6 +238,7 @@ MxBool CheckIfEntityExists(MxBool p_enable, const char* p_filename, MxS32 p_enti
void NotifyEntity(const char* p_filename, MxS32 p_entityId, LegoEntity* p_sender);
// FUNCTION: LEGO1 0x1003e430
// FUNCTION: BETA10 0x100d3fda
void InvokeAction(Extra::ActionType p_actionId, const MxAtomId& p_pAtom, MxS32 p_targetEntityId, LegoEntity* p_sender)
{
MxDSAction action;

View File

@ -188,8 +188,10 @@ void SetCurrentWorld(LegoWorld* p_world)
}
// FUNCTION: LEGO1 0x10015900
// FUNCTION: BETA10 0x100e4f02
MxTransitionManager* TransitionManager()
{
assert(LegoOmni::GetInstance());
return LegoOmni::GetInstance()->GetTransitionManager();
}

View File

@ -21,7 +21,7 @@ RECT g_fullScreenRect = {0, 0, 640, 480};
MxTransitionManager::MxTransitionManager()
{
m_animationTimer = 0;
m_transitionType = e_notTransitioning;
m_mode = e_idle;
m_ddSurface = NULL;
m_waitIndicator = NULL;
m_copyBuffer = NULL;
@ -60,7 +60,7 @@ MxResult MxTransitionManager::Tickle()
this->m_systemTime = timeGetTime();
switch (this->m_transitionType) {
switch (this->m_mode) {
case e_noAnimation:
NoTransition();
break;
@ -84,6 +84,7 @@ MxResult MxTransitionManager::Tickle()
}
// FUNCTION: LEGO1 0x1004bb70
// FUNCTION: BETA10 0x100ec4c1
MxResult MxTransitionManager::StartTransition(
TransitionType p_animationType,
MxS32 p_speed,
@ -91,13 +92,15 @@ MxResult MxTransitionManager::StartTransition(
MxBool p_playMusicInAnim
)
{
if (this->m_transitionType == e_notTransitioning) {
assert(m_mode == e_idle);
if (this->m_mode == e_idle) {
if (!p_playMusicInAnim) {
MxBackgroundAudioManager* backgroundAudioManager = BackgroundAudioManager();
backgroundAudioManager->Stop();
}
this->m_transitionType = p_animationType;
this->m_mode = p_animationType;
m_copyFlags.m_bit0 = p_doCopy;
@ -133,8 +136,8 @@ MxResult MxTransitionManager::StartTransition(
// FUNCTION: LEGO1 0x1004bc30
void MxTransitionManager::EndTransition(MxBool p_notifyWorld)
{
if (m_transitionType != e_notTransitioning) {
m_transitionType = e_notTransitioning;
if (m_mode != e_idle) {
m_mode = e_idle;
m_copyFlags.m_bit0 = FALSE;

View File

@ -45,6 +45,7 @@ void LegoControlManager::Register(MxCore* p_listener)
}
// FUNCTION: LEGO1 0x10028ea0
// FUNCTION: BETA10 0x1007c330
void LegoControlManager::Unregister(MxCore* p_listener)
{
LegoNotifyListCursor cursor(&m_notifyList);

View File

@ -339,6 +339,7 @@ void LegoInputManager::SetWorld(LegoWorld* p_world)
}
// FUNCTION: LEGO1 0x1005c730
// FUNCTION: BETA10 0x100896dc
void LegoInputManager::ClearWorld()
{
m_world = NULL;

View File

@ -285,7 +285,7 @@ void LegoVideoManager::ToggleFPS(MxBool p_visible)
MxResult LegoVideoManager::Tickle()
{
if (m_unk0x554 && !m_videoParam.Flags().GetFlipSurfaces() &&
TransitionManager()->GetTransitionType() == MxTransitionManager::e_notTransitioning) {
TransitionManager()->GetTransitionType() == MxTransitionManager::e_idle) {
Sleep(30);
}

View File

@ -19,6 +19,7 @@
// _free
// LIBRARY: LEGO1 0x1008b020
// LIBRARY: BETA10 0x100f8d20
// ___CxxFrameHandler
// LIBRARY: LEGO1 0x1008b24c

View File

@ -78,6 +78,7 @@ class Mx4DPointFloat : public Vector4 {
const float& operator[](int idx) const { return m_data[idx]; }
// SYNTHETIC: LEGO1 0x10064b20
// SYNTHETIC: BETA10 0x10070420
// Mx4DPointFloat::operator=
private:

View File

@ -20,6 +20,7 @@ class MxMatrix : public Matrix4 {
// FUNCTION: BETA10 0x10010860
float* operator[](int idx) { return m_data[idx]; }
// FUNCTION: BETA10 0x1001c670
const float* operator[](int idx) const { return m_data[idx]; }
// FUNCTION: LEGO1 0x10002850

View File

@ -178,6 +178,7 @@ void MxNotificationManager::Register(MxCore* p_listener)
}
// FUNCTION: LEGO1 0x100acdf0
// FUNCTION: BETA10 0x10126785
void MxNotificationManager::Unregister(MxCore* p_listener)
{
AUTOLOCK(m_lock);

View File

@ -51,6 +51,7 @@ class OrientableROI : public ROI {
// FUNCTION: BETA10 0x1004aa70
const float* GetWorldUp() const { return m_local2world[1]; }
// FUNCTION: BETA10 0x10070380
OrientableROI* GetParentROI() const { return m_parentROI; }
void SetParentROI(OrientableROI* p_parentROI) { m_parentROI = p_parentROI; }

View File

@ -152,6 +152,7 @@ class Vector2 {
virtual void SetVector(const Vector2* p_other) { EqualsImpl(p_other->m_data); } // vtable+0x6c
// SYNTHETIC: LEGO1 0x10010be0
// SYNTHETIC: BETA10 0x100121e0
// Vector3::operator=
// SYNTHETIC: BETA10 0x1004af40
@ -301,10 +302,14 @@ class Vector4 : public Vector3 {
// FUNCTION: BETA10 0x10048780
Vector4(float* p_data) : Vector3(p_data) {}
// Hack: Some code initializes a Vector4 from a (most likely) const float* source.
// Example: LegoCarBuild::VTable0x6c
// Some code initializes a Vector4 from a `const float*` source.
// Example: `LegoCarBuild::VTable0x6c`
// Vector4 however is a class that can mutate its underlying source, making
// initialization with a const source fundamentally incompatible.
// BETA10 appears to have two separate constructors for Vector4 as well,
// supporting the theory that this decompilation is correct.
// FUNCTION: BETA10 0x100701b0
Vector4(const float* p_data) : Vector3((float*) p_data) {}
// Note: virtual function overloads appear in the virtual table