From c57ef8ab6f3312caf6c2ed24b48694f5c515e121 Mon Sep 17 00:00:00 2001 From: Ed Kolis Date: Sun, 28 Jan 2024 19:03:49 -0500 Subject: [PATCH] Refactor 2024 01 28 (#279) * Fix crash on game start * Make ObjectLocation a record type * Fix a few compile warnings * Convert Vector2 to generic math * Update nuget packages --- FrEee.Tests/FrEee.Tests.csproj | 6 +- FrEee.Tests/Utility/VectorTest.cs | 14 +- FrEee.WinForms/Controls/BattleView.cs | 26 +-- FrEee.WinForms/Controls/GamePanel.cs | 13 +- FrEee.WinForms/FrEee.WinForms.csproj | 10 +- FrEee/Extensions/AbilityExtensions.cs | 10 +- FrEee/Extensions/CommonExtensions.cs | 17 +- FrEee/Extensions/ConversionExtensions.cs | 4 +- FrEee/FrEee.csproj | 11 +- FrEee/Modding/Templates/ComponentTemplate.cs | 2 +- FrEee/Modding/Templates/GalaxyTemplate.cs | 2 +- FrEee/Objects/Civilization/Empire.cs | 8 +- FrEee/Objects/Combat/Grid/Battle.cs | 44 ++-- FrEee/Objects/Combat/Grid/BattleEvent.cs | 6 +- .../Combat/Grid/CombatantAppearsEvent.cs | 2 +- .../Combat/Grid/CombatantDestroyedEvent.cs | 2 +- .../Combat/Grid/CombatantDisappearsEvent.cs | 2 +- .../Combat/Grid/CombatantLaunchedEvent.cs | 2 +- .../Combat/Grid/CombatantMovesEvent.cs | 2 +- .../Combat/Grid/CombatantsCollideEvent.cs | 2 +- FrEee/Objects/Combat/Grid/GroundBattle.cs | 4 +- FrEee/Objects/Combat/Grid/IBattleEvent.cs | 4 +- FrEee/Objects/Combat/Grid/SpaceBattle.cs | 10 +- FrEee/Objects/Combat/Grid/WeaponFiresEvent.cs | 2 +- FrEee/Objects/Space/ObjectLocation.cs | 43 +--- .../Technology/MountedComponentTemplate.cs | 4 +- FrEee/Objects/Vehicles/Design.cs | 5 + FrEee/Utility/HeatMap.cs | 26 +-- FrEee/Utility/IntVector2.cs | 193 ------------------ FrEee/Utility/Vector2.cs | 151 ++++++++++++-- FrEee/Utility/Vector2Utility.cs | 44 ++++ 31 files changed, 312 insertions(+), 359 deletions(-) delete mode 100644 FrEee/Utility/IntVector2.cs create mode 100644 FrEee/Utility/Vector2Utility.cs diff --git a/FrEee.Tests/FrEee.Tests.csproj b/FrEee.Tests/FrEee.Tests.csproj index fec84945..ab19b8c8 100644 --- a/FrEee.Tests/FrEee.Tests.csproj +++ b/FrEee.Tests/FrEee.Tests.csproj @@ -13,9 +13,9 @@ - - - + + + diff --git a/FrEee.Tests/Utility/VectorTest.cs b/FrEee.Tests/Utility/VectorTest.cs index 8d302bf8..2d869579 100644 --- a/FrEee.Tests/Utility/VectorTest.cs +++ b/FrEee.Tests/Utility/VectorTest.cs @@ -6,22 +6,22 @@ namespace FrEee.Tests.Utility; public class VectorTest { [Test] - public void IntVector2AddLinearGradientEightWay() + public void AddLinearGradientEightWay() { var map = new HeatMap(); - map.AddLinearGradientEightWay(new IntVector2(0, 0), 10, 10, -1); - map.AddLinearGradientEightWay(new IntVector2(1, 1), 2, 2, -1); + map.AddLinearGradientEightWay(new Vector2(0, 0), 10, 10, -1); + map.AddLinearGradientEightWay(new Vector2(1, 1), 2, 2, -1); Assert.AreEqual(11, map[0, 0]); Assert.AreEqual(11, map[1, 1]); Assert.AreEqual(0, map[99, 99]); } [Test] - public void IntVector2InterpolationEightWay() + public void InterpolationEightWay() { - var v1 = new IntVector2(-1, 3); - var v2 = new IntVector2(6, 4); - var interp = IntVector2.InterpolateEightWay(v1, v2, 3); + var v1 = new Vector2(-1, 3); + var v2 = new Vector2(6, 4); + var interp = Vector2.InterpolateEightWay(v1, v2, 3); var trip = v2 - v1; var traveled = interp - v1; var togo = v2 - interp; diff --git a/FrEee.WinForms/Controls/BattleView.cs b/FrEee.WinForms/Controls/BattleView.cs index c32d5fdd..08297728 100644 --- a/FrEee.WinForms/Controls/BattleView.cs +++ b/FrEee.WinForms/Controls/BattleView.cs @@ -51,12 +51,12 @@ public Battle Battle } } - public IntVector2 ClickLocation { get; private set; } + public Vector2 ClickLocation { get; private set; } /// /// The combat sector which is focused. /// - public IntVector2 FocusedLocation + public Vector2 FocusedLocation { get => focusedLocation; set @@ -138,8 +138,8 @@ public bool UseSquares private Battle battle; private List booms = new List(); private bool combatPhase = false; - private IntVector2 focusedLocation; - private SafeDictionary locations = new SafeDictionary(); + private Vector2 focusedLocation; + private SafeDictionary> locations = []; private List pewpews = new List(); /// @@ -201,7 +201,7 @@ protected override void OnPaint(PaintEventArgs pe) var drawx = drawPoint.X; var drawy = drawPoint.Y; - var pos = new IntVector2(x, y); + var pos = new Vector2(x, y); // draw image, owner flag, and name of largest space object (if any) var here = Battle.Combatants.Where(q => locations.Any(w => (w.Key == q || w.Key == Battle.StartCombatants[q.ID]) && w.Value == pos)); @@ -331,13 +331,13 @@ private void BattleView_SizeChanged(object sender, EventArgs e) Invalidate(); } - private IntVector2 GetClickPoint(int x, int y) + private Vector2 GetClickPoint(int x, int y) { if (AutoZoom) { var clickx = (x - SectorBorderSize) / (SectorDrawSize + SectorBorderSize) + Battle.UpperLeft[round].X; var clicky = (y - SectorBorderSize) / (SectorDrawSize + SectorBorderSize) + Battle.UpperLeft[round].Y; - return new IntVector2(clickx, clicky); + return new Vector2(clickx, clicky); } else { @@ -345,7 +345,7 @@ private IntVector2 GetClickPoint(int x, int y) FocusedLocation = (Battle.LowerRight[round] - Battle.UpperLeft[round]) / 2; var clickx = (x - Width / 2 - SectorBorderSize) / (SectorDrawSize + SectorBorderSize) + FocusedLocation.X; var clicky = (y - Height / 2 - SectorBorderSize) / (SectorDrawSize + SectorBorderSize) + FocusedLocation.Y; - return new IntVector2(clickx, clicky); + return new Vector2(clickx, clicky); } } @@ -463,28 +463,28 @@ private void UpdateData() private class Boom { - public Boom(IntVector2 pos, float size) + public Boom(Vector2 pos, float size) { Position = pos; Size = size; } - public IntVector2 Position { get; set; } + public Vector2 Position { get; set; } public float Size { get; set; } } private class Pewpew { - public Pewpew(IntVector2 start, IntVector2 end, bool isHit = true) + public Pewpew(Vector2 start, Vector2 end, bool isHit = true) { Start = start; End = end; IsHit = isHit; } - public IntVector2 End { get; set; } + public Vector2 End { get; set; } public bool IsHit { get; set; } - public IntVector2 Start { get; set; } + public Vector2 Start { get; set; } } private HashSet unarmedCombatants { get; } = new HashSet(); diff --git a/FrEee.WinForms/Controls/GamePanel.cs b/FrEee.WinForms/Controls/GamePanel.cs index 8b124a00..81841f8c 100644 --- a/FrEee.WinForms/Controls/GamePanel.cs +++ b/FrEee.WinForms/Controls/GamePanel.cs @@ -43,10 +43,17 @@ protected override void OnPaint(PaintEventArgs pe) // http://support.microsoft.com/kb/953934 protected override void OnSizeChanged(EventArgs e) { - this.BeginInvoke((MethodInvoker)delegate + try { - base.OnSizeChanged(e); - }); + this.BeginInvoke((MethodInvoker)delegate + { + base.OnSizeChanged(e); + }); + } + catch + { + // UI must not be set up yet + } } private void GamePanel_SizeChanged(object sender, EventArgs e) diff --git a/FrEee.WinForms/FrEee.WinForms.csproj b/FrEee.WinForms/FrEee.WinForms.csproj index 5bf79526..5bd035ce 100644 --- a/FrEee.WinForms/FrEee.WinForms.csproj +++ b/FrEee.WinForms/FrEee.WinForms.csproj @@ -8,12 +8,12 @@ - - - - + + + + - + diff --git a/FrEee/Extensions/AbilityExtensions.cs b/FrEee/Extensions/AbilityExtensions.cs index be1e3b0e..c80e3166 100644 --- a/FrEee/Extensions/AbilityExtensions.cs +++ b/FrEee/Extensions/AbilityExtensions.cs @@ -159,8 +159,8 @@ public static IEnumerable Ancestors(this IAbilityObject obj, Fun /// true if successful or unnecessary, otherwise false public static bool BurnSupplies(this Ability a) { - if (a.Container is Component) - return (a.Container as Component).BurnSupplies(); + if (a.Container is Component comp) + return comp.BurnSupplies(); else return true; // other ability containers don't use supplies } @@ -261,9 +261,9 @@ private static IEnumerable FindSharedAbilities(this IOwnableAbilityObje var rule = clause.AbilityRule; if (rule.CanTarget(obj.AbilityTarget)) { - if (rule.CanTarget(AbilityTargets.Sector) && obj is ILocated) + if (rule.CanTarget(AbilityTargets.Sector) && obj is ILocated locObj) { - var sector = ((ILocated)obj).Sector; + var sector = locObj.Sector; foreach (var emp in Galaxy.Current.Empires.Where(emp => emp != null)) { foreach (var abil in sector.EmpireAbilities(emp)) @@ -307,7 +307,7 @@ private static IEnumerable FindSharedAbilities(this IOwnableAbilityObje /// /// /// - public static string GetEmpireAbilityValue(this ICommonAbilityObject obj, Empire emp, string name, int index = 1, Func filter = null) + public static string? GetEmpireAbilityValue(this ICommonAbilityObject obj, Empire emp, string name, int index = 1, Func filter = null) { if (obj == null) return null; diff --git a/FrEee/Extensions/CommonExtensions.cs b/FrEee/Extensions/CommonExtensions.cs index 8e6d2fa4..8801d328 100644 --- a/FrEee/Extensions/CommonExtensions.cs +++ b/FrEee/Extensions/CommonExtensions.cs @@ -254,7 +254,7 @@ public static void DealWithMines(this ISpaceObject sobj) /// /// /// - public static T Default(this object value, T def = default, bool throwIfWrongType = false) + public static T? Default(this object value, T? def = default, bool throwIfWrongType = false) { if (throwIfWrongType && !(value is T)) throw new InvalidCastException($"Cannot convert {value} to type {typeof(T)}."); @@ -701,7 +701,7 @@ public static object Instantiate(this Type type, params object[] args) if (type.Name == "Battle") return typeof(SpaceBattle).Instantiate(); // HACK - old savegame compatibility if (type.GetConstructors().Where(c => c.GetParameters().Length == (args == null ? 0 : args.Length)).Any()) - return Activator.CreateInstance(type, args); + return Activator.CreateInstance(type, args) ?? throw new NullReferenceException($"Couldn't create instance of type {type}."); else return FormatterServices.GetSafeUninitializedObject(type); } @@ -903,11 +903,10 @@ public static int NormalizeSupplies(this IMobileSpaceObject sobj) /// /// /// - public static T Parse(this string s) + public static T Parse(this string s, IFormatProvider provider = null) + where T : IParsable { - var parser = typeof(T).GetMethod("Parse", BindingFlags.Static); - var expr = Expression.Call(parser); - return (T)expr.Method.Invoke(null, new object[] { s }); + return T.Parse(s, provider); } /// @@ -1054,10 +1053,14 @@ public static string ReadTo(this TextReader r, char c, StringBuilder log) public static string ReadToEndOfLine(this TextReader r, char c, StringBuilder log) { var sb = new StringBuilder(); - string data = ""; + string? data = ""; do { data = r.ReadLine(); + if (data is null) + { + throw new Exception($"Found end of text when looking for character '{c}'."); + } log?.Append(data); sb.Append(data); if (data.EndsWith(c.ToString())) diff --git a/FrEee/Extensions/ConversionExtensions.cs b/FrEee/Extensions/ConversionExtensions.cs index 315fa8c0..e8b022af 100644 --- a/FrEee/Extensions/ConversionExtensions.cs +++ b/FrEee/Extensions/ConversionExtensions.cs @@ -39,9 +39,9 @@ public static double AngleTo(this PointF p, PointF target) /// /// /// - public static T CastTo(this object o, T defaultValue = default(T)) + public static T? CastTo(this object o, T? defaultValue = default) { - return (T)((o ?? defaultValue) ?? default(T)); + return (T?)((o ?? defaultValue) ?? default); } /// diff --git a/FrEee/FrEee.csproj b/FrEee/FrEee.csproj index 28cc8bf4..e5feea6a 100644 --- a/FrEee/FrEee.csproj +++ b/FrEee/FrEee.csproj @@ -8,12 +8,13 @@ - - + + - - - + + + + diff --git a/FrEee/Modding/Templates/ComponentTemplate.cs b/FrEee/Modding/Templates/ComponentTemplate.cs index 158aa386..5ef2f619 100644 --- a/FrEee/Modding/Templates/ComponentTemplate.cs +++ b/FrEee/Modding/Templates/ComponentTemplate.cs @@ -330,7 +330,7 @@ public override int GetHashCode() return ModID.GetHashCode(); } - public static bool operator ==(ComponentTemplate t1, ComponentTemplate t2) + public static bool operator ==(ComponentTemplate? t1, ComponentTemplate? t2) { if (t1 is null && t2 is null) return true; diff --git a/FrEee/Modding/Templates/GalaxyTemplate.cs b/FrEee/Modding/Templates/GalaxyTemplate.cs index ea682f18..cd4ff5ab 100644 --- a/FrEee/Modding/Templates/GalaxyTemplate.cs +++ b/FrEee/Modding/Templates/GalaxyTemplate.cs @@ -131,7 +131,7 @@ public Galaxy Instantiate(Status status, double desiredProgress, PRNG dice) sys.Name = unusedNames.PickRandom(dice); unusedNames.Remove(sys.Name); NameStellarObjects(sys); - gal.StarSystemLocations.Add(new ObjectLocation { Location = p.Value, Item = sys }); + gal.StarSystemLocations.Add(new(sys, p.Value)); if (status != null) status.Progress += progressPerStarSystem; } diff --git a/FrEee/Objects/Civilization/Empire.cs b/FrEee/Objects/Civilization/Empire.cs index 86bf4f80..3fe851d1 100644 --- a/FrEee/Objects/Civilization/Empire.cs +++ b/FrEee/Objects/Civilization/Empire.cs @@ -835,14 +835,14 @@ public Visibility CheckVisibility(Empire emp) return Visibility.Unknown; } - public int CompareTo(Empire other) + public int CompareTo(Empire? other) { - return Name.CompareTo(other.Name); + return Name.CompareTo(other?.Name); } - public int CompareTo(object obj) + public int CompareTo(object? obj) { - return Name.CompareTo(obj.ToString()); + return Name.CompareTo(obj?.ToString()); } /// diff --git a/FrEee/Objects/Combat/Grid/Battle.cs b/FrEee/Objects/Combat/Grid/Battle.cs index 35bab934..22c7db6d 100644 --- a/FrEee/Objects/Combat/Grid/Battle.cs +++ b/FrEee/Objects/Combat/Grid/Battle.cs @@ -128,7 +128,7 @@ public IEnumerable IconPaths public IList Log { get; private set; } - public IList LowerRight { get; private set; } = new List(); + public IList> LowerRight { get; private set; } = new List>(); public abstract int MaxRounds { get; } @@ -162,7 +162,7 @@ public IEnumerable PortraitPaths public double Timestamp { get; private set; } - public IList UpperLeft { get; private set; } = new List(); + public IList> UpperLeft { get; private set; } = new List>(); public int GetDiameter(int round) { @@ -175,7 +175,7 @@ public virtual void Initialize(IEnumerable combatants) StartCombatants = combatants.Select(c => new { ID = c.ID, Copy = c.CopyAndAssignNewID() }).ToDictionary(q => q.ID, q => q.Copy); } - public abstract void PlaceCombatants(SafeDictionary locations); + public abstract void PlaceCombatants(SafeDictionary> locations); /// /// Resolves the battle. @@ -189,7 +189,7 @@ public void Resolve() Current.Add(this); var reloads = new SafeDictionary(); - var locations = new SafeDictionary(); + var locations = new SafeDictionary>(); PlaceCombatants(locations); @@ -250,7 +250,7 @@ int GetCombatSpeedThisRound(ICombatant c) continue; } s.DistanceTraveled += Math.Min(GetCombatSpeedThisRound(c), locations[s].DistanceToEightWay(locations[s.Target])); - locations[s] = IntVector2.InterpolateEightWay(locations[s], locations[s.Target], GetCombatSpeedThisRound(c)); + locations[s] = Vector2.InterpolateEightWay(locations[s], locations[s.Target], GetCombatSpeedThisRound(c)); if (s.DistanceTraveled > s.WeaponInfo.MaxRange) { s.Hitpoints = 0; @@ -338,16 +338,16 @@ int GetCombatSpeedThisRound(ICombatant c) maxdmgrange = 0; } var targetPos = locations[bestTarget]; - var tiles = new HashSet(); + var tiles = new HashSet>(); for (var x = targetPos.X - maxdmgrange; x <= targetPos.X + maxdmgrange; x++) { - tiles.Add(new IntVector2(x, targetPos.Y - maxdmgrange)); - tiles.Add(new IntVector2(x, targetPos.Y + maxdmgrange)); + tiles.Add(new Vector2(x, targetPos.Y - maxdmgrange)); + tiles.Add(new Vector2(x, targetPos.Y + maxdmgrange)); } for (var y = targetPos.Y - maxdmgrange; y <= targetPos.Y + maxdmgrange; y++) { - tiles.Add(new IntVector2(targetPos.X - maxdmgrange, y)); - tiles.Add(new IntVector2(targetPos.X + maxdmgrange, y)); + tiles.Add(new Vector2(targetPos.X - maxdmgrange, y)); + tiles.Add(new Vector2(targetPos.X + maxdmgrange, y)); } if (c.FillsCombatTile) { @@ -360,7 +360,7 @@ int GetCombatSpeedThisRound(ICombatant c) if (tiles.Any()) { var closest = tiles.WithMin(t => t.DistanceToEightWay(locations[c])).First(); - locations[c] = IntVector2.InterpolateEightWay(locations[c], closest, GetCombatSpeedThisRound(c), vec => locations.Values.Contains(vec)); + locations[c] = Vector2.InterpolateEightWay(locations[c], closest, GetCombatSpeedThisRound(c), vec => locations.Values.Contains(vec)); var newdist = locations[c].DistanceToEightWay(locations[bestTarget]); if (DistancesToTargets.ContainsKey(c) && newdist >= DistancesToTargets[c] && combatSpeeds[c] <= combatSpeeds[bestTarget] && !c.Weapons.Any(w => w.Template.WeaponMaxRange >= newdist)) { @@ -430,7 +430,7 @@ int GetCombatSpeedThisRound(ICombatant c) var w = info.Item2.Weapons.ElementAt(ix); var wc = StartCombatants[info.Item2.ID].Weapons.ElementAt(ix); } - locations[info.Launchee] = new IntVector2(locations[info.Launcher]); + locations[info.Launchee] = new Vector2(locations[info.Launcher]); Events.Last().Add(new CombatantLaunchedEvent(this, info.Launcher, info.Launchee, locations[info.Launchee])); } } @@ -588,7 +588,7 @@ public override string ToString() return Name; } - private void CheckSeekerDetonation(Seeker s, SafeDictionary locations) + private void CheckSeekerDetonation(Seeker s, SafeDictionary> locations) { if (locations[s] == locations[s.Target]) { @@ -611,7 +611,7 @@ private void CheckSeekerDetonation(Seeker s, SafeDictionary reloads, SafeDictionary locations, SafeDictionary> multiplex) + private void TryFireWeapon(ICombatant c, Component w, SafeDictionary reloads, SafeDictionary> locations, SafeDictionary> multiplex) { // find suitable targets in range ICombatant target; @@ -676,7 +676,7 @@ private void TryFireWeapon(ICombatant c, Component w, SafeDictionary(locations[c]); Events.Last().Add(new CombatantLaunchedEvent(this, c, seeker, locations[seeker])); } else @@ -731,16 +731,16 @@ private void TryFireWeapon(ICombatant c, Component w, SafeDictionary positions) + private void UpdateBounds(int round, IEnumerable> positions) { while (UpperLeft.Count() <= round) - UpperLeft.Add(new IntVector2()); + UpperLeft.Add(new Vector2()); while (LowerRight.Count() <= round) - LowerRight.Add(new IntVector2()); - UpperLeft[round].X = positions.MinOrDefault(q => q.X); - LowerRight[round].X = positions.MaxOrDefault(q => q.X); - UpperLeft[round].Y = positions.MinOrDefault(q => q.Y); - LowerRight[round].Y = positions.MaxOrDefault(q => q.Y); + LowerRight.Add(new Vector2()); + UpperLeft[round] = UpperLeft[round] with { X = positions.MinOrDefault(q => q.X) }; + LowerRight[round] = LowerRight[round] with { X = positions.MaxOrDefault(q => q.X) }; + UpperLeft[round] = UpperLeft[round] with { Y = positions.MinOrDefault(q => q.Y) }; + LowerRight[round] = LowerRight[round] with { Y = positions.MaxOrDefault(q => q.Y) }; } public void Dispose() diff --git a/FrEee/Objects/Combat/Grid/BattleEvent.cs b/FrEee/Objects/Combat/Grid/BattleEvent.cs index 28a3c89d..6a38c1a2 100644 --- a/FrEee/Objects/Combat/Grid/BattleEvent.cs +++ b/FrEee/Objects/Combat/Grid/BattleEvent.cs @@ -7,7 +7,7 @@ namespace FrEee.Objects.Combat.Grid; public abstract class BattleEvent : IBattleEvent { - protected BattleEvent(IBattle battle, ICombatant combatant, IntVector2 startPosition, IntVector2 endPosition) + protected BattleEvent(IBattle battle, ICombatant combatant, Vector2 startPosition, Vector2 endPosition) { Battle = battle; Combatant = combatant; @@ -27,7 +27,7 @@ public ICombatant Combatant set => combatant = value.ReferViaGalaxy(); } - public IntVector2 EndPosition { get; set; } + public Vector2 EndPosition { get; set; } - public IntVector2 StartPosition { get; set; } + public Vector2 StartPosition { get; set; } } diff --git a/FrEee/Objects/Combat/Grid/CombatantAppearsEvent.cs b/FrEee/Objects/Combat/Grid/CombatantAppearsEvent.cs index 7e8661a2..523656f8 100644 --- a/FrEee/Objects/Combat/Grid/CombatantAppearsEvent.cs +++ b/FrEee/Objects/Combat/Grid/CombatantAppearsEvent.cs @@ -6,7 +6,7 @@ namespace FrEee.Objects.Combat.Grid; public class CombatantAppearsEvent : BattleEvent { - public CombatantAppearsEvent(IBattle battle, ICombatant combatant, IntVector2 position) + public CombatantAppearsEvent(IBattle battle, ICombatant combatant, Vector2 position) : base(battle, combatant, position, position) { IsUnarmed = !(Combatant is Seeker) && !Combatant.Weapons.Any(); diff --git a/FrEee/Objects/Combat/Grid/CombatantDestroyedEvent.cs b/FrEee/Objects/Combat/Grid/CombatantDestroyedEvent.cs index aed43a8e..fc8bf27c 100644 --- a/FrEee/Objects/Combat/Grid/CombatantDestroyedEvent.cs +++ b/FrEee/Objects/Combat/Grid/CombatantDestroyedEvent.cs @@ -4,7 +4,7 @@ namespace FrEee.Objects.Combat.Grid; public class CombatantDestroyedEvent : BattleEvent { - public CombatantDestroyedEvent(IBattle battle, ICombatant combatant, IntVector2 position) + public CombatantDestroyedEvent(IBattle battle, ICombatant combatant, Vector2 position) : base(battle, combatant, position, position) { } diff --git a/FrEee/Objects/Combat/Grid/CombatantDisappearsEvent.cs b/FrEee/Objects/Combat/Grid/CombatantDisappearsEvent.cs index 98224c88..6424e1dd 100644 --- a/FrEee/Objects/Combat/Grid/CombatantDisappearsEvent.cs +++ b/FrEee/Objects/Combat/Grid/CombatantDisappearsEvent.cs @@ -7,7 +7,7 @@ namespace FrEee.Objects.Combat.Grid; [Obsolete("This class is deprecated; use CombatantDestroyedEvent if a combatant is destroyed.")] public class CombatantDisappearsEvent : BattleEvent { - public CombatantDisappearsEvent(IBattle battle, ICombatant combatant, IntVector2 position) + public CombatantDisappearsEvent(IBattle battle, ICombatant combatant, Vector2 position) : base(battle, combatant, position, position) { } diff --git a/FrEee/Objects/Combat/Grid/CombatantLaunchedEvent.cs b/FrEee/Objects/Combat/Grid/CombatantLaunchedEvent.cs index 6b744f88..8bfd3654 100644 --- a/FrEee/Objects/Combat/Grid/CombatantLaunchedEvent.cs +++ b/FrEee/Objects/Combat/Grid/CombatantLaunchedEvent.cs @@ -6,7 +6,7 @@ namespace FrEee.Objects.Combat.Grid; public class CombatantLaunchedEvent : BattleEvent { - public CombatantLaunchedEvent(Battle battle, ICombatant launcher, ICombatant combatant, IntVector2 position) + public CombatantLaunchedEvent(Battle battle, ICombatant launcher, ICombatant combatant, Vector2 position) : base(battle, combatant, position, position) { Launcher = launcher; diff --git a/FrEee/Objects/Combat/Grid/CombatantMovesEvent.cs b/FrEee/Objects/Combat/Grid/CombatantMovesEvent.cs index 561c25f1..6921ab93 100644 --- a/FrEee/Objects/Combat/Grid/CombatantMovesEvent.cs +++ b/FrEee/Objects/Combat/Grid/CombatantMovesEvent.cs @@ -4,7 +4,7 @@ namespace FrEee.Objects.Combat.Grid; public class CombatantMovesEvent : BattleEvent { - public CombatantMovesEvent(Battle battle, ICombatant combatant, IntVector2 here, IntVector2 there) + public CombatantMovesEvent(Battle battle, ICombatant combatant, Vector2 here, Vector2 there) : base(battle, combatant, here, there) { } diff --git a/FrEee/Objects/Combat/Grid/CombatantsCollideEvent.cs b/FrEee/Objects/Combat/Grid/CombatantsCollideEvent.cs index 519fc859..64e30d20 100644 --- a/FrEee/Objects/Combat/Grid/CombatantsCollideEvent.cs +++ b/FrEee/Objects/Combat/Grid/CombatantsCollideEvent.cs @@ -10,7 +10,7 @@ namespace FrEee.Objects.Combat.Grid; /// public class CombatantsCollideEvent : BattleEvent { - public CombatantsCollideEvent(Battle battle, ICombatant combatant, ICombatant target, IntVector2 location, int combatantDamage, int targetDamage, bool wasCombatantDisarmed, bool wasTargetDisarmed) + public CombatantsCollideEvent(Battle battle, ICombatant combatant, ICombatant target, Vector2 location, int combatantDamage, int targetDamage, bool wasCombatantDisarmed, bool wasTargetDisarmed) : base(battle, combatant, location, location) { Target = target; diff --git a/FrEee/Objects/Combat/Grid/GroundBattle.cs b/FrEee/Objects/Combat/Grid/GroundBattle.cs index f3ff6f9d..2b3d45f3 100644 --- a/FrEee/Objects/Combat/Grid/GroundBattle.cs +++ b/FrEee/Objects/Combat/Grid/GroundBattle.cs @@ -53,11 +53,11 @@ public override void Initialize(IEnumerable combatants) Dice = new PRNG((int)(moduloID / Galaxy.Current.Timestamp * 10)); } - public override void PlaceCombatants(SafeDictionary locations) + public override void PlaceCombatants(SafeDictionary> locations) { // in ground combat, for now everyone is right on top of each other foreach (var c in Combatants) - locations.Add(c, new IntVector2()); + locations.Add(c, new Vector2()); } public override int MaxRounds => Mod.Current.Settings.GroundCombatTurns; diff --git a/FrEee/Objects/Combat/Grid/IBattleEvent.cs b/FrEee/Objects/Combat/Grid/IBattleEvent.cs index 87876ffb..923b398f 100644 --- a/FrEee/Objects/Combat/Grid/IBattleEvent.cs +++ b/FrEee/Objects/Combat/Grid/IBattleEvent.cs @@ -6,6 +6,6 @@ public interface IBattleEvent { IBattle Battle { get; } ICombatant Combatant { get; } - IntVector2 EndPosition { get; } - IntVector2 StartPosition { get; } + Vector2 EndPosition { get; } + Vector2 StartPosition { get; } } \ No newline at end of file diff --git a/FrEee/Objects/Combat/Grid/SpaceBattle.cs b/FrEee/Objects/Combat/Grid/SpaceBattle.cs index 5663eab9..52554d2b 100644 --- a/FrEee/Objects/Combat/Grid/SpaceBattle.cs +++ b/FrEee/Objects/Combat/Grid/SpaceBattle.cs @@ -40,7 +40,7 @@ public override void Initialize(IEnumerable combatants) Dice = new PRNG((int)(moduloID / Galaxy.Current.Timestamp * 10)); } - public override void PlaceCombatants(SafeDictionary locations) + public override void PlaceCombatants(SafeDictionary> locations) { if (Sector.SpaceObjects.OfType().Any()) { @@ -69,7 +69,7 @@ public override void PlaceCombatants(SafeDictionary loca } } - private void PlaceCombatant(SafeDictionary locations, double x, double y, ICombatant comb) + private void PlaceCombatant(SafeDictionary> locations, double x, double y, ICombatant comb) { // scramble all tile-filling combatants in rings around the largest if (comb.FillsCombatTile) @@ -77,13 +77,13 @@ private void PlaceCombatant(SafeDictionary locations, do for (int r = 0; ; r++) { bool done = false; - var tiles = IntVector2.AtRadius(r); + var tiles = Vector2Utility.AtRadius(r); foreach (var tile in tiles.Shuffle(Dice)) { var atHere = locations.Where(q => q.Key.FillsCombatTile && q.Value == tile); if (!atHere.Any()) { - locations.Add(comb, new IntVector2((int)x + tile.X, (int)y + tile.Y)); + locations.Add(comb, new Vector2((int)x + tile.X, (int)y + tile.Y)); done = true; break; } @@ -93,7 +93,7 @@ private void PlaceCombatant(SafeDictionary locations, do } } else // put non-filling combatants in the center - locations.Add(comb, new IntVector2((int)x, (int)y)); + locations.Add(comb, new Vector2((int)x, (int)y)); } /// diff --git a/FrEee/Objects/Combat/Grid/WeaponFiresEvent.cs b/FrEee/Objects/Combat/Grid/WeaponFiresEvent.cs index 507950f5..0cda31c2 100644 --- a/FrEee/Objects/Combat/Grid/WeaponFiresEvent.cs +++ b/FrEee/Objects/Combat/Grid/WeaponFiresEvent.cs @@ -7,7 +7,7 @@ namespace FrEee.Objects.Combat.Grid; public class WeaponFiresEvent : BattleEvent { - public WeaponFiresEvent(Battle battle, ICombatant attacker, IntVector2 here, ICombatant target, IntVector2 there, Component weapon, Hit hit, bool wasTargetDisarmed) + public WeaponFiresEvent(Battle battle, ICombatant attacker, Vector2 here, ICombatant target, Vector2 there, Component weapon, Hit hit, bool wasTargetDisarmed) : base(battle, attacker, here, there) { Attacker = attacker; diff --git a/FrEee/Objects/Space/ObjectLocation.cs b/FrEee/Objects/Space/ObjectLocation.cs index fbe3cc6a..cec08cc3 100644 --- a/FrEee/Objects/Space/ObjectLocation.cs +++ b/FrEee/Objects/Space/ObjectLocation.cs @@ -8,49 +8,8 @@ namespace FrEee.Objects.Space; /// An item and its location. /// [Serializable] -public class ObjectLocation +public record ObjectLocation(T Item, Point Location) { - public ObjectLocation() - { - } - - public ObjectLocation(T item, Point location) - { - Item = item; - Location = location; - } - - public T Item { get; set; } - public Point Location { get; set; } - - public static bool operator !=(ObjectLocation l1, ObjectLocation l2) - { - return !(l1 == l2); - } - - public static bool operator ==(ObjectLocation l1, ObjectLocation l2) - { - if (object.ReferenceEquals(l1, l2)) - return true; - if (l1 is null || l2 is null) - return false; - if (object.ReferenceEquals(l1, l2)) - return l1.Location.Equals(l2.Location); - if (object.ReferenceEquals(l1.Item, null) || object.ReferenceEquals(l2.Item, null)) - return false; - return l1.Item.Equals(l2.Item) && l1.Location.Equals(l2.Location); - } - - public override bool Equals(object? obj) - { - return this == obj as ObjectLocation; - } - - public override int GetHashCode() - { - return HashCodeMasher.Mash(Item, Location); - } - public override string ToString() { return "(" + Location.X + ", " + Location.Y + ")" + ": " + Item; diff --git a/FrEee/Objects/Technology/MountedComponentTemplate.cs b/FrEee/Objects/Technology/MountedComponentTemplate.cs index 603b0a18..0ce1ec71 100644 --- a/FrEee/Objects/Technology/MountedComponentTemplate.cs +++ b/FrEee/Objects/Technology/MountedComponentTemplate.cs @@ -405,9 +405,9 @@ public override string ToString() /// public class SimpleEqualityComparer : IEqualityComparer { - public bool Equals(MountedComponentTemplate x, MountedComponentTemplate y) + public bool Equals(MountedComponentTemplate? x, MountedComponentTemplate? y) { - return x.ComponentTemplate == y.ComponentTemplate && x.Mount == y.Mount; + return x?.ComponentTemplate == y?.ComponentTemplate && x?.Mount == y?.Mount; } public int GetHashCode(MountedComponentTemplate obj) diff --git a/FrEee/Objects/Vehicles/Design.cs b/FrEee/Objects/Vehicles/Design.cs index b94c327b..16a6d2a9 100644 --- a/FrEee/Objects/Vehicles/Design.cs +++ b/FrEee/Objects/Vehicles/Design.cs @@ -761,6 +761,11 @@ public override bool Equals(object? obj) return false; } + public override int GetHashCode() + { + return HashCodeMasher.Mash(BaseName, Hull, HashCodeMasher.MashList(Components)); + } + /// /// A design is unlocked if its hull and all used mounts/components are unlocked. /// diff --git a/FrEee/Utility/HeatMap.cs b/FrEee/Utility/HeatMap.cs index 34d7d184..ac2c2ed4 100644 --- a/FrEee/Utility/HeatMap.cs +++ b/FrEee/Utility/HeatMap.cs @@ -4,21 +4,21 @@ /// A heatmap, used for pathfinding and the like. /// Maps points on a plane to heat values with positive values being hotter. /// -public class HeatMap : SafeDictionary +public class HeatMap : SafeDictionary, double> { public double this[int x, int y] { - get => this[new IntVector2(x, y)]; - set => this[new IntVector2(x, y)] = value; + get => this[new Vector2(x, y)]; + set => this[new Vector2(x, y)] = value; } - public void AddLinearGradientEightWay(IntVector2 pos, double startValue, int range, double deltaPerDistance) + public void AddLinearGradientEightWay(Vector2 pos, double startValue, int range, double deltaPerDistance) { for (var x = pos.X - range; x <= pos.X + range; x++) { for (var y = pos.Y - range; y <= pos.Y + range; y++) { - var pos2 = new IntVector2(x, y); + var pos2 = new Vector2(x, y); var dist = (pos2 - pos).LengthEightWay; var val = startValue + deltaPerDistance * dist; this[pos2] += val; @@ -26,29 +26,29 @@ public void AddLinearGradientEightWay(IntVector2 pos, double startValue, int ran } } - public IntVector2 FindMax(IntVector2 pos, int range) + public Vector2 FindMax(Vector2 pos, int range) { - IntVector2 result = null; + Vector2? result = null; for (var x = pos.X - range; x <= pos.X + range; x++) { for (var y = pos.Y - range; y <= pos.Y + range; y++) { - if (this.ContainsKey(new IntVector2(x, y)) && (result == null || this[x, y] > this[result])) - result = new IntVector2(x, y); + if (this.ContainsKey(new Vector2(x, y)) && (result == null || this[x, y] > this[result])) + result = new Vector2(x, y); } } return result ?? pos; } - public IntVector2 FindMin(IntVector2 pos, int range) + public Vector2 FindMin(Vector2 pos, int range) { - IntVector2 result = null; + Vector2? result = null; for (var x = pos.X - range; x <= pos.X + range; x++) { for (var y = pos.Y - range; y <= pos.Y + range; y++) { - if (this.ContainsKey(new IntVector2(x, y)) && (result == null || this[x, y] < this[result])) - result = new IntVector2(x, y); + if (this.ContainsKey(new Vector2(x, y)) && (result == null || this[x, y] < this[result])) + result = new Vector2(x, y); } } return result ?? pos; diff --git a/FrEee/Utility/IntVector2.cs b/FrEee/Utility/IntVector2.cs deleted file mode 100644 index d1c8819b..00000000 --- a/FrEee/Utility/IntVector2.cs +++ /dev/null @@ -1,193 +0,0 @@ -using FrEee.Extensions; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace FrEee.Utility; - -/// -/// A two dimensional vector of integers. -/// -public class IntVector2 : Vector2, IEquatable -{ - public IntVector2() - : base() - { - } - - public IntVector2(int x, int y) - : base(x, y) - { - } - - public IntVector2(Vector2 v) - : base(v) - { - } - - /// - /// Length of this vector moving in 8 directions along the grid. - /// - public int LengthEightWay - { - get - { - var dx = Math.Abs(X); - var dy = Math.Abs(Y); - return Math.Max(dx, dy); - } - } - - /// - /// Length of this vector moving only in the 4 cardinal directions. - /// - public int LengthManhattan - { - get - { - var dx = Math.Abs(X); - var dy = Math.Abs(Y); - return dx + dy; - } - } - - /// - /// Interpolates between two vectors. - /// - /// - /// - /// - /// Should we skip moving to the endpoint? (say because it is occupied...) - /// - public static IntVector2 InterpolateEightWay(IntVector2 start, IntVector2 end, int distance, Func skip = null) - { - if (distance <= 0) - return start; - var trip = end - start; - var tripLength = trip.LengthEightWay; - if (distance >= tripLength) - return end; - var dx = trip.X; - var dy = trip.Y; - var pos = new IntVector2(start); - var remainingDistance = distance; - while (remainingDistance > 0) - { - if (dx == 0 && dy == 0) - break; - if (dx != 0) - { - pos.X += Math.Sign(dx); - dx -= Math.Sign(dx); - } - if (dy != 0) - { - pos.Y += Math.Sign(dy); - dy -= Math.Sign(dy); - } - remainingDistance--; - } - if (skip != null && skip(pos)) - return InterpolateEightWay(start, end, distance - 1, skip); // TODO - find alternate endpoint rather than just stopping the journey one tile short - return pos; - } - - public static IntVector2 operator -(IntVector2 v) - { - return new IntVector2(-v.X, -v.Y); - } - - public static IntVector2 operator -(IntVector2 v1, IntVector2 v2) - { - return v1 + -v2; - } - - public static bool operator !=(IntVector2 v1, IntVector2 v2) - { - return !(v1 == v2); - } - - public static IntVector2 operator *(IntVector2 v, int s) - { - return new IntVector2(v.X * s, v.Y * s); - } - - public static IntVector2 operator *(int s, IntVector2 v) - { - return v * s; - } - - public static IntVector2 operator /(IntVector2 v, int s) - { - return new IntVector2(v.X / s, v.Y / s); - } - - public static IntVector2 operator +(IntVector2 v1, IntVector2 v2) - { - return new IntVector2(v1.X + v2.X, v1.Y + v2.Y); - } - - public static bool operator ==(IntVector2 v1, IntVector2 v2) - { - return v1.SafeEquals(v2); - } - - public int DistanceToEightWay(IntVector2 dest) - { - return (dest - this).LengthEightWay; - } - - public int DistanceToManhattan(IntVector2 dest) - { - return (dest - this).LengthManhattan; - } - - public override bool Equals(object? obj) - { - if (obj is IntVector2 v) - return Equals(v); - return false; - } - - public bool Equals(IntVector2? other) - { - return other is not null && X == other.X && Y == other.Y; - } - - public override int GetHashCode() - { - return HashCodeMasher.Mash(X, Y); - } - - public override string ToString() - { - return $"({X}, {Y})"; - } - - public static IEnumerable WithinRadius(int radius) - { - if (!withinRadius.ContainsKey(radius)) - { - if (radius < 0) - withinRadius[radius] = Enumerable.Empty(); - else - { - var list = new List(); - for (var x = -radius; x <= radius; x++) - { - for (var y = -radius; y <= radius; y++) - list.Add(new IntVector2(x, y)); - } - withinRadius.Add(radius, list); - } - } - return withinRadius[radius]; - } - - private static IDictionary> withinRadius = new Dictionary>(); - - public static IEnumerable AtRadius(int radius) - { - return WithinRadius(radius).Except(WithinRadius(radius - 1)); - } -} \ No newline at end of file diff --git a/FrEee/Utility/Vector2.cs b/FrEee/Utility/Vector2.cs index bb1bb85f..0f8152e1 100644 --- a/FrEee/Utility/Vector2.cs +++ b/FrEee/Utility/Vector2.cs @@ -1,5 +1,6 @@ using FrEee.Extensions; using System; +using System.Numerics; namespace FrEee.Utility; @@ -7,29 +8,155 @@ namespace FrEee.Utility; /// A generic two dimensional vector. /// /// -public class Vector2 : IEquatable> +public record Vector2(T X, T Y) + : IEquatable> + where T : INumber, ISignedNumber { - public Vector2() - : this(default(T), default(T)) + /// + /// Creates a vector with both values initialized to zero. + /// + public Vector2() : this(T.Zero, T.Zero) { } + + /// + /// Copies a vector. + /// + /// The vector to copy. + public Vector2(Vector2 other) : base() { + X = other.X; + Y = other.Y; } - public Vector2(T x, T y) + /// + /// Length of this vector moving in 8 directions along the grid. + /// + public T LengthEightWay { - X = x; - Y = y; + get + { + var dx = T.Abs(X); + var dy = T.Abs(Y); + return T.Max(dx, dy); + } } - public Vector2(Vector2 v) - : this(v.X, v.Y) + /// + /// Length of this vector moving only in the 4 cardinal directions. + /// + public T LengthManhattan { + get + { + var dx = T.Abs(X); + var dy = T.Abs(Y); + return dx + dy; + } } - public T X { get; set; } - public T Y { get; set; } + /// + /// Interpolates between two vectors. + /// + /// + /// + /// + /// Should we skip moving to the endpoint? (say because it is occupied...) + /// + public static Vector2 InterpolateEightWay(Vector2 start, Vector2 end, T distance, Func, bool> skip = null) + { + if (distance <= T.Zero) + return start; + var trip = end - start; + var tripLength = trip.LengthEightWay; + if (distance >= tripLength) + return end; + var dx = trip.X; + var dy = trip.Y; + var pos = new Vector2(start); + var remainingDistance = distance; + while (remainingDistance > T.Zero) + { + if (dx == T.Zero && dy == T.Zero) + break; + if (dx != T.Zero) + { + pos = new(pos.X + TSign(dx), pos.Y); + dx -= TSign(dx); + } + if (dy != T.Zero) + { + pos = new(pos.X, pos.Y + TSign(dy)); + dy -= TSign(dy); + } + remainingDistance--; + } + if (skip != null && skip(pos)) + return InterpolateEightWay(start, end, distance - T.One, skip); // TODO - find alternate endpoint rather than just stopping the journey one tile short + return pos; + } + + public static Vector2 operator -(Vector2 v) + { + return new Vector2(-v.X, -v.Y); + } + + public static Vector2 operator -(Vector2 v1, Vector2 v2) + { + return v1 + -v2; + } + + public static Vector2 operator *(Vector2 v, T s) + { + return new Vector2(v.X * s, v.Y * s); + } + + public static Vector2 operator *(T s, Vector2 v) + { + return v * s; + } + + public static Vector2 operator /(Vector2 v, T s) + { + return new Vector2(v.X / s, v.Y / s); + } + + public static Vector2 operator +(Vector2 v1, Vector2 v2) + { + return new Vector2(v1.X + v2.X, v1.Y + v2.Y); + } + + public T DistanceToEightWay(Vector2 dest) + { + return (dest - this).LengthEightWay; + } + + public T DistanceToManhattan(Vector2 dest) + { + return (dest - this).LengthManhattan; + } + + public override int GetHashCode() + { + return HashCodeMasher.Mash(X, Y); + } + + public override string ToString() + { + return $"({X}, {Y})"; + } - public bool Equals(Vector2 other) + private static T TSign(T num) { - return X.SafeEquals(other.X) && Y.SafeEquals(other.Y); + if (num < T.Zero) + { + return T.NegativeOne; + } + else if (num > T.Zero) + { + return T.One; + } + else + { + return T.Zero; + } } } \ No newline at end of file diff --git a/FrEee/Utility/Vector2Utility.cs b/FrEee/Utility/Vector2Utility.cs new file mode 100644 index 00000000..dde15345 --- /dev/null +++ b/FrEee/Utility/Vector2Utility.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace FrEee.Utility; + +/// +/// Utilities for . +/// +public static class Vector2Utility +{ + /// + /// Finds vectors that are within a particular radius from the origin on an integer grid. + /// + /// + /// + public static IEnumerable> WithinRadius(T radius) + where T : IBinaryInteger, ISignedNumber + { + if (radius < T.Zero) + yield break; + else + { + for (var x = -radius; x <= radius; x++) + { + for (var y = -radius; y <= radius; y++) + yield return new(x, y); + } + } + } + + /// + /// Finds vectors that are at a particular radius from the origin on an integer grid. + /// + /// + /// + public static IEnumerable> AtRadius(T radius) + where T : IBinaryInteger, ISignedNumber + { + // TODO: improve performance by just checking edges + return WithinRadius(radius).Except(WithinRadius(radius - T.One)); + } +} \ No newline at end of file