f58dbf7756
Adapt CypherCore from 11.2.5.63796 to 12.0.7.68453 using TrinityCore as byte-exact reference. - Fase 1: build gate 68453 (build_info + build_auth_key SQL, keys from TC) - Fase 2: Opcodes.cs remapped by name to 12.0.7 values - Fase 3: 32 DB2 structs + HotfixDatabase.cs SELECTs realigned; fixed ReputationManager/SpellInfo refs - Fase 4: UpdateFields ported (GameObject/Conversation/AreaTrigger/Player/ Unit/ActivePlayer + nested VisibleItem/QuestLog/BitVectors/CraftingOrderItem/ SkillInfo) - Critical protocol fixes: CreateObject header +3 bits (Room/Decor/MeshObject); EntityFragment enum renumbered to 12.0.7 (was 11.2.5 values, breaking every object packet) - New-entity foundation: TypeId/TypeMask/HighGuid.MeshObject, fragment tags - MeshObject entity scaffold (Midnight features are wire-format only) Builds clean (dotnet build -c Release, 0 errors). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
202 lines
7.6 KiB
C#
202 lines
7.6 KiB
C#
// Copyright (c) CypherCore <http://github.com/CypherCore> All rights reserved.
|
|
// Licensed under the GNU GENERAL PUBLIC LICENSE. See LICENSE file in the project root for full license information.
|
|
|
|
using System;
|
|
using System.Linq;
|
|
|
|
namespace Game.Entities
|
|
{
|
|
public enum EntityFragment
|
|
{
|
|
// 12.0.7 (build 68453): fragment IDs renumbered vs 11.2.5; these are written into every object create/update packet.
|
|
FEntityPosition = 1,
|
|
CGObject = 2, // UPDATEABLE, INDIRECT,
|
|
FTransportLink = 5,
|
|
FPlayerOwnershipLink = 13,
|
|
CActor = 15, // INDIRECT,
|
|
FVendor_C = 17, // UPDATEABLE,
|
|
FMirroredObject_C = 18,
|
|
FMeshObjectData_C = 19, // UPDATEABLE,
|
|
FHousingDecor_C = 20, // UPDATEABLE,
|
|
FHousingRoom_C = 21, // UPDATEABLE,
|
|
FHousingRoomComponentMesh_C = 22, // UPDATEABLE,
|
|
FHousingPlayerHouse_C = 23, // UPDATEABLE,
|
|
FJamHousingCornerstone_C = 27, // UPDATEABLE,
|
|
FHousingDecorActor_C = 28,
|
|
FNeighborhoodMirrorData_C = 30, // UPDATEABLE,
|
|
FMirroredPositionData_C = 31, // UPDATEABLE,
|
|
PlayerHouseInfoComponent_C = 32, // UPDATEABLE, INDIRECT,
|
|
FHousingStorage_C = 33, // UPDATEABLE,
|
|
FHousingFixture_C = 34, // UPDATEABLE,
|
|
PlayerInitiativeComponent_C = 37, // UPDATEABLE, INDIRECT,
|
|
Tag_Item = 200, // TAG,
|
|
Tag_Container = 201, // TAG,
|
|
Tag_AzeriteEmpoweredItem = 202, // TAG,
|
|
Tag_AzeriteItem = 203, // TAG,
|
|
Tag_Unit = 204, // TAG,
|
|
Tag_Player = 205, // TAG,
|
|
Tag_GameObject = 206, // TAG,
|
|
Tag_DynamicObject = 207, // TAG,
|
|
Tag_Corpse = 208, // TAG,
|
|
Tag_AreaTrigger = 209, // TAG,
|
|
Tag_SceneObject = 210, // TAG,
|
|
Tag_Conversation = 211, // TAG,
|
|
Tag_AIGroup = 212, // TAG,
|
|
Tag_Scenario = 213, // TAG,
|
|
Tag_LootObject = 214, // TAG,
|
|
Tag_ActivePlayer = 215, // TAG,
|
|
Tag_ActiveClient_S = 216, // TAG,
|
|
Tag_ActiveObject_C = 217, // TAG,
|
|
Tag_VisibleObject_C = 218, // TAG,
|
|
Tag_UnitVehicle = 219, // TAG,
|
|
Tag_HousingRoom = 220, // TAG,
|
|
Tag_MeshObject = 221, // TAG,
|
|
Tag_HouseExteriorPiece = 224, // TAG,
|
|
Tag_HouseExteriorRoot = 225, // TAG,
|
|
Tag_HousingDecorProxyGameObject = 226, // TAG,
|
|
End = 255,
|
|
}
|
|
|
|
public enum EntityFragmentSerializationType
|
|
{
|
|
Full = 0,
|
|
Partial = 1
|
|
}
|
|
|
|
class EntityDefinitionsConst
|
|
{
|
|
// common case optimization, make use of the fact that fragment arrays are sorted
|
|
public const byte CGObjectActiveMask = 0x1;
|
|
public const byte CGObjectChangedMask = 0x2;
|
|
public const byte CGObjectUpdateMask = CGObjectActiveMask | CGObjectChangedMask;
|
|
}
|
|
|
|
public class EntityFragmentsHolder
|
|
{
|
|
EntityFragment[] Ids =
|
|
[
|
|
EntityFragment.End, EntityFragment.End, EntityFragment.End, EntityFragment.End,
|
|
EntityFragment.End, EntityFragment.End, EntityFragment.End, EntityFragment.End
|
|
];
|
|
|
|
public byte Count;
|
|
public bool IdsChanged;
|
|
|
|
public EntityFragment[] UpdateableIds = [EntityFragment.End, EntityFragment.End];
|
|
public byte[] UpdateableMasks = new byte[2];
|
|
public byte UpdateableCount;
|
|
public byte ContentsChangedMask;
|
|
|
|
public void Add(EntityFragment fragment, bool update)
|
|
{
|
|
Cypher.Assert(Count < Ids.Length);
|
|
|
|
(int, bool) insertSorted(ref EntityFragment[] arr, ref byte count, EntityFragment f)
|
|
{
|
|
var whereIndex = Array.IndexOf(arr, f);
|
|
if (whereIndex != -1)
|
|
return (whereIndex, false);
|
|
|
|
whereIndex = Array.IndexOf(arr, EntityFragment.End);
|
|
|
|
arr.SetValue(f, whereIndex);
|
|
Array.Sort(arr);
|
|
++count;
|
|
return (whereIndex, true);
|
|
}
|
|
|
|
if (!insertSorted(ref Ids, ref Count, fragment).Item2)
|
|
return;
|
|
|
|
if (IsUpdateableFragment(fragment))
|
|
{
|
|
Cypher.Assert(UpdateableCount < UpdateableIds.Length);
|
|
|
|
var index = insertSorted(ref UpdateableIds, ref UpdateableCount, fragment).Item1;
|
|
byte maskLowPart = (byte)(ContentsChangedMask & ((1 << index) - 1));
|
|
byte maskHighPart = (byte)((ContentsChangedMask & ~((1 << index) - 1)) << (1 + (IsIndirectFragment(fragment) ? 1 : 0)));
|
|
ContentsChangedMask = (byte)(maskLowPart | maskHighPart);
|
|
for (byte i = 0, maskIndex = 0; i < UpdateableCount; ++i)
|
|
{
|
|
UpdateableMasks[i] = (byte)(1 << maskIndex++);
|
|
if (IsIndirectFragment(UpdateableIds[i]))
|
|
{
|
|
ContentsChangedMask |= UpdateableMasks[i]; // set the first bit to true to activate fragment
|
|
++maskIndex;
|
|
UpdateableMasks[i] <<= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (update)
|
|
IdsChanged = true;
|
|
}
|
|
|
|
public void Remove(EntityFragment fragment)
|
|
{
|
|
(int, bool) removeSorted(ref EntityFragment[] arr, ref byte count, EntityFragment f)
|
|
{
|
|
var where = Array.IndexOf(arr, f);
|
|
if (where != -1)
|
|
{
|
|
arr.SetValue(EntityFragment.End, where);
|
|
Array.Sort(arr);
|
|
--count;
|
|
return (where, true);
|
|
}
|
|
return (where, false);
|
|
}
|
|
|
|
if (!removeSorted(ref Ids, ref Count, fragment).Item2)
|
|
return;
|
|
|
|
if (IsUpdateableFragment(fragment))
|
|
{
|
|
var (index, removed) = removeSorted(ref UpdateableIds, ref UpdateableCount, fragment);
|
|
if (removed)
|
|
{
|
|
byte maskLowPart = (byte)(ContentsChangedMask & ((1 << index) - 1));
|
|
byte maskHighPart = (byte)((ContentsChangedMask & ~((1 << index) - 1)) >> (1 + (IsIndirectFragment(fragment) ? 1 : 0)));
|
|
ContentsChangedMask = (byte)(maskLowPart | maskHighPart);
|
|
for (byte i = 0, maskIndex = 0; i < UpdateableCount; ++i)
|
|
{
|
|
UpdateableMasks[i] = (byte)(1 << maskIndex++);
|
|
if (IsIndirectFragment(UpdateableIds[i]))
|
|
{
|
|
++maskIndex;
|
|
UpdateableMasks[i] <<= 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
IdsChanged = true;
|
|
}
|
|
|
|
public static bool IsUpdateableFragment(EntityFragment frag)
|
|
{
|
|
return frag == EntityFragment.CGObject || frag == EntityFragment.FVendor_C;
|
|
}
|
|
|
|
public static bool IsIndirectFragment(EntityFragment frag)
|
|
{
|
|
return frag == EntityFragment.CGObject || frag == EntityFragment.CActor || frag == EntityFragment.FVendor_C;
|
|
}
|
|
|
|
public EntityFragment[] GetIds() { return Ids[..Count]; }
|
|
public EntityFragment[] GetUpdateableIds() { return UpdateableIds[..UpdateableCount]; }
|
|
|
|
public byte GetUpdateMaskFor(EntityFragment fragment)
|
|
{
|
|
if (fragment == EntityFragment.CGObject) // common case optimization, make use of the fact that fragment arrays are sorted
|
|
return EntityDefinitionsConst.CGObjectChangedMask;
|
|
|
|
for (byte i = 1; i < UpdateableCount; ++i)
|
|
if (UpdateableIds[i] == fragment)
|
|
return UpdateableMasks[i];
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
}
|