mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-02 02:23:56 +00:00
Extract CharacterAnimator component, EncodeUsername utility, replace C stdlib with SDL
Extract ~420 lines of duplicated character animation logic from RemotePlayer and ThirdPersonCamera into a shared CharacterAnimator component. Both classes now compose a CharacterAnimator member that handles walk/idle/emote animation playback, vehicle ride animations, click animation tracking, name bubbles, and animation cache management. Behavioral differences between consumers (emote transform save/restore) are handled via CharacterAnimatorConfig. Also extract duplicated username encoding (letter indices to ASCII) from NetworkManager::BroadcastLocalState and ThirdPersonCamera::CreateNameBubble into EncodeUsername() in protocol.cpp. Replace C standard library usage across the multiplayer extension with SDL equivalents: sprintf->SDL_snprintf, sscanf->SDL_sscanf, atoi->SDL_atoi, strcmp->SDL_strcmp, fabsf->SDL_fabsf, floorf->SDL_floorf, and remove unnecessary <cmath>, <cstdio>, <cstdlib> headers.
This commit is contained in:
parent
9e8ecd6d44
commit
a5b2ea0ce9
@ -533,6 +533,7 @@ if (ISLE_EXTENSIONS)
|
||||
extensions/src/textureloader.cpp
|
||||
extensions/src/multiplayer.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
|
||||
|
||||
124
extensions/include/extensions/multiplayer/characteranimator.h
Normal file
124
extensions/include/extensions/multiplayer/characteranimator.h
Normal file
@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
|
||||
#include "extensions/multiplayer/animutils.h"
|
||||
#include "extensions/multiplayer/protocol.h"
|
||||
#include "mxgeometry/mxmatrix.h"
|
||||
#include "mxtypes.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class LegoROI;
|
||||
class LegoAnim;
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
|
||||
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).
|
||||
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.
|
||||
class CharacterAnimator {
|
||||
public:
|
||||
explicit CharacterAnimator(const CharacterAnimatorConfig& p_config);
|
||||
~CharacterAnimator();
|
||||
|
||||
// Core animation tick. Call each frame with the character's ROI and movement state.
|
||||
void Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving);
|
||||
|
||||
// Walk/idle animation selection
|
||||
void SetWalkAnimId(uint8_t p_walkAnimId, LegoROI* p_roi);
|
||||
void SetIdleAnimId(uint8_t p_idleAnimId, LegoROI* p_roi);
|
||||
uint8_t GetWalkAnimId() const { return m_walkAnimId; }
|
||||
uint8_t GetIdleAnimId() const { return m_idleAnimId; }
|
||||
|
||||
// Emote playback
|
||||
void TriggerEmote(uint8_t p_emoteId, LegoROI* p_roi, bool p_isMoving);
|
||||
|
||||
// Click animation tracking
|
||||
void SetClickAnimObjectId(MxU32 p_clickAnimObjectId) { m_clickAnimObjectId = p_clickAnimObjectId; }
|
||||
void StopClickAnimation();
|
||||
|
||||
// Vehicle ride animation
|
||||
void BuildRideAnimation(int8_t p_vehicleType, LegoROI* p_playerROI, uint32_t p_vehicleSuffix);
|
||||
void ClearRideAnimation();
|
||||
int8_t GetCurrentVehicleType() const { return m_currentVehicleType; }
|
||||
void SetCurrentVehicleType(int8_t p_vehicleType) { m_currentVehicleType = p_vehicleType; }
|
||||
bool IsInVehicle() const { return m_currentVehicleType != VEHICLE_NONE; }
|
||||
LegoROI* GetRideVehicleROI() const { return m_rideVehicleROI; }
|
||||
LegoAnim* GetRideAnim() const { return m_rideAnim; }
|
||||
LegoROI** GetRideRoiMap() const { return m_rideRoiMap; }
|
||||
MxU32 GetRideRoiMapSize() const { return m_rideRoiMapSize; }
|
||||
|
||||
// Animation cache management
|
||||
void InitAnimCaches(LegoROI* p_roi);
|
||||
void ClearAnimCaches();
|
||||
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; }
|
||||
|
||||
// Animation time (needed for vehicle ride tick in ThirdPersonCamera)
|
||||
float GetAnimTime() const { return m_animTime; }
|
||||
void SetAnimTime(float p_time) { m_animTime = p_time; }
|
||||
void ResetAnimState();
|
||||
|
||||
private:
|
||||
using AnimCache = AnimUtils::AnimCache;
|
||||
|
||||
AnimCache* GetOrBuildAnimCache(LegoROI* p_roi, const char* p_animName);
|
||||
|
||||
CharacterAnimatorConfig m_config;
|
||||
|
||||
// Walk/idle state
|
||||
uint8_t m_walkAnimId;
|
||||
uint8_t m_idleAnimId;
|
||||
AnimCache* m_walkAnimCache;
|
||||
AnimCache* m_idleAnimCache;
|
||||
float m_animTime;
|
||||
float m_idleTime;
|
||||
float m_idleAnimTime;
|
||||
bool m_wasMoving;
|
||||
|
||||
// Emote state
|
||||
AnimCache* m_emoteAnimCache;
|
||||
float m_emoteTime;
|
||||
float m_emoteDuration;
|
||||
bool m_emoteActive;
|
||||
MxMatrix m_emoteParentTransform;
|
||||
|
||||
// Click animation tracking (0 = none)
|
||||
MxU32 m_clickAnimObjectId;
|
||||
|
||||
// ROI map cache: animation name -> cached ROI map
|
||||
std::map<std::string, AnimCache> m_animCacheMap;
|
||||
|
||||
// Ride animation (vehicle-specific)
|
||||
LegoAnim* m_rideAnim;
|
||||
LegoROI** m_rideRoiMap;
|
||||
MxU32 m_rideRoiMapSize;
|
||||
LegoROI* m_rideVehicleROI;
|
||||
|
||||
int8_t m_currentVehicleType;
|
||||
|
||||
NameBubbleRenderer* m_nameBubble;
|
||||
};
|
||||
|
||||
} // namespace Multiplayer
|
||||
@ -33,7 +33,7 @@ class NetworkManager : public MxCore {
|
||||
|
||||
MxBool IsA(const char* p_name) const override
|
||||
{
|
||||
return !strcmp(p_name, NetworkManager::ClassName()) || MxCore::IsA(p_name);
|
||||
return !SDL_strcmp(p_name, NetworkManager::ClassName()) || MxCore::IsA(p_name);
|
||||
}
|
||||
|
||||
void Initialize(NetworkTransport* p_transport, PlatformCallbacks* p_callbacks);
|
||||
@ -53,8 +53,14 @@ class NetworkManager : public MxCore {
|
||||
// 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().
|
||||
void RequestToggleThirdPerson() { m_pendingToggleThirdPerson.store(true, std::memory_order_relaxed); }
|
||||
void RequestSetWalkAnimation(uint8_t p_walkAnimId) { m_pendingWalkAnim.store(p_walkAnimId, std::memory_order_relaxed); }
|
||||
void RequestSetIdleAnimation(uint8_t p_idleAnimId) { m_pendingIdleAnim.store(p_idleAnimId, std::memory_order_relaxed); }
|
||||
void RequestSetWalkAnimation(uint8_t p_walkAnimId)
|
||||
{
|
||||
m_pendingWalkAnim.store(p_walkAnimId, std::memory_order_relaxed);
|
||||
}
|
||||
void RequestSetIdleAnimation(uint8_t p_idleAnimId)
|
||||
{
|
||||
m_pendingIdleAnim.store(p_idleAnimId, std::memory_order_relaxed);
|
||||
}
|
||||
void RequestSendEmote(uint8_t p_emoteId) { m_pendingEmote.store(p_emoteId, std::memory_order_relaxed); }
|
||||
void RequestToggleNameBubbles() { m_pendingToggleNameBubbles.store(true, std::memory_order_relaxed); }
|
||||
void RequestToggleAllowCustomize() { m_pendingToggleAllowCustomize.store(true, std::memory_order_relaxed); }
|
||||
|
||||
@ -11,8 +11,8 @@ namespace Multiplayer
|
||||
{
|
||||
|
||||
// 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
|
||||
const uint32_t TARGET_BROADCAST = 0; // Broadcast to all except sender
|
||||
const uint32_t TARGET_HOST = 0xFFFFFFFF; // Send to host only
|
||||
const uint32_t TARGET_BROADCAST_ALL = 0xFFFFFFFE; // Broadcast to all including sender
|
||||
|
||||
enum MessageType : uint8_t {
|
||||
@ -94,12 +94,12 @@ struct PlayerStateMsg {
|
||||
float direction[3];
|
||||
float up[3];
|
||||
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)
|
||||
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)
|
||||
uint8_t displayActorIndex; // Index into g_actorInfoInit (0-65)
|
||||
uint8_t customizeData[5]; // Packed CustomizeState
|
||||
uint8_t customizeFlags; // Bit 0 = allowRemoteCustomize
|
||||
uint8_t customizeData[5]; // Packed CustomizeState
|
||||
uint8_t customizeFlags; // Bit 0 = allowRemoteCustomize
|
||||
};
|
||||
|
||||
// Server -> all: announces which peer is the host
|
||||
@ -181,6 +181,10 @@ inline bool IsValidActorId(uint8_t p_actorId)
|
||||
return p_actorId >= 1 && p_actorId <= 5;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// Parse the message type from a buffer. Returns MSG type or 0 on error.
|
||||
|
||||
@ -1,24 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "extensions/multiplayer/animutils.h"
|
||||
#include "extensions/multiplayer/characteranimator.h"
|
||||
#include "extensions/multiplayer/customizestate.h"
|
||||
#include "extensions/multiplayer/protocol.h"
|
||||
#include "mxtypes.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class LegoROI;
|
||||
class LegoWorld;
|
||||
class LegoAnim;
|
||||
class LegoTreeNode;
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
|
||||
class NameBubbleRenderer;
|
||||
|
||||
class RemotePlayer {
|
||||
public:
|
||||
RemotePlayer(uint32_t p_peerId, uint8_t p_actorId, uint8_t p_displayActorIndex);
|
||||
@ -48,18 +43,14 @@ class RemotePlayer {
|
||||
|
||||
const CustomizeState& GetCustomizeState() const { return m_customizeState; }
|
||||
bool GetAllowRemoteCustomize() const { return m_allowRemoteCustomize; }
|
||||
void SetClickAnimObjectId(MxU32 p_clickAnimObjectId) { m_clickAnimObjectId = p_clickAnimObjectId; }
|
||||
void SetClickAnimObjectId(MxU32 p_clickAnimObjectId) { m_animator.SetClickAnimObjectId(p_clickAnimObjectId); }
|
||||
void StopClickAnimation();
|
||||
bool IsInVehicle() const { return m_currentVehicleType != VEHICLE_NONE; }
|
||||
bool IsMoving() const { return m_currentVehicleType != VEHICLE_NONE || m_targetSpeed > 0.01f; }
|
||||
bool IsInVehicle() const { return m_animator.IsInVehicle(); }
|
||||
bool IsMoving() const { return m_animator.IsInVehicle() || m_targetSpeed > 0.01f; }
|
||||
|
||||
private:
|
||||
using AnimCache = AnimUtils::AnimCache;
|
||||
|
||||
AnimCache* GetOrBuildAnimCache(const char* p_animName);
|
||||
const char* GetDisplayActorName() const;
|
||||
void UpdateTransform(float p_deltaTime);
|
||||
void UpdateAnimation(float p_deltaTime);
|
||||
void UpdateVehicleState();
|
||||
void EnterVehicle(int8_t p_vehicleType);
|
||||
void ExitVehicle();
|
||||
@ -87,38 +78,9 @@ class RemotePlayer {
|
||||
float m_currentDirection[3];
|
||||
float m_currentUp[3];
|
||||
|
||||
// Animation state
|
||||
uint8_t m_walkAnimId;
|
||||
uint8_t m_idleAnimId;
|
||||
AnimCache* m_walkAnimCache;
|
||||
AnimCache* m_idleAnimCache;
|
||||
float m_animTime;
|
||||
float m_idleTime;
|
||||
float m_idleAnimTime;
|
||||
bool m_wasMoving;
|
||||
|
||||
// Emote state
|
||||
AnimCache* m_emoteAnimCache;
|
||||
float m_emoteTime;
|
||||
float m_emoteDuration;
|
||||
bool m_emoteActive;
|
||||
|
||||
// Click animation tracking (0 = none)
|
||||
MxU32 m_clickAnimObjectId;
|
||||
|
||||
// ROI map cache: animation name -> cached ROI map (invalidated on world change)
|
||||
std::map<std::string, AnimCache> m_animCacheMap;
|
||||
|
||||
// Ride animation (vehicle-specific, not cached globally)
|
||||
LegoAnim* m_rideAnim;
|
||||
LegoROI** m_rideRoiMap;
|
||||
MxU32 m_rideRoiMapSize;
|
||||
LegoROI* m_rideVehicleROI;
|
||||
CharacterAnimator m_animator;
|
||||
|
||||
LegoROI* m_vehicleROI;
|
||||
int8_t m_currentVehicleType;
|
||||
|
||||
NameBubbleRenderer* m_nameBubble;
|
||||
|
||||
CustomizeState m_customizeState;
|
||||
bool m_allowRemoteCustomize;
|
||||
|
||||
@ -1,28 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "extensions/multiplayer/animutils.h"
|
||||
#include "extensions/multiplayer/characteranimator.h"
|
||||
#include "extensions/multiplayer/customizestate.h"
|
||||
#include "extensions/multiplayer/protocol.h"
|
||||
#include "mxgeometry/mxgeometry3d.h"
|
||||
#include "mxgeometry/mxmatrix.h"
|
||||
#include "mxtypes.h"
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
class IslePathActor;
|
||||
class LegoPathActor;
|
||||
class LegoROI;
|
||||
class LegoWorld;
|
||||
class LegoAnim;
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
|
||||
class NameBubbleRenderer;
|
||||
|
||||
class ThirdPersonCamera {
|
||||
public:
|
||||
ThirdPersonCamera();
|
||||
@ -50,9 +44,9 @@ class ThirdPersonCamera {
|
||||
CustomizeState& GetCustomizeState() { return m_customizeState; }
|
||||
|
||||
void ApplyCustomizeChange(uint8_t p_changeType, uint8_t p_partIndex);
|
||||
void SetClickAnimObjectId(MxU32 p_clickAnimObjectId) { m_clickAnimObjectId = p_clickAnimObjectId; }
|
||||
void SetClickAnimObjectId(MxU32 p_clickAnimObjectId) { m_animator.SetClickAnimObjectId(p_clickAnimObjectId); }
|
||||
void StopClickAnimation();
|
||||
bool IsInVehicle() const { return m_currentVehicleType != VEHICLE_NONE; }
|
||||
bool IsInVehicle() const { return m_animator.IsInVehicle(); }
|
||||
|
||||
void SetNameBubbleVisible(bool p_visible);
|
||||
|
||||
@ -70,14 +64,7 @@ class ThirdPersonCamera {
|
||||
void ClampPitch();
|
||||
void ClampDistance();
|
||||
|
||||
using AnimCache = AnimUtils::AnimCache;
|
||||
|
||||
AnimCache* GetOrBuildAnimCache(const char* p_animName);
|
||||
void ClearAnimCaches();
|
||||
void SetupCamera(LegoPathActor* p_actor);
|
||||
void BuildRideAnimation(int8_t p_vehicleType);
|
||||
void ClearRideAnimation();
|
||||
void ApplyIdleFrame0();
|
||||
void ReinitForCharacter();
|
||||
|
||||
void CreateNameBubble();
|
||||
@ -90,45 +77,17 @@ class ThirdPersonCamera {
|
||||
|
||||
bool m_enabled;
|
||||
bool m_active;
|
||||
bool m_roiUnflipped; // True when Disable() flipped the ROI direction; ReinitForCharacter re-applies
|
||||
bool m_roiUnflipped; // True when Disable() flipped the ROI direction; ReinitForCharacter re-applies
|
||||
LegoROI* m_playerROI; // Borrowed, not owned
|
||||
|
||||
// Display actor override
|
||||
uint8_t m_displayActorIndex;
|
||||
LegoROI* m_displayROI; // Owned clone; nullptr = use native ROI
|
||||
LegoROI* m_displayROI; // Owned clone; nullptr = use native ROI
|
||||
char m_displayUniqueName[32];
|
||||
CustomizeState m_customizeState;
|
||||
|
||||
// Walk/idle state (same pattern as RemotePlayer)
|
||||
uint8_t m_walkAnimId;
|
||||
uint8_t m_idleAnimId;
|
||||
AnimCache* m_walkAnimCache;
|
||||
AnimCache* m_idleAnimCache;
|
||||
float m_animTime;
|
||||
float m_idleTime;
|
||||
float m_idleAnimTime;
|
||||
bool m_wasMoving;
|
||||
CharacterAnimator m_animator;
|
||||
|
||||
// Emote state
|
||||
AnimCache* m_emoteAnimCache;
|
||||
float m_emoteTime;
|
||||
float m_emoteDuration;
|
||||
bool m_emoteActive;
|
||||
MxMatrix m_emoteParentTransform;
|
||||
|
||||
// Click animation tracking (0 = none)
|
||||
MxU32 m_clickAnimObjectId;
|
||||
|
||||
// Vehicle ride state
|
||||
int8_t m_currentVehicleType;
|
||||
LegoAnim* m_rideAnim;
|
||||
LegoROI** m_rideRoiMap;
|
||||
MxU32 m_rideRoiMapSize;
|
||||
LegoROI* m_rideVehicleROI;
|
||||
|
||||
std::map<std::string, AnimCache> m_animCacheMap;
|
||||
|
||||
NameBubbleRenderer* m_nameBubble;
|
||||
bool m_showNameBubble;
|
||||
|
||||
// Orbit camera state
|
||||
|
||||
366
extensions/src/multiplayer/characteranimator.cpp
Normal file
366
extensions/src/multiplayer/characteranimator.cpp
Normal file
@ -0,0 +1,366 @@
|
||||
#include "extensions/multiplayer/characteranimator.h"
|
||||
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "anim/legoanim.h"
|
||||
#include "extensions/multiplayer/charactercustomizer.h"
|
||||
#include "extensions/multiplayer/namebubblerenderer.h"
|
||||
#include "legoanimpresenter.h"
|
||||
#include "legocharactermanager.h"
|
||||
#include "legovideomanager.h"
|
||||
#include "legoworld.h"
|
||||
#include "misc.h"
|
||||
#include "misc/legotree.h"
|
||||
#include "realtime/realtime.h"
|
||||
#include "roi/legoroi.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
using namespace Multiplayer;
|
||||
|
||||
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_clickAnimObjectId(0), m_rideAnim(nullptr),
|
||||
m_rideRoiMap(nullptr), m_rideRoiMapSize(0), m_rideVehicleROI(nullptr), m_currentVehicleType(VEHICLE_NONE),
|
||||
m_nameBubble(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
CharacterAnimator::~CharacterAnimator()
|
||||
{
|
||||
DestroyNameBubble();
|
||||
ClearRideAnimation();
|
||||
}
|
||||
|
||||
CharacterAnimator::AnimCache* CharacterAnimator::GetOrBuildAnimCache(LegoROI* p_roi, const char* p_animName)
|
||||
{
|
||||
return AnimUtils::GetOrBuildAnimCache(m_animCacheMap, p_roi, p_animName);
|
||||
}
|
||||
|
||||
void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
|
||||
{
|
||||
if (m_currentVehicleType != VEHICLE_NONE && IsLargeVehicle(m_currentVehicleType)) {
|
||||
StopClickAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the active walk/ride animation and its ROI map
|
||||
LegoAnim* walkAnim = nullptr;
|
||||
LegoROI** walkRoiMap = nullptr;
|
||||
MxU32 walkRoiMapSize = 0;
|
||||
|
||||
if (m_currentVehicleType != VEHICLE_NONE && m_rideAnim && m_rideRoiMap) {
|
||||
walkAnim = m_rideAnim;
|
||||
walkRoiMap = m_rideRoiMap;
|
||||
walkRoiMapSize = m_rideRoiMapSize;
|
||||
}
|
||||
else if (m_walkAnimCache && m_walkAnimCache->anim && m_walkAnimCache->roiMap) {
|
||||
walkAnim = m_walkAnimCache->anim;
|
||||
walkRoiMap = m_walkAnimCache->roiMap;
|
||||
walkRoiMapSize = m_walkAnimCache->roiMapSize;
|
||||
}
|
||||
|
||||
// Ensure visibility of all mapped ROIs
|
||||
if (walkRoiMap) {
|
||||
AnimUtils::EnsureROIMapVisibility(walkRoiMap, walkRoiMapSize);
|
||||
}
|
||||
if (m_idleAnimCache && m_idleAnimCache->roiMap) {
|
||||
AnimUtils::EnsureROIMapVisibility(m_idleAnimCache->roiMap, m_idleAnimCache->roiMapSize);
|
||||
}
|
||||
|
||||
bool inVehicle = (m_currentVehicleType != VEHICLE_NONE);
|
||||
bool isMoving = inVehicle || p_isMoving;
|
||||
|
||||
// Movement interrupts click animations and emotes
|
||||
if (isMoving) {
|
||||
StopClickAnimation();
|
||||
if (m_emoteActive) {
|
||||
m_emoteActive = false;
|
||||
m_emoteAnimCache = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMoving) {
|
||||
// Walking / riding
|
||||
if (!walkAnim || !walkRoiMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_isMoving) {
|
||||
m_animTime += p_deltaTime * 2000.0f;
|
||||
}
|
||||
float duration = (float) walkAnim->GetDuration();
|
||||
if (duration > 0.0f) {
|
||||
float timeInCycle = m_animTime - duration * SDL_floorf(m_animTime / duration);
|
||||
|
||||
MxMatrix transform(p_roi->GetLocal2World());
|
||||
LegoTreeNode* root = walkAnim->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(root->GetChild(i), transform, (LegoTime) timeInCycle, walkRoiMap);
|
||||
}
|
||||
}
|
||||
m_wasMoving = true;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
}
|
||||
else if (m_emoteActive && m_emoteAnimCache && m_emoteAnimCache->anim && m_emoteAnimCache->roiMap) {
|
||||
// Emote playback (one-shot)
|
||||
m_emoteTime += p_deltaTime * 1000.0f;
|
||||
|
||||
if (m_emoteTime >= m_emoteDuration) {
|
||||
// Emote completed -- return to stationary flow
|
||||
m_emoteActive = false;
|
||||
m_emoteAnimCache = nullptr;
|
||||
m_wasMoving = false;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
}
|
||||
else {
|
||||
MxMatrix transform(m_config.saveEmoteTransform ? m_emoteParentTransform : p_roi->GetLocal2World());
|
||||
|
||||
LegoTreeNode* root = m_emoteAnimCache->anim->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(
|
||||
root->GetChild(i),
|
||||
transform,
|
||||
(LegoTime) m_emoteTime,
|
||||
m_emoteAnimCache->roiMap
|
||||
);
|
||||
}
|
||||
|
||||
// Restore player ROI transform (animation root overwrote it).
|
||||
if (m_config.saveEmoteTransform) {
|
||||
p_roi->WrappedSetLocal2WorldWithWorldDataUpdate(m_emoteParentTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_idleAnimCache && m_idleAnimCache->anim && m_idleAnimCache->roiMap) {
|
||||
// Idle animation
|
||||
if (m_wasMoving) {
|
||||
m_wasMoving = false;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
}
|
||||
|
||||
m_idleTime += p_deltaTime;
|
||||
|
||||
// Hold standing pose for 2.5s, then loop breathing/swaying
|
||||
if (m_idleTime >= 2.5f) {
|
||||
m_idleAnimTime += p_deltaTime * 1000.0f;
|
||||
}
|
||||
|
||||
float duration = (float) m_idleAnimCache->anim->GetDuration();
|
||||
if (duration > 0.0f) {
|
||||
float timeInCycle = m_idleAnimTime - duration * SDL_floorf(m_idleAnimTime / duration);
|
||||
|
||||
MxMatrix transform(p_roi->GetLocal2World());
|
||||
LegoTreeNode* root = m_idleAnimCache->anim->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(
|
||||
root->GetChild(i),
|
||||
transform,
|
||||
(LegoTime) timeInCycle,
|
||||
m_idleAnimCache->roiMap
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAnimator::SetWalkAnimId(uint8_t p_walkAnimId, LegoROI* p_roi)
|
||||
{
|
||||
if (p_walkAnimId >= g_walkAnimCount) {
|
||||
return;
|
||||
}
|
||||
if (p_walkAnimId != m_walkAnimId) {
|
||||
m_walkAnimId = p_walkAnimId;
|
||||
if (p_roi) {
|
||||
m_walkAnimCache = GetOrBuildAnimCache(p_roi, g_walkAnimNames[m_walkAnimId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAnimator::SetIdleAnimId(uint8_t p_idleAnimId, LegoROI* p_roi)
|
||||
{
|
||||
if (p_idleAnimId >= g_idleAnimCount) {
|
||||
return;
|
||||
}
|
||||
if (p_idleAnimId != m_idleAnimId) {
|
||||
m_idleAnimId = p_idleAnimId;
|
||||
if (p_roi) {
|
||||
m_idleAnimCache = GetOrBuildAnimCache(p_roi, g_idleAnimNames[m_idleAnimId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAnimator::TriggerEmote(uint8_t p_emoteId, LegoROI* p_roi, bool p_isMoving)
|
||||
{
|
||||
if (p_emoteId >= g_emoteAnimCount || !p_roi) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only play emotes when stationary
|
||||
if (p_isMoving) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnimCache* cache = GetOrBuildAnimCache(p_roi, g_emoteAnimNames[p_emoteId]);
|
||||
if (!cache || !cache->anim) {
|
||||
return;
|
||||
}
|
||||
|
||||
StopClickAnimation();
|
||||
|
||||
m_emoteAnimCache = cache;
|
||||
m_emoteTime = 0.0f;
|
||||
m_emoteDuration = (float) cache->anim->GetDuration();
|
||||
m_emoteActive = true;
|
||||
|
||||
// Save clean transform to prevent scale accumulation during emote
|
||||
if (m_config.saveEmoteTransform) {
|
||||
m_emoteParentTransform = p_roi->GetLocal2World();
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAnimator::StopClickAnimation()
|
||||
{
|
||||
if (m_clickAnimObjectId != 0) {
|
||||
CharacterCustomizer::StopClickAnimation(m_clickAnimObjectId);
|
||||
m_clickAnimObjectId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterAnimator::BuildRideAnimation(int8_t p_vehicleType, LegoROI* p_playerROI, uint32_t p_vehicleSuffix)
|
||||
{
|
||||
if (p_vehicleType < 0 || p_vehicleType >= VEHICLE_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* rideAnimName = g_rideAnimNames[p_vehicleType];
|
||||
const char* vehicleVariantName = g_rideVehicleROINames[p_vehicleType];
|
||||
if (!rideAnimName || !vehicleVariantName) {
|
||||
return;
|
||||
}
|
||||
|
||||
LegoWorld* world = CurrentWorld();
|
||||
if (!world) {
|
||||
return;
|
||||
}
|
||||
|
||||
MxCore* presenter = world->Find("LegoAnimPresenter", rideAnimName);
|
||||
if (!presenter) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_rideAnim = static_cast<LegoAnimPresenter*>(presenter)->GetAnimation();
|
||||
if (!m_rideAnim) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create variant ROI, rename to match animation tree.
|
||||
const char* baseName = g_vehicleROINames[p_vehicleType];
|
||||
char variantName[48];
|
||||
if (p_vehicleSuffix != 0) {
|
||||
SDL_snprintf(variantName, sizeof(variantName), "%s_mp_%u", vehicleVariantName, p_vehicleSuffix);
|
||||
}
|
||||
else {
|
||||
SDL_snprintf(variantName, sizeof(variantName), "tp_vehicle");
|
||||
}
|
||||
m_rideVehicleROI = CharacterManager()->CreateAutoROI(variantName, baseName, FALSE);
|
||||
if (m_rideVehicleROI) {
|
||||
m_rideVehicleROI->SetName(vehicleVariantName);
|
||||
}
|
||||
|
||||
AnimUtils::BuildROIMap(m_rideAnim, p_playerROI, m_rideVehicleROI, m_rideRoiMap, m_rideRoiMapSize);
|
||||
m_animTime = 0.0f;
|
||||
}
|
||||
|
||||
void CharacterAnimator::ClearRideAnimation()
|
||||
{
|
||||
if (m_rideRoiMap) {
|
||||
delete[] m_rideRoiMap;
|
||||
m_rideRoiMap = nullptr;
|
||||
m_rideRoiMapSize = 0;
|
||||
}
|
||||
if (m_rideVehicleROI) {
|
||||
VideoManager()->Get3DManager()->Remove(*m_rideVehicleROI);
|
||||
CharacterManager()->ReleaseAutoROI(m_rideVehicleROI);
|
||||
m_rideVehicleROI = nullptr;
|
||||
}
|
||||
m_rideAnim = nullptr;
|
||||
m_currentVehicleType = VEHICLE_NONE;
|
||||
}
|
||||
|
||||
void CharacterAnimator::InitAnimCaches(LegoROI* p_roi)
|
||||
{
|
||||
m_walkAnimCache = GetOrBuildAnimCache(p_roi, g_walkAnimNames[m_walkAnimId]);
|
||||
m_idleAnimCache = GetOrBuildAnimCache(p_roi, g_idleAnimNames[m_idleAnimId]);
|
||||
}
|
||||
|
||||
void CharacterAnimator::ClearAnimCaches()
|
||||
{
|
||||
m_walkAnimCache = nullptr;
|
||||
m_idleAnimCache = nullptr;
|
||||
m_emoteAnimCache = nullptr;
|
||||
m_emoteActive = false;
|
||||
}
|
||||
|
||||
void CharacterAnimator::ClearAll()
|
||||
{
|
||||
m_animCacheMap.clear();
|
||||
ClearAnimCaches();
|
||||
}
|
||||
|
||||
void CharacterAnimator::ResetAnimState()
|
||||
{
|
||||
m_animTime = 0.0f;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
m_wasMoving = false;
|
||||
m_emoteActive = false;
|
||||
}
|
||||
|
||||
void CharacterAnimator::ApplyIdleFrame0(LegoROI* p_roi)
|
||||
{
|
||||
if (!p_roi || !m_idleAnimCache || !m_idleAnimCache->anim || !m_idleAnimCache->roiMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
MxMatrix transform(p_roi->GetLocal2World());
|
||||
LegoTreeNode* root = m_idleAnimCache->anim->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -11,7 +11,6 @@
|
||||
#include "viewmanager/viewlodlist.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <cstdio>
|
||||
#include <vec.h>
|
||||
|
||||
using namespace Multiplayer;
|
||||
@ -69,7 +68,7 @@ LegoROI* CharacterCloner::Clone(LegoCharacterManager* p_charMgr, const char* p_u
|
||||
|
||||
ViewLODList* lodList = lodManager->Lookup(parentName);
|
||||
MxS32 lodSize = lodList->Size();
|
||||
sprintf(lodName, "%s%d", p_uniqueName, i);
|
||||
SDL_snprintf(lodName, sizeof(lodName), "%s%d", p_uniqueName, i);
|
||||
ViewLODList* dupLodList = lodManager->Create(lodName, lodSize);
|
||||
|
||||
for (MxS32 j = 0; j < lodSize; j++) {
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
#include "extensions/multiplayer/charactercustomizer.h"
|
||||
|
||||
#include "extensions/multiplayer/charactercloner.h"
|
||||
#include "extensions/multiplayer/customizestate.h"
|
||||
#include "extensions/multiplayer/protocol.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 "legoactors.h"
|
||||
#include "legocharactermanager.h"
|
||||
#include "legovideomanager.h"
|
||||
@ -19,7 +18,6 @@
|
||||
#include "viewmanager/viewmanager.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <cstdio>
|
||||
|
||||
using namespace Multiplayer;
|
||||
|
||||
@ -195,20 +193,15 @@ int CharacterCustomizer::MapClickedPartIndex(const char* p_partName)
|
||||
return -1;
|
||||
}
|
||||
|
||||
void CharacterCustomizer::ApplyFullState(
|
||||
LegoROI* p_rootROI,
|
||||
uint8_t p_actorInfoIndex,
|
||||
const CustomizeState& p_state
|
||||
)
|
||||
void CharacterCustomizer::ApplyFullState(LegoROI* p_rootROI, uint8_t p_actorInfoIndex, const CustomizeState& p_state)
|
||||
{
|
||||
if (p_actorInfoIndex >= sizeOfArray(g_actorInfoInit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply colors for the 6 independent colorable parts
|
||||
static const int colorableParts[] = {
|
||||
c_infohatPart, c_infogronPart, c_armlftPart, c_armrtPart, c_leglftPart, c_legrtPart
|
||||
};
|
||||
static const int colorableParts[] =
|
||||
{c_infohatPart, c_infogronPart, c_armlftPart, c_armrtPart, c_leglftPart, c_legrtPart};
|
||||
|
||||
for (int i = 0; i < (int) sizeOfArray(colorableParts); i++) {
|
||||
int partIndex = colorableParts[i];
|
||||
@ -242,11 +235,7 @@ void CharacterCustomizer::ApplyFullState(
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterCustomizer::ApplyHatVariant(
|
||||
LegoROI* p_rootROI,
|
||||
uint8_t p_actorInfoIndex,
|
||||
const CustomizeState& p_state
|
||||
)
|
||||
void CharacterCustomizer::ApplyHatVariant(LegoROI* p_rootROI, uint8_t p_actorInfoIndex, const CustomizeState& p_state)
|
||||
{
|
||||
if (p_actorInfoIndex >= sizeOfArray(g_actorInfoInit)) {
|
||||
return;
|
||||
@ -266,7 +255,7 @@ void CharacterCustomizer::ApplyHatVariant(
|
||||
|
||||
ViewLODList* lodList = GetViewLODListManager()->Lookup(part.m_partName[partNameIndex]);
|
||||
MxS32 lodSize = lodList->Size();
|
||||
sprintf(lodName, "%s_cv%u", p_rootROI->GetName(), s_variantCounter++);
|
||||
SDL_snprintf(lodName, sizeof(lodName), "%s_cv%u", p_rootROI->GetName(), s_variantCounter++);
|
||||
ViewLODList* dupLodList = GetViewLODListManager()->Create(lodName, lodSize);
|
||||
|
||||
Tgl::Renderer* renderer = VideoManager()->GetRenderer();
|
||||
@ -300,8 +289,8 @@ void CharacterCustomizer::ApplyHatVariant(
|
||||
|
||||
void CharacterCustomizer::PlayClickSound(LegoROI* p_roi, const CustomizeState& p_state, bool p_basedOnMood)
|
||||
{
|
||||
MxU32 objectId = p_basedOnMood ? (p_state.mood + g_characterSoundIdMoodOffset)
|
||||
: (p_state.sound + g_characterSoundIdOffset);
|
||||
MxU32 objectId =
|
||||
p_basedOnMood ? (p_state.mood + g_characterSoundIdMoodOffset) : (p_state.sound + g_characterSoundIdOffset);
|
||||
|
||||
if (objectId) {
|
||||
MxDSAction action;
|
||||
|
||||
@ -334,25 +334,7 @@ void NetworkManager::BroadcastLocalState()
|
||||
msg.walkAnimId = m_localWalkAnimId;
|
||||
msg.idleAnimId = m_localIdleAnimId;
|
||||
|
||||
// Convert Username letters (0-25 = A-Z) to ASCII string.
|
||||
// The active player is always at m_players[0] after RegisterPlayer/SwitchPlayer.
|
||||
SDL_memset(msg.name, 0, sizeof(msg.name));
|
||||
LegoGameState* gs = GameState();
|
||||
if (gs && gs->m_playerCount > 0) {
|
||||
const LegoGameState::Username& username = gs->m_players[0];
|
||||
for (int i = 0; i < 7; i++) {
|
||||
MxS16 letter = username.m_letters[i];
|
||||
if (letter < 0) {
|
||||
break;
|
||||
}
|
||||
if (letter <= 25) {
|
||||
msg.name[i] = (char) ('A' + letter);
|
||||
}
|
||||
else {
|
||||
msg.name[i] = '?';
|
||||
}
|
||||
}
|
||||
}
|
||||
EncodeUsername(msg.name);
|
||||
|
||||
msg.displayActorIndex = m_localDisplayActorIndex;
|
||||
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
#include "extensions/multiplayer/protocol.h"
|
||||
|
||||
#include "legogamestate.h"
|
||||
#include "legopathactor.h"
|
||||
#include "misc.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
namespace Multiplayer
|
||||
{
|
||||
@ -36,12 +38,10 @@ const char* const g_vehicleROINames[VEHICLE_COUNT] =
|
||||
{"chtrbody", "jsuser", "dunebugy", "bike", "board", "moto", "towtk", "ambul"};
|
||||
|
||||
// Ride animation names for small vehicles (NULL = large vehicle, no ride anim)
|
||||
const char* const g_rideAnimNames[VEHICLE_COUNT] =
|
||||
{NULL, NULL, NULL, "CNs001Bd", "CNs001sk", "CNs011Ni", NULL, NULL};
|
||||
const char* const g_rideAnimNames[VEHICLE_COUNT] = {NULL, NULL, NULL, "CNs001Bd", "CNs001sk", "CNs011Ni", NULL, NULL};
|
||||
|
||||
// Vehicle variant ROI names used in ride animations
|
||||
const char* const g_rideVehicleROINames[VEHICLE_COUNT] =
|
||||
{NULL, NULL, NULL, "bikebd", "board", "motoni", NULL, NULL};
|
||||
const char* const g_rideVehicleROINames[VEHICLE_COUNT] = {NULL, NULL, NULL, "bikebd", "board", "motoni", NULL, NULL};
|
||||
|
||||
bool IsLargeVehicle(int8_t p_vehicleType)
|
||||
{
|
||||
@ -76,4 +76,25 @@ int8_t DetectVehicleType(LegoPathActor* p_actor)
|
||||
return VEHICLE_NONE;
|
||||
}
|
||||
|
||||
void EncodeUsername(char p_out[8])
|
||||
{
|
||||
SDL_memset(p_out, 0, 8);
|
||||
LegoGameState* gs = GameState();
|
||||
if (gs && gs->m_playerCount > 0) {
|
||||
const LegoGameState::Username& username = gs->m_players[0];
|
||||
for (int i = 0; i < 7; i++) {
|
||||
MxS16 letter = username.m_letters[i];
|
||||
if (letter < 0) {
|
||||
break;
|
||||
}
|
||||
if (letter <= 25) {
|
||||
p_out[i] = (char) ('A' + letter);
|
||||
}
|
||||
else {
|
||||
p_out[i] = '?';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Multiplayer
|
||||
|
||||
@ -1,17 +1,12 @@
|
||||
#include "extensions/multiplayer/remoteplayer.h"
|
||||
|
||||
#include "extensions/multiplayer/charactercustomizer.h"
|
||||
#include "extensions/multiplayer/namebubblerenderer.h"
|
||||
|
||||
#include "3dmanager/lego3dmanager.h"
|
||||
#include "anim/legoanim.h"
|
||||
#include "extensions/multiplayer/charactercloner.h"
|
||||
#include "legoanimpresenter.h"
|
||||
#include "extensions/multiplayer/charactercustomizer.h"
|
||||
#include "legocharactermanager.h"
|
||||
#include "legovideomanager.h"
|
||||
#include "legoworld.h"
|
||||
#include "misc.h"
|
||||
#include "misc/legotree.h"
|
||||
#include "mxgeometry/mxgeometry3d.h"
|
||||
#include "realtime/realtime.h"
|
||||
#include "roi/legoroi.h"
|
||||
@ -19,7 +14,6 @@
|
||||
#include <SDL3/SDL_log.h>
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <cmath>
|
||||
#include <vec.h>
|
||||
|
||||
using namespace Multiplayer;
|
||||
@ -27,11 +21,8 @@ using namespace Multiplayer;
|
||||
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_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_clickAnimObjectId(0), m_rideAnim(nullptr), m_rideRoiMap(nullptr), m_rideRoiMapSize(0),
|
||||
m_rideVehicleROI(nullptr), m_vehicleROI(nullptr), m_currentVehicleType(VEHICLE_NONE), m_nameBubble(nullptr),
|
||||
m_lastUpdateTime(SDL_GetTicks()), m_hasReceivedUpdate(false),
|
||||
m_animator(CharacterAnimatorConfig{/*.saveEmoteTransform=*/false}), m_vehicleROI(nullptr),
|
||||
m_allowRemoteCustomize(true)
|
||||
{
|
||||
m_displayName[0] = '\0';
|
||||
@ -89,8 +80,7 @@ void RemotePlayer::Spawn(LegoWorld* p_isleWorld)
|
||||
m_customizeState.InitFromActorInfo(actorInfoIndex);
|
||||
|
||||
// Build initial walk and idle animation caches
|
||||
m_walkAnimCache = GetOrBuildAnimCache(g_walkAnimNames[m_walkAnimId]);
|
||||
m_idleAnimCache = GetOrBuildAnimCache(g_idleAnimNames[m_idleAnimId]);
|
||||
m_animator.InitAnimCaches(m_roi);
|
||||
|
||||
// Create name bubble if we already have a name
|
||||
if (m_displayName[0] != '\0') {
|
||||
@ -104,7 +94,7 @@ void RemotePlayer::Despawn()
|
||||
return;
|
||||
}
|
||||
|
||||
StopClickAnimation();
|
||||
m_animator.StopClickAnimation();
|
||||
DestroyNameBubble();
|
||||
ExitVehicle();
|
||||
|
||||
@ -115,11 +105,7 @@ void RemotePlayer::Despawn()
|
||||
}
|
||||
|
||||
// Clear cached animation ROI maps (anim pointers are world-owned).
|
||||
m_animCacheMap.clear();
|
||||
m_walkAnimCache = nullptr;
|
||||
m_idleAnimCache = nullptr;
|
||||
m_emoteAnimCache = nullptr;
|
||||
m_emoteActive = false;
|
||||
m_animator.ClearAll();
|
||||
|
||||
m_spawned = false;
|
||||
m_visible = false;
|
||||
@ -187,15 +173,13 @@ void RemotePlayer::UpdateFromNetwork(const PlayerStateMsg& p_msg)
|
||||
m_allowRemoteCustomize = (p_msg.customizeFlags & 0x01) != 0;
|
||||
|
||||
// Swap walk animation if changed
|
||||
if (p_msg.walkAnimId != m_walkAnimId && p_msg.walkAnimId < g_walkAnimCount) {
|
||||
m_walkAnimId = p_msg.walkAnimId;
|
||||
m_walkAnimCache = GetOrBuildAnimCache(g_walkAnimNames[m_walkAnimId]);
|
||||
if (p_msg.walkAnimId != m_animator.GetWalkAnimId() && p_msg.walkAnimId < g_walkAnimCount) {
|
||||
m_animator.SetWalkAnimId(p_msg.walkAnimId, m_roi);
|
||||
}
|
||||
|
||||
// Swap idle animation if changed
|
||||
if (p_msg.idleAnimId != m_idleAnimId && p_msg.idleAnimId < g_idleAnimCount) {
|
||||
m_idleAnimId = p_msg.idleAnimId;
|
||||
m_idleAnimCache = GetOrBuildAnimCache(g_idleAnimNames[m_idleAnimId]);
|
||||
if (p_msg.idleAnimId != m_animator.GetIdleAnimId() && p_msg.idleAnimId < g_idleAnimCount) {
|
||||
m_animator.SetIdleAnimId(p_msg.idleAnimId, m_roi);
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,12 +191,10 @@ void RemotePlayer::Tick(float p_deltaTime)
|
||||
|
||||
UpdateVehicleState();
|
||||
UpdateTransform(p_deltaTime);
|
||||
UpdateAnimation(p_deltaTime);
|
||||
m_animator.Tick(p_deltaTime, m_roi, m_targetSpeed > 0.01f);
|
||||
|
||||
// Update name bubble position and billboard orientation
|
||||
if (m_nameBubble) {
|
||||
m_nameBubble->Update(m_roi);
|
||||
}
|
||||
m_animator.UpdateNameBubble(m_roi);
|
||||
}
|
||||
|
||||
void RemotePlayer::ReAddToScene()
|
||||
@ -223,8 +205,8 @@ void RemotePlayer::ReAddToScene()
|
||||
if (m_vehicleROI) {
|
||||
VideoManager()->Get3DManager()->Add(*m_vehicleROI);
|
||||
}
|
||||
if (m_rideVehicleROI) {
|
||||
VideoManager()->Get3DManager()->Add(*m_rideVehicleROI);
|
||||
if (m_animator.GetRideVehicleROI()) {
|
||||
VideoManager()->Get3DManager()->Add(*m_animator.GetRideVehicleROI());
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,7 +219,7 @@ void RemotePlayer::SetVisible(bool p_visible)
|
||||
m_visible = p_visible;
|
||||
|
||||
if (p_visible) {
|
||||
if (m_currentVehicleType != VEHICLE_NONE && IsLargeVehicle(m_currentVehicleType)) {
|
||||
if (m_animator.GetCurrentVehicleType() != VEHICLE_NONE && IsLargeVehicle(m_animator.GetCurrentVehicleType())) {
|
||||
m_roi->SetVisibility(FALSE);
|
||||
if (m_vehicleROI) {
|
||||
m_vehicleROI->SetVisibility(TRUE);
|
||||
@ -255,42 +237,21 @@ void RemotePlayer::SetVisible(bool p_visible)
|
||||
if (m_vehicleROI) {
|
||||
m_vehicleROI->SetVisibility(FALSE);
|
||||
}
|
||||
if (m_rideVehicleROI) {
|
||||
m_rideVehicleROI->SetVisibility(FALSE);
|
||||
if (m_animator.GetRideVehicleROI()) {
|
||||
m_animator.GetRideVehicleROI()->SetVisibility(FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RemotePlayer::AnimCache* RemotePlayer::GetOrBuildAnimCache(const char* p_animName)
|
||||
{
|
||||
return AnimUtils::GetOrBuildAnimCache(m_animCacheMap, m_roi, p_animName);
|
||||
}
|
||||
|
||||
void RemotePlayer::TriggerEmote(uint8_t p_emoteId)
|
||||
{
|
||||
if (p_emoteId >= g_emoteAnimCount || !m_spawned) {
|
||||
if (!m_spawned) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only play emotes when stationary
|
||||
if (m_targetSpeed > 0.01f) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnimCache* cache = GetOrBuildAnimCache(g_emoteAnimNames[p_emoteId]);
|
||||
if (!cache || !cache->anim) {
|
||||
return;
|
||||
}
|
||||
|
||||
StopClickAnimation();
|
||||
|
||||
m_emoteAnimCache = cache;
|
||||
m_emoteTime = 0.0f;
|
||||
m_emoteDuration = (float) cache->anim->GetDuration();
|
||||
m_emoteActive = true;
|
||||
m_animator.TriggerEmote(p_emoteId, m_roi, m_targetSpeed > 0.01f);
|
||||
}
|
||||
|
||||
|
||||
void RemotePlayer::UpdateTransform(float p_deltaTime)
|
||||
{
|
||||
LERP3(m_currentPosition, m_currentPosition, m_targetPosition, 0.2f);
|
||||
@ -311,140 +272,17 @@ void RemotePlayer::UpdateTransform(float p_deltaTime)
|
||||
m_roi->WrappedSetLocal2WorldWithWorldDataUpdate(mat);
|
||||
VideoManager()->Get3DManager()->Moved(*m_roi);
|
||||
|
||||
if (m_vehicleROI && m_currentVehicleType != VEHICLE_NONE && IsLargeVehicle(m_currentVehicleType)) {
|
||||
if (m_vehicleROI && m_animator.GetCurrentVehicleType() != VEHICLE_NONE &&
|
||||
IsLargeVehicle(m_animator.GetCurrentVehicleType())) {
|
||||
m_vehicleROI->WrappedSetLocal2WorldWithWorldDataUpdate(mat);
|
||||
VideoManager()->Get3DManager()->Moved(*m_vehicleROI);
|
||||
}
|
||||
}
|
||||
|
||||
void RemotePlayer::UpdateAnimation(float p_deltaTime)
|
||||
{
|
||||
if (m_currentVehicleType != VEHICLE_NONE && IsLargeVehicle(m_currentVehicleType)) {
|
||||
StopClickAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the active walk/ride animation and its ROI map
|
||||
LegoAnim* walkAnim = nullptr;
|
||||
LegoROI** walkRoiMap = nullptr;
|
||||
MxU32 walkRoiMapSize = 0;
|
||||
|
||||
if (m_currentVehicleType != VEHICLE_NONE && m_rideAnim && m_rideRoiMap) {
|
||||
walkAnim = m_rideAnim;
|
||||
walkRoiMap = m_rideRoiMap;
|
||||
walkRoiMapSize = m_rideRoiMapSize;
|
||||
}
|
||||
else if (m_walkAnimCache && m_walkAnimCache->anim && m_walkAnimCache->roiMap) {
|
||||
walkAnim = m_walkAnimCache->anim;
|
||||
walkRoiMap = m_walkAnimCache->roiMap;
|
||||
walkRoiMapSize = m_walkAnimCache->roiMapSize;
|
||||
}
|
||||
|
||||
// Ensure visibility of all mapped ROIs
|
||||
if (walkRoiMap) {
|
||||
AnimUtils::EnsureROIMapVisibility(walkRoiMap, walkRoiMapSize);
|
||||
}
|
||||
if (m_idleAnimCache && m_idleAnimCache->roiMap) {
|
||||
AnimUtils::EnsureROIMapVisibility(m_idleAnimCache->roiMap, m_idleAnimCache->roiMapSize);
|
||||
}
|
||||
|
||||
bool inVehicle = (m_currentVehicleType != VEHICLE_NONE);
|
||||
bool isMoving = inVehicle || m_targetSpeed > 0.01f;
|
||||
|
||||
// Movement interrupts click animations and emotes
|
||||
if (isMoving) {
|
||||
StopClickAnimation();
|
||||
if (m_emoteActive) {
|
||||
m_emoteActive = false;
|
||||
m_emoteAnimCache = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMoving) {
|
||||
// Walking / riding
|
||||
if (!walkAnim || !walkRoiMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_targetSpeed > 0.01f) {
|
||||
m_animTime += p_deltaTime * 2000.0f;
|
||||
}
|
||||
float duration = (float) walkAnim->GetDuration();
|
||||
if (duration > 0.0f) {
|
||||
float timeInCycle = m_animTime - duration * floorf(m_animTime / duration);
|
||||
|
||||
MxMatrix transform(m_roi->GetLocal2World());
|
||||
LegoTreeNode* root = walkAnim->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(root->GetChild(i), transform, (LegoTime) timeInCycle, walkRoiMap);
|
||||
}
|
||||
}
|
||||
m_wasMoving = true;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
}
|
||||
else if (m_emoteActive && m_emoteAnimCache && m_emoteAnimCache->anim && m_emoteAnimCache->roiMap) {
|
||||
// Emote playback (one-shot)
|
||||
m_emoteTime += p_deltaTime * 1000.0f;
|
||||
|
||||
if (m_emoteTime >= m_emoteDuration) {
|
||||
// Emote completed -- return to stationary flow
|
||||
m_emoteActive = false;
|
||||
m_emoteAnimCache = nullptr;
|
||||
m_wasMoving = false;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
}
|
||||
else {
|
||||
MxMatrix transform(m_roi->GetLocal2World());
|
||||
LegoTreeNode* root = m_emoteAnimCache->anim->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(
|
||||
root->GetChild(i),
|
||||
transform,
|
||||
(LegoTime) m_emoteTime,
|
||||
m_emoteAnimCache->roiMap
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (m_idleAnimCache && m_idleAnimCache->anim && m_idleAnimCache->roiMap) {
|
||||
// Idle animation
|
||||
if (m_wasMoving) {
|
||||
m_wasMoving = false;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
}
|
||||
|
||||
m_idleTime += p_deltaTime;
|
||||
|
||||
// Hold standing pose for 2.5s, then loop breathing/swaying
|
||||
if (m_idleTime >= 2.5f) {
|
||||
m_idleAnimTime += p_deltaTime * 1000.0f;
|
||||
}
|
||||
|
||||
float duration = (float) m_idleAnimCache->anim->GetDuration();
|
||||
if (duration > 0.0f) {
|
||||
float timeInCycle = m_idleAnimTime - duration * floorf(m_idleAnimTime / duration);
|
||||
|
||||
MxMatrix transform(m_roi->GetLocal2World());
|
||||
LegoTreeNode* root = m_idleAnimCache->anim->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(
|
||||
root->GetChild(i),
|
||||
transform,
|
||||
(LegoTime) timeInCycle,
|
||||
m_idleAnimCache->roiMap
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RemotePlayer::UpdateVehicleState()
|
||||
{
|
||||
if (m_targetVehicleType != m_currentVehicleType) {
|
||||
if (m_currentVehicleType != VEHICLE_NONE) {
|
||||
if (m_targetVehicleType != m_animator.GetCurrentVehicleType()) {
|
||||
if (m_animator.GetCurrentVehicleType() != VEHICLE_NONE) {
|
||||
ExitVehicle();
|
||||
}
|
||||
if (m_targetVehicleType != VEHICLE_NONE) {
|
||||
@ -459,8 +297,8 @@ void RemotePlayer::EnterVehicle(int8_t p_vehicleType)
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentVehicleType = p_vehicleType;
|
||||
m_animTime = 0.0f;
|
||||
m_animator.SetCurrentVehicleType(p_vehicleType);
|
||||
m_animator.SetAnimTime(0.0f);
|
||||
|
||||
if (IsLargeVehicle(p_vehicleType)) {
|
||||
char vehicleName[48];
|
||||
@ -475,50 +313,13 @@ void RemotePlayer::EnterVehicle(int8_t p_vehicleType)
|
||||
}
|
||||
}
|
||||
else {
|
||||
const char* rideAnimName = g_rideAnimNames[p_vehicleType];
|
||||
const char* vehicleVariantName = g_rideVehicleROINames[p_vehicleType];
|
||||
|
||||
if (!rideAnimName || !vehicleVariantName) {
|
||||
return;
|
||||
}
|
||||
|
||||
LegoWorld* world = CurrentWorld();
|
||||
if (!world) {
|
||||
return;
|
||||
}
|
||||
|
||||
MxCore* presenter = world->Find("LegoAnimPresenter", rideAnimName);
|
||||
if (!presenter) {
|
||||
return;
|
||||
}
|
||||
|
||||
LegoAnimPresenter* animPresenter = static_cast<LegoAnimPresenter*>(presenter);
|
||||
m_rideAnim = animPresenter->GetAnimation();
|
||||
if (!m_rideAnim) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the base vehicle LOD (e.g. "moto", "bike") which is always loaded as
|
||||
// a world object. The ride-specific variant LODs (e.g. "motoni", "bikebd")
|
||||
// are only available when the original animation pipeline starts locally.
|
||||
const char* baseName = g_vehicleROINames[p_vehicleType];
|
||||
char variantName[48];
|
||||
SDL_snprintf(variantName, sizeof(variantName), "%s_mp_%u", vehicleVariantName, m_peerId);
|
||||
m_rideVehicleROI = CharacterManager()->CreateAutoROI(variantName, baseName, FALSE);
|
||||
|
||||
// Rename to variant name so FindChildROI can match animation tree nodes
|
||||
// (e.g. "MOTONI" in the anim tree matches ROI named "motoni").
|
||||
if (m_rideVehicleROI) {
|
||||
m_rideVehicleROI->SetName(vehicleVariantName);
|
||||
}
|
||||
|
||||
AnimUtils::BuildROIMap(m_rideAnim, m_roi, m_rideVehicleROI, m_rideRoiMap, m_rideRoiMapSize);
|
||||
m_animator.BuildRideAnimation(p_vehicleType, m_roi, m_peerId);
|
||||
}
|
||||
}
|
||||
|
||||
void RemotePlayer::ExitVehicle()
|
||||
{
|
||||
if (m_currentVehicleType == VEHICLE_NONE) {
|
||||
if (m_animator.GetCurrentVehicleType() == VEHICLE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -528,57 +329,31 @@ void RemotePlayer::ExitVehicle()
|
||||
m_vehicleROI = nullptr;
|
||||
}
|
||||
|
||||
if (m_rideRoiMap) {
|
||||
delete[] m_rideRoiMap;
|
||||
m_rideRoiMap = nullptr;
|
||||
m_rideRoiMapSize = 0;
|
||||
}
|
||||
if (m_rideVehicleROI) {
|
||||
VideoManager()->Get3DManager()->Remove(*m_rideVehicleROI);
|
||||
CharacterManager()->ReleaseAutoROI(m_rideVehicleROI);
|
||||
m_rideVehicleROI = nullptr;
|
||||
}
|
||||
m_rideAnim = nullptr;
|
||||
m_animator.ClearRideAnimation();
|
||||
|
||||
if (m_visible) {
|
||||
m_roi->SetVisibility(TRUE);
|
||||
}
|
||||
|
||||
m_currentVehicleType = VEHICLE_NONE;
|
||||
m_animTime = 0.0f;
|
||||
m_wasMoving = false;
|
||||
m_animator.SetAnimTime(0.0f);
|
||||
}
|
||||
|
||||
void RemotePlayer::CreateNameBubble()
|
||||
{
|
||||
if (m_nameBubble || m_displayName[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
m_nameBubble = new NameBubbleRenderer();
|
||||
m_nameBubble->Create(m_displayName);
|
||||
m_animator.CreateNameBubble(m_displayName);
|
||||
}
|
||||
|
||||
void RemotePlayer::DestroyNameBubble()
|
||||
{
|
||||
if (m_nameBubble) {
|
||||
delete m_nameBubble;
|
||||
m_nameBubble = nullptr;
|
||||
}
|
||||
m_animator.DestroyNameBubble();
|
||||
}
|
||||
|
||||
void RemotePlayer::SetNameBubbleVisible(bool p_visible)
|
||||
{
|
||||
if (m_nameBubble) {
|
||||
m_nameBubble->SetVisible(p_visible);
|
||||
}
|
||||
m_animator.SetNameBubbleVisible(p_visible);
|
||||
}
|
||||
|
||||
void RemotePlayer::StopClickAnimation()
|
||||
{
|
||||
if (m_clickAnimObjectId != 0) {
|
||||
CharacterCustomizer::StopClickAnimation(m_clickAnimObjectId);
|
||||
m_clickAnimObjectId = 0;
|
||||
}
|
||||
m_animator.StopClickAnimation();
|
||||
}
|
||||
|
||||
|
||||
@ -4,10 +4,7 @@
|
||||
#include "anim/legoanim.h"
|
||||
#include "extensions/multiplayer/charactercloner.h"
|
||||
#include "extensions/multiplayer/charactercustomizer.h"
|
||||
#include "extensions/multiplayer/namebubblerenderer.h"
|
||||
#include "islepathactor.h"
|
||||
#include "legogamestate.h"
|
||||
#include "legoanimpresenter.h"
|
||||
#include "legocameracontroller.h"
|
||||
#include "legocharactermanager.h"
|
||||
#include "legovideomanager.h"
|
||||
@ -20,7 +17,6 @@
|
||||
#include "roi/legoroi.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <cmath>
|
||||
|
||||
using namespace Multiplayer;
|
||||
|
||||
@ -39,13 +35,10 @@ static void FlipROIDirection(LegoROI* p_roi)
|
||||
|
||||
ThirdPersonCamera::ThirdPersonCamera()
|
||||
: m_enabled(false), m_active(false), m_roiUnflipped(false), m_playerROI(nullptr),
|
||||
m_displayActorIndex(DISPLAY_ACTOR_NONE), m_displayROI(nullptr), 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_clickAnimObjectId(0), m_currentVehicleType(VEHICLE_NONE), m_rideAnim(nullptr), m_rideRoiMap(nullptr),
|
||||
m_rideRoiMapSize(0), m_rideVehicleROI(nullptr), m_nameBubble(nullptr), m_showNameBubble(true),
|
||||
m_orbitYaw(DEFAULT_ORBIT_YAW),
|
||||
m_orbitPitch(DEFAULT_ORBIT_PITCH), m_orbitDistance(DEFAULT_ORBIT_DISTANCE), m_touch{}
|
||||
m_displayActorIndex(DISPLAY_ACTOR_NONE), m_displayROI(nullptr),
|
||||
m_animator(CharacterAnimatorConfig{/*.saveEmoteTransform=*/true}), m_showNameBubble(true),
|
||||
m_orbitYaw(DEFAULT_ORBIT_YAW), m_orbitPitch(DEFAULT_ORBIT_PITCH), m_orbitDistance(DEFAULT_ORBIT_DISTANCE),
|
||||
m_touch{}
|
||||
{
|
||||
SDL_memset(m_displayUniqueName, 0, sizeof(m_displayUniqueName));
|
||||
}
|
||||
@ -94,9 +87,8 @@ void ThirdPersonCamera::Disable()
|
||||
m_active = false;
|
||||
DestroyNameBubble();
|
||||
DestroyDisplayClone();
|
||||
ClearRideAnimation();
|
||||
m_animCacheMap.clear();
|
||||
ClearAnimCaches();
|
||||
m_animator.ClearRideAnimation();
|
||||
m_animator.ClearAll();
|
||||
|
||||
ResetOrbitState();
|
||||
}
|
||||
@ -110,7 +102,7 @@ void ThirdPersonCamera::OnActorEnter(IslePathActor* p_actor)
|
||||
|
||||
// Always track vehicle type so OnActorExit can handle exits
|
||||
// even if Enable() was called after entering the vehicle.
|
||||
m_currentVehicleType = DetectVehicleType(userActor);
|
||||
m_animator.SetCurrentVehicleType(DetectVehicleType(userActor));
|
||||
|
||||
// Enter() calls TurnAround(), so any previous undo is superseded.
|
||||
m_roiUnflipped = false;
|
||||
@ -124,9 +116,10 @@ void ThirdPersonCamera::OnActorEnter(IslePathActor* p_actor)
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_currentVehicleType != VEHICLE_NONE) {
|
||||
if (m_animator.GetCurrentVehicleType() != VEHICLE_NONE) {
|
||||
// Large vehicles and helicopter: stay first-person.
|
||||
if (IsLargeVehicle(m_currentVehicleType) || m_currentVehicleType == VEHICLE_HELICOPTER) {
|
||||
if (IsLargeVehicle(m_animator.GetCurrentVehicleType()) ||
|
||||
m_animator.GetCurrentVehicleType() == VEHICLE_HELICOPTER) {
|
||||
// Hide walking character ROI (Enter doesn't call Exit on it).
|
||||
if (m_playerROI) {
|
||||
m_playerROI->SetVisibility(FALSE);
|
||||
@ -150,7 +143,7 @@ void ThirdPersonCamera::OnActorEnter(IslePathActor* p_actor)
|
||||
|
||||
m_active = true;
|
||||
SetupCamera(userActor);
|
||||
BuildRideAnimation(m_currentVehicleType);
|
||||
m_animator.BuildRideAnimation(m_animator.GetCurrentVehicleType(), m_playerROI, 0);
|
||||
CreateNameBubble();
|
||||
return;
|
||||
}
|
||||
@ -169,18 +162,11 @@ void ThirdPersonCamera::OnActorEnter(IslePathActor* p_actor)
|
||||
VideoManager()->Get3DManager()->Remove(*m_playerROI);
|
||||
VideoManager()->Get3DManager()->Add(*m_playerROI);
|
||||
|
||||
// Build animation caches
|
||||
m_walkAnimCache = GetOrBuildAnimCache(g_walkAnimNames[m_walkAnimId]);
|
||||
m_idleAnimCache = GetOrBuildAnimCache(g_idleAnimNames[m_idleAnimId]);
|
||||
// Build animation caches and reset state
|
||||
m_animator.InitAnimCaches(m_playerROI);
|
||||
m_animator.ResetAnimState();
|
||||
|
||||
// Reset animation state
|
||||
m_animTime = 0.0f;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
m_wasMoving = false;
|
||||
m_emoteActive = false;
|
||||
|
||||
ApplyIdleFrame0();
|
||||
m_animator.ApplyIdleFrame0(m_playerROI);
|
||||
|
||||
SetupCamera(userActor);
|
||||
CreateNameBubble();
|
||||
@ -194,7 +180,7 @@ void ThirdPersonCamera::OnActorExit(IslePathActor* p_actor)
|
||||
|
||||
// For vehicle exit, p_actor is the vehicle, not UserActor —
|
||||
// check m_currentVehicleType instead.
|
||||
if (m_currentVehicleType != VEHICLE_NONE) {
|
||||
if (m_animator.GetCurrentVehicleType() != VEHICLE_NONE) {
|
||||
// When 3rd-person camera is active, movement inversion causes the
|
||||
// vehicle to physically drive opposite to vanilla. CalculateTransform
|
||||
// re-inverts to keep the ROI z backward. Exit()'s TurnAround restores
|
||||
@ -206,9 +192,8 @@ void ThirdPersonCamera::OnActorExit(IslePathActor* p_actor)
|
||||
}
|
||||
|
||||
// Exiting a vehicle: reinitialize for the walking character.
|
||||
ClearRideAnimation();
|
||||
ClearAnimCaches();
|
||||
m_animCacheMap.clear();
|
||||
m_animator.ClearRideAnimation();
|
||||
m_animator.ClearAll();
|
||||
ReinitForCharacter();
|
||||
}
|
||||
else if (m_active && static_cast<LegoPathActor*>(p_actor) == UserActor()) {
|
||||
@ -218,9 +203,8 @@ void ThirdPersonCamera::OnActorExit(IslePathActor* p_actor)
|
||||
m_playerROI->SetVisibility(FALSE);
|
||||
VideoManager()->Get3DManager()->Remove(*m_playerROI);
|
||||
}
|
||||
ClearRideAnimation();
|
||||
ClearAnimCaches();
|
||||
m_currentVehicleType = VEHICLE_NONE;
|
||||
m_animator.ClearRideAnimation();
|
||||
m_animator.ClearAll();
|
||||
m_playerROI = nullptr;
|
||||
m_active = false;
|
||||
}
|
||||
@ -258,26 +242,24 @@ void ThirdPersonCamera::Tick(float p_deltaTime)
|
||||
// Update orbit camera position each frame so it tracks the player
|
||||
ApplyOrbitCamera();
|
||||
|
||||
if (m_nameBubble) {
|
||||
m_nameBubble->Update(m_playerROI);
|
||||
}
|
||||
m_animator.UpdateNameBubble(m_playerROI);
|
||||
|
||||
// Small vehicle with ride animation (like RemotePlayer)
|
||||
if (m_currentVehicleType != VEHICLE_NONE) {
|
||||
StopClickAnimation();
|
||||
if (m_rideAnim && m_rideRoiMap) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Force visibility of ride ROI map entries
|
||||
AnimUtils::EnsureROIMapVisibility(m_rideRoiMap, m_rideRoiMapSize);
|
||||
AnimUtils::EnsureROIMapVisibility(m_animator.GetRideRoiMap(), m_animator.GetRideRoiMapSize());
|
||||
|
||||
// Only advance animation time when actually moving
|
||||
float speed = actor->GetWorldSpeed();
|
||||
if (fabsf(speed) > 0.01f) {
|
||||
m_animTime += p_deltaTime * 2000.0f;
|
||||
if (SDL_fabsf(speed) > 0.01f) {
|
||||
m_animator.SetAnimTime(m_animator.GetAnimTime() + p_deltaTime * 2000.0f);
|
||||
}
|
||||
|
||||
// Use vehicle actor's transform as base.
|
||||
@ -287,17 +269,18 @@ void ThirdPersonCamera::Tick(float p_deltaTime)
|
||||
m_playerROI->WrappedSetLocal2WorldWithWorldDataUpdate(transform);
|
||||
m_playerROI->SetVisibility(TRUE);
|
||||
|
||||
float duration = (float) m_rideAnim->GetDuration();
|
||||
float duration = (float) m_animator.GetRideAnim()->GetDuration();
|
||||
if (duration > 0.0f) {
|
||||
float timeInCycle = m_animTime - duration * floorf(m_animTime / duration);
|
||||
float timeInCycle =
|
||||
m_animator.GetAnimTime() - duration * SDL_floorf(m_animator.GetAnimTime() / duration);
|
||||
|
||||
LegoTreeNode* root = m_rideAnim->GetRoot();
|
||||
LegoTreeNode* root = m_animator.GetRideAnim()->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(
|
||||
root->GetChild(i),
|
||||
transform,
|
||||
(LegoTime) timeInCycle,
|
||||
m_rideRoiMap
|
||||
m_animator.GetRideRoiMap()
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -320,170 +303,34 @@ void ThirdPersonCamera::Tick(float p_deltaTime)
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the active walk animation and its ROI map
|
||||
LegoAnim* walkAnim = nullptr;
|
||||
LegoROI** walkRoiMap = nullptr;
|
||||
MxU32 walkRoiMapSize = 0;
|
||||
|
||||
if (m_walkAnimCache && m_walkAnimCache->anim && m_walkAnimCache->roiMap) {
|
||||
walkAnim = m_walkAnimCache->anim;
|
||||
walkRoiMap = m_walkAnimCache->roiMap;
|
||||
walkRoiMapSize = m_walkAnimCache->roiMapSize;
|
||||
}
|
||||
|
||||
// Ensure visibility of all mapped ROIs
|
||||
if (walkRoiMap) {
|
||||
AnimUtils::EnsureROIMapVisibility(walkRoiMap, walkRoiMapSize);
|
||||
}
|
||||
if (m_idleAnimCache && m_idleAnimCache->roiMap) {
|
||||
AnimUtils::EnsureROIMapVisibility(m_idleAnimCache->roiMap, m_idleAnimCache->roiMapSize);
|
||||
}
|
||||
|
||||
float speed = userActor->GetWorldSpeed();
|
||||
bool isMoving = fabsf(speed) > 0.01f;
|
||||
bool isMoving = SDL_fabsf(speed) > 0.01f;
|
||||
|
||||
// Movement interrupts click animations and emotes
|
||||
if (isMoving) {
|
||||
StopClickAnimation();
|
||||
if (m_emoteActive) {
|
||||
m_emoteActive = false;
|
||||
m_emoteAnimCache = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (isMoving) {
|
||||
if (!walkAnim || !walkRoiMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_animTime += p_deltaTime * 2000.0f;
|
||||
float duration = (float) walkAnim->GetDuration();
|
||||
if (duration > 0.0f) {
|
||||
float timeInCycle = m_animTime - duration * floorf(m_animTime / duration);
|
||||
|
||||
MxMatrix transform(m_playerROI->GetLocal2World());
|
||||
LegoTreeNode* root = walkAnim->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(root->GetChild(i), transform, (LegoTime) timeInCycle, walkRoiMap);
|
||||
}
|
||||
}
|
||||
m_wasMoving = true;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
}
|
||||
else if (m_emoteActive && m_emoteAnimCache && m_emoteAnimCache->anim && m_emoteAnimCache->roiMap) {
|
||||
m_emoteTime += p_deltaTime * 1000.0f;
|
||||
|
||||
if (m_emoteTime >= m_emoteDuration) {
|
||||
m_emoteActive = false;
|
||||
m_emoteAnimCache = nullptr;
|
||||
m_wasMoving = false;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
}
|
||||
else {
|
||||
// Use saved clean transform to prevent scale accumulation.
|
||||
MxMatrix transform(m_emoteParentTransform);
|
||||
|
||||
LegoTreeNode* root = m_emoteAnimCache->anim->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(
|
||||
root->GetChild(i),
|
||||
transform,
|
||||
(LegoTime) m_emoteTime,
|
||||
m_emoteAnimCache->roiMap
|
||||
);
|
||||
}
|
||||
|
||||
// Restore player ROI transform (animation root overwrote it).
|
||||
m_playerROI->WrappedSetLocal2WorldWithWorldDataUpdate(m_emoteParentTransform);
|
||||
}
|
||||
}
|
||||
else if (m_idleAnimCache && m_idleAnimCache->anim && m_idleAnimCache->roiMap) {
|
||||
if (m_wasMoving) {
|
||||
m_wasMoving = false;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
}
|
||||
|
||||
m_idleTime += p_deltaTime;
|
||||
|
||||
if (m_idleTime >= 2.5f) {
|
||||
m_idleAnimTime += p_deltaTime * 1000.0f;
|
||||
}
|
||||
|
||||
float duration = (float) m_idleAnimCache->anim->GetDuration();
|
||||
if (duration > 0.0f) {
|
||||
float timeInCycle = m_idleAnimTime - duration * floorf(m_idleAnimTime / duration);
|
||||
|
||||
MxMatrix transform(m_playerROI->GetLocal2World());
|
||||
LegoTreeNode* root = m_idleAnimCache->anim->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(
|
||||
root->GetChild(i),
|
||||
transform,
|
||||
(LegoTime) timeInCycle,
|
||||
m_idleAnimCache->roiMap
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
m_animator.Tick(p_deltaTime, m_playerROI, isMoving);
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::SetWalkAnimId(uint8_t p_walkAnimId)
|
||||
{
|
||||
if (p_walkAnimId >= g_walkAnimCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_walkAnimId != m_walkAnimId) {
|
||||
m_walkAnimId = p_walkAnimId;
|
||||
if (m_active) {
|
||||
m_walkAnimCache = GetOrBuildAnimCache(g_walkAnimNames[m_walkAnimId]);
|
||||
}
|
||||
}
|
||||
m_animator.SetWalkAnimId(p_walkAnimId, m_active ? m_playerROI : nullptr);
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::SetIdleAnimId(uint8_t p_idleAnimId)
|
||||
{
|
||||
if (p_idleAnimId >= g_idleAnimCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (p_idleAnimId != m_idleAnimId) {
|
||||
m_idleAnimId = p_idleAnimId;
|
||||
if (m_active) {
|
||||
m_idleAnimCache = GetOrBuildAnimCache(g_idleAnimNames[m_idleAnimId]);
|
||||
}
|
||||
}
|
||||
m_animator.SetIdleAnimId(p_idleAnimId, m_active ? m_playerROI : nullptr);
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::TriggerEmote(uint8_t p_emoteId)
|
||||
{
|
||||
if (p_emoteId >= g_emoteAnimCount || !m_active) {
|
||||
if (!m_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
LegoPathActor* userActor = UserActor();
|
||||
if (!userActor || fabsf(userActor->GetWorldSpeed()) > 0.01f) {
|
||||
if (!userActor) {
|
||||
return;
|
||||
}
|
||||
|
||||
AnimCache* cache = GetOrBuildAnimCache(g_emoteAnimNames[p_emoteId]);
|
||||
if (!cache || !cache->anim) {
|
||||
return;
|
||||
}
|
||||
|
||||
StopClickAnimation();
|
||||
|
||||
m_emoteAnimCache = cache;
|
||||
m_emoteTime = 0.0f;
|
||||
m_emoteDuration = (float) cache->anim->GetDuration();
|
||||
m_emoteActive = true;
|
||||
|
||||
// Save clean transform to prevent scale accumulation during emote
|
||||
// (the animation root writes scaled values into the ROI each frame).
|
||||
m_emoteParentTransform = m_playerROI->GetLocal2World();
|
||||
m_animator.TriggerEmote(p_emoteId, m_playerROI, SDL_fabsf(userActor->GetWorldSpeed()) > 0.01f);
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::ApplyCustomizeChange(uint8_t p_changeType, uint8_t p_partIndex)
|
||||
@ -495,10 +342,7 @@ void ThirdPersonCamera::ApplyCustomizeChange(uint8_t p_changeType, uint8_t p_par
|
||||
|
||||
void ThirdPersonCamera::StopClickAnimation()
|
||||
{
|
||||
if (m_clickAnimObjectId != 0) {
|
||||
CharacterCustomizer::StopClickAnimation(m_clickAnimObjectId);
|
||||
m_clickAnimObjectId = 0;
|
||||
}
|
||||
m_animator.StopClickAnimation();
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::OnWorldEnabled(LegoWorld* p_world)
|
||||
@ -508,8 +352,7 @@ void ThirdPersonCamera::OnWorldEnabled(LegoWorld* p_world)
|
||||
}
|
||||
|
||||
// Animation presenters may have been recreated.
|
||||
m_animCacheMap.clear();
|
||||
ClearAnimCaches();
|
||||
m_animator.ClearAll();
|
||||
|
||||
ReinitForCharacter();
|
||||
}
|
||||
@ -525,22 +368,8 @@ void ThirdPersonCamera::OnWorldDisabled(LegoWorld* p_world)
|
||||
m_playerROI = nullptr;
|
||||
DestroyNameBubble();
|
||||
DestroyDisplayClone();
|
||||
ClearRideAnimation();
|
||||
m_animCacheMap.clear();
|
||||
ClearAnimCaches();
|
||||
}
|
||||
|
||||
ThirdPersonCamera::AnimCache* ThirdPersonCamera::GetOrBuildAnimCache(const char* p_animName)
|
||||
{
|
||||
return AnimUtils::GetOrBuildAnimCache(m_animCacheMap, m_playerROI, p_animName);
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::ClearAnimCaches()
|
||||
{
|
||||
m_walkAnimCache = nullptr;
|
||||
m_idleAnimCache = nullptr;
|
||||
m_emoteAnimCache = nullptr;
|
||||
m_emoteActive = false;
|
||||
m_animator.ClearRideAnimation();
|
||||
m_animator.ClearAll();
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::SetupCamera(LegoPathActor* p_actor)
|
||||
@ -556,44 +385,6 @@ void ThirdPersonCamera::SetupCamera(LegoPathActor* p_actor)
|
||||
p_actor->TransformPointOfView();
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::BuildRideAnimation(int8_t p_vehicleType)
|
||||
{
|
||||
if (p_vehicleType < 0 || p_vehicleType >= VEHICLE_COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* rideAnimName = g_rideAnimNames[p_vehicleType];
|
||||
const char* vehicleVariantName = g_rideVehicleROINames[p_vehicleType];
|
||||
if (!rideAnimName || !vehicleVariantName) {
|
||||
return;
|
||||
}
|
||||
|
||||
LegoWorld* world = CurrentWorld();
|
||||
if (!world) {
|
||||
return;
|
||||
}
|
||||
|
||||
MxCore* presenter = world->Find("LegoAnimPresenter", rideAnimName);
|
||||
if (!presenter) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_rideAnim = static_cast<LegoAnimPresenter*>(presenter)->GetAnimation();
|
||||
if (!m_rideAnim) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create variant ROI, rename to match animation tree.
|
||||
const char* baseName = g_vehicleROINames[p_vehicleType];
|
||||
m_rideVehicleROI = CharacterManager()->CreateAutoROI("tp_vehicle", baseName, FALSE);
|
||||
if (m_rideVehicleROI) {
|
||||
m_rideVehicleROI->SetName(vehicleVariantName);
|
||||
}
|
||||
|
||||
AnimUtils::BuildROIMap(m_rideAnim, m_playerROI, m_rideVehicleROI, m_rideRoiMap, m_rideRoiMapSize);
|
||||
m_animTime = 0.0f;
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::SetDisplayActorIndex(uint8_t p_displayActorIndex)
|
||||
{
|
||||
if (m_displayActorIndex != p_displayActorIndex) {
|
||||
@ -639,7 +430,7 @@ void ThirdPersonCamera::CreateDisplayClone()
|
||||
|
||||
void ThirdPersonCamera::DestroyDisplayClone()
|
||||
{
|
||||
StopClickAnimation();
|
||||
m_animator.StopClickAnimation();
|
||||
if (m_displayROI) {
|
||||
if (m_playerROI == m_displayROI) {
|
||||
m_playerROI = nullptr;
|
||||
@ -652,97 +443,39 @@ void ThirdPersonCamera::DestroyDisplayClone()
|
||||
|
||||
void ThirdPersonCamera::CreateNameBubble()
|
||||
{
|
||||
if (m_nameBubble) {
|
||||
return;
|
||||
}
|
||||
|
||||
char name[8] = {};
|
||||
LegoGameState* gs = GameState();
|
||||
if (gs && gs->m_playerCount > 0) {
|
||||
const LegoGameState::Username& username = gs->m_players[0];
|
||||
for (int i = 0; i < 7; i++) {
|
||||
MxS16 letter = username.m_letters[i];
|
||||
if (letter < 0) {
|
||||
break;
|
||||
}
|
||||
if (letter <= 25) {
|
||||
name[i] = (char) ('A' + letter);
|
||||
}
|
||||
else {
|
||||
name[i] = '?';
|
||||
}
|
||||
}
|
||||
}
|
||||
EncodeUsername(name);
|
||||
|
||||
if (name[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
m_nameBubble = new NameBubbleRenderer();
|
||||
m_nameBubble->Create(name);
|
||||
m_animator.CreateNameBubble(name);
|
||||
|
||||
if (!m_showNameBubble) {
|
||||
m_nameBubble->SetVisible(false);
|
||||
m_animator.SetNameBubbleVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::DestroyNameBubble()
|
||||
{
|
||||
if (m_nameBubble) {
|
||||
delete m_nameBubble;
|
||||
m_nameBubble = nullptr;
|
||||
}
|
||||
m_animator.DestroyNameBubble();
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::SetNameBubbleVisible(bool p_visible)
|
||||
{
|
||||
m_showNameBubble = p_visible;
|
||||
if (m_nameBubble) {
|
||||
m_nameBubble->SetVisible(p_visible);
|
||||
}
|
||||
m_animator.SetNameBubbleVisible(p_visible);
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::ClearRideAnimation()
|
||||
{
|
||||
if (m_rideRoiMap) {
|
||||
delete[] m_rideRoiMap;
|
||||
m_rideRoiMap = nullptr;
|
||||
m_rideRoiMapSize = 0;
|
||||
}
|
||||
if (m_rideVehicleROI) {
|
||||
VideoManager()->Get3DManager()->Remove(*m_rideVehicleROI);
|
||||
CharacterManager()->ReleaseAutoROI(m_rideVehicleROI);
|
||||
m_rideVehicleROI = nullptr;
|
||||
}
|
||||
m_rideAnim = nullptr;
|
||||
m_currentVehicleType = VEHICLE_NONE;
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::ApplyIdleFrame0()
|
||||
{
|
||||
if (!m_playerROI || !m_idleAnimCache || !m_idleAnimCache->anim || !m_idleAnimCache->roiMap) {
|
||||
return;
|
||||
}
|
||||
|
||||
MxMatrix transform(m_playerROI->GetLocal2World());
|
||||
LegoTreeNode* root = m_idleAnimCache->anim->GetRoot();
|
||||
for (LegoU32 i = 0; i < root->GetNumChildren(); i++) {
|
||||
LegoROI::ApplyAnimationTransformation(root->GetChild(i), transform, (LegoTime) 0.0f, m_idleAnimCache->roiMap);
|
||||
}
|
||||
}
|
||||
|
||||
void ThirdPersonCamera::ComputeOrbitVectors(
|
||||
Mx3DPointFloat& p_at,
|
||||
Mx3DPointFloat& p_dir,
|
||||
Mx3DPointFloat& p_up
|
||||
) const
|
||||
void ThirdPersonCamera::ComputeOrbitVectors(Mx3DPointFloat& p_at, Mx3DPointFloat& p_dir, Mx3DPointFloat& p_up) const
|
||||
{
|
||||
// Convert spherical coordinates to camera offset in entity-local space.
|
||||
// Entity local Z+ is "behind" (after TurnAround), which is where yaw=0 points.
|
||||
float cosP = cosf(m_orbitPitch);
|
||||
float sinP = sinf(m_orbitPitch);
|
||||
float sinY = sinf(m_orbitYaw);
|
||||
float cosY = cosf(m_orbitYaw);
|
||||
float cosP = SDL_cosf(m_orbitPitch);
|
||||
float sinP = SDL_sinf(m_orbitPitch);
|
||||
float sinY = SDL_sinf(m_orbitYaw);
|
||||
float cosY = SDL_cosf(m_orbitYaw);
|
||||
|
||||
p_at = Mx3DPointFloat(
|
||||
m_orbitDistance * sinY * cosP,
|
||||
@ -827,7 +560,7 @@ void ThirdPersonCamera::HandleSDLEvent(SDL_Event* p_event)
|
||||
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 = sqrtf(dx * dx + dy * dy);
|
||||
m_touch.initialPinchDist = SDL_sqrtf(dx * dx + dy * dy);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -855,7 +588,7 @@ void ThirdPersonCamera::HandleSDLEvent(SDL_Event* p_event)
|
||||
// Pinch zoom
|
||||
float dx = m_touch.x[1] - m_touch.x[0];
|
||||
float dy = m_touch.y[1] - m_touch.y[0];
|
||||
float newDist = sqrtf(dx * dx + dy * dy);
|
||||
float newDist = SDL_sqrtf(dx * dx + dy * dy);
|
||||
|
||||
if (m_touch.initialPinchDist > 0.001f) {
|
||||
float pinchDelta = m_touch.initialPinchDist - newDist;
|
||||
@ -921,7 +654,7 @@ void ThirdPersonCamera::ReinitForCharacter()
|
||||
return;
|
||||
}
|
||||
|
||||
m_currentVehicleType = vehicleType;
|
||||
m_animator.SetCurrentVehicleType(vehicleType);
|
||||
|
||||
if (vehicleType != VEHICLE_NONE) {
|
||||
if (!EnsureDisplayROI()) {
|
||||
@ -951,7 +684,7 @@ void ThirdPersonCamera::ReinitForCharacter()
|
||||
VideoManager()->Get3DManager()->Add(*m_playerROI);
|
||||
m_active = true;
|
||||
SetupCamera(userActor);
|
||||
BuildRideAnimation(vehicleType);
|
||||
m_animator.BuildRideAnimation(vehicleType, m_playerROI, 0);
|
||||
CreateNameBubble();
|
||||
return;
|
||||
}
|
||||
@ -978,17 +711,11 @@ void ThirdPersonCamera::ReinitForCharacter()
|
||||
VideoManager()->Get3DManager()->Remove(*m_playerROI);
|
||||
VideoManager()->Get3DManager()->Add(*m_playerROI);
|
||||
|
||||
m_walkAnimCache = GetOrBuildAnimCache(g_walkAnimNames[m_walkAnimId]);
|
||||
m_idleAnimCache = GetOrBuildAnimCache(g_idleAnimNames[m_idleAnimId]);
|
||||
|
||||
m_animTime = 0.0f;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
m_wasMoving = false;
|
||||
m_emoteActive = false;
|
||||
m_animator.InitAnimCaches(m_playerROI);
|
||||
m_animator.ResetAnimState();
|
||||
m_active = true;
|
||||
|
||||
ApplyIdleFrame0();
|
||||
m_animator.ApplyIdleFrame0(m_playerROI);
|
||||
SetupCamera(userActor);
|
||||
CreateNameBubble();
|
||||
}
|
||||
|
||||
@ -16,8 +16,6 @@
|
||||
#include "mxvariabletable.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
extern MxU8 g_counters[];
|
||||
@ -49,7 +47,7 @@ void WorldStateSync::SaveSkyLightState()
|
||||
{
|
||||
const char* bgValue = GameState()->GetBackgroundColor()->GetValue()->GetData();
|
||||
m_savedSkyColor = bgValue ? bgValue : "set 56 54 68";
|
||||
m_savedLightPos = atoi(VariableTable()->GetVariable("lightposition"));
|
||||
m_savedLightPos = SDL_atoi(VariableTable()->GetVariable("lightposition"));
|
||||
}
|
||||
|
||||
void WorldStateSync::RestoreSkyLightState()
|
||||
@ -64,7 +62,7 @@ void WorldStateSync::ApplySkyLightState(const char* p_skyColor, int p_lightPos)
|
||||
SetLightPosition(p_lightPos);
|
||||
|
||||
char buf[32];
|
||||
sprintf(buf, "%d", p_lightPos);
|
||||
SDL_snprintf(buf, sizeof(buf), "%d", p_lightPos);
|
||||
VariableTable()->SetVariable("lightposition", buf);
|
||||
}
|
||||
|
||||
@ -118,7 +116,7 @@ void WorldStateSync::HandleWorldSnapshot(const uint8_t* p_data, size_t p_length)
|
||||
|
||||
if (remaining >= 4) {
|
||||
char skyBuffer[32];
|
||||
sprintf(skyBuffer, "set %d %d %d", extraData[0], extraData[1], extraData[2]);
|
||||
SDL_snprintf(skyBuffer, sizeof(skyBuffer), "set %d %d %d", extraData[0], extraData[1], extraData[2]);
|
||||
ApplySkyLightState(skyBuffer, extraData[3]);
|
||||
}
|
||||
|
||||
@ -259,10 +257,10 @@ void WorldStateSync::SendWorldSnapshot(uint32_t p_targetPeerId)
|
||||
int skyH = 56, skyS = 54, skyV = 68; // defaults matching "set 56 54 68"
|
||||
const char* bgValue = GameState()->GetBackgroundColor()->GetValue()->GetData();
|
||||
if (bgValue) {
|
||||
sscanf(bgValue, "set %d %d %d", &skyH, &skyS, &skyV);
|
||||
SDL_sscanf(bgValue, "set %d %d %d", &skyH, &skyS, &skyV);
|
||||
}
|
||||
|
||||
int lightPos = atoi(VariableTable()->GetVariable("lightposition"));
|
||||
int lightPos = SDL_atoi(VariableTable()->GetVariable("lightposition"));
|
||||
|
||||
stateBuffer[dataLength++] = (uint8_t) skyH;
|
||||
stateBuffer[dataLength++] = (uint8_t) skyS;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user