mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-01 18:13:57 +00:00
Separate extensions (#18)
* WIP * WIP * Make camera the single source of truth for broadcast state Remove redundant local copies of walkAnimId, idleAnimId, and displayActorIndex from NetworkManager. BroadcastLocalState now reads these from the camera's Controller, eliminating dual-copy sync issues. Additional cleanup: - Early-return on null cam in SendEmote/HandleCustomize for clarity - Only consume camera-dependent pending requests when cam is available - Move local name bubble creation from BroadcastLocalState to Tickle - Remove dead NetworkManager::SetDisplayActorIndex method Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix clang format --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0ad5361e6a
commit
569c8b467b
@ -531,18 +531,28 @@ if (ISLE_EXTENSIONS)
|
||||
extensions/src/extensions.cpp
|
||||
extensions/src/siloader.cpp
|
||||
extensions/src/textureloader.cpp
|
||||
|
||||
# Common shared code
|
||||
extensions/src/common/animdata.cpp
|
||||
extensions/src/common/animutils.cpp
|
||||
extensions/src/common/characteranimator.cpp
|
||||
extensions/src/common/charactercloner.cpp
|
||||
extensions/src/common/charactercustomizer.cpp
|
||||
extensions/src/common/customizestate.cpp
|
||||
|
||||
# Third person camera extension
|
||||
extensions/src/thirdpersoncamera.cpp
|
||||
extensions/src/thirdpersoncamera/controller.cpp
|
||||
extensions/src/thirdpersoncamera/orbitcamera.cpp
|
||||
extensions/src/thirdpersoncamera/inputhandler.cpp
|
||||
extensions/src/thirdpersoncamera/displayactor.cpp
|
||||
|
||||
# Multiplayer extension
|
||||
extensions/src/multiplayer.cpp
|
||||
extensions/src/multiplayer/animdata.cpp
|
||||
extensions/src/multiplayer/animutils.cpp
|
||||
extensions/src/multiplayer/characteranimator.cpp
|
||||
extensions/src/multiplayer/charactercloner.cpp
|
||||
extensions/src/multiplayer/charactercustomizer.cpp
|
||||
extensions/src/multiplayer/customizestate.cpp
|
||||
extensions/src/multiplayer/namebubblerenderer.cpp
|
||||
extensions/src/multiplayer/networkmanager.cpp
|
||||
extensions/src/multiplayer/protocol.cpp
|
||||
extensions/src/multiplayer/remoteplayer.cpp
|
||||
extensions/src/multiplayer/thirdpersoncamera.cpp
|
||||
extensions/src/multiplayer/worldstatesync.cpp
|
||||
)
|
||||
if(EMSCRIPTEN)
|
||||
|
||||
@ -89,10 +89,10 @@ void Emscripten_SetupFilesystem()
|
||||
}
|
||||
|
||||
#ifdef EXTENSIONS
|
||||
if (Extensions::TextureLoader::enabled) {
|
||||
if (Extensions::TextureLoaderExt::enabled) {
|
||||
MxString directory =
|
||||
MxString("/LEGO") + Extensions::TextureLoader::options["texture loader:texture path"].c_str();
|
||||
Extensions::TextureLoader::options["texture loader:texture path"] = directory.GetData();
|
||||
MxString("/LEGO") + Extensions::TextureLoaderExt::options["texture loader:texture path"].c_str();
|
||||
Extensions::TextureLoaderExt::options["texture loader:texture path"] = directory.GetData();
|
||||
wasmfs_create_directory(directory.GetData(), 0644, fetchfs);
|
||||
|
||||
MxU32 i = 0;
|
||||
@ -102,17 +102,17 @@ void Emscripten_SetupFilesystem()
|
||||
registerFile(path.GetData());
|
||||
|
||||
if (!preloadFile(path.GetData())) {
|
||||
Extensions::TextureLoader::excludedFiles.emplace_back(file);
|
||||
Extensions::TextureLoaderExt::AddExcludedFile(file);
|
||||
}
|
||||
|
||||
Emscripten_SendExtensionProgress("HD Textures", (++i * 100) / sizeOfArray(g_textures));
|
||||
}
|
||||
}
|
||||
|
||||
if (Extensions::SiLoader::enabled) {
|
||||
if (Extensions::SiLoaderExt::enabled) {
|
||||
wasmfs_create_directory("/LEGO/extra", 0644, fetchfs);
|
||||
|
||||
for (const auto& file : Extensions::SiLoader::files) {
|
||||
for (const auto& file : Extensions::SiLoaderExt::GetFiles()) {
|
||||
registerFile(file.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,6 +38,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <extensions/multiplayer.h>
|
||||
#include <extensions/thirdpersoncamera.h>
|
||||
#include <miniwin/miniwindevice.h>
|
||||
#include <type_traits>
|
||||
#include <vec.h>
|
||||
@ -878,7 +879,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event)
|
||||
}
|
||||
|
||||
#ifdef EXTENSIONS
|
||||
Extensions::HandleMultiplayerSDLEvent(event);
|
||||
Extensions::ThirdPersonCameraExt::HandleSDLEvent(event);
|
||||
#endif
|
||||
|
||||
return SDL_APP_CONTINUE;
|
||||
|
||||
@ -13,10 +13,13 @@ class LegoActor;
|
||||
class LegoExtraActor;
|
||||
class LegoStorage;
|
||||
class LegoROI;
|
||||
namespace Multiplayer
|
||||
namespace Extensions
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
class CharacterCloner;
|
||||
}
|
||||
} // namespace Extensions
|
||||
|
||||
#pragma warning(disable : 4237)
|
||||
|
||||
@ -102,7 +105,7 @@ class LegoCharacterManager {
|
||||
static const char* GetCustomizeAnimFile() { return g_customizeAnimFile; }
|
||||
|
||||
private:
|
||||
friend class Multiplayer::CharacterCloner;
|
||||
friend class Extensions::Common::CharacterCloner;
|
||||
|
||||
LegoROI* CreateActorROI(const char* p_key);
|
||||
void RemoveROI(LegoROI* p_roi);
|
||||
|
||||
@ -7,10 +7,13 @@
|
||||
|
||||
struct LegoLocation;
|
||||
class Vector3;
|
||||
namespace Multiplayer
|
||||
namespace Extensions
|
||||
{
|
||||
class ThirdPersonCamera;
|
||||
namespace ThirdPersonCamera
|
||||
{
|
||||
class OrbitCamera;
|
||||
}
|
||||
} // namespace Extensions
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
@ -126,7 +129,7 @@ class LegoNavController : public MxCore {
|
||||
// LegoNavController::`scalar deleting destructor'
|
||||
|
||||
protected:
|
||||
friend class Multiplayer::ThirdPersonCamera;
|
||||
friend class Extensions::ThirdPersonCamera::OrbitCamera;
|
||||
|
||||
float CalculateNewVel(float p_targetVel, float p_currentVel, float p_accel, float p_time);
|
||||
float CalculateNewTargetVel(int p_pos, int p_center, float p_max);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#include "islepathactor.h"
|
||||
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "extensions/multiplayer.h"
|
||||
#include "extensions/thirdpersoncamera.h"
|
||||
#include "isle_actions.h"
|
||||
#include "jukebox_actions.h"
|
||||
#include "legoanimationmanager.h"
|
||||
@ -99,7 +99,7 @@ void IslePathActor::Enter()
|
||||
TransformPointOfView();
|
||||
}
|
||||
|
||||
Extension<MultiplayerExt>::Call(HandleActorEnter, this);
|
||||
Extension<ThirdPersonCameraExt>::Call(TP::HandleActorEnter, this);
|
||||
}
|
||||
|
||||
// FUNCTION: LEGO1 0x1001a3f0
|
||||
@ -160,7 +160,7 @@ void IslePathActor::Exit()
|
||||
TransformPointOfView();
|
||||
ResetViewVelocity();
|
||||
|
||||
Extension<MultiplayerExt>::Call(HandleActorExit, this);
|
||||
Extension<ThirdPersonCameraExt>::Call(TP::HandleActorExit, this);
|
||||
}
|
||||
|
||||
// GLOBAL: LEGO1 0x10102b28
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "decomp.h"
|
||||
#include "define.h"
|
||||
#include "extensions/multiplayer.h"
|
||||
#include "extensions/thirdpersoncamera.h"
|
||||
#include "islepathactor.h"
|
||||
#include "legoanimationmanager.h"
|
||||
#include "legoanimpresenter.h"
|
||||
@ -485,7 +485,7 @@ MxBool LegoAnimMMPresenter::FUN_1004b6d0(MxLong p_time)
|
||||
actor->SetActorState(LegoPathActor::c_initial);
|
||||
|
||||
if (m_tranInfo->m_unk0x29) {
|
||||
Extension<MultiplayerExt>::Call(HandleCamAnimEnd, actor);
|
||||
Extension<ThirdPersonCameraExt>::Call(TP::HandleCamAnimEnd, actor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "extensions/multiplayer.h"
|
||||
#include "extensions/thirdpersoncamera.h"
|
||||
#include "legoactors.h"
|
||||
#include "legoanimactor.h"
|
||||
#include "legobuildingmanager.h"
|
||||
@ -283,7 +284,8 @@ LegoROI* LegoCharacterManager::GetActorROI(const char* p_name, MxBool p_createEn
|
||||
|
||||
if (character != NULL) {
|
||||
if (p_createEntity && character->m_roi->GetEntity() == NULL &&
|
||||
!Extension<MultiplayerExt>::Call(IsClonedCharacter, p_name).value_or(FALSE)) {
|
||||
!Extension<ThirdPersonCameraExt>::Call(TP::IsClonedCharacter, p_name).value_or(FALSE) &&
|
||||
!Extension<MultiplayerExt>::Call(MP::IsClonedCharacter, p_name).value_or(FALSE)) {
|
||||
LegoExtraActor* actor = new LegoExtraActor();
|
||||
|
||||
actor->SetROI(character->m_roi, FALSE, FALSE);
|
||||
|
||||
@ -367,7 +367,7 @@ MxResult LegoGameState::DeleteState()
|
||||
// FUNCTION: BETA10 0x10084329
|
||||
MxResult LegoGameState::Load(MxULong p_slot)
|
||||
{
|
||||
Extension<MultiplayerExt>::Call(HandleBeforeSaveLoad);
|
||||
Extension<MultiplayerExt>::Call(MP::HandleBeforeSaveLoad);
|
||||
|
||||
MxResult result = FAILURE;
|
||||
LegoFile storage;
|
||||
@ -461,7 +461,7 @@ MxResult LegoGameState::Load(MxULong p_slot)
|
||||
result = SUCCESS;
|
||||
m_isDirty = FALSE;
|
||||
|
||||
Extension<MultiplayerExt>::Call(HandleSaveLoaded);
|
||||
Extension<MultiplayerExt>::Call(MP::HandleSaveLoaded);
|
||||
|
||||
done:
|
||||
if (result != SUCCESS) {
|
||||
|
||||
@ -59,7 +59,7 @@ LegoTextureInfo* LegoTextureInfo::Create(const char* p_name, LegoTexture* p_text
|
||||
strcpy(textureInfo->m_name, p_name);
|
||||
}
|
||||
|
||||
if (Extension<TextureLoader>::Call(PatchTexture, textureInfo).value_or(false)) {
|
||||
if (Extension<TextureLoaderExt>::Call(TL::PatchTexture, textureInfo).value_or(false)) {
|
||||
return textureInfo;
|
||||
}
|
||||
|
||||
|
||||
@ -503,8 +503,8 @@ MxBool RemoveFromCurrentWorld(const MxAtomId& p_atomId, MxS32 p_id)
|
||||
{
|
||||
LegoWorld* world = CurrentWorld();
|
||||
|
||||
auto result =
|
||||
Extension<SiLoader>::Call(HandleRemove, SiLoader::StreamObject{p_atomId, p_id}, world).value_or(std::nullopt);
|
||||
auto result = Extension<SiLoaderExt>::Call(SI::HandleRemove, SiLoaderExt::StreamObject{p_atomId, p_id}, world)
|
||||
.value_or(std::nullopt);
|
||||
if (result) {
|
||||
return result.value();
|
||||
}
|
||||
@ -543,8 +543,9 @@ MxBool RemoveFromWorld(
|
||||
{
|
||||
LegoWorld* world = FindWorld(p_worldAtom, p_worldEntityId);
|
||||
|
||||
auto result = Extension<SiLoader>::Call(HandleRemove, SiLoader::StreamObject{p_entityAtom, p_entityId}, world)
|
||||
.value_or(std::nullopt);
|
||||
auto result =
|
||||
Extension<SiLoaderExt>::Call(SI::HandleRemove, SiLoaderExt::StreamObject{p_entityAtom, p_entityId}, world)
|
||||
.value_or(std::nullopt);
|
||||
if (result) {
|
||||
return result.value();
|
||||
}
|
||||
|
||||
@ -486,7 +486,7 @@ MxLong LegoEntity::Notify(MxParam& p_param)
|
||||
InvokeAction(m_actionType, MxAtomId(m_siFile, e_lowerCase2), m_targetEntityId, this);
|
||||
}
|
||||
else {
|
||||
if (Extension<MultiplayerExt>::Call(HandleEntityNotify, this).value_or(FALSE)) {
|
||||
if (Extension<MultiplayerExt>::Call(MP::HandleEntityNotify, this).value_or(FALSE)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "act3.h"
|
||||
#include "extensions/multiplayer.h"
|
||||
#include "extensions/thirdpersoncamera.h"
|
||||
#include "infocenter.h"
|
||||
#include "legoanimationmanager.h"
|
||||
#include "legocameracontroller.h"
|
||||
@ -351,7 +351,8 @@ MxBool LegoNavController::CalculateNewPosDir(
|
||||
ProcessJoystickInput(rotatedY);
|
||||
}
|
||||
|
||||
if (Extension<MultiplayerExt>::Call(HandleNavOverride, this, p_curPos, p_curDir, p_newPos, p_newDir, deltaTime)
|
||||
if (Extension<
|
||||
ThirdPersonCameraExt>::Call(TP::HandleNavOverride, this, p_curPos, p_curDir, p_newPos, p_newDir, deltaTime)
|
||||
.value_or(FALSE)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include "anim/legoanim.h"
|
||||
#include "extensions/multiplayer.h"
|
||||
#include "extensions/siloader.h"
|
||||
#include "extensions/thirdpersoncamera.h"
|
||||
#include "legoanimationmanager.h"
|
||||
#include "legoanimpresenter.h"
|
||||
#include "legobuildingmanager.h"
|
||||
@ -640,8 +641,8 @@ MxCore* LegoWorld::Find(const char* p_class, const char* p_name)
|
||||
// FUNCTION: BETA10 0x100db3de
|
||||
MxCore* LegoWorld::Find(const MxAtomId& p_atom, MxS32 p_entityId)
|
||||
{
|
||||
auto result =
|
||||
Extension<SiLoader>::Call(HandleFind, SiLoader::StreamObject{p_atom, p_entityId}, this).value_or(std::nullopt);
|
||||
auto result = Extension<SiLoaderExt>::Call(SI::HandleFind, SiLoaderExt::StreamObject{p_atom, p_entityId}, this)
|
||||
.value_or(std::nullopt);
|
||||
if (result) {
|
||||
return result.value();
|
||||
}
|
||||
@ -755,7 +756,8 @@ void LegoWorld::Enable(MxBool p_enable)
|
||||
SetIsWorldActive(TRUE);
|
||||
#endif
|
||||
|
||||
Extension<MultiplayerExt>::Call(HandleWorldEnable, this, TRUE);
|
||||
Extension<ThirdPersonCameraExt>::Call(TP::HandleWorldEnable, this, TRUE);
|
||||
Extension<MultiplayerExt>::Call(MP::HandleWorldEnable, this, TRUE);
|
||||
}
|
||||
else if (!p_enable && m_disabledObjects.size() == 0) {
|
||||
MxPresenter* presenter;
|
||||
@ -819,7 +821,8 @@ void LegoWorld::Enable(MxBool p_enable)
|
||||
|
||||
GetViewManager()->RemoveAll(NULL);
|
||||
|
||||
Extension<MultiplayerExt>::Call(HandleWorldEnable, this, FALSE);
|
||||
Extension<ThirdPersonCameraExt>::Call(TP::HandleWorldEnable, this, FALSE);
|
||||
Extension<MultiplayerExt>::Call(MP::HandleWorldEnable, this, FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include "legoinputmanager.h"
|
||||
|
||||
#include "extensions/multiplayer.h"
|
||||
#include "extensions/thirdpersoncamera.h"
|
||||
#include "legocameracontroller.h"
|
||||
#include "legocontrolmanager.h"
|
||||
#include "legomain.h"
|
||||
@ -325,7 +326,7 @@ MxBool LegoInputManager::ProcessOneEvent(LegoEventNotificationParam& p_param)
|
||||
if (!Lego()->IsPaused()) {
|
||||
if ((p_param.GetModifier() & LegoEventNotificationParam::c_rButtonState) &&
|
||||
!(p_param.GetModifier() & LegoEventNotificationParam::c_lButtonState) &&
|
||||
Extension<MultiplayerExt>::Call(IsThirdPersonCameraActive).value_or(FALSE)) {
|
||||
Extension<ThirdPersonCameraExt>::Call(TP::IsThirdPersonCameraActive).value_or(FALSE)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@ -402,7 +403,8 @@ MxBool LegoInputManager::ProcessOneEvent(LegoEventNotificationParam& p_param)
|
||||
if (entity && entity->Notify(p_param) != 0) {
|
||||
return TRUE;
|
||||
}
|
||||
if (Extension<MultiplayerExt>::Call(HandleROIClick, roi, p_param).value_or(FALSE)) {
|
||||
if (Extension<MultiplayerExt>::Call(MP::HandleROIClick, roi, p_param).value_or(FALSE) ||
|
||||
Extension<ThirdPersonCameraExt>::Call(TP::HandleROIClick, roi, p_param).value_or(FALSE)) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
@ -638,7 +640,7 @@ void LegoInputManager::RemoveJoystick(SDL_JoystickID p_joystickID)
|
||||
|
||||
MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme)
|
||||
{
|
||||
if (Extension<MultiplayerExt>::Call(HandleTouchInput, p_event).value_or(FALSE)) {
|
||||
if (Extension<ThirdPersonCameraExt>::Call(TP::HandleTouchInput, p_event).value_or(FALSE)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "extensions/multiplayer.h"
|
||||
#include "extensions/siloader.h"
|
||||
#include "extensions/thirdpersoncamera.h"
|
||||
#include "islepathactor.h"
|
||||
#include "legoanimationmanager.h"
|
||||
#include "legobuildingmanager.h"
|
||||
@ -356,7 +357,8 @@ MxResult LegoOmni::Create(MxOmniCreateParam& p_param)
|
||||
m_gameState->SetCurrentAct(LegoGameState::e_act1);
|
||||
#endif
|
||||
|
||||
Extension<MultiplayerExt>::Call(HandleCreate);
|
||||
Extension<ThirdPersonCameraExt>::Call(TP::HandleCreate);
|
||||
Extension<MultiplayerExt>::Call(MP::HandleCreate);
|
||||
result = SUCCESS;
|
||||
}
|
||||
else {
|
||||
@ -416,7 +418,7 @@ void LegoOmni::AddWorld(LegoWorld* p_world)
|
||||
{
|
||||
m_worldList->Append(p_world);
|
||||
|
||||
Extension<SiLoader>::Call(HandleWorld, p_world);
|
||||
Extension<SiLoaderExt>::Call(SI::HandleWorld, p_world);
|
||||
}
|
||||
|
||||
// FUNCTION: LEGO1 0x1005adb0
|
||||
@ -484,7 +486,7 @@ LegoWorld* LegoOmni::FindWorld(const MxAtomId& p_atom, MxS32 p_entityid)
|
||||
// STUB: BETA10 0x1008e93e
|
||||
void LegoOmni::DeleteObject(MxDSAction& p_dsAction)
|
||||
{
|
||||
auto result = Extension<SiLoader>::Call(HandleDelete, p_dsAction).value_or(std::nullopt);
|
||||
auto result = Extension<SiLoaderExt>::Call(SI::HandleDelete, p_dsAction).value_or(std::nullopt);
|
||||
if (result && result.value()) {
|
||||
return;
|
||||
}
|
||||
@ -679,7 +681,7 @@ void LegoOmni::CreateBackgroundAudio()
|
||||
MxResult LegoOmni::Start(MxDSAction* p_dsAction)
|
||||
{
|
||||
{
|
||||
auto result = Extension<SiLoader>::Call(HandleStart, *p_dsAction).value_or(std::nullopt);
|
||||
auto result = Extension<SiLoaderExt>::Call(SI::HandleStart, *p_dsAction).value_or(std::nullopt);
|
||||
if (result) {
|
||||
return result.value();
|
||||
}
|
||||
@ -742,5 +744,5 @@ void LegoOmni::Resume()
|
||||
|
||||
void LegoOmni::LoadSiLoader()
|
||||
{
|
||||
Extension<SiLoader>::Call(Load);
|
||||
Extension<SiLoaderExt>::Call(SI::Load);
|
||||
}
|
||||
|
||||
@ -342,8 +342,9 @@ MxLong Infocenter::HandleEndAction(MxEndActionNotificationParam& p_param)
|
||||
|
||||
MxLong result = m_radio.Notify(p_param);
|
||||
|
||||
if (result || (action->GetAtomId() != m_atomId && action->GetAtomId() != *g_introScript &&
|
||||
!Extension<SiLoader>::Call(ReplacedIn, *action, m_atomId, *g_introScript).value_or(std::nullopt))) {
|
||||
if (result ||
|
||||
(action->GetAtomId() != m_atomId && action->GetAtomId() != *g_introScript &&
|
||||
!Extension<SiLoaderExt>::Call(SI::ReplacedIn, *action, m_atomId, *g_introScript).value_or(std::nullopt))) {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@ -217,7 +217,7 @@ MxLong Isle::HandleEndAction(MxEndActionNotificationParam& p_param)
|
||||
result = 1;
|
||||
}
|
||||
}
|
||||
else if (auto replacedObject = Extension<SiLoader>::Call(ReplacedIn, *p_param.GetAction(), *g_jukeboxScript).value_or(std::nullopt)) {
|
||||
else if (auto replacedObject = Extension<SiLoaderExt>::Call(SI::ReplacedIn, *p_param.GetAction(), *g_jukeboxScript).value_or(std::nullopt)) {
|
||||
MxS32 script = replacedObject->second;
|
||||
|
||||
if (script >= JukeboxScript::c_JBMusic1 && script <= JukeboxScript::c_JBMusic6) {
|
||||
@ -297,7 +297,8 @@ void Isle::ReadyWorld()
|
||||
MxLong Isle::HandleControl(LegoControlManagerNotificationParam& p_param)
|
||||
{
|
||||
if (p_param.m_enabledChild == 1) {
|
||||
if (Extension<MultiplayerExt>::Call(HandleSkyLightControl, (MxU32) p_param.m_clickedObjectId).value_or(FALSE)) {
|
||||
if (Extension<MultiplayerExt>::Call(MP::HandleSkyLightControl, (MxU32) p_param.m_clickedObjectId)
|
||||
.value_or(FALSE)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@ -110,7 +110,7 @@ MxResult MxNotificationManager::Tickle()
|
||||
m_sendList->pop_front();
|
||||
|
||||
if (notif->GetParam()->GetNotification() == c_notificationEndAction) {
|
||||
Extension<SiLoader>::Call(HandleEndAction, (MxEndActionNotificationParam&) *notif->GetParam());
|
||||
Extension<SiLoaderExt>::Call(SI::HandleEndAction, (MxEndActionNotificationParam&) *notif->GetParam());
|
||||
}
|
||||
|
||||
notif->GetTarget()->Notify(*notif->GetParam());
|
||||
@ -169,7 +169,7 @@ void MxNotificationManager::FlushPending(MxCore* p_listener)
|
||||
pending.pop_front();
|
||||
|
||||
if (notif->GetParam()->GetNotification() == c_notificationEndAction) {
|
||||
Extension<SiLoader>::Call(HandleEndAction, (MxEndActionNotificationParam&) *notif->GetParam());
|
||||
Extension<SiLoaderExt>::Call(SI::HandleEndAction, (MxEndActionNotificationParam&) *notif->GetParam());
|
||||
}
|
||||
|
||||
notif->GetTarget()->Notify(*notif->GetParam());
|
||||
|
||||
@ -1,12 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "extensions/multiplayer/protocol.h"
|
||||
#include "extensions/common/constants.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class LegoPathActor;
|
||||
|
||||
namespace Multiplayer
|
||||
namespace Extensions
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
|
||||
// Animation and vehicle tables (defined in animdata.cpp)
|
||||
@ -46,4 +48,5 @@ bool IsLargeVehicle(int8_t p_vehicleType);
|
||||
// Detect the vehicle type of a given actor, or VEHICLE_NONE if not a vehicle
|
||||
int8_t DetectVehicleType(LegoPathActor* p_actor);
|
||||
|
||||
} // namespace Multiplayer
|
||||
} // namespace Common
|
||||
} // namespace Extensions
|
||||
@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "mxtypes.h"
|
||||
#include "mxgeometry/mxmatrix.h"
|
||||
#include "realtime/vector.h"
|
||||
#include "roi/legoroi.h"
|
||||
|
||||
#include <map>
|
||||
@ -8,7 +10,9 @@
|
||||
|
||||
class LegoAnim;
|
||||
|
||||
namespace Multiplayer
|
||||
namespace Extensions
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
|
||||
namespace AnimUtils
|
||||
@ -77,6 +81,17 @@ inline void EnsureROIMapVisibility(LegoROI** p_roiMap, MxU32 p_roiMapSize)
|
||||
}
|
||||
}
|
||||
|
||||
// Flip a matrix from forward-z to backward-z (or vice versa) in place.
|
||||
inline void FlipMatrixDirection(MxMatrix& p_mat)
|
||||
{
|
||||
Vector3 right(p_mat[0]);
|
||||
Vector3 up(p_mat[1]);
|
||||
Vector3 direction(p_mat[2]);
|
||||
direction *= -1.0f;
|
||||
right.EqualsCross(up, direction);
|
||||
}
|
||||
|
||||
} // namespace AnimUtils
|
||||
|
||||
} // namespace Multiplayer
|
||||
} // namespace Common
|
||||
} // namespace Extensions
|
||||
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "extensions/multiplayer/animdata.h"
|
||||
#include "extensions/multiplayer/animutils.h"
|
||||
#include "extensions/common/animdata.h"
|
||||
#include "extensions/common/animutils.h"
|
||||
#include "mxgeometry/mxmatrix.h"
|
||||
#include "mxtypes.h"
|
||||
|
||||
@ -14,21 +14,21 @@ class LegoCacheSound;
|
||||
class LegoROI;
|
||||
class LegoAnim;
|
||||
|
||||
namespace Multiplayer
|
||||
namespace Extensions
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
|
||||
class NameBubbleRenderer;
|
||||
|
||||
// Configuration for CharacterAnimator behavior that differs between consumers.
|
||||
struct CharacterAnimatorConfig {
|
||||
// When true, save/restore the parent ROI transform during emote playback
|
||||
// to prevent scale accumulation (needed for ThirdPersonCamera's display clone).
|
||||
// to prevent scale accumulation (needed for ThirdPersonCameraExt's display clone).
|
||||
bool saveEmoteTransform;
|
||||
};
|
||||
|
||||
// Unified character animation component used by both RemotePlayer and ThirdPersonCamera.
|
||||
// Handles walk/idle/emote animation playback, vehicle ride animations, click animation
|
||||
// tracking, and name bubble management.
|
||||
// Unified character animation component used by both RemotePlayer and ThirdPersonCameraExt.
|
||||
// Handles walk/idle/emote animation playback, vehicle ride animations, and click animation
|
||||
// tracking.
|
||||
class CharacterAnimator {
|
||||
public:
|
||||
explicit CharacterAnimator(const CharacterAnimatorConfig& p_config);
|
||||
@ -72,13 +72,6 @@ class CharacterAnimator {
|
||||
void ClearAll();
|
||||
void ApplyIdleFrame0(LegoROI* p_roi);
|
||||
|
||||
// Name bubble management
|
||||
void CreateNameBubble(const char* p_name);
|
||||
void DestroyNameBubble();
|
||||
void SetNameBubbleVisible(bool p_visible);
|
||||
void UpdateNameBubble(LegoROI* p_roi);
|
||||
NameBubbleRenderer* GetNameBubble() const { return m_nameBubble; }
|
||||
|
||||
// Emote state accessors
|
||||
bool IsEmoteActive() const { return m_emoteActive; }
|
||||
|
||||
@ -91,7 +84,7 @@ class CharacterAnimator {
|
||||
int8_t GetFrozenEmoteId() const { return m_frozenEmoteId; }
|
||||
void SetFrozenEmoteId(int8_t p_emoteId, LegoROI* p_roi);
|
||||
|
||||
// Animation time (needed for vehicle ride tick in ThirdPersonCamera)
|
||||
// Animation time (needed for vehicle ride tick in ThirdPersonCameraExt)
|
||||
float GetAnimTime() const { return m_animTime; }
|
||||
void SetAnimTime(float p_time) { m_animTime = p_time; }
|
||||
void ResetAnimState();
|
||||
@ -146,8 +139,7 @@ class CharacterAnimator {
|
||||
LegoROI* m_rideVehicleROI;
|
||||
|
||||
int8_t m_currentVehicleType;
|
||||
|
||||
NameBubbleRenderer* m_nameBubble;
|
||||
};
|
||||
|
||||
} // namespace Multiplayer
|
||||
} // namespace Common
|
||||
} // namespace Extensions
|
||||
@ -1,12 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "extensions/common/constants.h"
|
||||
#include "legoactors.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <cstdint>
|
||||
|
||||
class LegoCharacterManager;
|
||||
class LegoROI;
|
||||
|
||||
namespace Multiplayer
|
||||
namespace Extensions
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
|
||||
inline bool IsValidDisplayActorIndex(uint8_t p_index)
|
||||
@ -14,6 +20,16 @@ inline bool IsValidDisplayActorIndex(uint8_t p_index)
|
||||
return p_index < sizeOfArray(g_actorInfoInit);
|
||||
}
|
||||
|
||||
inline uint8_t ResolveDisplayActorIndex(const char* p_name)
|
||||
{
|
||||
for (int i = 0; i < static_cast<int>(sizeOfArray(g_actorInfoInit)); i++) {
|
||||
if (!SDL_strcasecmp(g_actorInfoInit[i].m_name, p_name)) {
|
||||
return static_cast<uint8_t>(i);
|
||||
}
|
||||
}
|
||||
return DISPLAY_ACTOR_NONE;
|
||||
}
|
||||
|
||||
class CharacterCloner {
|
||||
public:
|
||||
// Creates an independent multi-part character ROI clone.
|
||||
@ -22,4 +38,5 @@ class CharacterCloner {
|
||||
static LegoROI* Clone(LegoCharacterManager* p_charMgr, const char* p_uniqueName, const char* p_characterType);
|
||||
};
|
||||
|
||||
} // namespace Multiplayer
|
||||
} // namespace Common
|
||||
} // namespace Extensions
|
||||
@ -6,7 +6,9 @@
|
||||
|
||||
class LegoROI;
|
||||
|
||||
namespace Multiplayer
|
||||
namespace Extensions
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
|
||||
struct CustomizeState;
|
||||
@ -31,10 +33,16 @@ class CharacterCustomizer {
|
||||
static MxU32 PlayClickAnimation(LegoROI* p_roi, const CustomizeState& p_state);
|
||||
static void StopClickAnimation(MxU32 p_objectId);
|
||||
|
||||
// Resolves the current actor's click to a change type and optional part index.
|
||||
// Returns false if the click should be consumed with no effect (Pepper in act2/3, Brickster)
|
||||
// or if the actor is unknown.
|
||||
static bool ResolveClickChangeType(uint8_t& p_changeType, int& p_partIndex, LegoROI* p_clickedROI);
|
||||
|
||||
private:
|
||||
static LegoROI* FindChildROI(LegoROI* p_rootROI, const char* p_name);
|
||||
static void ApplyHatVariant(LegoROI* p_rootROI, uint8_t p_actorInfoIndex,
|
||||
const CustomizeState& p_state);
|
||||
};
|
||||
|
||||
} // namespace Multiplayer
|
||||
} // namespace Common
|
||||
} // namespace Extensions
|
||||
42
extensions/include/extensions/common/constants.h
Normal file
42
extensions/include/extensions/common/constants.h
Normal file
@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Extensions
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
|
||||
enum VehicleType : int8_t {
|
||||
VEHICLE_NONE = -1,
|
||||
VEHICLE_HELICOPTER = 0,
|
||||
VEHICLE_JETSKI = 1,
|
||||
VEHICLE_DUNEBUGGY = 2,
|
||||
VEHICLE_BIKE = 3,
|
||||
VEHICLE_SKATEBOARD = 4,
|
||||
VEHICLE_MOTOCYCLE = 5,
|
||||
VEHICLE_TOWTRACK = 6,
|
||||
VEHICLE_AMBULANCE = 7,
|
||||
VEHICLE_COUNT = 8
|
||||
};
|
||||
|
||||
// Change types for world events (maps to Switch* methods on LegoEntity)
|
||||
enum WorldChangeType : uint8_t {
|
||||
CHANGE_VARIANT = 0,
|
||||
CHANGE_SOUND = 1,
|
||||
CHANGE_MOVE = 2,
|
||||
CHANGE_COLOR = 3,
|
||||
CHANGE_MOOD = 4,
|
||||
CHANGE_DECREMENT = 5
|
||||
};
|
||||
|
||||
static const uint8_t DISPLAY_ACTOR_NONE = 0xFF;
|
||||
|
||||
// Validate actorId is a playable character (1-5, not brickster)
|
||||
inline bool IsValidActorId(uint8_t p_actorId)
|
||||
{
|
||||
return p_actorId >= 1 && p_actorId <= 5;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
} // namespace Extensions
|
||||
@ -2,7 +2,9 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Multiplayer
|
||||
namespace Extensions
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
|
||||
struct CustomizeState {
|
||||
@ -19,4 +21,5 @@ struct CustomizeState {
|
||||
bool operator!=(const CustomizeState& p_other) const { return !(*this == p_other); }
|
||||
};
|
||||
|
||||
} // namespace Multiplayer
|
||||
} // namespace Common
|
||||
} // namespace Extensions
|
||||
@ -11,7 +11,7 @@
|
||||
namespace Extensions
|
||||
{
|
||||
constexpr const char* availableExtensions[] =
|
||||
{"extensions:texture loader", "extensions:si loader", "extensions:multiplayer"};
|
||||
{"extensions:texture loader", "extensions:si loader", "extensions:third person camera", "extensions:multiplayer"};
|
||||
|
||||
LEGO1_EXPORT void Enable(const char* p_key, std::map<std::string, std::string> p_options);
|
||||
|
||||
|
||||
@ -3,18 +3,13 @@
|
||||
#include "extensions/extensions.h"
|
||||
#include "mxtypes.h"
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class IslePathActor;
|
||||
class LegoEntity;
|
||||
class LegoEventNotificationParam;
|
||||
class LegoNavController;
|
||||
class LegoPathActor;
|
||||
class LegoROI;
|
||||
class LegoWorld;
|
||||
class Vector3;
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
@ -41,55 +36,22 @@ class MultiplayerExt {
|
||||
static MxBool HandleSkyLightControl(MxU32 p_controlId);
|
||||
|
||||
// Handles clicks on entity-less ROIs (remote players, display actor overrides).
|
||||
// When multiplayer is enabled, all customization goes through the network.
|
||||
static MxBool HandleROIClick(LegoROI* p_rootROI, LegoEventNotificationParam& p_param);
|
||||
|
||||
static std::map<std::string, std::string> options;
|
||||
static bool enabled;
|
||||
|
||||
static std::string relayUrl;
|
||||
static std::string room;
|
||||
|
||||
static void HandleActorEnter(IslePathActor* p_actor);
|
||||
static void HandleActorExit(IslePathActor* p_actor);
|
||||
static void HandleCamAnimEnd(LegoPathActor* p_actor);
|
||||
|
||||
// Returns TRUE if the name belongs to a multiplayer clone (entity-less ROI).
|
||||
static MxBool IsClonedCharacter(const char* p_name);
|
||||
|
||||
// Called before a save file is loaded. Captures current sky/light state.
|
||||
static void HandleBeforeSaveLoad();
|
||||
|
||||
// Called after a save file is loaded. Re-syncs world state with multiplayer peers.
|
||||
static void HandleSaveLoaded();
|
||||
|
||||
// Returns true if the multiplayer connection was rejected (e.g. room full).
|
||||
static MxBool CheckRejected();
|
||||
|
||||
// Forwards SDL events to the third-person camera for orbit controls.
|
||||
static void HandleSDLEvent(SDL_Event* p_event);
|
||||
|
||||
// Returns TRUE when the third-person camera is active.
|
||||
static MxBool IsThirdPersonCameraActive();
|
||||
|
||||
// Routes touch events by screen zone: right half → camera, left half → movement.
|
||||
// Returns TRUE if the event was consumed by the camera (caller should skip movement).
|
||||
static MxBool HandleTouchInput(SDL_Event* p_event);
|
||||
|
||||
// Overrides nav controller movement for camera-relative 3rd person controls.
|
||||
// Returns TRUE if the hook handled movement (caller should return early).
|
||||
static MxBool HandleNavOverride(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime
|
||||
);
|
||||
|
||||
static void SetNetworkManager(Multiplayer::NetworkManager* p_networkManager);
|
||||
static Multiplayer::NetworkManager* GetNetworkManager();
|
||||
|
||||
private:
|
||||
static std::string s_relayUrl;
|
||||
static std::string s_room;
|
||||
static Multiplayer::NetworkManager* s_networkManager;
|
||||
static Multiplayer::NetworkTransport* s_transport;
|
||||
static Multiplayer::PlatformCallbacks* s_callbacks;
|
||||
@ -97,41 +59,31 @@ class MultiplayerExt {
|
||||
|
||||
#ifdef EXTENSIONS
|
||||
LEGO1_EXPORT bool IsMultiplayerRejected();
|
||||
LEGO1_EXPORT void HandleMultiplayerSDLEvent(SDL_Event* p_event);
|
||||
#endif
|
||||
|
||||
namespace MP
|
||||
{
|
||||
#ifdef EXTENSIONS
|
||||
constexpr auto HandleCreate = &MultiplayerExt::HandleCreate;
|
||||
constexpr auto HandleWorldEnable = &MultiplayerExt::HandleWorldEnable;
|
||||
constexpr auto HandleEntityNotify = &MultiplayerExt::HandleEntityNotify;
|
||||
constexpr auto HandleSkyLightControl = &MultiplayerExt::HandleSkyLightControl;
|
||||
constexpr auto HandleROIClick = &MultiplayerExt::HandleROIClick;
|
||||
constexpr auto HandleActorEnter = &MultiplayerExt::HandleActorEnter;
|
||||
constexpr auto HandleActorExit = &MultiplayerExt::HandleActorExit;
|
||||
constexpr auto HandleCamAnimEnd = &MultiplayerExt::HandleCamAnimEnd;
|
||||
constexpr auto IsClonedCharacter = &MultiplayerExt::IsClonedCharacter;
|
||||
constexpr auto HandleBeforeSaveLoad = &MultiplayerExt::HandleBeforeSaveLoad;
|
||||
constexpr auto HandleSaveLoaded = &MultiplayerExt::HandleSaveLoaded;
|
||||
constexpr auto CheckRejected = &MultiplayerExt::CheckRejected;
|
||||
constexpr auto HandleSDLEvent = &MultiplayerExt::HandleSDLEvent;
|
||||
constexpr auto IsThirdPersonCameraActive = &MultiplayerExt::IsThirdPersonCameraActive;
|
||||
constexpr auto HandleTouchInput = &MultiplayerExt::HandleTouchInput;
|
||||
constexpr auto HandleNavOverride = &MultiplayerExt::HandleNavOverride;
|
||||
#else
|
||||
constexpr decltype(&MultiplayerExt::HandleCreate) HandleCreate = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleWorldEnable) HandleWorldEnable = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleEntityNotify) HandleEntityNotify = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleSkyLightControl) HandleSkyLightControl = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleROIClick) HandleROIClick = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleActorEnter) HandleActorEnter = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleActorExit) HandleActorExit = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleCamAnimEnd) HandleCamAnimEnd = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::IsClonedCharacter) IsClonedCharacter = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleBeforeSaveLoad) HandleBeforeSaveLoad = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleSaveLoaded) HandleSaveLoaded = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::CheckRejected) CheckRejected = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleSDLEvent) HandleSDLEvent = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::IsThirdPersonCameraActive) IsThirdPersonCameraActive = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleTouchInput) HandleTouchInput = nullptr;
|
||||
constexpr decltype(&MultiplayerExt::HandleNavOverride) HandleNavOverride = nullptr;
|
||||
#endif
|
||||
} // namespace MP
|
||||
|
||||
}; // namespace Extensions
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
#include "extensions/multiplayer/platformcallbacks.h"
|
||||
#include "extensions/multiplayer/protocol.h"
|
||||
#include "extensions/multiplayer/remoteplayer.h"
|
||||
#include "extensions/multiplayer/thirdpersoncamera.h"
|
||||
#include "extensions/multiplayer/worldstatesync.h"
|
||||
#include "mxcore.h"
|
||||
#include "mxtypes.h"
|
||||
@ -19,9 +18,16 @@
|
||||
class LegoEntity;
|
||||
class LegoWorld;
|
||||
|
||||
namespace Extensions
|
||||
{
|
||||
class ThirdPersonCameraExt;
|
||||
}
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
|
||||
class NameBubbleRenderer;
|
||||
|
||||
class NetworkManager : public MxCore {
|
||||
public:
|
||||
NetworkManager();
|
||||
@ -48,7 +54,6 @@ class NetworkManager : public MxCore {
|
||||
void SetWalkAnimation(uint8_t p_walkAnimId);
|
||||
void SetIdleAnimation(uint8_t p_idleAnimId);
|
||||
void SendEmote(uint8_t p_emoteId);
|
||||
void SetDisplayActorIndex(uint8_t p_displayActorIndex);
|
||||
|
||||
// Thread-safe request methods for cross-thread callers (e.g. WASM exports
|
||||
// running on the browser main thread). Deferred to the game thread in Tickle().
|
||||
@ -77,8 +82,6 @@ class NetworkManager : public MxCore {
|
||||
void OnBeforeSaveLoad();
|
||||
void OnSaveLoaded();
|
||||
|
||||
ThirdPersonCamera& GetThirdPersonCamera() { return m_thirdPersonCamera; }
|
||||
|
||||
void NotifyThirdPersonChanged(bool p_enabled);
|
||||
void NotifyNameBubblesChanged(bool p_enabled);
|
||||
void NotifyAllowCustomizeChanged(bool p_enabled);
|
||||
@ -107,7 +110,6 @@ class NetworkManager : public MxCore {
|
||||
void HandleEmote(const EmoteMsg& p_msg);
|
||||
void HandleCustomize(const CustomizeMsg& p_msg);
|
||||
|
||||
void DeriveDisplayActorIndex(uint8_t p_actorId);
|
||||
void ProcessPendingRequests();
|
||||
void RemoveRemotePlayer(uint32_t p_peerId);
|
||||
void RemoveAllRemotePlayers();
|
||||
@ -121,7 +123,7 @@ class NetworkManager : public MxCore {
|
||||
NetworkTransport* m_transport;
|
||||
PlatformCallbacks* m_callbacks;
|
||||
WorldStateSync m_worldSync;
|
||||
ThirdPersonCamera m_thirdPersonCamera;
|
||||
NameBubbleRenderer* m_localNameBubble;
|
||||
std::map<uint32_t, std::unique_ptr<RemotePlayer>> m_remotePlayers;
|
||||
std::map<LegoROI*, RemotePlayer*> m_roiToPlayer;
|
||||
|
||||
@ -130,10 +132,6 @@ class NetworkManager : public MxCore {
|
||||
uint32_t m_sequence;
|
||||
uint32_t m_lastBroadcastTime;
|
||||
uint8_t m_lastValidActorId;
|
||||
uint8_t m_localWalkAnimId;
|
||||
uint8_t m_localIdleAnimId;
|
||||
uint8_t m_localDisplayActorIndex;
|
||||
bool m_displayActorFrozen;
|
||||
bool m_localAllowRemoteCustomize;
|
||||
bool m_inIsleWorld;
|
||||
bool m_registered;
|
||||
@ -146,6 +144,7 @@ class NetworkManager : public MxCore {
|
||||
std::atomic<bool> m_pendingToggleAllowCustomize;
|
||||
|
||||
bool m_showNameBubbles;
|
||||
bool m_lastCameraEnabled;
|
||||
|
||||
static const uint32_t BROADCAST_INTERVAL_MS = 66; // ~15Hz
|
||||
static const uint32_t TIMEOUT_MS = 5000; // 5 second timeout
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#include "extensions/common/constants.h"
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
|
||||
@ -26,18 +28,17 @@ enum MessageType : uint8_t {
|
||||
MSG_ASSIGN_ID = 0xFF
|
||||
};
|
||||
|
||||
enum VehicleType : int8_t {
|
||||
VEHICLE_NONE = -1,
|
||||
VEHICLE_HELICOPTER = 0,
|
||||
VEHICLE_JETSKI = 1,
|
||||
VEHICLE_DUNEBUGGY = 2,
|
||||
VEHICLE_BIKE = 3,
|
||||
VEHICLE_SKATEBOARD = 4,
|
||||
VEHICLE_MOTOCYCLE = 5,
|
||||
VEHICLE_TOWTRACK = 6,
|
||||
VEHICLE_AMBULANCE = 7,
|
||||
VEHICLE_COUNT = 8
|
||||
};
|
||||
using Extensions::Common::VehicleType;
|
||||
using Extensions::Common::VEHICLE_NONE;
|
||||
using Extensions::Common::VEHICLE_HELICOPTER;
|
||||
using Extensions::Common::VEHICLE_JETSKI;
|
||||
using Extensions::Common::VEHICLE_DUNEBUGGY;
|
||||
using Extensions::Common::VEHICLE_BIKE;
|
||||
using Extensions::Common::VEHICLE_SKATEBOARD;
|
||||
using Extensions::Common::VEHICLE_MOTOCYCLE;
|
||||
using Extensions::Common::VEHICLE_TOWTRACK;
|
||||
using Extensions::Common::VEHICLE_AMBULANCE;
|
||||
using Extensions::Common::VEHICLE_COUNT;
|
||||
|
||||
// Entity types for world events
|
||||
enum WorldEntityType : uint8_t {
|
||||
@ -47,15 +48,13 @@ enum WorldEntityType : uint8_t {
|
||||
ENTITY_LIGHT = 3
|
||||
};
|
||||
|
||||
// Change types for world events (maps to Switch* methods on LegoEntity)
|
||||
enum WorldChangeType : uint8_t {
|
||||
CHANGE_VARIANT = 0,
|
||||
CHANGE_SOUND = 1,
|
||||
CHANGE_MOVE = 2,
|
||||
CHANGE_COLOR = 3,
|
||||
CHANGE_MOOD = 4,
|
||||
CHANGE_DECREMENT = 5
|
||||
};
|
||||
using Extensions::Common::WorldChangeType;
|
||||
using Extensions::Common::CHANGE_VARIANT;
|
||||
using Extensions::Common::CHANGE_SOUND;
|
||||
using Extensions::Common::CHANGE_MOVE;
|
||||
using Extensions::Common::CHANGE_COLOR;
|
||||
using Extensions::Common::CHANGE_MOOD;
|
||||
using Extensions::Common::CHANGE_DECREMENT;
|
||||
|
||||
// Change types for ENTITY_SKY
|
||||
enum SkyChangeType : uint8_t {
|
||||
@ -153,17 +152,13 @@ struct CustomizeMsg {
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
// Validate actorId is a playable character (1-5, not brickster)
|
||||
inline bool IsValidActorId(uint8_t p_actorId)
|
||||
{
|
||||
return p_actorId >= 1 && p_actorId <= 5;
|
||||
}
|
||||
using Extensions::Common::IsValidActorId;
|
||||
|
||||
// Convert LegoGameState::Username letter indices (0-25 = A-Z) to ASCII.
|
||||
// Writes up to 7 characters + null terminator into p_out (must be at least 8 bytes).
|
||||
void EncodeUsername(char p_out[8]);
|
||||
|
||||
static const uint8_t DISPLAY_ACTOR_NONE = 0xFF;
|
||||
using Extensions::Common::DISPLAY_ACTOR_NONE;
|
||||
|
||||
// Parse the message type from a buffer. Returns MSG type or 0 on error.
|
||||
inline uint8_t ParseMessageType(const uint8_t* p_data, size_t p_length)
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "extensions/multiplayer/characteranimator.h"
|
||||
#include "extensions/multiplayer/customizestate.h"
|
||||
#include "extensions/common/characteranimator.h"
|
||||
#include "extensions/common/customizestate.h"
|
||||
#include "extensions/multiplayer/protocol.h"
|
||||
#include "mxtypes.h"
|
||||
|
||||
@ -14,6 +14,8 @@ class LegoWorld;
|
||||
namespace Multiplayer
|
||||
{
|
||||
|
||||
class NameBubbleRenderer;
|
||||
|
||||
class RemotePlayer {
|
||||
public:
|
||||
RemotePlayer(uint32_t p_peerId, uint8_t p_actorId, uint8_t p_displayActorIndex);
|
||||
@ -41,7 +43,7 @@ class RemotePlayer {
|
||||
void CreateNameBubble();
|
||||
void DestroyNameBubble();
|
||||
|
||||
const CustomizeState& GetCustomizeState() const { return m_customizeState; }
|
||||
const Extensions::Common::CustomizeState& GetCustomizeState() const { return m_customizeState; }
|
||||
bool GetAllowRemoteCustomize() const { return m_allowRemoteCustomize; }
|
||||
void SetClickAnimObjectId(MxU32 p_clickAnimObjectId) { m_animator.SetClickAnimObjectId(p_clickAnimObjectId); }
|
||||
void StopClickAnimation();
|
||||
@ -79,11 +81,13 @@ class RemotePlayer {
|
||||
float m_currentDirection[3];
|
||||
float m_currentUp[3];
|
||||
|
||||
CharacterAnimator m_animator;
|
||||
Extensions::Common::CharacterAnimator m_animator;
|
||||
|
||||
LegoROI* m_vehicleROI;
|
||||
|
||||
CustomizeState m_customizeState;
|
||||
NameBubbleRenderer* m_nameBubble;
|
||||
|
||||
Extensions::Common::CustomizeState m_customizeState;
|
||||
bool m_allowRemoteCustomize;
|
||||
};
|
||||
|
||||
|
||||
@ -1,151 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "extensions/multiplayer/characteranimator.h"
|
||||
#include "extensions/multiplayer/customizestate.h"
|
||||
#include "mxgeometry/mxgeometry3d.h"
|
||||
#include "mxtypes.h"
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <cstdint>
|
||||
|
||||
class IslePathActor;
|
||||
class LegoNavController;
|
||||
class LegoPathActor;
|
||||
class LegoROI;
|
||||
class LegoWorld;
|
||||
class Vector3;
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
|
||||
class ThirdPersonCamera {
|
||||
public:
|
||||
ThirdPersonCamera();
|
||||
|
||||
void Enable();
|
||||
void Disable();
|
||||
bool IsEnabled() const { return m_enabled; }
|
||||
bool IsActive() const { return m_active; }
|
||||
|
||||
// Core hooks
|
||||
void OnActorEnter(IslePathActor* p_actor);
|
||||
void OnActorExit(IslePathActor* p_actor);
|
||||
void OnCamAnimEnd(LegoPathActor* p_actor);
|
||||
|
||||
// Called every frame from NetworkManager::Tickle()
|
||||
void Tick(float p_deltaTime);
|
||||
|
||||
// Animation selection (forwarded from NetworkManager)
|
||||
void SetWalkAnimId(uint8_t p_walkAnimId);
|
||||
void SetIdleAnimId(uint8_t p_idleAnimId);
|
||||
void TriggerEmote(uint8_t p_emoteId);
|
||||
bool IsInMultiPartEmote() const;
|
||||
int8_t GetFrozenEmoteId() const;
|
||||
void SetDisplayActorIndex(uint8_t p_displayActorIndex);
|
||||
uint8_t GetDisplayActorIndex() const { return m_displayActorIndex; }
|
||||
LegoROI* GetDisplayROI() const { return m_displayROI; }
|
||||
CustomizeState& GetCustomizeState() { return m_customizeState; }
|
||||
|
||||
void ApplyCustomizeChange(uint8_t p_changeType, uint8_t p_partIndex);
|
||||
void SetClickAnimObjectId(MxU32 p_clickAnimObjectId) { m_animator.SetClickAnimObjectId(p_clickAnimObjectId); }
|
||||
void StopClickAnimation();
|
||||
bool IsInVehicle() const { return m_animator.IsInVehicle(); }
|
||||
|
||||
void SetNameBubbleVisible(bool p_visible);
|
||||
|
||||
void OnWorldEnabled(LegoWorld* p_world);
|
||||
void OnWorldDisabled(LegoWorld* p_world);
|
||||
|
||||
// Camera-relative movement override (called from nav controller hook)
|
||||
MxBool HandleCameraRelativeMovement(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime
|
||||
);
|
||||
|
||||
// Free camera input handling
|
||||
void HandleSDLEvent(SDL_Event* p_event);
|
||||
|
||||
// Auto-switch flags (set by HandleSDLEvent, consumed by caller)
|
||||
bool ConsumeAutoDisable();
|
||||
bool ConsumeAutoEnable();
|
||||
|
||||
float GetOrbitDistance() const { return m_orbitDistance; }
|
||||
void SetOrbitDistance(float p_distance) { m_orbitDistance = p_distance; }
|
||||
void ResetTouchState() { m_touch = {}; }
|
||||
|
||||
// Finger-claiming API for split-screen touch zones (left=movement, right=camera)
|
||||
bool TryClaimFinger(const SDL_TouchFingerEvent& event);
|
||||
bool TryReleaseFinger(SDL_FingerID id);
|
||||
bool IsFingerTracked(SDL_FingerID id) const;
|
||||
|
||||
static constexpr float CAMERA_ZONE_X = 0.5f;
|
||||
static constexpr float MIN_DISTANCE = 1.5f;
|
||||
|
||||
private:
|
||||
// Orbit camera helpers
|
||||
void ComputeOrbitVectors(float p_yaw, Mx3DPointFloat& p_at, Mx3DPointFloat& p_dir, Mx3DPointFloat& p_up) const;
|
||||
void ApplyOrbitCamera();
|
||||
void ResetOrbitState();
|
||||
void ClampPitch();
|
||||
void ClampDistance();
|
||||
|
||||
float GetLocalYaw(LegoROI* p_roi) const;
|
||||
void InitAbsoluteYaw(LegoROI* p_roi);
|
||||
|
||||
void SetupCamera(LegoPathActor* p_actor);
|
||||
void ReinitForCharacter();
|
||||
|
||||
void CreateNameBubble();
|
||||
void DestroyNameBubble();
|
||||
|
||||
bool EnsureDisplayROI();
|
||||
void CreateDisplayClone();
|
||||
void DestroyDisplayClone();
|
||||
bool HasDisplayOverride() const { return m_displayROI != nullptr; }
|
||||
|
||||
bool m_enabled;
|
||||
bool m_active;
|
||||
bool m_pendingWorldTransition; // True between OnWorldEnabled and first Tick; defers camera setup
|
||||
LegoROI* m_playerROI; // Borrowed, not owned
|
||||
|
||||
// Display actor override
|
||||
uint8_t m_displayActorIndex;
|
||||
LegoROI* m_displayROI; // Owned clone; nullptr = use native ROI
|
||||
char m_displayUniqueName[32];
|
||||
CustomizeState m_customizeState;
|
||||
|
||||
CharacterAnimator m_animator;
|
||||
|
||||
bool m_showNameBubble;
|
||||
|
||||
// Orbit camera state
|
||||
float m_orbitPitch;
|
||||
float m_orbitDistance;
|
||||
float m_absoluteYaw; // Camera yaw in world space (decoupled from player facing)
|
||||
float m_smoothedSpeed; // Extension-managed velocity for smooth acceleration/deceleration
|
||||
|
||||
// Touch gesture tracking
|
||||
struct TouchState {
|
||||
SDL_FingerID id[2];
|
||||
float x[2], y[2];
|
||||
int count;
|
||||
float initialPinchDist;
|
||||
} m_touch;
|
||||
|
||||
static constexpr float DEFAULT_ORBIT_YAW = 0.0f;
|
||||
static constexpr float DEFAULT_ORBIT_PITCH = 0.3f;
|
||||
static constexpr float DEFAULT_ORBIT_DISTANCE = 3.5f;
|
||||
static constexpr float ORBIT_TARGET_HEIGHT = 1.5f;
|
||||
static constexpr float MIN_PITCH = 0.05f;
|
||||
static constexpr float MAX_PITCH = 1.4f;
|
||||
static constexpr float MAX_DISTANCE = 15.0f;
|
||||
|
||||
bool m_wantsAutoDisable;
|
||||
bool m_wantsAutoEnable;
|
||||
};
|
||||
|
||||
} // namespace Multiplayer
|
||||
@ -15,7 +15,7 @@ class Core;
|
||||
|
||||
namespace Extensions
|
||||
{
|
||||
class SiLoader {
|
||||
class SiLoaderExt {
|
||||
public:
|
||||
typedef std::pair<MxAtomId, MxU32> StreamObject;
|
||||
|
||||
@ -31,12 +31,14 @@ class SiLoader {
|
||||
template <typename... Args>
|
||||
static std::optional<StreamObject> ReplacedIn(MxDSAction& p_action, Args... p_args);
|
||||
|
||||
static const std::vector<std::string>& GetFiles() { return files; }
|
||||
|
||||
static std::map<std::string, std::string> options;
|
||||
static std::vector<std::string> files;
|
||||
static std::vector<std::string> directives;
|
||||
static bool enabled;
|
||||
|
||||
private:
|
||||
static std::vector<std::string> files;
|
||||
static std::vector<std::string> directives;
|
||||
static std::vector<std::pair<StreamObject, StreamObject>> startWith;
|
||||
static std::vector<std::pair<StreamObject, StreamObject>> removeWith;
|
||||
static std::vector<std::pair<StreamObject, StreamObject>> replace;
|
||||
@ -53,7 +55,7 @@ class SiLoader {
|
||||
|
||||
#ifdef EXTENSIONS
|
||||
template <typename... Args>
|
||||
std::optional<SiLoader::StreamObject> SiLoader::ReplacedIn(MxDSAction& p_action, Args... p_args)
|
||||
std::optional<SiLoaderExt::StreamObject> SiLoaderExt::ReplacedIn(MxDSAction& p_action, Args... p_args)
|
||||
{
|
||||
StreamObject object{p_action.GetAtomId(), p_action.GetObjectId()};
|
||||
auto checkAtomId = [&p_action, &object](const auto& p_atomId) -> std::optional<StreamObject> {
|
||||
@ -70,26 +72,34 @@ std::optional<SiLoader::StreamObject> SiLoader::ReplacedIn(MxDSAction& p_action,
|
||||
((void) (!result.has_value() && (result = checkAtomId(p_args), true)), ...);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
constexpr auto Load = &SiLoader::Load;
|
||||
constexpr auto HandleFind = &SiLoader::HandleFind;
|
||||
constexpr auto HandleStart = &SiLoader::HandleStart;
|
||||
constexpr auto HandleWorld = &SiLoader::HandleWorld;
|
||||
constexpr auto HandleRemove = &SiLoader::HandleRemove;
|
||||
constexpr auto HandleDelete = &SiLoader::HandleDelete;
|
||||
constexpr auto HandleEndAction = &SiLoader::HandleEndAction;
|
||||
constexpr auto ReplacedIn = [](auto&&... args) { return SiLoader::ReplacedIn(std::forward<decltype(args)>(args)...); };
|
||||
namespace SI
|
||||
{
|
||||
#ifdef EXTENSIONS
|
||||
constexpr auto Load = &SiLoaderExt::Load;
|
||||
constexpr auto HandleFind = &SiLoaderExt::HandleFind;
|
||||
constexpr auto HandleStart = &SiLoaderExt::HandleStart;
|
||||
constexpr auto HandleWorld = &SiLoaderExt::HandleWorld;
|
||||
constexpr auto HandleRemove = &SiLoaderExt::HandleRemove;
|
||||
constexpr auto HandleDelete = &SiLoaderExt::HandleDelete;
|
||||
constexpr auto HandleEndAction = &SiLoaderExt::HandleEndAction;
|
||||
constexpr auto ReplacedIn = [](auto&&... args) {
|
||||
return SiLoaderExt::ReplacedIn(std::forward<decltype(args)>(args)...);
|
||||
};
|
||||
#else
|
||||
constexpr decltype(&SiLoader::Load) Load = nullptr;
|
||||
constexpr decltype(&SiLoader::HandleFind) HandleFind = nullptr;
|
||||
constexpr decltype(&SiLoader::HandleStart) HandleStart = nullptr;
|
||||
constexpr decltype(&SiLoader::HandleWorld) HandleWorld = nullptr;
|
||||
constexpr decltype(&SiLoader::HandleRemove) HandleRemove = nullptr;
|
||||
constexpr decltype(&SiLoader::HandleDelete) HandleDelete = nullptr;
|
||||
constexpr decltype(&SiLoader::HandleEndAction) HandleEndAction = nullptr;
|
||||
constexpr auto ReplacedIn = [](auto&&... args) -> std::optional<SiLoader::StreamObject> {
|
||||
constexpr decltype(&SiLoaderExt::Load) Load = nullptr;
|
||||
constexpr decltype(&SiLoaderExt::HandleFind) HandleFind = nullptr;
|
||||
constexpr decltype(&SiLoaderExt::HandleStart) HandleStart = nullptr;
|
||||
constexpr decltype(&SiLoaderExt::HandleWorld) HandleWorld = nullptr;
|
||||
constexpr decltype(&SiLoaderExt::HandleRemove) HandleRemove = nullptr;
|
||||
constexpr decltype(&SiLoaderExt::HandleDelete) HandleDelete = nullptr;
|
||||
constexpr decltype(&SiLoaderExt::HandleEndAction) HandleEndAction = nullptr;
|
||||
constexpr auto ReplacedIn = [](auto&&... args) -> std::optional<SiLoaderExt::StreamObject> {
|
||||
((void) args, ...);
|
||||
return std::nullopt;
|
||||
};
|
||||
#endif
|
||||
} // namespace SI
|
||||
|
||||
}; // namespace Extensions
|
||||
|
||||
@ -9,13 +9,13 @@
|
||||
|
||||
namespace Extensions
|
||||
{
|
||||
class TextureLoader {
|
||||
class TextureLoaderExt {
|
||||
public:
|
||||
static void Initialize();
|
||||
static bool PatchTexture(LegoTextureInfo* p_textureInfo);
|
||||
static void AddExcludedFile(const std::string& p_file);
|
||||
|
||||
static std::map<std::string, std::string> options;
|
||||
static std::vector<std::string> excludedFiles;
|
||||
static bool enabled;
|
||||
|
||||
static constexpr std::array<std::pair<std::string_view, std::string_view>, 1> defaults = {
|
||||
@ -23,12 +23,17 @@ class TextureLoader {
|
||||
};
|
||||
|
||||
private:
|
||||
static std::vector<std::string> excludedFiles;
|
||||
static SDL_Surface* FindTexture(const char* p_name);
|
||||
};
|
||||
|
||||
namespace TL
|
||||
{
|
||||
#ifdef EXTENSIONS
|
||||
constexpr auto PatchTexture = &TextureLoader::PatchTexture;
|
||||
constexpr auto PatchTexture = &TextureLoaderExt::PatchTexture;
|
||||
#else
|
||||
constexpr decltype(&TextureLoader::PatchTexture) PatchTexture = nullptr;
|
||||
constexpr decltype(&TextureLoaderExt::PatchTexture) PatchTexture = nullptr;
|
||||
#endif
|
||||
} // namespace TL
|
||||
|
||||
}; // namespace Extensions
|
||||
|
||||
91
extensions/include/extensions/thirdpersoncamera.h
Normal file
91
extensions/include/extensions/thirdpersoncamera.h
Normal file
@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include "extensions/extensions.h"
|
||||
#include "mxtypes.h"
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class IslePathActor;
|
||||
class LegoEventNotificationParam;
|
||||
class LegoNavController;
|
||||
class LegoPathActor;
|
||||
class LegoROI;
|
||||
class LegoWorld;
|
||||
class Vector3;
|
||||
|
||||
namespace Extensions
|
||||
{
|
||||
|
||||
namespace ThirdPersonCamera
|
||||
{
|
||||
class Controller;
|
||||
}
|
||||
|
||||
class ThirdPersonCameraExt {
|
||||
public:
|
||||
static void Initialize();
|
||||
|
||||
static void HandleActorEnter(IslePathActor* p_actor);
|
||||
static void HandleActorExit(IslePathActor* p_actor);
|
||||
static void HandleCamAnimEnd(LegoPathActor* p_actor);
|
||||
static void OnSDLEvent(SDL_Event* p_event);
|
||||
static MxBool IsThirdPersonCameraActive();
|
||||
static MxBool HandleTouchInput(SDL_Event* p_event);
|
||||
static MxBool HandleNavOverride(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime
|
||||
);
|
||||
static MxBool HandleWorldEnable(LegoWorld* p_world, MxBool p_enable);
|
||||
|
||||
static MxBool HandleROIClick(LegoROI* p_rootROI, LegoEventNotificationParam& p_param);
|
||||
static MxBool IsClonedCharacter(const char* p_name);
|
||||
static void HandleCreate();
|
||||
LEGO1_EXPORT static void HandleSDLEvent(SDL_Event* p_event);
|
||||
|
||||
static ThirdPersonCamera::Controller* GetCamera();
|
||||
|
||||
static std::map<std::string, std::string> options;
|
||||
static bool enabled;
|
||||
|
||||
private:
|
||||
static ThirdPersonCamera::Controller* s_camera;
|
||||
static bool s_registered;
|
||||
static bool s_inIsleWorld;
|
||||
};
|
||||
|
||||
namespace TP
|
||||
{
|
||||
#ifdef EXTENSIONS
|
||||
constexpr auto HandleCreate = &ThirdPersonCameraExt::HandleCreate;
|
||||
constexpr auto HandleWorldEnable = &ThirdPersonCameraExt::HandleWorldEnable;
|
||||
constexpr auto HandleActorEnter = &ThirdPersonCameraExt::HandleActorEnter;
|
||||
constexpr auto HandleActorExit = &ThirdPersonCameraExt::HandleActorExit;
|
||||
constexpr auto HandleCamAnimEnd = &ThirdPersonCameraExt::HandleCamAnimEnd;
|
||||
constexpr auto HandleSDLEvent = &ThirdPersonCameraExt::OnSDLEvent;
|
||||
constexpr auto IsThirdPersonCameraActive = &ThirdPersonCameraExt::IsThirdPersonCameraActive;
|
||||
constexpr auto HandleTouchInput = &ThirdPersonCameraExt::HandleTouchInput;
|
||||
constexpr auto HandleNavOverride = &ThirdPersonCameraExt::HandleNavOverride;
|
||||
constexpr auto HandleROIClick = &ThirdPersonCameraExt::HandleROIClick;
|
||||
constexpr auto IsClonedCharacter = &ThirdPersonCameraExt::IsClonedCharacter;
|
||||
#else
|
||||
constexpr decltype(&ThirdPersonCameraExt::HandleCreate) HandleCreate = nullptr;
|
||||
constexpr decltype(&ThirdPersonCameraExt::HandleWorldEnable) HandleWorldEnable = nullptr;
|
||||
constexpr decltype(&ThirdPersonCameraExt::HandleActorEnter) HandleActorEnter = nullptr;
|
||||
constexpr decltype(&ThirdPersonCameraExt::HandleActorExit) HandleActorExit = nullptr;
|
||||
constexpr decltype(&ThirdPersonCameraExt::HandleCamAnimEnd) HandleCamAnimEnd = nullptr;
|
||||
constexpr decltype(&ThirdPersonCameraExt::OnSDLEvent) HandleSDLEvent = nullptr;
|
||||
constexpr decltype(&ThirdPersonCameraExt::IsThirdPersonCameraActive) IsThirdPersonCameraActive = nullptr;
|
||||
constexpr decltype(&ThirdPersonCameraExt::HandleTouchInput) HandleTouchInput = nullptr;
|
||||
constexpr decltype(&ThirdPersonCameraExt::HandleNavOverride) HandleNavOverride = nullptr;
|
||||
constexpr decltype(&ThirdPersonCameraExt::HandleROIClick) HandleROIClick = nullptr;
|
||||
constexpr decltype(&ThirdPersonCameraExt::IsClonedCharacter) IsClonedCharacter = nullptr;
|
||||
#endif
|
||||
} // namespace TP
|
||||
|
||||
}; // namespace Extensions
|
||||
109
extensions/include/extensions/thirdpersoncamera/controller.h
Normal file
109
extensions/include/extensions/thirdpersoncamera/controller.h
Normal file
@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "extensions/common/characteranimator.h"
|
||||
#include "extensions/thirdpersoncamera/displayactor.h"
|
||||
#include "extensions/thirdpersoncamera/inputhandler.h"
|
||||
#include "extensions/thirdpersoncamera/orbitcamera.h"
|
||||
#include "mxtypes.h"
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <cstdint>
|
||||
|
||||
class IslePathActor;
|
||||
class LegoNavController;
|
||||
class LegoPathActor;
|
||||
class LegoROI;
|
||||
class LegoWorld;
|
||||
class Vector3;
|
||||
|
||||
namespace Extensions
|
||||
{
|
||||
namespace ThirdPersonCamera
|
||||
{
|
||||
|
||||
class Controller {
|
||||
public:
|
||||
Controller();
|
||||
|
||||
void Enable();
|
||||
void Disable();
|
||||
bool IsEnabled() const { return m_enabled; }
|
||||
bool IsActive() const { return m_active; }
|
||||
|
||||
void OnActorEnter(IslePathActor* p_actor);
|
||||
void OnActorExit(IslePathActor* p_actor);
|
||||
void OnCamAnimEnd(LegoPathActor* p_actor);
|
||||
|
||||
void Tick(float p_deltaTime);
|
||||
|
||||
void SetWalkAnimId(uint8_t p_walkAnimId);
|
||||
uint8_t GetWalkAnimId() const { return m_animator.GetWalkAnimId(); }
|
||||
void SetIdleAnimId(uint8_t p_idleAnimId);
|
||||
uint8_t GetIdleAnimId() const { return m_animator.GetIdleAnimId(); }
|
||||
void TriggerEmote(uint8_t p_emoteId);
|
||||
bool IsInMultiPartEmote() const;
|
||||
int8_t GetFrozenEmoteId() const;
|
||||
|
||||
void SetDisplayActorIndex(uint8_t p_displayActorIndex) { m_display.SetDisplayActorIndex(p_displayActorIndex); }
|
||||
uint8_t GetDisplayActorIndex() const { return m_display.GetDisplayActorIndex(); }
|
||||
LegoROI* GetDisplayROI() const { return m_display.GetDisplayROI(); }
|
||||
Common::CustomizeState& GetCustomizeState() { return m_display.GetCustomizeState(); }
|
||||
|
||||
void ApplyCustomizeChange(uint8_t p_changeType, uint8_t p_partIndex)
|
||||
{
|
||||
m_display.ApplyCustomizeChange(p_changeType, p_partIndex);
|
||||
}
|
||||
void SetClickAnimObjectId(MxU32 p_clickAnimObjectId) { m_animator.SetClickAnimObjectId(p_clickAnimObjectId); }
|
||||
void StopClickAnimation();
|
||||
bool IsInVehicle() const { return m_animator.IsInVehicle(); }
|
||||
|
||||
void OnWorldEnabled(LegoWorld* p_world);
|
||||
void OnWorldDisabled(LegoWorld* p_world);
|
||||
|
||||
MxBool HandleCameraRelativeMovement(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime
|
||||
);
|
||||
|
||||
void HandleSDLEventImpl(SDL_Event* p_event);
|
||||
|
||||
bool ConsumeAutoDisable() { return m_input.ConsumeAutoDisable(); }
|
||||
bool ConsumeAutoEnable() { return m_input.ConsumeAutoEnable(); }
|
||||
|
||||
float GetOrbitDistance() const { return m_orbit.GetOrbitDistance(); }
|
||||
void SetOrbitDistance(float p_distance) { m_orbit.SetOrbitDistance(p_distance); }
|
||||
void ResetTouchState() { m_input.ResetTouchState(); }
|
||||
|
||||
bool TryClaimFinger(const SDL_TouchFingerEvent& event) { return m_input.TryClaimFinger(event, m_active); }
|
||||
bool TryReleaseFinger(SDL_FingerID id) { return m_input.TryReleaseFinger(id); }
|
||||
bool IsFingerTracked(SDL_FingerID id) const { return m_input.IsFingerTracked(id); }
|
||||
|
||||
void FreezeDisplayActor() { m_display.FreezeDisplayActor(); }
|
||||
void UnfreezeDisplayActor() { m_display.UnfreezeDisplayActor(); }
|
||||
bool IsDisplayActorFrozen() const { return m_display.IsDisplayActorFrozen(); }
|
||||
|
||||
LegoROI* GetPlayerROI() const { return m_playerROI; }
|
||||
|
||||
static constexpr float CAMERA_ZONE_X = InputHandler::CAMERA_ZONE_X;
|
||||
static constexpr float MIN_DISTANCE = OrbitCamera::MIN_DISTANCE;
|
||||
|
||||
private:
|
||||
void ReinitForCharacter();
|
||||
|
||||
OrbitCamera m_orbit;
|
||||
InputHandler m_input;
|
||||
DisplayActor m_display;
|
||||
Common::CharacterAnimator m_animator;
|
||||
|
||||
bool m_enabled;
|
||||
bool m_active;
|
||||
bool m_pendingWorldTransition;
|
||||
LegoROI* m_playerROI;
|
||||
};
|
||||
|
||||
} // namespace ThirdPersonCamera
|
||||
} // namespace Extensions
|
||||
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "extensions/common/customizestate.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class LegoROI;
|
||||
|
||||
namespace Extensions
|
||||
{
|
||||
namespace ThirdPersonCamera
|
||||
{
|
||||
|
||||
class DisplayActor {
|
||||
public:
|
||||
DisplayActor();
|
||||
|
||||
void SetDisplayActorIndex(uint8_t p_displayActorIndex);
|
||||
uint8_t GetDisplayActorIndex() const { return m_displayActorIndex; }
|
||||
|
||||
bool EnsureDisplayROI();
|
||||
void CreateDisplayClone();
|
||||
void DestroyDisplayClone();
|
||||
|
||||
bool HasDisplayOverride() const { return m_displayROI != nullptr; }
|
||||
LegoROI* GetDisplayROI() const { return m_displayROI; }
|
||||
|
||||
Common::CustomizeState& GetCustomizeState() { return m_customizeState; }
|
||||
|
||||
void ApplyCustomizeChange(uint8_t p_changeType, uint8_t p_partIndex);
|
||||
|
||||
void SyncTransformFromNative(LegoROI* p_nativeROI);
|
||||
|
||||
void FreezeDisplayActor() { m_displayActorFrozen = true; }
|
||||
void UnfreezeDisplayActor() { m_displayActorFrozen = false; }
|
||||
bool IsDisplayActorFrozen() const { return m_displayActorFrozen; }
|
||||
|
||||
private:
|
||||
uint8_t m_displayActorIndex;
|
||||
bool m_displayActorFrozen;
|
||||
LegoROI* m_displayROI;
|
||||
char m_displayUniqueName[32];
|
||||
Common::CustomizeState m_customizeState;
|
||||
};
|
||||
|
||||
} // namespace ThirdPersonCamera
|
||||
} // namespace Extensions
|
||||
@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
|
||||
namespace Extensions
|
||||
{
|
||||
namespace ThirdPersonCamera
|
||||
{
|
||||
|
||||
class OrbitCamera;
|
||||
|
||||
class InputHandler {
|
||||
public:
|
||||
InputHandler();
|
||||
|
||||
void HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool p_active);
|
||||
|
||||
bool TryClaimFinger(const SDL_TouchFingerEvent& p_event, bool p_active);
|
||||
bool TryReleaseFinger(SDL_FingerID p_id);
|
||||
bool IsFingerTracked(SDL_FingerID p_id) const;
|
||||
|
||||
bool ConsumeAutoDisable();
|
||||
bool ConsumeAutoEnable();
|
||||
|
||||
void ResetTouchState() { m_touch = {}; }
|
||||
|
||||
static constexpr float CAMERA_ZONE_X = 0.5f;
|
||||
|
||||
private:
|
||||
struct TouchState {
|
||||
SDL_FingerID id[2];
|
||||
float x[2], y[2];
|
||||
int count;
|
||||
float initialPinchDist;
|
||||
} m_touch;
|
||||
|
||||
bool m_wantsAutoDisable;
|
||||
bool m_wantsAutoEnable;
|
||||
};
|
||||
|
||||
} // namespace ThirdPersonCamera
|
||||
} // namespace Extensions
|
||||
@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include "mxgeometry/mxgeometry3d.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
class LegoNavController;
|
||||
class LegoPathActor;
|
||||
class LegoROI;
|
||||
class LegoWorld;
|
||||
class Vector3;
|
||||
|
||||
namespace Extensions
|
||||
{
|
||||
namespace ThirdPersonCamera
|
||||
{
|
||||
|
||||
class OrbitCamera {
|
||||
public:
|
||||
OrbitCamera();
|
||||
|
||||
void SetupCamera(LegoPathActor* p_actor);
|
||||
void ApplyOrbitCamera();
|
||||
void ResetOrbitState();
|
||||
void ClampPitch();
|
||||
void ClampDistance();
|
||||
void InitAbsoluteYaw(LegoROI* p_roi);
|
||||
|
||||
void RestoreFirstPersonCamera();
|
||||
|
||||
MxBool HandleCameraRelativeMovement(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime,
|
||||
bool p_isInMultiPartEmote
|
||||
);
|
||||
|
||||
void AdjustYaw(float p_delta) { m_absoluteYaw += p_delta; }
|
||||
void AdjustPitch(float p_delta) { m_orbitPitch += p_delta; }
|
||||
void AdjustDistance(float p_delta) { m_orbitDistance += p_delta; }
|
||||
|
||||
float GetOrbitDistance() const { return m_orbitDistance; }
|
||||
void SetOrbitDistance(float p_distance) { m_orbitDistance = p_distance; }
|
||||
float GetSmoothedSpeed() const { return m_smoothedSpeed; }
|
||||
|
||||
static constexpr float DEFAULT_ORBIT_YAW = 0.0f;
|
||||
static constexpr float DEFAULT_ORBIT_PITCH = 0.3f;
|
||||
static constexpr float DEFAULT_ORBIT_DISTANCE = 3.5f;
|
||||
static constexpr float ORBIT_TARGET_HEIGHT = 1.5f;
|
||||
static constexpr float MIN_PITCH = 0.05f;
|
||||
static constexpr float MAX_PITCH = 1.4f;
|
||||
static constexpr float MIN_DISTANCE = 1.5f;
|
||||
static constexpr float MAX_DISTANCE = 15.0f;
|
||||
|
||||
private:
|
||||
void ComputeOrbitVectors(float p_yaw, Mx3DPointFloat& p_at, Mx3DPointFloat& p_dir, Mx3DPointFloat& p_up) const;
|
||||
float GetLocalYaw(LegoROI* p_roi) const;
|
||||
|
||||
float m_orbitPitch;
|
||||
float m_orbitDistance;
|
||||
float m_absoluteYaw;
|
||||
float m_smoothedSpeed;
|
||||
};
|
||||
|
||||
} // namespace ThirdPersonCamera
|
||||
} // namespace Extensions
|
||||
@ -1,8 +1,10 @@
|
||||
#include "extensions/multiplayer/animdata.h"
|
||||
#include "extensions/common/animdata.h"
|
||||
|
||||
#include "legopathactor.h"
|
||||
|
||||
namespace Multiplayer
|
||||
namespace Extensions
|
||||
{
|
||||
namespace Common
|
||||
{
|
||||
|
||||
const char* const g_walkAnimNames[] = {
|
||||
@ -75,4 +77,5 @@ int8_t DetectVehicleType(LegoPathActor* p_actor)
|
||||
return VEHICLE_NONE;
|
||||
}
|
||||
|
||||
} // namespace Multiplayer
|
||||
} // namespace Common
|
||||
} // namespace Extensions
|
||||
@ -1,4 +1,4 @@
|
||||
#include "extensions/multiplayer/animutils.h"
|
||||
#include "extensions/common/animutils.h"
|
||||
|
||||
#include "anim/legoanim.h"
|
||||
#include "legoanimpresenter.h"
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
using namespace Multiplayer;
|
||||
using namespace Extensions::Common;
|
||||
|
||||
// Mirrors the game's UpdateStructMapAndROIIndex: assigns ROI indices at runtime
|
||||
// via SetROIIndex() since m_roiIndex starts at 0 for all animation nodes.
|
||||
@ -1,9 +1,8 @@
|
||||
#include "extensions/multiplayer/characteranimator.h"
|
||||
#include "extensions/common/characteranimator.h"
|
||||
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "anim/legoanim.h"
|
||||
#include "extensions/multiplayer/charactercustomizer.h"
|
||||
#include "extensions/multiplayer/namebubblerenderer.h"
|
||||
#include "extensions/common/charactercustomizer.h"
|
||||
#include "legoanimpresenter.h"
|
||||
#include "legocachesoundmanager.h"
|
||||
#include "legocachsound.h"
|
||||
@ -18,21 +17,19 @@
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
using namespace Multiplayer;
|
||||
using namespace Extensions::Common;
|
||||
|
||||
CharacterAnimator::CharacterAnimator(const CharacterAnimatorConfig& p_config)
|
||||
: m_config(p_config), m_walkAnimId(0), m_idleAnimId(0), m_walkAnimCache(nullptr), m_idleAnimCache(nullptr),
|
||||
m_animTime(0.0f), m_idleTime(0.0f), m_idleAnimTime(0.0f), m_wasMoving(false), m_emoteAnimCache(nullptr),
|
||||
m_emoteTime(0.0f), m_emoteDuration(0.0f), m_emoteActive(false), m_currentEmoteId(0), m_frozenEmoteId(-1),
|
||||
m_frozenAnimCache(nullptr), m_frozenAnimDuration(0.0f), m_clickAnimObjectId(0), m_rideAnim(nullptr),
|
||||
m_rideRoiMap(nullptr), m_rideRoiMapSize(0), m_rideVehicleROI(nullptr), m_currentVehicleType(VEHICLE_NONE),
|
||||
m_nameBubble(nullptr)
|
||||
m_rideRoiMap(nullptr), m_rideRoiMapSize(0), m_rideVehicleROI(nullptr), m_currentVehicleType(VEHICLE_NONE)
|
||||
{
|
||||
}
|
||||
|
||||
CharacterAnimator::~CharacterAnimator()
|
||||
{
|
||||
DestroyNameBubble();
|
||||
ClearRideAnimation();
|
||||
}
|
||||
|
||||
@ -460,35 +457,3 @@ void CharacterAnimator::ApplyIdleFrame0(LegoROI* p_roi)
|
||||
LegoROI::ApplyAnimationTransformation(root->GetChild(i), transform, (LegoTime) 0.0f, m_idleAnimCache->roiMap);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAnimator::CreateNameBubble(const char* p_name)
|
||||
{
|
||||
if (m_nameBubble || !p_name || p_name[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
m_nameBubble = new NameBubbleRenderer();
|
||||
m_nameBubble->Create(p_name);
|
||||
}
|
||||
|
||||
void CharacterAnimator::DestroyNameBubble()
|
||||
{
|
||||
if (m_nameBubble) {
|
||||
delete m_nameBubble;
|
||||
m_nameBubble = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAnimator::SetNameBubbleVisible(bool p_visible)
|
||||
{
|
||||
if (m_nameBubble) {
|
||||
m_nameBubble->SetVisible(p_visible);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAnimator::UpdateNameBubble(LegoROI* p_roi)
|
||||
{
|
||||
if (m_nameBubble) {
|
||||
m_nameBubble->Update(p_roi);
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
#include "extensions/multiplayer/charactercloner.h"
|
||||
#include "extensions/common/charactercloner.h"
|
||||
|
||||
#include "legoactors.h"
|
||||
#include "legocharactermanager.h"
|
||||
@ -13,7 +13,7 @@
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <vec.h>
|
||||
|
||||
using namespace Multiplayer;
|
||||
using namespace Extensions::Common;
|
||||
|
||||
LegoROI* CharacterCloner::Clone(LegoCharacterManager* p_charMgr, const char* p_uniqueName, const char* p_characterType)
|
||||
{
|
||||
@ -1,12 +1,14 @@
|
||||
#include "extensions/multiplayer/charactercustomizer.h"
|
||||
#include "extensions/common/charactercustomizer.h"
|
||||
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "3dmanager/lego3dview.h"
|
||||
#include "extensions/multiplayer/charactercloner.h"
|
||||
#include "extensions/multiplayer/customizestate.h"
|
||||
#include "extensions/multiplayer/protocol.h"
|
||||
#include "extensions/common/charactercloner.h"
|
||||
#include "extensions/common/customizestate.h"
|
||||
#include "extensions/common/constants.h"
|
||||
#include "legoactor.h"
|
||||
#include "legoactors.h"
|
||||
#include "legocharactermanager.h"
|
||||
#include "legogamestate.h"
|
||||
#include "legovideomanager.h"
|
||||
#include "misc.h"
|
||||
#include "mxatom.h"
|
||||
@ -19,7 +21,7 @@
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
using namespace Multiplayer;
|
||||
using namespace Extensions::Common;
|
||||
|
||||
static const MxU32 g_characterSoundIdOffset = 50;
|
||||
static const MxU32 g_characterSoundIdMoodOffset = 66;
|
||||
@ -326,3 +328,42 @@ void CharacterCustomizer::StopClickAnimation(MxU32 p_objectId)
|
||||
action.SetObjectId(p_objectId);
|
||||
DeleteObject(action);
|
||||
}
|
||||
|
||||
bool CharacterCustomizer::ResolveClickChangeType(uint8_t& p_changeType, int& p_partIndex, LegoROI* p_clickedROI)
|
||||
{
|
||||
p_partIndex = -1;
|
||||
|
||||
switch (GameState()->GetActorId()) {
|
||||
case LegoActor::c_pepper:
|
||||
if (GameState()->GetCurrentAct() == LegoGameState::e_act2 ||
|
||||
GameState()->GetCurrentAct() == LegoGameState::e_act3) {
|
||||
return false;
|
||||
}
|
||||
p_changeType = CHANGE_VARIANT;
|
||||
break;
|
||||
case LegoActor::c_mama:
|
||||
p_changeType = CHANGE_SOUND;
|
||||
break;
|
||||
case LegoActor::c_papa:
|
||||
p_changeType = CHANGE_MOVE;
|
||||
break;
|
||||
case LegoActor::c_nick:
|
||||
p_changeType = CHANGE_COLOR;
|
||||
if (p_clickedROI) {
|
||||
p_partIndex = MapClickedPartIndex(p_clickedROI->GetName());
|
||||
}
|
||||
if (p_partIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case LegoActor::c_laura:
|
||||
p_changeType = CHANGE_MOOD;
|
||||
break;
|
||||
case LegoActor::c_brickster:
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
#include "extensions/multiplayer/customizestate.h"
|
||||
#include "extensions/common/customizestate.h"
|
||||
|
||||
#include "legoactors.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
using namespace Multiplayer;
|
||||
using namespace Extensions::Common;
|
||||
|
||||
void CustomizeState::InitFromActorInfo(uint8_t p_actorInfoIndex)
|
||||
{
|
||||
@ -3,6 +3,7 @@
|
||||
#include "extensions/multiplayer.h"
|
||||
#include "extensions/siloader.h"
|
||||
#include "extensions/textureloader.h"
|
||||
#include "extensions/thirdpersoncamera.h"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
|
||||
@ -11,14 +12,19 @@ void Extensions::Enable(const char* p_key, std::map<std::string, std::string> p_
|
||||
for (const char* key : availableExtensions) {
|
||||
if (!SDL_strcasecmp(p_key, key)) {
|
||||
if (!SDL_strcasecmp(p_key, "extensions:texture loader")) {
|
||||
TextureLoader::options = std::move(p_options);
|
||||
TextureLoader::enabled = true;
|
||||
TextureLoader::Initialize();
|
||||
TextureLoaderExt::options = std::move(p_options);
|
||||
TextureLoaderExt::enabled = true;
|
||||
TextureLoaderExt::Initialize();
|
||||
}
|
||||
else if (!SDL_strcasecmp(p_key, "extensions:si loader")) {
|
||||
SiLoader::options = std::move(p_options);
|
||||
SiLoader::enabled = true;
|
||||
SiLoader::Initialize();
|
||||
SiLoaderExt::options = std::move(p_options);
|
||||
SiLoaderExt::enabled = true;
|
||||
SiLoaderExt::Initialize();
|
||||
}
|
||||
else if (!SDL_strcasecmp(p_key, "extensions:third person camera")) {
|
||||
ThirdPersonCameraExt::options = std::move(p_options);
|
||||
ThirdPersonCameraExt::enabled = true;
|
||||
ThirdPersonCameraExt::Initialize();
|
||||
}
|
||||
else if (!SDL_strcasecmp(p_key, "extensions:multiplayer")) {
|
||||
MultiplayerExt::options = std::move(p_options);
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
#include "extensions/multiplayer.h"
|
||||
|
||||
#include "extensions/multiplayer/charactercustomizer.h"
|
||||
#include "extensions/common/charactercloner.h"
|
||||
#include "extensions/common/charactercustomizer.h"
|
||||
#include "extensions/common/constants.h"
|
||||
#include "extensions/multiplayer/networkmanager.h"
|
||||
#include "extensions/multiplayer/networktransport.h"
|
||||
#include "extensions/multiplayer/protocol.h"
|
||||
#include "extensions/thirdpersoncamera.h"
|
||||
#include "extensions/thirdpersoncamera/controller.h"
|
||||
#include "isle_actions.h"
|
||||
#include "islepathactor.h"
|
||||
#include "legoactor.h"
|
||||
#include "legoactors.h"
|
||||
#include "legoentity.h"
|
||||
#include "legoeventnotificationparam.h"
|
||||
#include "legogamestate.h"
|
||||
#include "legonavcontroller.h"
|
||||
#include "legopathactor.h"
|
||||
#include "misc.h"
|
||||
#include "roi/legoroi.h"
|
||||
@ -27,49 +29,48 @@
|
||||
|
||||
using namespace Extensions;
|
||||
|
||||
static uint8_t ResolveDisplayActorIndex(const char* p_name)
|
||||
{
|
||||
for (int i = 0; i < static_cast<int>(sizeOfArray(g_actorInfoInit)); i++) {
|
||||
if (!SDL_strcasecmp(g_actorInfoInit[i].m_name, p_name)) {
|
||||
return static_cast<uint8_t>(i);
|
||||
}
|
||||
}
|
||||
return Multiplayer::DISPLAY_ACTOR_NONE;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> MultiplayerExt::options;
|
||||
bool MultiplayerExt::enabled = false;
|
||||
std::string MultiplayerExt::relayUrl;
|
||||
std::string MultiplayerExt::room;
|
||||
std::string MultiplayerExt::s_relayUrl;
|
||||
std::string MultiplayerExt::s_room;
|
||||
Multiplayer::NetworkManager* MultiplayerExt::s_networkManager = nullptr;
|
||||
Multiplayer::NetworkTransport* MultiplayerExt::s_transport = nullptr;
|
||||
Multiplayer::PlatformCallbacks* MultiplayerExt::s_callbacks = nullptr;
|
||||
|
||||
void MultiplayerExt::Initialize()
|
||||
{
|
||||
relayUrl = options["multiplayer:relay url"];
|
||||
room = options["multiplayer:room"];
|
||||
// Multiplayer depends on camera - ensure it's enabled
|
||||
if (!ThirdPersonCameraExt::enabled) {
|
||||
ThirdPersonCameraExt::enabled = true;
|
||||
ThirdPersonCameraExt::Initialize();
|
||||
}
|
||||
|
||||
s_relayUrl = options["multiplayer:relay url"];
|
||||
s_room = options["multiplayer:room"];
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
s_transport = new Multiplayer::WebSocketTransport(relayUrl);
|
||||
s_transport = new Multiplayer::WebSocketTransport(s_relayUrl);
|
||||
s_callbacks = new Multiplayer::EmscriptenCallbacks();
|
||||
|
||||
s_networkManager = new Multiplayer::NetworkManager();
|
||||
s_networkManager->Initialize(s_transport, s_callbacks);
|
||||
|
||||
// Third-person camera enabled by default, toggled via WASM export
|
||||
s_networkManager->GetThirdPersonCamera().Enable();
|
||||
ThirdPersonCamera::Controller* cam = ThirdPersonCameraExt::GetCamera();
|
||||
if (cam) {
|
||||
cam->Enable();
|
||||
}
|
||||
|
||||
std::string actor = options["multiplayer:actor"];
|
||||
if (!actor.empty()) {
|
||||
uint8_t displayIndex = ResolveDisplayActorIndex(actor.c_str());
|
||||
if (displayIndex != Multiplayer::DISPLAY_ACTOR_NONE) {
|
||||
s_networkManager->SetDisplayActorIndex(displayIndex);
|
||||
uint8_t displayIndex = Common::ResolveDisplayActorIndex(actor.c_str());
|
||||
if (displayIndex != Common::DISPLAY_ACTOR_NONE && cam) {
|
||||
cam->SetDisplayActorIndex(displayIndex);
|
||||
cam->FreezeDisplayActor();
|
||||
}
|
||||
}
|
||||
|
||||
if (!relayUrl.empty() && !room.empty()) {
|
||||
s_networkManager->Connect(room.c_str());
|
||||
if (!s_relayUrl.empty() && !s_room.empty()) {
|
||||
s_networkManager->Connect(s_room.c_str());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -108,10 +109,8 @@ MxBool MultiplayerExt::HandleROIClick(LegoROI* p_rootROI, LegoEventNotificationP
|
||||
// Check if it's a remote player
|
||||
Multiplayer::RemotePlayer* remote = mgr->FindPlayerByROI(p_rootROI);
|
||||
|
||||
// Check if it's our own 3rd-person display actor override
|
||||
bool isSelf =
|
||||
(mgr->GetThirdPersonCamera().GetDisplayROI() != nullptr &&
|
||||
mgr->GetThirdPersonCamera().GetDisplayROI() == p_rootROI);
|
||||
ThirdPersonCamera::Controller* cam = ThirdPersonCameraExt::GetCamera();
|
||||
bool isSelf = (cam && cam->GetDisplayROI() != nullptr && cam->GetDisplayROI() == p_rootROI);
|
||||
|
||||
if (!remote && !isSelf) {
|
||||
return FALSE;
|
||||
@ -124,37 +123,9 @@ MxBool MultiplayerExt::HandleROIClick(LegoROI* p_rootROI, LegoEventNotificationP
|
||||
|
||||
// Determine change type from clicker's actor ID
|
||||
uint8_t changeType;
|
||||
int partIndex = -1;
|
||||
switch (GameState()->GetActorId()) {
|
||||
case LegoActor::c_pepper:
|
||||
if (GameState()->GetCurrentAct() == LegoGameState::e_act2 ||
|
||||
GameState()->GetCurrentAct() == LegoGameState::e_act3) {
|
||||
return TRUE;
|
||||
}
|
||||
changeType = Multiplayer::CHANGE_VARIANT;
|
||||
break;
|
||||
case LegoActor::c_mama:
|
||||
changeType = Multiplayer::CHANGE_SOUND;
|
||||
break;
|
||||
case LegoActor::c_papa:
|
||||
changeType = Multiplayer::CHANGE_MOVE;
|
||||
break;
|
||||
case LegoActor::c_nick:
|
||||
changeType = Multiplayer::CHANGE_COLOR;
|
||||
if (p_param.GetROI()) {
|
||||
partIndex = Multiplayer::CharacterCustomizer::MapClickedPartIndex(p_param.GetROI()->GetName());
|
||||
}
|
||||
if (partIndex < 0) {
|
||||
return TRUE;
|
||||
}
|
||||
break;
|
||||
case LegoActor::c_laura:
|
||||
changeType = Multiplayer::CHANGE_MOOD;
|
||||
break;
|
||||
case LegoActor::c_brickster:
|
||||
int partIndex;
|
||||
if (!Common::CharacterCustomizer::ResolveClickChangeType(changeType, partIndex, p_param.GetROI())) {
|
||||
return TRUE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Send a customize request to the server. The server echoes it back to all peers
|
||||
@ -263,27 +234,6 @@ void MultiplayerExt::HandleSaveLoaded()
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerExt::HandleActorEnter(IslePathActor* p_actor)
|
||||
{
|
||||
if (s_networkManager) {
|
||||
s_networkManager->GetThirdPersonCamera().OnActorEnter(p_actor);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerExt::HandleActorExit(IslePathActor* p_actor)
|
||||
{
|
||||
if (s_networkManager) {
|
||||
s_networkManager->GetThirdPersonCamera().OnActorExit(p_actor);
|
||||
}
|
||||
}
|
||||
|
||||
void MultiplayerExt::HandleCamAnimEnd(LegoPathActor* p_actor)
|
||||
{
|
||||
if (s_networkManager) {
|
||||
s_networkManager->GetThirdPersonCamera().OnCamAnimEnd(p_actor);
|
||||
}
|
||||
}
|
||||
|
||||
MxBool MultiplayerExt::IsClonedCharacter(const char* p_name)
|
||||
{
|
||||
if (!s_networkManager) {
|
||||
@ -293,93 +243,6 @@ MxBool MultiplayerExt::IsClonedCharacter(const char* p_name)
|
||||
return s_networkManager->IsClonedCharacter(p_name) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
void MultiplayerExt::HandleSDLEvent(SDL_Event* p_event)
|
||||
{
|
||||
if (!s_networkManager || !s_networkManager->IsInIsleWorld()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Multiplayer::ThirdPersonCamera& camera = s_networkManager->GetThirdPersonCamera();
|
||||
|
||||
camera.HandleSDLEvent(p_event);
|
||||
|
||||
// Auto-switch 3rd → 1st: zoom-in past minimum distance
|
||||
if (camera.ConsumeAutoDisable()) {
|
||||
camera.Disable();
|
||||
s_networkManager->NotifyThirdPersonChanged(false);
|
||||
}
|
||||
// Auto-switch 1st → 3rd: zoom-out from 1st person
|
||||
else if (camera.ConsumeAutoEnable()) {
|
||||
camera.ResetTouchState();
|
||||
camera.SetOrbitDistance(Multiplayer::ThirdPersonCamera::MIN_DISTANCE);
|
||||
camera.Enable();
|
||||
s_networkManager->NotifyThirdPersonChanged(true);
|
||||
}
|
||||
}
|
||||
|
||||
MxBool MultiplayerExt::IsThirdPersonCameraActive()
|
||||
{
|
||||
if (s_networkManager && s_networkManager->GetThirdPersonCamera().IsActive()) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
MxBool MultiplayerExt::HandleTouchInput(SDL_Event* p_event)
|
||||
{
|
||||
if (!s_networkManager || !s_networkManager->GetThirdPersonCamera().IsActive()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Multiplayer::ThirdPersonCamera& cam = s_networkManager->GetThirdPersonCamera();
|
||||
|
||||
switch (p_event->type) {
|
||||
case SDL_EVENT_FINGER_DOWN:
|
||||
if (cam.TryClaimFinger(p_event->tfinger)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
||||
case SDL_EVENT_FINGER_MOTION:
|
||||
if (cam.IsFingerTracked(p_event->tfinger.fingerID)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
||||
case SDL_EVENT_FINGER_UP:
|
||||
case SDL_EVENT_FINGER_CANCELED:
|
||||
if (cam.TryReleaseFinger(p_event->tfinger.fingerID)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
MxBool MultiplayerExt::HandleNavOverride(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime
|
||||
)
|
||||
{
|
||||
if (!s_networkManager) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Multiplayer::ThirdPersonCamera& cam = s_networkManager->GetThirdPersonCamera();
|
||||
if (!cam.IsActive()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return cam.HandleCameraRelativeMovement(p_nav, p_curPos, p_curDir, p_newPos, p_newDir, p_deltaTime);
|
||||
}
|
||||
|
||||
MxBool MultiplayerExt::CheckRejected()
|
||||
{
|
||||
if (s_networkManager && s_networkManager->WasRejected()) {
|
||||
@ -389,11 +252,6 @@ MxBool MultiplayerExt::CheckRejected()
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void MultiplayerExt::SetNetworkManager(Multiplayer::NetworkManager* p_networkManager)
|
||||
{
|
||||
s_networkManager = p_networkManager;
|
||||
}
|
||||
|
||||
Multiplayer::NetworkManager* MultiplayerExt::GetNetworkManager()
|
||||
{
|
||||
return s_networkManager;
|
||||
@ -401,10 +259,5 @@ Multiplayer::NetworkManager* MultiplayerExt::GetNetworkManager()
|
||||
|
||||
bool Extensions::IsMultiplayerRejected()
|
||||
{
|
||||
return Extension<MultiplayerExt>::Call(CheckRejected).value_or(FALSE);
|
||||
}
|
||||
|
||||
void Extensions::HandleMultiplayerSDLEvent(SDL_Event* p_event)
|
||||
{
|
||||
Extension<MultiplayerExt>::Call(HandleSDLEvent, p_event);
|
||||
return Extension<MultiplayerExt>::Call(MP::CheckRejected).value_or(FALSE);
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
#include "extensions/multiplayer/networkmanager.h"
|
||||
|
||||
#include "extensions/multiplayer/animdata.h"
|
||||
#include "extensions/multiplayer/charactercloner.h"
|
||||
#include "extensions/multiplayer/charactercustomizer.h"
|
||||
#include "extensions/common/animdata.h"
|
||||
#include "extensions/common/charactercustomizer.h"
|
||||
#include "extensions/multiplayer/namebubblerenderer.h"
|
||||
#include "extensions/thirdpersoncamera.h"
|
||||
#include "extensions/thirdpersoncamera/controller.h"
|
||||
#include "legoanimationmanager.h"
|
||||
#include "legogamestate.h"
|
||||
#include "legomain.h"
|
||||
@ -17,7 +19,10 @@
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <vector>
|
||||
|
||||
using namespace Extensions;
|
||||
using namespace Multiplayer;
|
||||
using Common::DetectVehicleType;
|
||||
using Common::IsMultiPartEmote;
|
||||
|
||||
template <typename T>
|
||||
void NetworkManager::SendMessage(const T& p_msg)
|
||||
@ -34,12 +39,11 @@ void NetworkManager::SendMessage(const T& p_msg)
|
||||
}
|
||||
|
||||
NetworkManager::NetworkManager()
|
||||
: m_transport(nullptr), m_callbacks(nullptr), m_localPeerId(0), m_hostPeerId(0), m_sequence(0),
|
||||
m_lastBroadcastTime(0), m_lastValidActorId(0), m_localWalkAnimId(0), m_localIdleAnimId(0),
|
||||
m_localDisplayActorIndex(DISPLAY_ACTOR_NONE), m_displayActorFrozen(false), m_localAllowRemoteCustomize(true),
|
||||
m_inIsleWorld(false), m_registered(false), m_pendingToggleThirdPerson(false), m_pendingToggleNameBubbles(false),
|
||||
m_pendingWalkAnim(-1), m_pendingIdleAnim(-1), m_pendingEmote(-1), m_pendingToggleAllowCustomize(false),
|
||||
m_showNameBubbles(true)
|
||||
: m_transport(nullptr), m_callbacks(nullptr), m_localNameBubble(nullptr), m_localPeerId(0), m_hostPeerId(0),
|
||||
m_sequence(0), m_lastBroadcastTime(0), m_lastValidActorId(0), m_localAllowRemoteCustomize(true),
|
||||
m_inIsleWorld(false), m_registered(false), m_pendingToggleThirdPerson(false),
|
||||
m_pendingToggleNameBubbles(false), m_pendingWalkAnim(-1), m_pendingIdleAnim(-1), m_pendingEmote(-1),
|
||||
m_pendingToggleAllowCustomize(false), m_showNameBubbles(true), m_lastCameraEnabled(false)
|
||||
{
|
||||
}
|
||||
|
||||
@ -48,20 +52,38 @@ NetworkManager::~NetworkManager()
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
static ThirdPersonCamera::Controller* GetCamera()
|
||||
{
|
||||
return ThirdPersonCameraExt::GetCamera();
|
||||
}
|
||||
|
||||
MxResult NetworkManager::Tickle()
|
||||
{
|
||||
// Derive display actor early so it is valid before ProcessPendingRequests
|
||||
// may toggle the 3rd-person camera (which needs a valid display actor index).
|
||||
{
|
||||
LegoPathActor* userActor = UserActor();
|
||||
if (userActor) {
|
||||
DeriveDisplayActorIndex(static_cast<LegoActor*>(userActor)->GetActorId());
|
||||
ProcessPendingRequests();
|
||||
|
||||
// Detect camera state changes for platform notification
|
||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||
if (cam) {
|
||||
bool cameraEnabled = cam->IsEnabled();
|
||||
if (cameraEnabled != m_lastCameraEnabled) {
|
||||
m_lastCameraEnabled = cameraEnabled;
|
||||
NotifyThirdPersonChanged(cameraEnabled);
|
||||
}
|
||||
|
||||
// Create local name bubble when display ROI becomes available
|
||||
if (m_showNameBubbles && !m_localNameBubble && cam->GetDisplayROI()) {
|
||||
char name[8];
|
||||
EncodeUsername(name);
|
||||
m_localNameBubble = new NameBubbleRenderer();
|
||||
m_localNameBubble->Create(name);
|
||||
}
|
||||
|
||||
// Update local name bubble position
|
||||
if (m_localNameBubble && cam->GetDisplayROI()) {
|
||||
m_localNameBubble->Update(cam->GetDisplayROI());
|
||||
}
|
||||
}
|
||||
|
||||
ProcessPendingRequests();
|
||||
m_thirdPersonCamera.Tick(0.016f);
|
||||
|
||||
if (!m_transport) {
|
||||
return SUCCESS;
|
||||
}
|
||||
@ -121,6 +143,9 @@ void NetworkManager::Shutdown()
|
||||
m_worldSync.SetTransport(nullptr);
|
||||
}
|
||||
|
||||
delete m_localNameBubble;
|
||||
m_localNameBubble = nullptr;
|
||||
|
||||
RemoveAllRemotePlayers();
|
||||
}
|
||||
|
||||
@ -156,7 +181,6 @@ void NetworkManager::OnWorldEnabled(LegoWorld* p_world)
|
||||
}
|
||||
|
||||
if (p_world->GetWorldId() == LegoOmni::e_act1) {
|
||||
m_thirdPersonCamera.OnWorldEnabled(p_world);
|
||||
m_inIsleWorld = true;
|
||||
m_worldSync.SetInIsleWorld(true);
|
||||
|
||||
@ -188,9 +212,16 @@ void NetworkManager::OnWorldDisabled(LegoWorld* p_world)
|
||||
}
|
||||
|
||||
if (p_world->GetWorldId() == LegoOmni::e_act1) {
|
||||
m_thirdPersonCamera.OnWorldDisabled(p_world);
|
||||
m_inIsleWorld = false;
|
||||
m_worldSync.SetInIsleWorld(false);
|
||||
|
||||
// Destroy local name bubble (ROI is about to be destroyed)
|
||||
if (m_localNameBubble) {
|
||||
m_localNameBubble->Destroy();
|
||||
delete m_localNameBubble;
|
||||
m_localNameBubble = nullptr;
|
||||
}
|
||||
|
||||
for (auto& [peerId, player] : m_remotePlayers) {
|
||||
player->SetVisible(false);
|
||||
player->SetNameBubbleVisible(false);
|
||||
@ -238,29 +269,35 @@ MxBool NetworkManager::HandleSkyLightMutation(uint8_t p_entityType, uint8_t p_ch
|
||||
|
||||
void NetworkManager::ProcessPendingRequests()
|
||||
{
|
||||
if (m_pendingToggleThirdPerson.exchange(false, std::memory_order_relaxed)) {
|
||||
if (m_thirdPersonCamera.IsEnabled()) {
|
||||
m_thirdPersonCamera.Disable();
|
||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||
|
||||
// Camera-dependent requests: only consume when cam is available so
|
||||
// the request survives until the camera exists.
|
||||
if (cam) {
|
||||
if (m_pendingToggleThirdPerson.exchange(false, std::memory_order_relaxed)) {
|
||||
if (cam->IsEnabled()) {
|
||||
cam->Disable();
|
||||
}
|
||||
else {
|
||||
cam->Enable();
|
||||
}
|
||||
NotifyThirdPersonChanged(cam->IsEnabled());
|
||||
}
|
||||
else {
|
||||
m_thirdPersonCamera.Enable();
|
||||
|
||||
int walkAnim = m_pendingWalkAnim.exchange(-1, std::memory_order_relaxed);
|
||||
if (walkAnim >= 0) {
|
||||
SetWalkAnimation(static_cast<uint8_t>(walkAnim));
|
||||
}
|
||||
NotifyThirdPersonChanged(m_thirdPersonCamera.IsEnabled());
|
||||
}
|
||||
|
||||
int walkAnim = m_pendingWalkAnim.exchange(-1, std::memory_order_relaxed);
|
||||
if (walkAnim >= 0) {
|
||||
SetWalkAnimation(static_cast<uint8_t>(walkAnim));
|
||||
}
|
||||
int idleAnim = m_pendingIdleAnim.exchange(-1, std::memory_order_relaxed);
|
||||
if (idleAnim >= 0) {
|
||||
SetIdleAnimation(static_cast<uint8_t>(idleAnim));
|
||||
}
|
||||
|
||||
int idleAnim = m_pendingIdleAnim.exchange(-1, std::memory_order_relaxed);
|
||||
if (idleAnim >= 0) {
|
||||
SetIdleAnimation(static_cast<uint8_t>(idleAnim));
|
||||
}
|
||||
|
||||
int emote = m_pendingEmote.exchange(-1, std::memory_order_relaxed);
|
||||
if (emote >= 0) {
|
||||
SendEmote(static_cast<uint8_t>(emote));
|
||||
int emote = m_pendingEmote.exchange(-1, std::memory_order_relaxed);
|
||||
if (emote >= 0) {
|
||||
SendEmote(static_cast<uint8_t>(emote));
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pendingToggleAllowCustomize.exchange(false, std::memory_order_relaxed)) {
|
||||
@ -273,7 +310,9 @@ void NetworkManager::ProcessPendingRequests()
|
||||
for (auto& [peerId, player] : m_remotePlayers) {
|
||||
player->SetNameBubbleVisible(m_showNameBubbles);
|
||||
}
|
||||
m_thirdPersonCamera.SetNameBubbleVisible(m_showNameBubbles);
|
||||
if (m_localNameBubble) {
|
||||
m_localNameBubble->SetVisible(m_showNameBubbles);
|
||||
}
|
||||
NotifyNameBubblesChanged(m_showNameBubbles);
|
||||
}
|
||||
}
|
||||
@ -313,6 +352,8 @@ void NetworkManager::BroadcastLocalState()
|
||||
return;
|
||||
}
|
||||
|
||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||
|
||||
PlayerStateMsg msg{};
|
||||
msg.header = {MSG_STATE, m_localPeerId, m_sequence++, TARGET_BROADCAST};
|
||||
msg.actorId = actorId;
|
||||
@ -322,27 +363,29 @@ void NetworkManager::BroadcastLocalState()
|
||||
SDL_memcpy(msg.direction, dir, sizeof(msg.direction));
|
||||
SDL_memcpy(msg.up, up, sizeof(msg.up));
|
||||
msg.speed = speed;
|
||||
msg.walkAnimId = m_localWalkAnimId;
|
||||
msg.idleAnimId = m_localIdleAnimId;
|
||||
|
||||
EncodeUsername(msg.name);
|
||||
|
||||
msg.displayActorIndex = m_localDisplayActorIndex;
|
||||
if (cam) {
|
||||
msg.walkAnimId = cam->GetWalkAnimId();
|
||||
msg.idleAnimId = cam->GetIdleAnimId();
|
||||
msg.displayActorIndex = cam->GetDisplayActorIndex();
|
||||
cam->GetCustomizeState().Pack(msg.customizeData);
|
||||
|
||||
m_thirdPersonCamera.GetCustomizeState().Pack(msg.customizeData);
|
||||
msg.customizeFlags = m_localAllowRemoteCustomize ? 0x01 : 0x00;
|
||||
// Encode multi-part emote frozen state (0x02 = frozen, emote ID in bits 2-4, max 8 emotes)
|
||||
int8_t frozenId = cam->GetFrozenEmoteId();
|
||||
if (frozenId >= 0) {
|
||||
msg.customizeFlags |= 0x02;
|
||||
msg.customizeFlags |= (frozenId & 0x07) << 2;
|
||||
}
|
||||
|
||||
// Encode multi-part emote frozen state (0x02 = frozen, emote ID in bits 2-4, max 8 emotes)
|
||||
int8_t frozenId = m_thirdPersonCamera.GetFrozenEmoteId();
|
||||
if (frozenId >= 0) {
|
||||
msg.customizeFlags |= 0x02;
|
||||
msg.customizeFlags |= (frozenId & 0x07) << 2;
|
||||
// Zero speed when in any phase of a multi-part emote
|
||||
if (cam->IsInMultiPartEmote()) {
|
||||
msg.speed = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Zero speed when in any phase of a multi-part emote
|
||||
if (m_thirdPersonCamera.IsInMultiPartEmote()) {
|
||||
msg.speed = 0.0f;
|
||||
}
|
||||
msg.customizeFlags |= m_localAllowRemoteCustomize ? 0x01 : 0x00;
|
||||
|
||||
SendMessage(msg);
|
||||
}
|
||||
@ -534,33 +577,38 @@ void NetworkManager::HandleHostAssign(const HostAssignMsg& p_msg)
|
||||
|
||||
void NetworkManager::SetWalkAnimation(uint8_t p_walkAnimId)
|
||||
{
|
||||
if (p_walkAnimId < g_walkAnimCount) {
|
||||
m_localWalkAnimId = p_walkAnimId;
|
||||
m_thirdPersonCamera.SetWalkAnimId(p_walkAnimId);
|
||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||
if (cam && p_walkAnimId < Common::g_walkAnimCount) {
|
||||
cam->SetWalkAnimId(p_walkAnimId);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkManager::SetIdleAnimation(uint8_t p_idleAnimId)
|
||||
{
|
||||
if (p_idleAnimId < g_idleAnimCount) {
|
||||
m_localIdleAnimId = p_idleAnimId;
|
||||
m_thirdPersonCamera.SetIdleAnimId(p_idleAnimId);
|
||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||
if (cam && p_idleAnimId < Common::g_idleAnimCount) {
|
||||
cam->SetIdleAnimId(p_idleAnimId);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkManager::SendEmote(uint8_t p_emoteId)
|
||||
{
|
||||
if (p_emoteId >= g_emoteAnimCount) {
|
||||
if (p_emoteId >= Common::g_emoteAnimCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||
if (!cam) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Multi-part emotes require 3rd person camera to be active (they need the display clone).
|
||||
// In 1st person mode, skip them entirely to avoid broadcasting an emote the local player can't play.
|
||||
if (!m_thirdPersonCamera.IsActive() && IsMultiPartEmote(p_emoteId)) {
|
||||
if (!cam->IsActive() && IsMultiPartEmote(p_emoteId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_thirdPersonCamera.TriggerEmote(p_emoteId);
|
||||
cam->TriggerEmote(p_emoteId);
|
||||
|
||||
EmoteMsg msg{};
|
||||
msg.header = {MSG_EMOTE, m_localPeerId, m_sequence++, TARGET_BROADCAST};
|
||||
@ -568,25 +616,6 @@ void NetworkManager::SendEmote(uint8_t p_emoteId)
|
||||
SendMessage(msg);
|
||||
}
|
||||
|
||||
void NetworkManager::SetDisplayActorIndex(uint8_t p_displayActorIndex)
|
||||
{
|
||||
m_localDisplayActorIndex = p_displayActorIndex;
|
||||
m_displayActorFrozen = true;
|
||||
m_thirdPersonCamera.SetDisplayActorIndex(p_displayActorIndex);
|
||||
}
|
||||
|
||||
void NetworkManager::DeriveDisplayActorIndex(uint8_t p_actorId)
|
||||
{
|
||||
if (m_displayActorFrozen || !IsValidActorId(p_actorId)) {
|
||||
return;
|
||||
}
|
||||
uint8_t derived = p_actorId - 1;
|
||||
if (derived != m_localDisplayActorIndex) {
|
||||
m_localDisplayActorIndex = derived;
|
||||
m_thirdPersonCamera.SetDisplayActorIndex(derived);
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkManager::HandleEmote(const EmoteMsg& p_msg)
|
||||
{
|
||||
uint32_t peerId = p_msg.header.peerId;
|
||||
@ -697,12 +726,6 @@ bool NetworkManager::IsClonedCharacter(const char* p_name) const
|
||||
}
|
||||
}
|
||||
|
||||
// Check local 3rd-person display actor clone
|
||||
if (m_thirdPersonCamera.GetDisplayROI() != nullptr &&
|
||||
!SDL_strcasecmp(m_thirdPersonCamera.GetDisplayROI()->GetName(), p_name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -721,7 +744,7 @@ void NetworkManager::HandleCustomize(const CustomizeMsg& p_msg)
|
||||
uint32_t targetPeerId = p_msg.targetPeerId;
|
||||
|
||||
// Check if the target is a remote player on this client.
|
||||
// Only play effects here — do NOT modify the remote player's customize state.
|
||||
// Only play effects here -- do NOT modify the remote player's customize state.
|
||||
// State changes come exclusively through UpdateFromNetwork (from the target's
|
||||
// authoritative PlayerStateMsg), which prevents flip-flop from stale state messages.
|
||||
// Note: sound/mood feedback uses the old state (before the authoritative update arrives),
|
||||
@ -729,15 +752,17 @@ void NetworkManager::HandleCustomize(const CustomizeMsg& p_msg)
|
||||
auto it = m_remotePlayers.find(targetPeerId);
|
||||
if (it != m_remotePlayers.end()) {
|
||||
if (it->second->GetROI()) {
|
||||
CharacterCustomizer::PlayClickSound(
|
||||
Common::CharacterCustomizer::PlayClickSound(
|
||||
it->second->GetROI(),
|
||||
it->second->GetCustomizeState(),
|
||||
p_msg.changeType == CHANGE_MOOD
|
||||
);
|
||||
if (!it->second->IsMoving() && !it->second->IsInMultiPartEmote()) {
|
||||
it->second->StopClickAnimation();
|
||||
MxU32 clickAnimId =
|
||||
CharacterCustomizer::PlayClickAnimation(it->second->GetROI(), it->second->GetCustomizeState());
|
||||
MxU32 clickAnimId = Common::CharacterCustomizer::PlayClickAnimation(
|
||||
it->second->GetROI(),
|
||||
it->second->GetCustomizeState()
|
||||
);
|
||||
it->second->SetClickAnimObjectId(clickAnimId);
|
||||
}
|
||||
}
|
||||
@ -751,31 +776,33 @@ void NetworkManager::HandleCustomize(const CustomizeMsg& p_msg)
|
||||
return;
|
||||
}
|
||||
|
||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||
if (!cam) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ApplyCustomizeChange handles null display ROI (advances state without visual)
|
||||
m_thirdPersonCamera.ApplyCustomizeChange(p_msg.changeType, p_msg.partIndex);
|
||||
cam->ApplyCustomizeChange(p_msg.changeType, p_msg.partIndex);
|
||||
|
||||
// Use display ROI for effects in 3rd person, native ROI in 1st person
|
||||
LegoROI* effectROI = m_thirdPersonCamera.GetDisplayROI();
|
||||
LegoROI* effectROI = cam->GetDisplayROI();
|
||||
if (!effectROI && UserActor()) {
|
||||
effectROI = UserActor()->GetROI();
|
||||
}
|
||||
|
||||
if (effectROI) {
|
||||
CharacterCustomizer::PlayClickSound(
|
||||
Common::CharacterCustomizer::PlayClickSound(
|
||||
effectROI,
|
||||
m_thirdPersonCamera.GetCustomizeState(),
|
||||
cam->GetCustomizeState(),
|
||||
p_msg.changeType == CHANGE_MOOD
|
||||
);
|
||||
|
||||
// Only play click animation in 3rd person (not visible in 1st person or multi-part emote)
|
||||
if (m_thirdPersonCamera.GetDisplayROI() && !m_thirdPersonCamera.IsInVehicle() &&
|
||||
!m_thirdPersonCamera.IsInMultiPartEmote()) {
|
||||
m_thirdPersonCamera.StopClickAnimation();
|
||||
MxU32 clickAnimId = CharacterCustomizer::PlayClickAnimation(
|
||||
m_thirdPersonCamera.GetDisplayROI(),
|
||||
m_thirdPersonCamera.GetCustomizeState()
|
||||
);
|
||||
m_thirdPersonCamera.SetClickAnimObjectId(clickAnimId);
|
||||
if (cam->GetDisplayROI() && !cam->IsInVehicle() && !cam->IsInMultiPartEmote()) {
|
||||
cam->StopClickAnimation();
|
||||
MxU32 clickAnimId =
|
||||
Common::CharacterCustomizer::PlayClickAnimation(cam->GetDisplayROI(), cam->GetCustomizeState());
|
||||
cam->SetClickAnimObjectId(clickAnimId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
#include "extensions/multiplayer/remoteplayer.h"
|
||||
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "extensions/multiplayer/charactercloner.h"
|
||||
#include "extensions/multiplayer/charactercustomizer.h"
|
||||
#include "extensions/common/charactercloner.h"
|
||||
#include "extensions/common/charactercustomizer.h"
|
||||
#include "extensions/multiplayer/namebubblerenderer.h"
|
||||
#include "legocharactermanager.h"
|
||||
#include "legovideomanager.h"
|
||||
#include "legoworld.h"
|
||||
@ -15,14 +16,20 @@
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <vec.h>
|
||||
|
||||
using namespace Extensions;
|
||||
using namespace Multiplayer;
|
||||
using Common::DetectVehicleType;
|
||||
using Common::g_idleAnimCount;
|
||||
using Common::g_vehicleROINames;
|
||||
using Common::g_walkAnimCount;
|
||||
using Common::IsLargeVehicle;
|
||||
|
||||
RemotePlayer::RemotePlayer(uint32_t p_peerId, uint8_t p_actorId, uint8_t p_displayActorIndex)
|
||||
: m_peerId(p_peerId), m_actorId(p_actorId), m_displayActorIndex(p_displayActorIndex), m_roi(nullptr),
|
||||
m_spawned(false), m_visible(false), m_targetSpeed(0.0f), m_targetVehicleType(VEHICLE_NONE), m_targetWorldId(-1),
|
||||
m_lastUpdateTime(SDL_GetTicks()), m_hasReceivedUpdate(false),
|
||||
m_animator(CharacterAnimatorConfig{/*.saveEmoteTransform=*/false}), m_vehicleROI(nullptr),
|
||||
m_allowRemoteCustomize(true)
|
||||
m_animator(Common::CharacterAnimatorConfig{/*.saveEmoteTransform=*/false}), m_vehicleROI(nullptr),
|
||||
m_nameBubble(nullptr), m_allowRemoteCustomize(true)
|
||||
{
|
||||
m_displayName[0] = '\0';
|
||||
const char* displayName = GetDisplayActorName();
|
||||
@ -62,7 +69,7 @@ void RemotePlayer::Spawn(LegoWorld* p_isleWorld)
|
||||
return;
|
||||
}
|
||||
|
||||
m_roi = CharacterCloner::Clone(charMgr, m_uniqueName, actorName);
|
||||
m_roi = Common::CharacterCloner::Clone(charMgr, m_uniqueName, actorName);
|
||||
if (!m_roi) {
|
||||
return;
|
||||
}
|
||||
@ -75,7 +82,7 @@ void RemotePlayer::Spawn(LegoWorld* p_isleWorld)
|
||||
m_visible = false;
|
||||
|
||||
// Initialize customize state from the display actor's info
|
||||
uint8_t actorInfoIndex = CharacterCustomizer::ResolveActorInfoIndex(m_displayActorIndex);
|
||||
uint8_t actorInfoIndex = Common::CharacterCustomizer::ResolveActorInfoIndex(m_displayActorIndex);
|
||||
m_customizeState.InitFromActorInfo(actorInfoIndex);
|
||||
|
||||
// Build initial walk and idle animation caches
|
||||
@ -150,14 +157,14 @@ void RemotePlayer::UpdateFromNetwork(const PlayerStateMsg& p_msg)
|
||||
}
|
||||
|
||||
// Update customize state from packed data
|
||||
CustomizeState newState;
|
||||
Common::CustomizeState newState;
|
||||
newState.Unpack(p_msg.customizeData);
|
||||
|
||||
if (newState != m_customizeState) {
|
||||
uint8_t actorInfoIndex = CharacterCustomizer::ResolveActorInfoIndex(m_displayActorIndex);
|
||||
uint8_t actorInfoIndex = Common::CharacterCustomizer::ResolveActorInfoIndex(m_displayActorIndex);
|
||||
m_customizeState = newState;
|
||||
if (m_spawned && m_roi) {
|
||||
CharacterCustomizer::ApplyFullState(m_roi, actorInfoIndex, m_customizeState);
|
||||
Common::CharacterCustomizer::ApplyFullState(m_roi, actorInfoIndex, m_customizeState);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,7 +205,9 @@ void RemotePlayer::Tick(float p_deltaTime)
|
||||
m_animator.Tick(p_deltaTime, m_roi, isMoving);
|
||||
|
||||
// Update name bubble position and billboard orientation
|
||||
m_animator.UpdateNameBubble(m_roi);
|
||||
if (m_nameBubble) {
|
||||
m_nameBubble->Update(m_roi);
|
||||
}
|
||||
}
|
||||
|
||||
void RemotePlayer::ReAddToScene()
|
||||
@ -346,17 +355,26 @@ void RemotePlayer::ExitVehicle()
|
||||
|
||||
void RemotePlayer::CreateNameBubble()
|
||||
{
|
||||
m_animator.CreateNameBubble(m_displayName);
|
||||
if (!m_nameBubble) {
|
||||
m_nameBubble = new NameBubbleRenderer();
|
||||
}
|
||||
m_nameBubble->Create(m_displayName);
|
||||
}
|
||||
|
||||
void RemotePlayer::DestroyNameBubble()
|
||||
{
|
||||
m_animator.DestroyNameBubble();
|
||||
if (m_nameBubble) {
|
||||
m_nameBubble->Destroy();
|
||||
delete m_nameBubble;
|
||||
m_nameBubble = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void RemotePlayer::SetNameBubbleVisible(bool p_visible)
|
||||
{
|
||||
m_animator.SetNameBubbleVisible(p_visible);
|
||||
if (m_nameBubble) {
|
||||
m_nameBubble->SetVisible(p_visible);
|
||||
}
|
||||
}
|
||||
|
||||
void RemotePlayer::StopClickAnimation()
|
||||
|
||||
@ -66,8 +66,6 @@ export class GameRoom implements DurableObject {
|
||||
return new Response(null, { status: 101, webSocket: client });
|
||||
}
|
||||
|
||||
// ---- HTTP API ----
|
||||
|
||||
private async handleHttpRequest(request: Request): Promise<Response> {
|
||||
const method = request.method.toUpperCase();
|
||||
|
||||
@ -132,8 +130,6 @@ export class GameRoom implements DurableObject {
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Connection lifecycle ----
|
||||
|
||||
private assignHostIfNeeded(peerId: number, ws: WebSocket): void {
|
||||
if (this.hostPeerId === 0 || !this.connections.has(this.hostPeerId)) {
|
||||
this.hostPeerId = peerId;
|
||||
@ -152,8 +148,6 @@ export class GameRoom implements DurableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Message routing ----
|
||||
|
||||
private handleMessage(event: MessageEvent, peerId: number): void {
|
||||
if (!(event.data instanceof ArrayBuffer)) {
|
||||
return;
|
||||
@ -194,8 +188,6 @@ export class GameRoom implements DurableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Broadcasting ----
|
||||
|
||||
private broadcast(msg: ArrayBuffer): void {
|
||||
for (const ws of this.connections.values()) {
|
||||
this.trySend(ws, msg);
|
||||
@ -221,8 +213,6 @@ export class GameRoom implements DurableObject {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Host election ----
|
||||
|
||||
private electNewHost(): void {
|
||||
this.hostPeerId = 0;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -159,8 +159,6 @@ void WorldStateSync::HandleWorldEventRequest(const WorldEventRequestMsg& p_msg)
|
||||
BroadcastWorldEvent(p_msg.entityType, p_msg.changeType, p_msg.entityIndex);
|
||||
}
|
||||
|
||||
// ---- Entity mutation routing ----
|
||||
|
||||
template <typename TInfo>
|
||||
static int FindEntityIndex(TInfo* p_infoArray, MxS32 p_count, LegoEntity* p_entity)
|
||||
{
|
||||
@ -225,8 +223,6 @@ MxBool WorldStateSync::HandleSkyLightMutation(uint8_t p_entityType, uint8_t p_ch
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Send helpers ----
|
||||
|
||||
void WorldStateSync::SendSnapshotRequest()
|
||||
{
|
||||
RequestSnapshotMsg msg{};
|
||||
@ -298,8 +294,6 @@ void WorldStateSync::SendWorldEventRequest(uint8_t p_entityType, uint8_t p_chang
|
||||
SendMessage(msg);
|
||||
}
|
||||
|
||||
// ---- Apply world events ----
|
||||
|
||||
// Dispatch Switch*() calls shared by all entity types.
|
||||
// Returns true if the change was handled, false for type-specific changes.
|
||||
static bool DispatchEntitySwitch(LegoEntity* p_entity, uint8_t p_changeType)
|
||||
|
||||
@ -13,38 +13,38 @@ using namespace Extensions;
|
||||
|
||||
const char prependedMarker[] = ";;prepended;;";
|
||||
|
||||
std::map<std::string, std::string> SiLoader::options;
|
||||
std::vector<std::string> SiLoader::files;
|
||||
std::vector<std::string> SiLoader::directives;
|
||||
std::vector<std::pair<SiLoader::StreamObject, SiLoader::StreamObject>> SiLoader::startWith;
|
||||
std::vector<std::pair<SiLoader::StreamObject, SiLoader::StreamObject>> SiLoader::removeWith;
|
||||
std::vector<std::pair<SiLoader::StreamObject, SiLoader::StreamObject>> SiLoader::replace;
|
||||
std::vector<std::pair<SiLoader::StreamObject, SiLoader::StreamObject>> SiLoader::prepend;
|
||||
std::vector<SiLoader::StreamObject> SiLoader::fullScreenMovie;
|
||||
std::vector<SiLoader::StreamObject> SiLoader::disable3d;
|
||||
bool SiLoader::enabled = false;
|
||||
std::map<std::string, std::string> SiLoaderExt::options;
|
||||
std::vector<std::string> SiLoaderExt::files;
|
||||
std::vector<std::string> SiLoaderExt::directives;
|
||||
std::vector<std::pair<SiLoaderExt::StreamObject, SiLoaderExt::StreamObject>> SiLoaderExt::startWith;
|
||||
std::vector<std::pair<SiLoaderExt::StreamObject, SiLoaderExt::StreamObject>> SiLoaderExt::removeWith;
|
||||
std::vector<std::pair<SiLoaderExt::StreamObject, SiLoaderExt::StreamObject>> SiLoaderExt::replace;
|
||||
std::vector<std::pair<SiLoaderExt::StreamObject, SiLoaderExt::StreamObject>> SiLoaderExt::prepend;
|
||||
std::vector<SiLoaderExt::StreamObject> SiLoaderExt::fullScreenMovie;
|
||||
std::vector<SiLoaderExt::StreamObject> SiLoaderExt::disable3d;
|
||||
bool SiLoaderExt::enabled = false;
|
||||
|
||||
void SiLoader::Initialize()
|
||||
void SiLoaderExt::Initialize()
|
||||
{
|
||||
char* files = SDL_strdup(options["si loader:files"].c_str());
|
||||
char* saveptr;
|
||||
|
||||
for (char* file = SDL_strtok_r(files, ",\n\r ", &saveptr); file; file = SDL_strtok_r(NULL, ",\n\r ", &saveptr)) {
|
||||
SiLoader::files.emplace_back(file);
|
||||
SiLoaderExt::files.emplace_back(file);
|
||||
}
|
||||
|
||||
char* directives = SDL_strdup(options["si loader:directives"].c_str());
|
||||
|
||||
for (char* directive = SDL_strtok_r(directives, ",\n\r ", &saveptr); directive;
|
||||
directive = SDL_strtok_r(NULL, ",\n\r ", &saveptr)) {
|
||||
SiLoader::directives.emplace_back(directive);
|
||||
SiLoaderExt::directives.emplace_back(directive);
|
||||
}
|
||||
|
||||
SDL_free(files);
|
||||
SDL_free(directives);
|
||||
}
|
||||
|
||||
bool SiLoader::Load()
|
||||
bool SiLoaderExt::Load()
|
||||
{
|
||||
for (const auto& file : files) {
|
||||
LoadFile(file.c_str());
|
||||
@ -57,7 +57,7 @@ bool SiLoader::Load()
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<MxCore*> SiLoader::HandleFind(StreamObject p_object, LegoWorld* world)
|
||||
std::optional<MxCore*> SiLoaderExt::HandleFind(StreamObject p_object, LegoWorld* world)
|
||||
{
|
||||
for (const auto& key : replace) {
|
||||
if (key.first == p_object) {
|
||||
@ -68,7 +68,7 @@ std::optional<MxCore*> SiLoader::HandleFind(StreamObject p_object, LegoWorld* wo
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<MxResult> SiLoader::HandleStart(MxDSAction& p_action)
|
||||
std::optional<MxResult> SiLoaderExt::HandleStart(MxDSAction& p_action)
|
||||
{
|
||||
StreamObject object{p_action.GetAtomId(), p_action.GetObjectId()};
|
||||
auto start = [](const StreamObject& p_object, MxDSAction& p_in, MxDSAction& p_out) -> MxResult {
|
||||
@ -130,7 +130,7 @@ std::optional<MxResult> SiLoader::HandleStart(MxDSAction& p_action)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MxBool SiLoader::HandleWorld(LegoWorld* p_world)
|
||||
MxBool SiLoaderExt::HandleWorld(LegoWorld* p_world)
|
||||
{
|
||||
StreamObject object{p_world->GetAtomId(), p_world->GetEntityId()};
|
||||
auto start = [](const StreamObject& p_object, MxDSAction& p_out) {
|
||||
@ -154,7 +154,7 @@ MxBool SiLoader::HandleWorld(LegoWorld* p_world)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
std::optional<MxBool> SiLoader::HandleRemove(StreamObject p_object, LegoWorld* world)
|
||||
std::optional<MxBool> SiLoaderExt::HandleRemove(StreamObject p_object, LegoWorld* world)
|
||||
{
|
||||
for (const auto& key : removeWith) {
|
||||
if (key.first == p_object) {
|
||||
@ -171,7 +171,7 @@ std::optional<MxBool> SiLoader::HandleRemove(StreamObject p_object, LegoWorld* w
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<MxBool> SiLoader::HandleDelete(MxDSAction& p_action)
|
||||
std::optional<MxBool> SiLoaderExt::HandleDelete(MxDSAction& p_action)
|
||||
{
|
||||
StreamObject object{p_action.GetAtomId(), p_action.GetObjectId()};
|
||||
auto deleteObject = [](const StreamObject& p_object, MxDSAction& p_in, MxDSAction& p_out) {
|
||||
@ -202,7 +202,7 @@ std::optional<MxBool> SiLoader::HandleDelete(MxDSAction& p_action)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MxBool SiLoader::HandleEndAction(MxEndActionNotificationParam& p_param)
|
||||
MxBool SiLoaderExt::HandleEndAction(MxEndActionNotificationParam& p_param)
|
||||
{
|
||||
StreamObject object{p_param.GetAction()->GetAtomId(), p_param.GetAction()->GetObjectId()};
|
||||
auto start = [](const StreamObject& p_object, MxDSAction& p_in, MxDSAction& p_out) -> MxResult {
|
||||
@ -235,7 +235,7 @@ MxBool SiLoader::HandleEndAction(MxEndActionNotificationParam& p_param)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
bool SiLoader::LoadFile(const char* p_file)
|
||||
bool SiLoaderExt::LoadFile(const char* p_file)
|
||||
{
|
||||
si::Interleaf si;
|
||||
MxStreamController* controller;
|
||||
@ -245,8 +245,7 @@ bool SiLoader::LoadFile(const char* p_file)
|
||||
if (si.Read(path.GetData(), si::Interleaf::ObjectsOnly) != si::Interleaf::ERROR_SUCCESS) {
|
||||
path = MxString(MxOmni::GetCD()) + p_file;
|
||||
path.MapPathToFilesystem();
|
||||
if (si.Read(path.GetData(), si::Interleaf::ObjectsOnly) !=
|
||||
si::Interleaf::ERROR_SUCCESS) {
|
||||
if (si.Read(path.GetData(), si::Interleaf::ObjectsOnly) != si::Interleaf::ERROR_SUCCESS) {
|
||||
SDL_Log("Could not parse SI file %s", p_file);
|
||||
return false;
|
||||
}
|
||||
@ -260,7 +259,7 @@ bool SiLoader::LoadFile(const char* p_file)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SiLoader::LoadDirective(const char* p_directive)
|
||||
bool SiLoaderExt::LoadDirective(const char* p_directive)
|
||||
{
|
||||
char originAtom[256], targetAtom[256];
|
||||
uint32_t originId, targetId;
|
||||
@ -306,7 +305,7 @@ bool SiLoader::LoadDirective(const char* p_directive)
|
||||
return true;
|
||||
}
|
||||
|
||||
MxStreamController* SiLoader::OpenStream(const char* p_file)
|
||||
MxStreamController* SiLoaderExt::OpenStream(const char* p_file)
|
||||
{
|
||||
MxStreamController* controller;
|
||||
|
||||
@ -318,7 +317,7 @@ MxStreamController* SiLoader::OpenStream(const char* p_file)
|
||||
return controller;
|
||||
}
|
||||
|
||||
void SiLoader::ParseExtra(const MxAtomId& p_atom, si::Core* p_core)
|
||||
void SiLoaderExt::ParseExtra(const MxAtomId& p_atom, si::Core* p_core)
|
||||
{
|
||||
for (si::Core* child : p_core->GetChildren()) {
|
||||
if (si::Object* object = dynamic_cast<si::Object*>(child)) {
|
||||
@ -378,7 +377,7 @@ void SiLoader::ParseExtra(const MxAtomId& p_atom, si::Core* p_core)
|
||||
}
|
||||
}
|
||||
|
||||
bool SiLoader::IsWorld(const StreamObject& p_object)
|
||||
bool SiLoaderExt::IsWorld(const StreamObject& p_object)
|
||||
{
|
||||
// The convention in LEGO Island is that world objects are always at ID 0
|
||||
if (p_object.second == 0) {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
#include "extensions/textureloader.h"
|
||||
|
||||
#include "legovideomanager.h"
|
||||
#include "misc.h"
|
||||
#include "mxdirectx/mxdirect3d.h"
|
||||
@ -7,11 +8,11 @@
|
||||
|
||||
using namespace Extensions;
|
||||
|
||||
std::map<std::string, std::string> TextureLoader::options;
|
||||
std::vector<std::string> TextureLoader::excludedFiles;
|
||||
bool TextureLoader::enabled = false;
|
||||
std::map<std::string, std::string> TextureLoaderExt::options;
|
||||
std::vector<std::string> TextureLoaderExt::excludedFiles;
|
||||
bool TextureLoaderExt::enabled = false;
|
||||
|
||||
void TextureLoader::Initialize()
|
||||
void TextureLoaderExt::Initialize()
|
||||
{
|
||||
for (const auto& option : defaults) {
|
||||
if (!options.count(option.first.data())) {
|
||||
@ -20,7 +21,12 @@ void TextureLoader::Initialize()
|
||||
}
|
||||
}
|
||||
|
||||
bool TextureLoader::PatchTexture(LegoTextureInfo* p_textureInfo)
|
||||
void TextureLoaderExt::AddExcludedFile(const std::string& p_file)
|
||||
{
|
||||
excludedFiles.emplace_back(p_file);
|
||||
}
|
||||
|
||||
bool TextureLoaderExt::PatchTexture(LegoTextureInfo* p_textureInfo)
|
||||
{
|
||||
SDL_Surface* surface = FindTexture(p_textureInfo->m_name);
|
||||
if (!surface) {
|
||||
@ -103,7 +109,7 @@ bool TextureLoader::PatchTexture(LegoTextureInfo* p_textureInfo)
|
||||
return true;
|
||||
}
|
||||
|
||||
SDL_Surface* TextureLoader::FindTexture(const char* p_name)
|
||||
SDL_Surface* TextureLoaderExt::FindTexture(const char* p_name)
|
||||
{
|
||||
if (std::find(excludedFiles.begin(), excludedFiles.end(), p_name) != excludedFiles.end()) {
|
||||
return nullptr;
|
||||
|
||||
241
extensions/src/thirdpersoncamera.cpp
Normal file
241
extensions/src/thirdpersoncamera.cpp
Normal file
@ -0,0 +1,241 @@
|
||||
#include "extensions/thirdpersoncamera.h"
|
||||
|
||||
#include "extensions/common/charactercustomizer.h"
|
||||
#include "extensions/thirdpersoncamera/controller.h"
|
||||
#include "islepathactor.h"
|
||||
#include "legoeventnotificationparam.h"
|
||||
#include "legonavcontroller.h"
|
||||
#include "legopathactor.h"
|
||||
#include "legovideomanager.h"
|
||||
#include "misc.h"
|
||||
#include "mxcore.h"
|
||||
#include "mxmisc.h"
|
||||
#include "mxticklemanager.h"
|
||||
#include "realtime/vector.h"
|
||||
#include "roi/legoroi.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
using namespace Extensions;
|
||||
using namespace Extensions::Common;
|
||||
|
||||
std::map<std::string, std::string> ThirdPersonCameraExt::options;
|
||||
bool ThirdPersonCameraExt::enabled = false;
|
||||
ThirdPersonCamera::Controller* ThirdPersonCameraExt::s_camera = nullptr;
|
||||
bool ThirdPersonCameraExt::s_registered = false;
|
||||
bool ThirdPersonCameraExt::s_inIsleWorld = false;
|
||||
|
||||
namespace Extensions
|
||||
{
|
||||
namespace ThirdPersonCamera
|
||||
{
|
||||
|
||||
class TickleAdapter : public MxCore {
|
||||
public:
|
||||
TickleAdapter(Controller* p_camera) : m_camera(p_camera) {}
|
||||
|
||||
MxResult Tickle() override
|
||||
{
|
||||
if (m_camera) {
|
||||
m_camera->Tick(0.016f);
|
||||
}
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
const char* ClassName() const override { return "ThirdPersonCamera::TickleAdapter"; }
|
||||
|
||||
private:
|
||||
Controller* m_camera;
|
||||
};
|
||||
|
||||
} // namespace ThirdPersonCamera
|
||||
} // namespace Extensions
|
||||
|
||||
static Extensions::ThirdPersonCamera::TickleAdapter* s_tickleAdapter = nullptr;
|
||||
|
||||
void ThirdPersonCameraExt::Initialize()
|
||||
{
|
||||
if (!s_camera) {
|
||||
s_camera = new ThirdPersonCamera::Controller();
|
||||
}
|
||||
s_camera->Enable();
|
||||
}
|
||||
|
||||
void ThirdPersonCameraExt::HandleCreate()
|
||||
{
|
||||
if (!s_registered && s_camera) {
|
||||
s_tickleAdapter = new Extensions::ThirdPersonCamera::TickleAdapter(s_camera);
|
||||
TickleManager()->RegisterClient(s_tickleAdapter, 10);
|
||||
s_registered = true;
|
||||
}
|
||||
}
|
||||
|
||||
MxBool ThirdPersonCameraExt::HandleWorldEnable(LegoWorld* p_world, MxBool p_enable)
|
||||
{
|
||||
if (!s_camera) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (p_enable) {
|
||||
s_camera->OnWorldEnabled(p_world);
|
||||
s_inIsleWorld = true;
|
||||
}
|
||||
else {
|
||||
s_camera->OnWorldDisabled(p_world);
|
||||
s_inIsleWorld = false;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void ThirdPersonCameraExt::HandleActorEnter(IslePathActor* p_actor)
|
||||
{
|
||||
if (s_camera) {
|
||||
s_camera->OnActorEnter(p_actor);
|
||||
}
|
||||
}
|
||||
|
||||
void ThirdPersonCameraExt::HandleActorExit(IslePathActor* p_actor)
|
||||
{
|
||||
if (s_camera) {
|
||||
s_camera->OnActorExit(p_actor);
|
||||
}
|
||||
}
|
||||
|
||||
void ThirdPersonCameraExt::HandleCamAnimEnd(LegoPathActor* p_actor)
|
||||
{
|
||||
if (s_camera) {
|
||||
s_camera->OnCamAnimEnd(p_actor);
|
||||
}
|
||||
}
|
||||
|
||||
void ThirdPersonCameraExt::OnSDLEvent(SDL_Event* p_event)
|
||||
{
|
||||
if (!s_camera || !s_inIsleWorld) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_camera->HandleSDLEventImpl(p_event);
|
||||
|
||||
if (s_camera->ConsumeAutoDisable()) {
|
||||
s_camera->Disable();
|
||||
}
|
||||
else if (s_camera->ConsumeAutoEnable()) {
|
||||
s_camera->ResetTouchState();
|
||||
s_camera->SetOrbitDistance(ThirdPersonCamera::Controller::MIN_DISTANCE);
|
||||
s_camera->Enable();
|
||||
}
|
||||
}
|
||||
|
||||
MxBool ThirdPersonCameraExt::IsThirdPersonCameraActive()
|
||||
{
|
||||
if (s_camera && s_camera->IsActive()) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
MxBool ThirdPersonCameraExt::HandleTouchInput(SDL_Event* p_event)
|
||||
{
|
||||
if (!s_camera || !s_camera->IsActive()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
switch (p_event->type) {
|
||||
case SDL_EVENT_FINGER_DOWN:
|
||||
if (s_camera->TryClaimFinger(p_event->tfinger)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
||||
case SDL_EVENT_FINGER_MOTION:
|
||||
if (s_camera->IsFingerTracked(p_event->tfinger.fingerID)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
||||
case SDL_EVENT_FINGER_UP:
|
||||
case SDL_EVENT_FINGER_CANCELED:
|
||||
if (s_camera->TryReleaseFinger(p_event->tfinger.fingerID)) {
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
MxBool ThirdPersonCameraExt::HandleNavOverride(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime
|
||||
)
|
||||
{
|
||||
if (!s_camera) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!s_camera->IsActive()) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return s_camera->HandleCameraRelativeMovement(p_nav, p_curPos, p_curDir, p_newPos, p_newDir, p_deltaTime);
|
||||
}
|
||||
|
||||
MxBool ThirdPersonCameraExt::HandleROIClick(LegoROI* p_rootROI, LegoEventNotificationParam& p_param)
|
||||
{
|
||||
if (!s_camera) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!s_camera->GetDisplayROI() || s_camera->GetDisplayROI() != p_rootROI) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
uint8_t changeType;
|
||||
int partIndex;
|
||||
if (!CharacterCustomizer::ResolveClickChangeType(changeType, partIndex, p_param.GetROI())) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
s_camera->ApplyCustomizeChange(changeType, static_cast<uint8_t>(partIndex >= 0 ? partIndex : 0xFF));
|
||||
|
||||
LegoROI* effectROI = s_camera->GetDisplayROI();
|
||||
if (effectROI) {
|
||||
CharacterCustomizer::PlayClickSound(effectROI, s_camera->GetCustomizeState(), changeType == CHANGE_MOOD);
|
||||
if (!s_camera->IsInVehicle() && !s_camera->IsInMultiPartEmote()) {
|
||||
s_camera->StopClickAnimation();
|
||||
MxU32 clickAnimId = CharacterCustomizer::PlayClickAnimation(effectROI, s_camera->GetCustomizeState());
|
||||
s_camera->SetClickAnimObjectId(clickAnimId);
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
MxBool ThirdPersonCameraExt::IsClonedCharacter(const char* p_name)
|
||||
{
|
||||
if (!s_camera) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (s_camera->GetDisplayROI() != nullptr && !SDL_strcasecmp(s_camera->GetDisplayROI()->GetName(), p_name)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ThirdPersonCamera::Controller* ThirdPersonCameraExt::GetCamera()
|
||||
{
|
||||
return s_camera;
|
||||
}
|
||||
|
||||
void ThirdPersonCameraExt::HandleSDLEvent(SDL_Event* p_event)
|
||||
{
|
||||
Extension<ThirdPersonCameraExt>::Call(TP::HandleSDLEvent, p_event);
|
||||
}
|
||||
429
extensions/src/thirdpersoncamera/controller.cpp
Normal file
429
extensions/src/thirdpersoncamera/controller.cpp
Normal file
@ -0,0 +1,429 @@
|
||||
#include "extensions/thirdpersoncamera/controller.h"
|
||||
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "anim/legoanim.h"
|
||||
#include "extensions/common/animutils.h"
|
||||
#include "extensions/common/charactercustomizer.h"
|
||||
#include "extensions/common/constants.h"
|
||||
#include "islepathactor.h"
|
||||
#include "legoactor.h"
|
||||
#include "legoactors.h"
|
||||
#include "legocameracontroller.h"
|
||||
#include "legonavcontroller.h"
|
||||
#include "legopathactor.h"
|
||||
#include "legovideomanager.h"
|
||||
#include "legoworld.h"
|
||||
#include "misc.h"
|
||||
#include "misc/legotree.h"
|
||||
#include "mxgeometry/mxgeometry3d.h"
|
||||
#include "mxgeometry/mxmatrix.h"
|
||||
#include "mxmisc.h"
|
||||
#include "realtime/vector.h"
|
||||
#include "roi/legoroi.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
using namespace Extensions;
|
||||
using namespace Extensions::Common;
|
||||
using namespace Extensions::ThirdPersonCamera;
|
||||
|
||||
Controller::Controller()
|
||||
: m_animator(CharacterAnimatorConfig{/*.saveEmoteTransform=*/true}), m_enabled(false), m_active(false),
|
||||
m_pendingWorldTransition(false), m_playerROI(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void Controller::Enable()
|
||||
{
|
||||
m_enabled = true;
|
||||
ReinitForCharacter();
|
||||
}
|
||||
|
||||
void Controller::Disable()
|
||||
{
|
||||
m_enabled = false;
|
||||
|
||||
if (m_active && m_playerROI) {
|
||||
m_playerROI->SetVisibility(FALSE);
|
||||
VideoManager()->Get3DManager()->Remove(*m_playerROI);
|
||||
|
||||
m_orbit.RestoreFirstPersonCamera();
|
||||
}
|
||||
|
||||
m_active = false;
|
||||
m_pendingWorldTransition = false;
|
||||
m_animator.StopROISounds();
|
||||
m_animator.StopClickAnimation();
|
||||
m_display.DestroyDisplayClone();
|
||||
m_playerROI = nullptr;
|
||||
m_animator.ClearRideAnimation();
|
||||
m_animator.ClearAll();
|
||||
|
||||
m_orbit.ResetOrbitState();
|
||||
m_input.ResetTouchState();
|
||||
}
|
||||
|
||||
void Controller::OnActorEnter(IslePathActor* p_actor)
|
||||
{
|
||||
LegoPathActor* userActor = UserActor();
|
||||
if (static_cast<LegoPathActor*>(p_actor) != userActor) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_animator.SetCurrentVehicleType(DetectVehicleType(userActor));
|
||||
|
||||
if (!m_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_pendingWorldTransition && m_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
LegoROI* newROI = userActor->GetROI();
|
||||
if (!newROI) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_animator.GetCurrentVehicleType() != VEHICLE_NONE) {
|
||||
if (IsLargeVehicle(m_animator.GetCurrentVehicleType()) ||
|
||||
m_animator.GetCurrentVehicleType() == VEHICLE_HELICOPTER) {
|
||||
if (m_playerROI) {
|
||||
m_playerROI->SetVisibility(FALSE);
|
||||
VideoManager()->Get3DManager()->Remove(*m_playerROI);
|
||||
}
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_playerROI) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_active = true;
|
||||
m_orbit.SetupCamera(userActor);
|
||||
m_animator.BuildRideAnimation(m_animator.GetCurrentVehicleType(), m_playerROI, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
newROI->SetVisibility(FALSE);
|
||||
if (!m_display.EnsureDisplayROI()) {
|
||||
return;
|
||||
}
|
||||
m_playerROI = m_display.GetDisplayROI();
|
||||
m_active = true;
|
||||
|
||||
m_playerROI->SetVisibility(TRUE);
|
||||
|
||||
VideoManager()->Get3DManager()->Remove(*m_playerROI);
|
||||
VideoManager()->Get3DManager()->Add(*m_playerROI);
|
||||
|
||||
m_animator.InitAnimCaches(m_playerROI);
|
||||
m_animator.ResetAnimState();
|
||||
|
||||
m_animator.ApplyIdleFrame0(m_playerROI);
|
||||
|
||||
m_orbit.SetupCamera(userActor);
|
||||
}
|
||||
|
||||
void Controller::OnActorExit(IslePathActor* p_actor)
|
||||
{
|
||||
if (!m_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_animator.GetCurrentVehicleType() != VEHICLE_NONE) {
|
||||
m_animator.ClearRideAnimation();
|
||||
m_animator.ClearAll();
|
||||
ReinitForCharacter();
|
||||
}
|
||||
else if (m_active && static_cast<LegoPathActor*>(p_actor) == UserActor()) {
|
||||
if (m_playerROI) {
|
||||
m_playerROI->SetVisibility(FALSE);
|
||||
VideoManager()->Get3DManager()->Remove(*m_playerROI);
|
||||
}
|
||||
m_animator.ClearRideAnimation();
|
||||
m_animator.ClearAll();
|
||||
m_playerROI = nullptr;
|
||||
m_active = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::OnCamAnimEnd(LegoPathActor* p_actor)
|
||||
{
|
||||
m_pendingWorldTransition = false;
|
||||
|
||||
if (!m_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_orbit.SetupCamera(p_actor);
|
||||
}
|
||||
|
||||
void Controller::Tick(float p_deltaTime)
|
||||
{
|
||||
if (!m_display.IsDisplayActorFrozen()) {
|
||||
LegoPathActor* userActor = UserActor();
|
||||
if (userActor) {
|
||||
uint8_t actorId = static_cast<LegoActor*>(userActor)->GetActorId();
|
||||
if (IsValidActorId(actorId)) {
|
||||
uint8_t derived = actorId - 1;
|
||||
if (derived != m_display.GetDisplayActorIndex()) {
|
||||
m_display.SetDisplayActorIndex(derived);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_playerROI) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_pendingWorldTransition) {
|
||||
m_pendingWorldTransition = false;
|
||||
LegoPathActor* actor = UserActor();
|
||||
if (actor && actor->GetROI()) {
|
||||
m_orbit.InitAbsoluteYaw(actor->GetROI());
|
||||
}
|
||||
}
|
||||
|
||||
if (!UserActor() || UserActor()->GetActorState() != LegoPathActor::c_disabled) {
|
||||
m_orbit.ApplyOrbitCamera();
|
||||
}
|
||||
|
||||
// Small vehicle with ride animation
|
||||
if (m_animator.GetCurrentVehicleType() != VEHICLE_NONE) {
|
||||
m_animator.StopClickAnimation();
|
||||
if (m_animator.GetRideAnim() && m_animator.GetRideRoiMap()) {
|
||||
LegoPathActor* actor = UserActor();
|
||||
if (!actor || !actor->GetROI()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnimUtils::EnsureROIMapVisibility(m_animator.GetRideRoiMap(), m_animator.GetRideRoiMapSize());
|
||||
|
||||
float speed = actor->GetWorldSpeed();
|
||||
if (SDL_fabsf(speed) > 0.01f) {
|
||||
m_animator.SetAnimTime(m_animator.GetAnimTime() + p_deltaTime * 2000.0f);
|
||||
}
|
||||
|
||||
MxMatrix transform(actor->GetROI()->GetLocal2World());
|
||||
AnimUtils::FlipMatrixDirection(transform);
|
||||
|
||||
m_playerROI->WrappedSetLocal2WorldWithWorldDataUpdate(transform);
|
||||
m_playerROI->SetVisibility(TRUE);
|
||||
|
||||
float duration = (float) m_animator.GetRideAnim()->GetDuration();
|
||||
if (duration > 0.0f) {
|
||||
float timeInCycle =
|
||||
m_animator.GetAnimTime() - duration * SDL_floorf(m_animator.GetAnimTime() / duration);
|
||||
|
||||
LegoTreeNode* root = m_animator.GetRideAnim()->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(
|
||||
root->GetChild(i),
|
||||
transform,
|
||||
(LegoTime) timeInCycle,
|
||||
m_animator.GetRideRoiMap()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
LegoPathActor* userActor = UserActor();
|
||||
if (!userActor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sync display clone position from native ROI
|
||||
if (m_display.GetDisplayROI() && m_display.GetDisplayROI() == m_playerROI) {
|
||||
m_display.SyncTransformFromNative(userActor->GetROI());
|
||||
}
|
||||
|
||||
float speed = userActor->GetWorldSpeed();
|
||||
bool isMoving = SDL_fabsf(speed) > 0.01f;
|
||||
if (m_animator.IsInMultiPartEmote()) {
|
||||
isMoving = false;
|
||||
userActor->SetWorldSpeed(0.0f);
|
||||
NavController()->SetLinearVel(0.0f);
|
||||
}
|
||||
|
||||
m_animator.Tick(p_deltaTime, m_playerROI, isMoving);
|
||||
}
|
||||
|
||||
void Controller::SetWalkAnimId(uint8_t p_walkAnimId)
|
||||
{
|
||||
m_animator.SetWalkAnimId(p_walkAnimId, m_active ? m_playerROI : nullptr);
|
||||
}
|
||||
|
||||
void Controller::SetIdleAnimId(uint8_t p_idleAnimId)
|
||||
{
|
||||
m_animator.SetIdleAnimId(p_idleAnimId, m_active ? m_playerROI : nullptr);
|
||||
}
|
||||
|
||||
bool Controller::IsInMultiPartEmote() const
|
||||
{
|
||||
return m_animator.IsInMultiPartEmote();
|
||||
}
|
||||
|
||||
int8_t Controller::GetFrozenEmoteId() const
|
||||
{
|
||||
return m_animator.GetFrozenEmoteId();
|
||||
}
|
||||
|
||||
void Controller::TriggerEmote(uint8_t p_emoteId)
|
||||
{
|
||||
if (!m_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
LegoPathActor* userActor = UserActor();
|
||||
if (!userActor) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool isMoving = SDL_fabsf(userActor->GetWorldSpeed()) > 0.01f;
|
||||
if (m_animator.IsInMultiPartEmote()) {
|
||||
isMoving = false;
|
||||
}
|
||||
m_animator.TriggerEmote(p_emoteId, m_playerROI, isMoving);
|
||||
}
|
||||
|
||||
void Controller::StopClickAnimation()
|
||||
{
|
||||
m_animator.StopClickAnimation();
|
||||
}
|
||||
|
||||
void Controller::OnWorldEnabled(LegoWorld* p_world)
|
||||
{
|
||||
if (!p_world) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_animator.ClearAll();
|
||||
|
||||
m_orbit.ResetOrbitState();
|
||||
|
||||
m_pendingWorldTransition = true;
|
||||
|
||||
ReinitForCharacter();
|
||||
}
|
||||
|
||||
void Controller::OnWorldDisabled(LegoWorld* p_world)
|
||||
{
|
||||
if (!p_world) {
|
||||
return;
|
||||
}
|
||||
m_active = false;
|
||||
m_pendingWorldTransition = false;
|
||||
m_playerROI = nullptr;
|
||||
m_animator.StopROISounds();
|
||||
m_animator.StopClickAnimation();
|
||||
m_display.DestroyDisplayClone();
|
||||
m_animator.ClearRideAnimation();
|
||||
m_animator.ClearAll();
|
||||
}
|
||||
|
||||
MxBool Controller::HandleCameraRelativeMovement(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime
|
||||
)
|
||||
{
|
||||
return m_orbit.HandleCameraRelativeMovement(
|
||||
p_nav,
|
||||
p_curPos,
|
||||
p_curDir,
|
||||
p_newPos,
|
||||
p_newDir,
|
||||
p_deltaTime,
|
||||
m_animator.IsInMultiPartEmote()
|
||||
);
|
||||
}
|
||||
|
||||
void Controller::HandleSDLEventImpl(SDL_Event* p_event)
|
||||
{
|
||||
m_input.HandleSDLEvent(p_event, m_orbit, m_active);
|
||||
}
|
||||
|
||||
void Controller::ReinitForCharacter()
|
||||
{
|
||||
LegoPathActor* userActor = UserActor();
|
||||
if (!userActor) {
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
LegoROI* roi = userActor->GetROI();
|
||||
if (!roi) {
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int8_t vehicleType = DetectVehicleType(userActor);
|
||||
|
||||
if (vehicleType == VEHICLE_HELICOPTER || (vehicleType != VEHICLE_NONE && IsLargeVehicle(vehicleType))) {
|
||||
m_active = false;
|
||||
m_pendingWorldTransition = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_animator.SetCurrentVehicleType(vehicleType);
|
||||
|
||||
if (vehicleType != VEHICLE_NONE) {
|
||||
if (!m_display.EnsureDisplayROI()) {
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
m_playerROI = m_display.GetDisplayROI();
|
||||
|
||||
if (!m_playerROI) {
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_pendingWorldTransition = false;
|
||||
|
||||
VideoManager()->Get3DManager()->Remove(*m_playerROI);
|
||||
VideoManager()->Get3DManager()->Add(*m_playerROI);
|
||||
m_active = true;
|
||||
m_orbit.SetupCamera(userActor);
|
||||
m_animator.BuildRideAnimation(vehicleType, m_playerROI, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
roi->SetVisibility(FALSE);
|
||||
if (!m_display.EnsureDisplayROI()) {
|
||||
m_active = false;
|
||||
return;
|
||||
}
|
||||
m_playerROI = m_display.GetDisplayROI();
|
||||
|
||||
m_playerROI->SetVisibility(TRUE);
|
||||
|
||||
VideoManager()->Get3DManager()->Remove(*m_playerROI);
|
||||
VideoManager()->Get3DManager()->Add(*m_playerROI);
|
||||
|
||||
m_animator.InitAnimCaches(m_playerROI);
|
||||
m_animator.ResetAnimState();
|
||||
m_active = true;
|
||||
|
||||
m_animator.ApplyIdleFrame0(m_playerROI);
|
||||
|
||||
if (!m_pendingWorldTransition) {
|
||||
m_orbit.SetupCamera(userActor);
|
||||
}
|
||||
}
|
||||
90
extensions/src/thirdpersoncamera/displayactor.cpp
Normal file
90
extensions/src/thirdpersoncamera/displayactor.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
#include "extensions/thirdpersoncamera/displayactor.h"
|
||||
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "extensions/common/animutils.h"
|
||||
#include "extensions/common/charactercloner.h"
|
||||
#include "extensions/common/charactercustomizer.h"
|
||||
#include "extensions/common/constants.h"
|
||||
#include "legocharactermanager.h"
|
||||
#include "legovideomanager.h"
|
||||
#include "misc.h"
|
||||
#include "mxgeometry/mxmatrix.h"
|
||||
#include "realtime/vector.h"
|
||||
#include "roi/legoroi.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
using namespace Extensions::ThirdPersonCamera;
|
||||
using namespace Extensions::Common;
|
||||
|
||||
DisplayActor::DisplayActor()
|
||||
: m_displayActorIndex(DISPLAY_ACTOR_NONE), m_displayActorFrozen(false), m_displayROI(nullptr)
|
||||
{
|
||||
SDL_memset(m_displayUniqueName, 0, sizeof(m_displayUniqueName));
|
||||
}
|
||||
|
||||
void DisplayActor::SetDisplayActorIndex(uint8_t p_displayActorIndex)
|
||||
{
|
||||
if (m_displayActorIndex != p_displayActorIndex) {
|
||||
m_customizeState.InitFromActorInfo(p_displayActorIndex);
|
||||
}
|
||||
m_displayActorIndex = p_displayActorIndex;
|
||||
}
|
||||
|
||||
bool DisplayActor::EnsureDisplayROI()
|
||||
{
|
||||
if (!IsValidDisplayActorIndex(m_displayActorIndex)) {
|
||||
return false;
|
||||
}
|
||||
if (!m_displayROI) {
|
||||
CreateDisplayClone();
|
||||
}
|
||||
if (!m_displayROI) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DisplayActor::CreateDisplayClone()
|
||||
{
|
||||
if (!IsValidDisplayActorIndex(m_displayActorIndex)) {
|
||||
return;
|
||||
}
|
||||
LegoCharacterManager* charMgr = CharacterManager();
|
||||
const char* actorName = charMgr->GetActorName(m_displayActorIndex);
|
||||
if (!actorName) {
|
||||
return;
|
||||
}
|
||||
SDL_snprintf(m_displayUniqueName, sizeof(m_displayUniqueName), "tp_display");
|
||||
m_displayROI = CharacterCloner::Clone(charMgr, m_displayUniqueName, actorName);
|
||||
|
||||
if (m_displayROI) {
|
||||
CharacterCustomizer::ApplyFullState(m_displayROI, m_displayActorIndex, m_customizeState);
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayActor::DestroyDisplayClone()
|
||||
{
|
||||
if (m_displayROI) {
|
||||
VideoManager()->Get3DManager()->Remove(*m_displayROI);
|
||||
CharacterManager()->ReleaseActor(m_displayUniqueName);
|
||||
m_displayROI = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void DisplayActor::ApplyCustomizeChange(uint8_t p_changeType, uint8_t p_partIndex)
|
||||
{
|
||||
uint8_t actorInfoIndex = CharacterCustomizer::ResolveActorInfoIndex(m_displayActorIndex);
|
||||
|
||||
CharacterCustomizer::ApplyChange(m_displayROI, actorInfoIndex, m_customizeState, p_changeType, p_partIndex);
|
||||
}
|
||||
|
||||
void DisplayActor::SyncTransformFromNative(LegoROI* p_nativeROI)
|
||||
{
|
||||
if (m_displayROI && p_nativeROI) {
|
||||
MxMatrix mat(p_nativeROI->GetLocal2World());
|
||||
AnimUtils::FlipMatrixDirection(mat);
|
||||
m_displayROI->WrappedSetLocal2WorldWithWorldDataUpdate(mat);
|
||||
VideoManager()->Get3DManager()->Moved(*m_displayROI);
|
||||
}
|
||||
}
|
||||
210
extensions/src/thirdpersoncamera/inputhandler.cpp
Normal file
210
extensions/src/thirdpersoncamera/inputhandler.cpp
Normal file
@ -0,0 +1,210 @@
|
||||
#include "extensions/thirdpersoncamera/inputhandler.h"
|
||||
|
||||
#include "extensions/thirdpersoncamera/orbitcamera.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <utility>
|
||||
|
||||
using namespace Extensions::ThirdPersonCamera;
|
||||
|
||||
InputHandler::InputHandler() : m_touch{}, m_wantsAutoDisable(false), m_wantsAutoEnable(false)
|
||||
{
|
||||
}
|
||||
|
||||
bool InputHandler::TryClaimFinger(const SDL_TouchFingerEvent& p_event, bool p_active)
|
||||
{
|
||||
if (!p_active || m_touch.count >= 2 || p_event.x < CAMERA_ZONE_X) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int idx = m_touch.count;
|
||||
m_touch.id[idx] = p_event.fingerID;
|
||||
m_touch.x[idx] = p_event.x;
|
||||
m_touch.y[idx] = p_event.y;
|
||||
m_touch.count++;
|
||||
|
||||
if (m_touch.count == 2) {
|
||||
float dx = m_touch.x[1] - m_touch.x[0];
|
||||
float dy = m_touch.y[1] - m_touch.y[0];
|
||||
m_touch.initialPinchDist = SDL_sqrtf(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputHandler::TryReleaseFinger(SDL_FingerID p_id)
|
||||
{
|
||||
for (int i = 0; i < m_touch.count; i++) {
|
||||
if (m_touch.id[i] == p_id) {
|
||||
if (i == 0 && m_touch.count == 2) {
|
||||
m_touch.id[0] = m_touch.id[1];
|
||||
m_touch.x[0] = m_touch.x[1];
|
||||
m_touch.y[0] = m_touch.y[1];
|
||||
}
|
||||
m_touch.count--;
|
||||
m_touch.initialPinchDist = 0.0f;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputHandler::IsFingerTracked(SDL_FingerID p_id) const
|
||||
{
|
||||
for (int i = 0; i < m_touch.count; i++) {
|
||||
if (m_touch.id[i] == p_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputHandler::ConsumeAutoDisable()
|
||||
{
|
||||
return std::exchange(m_wantsAutoDisable, false);
|
||||
}
|
||||
|
||||
bool InputHandler::ConsumeAutoEnable()
|
||||
{
|
||||
return std::exchange(m_wantsAutoEnable, false);
|
||||
}
|
||||
|
||||
void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool p_active)
|
||||
{
|
||||
switch (p_event->type) {
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
if (!p_active) {
|
||||
if (p_event->wheel.y < 0) {
|
||||
m_wantsAutoEnable = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (p_orbit.GetOrbitDistance() <= OrbitCamera::MIN_DISTANCE && p_event->wheel.y > 0) {
|
||||
m_wantsAutoDisable = true;
|
||||
break;
|
||||
}
|
||||
p_orbit.AdjustDistance(-p_event->wheel.y * 0.5f);
|
||||
p_orbit.ClampDistance();
|
||||
break;
|
||||
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
if (!p_active) {
|
||||
break;
|
||||
}
|
||||
if (p_event->motion.state & SDL_BUTTON_RMASK) {
|
||||
p_orbit.AdjustYaw(-p_event->motion.xrel * 0.005f);
|
||||
p_orbit.AdjustPitch(p_event->motion.yrel * 0.005f);
|
||||
p_orbit.ClampPitch();
|
||||
}
|
||||
break;
|
||||
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_BUTTON_UP: {
|
||||
if (!p_active) {
|
||||
break;
|
||||
}
|
||||
SDL_Window* window = SDL_GetWindowFromID(p_event->button.windowID);
|
||||
if (window) {
|
||||
SDL_SetWindowRelativeMouseMode(window, SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_RMASK);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_FINGER_DOWN: {
|
||||
if (!IsFingerTracked(p_event->tfinger.fingerID) && m_touch.count < 2 &&
|
||||
p_event->tfinger.x >= CAMERA_ZONE_X) {
|
||||
int idx = m_touch.count;
|
||||
m_touch.id[idx] = p_event->tfinger.fingerID;
|
||||
m_touch.x[idx] = p_event->tfinger.x;
|
||||
m_touch.y[idx] = p_event->tfinger.y;
|
||||
m_touch.count++;
|
||||
|
||||
if (m_touch.count == 2) {
|
||||
float dx = m_touch.x[1] - m_touch.x[0];
|
||||
float dy = m_touch.y[1] - m_touch.y[0];
|
||||
m_touch.initialPinchDist = SDL_sqrtf(dx * dx + dy * dy);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_FINGER_MOTION: {
|
||||
if (m_touch.count == 1) {
|
||||
if (!p_active) {
|
||||
break;
|
||||
}
|
||||
if (m_touch.id[0] == p_event->tfinger.fingerID) {
|
||||
float oldX = m_touch.x[0];
|
||||
float oldY = m_touch.y[0];
|
||||
m_touch.x[0] = p_event->tfinger.x;
|
||||
m_touch.y[0] = p_event->tfinger.y;
|
||||
|
||||
float moveX = m_touch.x[0] - oldX;
|
||||
float moveY = m_touch.y[0] - oldY;
|
||||
p_orbit.AdjustYaw(-moveX * 2.0f);
|
||||
p_orbit.AdjustPitch(moveY * 2.0f);
|
||||
p_orbit.ClampPitch();
|
||||
}
|
||||
}
|
||||
else if (m_touch.count == 2) {
|
||||
int idx = -1;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (m_touch.id[i] == p_event->tfinger.fingerID) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
float oldX = m_touch.x[idx];
|
||||
float oldY = m_touch.y[idx];
|
||||
m_touch.x[idx] = p_event->tfinger.x;
|
||||
m_touch.y[idx] = p_event->tfinger.y;
|
||||
|
||||
float dx = m_touch.x[1] - m_touch.x[0];
|
||||
float dy = m_touch.y[1] - m_touch.y[0];
|
||||
float newDist = SDL_sqrtf(dx * dx + dy * dy);
|
||||
|
||||
if (m_touch.initialPinchDist > 0.001f) {
|
||||
float pinchDelta = m_touch.initialPinchDist - newDist;
|
||||
|
||||
if (!p_active) {
|
||||
if (pinchDelta > 0) {
|
||||
m_wantsAutoEnable = true;
|
||||
}
|
||||
m_touch.initialPinchDist = newDist;
|
||||
break;
|
||||
}
|
||||
|
||||
if (p_orbit.GetOrbitDistance() <= OrbitCamera::MIN_DISTANCE && pinchDelta < 0) {
|
||||
m_wantsAutoDisable = true;
|
||||
m_touch.initialPinchDist = newDist;
|
||||
break;
|
||||
}
|
||||
|
||||
p_orbit.AdjustDistance(pinchDelta * 15.0f);
|
||||
p_orbit.ClampDistance();
|
||||
m_touch.initialPinchDist = newDist;
|
||||
}
|
||||
|
||||
float moveX = m_touch.x[idx] - oldX;
|
||||
float moveY = m_touch.y[idx] - oldY;
|
||||
p_orbit.AdjustYaw(-moveX * 2.0f);
|
||||
p_orbit.AdjustPitch(moveY * 2.0f);
|
||||
p_orbit.ClampPitch();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_EVENT_FINGER_UP:
|
||||
case SDL_EVENT_FINGER_CANCELED: {
|
||||
TryReleaseFinger(p_event->tfinger.fingerID);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
287
extensions/src/thirdpersoncamera/orbitcamera.cpp
Normal file
287
extensions/src/thirdpersoncamera/orbitcamera.cpp
Normal file
@ -0,0 +1,287 @@
|
||||
#include "extensions/thirdpersoncamera/orbitcamera.h"
|
||||
|
||||
#include "extensions/common/characteranimator.h"
|
||||
#include "legocameracontroller.h"
|
||||
#include "legoinputmanager.h"
|
||||
#include "legonavcontroller.h"
|
||||
#include "legopathactor.h"
|
||||
#include "legoworld.h"
|
||||
#include "misc.h"
|
||||
#include "mxgeometry/mxmatrix.h"
|
||||
#include "realtime/vector.h"
|
||||
#include "roi/legoroi.h"
|
||||
|
||||
using namespace Extensions::ThirdPersonCamera;
|
||||
|
||||
static constexpr float TURN_RATE = 10.0f;
|
||||
|
||||
OrbitCamera::OrbitCamera()
|
||||
: m_orbitPitch(DEFAULT_ORBIT_PITCH), m_orbitDistance(DEFAULT_ORBIT_DISTANCE),
|
||||
m_absoluteYaw(DEFAULT_ORBIT_YAW), m_smoothedSpeed(0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
void OrbitCamera::ComputeOrbitVectors(
|
||||
float p_yaw,
|
||||
Mx3DPointFloat& p_at,
|
||||
Mx3DPointFloat& p_dir,
|
||||
Mx3DPointFloat& p_up
|
||||
) const
|
||||
{
|
||||
float cosP = SDL_cosf(m_orbitPitch);
|
||||
float sinP = SDL_sinf(m_orbitPitch);
|
||||
float sinY = SDL_sinf(p_yaw);
|
||||
float cosY = SDL_cosf(p_yaw);
|
||||
|
||||
p_at = Mx3DPointFloat(
|
||||
m_orbitDistance * sinY * cosP,
|
||||
ORBIT_TARGET_HEIGHT + m_orbitDistance * sinP,
|
||||
-m_orbitDistance * cosY * cosP
|
||||
);
|
||||
|
||||
p_dir = Mx3DPointFloat(-sinY * cosP, -sinP, cosY * cosP);
|
||||
|
||||
p_up = Mx3DPointFloat(0.0f, 1.0f, 0.0f);
|
||||
}
|
||||
|
||||
float OrbitCamera::GetLocalYaw(LegoROI* p_roi) const
|
||||
{
|
||||
if (p_roi) {
|
||||
const float* dir = p_roi->GetWorldDirection();
|
||||
float playerWorldYaw = SDL_atan2f(-dir[0], dir[2]);
|
||||
return m_absoluteYaw - playerWorldYaw;
|
||||
}
|
||||
return m_absoluteYaw;
|
||||
}
|
||||
|
||||
void OrbitCamera::InitAbsoluteYaw(LegoROI* p_roi)
|
||||
{
|
||||
const float* dir = p_roi->GetWorldDirection();
|
||||
m_absoluteYaw = SDL_atan2f(-dir[0], dir[2]) + DEFAULT_ORBIT_YAW;
|
||||
}
|
||||
|
||||
void OrbitCamera::SetupCamera(LegoPathActor* p_actor)
|
||||
{
|
||||
LegoWorld* world = CurrentWorld();
|
||||
if (!world || !world->GetCameraController()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LegoROI* roi = p_actor->GetROI();
|
||||
if (roi) {
|
||||
InitAbsoluteYaw(roi);
|
||||
}
|
||||
m_smoothedSpeed = 0.0f;
|
||||
|
||||
Mx3DPointFloat at, camDir, up;
|
||||
ComputeOrbitVectors(DEFAULT_ORBIT_YAW, at, camDir, up);
|
||||
|
||||
world->GetCameraController()->SetWorldTransform(at, camDir, up);
|
||||
p_actor->TransformPointOfView();
|
||||
}
|
||||
|
||||
void OrbitCamera::ApplyOrbitCamera()
|
||||
{
|
||||
LegoPathActor* actor = UserActor();
|
||||
LegoWorld* world = CurrentWorld();
|
||||
if (!actor || !world || !world->GetCameraController()) {
|
||||
return;
|
||||
}
|
||||
|
||||
float localYaw = GetLocalYaw(actor->GetROI());
|
||||
|
||||
Mx3DPointFloat at, camDir, up;
|
||||
ComputeOrbitVectors(localYaw, at, camDir, up);
|
||||
|
||||
world->GetCameraController()->SetWorldTransform(at, camDir, up);
|
||||
actor->TransformPointOfView();
|
||||
}
|
||||
|
||||
void OrbitCamera::ResetOrbitState()
|
||||
{
|
||||
m_orbitPitch = DEFAULT_ORBIT_PITCH;
|
||||
m_orbitDistance = DEFAULT_ORBIT_DISTANCE;
|
||||
m_absoluteYaw = DEFAULT_ORBIT_YAW;
|
||||
m_smoothedSpeed = 0.0f;
|
||||
}
|
||||
|
||||
void OrbitCamera::ClampPitch()
|
||||
{
|
||||
if (m_orbitPitch < MIN_PITCH) {
|
||||
m_orbitPitch = MIN_PITCH;
|
||||
}
|
||||
if (m_orbitPitch > MAX_PITCH) {
|
||||
m_orbitPitch = MAX_PITCH;
|
||||
}
|
||||
}
|
||||
|
||||
void OrbitCamera::ClampDistance()
|
||||
{
|
||||
if (m_orbitDistance < MIN_DISTANCE) {
|
||||
m_orbitDistance = MIN_DISTANCE;
|
||||
}
|
||||
if (m_orbitDistance > MAX_DISTANCE) {
|
||||
m_orbitDistance = MAX_DISTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
void OrbitCamera::RestoreFirstPersonCamera()
|
||||
{
|
||||
LegoPathActor* userActor = UserActor();
|
||||
LegoWorld* world = CurrentWorld();
|
||||
|
||||
if (userActor && world && world->GetCameraController()) {
|
||||
world->GetCameraController()->SetWorldTransform(
|
||||
Mx3DPointFloat(0.0F, 1.25F, 0.0F),
|
||||
Mx3DPointFloat(0.0F, 0.0F, 1.0F),
|
||||
Mx3DPointFloat(0.0F, 1.0F, 0.0F)
|
||||
);
|
||||
userActor->TransformPointOfView();
|
||||
}
|
||||
}
|
||||
|
||||
MxBool OrbitCamera::HandleCameraRelativeMovement(
|
||||
LegoNavController* p_nav,
|
||||
const Vector3& p_curPos,
|
||||
const Vector3& p_curDir,
|
||||
Vector3& p_newPos,
|
||||
Vector3& p_newDir,
|
||||
float p_deltaTime,
|
||||
bool p_isInMultiPartEmote
|
||||
)
|
||||
{
|
||||
LegoInputManager* inputManager = InputManager();
|
||||
MxU32 keyFlags = 0;
|
||||
if (!inputManager || inputManager->GetNavigationKeyStates(keyFlags) == FAILURE) {
|
||||
keyFlags = 0;
|
||||
}
|
||||
|
||||
float camForwardX = -SDL_sinf(m_absoluteYaw);
|
||||
float camForwardZ = SDL_cosf(m_absoluteYaw);
|
||||
float camRightX = SDL_cosf(m_absoluteYaw);
|
||||
float camRightZ = SDL_sinf(m_absoluteYaw);
|
||||
|
||||
float moveDirX = 0.0f;
|
||||
float moveDirZ = 0.0f;
|
||||
|
||||
if (keyFlags & LegoInputManager::c_up) {
|
||||
moveDirX += camForwardX;
|
||||
moveDirZ += camForwardZ;
|
||||
}
|
||||
if (keyFlags & LegoInputManager::c_down) {
|
||||
moveDirX -= camForwardX;
|
||||
moveDirZ -= camForwardZ;
|
||||
}
|
||||
if (keyFlags & LegoInputManager::c_left) {
|
||||
moveDirX -= camRightX;
|
||||
moveDirZ -= camRightZ;
|
||||
}
|
||||
if (keyFlags & LegoInputManager::c_right) {
|
||||
moveDirX += camRightX;
|
||||
moveDirZ += camRightZ;
|
||||
}
|
||||
|
||||
if (keyFlags == 0 && inputManager) {
|
||||
MxU32 joystickX, joystickY, povPosition;
|
||||
if (inputManager->GetJoystickState(&joystickX, &joystickY, &povPosition) == SUCCESS) {
|
||||
float jx = (joystickX - 50.0f) / 50.0f;
|
||||
float jy = -(joystickY - 50.0f) / 50.0f;
|
||||
|
||||
if (SDL_fabsf(jx) < 0.1f) {
|
||||
jx = 0.0f;
|
||||
}
|
||||
if (SDL_fabsf(jy) < 0.1f) {
|
||||
jy = 0.0f;
|
||||
}
|
||||
|
||||
moveDirX += camForwardX * jy + camRightX * jx;
|
||||
moveDirZ += camForwardZ * jy + camRightZ * jx;
|
||||
}
|
||||
}
|
||||
|
||||
float moveDirLen = SDL_sqrtf(moveDirX * moveDirX + moveDirZ * moveDirZ);
|
||||
bool hasInput = moveDirLen > 0.001f;
|
||||
|
||||
if (p_isInMultiPartEmote) {
|
||||
hasInput = false;
|
||||
m_smoothedSpeed = 0.0f;
|
||||
}
|
||||
|
||||
if (hasInput) {
|
||||
moveDirX /= moveDirLen;
|
||||
moveDirZ /= moveDirLen;
|
||||
}
|
||||
|
||||
float maxSpeed = p_nav->m_maxLinearVel;
|
||||
if (hasInput) {
|
||||
float accel = p_nav->m_maxLinearAccel;
|
||||
m_smoothedSpeed += accel * p_deltaTime;
|
||||
if (m_smoothedSpeed > maxSpeed) {
|
||||
m_smoothedSpeed = maxSpeed;
|
||||
}
|
||||
}
|
||||
else {
|
||||
float decel = p_nav->m_maxLinearDeccel;
|
||||
m_smoothedSpeed -= decel * p_deltaTime;
|
||||
if (m_smoothedSpeed < 0.0f) {
|
||||
m_smoothedSpeed = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_smoothedSpeed < p_nav->m_zeroThreshold && !hasInput) {
|
||||
m_smoothedSpeed = 0.0f;
|
||||
p_newPos = p_curPos;
|
||||
p_newDir = p_curDir;
|
||||
}
|
||||
else {
|
||||
float speed = m_smoothedSpeed * p_deltaTime;
|
||||
if (hasInput) {
|
||||
p_newPos[0] = p_curPos[0] + moveDirX * speed;
|
||||
p_newPos[1] = p_curPos[1] + p_curDir[1] * speed;
|
||||
p_newPos[2] = p_curPos[2] + moveDirZ * speed;
|
||||
|
||||
float targetYaw = SDL_atan2f(-moveDirX, moveDirZ);
|
||||
float currentYaw = SDL_atan2f(-p_curDir[0], p_curDir[2]);
|
||||
float angleDiff = targetYaw - currentYaw;
|
||||
|
||||
while (angleDiff > SDL_PI_F) {
|
||||
angleDiff -= 2.0f * SDL_PI_F;
|
||||
}
|
||||
while (angleDiff < -SDL_PI_F) {
|
||||
angleDiff += 2.0f * SDL_PI_F;
|
||||
}
|
||||
|
||||
float maxTurn = TURN_RATE * p_deltaTime;
|
||||
if (SDL_fabsf(angleDiff) > maxTurn) {
|
||||
angleDiff = angleDiff > 0 ? maxTurn : -maxTurn;
|
||||
}
|
||||
|
||||
float newYaw = currentYaw + angleDiff;
|
||||
p_newDir[0] = -SDL_sinf(newYaw);
|
||||
p_newDir[1] = p_curDir[1];
|
||||
p_newDir[2] = SDL_cosf(newYaw);
|
||||
}
|
||||
else {
|
||||
p_newPos[0] = p_curPos[0] + p_curDir[0] * speed;
|
||||
p_newPos[1] = p_curPos[1] + p_curDir[1] * speed;
|
||||
p_newPos[2] = p_curPos[2] + p_curDir[2] * speed;
|
||||
p_newDir = p_curDir;
|
||||
}
|
||||
}
|
||||
|
||||
p_nav->m_linearVel = m_smoothedSpeed;
|
||||
p_nav->m_rotationalVel = 0.0f;
|
||||
|
||||
LegoWorld* world = CurrentWorld();
|
||||
if (world && world->GetCameraController()) {
|
||||
float newPlayerYaw = SDL_atan2f(-p_newDir[0], p_newDir[2]);
|
||||
float localYaw = m_absoluteYaw - newPlayerYaw;
|
||||
|
||||
Mx3DPointFloat at, camDir, camUp;
|
||||
ComputeOrbitVectors(localYaw, at, camDir, camUp);
|
||||
|
||||
world->GetCameraController()->SetWorldTransform(at, camDir, camUp);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user