Skip to content

Commit

Permalink
Merge pull request #572 from xanunderscore/dd
Browse files Browse the repository at this point in the history
Add DD to WorldState, pomanders and magicite to ActionID
  • Loading branch information
awgil authored Jan 26, 2025
2 parents cde4c70 + 8c58918 commit 5e2f394
Show file tree
Hide file tree
Showing 7 changed files with 380 additions and 1 deletion.
21 changes: 21 additions & 0 deletions BossMod/ActionQueue/ActionDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,27 @@ private ActionDefinitions()
// bozja actions
for (var i = BozjaHolsterID.None + 1; i < BozjaHolsterID.Count; ++i)
RegisterBozja(i);

// pomanders
for (var i = PomanderID.Safety; i < PomanderID.Count; ++i)
{
var pid = new ActionID(ActionType.Pomander, (uint)i);
_definitions[pid] = new(pid)
{
InstantAnimLock = 2.1f,
AllowedTargets = ActionTargets.Self
};
}

for (var i = 1u; i <= 3; i++)
{
var mid = new ActionID(ActionType.Magicite, i);
_definitions[mid] = new(mid)
{
InstantAnimLock = 2.1f,
AllowedTargets = ActionTargets.Self
};
}
}

public void Dispose()
Expand Down
2 changes: 2 additions & 0 deletions BossMod/Data/ActionID.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public enum ActionType : byte
// below are custom additions, these aren't proper actions from game's point of view, but it makes sense for us to treat them as such
BozjaHolsterSlot0 = 0xE0, // id = BozjaHolsterID, use from holster to replace duty action 0
BozjaHolsterSlot1 = 0xE1, // id = BozjaHolsterID, use from holster to replace duty action 1
Pomander = 0xE2, // id = PomanderID
Magicite = 0xE3, // id = slot (1-3)
}

public enum Positional { Any, Flank, Rear, Front }
Expand Down
180 changes: 180 additions & 0 deletions BossMod/Data/DeepDungeonState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
using static FFXIVClientStructs.FFXIV.Client.Game.InstanceContent.InstanceContentDeepDungeon;

namespace BossMod;

