mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-01 18:13:57 +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
|
||||
|
||||
#include "mxtypes.h"
|
||||
#include "mxgeometry/mxmatrix.h"
|
||||
#include "mxtypes.h"
|
||||
#include "realtime/vector.h"
|
||||
#include "roi/legoroi.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class LegoAnim;
|
||||
|
||||
@ -34,8 +35,7 @@ struct AnimCache {
|
||||
|
||||
AnimCache(const AnimCache&) = delete;
|
||||
AnimCache& operator=(const AnimCache&) = delete;
|
||||
AnimCache(AnimCache&& p_other) noexcept
|
||||
: anim(p_other.anim), roiMap(p_other.roiMap), roiMapSize(p_other.roiMapSize)
|
||||
AnimCache(AnimCache&& p_other) noexcept : anim(p_other.anim), roiMap(p_other.roiMap), roiMapSize(p_other.roiMapSize)
|
||||
{
|
||||
p_other.roiMap = nullptr;
|
||||
p_other.roiMapSize = 0;
|
||||
@ -61,16 +61,15 @@ struct AnimCache {
|
||||
void BuildROIMap(
|
||||
LegoAnim* p_anim,
|
||||
LegoROI* p_rootROI,
|
||||
LegoROI* p_extraROI,
|
||||
LegoROI** p_extraROIs,
|
||||
int p_extraROICount,
|
||||
LegoROI**& p_roiMap,
|
||||
MxU32& p_roiMapSize
|
||||
);
|
||||
|
||||
AnimCache* GetOrBuildAnimCache(
|
||||
std::map<std::string, AnimCache>& p_cacheMap,
|
||||
LegoROI* p_roi,
|
||||
const char* p_animName
|
||||
);
|
||||
void CollectUnmatchedNodes(LegoAnim* p_anim, LegoROI* p_rootROI, std::vector<std::string>& p_unmatchedNames);
|
||||
|
||||
AnimCache* GetOrBuildAnimCache(std::map<std::string, AnimCache>& p_cacheMap, LegoROI* p_roi, const char* p_animName);
|
||||
|
||||
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
|
||||
// to prevent scale accumulation (needed for ThirdPersonCameraExt's display clone).
|
||||
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.
|
||||
@ -61,10 +74,10 @@ class CharacterAnimator {
|
||||
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; }
|
||||
LegoROI* GetRideVehicleROI() const { return m_ridePropGroup.propCount > 0 ? m_ridePropGroup.propROIs[0] : nullptr; }
|
||||
LegoAnim* GetRideAnim() const { return m_ridePropGroup.anim; }
|
||||
LegoROI** GetRideRoiMap() const { return m_ridePropGroup.roiMap; }
|
||||
MxU32 GetRideRoiMapSize() const { return m_ridePropGroup.roiMapSize; }
|
||||
|
||||
// Animation cache management
|
||||
void InitAnimCaches(LegoROI* p_roi);
|
||||
@ -94,6 +107,8 @@ class CharacterAnimator {
|
||||
|
||||
AnimCache* GetOrBuildAnimCache(LegoROI* p_roi, const char* p_animName);
|
||||
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);
|
||||
|
||||
CharacterAnimatorConfig m_config;
|
||||
@ -133,12 +148,11 @@ class CharacterAnimator {
|
||||
std::map<std::string, AnimCache> m_animCacheMap;
|
||||
|
||||
// Ride animation (vehicle-specific)
|
||||
LegoAnim* m_rideAnim;
|
||||
LegoROI** m_rideRoiMap;
|
||||
MxU32 m_rideRoiMapSize;
|
||||
LegoROI* m_rideVehicleROI;
|
||||
|
||||
PropGroup m_ridePropGroup;
|
||||
int8_t m_currentVehicleType;
|
||||
|
||||
// Emote prop animation (dynamically-created props for emotes like Toss)
|
||||
PropGroup m_emotePropGroup;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "misc/legotree.h"
|
||||
#include "roi/legoroi.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
using namespace Extensions::Common;
|
||||
@ -17,7 +18,8 @@ static void AssignROIIndices(
|
||||
LegoTreeNode* p_node,
|
||||
LegoROI* p_parentROI,
|
||||
LegoROI* p_rootROI,
|
||||
LegoROI* p_extraROI,
|
||||
LegoROI** p_extraROIs,
|
||||
int p_extraROICount,
|
||||
MxU32& p_nextIndex,
|
||||
std::vector<LegoROI*>& p_entries,
|
||||
bool& p_rootClaimed
|
||||
@ -36,11 +38,29 @@ static void AssignROIIndices(
|
||||
matchedROI = p_rootROI;
|
||||
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 {
|
||||
matchedROI = p_parentROI->FindChildROI(name, p_parentROI);
|
||||
if (matchedROI == nullptr && p_extraROI != nullptr) {
|
||||
matchedROI = p_extraROI->FindChildROI(name, p_extraROI);
|
||||
if (matchedROI == nullptr) {
|
||||
// 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++) {
|
||||
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(
|
||||
LegoAnim* p_anim,
|
||||
LegoROI* p_rootROI,
|
||||
LegoROI* p_extraROI,
|
||||
LegoROI** p_extraROIs,
|
||||
int p_extraROICount,
|
||||
LegoROI**& p_roiMap,
|
||||
MxU32& p_roiMapSize
|
||||
)
|
||||
@ -79,7 +109,7 @@ void AnimUtils::BuildROIMap(
|
||||
MxU32 nextIndex = 1;
|
||||
std::vector<LegoROI*> entries;
|
||||
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()) {
|
||||
return;
|
||||
@ -129,7 +159,67 @@ AnimUtils::AnimCache* AnimUtils::GetOrBuildAnimCache(
|
||||
// Build and cache
|
||||
AnimCache& cache = p_cacheMap[p_animName];
|
||||
cache.anim = anim;
|
||||
BuildROIMap(anim, p_roi, nullptr, cache.roiMap, cache.roiMapSize);
|
||||
BuildROIMap(anim, p_roi, nullptr, 0, cache.roiMap, cache.roiMapSize);
|
||||
|
||||
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_animTime(0.0f), m_idleTime(0.0f), m_idleAnimTime(0.0f), m_wasMoving(false), m_emoteAnimCache(nullptr),
|
||||
m_emoteTime(0.0f), m_emoteDuration(0.0f), m_emoteActive(false), m_currentEmoteId(0), m_frozenEmoteId(-1),
|
||||
m_frozenAnimCache(nullptr), m_frozenAnimDuration(0.0f), m_clickAnimObjectId(0), m_rideAnim(nullptr),
|
||||
m_rideRoiMap(nullptr), m_rideRoiMapSize(0), m_rideVehicleROI(nullptr), m_currentVehicleType(VEHICLE_NONE)
|
||||
m_frozenAnimCache(nullptr), m_frozenAnimDuration(0.0f), m_clickAnimObjectId(0), m_currentVehicleType(VEHICLE_NONE)
|
||||
{
|
||||
}
|
||||
|
||||
CharacterAnimator::~CharacterAnimator()
|
||||
{
|
||||
ClearPropGroup(m_emotePropGroup);
|
||||
ClearRideAnimation();
|
||||
}
|
||||
|
||||
@ -50,10 +50,10 @@ void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
|
||||
LegoROI** walkRoiMap = nullptr;
|
||||
MxU32 walkRoiMapSize = 0;
|
||||
|
||||
if (m_currentVehicleType != VEHICLE_NONE && m_rideAnim && m_rideRoiMap) {
|
||||
walkAnim = m_rideAnim;
|
||||
walkRoiMap = m_rideRoiMap;
|
||||
walkRoiMapSize = m_rideRoiMapSize;
|
||||
if (m_currentVehicleType != VEHICLE_NONE && m_ridePropGroup.anim && m_ridePropGroup.roiMap) {
|
||||
walkAnim = m_ridePropGroup.anim;
|
||||
walkRoiMap = m_ridePropGroup.roiMap;
|
||||
walkRoiMapSize = m_ridePropGroup.roiMapSize;
|
||||
}
|
||||
else if (m_walkAnimCache && m_walkAnimCache->anim && m_walkAnimCache->roiMap) {
|
||||
walkAnim = m_walkAnimCache->anim;
|
||||
@ -78,6 +78,7 @@ void CharacterAnimator::Tick(float p_deltaTime, LegoROI* p_roi, bool p_isMoving)
|
||||
if (m_emoteActive) {
|
||||
m_emoteActive = false;
|
||||
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
|
||||
m_emoteActive = false;
|
||||
m_emoteAnimCache = nullptr;
|
||||
ClearPropGroup(m_emotePropGroup);
|
||||
m_wasMoving = false;
|
||||
m_idleTime = 0.0f;
|
||||
m_idleAnimTime = 0.0f;
|
||||
}
|
||||
}
|
||||
else {
|
||||
LegoROI** emoteRoiMap =
|
||||
m_emotePropGroup.roiMap != nullptr ? m_emotePropGroup.roiMap : m_emoteAnimCache->roiMap;
|
||||
MxMatrix transform(m_config.saveEmoteTransform ? m_emoteParentTransform : p_roi->GetLocal2World());
|
||||
|
||||
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),
|
||||
transform,
|
||||
(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();
|
||||
ClearPropGroup(m_emotePropGroup);
|
||||
|
||||
m_currentEmoteId = p_emoteId;
|
||||
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_emoteActive = true;
|
||||
|
||||
BuildEmoteProps(m_emotePropGroup, cache->anim, p_roi);
|
||||
|
||||
const char* sound = g_emoteEntries[p_emoteId].phases[1].sound;
|
||||
if (sound) {
|
||||
PlayROISound(sound, p_roi);
|
||||
@ -279,6 +286,7 @@ void CharacterAnimator::TriggerEmote(uint8_t p_emoteId, LegoROI* p_roi, bool p_i
|
||||
}
|
||||
|
||||
StopClickAnimation();
|
||||
ClearPropGroup(m_emotePropGroup);
|
||||
|
||||
m_currentEmoteId = p_emoteId;
|
||||
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_emoteActive = true;
|
||||
|
||||
BuildEmoteProps(m_emotePropGroup, cache->anim, p_roi);
|
||||
|
||||
const char* sound = g_emoteEntries[p_emoteId].phases[0].sound;
|
||||
if (sound) {
|
||||
PlayROISound(sound, p_roi);
|
||||
@ -344,8 +354,8 @@ void CharacterAnimator::BuildRideAnimation(int8_t p_vehicleType, LegoROI* p_play
|
||||
return;
|
||||
}
|
||||
|
||||
m_rideAnim = static_cast<LegoAnimPresenter*>(presenter)->GetAnimation();
|
||||
if (!m_rideAnim) {
|
||||
m_ridePropGroup.anim = static_cast<LegoAnimPresenter*>(presenter)->GetAnimation();
|
||||
if (!m_ridePropGroup.anim) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -358,28 +368,28 @@ void CharacterAnimator::BuildRideAnimation(int8_t p_vehicleType, LegoROI* p_play
|
||||
else {
|
||||
SDL_snprintf(variantName, sizeof(variantName), "tp_vehicle");
|
||||
}
|
||||
m_rideVehicleROI = CharacterManager()->CreateAutoROI(variantName, baseName, FALSE);
|
||||
if (m_rideVehicleROI) {
|
||||
m_rideVehicleROI->SetName(vehicleVariantName);
|
||||
LegoROI* vehicleROI = CharacterManager()->CreateAutoROI(variantName, baseName, FALSE);
|
||||
if (vehicleROI) {
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
ClearPropGroup(m_ridePropGroup);
|
||||
m_currentVehicleType = VEHICLE_NONE;
|
||||
}
|
||||
|
||||
@ -417,6 +427,89 @@ void CharacterAnimator::ClearFrozenState()
|
||||
m_frozenEmoteId = -1;
|
||||
m_frozenAnimCache = nullptr;
|
||||
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()
|
||||
|
||||
@ -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_spawned(false), m_visible(false), m_targetSpeed(0.0f), m_targetVehicleType(VEHICLE_NONE), m_targetWorldId(-1),
|
||||
m_lastUpdateTime(SDL_GetTicks()), m_hasReceivedUpdate(false),
|
||||
m_animator(Common::CharacterAnimatorConfig{/*.saveEmoteTransform=*/false}), m_vehicleROI(nullptr),
|
||||
m_nameBubble(nullptr), m_allowRemoteCustomize(true)
|
||||
m_animator(Common::CharacterAnimatorConfig{/*.saveEmoteTransform=*/false, /*.propSuffix=*/p_peerId}),
|
||||
m_vehicleROI(nullptr), m_nameBubble(nullptr), m_allowRemoteCustomize(true)
|
||||
{
|
||||
m_displayName[0] = '\0';
|
||||
const char* displayName = GetDisplayActorName();
|
||||
|
||||
@ -28,8 +28,8 @@ using namespace Extensions::Common;
|
||||
using namespace Extensions::ThirdPersonCamera;
|
||||
|
||||
Controller::Controller()
|
||||
: m_animator(CharacterAnimatorConfig{/*.saveEmoteTransform=*/true}), m_enabled(false), m_active(false),
|
||||
m_pendingWorldTransition(false), m_playerROI(nullptr)
|
||||
: m_animator(CharacterAnimatorConfig{/*.saveEmoteTransform=*/true, /*.propSuffix=*/0}), m_enabled(false),
|
||||
m_active(false), m_pendingWorldTransition(false), m_playerROI(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user