Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Utils/IdRemap.cs
2022-02-26 02:43:01 +09:00

136 lines
4.6 KiB
C#

#nullable enable
using Barotrauma.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
public sealed class IdRemap
{
public static readonly IdRemap DiscardId = new IdRemap(null, -1);
private int maxId;
private readonly List<Range<int>>? srcRanges;
private readonly int destOffset;
public IdRemap(XElement? parentElement, int offset)
{
destOffset = offset;
if (parentElement is { HasElements: true })
{
srcRanges = new List<Range<int>>();
foreach (var subElement in parentElement.Elements())
{
int id = subElement.GetAttributeInt("ID", -1);
if (id > 0) { InsertId(id); }
}
maxId = GetOffsetId(srcRanges.Any() ? srcRanges.Last().End : offset);
}
else
{
maxId = offset;
}
}
public void AssignMaxId(out ushort result)
{
maxId++;
result = (ushort)maxId;
}
private void InsertId(int id)
{
if (srcRanges is null) { throw new NullReferenceException("Called InsertId when srcRanges is null"); }
void tryMergeRangeWithNext(int indexA)
{
int indexB = indexA + 1;
if (indexA < 0 /* Index A out of bounds */
|| indexB >= srcRanges.Count /* Index B out of bounds */)
{
return;
}
Range<int> rangeA = srcRanges[indexA];
Range<int> rangeB = srcRanges[indexB];
if ((rangeA.End+1) >= rangeB.Start) //The end of range A is right before the start of range B, this should be one range
{
srcRanges[indexA] = new Range<int>(rangeA.Start, rangeB.End);
srcRanges.RemoveAt(indexB);
}
}
int insertIndex = srcRanges.Count;
for (int i = 0; i < srcRanges.Count; i++)
{
if (srcRanges[i].Contains(id)) //We already have a range that contains this ID, duplicates are invalid input!
{
throw new InvalidOperationException($"Duplicate ID: {id}");
}
if (srcRanges[i].Start > id) //ID is between srcRanges[i-1] and srcRanges[i], insert at i
{
insertIndex = i;
break;
}
}
srcRanges.Insert(insertIndex, new Range<int>(id, id)); //Insert new range consisting of solely the new ID
tryMergeRangeWithNext(insertIndex); //Try merging new range with the one that comes after it
tryMergeRangeWithNext(insertIndex - 1); //Try merging new range with the one that comes before it
}
public ushort GetOffsetId(XElement element)
{
return GetOffsetId(element.GetAttributeInt("ID", 0));
}
public ushort GetOffsetId(int id)
{
if (id <= 0) //Input cannot be remapped because it's negative
{
return 0;
}
if (destOffset < 0) //Remapper has been defined to discard all input
{
return 0;
}
if (srcRanges is null) //Remapper defines no source ranges so it just adds an offset
{
return (ushort)(id + destOffset);
}
int rangeSize(in Range<int> r)
=> r.End - r.Start + 1;
int currOffset = destOffset;
for (int i = 0; i < srcRanges.Count; i++)
{
if (srcRanges[i].Contains(id))
{
//The source range for this ID has been found!
//The return value is such that all IDs that
//are returned by this remapper are contiguous,
//even if they weren't originally
return (ushort)(id - srcRanges[i].Start + currOffset);
}
currOffset += rangeSize(srcRanges[i]);
}
return 0;
}
public static ushort DetermineNewOffset()
{
int largestEntityId = 0;
foreach (Entity e in Entity.GetEntities())
{
if (e.ID > Entity.ReservedIDStart || e is Submarine) { continue; }
largestEntityId = Math.Max(largestEntityId, e.ID);
}
return (ushort)(largestEntityId+1);
}
}
}