#include "legonavcontroller.h"

#include "3dmanager/lego3dmanager.h"
#include "act3.h"
#include "extensions/thirdpersoncamera.h"
#include "infocenter.h"
#include "legoanimationmanager.h"
#include "legocameracontroller.h"
#include "legocharactermanager.h"
#include "legogamestate.h"
#include "legoinputmanager.h"
#include "legolocations.h"
#include "legomain.h"
#include "legoplantmanager.h"
#include "legosoundmanager.h"
#include "legoutils.h"
#include "legovideomanager.h"
#include "legoworld.h"
#include "misc.h"
#include "mxbackgroundaudiomanager.h"
#include "mxdebug.h"
#include "mxmisc.h"
#include "mxtimer.h"
#include "mxtransitionmanager.h"
#include "mxutilities.h"
#include "realtime/realtime.h"
#include "realtime/realtimeview.h"
#include "viewmanager/viewmanager.h"

#include <SDL3/SDL_stdinc.h>
#include <vec.h>

using namespace Extensions;

DECOMP_SIZE_ASSERT(LegoNavController, 0x70)

// MSVC 4.20 didn't define a macro for this key
#ifndef VK_OEM_MINUS
#define VK_OEM_MINUS 0xBD
#endif

//////////////////////////////////////////////////////////////////////

#ifndef M_PI
#define M_PI 3.1416
#endif
#ifdef DTOR
#undef DTOR
#endif
#define DTOR(angle) ((angle) * M_PI / 180.)

//////////////////////////////////////////////////////////////////////

// GLOBAL: LEGO1 0x100f4c28
int LegoNavController::g_defdeadZone = 40;

// GLOBAL: LEGO1 0x100f4c2c
float LegoNavController::g_defzeroThreshold = 0.001f;

// GLOBAL: LEGO1 0x100f4c30
float LegoNavController::g_defmaxLinearVel = 40.0f;

// GLOBAL: LEGO1 0x100f4c34
float LegoNavController::g_defmaxRotationalVel = 20.0f;

// GLOBAL: LEGO1 0x100f4c38
float LegoNavController::g_defmaxLinearAccel = 15.0f;

// GLOBAL: LEGO1 0x100f4c3c
float LegoNavController::g_defmaxRotationalAccel = 30.0f;

// GLOBAL: LEGO1 0x100f4c40
float LegoNavController::g_defminLinearAccel = 4.0f;

// GLOBAL: LEGO1 0x100f4c44
float LegoNavController::g_defminRotationalAccel = 15.0f;

// GLOBAL: LEGO1 0x100f4c48
float LegoNavController::g_defmaxLinearDeccel = 50.0f;

// GLOBAL: LEGO1 0x100f4c4c
float LegoNavController::g_defmaxRotationalDeccel = 50.0f;

// GLOBAL: LEGO1 0x100f4c50
float LegoNavController::g_defrotSensitivity = 0.4f;

// GLOBAL: LEGO1 0x100f4c54
MxBool LegoNavController::g_defuseRotationalVel = FALSE;

// GLOBAL: LEGO1 0x100f66a0
MxU32 g_changeLight = FALSE;

// GLOBAL: LEGO1 0x100f66a4
MxS32 g_locationCalcStep = 0;

// GLOBAL: LEGO1 0x100f66a8
MxS32 g_nextLocation = 0;

// GLOBAL: LEGO1 0x100f66ac
MxBool g_resetPlants = FALSE;

// GLOBAL: LEGO1 0x100f66b0
MxS32 g_animationCalcStep = 0;

// GLOBAL: LEGO1 0x100f66b4
MxS32 g_nextAnimation = 0;

// GLOBAL: LEGO1 0x100f66b8
MxU32 g_switchAct = FALSE;

// GLOBAL: LEGO1 0x100f66bc
LegoAnimationManager::PlayMode g_unk0x100f66bc = LegoAnimationManager::e_unk2;

// GLOBAL: LEGO1 0x100f66c0
SDL_Keycode g_debugPassword[] = {
	// "OGEL"
	SDLK_O,
	SDLK_G,
	SDLK_E,
	SDLK_L,
	0,
};

// GLOBAL: LEGO1 0x100f66c8
SDL_Keycode* g_currentInput = g_debugPassword;

// GLOBAL: LEGO1 0x100f66cc
MxS32 g_nextCharacter = -1;

// GLOBAL: LEGO1 0x100f66d0
MxBool g_enableMusic = TRUE;

// GLOBAL: LEGO1 0x100f66d4
MxU32 g_fpsEnabled = TRUE;

