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)
|
||||
};
|
||||
|
||||
// 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<const CatalogEntry*> 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);
|
||||
|
||||
|
||||
@ -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; }
|
||||
};
|
||||
|
||||
@ -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; }
|
||||
};
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -20,7 +20,6 @@
|
||||
#include "mxticklemanager.h"
|
||||
#include "roi/legoroi.h"
|
||||
|
||||
#include <SDL3/SDL_log.h>
|
||||
#include <SDL3/SDL_stdinc.h>
|
||||
#include <SDL3/SDL_timer.h>
|
||||
#include <algorithm>
|
||||
@ -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<int8_t> proximityCharIndices;
|
||||
std::vector<uint8_t> 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<int8_t> locationCharIndices;
|
||||
std::vector<uint8_t> 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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user