diff --git a/extensions/include/extensions/multiplayer/animation/catalog.h b/extensions/include/extensions/multiplayer/animation/catalog.h index bc1def50..dc84002a 100644 --- a/extensions/include/extensions/multiplayer/animation/catalog.h +++ b/extensions/include/extensions/multiplayer/animation/catalog.h @@ -16,7 +16,7 @@ enum AnimCategory : uint8_t { e_otherAnim // no named character performers (ambient/prop-only) }; -// Number of core playable characters (Pepper, Mama, Papa, Nick, Laura) = g_characters indices 0-4 +// Number of core playable characters (Pepper, Mama, Papa, Nick, Laura) = g_actorInfoInit indices 0-4 static const int8_t CORE_CHARACTER_COUNT = 5; // Spectator mask with all core characters enabled @@ -32,9 +32,8 @@ struct CatalogEntry { uint16_t animIndex; // Index into LegoAnimationManager::m_anims[] AnimCategory category; uint8_t spectatorMask; // Which core actors can trigger (bit0=Pepper..bit4=Laura) - uint64_t performerMask; // Bitmask of g_characters[] indices that appear as character models + uint64_t performerMask; // Bitmask of g_actorInfoInit[] indices that appear as character models int16_t location; // -1 = anywhere, >= 0 = specific location - int8_t characterIndex; // Primary character index into g_characters[] uint8_t modelCount; // Number of models in animation uint8_t vehicleMask; // Bitmask of g_vehicles[] indices required (bit0=bikebd..bit6=board) }; @@ -50,10 +49,10 @@ class Catalog { std::vector GetAnimationsAtLocation(int16_t p_location) const; // Check if a player can fill any role (spectator or participant) in this animation. - // Accepts a display actor index (converted to g_characters index internally). + // Accepts a display actor index (converted to g_actorInfoInit index internally). bool CanParticipate(const CatalogEntry* p_entry, uint8_t p_displayActorIndex) const; - // Same check but using a g_characters index directly. + // Same check but using a g_actorInfoInit index directly. static bool CanParticipateChar(const CatalogEntry* p_entry, int8_t p_charIndex); // Check if a set of character indices can collectively trigger this animation. @@ -86,7 +85,11 @@ class Catalog { // Determine the vehicle state for a character given their current ride vehicle ROI. static VehicleState GetVehicleState(int8_t p_charIndex, class LegoROI* p_vehicleROI); - // Convert a display actor index to the g_characters[] index used by animations. + // Classify a g_vehicles[] index into a vehicle category. + // Returns 0=bike, 1=motorcycle, 2=skateboard, -1=invalid. + static int8_t GetVehicleCategory(int8_t p_vehicleIdx); + + // Convert a display actor index to the g_actorInfoInit[] index used by animations. // Returns -1 if no match. static int8_t DisplayActorToCharacterIndex(uint8_t p_displayActorIndex); diff --git a/extensions/include/extensions/multiplayer/animation/sceneplayer.h b/extensions/include/extensions/multiplayer/animation/sceneplayer.h index 2ca0101e..0bc8beef 100644 --- a/extensions/include/extensions/multiplayer/animation/sceneplayer.h +++ b/extensions/include/extensions/multiplayer/animation/sceneplayer.h @@ -23,7 +23,7 @@ struct ParticipantROI { LegoROI* vehicleROI; // Ride vehicle ROI (bike/board/moto), or nullptr MxMatrix savedTransform; std::string savedName; - int8_t charIndex; // g_characters[] index, or -1 for spectator + int8_t charIndex; // g_actorInfoInit[] index, or -1 for spectator bool IsSpectator() const { return charIndex < 0; } }; diff --git a/extensions/include/extensions/multiplayer/animation/sessionhost.h b/extensions/include/extensions/multiplayer/animation/sessionhost.h index ec528680..139acece 100644 --- a/extensions/include/extensions/multiplayer/animation/sessionhost.h +++ b/extensions/include/extensions/multiplayer/animation/sessionhost.h @@ -13,7 +13,7 @@ enum class CoordinationState : uint8_t; struct SessionSlot { uint32_t peerId; // 0 = unfilled - int8_t charIndex; // g_characters index, or -1 for spectator + int8_t charIndex; // g_actorInfoInit index, or -1 for spectator bool IsSpectator() const { return charIndex < 0; } }; diff --git a/extensions/include/extensions/multiplayer/networkmanager.h b/extensions/include/extensions/multiplayer/networkmanager.h index 7615ab87..e8a4abbb 100644 --- a/extensions/include/extensions/multiplayer/networkmanager.h +++ b/extensions/include/extensions/multiplayer/networkmanager.h @@ -197,7 +197,6 @@ class NetworkManager : public MxCore { bool m_showNameBubbles; bool m_lastCameraEnabled; uint8_t m_lastVehicleState; - bool m_vehicleFilterLogPending; // TODO(vehicle-filter): Remove after verification bool m_wasInRestrictedArea; // NPC animation playback diff --git a/extensions/src/multiplayer/animation/catalog.cpp b/extensions/src/multiplayer/animation/catalog.cpp index cb79fc56..c1aff2b7 100644 --- a/extensions/src/multiplayer/animation/catalog.cpp +++ b/extensions/src/multiplayer/animation/catalog.cpp @@ -11,28 +11,28 @@ using namespace Multiplayer::Animation; -// Static mapping of character index to g_vehicles[] index. -// Mirrors g_characters[].m_vehicleId for characters that own a vehicle. -static int8_t GetCharacterVehicleId(int8_t p_charIndex) +// Defined in legoanimationmanager.cpp — not exported in headers. +extern LegoAnimationManager::Character g_characters[47]; +extern LegoAnimationManager::Vehicle g_vehicles[7]; + +// Look up the g_vehicles[] index for a character's owned vehicle. +// p_actorInfoIndex is an index into g_actorInfoInit[]. +// Returns -1 if the character has no vehicle. +static int8_t GetCharacterVehicleId(int8_t p_actorInfoIndex) { - switch (p_charIndex) { - case 0: - return 6; // pepper -> board (skateboard) - case 3: - return 4; // nick -> motoni (motorcycle) - case 4: - return 5; // laura -> motola (motorcycle) - case 36: - return 2; // rd -> bikerd - case 37: - return 1; // pg -> bikepg - case 38: - return 0; // bd -> bikebd - case 39: - return 3; // sy -> bikesy - default: + if (p_actorInfoIndex < 0 || p_actorInfoIndex >= (int8_t) SDL_min(sizeOfArray(g_actorInfoInit), (size_t) 64)) { return -1; } + const char* name = g_actorInfoInit[p_actorInfoIndex].m_name; + if (!name) { + return -1; + } + for (int i = 0; i < (int) sizeOfArray(g_characters); i++) { + if (!SDL_strcasecmp(name, g_characters[i].m_name)) { + return g_characters[i].m_vehicleId; + } + } + return -1; } // Exact-match a model name against g_actorInfoInit[].m_name. @@ -88,7 +88,6 @@ void Catalog::Refresh(LegoAnimationManager* p_am) entry.animIndex = i; entry.spectatorMask = m_animsBase[i].m_unk0x0c; entry.location = m_animsBase[i].m_location; - entry.characterIndex = m_animsBase[i].m_characterIndex; entry.modelCount = m_animsBase[i].m_modelCount; // Compute performerMask by matching models against g_actorInfoInit[].m_name @@ -107,7 +106,7 @@ void Catalog::Refresh(LegoAnimationManager* p_am) // with m_unk0x2c=1 that match a known vehicle name. entry.vehicleMask = 0; for (int k = 0; k < 3; k++) { - if (m_animsBase[i].m_unk0x2a[k] >= 0 && m_animsBase[i].m_unk0x2a[k] < 8) { + if (m_animsBase[i].m_unk0x2a[k] >= 0 && m_animsBase[i].m_unk0x2a[k] < (int8_t) sizeOfArray(g_vehicles)) { entry.vehicleMask |= (1 << m_animsBase[i].m_unk0x2a[k]); } } @@ -232,17 +231,16 @@ bool Catalog::CheckVehicleEligibility(const CatalogEntry* p_entry, int8_t p_char } } -// Vehicle category grouping (matches ScenePlayer::GetVehicleCategory) -static int8_t GetVehicleCategory(int8_t p_vehicleIdx) +int8_t Catalog::GetVehicleCategory(int8_t p_vehicleIdx) { if (p_vehicleIdx >= 0 && p_vehicleIdx <= 3) { - return 0; // bike + return 0; // bike (bikebd, bikepg, bikerd, bikesy) } if (p_vehicleIdx >= 4 && p_vehicleIdx <= 5) { - return 1; // motorcycle + return 1; // motorcycle (motoni, motola) } if (p_vehicleIdx == 6) { - return 2; // skateboard + return 2; // skateboard (board) } return -1; } diff --git a/extensions/src/multiplayer/animation/sceneplayer.cpp b/extensions/src/multiplayer/animation/sceneplayer.cpp index 80f86596..67bf58ef 100644 --- a/extensions/src/multiplayer/animation/sceneplayer.cpp +++ b/extensions/src/multiplayer/animation/sceneplayer.cpp @@ -29,27 +29,6 @@ using namespace Multiplayer::Animation; namespace AnimUtils = Extensions::Common::AnimUtils; using Extensions::Common::CharacterCloner; -enum VehicleCategory { - e_bike, - e_motorcycle, - e_skateboard, - e_unknownVehicle -}; - -static VehicleCategory GetVehicleCategory(MxU32 p_vehicleIdx) -{ - if (p_vehicleIdx <= 3) { - return e_bike; - } - if (p_vehicleIdx <= 5) { - return e_motorcycle; - } - if (p_vehicleIdx == 6) { - return e_skateboard; - } - return e_unknownVehicle; -} - static bool MatchesCharacter(const std::string& p_actorName, int8_t p_charIndex) { if (p_charIndex < 0 || p_charIndex >= (int8_t) sizeOfArray(g_actorInfoInit)) { @@ -200,7 +179,8 @@ void ScenePlayer::SetupROIs(const AnimInfo* p_animInfo) MxU32 perfVehicleIdx; if (AnimationManager()->FindVehicle(m_participants[p].vehicleROI->GetName(), perfVehicleIdx)) { - if (GetVehicleCategory(animVehicleIdx) == GetVehicleCategory(perfVehicleIdx)) { + if (Catalog::GetVehicleCategory((int8_t) animVehicleIdx) == + Catalog::GetVehicleCategory((int8_t) perfVehicleIdx)) { m_vehicleROI = m_participants[p].vehicleROI; addAlias(lowered, m_vehicleROI); roi = m_vehicleROI; diff --git a/extensions/src/multiplayer/networkmanager.cpp b/extensions/src/multiplayer/networkmanager.cpp index 6fd2e1df..218b05a4 100644 --- a/extensions/src/multiplayer/networkmanager.cpp +++ b/extensions/src/multiplayer/networkmanager.cpp @@ -20,7 +20,6 @@ #include "mxticklemanager.h" #include "roi/legoroi.h" -#include #include #include #include @@ -68,8 +67,7 @@ NetworkManager::NetworkManager() m_inIsleWorld(false), m_registered(false), m_pendingToggleThirdPerson(false), m_pendingToggleNameBubbles(false), m_pendingWalkAnim(-1), m_pendingIdleAnim(-1), m_pendingEmote(-1), m_pendingToggleAllowCustomize(false), m_pendingAnimInterest(-1), m_pendingAnimCancel(false), m_localPendingAnimInterest(-1), m_showNameBubbles(true), - m_lastCameraEnabled(false), m_lastVehicleState(0), m_vehicleFilterLogPending(false), - m_wasInRestrictedArea(false), m_animStateDirty(false), + m_lastCameraEnabled(false), m_lastVehicleState(0), m_wasInRestrictedArea(false), m_animStateDirty(false), m_animInterestDirty(false), m_lastAnimPushTime(0), m_connectionState(STATE_DISCONNECTED), m_wasRejected(false), m_reconnectAttempt(0), m_reconnectDelay(0), m_nextReconnectTime(0) { @@ -127,7 +125,6 @@ MxResult NetworkManager::Tickle() if (vehicleState != m_lastVehicleState) { m_lastVehicleState = vehicleState; m_animStateDirty = true; - m_vehicleFilterLogPending = true; // Cancel active session if the current animation is no longer eligible. // Only cancel if the local player is a performer — spectators aren't vehicle-constrained. @@ -2141,11 +2138,13 @@ void NetworkManager::PushAnimationState() const float* localPos = userActor->GetROI()->GetWorldPosition(); float localX = localPos[0], localZ = localPos[2]; + uint8_t localVehicleState = Animation::Catalog::GetVehicleState(localCharIndex, cam->GetRideVehicleROI()); + // Build proximity character indices and vehicle state (for NPC anims — position-based, not location-based) std::vector proximityCharIndices; std::vector proximityVehicleState; proximityCharIndices.push_back(localCharIndex); - proximityVehicleState.push_back(Animation::Catalog::GetVehicleState(localCharIndex, cam->GetRideVehicleROI())); + proximityVehicleState.push_back(localVehicleState); for (const auto& [peerId, player] : m_remotePlayers) { if (!player->IsSpawned() || !player->GetROI() || player->GetWorldId() != (int8_t) LegoOmni::e_act1) { @@ -2177,7 +2176,7 @@ void NetworkManager::PushAnimationState() std::vector locationCharIndices; std::vector locationVehicleState; locationCharIndices.push_back(localCharIndex); - locationVehicleState.push_back(Animation::Catalog::GetVehicleState(localCharIndex, cam->GetRideVehicleROI())); + locationVehicleState.push_back(localVehicleState); for (const auto& [peerId, player] : m_remotePlayers) { if (!player->IsSpawned() || !player->GetROI() || player->GetWorldId() != (int8_t) LegoOmni::e_act1) { @@ -2207,37 +2206,6 @@ void NetworkManager::PushAnimationState() } } - // TODO(vehicle-filter): Remove this logging block after verification - if (m_vehicleFilterLogPending) { - m_vehicleFilterLogPending = false; - uint8_t vehState = Animation::Catalog::GetVehicleState(localCharIndex, cam->GetRideVehicleROI()); - const char* stateNames[] = {"onFoot", "onOwnVehicle", "onOtherVehicle"}; - SDL_Log( - "[VehicleFilter] Vehicle state changed: char=%d state=%s — %zu eligible animations", - localCharIndex, - stateNames[vehState < 3 ? vehState : 0], - eligibility.size() - ); - uint32_t vehicleAnimCount = 0; - for (const auto& info : eligibility) { - const AnimInfo* ai = m_animCatalog.GetAnimInfo(info.animIndex); - if (ai && info.entry && info.entry->vehicleMask) { - vehicleAnimCount++; - SDL_Log( - " [%u] %s (objId=%u loc=%d vmask=0x%02x)", - info.animIndex, - ai->m_name, - ai->m_objectId, - ai->m_location, - info.entry->vehicleMask - ); - } - } - if (vehicleAnimCount == 0) { - SDL_Log(" (no vehicle animations in eligible set)"); - } - } - // Build JSON std::string json; json.reserve(2048);