// FUNCTION: LEGO1 0x10054ac0
LegoNavController::LegoNavController()
{
	SetToDefaultParams();

	m_linearVel = 0.0f;
	m_rotationalVel = 0.0f;
	m_targetLinearVel = 0.0f;
	m_targetRotationalVel = 0.0f;
	m_linearAccel = 0.0f;
	m_rotationalAccel = 0.0f;
	m_trackDefault = FALSE;
	m_keyPressed = FALSE;
	m_isAccelerating = FALSE;
	m_additionalScale = 0.0f;
	m_additionalRotationY = 0.0f;
	m_additionalHeightOffset = 0.0f;

	m_lastTime = Timer()->GetTime();

	InputManager()->Register(this);
}

// FUNCTION: LEGO1 0x10054c30
LegoNavController::~LegoNavController()
{
	InputManager()->UnRegister(this);
}

// FUNCTION: LEGO1 0x10054ca0
void LegoNavController::SetControlMax(int p_hMax, int p_vMax)
{
	m_hMax = p_hMax;
	m_vMax = p_vMax;

	if (VideoManager()->GetVideoParam().Flags().GetFullScreen()) {
		m_hMax = 640;
		m_vMax = 480;
	}
}

// FUNCTION: LEGO1 0x10054cd0
// FUNCTION: BETA10 0x1009ad76
void LegoNavController::SetToDefaultParams()
{
	m_deadZone = g_defdeadZone;
	m_zeroThreshold = g_defzeroThreshold;
	m_maxRotationalAccel = g_defmaxRotationalAccel;
	m_maxLinearAccel = g_defmaxLinearAccel;
	m_minRotationalAccel = g_defminRotationalAccel;
	m_minLinearAccel = g_defminLinearAccel;
	m_maxRotationalDeccel = g_defmaxRotationalDeccel;
	m_maxLinearDeccel = g_defmaxLinearDeccel;
	m_maxRotationalVel = g_defmaxRotationalVel;
	m_maxLinearVel = g_defmaxLinearVel;
	m_useRotationalVel = g_defuseRotationalVel;
	m_rotSensitivity = g_defrotSensitivity;
}

// FUNCTION: LEGO1 0x10054d40
void LegoNavController::GetDefaults(
	int* p_dz,
	float* p_lv,
	float* p_rv,
	float* p_la,
	float* p_ra,
	float* p_ld,
	float* p_rd,
	float* p_lmina,
	float* p_rmina,
	float* p_rs,
	MxBool* p_urs
)
{
	*p_dz = g_defdeadZone;
	*p_lv = g_defmaxLinearVel;
	*p_rv = g_defmaxRotationalVel;
	*p_la = g_defmaxLinearAccel;
	*p_ra = g_defmaxRotationalAccel;
	*p_ld = g_defmaxLinearDeccel;
	*p_rd = g_defmaxRotationalDeccel;
	*p_lmina = g_defminLinearAccel;
	*p_rmina = g_defminRotationalAccel;
	*p_rs = g_defrotSensitivity;
	*p_urs = g_defuseRotationalVel;
}

// FUNCTION: LEGO1 0x10054dd0
void LegoNavController::SetDefaults(
	int p_dz,
	float p_lv,
	float p_rv,
	float p_la,
	float p_ra,
	float p_ld,
	float p_rd,
	float p_lmina,
	float p_rmina,
	float p_rs,
	MxBool p_urs
)
{
	g_defdeadZone = p_dz;
	g_defmaxLinearVel = p_lv;
	g_defmaxRotationalVel = p_rv;
	g_defmaxLinearAccel = p_la;
	g_defmaxRotationalAccel = p_ra;
	g_defmaxLinearDeccel = p_ld;
	g_defmaxRotationalDeccel = p_rd;
	g_defminLinearAccel = p_lmina;
	g_defminRotationalAccel = p_rmina;
	g_defrotSensitivity = p_rs;
	g_defuseRotationalVel = p_urs;
}

// FUNCTION: LEGO1 0x10054e40
void LegoNavController::SetTargets(int p_hPos, int p_vPos, MxBool p_accel)
{
	if (m_trackDefault != FALSE) {
		SetToDefaultParams();
	}

	if (p_accel != FALSE) {
		m_targetRotationalVel = CalculateNewTargetVel(p_hPos, m_hMax / 2, m_maxRotationalVel);
		m_targetLinearVel = CalculateNewTargetVel(m_vMax - p_vPos, m_vMax / 2, m_maxLinearVel);
		m_rotationalAccel = CalculateNewAccel(p_hPos, m_hMax / 2, m_maxRotationalAccel, (int) m_minRotationalAccel);
		m_linearAccel = CalculateNewAccel(m_vMax - p_vPos, m_vMax / 2, m_maxLinearAccel, (int) m_minLinearAccel);
	}
	else {
		m_targetRotationalVel = 0;
		m_targetLinearVel = 0;
		m_linearAccel = m_maxLinearDeccel;
		m_rotationalAccel = m_maxRotationalDeccel;
	}
}

