|
| 1 | +using System; |
| 2 | +using System.Collections.Generic; |
| 3 | +using System.Runtime.InteropServices; |
| 4 | + |
| 5 | +namespace RainbowMage.OverlayPlugin.MemoryProcessors.Combatant |
| 6 | +{ |
| 7 | + interface ICombatantMemory65 : ICombatantMemory { } |
| 8 | + |
| 9 | + class CombatantMemory65 : CombatantMemory, ICombatantMemory65 |
| 10 | + { |
| 11 | + private const string charmapSignature = "488B5720B8000000E0483BD00F84????????488D0D"; |
| 12 | + |
| 13 | + public CombatantMemory65(TinyIoCContainer container) |
| 14 | + : base(container, charmapSignature, CombatantMemory.Size, EffectMemory.Size) |
| 15 | + { |
| 16 | + |
| 17 | + } |
| 18 | + |
| 19 | + public override Version GetVersion() |
| 20 | + { |
| 21 | + return new Version(6, 5); |
| 22 | + } |
| 23 | + |
| 24 | + // Returns a combatant if the combatant is a mob or a PC. |
| 25 | + protected override unsafe Combatant GetMobFromByteArray(byte[] source, uint mycharID) |
| 26 | + { |
| 27 | + fixed (byte* p = source) |
| 28 | + { |
| 29 | + CombatantMemory mem = *(CombatantMemory*)&p[0]; |
| 30 | + ObjectType type = (ObjectType)mem.Type; |
| 31 | + if (mem.ID == 0 || mem.ID == emptyID) |
| 32 | + return null; |
| 33 | + } |
| 34 | + return GetCombatantFromByteArray(source, mycharID, false); |
| 35 | + } |
| 36 | + |
| 37 | + // Will return any kind of combatant, even if not a mob. |
| 38 | + // This function always returns a combatant object, even if empty. |
| 39 | + protected override unsafe Combatant GetCombatantFromByteArray(byte[] source, uint mycharID, bool isPlayer, bool exceptEffects = false) |
| 40 | + { |
| 41 | + fixed (byte* p = source) |
| 42 | + { |
| 43 | + CombatantMemory mem = *(CombatantMemory*)&p[0]; |
| 44 | + |
| 45 | + if (isPlayer) |
| 46 | + { |
| 47 | + mycharID = mem.ID; |
| 48 | + } |
| 49 | + |
| 50 | + Combatant combatant = new Combatant() |
| 51 | + { |
| 52 | + Name = FFXIVMemory.GetStringFromBytes(mem.Name, CombatantMemory.NameBytes), |
| 53 | + Job = mem.Job, |
| 54 | + ID = mem.ID, |
| 55 | + OwnerID = mem.OwnerID == emptyID ? 0 : mem.OwnerID, |
| 56 | + Type = (ObjectType)mem.Type, |
| 57 | + MonsterType = (MonsterType)mem.MonsterType, |
| 58 | + Status = (ObjectStatus)mem.Status, |
| 59 | + ModelStatus = (ModelStatus)mem.ModelStatus, |
| 60 | + // Normalize all possible aggression statuses into the basic 4 ones. |
| 61 | + AggressionStatus = (AggressionStatus)(mem.AggressionStatus - (mem.AggressionStatus / 4) * 4), |
| 62 | + NPCTargetID = mem.NPCTargetID, |
| 63 | + RawEffectiveDistance = mem.EffectiveDistance, |
| 64 | + PosX = mem.PosX, |
| 65 | + // Y and Z are deliberately swapped to match FFXIV_ACT_Plugin's data model |
| 66 | + PosY = mem.PosZ, |
| 67 | + PosZ = mem.PosY, |
| 68 | + Heading = mem.Heading, |
| 69 | + Radius = mem.Radius, |
| 70 | + // In-memory there are separate values for PC's current target and NPC's current target |
| 71 | + TargetID = (ObjectType)mem.Type == ObjectType.PC ? mem.PCTargetID : mem.NPCTargetID, |
| 72 | + CurrentHP = mem.CurrentHP, |
| 73 | + MaxHP = mem.MaxHP, |
| 74 | + Effects = exceptEffects ? new List<EffectEntry>() : GetEffectEntries(mem.Effects, (ObjectType)mem.Type, mycharID), |
| 75 | + |
| 76 | + BNpcID = mem.BNpcID, |
| 77 | + CurrentMP = mem.CurrentMP, |
| 78 | + MaxMP = mem.MaxMP, |
| 79 | + CurrentGP = mem.CurrentGP, |
| 80 | + MaxGP = mem.MaxGP, |
| 81 | + CurrentCP = mem.CurrentCP, |
| 82 | + MaxCP = mem.MaxCP, |
| 83 | + Level = mem.Level, |
| 84 | + PCTargetID = mem.PCTargetID, |
| 85 | + |
| 86 | + BNpcNameID = mem.BNpcNameID, |
| 87 | + |
| 88 | + WorldID = mem.WorldID, |
| 89 | + CurrentWorldID = mem.CurrentWorldID, |
| 90 | + |
| 91 | + IsCasting1 = mem.IsCasting1, |
| 92 | + IsCasting2 = mem.IsCasting2, |
| 93 | + CastBuffID = mem.CastBuffID, |
| 94 | + CastTargetID = mem.CastTargetID, |
| 95 | + CastDurationCurrent = mem.CastDurationCurrent, |
| 96 | + CastDurationMax = mem.CastDurationMax, |
| 97 | + |
| 98 | + TransformationId = mem.TransformationId, |
| 99 | + WeaponId = mem.WeaponId |
| 100 | + }; |
| 101 | + combatant.IsTargetable = |
| 102 | + (combatant.ModelStatus == ModelStatus.Visible) |
| 103 | + && ((combatant.Status == ObjectStatus.NormalActorStatus) || (combatant.Status == ObjectStatus.NormalSubActorStatus)); |
| 104 | + if (combatant.Type != ObjectType.PC && combatant.Type != ObjectType.Monster) |
| 105 | + { |
| 106 | + // Other types have garbage memory for hp. |
| 107 | + combatant.CurrentHP = 0; |
| 108 | + combatant.MaxHP = 0; |
| 109 | + } |
| 110 | + return combatant; |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + [StructLayout(LayoutKind.Explicit)] |
| 115 | + private unsafe struct CombatantMemory |
| 116 | + { |
| 117 | + public static int Size => Marshal.SizeOf(typeof(CombatantMemory)); |
| 118 | + |
| 119 | + // 64 bytes per both FFXIV_ACT_Plugin and aers/FFXIVClientStructs |
| 120 | + public const int NameBytes = 64; |
| 121 | + |
| 122 | + public const int EffectCount = 60; |
| 123 | + public const int EffectBytes = EffectMemory.Size * EffectCount; |
| 124 | + |
| 125 | + [FieldOffset(0x30)] |
| 126 | + public fixed byte Name[NameBytes]; |
| 127 | + |
| 128 | + [FieldOffset(0x74)] |
| 129 | + public uint ID; |
| 130 | + |
| 131 | + [FieldOffset(0x80)] |
| 132 | + public uint BNpcID; |
| 133 | + |
| 134 | + [FieldOffset(0x84)] |
| 135 | + public uint OwnerID; |
| 136 | + |
| 137 | + [FieldOffset(0x8C)] |
| 138 | + public byte Type; |
| 139 | + |
| 140 | + [FieldOffset(0x92)] |
| 141 | + public byte EffectiveDistance; |
| 142 | + |
| 143 | + [FieldOffset(0x94)] |
| 144 | + public byte Status; |
| 145 | + |
| 146 | + [FieldOffset(0xB0)] |
| 147 | + public Single PosX; |
| 148 | + |
| 149 | + [FieldOffset(0xB4)] |
| 150 | + public Single PosY; |
| 151 | + |
| 152 | + [FieldOffset(0xB8)] |
| 153 | + public Single PosZ; |
| 154 | + |
| 155 | + [FieldOffset(0xC0)] |
| 156 | + public Single Heading; |
| 157 | + |
| 158 | + [FieldOffset(0xD0)] |
| 159 | + public Single Radius; |
| 160 | + |
| 161 | + [FieldOffset(0x114)] |
| 162 | + public int ModelStatus; |
| 163 | + |
| 164 | + [FieldOffset(0x1BC)] |
| 165 | + public int CurrentHP; |
| 166 | + |
| 167 | + [FieldOffset(0x1C0)] |
| 168 | + public int MaxHP; |
| 169 | + |
| 170 | + [FieldOffset(0x1C4)] |
| 171 | + public int CurrentMP; |
| 172 | + |
| 173 | + [FieldOffset(0x1C8)] |
| 174 | + public int MaxMP; |
| 175 | + |
| 176 | + [FieldOffset(0x1CC)] |
| 177 | + public ushort CurrentGP; |
| 178 | + |
| 179 | + [FieldOffset(0x1CE)] |
| 180 | + public ushort MaxGP; |
| 181 | + |
| 182 | + [FieldOffset(0x1D0)] |
| 183 | + public ushort CurrentCP; |
| 184 | + |
| 185 | + [FieldOffset(0x1D2)] |
| 186 | + public ushort MaxCP; |
| 187 | + |
| 188 | + [FieldOffset(0x1D4)] |
| 189 | + public short TransformationId; |
| 190 | + |
| 191 | + [FieldOffset(0x1DA)] |
| 192 | + public byte Job; |
| 193 | + |
| 194 | + [FieldOffset(0x1DB)] |
| 195 | + public byte Level; |
| 196 | + |
| 197 | + [FieldOffset(0xC30)] |
| 198 | + public byte WeaponId; |
| 199 | + |
| 200 | + [FieldOffset(0xD00)] |
| 201 | + public uint PCTargetID; |
| 202 | + |
| 203 | + // TODO: this is still incorrect as of 6.5, should we drop this field if we can't find it again? |
| 204 | + [FieldOffset(0x19C3)] |
| 205 | + public byte MonsterType; |
| 206 | + |
| 207 | + // TODO: this is still incorrect as of 6.5, should we drop this field if we can't find it again? |
| 208 | + [FieldOffset(0x19DF)] |
| 209 | + public byte AggressionStatus; |
| 210 | + |
| 211 | + [FieldOffset(0x1B58)] |
| 212 | + public uint NPCTargetID; |
| 213 | + |
| 214 | + [FieldOffset(0x1B98)] |
| 215 | + public uint BNpcNameID; |
| 216 | + |
| 217 | + [FieldOffset(0x1BB0)] |
| 218 | + public ushort CurrentWorldID; |
| 219 | + |
| 220 | + [FieldOffset(0x1BB2)] |
| 221 | + public ushort WorldID; |
| 222 | + |
| 223 | + [FieldOffset(0x1C18)] |
| 224 | + public fixed byte Effects[EffectBytes]; |
| 225 | + |
| 226 | + [FieldOffset(0x1F00)] |
| 227 | + public byte IsCasting1; |
| 228 | + |
| 229 | + [FieldOffset(0x1F02)] |
| 230 | + public byte IsCasting2; |
| 231 | + |
| 232 | + [FieldOffset(0x1F04)] |
| 233 | + public uint CastBuffID; |
| 234 | + |
| 235 | + [FieldOffset(0x1F10)] |
| 236 | + public uint CastTargetID; |
| 237 | + |
| 238 | + [FieldOffset(0x1F34)] |
| 239 | + public float CastDurationCurrent; |
| 240 | + |
| 241 | + [FieldOffset(0x1F38)] |
| 242 | + public float CastDurationMax; |
| 243 | + |
| 244 | + // Missing PartyType |
| 245 | + } |
| 246 | + } |
| 247 | +} |
0 commit comments