clean up & some documentation

pull/6/head
John Montagu, the 4th Earl of Sandvich 2023-08-31 19:03:16 -07:00
parent 8e7750111b
commit ce093646aa
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
6 changed files with 120 additions and 176 deletions

View File

@ -26,9 +26,6 @@ public partial class Character : CharacterBody2D, IFaction
} }
} }
[Export]
public float Stealth { get; protected set; } = 0;
[Signal] [Signal]
public delegate void HealthChangedEventHandler(Events.HealthChangedArgs args); 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() public virtual void Die()
{ {
if (HurtAnimation.HasAnimation("death")) if (HurtAnimation.HasAnimation("death"))
@ -190,11 +190,20 @@ public partial class Character : CharacterBody2D, IFaction
NetImpulse += impulse / Mass; 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) public virtual void Stun(float time)
{ {
StunTime = Mathf.Max(time, StunTime); 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() protected virtual void DrawTarget()
{ {
Vector2 target = Target; Vector2 target = Target;
@ -213,6 +222,10 @@ public partial class Character : CharacterBody2D, IFaction
Inventory.Rotation = angle; 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() public void UseCurrentItem()
{ {
if (StunTime > 0 || !IsAlive) 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( protected virtual float ReceiveDamage(
float damage, float damage,
Character inflictor, Character inflictor,
@ -285,6 +301,9 @@ public partial class Character : CharacterBody2D, IFaction
_curDamageText.ShowText(); _curDamageText.ShowText();
} }
/// <summary>
/// Handles the character taking damage.
/// </summary>
protected virtual void OnReceivedDamage( protected virtual void OnReceivedDamage(
float damage, float damage,
Character inflictor, Character inflictor,
@ -348,6 +367,7 @@ public partial class Character : CharacterBody2D, IFaction
} }
} }
#if DEBUG
/// <summary> /// <summary>
/// For debugging purposes /// For debugging purposes
/// </summary> /// </summary>
@ -355,7 +375,12 @@ public partial class Character : CharacterBody2D, IFaction
{ {
OnReceivedDamage(damage, null, 0); 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() public virtual void Footstep()
{ {
if (GetNode("Effects/Footstep") is AudioStreamPlayer2D player) 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) public bool HasLineOfSight(Character character, bool excludeClip = false)
{ {
var exclude = new Godot.Collections.Array<Godot.Rid>(); var exclude = new Godot.Collections.Array<Godot.Rid>();

View File

@ -96,29 +96,9 @@ public partial class NPC : Character
}; };
} }
public override void _Draw() /// <summary>
{ /// Finds the NPC's best character to target.
#if DEBUG /// </summary>
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();
}
public virtual Character FindBestTarget() public virtual Character FindBestTarget()
{ {
float bestScore = float.MaxValue; float bestScore = float.MaxValue;
@ -136,21 +116,9 @@ public partial class NPC : Character
float score = 0; float score = 0;
score += Position.DistanceTo(character.Position); 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 (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; bestScore = score;
bestChar = character; bestChar = character;
} }
@ -170,132 +138,4 @@ public partial class NPC : Character
ThinkerStateMachine.PhysicsProcess(delta); ThinkerStateMachine.PhysicsProcess(delta);
base._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();
}
}
}
}
} }

View File

@ -51,10 +51,6 @@ public sealed partial class Player : Character
public override void _Process(double delta) public override void _Process(double delta)
{ {
base._Process(delta); base._Process(delta);
var mod = Sprite.SelfModulate;
mod.A = 1 - (Stealth / 2);
Sprite.SelfModulate = mod;
} }
public override void _Input(InputEvent @event) 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() public void Spawn()
{ {
Health = 100; Health = 100;

View File

@ -23,6 +23,12 @@ public abstract partial class Item : Node2D
public Character CharacterOwner { get; set; } 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; public virtual bool IsUsing => false;
/// <summary> /// <summary>

View File

@ -7,6 +7,9 @@ using SupaLidlGame.State.Weapon;
namespace SupaLidlGame.Items.Weapons; namespace SupaLidlGame.Items.Weapons;
/// <summary>
/// A basic melee weapon.
/// </summary>
public partial class Sword : Weapon, IParryable public partial class Sword : Weapon, IParryable
{ {
public bool IsAttacking { get; protected set; } public bool IsAttacking { get; protected set; }
@ -77,6 +80,9 @@ public partial class Sword : Weapon, IParryable
EnableParry(Time.GetTicksMsec()); EnableParry(Time.GetTicksMsec());
} }
/// <summary>
/// Makes this melee weapon be able to parry and be parried.
/// </summary>
public void EnableParry(ulong parryTimeOrigin) public void EnableParry(ulong parryTimeOrigin)
{ {
IsParried = false; IsParried = false;
@ -84,6 +90,9 @@ public partial class Sword : Weapon, IParryable
ParryTimeOrigin = parryTimeOrigin; ParryTimeOrigin = parryTimeOrigin;
} }
/// <summary>
/// Makes this melee weapon be able to parry and be parried.
/// </summary>
public void DisableParry() public void DisableParry()
{ {
IsParryable = false; IsParryable = false;
@ -113,6 +122,10 @@ public partial class Sword : Weapon, IParryable
base.DeuseAlt(); 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() public void Attack()
{ {
//RemainingAttackTime = AttackTime; //RemainingAttackTime = AttackTime;
@ -120,6 +133,9 @@ public partial class Sword : Weapon, IParryable
Hitbox.IsDisabled = false; Hitbox.IsDisabled = false;
} }
/// <summary>
/// Disables the weapon's hitbox and processes all hurtboxes it hit.
/// </summary>
public void Deattack() public void Deattack()
{ {
IsAttacking = false; IsAttacking = false;
@ -161,6 +177,9 @@ public partial class Sword : Weapon, IParryable
base._Process(delta); base._Process(delta);
} }
/// <summary>
/// Processes all hits and applies damages to hurtboxes.
/// </summary>
public void ProcessHits() public void ProcessHits()
{ {
if (IsParried) 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() public void Stun()
{ {
IsParried = true; IsParried = true;
@ -238,9 +261,4 @@ public partial class Sword : Weapon, IParryable
} }
} }
} }
protected void SetAnimationCondition(string condition, bool value)
{
}
} }

View File

@ -18,11 +18,28 @@ public abstract partial class StateMachine<T> : Node where T : Node, IState<T>
ChangeState(InitialState); 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) public virtual bool ChangeState(T nextState)
{ {
return ChangeState(nextState, out Stack<T> _); 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) public bool ChangeState(T nextState, out T finalState)
{ {
var status = ChangeState(nextState, out Stack<T> states); 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; 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) public bool ChangeState(T nextState, out Stack<T> states)
{ {
states = new Stack<T>(); states = new Stack<T>();
@ -71,14 +97,35 @@ public abstract partial class StateMachine<T> : Node where T : Node, IState<T>
} }
/// <summary> /// <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> /// </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 public bool ChangeState<U>(out U state) where U : T
{ {
state = this.FindChildOfType<U>(); state = this.FindChildOfType<U>();
return ChangeState(state); 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) public bool ChangeState(string name, out T state)
{ {
state = GetNode<T>(name); state = GetNode<T>(name);