Refactor extensions: DRY, named constants, and cleanup

Replace magic numbers with named constants across third-person camera,
multiplayer, and common utilities. Extract duplicated code into shared
helpers: CancelExternalAnim, StartEmotePhase, DeriveDependentIndices,
ReaddROI, SendFixedMessage. Deduplicate finger-down handling via
TryClaimFinger and tighten Extensions::Enable() dispatch with a
table-driven approach. Fix missing <functional> include in sceneplayer.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christian Semmler 2026-03-28 15:08:23 -07:00
parent af80f161d6
commit f7947e1720
No known key found for this signature in database
GPG Key ID: 086DAA1360BEEE5C
21 changed files with 197 additions and 183 deletions

View File

@ -102,10 +102,15 @@ class CharacterAnimator {
void SetAnimTime(float p_time) { m_animTime = p_time; }
void ResetAnimState();
static constexpr float ANIM_TIME_SCALE = 2000.0f;
static constexpr float EMOTE_TIME_SCALE = 1000.0f;
static constexpr float IDLE_DELAY_SECONDS = 2.5f;
private:
using AnimCache = AnimUtils::AnimCache;
AnimCache* GetOrBuildAnimCache(LegoROI* p_roi, const char* p_animName);
void StartEmotePhase(uint8_t p_emoteId, int p_phaseIndex, AnimCache* p_cache, LegoROI* p_roi);
void ClearFrozenState();
void ClearPropGroup(PropGroup& p_group);
void BuildEmoteProps(PropGroup& p_group, LegoAnim* p_anim, LegoROI* p_playerROI);

View File

@ -32,6 +32,8 @@ enum WorldChangeType : uint8_t {
static const uint8_t DISPLAY_ACTOR_NONE = 0xFF;
static constexpr float FIXED_TICK_DELTA = 0.016f; // ~60 Hz
// Validate actorId is a playable character (1-5, not brickster)
inline bool IsValidActorId(uint8_t p_actorId)
{

View File

@ -19,6 +19,9 @@ struct CustomizeState {
void Unpack(const uint8_t p_in[5]);
bool operator==(const CustomizeState& p_other) const;
bool operator!=(const CustomizeState& p_other) const { return !(*this == p_other); }
private:
void DeriveDependentIndices();
};
} // namespace Common

View File

@ -1,6 +1,7 @@
#pragma once
#include "extensions/common/constants.h"
#include "extensions/multiplayer/networktransport.h"
#include <SDL3/SDL_stdinc.h>
#include <cstddef>
@ -10,6 +11,8 @@
namespace Multiplayer
{
static constexpr size_t USERNAME_BUFFER_SIZE = 8; // 7 chars + null terminator
// Routing target constants for MessageHeader.target
const uint32_t TARGET_BROADCAST = 0; // Broadcast to all except sender
const uint32_t TARGET_HOST = 0xFFFFFFFF; // Send to host only
@ -100,7 +103,7 @@ struct PlayerStateMsg {
float speed;
uint8_t walkAnimId; // Index into walk animation table (0 = default)
uint8_t idleAnimId; // Index into idle animation table (0 = default)
char name[8]; // Player display name (7 chars + null terminator)
char name[USERNAME_BUFFER_SIZE]; // Player display name (7 chars + null terminator)
uint8_t displayActorIndex; // Index into g_actorInfoInit (0-65)
uint8_t customizeData[5]; // Packed CustomizeState
uint8_t customizeFlags; // Bit 0 = allowRemoteCustomize
@ -200,7 +203,7 @@ struct AnimStartMsg {
struct AnimCompletionParticipant {
uint32_t peerId;
int8_t charIndex; // Participant's character (g_actorInfoInit index)
char displayName[8]; // 7 chars + null
char displayName[USERNAME_BUFFER_SIZE]; // 7 chars + null
};
// Host -> All: animation completed successfully (natural completion only, not cancellation)
@ -214,11 +217,17 @@ struct AnimCompleteMsg {
#pragma pack(pop)
// Bitmask constants for PlayerStateMsg::customizeFlags
static constexpr uint8_t CUSTOMIZE_FLAG_ALLOW_REMOTE = 0x01;
static constexpr uint8_t CUSTOMIZE_FLAG_FROZEN = 0x02;
static constexpr uint8_t CUSTOMIZE_FLAG_FROZEN_EMOTE_SHIFT = 2;
static constexpr uint8_t CUSTOMIZE_FLAG_FROZEN_EMOTE_MASK = 0x07;
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]);
void EncodeUsername(char p_out[USERNAME_BUFFER_SIZE]);
using Extensions::Common::DISPLAY_ACTOR_NONE;
@ -255,4 +264,19 @@ inline bool DeserializeMsg(const uint8_t* p_data, size_t p_length, T& p_out)
return true;
}
// Serialize and send a fixed-size message via the transport.
template <typename T>
inline void SendFixedMessage(NetworkTransport* p_transport, const T& p_msg)
{
if (!p_transport || !p_transport->IsConnected()) {
return;
}
uint8_t buf[sizeof(T)];
size_t len = SerializeMsg(buf, sizeof(buf), p_msg);
if (len > 0) {
p_transport->Send(buf, len);
}
}
} // namespace Multiplayer

View File

@ -81,7 +81,7 @@ class RemotePlayer {
uint8_t m_actorId;
uint8_t m_displayActorIndex;
char m_uniqueName[32];
char m_displayName[8];
char m_displayName[USERNAME_BUFFER_SIZE];
LegoROI* m_roi;
bool m_spawned;

View File

@ -126,6 +126,7 @@ class Controller {
static constexpr float MIN_DISTANCE = OrbitCamera::MIN_DISTANCE;
private:
void CancelExternalAnim();
void Deactivate();
void ReinitForCharacter();

View File

@ -33,6 +33,10 @@ class InputHandler {
static constexpr float CAMERA_ZONE_X = 0.5f;
static constexpr float PINCH_TRANSITION_THRESHOLD = 0.03f;
static constexpr Uint64 LMB_HOLD_THRESHOLD_MS = 300;
static constexpr float MOUSE_SENSITIVITY = 0.005f;
static constexpr float MOUSE_WHEEL_ZOOM_STEP = 0.5f;
static constexpr float TOUCH_YAW_PITCH_SCALE = 2.0f;
static constexpr float PINCH_ZOOM_SCALE = 6.0f;
private:
struct TouchState {

View File

@ -57,6 +57,10 @@ class OrbitCamera {
static constexpr float MIN_DISTANCE = 1.5f;
static constexpr float SWITCH_TO_FIRST_PERSON_DISTANCE = 0.5f;
static constexpr float MAX_DISTANCE = 15.0f;
static constexpr float CHARACTER_TURN_RATE = 10.0f;
static constexpr float JOYSTICK_CENTER = 50.0f;
static constexpr float JOYSTICK_DEAD_ZONE = 0.1f;
static constexpr float MOVEMENT_DIR_EPSILON = 0.001f;
private:
void ComputeOrbitVectors(float p_yaw, Mx3DPointFloat& p_at, Mx3DPointFloat& p_dir, Mx3DPointFloat& p_up) const;

View File

@ -89,7 +89,7 @@ void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
}
if (p_isMoving) {
m_animTime += p_deltaTime * 2000.0f;
m_animTime += p_deltaTime * ANIM_TIME_SCALE;
}
float duration = (float) walkAnim->GetDuration();
if (duration > 0.0f) {
@ -104,7 +104,7 @@ void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
}
else if (m_emoteActive && m_emoteAnimCache && m_emoteAnimCache->anim && m_emoteAnimCache->roiMap) {
// Emote playback
m_emoteTime += p_deltaTime * 1000.0f;
m_emoteTime += p_deltaTime * EMOTE_TIME_SCALE;
if (m_emoteTime >= m_emoteDuration) {
if (IsMultiPartEmote(m_currentEmoteId) && m_frozenEmoteId < 0) {
@ -169,8 +169,8 @@ void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
m_idleTime += p_deltaTime;
// Hold standing pose for 2.5s, then loop breathing/swaying
if (m_idleTime >= 2.5f) {
// Hold standing pose, then loop breathing/swaying
if (m_idleTime >= IDLE_DELAY_SECONDS) {
m_idleAnimTime += p_deltaTime * 1000.0f;
}
@ -210,6 +210,25 @@ void CharacterAnimator::SetIdleAnimId(uint8_t p_idleAnimId, LegoROI* p_roi)
}
}
void CharacterAnimator::StartEmotePhase(uint8_t p_emoteId, int p_phaseIndex, AnimCache* p_cache, LegoROI* p_roi)
{
StopClickAnimation();
ClearPropGroup(m_emotePropGroup);
m_currentEmoteId = p_emoteId;
m_emoteAnimCache = p_cache;
m_emoteTime = 0.0f;
m_emoteDuration = (float) p_cache->anim->GetDuration();
m_emoteActive = true;
BuildEmoteProps(m_emotePropGroup, p_cache->anim, p_roi);
const char* sound = g_emoteEntries[p_emoteId].phases[p_phaseIndex].sound;
if (sound) {
PlayROISound(sound, p_roi);
}
}
void CharacterAnimator::TriggerEmote(uint8_t p_emoteId, LegoROI* p_roi, bool p_isMoving)
{
if (p_emoteId >= g_emoteAnimCount || !p_roi || m_currentVehicleType != VEHICLE_NONE) {
@ -224,21 +243,7 @@ void CharacterAnimator::TriggerEmote(uint8_t p_emoteId, LegoROI* p_roi, bool p_i
return;
}
StopClickAnimation();
ClearPropGroup(m_emotePropGroup);
m_currentEmoteId = p_emoteId;
m_emoteAnimCache = cache;
m_emoteTime = 0.0f;
m_emoteDuration = (float) cache->anim->GetDuration();
m_emoteActive = true;
BuildEmoteProps(m_emotePropGroup, cache->anim, p_roi);
const char* sound = g_emoteEntries[p_emoteId].phases[1].sound;
if (sound) {
PlayROISound(sound, p_roi);
}
StartEmotePhase(p_emoteId, 1, cache, p_roi);
if (m_config.saveEmoteTransform) {
m_emoteParentTransform = m_frozenParentTransform;
@ -263,21 +268,7 @@ void CharacterAnimator::TriggerEmote(uint8_t p_emoteId, LegoROI* p_roi, bool p_i
return;
}
StopClickAnimation();
ClearPropGroup(m_emotePropGroup);
m_currentEmoteId = p_emoteId;
m_emoteAnimCache = cache;
m_emoteTime = 0.0f;
m_emoteDuration = (float) cache->anim->GetDuration();
m_emoteActive = true;
BuildEmoteProps(m_emotePropGroup, cache->anim, p_roi);
const char* sound = g_emoteEntries[p_emoteId].phases[0].sound;
if (sound) {
PlayROISound(sound, p_roi);
}
StartEmotePhase(p_emoteId, 0, cache, p_roi);
// Save clean transform to prevent scale accumulation during emote
if (m_config.saveEmoteTransform) {

View File

@ -29,6 +29,7 @@ static const MxU32 g_characterAnimationId = 10;
static const MxU32 g_maxSound = 9;
static const MxU32 g_maxMove = 4;
static constexpr int COLORABLE_PARTS_COUNT = 10;
static uint32_t s_variantCounter = 10000;
// MARK: Private helpers
@ -62,7 +63,7 @@ bool CharacterCustomizer::SwitchColor(
int p_partIndex
)
{
if (p_partIndex < 0 || p_partIndex >= 10) {
if (p_partIndex < 0 || p_partIndex >= COLORABLE_PARTS_COUNT) {
return false;
}
@ -187,7 +188,7 @@ void CharacterCustomizer::ApplyChange(
int CharacterCustomizer::MapClickedPartIndex(const char* p_partName)
{
for (int i = 0; i < 10; i++) {
for (int i = 0; i < COLORABLE_PARTS_COUNT; i++) {
if (!SDL_strcasecmp(p_partName, g_actorLODs[i + 1].m_name)) {
return i;
}

View File

@ -23,11 +23,7 @@ void CustomizeState::InitFromActorInfo(uint8_t p_actorInfoIndex)
colorIndices[c_leglftPart] = info.m_parts[c_leglftPart].m_nameIndex;
colorIndices[c_legrtPart] = info.m_parts[c_legrtPart].m_nameIndex;
// Derive dependent parts (must match Unpack derivation rules)
colorIndices[c_bodyPart] = colorIndices[c_infogronPart];
colorIndices[c_headPart] = colorIndices[c_infohatPart];
colorIndices[c_clawlftPart] = colorIndices[c_armlftPart];
colorIndices[c_clawrtPart] = colorIndices[c_armrtPart];
DeriveDependentIndices();
hatVariantIndex = info.m_parts[c_infohatPart].m_partNameIndex;
sound = (uint8_t) info.m_sound;
@ -75,7 +71,11 @@ void CustomizeState::Unpack(const uint8_t p_in[5])
colorIndices[c_leglftPart] = p_in[4] & 0x0F;
colorIndices[c_legrtPart] = (p_in[4] >> 4) & 0x0F;
// Derive non-independent parts
DeriveDependentIndices();
}
void CustomizeState::DeriveDependentIndices()
{
colorIndices[c_bodyPart] = colorIndices[c_infogronPart];
colorIndices[c_headPart] = colorIndices[c_infohatPart];
colorIndices[c_clawlftPart] = colorIndices[c_armlftPart];

View File

@ -7,33 +7,47 @@
#include <SDL3/SDL_log.h>
using namespace Extensions;
static void InitTextureLoader(std::map<std::string, std::string> p_options)
{
TextureLoaderExt::options = std::move(p_options);
TextureLoaderExt::enabled = true;
TextureLoaderExt::Initialize();
}
static void InitSiLoader(std::map<std::string, std::string> p_options)
{
SiLoaderExt::options = std::move(p_options);
SiLoaderExt::enabled = true;
SiLoaderExt::Initialize();
}
static void InitThirdPersonCamera(std::map<std::string, std::string> p_options)
{
ThirdPersonCameraExt::options = std::move(p_options);
ThirdPersonCameraExt::enabled = true;
ThirdPersonCameraExt::Initialize();
}
static void InitMultiplayer(std::map<std::string, std::string> p_options)
{
MultiplayerExt::options = std::move(p_options);
MultiplayerExt::enabled = true;
MultiplayerExt::Initialize();
}
using InitFn = void (*)(std::map<std::string, std::string>);
static const InitFn extensionInits[] = {InitTextureLoader, InitSiLoader, InitThirdPersonCamera, InitMultiplayer};
void Extensions::Enable(const char* p_key, std::map<std::string, std::string> p_options)
{
for (const char* key : availableExtensions) {
if (!SDL_strcasecmp(p_key, key)) {
if (!SDL_strcasecmp(p_key, "extensions:texture loader")) {
TextureLoaderExt::options = std::move(p_options);
TextureLoaderExt::enabled = true;
TextureLoaderExt::Initialize();
}
else if (!SDL_strcasecmp(p_key, "extensions:si loader")) {
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);
MultiplayerExt::enabled = true;
MultiplayerExt::Initialize();
}
for (int i = 0; i < (int) (sizeof(availableExtensions) / sizeof(availableExtensions[0])); i++) {
if (!SDL_strcasecmp(p_key, availableExtensions[i])) {
extensionInits[i](std::move(p_options));
SDL_Log("Enabled extension: %s", p_key);
break;
return;
}
}
}

View File

@ -22,6 +22,7 @@
#include <SDL3/SDL_timer.h>
#include <algorithm>
#include <cmath>
#include <functional>
#include <deque>
#include <vector>

View File

@ -53,15 +53,7 @@ static void ExtractSlotPeerIds(const AnimUpdateMsg& p_msg, uint32_t p_out[8])
template <typename T>
void NetworkManager::SendMessage(const T& p_msg)
{
if (!m_transport || !m_transport->IsConnected()) {
return;
}
uint8_t buf[sizeof(T)];
size_t len = SerializeMsg(buf, sizeof(buf), p_msg);
if (len > 0) {
m_transport->Send(buf, len);
}
SendFixedMessage(m_transport, p_msg);
}
NetworkManager::NetworkManager()
@ -148,7 +140,7 @@ MxResult NetworkManager::Tickle()
// Create local name bubble when display ROI becomes available
if (m_showNameBubbles && !m_localNameBubble && cam->GetDisplayROI()) {
char name[8];
char name[USERNAME_BUFFER_SIZE];
EncodeUsername(name);
m_localNameBubble = new NameBubbleRenderer();
m_localNameBubble->Create(name);
@ -221,7 +213,7 @@ MxResult NetworkManager::Tickle()
}
ProcessIncomingPackets();
UpdateRemotePlayers(0.016f);
UpdateRemotePlayers(Common::FIXED_TICK_DELTA);
TickAnimation();
// Re-read time; ProcessIncomingPackets may have advanced SDL_GetTicks.
@ -765,11 +757,11 @@ void NetworkManager::BroadcastLocalState()
msg.displayActorIndex = cam->GetDisplayActorIndex();
cam->GetCustomizeState().Pack(msg.customizeData);
// Encode multi-part emote frozen state (0x02 = frozen, emote ID in bits 2-4, max 8 emotes)
// Encode multi-part emote frozen state
int8_t frozenId = cam->GetFrozenEmoteId();
if (frozenId >= 0) {
msg.customizeFlags |= 0x02;
msg.customizeFlags |= (frozenId & 0x07) << 2;
msg.customizeFlags |= CUSTOMIZE_FLAG_FROZEN;
msg.customizeFlags |= (frozenId & CUSTOMIZE_FLAG_FROZEN_EMOTE_MASK) << CUSTOMIZE_FLAG_FROZEN_EMOTE_SHIFT;
}
// Zero speed when in any phase of a multi-part emote or animation playback
@ -778,7 +770,7 @@ void NetworkManager::BroadcastLocalState()
}
}
msg.customizeFlags |= m_localAllowRemoteCustomize ? 0x01 : 0x00;
msg.customizeFlags |= m_localAllowRemoteCustomize ? CUSTOMIZE_FLAG_ALLOW_REMOTE : 0x00;
SendMessage(msg);
}
@ -1960,10 +1952,10 @@ void NetworkManager::HandleAnimComplete(const AnimCompleteMsg& p_msg)
}
first = false;
const AnimCompletionParticipant& p = p_msg.participants[i];
// Ensure null-termination safety for displayName (protocol uses fixed char[8])
char name[8];
// Ensure null-termination safety for displayName
char name[USERNAME_BUFFER_SIZE];
SDL_memcpy(name, p.displayName, sizeof(name));
name[7] = '\0';
name[USERNAME_BUFFER_SIZE - 1] = '\0';
json += "{\"charIndex\":";
json += std::to_string(static_cast<int>(p.charIndex));
json += ",\"displayName\":\"";

View File

@ -8,9 +8,9 @@
namespace Multiplayer
{
void EncodeUsername(char p_out[8])
void EncodeUsername(char p_out[USERNAME_BUFFER_SIZE])
{
SDL_memset(p_out, 0, 8);
SDL_memset(p_out, 0, USERNAME_BUFFER_SIZE);
LegoGameState* gs = GameState();
if (gs && gs->m_playerCount > 0) {
const LegoGameState::Username& username = gs->m_players[0];

View File

@ -28,6 +28,9 @@ using Common::g_walkAnimCount;
using Common::IsLargeVehicle;
using Common::WORLD_NOT_VISIBLE;
static constexpr float REMOTE_SPEED_THRESHOLD = 0.01f;
static constexpr float POSITION_LERP_FACTOR = 0.2f;
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),
@ -139,7 +142,7 @@ void RemotePlayer::UpdateFromNetwork(const PlayerStateMsg& p_msg)
SET3(m_targetPosition, p_msg.position);
SET3(m_targetDirection, p_msg.direction);
SET3(m_targetUp, p_msg.up);
m_targetSpeed = posDelta > 0.01f ? posDelta : 0.0f;
m_targetSpeed = posDelta > REMOTE_SPEED_THRESHOLD ? posDelta : 0.0f;
m_targetVehicleType = p_msg.vehicleType;
m_targetWorldId = p_msg.worldId;
m_lastUpdateTime = SDL_GetTicks();
@ -153,9 +156,9 @@ void RemotePlayer::UpdateFromNetwork(const PlayerStateMsg& p_msg)
}
// Update display name (can change when player switches save file)
char newName[8];
char newName[USERNAME_BUFFER_SIZE];
SDL_memcpy(newName, p_msg.name, sizeof(newName));
newName[sizeof(newName) - 1] = '\0';
newName[USERNAME_BUFFER_SIZE - 1] = '\0';
if (SDL_strcmp(m_displayName, newName) != 0) {
SDL_memcpy(m_displayName, newName, sizeof(m_displayName));
@ -179,11 +182,13 @@ void RemotePlayer::UpdateFromNetwork(const PlayerStateMsg& p_msg)
}
// Update allow remote customize flag
m_allowRemoteCustomize = (p_msg.customizeFlags & 0x01) != 0;
m_allowRemoteCustomize = (p_msg.customizeFlags & CUSTOMIZE_FLAG_ALLOW_REMOTE) != 0;
// Sync multi-part emote frozen state from remote
bool isFrozen = (p_msg.customizeFlags & 0x02) != 0;
int8_t frozenEmoteId = isFrozen ? (int8_t) ((p_msg.customizeFlags >> 2) & 0x07) : -1;
bool isFrozen = (p_msg.customizeFlags & CUSTOMIZE_FLAG_FROZEN) != 0;
int8_t frozenEmoteId =
isFrozen ? (int8_t) ((p_msg.customizeFlags >> CUSTOMIZE_FLAG_FROZEN_EMOTE_SHIFT) & CUSTOMIZE_FLAG_FROZEN_EMOTE_MASK)
: -1;
if (frozenEmoteId != m_animator.GetFrozenEmoteId()) {
m_animator.SetFrozenEmoteId(frozenEmoteId, m_roi);
}
@ -217,7 +222,7 @@ void RemotePlayer::Tick(float p_deltaTime)
UpdateVehicleState();
UpdateTransform(p_deltaTime);
bool isMoving = m_targetSpeed > 0.01f;
bool isMoving = m_targetSpeed > REMOTE_SPEED_THRESHOLD;
if (m_animator.GetFrozenEmoteId() >= 0) {
isMoving = false;
}
@ -281,7 +286,7 @@ void RemotePlayer::TriggerEmote(uint8_t p_emoteId)
return;
}
bool isMoving = m_targetSpeed > 0.01f;
bool isMoving = m_targetSpeed > REMOTE_SPEED_THRESHOLD;
if (m_animator.GetFrozenEmoteId() >= 0) {
isMoving = false;
}
@ -290,9 +295,9 @@ void RemotePlayer::TriggerEmote(uint8_t p_emoteId)
void RemotePlayer::UpdateTransform(float p_deltaTime)
{
LERP3(m_currentPosition, m_currentPosition, m_targetPosition, 0.2f);
LERP3(m_currentDirection, m_currentDirection, m_targetDirection, 0.2f);
LERP3(m_currentUp, m_currentUp, m_targetUp, 0.2f);
LERP3(m_currentPosition, m_currentPosition, m_targetPosition, POSITION_LERP_FACTOR);
LERP3(m_currentDirection, m_currentDirection, m_targetDirection, POSITION_LERP_FACTOR);
LERP3(m_currentUp, m_currentUp, m_targetUp, POSITION_LERP_FACTOR);
// The network sends forward-z (visual forward). Character meshes face -z,
// so negate to get backward-z for the ROI (mesh faces the correct way).

View File

@ -26,15 +26,7 @@ using namespace Multiplayer;
template <typename T>
void WorldStateSync::SendMessage(const T& p_msg)
{
if (!m_transport || !m_transport->IsConnected()) {
return;
}
uint8_t buf[sizeof(T)];
size_t len = SerializeMsg(buf, sizeof(buf), p_msg);
if (len > 0) {
m_transport->Send(buf, len);
}
SendFixedMessage(m_transport, p_msg);
}
WorldStateSync::WorldStateSync()

View File

@ -2,6 +2,7 @@
#include "extensions/common/arearestriction.h"
#include "extensions/common/charactercustomizer.h"
#include "extensions/common/constants.h"
#include "extensions/thirdpersoncamera/controller.h"
#include "islepathactor.h"
#include "legoeventnotificationparam.h"
@ -39,7 +40,7 @@ class TickleAdapter : public MxCore {
MxResult Tickle() override
{
if (m_camera) {
m_camera->Tick(0.016f);
m_camera->Tick(FIXED_TICK_DELTA);
}
return SUCCESS;
}

View File

@ -28,6 +28,14 @@ using namespace Extensions;
using namespace Extensions::Common;
using namespace Extensions::ThirdPersonCamera;
static constexpr float SPEED_EPSILON = 0.01f;
static void ReaddROI(LegoROI& p_roi)
{
VideoManager()->Get3DManager()->Remove(p_roi);
VideoManager()->Get3DManager()->Add(p_roi);
}
Controller::Controller()
: m_animator(CharacterAnimatorConfig{/*.saveEmoteTransform=*/true, /*.propSuffix=*/0}), m_enabled(false),
m_active(false), m_pendingWorldTransition(false), m_animPlaying(false), m_animLockDisplay(false),
@ -51,9 +59,8 @@ void Controller::Disable(bool p_preserveTouch)
}
}
void Controller::Deactivate()
void Controller::CancelExternalAnim()
{
// Stop external animation before destroying the display ROI
if (m_animPlaying) {
if (m_animStopCallback) {
m_animStopCallback();
@ -61,6 +68,12 @@ void Controller::Deactivate()
m_animPlaying = false;
m_animStopCallback = nullptr;
}
}
void Controller::Deactivate()
{
// Stop external animation before destroying the display ROI
CancelExternalAnim();
if (m_active && m_playerROI) {
m_playerROI->SetVisibility(FALSE);
@ -107,13 +120,7 @@ void Controller::OnActorEnter(IslePathActor* p_actor)
// Stop external animation before modifying ride/display state —
// the ScenePlayer may hold a reference to the ride vehicle ROI.
if (m_animPlaying) {
if (m_animStopCallback) {
m_animStopCallback();
}
m_animPlaying = false;
m_animStopCallback = nullptr;
}
CancelExternalAnim();
LegoROI* newROI = userActor->GetROI();
if (!newROI) {
@ -150,8 +157,7 @@ void Controller::OnActorEnter(IslePathActor* p_actor)
m_playerROI->SetVisibility(TRUE);
VideoManager()->Get3DManager()->Remove(*m_playerROI);
VideoManager()->Get3DManager()->Add(*m_playerROI);
ReaddROI(*m_playerROI);
m_animator.InitAnimCaches(m_playerROI);
m_animator.ResetAnimState();
@ -169,13 +175,7 @@ void Controller::OnActorExit(IslePathActor* p_actor)
// Stop external animation before clearing ride animation state —
// the ScenePlayer may hold a reference to the ride vehicle ROI.
if (m_animPlaying) {
if (m_animStopCallback) {
m_animStopCallback();
}
m_animPlaying = false;
m_animStopCallback = nullptr;
}
CancelExternalAnim();
if (m_animator.GetCurrentVehicleType() != VEHICLE_NONE) {
m_animator.ClearRideAnimation();
@ -257,8 +257,8 @@ void Controller::Tick(float p_deltaTime)
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);
if (SDL_fabsf(speed) > SPEED_EPSILON) {
m_animator.SetAnimTime(m_animator.GetAnimTime() + p_deltaTime * CharacterAnimator::ANIM_TIME_SCALE);
}
MxMatrix transform(actor->GetROI()->GetLocal2World());
@ -300,7 +300,7 @@ void Controller::Tick(float p_deltaTime)
}
float speed = userActor->GetWorldSpeed();
bool isMoving = SDL_fabsf(speed) > 0.01f;
bool isMoving = SDL_fabsf(speed) > SPEED_EPSILON;
if (m_animator.IsInMultiPartEmote()) {
isMoving = false;
userActor->SetWorldSpeed(0.0f);
@ -341,7 +341,7 @@ void Controller::TriggerEmote(uint8_t p_emoteId)
return;
}
bool isMoving = SDL_fabsf(userActor->GetWorldSpeed()) > 0.01f;
bool isMoving = SDL_fabsf(userActor->GetWorldSpeed()) > SPEED_EPSILON;
if (m_animator.IsInMultiPartEmote()) {
isMoving = false;
}
@ -385,13 +385,7 @@ void Controller::OnWorldDisabled(LegoWorld* p_world)
}
// Stop external animation before destroying the display ROI
if (m_animPlaying) {
if (m_animStopCallback) {
m_animStopCallback();
}
m_animPlaying = false;
m_animStopCallback = nullptr;
}
CancelExternalAnim();
m_active = false;
m_pendingWorldTransition = false;
@ -497,8 +491,7 @@ void Controller::ReinitForCharacter()
m_pendingWorldTransition = false;
VideoManager()->Get3DManager()->Remove(*m_playerROI);
VideoManager()->Get3DManager()->Add(*m_playerROI);
ReaddROI(*m_playerROI);
m_active = true;
m_orbit.SetupCamera(userActor);
m_animator.BuildRideAnimation(vehicleType, m_playerROI, 0);
@ -514,8 +507,7 @@ void Controller::ReinitForCharacter()
m_playerROI->SetVisibility(TRUE);
VideoManager()->Get3DManager()->Remove(*m_playerROI);
VideoManager()->Get3DManager()->Add(*m_playerROI);
ReaddROI(*m_playerROI);
m_animator.InitAnimCaches(m_playerROI);
m_animator.ResetAnimState();

View File

@ -16,7 +16,7 @@ InputHandler::InputHandler()
bool InputHandler::TryClaimFinger(const SDL_TouchFingerEvent& p_event, bool p_active)
{
if (!p_active || m_touch.count >= 2 || p_event.x < CAMERA_ZONE_X) {
if (!p_active || m_touch.count >= 2 || p_event.x < CAMERA_ZONE_X || IsFingerTracked(p_event.fingerID)) {
return false;
}
@ -104,7 +104,7 @@ void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool
m_wantsAutoDisable = true;
break;
}
p_orbit.AdjustDistance(-p_event->wheel.y * 0.5f);
p_orbit.AdjustDistance(-p_event->wheel.y * MOUSE_WHEEL_ZOOM_STEP);
p_orbit.ClampDistance();
break;
@ -113,8 +113,8 @@ void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool
break;
}
if (m_rightButtonHeld) {
p_orbit.AdjustYaw(-p_event->motion.xrel * 0.005f);
p_orbit.AdjustPitch(p_event->motion.yrel * 0.005f);
p_orbit.AdjustYaw(-p_event->motion.xrel * MOUSE_SENSITIVITY);
p_orbit.AdjustPitch(p_event->motion.yrel * MOUSE_SENSITIVITY);
p_orbit.ClampPitch();
}
break;
@ -144,24 +144,9 @@ void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool
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.synced[idx] = true;
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);
m_touch.gesturePinchDist = m_touch.initialPinchDist;
}
}
case SDL_EVENT_FINGER_DOWN:
TryClaimFinger(p_event->tfinger, p_active);
break;
}
case SDL_EVENT_FINGER_MOTION: {
if (m_touch.count == 1) {
@ -183,8 +168,8 @@ void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool
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.AdjustYaw(-moveX * TOUCH_YAW_PITCH_SCALE);
p_orbit.AdjustPitch(moveY * TOUCH_YAW_PITCH_SCALE);
p_orbit.ClampPitch();
}
}
@ -246,15 +231,15 @@ void InputHandler::HandleSDLEvent(SDL_Event* p_event, OrbitCamera& p_orbit, bool
}
}
p_orbit.AdjustDistance(pinchDelta * 6.0f);
p_orbit.AdjustDistance(pinchDelta * PINCH_ZOOM_SCALE);
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.AdjustYaw(-moveX * TOUCH_YAW_PITCH_SCALE);
p_orbit.AdjustPitch(moveY * TOUCH_YAW_PITCH_SCALE);
p_orbit.ClampPitch();
}
break;

View File

@ -13,8 +13,6 @@
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)
@ -131,11 +129,10 @@ void OrbitCamera::RestoreFirstPersonCamera()
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)
);
static const Mx3DPointFloat eyeOffset(0.0f, 1.25f, 0.0f);
static const Mx3DPointFloat forward(0.0f, 0.0f, 1.0f);
static const Mx3DPointFloat up(0.0f, 1.0f, 0.0f);
world->GetCameraController()->SetWorldTransform(eyeOffset, forward, up);
userActor->TransformPointOfView();
}
}
@ -189,13 +186,13 @@ MxBool OrbitCamera::HandleCameraRelativeMovement(
if (keyFlags == 0 && !p_lmbHeld && 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;
float jx = (joystickX - JOYSTICK_CENTER) / JOYSTICK_CENTER;
float jy = -(joystickY - JOYSTICK_CENTER) / JOYSTICK_CENTER;
if (SDL_fabsf(jx) < 0.1f) {
if (SDL_fabsf(jx) < JOYSTICK_DEAD_ZONE) {
jx = 0.0f;
}
if (SDL_fabsf(jy) < 0.1f) {
if (SDL_fabsf(jy) < JOYSTICK_DEAD_ZONE) {
jy = 0.0f;
}
@ -205,7 +202,7 @@ MxBool OrbitCamera::HandleCameraRelativeMovement(
}
float moveDirLen = SDL_sqrtf(moveDirX * moveDirX + moveDirZ * moveDirZ);
bool hasInput = moveDirLen > 0.001f;
bool hasInput = moveDirLen > MOVEMENT_DIR_EPSILON;
if (p_isInMultiPartEmote) {
hasInput = false;
@ -256,7 +253,7 @@ MxBool OrbitCamera::HandleCameraRelativeMovement(
angleDiff += 2.0f * SDL_PI_F;
}
float maxTurn = TURN_RATE * p_deltaTime;
float maxTurn = CHARACTER_TURN_RATE * p_deltaTime;
if (SDL_fabsf(angleDiff) > maxTurn) {
angleDiff = angleDiff > 0 ? maxTurn : -maxTurn;
}