isle/LEGO1/lego/legoomni/src/actors/helicopter.cpp
2026-01-08 00:26:46 +01:00

508 lines
13 KiB
C++

#include "helicopter.h"
#include "act3.h"
#include "act3_actions.h"
#include "isle.h"
#include "isle_actions.h"
#include "jukebox.h"
#include "jukebox_actions.h"
#include "legoanimationmanager.h"
#include "legocameracontroller.h"
#include "legocontrolmanager.h"
#include "legogamestate.h"
#include "legoutils.h"
#include "legoworld.h"
#include "misc.h"
#include "mxdebug.h"
#include "mxmisc.h"
#include "mxtimer.h"
#include "mxtransitionmanager.h"
#include "scripts.h"
DECOMP_SIZE_ASSERT(Helicopter, 0x230)
DECOMP_SIZE_ASSERT(HelicopterState, 0x0c)
DECOMP_SIZE_ASSERT(Vector2, 0x08)
DECOMP_SIZE_ASSERT(Vector3, 0x08)
DECOMP_SIZE_ASSERT(Vector4, 0x08)
DECOMP_SIZE_ASSERT(Mx3DPointFloat, 0x14)
DECOMP_SIZE_ASSERT(Mx4DPointFloat, 0x18)
DECOMP_SIZE_ASSERT(Matrix4, 0x08)
DECOMP_SIZE_ASSERT(MxMatrix, 0x48)
// FUNCTION: LEGO1 0x10001e60
// FUNCTION: BETA10 0x1002a0d0
Helicopter::Helicopter()
{
m_maxLinearVel = 60;
}
// FUNCTION: LEGO1 0x10003230
Helicopter::~Helicopter()
{
ControlManager()->Unregister(this);
IslePathActor::Destroy(TRUE);
}
// FUNCTION: LEGO1 0x100032c0
// FUNCTION: BETA10 0x1002a16d
MxResult Helicopter::Create(MxDSAction& p_dsAction)
{
MxResult result = IslePathActor::Create(p_dsAction);
m_world = CurrentWorld();
if (m_world->IsA("Act3")) {
((Act3*) m_world)->SetHelicopter(this);
}
assert(m_world);
if (m_world != NULL) {
m_world->Add(this);
}
CreateState();
return result;
}
// FUNCTION: LEGO1 0x10003320
// FUNCTION: BETA10 0x1002a240
void Helicopter::CreateState()
{
m_state = (HelicopterState*) GameState()->GetState("HelicopterState");
if (!m_state) {
m_state = (HelicopterState*) GameState()->CreateState("HelicopterState");
}
}
// FUNCTION: LEGO1 0x10003360
// FUNCTION: BETA10 0x1002a29a
void Helicopter::Exit()
{
assert(UserActor() == this);
if (GameState()->GetCurrentAct() == LegoGameState::e_act1) {
SpawnPlayer(
LegoGameState::e_helicopterSpawn,
TRUE,
IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
);
}
IslePathActor::Exit();
if (GameState()->GetCurrentAct() == LegoGameState::e_act1) {
GameState()->m_currentArea = LegoGameState::e_copter;
if (UserActor() && UserActor()->IsA("IslePathActor")) {
((IslePathActor*) UserActor())
->SpawnPlayer(
LegoGameState::e_unk55,
TRUE,
IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
);
}
}
m_state->m_status = 0;
RemoveFromCurrentWorld(m_script, IsleScript::c_HelicopterDashboard_Bitmap);
RemoveFromCurrentWorld(m_script, IsleScript::c_HelicopterArms_Ctl);
RemoveFromCurrentWorld(m_script, IsleScript::c_Helicopter_TakeOff_Ctl);
RemoveFromCurrentWorld(m_script, IsleScript::c_Helicopter_Land_Ctl);
RemoveFromCurrentWorld(m_script, Act3Script::c_Helicopter_Pizza_Ctl);
RemoveFromCurrentWorld(m_script, Act3Script::c_Helicopter_Donut_Ctl);
RemoveFromCurrentWorld(m_script, Act3Script::c_Helicopter_Info_Ctl);
RemoveFromCurrentWorld(m_script, 0x1d);
RemoveFromCurrentWorld(m_script, 0x1e);
RemoveFromCurrentWorld(m_script, 0x1f);
AnimationManager()->FUN_1005f6d0(TRUE);
ControlManager()->Unregister(this);
}
// FUNCTION: LEGO1 0x10003480
// FUNCTION: BETA10 0x1002a3db
MxLong Helicopter::HandleClick()
{
if (!CanExit()) {
return 1;
}
if (!m_world) {
m_world = CurrentWorld();
assert(m_world);
}
AnimationManager()->FUN_1005f6d0(FALSE);
if (UserActor()) {
if (UserActor()->GetActorId() != GameState()->GetActorId()) {
((IslePathActor*) UserActor())->Exit();
}
}
switch (GameState()->GetCurrentAct()) {
case LegoGameState::e_act1:
m_script = *g_isleScript;
AnimationManager()->FUN_10064670(NULL);
SpawnPlayer(
LegoGameState::e_unk41,
TRUE,
IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
);
((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::e_copter);
Disable(TRUE, 0);
TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, TRUE);
SetActorState(c_disabled);
PlayMusic(JukeboxScript::c_Jail_Music);
break;
case LegoGameState::e_act2:
m_script = *g_act2mainScript;
break;
case LegoGameState::e_act3:
m_script = *g_act3Script;
break;
}
Enter();
InvokeAction(Extra::ActionType::e_start, m_script, IsleScript::c_HelicopterDashboard, NULL);
GetCurrentAction().SetObjectId(-1);
ControlManager()->Register(this);
return 1;
}
// FUNCTION: LEGO1 0x100035e0
// FUNCTION: BETA10 0x1002a587
MxLong Helicopter::HandleControl(LegoControlManagerNotificationParam& p_param)
{
MxLong result = 0;
assert(m_world);
MxAtomId script;
switch (GameState()->GetCurrentAct()) {
case LegoGameState::e_act1:
script = *g_isleScript;
break;
case LegoGameState::e_act2:
script = *g_act2mainScript;
break;
case LegoGameState::e_act3:
script = *g_act3Script;
break;
}
if (p_param.m_enabledChild == 1) {
MxU32 isPizza = FALSE;
switch (p_param.m_clickedObjectId) {
case IsleScript::c_HelicopterArms_Ctl:
if (*g_act3Script == script) {
((Act3*) CurrentWorld())->SetDestLocation(LegoGameState::e_infomain);
TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE);
}
else if (m_state->m_status != 0) {
break;
}
Exit();
GameState()->m_currentArea = LegoGameState::e_vehicleExited;
result = 1;
break;
case IsleScript::c_Helicopter_TakeOff_Ctl: {
if (*g_act3Script == script) {
break;
}
Act1State* act1State = (Act1State*) GameState()->GetState("Act1State");
assert(act1State);
if (m_state->m_status == 0) {
act1State->m_state = Act1State::e_helicopter;
m_state->m_status = 1;
m_world->RemoveActor(this);
InvokeAction(Extra::ActionType::e_start, script, IsleScript::c_HelicopterTakeOff_Anim, NULL);
SetActorState(c_initial);
}
result = 1;
break;
}
case IsleScript::c_Helicopter_Land_Ctl:
if (*g_act3Script == script) {
break;
}
if (m_state->m_status == 2) {
m_state->m_status = 3;
m_world->RemoveActor(this);
InvokeAction(Extra::ActionType::e_start, script, IsleScript::c_HelicopterLand_Anim, NULL);
SetActorState(c_disabled);
}
result = 1;
break;
case Act3Script::c_Helicopter_Pizza_Ctl:
if (*g_act3Script != script) {
break;
}
isPizza = TRUE;
case Act3Script::c_Helicopter_Donut_Ctl:
if (*g_act3Script != script) {
break;
}
assert(m_pathController);
if (m_world && m_world->GetCameraController()) {
Mx3DPointFloat location, direction, lookat;
location = m_world->GetCameraController()->GetWorldLocation();
direction = m_world->GetCameraController()->GetWorldDirection();
lookat = direction;
lookat *= 3.0f;
location += lookat;
Mx3DPointFloat cameraUp, right, up;
Mx3DPointFloat worldUp(0, 1, 0);
cameraUp = m_world->GetCameraController()->GetWorldUp();
right.EqualsCross(cameraUp, direction);
up.EqualsCross(right, worldUp);
if (isPizza) {
if (((Act3*) m_world)->ShootPizza(m_pathController, location, direction, up) != SUCCESS) {
MxTrace("Shoot pizza failed\n");
break;
}
}
else {
if (((Act3*) m_world)->ShootDonut(m_pathController, location, direction, up) != SUCCESS) {
MxTrace("Shoot donut failed\n");
break;
}
}
}
result = 1;
break;
/* case Act3Script::c_Helicopter_Info_Ctl: */
case IsleScript::c_Helicopter_Info_Ctl:
if (GameState()->GetCurrentAct() == LegoGameState::e_act1) {
((Isle*) CurrentWorld())->SetDestLocation(LegoGameState::e_infomain);
TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE);
Exit();
}
else if (*g_act3Script == script) {
((Act3*) CurrentWorld())->SetDestLocation(LegoGameState::e_infomain);
TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE);
}
result = 1;
break;
// Unknown object ID
case 0x1d:
result = 1;
break;
}
}
return result;
}
// FUNCTION: LEGO1 0x10003c20
// FUNCTION: BETA10 0x1002ab4c
MxLong Helicopter::HandleEndAnim(LegoEndAnimNotificationParam& p_param)
{
MxLong result = 0;
switch (m_state->m_status) {
case 1: {
if (GameState()->GetCurrentAct() == LegoGameState::e_act1) {
Act1State* act1State = (Act1State*) GameState()->GetState("Act1State");
assert(act1State);
act1State->m_state = Act1State::e_helicopter;
SpawnPlayer(
LegoGameState::e_unk42,
TRUE,
IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
);
}
else {
SpawnPlayer(
LegoGameState::e_unk49,
TRUE,
IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
);
}
m_state->m_status = 2;
MxMatrix matrix;
matrix.SetIdentity();
matrix.RotateX(0.5235987901687622); // PI / 6, 30 deg
Vector3 at(matrix[3]), dir(matrix[2]), up(matrix[1]);
assert(m_world && m_world->GetCameraController());
m_world->GetCameraController()->SetWorldTransform(at, dir, up);
TransformPointOfView();
result = 1;
break;
}
case 3: {
MxMatrix matrix;
matrix.SetIdentity();
Vector3 at(matrix[3]), dir(matrix[2]), up(matrix[1]);
at[1] = 1.25;
assert(m_world && m_world->GetCameraController());
m_world->GetCameraController()->SetWorldTransform(at, dir, up);
if (GameState()->GetCurrentAct() == LegoGameState::e_act1) {
Act1State* act1State = (Act1State*) GameState()->GetState("Act1State");
assert(act1State);
act1State->m_state = Act1State::e_none;
SpawnPlayer(
LegoGameState::e_unk41,
TRUE,
IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
);
}
else {
SpawnPlayer(
LegoGameState::e_unk48,
TRUE,
IslePathActor::c_spawnBit1 | IslePathActor::c_playMusic | IslePathActor::c_spawnBit3
);
}
m_state->m_status = 0;
result = 1;
break;
}
}
return result;
}
// FUNCTION: LEGO1 0x10003e90
void Helicopter::VTable0x74(Matrix4& p_transform)
{
if (m_userNavFlag) {
m_roi->UpdateTransformationRelativeToParent(p_transform);
TransformPointOfView();
}
else {
m_roi->SetLocal2World(p_transform);
m_roi->WrappedUpdateWorldData();
if (m_cameraFlag) {
TransformPointOfView();
}
}
}
// FUNCTION: LEGO1 0x10003ee0
void Helicopter::Animate(float p_time)
{
if (m_state->m_status == 4 || m_state->m_status == 5) {
float remainingTime = m_cameraTransitionTime - p_time + 3000.0f;
if (remainingTime >= 0) {
float interpolationFactor = remainingTime / -3000.0f + 1;
if (interpolationFactor < 0) {
interpolationFactor = 0;
}
if (interpolationFactor > 1.0f) {
interpolationFactor = 1.0f;
}
MxMatrix transform;
Vector3 startPosition(m_cameraTransitionStartMatrix[3]);
Vector3 interpolatedPosition(transform[3]);
Vector3 endPosition(m_cameraTransitionEndMatrix[3]);
transform.SetIdentity();
m_cameraTransitionInterpolator.InterpolateToMatrix(transform, interpolationFactor);
interpolatedPosition = endPosition;
interpolatedPosition -= startPosition;
interpolatedPosition *= interpolationFactor;
interpolatedPosition += startPosition;
m_world->GetCameraController()->TransformPointOfView(transform, 0);
}
else {
if (m_state->m_status == 4) {
((Act3*) m_world)->FUN_10073400();
}
else {
((Act3*) m_world)->FUN_10073430();
}
SetActorState(c_disabled);
}
}
else {
LegoPathActor::Animate(p_time);
}
}
// FUNCTION: LEGO1 0x100042a0
void Helicopter::SetupCameraTransition(const Matrix4& p_matrix)
{
MxMatrix startMatrix;
MxMatrix endMatrix;
Vector3 startPos(startMatrix[3]); // local98 // esp+0x30
Vector3 endPos(endMatrix[3]); // localac // esp+0x1c
Vector3 endMatrixX(m_cameraTransitionEndMatrix[0]); // locala8 // esp+0x20
Vector3 endMatrixY(m_cameraTransitionEndMatrix[1]); // localb8 // esp+0x10
Vector3 endMatrixZ(m_cameraTransitionEndMatrix[2]); // EDI
// the typecast makes this function match for unknown reasons
Vector3 endMatrixPos((const float*) m_cameraTransitionEndMatrix[3]); // locala0 // esp+0x28
m_world->GetCameraController()->GetPointOfView(startMatrix);
m_cameraTransitionEndMatrix.SetIdentity();
endMatrix = p_matrix;
endPos[1] += 20.0f;
endMatrixY = endPos;
endMatrixY -= startPos;
endMatrixY.Unitize();
endMatrixZ[0] = endMatrixZ[2] = 0.0f;
endMatrixZ[1] = -1.0f;
endMatrixX.EqualsCross(endMatrixY, endMatrixZ);
endMatrixX.Unitize();
endMatrixY.EqualsCross(endMatrixZ, endMatrixX);
endMatrixPos = endPos;
endMatrix = m_cameraTransitionEndMatrix;
m_cameraTransitionStartMatrix = startMatrix;
startPos.Clear();
endPos.Clear();
m_cameraTransitionTime = Timer()->GetTime();
m_cameraTransitionInterpolator.SetStartEnd(startMatrix, endMatrix);
m_cameraTransitionInterpolator.NormalizeDirection();
}
// FUNCTION: LEGO1 0x10004640
void Helicopter::StartGoodEndingCamera(const Matrix4& p_matrix)
{
if (m_state->m_status != 4 && m_state->m_status != 5) {
m_state->m_status = 4;
SetupCameraTransition(p_matrix);
}
}
// FUNCTION: LEGO1 0x10004670
void Helicopter::StartBadEndingCamera(const Matrix4& p_matrix)
{
if (m_state->m_status != 4 && m_state->m_status != 5) {
m_state->m_status = 5;
SetupCameraTransition(p_matrix);
}
}