public sealed class DeepDungeonState
{
public DungeonProgress Progress;
public byte DungeonId;
public RoomFlags[] MapData = new RoomFlags[25];
public PartyMember[] Party = new PartyMember[4];
public Item[] Items = new Item[16];
public Chest[] Chests = new Chest[16];
public byte[] Magicite = new byte[3];

public enum DungeonType : byte
{
None = 0,
POTD = 1,
HOH = 2,
EO = 3
}

public DungeonType Type => (DungeonType)DungeonId;

public record struct DungeonProgress(byte Floor, byte Tileset, byte WeaponLevel, byte ArmorLevel, byte SyncedGearLevel, byte HoardCount, byte ReturnProgress, byte PassageProgress)
{
public readonly bool IsBossFloor => Floor % 10 == 0;
}
public record struct PartyMember(ulong EntityId, byte Room);
public record struct Item(byte Count, byte Flags)
{
public readonly bool Usable => (Flags & (1 << 0)) != 0;
public readonly bool Active => (Flags & (1 << 1)) != 0;
}
public record struct Chest(byte Type, byte Room);

public Item GetItem(PomanderID pid) => GetSlotForPomander(pid) is var s && s >= 0 ? Items[s] : default;

public int GetSlotForPomander(PomanderID pid) => Service.LuminaRow<Lumina.Excel.Sheets.DeepDungeon>(DungeonId)!.Value.PomanderSlot.ToList().FindIndex(p => p.RowId == (uint)pid);
public PomanderID GetPomanderForSlot(int slot)
{
var slots = Service.LuminaRow<Lumina.Excel.Sheets.DeepDungeon>(DungeonId)!.Value.PomanderSlot;
return slot >= 0 && slot < slots.Count ? (PomanderID)slots[slot].RowId : PomanderID.None;
}

public bool ReturnActive => Progress.ReturnProgress >= 11;
public bool PassageActive => Progress.PassageProgress >= 11;
public byte Floor => Progress.Floor;

public IEnumerable<WorldState.Operation> CompareToInitial()
{
if (Progress != default || DungeonId != 0)
yield return new OpProgressChange(DungeonId, Progress);

if (MapData.Any(m => m > 0))
yield return new OpMapDataChange(MapData);

if (Party.Any(p => p != default))
yield return new OpPartyStateChange(Party);

if (Items.Any(i => i != default))
yield return new OpItemsChange(Items);

if (Chests.Any(c => c != default))
yield return new OpChestsChange(Chests);

if (Magicite.Any(c => c > 0))
yield return new OpMagiciteChange(Magicite);
}

public Event<OpProgressChange> ProgressChanged = new();
public sealed record class OpProgressChange(byte DungeonId, DungeonProgress Value) : WorldState.Operation
{
protected override void Exec(WorldState ws)
{
ws.DeepDungeon.DungeonId = DungeonId;
ws.DeepDungeon.Progress = Value;
ws.DeepDungeon.ProgressChanged.Fire(this);
}
public override void Write(ReplayRecorder.Output output)
{
output.EmitFourCC("DDPG"u8)
.Emit(DungeonId)
.Emit(Value.Floor)
.Emit(Value.Tileset)
.Emit(Value.WeaponLevel)
.Emit(Value.ArmorLevel)
.Emit(Value.SyncedGearLevel)
.Emit(Value.HoardCount)
.Emit(Value.ReturnProgress)
.Emit(Value.PassageProgress);
}
}

public Event<OpMapDataChange> MapDataChanged = new();
public sealed record class OpMapDataChange(RoomFlags[] Value) : WorldState.Operation
{
public readonly RoomFlags[] Value = Value;

protected override void Exec(WorldState ws)
{
ws.DeepDungeon.MapData = Value;
ws.DeepDungeon.MapDataChanged.Fire(this);
}
public override void Write(ReplayRecorder.Output output)
{
output.EmitFourCC("DDMP"u8).Emit(Array.ConvertAll(Value, r => (byte)r));
}
}

public Event<OpPartyStateChange> PartyStateChanged = new();
public sealed record class OpPartyStateChange(PartyMember[] Value) : WorldState.Operation
{
public readonly PartyMember[] Value = Value;

protected override void Exec(WorldState ws)
{
ws.DeepDungeon.Party = Value;
ws.DeepDungeon.PartyStateChanged.Fire(this);
}
public override void Write(ReplayRecorder.Output output)
{
output.EmitFourCC("DDPT"u8);
foreach (var member in Value)
output.EmitActor(member.EntityId).Emit(member.Room);
}
}

public Event<OpItemsChange> ItemsChanged = new();
public sealed record class OpItemsChange(Item[] Value) : WorldState.Operation
{
public readonly Item[] Value = Value;

protected override void Exec(WorldState ws)
{
ws.DeepDungeon.Items = Value;
ws.DeepDungeon.ItemsChanged.Fire(this);
}
public override void Write(ReplayRecorder.Output output)
{
output.EmitFourCC("DDIT"u8);
foreach (var item in Value)
output.Emit(item.Count).Emit(item.Flags, "X");
}
}

public Event<OpChestsChange> ChestsChanged = new();
public sealed record class OpChestsChange(Chest[] Value) : WorldState.Operation
{
public readonly Chest[] Value = Value;

protected override void Exec(WorldState ws)
{
ws.DeepDungeon.Chests = Value;
ws.DeepDungeon.ChestsChanged.Fire(this);
}
public override void Write(ReplayRecorder.Output output)
{
output.EmitFourCC("DDCT"u8);
foreach (var chest in Value)
output.Emit(chest.Type).Emit(chest.Room);
}
}

public Event<OpMagiciteChange> MagiciteChanged = new();
public sealed record class OpMagiciteChange(byte[] Value) : WorldState.Operation
{
public readonly byte[] Value = Value;

protected override void Exec(WorldState ws)
{
ws.DeepDungeon.Magicite = Value;
ws.DeepDungeon.MagiciteChanged.Fire(this);
}
public override void Write(ReplayRecorder.Output output)
{
output.EmitFourCC("DDMG"u8).Emit(Value);
}
}
}
48 changes: 48 additions & 0 deletions BossMod/Data/PomanderID.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
namespace BossMod;

public enum PomanderID : uint
{
None,

// Pomanders - PotD/HoH
Safety,
Sight,
Strength,
Steel,
Affluence,
Flight,
Alteration,
Purity,
Fortune,
Witching,
Serenity,
Rage, // palace only
Lust, // palace only
Intuition,
Raising,
Resolution, // palace only
Frailty, // HoH only
Concealment, // HoH only
Petrification, // HoH only

// Protomanders - EO
ProtoLethargy,
ProtoStorms,
ProtoDread,
ProtoSafety,
ProtoSight,
ProtoStrength,
ProtoSteel,
ProtoAffluence,
ProtoFlight,
ProtoAlteration,
ProtoPurity,
ProtoFortune,
ProtoWitching,
ProtoSerenity,
ProtoIntuition,
ProtoRaising,

Count
}

3 changes: 3 additions & 0 deletions BossMod/Data/WorldState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public sealed class WorldState
public readonly ActorState Actors = new();
public readonly PartyState Party;
public readonly ClientState Client = new();
public readonly DeepDungeonState DeepDungeon = new();
public readonly NetworkState Network = new();

