mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-01 18:13:57 +00:00
WIP: Filter animations by vehicle eligibility
Add vehicle-based filtering to the multiplayer ScenePlayer so that animations requiring a specific vehicle (skateboard, bike, motorcycle) are only offered when the performer is actually riding that vehicle. - Add vehicleMask to CatalogEntry from AnimInfo::m_unk0x2a - Three-state vehicle detection: on foot, on own vehicle, on foreign vehicle - Filter performer animations by vehicle state in eligibility computation - Spectator-only roles remain visible regardless of vehicle state - Host validates vehicle state on interest and re-validates during countdown - Cancel active sessions when local player's vehicle state changes Includes temporary debug logging tagged TODO(vehicle-filter). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
964013203a
commit
aa9df3370b
@ -36,6 +36,7 @@ struct CatalogEntry {
|
||||
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)
|
||||
};
|
||||
|
||||
class Catalog {
|
||||
@ -56,11 +57,13 @@ class Catalog {
|
||||
static bool CanParticipateChar(const CatalogEntry* p_entry, int8_t p_charIndex);
|
||||
|
||||
// Check if a set of character indices can collectively trigger this animation.
|
||||
// p_onVehicle: parallel array indicating if each player is riding their vehicle (nullable).
|
||||
// p_filledPerformers: bitmask of which performer bits in performerMask are covered.
|
||||
// p_spectatorFilled: whether a valid spectator was found among unassigned players.
|
||||
bool CanTrigger(
|
||||
const CatalogEntry* p_entry,
|
||||
const int8_t* p_charIndices,
|
||||
const uint8_t* p_onVehicle,
|
||||
uint8_t p_count,
|
||||
uint64_t* p_filledPerformers,
|
||||
bool* p_spectatorFilled
|
||||
@ -70,6 +73,19 @@ class Catalog {
|
||||
// Does NOT check performer exclusion — caller must do that if needed.
|
||||
static bool CheckSpectatorMask(const CatalogEntry* p_entry, int8_t p_charIndex);
|
||||
|
||||
// Vehicle riding state for eligibility checks.
|
||||
enum VehicleState : uint8_t {
|
||||
e_onFoot = 0, // Not riding anything
|
||||
e_onOwnVehicle = 1, // Riding character's own vehicle (e.g. Pepper on skateboard)
|
||||
e_onOtherVehicle = 2 // Riding a vehicle that isn't the character's own
|
||||
};
|
||||
|
||||
// Check if a player's vehicle state is compatible with the animation's vehicle requirements.
|
||||
static bool CheckVehicleEligibility(const CatalogEntry* p_entry, int8_t p_charIndex, uint8_t p_vehicleState);
|
||||
|
||||
// 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.
|
||||
// Returns -1 if no match.
|
||||
static int8_t DisplayActorToCharacterIndex(uint8_t p_displayActorIndex);
|
||||
|
||||
@ -57,11 +57,14 @@ class Coordinator {
|
||||
// Compute eligibility for animations at a location.
|
||||
// p_locationChars: local player + remote players at the same location (for cam anims).
|
||||
// p_proximityChars: local player + remote players within proximity (for NPC anims).
|
||||
// p_locationVehicles/p_proximityVehicles: parallel bool arrays indicating vehicle riding state.
|
||||
std::vector<EligibilityInfo> ComputeEligibility(
|
||||
int16_t p_location,
|
||||
const int8_t* p_locationChars,
|
||||
const uint8_t* p_locationVehicles,
|
||||
uint8_t p_locationCount,
|
||||
const int8_t* p_proximityChars,
|
||||
const uint8_t* p_proximityVehicles,
|
||||
uint8_t p_proximityCount
|
||||
) const;
|
||||
|
||||
|
||||
@ -145,6 +145,7 @@ class NetworkManager : public MxCore {
|
||||
bool IsPeerAtLocation(uint32_t p_peerId, int16_t p_location) const;
|
||||
bool GetPeerPosition(uint32_t p_peerId, float& p_x, float& p_z) const;
|
||||
bool IsPeerNearby(uint32_t p_peerId, float p_refX, float p_refZ) const;
|
||||
uint8_t GetPeerVehicleState(uint32_t p_peerId, int8_t p_charIndex) const;
|
||||
bool ValidateSessionLocations(uint16_t p_animIndex);
|
||||
|
||||
void ResetAnimationState();
|
||||
@ -195,6 +196,8 @@ 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
|
||||
|
||||
@ -5,11 +5,36 @@
|
||||
#include "legoactors.h"
|
||||
#include "legoanimationmanager.h"
|
||||
#include "misc.h"
|
||||
#include "roi/legoroi.h"
|
||||
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
|
||||
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)
|
||||
{
|
||||
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:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Exact-match a model name against g_actorInfoInit[].m_name.
|
||||
// The engine's LegoAnimationManager::GetCharacterIndex uses 2-char prefix matching,
|
||||
// which causes false positives (e.g. "ladder" matching "laura"). We need exact
|
||||
@ -77,6 +102,16 @@ void Catalog::Refresh(LegoAnimationManager* p_am)
|
||||
}
|
||||
}
|
||||
|
||||
// Compute vehicleMask from the pre-populated vehicle list (m_unk0x2a).
|
||||
// Each entry is a g_vehicles[] index set during LoadWorldInfo for models
|
||||
// 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) {
|
||||
entry.vehicleMask |= (1 << m_animsBase[i].m_unk0x2a[k]);
|
||||
}
|
||||
}
|
||||
|
||||
// Categorize based on whether the animation has named character performers.
|
||||
// g_actorInfoInit layout:
|
||||
// 0-47: named characters (pepper through jk)
|
||||
@ -178,6 +213,65 @@ bool Catalog::CheckSpectatorMask(const CatalogEntry* p_entry, int8_t p_charIndex
|
||||
return p_entry->spectatorMask == ALL_CORE_ACTORS_MASK;
|
||||
}
|
||||
|
||||
bool Catalog::CheckVehicleEligibility(const CatalogEntry* p_entry, int8_t p_charIndex, uint8_t p_vehicleState)
|
||||
{
|
||||
int8_t vehicleId = GetCharacterVehicleId(p_charIndex);
|
||||
if (vehicleId < 0) {
|
||||
return true; // Character has no vehicle — no constraint (Mama, Papa, NPCs)
|
||||
}
|
||||
|
||||
bool animUsesVehicle = (p_entry->vehicleMask >> vehicleId) & 1;
|
||||
|
||||
switch (p_vehicleState) {
|
||||
case e_onOwnVehicle:
|
||||
return animUsesVehicle; // Only animations that use this character's vehicle
|
||||
case e_onOtherVehicle:
|
||||
return false; // On a foreign vehicle — no animations eligible
|
||||
default: // e_onFoot
|
||||
return !animUsesVehicle; // Only animations that don't use this character's vehicle
|
||||
}
|
||||
}
|
||||
|
||||
// Vehicle category grouping (matches ScenePlayer::GetVehicleCategory)
|
||||
static int8_t GetVehicleCategory(int8_t p_vehicleIdx)
|
||||
{
|
||||
if (p_vehicleIdx >= 0 && p_vehicleIdx <= 3) {
|
||||
return 0; // bike
|
||||
}
|
||||
if (p_vehicleIdx >= 4 && p_vehicleIdx <= 5) {
|
||||
return 1; // motorcycle
|
||||
}
|
||||
if (p_vehicleIdx == 6) {
|
||||
return 2; // skateboard
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Catalog::VehicleState Catalog::GetVehicleState(int8_t p_charIndex, LegoROI* p_vehicleROI)
|
||||
{
|
||||
if (!p_vehicleROI || !p_vehicleROI->GetName()) {
|
||||
return e_onFoot;
|
||||
}
|
||||
|
||||
int8_t charVehicleId = GetCharacterVehicleId(p_charIndex);
|
||||
if (charVehicleId < 0) {
|
||||
return e_onFoot; // Character has no vehicle — treat any ride as irrelevant
|
||||
}
|
||||
|
||||
MxU32 rideVehicleIdx;
|
||||
if (!AnimationManager()->FindVehicle(p_vehicleROI->GetName(), rideVehicleIdx)) {
|
||||
return e_onOtherVehicle; // Unknown vehicle — treat as foreign
|
||||
}
|
||||
|
||||
// Compare by category — the ride system uses representative names (bikebd/motoni/board)
|
||||
// that may differ from the character's specific vehicle index but share the same category.
|
||||
if (GetVehicleCategory((int8_t) rideVehicleIdx) == GetVehicleCategory(charVehicleId)) {
|
||||
return e_onOwnVehicle;
|
||||
}
|
||||
|
||||
return e_onOtherVehicle;
|
||||
}
|
||||
|
||||
bool Catalog::CanParticipateChar(const CatalogEntry* p_entry, int8_t p_charIndex)
|
||||
{
|
||||
if (p_charIndex < 0) {
|
||||
@ -201,6 +295,7 @@ bool Catalog::CanParticipate(const CatalogEntry* p_entry, uint8_t p_displayActor
|
||||
bool Catalog::CanTrigger(
|
||||
const CatalogEntry* p_entry,
|
||||
const int8_t* p_charIndices,
|
||||
const uint8_t* p_onVehicle,
|
||||
uint8_t p_count,
|
||||
uint64_t* p_filledPerformers,
|
||||
bool* p_spectatorFilled
|
||||
@ -220,6 +315,10 @@ bool Catalog::CanTrigger(
|
||||
|
||||
uint64_t charBit = uint64_t(1) << charIndex;
|
||||
if ((p_entry->performerMask & charBit) && !(*p_filledPerformers & charBit)) {
|
||||
if (p_onVehicle && !CheckVehicleEligibility(p_entry, charIndex, p_onVehicle[i])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
*p_filledPerformers |= charBit;
|
||||
assignedAsPerformer[i] = true;
|
||||
}
|
||||
|
||||
@ -82,8 +82,10 @@ static void BuildSlots(
|
||||
std::vector<EligibilityInfo> Coordinator::ComputeEligibility(
|
||||
int16_t p_location,
|
||||
const int8_t* p_locationChars,
|
||||
const uint8_t* p_locationVehicles,
|
||||
uint8_t p_locationCount,
|
||||
const int8_t* p_proximityChars,
|
||||
const uint8_t* p_proximityVehicles,
|
||||
uint8_t p_proximityCount
|
||||
) const
|
||||
{
|
||||
@ -101,9 +103,18 @@ std::vector<EligibilityInfo> Coordinator::ComputeEligibility(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Vehicle eligibility: only filter if the local player would be a performer.
|
||||
// Spectator-only roles remain visible so players on vehicles can still watch nearby scenes.
|
||||
if ((entry->performerMask >> p_locationChars[0]) & 1) {
|
||||
if (!Catalog::CheckVehicleEligibility(entry, p_locationChars[0], p_locationVehicles[0])) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// NPC anims (location == -1): use proximity characters
|
||||
// Cam anims (location >= 0): use location characters
|
||||
const int8_t* chars = (entry->location == -1) ? p_proximityChars : p_locationChars;
|
||||
const uint8_t* vehicles = (entry->location == -1) ? p_proximityVehicles : p_locationVehicles;
|
||||
uint8_t count = (entry->location == -1) ? p_proximityCount : p_locationCount;
|
||||
|
||||
EligibilityInfo info;
|
||||
@ -117,7 +128,7 @@ std::vector<EligibilityInfo> Coordinator::ComputeEligibility(
|
||||
bool spectatorFilled = false;
|
||||
|
||||
if (atLoc) {
|
||||
info.eligible = m_catalog->CanTrigger(entry, chars, count, &filledPerformers, &spectatorFilled);
|
||||
info.eligible = m_catalog->CanTrigger(entry, chars, vehicles, count, &filledPerformers, &spectatorFilled);
|
||||
}
|
||||
else {
|
||||
info.eligible = false;
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
#include "mxticklemanager.h"
|
||||
#include "roi/legoroi.h"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <algorithm>
|
||||
@ -67,9 +68,10 @@ 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_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)
|
||||
m_lastCameraEnabled(false), m_lastVehicleState(0), m_vehicleFilterLogPending(false),
|
||||
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)
|
||||
{
|
||||
}
|
||||
|
||||
@ -118,6 +120,31 @@ MxResult NetworkManager::Tickle()
|
||||
}
|
||||
}
|
||||
|
||||
// Detect vehicle state changes for animation eligibility refresh.
|
||||
// Tracks three states: on foot, on own vehicle, on foreign vehicle.
|
||||
int8_t localChar = Animation::Catalog::DisplayActorToCharacterIndex(cam->GetDisplayActorIndex());
|
||||
uint8_t vehicleState = Animation::Catalog::GetVehicleState(localChar, cam->GetRideVehicleROI());
|
||||
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.
|
||||
if (m_animCoordinator.GetState() != Animation::CoordinationState::e_idle) {
|
||||
uint16_t currentAnim = m_animCoordinator.GetCurrentAnimIndex();
|
||||
if (currentAnim != Animation::ANIM_INDEX_NONE) {
|
||||
const Animation::CatalogEntry* entry = m_animCatalog.FindEntry(currentAnim);
|
||||
if (entry && (entry->performerMask >> localChar) & 1) {
|
||||
if (!Animation::Catalog::CheckVehicleEligibility(entry, localChar, vehicleState)) {
|
||||
CancelLocalAnimInterest();
|
||||
StopScenePlayback(currentAnim, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create local name bubble when display ROI becomes available
|
||||
if (m_showNameBubbles && !m_localNameBubble && cam->GetDisplayROI()) {
|
||||
char name[8];
|
||||
@ -1323,6 +1350,29 @@ void NetworkManager::TickHostSessions()
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-remove participants whose vehicle state no longer matches
|
||||
if (entry && entry->vehicleMask) {
|
||||
std::vector<uint32_t> toRemove;
|
||||
for (const auto& slot : session->slots) {
|
||||
if (slot.peerId != 0 && !slot.IsSpectator()) {
|
||||
int8_t charIdx = slot.charIndex;
|
||||
uint8_t onVehicle = GetPeerVehicleState(slot.peerId, charIdx);
|
||||
if (!Animation::Catalog::CheckVehicleEligibility(entry, charIdx, onVehicle)) {
|
||||
toRemove.push_back(slot.peerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint32_t pid : toRemove) {
|
||||
std::vector<uint16_t> changed;
|
||||
m_animSessionHost.HandleCancel(pid, changed);
|
||||
BroadcastChangedSessions(changed);
|
||||
}
|
||||
session = m_animSessionHost.FindSession(animIndex);
|
||||
if (!session) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
bool allFilled = m_animSessionHost.AreAllSlotsFilled(animIndex);
|
||||
bool coLocated = allFilled && ValidateSessionLocations(animIndex);
|
||||
|
||||
@ -1373,6 +1423,17 @@ void NetworkManager::HandleAnimInterest(uint32_t p_peerId, uint16_t p_animIndex,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate vehicle eligibility if the joining player would be a performer
|
||||
if (entry) {
|
||||
int8_t charIndex = Animation::Catalog::DisplayActorToCharacterIndex(p_displayActorIndex);
|
||||
if ((entry->performerMask >> charIndex) & 1) {
|
||||
uint8_t onVehicle = GetPeerVehicleState(p_peerId, charIndex);
|
||||
if (!Animation::Catalog::CheckVehicleEligibility(entry, charIndex, onVehicle)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For NPC anims: if all slots are full, remove far-away participants to make room
|
||||
// for the new nearby player. This only fires when slots are exhausted — if there's
|
||||
// an open slot, the new player just joins normally without disturbing anyone.
|
||||
@ -1832,6 +1893,20 @@ bool NetworkManager::IsPeerNearby(uint32_t p_peerId, float p_refX, float p_refZ)
|
||||
return (dx * dx + dz * dz) <= NPC_ANIM_NEARBY_RADIUS_SQ;
|
||||
}
|
||||
|
||||
uint8_t NetworkManager::GetPeerVehicleState(uint32_t p_peerId, int8_t p_charIndex) const
|
||||
{
|
||||
if (p_peerId == m_localPeerId) {
|
||||
ThirdPersonCamera::Controller* cam = GetCamera();
|
||||
return cam ? Animation::Catalog::GetVehicleState(p_charIndex, cam->GetRideVehicleROI())
|
||||
: Animation::Catalog::e_onFoot;
|
||||
}
|
||||
auto it = m_remotePlayers.find(p_peerId);
|
||||
if (it == m_remotePlayers.end() || !it->second->IsSpawned()) {
|
||||
return Animation::Catalog::e_onFoot;
|
||||
}
|
||||
return Animation::Catalog::GetVehicleState(p_charIndex, it->second->GetRideVehicleROI());
|
||||
}
|
||||
|
||||
bool NetworkManager::ValidateSessionLocations(uint16_t p_animIndex)
|
||||
{
|
||||
const Animation::AnimSession* session = m_animSessionHost.FindSession(p_animIndex);
|
||||
@ -2066,9 +2141,11 @@ void NetworkManager::PushAnimationState()
|
||||
const float* localPos = userActor->GetROI()->GetWorldPosition();
|
||||
float localX = localPos[0], localZ = localPos[2];
|
||||
|
||||
// Build proximity character indices (for NPC anims — position-based, not location-based)
|
||||
// Build proximity character indices and vehicle state (for NPC anims — position-based, not location-based)
|
||||
std::vector<int8_t> proximityCharIndices;
|
||||
std::vector<uint8_t> proximityVehicleState;
|
||||
proximityCharIndices.push_back(localCharIndex);
|
||||
proximityVehicleState.push_back(Animation::Catalog::GetVehicleState(localCharIndex, cam->GetRideVehicleROI()));
|
||||
|
||||
for (const auto& [peerId, player] : m_remotePlayers) {
|
||||
if (!player->IsSpawned() || !player->GetROI() || player->GetWorldId() != (int8_t) LegoOmni::e_act1) {
|
||||
@ -2082,6 +2159,7 @@ void NetworkManager::PushAnimationState()
|
||||
if ((dx * dx + dz * dz) <= (Animation::NPC_ANIM_PROXIMITY * Animation::NPC_ANIM_PROXIMITY)) {
|
||||
int8_t charIdx = Animation::Catalog::DisplayActorToCharacterIndex(player->GetDisplayActorIndex());
|
||||
proximityCharIndices.push_back(charIdx);
|
||||
proximityVehicleState.push_back(Animation::Catalog::GetVehicleState(charIdx, player->GetRideVehicleROI()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -2095,9 +2173,11 @@ void NetworkManager::PushAnimationState()
|
||||
std::vector<int16_t> locationsToProcess = locations.empty() ? std::vector<int16_t>{int16_t(-1)} : locations;
|
||||
|
||||
for (int16_t loc : locationsToProcess) {
|
||||
// Build per-location character indices (for cam anims at this location)
|
||||
// Build per-location character indices and vehicle state (for cam anims at this location)
|
||||
std::vector<int8_t> locationCharIndices;
|
||||
std::vector<uint8_t> locationVehicleState;
|
||||
locationCharIndices.push_back(localCharIndex);
|
||||
locationVehicleState.push_back(Animation::Catalog::GetVehicleState(localCharIndex, cam->GetRideVehicleROI()));
|
||||
|
||||
for (const auto& [peerId, player] : m_remotePlayers) {
|
||||
if (!player->IsSpawned() || !player->GetROI() || player->GetWorldId() != (int8_t) LegoOmni::e_act1) {
|
||||
@ -2106,14 +2186,17 @@ void NetworkManager::PushAnimationState()
|
||||
if (player->IsAtLocation(loc)) {
|
||||
int8_t charIdx = Animation::Catalog::DisplayActorToCharacterIndex(player->GetDisplayActorIndex());
|
||||
locationCharIndices.push_back(charIdx);
|
||||
locationVehicleState.push_back(Animation::Catalog::GetVehicleState(charIdx, player->GetRideVehicleROI()));
|
||||
}
|
||||
}
|
||||
|
||||
auto locEligibility = m_animCoordinator.ComputeEligibility(
|
||||
loc,
|
||||
locationCharIndices.data(),
|
||||
locationVehicleState.data(),
|
||||
static_cast<uint8_t>(locationCharIndices.size()),
|
||||
proximityCharIndices.data(),
|
||||
proximityVehicleState.data(),
|
||||
static_cast<uint8_t>(proximityCharIndices.size())
|
||||
);
|
||||
|
||||
@ -2124,6 +2207,37 @@ 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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user