penev discovered that the RulesetLoaded functions of projectiles were never being called, meaning that their blocking calculations were not properly accounting for actors with large hitboxes. The best fix for this is to change FindActorsOnLine to always account for the largest actor's hit radius, rather than forcing callers to pass the largest radius. Per the comment in Util.cs, as a result, move this computation to ActorMap. I decided to simplify by not making a separate calculation for actors that block projectiles only; this may cause a small performance degradation as the search space is a bit larger. Similarly to this, I've removed the ability to specify a search radius manually. Because this is only a search radius, setting a value smaller than the largest eligible actor makes no sense; that would lead to completely inconsistent blocking. Setting a larger value, on the other hand, would make no difference. CreateEffectWarhead was the only place in core code any of these search radii were set, and that's because 0 was a mysterious magic value that made the warhead incapable of hitting actors. I replaced it with a boolean flag that more clearly indicates the actual behaviour. Fixes #14151.
251 lines
6.6 KiB
C#
251 lines
6.6 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
|
|
* 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.Drawing;
|
|
using System.Linq;
|
|
using OpenRA.GameRules;
|
|
using OpenRA.Graphics;
|
|
using OpenRA.Mods.Common.Traits;
|
|
using OpenRA.Support;
|
|
using OpenRA.Traits;
|
|
|
|
namespace OpenRA.Mods.Common
|
|
{
|
|
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;
|
|
}
|
|
|
|
public static int GetNearestFacing(int facing, int desiredFacing)
|
|
{
|
|
var turn = desiredFacing - facing;
|
|
if (turn > 128)
|
|
turn -= 256;
|
|
if (turn < -128)
|
|
turn += 256;
|
|
|
|
return facing + turn;
|
|
}
|
|
|
|
public static int QuantizeFacing(int facing, int numFrames)
|
|
{
|
|
var step = 256 / numFrames;
|
|
var a = (facing + step / 2) & 0xff;
|
|
return a / step;
|
|
}
|
|
|
|
public static int QuantizeFacing(int facing, int numFrames, bool useClassicFacingFudge)
|
|
{
|
|
if (!useClassicFacingFudge || numFrames != 32)
|
|
return Util.QuantizeFacing(facing, numFrames);
|
|
|
|
// TD and RA divided the facing artwork into 3 frames from (north|south) to (north|south)-(east|west)
|
|
// and then 5 frames from (north|south)-(east|west) to (east|west)
|
|
var quadrant = ((facing + 31) & 0xFF) / 64;
|
|
if (quadrant == 0 || quadrant == 2)
|
|
{
|
|
var frame = Util.QuantizeFacing(facing, 24);
|
|
if (frame > 18)
|
|
return frame + 6;
|
|
if (frame > 4)
|
|
return frame + 3;
|
|
return frame;
|
|
}
|
|
else
|
|
{
|
|
var frame = Util.QuantizeFacing(facing, 40);
|
|
return frame < 20 ? frame - 3 : frame - 8;
|
|
}
|
|
}
|
|
|
|
/// <summary>Wraps an arbitrary integer facing value into the range 0 - 255</summary>
|
|
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(int facing, int desiredFacing, int facingTolerance)
|
|
{
|
|
if (facingTolerance == 0 && facing == desiredFacing)
|
|
return true;
|
|
|
|
var delta = Util.NormalizeFacing(desiredFacing - facing);
|
|
return delta <= facingTolerance || delta >= 256 - facingTolerance;
|
|
}
|
|
|
|
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 IEnumerable<T> Shuffle<T>(this IEnumerable<T> 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);
|
|
var item = items[i + j];
|
|
items[i + j] = items[i];
|
|
items[i] = item;
|
|
yield return item;
|
|
}
|
|
|
|
if (items.Length > 0)
|
|
yield return items[items.Length - 1];
|
|
}
|
|
|
|
static IEnumerable<CPos> 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 IEnumerable<CPos> ExpandFootprint(IEnumerable<CPos> cells, bool allowDiagonal)
|
|
{
|
|
return cells.SelectMany(c => Neighbours(c, allowDiagonal)).Distinct();
|
|
}
|
|
|
|
public static IEnumerable<CPos> AdjacentCells(World w, Target target)
|
|
{
|
|
var cells = target.Positions.Select(p => w.Map.CellContaining(p)).Distinct();
|
|
return ExpandFootprint(cells, true);
|
|
}
|
|
|
|
public static int ApplyPercentageModifiers(int number, IEnumerable<int> 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<CPos> RandomWalk(CPos p, MersenneTwister r)
|
|
{
|
|
for (;;)
|
|
{
|
|
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 RandomDelay(World world, int[] range)
|
|
{
|
|
if (range.Length == 0)
|
|
return 0;
|
|
|
|
if (range.Length == 1)
|
|
return range[0];
|
|
|
|
return world.SharedRandom.Next(range[0], range[1]);
|
|
}
|
|
|
|
public static string FriendlyTypeName(Type t)
|
|
{
|
|
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(HashSet<>))
|
|
return "Set of {0}".F(t.GetGenericArguments().Select(FriendlyTypeName).ToArray());
|
|
|
|
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
|
return "Mapping of {0} to {1}".F(t.GetGenericArguments().Select(FriendlyTypeName).ToArray());
|
|
|
|
if (t.IsSubclassOf(typeof(Array)))
|
|
return "Collection of {0}".F(FriendlyTypeName(t.GetElementType()));
|
|
|
|
if (t.IsGenericType && t.GetGenericTypeDefinition().GetInterfaces().Any(e => e.IsGenericType && e.GetGenericTypeDefinition() == typeof(IEnumerable<>)))
|
|
return "Collection of {0}".F(FriendlyTypeName(t.GetGenericArguments().First()));
|
|
|
|
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(HSLColor) || t == typeof(Color))
|
|
return "Color (RRGGBB[AA] notation)";
|
|
|
|
if (t == typeof(IProjectileInfo))
|
|
return "Projectile";
|
|
|
|
if (t == typeof(IWarhead))
|
|
return "Warhead";
|
|
|
|
return t.Name;
|
|
}
|
|
}
|
|
}
|