public DateTime CurrentTime => Frame.Timestamp;
Expand Down Expand Up @@ -69,6 +70,8 @@ public IEnumerable<Operation> CompareToInitial()
yield return o;
foreach (var o in Network.CompareToInitial())
yield return o;
foreach (var o in DeepDungeon.CompareToInitial())
yield return o;
}

// implementation of operations
Expand Down
80 changes: 80 additions & 0 deletions BossMod/Framework/WorldStateGameSync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using FFXIVClientStructs.FFXIV.Client.Game.Event;
using FFXIVClientStructs.FFXIV.Client.Game.Fate;
using FFXIVClientStructs.FFXIV.Client.Game.Group;
using FFXIVClientStructs.FFXIV.Client.Game.InstanceContent;
Expand Down Expand Up @@ -182,6 +183,7 @@ public unsafe void Update(TimeSpan prevFramePerf)
UpdateActors();
UpdateParty();
UpdateClient();
UpdateDeepDungeon();
}

private unsafe void UpdateWaymarks()
Expand Down Expand Up @@ -663,6 +665,84 @@ private unsafe void UpdateClient()
_ws.Execute(new ClientState.OpFocusTargetChange(focusTargetId));
}

private unsafe void UpdateDeepDungeon()
{
var ddold = _ws.DeepDungeon;
var ddnew = GetDeepDungeonState();

if (ddold.DungeonId != ddnew.DungeonId || ddold.Progress != ddnew.Progress)
_ws.Execute(new DeepDungeonState.OpProgressChange(ddnew.DungeonId, ddnew.Progress));
if (!MemoryExtensions.SequenceEqual<InstanceContentDeepDungeon.RoomFlags>(ddold.MapData, ddnew.MapData))
_ws.Execute(new DeepDungeonState.OpMapDataChange(ddnew.MapData));
if (!MemoryExtensions.SequenceEqual<DeepDungeonState.PartyMember>(ddold.Party, ddnew.Party))
_ws.Execute(new DeepDungeonState.OpPartyStateChange(ddnew.Party));
if (!MemoryExtensions.SequenceEqual<DeepDungeonState.Item>(ddold.Items, ddnew.Items))
_ws.Execute(new DeepDungeonState.OpItemsChange(ddnew.Items));
if (!MemoryExtensions.SequenceEqual<DeepDungeonState.Chest>(ddold.Chests, ddnew.Chests))
_ws.Execute(new DeepDungeonState.OpChestsChange(ddnew.Chests));
if (!MemoryExtensions.SequenceEqual<byte>(ddold.Magicite, ddnew.Magicite))
_ws.Execute(new DeepDungeonState.OpMagiciteChange(ddnew.Magicite));
}

private unsafe DeepDungeonState GetDeepDungeonState()
{
var dd = EventFramework.Instance()->GetInstanceContentDeepDungeon();
if (dd == null)
return new();

var progress = new DeepDungeonState.DungeonProgress
{
Floor = dd->Floor,
WeaponLevel = dd->WeaponLevel,
ArmorLevel = dd->ArmorLevel,

SyncedGearLevel = dd->SyncedGearLevel,
HoardCount = dd->HoardCount,

ReturnProgress = dd->ReturnProgress,
PassageProgress = dd->PassageProgress,

Tileset = dd->ActiveLayoutIndex
};

var state = new DeepDungeonState
{
Progress = progress,
Magicite = dd->Magicite.ToArray(),
DungeonId = dd->DeepDungeonId
};

dd->MapData.CopyTo(state.MapData);

var ddParty = dd->Party;
for (var i = 0; i < 4; i++)
{
ref var pinfo = ref state.Party[i];
pinfo.EntityId = (uint)SanitizedObjectID(ddParty[i].EntityId);
pinfo.Room = SanitizeRoom(ddParty[i].RoomIndex);
}

var ddItem = dd->Items;
for (var i = 0; i < ddItem.Length; i++)
{
ref var pitem = ref state.Items[i];
pitem.Count = ddItem[i].Count;
pitem.Flags = ddItem[i].Flags;
}

var ddChest = dd->Chests;
for (var i = 0; i < ddChest.Length; i++)
{
ref var pchest = ref state.Chests[i];
pchest.Type = ddChest[i].ChestType;
pchest.Room = SanitizeRoom(ddChest[i].RoomIndex);
}

return state;
}

private byte SanitizeRoom(sbyte room) => room < 0 ? (byte)0 : (byte)room;

private ulong SanitizedObjectID(ulong raw) => raw != InvalidEntityId ? raw : 0;

private void DispatchActorEvents(ulong instanceID)
Expand Down
Loading

0 comments on commit 5e2f394

Please sign in to comment.