mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-02 02:23:56 +00:00
Add emote prop ROI support with dynamic detection from animation tree
Emote animations like Toss (CNs013Pa) reference prop nodes (POPMUG, *POPMUG01) that don't exist in the player's ROI hierarchy. This change dynamically detects unmatched animation tree nodes and creates prop ROIs for them, making pizza props visible during the Toss emote. - Add shared PropGroup struct for ride and emote prop lifecycle - Add CollectUnmatchedNodes to scan animation trees for missing ROIs - Extend BuildROIMap/AssignROIIndices to accept an array of extra ROIs - Add *-prefix fallback: subsequent *-nodes search extra ROIs - Add ResolvePropLODName mapping for node-to-LOD name differences - Refactor ride system to use PropGroup (no behavior change) - Clean up emote props on completion, movement interrupt, and world transition Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0de220644b
commit
ede39a8bde
@ -1,12 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "mxtypes.h"
|
|
||||||
#include "mxgeometry/mxmatrix.h"
|
#include "mxgeometry/mxmatrix.h"
|
||||||
|
#include "mxtypes.h"
|
||||||
#include "realtime/vector.h"
|
#include "realtime/vector.h"
|
||||||
#include "roi/legoroi.h"
|
#include "roi/legoroi.h"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class LegoAnim;
|
class LegoAnim;
|
||||||
|
|
||||||
@ -34,8 +35,7 @@ struct AnimCache {
|
|||||||
|
|
||||||
AnimCache(const AnimCache&) = delete;
|
AnimCache(const AnimCache&) = delete;
|
||||||
AnimCache& operator=(const AnimCache&) = delete;
|
AnimCache& operator=(const AnimCache&) = delete;
|
||||||
AnimCache(AnimCache&& p_other) noexcept
|
AnimCache(AnimCache&& p_other) noexcept : anim(p_other.anim), roiMap(p_other.roiMap), roiMapSize(p_other.roiMapSize)
|
||||||
: anim(p_other.anim), roiMap(p_other.roiMap), roiMapSize(p_other.roiMapSize)
|
|
||||||
{
|
{
|
||||||
p_other.roiMap = nullptr;
|
p_other.roiMap = nullptr;
|
||||||
p_other.roiMapSize = 0;
|
p_other.roiMapSize = 0;
|
||||||
@ -61,16 +61,15 @@ struct AnimCache {
|
|||||||
void BuildROIMap(
|
void BuildROIMap(
|
||||||
LegoAnim* p_anim,
|
LegoAnim* p_anim,
|
||||||
LegoROI* p_rootROI,
|
LegoROI* p_rootROI,
|
||||||
LegoROI* p_extraROI,
|
LegoROI** p_extraROIs,
|
||||||
|
int p_extraROICount,
|
||||||
LegoROI**& p_roiMap,
|
LegoROI**& p_roiMap,
|
||||||
MxU32& p_roiMapSize
|
MxU32& p_roiMapSize
|
||||||
);
|
);
|
||||||
|
|
||||||
AnimCache* GetOrBuildAnimCache(
|
void CollectUnmatchedNodes(LegoAnim* p_anim, LegoROI* p_rootROI, std::vector<std::string>& p_unmatchedNames);
|
||||||
std::map<std::string, AnimCache>& p_cacheMap,
|
|
||||||
LegoROI* p_roi,
|
AnimCache* GetOrBuildAnimCache(std::map<std::string, AnimCache>& p_cacheMap, LegoROI* p_roi, const char* p_animName);
|
||||||
const char* p_animName
|
|
||||||
);
|
|
||||||
|
|
||||||
inline void EnsureROIMapVisibility(LegoROI** p_roiMap, MxU32 p_roiMapSize)
|
inline void EnsureROIMapVisibility(LegoROI** p_roiMap, MxU32 p_roiMapSize)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -24,6 +24,19 @@ struct CharacterAnimatorConfig {
|
|||||||
// When true, save/restore the parent ROI transform during emote playback
|
// When true, save/restore the parent ROI transform during emote playback
|
||||||
// to prevent scale accumulation (needed for ThirdPersonCameraExt's display clone).
|
// to prevent scale accumulation (needed for ThirdPersonCameraExt's display clone).
|
||||||
bool saveEmoteTransform;
|
bool saveEmoteTransform;
|
||||||
|
|
||||||
|
// Suffix used for unique naming of prop ROIs.
|
||||||
|
// Remote players use m_peerId, local player uses 0.
|
||||||
|
uint32_t propSuffix;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A group of dynamically-created prop ROIs for an animation (ride or emote).
|
||||||
|
struct PropGroup {
|
||||||
|
LegoAnim* anim = nullptr;
|
||||||
|
LegoROI** roiMap = nullptr;
|
||||||
|
MxU32 roiMapSize = 0;
|
||||||
|
LegoROI** propROIs = nullptr;
|
||||||
|
uint8_t propCount = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Unified character animation component used by both RemotePlayer and ThirdPersonCameraExt.
|
// Unified character animation component used by both RemotePlayer and ThirdPersonCameraExt.
|
||||||
@ -61,10 +74,10 @@ class CharacterAnimator {
|
|||||||
int8_t GetCurrentVehicleType() const { return m_currentVehicleType; }
|
int8_t GetCurrentVehicleType() const { return m_currentVehicleType; }
|
||||||
void SetCurrentVehicleType(int8_t p_vehicleType) { m_currentVehicleType = p_vehicleType; }
|
void SetCurrentVehicleType(int8_t p_vehicleType) { m_currentVehicleType = p_vehicleType; }
|
||||||
bool IsInVehicle() const { return m_currentVehicleType != VEHICLE_NONE; }
|
bool IsInVehicle() const { return m_currentVehicleType != VEHICLE_NONE; }
|
||||||
LegoROI* GetRideVehicleROI() const { return m_rideVehicleROI; }
|
LegoROI* GetRideVehicleROI() const { return m_ridePropGroup.propCount > 0 ? m_ridePropGroup.propROIs[0] : nullptr; }
|
||||||
LegoAnim* GetRideAnim() const { return m_rideAnim; }
|
LegoAnim* GetRideAnim() const { return m_ridePropGroup.anim; }
|
||||||
LegoROI** GetRideRoiMap() const { return m_rideRoiMap; }
|
LegoROI** GetRideRoiMap() const { return m_ridePropGroup.roiMap; }
|
||||||
MxU32 GetRideRoiMapSize() const { return m_rideRoiMapSize; }
|
MxU32 GetRideRoiMapSize() const { return m_ridePropGroup.roiMapSize; }
|
||||||
|
|
||||||
// Animation cache management
|
// Animation cache management
|
||||||
void InitAnimCaches(LegoROI* p_roi);
|
void InitAnimCaches(LegoROI* p_roi);
|
||||||
@ -94,6 +107,8 @@ class CharacterAnimator {
|
|||||||
|
|
||||||
AnimCache* GetOrBuildAnimCache(LegoROI* p_roi, const char* p_animName);
|
AnimCache* GetOrBuildAnimCache(LegoROI* p_roi, const char* p_animName);
|
||||||
void ClearFrozenState();
|
void ClearFrozenState();
|
||||||
|
void ClearPropGroup(PropGroup& p_group);
|
||||||
|
void BuildEmoteProps(PropGroup& p_group, LegoAnim* p_anim, LegoROI* p_playerROI);
|
||||||
void PlayROISound(const char* p_key, LegoROI* p_roi);
|
void PlayROISound(const char* p_key, LegoROI* p_roi);
|
||||||
|
|
||||||
CharacterAnimatorConfig m_config;
|
CharacterAnimatorConfig m_config;
|
||||||
@ -133,12 +148,11 @@ class CharacterAnimator {
|
|||||||
std::map<std::string, AnimCache> m_animCacheMap;
|
std::map<std::string, AnimCache> m_animCacheMap;
|
||||||
|
|
||||||
// Ride animation (vehicle-specific)
|
// Ride animation (vehicle-specific)
|
||||||
LegoAnim* m_rideAnim;
|
PropGroup m_ridePropGroup;
|
||||||
LegoROI** m_rideRoiMap;
|
|
||||||
MxU32 m_rideRoiMapSize;
|
|
||||||
LegoROI* m_rideVehicleROI;
|
|
||||||
|
|
||||||
int8_t m_currentVehicleType;
|
int8_t m_currentVehicleType;
|
||||||
|
|
||||||
|
// Emote prop animation (dynamically-created props for emotes like Toss)
|
||||||
|
PropGroup m_emotePropGroup;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include "misc/legotree.h"
|
#include "misc/legotree.h"
|
||||||
#include "roi/legoroi.h"
|
#include "roi/legoroi.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using namespace Extensions::Common;
|
using namespace Extensions::Common;
|
||||||
@ -17,7 +18,8 @@ static void AssignROIIndices(
|
|||||||
LegoTreeNode* p_node,
|
LegoTreeNode* p_node,
|
||||||
LegoROI* p_parentROI,
|
LegoROI* p_parentROI,
|
||||||
LegoROI* p_rootROI,
|
LegoROI* p_rootROI,
|
||||||
LegoROI* p_extraROI,
|
LegoROI** p_extraROIs,
|
||||||
|
int p_extraROICount,
|
||||||
MxU32& p_nextIndex,
|
MxU32& p_nextIndex,
|
||||||
std::vector<LegoROI*>& p_entries,
|
std::vector<LegoROI*>& p_entries,
|
||||||
bool& p_rootClaimed
|
bool& p_rootClaimed
|
||||||
@ -36,11 +38,29 @@ static void AssignROIIndices(
|
|||||||
matchedROI = p_rootROI;
|
matchedROI = p_rootROI;
|
||||||
p_rootClaimed = true;
|
p_rootClaimed = true;
|
||||||
}
|
}
|
||||||
|
else if (*name == '*' && p_extraROICount > 0) {
|
||||||
|
// Subsequent *-prefixed node: search extra ROIs by stripped name.
|
||||||
|
// FindChildROI checks self first, then children recursively.
|
||||||
|
const char* stripped = name + 1;
|
||||||
|
for (int e = 0; e < p_extraROICount; e++) {
|
||||||
|
matchedROI = p_extraROIs[e]->FindChildROI(stripped, p_extraROIs[e]);
|
||||||
|
if (matchedROI != nullptr) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
matchedROI = p_parentROI->FindChildROI(name, p_parentROI);
|
matchedROI = p_parentROI->FindChildROI(name, p_parentROI);
|
||||||
if (matchedROI == nullptr && p_extraROI != nullptr) {
|
if (matchedROI == nullptr) {
|
||||||
matchedROI = p_extraROI->FindChildROI(name, p_extraROI);
|
// FindChildROI checks self first, so this handles both
|
||||||
|
// direct name matches and child searches on extra ROIs.
|
||||||
|
for (int e = 0; e < p_extraROICount; e++) {
|
||||||
|
matchedROI = p_extraROIs[e]->FindChildROI(name, p_extraROIs[e]);
|
||||||
|
if (matchedROI != nullptr) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,14 +75,24 @@ static void AssignROIIndices(
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (MxS32 i = 0; i < p_node->GetNumChildren(); i++) {
|
for (MxS32 i = 0; i < p_node->GetNumChildren(); i++) {
|
||||||
AssignROIIndices(p_node->GetChild(i), roi, p_rootROI, p_extraROI, p_nextIndex, p_entries, p_rootClaimed);
|
AssignROIIndices(
|
||||||
|
p_node->GetChild(i),
|
||||||
|
roi,
|
||||||
|
p_rootROI,
|
||||||
|
p_extraROIs,
|
||||||
|
p_extraROICount,
|
||||||
|
p_nextIndex,
|
||||||
|
p_entries,
|
||||||
|
p_rootClaimed
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimUtils::BuildROIMap(
|
void AnimUtils::BuildROIMap(
|
||||||
LegoAnim* p_anim,
|
LegoAnim* p_anim,
|
||||||
LegoROI* p_rootROI,
|
LegoROI* p_rootROI,
|
||||||
LegoROI* p_extraROI,
|
LegoROI** p_extraROIs,
|
||||||
|
int p_extraROICount,
|
||||||
LegoROI**& p_roiMap,
|
LegoROI**& p_roiMap,
|
||||||
MxU32& p_roiMapSize
|
MxU32& p_roiMapSize
|
||||||
)
|
)
|
||||||
@ -79,7 +109,7 @@ void AnimUtils::BuildROIMap(
|
|||||||
MxU32 nextIndex = 1;
|
MxU32 nextIndex = 1;
|
||||||
std::vector<LegoROI*> entries;
|
std::vector<LegoROI*> entries;
|
||||||
bool rootClaimed = false;
|
bool rootClaimed = false;
|
||||||
AssignROIIndices(root, nullptr, p_rootROI, p_extraROI, nextIndex, entries, rootClaimed);
|
AssignROIIndices(root, nullptr, p_rootROI, p_extraROIs, p_extraROICount, nextIndex, entries, rootClaimed);
|
||||||
|
|
||||||
if (entries.empty()) {
|
if (entries.empty()) {
|
||||||
return;
|
return;
|
||||||
@ -129,7 +159,67 @@ AnimUtils::AnimCache* AnimUtils::GetOrBuildAnimCache(
|
|||||||
// Build and cache
|
// Build and cache
|
||||||
AnimCache& cache = p_cacheMap[p_animName];
|
AnimCache& cache = p_cacheMap[p_animName];
|
||||||
cache.anim = anim;
|
cache.anim = anim;
|
||||||
BuildROIMap(anim, p_roi, nullptr, cache.roiMap, cache.roiMapSize);
|
BuildROIMap(anim, p_roi, nullptr, 0, cache.roiMap, cache.roiMapSize);
|
||||||
|
|
||||||
return &cache;
|
return &cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read-only tree walk: collect names of animation nodes that don't match the player's ROI hierarchy.
|
||||||
|
static void CollectUnmatchedNodesRecursive(
|
||||||
|
LegoTreeNode* p_node,
|
||||||
|
LegoROI* p_parentROI,
|
||||||
|
LegoROI* p_rootROI,
|
||||||
|
std::vector<std::string>& p_unmatchedNames,
|
||||||
|
bool& p_rootClaimed
|
||||||
|
)
|
||||||
|
{
|
||||||
|
LegoROI* roi = p_parentROI;
|
||||||
|
LegoAnimNodeData* data = (LegoAnimNodeData*) p_node->GetData();
|
||||||
|
const char* name = data ? data->GetName() : nullptr;
|
||||||
|
|
||||||
|
if (name != nullptr && *name != '-') {
|
||||||
|
if (*name == '*' || p_parentROI == nullptr) {
|
||||||
|
roi = p_rootROI;
|
||||||
|
if (!p_rootClaimed) {
|
||||||
|
p_rootClaimed = true;
|
||||||
|
}
|
||||||
|
else if (*name == '*') {
|
||||||
|
// Subsequent *-prefixed node: strip prefix, add lowercased name
|
||||||
|
std::string stripped(name + 1);
|
||||||
|
std::transform(stripped.begin(), stripped.end(), stripped.begin(), ::tolower);
|
||||||
|
if (std::find(p_unmatchedNames.begin(), p_unmatchedNames.end(), stripped) == p_unmatchedNames.end()) {
|
||||||
|
p_unmatchedNames.push_back(stripped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LegoROI* matchedROI = p_parentROI->FindChildROI(name, p_parentROI);
|
||||||
|
if (matchedROI == nullptr) {
|
||||||
|
std::string lowered(name);
|
||||||
|
std::transform(lowered.begin(), lowered.end(), lowered.begin(), ::tolower);
|
||||||
|
if (std::find(p_unmatchedNames.begin(), p_unmatchedNames.end(), lowered) == p_unmatchedNames.end()) {
|
||||||
|
p_unmatchedNames.push_back(lowered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (MxS32 i = 0; i < p_node->GetNumChildren(); i++) {
|
||||||
|
CollectUnmatchedNodesRecursive(p_node->GetChild(i), roi, p_rootROI, p_unmatchedNames, p_rootClaimed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AnimUtils::CollectUnmatchedNodes(LegoAnim* p_anim, LegoROI* p_rootROI, std::vector<std::string>& p_unmatchedNames)
|
||||||
|
{
|
||||||
|
if (!p_anim || !p_rootROI) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LegoTreeNode* root = p_anim->GetRoot();
|
||||||
|
if (!root) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rootClaimed = false;
|
||||||
|
CollectUnmatchedNodesRecursive(root, nullptr, p_rootROI, p_unmatchedNames, rootClaimed);
|
||||||
|
}
|
||||||
|
|||||||
@ -23,13 +23,13 @@ CharacterAnimator::CharacterAnimator(const CharacterAnimatorConfig& p_config)
|
|||||||
: m_config(p_config), m_walkAnimId(0), m_idleAnimId(0), m_walkAnimCache(nullptr), m_idleAnimCache(nullptr),
|
: 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_animTime(0.0f), m_idleTime(0.0f), m_idleAnimTime(0.0f), m_wasMoving(false), m_emoteAnimCache(nullptr),
|
||||||
m_emoteTime(0.0f), m_emoteDuration(0.0f), m_emoteActive(false), m_currentEmoteId(0), m_frozenEmoteId(-1),
|
m_emoteTime(0.0f), m_emoteDuration(0.0f), m_emoteActive(false), m_currentEmoteId(0), m_frozenEmoteId(-1),
|
||||||
m_frozenAnimCache(nullptr), m_frozenAnimDuration(0.0f), m_clickAnimObjectId(0), m_rideAnim(nullptr),
|
m_frozenAnimCache(nullptr), m_frozenAnimDuration(0.0f), m_clickAnimObjectId(0), m_currentVehicleType(VEHICLE_NONE)
|
||||||
m_rideRoiMap(nullptr), m_rideRoiMapSize(0), m_rideVehicleROI(nullptr), m_currentVehicleType(VEHICLE_NONE)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
CharacterAnimator::~CharacterAnimator()
|
CharacterAnimator::~CharacterAnimator()
|
||||||
{
|
{
|
||||||
|
ClearPropGroup(m_emotePropGroup);
|
||||||
ClearRideAnimation();
|
ClearRideAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,10 +50,10 @@ void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
|
|||||||
LegoROI** walkRoiMap = nullptr;
|
LegoROI** walkRoiMap = nullptr;
|
||||||
MxU32 walkRoiMapSize = 0;
|
MxU32 walkRoiMapSize = 0;
|
||||||
|
|
||||||
if (m_currentVehicleType != VEHICLE_NONE && m_rideAnim && m_rideRoiMap) {
|
if (m_currentVehicleType != VEHICLE_NONE && m_ridePropGroup.anim && m_ridePropGroup.roiMap) {
|
||||||
walkAnim = m_rideAnim;
|
walkAnim = m_ridePropGroup.anim;
|
||||||
walkRoiMap = m_rideRoiMap;
|
walkRoiMap = m_ridePropGroup.roiMap;
|
||||||
walkRoiMapSize = m_rideRoiMapSize;
|
walkRoiMapSize = m_ridePropGroup.roiMapSize;
|
||||||
}
|
}
|
||||||
else if (m_walkAnimCache && m_walkAnimCache->anim && m_walkAnimCache->roiMap) {
|
else if (m_walkAnimCache && m_walkAnimCache->anim && m_walkAnimCache->roiMap) {
|
||||||
walkAnim = m_walkAnimCache->anim;
|
walkAnim = m_walkAnimCache->anim;
|
||||||
@ -78,6 +78,7 @@ void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
|
|||||||
if (m_emoteActive) {
|
if (m_emoteActive) {
|
||||||
m_emoteActive = false;
|
m_emoteActive = false;
|
||||||
m_emoteAnimCache = nullptr;
|
m_emoteAnimCache = nullptr;
|
||||||
|
ClearPropGroup(m_emotePropGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,12 +128,15 @@ void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
|
|||||||
// Emote completed -- return to stationary flow
|
// Emote completed -- return to stationary flow
|
||||||
m_emoteActive = false;
|
m_emoteActive = false;
|
||||||
m_emoteAnimCache = nullptr;
|
m_emoteAnimCache = nullptr;
|
||||||
|
ClearPropGroup(m_emotePropGroup);
|
||||||
m_wasMoving = false;
|
m_wasMoving = false;
|
||||||
m_idleTime = 0.0f;
|
m_idleTime = 0.0f;
|
||||||
m_idleAnimTime = 0.0f;
|
m_idleAnimTime = 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
LegoROI** emoteRoiMap =
|
||||||
|
m_emotePropGroup.roiMap != nullptr ? m_emotePropGroup.roiMap : m_emoteAnimCache->roiMap;
|
||||||
MxMatrix transform(m_config.saveEmoteTransform ? m_emoteParentTransform : p_roi->GetLocal2World());
|
MxMatrix transform(m_config.saveEmoteTransform ? m_emoteParentTransform : p_roi->GetLocal2World());
|
||||||
|
|
||||||
LegoTreeNode* root = m_emoteAnimCache->anim->GetRoot();
|
LegoTreeNode* root = m_emoteAnimCache->anim->GetRoot();
|
||||||
@ -141,7 +145,7 @@ void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
|
|||||||
root->GetChild(i),
|
root->GetChild(i),
|
||||||
transform,
|
transform,
|
||||||
(LegoTime) m_emoteTime,
|
(LegoTime) m_emoteTime,
|
||||||
m_emoteAnimCache->roiMap
|
emoteRoiMap
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,6 +247,7 @@ void CharacterAnimator::TriggerEmote(uint8_t p_emoteId, LegoROI* p_roi, bool p_i
|
|||||||
}
|
}
|
||||||
|
|
||||||
StopClickAnimation();
|
StopClickAnimation();
|
||||||
|
ClearPropGroup(m_emotePropGroup);
|
||||||
|
|
||||||
m_currentEmoteId = p_emoteId;
|
m_currentEmoteId = p_emoteId;
|
||||||
m_emoteAnimCache = cache;
|
m_emoteAnimCache = cache;
|
||||||
@ -250,6 +255,8 @@ void CharacterAnimator::TriggerEmote(uint8_t p_emoteId, LegoROI* p_roi, bool p_i
|
|||||||
m_emoteDuration = (float) cache->anim->GetDuration();
|
m_emoteDuration = (float) cache->anim->GetDuration();
|
||||||
m_emoteActive = true;
|
m_emoteActive = true;
|
||||||
|
|
||||||
|
BuildEmoteProps(m_emotePropGroup, cache->anim, p_roi);
|
||||||
|
|
||||||
const char* sound = g_emoteEntries[p_emoteId].phases[1].sound;
|
const char* sound = g_emoteEntries[p_emoteId].phases[1].sound;
|
||||||
if (sound) {
|
if (sound) {
|
||||||
PlayROISound(sound, p_roi);
|
PlayROISound(sound, p_roi);
|
||||||
@ -279,6 +286,7 @@ void CharacterAnimator::TriggerEmote(uint8_t p_emoteId, LegoROI* p_roi, bool p_i
|
|||||||
}
|
}
|
||||||
|
|
||||||
StopClickAnimation();
|
StopClickAnimation();
|
||||||
|
ClearPropGroup(m_emotePropGroup);
|
||||||
|
|
||||||
m_currentEmoteId = p_emoteId;
|
m_currentEmoteId = p_emoteId;
|
||||||
m_emoteAnimCache = cache;
|
m_emoteAnimCache = cache;
|
||||||
@ -286,6 +294,8 @@ void CharacterAnimator::TriggerEmote(uint8_t p_emoteId, LegoROI* p_roi, bool p_i
|
|||||||
m_emoteDuration = (float) cache->anim->GetDuration();
|
m_emoteDuration = (float) cache->anim->GetDuration();
|
||||||
m_emoteActive = true;
|
m_emoteActive = true;
|
||||||
|
|
||||||
|
BuildEmoteProps(m_emotePropGroup, cache->anim, p_roi);
|
||||||
|
|
||||||
const char* sound = g_emoteEntries[p_emoteId].phases[0].sound;
|
const char* sound = g_emoteEntries[p_emoteId].phases[0].sound;
|
||||||
if (sound) {
|
if (sound) {
|
||||||
PlayROISound(sound, p_roi);
|
PlayROISound(sound, p_roi);
|
||||||
@ -344,8 +354,8 @@ void CharacterAnimator::BuildRideAnimation(int8_t p_vehicleType, LegoROI* p_play
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_rideAnim = static_cast<LegoAnimPresenter*>(presenter)->GetAnimation();
|
m_ridePropGroup.anim = static_cast<LegoAnimPresenter*>(presenter)->GetAnimation();
|
||||||
if (!m_rideAnim) {
|
if (!m_ridePropGroup.anim) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,28 +368,28 @@ void CharacterAnimator::BuildRideAnimation(int8_t p_vehicleType, LegoROI* p_play
|
|||||||
else {
|
else {
|
||||||
SDL_snprintf(variantName, sizeof(variantName), "tp_vehicle");
|
SDL_snprintf(variantName, sizeof(variantName), "tp_vehicle");
|
||||||
}
|
}
|
||||||
m_rideVehicleROI = CharacterManager()->CreateAutoROI(variantName, baseName, FALSE);
|
LegoROI* vehicleROI = CharacterManager()->CreateAutoROI(variantName, baseName, FALSE);
|
||||||
if (m_rideVehicleROI) {
|
if (vehicleROI) {
|
||||||
m_rideVehicleROI->SetName(vehicleVariantName);
|
vehicleROI->SetName(vehicleVariantName);
|
||||||
|
m_ridePropGroup.propROIs = new LegoROI*[1];
|
||||||
|
m_ridePropGroup.propROIs[0] = vehicleROI;
|
||||||
|
m_ridePropGroup.propCount = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimUtils::BuildROIMap(m_rideAnim, p_playerROI, m_rideVehicleROI, m_rideRoiMap, m_rideRoiMapSize);
|
AnimUtils::BuildROIMap(
|
||||||
|
m_ridePropGroup.anim,
|
||||||
|
p_playerROI,
|
||||||
|
m_ridePropGroup.propROIs,
|
||||||
|
m_ridePropGroup.propCount,
|
||||||
|
m_ridePropGroup.roiMap,
|
||||||
|
m_ridePropGroup.roiMapSize
|
||||||
|
);
|
||||||
m_animTime = 0.0f;
|
m_animTime = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterAnimator::ClearRideAnimation()
|
void CharacterAnimator::ClearRideAnimation()
|
||||||
{
|
{
|
||||||
if (m_rideRoiMap) {
|
ClearPropGroup(m_ridePropGroup);
|
||||||
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;
|
m_currentVehicleType = VEHICLE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,6 +427,89 @@ void CharacterAnimator::ClearFrozenState()
|
|||||||
m_frozenEmoteId = -1;
|
m_frozenEmoteId = -1;
|
||||||
m_frozenAnimCache = nullptr;
|
m_frozenAnimCache = nullptr;
|
||||||
m_frozenAnimDuration = 0.0f;
|
m_frozenAnimDuration = 0.0f;
|
||||||
|
ClearPropGroup(m_emotePropGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterAnimator::ClearPropGroup(PropGroup& p_group)
|
||||||
|
{
|
||||||
|
delete[] p_group.roiMap;
|
||||||
|
p_group.roiMap = nullptr;
|
||||||
|
p_group.roiMapSize = 0;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < p_group.propCount; i++) {
|
||||||
|
if (p_group.propROIs[i]) {
|
||||||
|
VideoManager()->Get3DManager()->Remove(*p_group.propROIs[i]);
|
||||||
|
CharacterManager()->ReleaseAutoROI(p_group.propROIs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete[] p_group.propROIs;
|
||||||
|
p_group.propROIs = nullptr;
|
||||||
|
p_group.propCount = 0;
|
||||||
|
p_group.anim = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps animation tree node names to actual LOD names when they differ.
|
||||||
|
static const char* ResolvePropLODName(const char* p_nodeName)
|
||||||
|
{
|
||||||
|
static const struct {
|
||||||
|
const char* nodePrefix;
|
||||||
|
const char* lodName;
|
||||||
|
} mappings[] = {
|
||||||
|
{"popmug", "pizpie"},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& m : mappings) {
|
||||||
|
if (!SDL_strncasecmp(p_nodeName, m.nodePrefix, SDL_strlen(m.nodePrefix))) {
|
||||||
|
return m.lodName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p_nodeName;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CharacterAnimator::BuildEmoteProps(PropGroup& p_group, LegoAnim* p_anim, LegoROI* p_playerROI)
|
||||||
|
{
|
||||||
|
std::vector<std::string> unmatchedNames;
|
||||||
|
AnimUtils::CollectUnmatchedNodes(p_anim, p_playerROI, unmatchedNames);
|
||||||
|
if (unmatchedNames.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<LegoROI*> createdROIs;
|
||||||
|
for (const std::string& name : unmatchedNames) {
|
||||||
|
char uniqueName[64];
|
||||||
|
if (m_config.propSuffix != 0) {
|
||||||
|
SDL_snprintf(uniqueName, sizeof(uniqueName), "%s_mp_%u", name.c_str(), m_config.propSuffix);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
SDL_snprintf(uniqueName, sizeof(uniqueName), "tp_prop_%s", name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* lodName = ResolvePropLODName(name.c_str());
|
||||||
|
LegoROI* propROI = CharacterManager()->CreateAutoROI(uniqueName, lodName, FALSE);
|
||||||
|
if (propROI) {
|
||||||
|
propROI->SetName(name.c_str());
|
||||||
|
createdROIs.push_back(propROI);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createdROIs.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
p_group.propCount = (uint8_t) createdROIs.size();
|
||||||
|
p_group.propROIs = new LegoROI*[p_group.propCount];
|
||||||
|
for (uint8_t i = 0; i < p_group.propCount; i++) {
|
||||||
|
p_group.propROIs[i] = createdROIs[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimUtils::BuildROIMap(
|
||||||
|
p_anim,
|
||||||
|
p_playerROI,
|
||||||
|
p_group.propROIs,
|
||||||
|
p_group.propCount,
|
||||||
|
p_group.roiMap,
|
||||||
|
p_group.roiMapSize
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CharacterAnimator::ClearAnimCaches()
|
void CharacterAnimator::ClearAnimCaches()
|
||||||
|
|||||||
@ -28,8 +28,8 @@ RemotePlayer::RemotePlayer(uint32_t p_peerId, uint8_t p_actorId, uint8_t p_displ
|
|||||||
: m_peerId(p_peerId), m_actorId(p_actorId), m_displayActorIndex(p_displayActorIndex), m_roi(nullptr),
|
: 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_spawned(false), m_visible(false), m_targetSpeed(0.0f), m_targetVehicleType(VEHICLE_NONE), m_targetWorldId(-1),
|
||||||
m_lastUpdateTime(SDL_GetTicks()), m_hasReceivedUpdate(false),
|
m_lastUpdateTime(SDL_GetTicks()), m_hasReceivedUpdate(false),
|
||||||
m_animator(Common::CharacterAnimatorConfig{/*.saveEmoteTransform=*/false}), m_vehicleROI(nullptr),
|
m_animator(Common::CharacterAnimatorConfig{/*.saveEmoteTransform=*/false, /*.propSuffix=*/p_peerId}),
|
||||||
m_nameBubble(nullptr), m_allowRemoteCustomize(true)
|
m_vehicleROI(nullptr), m_nameBubble(nullptr), m_allowRemoteCustomize(true)
|
||||||
{
|
{
|
||||||
m_displayName[0] = '\0';
|
m_displayName[0] = '\0';
|
||||||
const char* displayName = GetDisplayActorName();
|
const char* displayName = GetDisplayActorName();
|
||||||
|
|||||||
@ -28,8 +28,8 @@ using namespace Extensions::Common;
|
|||||||
using namespace Extensions::ThirdPersonCamera;
|
using namespace Extensions::ThirdPersonCamera;
|
||||||
|
|
||||||
Controller::Controller()
|
Controller::Controller()
|
||||||
: m_animator(CharacterAnimatorConfig{/*.saveEmoteTransform=*/true}), m_enabled(false), m_active(false),
|
: m_animator(CharacterAnimatorConfig{/*.saveEmoteTransform=*/true, /*.propSuffix=*/0}), m_enabled(false),
|
||||||
m_pendingWorldTransition(false), m_playerROI(nullptr)
|
m_active(false), m_pendingWorldTransition(false), m_playerROI(nullptr)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user