Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zzre: Add duel AI movement #393

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions zzio/InventoryCard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ public class InventoryFairy : InventoryCard
public uint maxMHP;
public float moveSpeed;
public float jumpPower;
public float jumpMana = 10000f;
public float maxJumpMana = 10000f;
public int jumpMana = 10000;
public int maxJumpMana = 10000;
public float criticalHit;

protected override void ReadSub(BinaryReader r)
Expand Down
64 changes: 63 additions & 1 deletion zzre.core.tests/math/TestMath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Numerics;
using NUnit.Framework;

namespace zzre.core.tests.math;
namespace zzre.tests;

[TestFixture]
public class TestMath
Expand Down Expand Up @@ -58,4 +58,66 @@ public static IEnumerable<Vector3> GenerateUniformPoints()
yield return vec;
}
}

public readonly record struct AlmostANumber(int value)
: IComparable<AlmostANumber>, IComparisonOperators<AlmostANumber, AlmostANumber, bool>
{
public int CompareTo(AlmostANumber other) => value - other.value;
public static bool operator <(AlmostANumber left, AlmostANumber right) => left.CompareTo(right) < 0;
public static bool operator >(AlmostANumber left, AlmostANumber right) => left.CompareTo(right) > 0;
public static bool operator <=(AlmostANumber left, AlmostANumber right) => left.CompareTo(right) <= 0;
public static bool operator >=(AlmostANumber left, AlmostANumber right) => left.CompareTo(right) >= 0;
}
public static readonly AlmostANumber A = new(1), B = new(2), C = new(3), D = new(4), E = new(5);

[Test, Repeat(1000)]
public void TestNextOf()
{
var value = Random.Shared.NextOf(new[] { A, B, C, D, E });
Assert.That(value, Is.AnyOf(A, B, C, D, E));
}

[Test]
public void TestNextOfEmpty()
{
Assert.That(() =>
{
Random.Shared.NextOf(new AlmostANumber[] { });
}, Throws.ArgumentException);
}

[Test, Repeat(1000)]
public void TestNextOfExceptEmpty()
{
var value = Random.Shared.NextOf<AlmostANumber>([A, B, C, D, E], []);
Assert.That(value, Is.AnyOf(A, B, C, D, E));
}

[Test, Repeat(1000)]
public void TestNextOfDisjunct()
{
var value = Random.Shared.NextOf<AlmostANumber>([A, B, C], [D, E]);
Assert.That(value, Is.AnyOf(A, B, C));
}

[Test, Repeat(1000)]
public void TestNextOfEmptyInput()
{
var value = Random.Shared.NextOf<AlmostANumber>([], [A, B, C, D, E]);
Assert.That(value, Is.Null);
}

[Test, Repeat(1000)]
public void TestNextOfSuper()
{
var value = Random.Shared.NextOf<AlmostANumber>([B, C, D], [A, B, C, D, E]);
Assert.That(value, Is.Null);
}

[Test, Repeat(1000)]
public void TestNextOfExcept()
{
var value = Random.Shared.NextOf<AlmostANumber>([A, B, C, D, E], [B, C, D]);
Assert.That(value, Is.AnyOf(A, E));
}
}
7 changes: 5 additions & 2 deletions zzre.core/PooledList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public struct PooledList<T> : IDisposable, IEnumerable<T> where T : struct
private int count;

public readonly int Capacity => array.Length;
public readonly bool IsFull => count >= array.Length;
public readonly bool IsFull => count >= array.Length;
public readonly bool IsEmpty => count == 0;

public int Count
{
Expand Down Expand Up @@ -79,5 +80,7 @@ public ref T Add()
public readonly ArraySegment<T>.Enumerator GetEnumerator() =>
new ArraySegment<T>(array, 0, count).GetEnumerator();
readonly IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

public static implicit operator ReadOnlySpan<T>(in PooledList<T> list) => list.Span;
}
13 changes: 13 additions & 0 deletions zzre.core/math/MathEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public static class MathEx
public const float DegToRad = MathF.PI / 180f;
public const float RadToDeg = 180f / MathF.PI;
public const float ZeroEpsilon = 0.1E-10f;
public const float TwoPI = MathF.PI * 2f;
public static readonly Vector2 Vector2NaN = Vector2.One * float.NaN;
public static readonly Vector3 Vector3NaN = Vector3.One * float.NaN;
public static readonly Vector4 Vector4NaN = Vector4.One * float.NaN;
Expand Down Expand Up @@ -43,6 +44,18 @@ public static bool Cmp(float a, float b) =>
[MethodImpl(MIOptions)]
public static bool CmpZero(float a) => Math.Abs(a) < ZeroEpsilon;

[MethodImpl(MIOptions)]
public static float NormalizeAngle(float angle)
{
while (angle < -MathF.PI) angle += TwoPI;
while (angle > +MathF.PI) angle -= TwoPI;
return angle;
}

[MethodImpl(MIOptions)]
public static Vector3 HorizontalDirection(float angle) =>
new(MathF.Sin(angle), 0f, MathF.Cos(angle));

[MethodImpl(MIOptions)]
public static Vector2 Floor(Vector2 v) =>
new(MathF.Floor(v.X), MathF.Floor(v.Y));
Expand Down
84 changes: 84 additions & 0 deletions zzre.core/math/NumericsExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
Expand Down Expand Up @@ -129,6 +130,89 @@ public static T NextOf<T>(this Random random, IReadOnlyList<T> list) => !list.An
public static T NextOf<T>(this Random random) where T : struct, Enum =>
random.NextOf(Enum.GetValues<T>());