// FUNCTION: LEGO1 0x10054f10
float LegoNavController::CalculateNewTargetVel(int p_pos, int p_center, float p_max)
{
	float newVel;
	int diff = p_pos - p_center;

	if (diff > m_deadZone) {
		newVel = (diff - m_deadZone) * p_max / (p_center - m_deadZone);
	}
	else if (diff < -m_deadZone) {
		newVel = (diff + m_deadZone) * p_max / (p_center - m_deadZone);
	}
	else {
		newVel = 0.0;
	}

	return newVel;
}

// FUNCTION: LEGO1 0x10054f90
float LegoNavController::CalculateNewAccel(int p_pos, int p_center, float p_max, int p_min)
{
	float newAccel;
	int diff = p_pos - p_center;

	newAccel = Abs(diff) * p_max / p_center;

	if (newAccel < p_min) {
		newAccel = (float) p_min;
	}

	return newAccel;
}

// FUNCTION: LEGO1 0x10054fe0
float LegoNavController::CalculateNewVel(float p_targetVel, float p_currentVel, float p_accel, float p_time)
{
	float newVel = p_currentVel;

	float velDiff = p_targetVel - p_currentVel;
	int vSign = velDiff > 0 ? 1 : -1;

	if (Abs(velDiff) > m_zeroThreshold) {
		float deltaVel = p_accel * p_time;
		newVel = p_currentVel + (deltaVel * vSign);

		if (vSign > 0) {
			newVel = Min(newVel, p_targetVel);
		}
		else {
			newVel = Max(newVel, p_targetVel);
		}
	}

	return newVel;
}

// FUNCTION: LEGO1 0x10055080
// FUNCTION: BETA10 0x1009b26b
MxBool LegoNavController::CalculateNewPosDir(
	const Vector3& p_curPos,
	const Vector3& p_curDir,
	Vector3& p_newPos,
	Vector3& p_newDir,
	const Vector3* p_up
)
{
	if (!g_isWorldActive) {
		return FALSE;
	}

	MxBool changed = FALSE;
	MxBool rotatedY = FALSE;

	MxTime currentTime = Timer()->GetTime();
	float deltaTime = Min((currentTime - m_lastTime) / 1000.0, 1. / 10.);
	m_lastTime = currentTime;

	if (ProcessKeyboardInput() == FAILURE) {
		ProcessJoystickInput(rotatedY);
	}

	if (Extension<
			ThirdPersonCameraExt>::Call(TP::HandleNavOverride, this, p_curPos, p_curDir, p_newPos, p_newDir, deltaTime)
			.value_or(FALSE)) {
		return TRUE;
	}

	if (m_useRotationalVel) {
		m_rotationalVel = CalculateNewVel(m_targetRotationalVel, m_rotationalVel, m_rotationalAccel * 40.0f, deltaTime);
	}
	else {
		m_rotationalVel = m_targetRotationalVel * m_maxRotationalVel * deltaTime;
	}

	m_linearVel = CalculateNewVel(m_targetLinearVel, m_linearVel, m_linearAccel, deltaTime);

	if (rotatedY || (Abs(m_rotationalVel) > m_zeroThreshold) || (Abs(m_linearVel) > m_zeroThreshold)) {
		float rot_mat[3][3];
		Mx3DPointFloat delta_pos, new_dir, new_pos;

		if (m_linearVel < -(m_maxLinearVel * 0.4f)) {
			m_linearVel = -(m_maxLinearVel * 0.4f);
		}

		VXS3(delta_pos, p_curDir, m_linearVel * deltaTime);
		VPV3(p_newPos, p_curPos, delta_pos);

		float delta_rad;
		if (m_useRotationalVel) {
			delta_rad = DTOR(m_rotationalVel * deltaTime);
		}
		else {
			delta_rad = DTOR(m_rotationalVel * m_rotSensitivity);
		}

		if (p_up != NULL && (*p_up)[1] < 0.0f) {
			delta_rad = -delta_rad;
		}

		IDENTMAT3(rot_mat);
		rot_mat[0][0] = rot_mat[2][2] = cos(delta_rad);
		rot_mat[0][2] = rot_mat[2][0] = sin(delta_rad);
		rot_mat[0][2] *= -1.0f;
		VXM3(p_newDir, p_curDir, rot_mat);

		changed = TRUE;
	}

	if (m_keyPressed) {
		float rot_mat[3][3];
		Mx3DPointFloat delta_pos, new_pos, new_dir;

		if (changed) {
			SET3(new_pos, p_newPos);
			SET3(new_dir, p_newDir);
		}
		else {
			SET3(new_pos, p_curPos);
			SET3(new_dir, p_curDir);
		}

		if (m_additionalScale != 0.0f) {
			delta_pos[0] = new_dir[0] * m_additionalScale;
			delta_pos[1] = new_dir[1] * m_additionalScale;
			delta_pos[2] = new_dir[2] * m_additionalScale;
		}
		else {
			FILLVEC3(delta_pos, 0.0f);
		}

		delta_pos[1] += m_additionalHeightOffset;
		VPV3(p_newPos, new_pos, delta_pos);

		if (m_additionalRotationY != 0.0f) {
			float delta_rad = DTOR(m_additionalRotationY);
			IDENTMAT3(rot_mat);
			rot_mat[0][0] = rot_mat[2][2] = cos(delta_rad);
			rot_mat[0][2] = rot_mat[2][0] = sin(delta_rad);
			rot_mat[0][2] *= -1.0f;
			VXM3(p_newDir, new_dir, rot_mat);
		}
		else {
			SET3(p_newDir, new_dir);
		}

		m_additionalHeightOffset = m_additionalScale = m_additionalRotationY = 0.0f;
		m_keyPressed = FALSE;
		changed = TRUE;
	}

	return changed;
}

