Add buff/debuff/heal/rust shaders for enemies and throwing glue jar immobilization

This commit is contained in:
2026-06-06 13:54:47 -07:00
parent 7b4fe37dd5
commit 543a967fd5
19 changed files with 358 additions and 114 deletions
@@ -21,6 +21,12 @@ namespace Zennysoft.Ma.Adapter.Entity
public void OnMorph();
public void OnBuff();
public void OnDebuff();
public void OnHealed();
public void IncrementDefeatCount();
public int GetDefeatCount(IEnemy enemyType);
@@ -39,6 +45,8 @@ namespace Zennysoft.Ma.Adapter.Entity
public ElementalResistanceSet ElementalResistanceSet { get; }
public void SetEnemySpeedByMultiplier(double multiplier);
public int InitialHP { get; }
public int InitialAttack { get; }
+29 -1
View File
@@ -213,6 +213,23 @@ public abstract partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLo
tweener.TweenCallback(Callable.From(QueueFree));
}
public virtual void OnBuff()
{
EnemyModelView.PlayBuffAnimation();
SfxDatabase.Instance.Play(SoundEffect.IncreaseStat);
}
public virtual void OnDebuff()
{
EnemyModelView.PlayDebuffAnimation();
SfxDatabase.Instance.Play(SoundEffect.DecreaseStat);
}
public virtual void OnHealed()
{
EnemyModelView.PlayHealAnimation();
}
public IDungeonRoom GetCurrentRoom(ImmutableList<IDungeonRoom> roomList)
{
foreach (var room in roomList)
@@ -233,6 +250,16 @@ public abstract partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLo
EnemyModelView.HitPlayer -= EnemyModelView_HitPlayer;
}
public void SetEnemySpeedByMultiplier(double multiplier)
{
if (this is IHaveFleeBehavior fleeBehavior)
fleeBehavior.FleeBehavior.FleeSpeed *= multiplier;
if (this is IHavePatrolBehavior patrolBehavior)
patrolBehavior.PatrolBehavior.PatrolSpeed *= multiplier;
if (this is IHaveFollowBehavior followBehavior)
followBehavior.FollowBehavior.FollowSpeed *= multiplier;
}
public virtual void MoveEnemyToNewRoom(IDungeonRoom newRoom)
{
if (newRoom is MonsterRoom monsterRoom)
@@ -277,6 +304,7 @@ public abstract partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLo
{
_rustTimer.Start();
_rustDuration.Start();
EnemyModelView.PlayRustActivateAnimation();
}
else
{
@@ -287,7 +315,7 @@ public abstract partial class Enemy : CharacterBody3D, IEnemy, IProvide<IEnemyLo
private void _rustTimer_Timeout()
{
HealthComponent.Damage(3, ElementType.Ferrum);
TakeHit(ElementType.Ferrum);
EnemyModelView.PlayRustDamageAnimation();
}
private void _rustDuration_Timeout()
@@ -0,0 +1,11 @@
[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://gvgpe17jldat"]
[ext_resource type="Shader" uid="uid://cjvysm0rhhdgi" path="res://src/vfx/shaders/DamageHit.gdshader" id="1_23uje"]
[resource]
resource_local_to_scene = true
shader = ExtResource("1_23uje")
shader_parameter/shock_color = Color(1, 0, 0, 1)
shader_parameter/amplitude = 30.0
shader_parameter/progress = -1.0
shader_parameter/frequecy = 10.0
@@ -117,6 +117,16 @@ public abstract partial class EnemyModelView : Node3D, IEnemyModelView
public virtual void PlayHitAnimation() => throw new System.NotImplementedException();
public virtual void PlayBuffAnimation() => throw new System.NotImplementedException();
public virtual void PlayDebuffAnimation() => throw new System.NotImplementedException();
public virtual void PlayHealAnimation() => throw new System.NotImplementedException();
public virtual void PlayRustActivateAnimation() => throw new System.NotImplementedException();
public virtual void PlayRustDamageAnimation() => throw new System.NotImplementedException();
protected virtual void OnPlayerHit(AttackEventArgs arg) => HitPlayer?.Invoke(this, arg);
protected void AnimationTree_AnimationFinished(StringName animName)
@@ -66,6 +66,39 @@ public partial class EnemyModelView2D : EnemyModelView, IEnemyModelView
tweener.TweenMethod(Callable.From((float x) => SetShaderValue(x)), 0.0f, 0.1f, 0.8f);
}
public override void PlayBuffAnimation()
{
LoadShader("res://src/vfx/shaders/EnemyBuff.gdshader");
var tweener = GetTree().CreateTween();
tweener.TweenMethod(Callable.From((float x) => SetShaderValue(x)), 0.0f, 1.0f, 1.0f);
}
public override void PlayDebuffAnimation()
{
LoadShader("res://src/vfx/shaders/EnemyDebuff.gdshader");
var tweener = GetTree().CreateTween();
tweener.TweenMethod(Callable.From((float x) => SetShaderValue(x)), 0.0f, 1.0f, 1.0f);
}
public override void PlayHealAnimation()
{
LoadShader("res://src/vfx/shaders/EnemyHeal.gdshader");
var tweener = GetTree().CreateTween();
tweener.TweenMethod(Callable.From((float x) => SetShaderValue(x)), 0.0f, 1.0f, 1.0f);
}
public override void PlayRustActivateAnimation()
{
RustHitAnimation.Play("RustActivate");
}
public override void PlayRustDamageAnimation()
{
LoadShader("res://src/vfx/shaders/EnemyRust.gdshader");
var tweener = GetTree().CreateTween();
tweener.TweenMethod(Callable.From((float x) => SetShaderValue(x)), 0.0f, 1.0f, 1.0f);
}
public override void PlayElementalDamageAnimation(ElementType elementType)
{
if (elementType == ElementType.Igneous)
@@ -104,7 +137,6 @@ public partial class EnemyModelView2D : EnemyModelView, IEnemyModelView
EarthHitAnimation.Stop();
EarthHitAnimation.Play("EarthHit");
}
}
private EnemyDirection GetEnemyDirection(
@@ -27,6 +27,16 @@ public interface IEnemyModelView : INode3D
public void PlayDeathAnimation();
public void PlayBuffAnimation();
public void PlayDebuffAnimation();
public void PlayHealAnimation();
public void PlayRustActivateAnimation();
public void PlayRustDamageAnimation();
public void PlayElementalDamageAnimation(ElementType elementType);
public double ViewerSize { get; }
@@ -11,7 +11,7 @@ public partial class FleeBehavior : Node3D, IBehavior
{
public override void _Notification(int what) => this.Notify(what);
[Export] private double _fleeSpeed { get; set; } = 300f;
[Export] public double FleeSpeed { get; set; } = 300f;
[Export] private double _thinkTime { get; set; } = 2f;
@@ -52,7 +52,7 @@ public partial class FleeBehavior : Node3D, IBehavior
{
var nextVelocity = _navigationAgent.GetNextPathPosition();
var parent = GetParent() as Node3D;
var velocity = parent.GlobalPosition.DirectionTo(nextVelocity) * (float)_fleeSpeed * (float)delta;
var velocity = parent.GlobalPosition.DirectionTo(nextVelocity) * (float)FleeSpeed * (float)delta;
EmitSignal(SignalName.OnVelocityComputed, velocity);
}
}
@@ -12,7 +12,7 @@ public partial class FollowBehavior : Node3D, IBehavior
{
public override void _Notification(int what) => this.Notify(what);
[Export] private double _followSpeed { get; set; } = 100f;
[Export] public double FollowSpeed { get; set; } = 100f;
[Export] private double _thinkTime { get; set; } = 2f;
@@ -56,7 +56,7 @@ public partial class FollowBehavior : Node3D, IBehavior
{
var nextVelocity = _navigationAgent.GetNextPathPosition();
var parent = GetParent() as Node3D;
var velocity = parent.GlobalPosition.DirectionTo(nextVelocity) * (float)_followSpeed * (float)delta;
var velocity = parent.GlobalPosition.DirectionTo(nextVelocity) * (float)FollowSpeed * (float)delta;
EmitSignal(SignalName.OnVelocityComputed, velocity);
}
@@ -12,7 +12,7 @@ public partial class PatrolBehavior : Node3D, IBehavior
{
public override void _Notification(int what) => this.Notify(what);
[Export] private double _patrolSpeed { get; set; } = 100f;
[Export] public double PatrolSpeed { get; set; } = 100f;
[Export] private double _thinkTime { get; set; } = 0.8f;
@@ -72,7 +72,7 @@ public partial class PatrolBehavior : Node3D, IBehavior
{
var nextVelocity = _navigationAgent.GetNextPathPosition();
var parent = GetParent() as Node3D;
var velocity = parent.GlobalPosition.DirectionTo(nextVelocity) * (float)_patrolSpeed * (float)delta;
var velocity = parent.GlobalPosition.DirectionTo(nextVelocity) * (float)PatrolSpeed * (float)delta;
EmitSignal(SignalName.OnVelocityComputed, velocity);
}
@@ -84,6 +84,7 @@ public class EffectService
return;
currentRoom.EnemiesInRoom.ForEach(e => e.HealthComponent.SetCurrentHealth(e.HealthComponent.MaximumHP.Value));
currentRoom.EnemiesInRoom.ForEach(e => e.OnHealed());
_player.HealthComponent.SetCurrentHealth(_player.HealthComponent.MaximumHP.Value);
SfxDatabase.Instance.Play(SoundEffect.HealHP);
}
@@ -225,6 +226,11 @@ public class EffectService
enemy.MoveEnemyToNewRoom(randomRoom);
}
public void DisableEnemyMovement(IEnemy enemy)
{
enemy.SetEnemySpeedByMultiplier(0);
}
public void CloneEnemy(IEnemy enemy)
{
var enemyPosition = new Vector3(enemy.GlobalPosition.X, 0f, enemy.GlobalPosition.Z);
@@ -116,35 +116,40 @@ public partial class ThrownItem : RigidBody3D, IThrownItem
{
case UsableItemTag.LowerTargetTo1HP:
enemy.HealthComponent.SetCurrentHealth(1);
SfxDatabase.Instance.Play(SoundEffect.DecreaseStat);
enemy.OnDebuff();
break;
case UsableItemTag.DecreaseAllStats:
enemy.AttackComponent.Reduce(usableItem.Stats.BonusAttack);
enemy.DefenseComponent.Reduce(usableItem.Stats.BonusDefense);
SfxDatabase.Instance.Play(SoundEffect.DecreaseStat);
enemy.OnDebuff();
break;
case UsableItemTag.DecreaseAttack:
enemy.AttackComponent.LowerMaximumAttack(usableItem.Stats.BonusAttack);
SfxDatabase.Instance.Play(SoundEffect.DecreaseStat);
enemy.OnDebuff();
break;
case UsableItemTag.DecreaseDefense:
enemy.DefenseComponent.LowerMaximumDefense(usableItem.Stats.BonusDefense);
SfxDatabase.Instance.Play(SoundEffect.DecreaseStat);
enemy.OnDebuff();
break;
case UsableItemTag.IncreaseAttack:
enemy.AttackComponent.RaiseMaximumAttack(usableItem.Stats.BonusAttack);
SfxDatabase.Instance.Play(SoundEffect.IncreaseStat);
enemy.OnBuff();
break;
case UsableItemTag.IncreaseDefense:
enemy.DefenseComponent.RaiseMaximumDefense(usableItem.Stats.BonusDefense);
SfxDatabase.Instance.Play(SoundEffect.IncreaseStat);
enemy.OnBuff();
break;
case UsableItemTag.TeleportToRandomLocation:
_effectService.TeleportToRandomRoom(enemy);
SfxDatabase.Instance.Play(SoundEffect.TeleportToRandomRoom);
break;
case UsableItemTag.GlueAllEquipment:
_effectService.DisableEnemyMovement(enemy);
enemy.OnDebuff();
break;
case UsableItemTag.Clone:
_effectService.CloneEnemy(enemy);
enemy.OnBuff();
break;
default:
var damageDealt = DamageCalculator.CalculateDamage(new AttackData(usableItem.ThrowDamage, ElementType.None), enemy.DefenseComponent.CurrentDefense.Value, enemy.ElementalResistanceSet);
@@ -152,6 +157,20 @@ public partial class ThrownItem : RigidBody3D, IThrownItem
break;
}
}
else if (ItemThatIsThrown is BoxItem boxItem)
{
switch (boxItem.Stats.ItemTag)
{
case ItemTag.RestrictUnequip:
_effectService.DisableEnemyMovement(enemy);
enemy.OnDebuff();
break;
default:
var damageDealt = DamageCalculator.CalculateDamage(new AttackData(boxItem.ThrowDamage, ElementType.None), enemy.DefenseComponent.CurrentDefense.Value, enemy.ElementalResistanceSet);
enemy.HealthComponent.Damage(damageDealt, ElementType.None);
break;
}
}
else
{
var damageDealt = DamageCalculator.CalculateDamage(new AttackData(ItemThatIsThrown.ThrowDamage, ElementType.None), enemy.DefenseComponent.CurrentDefense.Value, enemy.ElementalResistanceSet);
@@ -0,0 +1,29 @@
shader_type canvas_item;
/**
Color to use for the shock.
*/
uniform vec3 shock_color : source_color = vec3(0.0, 0.0, 0.6);
/**
Initial amplitude of the shock. This will start at this amplitude and
gradually attenuate.
*/
uniform float amplitude = 12.0;
uniform float progress: hint_range(0.0, 1.0) = -1.0;
/**
How fast shold it move side to side, more frequency means it'll move more quickly
side to side.
*/
uniform float frequecy = 10.0;
void vertex() {
float exponent = mod(progress, 3.0);
VERTEX.x += amplitude * exp(-3.0*exponent) * sin(frequecy*exponent);
}
void fragment() {
float exponent = mod(progress, 3.0);
vec3 normal_color = texture(TEXTURE, UV).rgb;
COLOR.rgb = normal_color + shock_color * exp(-3.0*exponent);
}
@@ -0,0 +1 @@
uid://d4a0aeg7ud76g
@@ -0,0 +1,29 @@
shader_type canvas_item;
/**
Color to use for the shock.
*/
uniform vec3 shock_color : source_color = vec3(0.3, 0.0, 0.5);
/**
Initial amplitude of the shock. This will start at this amplitude and
gradually attenuate.
*/
uniform float amplitude = 20.0;
uniform float progress: hint_range(0.0, 1.0) = -1.0;
/**
How fast shold it move side to side, more frequency means it'll move more quickly
side to side.
*/
uniform float frequecy = 10.0;
void vertex() {
float exponent = mod(progress, 3.0);
VERTEX.x += amplitude * exp(-3.0*exponent) * sin(frequecy*exponent);
}
void fragment() {
float exponent = mod(progress, 3.0);
vec3 normal_color = texture(TEXTURE, UV).rgb;
COLOR.rgb = normal_color + shock_color * exp(-3.0*exponent);
}
@@ -0,0 +1 @@
uid://dv81aqs1cd1br
@@ -0,0 +1,29 @@
shader_type canvas_item;
/**
Color to use for the shock.
*/
uniform vec3 shock_color : source_color = vec3(0.0, 0.6, 0.0);
/**
Initial amplitude of the shock. This will start at this amplitude and
gradually attenuate.
*/
uniform float amplitude = 12.0;
uniform float progress: hint_range(0.0, 1.0) = -1.0;
/**
How fast shold it move side to side, more frequency means it'll move more quickly
side to side.
*/
uniform float frequecy = 10.0;
void vertex() {
float exponent = mod(progress, 3.0);
VERTEX.x += amplitude * exp(-3.0*exponent) * sin(frequecy*exponent);
}
void fragment() {
float exponent = mod(progress, 3.0);
vec3 normal_color = texture(TEXTURE, UV).rgb;
COLOR.rgb = normal_color + shock_color * exp(-3.0*exponent);
}
@@ -0,0 +1 @@
uid://bgf5fav78hmuj
@@ -0,0 +1,29 @@
shader_type canvas_item;
/**
Color to use for the shock.
*/
uniform vec3 shock_color : source_color = vec3(0.6, 0.3, 0.0);
/**
Initial amplitude of the shock. This will start at this amplitude and
gradually attenuate.
*/
uniform float amplitude = 30.0;
uniform float progress: hint_range(0.0, 1.0) = -1.0;
/**
How fast shold it move side to side, more frequency means it'll move more quickly
side to side.
*/
uniform float frequecy = 10.0;
void vertex() {
float exponent = mod(progress, 3.0);
VERTEX.x += amplitude * exp(-3.0*exponent) * sin(frequecy*exponent);
}
void fragment() {
float exponent = mod(progress, 3.0);
vec3 normal_color = texture(TEXTURE, UV).rgb;
COLOR.rgb = normal_color + shock_color * exp(-3.0*exponent);
}
@@ -0,0 +1 @@
uid://uq8jl6jjs453