public static bool IsSorted<T>(this ReadOnlySpan<T> list)
where T : struct, IComparisonOperators<T, T, bool>
{
for (int i = 1; i < list.Length; i++)
{
if (list[i - 1] > list[i])
return false;
}
return true;
}

// the original code would just give up after 20 tries, I will at least try a bit more
private const int NextOfRandomCount = 20;

public static T? NextOf<T>(this Random random, ReadOnlySpan<T> from, ReadOnlySpan<T> except)
where T : struct, IComparable<T>, IEquatable<T>, IComparisonOperators<T, T, bool>
{
if (from.IsEmpty)
return null;
if (except.IsEmpty)
return from[random.Next(from.Length)];

if (except.Length > 8 && except.IsSorted())
{
for (int i = 0; i < NextOfRandomCount; i++)
{
int index = random.Next(from.Length);
if (except.BinarySearch(from[index]) < 0)
return from[index];
}
}
else
{
for (int i = 0; i < NextOfRandomCount; i++)
{
int index = random.Next(from.Length);
if (!except.Contains(from[index]))
return from[index];
}
}

return NextOfPrefilter(random, from, except);
}

public static T? NextOfPrefilter<T>(this Random random, ReadOnlySpan<T> fromOriginal, ReadOnlySpan<T> exceptOriginal)
where T : struct, IComparable<T>, IEquatable<T>, IComparisonOperators<T, T, bool>
{
if (fromOriginal.IsEmpty)
return null;
if (exceptOriginal.IsEmpty)
return fromOriginal[random.Next(fromOriginal.Length)];

var buffer = ArrayPool<T>.Shared.Rent(fromOriginal.Length * 2 + exceptOriginal.Length);
var from = buffer.AsSpan(0, fromOriginal.Length);
var except = buffer.AsSpan(from.Length, exceptOriginal.Length);

var destination = buffer.AsSpan(from.Length + except.Length);
fromOriginal.CopyTo(buffer);
exceptOriginal.CopyTo(buffer.AsSpan(from.Length));
Array.Sort(buffer, 0, from.Length);
Array.Sort(buffer, from.Length, except.Length);

int j = 0;
int k = 0;
for (int i = 0; i < from.Length; i++)
{
while (j < except.Length && except[j] < from[i])
j++;
if (j >= except.Length)
{
from[i..].CopyTo(destination[k..]);
k += from.Length - i;
break;
}
if (except[j] > from[i])
destination[k++] = from[i];
}

T? result = k > 0 ? destination[random.Next(k)] : null;
ArrayPool<T>.Shared.Return(buffer);
return result;
}

public static bool IsFinite(this Vector2 v) =>
float.IsFinite(v.X) && float.IsFinite(v.Y);

Expand Down
16 changes: 13 additions & 3 deletions zzre/game/DuelGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public DuelGame(ITagContainer diContainer, messages.StartDuel message) : base(di
new systems.effect.Sound(this),

// Fairies
new systems.AIPath(this),
new systems.AIMovement(this),
new systems.FairyPhysics(this),
new systems.FairyAnimation(this),
new systems.FairyActivation(this),
Expand Down Expand Up @@ -90,20 +92,25 @@ public DuelGame(ITagContainer diContainer, messages.StartDuel message) : base(di
new systems.ModelRenderer(this, components.RenderOrder.LateSolid),
new systems.ModelRenderer(this, components.RenderOrder.LateAdditive),
new systems.effect.EffectRenderer(this, components.RenderOrder.LateEffect),
new systems.effect.EffectModelRenderer(this, components.RenderOrder.LateEffect));
new systems.effect.EffectModelRenderer(this, components.RenderOrder.LateEffect),

new systems.DebugAIPath(this));

LoadScene($"sd_{message.SceneId:D4}");
camera.Location.LocalPosition = -ecsWorld.Get<rendering.WorldMesh>().Origin;

var playerEntity = CreateParticipant(message.OverworldPlayer, isPlayer: true);
foreach (var enemy in message.OverworldEnemies)
CreateParticipant(enemy, isPlayer: false);
var enemyEntities = new DefaultEcs.Entity[message.OverworldEnemies.Length];
for (int i = 0; i < enemyEntities.Length; i++)
enemyEntities[i] = CreateParticipant(message.OverworldEnemies[i], isPlayer: false);

playerEntity.Set<components.SoundListener>();
playerEntity.Set(components.DuelCameraMode.ZoomIn);
ecsWorld.Set(new components.PlayerEntity(playerEntity));

ecsWorld.Publish(new messages.SwitchFairy(playerEntity));
foreach (var enemy in enemyEntities)
ecsWorld.Publish(new messages.SwitchFairy(enemy));
}

private DefaultEcs.Entity CreateParticipant(DefaultEcs.Entity overworldEntity, bool isPlayer)
Expand All @@ -122,7 +129,10 @@ private DefaultEcs.Entity CreateParticipant(DefaultEcs.Entity overworldEntity, b
continue;
var fairy = CreateFairyFor(duelEntity, invFairy);
if (!isPlayer)
{
fairy.Set<components.FairyDistanceVisibility>();
fairy.Set<components.AIMovement>();
}
participant.Fairies[i] = fairy;
}

Expand Down
5 changes: 5 additions & 0 deletions zzre/game/Inventory.GameLogic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ public void AddMana(InventorySpell spell, int delta)
spell.mana = (uint)Math.Clamp((int)spell.mana + delta, 0, dbRow.MaxMana);
}

public static void AddJumpMana(InventoryFairy fairy, int delta)
{
fairy.jumpMana = Math.Clamp(fairy.jumpMana + delta, 0, fairy.maxJumpMana);
}

public void AddXP(InventoryFairy fairy, uint moreXP)
{
fairy.xpChangeCount += moreXP;
Expand Down
Loading
Loading