fix: give the destroyable and buff components their real component types

eReplicaComponentType had the destroyable component's registry type (7) named
BUFF, the real buff component (98) as BUFF_REAL, and a made-up DESTROYABLE =
1000 that DestroyableComponent was stored under. Now DESTROYABLE = 7 and
BUFF = 98, as in ComponentsRegistry and the client.

Undone with it:
- Destructible stats came from whichever of the "buff" (7), quick build and
  collectible registry ids was set, so the few objects without a type 7 entry
  read a DestructibleComponent row with an unrelated id (the NJ dragon relics
  16482-16485 via their collectible id, 125 quick build LOTs when placed with
  is_smashable). The type 7 entry is used now; objects without one keep the
  defaults (is_smashable objects: 1 health, smashable, factions -1 and 6;
  collectibles: an empty destroyable). The client does the same
  (LWODestroyableComponent::AllocateComponents / DoObjectComponentLoad).
- DestroyableComponent::Reinitialize, an unused copy of that pick order.
- WriteComponents' destroyableSerialized flags: the components are written from
  a list in the client's order, and where the destroyable goes (its own place
  after the buff, right before a quick build that has no registry entry for it,
  or after the render component) is one function.
- The dashboard's registry 7 -> DESTROYABLE mapping; the destroyable type also
  has a name there now (1000 was outside magic_enum's range).

Component types are not stored or sent as enum numbers anywhere besides the
CDClient's own values, which now match. migrations/cdserver/4 is unrelated (it
restores LOT 12916's registry rows that migration 0 overwrote) and stays.

Verified: dGameTests ReplicaComponentOrderTests serialize players, enemies,
smashables, quick builds and collectibles with and without a registry entry,
NPCs, pets, vehicles, models and an entity with every listed component with
the new code and a frozen copy of the old WriteComponents, and expect the same
bits for construction and serialization (a deliberately wrong destroyable place
fails them). Full ctest passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-27 00:38:57 -05:00
parent 931277e76c
commit caebff4dca
10 changed files with 622 additions and 282 deletions

View File

@@ -199,7 +199,7 @@ namespace CDClientSchema {
inline const Table* ComponentTable(const Schema& schema, eReplicaComponentType type) {
using enum eReplicaComponentType;
static const std::map<eReplicaComponentType, std::string_view> DIFFERENT{
{ BUFF, "DestructibleComponent" }, // the enum's BUFF (7) is the destroyable component
{ DESTROYABLE, "DestructibleComponent" },
{ CONTROLLABLE_PHYSICS, "PhysicsComponent" }, { SIMPLE_PHYSICS, "PhysicsComponent" }, { PHANTOM_PHYSICS, "PhysicsComponent" },
{ RIGID_BODY_PHANTOM_PHYSICS, "PhysicsComponent" }, { HAVOK_VEHICLE_PHYSICS, "VehiclePhysics" },
{ QUICK_BUILD, "RebuildComponent" }, { MISSION_OFFER, "MissionNPCComponent" }, { ROCKET_LAUNCH, "RocketLaunchpadControlComponent" },

View File

@@ -131,16 +131,11 @@ namespace WorldScene {
eReplicaComponentType::SCRIPTED_ACTIVITY, eReplicaComponentType::DESTROYABLE
};
// ComponentsRegistry stores the destroyable component as 7 (the enum's BUFF; see eReplicaComponentType)
inline eReplicaComponentType FromRegistry(uint32_t componentType) {
return componentType == 7 ? eReplicaComponentType::DESTROYABLE : static_cast<eReplicaComponentType>(componentType);
}
// Index into KINDS of an object with these ComponentsRegistry component types, or KINDS.size() for "other"
inline size_t Classify(const std::vector<uint32_t>& registryComponents) {
size_t best = KINDS.size();
for (const auto component : registryComponents) {
const auto type = FromRegistry(component);
const auto type = static_cast<eReplicaComponentType>(component);
for (size_t i = 0; i < best; i++) {
if (KINDS[i] == type) { best = i; break; }
}