#region Copyright & License Information /* * Copyright (c) The OpenRA Developers and Contributors * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. For more * information, see COPYING. */ #endregion using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; using OpenRA.GameRules; using OpenRA.Mods.Common.Traits; using OpenRA.Primitives; using OpenRA.Support; using OpenRA.Traits; namespace OpenRA.Mods.Common { public enum InaccuracyType { Maximum, PerCellIncrement, Absolute } public static class Util { public static int TickFacing(int facing, int desiredFacing, int rot) { var leftTurn = (facing - desiredFacing) & 0xFF; var rightTurn = (desiredFacing - facing) & 0xFF; if (Math.Min(leftTurn, rightTurn) < rot) return desiredFacing & 0xFF; else if (rightTurn < leftTurn) return (facing + rot) & 0xFF; else return (facing - rot) & 0xFF; } /// /// Adds step angle units to facing in the direction that takes it closer to desiredFacing. /// If facing is already within step of desiredFacing then desiredFacing is returned. /// Step is given as an integer to allow negative values (step away from the desired facing) /// public static WAngle TickFacing(WAngle facing, WAngle desiredFacing, WAngle step) { var leftTurn = (facing - desiredFacing).Angle; var rightTurn = (desiredFacing - facing).Angle; if (leftTurn < step.Angle || rightTurn < step.Angle) return desiredFacing; return rightTurn < leftTurn ? facing + step : facing - step; } /// /// Determines whether desiredFacing is clockwise (-1) or anticlockwise (+1) of facing. /// If desiredFacing is equal to facing or directly behind facing we treat it as being anticlockwise /// public static int GetTurnDirection(WAngle facing, WAngle desiredFacing) { return (facing - desiredFacing).Angle < 512 ? -1 : 1; } /// /// Calculate the frame index (between 0..numFrames) that /// should be used for the given facing value. /// public static int IndexFacing(WAngle facing, int numFrames) { // 1024 here is the max angle, so we divide the max angle by the total number of facings (numFrames) var step = 1024 / numFrames; var a = (facing.Angle + step / 2) & 1023; return a / step; } /// /// Returns the remainder angle after rounding to the nearest whole step / facing /// public static WAngle AngleDiffToStep(WAngle facing, int numFrames) { var step = 1024 / numFrames; var a = (facing.Angle + step / 2) & 1023; return new WAngle(a % step - step / 2); } public static WAngle GetInterpolatedFacing(WAngle facing, int facings, int interpolatedFacings) { var step = 1024 / interpolatedFacings; return new WAngle(AngleDiffToStep(facing, facings).Angle / step * step); } /// Rounds the given facing value to the nearest quantized step. public static WAngle QuantizeFacing(WAngle facing, int steps) { return new WAngle(IndexFacing(facing, steps) * (1024 / steps)); } /// Wraps an arbitrary integer facing value into the range 0 - 255 public static int NormalizeFacing(int f) { if (f >= 0) return f & 0xFF; var negative = -f & 0xFF; return negative == 0 ? 0 : 256 - negative; } public static bool FacingWithinTolerance(WAngle facing, WAngle desiredFacing, WAngle facingTolerance) { if (facingTolerance.Angle == 0 && facing == desiredFacing) return true; var delta = (desiredFacing - facing).Angle; return delta <= facingTolerance.Angle || delta >= 1024 - facingTolerance.Angle; } public static WPos BetweenCells(World w, CPos from, CPos to) { var fromPos = from.Layer == 0 ? w.Map.CenterOfCell(from) : w.GetCustomMovementLayers()[from.Layer].CenterOfCell(from); var toPos = to.Layer == 0 ? w.Map.CenterOfCell(to) : w.GetCustomMovementLayers()[to.Layer].CenterOfCell(to); return WPos.Lerp(fromPos, toPos, 1, 2); } public static WAngle GetVerticalAngle(WPos source, WPos target) { var delta = target - source; var horizontalDelta = delta.HorizontalLength; var verticalVector = new WVec(-delta.Z, -horizontalDelta, 0); return verticalVector.Yaw; } public static IEnumerable Shuffle(this IEnumerable ts, MersenneTwister random) { // Fisher-Yates var items = ts.ToArray(); for (var i = 0; i < items.Length - 1; i++) { var j = random.Next(items.Length - i); (items[i], items[i + j]) = (items[i + j], items[i]); yield return items[i]; } if (items.Length > 0) yield return items[items.Length - 1]; } static IEnumerable Neighbours(CPos c, bool allowDiagonal) { yield return c; yield return new CPos(c.X - 1, c.Y); yield return new CPos(c.X + 1, c.Y); yield return new CPos(c.X, c.Y - 1); yield return new CPos(c.X, c.Y + 1); if (allowDiagonal) { yield return new CPos(c.X - 1, c.Y - 1); yield return new CPos(c.X + 1, c.Y - 1); yield return new CPos(c.X - 1, c.Y + 1); yield return new CPos(c.X + 1, c.Y + 1); } } public static bool AreAdjacentCells(CPos a, CPos b) { var offset = b - a; return Math.Abs(offset.X) < 2 && Math.Abs(offset.Y) < 2; } public static IEnumerable ExpandFootprint(CPos cell, bool allowDiagonal) { return Neighbours(cell, allowDiagonal); } public static IEnumerable ExpandFootprint(IEnumerable cells, bool allowDiagonal) { return cells.SelectMany(c => Neighbours(c, allowDiagonal)).Distinct(); } public static IEnumerable AdjacentCells(World w, in Target target) { var cells = target.Positions.Select(p => w.Map.CellContaining(p)).Distinct(); return ExpandFootprint(cells, true); } public static int ApplyPercentageModifiers(int number, IEnumerable percentages) { // See the comments of PR#6079 for a faster algorithm if this becomes a performance bottleneck var a = (decimal)number; foreach (var p in percentages) a *= p / 100m; return (int)a; } public static IEnumerable RandomWalk(CPos p, MersenneTwister r) { while (true) { var dx = r.Next(-1, 2); var dy = r.Next(-1, 2); if (dx == 0 && dy == 0) continue; p += new CVec(dx, dy); yield return p; } } public static int RandomInRange(MersenneTwister random, int[] range) { if (range.Length == 0) return 0; if (range.Length == 1) return range[0]; return random.Next(range[0], range[1]); } public static string InternalTypeName(Type t) { return t.IsGenericType ? $"{t.Name.Substring(0, t.Name.IndexOf('`'))}<{string.Join(", ", t.GenericTypeArguments.Select(arg => arg.Name))}>" : t.Name; } public static string FriendlyTypeName(Type t) { if (t.IsEnum) return $"{t.Name} (enum)"; if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(HashSet<>)) return $"Set of {t.GetGenericArguments().Select(FriendlyTypeName).First()}"; if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>)) { var args = t.GetGenericArguments().Select(FriendlyTypeName).ToArray(); return $"Dictionary with Key: {args[0]}, Value: {args[1]}"; } if (t.IsSubclassOf(typeof(Array))) return $"Collection of {FriendlyTypeName(t.GetElementType())}"; if (t.IsGenericType && t.GetGenericTypeDefinition().GetInterfaces().Any(e => e.IsGenericType && e.GetGenericTypeDefinition() == typeof(IEnumerable<>))) return $"Collection of {FriendlyTypeName(t.GetGenericArguments().First())}"; if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) return $"{t.GetGenericArguments().Select(FriendlyTypeName).First()} (optional)"; if (t == typeof(int) || t == typeof(uint)) return "Integer"; if (t == typeof(int2)) return "2D Integer"; if (t == typeof(float) || t == typeof(decimal)) return "Real Number"; if (t == typeof(float2)) return "2D Real Number"; if (t == typeof(CPos)) return "2D Cell Position"; if (t == typeof(CVec)) return "2D Cell Vector"; if (t == typeof(WAngle)) return "1D World Angle"; if (t == typeof(WRot)) return "3D World Rotation"; if (t == typeof(WPos)) return "3D World Position"; if (t == typeof(WDist)) return "1D World Distance"; if (t == typeof(WVec)) return "3D World Vector"; if (t == typeof(Color)) return "Color (RRGGBB[AA] notation)"; if (t == typeof(IProjectileInfo)) return "Projectile"; if (t == typeof(IWarhead)) return "Warhead"; return t.Name; } public static string GetAttributeParameterValue(CustomAttributeTypedArgument value) { if (value.ArgumentType.IsEnum) return Enum.Parse(value.ArgumentType, value.Value.ToString()).ToString(); if (value.ArgumentType == typeof(Type) && value.Value != null) return (value.Value as Type).Name; if (value.ArgumentType.IsArray) { var names = (value.Value as IReadOnlyCollection).Select(x => (x.Value as Type).Name); return string.Join(", ", names); } return value.Value?.ToString(); } public static int GetProjectileInaccuracy(int baseInaccuracy, InaccuracyType inaccuracyType, ProjectileArgs args) { var inaccuracy = ApplyPercentageModifiers(baseInaccuracy, args.InaccuracyModifiers); switch (inaccuracyType) { case InaccuracyType.Maximum: var weaponMaxRange = ApplyPercentageModifiers(args.Weapon.Range.Length, args.RangeModifiers); return inaccuracy * (args.PassiveTarget - args.Source).Length / weaponMaxRange; case InaccuracyType.PerCellIncrement: return inaccuracy * (args.PassiveTarget - args.Source).Length / 1024; case InaccuracyType.Absolute: return inaccuracy; default: throw new InvalidEnumArgumentException(nameof(inaccuracyType), (int)inaccuracyType, typeof(InaccuracyType)); } } } }