Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement inserting and deleting replay events #4

Merged
merged 12 commits into from
Oct 15, 2023
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

This library uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.6.0

Replays can now be edited. This API is still a work in progress and currently has a couple problems which will be fixed later. See the remarks in the `ReplayEventsData` class for more information.

### Added

- Added `ReplayEventsData.InsertEvent` and `ReplayEventsData.RemoveEvent` methods.
- Added `ReplayEventsData.ChangeEntityType` method. This is a temporary method that will be removed in the future.

### Changed

- Spawn events now have a public setter for `EntityId`. This should never be used and will be removed in the future.
- `ReplayEventsData.AddEvent` now overwrites the `EntityId` to be correct. This will change in the future in a way that doesn't let you pass an `EntityId` to this method at all.

## 0.5.0

### Changed
Expand Down
2 changes: 2 additions & 0 deletions src/DevilDaggersInfo.Core.Replay/Events/BoidSpawnEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public record BoidSpawnEvent(int EntityId, int SpawnerEntityId, BoidType BoidTyp
public Vector3 Velocity = Velocity;
public float Speed = Speed;

public int EntityId { get; set; } = EntityId;

public EntityType EntityType => BoidType switch
{
BoidType.Skull1 => EntityType.Skull1,
Expand Down
2 changes: 2 additions & 0 deletions src/DevilDaggersInfo.Core.Replay/Events/DaggerSpawnEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public record DaggerSpawnEvent(int EntityId, int A, Int16Vec3 Position, Int16Mat
public bool IsShot = IsShot;
public DaggerType DaggerType = DaggerType;

public int EntityId { get; set; } = EntityId;

public EntityType EntityType => DaggerType switch
{
DaggerType.Level1 => EntityType.Level1Dagger,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace DevilDaggersInfo.Core.Replay.Events.Interfaces;

public interface IEntitySpawnEvent : IEvent
{
int EntityId { get; }
int EntityId { get; internal set; }

EntityType EntityType { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public record LeviathanSpawnEvent(int EntityId, int A) : IEntitySpawnEvent
{
public int A = A;

public int EntityId { get; set; } = EntityId;

public EntityType EntityType => EntityType.Leviathan;

public void Write(BinaryWriter bw)
Expand Down
2 changes: 2 additions & 0 deletions src/DevilDaggersInfo.Core.Replay/Events/PedeSpawnEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public record PedeSpawnEvent(int EntityId, PedeType PedeType, int A, Vector3 Pos
public Vector3 B = B;
public Matrix3x3 Orientation = Orientation;

public int EntityId { get; set; } = EntityId;

public EntityType EntityType => PedeType switch
{
PedeType.Centipede => EntityType.Centipede,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public record SpiderEggSpawnEvent(int EntityId, int SpawnerEntityId, Vector3 Pos
public Vector3 Position = Position;
public Vector3 TargetPosition = TargetPosition;

public int EntityId { get; set; } = EntityId;

public EntityType EntityType => EntityType.SpiderEgg;

public void Write(BinaryWriter bw)
Expand Down
2 changes: 2 additions & 0 deletions src/DevilDaggersInfo.Core.Replay/Events/SpiderSpawnEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public record SpiderSpawnEvent(int EntityId, SpiderType SpiderType, int A, Vecto
public int A = A;
public Vector3 Position = Position;

public int EntityId { get; set; } = EntityId;

public EntityType EntityType => SpiderType switch
{
SpiderType.Spider1 => EntityType.Spider1,
Expand Down
2 changes: 2 additions & 0 deletions src/DevilDaggersInfo.Core.Replay/Events/SquidSpawnEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public record SquidSpawnEvent(int EntityId, SquidType SquidType, int A, Vector3
public Vector3 Direction = Direction;
public float RotationInRadians = RotationInRadians;

public int EntityId { get; set; } = EntityId;

public EntityType EntityType => SquidType switch
{
SquidType.Squid1 => EntityType.Squid1,
Expand Down
2 changes: 2 additions & 0 deletions src/DevilDaggersInfo.Core.Replay/Events/ThornSpawnEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ public record ThornSpawnEvent(int EntityId, int A, Vector3 Position, float Rotat
public Vector3 Position = Position;
public float RotationInRadians = RotationInRadians;

public int EntityId { get; set; } = EntityId;

public EntityType EntityType => EntityType.Thorn;

public void Write(BinaryWriter bw)
Expand Down
124 changes: 124 additions & 0 deletions src/DevilDaggersInfo.Core.Replay/ReplayEventsData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@

namespace DevilDaggersInfo.Core.Replay;

/// <summary>
/// Represents all the events in a replay.
/// <remarks>
/// IMPORTANT: This API is unfinished and will change in the future. Right now, the class generally lets you corrupt its state for the sake of performance and ease of use. This will be solved in the future.
/// <list type="bullet">
/// <item><description>When changing the internal type of a spawn event, be sure to also update the list of entity types using <see cref="ChangeEntityType(int, EntityType)"/>.</description></item>
/// <item><description>When adding or inserting a spawn event, the entity ID is re-calculated and overwritten. This will be changed in the future.</description></item>
/// <item><description>The event types currently let you change their ID, but this should only ever be done by the <see cref="ReplayEventsData"/> class itself. This will be removed in the future.</description></item>
/// </list>
/// </remarks>
/// </summary>
// TODO: Rewrite:
// We should rewrite the event classes to be mutable structs and exclude EntityId and EntityType from them, then add a new wrapper class containing EntityId, EntityType, and TEventStruct as properties instead. This fixes point 2 above.
// The wrapper class should have an internal set for EntityId. This fixes point 3 above.
// Point 1 above could be solved by referencing to all Spawn events (instances of the wrapper class) from the _events list, instead of keeping a list of EntityType enums.
public class ReplayEventsData
{
private readonly List<IEvent> _events = new();
Expand Down Expand Up @@ -31,6 +46,9 @@ public void Clear()

public void AddEvent(IEvent e)
{
if (e is IEntitySpawnEvent spawnEvent)
spawnEvent.EntityId = _entityTypes.Count;

_events.Add(e);
_eventOffsetsPerTick[^1]++;

Expand All @@ -39,4 +57,110 @@ public void AddEvent(IEvent e)
else if (e is IEntitySpawnEvent ese)
_entityTypes.Add(ese.EntityType);
}

public void RemoveEvent(int index)
{
if (index < 0 || index >= _events.Count)
throw new ArgumentOutOfRangeException(nameof(index));

IEvent e = _events[index];
if (e is IEntitySpawnEvent spawnEvent)
{
_entityTypes.RemoveAt(spawnEvent.EntityId);

// Decrement all entity IDs that are higher than the removed entity ID.
for (int i = 0; i < _events.Count; i++)
{
if (i == index)
continue;

if (_events[i] is IEntitySpawnEvent otherSpawnEvent && otherSpawnEvent.EntityId > spawnEvent.EntityId)
otherSpawnEvent.EntityId--;
}
}

_events.Remove(e);

int? containingTick = null;
bool isInputsEvent = e is InputsEvent;
for (int i = 0; i < _eventOffsetsPerTick.Count; i++)
{
if (index >= _eventOffsetsPerTick[i])
continue; // Skip ticks that are before the event.

// Remove the tick offset when removing an inputs event.
if (!containingTick.HasValue && isInputsEvent)
{
_eventOffsetsPerTick.RemoveAt(i);
containingTick = i;
}

// For every tick that is after the event, decrement the offset by 1.
if (_eventOffsetsPerTick.Count > i && _eventOffsetsPerTick[i] > 0)
_eventOffsetsPerTick[i]--;

// If the tick offset is the same as the previous one, remove it.
if (i > 0 && _eventOffsetsPerTick.Count > i && _eventOffsetsPerTick[i] == _eventOffsetsPerTick[i - 1])
_eventOffsetsPerTick.RemoveAt(i);
}
}

public void InsertEvent(int index, IEvent e)
{
if (index < 0 || index > _events.Count)
throw new ArgumentOutOfRangeException(nameof(index));

if (e is IEntitySpawnEvent spawnEvent)
{
// Increment all entity IDs that are higher than the added entity ID.
int entityId = 1; // Skip 0 as it is always reserved.
for (int i = 0; i < _events.Count; i++)
{
if (i == index)
{
spawnEvent.EntityId = entityId;
_events.Insert(index, e);
_entityTypes.Insert(entityId, spawnEvent.EntityType);
}
else if (_events[i] is IEntitySpawnEvent otherSpawnEvent)
{
if (i >= index)
otherSpawnEvent.EntityId++;
else
entityId++;
}
}
}
else
{
_events.Insert(index, e);
}

int? containingTick = null;
for (int i = 0; i < _eventOffsetsPerTick.Count; i++)
{
if (index >= _eventOffsetsPerTick[i])
continue; // Skip ticks that are before the event.

// The first tick that does not lie before the event is the tick that contains the event.
// Add new tick if needed. This always means an input event was added.
if (!containingTick.HasValue && e is InputsEvent)
{
int previousOffset = i > 0 ? _eventOffsetsPerTick[i - 1] : 0;
_eventOffsetsPerTick.Insert(i, previousOffset);
containingTick = i;
}

// For every tick that is after the event, increment the offset by 1.
_eventOffsetsPerTick[i]++;
}
}

public void ChangeEntityType(int entityId, EntityType entityType)
{
if (entityId < 0 || entityId >= _entityTypes.Count)
throw new ArgumentOutOfRangeException(nameof(entityId));

_entityTypes[entityId] = entityType;
}
}
2 changes: 1 addition & 1 deletion src/DevilDaggersInfo.Core/DevilDaggersInfo.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<Copyright>Copyright © Noah Stolk</Copyright>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/NoahStolk/ddinfo-core</RepositoryUrl>
<Version>0.5.0</Version>
<Version>0.6.0</Version>
</PropertyGroup>

<PropertyGroup Label="Build properties">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public void ParseAndCompileEvents()
}

[TestMethod]
public void ParseAndEditAndCompileEvents()
public void EditEventData()
{
string replayFilePath = Path.Combine("Resources", "SkullTest.ddreplay");
byte[] replayBuffer = File.ReadAllBytes(replayFilePath);
Expand Down
Loading
Loading