diff --git a/src/FortniteReplayReader/FortniteReplayBuilder.cs b/src/FortniteReplayReader/FortniteReplayBuilder.cs index 05854ac..387c6e8 100644 --- a/src/FortniteReplayReader/FortniteReplayBuilder.cs +++ b/src/FortniteReplayReader/FortniteReplayBuilder.cs @@ -176,7 +176,7 @@ public void UpdateTeamData() { TeamIndex = playerData.TeamIndex, PlayerIds = new List() { playerData.Id }, - PlayerNames = new List() { playerData.PlayerId }, + PlayerNames = new List() { playerData.PlayerName ?? playerData.PlayerId }, Placement = playerData.Placement, PartyOwnerId = playerData.IsPartyLeader ? playerData.Id : null, TeamKills = playerData.TeamKills @@ -188,7 +188,7 @@ public void UpdateTeamData() teamData.TeamKills ??= playerData.TeamKills; teamData.PlayerIds.Add(playerData.Id); - teamData.PlayerNames.Add(playerData.PlayerId); + teamData.PlayerNames.Add(playerData.PlayerName ?? playerData.PlayerId); if (playerData.IsPartyLeader) { teamData.PartyOwnerId = playerData.Id; @@ -253,7 +253,6 @@ public void UpdatePlayerState(uint channelIndex, FortPlayerState state) { playerData.DeathTime = ReplicatedWorldTimeSeconds; playerData.DeathTimeDouble = ReplicatedWorldTimeSecondsDouble; - } playerData.Cosmetics.Parts ??= state.Parts?.Name; diff --git a/src/FortniteReplayReader/FortniteReplayReader.cs b/src/FortniteReplayReader/FortniteReplayReader.cs index ac5ca03..945c2c8 100644 --- a/src/FortniteReplayReader/FortniteReplayReader.cs +++ b/src/FortniteReplayReader/FortniteReplayReader.cs @@ -205,7 +205,7 @@ public override void ReadEvent(FArchive archive) return; } - _logger?.LogInformation("Unknown event {group} ({metadata}) of size {sizeInBytes}", info.Group, info.Metadata, info.SizeInBytes); + _logger?.LogDebug("Unknown event {group} ({metadata}) of size {sizeInBytes}", info.Group, info.Metadata, info.SizeInBytes); if (IsDebugMode) { throw new UnknownEventException($"Unknown event {info.Group} ({info.Metadata}) of size {info.SizeInBytes}"); diff --git a/src/FortniteReplayReader/Models/KillFeedEntry.cs b/src/FortniteReplayReader/Models/KillFeedEntry.cs index 1c72a7f..06cdc15 100644 --- a/src/FortniteReplayReader/Models/KillFeedEntry.cs +++ b/src/FortniteReplayReader/Models/KillFeedEntry.cs @@ -6,11 +6,11 @@ namespace FortniteReplayReader.Models public class KillFeedEntry { public int? PlayerId { get; set; } - public string PlayerName { get; set; } + public string? PlayerName { get; set; } public bool PlayerIsBot { get; set; } public int? FinisherOrDowner { get; set; } - public string FinisherOrDownerName { get; set; } + public string? FinisherOrDownerName { get; set; } public bool FinisherOrDownerIsBot { get; set; } public float? ReplicatedWorldTimeSeconds { get; set; } diff --git a/src/FortniteReplayReader/Models/NetFieldExports/FortPlayerState.cs b/src/FortniteReplayReader/Models/NetFieldExports/FortPlayerState.cs index 4b35f66..fb9942c 100644 --- a/src/FortniteReplayReader/Models/NetFieldExports/FortPlayerState.cs +++ b/src/FortniteReplayReader/Models/NetFieldExports/FortPlayerState.cs @@ -31,9 +31,12 @@ public class FortPlayerState : INetFieldExportGroup [NetFieldExport("Score", RepLayoutCmdType.PropertyUInt32)] public uint? Score { get; set; } - + [NetFieldExport("PlayerID", RepLayoutCmdType.PropertyInt)] public int? PlayerID { get; set; } + + [NetFieldExport("PlayerId", RepLayoutCmdType.PropertyUInt32)] + public uint? PlayerId { get; set; } [NetFieldExport("Ping", RepLayoutCmdType.Ignore)] public byte? Ping { get; set; } @@ -52,6 +55,9 @@ public class FortPlayerState : INetFieldExportGroup [NetFieldExport("UniqueId", RepLayoutCmdType.PropertyNetId)] public string UniqueId { get; set; } + + [NetFieldExport("UniqueID", RepLayoutCmdType.PropertyNetId)] + public string UniqueID { get; set; } [NetFieldExport("PlayerNamePrivate", RepLayoutCmdType.Ignore)] public string PlayerNamePrivate { get; set; } @@ -71,8 +77,8 @@ public class FortPlayerState : INetFieldExportGroup [NetFieldExport("PartyOwnerUniqueId", RepLayoutCmdType.PropertyNetId)] public string PartyOwnerUniqueId { get; set; } - [NetFieldExport("WorldPlayerId", RepLayoutCmdType.PropertyInt)] - public int? WorldPlayerId { get; set; } + [NetFieldExport("WorldPlayerId", RepLayoutCmdType.PropertyInt16)] + public short? WorldPlayerId { get; set; } [NetFieldExport("HeroType", RepLayoutCmdType.Property)] public ItemDefinition HeroType { get; set; } @@ -200,6 +206,12 @@ public class FortPlayerState : INetFieldExportGroup [NetFieldExport("DeathTags", RepLayoutCmdType.Property)] public FGameplayTagContainer? DeathTags { get; set; } + [NetFieldExport("VictimTags", RepLayoutCmdType.Property)] + public FGameplayTagContainer? VictimTags { get; set; } + + [NetFieldExport("FinisherOrDownerTags", RepLayoutCmdType.Property)] + public FGameplayTagContainer? FinisherOrDownerTags { get; set; } + [NetFieldExport("bResurrectionChipAvailable", RepLayoutCmdType.PropertyBool)] public bool? bResurrectionChipAvailable { get; set; } @@ -238,5 +250,8 @@ public class FortPlayerState : INetFieldExportGroup [NetFieldExport("bIsAnAthenaGameParticipant", RepLayoutCmdType.PropertyBool)] public bool? bIsAnAthenaGameParticipant { get; set; } + + [NetFieldExport("bHidden", RepLayoutCmdType.Ignore)] + public bool? bHidden { get; set; } } } \ No newline at end of file diff --git a/src/FortniteReplayReader/Models/NetFieldExports/FortTeamPrivateInfo.cs b/src/FortniteReplayReader/Models/NetFieldExports/FortTeamPrivateInfo.cs index 0b01fbc..b998402 100644 --- a/src/FortniteReplayReader/Models/NetFieldExports/FortTeamPrivateInfo.cs +++ b/src/FortniteReplayReader/Models/NetFieldExports/FortTeamPrivateInfo.cs @@ -30,6 +30,9 @@ public class FortTeamPrivateInfo : INetFieldExportGroup [NetFieldExport("Value", RepLayoutCmdType.PropertyFloat)] public float? Value { get; set; } + [NetFieldExport("PlayerId", RepLayoutCmdType.PropertyNetId)] + public string PlayerId { get; set; } + [NetFieldExport("PlayerID", RepLayoutCmdType.PropertyNetId)] public string PlayerID { get; set; } diff --git a/src/FortniteReplayReader/Models/NetFieldExports/GameState.cs b/src/FortniteReplayReader/Models/NetFieldExports/GameState.cs index b22e0e1..3ccee52 100644 --- a/src/FortniteReplayReader/Models/NetFieldExports/GameState.cs +++ b/src/FortniteReplayReader/Models/NetFieldExports/GameState.cs @@ -111,6 +111,9 @@ public class GameState : INetFieldExportGroup [NetFieldExport("ReplicatedWorldTimeSecondsDouble", RepLayoutCmdType.PropertyDouble)] public double? ReplicatedWorldTimeSecondsDouble { get; set; } + [NetFieldExport("ReplicatedWorldRealTimeSecondsDouble", RepLayoutCmdType.PropertyDouble)] + public double? ReplicatedWorldRealTimeSecondsDouble { get; set; } + [NetFieldExport("MatchState", RepLayoutCmdType.Property)] public FName MatchState { get; set; } @@ -371,5 +374,21 @@ public class GameState : INetFieldExportGroup [NetFieldExport("Mappings", RepLayoutCmdType.DynamicArray)] public ItemDefinition[] Mappings { get; set; } + + [NetFieldExport("PlayersLoaded", RepLayoutCmdType.PropertyFloat)] + public float PlayersLoaded { get; set; } + + [NetFieldExport("bIsCustomMatch", RepLayoutCmdType.PropertyBool)] + public bool bIsCustomMatch { get; set; } + + [NetFieldExport("bCraftingEnabled", RepLayoutCmdType.PropertyBool)] + public bool bCraftingEnabled { get; set; } + + [NetFieldExport("MatchStartTime", RepLayoutCmdType.PropertyFloat)] + public float MatchStartTime { get; set; } + + [NetFieldExport("RealMatchStartTime", RepLayoutCmdType.PropertyDouble)] + public double RealMatchStartTime { get; set; } + } } \ No newline at end of file diff --git a/src/FortniteReplayReader/Models/NetFieldExports/PlayerPawn.cs b/src/FortniteReplayReader/Models/NetFieldExports/PlayerPawn.cs index 751716b..1194b2e 100644 --- a/src/FortniteReplayReader/Models/NetFieldExports/PlayerPawn.cs +++ b/src/FortniteReplayReader/Models/NetFieldExports/PlayerPawn.cs @@ -501,5 +501,8 @@ public class PlayerPawn : INetFieldExportGroup [NetFieldExport("bReplicatedIsInVortex", RepLayoutCmdType.PropertyBool)] public bool? bReplicatedIsInVortex { get; set; } + + [NetFieldExport("bIsTacticalSprinting", RepLayoutCmdType.PropertyBool)] + public bool? bIsTacticalSprinting { get; set; } } } \ No newline at end of file diff --git a/src/FortniteReplayReader/Models/NetFieldExports/SafeZoneIndicator.cs b/src/FortniteReplayReader/Models/NetFieldExports/SafeZoneIndicator.cs index d965524..3ab0f40 100644 --- a/src/FortniteReplayReader/Models/NetFieldExports/SafeZoneIndicator.cs +++ b/src/FortniteReplayReader/Models/NetFieldExports/SafeZoneIndicator.cs @@ -46,5 +46,23 @@ public class SafeZoneIndicator : INetFieldExportGroup [NetFieldExport("Radius", RepLayoutCmdType.PropertyFloat)] public float Radius { get; set; } + + [NetFieldExport("PreviousRadius", RepLayoutCmdType.PropertyFloat)] + public float PreviousRadius { get; set; } + + [NetFieldExport("CurrentPhase", RepLayoutCmdType.PropertyFloat)] + public float CurrentPhase { get; set; } + + [NetFieldExport("PreviousCenter", RepLayoutCmdType.PropertyVector100)] + public FVector PreviousCenter { get; set; } + + [NetFieldExport("Damage", RepLayoutCmdType.PropertyFloat)] + public float Damage { get; set; } + + [NetFieldExport("PhaseCount", RepLayoutCmdType.PropertyFloat)] + public float PhaseCount { get; set; } + + [NetFieldExport("TimeRemainingWhenPhasePaused", RepLayoutCmdType.PropertyFloat)] + public float TimeRemainingWhenPhasePaused { get; set; } } } \ No newline at end of file diff --git a/src/FortniteReplayReader/Models/PlayerData.cs b/src/FortniteReplayReader/Models/PlayerData.cs index 6bd01e1..db497ae 100644 --- a/src/FortniteReplayReader/Models/PlayerData.cs +++ b/src/FortniteReplayReader/Models/PlayerData.cs @@ -8,15 +8,15 @@ public class PlayerData { public PlayerData(FortPlayerState playerState) { - Id = playerState.PlayerID; - EpicId = playerState.UniqueId; + Id = playerState.PlayerId is null ? playerState.PlayerID : (int?) playerState.PlayerId; + EpicId = playerState.UniqueId ?? playerState.UniqueID; BotId = playerState.BotUniqueId; IsBot = playerState.bIsABot == true; PlayerNameCustomOverride = playerState.PlayerNameCustomOverride?.Text; IsGameSessionOwner = playerState.bIsGameSessionOwner; - PlayerNumber = playerState.WorldPlayerId; + PlayerNumber = playerState.WorldPlayerId is not null ? (int?) playerState.WorldPlayerId : null; StreamerModeName = playerState.StreamerModeName?.Text; - IsPartyLeader = playerState.PartyOwnerUniqueId == playerState.UniqueId; + IsPartyLeader = playerState.PartyOwnerUniqueId == playerState.UniqueId || playerState.PartyOwnerUniqueId == playerState.UniqueID; TeamIndex = playerState.TeamIndex; Level = playerState.Level; SeasonLevelUIDisplay = playerState.SeasonLevelUIDisplay; @@ -36,12 +36,12 @@ public PlayerData(FortPlayerState playerState) } public int? Id { get; set; } - public string PlayerId => (IsBot == true) ? BotId : EpicId ?? PlatformUniqueNetId; - public string EpicId { get; set; } - public string PlatformUniqueNetId { get; set; } - public string BotId { get; set; } + public string? PlayerId => (IsBot == true) ? BotId : EpicId ?? PlatformUniqueNetId; + public string? EpicId { get; set; } + public string? PlatformUniqueNetId { get; set; } + public string? BotId { get; set; } public bool IsBot { get; set; } - public string PlayerName { get; set; } + public string? PlayerName { get; set; } public string? PlayerNameCustomOverride { get; set; } public string? StreamerModeName { get; set; } public string Platform { get; set; } @@ -68,8 +68,8 @@ public PlayerData(FortPlayerState playerState) public uint? TeamKills { get; set; } public int? DeathCause { get; set; } public int? DeathCircumstance { get; set; } - public IEnumerable DeathTags { get; set; } - public FVector DeathLocation { get; set; } + public IEnumerable? DeathTags { get; set; } + public FVector? DeathLocation { get; set; } public float? DeathTime { get; set; } public double? DeathTimeDouble { get; set; } public Cosmetics Cosmetics { get; set; } @@ -82,11 +82,11 @@ public class Cosmetics { public int? CharacterGender { get; set; } public int? CharacterBodyType { get; set; } - public string Parts { get; set; } + public string? Parts { get; set; } public IEnumerable VariantRequiredCharacterParts { get; set; } - public string HeroType { get; set; } - public string BannerIconId { get; set; } - public string BannerColorId { get; set; } + public string? HeroType { get; set; } + public string? BannerIconId { get; set; } + public string? BannerColorId { get; set; } public IEnumerable ItemWraps { get; set; } public string SkyDiveContrail { get; set; } public string Glider { get; set; } diff --git a/src/FortniteReplayReader/Models/TeamData.cs b/src/FortniteReplayReader/Models/TeamData.cs index 72afb9e..2930a41 100644 --- a/src/FortniteReplayReader/Models/TeamData.cs +++ b/src/FortniteReplayReader/Models/TeamData.cs @@ -6,7 +6,7 @@ public class TeamData { public int? TeamIndex { get; set; } public IList PlayerIds { get; set; } - public IList PlayerNames { get; set; } + public IList PlayerNames { get; set; } public int? PartyOwnerId { get; set; } public int? Placement { get; set; } public uint? TeamKills { get; set; } diff --git a/src/Unreal.Core/KeyList.cs b/src/Unreal.Core/KeyList.cs index 93a487b..5fcaa55 100644 --- a/src/Unreal.Core/KeyList.cs +++ b/src/Unreal.Core/KeyList.cs @@ -7,14 +7,11 @@ namespace Unreal.Core { public class KeyList { - private readonly List _vals = new List(); - private readonly Dictionary _keys = new Dictionary(); + private readonly List _vals = new(); + private readonly Dictionary _keys = new(); public int Length => _vals.Count; - public int Count(Func func) - { - return _vals.Count(func); - } + public int Count(Func func) => _vals.Count(func); public void Add(K key, V val) { @@ -24,10 +21,7 @@ public void Add(K key, V val) } } - public bool TryGetIndex(K key, [NotNullWhen(returnValue: true)] out int index) - { - return _keys.TryGetValue(key, out index); - } + public bool TryGetIndex(K key, [NotNullWhen(returnValue: true)] out int index) => _keys.TryGetValue(key, out index); public bool TryGetValue(int keyId, [NotNullWhen(returnValue: true)] out V? val) { diff --git a/src/Unreal.Core/Models/Enums/EngineNetworkVersionHistory.cs b/src/Unreal.Core/Models/Enums/EngineNetworkVersionHistory.cs index 2d16a71..17f99e6 100644 --- a/src/Unreal.Core/Models/Enums/EngineNetworkVersionHistory.cs +++ b/src/Unreal.Core/Models/Enums/EngineNetworkVersionHistory.cs @@ -35,7 +35,8 @@ public enum EngineNetworkVersionHistory HISTORY_RUNTIME_FEATURES_COMPATIBILITY = 28, // Bump version to add network runtime feature compatibility test to handshake (hello/upgrade) control messages HISTORY_SOFTOBJECTPTR_NETGUIDS = 29, // Bump version to support replicating SoftObjectPtrs by NetGuid instead of raw strings. HISTORY_SUBOBJECT_DESTROY_FLAG = 30, // Bump version to support subobject destruction message flags - HISTORY_GAMESTATE_REPLCIATED_TIME_AS_DOUBLE = 31, // Bump version to support AGameStateBase::ReplicatedWorldTimeSeconds as double instead of float. + HISTORY_GAMESTATE_REPLCIATED_TIME_AS_DOUBLE = 31, // Bump version to support AGameStateBase::ReplicatedWorldTimeSeconds as double instead of float. + HISTORY_CUSTOMVERION = 32, // Bump version to switch to using custom versions HISTORY_ENGINENETVERSION_PLUS_ONE, LATEST = HISTORY_ENGINENETVERSION_PLUS_ONE - 1, diff --git a/src/Unreal.Core/Models/Enums/NetworkVersionHistory.cs b/src/Unreal.Core/Models/Enums/NetworkVersionHistory.cs index 88fd3cd..a52aa95 100644 --- a/src/Unreal.Core/Models/Enums/NetworkVersionHistory.cs +++ b/src/Unreal.Core/Models/Enums/NetworkVersionHistory.cs @@ -21,9 +21,9 @@ public enum NetworkVersionHistory HISTORY_CHARACTER_MOVEMENT_NOINTERP = 14, // No longer recording interpolated movement samples HISTORY_GUID_NAMETABLE = 15, // Added a string table for exported guids HISTORY_GUIDCACHE_CHECKSUMS = 16, // Removing guid export checksums from saved data, they are ignored during playback - HISTORY_SAVE_PACKAGE_VERSION_UE = 17, // Save engine and licensee package version as well, in case serialization functions need them for compatibility - HISTORY_RECORDING_METADATA = 18, // Adding additional record-time information to the header - HISTORY_USE_CUSTOM_VERSION = 19, // Serializing replay and network versions as custom verions going forward + HISTORY_SAVE_PACKAGE_VERSION_UE = 17, // Save engine and licensee package version as well, in case serialization functions need them for compatibility + HISTORY_RECORDING_METADATA = 18, // Adding additional record-time information to the header + HISTORY_USE_CUSTOM_VERSION = 19, // Serializing replay and network versions as custom verions going forward HISTORY_NETWORKVERSION_PLUS_ONE, LATEST = HISTORY_NETWORKVERSION_PLUS_ONE - 1, diff --git a/src/Unreal.Core/Models/Enums/ReplayVersionHistory.cs b/src/Unreal.Core/Models/Enums/ReplayVersionHistory.cs index 3107a92..11918ba 100644 --- a/src/Unreal.Core/Models/Enums/ReplayVersionHistory.cs +++ b/src/Unreal.Core/Models/Enums/ReplayVersionHistory.cs @@ -1,7 +1,8 @@ namespace Unreal.Core.Models { /// - /// see https://github.com/EpicGames/UnrealEngine/blob/996ef9a9f4ad5a899abf70fb292d2914a46d0876/Engine/Source/Runtime/NetworkReplayStreaming/LocalFileNetworkReplayStreaming/Public/LocalFileNetworkReplayStreaming.h#L34 + /// see https://github.com/EpicGames/UnrealEngine/blob/70bc980c6361d9a7d23f6d23ffe322a2d6ef16fb/Engine/Source/Runtime/NetworkReplayStreaming/LocalFileNetworkReplayStreaming/Private/LocalFileNetworkReplayStreaming.cpp#L45 + /// see https://github.com/EpicGames/UnrealEngine/blob/407acc04a93f09ecb42c07c98b74fd00cc967100/Engine/Source/Runtime/NetworkReplayStreaming/LocalFileNetworkReplayStreaming/Public/LocalFileNetworkReplayStreaming.h#LL21C24-L21C24 /// [System.Flags] public enum ReplayVersionHistory : uint diff --git a/src/Unreal.Core/ReplayReader.cs b/src/Unreal.Core/ReplayReader.cs index 8483fdc..7bc1c36 100644 --- a/src/Unreal.Core/ReplayReader.cs +++ b/src/Unreal.Core/ReplayReader.cs @@ -318,7 +318,7 @@ public virtual void ReadReplayChunks(FArchive archive) var chunkSize = archive.ReadInt32(); var offset = archive.Position; - if (chunkType == ReplayChunkType.ReplayData && _parseMode.HasFlag(ParseMode.Minimal)) + if (chunkType == ReplayChunkType.ReplayData && _parseMode > ParseMode.EventsOnly) { ReadReplayData(archive, chunkSize); }