SupaLidlGame/Characters/NPC.cs

300 lines
9.2 KiB
C#
Raw Normal View History

2022-11-10 20:29:53 -08:00
using Godot;
2022-11-27 19:37:16 -08:00
using SupaLidlGame.Extensions;
2022-11-19 21:21:12 -08:00
using SupaLidlGame.Items;
2022-11-10 20:29:53 -08:00
using System;
namespace SupaLidlGame.Characters
{
public partial class NPC : Character
{
2022-11-12 16:45:04 -08:00
/// <summary>
/// Time in seconds it takes for the NPC to think FeelsDankCube
/// </summary>
2022-11-19 21:21:12 -08:00
public const float ThinkTime = 0.125f;
2022-11-10 20:29:53 -08:00
public float[] Weights => _weights;
2022-11-27 19:37:16 -08:00
[Export]
public float PreferredDistance { get; protected set; } = 64.0f;
2022-11-13 19:52:09 -08:00
protected float[] _weights = new float[16];
protected int _bestWeightIdx;
2022-11-10 20:29:53 -08:00
protected double _thinkTimeElapsed = 0;
2022-11-27 19:37:16 -08:00
protected Vector2 _blockingDir;
protected static readonly Vector2[] _weightDirs = new Vector2[16];
2022-11-10 20:29:53 -08:00
2022-11-27 19:37:16 -08:00
static NPC()
2022-11-10 20:29:53 -08:00
{
2022-11-12 16:45:04 -08:00
for (int i = 0; i < 16; i++)
{
float y = Mathf.Sin(Mathf.Pi * i * 2 / 16);
float x = Mathf.Cos(Mathf.Pi * i * 2 / 16);
_weightDirs[i] = new Vector2(x, y);
}
2022-11-10 20:29:53 -08:00
}
2022-11-27 19:37:16 -08:00
public override void _Ready()
{
base._Ready();
Array.Fill(_weights, 0);
}
2022-11-13 15:42:04 -08:00
/*
2022-11-10 20:29:53 -08:00
public override void _Process(double delta)
{
if ((_thinkTimeElapsed += delta) > ThinkTime)
{
_thinkTimeElapsed = 0;
Think();
}
2022-11-12 16:45:04 -08:00
Direction = _weightDirs[_bestWeightIdx];
2022-11-10 20:29:53 -08:00
//Direction = (Target.GlobalPosition - GlobalPosition).Normalized();
base._Process(delta);
}
2022-11-13 15:42:04 -08:00
*/
2022-11-10 20:29:53 -08:00
public override void _Draw()
{
2022-11-27 19:37:16 -08:00
#if DEBUG
2022-11-12 16:45:04 -08:00
for (int i = 0; i < 16; i++)
2022-11-10 20:29:53 -08:00
{
2022-11-27 19:37:16 -08:00
Vector2 vec = _weightDirs[i] * _weights[i] * 32;
2022-11-12 16:45:04 -08:00
Color c = Colors.Green;
if (_bestWeightIdx == i)
{
c = Colors.Blue;
}
else if (_weights[i] < 0)
{
c = Colors.Red;
vec = -vec;
}
DrawLine(Vector2.Zero, vec, c);
2022-11-10 20:29:53 -08:00
}
2022-11-27 19:37:16 -08:00
#endif
2022-11-10 20:29:53 -08:00
base._Draw();
}
2022-11-12 16:45:04 -08:00
protected virtual Character FindBestTarget()
2022-11-10 20:29:53 -08:00
{
float bestDist = float.MaxValue;
Character bestChar = null;
foreach (Node node in GetParent().GetChildren())
{
2022-11-19 21:21:12 -08:00
if (node is Character character && character.Faction != Faction)
2022-11-10 20:29:53 -08:00
{
float dist = Position.DistanceTo(character.Position);
if (dist < bestDist)
{
bestDist = dist;
bestChar = character;
}
}
}
return bestChar;
}
2022-11-13 15:42:04 -08:00
public void ThinkProcess(double delta)
{
if ((_thinkTimeElapsed += delta) > ThinkTime)
{
_thinkTimeElapsed = 0;
Think();
2022-11-27 19:37:16 -08:00
QueueRedraw();
2022-11-13 15:42:04 -08:00
}
2022-11-27 19:37:16 -08:00
Direction = _weightDirs[_bestWeightIdx];
2022-11-13 15:42:04 -08:00
}
2022-11-27 19:37:16 -08:00
public void UpdateWeights(Vector2 pos)
2022-11-10 20:29:53 -08:00
{
2022-11-27 19:37:16 -08:00
// FIXME: TODO: remove all the spaghetti
2022-11-19 21:21:12 -08:00
Vector2 dir = Target.Normalized();
2022-11-12 16:45:04 -08:00
float dist = GlobalPosition.DistanceSquaredTo(pos);
2022-11-10 20:29:53 -08:00
2023-01-29 12:05:44 -08:00
var spaceState = GetWorld2D().DirectSpaceState;
var exclude = new Godot.Collections.Array<Godot.Rid>();
2022-11-27 19:37:16 -08:00
exclude.Add(this.GetRid());
2022-11-19 21:21:12 -08:00
2022-11-12 16:45:04 -08:00
for (int i = 0; i < 16; i++)
2022-11-10 20:29:53 -08:00
{
2022-11-12 16:45:04 -08:00
float directDot = _weightDirs[i].Dot(dir);
2022-11-27 19:37:16 -08:00
// clamp dot from [-1, 1] to [0, 1]
2022-11-12 16:45:04 -08:00
directDot = (directDot + 1) / 2;
2022-11-27 19:37:16 -08:00
float strafeDot = Math.Abs(_weightDirs[i].Dot(dir.Clockwise90()));
2022-11-12 16:45:04 -08:00
float currDirDot = (_weightDirs[i].Dot(Direction) + 1) / 16;
2022-11-27 19:37:16 -08:00
strafeDot = Mathf.Pow((strafeDot + 1) / 2, 2) + currDirDot;
2022-11-12 16:45:04 -08:00
2022-11-19 21:21:12 -08:00
if (dist > 4096)
2022-11-12 16:45:04 -08:00
{
_weights[i] = directDot;
}
else if (dist > 64)
2022-11-10 20:29:53 -08:00
{
2022-11-27 19:37:16 -08:00
float dDotWeight = Mathf.Sqrt(dist / 4096);
float sDotWeight = 1 - dDotWeight;
_weights[i] = (dDotWeight * directDot) + (sDotWeight * strafeDot);
2022-11-10 20:29:53 -08:00
}
else
{
2022-11-27 19:37:16 -08:00
_weights[i] = strafeDot;
2022-11-10 20:29:53 -08:00
}
2022-11-27 19:37:16 -08:00
}
2022-11-12 16:45:04 -08:00
2022-11-27 19:37:16 -08:00
for (int i = 0; i < 16; i++)
{
var rayParams = new PhysicsRayQueryParameters2D
2022-11-10 20:29:53 -08:00
{
2022-11-27 19:37:16 -08:00
Exclude = exclude,
CollideWithBodies = true,
From = GlobalPosition,
To = GlobalPosition + (_weightDirs[i] * 24),
CollisionMask = 1 + 8
};
2022-11-12 16:45:04 -08:00
2022-11-27 19:37:16 -08:00
var result = spaceState.IntersectRay(rayParams);
2022-11-12 16:45:04 -08:00
2022-11-27 19:37:16 -08:00
// if we hit something
if (result.Count > 0)
{
// then we subtract the value of this from the other weights
float oldWeight = _weights[i];
for (int j = 0; j < 16; j++)
2022-11-10 20:29:53 -08:00
{
2022-11-27 19:37:16 -08:00
if (i == j)
2022-11-12 16:45:04 -08:00
{
2022-11-27 19:37:16 -08:00
_weights[i] = 0;
}
else
{
float dot = _weightDirs[i].Dot(_weightDirs[j]);
_weights[j] -= _weights[j] * dot;
2022-11-12 16:45:04 -08:00
}
2022-11-10 20:29:53 -08:00
}
}
2022-11-12 16:45:04 -08:00
}
2022-11-10 20:29:53 -08:00
2022-11-27 19:37:16 -08:00
float bestWeight = 0;
2022-11-12 16:45:04 -08:00
for (int i = 0; i < 16; i++)
{
2022-11-10 20:29:53 -08:00
if (_weights[i] > bestWeight)
{
2022-11-12 16:45:04 -08:00
_bestWeightIdx = i;
2022-11-27 19:37:16 -08:00
bestWeight = _weights[i];
}
}
}
public Vector2 GetDirection(Vector2 towards)
{
float directWeight;
float strafeWeight;
float dist = towards.Length();
Vector2 directDir = towards.Normalized();
Vector2 strafeDir;
float crossProduct = towards.Cross(Direction);
// strafeDir is either counter clockwise or clockwise depending on
// the direction the NPC is already traveling
// enemies might rapidly change direction if compared to 0
if (crossProduct < -1)
{
strafeDir = directDir.Counterclockwise90();
}
else
{
strafeDir = directDir.Counterclockwise90();
//strafeDir = directDir.Clockwise90();
}
// weights approach 1
// dy/dx = 1 - y
// y = 1 - e^(-x)
directWeight = 1 - Mathf.Pow(Mathf.E, -(dist / PreferredDistance));
strafeWeight = 1 - directWeight;
Vector2 midpoint = (directDir * directWeight)
.Midpoint(strafeDir * strafeWeight);
_blockingDir = GetBlocking();
midpoint += _blockingDir;
return midpoint.Normalized();
}
public Vector2 GetBlocking()
{
2023-01-29 12:05:44 -08:00
var spaceState = GetWorld2D().DirectSpaceState;
2022-11-27 19:37:16 -08:00
int rayLength = 16;
float[] weights = new float[16];
Vector2[] rays = new Vector2[16];
Vector2 net = Vector2.Zero;
for (int i = 0; i < 16; i++)
{
// cast ray and gets its length
// the length determines its strength
// exclude itself from raycasts
2023-01-29 12:05:44 -08:00
var exclude = new Godot.Collections.Array<Godot.Rid>();
2022-11-27 19:37:16 -08:00
exclude.Add(GetRid());
var rayParams = new PhysicsRayQueryParameters2D
{
CollisionMask = 9,
From = GlobalPosition,
To = _weightDirs[i] * rayLength,
Exclude = exclude
};
var result = spaceState.IntersectRay(rayParams);
if (result.Count > 0)
{
Vector2 position = (Vector2)result["position"];
float hitDist = GlobalPosition.DistanceTo(position);
float weight = rayLength - hitDist;
GD.Print(weight);
rays[i] = _weightDirs[i] * weight;
net += rays[i];
}
else
{
rays[i] = Vector2.Zero;
2022-11-10 20:29:53 -08:00
}
}
2022-11-12 16:45:04 -08:00
2022-11-27 19:37:16 -08:00
return net;
//return -Vector2Extensions.Midpoints(rays);
}
protected virtual void Think()
{
2023-03-19 13:54:48 -07:00
// TODO: the entity should wander if it doesn't find a best target
2023-03-19 23:36:36 -07:00
Character bestTarget = FindBestTarget();
if (bestTarget is not null)
2022-11-27 19:37:16 -08:00
{
2023-03-19 23:36:36 -07:00
Vector2 pos = FindBestTarget().GlobalPosition;
Target = pos - GlobalPosition;
Vector2 dir = Target;
float dist = GlobalPosition.DistanceSquaredTo(pos);
UpdateWeights(pos);
if (Target.LengthSquared() < 1024)
2022-11-27 19:37:16 -08:00
{
2023-03-19 23:36:36 -07:00
if (Inventory.SelectedItem is Weapon weapon)
{
UseCurrentItem();
}
2022-11-27 19:37:16 -08:00
}
}
2022-11-10 20:29:53 -08:00
}
}
}