Skip to content

Commit

Permalink
Shoveable system improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
benvalkin committed Oct 25, 2024
1 parent 1f6db3e commit 5d92554
Show file tree
Hide file tree
Showing 6 changed files with 350 additions and 34 deletions.
290 changes: 290 additions & 0 deletions UncreatedWarfare/FOBs/Construction/BuildResponsibilityCollection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Uncreated.Warfare.Players;

namespace Uncreated.Warfare.FOBs.Construction;
/// <summary>
/// A thread-safe collection of ticks associated with players. Used to calculate player contribution to a collective task.
/// </returns>
public class TickResponsibilityCollection : IEnumerable<TickResponsibility>
{
private readonly List<TickResponsibility> _list;
private float _total;
private bool _anyDuplicateSessions;

/// <summary>
/// Total number of ticks in the collection.
/// </returns>
public float Ticks
{
get
{
lock (_list)
return _total;
}
}

public TickResponsibilityCollection() : this(4) { }
public TickResponsibilityCollection(int capacity)
{
_list = new List<TickResponsibility>(capacity);
}

public int Count
{
get
{
lock (_list)
return _list.Count;
}
}

public void RetrieveLock() => Monitor.Enter(_list);
public void ReturnLock() => Monitor.Exit(_list);

public float GetTicksNoLock() => _total;

/// <returns>The percentage of the work <paramref name="player"/> has done (from 0 to 1).</returns>
public float this[IPlayer player] => this[player.Steam64.m_SteamID];

/// <returns>The percentage of the work <paramref name="steam64"/> has done (from 0 to 1).</returns>
public float this[ulong steam64]
{
get
{
lock (_list)
{
for (int i = 0; i < _list.Count; ++i)
{
if (_list[i].Steam64 == steam64)
return _list[i].Ticks / _total;
}
}

return 0f;
}
}

/// <returns>The percentage of the work <paramref name="player"/> has done (from 0 to 1).</returns>
public float this[IPlayer player, ulong sessionId] => this[player.Steam64.m_SteamID, sessionId];

/// <returns>The percentage of the work <paramref name="steam64"/> has done (from 0 to 1).</returns>
public float this[ulong steam64, ulong sessionId]
{
get
{
lock (_list)
{
for (int i = 0; i < _list.Count; ++i)
{
TickResponsibility responsibility = _list[i];
if (responsibility.Steam64 == steam64 && responsibility.SessionId == sessionId)
return responsibility.Ticks / _total;
}
}

return 0f;
}
}

public float GetLastUpdatedTime(ulong steam64)
{
lock (_list)
{
float min = float.NaN;
for (int i = 0; i < _list.Count; ++i)
{
TickResponsibility responsibility = _list[i];
if (responsibility.Steam64 != steam64)
continue;

if (float.IsNaN(min) || min > responsibility.LastUpdated)
min = responsibility.LastUpdated;
}

return float.IsNaN(min) ? 0f : min;
}

return 0f;
}

/// <summary>
/// Adds a player to the list, or increment their tick count by <paramref name="ticks"/>.
/// </summary>
public void Increment(ulong player, float ticks, ulong sessionId)
{
lock (_list)
{
_total += ticks;
for (int i = 0; i < _list.Count; ++i)
{
TickResponsibility r = _list[i];
if (r.Steam64 != player)
continue;

if (r.SessionId != sessionId)
{
_anyDuplicateSessions = true;
continue;
}

_list[i] = new TickResponsibility(player, sessionId, r.Ticks + ticks);
return;
}

_list.Add(new TickResponsibility(player, sessionId, ticks));
}
}
/// <summary>
/// Adds a player to the list, or set their tick count by <paramref name="ticks"/>.
/// </summary>
public void Set(ulong player, float ticks, ulong sessionId)
{
lock (_list)
{
for (int i = 0; i < _list.Count; ++i)
{
TickResponsibility r = _list[i];
if (r.Steam64 != player)
continue;

if (r.SessionId != sessionId)
{
_anyDuplicateSessions = true;
continue;
}

_total += ticks - r.Ticks;
_list[i] = new TickResponsibility(player, sessionId, ticks);
return;
}

_total += ticks;
_list.Add(new TickResponsibility(player, sessionId, ticks));
}
}
public void Clear()
{
lock (_list)
{
_total = 0;
_list.Clear();
}
}
/// <summary>
/// Remove a player's contribution (effectively setting it to 0).
/// </returns>
public bool Remove(IPlayer player) => Remove(player.Steam64.m_SteamID);
/// <summary>
/// Remove a player's contribution (effectively setting it to 0).
/// </returns>
public bool Remove(ulong player)
{
bool any = false;
lock (_list)
{
for (int i = _list.Count - 1; i >= 0; --i)
{
if (_list[i].Steam64 != player)
continue;

_total -= _list[i].Ticks;
_list.RemoveAt(i);
any = true;
}
}

return any;
}
/// <summary>
/// Remove a player's contribution (effectively setting it to 0).
/// </returns>
public bool Remove(IPlayer player, ulong sessionId) => Remove(player.Steam64.m_SteamID, sessionId);
/// <summary>
/// Remove a player's contribution (effectively setting it to 0).
/// </returns>
public bool Remove(ulong player, ulong sessionId)
{
bool any = false;
lock (_list)
{
for (int i = _list.Count - 1; i >= 0; --i)
{
TickResponsibility responsibility = _list[i];
if (responsibility.Steam64 != player || responsibility.SessionId != sessionId)
continue;

_total -= responsibility.Ticks;
_list.RemoveAt(i);
any = true;
}
}

return any;
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public IEnumerator<TickResponsibility> GetEnumerator()
{
lock (_list)
return _list.GetEnumerator();
}
public IEnumerable<TickResponsibility> GetGroupedEnumerator()
{
lock (_list)
{
if (!_anyDuplicateSessions)
return _list;

TickResponsibility[] totals = new TickResponsibility[_list.Count];
int c = -1;
for (int i = 0; i < _list.Count; ++i)
{
TickResponsibility res = _list[i];

bool found = false;
for (int j = 0; j <= c; ++j)
{
ref TickResponsibility res2 = ref totals[i];
if (res.Steam64 != res2.Steam64)
continue;

res2 = new TickResponsibility(res.Steam64, 0, res2.Ticks + res.Ticks, Mathf.Max(res.LastUpdated, res2.LastUpdated));
found = true;
break;
}

if (!found)
totals[++c] = res;
}

// ReSharper disable NotDisposedResourceIsReturned
return c != totals.Length - 1 ? totals.Take(c + 1) : totals;

// ReSharper restore NotDisposedResourceIsReturned
}
}
}

public readonly struct TickResponsibility
{
public readonly ulong Steam64;
public readonly ulong SessionId;
public readonly float Ticks;
public readonly float LastUpdated;
public TickResponsibility(ulong steam64, ulong sessionId, float ticks)
{
Steam64 = steam64;
SessionId = sessionId;
Ticks = ticks;
LastUpdated = Time.realtimeSinceStartup;
}
public TickResponsibility(ulong steam64, ulong sessionId, float ticks, float lastUpdated)
{
Steam64 = steam64;
SessionId = sessionId;
Ticks = ticks;
LastUpdated = lastUpdated;
}
}
19 changes: 9 additions & 10 deletions UncreatedWarfare/FOBs/Construction/EmplacementInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,29 @@ public class EmplacementInfo
/// <summary>
/// The vehicle to be spawned on build.
/// </summary>
public IAssetLink<VehicleAsset> EmplacementVehicle { get; set; }
public IAssetLink<VehicleAsset> Vehicle { get; set; }

/// <summary>
/// The base barricade to be spawned under the vehicle.
/// Item used when resupplying with ammo.
/// </summary>
public IAssetLink<ItemPlaceableAsset> BaseBuildable { get; set; }
public IAssetLink<ItemAsset> AmmoItem { get; set; }

/// <summary>
/// Item used with /ammo refilling.
/// Number of <see cref="AmmoItem"/> items to spawn.
/// </summary>
public IAssetLink<ItemAsset> Ammo { get; set; }

public int AmmoCount { get; set; } = 1;
/// <summary>
/// Number of <see cref="Ammo"/> items to spawn.
/// The number of supplies substracted when resupplying this vehicle.
/// </summary>
public int AmmoCount { get; set; }
public int ResupplyCost { get; set; } = 1;

/// <summary>
/// If this emplacement should warn friendlies when they're in the fire zone.
/// </summary>
public bool ShouldWarnFriendlies { get; set; }
public bool ShouldWarnFriendlies { get; set; } = false;

/// <summary>
/// If this emplacement should warn enemies when they're in the fire zone and have the ability to sense incoming projectiles.
/// </summary>
public bool ShouldWarnEnemies { get; set; }
public bool ShouldWarnEnemies { get; set; } = false;
}
1 change: 1 addition & 0 deletions UncreatedWarfare/FOBs/Construction/IShovelable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
namespace Uncreated.Warfare.FOBs.Construction;
public interface IShovelable : IBuildableComponent
{
ShovelableInfo Info { get; }
TickResponsibilityCollection Builders { get; }
bool Shovel(WarfarePlayer shoveler, Vector3 point);
void Complete(WarfarePlayer shoveler);
Expand Down
11 changes: 9 additions & 2 deletions UncreatedWarfare/FOBs/Construction/PointsShoveable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,20 @@
namespace Uncreated.Warfare.FOBs.Construction;
internal abstract class PointsShoveable : IShovelable
{
public ShovelableInfo Info { get; }
public int HitsRemaining { get; private set; }
public bool IsCompleted => HitsRemaining <= 0;
public TickResponsibilityCollection Builders { get; }
public IBuildable Buildable { get; }
private readonly EffectAsset? _shovelEffect;
private readonly Guid _sessionId;

public PointsShoveable(int hitsRemaining, IBuildable buildable, IAssetLink<EffectAsset>? shovelEffect = null)
public PointsShoveable(ShovelableInfo info, IBuildable buildable, IAssetLink<EffectAsset>? shovelEffect = null)
{
Info = info;
Buildable = buildable;
_shovelEffect = shovelEffect?.GetAssetOrFail();
HitsRemaining = hitsRemaining;
HitsRemaining = info.RequiredHits;
Builders = new TickResponsibilityCollection();
_sessionId = Guid.NewGuid();
}
Expand Down Expand Up @@ -49,4 +51,9 @@ public bool Shovel(WarfarePlayer shoveler, Vector3 point)
});
return true;
}

public void Dispose()
{

}
}
Loading

0 comments on commit 5d92554

Please sign in to comment.