Merge branch 'master' into controller-support
commit
6c5bc4edac
|
@ -1,4 +1,4 @@
|
|||
[gd_resource type="ParticleProcessMaterial" load_steps=5 format=3 uid="uid://cbfaqolx1ydvv"]
|
||||
[gd_resource type="ParticleProcessMaterial" load_steps=7 format=3 uid="uid://cbfaqolx1ydvv"]
|
||||
|
||||
[sub_resource type="Gradient" id="Gradient_44upg"]
|
||||
offsets = PackedFloat32Array(0, 0.5)
|
||||
|
@ -7,16 +7,27 @@ colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0)
|
|||
[sub_resource type="GradientTexture1D" id="GradientTexture1D_droiy"]
|
||||
gradient = SubResource("Gradient_44upg")
|
||||
|
||||
[sub_resource type="Curve" id="Curve_4kf4j"]
|
||||
_data = [Vector2(0, 0.5), 0.0, 0.0, 0, 0, Vector2(0.1, 1), 0.0, 0.0, 0, 0, Vector2(1, 0.5), 0.0, 0.0, 0, 0]
|
||||
point_count = 3
|
||||
[sub_resource type="Curve" id="Curve_mx7gl"]
|
||||
_data = [Vector2(0, 0.3), 0.0, 0.0, 0, 1, Vector2(1, 0.3), 0.0, 0.0, 1, 0]
|
||||
point_count = 2
|
||||
|
||||
[sub_resource type="CurveTexture" id="CurveTexture_qqrjb"]
|
||||
curve = SubResource("Curve_4kf4j")
|
||||
[sub_resource type="Curve" id="Curve_lfa60"]
|
||||
_data = [Vector2(0.1, 1), 0.0, -1.75, 0, 1, Vector2(0.5, 0.3), -1.75, 0.0, 1, 0]
|
||||
point_count = 2
|
||||
|
||||
[sub_resource type="Curve" id="Curve_3iug1"]
|
||||
_data = [Vector2(0, 1), 0.0, 0.0, 0, 1, Vector2(1, 1), 0.0, 0.0, 1, 0]
|
||||
point_count = 2
|
||||
|
||||
[sub_resource type="CurveXYZTexture" id="CurveXYZTexture_xryvh"]
|
||||
curve_x = SubResource("Curve_mx7gl")
|
||||
curve_y = SubResource("Curve_lfa60")
|
||||
curve_z = SubResource("Curve_3iug1")
|
||||
|
||||
[resource]
|
||||
emission_shape = 1
|
||||
emission_sphere_radius = 4.0
|
||||
particle_flag_align_y = true
|
||||
particle_flag_disable_z = true
|
||||
spread = 180.0
|
||||
gravity = Vector3(0, 0, 0)
|
||||
|
@ -26,7 +37,7 @@ orbit_velocity_min = 0.0
|
|||
orbit_velocity_max = 0.0
|
||||
linear_accel_min = -512.0
|
||||
linear_accel_max = -512.0
|
||||
scale_min = 2.0
|
||||
scale_max = 2.0
|
||||
scale_curve = SubResource("CurveTexture_qqrjb")
|
||||
scale_min = 0.1
|
||||
scale_max = 0.1
|
||||
scale_curve = SubResource("CurveXYZTexture_xryvh")
|
||||
color_ramp = SubResource("GradientTexture1D_droiy")
|
||||
|
|
|
@ -35,7 +35,6 @@ public partial class ConnectorBox : Area2D
|
|||
{
|
||||
BodyEntered += (Node2D body) =>
|
||||
{
|
||||
GD.Print(body.Name + " entered");
|
||||
if (body is Player && InteractionTrigger is null)
|
||||
{
|
||||
OnInteraction();
|
||||
|
|
|
@ -11,6 +11,7 @@ public partial class Hurtbox : BoundingBox, IFaction
|
|||
float damage,
|
||||
Character inflictor,
|
||||
float knockback,
|
||||
Items.Weapon weapon = null,
|
||||
Vector2 knockbackDir = default);
|
||||
|
||||
/// <summary>
|
||||
|
@ -30,7 +31,6 @@ public partial class Hurtbox : BoundingBox, IFaction
|
|||
{
|
||||
InvincibilityTimer.Timeout += () =>
|
||||
{
|
||||
GD.Print("invincibility off");
|
||||
Monitorable = true;
|
||||
};
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ public partial class Hurtbox : BoundingBox, IFaction
|
|||
float damage,
|
||||
Character inflictor,
|
||||
float knockback,
|
||||
Items.Weapon weapon = default,
|
||||
Vector2 knockbackOrigin = default,
|
||||
Vector2 knockbackVector = default)
|
||||
{
|
||||
|
@ -77,7 +78,6 @@ public partial class Hurtbox : BoundingBox, IFaction
|
|||
InvincibilityTimer.Start();
|
||||
//Monitorable = false;
|
||||
SetDeferred("monitorable", false);
|
||||
GD.Print("invincible");
|
||||
}
|
||||
|
||||
EmitSignal(
|
||||
|
@ -85,6 +85,7 @@ public partial class Hurtbox : BoundingBox, IFaction
|
|||
damage,
|
||||
inflictor,
|
||||
knockback,
|
||||
weapon,
|
||||
knockbackDir);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,10 +301,14 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
_curDamageText.ShowText();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the character taking damage.
|
||||
/// </summary>
|
||||
protected virtual void OnReceivedDamage(
|
||||
float damage,
|
||||
Character inflictor,
|
||||
float knockback,
|
||||
Weapon weapon = null,
|
||||
Vector2 knockbackDir = default)
|
||||
{
|
||||
if (Health <= 0)
|
||||
|
@ -325,7 +345,6 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
if (this.GetNode("Effects/HurtSound") is AudioStreamPlayer2D sound)
|
||||
{
|
||||
// very small pitch deviation
|
||||
GD.Print("hurt sound");
|
||||
sound.At(GlobalPosition).WithPitchDeviation(0.125f).PlayOneShot();
|
||||
}
|
||||
|
||||
|
@ -334,11 +353,17 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
Attacker = inflictor,
|
||||
OldHealth = oldHealth,
|
||||
NewHealth = Health,
|
||||
Weapon = weapon,
|
||||
Damage = damage,
|
||||
};
|
||||
|
||||
EmitSignal(SignalName.Hurt, args);
|
||||
|
||||
if (inflictor is Player)
|
||||
{
|
||||
EmitPlayerHitSignal(args);
|
||||
}
|
||||
|
||||
if (Health <= 0)
|
||||
{
|
||||
EmitSignal(SignalName.Death, args);
|
||||
|
@ -348,6 +373,26 @@ public partial class Character : CharacterBody2D, IFaction
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a HurtArgs to HitArgs if the attacker was a player and emits
|
||||
/// the <c>EventBus.PlayerHit</c> signal.
|
||||
/// </summary>
|
||||
private void EmitPlayerHitSignal(Events.HurtArgs args)
|
||||
{
|
||||
var newArgs = new Events.HitArgs
|
||||
{
|
||||
OldHealth = args.OldHealth,
|
||||
NewHealth = args.NewHealth,
|
||||
Damage = args.Damage,
|
||||
Weapon = args.Weapon,
|
||||
Victim = this,
|
||||
};
|
||||
|
||||
var bus = Events.EventBus.Instance;
|
||||
bus.EmitSignal(Events.EventBus.SignalName.PlayerHit, newArgs);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
/// <summary>
|
||||
/// For debugging purposes
|
||||
/// </summary>
|
||||
|
@ -355,7 +400,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 +414,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ public sealed partial class Player : Character
|
|||
{
|
||||
private string _spriteAnim;
|
||||
|
||||
public Vector2 DesiredTarget { get; set; }
|
||||
|
||||
[Export]
|
||||
public PlayerCamera Camera { get; set; }
|
||||
|
||||
|
@ -25,11 +27,6 @@ public sealed partial class Player : Character
|
|||
public override void _Ready()
|
||||
{
|
||||
InteractionRay = GetNode<InteractionRay>("Direction2D/InteractionRay");
|
||||
Death += async (Events.HurtArgs args) =>
|
||||
{
|
||||
HurtAnimation.Play("death");
|
||||
await ToSignal(HurtAnimation, "animation_finished");
|
||||
};
|
||||
|
||||
base._Ready();
|
||||
|
||||
|
@ -51,10 +48,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 +58,9 @@ public sealed partial class Player : Character
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Respawns the player with full health and plays spawn animation
|
||||
/// </summary>
|
||||
public void Spawn()
|
||||
{
|
||||
Health = 100;
|
||||
|
@ -87,6 +83,7 @@ public sealed partial class Player : Character
|
|||
float damage,
|
||||
Character inflictor,
|
||||
float knockback,
|
||||
Items.Weapon weapon = null,
|
||||
Vector2 knockbackDir = default)
|
||||
{
|
||||
if (damage >= 10 && IsAlive)
|
||||
|
@ -97,21 +94,22 @@ public sealed partial class Player : Character
|
|||
GetNode<GpuParticles2D>("Effects/HurtParticles")
|
||||
.SetDirection(knockbackDir);
|
||||
|
||||
base.OnReceivedDamage(damage, inflictor, knockback, knockbackDir);
|
||||
base.OnReceivedDamage(damage,
|
||||
inflictor,
|
||||
knockback,
|
||||
weapon,
|
||||
knockbackDir);
|
||||
}
|
||||
|
||||
public override void Die()
|
||||
{
|
||||
GD.Print("died");
|
||||
//base.Die();
|
||||
HurtAnimation.Play("death");
|
||||
}
|
||||
|
||||
protected override void DrawTarget()
|
||||
{
|
||||
base.DrawTarget();
|
||||
DirectionMarker.GlobalRotation = DirectionMarker.GlobalPosition
|
||||
.DirectionTo(GetGlobalMousePosition())
|
||||
.Angle();
|
||||
DirectionMarker.GlobalRotation = DesiredTarget.Angle();
|
||||
}
|
||||
|
||||
public override void Footstep()
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
[gd_scene load_steps=63 format=3 uid="uid://b2254pup8k161"]
|
||||
[gd_scene load_steps=66 format=3 uid="uid://b2254pup8k161"]
|
||||
|
||||
[ext_resource type="Script" path="res://Characters/Player.cs" id="1_flygr"]
|
||||
[ext_resource type="Shader" path="res://Shaders/Flash.gdshader" id="2_ngsgt"]
|
||||
[ext_resource type="Texture2D" uid="uid://dpepm54hjuyga" path="res://Assets/Sprites/Characters/forsen-hand.png" id="3_3dqh7"]
|
||||
[ext_resource type="Texture2D" uid="uid://bej8thq7ruyty" path="res://Assets/Sprites/Characters/forsen2.png" id="4_5vird"]
|
||||
[ext_resource type="Script" path="res://Utils/PlayerStats.cs" id="4_06oya"]
|
||||
[ext_resource type="PackedScene" uid="uid://cl56eadpklnbo" path="res://Utils/PlayerCamera.tscn" id="4_ym125"]
|
||||
[ext_resource type="Script" path="res://State/Character/CharacterStateMachine.cs" id="5_rgckv"]
|
||||
[ext_resource type="Script" path="res://Utils/Values/DoubleValue.cs" id="5_txl0r"]
|
||||
[ext_resource type="Script" path="res://State/Character/CharacterDashState.cs" id="6_rft7p"]
|
||||
[ext_resource type="Script" path="res://Utils/Values/IntValue.cs" id="6_sunc5"]
|
||||
[ext_resource type="Script" path="res://State/Character/PlayerIdleState.cs" id="6_wkfdm"]
|
||||
[ext_resource type="PackedScene" uid="uid://dvqap2uhcah63" path="res://Items/Weapons/Sword.tscn" id="7_4rxuv"]
|
||||
[ext_resource type="Script" path="res://State/Character/PlayerMoveState.cs" id="7_dfqd8"]
|
||||
|
@ -331,6 +334,15 @@ StateMachine = NodePath("StateMachine")
|
|||
Hurtbox = NodePath("Hurtbox")
|
||||
Faction = 1
|
||||
|
||||
[node name="Stats" type="Node" parent="."]
|
||||
script = ExtResource("4_06oya")
|
||||
|
||||
[node name="XP" type="Node" parent="Stats"]
|
||||
script = ExtResource("5_txl0r")
|
||||
|
||||
[node name="Level" type="Node" parent="Stats"]
|
||||
script = ExtResource("6_sunc5")
|
||||
|
||||
[node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")]
|
||||
script = ExtResource("5_rgckv")
|
||||
InitialState = NodePath("Idle")
|
||||
|
|
|
@ -71,7 +71,6 @@ public partial class DynamicDoor : StaticBody2D
|
|||
{
|
||||
if (anim.TrackGetPath(i) == nodePath)
|
||||
{
|
||||
GD.Print($"Disabled anim for {nodePath}");
|
||||
anim.TrackSetEnabled(i, isEnabled);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,8 +43,11 @@ public partial class Projectile : RigidBody2D
|
|||
[Export]
|
||||
public double Delay { get; set; } = 0;
|
||||
|
||||
[System.Obsolete]
|
||||
public Character Character { get; set; }
|
||||
|
||||
public Items.Weapon Weapon { get; set; }
|
||||
|
||||
public bool IsDead { get; set; }
|
||||
|
||||
public override void _Ready()
|
||||
|
@ -87,8 +90,9 @@ public partial class Projectile : RigidBody2D
|
|||
{
|
||||
hurtbox.InflictDamage(
|
||||
Hitbox.Damage,
|
||||
Character,
|
||||
Hitbox.Inflictor,
|
||||
Hitbox.Knockback,
|
||||
weapon: Weapon,
|
||||
knockbackVector: Direction
|
||||
);
|
||||
EmitSignal(SignalName.Hit, box);
|
||||
|
|
|
@ -63,6 +63,7 @@ public partial class ShungiteSpike : Projectile
|
|||
float damage,
|
||||
Characters.Character inflictor,
|
||||
float knockback,
|
||||
Items.Weapon weapon,
|
||||
Vector2 knockbackDir)
|
||||
{
|
||||
// if we were hit by the player before the spike freezes,
|
||||
|
|
|
@ -4,6 +4,8 @@ namespace SupaLidlGame.Events;
|
|||
|
||||
public partial class EventBus : Node
|
||||
{
|
||||
public static EventBus Instance { get; private set; }
|
||||
|
||||
[Signal]
|
||||
public delegate void RequestMoveToAreaEventHandler(RequestAreaArgs args);
|
||||
|
||||
|
@ -16,6 +18,15 @@ public partial class EventBus : Node
|
|||
[Signal]
|
||||
public delegate void PlayerHurtEventHandler(HurtArgs args);
|
||||
|
||||
[Signal]
|
||||
public delegate void PlayerHitEventHandler(HitArgs args);
|
||||
|
||||
[Signal]
|
||||
public delegate void PlayerXPChangedEventHandler(double xp);
|
||||
|
||||
[Signal]
|
||||
public delegate void PlayerLevelChangedEventHandler(int level);
|
||||
|
||||
[Signal]
|
||||
public delegate void PlayerHealthChangedEventHandler(HealthChangedArgs args);
|
||||
|
||||
|
@ -37,5 +48,10 @@ public partial class EventBus : Node
|
|||
public override void _Ready()
|
||||
{
|
||||
ProcessMode = ProcessModeEnum.Always;
|
||||
if (Instance is not null)
|
||||
{
|
||||
throw new MultipleSingletonsException();
|
||||
}
|
||||
Instance = this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
namespace SupaLidlGame.Events;
|
||||
|
||||
public partial class HitArgs : HurtArgs
|
||||
{
|
||||
public Characters.Character Victim { get; set; }
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace SupaLidlGame;
|
||||
|
||||
public class MultipleSingletonsException : System.Exception
|
||||
{
|
||||
|
||||
}
|
|
@ -64,7 +64,6 @@ public partial class Inventory : Node2D
|
|||
{
|
||||
if (child is Item item)
|
||||
{
|
||||
GD.Print("Adding item " + item.Name);
|
||||
AddItem(item);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -60,6 +60,9 @@ public abstract partial class Weapon : Item
|
|||
[Export]
|
||||
public float MaxDistanceHint { get; set; }
|
||||
|
||||
[Export]
|
||||
public float PlayerLevelGain { get; set; }
|
||||
|
||||
[Export]
|
||||
public Sprite2D HandAnchor { get; set; }
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=21 format=3 uid="uid://p7oijq6dbvvk"]
|
||||
[gd_scene load_steps=22 format=3 uid="uid://p7oijq6dbvvk"]
|
||||
|
||||
[ext_resource type="Script" path="res://Items/Weapons/Sword.cs" id="1_1oyma"]
|
||||
[ext_resource type="Script" path="res://State/Weapon/WeaponStateMachine.cs" id="2_c41ov"]
|
||||
|
@ -6,6 +6,7 @@
|
|||
[ext_resource type="Script" path="res://State/Weapon/SwordAnticipateState.cs" id="4_t7af2"]
|
||||
[ext_resource type="Script" path="res://State/Weapon/SwordAttackState.cs" id="5_i5v42"]
|
||||
[ext_resource type="Texture2D" uid="uid://o7enu13gvji5" path="res://Assets/Sprites/doc-lance.png" id="6_7t87o"]
|
||||
[ext_resource type="Texture2D" uid="uid://d75jkoev5v3w" path="res://Assets/Sprites/Particles/circle-64.png" id="8_gufhv"]
|
||||
[ext_resource type="Material" uid="uid://cbfaqolx1ydvv" path="res://Assets/Sprites/Particles/ParryParticles.tres" id="8_y2qyn"]
|
||||
[ext_resource type="PackedScene" uid="uid://du5vhccg75nrq" path="res://BoundingBoxes/Hitbox.tscn" id="9_buajm"]
|
||||
[ext_resource type="Texture2D" uid="uid://cmvh6pc71ir1m" path="res://Assets/Sprites/sword-swing-large.png" id="11_46l1i"]
|
||||
|
@ -34,7 +35,7 @@ orbit_velocity_max = 0.0
|
|||
scale_min = 2.0
|
||||
scale_max = 2.0
|
||||
scale_curve = SubResource("CurveTexture_383y7")
|
||||
color = Color(1, 0, 1, 1)
|
||||
color = Color(0.560784, 0.145098, 0.180392, 1)
|
||||
|
||||
[sub_resource type="Animation" id="Animation_b7327"]
|
||||
length = 0.001
|
||||
|
@ -239,17 +240,15 @@ texture = ExtResource("6_7t87o")
|
|||
|
||||
[node name="ParryParticles" type="GPUParticles2D" parent="Anchor/Node2D/Sprite2D"]
|
||||
modulate = Color(1.2, 1.2, 1.2, 1)
|
||||
position = Vector2(-0.221825, -3.12132)
|
||||
position = Vector2(0, -3)
|
||||
rotation = 0.785398
|
||||
emitting = false
|
||||
amount = 16
|
||||
process_material = ExtResource("8_y2qyn")
|
||||
texture = ExtResource("8_gufhv")
|
||||
lifetime = 2.0
|
||||
one_shot = true
|
||||
explosiveness = 1.0
|
||||
trail_enabled = true
|
||||
trail_lifetime = 0.1
|
||||
trail_sections = 4
|
||||
|
||||
[node name="GPUParticles2D" type="GPUParticles2D" parent="Anchor/Node2D/Sprite2D"]
|
||||
position = Vector2(-2.28882e-05, -6)
|
||||
|
|
|
@ -49,6 +49,9 @@ public partial class ProjectileSpawner : Ranged
|
|||
}
|
||||
}
|
||||
|
||||
projectile.Hitbox.Inflictor = Character;
|
||||
projectile.Weapon = this;
|
||||
|
||||
if (projectile is Utils.ITarget target)
|
||||
{
|
||||
if (Character is Characters.NPC npc)
|
||||
|
@ -92,7 +95,6 @@ public partial class ProjectileSpawner : Ranged
|
|||
for (int i = 0; i < ProjectileCount; i++)
|
||||
{
|
||||
float curDeviation = -i + maxAngleDeviations;
|
||||
GD.Print(curDeviation);
|
||||
SpawnProjectile(map, target.Rotated(curDeviation * theta));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=28 format=3 uid="uid://5y1acxl4j4n7"]
|
||||
[gd_scene load_steps=29 format=3 uid="uid://5y1acxl4j4n7"]
|
||||
|
||||
[ext_resource type="Script" path="res://Items/Weapons/Sword.cs" id="1_mai31"]
|
||||
[ext_resource type="Script" path="res://State/Weapon/WeaponStateMachine.cs" id="2_5ramr"]
|
||||
|
@ -8,6 +8,7 @@
|
|||
[ext_resource type="Texture2D" uid="uid://dfpe74vxvuwal" path="res://Assets/Sprites/Items/pugio.png" id="6_d28k5"]
|
||||
[ext_resource type="Script" path="res://State/Weapon/SwordBlockState.cs" id="6_yvm8x"]
|
||||
[ext_resource type="Material" uid="uid://cbfaqolx1ydvv" path="res://Assets/Sprites/Particles/ParryParticles.tres" id="8_we1sv"]
|
||||
[ext_resource type="Texture2D" uid="uid://d75jkoev5v3w" path="res://Assets/Sprites/Particles/circle-64.png" id="9_3p5s2"]
|
||||
[ext_resource type="AudioStream" uid="uid://m1sbk3c4eask" path="res://Assets/Sounds/metal-bash2.wav" id="9_b6yro"]
|
||||
[ext_resource type="PackedScene" uid="uid://du5vhccg75nrq" path="res://BoundingBoxes/Hitbox.tscn" id="9_qimey"]
|
||||
[ext_resource type="AudioStream" uid="uid://kao8wbfaum27" path="res://Assets/Sounds/metal-bash3.wav" id="10_istfq"]
|
||||
|
@ -386,6 +387,7 @@ Damage = 20.0
|
|||
UseTime = 0.55
|
||||
UseAltTime = 1.5
|
||||
Knockback = 64.0
|
||||
PlayerLevelGain = 1.0
|
||||
HandAnchor = NodePath("Anchor/Node2D/Sprite2D/Hand")
|
||||
|
||||
[node name="State" type="Node" parent="." node_paths=PackedStringArray("InitialState")]
|
||||
|
@ -435,17 +437,14 @@ texture = ExtResource("6_d28k5")
|
|||
|
||||
[node name="ParryParticles" type="GPUParticles2D" parent="Anchor/Node2D/Sprite2D"]
|
||||
modulate = Color(1.2, 1.2, 1.2, 1)
|
||||
position = Vector2(-0.221825, -3.12132)
|
||||
position = Vector2(0, -3)
|
||||
rotation = 0.785398
|
||||
emitting = false
|
||||
amount = 16
|
||||
process_material = ExtResource("8_we1sv")
|
||||
texture = ExtResource("9_3p5s2")
|
||||
lifetime = 2.0
|
||||
one_shot = true
|
||||
explosiveness = 1.0
|
||||
trail_enabled = true
|
||||
trail_lifetime = 0.1
|
||||
trail_sections = 4
|
||||
|
||||
[node name="Hand" type="Sprite2D" parent="Anchor/Node2D/Sprite2D"]
|
||||
position = Vector2(-2.52724e-05, 7)
|
||||
|
|
|
@ -258,6 +258,7 @@ StateMachine = NodePath("State")
|
|||
Damage = 12.0
|
||||
UseTime = 1.5
|
||||
InitialVelocity = 220.0
|
||||
PlayerLevelGain = 0.5
|
||||
|
||||
[node name="State" type="Node" parent="." node_paths=PackedStringArray("InitialState")]
|
||||
script = ExtResource("2_ag6rd")
|
||||
|
|
|
@ -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)
|
||||
|
@ -172,7 +191,11 @@ public partial class Sword : Weapon, IParryable
|
|||
{
|
||||
if (box is Hurtbox hurtbox)
|
||||
{
|
||||
hurtbox.InflictDamage(Damage, Character, Knockback);
|
||||
hurtbox.InflictDamage(
|
||||
Damage,
|
||||
Character,
|
||||
Knockback,
|
||||
this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -199,6 +222,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 +265,4 @@ public partial class Sword : Weapon, IParryable
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void SetAnimationCondition(string condition, bool value)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[gd_scene load_steps=36 format=3 uid="uid://dvqap2uhcah63"]
|
||||
[gd_scene load_steps=37 format=3 uid="uid://dvqap2uhcah63"]
|
||||
|
||||
[ext_resource type="Script" path="res://Items/Weapons/Sword.cs" id="1_mlo73"]
|
||||
[ext_resource type="Script" path="res://State/Weapon/WeaponStateMachine.cs" id="2_vwirq"]
|
||||
|
@ -10,6 +10,7 @@
|
|||
[ext_resource type="Script" path="res://State/Weapon/SwordAttackState.cs" id="5_hmisb"]
|
||||
[ext_resource type="AudioStream" uid="uid://c4n7ioxpukdwi" path="res://Assets/Sounds/parry.wav" id="6_8nxjm"]
|
||||
[ext_resource type="Material" uid="uid://cbfaqolx1ydvv" path="res://Assets/Sprites/Particles/ParryParticles.tres" id="8_10gir"]
|
||||
[ext_resource type="Texture2D" uid="uid://d75jkoev5v3w" path="res://Assets/Sprites/Particles/circle-64.png" id="9_o34ry"]
|
||||
[ext_resource type="Shape2D" uid="uid://dw4e4r2yxwk1b" path="res://Items/Weapons/SwordCollisionShape.tres" id="9_wsprl"]
|
||||
[ext_resource type="Texture2D" uid="uid://cmvh6pc71ir1m" path="res://Assets/Sprites/sword-swing-large.png" id="10_672jv"]
|
||||
[ext_resource type="AudioStream" uid="uid://qvthq6tppp63" path="res://Assets/Sounds/whoosh.wav" id="10_mfnl7"]
|
||||
|
@ -369,6 +370,7 @@ Damage = 20.0
|
|||
UseTime = 0.55
|
||||
Knockback = 64.0
|
||||
ShouldHideIdle = true
|
||||
PlayerLevelGain = 1.0
|
||||
HandAnchor = NodePath("Anchor/Node2D/Sprite2D/Hand")
|
||||
|
||||
[node name="State" type="Node" parent="." node_paths=PackedStringArray("InitialState")]
|
||||
|
@ -414,23 +416,22 @@ y_sort_enabled = true
|
|||
position = Vector2(0, -8)
|
||||
texture = ExtResource("3_r75ni")
|
||||
|
||||
[node name="Hand" type="Sprite2D" parent="Anchor/Node2D/Sprite2D"]
|
||||
position = Vector2(-2.52724e-05, 7)
|
||||
rotation = 1.5708
|
||||
|
||||
[node name="ParryParticles" type="GPUParticles2D" parent="Anchor/Node2D/Sprite2D"]
|
||||
modulate = Color(1.2, 1.2, 1.2, 1)
|
||||
position = Vector2(-0.221825, -3.12132)
|
||||
position = Vector2(0, -3)
|
||||
rotation = 0.785398
|
||||
emitting = false
|
||||
amount = 16
|
||||
process_material = ExtResource("8_10gir")
|
||||
texture = ExtResource("9_o34ry")
|
||||
lifetime = 2.0
|
||||
one_shot = true
|
||||
explosiveness = 1.0
|
||||
trail_enabled = true
|
||||
trail_lifetime = 0.1
|
||||
trail_sections = 4
|
||||
|
||||
[node name="Hand" type="Sprite2D" parent="Anchor/Node2D/Sprite2D"]
|
||||
position = Vector2(-2.52724e-05, 7)
|
||||
rotation = 1.5708
|
||||
fixed_fps = 16
|
||||
|
||||
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
|
||||
libraries = {
|
||||
|
|
17
README.md
17
README.md
|
@ -4,7 +4,22 @@ Forsen-related game
|
|||
|
||||
![](./Assets/Sprites/Characters/forsen2-portrait.png)
|
||||
|
||||
![](https://i.ibb.co/t367kD4/baj.gif)
|
||||
## Building
|
||||
|
||||
This is currently being developed on a custom dev branch of Godot 4.1.1:
|
||||
https://github.com/ryze312/godot/tree/x11_event_fix
|
||||
|
||||
Try to avoid using newer versions of Godot as they are unstable with C#.
|
||||
|
||||
## Notes
|
||||
|
||||
The tilde key (`~`) can open the developer console. This allows access to
|
||||
singletons --- an instance of `Utils.World` can be accessed through `World`,
|
||||
and the player character can be accessed through `World.CurrentPlayer`.
|
||||
|
||||
The default starting scene is `res://Scenes/ArenaExterior.tscn`, and running a
|
||||
non-map scene may spill out errors from `SupaLidlGame.Utils.World`. Eventually
|
||||
this will be fixed to allow main menu scenes.
|
||||
|
||||
## Attributions
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
#+TITLE: SupaLidlGame
|
||||
|
||||
Forsen-related game
|
||||
|
||||
#+attr_html: :style margin-left: auto; margin-right: auto;
|
||||
[[./Assets/Sprites/Characters/forsen2-portrait.png]]
|
||||
|
||||
[[https://i.ibb.co/t367kD4/baj.gif]]
|
|
@ -42,7 +42,6 @@ public abstract partial class PlayerState : CharacterState
|
|||
if (@event.IsActionPressed("interact"))
|
||||
{
|
||||
// if looking at a trigger then interact with it
|
||||
GD.Print("interacting");
|
||||
player.InteractionRay.Trigger?.InvokeInteraction();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
21
UI/Base.tscn
21
UI/Base.tscn
|
@ -1,9 +1,10 @@
|
|||
[gd_scene load_steps=7 format=3 uid="uid://c271rdjhd1gfo"]
|
||||
[gd_scene load_steps=8 format=3 uid="uid://c271rdjhd1gfo"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://73jm5qjy52vq" path="res://Dialogue/balloon.tscn" id="1_atjb1"]
|
||||
[ext_resource type="Script" path="res://UI/UIController.cs" id="2_b4b6l"]
|
||||
[ext_resource type="PackedScene" uid="uid://bxo553hblp6nf" path="res://UI/HealthBar.tscn" id="3_j1j6h"]
|
||||
[ext_resource type="PackedScene" uid="uid://01d24ij5av1y" path="res://UI/BossBar.tscn" id="4_igi28"]
|
||||
[ext_resource type="PackedScene" uid="uid://cr7tkxctmyags" path="res://UI/LevelBar.tscn" id="4_rcekd"]
|
||||
[ext_resource type="PackedScene" uid="uid://c77754nvmckn" path="res://UI/LocationDisplay.tscn" id="5_cr6vo"]
|
||||
[ext_resource type="PackedScene" uid="uid://d3q1yu3n7cqfj" path="res://UI/SceneTransition.tscn" id="6_j0nhv"]
|
||||
|
||||
|
@ -47,7 +48,7 @@ BossBar = NodePath("Bottom/BossBar")
|
|||
layout_mode = 1
|
||||
anchors_preset = 10
|
||||
anchor_right = 1.0
|
||||
offset_bottom = 40.0
|
||||
offset_bottom = 64.0
|
||||
grow_horizontal = 2
|
||||
|
||||
[node name="Margin" type="MarginContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top"]
|
||||
|
@ -55,9 +56,21 @@ layout_mode = 2
|
|||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 16
|
||||
|
||||
[node name="HealthBar" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin" instance=ExtResource("3_j1j6h")]
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
theme_override_constants/separation = 12
|
||||
|
||||
[node name="HealthBar" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin/VBoxContainer" instance=ExtResource("3_j1j6h")]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="LevelBar" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin/VBoxContainer" instance=ExtResource("4_rcekd")]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Margin2" type="MarginContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin/VBoxContainer"]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
theme_override_constants/margin_left = 16
|
||||
theme_override_constants/margin_top = 16
|
||||
|
||||
[node name="Bottom" type="HBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main"]
|
||||
layout_mode = 1
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
using Godot;
|
||||
using SupaLidlGame.Events;
|
||||
|
||||
namespace SupaLidlGame.UI;
|
||||
|
||||
public partial class LevelBar : Control
|
||||
{
|
||||
public TextureProgressBar XPBar { get; set; }
|
||||
|
||||
private TextureProgressBar[] _levelBars = new TextureProgressBar[4];
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
XPBar = GetNode<TextureProgressBar>("%XPBar");
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
_levelBars[i] = GetNode<TextureProgressBar>($"%Level{i + 1}Bar");
|
||||
}
|
||||
|
||||
EventBus.Instance.PlayerXPChanged += (xp) =>
|
||||
{
|
||||
XPBar.Value = xp;
|
||||
};
|
||||
|
||||
EventBus.Instance.PlayerLevelChanged += (level) =>
|
||||
{
|
||||
for (int i = 0; i < _levelBars.Length; i++)
|
||||
{
|
||||
// level 0: 0 is not less than 0
|
||||
// level 1: 0 is less than 1
|
||||
_levelBars[i].Value = i < level ? 1 : 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
[gd_scene load_steps=4 format=3 uid="uid://cr7tkxctmyags"]
|
||||
|
||||
[ext_resource type="Script" path="res://UI/LevelBar.cs" id="1_eqetx"]
|
||||
[ext_resource type="Texture2D" uid="uid://b75oak1nd2q6x" path="res://Assets/Sprites/UI/over-under-bar.png" id="2_f332l"]
|
||||
[ext_resource type="Texture2D" uid="uid://co7xm7i5f6n51" path="res://Assets/Sprites/UI/progress-bar.png" id="3_arvub"]
|
||||
|
||||
[node name="LevelBar" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 0
|
||||
offset_right = 128.0
|
||||
offset_bottom = 8.0
|
||||
script = ExtResource("1_eqetx")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
layout_mode = 2
|
||||
offset_right = 128.0
|
||||
offset_bottom = 8.0
|
||||
|
||||
[node name="XPBar" type="TextureProgressBar" parent="HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
texture_filter = 1
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
max_value = 4.0
|
||||
step = 0.01
|
||||
nine_patch_stretch = true
|
||||
stretch_margin_left = 3
|
||||
stretch_margin_top = 3
|
||||
stretch_margin_right = 3
|
||||
stretch_margin_bottom = 3
|
||||
texture_under = ExtResource("2_f332l")
|
||||
texture_progress = ExtResource("3_arvub")
|
||||
|
||||
[node name="Level1Bar" type="TextureProgressBar" parent="HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
texture_filter = 1
|
||||
layout_mode = 2
|
||||
max_value = 1.0
|
||||
nine_patch_stretch = true
|
||||
stretch_margin_left = 3
|
||||
stretch_margin_top = 3
|
||||
stretch_margin_right = 3
|
||||
stretch_margin_bottom = 3
|
||||
texture_under = ExtResource("2_f332l")
|
||||
texture_progress = ExtResource("3_arvub")
|
||||
|
||||
[node name="Level2Bar" type="TextureProgressBar" parent="HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
texture_filter = 1
|
||||
layout_mode = 2
|
||||
max_value = 1.0
|
||||
nine_patch_stretch = true
|
||||
stretch_margin_left = 3
|
||||
stretch_margin_top = 3
|
||||
stretch_margin_right = 3
|
||||
stretch_margin_bottom = 3
|
||||
texture_under = ExtResource("2_f332l")
|
||||
texture_progress = ExtResource("3_arvub")
|
||||
|
||||
[node name="Level3Bar" type="TextureProgressBar" parent="HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
texture_filter = 1
|
||||
layout_mode = 2
|
||||
max_value = 1.0
|
||||
nine_patch_stretch = true
|
||||
stretch_margin_left = 3
|
||||
stretch_margin_top = 3
|
||||
stretch_margin_right = 3
|
||||
stretch_margin_bottom = 3
|
||||
texture_under = ExtResource("2_f332l")
|
||||
texture_progress = ExtResource("3_arvub")
|
||||
|
||||
[node name="Level4Bar" type="TextureProgressBar" parent="HBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
texture_filter = 1
|
||||
layout_mode = 2
|
||||
max_value = 1.0
|
||||
nine_patch_stretch = true
|
||||
stretch_margin_left = 3
|
||||
stretch_margin_top = 3
|
||||
stretch_margin_right = 3
|
||||
stretch_margin_bottom = 3
|
||||
texture_under = ExtResource("2_f332l")
|
||||
texture_progress = ExtResource("3_arvub")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="."]
|
||||
visible = false
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
theme_override_constants/separation = 4
|
|
@ -23,12 +23,9 @@ public static class Physics
|
|||
var relVel = relIdle + relDir * speed;
|
||||
var relSpeed = relVel.Length();
|
||||
|
||||
GD.Print("Relative velocity: " + relVel);
|
||||
|
||||
// get t = time to travel
|
||||
// = (|s2| - |s1|)/(|v2| - |v1|)
|
||||
time = position.DistanceTo(targetPosition) / relSpeed;
|
||||
GD.Print("Time: " + time);
|
||||
|
||||
// predict target's position after time t
|
||||
return targetPosition + targetVelocity * time;
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
using Godot;
|
||||
using SupaLidlGame.Events;
|
||||
|
||||
namespace SupaLidlGame.Utils;
|
||||
|
||||
public partial class PlayerStats : Node
|
||||
{
|
||||
public const int MAX_XP_PER_LEVEL = 4;
|
||||
|
||||
public const int MAX_LEVELS = 4;
|
||||
|
||||
public DoubleValue XP { get; set; }
|
||||
|
||||
public IntValue Level { get; set; }
|
||||
|
||||
public double XPDecayVelocity { get; set; } = 1;
|
||||
|
||||
protected bool _shouldDecayXP = true;
|
||||
|
||||
protected Timer _xpDecayTimer;
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
XP = GetNode<DoubleValue>("XP");
|
||||
Level = GetNode<IntValue>("Level");
|
||||
|
||||
_xpDecayTimer = new Timer();
|
||||
_xpDecayTimer.Timeout += () => _shouldDecayXP = true;
|
||||
_xpDecayTimer.Stop();
|
||||
AddChild(_xpDecayTimer);
|
||||
|
||||
var bus = EventBus.Instance;
|
||||
XP.Changed += (oldValue, newValue) =>
|
||||
{
|
||||
bus.EmitSignal(EventBus.SignalName.PlayerXPChanged, newValue);
|
||||
};
|
||||
|
||||
Level.Changed += (oldValue, newValue) =>
|
||||
{
|
||||
bus.EmitSignal(EventBus.SignalName.PlayerLevelChanged, newValue);
|
||||
};
|
||||
|
||||
bus.PlayerHit += (args) =>
|
||||
{
|
||||
double xp = XP.Value;
|
||||
xp += args.Weapon?.PlayerLevelGain ?? 0;
|
||||
|
||||
if (xp >= MAX_XP_PER_LEVEL)
|
||||
{
|
||||
int deltaLevel = (int)(xp / MAX_XP_PER_LEVEL);
|
||||
xp %= MAX_XP_PER_LEVEL;
|
||||
Level.Value = Mathf.Min(Level.Value + deltaLevel, MAX_LEVELS);
|
||||
}
|
||||
|
||||
if (Level.Value == MAX_LEVELS)
|
||||
{
|
||||
// if max level, can only go up to 1 xp
|
||||
xp = Mathf.Min(xp, 1);
|
||||
}
|
||||
|
||||
XP.Value = xp;
|
||||
|
||||
_shouldDecayXP = false;
|
||||
_xpDecayTimer.Start(1);
|
||||
};
|
||||
}
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (_shouldDecayXP)
|
||||
{
|
||||
XP.Value = Mathf.MoveToward(XP.Value, 1, XPDecayVelocity * delta);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using Godot;
|
||||
|
||||
namespace SupaLidlGame;
|
||||
|
||||
public partial class DoubleValue : Node, IValue<double>
|
||||
{
|
||||
[Signal]
|
||||
public delegate void ChangedEventHandler(double oldValue, double newValue);
|
||||
|
||||
protected double _value = default;
|
||||
|
||||
[Export]
|
||||
public double Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
EmitSignal(SignalName.Changed, _value, value);
|
||||
_value = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using Godot;
|
||||
|
||||
namespace SupaLidlGame;
|
||||
|
||||
public partial class FloatValue : Node, IValue<float>
|
||||
{
|
||||
[Signal]
|
||||
public delegate void ChangedEventHandler(float oldValue, float newValue);
|
||||
|
||||
protected float _value = default;
|
||||
|
||||
[Export]
|
||||
public float Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
EmitSignal(SignalName.Changed, _value, value);
|
||||
_value = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
namespace SupaLidlGame;
|
||||
|
||||
public interface IValue<T>
|
||||
{
|
||||
public delegate void ChangedEventHandler(T oldValue, T newValue);
|
||||
|
||||
public T Value { get; set; }
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using Godot;
|
||||
|
||||
namespace SupaLidlGame;
|
||||
|
||||
public partial class IntValue : Node, IValue<int>
|
||||
{
|
||||
[Signal]
|
||||
public delegate void ChangedEventHandler(int oldValue, int newValue);
|
||||
|
||||
protected int _value = default;
|
||||
|
||||
[Export]
|
||||
public int Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
EmitSignal(SignalName.Changed, _value, value);
|
||||
_value = value;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -327,8 +327,6 @@ public partial class World : Node
|
|||
public void SpawnPlayer()
|
||||
{
|
||||
// TODO: add max health property
|
||||
//CurrentPlayer.Health = 100;
|
||||
//CurrentPlayer.Sprite.Visible = true;
|
||||
if (CurrentMap.SceneFilePath != GlobalState.Stats.SaveMapKey)
|
||||
{
|
||||
LoadScene(GlobalState.Stats.SaveMapKey);
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
#+TITLE: SupaLidlGame To-do List
|
||||
|
||||
* STARTED Campfires
|
||||
DEADLINE: <2022-12-03 Sat>
|
||||
|
||||
* DONE Enemy Spawning
|
||||
|
||||
* DONE Handle Character Death
|
||||
DEADLINE: <2022-12-04 Sun>
|
||||
|
||||
* Doc Boss
|
||||
|
||||
** DONE Reset possible attacks after each cycle
|
||||
CLOSED: [2023-07-21 Fri]
|
||||
|
||||
** DONE Attack animations
|
||||
CLOSED: [2023-07-20 Thu]
|
||||
|
||||
** DONE Boss Music
|
||||
CLOSED: [2023-07-24 Mon]
|
||||
|
||||
* TODO Boss cards
|
||||
|
||||
* DONE Dialog
|
||||
CLOSED: [2023-07-25 Tue]
|
||||
|
||||
* TODO Short arena entrance
|
||||
CLOSED: [2023-08-02 Wed]
|
||||
|
||||
* DONE Video demonstration
|
||||
CLOSED: [2023-07-25 Tue]
|
||||
|
||||
* TODO Combo Attacks ("Level system")
|
||||
|
||||
** TODO Healing
|
||||
|
||||
** TODO Alt attack
|
||||
|
||||
** TODO Max level
|
|
@ -206,4 +206,5 @@ locale/translations_pot_files=PackedStringArray("res://Assets/Dialog/doc.dialogu
|
|||
[rendering]
|
||||
|
||||
textures/canvas_textures/default_texture_filter=0
|
||||
renderer/rendering_method="gl_compatibility"
|
||||
environment/defaults/default_clear_color=Color(0.301961, 0.301961, 0.301961, 1)
|
||||
|
|
Loading…
Reference in New Issue