mirror of
https://github.com/isledecomp/isle-portable.git
synced 2026-05-02 02:23:56 +00:00
Fix vehicle filter bug and refactor: wrong index space in GetCharacterVehicleId
GetCharacterVehicleId used hardcoded g_characters[] indices but was called with g_actorInfoInit[] indices. Since g_actorInfoInit has an extra "infoman" entry at index 5, all characters after Laura were off-by-one — bikers got the wrong vehicle and sy (Shiney Doris) fell off the switch entirely, disabling filtering completely. Replace with data-driven lookup (actorInfoInit name → g_characters vehicleId), consolidate duplicate GetVehicleCategory into Catalog, remove dead characterIndex field, fix stale g_characters comments, remove temporary debug logging, and DRY local vehicle state computation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
aa9df3370b
commit
3396d42db8
@ -16,7 +16,7 @@ enum AnimCategory : uint8_t {
|
|||||||
e_otherAnim // no named character performers (ambient/prop-only)
|
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;
|
static const int8_t CORE_CHARACTER_COUNT = 5;
|
||||||
|
|
||||||
// Spectator mask with all core characters enabled
|
// Spectator mask with all core characters enabled
|
||||||
@ -32,9 +32,8 @@ struct CatalogEntry {
|
|||||||
uint16_t animIndex; // Index into LegoAnimationManager::m_anims[]
|
uint16_t animIndex; // Index into LegoAnimationManager::m_anims[]
|
||||||
AnimCategory category;
|
AnimCategory category;
|
||||||
uint8_t spectatorMask; // Which core actors can trigger (bit0=Pepper..bit4=Laura)
|
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
|
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 modelCount; // Number of models in animation
|
||||||
uint8_t vehicleMask; // Bitmask of g_vehicles[] indices required (bit0=bikebd..bit6=board)
|
uint8_t vehicleMask; // Bitmask of g_vehicles[] indices required (bit0=bikebd..bit6=board)
|
||||||
};
|
};
|
||||||
@ -50,10 +49,10 @@ class Catalog {
|
|||||||
std::vector<const CatalogEntry*> GetAnimationsAtLocation(int16_t p_location) const;
|
std::vector<const CatalogEntry*> GetAnimationsAtLocation(int16_t p_location) const;
|
||||||
|
|
||||||
// Check if a player can fill any role (spectator or participant) in this animation.
|
// 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;
|
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);
|
static bool CanParticipateChar(const CatalogEntry* p_entry, int8_t p_charIndex);
|
||||||
|
|
||||||
// Check if a set of character indices can collectively trigger this animation.
|
// 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.
|
// Determine the vehicle state for a character given their current ride vehicle ROI.
|
||||||
static VehicleState GetVehicleState(int8_t p_charIndex, class LegoROI* p_vehicleROI);
|
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.
|
// Returns -1 if no match.
|
||||||
static int8_t DisplayActorToCharacterIndex(uint8_t p_displayActorIndex);
|
static int8_t DisplayActorToCharacterIndex(uint8_t p_displayActorIndex);
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ struct ParticipantROI {
|
|||||||
LegoROI* vehicleROI; // Ride vehicle ROI (bike/board/moto), or nullptr
|
LegoROI* vehicleROI; // Ride vehicle ROI (bike/board/moto), or nullptr
|
||||||
MxMatrix savedTransform;
|
MxMatrix savedTransform;
|
||||||
std::string savedName;
|
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; }
|
bool IsSpectator() const { return charIndex < 0; }
|
||||||
};
|
};
|
||||||
|
|||||||
@ -13,7 +13,7 @@ enum class CoordinationState : uint8_t;
|
|||||||
|
|
||||||
struct SessionSlot {
|
struct SessionSlot {
|
||||||
uint32_t peerId; // 0 = unfilled
|
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; }
|
bool IsSpectator() const { return charIndex < 0; }
|
||||||
};
|
};
|
||||||
|
|||||||
@ -197,7 +197,6 @@ class NetworkManager : public MxCore {
|
|||||||
bool m_showNameBubbles;
|
bool m_showNameBubbles;
|
||||||
bool m_lastCameraEnabled;
|
bool m_lastCameraEnabled;
|
||||||
uint8_t m_lastVehicleState;
|
uint8_t m_lastVehicleState;
|
||||||
bool m_vehicleFilterLogPending; // TODO(vehicle-filter): Remove after verification
|
|
||||||
bool m_wasInRestrictedArea;
|
bool m_wasInRestrictedArea;
|
||||||
|
|
||||||
// NPC animation playback
|
// NPC animation playback
|
||||||
|
|||||||
@ -11,28 +11,28 @@
|
|||||||
|
|
||||||
using namespace Multiplayer::Animation;
|
using namespace Multiplayer::Animation;
|
||||||
|
|
||||||
// Static mapping of character index to g_vehicles[] index.
|
// Defined in legoanimationmanager.cpp — not exported in headers.
|
||||||
// Mirrors g_characters[].m_vehicleId for characters that own a vehicle.
|
extern LegoAnimationManager::Character g_characters[47];
|
||||||
static int8_t GetCharacterVehicleId(int8_t p_charIndex)
|
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) {
|
if (p_actorInfoIndex < 0 || p_actorInfoIndex >= (int8_t) SDL_min(sizeOfArray(g_actorInfoInit), (size_t) 64)) {
|
||||||
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;
|
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.
|
// Exact-match a model name against g_actorInfoInit[].m_name.
|
||||||
@ -88,7 +88,6 @@ void Catalog::Refresh(LegoAnimationManager* p_am)
|
|||||||
entry.animIndex = i;
|
entry.animIndex = i;
|
||||||
entry.spectatorMask = m_animsBase[i].m_unk0x0c;
|
entry.spectatorMask = m_animsBase[i].m_unk0x0c;
|
||||||
entry.location = m_animsBase[i].m_location;
|
entry.location = m_animsBase[i].m_location;
|
||||||
entry.characterIndex = m_animsBase[i].m_characterIndex;
|
|
||||||
entry.modelCount = m_animsBase[i].m_modelCount;
|
entry.modelCount = m_animsBase[i].m_modelCount;
|
||||||
|
|
||||||
// Compute performerMask by matching models against g_actorInfoInit[].m_name
|
// 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.
|
// with m_unk0x2c=1 that match a known vehicle name.
|
||||||
entry.vehicleMask = 0;
|
entry.vehicleMask = 0;
|
||||||
for (int k = 0; k < 3; k++) {
|
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]);
|
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)
|
int8_t Catalog::GetVehicleCategory(int8_t p_vehicleIdx)
|
||||||
static int8_t GetVehicleCategory(int8_t p_vehicleIdx)
|
|
||||||
{
|
{
|
||||||
if (p_vehicleIdx >= 0 && p_vehicleIdx <= 3) {
|
if (p_vehicleIdx >= 0 && p_vehicleIdx <= 3) {
|
||||||
return 0; // bike
|
return 0; // bike (bikebd, bikepg, bikerd, bikesy)
|
||||||
}
|
}
|
||||||
if (p_vehicleIdx >= 4 && p_vehicleIdx <= 5) {
|
if (p_vehicleIdx >= 4 && p_vehicleIdx <= 5) {
|
||||||
return 1; // motorcycle
|
return 1; // motorcycle (motoni, motola)
|
||||||
}
|
}
|
||||||
if (p_vehicleIdx == 6) {
|
if (p_vehicleIdx == 6) {
|
||||||
return 2; // skateboard
|
return 2; // skateboard (board)
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,27 +29,6 @@ using namespace Multiplayer::Animation;
|
|||||||
namespace AnimUtils = Extensions::Common::AnimUtils;
|
namespace AnimUtils = Extensions::Common::AnimUtils;
|
||||||
using Extensions::Common::CharacterCloner;
|
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)
|
static bool MatchesCharacter(const std::string& p_actorName, int8_t p_charIndex)
|
||||||
{
|
{
|
||||||
if (p_charIndex < 0 || p_charIndex >= (int8_t) sizeOfArray(g_actorInfoInit)) {
|
if (p_charIndex < 0 || p_charIndex >= (int8_t) sizeOfArray(g_actorInfoInit)) {
|
||||||
@ -200,7 +179,8 @@ void ScenePlayer::SetupROIs(const AnimInfo* p_animInfo)
|
|||||||
|
|
||||||
MxU32 perfVehicleIdx;
|
MxU32 perfVehicleIdx;
|
||||||
if (AnimationManager()->FindVehicle(m_participants[p].vehicleROI->GetName(), 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;
|
m_vehicleROI = m_participants[p].vehicleROI;
|
||||||
addAlias(lowered, m_vehicleROI);
|
addAlias(lowered, m_vehicleROI);
|
||||||
roi = m_vehicleROI;
|
roi = m_vehicleROI;
|
||||||
|
|||||||
@ -20,7 +20,6 @@
|
|||||||
#include "mxticklemanager.h"
|
#include "mxticklemanager.h"
|
||||||
#include "roi/legoroi.h"
|
#include "roi/legoroi.h"
|
||||||
|
|
||||||
#include <SDL3/SDL_log.h>
|
|
||||||
#include <SDL3/SDL_stdinc.h>
|
#include <SDL3/SDL_stdinc.h>
|
||||||
#include <SDL3/SDL_timer.h>
|
#include <SDL3/SDL_timer.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@ -68,8 +67,7 @@ NetworkManager::NetworkManager()
|
|||||||
m_inIsleWorld(false), m_registered(false), m_pendingToggleThirdPerson(false), m_pendingToggleNameBubbles(false),
|
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_pendingWalkAnim(-1), m_pendingIdleAnim(-1), m_pendingEmote(-1), m_pendingToggleAllowCustomize(false),
|
||||||
m_pendingAnimInterest(-1), m_pendingAnimCancel(false), m_localPendingAnimInterest(-1), m_showNameBubbles(true),
|
m_pendingAnimInterest(-1), m_pendingAnimCancel(false), m_localPendingAnimInterest(-1), m_showNameBubbles(true),
|
||||||
m_lastCameraEnabled(false), m_lastVehicleState(0), m_vehicleFilterLogPending(false),
|
m_lastCameraEnabled(false), m_lastVehicleState(0), m_wasInRestrictedArea(false), m_animStateDirty(false),
|
||||||
m_wasInRestrictedArea(false), m_animStateDirty(false),
|
|
||||||
m_animInterestDirty(false), m_lastAnimPushTime(0), m_connectionState(STATE_DISCONNECTED), m_wasRejected(false),
|
m_animInterestDirty(false), m_lastAnimPushTime(0), m_connectionState(STATE_DISCONNECTED), m_wasRejected(false),
|
||||||
m_reconnectAttempt(0), m_reconnectDelay(0), m_nextReconnectTime(0)
|
m_reconnectAttempt(0), m_reconnectDelay(0), m_nextReconnectTime(0)
|
||||||
{
|
{
|
||||||
@ -127,7 +125,6 @@ MxResult NetworkManager::Tickle()
|
|||||||
if (vehicleState != m_lastVehicleState) {
|
if (vehicleState != m_lastVehicleState) {
|
||||||
m_lastVehicleState = vehicleState;
|
m_lastVehicleState = vehicleState;
|
||||||
m_animStateDirty = true;
|
m_animStateDirty = true;
|
||||||
m_vehicleFilterLogPending = true;
|
|
||||||
|
|
||||||
// Cancel active session if the current animation is no longer eligible.
|
// 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.
|
// 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();
|
const float* localPos = userActor->GetROI()->GetWorldPosition();
|
||||||
float localX = localPos[0], localZ = localPos[2];
|
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)
|
// Build proximity character indices and vehicle state (for NPC anims — position-based, not location-based)
|
||||||
std::vector<int8_t> proximityCharIndices;
|
std::vector<int8_t> proximityCharIndices;
|
||||||
std::vector<uint8_t> proximityVehicleState;
|
std::vector<uint8_t> proximityVehicleState;
|
||||||
proximityCharIndices.push_back(localCharIndex);
|
proximityCharIndices.push_back(localCharIndex);
|
||||||
proximityVehicleState.push_back(Animation::Catalog::GetVehicleState(localCharIndex, cam->GetRideVehicleROI()));
|
proximityVehicleState.push_back(localVehicleState);
|
||||||
|
|
||||||
for (const auto& [peerId, player] : m_remotePlayers) {
|
for (const auto& [peerId, player] : m_remotePlayers) {
|
||||||
if (!player->IsSpawned() || !player->GetROI() || player->GetWorldId() != (int8_t) LegoOmni::e_act1) {
|
if (!player->IsSpawned() || !player->GetROI() || player->GetWorldId() != (int8_t) LegoOmni::e_act1) {
|
||||||
@ -2177,7 +2176,7 @@ void NetworkManager::PushAnimationState()
|
|||||||
std::vector<int8_t> locationCharIndices;
|
std::vector<int8_t> locationCharIndices;
|
||||||
std::vector<uint8_t> locationVehicleState;
|
std::vector<uint8_t> locationVehicleState;
|
||||||
locationCharIndices.push_back(localCharIndex);
|
locationCharIndices.push_back(localCharIndex);
|
||||||
locationVehicleState.push_back(Animation::Catalog::GetVehicleState(localCharIndex, cam->GetRideVehicleROI()));
|
locationVehicleState.push_back(localVehicleState);
|
||||||
|
|
||||||
for (const auto& [peerId, player] : m_remotePlayers) {
|
for (const auto& [peerId, player] : m_remotePlayers) {
|
||||||
if (!player->IsSpawned() || !player->GetROI() || player->GetWorldId() != (int8_t) LegoOmni::e_act1) {
|
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
|
// Build JSON
|
||||||
std::string json;
|
std::string json;
|
||||||
json.reserve(2048);
|
json.reserve(2048);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user