// FUNCTION: LEGO1 0x10055500
// FUNCTION: BETA10 0x1009bff8
MxResult LegoNavController::UpdateLocation(const char* p_location)
{
	MxResult result = FAILURE;

	for (MxS32 i = 0; i < (MxS32) sizeOfArray(g_locations); i++) {
		if (!SDL_strcasecmp(p_location, g_locations[i].m_name)) {
			MxMatrix mat;
			LegoROI* viewROI = VideoManager()->GetViewROI();

			CalcLocalTransform(g_locations[i].m_position, g_locations[i].m_direction, g_locations[i].m_up, mat);

			Mx3DPointFloat vec;
			vec.Clear();

			viewROI->SetWorldVelocity(vec);
			viewROI->WrappedSetLocal2WorldWithWorldDataUpdate(mat);
			VideoManager()->Get3DManager()->Moved(*viewROI);

			SoundManager()->UpdateListener(
				viewROI->GetWorldPosition(),
				viewROI->GetWorldDirection(),
				viewROI->GetWorldUp(),
				viewROI->GetWorldVelocity()
			);

			result = SUCCESS;
		}
	}

	return result;
}

// FUNCTION: LEGO1 0x10055620
// FUNCTION: BETA10 0x1009c145
MxResult LegoNavController::UpdateLocation(MxU32 p_location)
{
	MxResult result = FAILURE;

	if (p_location < sizeOfArray(g_locations)) {
		MxMatrix mat;
		LegoROI* viewROI = VideoManager()->GetViewROI();

		CalcLocalTransform(
			g_locations[p_location].m_position,
			g_locations[p_location].m_direction,
			g_locations[p_location].m_up,
			mat
		);

		Mx3DPointFloat vec;
		vec.Clear();

		viewROI->SetWorldVelocity(vec);
		viewROI->WrappedSetLocal2WorldWithWorldDataUpdate(mat);
		VideoManager()->Get3DManager()->Moved(*viewROI);

		SoundManager()->UpdateListener(
			viewROI->GetWorldPosition(),
			viewROI->GetWorldDirection(),
			viewROI->GetWorldUp(),
			viewROI->GetWorldVelocity()
		);

		result = SUCCESS;
	}

	return result;
}

// FUNCTION: LEGO1 0x10055720
// FUNCTION: BETA10 0x1009c259
LegoLocation* LegoNavController::GetLocation(MxU32 p_location)
{
	if (p_location < sizeOfArray(g_locations)) {
		return &g_locations[p_location];
	}

	return NULL;
}

// FUNCTION: LEGO1 0x10055740
MxS32 LegoNavController::GetNumLocations()
{
	return sizeOfArray(g_locations);
}

// FUNCTION: LEGO1 0x10055750
MxResult LegoNavController::ProcessJoystickInput(MxBool& p_rotatedY)
{
	LegoOmni* instance = LegoOmni::GetInstance();

	if (instance->GetInputManager()) {
		MxS32 joystickX;
		MxS32 joystickY;
		MxS32 povPosition;

		if (instance->GetInputManager()
				->GetJoystickState((MxU32*) &joystickX, (MxU32*) &joystickY, (MxU32*) &povPosition) != FAILURE) {
			MxU32 yVal = (joystickY * m_vMax) / 100;
			MxU32 xVal = (joystickX * m_hMax) / 100;

			if (joystickX <= 45 || joystickX >= 55 || joystickY <= 45 || joystickY >= 55) {
				m_targetLinearVel = CalculateNewTargetVel(m_vMax - yVal, m_vMax / 2, m_maxLinearVel);
				m_linearAccel = CalculateNewAccel(m_vMax - yVal, m_vMax / 2, m_maxLinearAccel, (int) m_minLinearAccel);
				m_targetRotationalVel = CalculateNewTargetVel(xVal, m_hMax / 2, m_maxRotationalVel);
				m_rotationalAccel =
					CalculateNewAccel(xVal, m_hMax / 2, m_maxRotationalAccel, (int) m_minRotationalAccel);
			}
			else {
				m_targetRotationalVel = 0.0;
				m_targetLinearVel = 0.0;
				m_linearAccel = m_maxLinearDeccel;
				m_rotationalAccel = m_maxRotationalDeccel;
			}

			if (povPosition >= 0) {
				LegoWorld* world = CurrentWorld();

				if (world && world->GetCameraController()) {
					world->GetCameraController()->RotateY(DTOR(povPosition));
					p_rotatedY = TRUE;
				}
			}

			return SUCCESS;
		}
	}

	return FAILURE;
}

