clean up & some documentation
parent
8e7750111b
commit
ce093646aa
|
@ -26,9 +26,6 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
}
|
||||
}
|
||||
|
||||
[Export]
|
||||
public float Stealth { get; protected set; } = 0;
|
||||
|
||||
[Signal]
|
||||
public delegate void HealthChangedEventHandler(Events.HealthChangedArgs args);
|
||||
|
||||
|
@ -168,6 +165,9 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the <c>Character</c>'s death.
|
||||
/// </summary>
|
||||
public virtual void Die()
|
||||
{
|
||||
if (HurtAnimation.HasAnimation("death"))
|
||||
|
@ -190,11 +190,20 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
NetImpulse += impulse / Mass;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stuns the <c>Chararacter</c> for an amount of time. If
|
||||
/// <paramref name="time"/> is less than the <c>Character</c>'s current
|
||||
/// stun time left, it will have no effect.
|
||||
/// </summary>
|
||||
public virtual void Stun(float time)
|
||||
{
|
||||
StunTime = Mathf.Max(time, StunTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the character so that its sprite and inventory items face the
|
||||
/// character's direction.
|
||||
/// </summary>
|
||||
protected virtual void DrawTarget()
|
||||
{
|
||||
Vector2 target = Target;
|
||||
|
@ -213,6 +222,10 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
Inventory.Rotation = angle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use the current item the character is using. Prefer to call this over
|
||||
/// <c>Item.Use</c> as it will check if the character is stunned or alive.
|
||||
/// </summary>
|
||||
public void UseCurrentItem()
|
||||
{
|
||||
if (StunTime > 0 || !IsAlive)
|
||||
|
@ -264,6 +277,9 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Override this method to modify the damage the character takes.
|
||||
/// </summary>
|
||||
protected virtual float ReceiveDamage(
|
||||
float damage,
|
||||
Character inflictor,
|
||||
|
@ -285,6 +301,9 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
_curDamageText.ShowText();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the character taking damage.
|
||||
/// </summary>
|
||||
protected virtual void OnReceivedDamage(
|
||||
float damage,
|
||||
Character inflictor,
|
||||
|
@ -348,6 +367,7 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// For debugging purposes
|
||||
/// </summary>
|
||||
|
@ -355,7 +375,12 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
{
|
||||
OnReceivedDamage(damage, null, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Plays a footstep sound. This should be called through an
|
||||
/// <c>AnimationPlayer</c> to sync sounds with animations.
|
||||
/// </summary>
|
||||
public virtual void Footstep()
|
||||
{
|
||||
if (GetNode("Effects/Footstep") is AudioStreamPlayer2D player)
|
||||
|
@ -364,6 +389,15 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns whether the <c>Character</c> has line of sight with
|
||||
/// <paramref name="character"/>.
|
||||
/// </summary>
|
||||
/// <param name="character">The character to check for LOS</param>
|
||||
/// <param name="excludeClip">
|
||||
/// Determines whether the raycast should pass through world clips (physics
|
||||
/// layer 5)
|
||||
/// </param>
|
||||
public bool HasLineOfSight(Character character, bool excludeClip = false)
|
||||
{
|
||||
var exclude = new Godot.Collections.Array<Godot.Rid>();
|
||||
|
|
|
@ -96,29 +96,9 @@ public partial class NPC : Character
|
|||
};
|
||||
}
|
||||
|
||||
public override void _Draw()
|
||||
{
|
||||
#if DEBUG
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
Vector2 vec = _weightDirs[i] * _weights[i] * 32;
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
|
||||
base._Draw();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the NPC's best character to target.
|
||||
/// </summary>
|
||||
public virtual Character FindBestTarget()
|
||||
{
|
||||
float bestScore = float.MaxValue;
|
||||
|
@ -136,21 +116,9 @@ public partial class NPC : Character
|
|||
|
||||
float score = 0;
|
||||
score += Position.DistanceTo(character.Position);
|
||||
score *= (character.Stealth + 1);
|
||||
|
||||
// if the character has enough stealth, the dot product of the
|
||||
// enemy's current direction and to the character will affect
|
||||
// the score
|
||||
// TODO: implement
|
||||
|
||||
if (score < bestScore)
|
||||
{
|
||||
// if the character has enough stealth, they won't be
|
||||
// targeted if the NPC is not able to see
|
||||
if (!HasLineOfSight(character) && character.Stealth >= 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
bestScore = score;
|
||||
bestChar = character;
|
||||
}
|
||||
|
@ -170,132 +138,4 @@ public partial class NPC : Character
|
|||
ThinkerStateMachine.PhysicsProcess(delta);
|
||||
base._PhysicsProcess(delta);
|
||||
}
|
||||
|
||||
public void ThinkProcess(double delta)
|
||||
{
|
||||
if ((_thinkTimeElapsed += delta) > ThinkTime)
|
||||
{
|
||||
_thinkTimeElapsed = 0;
|
||||
Think();
|
||||
#if DEBUG_NPC
|
||||
QueueRedraw();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!ShouldMove || (!ShouldMoveWhenUsingItem && Inventory.IsUsingItem))
|
||||
{
|
||||
Direction = Vector2.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
Direction = _weightDirs[_bestWeightIdx];
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateWeights(Vector2 pos)
|
||||
{
|
||||
// FIXME: TODO: remove all the spaghetti
|
||||
Vector2 dir = Target.Normalized();
|
||||
float distSq = GlobalPosition.DistanceSquaredTo(pos);
|
||||
|
||||
var spaceState = GetWorld2D().DirectSpaceState;
|
||||
var exclude = new Godot.Collections.Array<Godot.Rid>();
|
||||
exclude.Add(this.GetRid());
|
||||
|
||||
// calculate weights based on distance
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
float directDot = _weightDirs[i].Dot(dir);
|
||||
// clamp dot from [-1, 1] to [0, 1]
|
||||
directDot = (directDot + 1) / 2;
|
||||
|
||||
float strafeDot = Math.Abs(_weightDirs[i].Dot(dir.Clockwise90()));
|
||||
float currDirDot = (_weightDirs[i].Dot(Direction) + 1) / 16;
|
||||
strafeDot = Mathf.Pow((strafeDot + 1) / 2, 2) + currDirDot;
|
||||
|
||||
// favor strafing when getting closer
|
||||
if (distSq > _preferredWeightDistanceSq)
|
||||
{
|
||||
_weights[i] = directDot;
|
||||
}
|
||||
else if (distSq > _maxWeightDistanceSq)
|
||||
{
|
||||
float dDotWeight = Mathf.Sqrt(distSq / 4096);
|
||||
float sDotWeight = 1 - dDotWeight;
|
||||
_weights[i] = (dDotWeight * directDot) +
|
||||
(sDotWeight * strafeDot);
|
||||
}
|
||||
else
|
||||
{
|
||||
_weights[i] = strafeDot;
|
||||
}
|
||||
}
|
||||
|
||||
// subtract weights that collide
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
var rayParams = new PhysicsRayQueryParameters2D
|
||||
{
|
||||
Exclude = exclude,
|
||||
CollideWithBodies = true,
|
||||
From = GlobalPosition,
|
||||
To = GlobalPosition + (_weightDirs[i] * 24),
|
||||
CollisionMask = 1 + 2 + 16
|
||||
};
|
||||
|
||||
var result = spaceState.IntersectRay(rayParams);
|
||||
|
||||
// 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++)
|
||||
{
|
||||
if (i == j)
|
||||
{
|
||||
_weights[i] = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
float dot = _weightDirs[i].Dot(_weightDirs[j]);
|
||||
_weights[j] -= _weights[j] * dot;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
float bestWeight = 0;
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
if (_weights[i] > bestWeight)
|
||||
{
|
||||
_bestWeightIdx = i;
|
||||
bestWeight = _weights[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Think()
|
||||
{
|
||||
// TODO: the entity should wander if it doesn't find a best target
|
||||
Character bestTarget = FindBestTarget();
|
||||
if (bestTarget is not null)
|
||||
{
|
||||
Vector2 pos = FindBestTarget().GlobalPosition;
|
||||
Target = pos - GlobalPosition;
|
||||
Vector2 dir = Target;
|
||||
float dist = GlobalPosition.DistanceSquaredTo(pos);
|
||||
UpdateWeights(pos);
|
||||
|
||||
if (dist < 1600 && CanAttack)
|
||||
{
|
||||
if (Inventory.SelectedItem is Weapon weapon)
|
||||
{
|
||||
UseCurrentItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,10 +51,6 @@ public sealed partial class Player : Character
|
|||
public override void _Process(double delta)
|
||||
{
|
||||
base._Process(delta);
|
||||
|
||||
var mod = Sprite.SelfModulate;
|
||||
mod.A = 1 - (Stealth / 2);
|
||||
Sprite.SelfModulate = mod;
|
||||
}
|
||||
|
||||
public override void _Input(InputEvent @event)
|
||||
|
@ -65,6 +61,9 @@ public sealed partial class Player : Character
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Respawns the player with full health and plays spawn animation
|
||||
/// </summary>
|
||||
public void Spawn()
|
||||
{
|
||||
Health = 100;
|
||||
|
|
|
@ -23,6 +23,12 @@ public abstract partial class Item : Node2D
|
|||
|
||||
public Character CharacterOwner { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the item is being used. This property determines if
|
||||
/// a character can use another item or not.
|
||||
/// See <see cref="Character.UseCurrentItem"/>
|
||||
/// </summary>
|
||||
///
|
||||
public virtual bool IsUsing => false;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -7,6 +7,9 @@ using SupaLidlGame.State.Weapon;
|
|||
|
||||
namespace SupaLidlGame.Items.Weapons;
|
||||
|
||||
/// <summary>
|
||||
/// A basic melee weapon.
|
||||
/// </summary>
|
||||
public partial class Sword : Weapon, IParryable
|
||||
{
|
||||
public bool IsAttacking { get; protected set; }
|
||||
|
@ -77,6 +80,9 @@ public partial class Sword : Weapon, IParryable
|
|||
EnableParry(Time.GetTicksMsec());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes this melee weapon be able to parry and be parried.
|
||||
/// </summary>
|
||||
public void EnableParry(ulong parryTimeOrigin)
|
||||
{
|
||||
IsParried = false;
|
||||
|
@ -84,6 +90,9 @@ public partial class Sword : Weapon, IParryable
|
|||
ParryTimeOrigin = parryTimeOrigin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes this melee weapon be able to parry and be parried.
|
||||
/// </summary>
|
||||
public void DisableParry()
|
||||
{
|
||||
IsParryable = false;
|
||||
|
@ -113,6 +122,10 @@ public partial class Sword : Weapon, IParryable
|
|||
base.DeuseAlt();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables the weapon's hitbox. Prefer to call this from a state machine
|
||||
/// rather than managing state through the weapon script.
|
||||
/// </summary>
|
||||
public void Attack()
|
||||
{
|
||||
//RemainingAttackTime = AttackTime;
|
||||
|
@ -120,6 +133,9 @@ public partial class Sword : Weapon, IParryable
|
|||
Hitbox.IsDisabled = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disables the weapon's hitbox and processes all hurtboxes it hit.
|
||||
/// </summary>
|
||||
public void Deattack()
|
||||
{
|
||||
IsAttacking = false;
|
||||
|
@ -161,6 +177,9 @@ public partial class Sword : Weapon, IParryable
|
|||
base._Process(delta);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes all hits and applies damages to hurtboxes.
|
||||
/// </summary>
|
||||
public void ProcessHits()
|
||||
{
|
||||
if (IsParried)
|
||||
|
@ -199,6 +218,10 @@ public partial class Sword : Weapon, IParryable
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stuns the wepaon holder. This is unique to swords and melee weapons
|
||||
/// if they can parry.
|
||||
/// </summary>
|
||||
public void Stun()
|
||||
{
|
||||
IsParried = true;
|
||||
|
@ -238,9 +261,4 @@ public partial class Sword : Weapon, IParryable
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetAnimationCondition(string condition, bool value)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,11 +18,28 @@ public abstract partial class StateMachine<T> : Node where T : Node, IState<T>
|
|||
ChangeState(InitialState);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the state of the <c>StateMachine</c>.
|
||||
/// </summary>
|
||||
/// <param name="nextState">The next state to transition to.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="nextState" /> is a
|
||||
/// valid state, otherwise <see langword="false" />
|
||||
/// </returns>
|
||||
public virtual bool ChangeState(T nextState)
|
||||
{
|
||||
return ChangeState(nextState, out Stack<T> _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the state of the <c>StateMachine</c>.
|
||||
/// </summary>
|
||||
/// <param name="nextState">The next state to transition to.</param>
|
||||
/// <param name="finalState">The actual state.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="nextState" /> is a
|
||||
/// valid state, otherwise <see langword="false" />
|
||||
/// </returns>
|
||||
public bool ChangeState(T nextState, out T finalState)
|
||||
{
|
||||
var status = ChangeState(nextState, out Stack<T> states);
|
||||
|
@ -30,6 +47,15 @@ public abstract partial class StateMachine<T> : Node where T : Node, IState<T>
|
|||
return status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the state of the <c>StateMachine</c>.
|
||||
/// </summary>
|
||||
/// <param name="nextState">The next state to transition to.</param>
|
||||
/// <param name="states">Stack of all states that transitioned/proxied.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="nextState" /> is a
|
||||
/// valid state, otherwise <see langword="false" />
|
||||
/// </returns>
|
||||
public bool ChangeState(T nextState, out Stack<T> states)
|
||||
{
|
||||
states = new Stack<T>();
|
||||
|
@ -71,14 +97,35 @@ public abstract partial class StateMachine<T> : Node where T : Node, IState<T>
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the current state to a state of type U which must inherit from T.
|
||||
/// Changes the state of the <c>StateMachine</c> of type
|
||||
/// <typeparamref name="U" /> which must inherit from
|
||||
/// <typeparamref name="T" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="U">The type of the state to transition to.</typeparam>
|
||||
/// <param name="state">The resulting state to be transitioned to.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="nextState" /> is a
|
||||
/// valid state, otherwise <see langword="false" />
|
||||
/// </returns>
|
||||
public bool ChangeState<U>(out U state) where U : T
|
||||
{
|
||||
state = this.FindChildOfType<U>();
|
||||
return ChangeState(state);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changes the state of the <c>StateMachine</c> with node name
|
||||
/// <paramref name="name" />.
|
||||
/// </summary>
|
||||
/// <typeparam name="U">The type of the state to transition to.</typeparam>
|
||||
/// <param name="name">
|
||||
/// The name of the <typeparamref name="T" /> node.
|
||||
/// </param>
|
||||
/// <param name="state">The resulting state to be transitioned to.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if <paramref name="nextState" /> is a
|
||||
/// valid state, otherwise <see langword="false" />
|
||||
/// </returns>
|
||||
public bool ChangeState(string name, out T state)
|
||||
{
|
||||
state = GetNode<T>(name);
|
||||
|
|
Loading…
Reference in New Issue