// FUNCTION: LEGO1 0x100558b0
MxResult LegoNavController::ProcessKeyboardInput()
{
	MxBool skipRotationVelAndAccelCalc = FALSE;
	MxBool skipLinearVelAndAccelCalc = FALSE;
	LegoInputManager* inputManager = LegoOmni::GetInstance()->GetInputManager();
	MxU32 keyFlags;

	if (inputManager == NULL || inputManager->GetNavigationKeyStates(keyFlags) == FAILURE) {
		return FAILURE;
	}

	if (keyFlags == 0) {
		if (m_isAccelerating) {
			m_targetRotationalVel = 0.0;
			m_targetLinearVel = 0.0;
			m_rotationalAccel = m_maxRotationalDeccel;
			m_linearAccel = m_maxLinearDeccel;
			m_isAccelerating = FALSE;
		}

		return FAILURE;
	}

	m_isAccelerating = TRUE;

	MxS32 hMax;
	switch (keyFlags & LegoInputManager::c_leftOrRight) {
	case LegoInputManager::c_left:
		hMax = 0;
		break;
	case LegoInputManager::c_right:
		hMax = m_hMax;
		break;
	default:
		m_targetRotationalVel = 0.0;
		m_rotationalAccel = m_maxRotationalDeccel;
		skipRotationVelAndAccelCalc = TRUE;
		break;
	}

	MxS32 vMax;
	switch (keyFlags & LegoInputManager::c_upOrDown) {
	case LegoInputManager::c_up:
		vMax = 0;
		break;
	case LegoInputManager::c_down:
		vMax = m_vMax;
		break;
	default:
		m_targetLinearVel = 0.0;
		m_linearAccel = m_maxLinearDeccel;
		skipLinearVelAndAccelCalc = TRUE;
		break;
	}

	MxFloat maxAccelDivisor = keyFlags & LegoInputManager::c_ctrl ? 1.0f : 4.0f;
	MxFloat minAccelDivisor = keyFlags & LegoInputManager::c_ctrl ? 1.0f : 2.0f;

	if (!skipRotationVelAndAccelCalc) {
		m_targetRotationalVel = CalculateNewTargetVel(hMax, m_hMax / 2, m_maxRotationalVel);
		m_rotationalAccel = CalculateNewAccel(
			hMax,
			m_hMax / 2,
			m_maxRotationalAccel / maxAccelDivisor,
			(int) (m_minRotationalAccel / minAccelDivisor)
		);
	}

	if (!skipLinearVelAndAccelCalc) {
		m_targetLinearVel = CalculateNewTargetVel(m_vMax - vMax, m_vMax / 2, m_maxLinearVel);
		m_linearAccel = CalculateNewAccel(
			m_vMax - vMax,
			m_vMax / 2,
			m_maxLinearAccel / maxAccelDivisor,
			(int) (m_minLinearAccel / minAccelDivisor)
		);
	}

	return SUCCESS;
}

// FUNCTION: LEGO1 0x10055a60
// FUNCTION: BETA10 0x1009c712
MxLong LegoNavController::Notify(MxParam& p_param)
{
	if (((MxNotificationParam&) p_param).GetNotification() == c_notificationKeyPress) {
		m_keyPressed = TRUE;
		SDL_Keycode originKey = ((LegoEventNotificationParam&) p_param).GetKey();
		SDL_Keycode key = originKey;

		// This is necessary so any players using the WASD movement option can
		// also use Debug Mode, which normally makes use of the WASD keys.
		//
		// For those players, we just swap the two and remap
		// those conflicting debug keys to the arrow keys.
		if (LegoOmni::GetInstance()->GetInputManager()->GetWasd()) {
			switch (originKey) {
			case SDLK_W:
				key = SDLK_UP;
				break;
			case SDLK_A:
				key = SDLK_LEFT;
				break;
			case SDLK_S:
				key = SDLK_DOWN;
				break;
			case SDLK_D:
				key = SDLK_RIGHT;
				break;
			case SDLK_UP:
				key = SDLK_W;
				break;
			case SDLK_LEFT:
				key = SDLK_A;
				break;
			case SDLK_DOWN:
				key = SDLK_S;
				break;
			case SDLK_RIGHT:
				key = SDLK_D;
				break;
			default:
				break;
			}
		}

		switch (key) {
		case SDLK_PAUSE: // Pause game
			if (Lego()->IsPaused()) {
				Lego()->Resume();
			}
			else {
				Lego()->Pause();
			}
			break;
		case SDLK_ESCAPE: { // Return to infocenter
			LegoWorld* currentWorld = CurrentWorld();
			if (currentWorld != NULL) {
				InfocenterState* state = (InfocenterState*) GameState()->GetState("InfocenterState");
				assert(state);

				if (state != NULL && state->m_state != InfocenterState::e_exitQueried && currentWorld->Escape()) {
					BackgroundAudioManager()->Stop();
					TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE);
					state->m_state = InfocenterState::e_exitQueried;
				}
			}
			break;
		}
		case SDLK_SPACE: // Interrupt/end animations or free navigation
			AnimationManager()->FUN_10061010(TRUE);
			break;
		case SDLK_Z: { // Make nearby plants "dance"
			LegoOmni* omni = Lego();

			if (omni->GetCurrentWorld() != NULL && omni->GetCurrentWorld()->GetWorldId() == LegoOmni::e_act1) {
				LegoVideoManager* videoMgr = LegoOmni::GetInstance()->GetVideoManager();
				ViewROI* roi = videoMgr->GetViewROI();
				ViewManager* view = videoMgr->Get3DManager()->GetLego3DView()->GetViewManager();
				LegoPlantManager* plantMgr = LegoOmni::GetInstance()->GetPlantManager();
				Mx3DPointFloat viewPosition(roi->GetWorldPosition());
				MxS32 numPlants = plantMgr->GetNumPlants();

				for (MxS32 i = 0; i < numPlants; i++) {
					LegoEntity* entity = plantMgr->CreatePlant(i, NULL, LegoOmni::e_act1);

					if (entity != NULL && !entity->IsInteraction(LegoEntity::c_disabled)) {
						LegoROI* roi = entity->GetROI();

						if (roi != NULL && roi->GetVisibility()) {
							const BoundingBox& box = roi->GetWorldBoundingBox();

							if (view->IsBoundingBoxInFrustum(box)) {
								Mx3DPointFloat roiPosition(roi->GetWorldPosition());
								roiPosition -= viewPosition;

								if (roiPosition.LenSquared() < 2000.0 || roi->GetLodLevel() > 0) {
									entity->ClickAnimation();
								}
							}
						}
					}
				}
			}
			break;
		}
		case SDLK_KP_PLUS:
		case SDLK_KP_MINUS: { // Cycles through characters and puts them in front of you
			if (g_nextCharacter == -1) {
				g_nextCharacter = 0;
			}
			else {
				CharacterManager()->ReleaseActor(CharacterManager()->GetActorName(g_nextCharacter));

				if (key == SDLK_KP_PLUS) {
					g_nextCharacter++;
					if (g_nextCharacter >= CharacterManager()->GetNumActors()) {
						g_nextCharacter = 0;
					}
				}
				else {
					g_nextCharacter--;
					if (g_nextCharacter < 0) {
						g_nextCharacter = CharacterManager()->GetNumActors() - 1;
					}
				}
			}

			LegoROI* roi = CharacterManager()->GetActorROI(CharacterManager()->GetActorName(g_nextCharacter), TRUE);
			if (roi != NULL) {
				MxMatrix mat;
				ViewROI* viewRoi = LegoOmni::GetInstance()->GetVideoManager()->GetViewROI();
				const float* position = viewRoi->GetWorldPosition();
				const float* direction = viewRoi->GetWorldDirection();
				const float* up = viewRoi->GetWorldUp();
				CalcLocalTransform(position, direction, up, mat);
				mat.TranslateBy(direction[0] * 2.0f, direction[1] - 1.0, direction[2] * 2.0f);
				roi->UpdateTransformationRelativeToParent(mat);
			}
			break;
		}
		case SDLK_F12: { // Saves the game
			InfocenterState* state = (InfocenterState*) GameState()->GetState("InfocenterState");
			if (state && state->HasRegistered()) {
				GameState()->Save(0);
			}
			break;
		}
		default:
			// Check if the the key is part of the debug password
			if (!*g_currentInput) {
				// password "protected" debug shortcuts
				switch (key) {
				case SDLK_TAB:
					VideoManager()->ToggleFPS(g_fpsEnabled);
					if (g_fpsEnabled) {
						g_fpsEnabled = FALSE;
					}
					else {
						g_fpsEnabled = TRUE;
					}
				default:
					m_keyPressed = FALSE;
					break;
				case SDLK_0:
				case SDLK_1:
				case SDLK_2:
				case SDLK_3:
				case SDLK_4:
				case SDLK_5:
				case SDLK_6:
				case SDLK_7:
				case SDLK_8:
				case SDLK_9:
					if (g_changeLight && key <= '1') {
						LegoROI* roi = VideoManager()->GetViewROI();
						Tgl::FloatMatrix4 matrix;
						Matrix4 in(matrix);
						roi->GetLocalTransform(in);
						VideoManager()->Get3DManager()->GetLego3DView()->SetLightTransform(key - '0', matrix);
						g_changeLight = FALSE;
					}
					else if (g_locationCalcStep) {
						if (g_locationCalcStep == 1) {
							// Calculate base offset into g_locations
							g_nextLocation = (key - '0') * 10;
							g_locationCalcStep = 2;
						}
						else {
							// Add to base g_locations offset
							g_nextLocation += key - '0';
							g_locationCalcStep = 0;

							LegoPathActor* userActor = UserActor();
							if (userActor != NULL && (MxU32) g_nextLocation < sizeOfArray(g_locations)) {
								LegoWorld* world = CurrentWorld();
								LegoLocation::Boundary* boundary = &g_locations[g_nextLocation].m_boundaryA;
								if (world != NULL && boundary->m_name != NULL) {
									world->PlaceActor(
										userActor,
										boundary->m_name,
										boundary->m_src,
										boundary->m_srcScale,
										boundary->m_dest,
										boundary->m_destScale
									);
								}
								userActor->SetLocation(
									g_locations[g_nextLocation].m_position,
									g_locations[g_nextLocation].m_direction,
									g_locations[g_nextLocation].m_up,
									FALSE
								);
							}
						}
					}
					else if (g_animationCalcStep) {
						if (g_animationCalcStep == 1) {
							// Calculate base offset into possible animation object IDs (up to 999)
							g_nextAnimation = (key - '0') * 100;
							g_animationCalcStep = 2;
						}
						else if (g_animationCalcStep == 2) {
							// Add to animation object ID offset
							g_nextAnimation += (key - '0') * 10;
							g_animationCalcStep = 3;
						}
						else {
							// Add to animation object ID offset
							g_nextAnimation += key - '0';
							g_animationCalcStep = 0;
							AnimationManager()->FUN_10060dc0(
								g_nextAnimation,
								NULL,
								TRUE,
								g_unk0x100f66bc,
								NULL,
								TRUE,
								TRUE,
								TRUE,
								TRUE
							);

							g_unk0x100f66bc = LegoAnimationManager::e_unk2;
						}
					}

					if (g_switchAct && key >= SDLK_1 && key <= SDLK_5) {
						switch (GameState()->GetCurrentAct()) {
						case LegoGameState::e_act1:
							GameState()->m_currentArea = LegoGameState::e_isle;
							break;
						case LegoGameState::e_act2:
							GameState()->m_currentArea = LegoGameState::e_act2main;
							break;
						case LegoGameState::e_act3:
							GameState()->m_currentArea = LegoGameState::e_act3script;
							break;
						}

						switch (key) {
						case SDLK_1:
							GameState()->SetCurrentAct(LegoGameState::e_act1);
							GameState()->SwitchArea(LegoGameState::e_isle);
							break;
						case SDLK_2:
							GameState()->SwitchArea(LegoGameState::e_act2main);
							break;
						case SDLK_3:
							GameState()->SwitchArea(LegoGameState::e_act3script);
							break;
						case SDLK_4: {
							Act3State* act3State = (Act3State*) GameState()->GetState("Act3State");
							if (act3State == NULL) {
								act3State = new Act3State();
								assert(act3State);
								GameState()->RegisterState(act3State);
							}

							GameState()->SetCurrentAct(LegoGameState::e_act3);
							act3State->m_state = Act3State::e_goodEnding;
							GameState()->m_currentArea = LegoGameState::e_act3script;
							GameState()->SwitchArea(LegoGameState::e_infomain);
							break;
						}
						case SDLK_5: {
							Act3State* act3State = (Act3State*) GameState()->GetState("Act3State");
							if (act3State == NULL) {
								act3State = new Act3State();
								assert(act3State);
								GameState()->RegisterState(act3State);
							}

							GameState()->SetCurrentAct(LegoGameState::e_act3);
							act3State->m_state = Act3State::e_badEnding;
							GameState()->m_currentArea = LegoGameState::e_act3script;
							GameState()->SwitchArea(LegoGameState::e_infomain);
							break;
						}
						}

						g_switchAct = FALSE;
					}
					else {
						MxDSAction action;
						action.SetObjectId(key - '0');
						action.SetAtomId(MxAtomId("q:\\lego\\media\\model\\common\\common", e_lowerCase2));
						LegoOmni::GetInstance()->Start(&action);
					}
					break;
				case SDLK_A:
					if (g_animationCalcStep == 1) {
						Lego()->m_initialized = TRUE;
						AnimationManager()->FUN_10060570(TRUE);
						g_animationCalcStep = 0;
					}
					else {
						LegoWorld* world = CurrentWorld();
						if (world != NULL) {
							MxDSAction action;
							action.SetObjectId(1);
							action.SetAtomId(world->GetAtomId());
							LegoOmni::GetInstance()->Start(&action);
						}
					}
					break;
				case SDLK_C:
					g_locationCalcStep = 1;
					break;
				case SDLK_D:
					m_additionalHeightOffset = -1.0;
					break;
				case SDLK_F:
					RealtimeView::SetUserMaxLOD(0.0);
					break;
				case SDLK_G:
					g_switchAct = TRUE;
					break;
				case SDLK_H:
					RealtimeView::SetUserMaxLOD(5.0);
					break;
				case SDLK_I: {
					LegoROI* roi = VideoManager()->GetViewROI();
					MxMatrix mat;
					mat.SetIdentity();
					mat.RotateX(0.2618f);
					roi->WrappedUpdateWorldDataWithTransform(mat);
					break;
				}
				case SDLK_J: {
					LegoROI* roi = VideoManager()->GetViewROI();
					MxMatrix mat;
					mat.SetIdentity();
					mat.RotateZ(0.2618f);
					roi->WrappedUpdateWorldDataWithTransform(mat);
					break;
				}
				case SDLK_K: {
					MxMatrix mat;
					LegoROI* roi = LegoOmni::GetInstance()->GetVideoManager()->GetViewROI();
					mat.SetIdentity();
					mat.RotateZ(-0.2618f);
					roi->WrappedUpdateWorldDataWithTransform(mat);
					break;
				}
				case SDLK_L:
					g_changeLight = TRUE;
					break;
				case SDLK_M: {
					LegoROI* roi = LegoOmni::GetInstance()->GetVideoManager()->GetViewROI();
					MxMatrix mat;
					mat.SetIdentity();
					mat.RotateX(-0.2618f);
					roi->WrappedUpdateWorldDataWithTransform(mat);
					break;
				}
				case SDLK_N:
					if (VideoManager()) {
						VideoManager()->SetRender3D(!VideoManager()->GetRender3D());
					}
					break;
				case SDLK_P:
					if (!g_resetPlants) {
						PlantManager()->LoadWorldInfo(LegoOmni::e_act1);
						g_resetPlants = TRUE;
					}
					else {
						PlantManager()->Reset(LegoOmni::e_act1);
						g_resetPlants = FALSE;
					}
					break;
				case SDLK_S:
					g_enableMusic = g_enableMusic == FALSE;
					BackgroundAudioManager()->Enable(g_enableMusic);
					break;
				case SDLK_U:
					m_additionalHeightOffset = 1.0;
					break;
				case SDLK_V:
					if (g_nextAnimation > 0 && g_animationCalcStep == 0) {
						AnimationManager()->FUN_10061010(FALSE);
					}

					if (g_animationCalcStep != 0) {
						g_unk0x100f66bc = LegoAnimationManager::e_unk2;
					}

					g_nextAnimation = 0;
					g_animationCalcStep = 1;
					break;
				case SDLK_W: {
					MxMatrix mat;
					LegoROI* roi = LegoOmni::GetInstance()->GetVideoManager()->GetViewROI();
					const float* position = roi->GetWorldPosition();
					const float* direction = roi->GetWorldDirection();
					const float* up = roi->GetWorldUp();

					MxTrace(
						"pos: %f, %f, %f\ndir: %f, %f, %f\nup: %f, %f, %f\n",
						EXPAND3(position),
						EXPAND3(direction),
						EXPAND3(up)
					);
					break;
				}
				case SDLK_X:
					RealtimeView::SetUserMaxLOD(3.6);
					break;
				case SDLK_KP_MULTIPLY: {
					MxU8 newActor = GameState()->GetActorId() + 1;

					if (newActor > LegoActor::c_laura) {
						newActor = LegoActor::c_pepper;
					}

					GameState()->SetActorId(newActor);
					break;
				}
				case SDLK_KP_DIVIDE:
					GameState()->SetActorId(LegoActor::c_brickster);
					break;
				case SDLK_F11:
					if (GameState()->m_isDirty) {
						GameState()->m_isDirty = FALSE;
					}
					else {
						GameState()->m_isDirty = TRUE;
					}
					break;
				case SDLK_MINUS:
					g_unk0x100f66bc = LegoAnimationManager::e_unk1;
					break;
				}
			}
			else {
				if (*g_currentInput == ((LegoEventNotificationParam&) p_param).GetKey()) {
					g_currentInput++;
				}
				else {
					g_currentInput = g_debugPassword;
				}
			}
		}
	}

	return 0;
}
