Refactor Enemy

This commit is contained in:
2025-02-06 01:58:36 -08:00
parent badc6d2375
commit 4e6c545e81
38 changed files with 581 additions and 589 deletions

View File

@@ -3,6 +3,10 @@
<TargetFramework>net8.0</TargetFramework>
<EnableDynamicLoading>true</EnableDynamicLoading>
</PropertyGroup>
<ItemGroup>
<Compile Remove="src\enemy\enemy_types\NewFolder\**" />
<EmbeddedResource Remove="src\enemy\enemy_types\NewFolder\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Chickensoft.AutoInject" Version="2.5.0" />
<PackageReference Include="Chickensoft.GodotNodeInterfaces" Version="2.4.0" />

View File

@@ -19,7 +19,6 @@ boot_splash/show_image=false
DialogueManager="*res://addons/dialogue_manager/dialogue_manager.gd"
DialogueController="*res://src/game/DialogueController.cs"
SettingsManager="*res://addons/deploy_to_steamos/SettingsManager.cs"
[dialogue_manager]
@@ -38,7 +37,7 @@ project/assembly_name="GameJamDungeon"
[editor_plugins]
enabled=PackedStringArray("res://addons/SimpleDungeons/plugin.cfg", "res://addons/deploy_to_steamos/plugin.cfg", "res://addons/dialogue_manager/plugin.cfg")
enabled=PackedStringArray("res://addons/SimpleDungeons/plugin.cfg", "res://addons/dialogue_manager/plugin.cfg")
[file_customization]

View File

@@ -1,9 +1,9 @@
@startuml AppLogic
state "AppLogic State" as GameJamDungeon_AppLogic_State {
state "SetupGameScene" as GameJamDungeon_AppLogic_State_SetupGameScene
state "MainMenu" as GameJamDungeon_AppLogic_State_MainMenu
state "LoadingScreen" as GameJamDungeon_AppLogic_State_LoadingScreen
state "InGame" as GameJamDungeon_AppLogic_State_InGame
state "LoadingScreen" as GameJamDungeon_AppLogic_State_LoadingScreen
state "MainMenu" as GameJamDungeon_AppLogic_State_MainMenu
}
GameJamDungeon_AppLogic_State_InGame --> GameJamDungeon_AppLogic_State_MainMenu : GameOver

View File

@@ -3,7 +3,6 @@ using Chickensoft.Collections;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.Introspection;
using Godot;
using static System.Net.Mime.MediaTypeNames;
namespace GameJamDungeon
{
@@ -84,21 +83,21 @@ namespace GameJamDungeon
private void AttackBox_AreaEntered(Area3D area)
{
var bossHitDamage = DamageCalculator.CalculateEnemyAttackDamage(GameRepo.PlayerData.CurrentDefense.Value + GameRepo.PlayerData.BonusDefense,
BossResource,
GameRepo.PlayerData.Inventory.EquippedArmor.Value.ArmorStats,
false);
GameRepo.PlayerData.SetCurrentHP(GameRepo.PlayerData.CurrentHP.Value - Mathf.RoundToInt(bossHitDamage));
//var bossHitDamage = DamageCalculator.CalculateEnemyAttackDamage(GameRepo.PlayerData.CurrentDefense.Value + GameRepo.PlayerData.BonusDefense,
// BossResource,
// GameRepo.PlayerData.Inventory.EquippedArmor.Value.ArmorStats,
// false);
//GameRepo.PlayerData.SetCurrentHP(GameRepo.PlayerData.CurrentHP.Value - Mathf.RoundToInt(bossHitDamage));
}
private void SecondaryAttackBox_AreaEntered(Area3D area)
{
var bossHitDamage = DamageCalculator.CalculateEnemyAttackDamage(GameRepo.PlayerData.CurrentDefense.Value + GameRepo.PlayerData.BonusDefense,
BossResource,
GameRepo.PlayerData.Inventory.EquippedArmor.Value.ArmorStats,
false);
var nerfDamage = bossHitDamage *= 0.25f;
GameRepo.PlayerData.SetCurrentHP(GameRepo.PlayerData.CurrentHP.Value - Mathf.RoundToInt(nerfDamage));
//var bossHitDamage = DamageCalculator.CalculateEnemyAttackDamage(GameRepo.PlayerData.CurrentDefense.Value + GameRepo.PlayerData.BonusDefense,
// BossResource,
// GameRepo.PlayerData.Inventory.EquippedArmor.Value.ArmorStats,
// false);
//var nerfDamage = bossHitDamage *= 0.25f;
//GameRepo.PlayerData.SetCurrentHP(GameRepo.PlayerData.CurrentHP.Value - Mathf.RoundToInt(nerfDamage));
Game.Player.ApplyCentralImpulseToPlayer(GlobalBasis.Z.Normalized());
}
@@ -118,9 +117,9 @@ namespace GameJamDungeon
var roll = rng.Randf();
if (roll <= GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.Luck)
isCriticalHit = true;
var damage = DamageCalculator.CalculateWeaponAttackDamage(GameRepo.PlayerData.CurrentAttack.Value + GameRepo.PlayerData.BonusAttack, BossResource, GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats, isCriticalHit);
GD.Print($"Enemy Hit for {damage} damage.");
BossLogic.Input(new BossLogic.Input.HitByPlayer(damage));
//var damage = DamageCalculator.CalculateWeaponAttackDamage(GameRepo.PlayerData.CurrentAttack.Value + GameRepo.PlayerData.BonusAttack, BossResource, GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats, isCriticalHit);
//GD.Print($"Enemy Hit for {damage} damage.");
//BossLogic.Input(new BossLogic.Input.HitByPlayer(damage));
}
}

View File

@@ -56,7 +56,7 @@ public partial class DataViewer : Control
if (Input.IsActionJustPressed(GameInputs.Attack))
{
_currentModel.PlayAttackAnimation();
_currentModel.PlayPrimaryAttackAnimation();
}
if (Input.IsActionJustPressed(GameInputs.Next))
{

View File

@@ -10,119 +10,123 @@ public partial class Enemy : RigidBody3D, IEnemy, IProvide<IEnemyLogic>
{
public override void _Notification(int what) => this.Notify(what);
public IEnemyLogic EnemyLogic { get; set; } = default!;
private IEnemyLogic _enemyLogic { get; set; } = default!;
IEnemyLogic IProvide<IEnemyLogic>.Value() => EnemyLogic;
IEnemyLogic IProvide<IEnemyLogic>.Value() => _enemyLogic;
public EnemyLogic.IBinding EnemyBinding { get; set; } = default!;
#region Dependencies
[Dependency] IGameRepo GameRepo => this.DependOn<IGameRepo>();
[Dependency] IGameEventDepot GameEventDepot => this.DependOn<IGameEventDepot>();
#endregion
[Export]
public EnemyStatResource EnemyStatResource { get; set; } = default!;
#region Exports
[Export] private EnemyStatResource _enemyStatResource { get; set; } = default!;
#endregion
public static PackedScene CollisionDetectorScene => GD.Load<PackedScene>("res://src/enemy/CollisionDetector.tscn");
#region Node Dependencies
[Node] private NavigationAgent3D NavAgent { get; set; } = default!;
public static Area3D CollisionDetector { get; set; } = default!;
[Node] private Area3D LineOfSight { get; set; } = default!;
public AutoProp<double> CurrentHP { get; set; }
[Node] private Timer PatrolTimer { get; set; } = default!;
[Node] public NavigationAgent3D NavAgent { get; set; } = default!;
[Node] private Timer AttackTimer { get; set; } = default!;
[Node] public Area3D LineOfSight { get; set; } = default!;
[Node] private RayCast3D Raycast { get; set; } = default!;
[Node] public Timer PatrolTimer { get; set; } = default!;
[Node] protected EnemyModelView EnemyModelView { get; set; } = default!;
#endregion
[Node] public Timer AttackTimer { get; set; } = default!;
public double CurrentHP => _currentHP.Value;
[Node] public RayCast3D Raycast { get; set; } = default!;
[Node] public EnemyModelView EnemyModelView { get; set; } = default!;
private AutoProp<double> _currentHP { get; set; }
private float _knockbackStrength = 0.0f;
private Vector3 _knockbackDirection = Vector3.Zero;
public void Setup()
{
EnemyLogic = new EnemyLogic();
EnemyLogic.Set(EnemyStatResource);
EnemyLogic.Set(this as IEnemy);
EnemyLogic.Set(GameRepo);
_enemyLogic = new EnemyLogic();
_enemyLogic.Set(_enemyStatResource);
_enemyLogic.Set(this as IEnemy);
_enemyLogic.Set(GameRepo);
}
public void OnReady()
{
SetPhysicsProcess(true);
CollisionDetector = CollisionDetectorScene.Instantiate<Area3D>();
CollisionDetector.AreaEntered += OnPlayerHitboxEntered;
AddChild(CollisionDetector);
EnemyModelView.Hitbox.AreaEntered += Hitbox_AreaEntered;
}
private void Hitbox_AreaEntered(Area3D area)
public void TakeDamage(double damage, ElementType elementType, bool isCriticalHit = false, bool ignoreDefense = false, bool ignoreElementalResistance = false)
{
if (area.GetParent().GetParent() is IPlayer player)
if (_currentHP.Value > 0)
{
var isCriticalHit = false;
var rng = new RandomNumberGenerator();
rng.Randomize();
var roll = rng.Randf();
if (roll <= EnemyStatResource.Luck)
isCriticalHit = true;
var damage = DamageCalculator.CalculateEnemyAttackDamage(
GameRepo.PlayerData.CurrentDefense.Value + GameRepo.PlayerData.BonusDefense,
EnemyStatResource,
GameRepo.PlayerData.Inventory.EquippedArmor.Value.ArmorStats,
isCriticalHit);
GameRepo.PlayerData.SetCurrentHP(GameRepo.PlayerData.CurrentHP.Value - Mathf.RoundToInt(damage));
GD.Print($"Player hit for {damage} damage.");
ApplyKnockback();
if (!ignoreElementalResistance)
damage = CalculateElementalResistance(damage, elementType);
if (!ignoreDefense)
damage = CalculateDefenseResistance(damage);
if (isCriticalHit)
damage *= 2;
GD.Print($"Enemy Hit for {damage} damage.");
_currentHP.OnNext(_currentHP.Value - damage);
GD.Print("Current HP: " + _currentHP.Value);
EnemyModelView.PlayHitAnimation();
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
}
}
public void MoveToLocation(Vector3 target, float delta)
{
NavAgent.TargetPosition = target;
var targetPosition = NavAgent.GetNextPathPosition();
var velocity = (targetPosition - GlobalTransform.Origin).Normalized() * 2f * delta;
var lookAtDir = GlobalTransform.Origin - velocity;
var lookAtPosition = new Vector3(lookAtDir.X, GlobalPosition.Y, lookAtDir.Z);
if (GlobalPosition.DistanceTo(target) > 1.0f && !velocity.IsEqualApprox(Vector3.Zero) && !GlobalPosition.IsEqualApprox(lookAtPosition))
LookAt(lookAtPosition);
EnemyModelView.RotateModel(GlobalTransform.Basis, -GameRepo.PlayerGlobalTransform.Value.Basis.Z);
_knockbackStrength = _knockbackStrength * 0.9f;
MoveAndCollide(velocity + (_knockbackDirection * _knockbackStrength));
}
public void Die()
{
_enemyLogic.Input(new EnemyLogic.Input.EnemyDefeated());
EnemyModelView.PlayDeathAnimation();
var tweener = GetTree().CreateTween();
tweener.TweenInterval(1.0f);
tweener.TweenCallback(Callable.From(QueueFree));
GameEventDepot.OnEnemyDefeated(GlobalPosition, _enemyStatResource);
}
public void OnResolved()
{
EnemyBinding = EnemyLogic.Bind();
EnemyBinding = _enemyLogic.Bind();
EnemyBinding
.Handle((in EnemyLogic.Output.MovementComputed output) =>
.Handle((in EnemyLogic.Output.TakeAction _) =>
{
EnemyModelView.RotateModel(GlobalTransform.Basis, -GameRepo.PlayerGlobalTransform.Value.Basis.Z);
_knockbackStrength = _knockbackStrength * 0.9f;
MoveAndCollide(output.LinearVelocity + (_knockbackDirection * _knockbackStrength));
})
.Handle((in EnemyLogic.Output.HitByPlayer output) =>
{
EnemyModelView.PlayHitAnimation();
// TODO: Make this an event to notify game that player hit someone
if (GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.WeaponTags.Contains(WeaponTag.SelfDamage))
GameRepo.PlayerData.SetCurrentHP(GameRepo.PlayerData.CurrentHP.Value - 5);
if (GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.WeaponTags.Contains(WeaponTag.Knockback))
{
_knockbackDirection = -GameRepo.PlayerGlobalTransform.Value.Basis.Z.Normalized();
_knockbackStrength = 0.3f;
}
})
.Handle((in EnemyLogic.Output.Attack _) =>
{
EnemyModelView.PlayAttackAnimation();
TakeAction();
})
.Handle((in EnemyLogic.Output.Defeated output) =>
{
EnemyModelView.PlayDeathAnimation();
var tweener = GetTree().CreateTween();
tweener.TweenInterval(1.0f);
tweener.TweenCallback(Callable.From(QueueFree));
GameEventDepot.OnEnemyDefeated(GlobalPosition, EnemyStatResource);
Die();
});
this.Provide();
EnemyLogic.Start();
_enemyLogic.Start();
CurrentHP = new AutoProp<double>(EnemyStatResource.MaximumHP);
CurrentHP.Sync += OnHPChanged;
_currentHP = new AutoProp<double>(_enemyStatResource.MaximumHP);
_currentHP.Sync += OnHPChanged;
LineOfSight.BodyEntered += LineOfSight_BodyEntered;
PatrolTimer.Timeout += OnPatrolTimeout;
AttackTimer.Timeout += OnAttackTimeout;
@@ -133,7 +137,7 @@ public partial class Enemy : RigidBody3D, IEnemy, IProvide<IEnemyLogic>
public void OnExitTree()
{
EnemyLogic.Stop();
_enemyLogic.Stop();
EnemyBinding.Dispose();
}
@@ -142,32 +146,13 @@ public partial class Enemy : RigidBody3D, IEnemy, IProvide<IEnemyLogic>
var rng = new RandomNumberGenerator();
rng.Randomize();
var randomizedSpot = new Vector3(rng.RandfRange(-7.0f, 7.0f), 0, rng.RandfRange(-7.0f, 7.0f));
EnemyLogic.Input(new EnemyLogic.Input.PatrolToRandomSpot(GlobalPosition + randomizedSpot));
_enemyLogic.Input(new EnemyLogic.Input.PatrolToRandomSpot(GlobalPosition + randomizedSpot));
PatrolTimer.WaitTime = rng.RandfRange(5.0f, 10.0f);
}
public void OnPhysicsProcess(double delta)
{
EnemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
}
public void OnPlayerHitboxEntered(Area3D body)
{
if (body is IHitbox hitBox)
{
if (CurrentHP.Value > 0)
{
var isCriticalHit = false;
var rng = new RandomNumberGenerator();
rng.Randomize();
var roll = rng.Randf();
if (roll <= GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.Luck)
isCriticalHit = true;
var damage = DamageCalculator.CalculateWeaponAttackDamage(GameRepo.PlayerData.CurrentAttack.Value + GameRepo.PlayerData.BonusAttack, EnemyStatResource, GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats, isCriticalHit);
GD.Print($"Enemy Hit for {damage} damage.");
EnemyLogic.Input(new EnemyLogic.Input.HitByPlayer(damage));
}
}
_enemyLogic.Input(new EnemyLogic.Input.PhysicsTick(delta));
}
private void OnAttackTimeout()
@@ -177,7 +162,7 @@ public partial class Enemy : RigidBody3D, IEnemy, IProvide<IEnemyLogic>
var rng = new RandomNumberGenerator();
rng.Randomize();
EnemyLogic.Input(new EnemyLogic.Input.AttackTimer());
_enemyLogic.Input(new EnemyLogic.Input.AttackTimer());
AttackTimer.Stop();
AttackTimer.WaitTime = rng.RandfRange(2f, 5.0f);
AttackTimer.Start();
@@ -197,20 +182,49 @@ public partial class Enemy : RigidBody3D, IEnemy, IProvide<IEnemyLogic>
if (collider is IPlayer player)
{
Raycast.DebugShapeCustomColor = Color.FromString("Purple", Colors.Purple);
EnemyLogic.Input(new EnemyLogic.Input.Alerted());
_enemyLogic.Input(new EnemyLogic.Input.Alerted());
}
}
}
}
private void ApplyKnockback()
{
if (GameRepo.PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.WeaponTags.Contains(WeaponTag.Knockback))
{
_knockbackDirection = -GameRepo.PlayerGlobalTransform.Value.Basis.Z.Normalized();
_knockbackStrength = 0.3f;
}
}
private void OnHPChanged(double newHP)
{
if (newHP <= 0)
EnemyLogic.Input(new EnemyLogic.Input.EnemyDefeated());
Die();
}
private void DoNothing()
private double CalculateElementalResistance(double incomingDamage, ElementType incomingElementType)
{
if (incomingElementType == ElementType.Aeolic)
return Mathf.Max(incomingDamage - (incomingDamage * _enemyStatResource.AeolicResistance), 0.0);
if (incomingElementType == ElementType.Hydric)
return Mathf.Max(incomingDamage - (incomingDamage * _enemyStatResource.HydricResistance), 0.0);
if (incomingElementType == ElementType.Igneous)
return Mathf.Max(incomingDamage - (incomingDamage * _enemyStatResource.IgneousResistance), 0.0);
if (incomingElementType == ElementType.Ferrum)
return Mathf.Max(incomingDamage - (incomingDamage * _enemyStatResource.FerrumResistance), 0.0);
if (incomingElementType == ElementType.Telluric)
return Mathf.Max(incomingDamage - (incomingDamage * _enemyStatResource.TelluricResistance), 0.0);
return Mathf.Max(incomingDamage, 0.0);
}
private double CalculateDefenseResistance(double incomingDamage)
{
return Mathf.Max(incomingDamage - _enemyStatResource.CurrentDefense, 0.0);
}
public virtual void TakeAction()
{
}
}

View File

@@ -6,7 +6,11 @@ using Godot;
public interface IEnemyModelView : INode3D
{
public void PlayAttackAnimation();
public void PlayPrimaryAttackAnimation();
public void PlaySecondaryAttackAnimation();
public void PlayPrimarySkillAnimation();
public void PlayHitAnimation();
public void PlayDeathAnimation();
@@ -19,7 +23,9 @@ public interface IEnemyModelView : INode3D
[Meta(typeof(IAutoNode))]
public partial class EnemyModelView : Node3D, IEnemyModelView
{
private const string ATTACK = "attack";
private const string PRIMARY_ATTACK = "primary_attack";
private const string SECONDARY_ATTACK = "secondary_attack";
private const string PRIMARY_SKILL = "primary_skill";
private const string IDLE_FORWARD = "idle_front_walk";
private const string IDLE_LEFT = "idle_left_walk";
private const string IDLE_BACK = "idle_back_walk";
@@ -43,9 +49,19 @@ public partial class EnemyModelView : Node3D, IEnemyModelView
AnimationTree.Get(PARAMETERS_PLAYBACK).As<AnimationNodeStateMachinePlayback>().Start(IDLE_FORWARD);
}
public void PlayAttackAnimation()
public void PlayPrimaryAttackAnimation()
{
AnimationTree.Get(PARAMETERS_PLAYBACK).As<AnimationNodeStateMachinePlayback>().Travel(ATTACK);
AnimationTree.Get(PARAMETERS_PLAYBACK).As<AnimationNodeStateMachinePlayback>().Travel(PRIMARY_ATTACK);
}
public void PlaySecondaryAttackAnimation()
{
AnimationTree.Get(PARAMETERS_PLAYBACK).As<AnimationNodeStateMachinePlayback>().Travel(SECONDARY_ATTACK);
}
public void PlayPrimarySkillAnimation()
{
AnimationTree.Get(PARAMETERS_PLAYBACK).As<AnimationNodeStateMachinePlayback>().Travel(PRIMARY_SKILL);
}
public void PlayHitAnimation()

View File

@@ -45,19 +45,10 @@ namespace GameJamDungeon
public double FerrumResistance { get; set; }
[Export]
public double TelluricDamageBonus { get; set; }
public ElementType SecondaryAttackElementalType { get; set; }
[Export]
public double AeolicDamageBonus { get; set; }
[Export]
public double BaseHydricDamageBonus { get; set; }
[Export]
public double IgneousDamageBonus { get; set; }
[Export]
public double FerrumDamageBonus { get; set; }
public double SecondaryAttackElementalDamageBonus { get; set; } = 1.0;
[Export]
public float DropsSoulGemChance { get; set; } = 0.75f;

View File

@@ -1,20 +1,16 @@
using Chickensoft.Collections;
using Chickensoft.GodotNodeInterfaces;
using Godot;
using Godot;
namespace GameJamDungeon;
public interface IEnemy : IRigidBody3D
public interface IEnemy
{
public IEnemyLogic EnemyLogic { get; }
public void TakeAction();
public AutoProp<double> CurrentHP { get; set; }
public void TakeDamage(double damage, ElementType elementType, bool isCriticalHit = false, bool ignoreDefense = false, bool ignoreElementalResistance = false);
public EnemyStatResource EnemyStatResource { get; set; }
public void MoveToLocation(Vector3 target, float delta);
public NavigationAgent3D NavAgent { get; set; }
public void Die();
public Area3D LineOfSight { get; set; }
public Timer AttackTimer { get; set; }
public double CurrentHP { get; }
}

View File

@@ -0,0 +1,23 @@
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
namespace GameJamDungeon;
[Meta(typeof(IAutoNode))]
public partial class Chariot : Enemy, IHasPrimaryAttack
{
[Export]
public ElementType PrimaryAttackElementalType { get; set; } = ElementType.None;
[Export]
public double PrimaryAttackElementalDamageBonus { get; set; } = 1.0;
public override void TakeAction()
{
PrimaryAttack();
}
public void PrimaryAttack()
{
EnemyModelView.PlayPrimaryAttackAnimation();
}
}

View File

@@ -1,8 +1,8 @@
[gd_scene load_steps=137 format=3 uid="uid://dlw5cvutvypxn"]
[ext_resource type="Script" path="res://src/enemy/Enemy.cs" id="1_t62lw"]
[ext_resource type="Script" path="res://src/enemy/EnemyStatResource.cs" id="2_77bk6"]
[ext_resource type="Script" path="res://src/hitbox/Hitbox.cs" id="3_lg405"]
[ext_resource type="Script" path="res://src/enemy/enemy_types/chariot/Chariot.cs" id="1_hqeyd"]
[ext_resource type="Script" uid="uid://dnkmr0eq1sij0" path="res://src/enemy/EnemyStatResource.cs" id="2_77bk6"]
[ext_resource type="Script" uid="uid://6edayafleq8y" path="res://src/hitbox/Hitbox.cs" id="3_lg405"]
[ext_resource type="Texture2D" uid="uid://c6fvuw1escea1" path="res://src/enemy/enemy_types/chariot/animations/Chariot Back Walk/Layer 1.png" id="4_fokm1"]
[ext_resource type="Texture2D" uid="uid://drjnht11skb0l" path="res://src/enemy/enemy_types/chariot/animations/Chariot Front Walk/Layer 1.png" id="4_tav2y"]
[ext_resource type="Texture2D" uid="uid://yl1m0fik4fab" path="res://src/enemy/enemy_types/chariot/animations/Chariot Back Walk/Layer 2.png" id="5_gp46b"]
@@ -106,26 +106,25 @@
[ext_resource type="Texture2D" uid="uid://b1qg8g32xpddx" path="res://src/enemy/enemy_types/chariot/animations/2nd Sprite Fabrics/2nd Sprite Fabrics/Layer 63.png" id="104_1k5tm"]
[ext_resource type="Texture2D" uid="uid://upyfmmm6ksmo" path="res://src/enemy/enemy_types/chariot/animations/2nd Sprite Fabrics/2nd Sprite Fabrics/Layer 50.png" id="105_s26ni"]
[sub_resource type="Resource" id="Resource_rxw8v"]
[sub_resource type="Resource" id="Resource_dvne1"]
script = ExtResource("2_77bk6")
CurrentHP = 45.0
MaximumHP = 45.0
CurrentAttack = 20
CurrentDefense = 10
MaxAttack = 20
MaxDefense = 10
CurrentHP = 50.0
MaximumHP = 50.0
CurrentAttack = 5
CurrentDefense = 5
MaxAttack = 5
MaxDefense = 5
ExpFromDefeat = 15
Luck = 0.05
TelluricResistance = 0.0
AeolicResistance = 0.0
HydricResistance = 0.0
IgneousResistance = 0.0
FerrumResistance = 0.0
TelluricDamageBonus = 0.0
AeolicDamageBonus = 0.0
BaseHydricDamageBonus = 0.0
IgneousDamageBonus = 0.0
FerrumDamageBonus = 0.0
DropsSoulGemChance = 0.9
SecondaryAttackElementalType = 0
SecondaryAttackElementalDamageBonus = 1.0
DropsSoulGemChance = 0.75
metadata/_custom_type_script = ExtResource("2_77bk6")
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_cwfph"]
radius = 1.0
@@ -492,23 +491,6 @@ tracks/0/keys = {
"values": [true]
}
[sub_resource type="Animation" id="Animation_ruc6s"]
resource_name = "attack"
length = 0.750008
step = 0.0833333
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Hitbox/CollisionShape3D:disabled")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.25, 0.666666),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [true, false, true]
}
[sub_resource type="Animation" id="Animation_1tda5"]
resource_name = "idle_back_walk"
length = 1.25001
@@ -527,17 +509,31 @@ length = 1.25001
loop_mode = 1
step = 0.0833333
[sub_resource type="AnimationLibrary" id="AnimationLibrary_6tj5r"]
_data = {
"RESET": SubResource("Animation_ch8ic"),
"attack": SubResource("Animation_ruc6s"),
"idle_back_walk": SubResource("Animation_1tda5"),
"idle_front_walk": SubResource("Animation_31nry"),
"idle_left_walk": SubResource("Animation_1870e")
[sub_resource type="Animation" id="Animation_ruc6s"]
resource_name = "attack"
length = 0.750008
step = 0.0833333
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Hitbox/CollisionShape3D:disabled")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.25, 0.666666),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [true, false, true]
}
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_erbrx"]
animation = &"attack"
[sub_resource type="AnimationLibrary" id="AnimationLibrary_6tj5r"]
_data = {
&"RESET": SubResource("Animation_ch8ic"),
&"idle_back_walk": SubResource("Animation_1tda5"),
&"idle_front_walk": SubResource("Animation_31nry"),
&"idle_left_walk": SubResource("Animation_1870e"),
&"primary_attack": SubResource("Animation_ruc6s")
}
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_o0tmb"]
animation = &"idle_back_walk"
@@ -548,6 +544,9 @@ animation = &"idle_front_walk"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_dvj10"]
animation = &"idle_left_walk"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_erbrx"]
animation = &"attack"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_vljb2"]
advance_mode = 2
@@ -588,15 +587,15 @@ switch_mode = 2
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_acrfn"]
states/End/position = Vector2(1464, 100)
states/attack/node = SubResource("AnimationNodeAnimation_erbrx")
states/attack/position = Vector2(1024, 92.9474)
states/idle_back_walk/node = SubResource("AnimationNodeAnimation_o0tmb")
states/idle_back_walk/position = Vector2(491, 92.9474)
states/idle_front_walk/node = SubResource("AnimationNodeAnimation_a6s5c")
states/idle_front_walk/position = Vector2(331, -12)
states/idle_left_walk/node = SubResource("AnimationNodeAnimation_dvj10")
states/idle_left_walk/position = Vector2(331, 196.947)
transitions = ["Start", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_vljb2"), "idle_front_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_3xv6a"), "idle_left_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_0h1op"), "idle_front_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_361b7"), "idle_back_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_wftla"), "idle_back_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_gqqkl"), "idle_left_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_5cj36"), "idle_front_walk", "attack", SubResource("AnimationNodeStateMachineTransition_4t05h"), "attack", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_8hgxu"), "attack", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_fq2yw"), "attack", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_yqm0k"), "idle_back_walk", "attack", SubResource("AnimationNodeStateMachineTransition_bmy1k"), "idle_left_walk", "attack", SubResource("AnimationNodeStateMachineTransition_mxl7w")]
states/primary_attack/node = SubResource("AnimationNodeAnimation_erbrx")
states/primary_attack/position = Vector2(1024, 92.9474)
transitions = ["Start", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_vljb2"), "idle_front_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_3xv6a"), "idle_left_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_0h1op"), "idle_front_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_361b7"), "idle_back_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_wftla"), "idle_back_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_gqqkl"), "idle_left_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_5cj36"), "idle_front_walk", "primary_attack", SubResource("AnimationNodeStateMachineTransition_4t05h"), "primary_attack", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_8hgxu"), "primary_attack", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_fq2yw"), "primary_attack", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_yqm0k"), "idle_back_walk", "primary_attack", SubResource("AnimationNodeStateMachineTransition_bmy1k"), "idle_left_walk", "primary_attack", SubResource("AnimationNodeStateMachineTransition_mxl7w")]
graph_offset = Vector2(-190, -62.0526)
[node name="Chariot" type="RigidBody3D"]
@@ -608,8 +607,8 @@ axis_lock_linear_y = true
axis_lock_angular_x = true
contact_monitor = true
max_contacts_reported = 1
script = ExtResource("1_t62lw")
EnemyStatResource = SubResource("Resource_rxw8v")
script = ExtResource("1_hqeyd")
_enemyStatResource = SubResource("Resource_dvne1")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0)
@@ -690,7 +689,7 @@ offset = Vector2(250, 250)
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
unique_name_in_owner = true
libraries = {
"": SubResource("AnimationLibrary_6tj5r")
&"": SubResource("AnimationLibrary_6tj5r")
}
[node name="AnimationTree" type="AnimationTree" parent="."]

View File

@@ -0,0 +1,38 @@
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
using System;
using System.Collections.Generic;
namespace GameJamDungeon;
[Meta(typeof(IAutoNode))]
public partial class FilthEater : Enemy, IHasPrimaryAttack, IHasSecondaryAttack
{
[Export]
public ElementType PrimaryAttackElementalType { get; set; } = ElementType.None;
[Export]
public double PrimaryAttackElementalDamageBonus { get; set; } = 1.0;
[Export]
public ElementType SecondaryAttackElementalType { get; set; } = ElementType.None;
[Export]
public double SecondaryAttackElementalDamageBonus { get; set; } = 1.0;
public override void TakeAction()
{
var rng = new RandomNumberGenerator();
var options = new List<Action>() { PrimaryAttack, SecondaryAttack };
var selection = rng.RandWeighted([0.875f, 0.125f]);
options[(int)selection].Invoke();
}
public void PrimaryAttack()
{
EnemyModelView.PlayPrimaryAttackAnimation();
}
public void SecondaryAttack()
{
EnemyModelView.PlaySecondaryAttackAnimation();
}
}

View File

@@ -1,8 +1,8 @@
[gd_scene load_steps=120 format=3 uid="uid://cvk007twac22c"]
[gd_scene load_steps=127 format=3 uid="uid://cvk007twac22c"]
[ext_resource type="Script" path="res://src/enemy/Enemy.cs" id="1_a8er3"]
[ext_resource type="Script" path="res://src/enemy/EnemyStatResource.cs" id="2_mqiog"]
[ext_resource type="Script" path="res://src/hitbox/Hitbox.cs" id="3_ipypc"]
[ext_resource type="Script" path="res://src/enemy/enemy_types/filth_eater/FilthEater.cs" id="1_fyc13"]
[ext_resource type="Script" uid="uid://dnkmr0eq1sij0" path="res://src/enemy/EnemyStatResource.cs" id="2_mqiog"]
[ext_resource type="Script" uid="uid://6edayafleq8y" path="res://src/hitbox/Hitbox.cs" id="3_ipypc"]
[ext_resource type="Texture2D" uid="uid://cx1wk7n77hdpu" path="res://src/enemy/enemy_types/filth_eater/animations/WALK BACK/Layer 1.png" id="4_ag3g0"]
[ext_resource type="Texture2D" uid="uid://bv4du7bb3m6ug" path="res://src/enemy/enemy_types/filth_eater/animations/SWIPE/Layer 1.png" id="4_qpdqy"]
[ext_resource type="Texture2D" uid="uid://bdl6xn7wskn2r" path="res://src/enemy/enemy_types/filth_eater/animations/WALK BACK/Layer 2.png" id="5_881nf"]
@@ -89,26 +89,27 @@
[ext_resource type="Texture2D" uid="uid://d32ej6fsx53io" path="res://src/enemy/enemy_types/filth_eater/animations/TELLERIC ATTACK/Layer 39.png" id="57_o526l"]
[ext_resource type="Texture2D" uid="uid://cy5dacfrmsxwe" path="res://src/enemy/enemy_types/filth_eater/animations/TELLERIC ATTACK/Layer 40.png" id="58_4tgv5"]
[sub_resource type="Resource" id="Resource_rxw8v"]
[sub_resource type="Resource" id="Resource_0y048"]
script = ExtResource("2_mqiog")
CurrentHP = 45.0
MaximumHP = 45.0
CurrentAttack = 20
CurrentDefense = 10
MaxAttack = 20
MaxDefense = 10
CurrentHP = 50.0
MaximumHP = 50.0
CurrentAttack = 5
CurrentDefense = 5
MaxAttack = 5
MaxDefense = 5
ExpFromDefeat = 15
Luck = 0.05
TelluricResistance = 0.0
AeolicResistance = 0.0
HydricResistance = 0.0
IgneousResistance = 0.0
FerrumResistance = 0.0
TelluricDamageBonus = 0.0
AeolicDamageBonus = 0.0
BaseHydricDamageBonus = 0.0
IgneousDamageBonus = 0.0
FerrumDamageBonus = 0.0
DropsSoulGemChance = 0.9
PrimaryAttackElementalType = 0
PrimaryAttackElementalDamageBonus = 1.0
SecondaryAttackElementalType = 2
SecondaryAttackElementalDamageBonus = 1.15
DropsSoulGemChance = 0.75
metadata/_custom_type_script = ExtResource("2_mqiog")
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_cwfph"]
radius = 1.0
@@ -445,6 +446,24 @@ tracks/2/keys = {
"values": [0]
}
[sub_resource type="Animation" id="Animation_1tda5"]
resource_name = "idle_back_walk"
length = 1.25001
loop_mode = 1
step = 0.0833333
[sub_resource type="Animation" id="Animation_31nry"]
resource_name = "idle_front_walk"
length = 1.25001
loop_mode = 1
step = 0.0833333
[sub_resource type="Animation" id="Animation_1870e"]
resource_name = "idle_left_walk"
length = 1.25001
loop_mode = 1
step = 0.0833333
[sub_resource type="Animation" id="Animation_ruc6s"]
resource_name = "attack"
length = 0.750008
@@ -490,37 +509,16 @@ tracks/1/keys = {
"values": [0]
}
[sub_resource type="Animation" id="Animation_1tda5"]
resource_name = "idle_back_walk"
length = 1.25001
loop_mode = 1
step = 0.0833333
[sub_resource type="Animation" id="Animation_31nry"]
resource_name = "idle_front_walk"
length = 1.25001
loop_mode = 1
step = 0.0833333
[sub_resource type="Animation" id="Animation_1870e"]
resource_name = "idle_left_walk"
length = 1.25001
loop_mode = 1
step = 0.0833333
[sub_resource type="AnimationLibrary" id="AnimationLibrary_6tj5r"]
_data = {
"RESET": SubResource("Animation_ch8ic"),
"attack": SubResource("Animation_ruc6s"),
"attack2": SubResource("Animation_wwl4v"),
"idle_back_walk": SubResource("Animation_1tda5"),
"idle_front_walk": SubResource("Animation_31nry"),
"idle_left_walk": SubResource("Animation_1870e")
&"RESET": SubResource("Animation_ch8ic"),
&"idle_back_walk": SubResource("Animation_1tda5"),
&"idle_front_walk": SubResource("Animation_31nry"),
&"idle_left_walk": SubResource("Animation_1870e"),
&"primary_attack": SubResource("Animation_ruc6s"),
&"secondary_attack": SubResource("Animation_wwl4v")
}
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_erbrx"]
animation = &"attack"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_o0tmb"]
animation = &"idle_back_walk"
@@ -530,6 +528,12 @@ animation = &"idle_front_walk"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_dvj10"]
animation = &"idle_left_walk"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_erbrx"]
animation = &"primary_attack"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_fyc13"]
animation = &"secondary_attack"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_vljb2"]
advance_mode = 2
@@ -568,17 +572,37 @@ switch_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_mxl7w"]
switch_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_0y048"]
switch_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_uhpql"]
switch_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_l7m4y"]
switch_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_464kr"]
switch_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_45of7"]
switch_mode = 2
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_c482n"]
switch_mode = 2
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_acrfn"]
states/End/position = Vector2(1464, 100)
states/attack/node = SubResource("AnimationNodeAnimation_erbrx")
states/attack/position = Vector2(1024, 92.9474)
states/idle_back_walk/node = SubResource("AnimationNodeAnimation_o0tmb")
states/idle_back_walk/position = Vector2(491, 92.9474)
states/idle_back_walk/position = Vector2(519, 75.9474)
states/idle_front_walk/node = SubResource("AnimationNodeAnimation_a6s5c")
states/idle_front_walk/position = Vector2(331, -12)
states/idle_left_walk/node = SubResource("AnimationNodeAnimation_dvj10")
states/idle_left_walk/position = Vector2(331, 196.947)
transitions = ["Start", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_vljb2"), "idle_front_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_3xv6a"), "idle_left_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_0h1op"), "idle_front_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_361b7"), "idle_back_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_wftla"), "idle_back_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_gqqkl"), "idle_left_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_5cj36"), "idle_front_walk", "attack", SubResource("AnimationNodeStateMachineTransition_4t05h"), "attack", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_8hgxu"), "attack", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_fq2yw"), "attack", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_yqm0k"), "idle_back_walk", "attack", SubResource("AnimationNodeStateMachineTransition_bmy1k"), "idle_left_walk", "attack", SubResource("AnimationNodeStateMachineTransition_mxl7w")]
states/primary_attack/node = SubResource("AnimationNodeAnimation_erbrx")
states/primary_attack/position = Vector2(1067, 33.9474)
states/secondary_attack/node = SubResource("AnimationNodeAnimation_fyc13")
states/secondary_attack/position = Vector2(1057, 151.947)
transitions = ["Start", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_vljb2"), "idle_front_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_3xv6a"), "idle_left_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_0h1op"), "idle_front_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_361b7"), "idle_back_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_wftla"), "idle_back_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_gqqkl"), "idle_left_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_5cj36"), "idle_front_walk", "primary_attack", SubResource("AnimationNodeStateMachineTransition_4t05h"), "primary_attack", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_8hgxu"), "primary_attack", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_fq2yw"), "primary_attack", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_yqm0k"), "idle_back_walk", "primary_attack", SubResource("AnimationNodeStateMachineTransition_bmy1k"), "idle_left_walk", "primary_attack", SubResource("AnimationNodeStateMachineTransition_mxl7w"), "idle_back_walk", "secondary_attack", SubResource("AnimationNodeStateMachineTransition_0y048"), "idle_left_walk", "secondary_attack", SubResource("AnimationNodeStateMachineTransition_uhpql"), "idle_front_walk", "secondary_attack", SubResource("AnimationNodeStateMachineTransition_l7m4y"), "secondary_attack", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_464kr"), "secondary_attack", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_45of7"), "secondary_attack", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_c482n")]
graph_offset = Vector2(-121, -17.0526)
[node name="FilthEater" type="RigidBody3D"]
@@ -590,8 +614,8 @@ axis_lock_linear_y = true
axis_lock_angular_x = true
contact_monitor = true
max_contacts_reported = 1
script = ExtResource("1_a8er3")
EnemyStatResource = SubResource("Resource_rxw8v")
script = ExtResource("1_fyc13")
_enemyStatResource = SubResource("Resource_0y048")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0)
@@ -662,7 +686,7 @@ offset = Vector2(150, 150)
[node name="AnimationPlayer" type="AnimationPlayer" parent="."]
unique_name_in_owner = true
libraries = {
"": SubResource("AnimationLibrary_6tj5r")
&"": SubResource("AnimationLibrary_6tj5r")
}
[node name="AnimationTree" type="AnimationTree" parent="."]

View File

@@ -0,0 +1,23 @@
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
namespace GameJamDungeon;
[Meta(typeof(IAutoNode))]
public partial class Michael : Enemy, IHasPrimaryAttack
{
[Export]
public ElementType PrimaryAttackElementalType { get; set; } = ElementType.None;
[Export]
public double PrimaryAttackElementalDamageBonus { get; set; } = 1.0;
public override void TakeAction()
{
PrimaryAttack();
}
public void PrimaryAttack()
{
EnemyModelView.PlayPrimaryAttackAnimation();
}
}

View File

@@ -1,31 +1,8 @@
[gd_scene load_steps=7 format=3 uid="uid://b0gwivt7cw7nd"]
[gd_scene load_steps=5 format=3 uid="uid://b0gwivt7cw7nd"]
[ext_resource type="Script" uid="uid://2d34jler3rmv" path="res://src/enemy/Enemy.cs" id="1_a6wro"]
[ext_resource type="Script" uid="uid://dnkmr0eq1sij0" path="res://src/enemy/EnemyStatResource.cs" id="2_x4pjh"]
[ext_resource type="Script" path="res://src/enemy/enemy_types/michael/Michael.cs" id="1_wfjxm"]
[ext_resource type="PackedScene" uid="uid://bjg8wyvp8q6oc" path="res://src/enemy/enemy_types/michael/MichaelModelView.tscn" id="3_wrps7"]
[sub_resource type="Resource" id="Resource_k2g1o"]
script = ExtResource("2_x4pjh")
CurrentHP = 45.0
MaximumHP = 45.0
CurrentAttack = 20
CurrentDefense = 11
MaxAttack = 20
MaxDefense = 11
ExpFromDefeat = 10
Luck = 0.05
TelluricResistance = 0.0
AeolicResistance = 0.0
HydricResistance = 0.0
IgneousResistance = 0.0
FerrumResistance = 0.0
TelluricDamageBonus = 0.0
AeolicDamageBonus = 0.0
BaseHydricDamageBonus = 0.0
IgneousDamageBonus = 0.0
FerrumDamageBonus = 0.0
DropsSoulGemChance = 0.75
[sub_resource type="CylinderShape3D" id="CylinderShape3D_jbgmx"]
height = 5.0
radius = 1.0
@@ -40,8 +17,7 @@ collision_layer = 10
collision_mask = 11
axis_lock_linear_y = true
axis_lock_angular_x = true
script = ExtResource("1_a6wro")
EnemyStatResource = SubResource("Resource_k2g1o")
script = ExtResource("1_wfjxm")
[node name="LineOfSight" type="Area3D" parent="."]
unique_name_in_owner = true

View File

@@ -1,6 +1,6 @@
[gd_scene load_steps=101 format=3 uid="uid://bjg8wyvp8q6oc"]
[ext_resource type="Script" path="res://src/enemy/EnemyModelView.cs" id="1_iajg3"]
[ext_resource type="Script" uid="uid://chymnqdw7hibn" path="res://src/enemy/EnemyModelView.cs" id="1_iajg3"]
[ext_resource type="Texture2D" uid="uid://clpqh2pyqljkn" path="res://src/enemy/enemy_types/michael/animations/IDLE_WALK/BACK/Michael_Walk_Idle_Back (1).png" id="2_3g180"]
[ext_resource type="Resource" uid="uid://6d7ivtna8dqb" path="res://src/enemy/enemy_types/michael/MichaelLoreInfo.tres" id="2_fssmb"]
[ext_resource type="Texture2D" uid="uid://b0dec8ak2bo5t" path="res://src/enemy/enemy_types/michael/animations/IDLE_WALK/BACK/Michael_Walk_Idle_Back (2).png" id="3_fssmb"]
@@ -71,7 +71,7 @@
[ext_resource type="Texture2D" uid="uid://vxphbifafq0q" path="res://src/enemy/enemy_types/michael/animations/IDLE_WALK/LEFT SIDE/Michael_IdleWalk_Left (21).png" id="68_msiau"]
[ext_resource type="Texture2D" uid="uid://7r30bjydumon" path="res://src/enemy/enemy_types/michael/animations/IDLE_WALK/LEFT SIDE/Michael_IdleWalk_Left (22).png" id="69_lec8c"]
[ext_resource type="Texture2D" uid="uid://djspx2smexhme" path="res://src/enemy/enemy_types/michael/animations/IDLE_WALK/LEFT SIDE/Michael_IdleWalk_Left (23).png" id="70_f0jo7"]
[ext_resource type="Script" path="res://src/hitbox/Hitbox.cs" id="71_ul4dn"]
[ext_resource type="Script" uid="uid://6edayafleq8y" path="res://src/hitbox/Hitbox.cs" id="71_ul4dn"]
[sub_resource type="ViewportTexture" id="ViewportTexture_v7t0v"]
viewport_path = NodePath("Sprite/SubViewport")
@@ -345,47 +345,6 @@ tracks/2/keys = {
"values": [&"idle_front_walk"]
}
[sub_resource type="Animation" id="Animation_0k3e8"]
resource_name = "attack"
length = 0.416668
step = 0.0166667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Hitbox/CollisionShape3D:disabled")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.0333333, 0.3),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [true, false, true]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Sprite/SubViewport/AnimatedSprite:animation")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"idle_front_walk"]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Sprite/SubViewport/AnimatedSprite:frame")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.0166667, 0.0333334, 0.0500001, 0.0666668, 0.0833335, 0.1, 0.116667, 0.133334, 0.15, 0.166667, 0.183334, 0.2, 0.216667, 0.233334, 0.25, 0.266667, 0.283334, 0.300001, 0.316667, 0.333334, 0.350001, 0.366667),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]
}
[sub_resource type="Animation" id="Animation_ppbeh"]
resource_name = "idle_back_walk"
length = 1.91667
@@ -476,18 +435,56 @@ tracks/1/keys = {
"values": [&"idle_left_walk"]
}
[sub_resource type="Animation" id="Animation_0k3e8"]
resource_name = "attack"
length = 0.416668
step = 0.0166667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Hitbox/CollisionShape3D:disabled")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.0333333, 0.3),
"transitions": PackedFloat32Array(1, 1, 1),
"update": 1,
"values": [true, false, true]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Sprite/SubViewport/AnimatedSprite:animation")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 1,
"values": [&"idle_front_walk"]
}
tracks/2/type = "value"
tracks/2/imported = false
tracks/2/enabled = true
tracks/2/path = NodePath("Sprite/SubViewport/AnimatedSprite:frame")
tracks/2/interp = 1
tracks/2/loop_wrap = true
tracks/2/keys = {
"times": PackedFloat32Array(0, 0.0166667, 0.0333334, 0.0500001, 0.0666668, 0.0833335, 0.1, 0.116667, 0.133334, 0.15, 0.166667, 0.183334, 0.2, 0.216667, 0.233334, 0.25, 0.266667, 0.283334, 0.300001, 0.316667, 0.333334, 0.350001, 0.366667),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 1,
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_346xs"]
_data = {
&"RESET": SubResource("Animation_41ppy"),
&"attack": SubResource("Animation_0k3e8"),
&"idle_back_walk": SubResource("Animation_ppbeh"),
&"idle_front_walk": SubResource("Animation_3dffb"),
&"idle_left_walk": SubResource("Animation_0qxqf")
&"idle_left_walk": SubResource("Animation_0qxqf"),
&"primary_attack": SubResource("Animation_0k3e8")
}
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_erbrx"]
animation = &"attack"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_o0tmb"]
animation = &"idle_back_walk"
@@ -497,6 +494,9 @@ animation = &"idle_front_walk"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_dvj10"]
animation = &"idle_left_walk"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_erbrx"]
animation = &"primary_attack"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_vljb2"]
advance_mode = 2
@@ -537,15 +537,15 @@ switch_mode = 2
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_r23qr"]
states/End/position = Vector2(1464, 100)
states/attack/node = SubResource("AnimationNodeAnimation_erbrx")
states/attack/position = Vector2(1151, 86.9474)
states/idle_back_walk/node = SubResource("AnimationNodeAnimation_o0tmb")
states/idle_back_walk/position = Vector2(490, 92.9474)
states/idle_front_walk/node = SubResource("AnimationNodeAnimation_a6s5c")
states/idle_front_walk/position = Vector2(331, -12)
states/idle_left_walk/node = SubResource("AnimationNodeAnimation_dvj10")
states/idle_left_walk/position = Vector2(331, 196.947)
transitions = ["Start", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_vljb2"), "idle_front_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_3xv6a"), "idle_left_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_0h1op"), "idle_front_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_361b7"), "idle_back_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_wftla"), "idle_back_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_gqqkl"), "idle_left_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_5cj36"), "idle_front_walk", "attack", SubResource("AnimationNodeStateMachineTransition_4t05h"), "attack", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_8hgxu"), "attack", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_fq2yw"), "attack", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_yqm0k"), "idle_back_walk", "attack", SubResource("AnimationNodeStateMachineTransition_bmy1k"), "idle_left_walk", "attack", SubResource("AnimationNodeStateMachineTransition_mxl7w")]
states/primary_attack/node = SubResource("AnimationNodeAnimation_erbrx")
states/primary_attack/position = Vector2(1151, 86.9474)
transitions = ["Start", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_vljb2"), "idle_front_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_3xv6a"), "idle_left_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_0h1op"), "idle_front_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_361b7"), "idle_back_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_wftla"), "idle_back_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_gqqkl"), "idle_left_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_5cj36"), "idle_front_walk", "primary_attack", SubResource("AnimationNodeStateMachineTransition_4t05h"), "primary_attack", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_8hgxu"), "primary_attack", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_fq2yw"), "primary_attack", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_yqm0k"), "idle_back_walk", "primary_attack", SubResource("AnimationNodeStateMachineTransition_bmy1k"), "idle_left_walk", "primary_attack", SubResource("AnimationNodeStateMachineTransition_mxl7w")]
graph_offset = Vector2(-190, -62.0526)
[node name="EnemyModelView" type="Node3D"]

View File

@@ -0,0 +1,23 @@
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;
namespace GameJamDungeon;
[Meta(typeof(IAutoNode))]
public partial class Sproingy : Enemy, IHasPrimaryAttack
{
[Export]
public ElementType PrimaryAttackElementalType { get; set; } = ElementType.None;
[Export]
public double PrimaryAttackElementalDamageBonus { get; set; } = 1.0;
public override void TakeAction()
{
PrimaryAttack();
}
public void PrimaryAttack()
{
EnemyModelView.PlayPrimaryAttackAnimation();
}
}

View File

@@ -1,31 +1,8 @@
[gd_scene load_steps=7 format=3 uid="uid://bksq62muhk3h5"]
[gd_scene load_steps=5 format=3 uid="uid://bksq62muhk3h5"]
[ext_resource type="Script" uid="uid://2d34jler3rmv" path="res://src/enemy/Enemy.cs" id="1_7tinp"]
[ext_resource type="Script" uid="uid://dnkmr0eq1sij0" path="res://src/enemy/EnemyStatResource.cs" id="2_j3knd"]
[ext_resource type="Script" path="res://src/enemy/enemy_types/sproingy/Sproingy.cs" id="1_ldo22"]
[ext_resource type="PackedScene" uid="uid://bli0t0d6ommvi" path="res://src/enemy/enemy_types/sproingy/SproingyModelView.tscn" id="4_o3b7p"]
[sub_resource type="Resource" id="Resource_rxw8v"]
script = ExtResource("2_j3knd")
CurrentHP = 45.0
MaximumHP = 45.0
CurrentAttack = 20
CurrentDefense = 10
MaxAttack = 20
MaxDefense = 10
ExpFromDefeat = 8
Luck = 0.05
TelluricResistance = 0.0
AeolicResistance = 0.0
HydricResistance = 0.0
IgneousResistance = 0.0
FerrumResistance = 0.0
TelluricDamageBonus = 0.0
AeolicDamageBonus = 0.0
BaseHydricDamageBonus = 0.0
IgneousDamageBonus = 0.0
FerrumDamageBonus = 0.0
DropsSoulGemChance = 0.9
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_cwfph"]
radius = 0.226425
height = 2.02807
@@ -43,8 +20,7 @@ axis_lock_linear_y = true
axis_lock_angular_x = true
contact_monitor = true
max_contacts_reported = 1
script = ExtResource("1_7tinp")
EnemyStatResource = SubResource("Resource_rxw8v")
script = ExtResource("1_ldo22")
[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
transform = Transform3D(-1, 0, -8.74228e-08, 0, 1, 0, 8.74228e-08, 0, -1, 0, 0, 0)

View File

@@ -1,6 +1,6 @@
[gd_scene load_steps=86 format=3 uid="uid://bli0t0d6ommvi"]
[ext_resource type="Script" path="res://src/enemy/EnemyModelView.cs" id="1_0vbio"]
[ext_resource type="Script" uid="uid://chymnqdw7hibn" path="res://src/enemy/EnemyModelView.cs" id="1_0vbio"]
[ext_resource type="Texture2D" uid="uid://dd0ia6isdqg61" path="res://src/enemy/enemy_types/sproingy/animations/ATTACK/Layer 1.png" id="1_pbx41"]
[ext_resource type="Texture2D" uid="uid://bs4ico5ouo5d3" path="res://src/enemy/enemy_types/sproingy/animations/ATTACK/Layer 2.png" id="2_0vbio"]
[ext_resource type="Resource" uid="uid://bctxs1jlkhgmc" path="res://src/enemy/enemy_types/sproingy/SproingyLoreInfo.tres" id="2_53wuj"]
@@ -57,7 +57,7 @@
[ext_resource type="Texture2D" uid="uid://b3gndmrlrvexy" path="res://src/enemy/enemy_types/sproingy/animations/IDLE_WALK_SIDE/Layer 13.png" id="53_nr2vc"]
[ext_resource type="Texture2D" uid="uid://b1cmx8l4ia3fv" path="res://src/enemy/enemy_types/sproingy/animations/IDLE_WALK_SIDE/Layer 14.png" id="54_jdvn0"]
[ext_resource type="Texture2D" uid="uid://c7t4626rox02s" path="res://src/enemy/enemy_types/sproingy/animations/IDLE_WALK_SIDE/Layer 15.png" id="55_2eqor"]
[ext_resource type="Script" path="res://src/hitbox/Hitbox.cs" id="57_lae8t"]
[ext_resource type="Script" uid="uid://6edayafleq8y" path="res://src/hitbox/Hitbox.cs" id="57_lae8t"]
[sub_resource type="ViewportTexture" id="ViewportTexture_h1kaf"]
viewport_path = NodePath("Sprite3D/SubViewport")
@@ -281,35 +281,6 @@ tracks/1/keys = {
"values": [0]
}
[sub_resource type="Animation" id="Animation_ruc6s"]
resource_name = "attack"
length = 0.750008
step = 0.0833333
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite3D/SubViewport/AnimatedSprite:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [&"attack"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Sprite3D/SubViewport/AnimatedSprite:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0833333, 0.166667, 0.25, 0.333333, 0.416667, 0.5, 0.583333, 0.666666, 0.75),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 0,
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
[sub_resource type="Animation" id="Animation_1tda5"]
resource_name = "idle_back_walk"
length = 1.16667
@@ -400,18 +371,44 @@ tracks/1/keys = {
"values": [&"idle_left_walk"]
}
[sub_resource type="Animation" id="Animation_ruc6s"]
resource_name = "attack"
length = 0.750008
step = 0.0833333
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Sprite3D/SubViewport/AnimatedSprite:animation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0),
"transitions": PackedFloat32Array(1),
"update": 0,
"values": [&"attack"]
}
tracks/1/type = "value"
tracks/1/imported = false
tracks/1/enabled = true
tracks/1/path = NodePath("Sprite3D/SubViewport/AnimatedSprite:frame")
tracks/1/interp = 1
tracks/1/loop_wrap = true
tracks/1/keys = {
"times": PackedFloat32Array(0, 0.0833333, 0.166667, 0.25, 0.333333, 0.416667, 0.5, 0.583333, 0.666666, 0.75),
"transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
"update": 0,
"values": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_6tj5r"]
_data = {
&"RESET": SubResource("Animation_ch8ic"),
&"attack": SubResource("Animation_ruc6s"),
&"idle_back_walk": SubResource("Animation_1tda5"),
&"idle_front_walk": SubResource("Animation_31nry"),
&"idle_left_walk": SubResource("Animation_1870e")
&"idle_left_walk": SubResource("Animation_1870e"),
&"primary_attack": SubResource("Animation_ruc6s")
}
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_erbrx"]
animation = &"attack"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_o0tmb"]
animation = &"idle_back_walk"
@@ -421,6 +418,9 @@ animation = &"idle_front_walk"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_dvj10"]
animation = &"idle_left_walk"
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_erbrx"]
animation = &"primary_attack"
[sub_resource type="AnimationNodeStateMachineTransition" id="AnimationNodeStateMachineTransition_vljb2"]
advance_mode = 2
@@ -461,15 +461,15 @@ switch_mode = 2
[sub_resource type="AnimationNodeStateMachine" id="AnimationNodeStateMachine_d5bmw"]
states/End/position = Vector2(1464, 100)
states/attack/node = SubResource("AnimationNodeAnimation_erbrx")
states/attack/position = Vector2(1024, 92.9474)
states/idle_back_walk/node = SubResource("AnimationNodeAnimation_o0tmb")
states/idle_back_walk/position = Vector2(491, 92.9474)
states/idle_front_walk/node = SubResource("AnimationNodeAnimation_a6s5c")
states/idle_front_walk/position = Vector2(331, -12)
states/idle_left_walk/node = SubResource("AnimationNodeAnimation_dvj10")
states/idle_left_walk/position = Vector2(331, 196.947)
transitions = ["Start", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_vljb2"), "idle_front_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_3xv6a"), "idle_left_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_0h1op"), "idle_front_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_361b7"), "idle_back_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_wftla"), "idle_back_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_gqqkl"), "idle_left_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_5cj36"), "idle_front_walk", "attack", SubResource("AnimationNodeStateMachineTransition_4t05h"), "attack", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_8hgxu"), "attack", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_fq2yw"), "attack", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_yqm0k"), "idle_back_walk", "attack", SubResource("AnimationNodeStateMachineTransition_bmy1k"), "idle_left_walk", "attack", SubResource("AnimationNodeStateMachineTransition_mxl7w")]
states/primary_attack/node = SubResource("AnimationNodeAnimation_erbrx")
states/primary_attack/position = Vector2(1024, 92.9474)
transitions = ["Start", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_vljb2"), "idle_front_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_3xv6a"), "idle_left_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_0h1op"), "idle_front_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_361b7"), "idle_back_walk", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_wftla"), "idle_back_walk", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_gqqkl"), "idle_left_walk", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_5cj36"), "idle_front_walk", "primary_attack", SubResource("AnimationNodeStateMachineTransition_4t05h"), "primary_attack", "idle_front_walk", SubResource("AnimationNodeStateMachineTransition_8hgxu"), "primary_attack", "idle_back_walk", SubResource("AnimationNodeStateMachineTransition_fq2yw"), "primary_attack", "idle_left_walk", SubResource("AnimationNodeStateMachineTransition_yqm0k"), "idle_back_walk", "primary_attack", SubResource("AnimationNodeStateMachineTransition_bmy1k"), "idle_left_walk", "primary_attack", SubResource("AnimationNodeStateMachineTransition_mxl7w")]
graph_offset = Vector2(46.1163, -62.0526)
[node name="EnemyModelView" type="Node3D"]

View File

@@ -13,8 +13,6 @@ namespace GameJamDungeon
public readonly record struct PhysicsTick(double Delta);
public readonly record struct HitByPlayer(double Damage);
public readonly record struct EnemyDefeated();
public readonly record struct PatrolToRandomSpot(Vector3 PatrolTarget);

View File

@@ -10,9 +10,7 @@ namespace GameJamDungeon
public readonly record struct MovementComputed(Vector3 LinearVelocity);
public readonly record struct HitByPlayer(double CurrentHP);
public readonly record struct Attack();
public readonly record struct TakeAction();
public readonly record struct Defeated();
}

View File

@@ -1,14 +1,13 @@
@startuml EnemyLogic
state "EnemyLogic State" as GameJamDungeon_EnemyLogic_State {
state "Defeated" as GameJamDungeon_EnemyLogic_State_Defeated
state "Alive" as GameJamDungeon_EnemyLogic_State_Alive {
state "FollowPlayer" as GameJamDungeon_EnemyLogic_State_FollowPlayer
state "Idle" as GameJamDungeon_EnemyLogic_State_Idle
}
state "Defeated" as GameJamDungeon_EnemyLogic_State_Defeated
}
GameJamDungeon_EnemyLogic_State_Alive --> GameJamDungeon_EnemyLogic_State_Alive : AttackTimer
GameJamDungeon_EnemyLogic_State_Alive --> GameJamDungeon_EnemyLogic_State_Alive : HitByPlayer
GameJamDungeon_EnemyLogic_State_Alive --> GameJamDungeon_EnemyLogic_State_Defeated : EnemyDefeated
GameJamDungeon_EnemyLogic_State_FollowPlayer --> GameJamDungeon_EnemyLogic_State_FollowPlayer : PhysicsTick
GameJamDungeon_EnemyLogic_State_FollowPlayer --> GameJamDungeon_EnemyLogic_State_Idle : LostPlayer
@@ -16,11 +15,8 @@ GameJamDungeon_EnemyLogic_State_Idle --> GameJamDungeon_EnemyLogic_State_FollowP
GameJamDungeon_EnemyLogic_State_Idle --> GameJamDungeon_EnemyLogic_State_Idle : PatrolToRandomSpot
GameJamDungeon_EnemyLogic_State_Idle --> GameJamDungeon_EnemyLogic_State_Idle : PhysicsTick
GameJamDungeon_EnemyLogic_State_Alive : OnAttackTimer → Attack
GameJamDungeon_EnemyLogic_State_Alive : OnAttackTimer → TakeAction
GameJamDungeon_EnemyLogic_State_Alive : OnEnemyDefeated → Defeated
GameJamDungeon_EnemyLogic_State_Alive : OnHitByPlayer → HitByPlayer
GameJamDungeon_EnemyLogic_State_FollowPlayer : OnPhysicsTick → MovementComputed
GameJamDungeon_EnemyLogic_State_Idle : OnPhysicsTick → MovementComputed
[*] --> GameJamDungeon_EnemyLogic_State_Idle
@enduml

View File

@@ -8,23 +8,11 @@ namespace GameJamDungeon
public partial record State
{
[Meta, Id("enemy_logic_state_alive")]
public abstract partial record Alive : State, IGet<Input.HitByPlayer>, IGet<Input.AttackTimer>, IGet<Input.EnemyDefeated>
public abstract partial record Alive : State, IGet<Input.AttackTimer>, IGet<Input.EnemyDefeated>
{
public Transition On(in Input.HitByPlayer input)
{
var enemy = Get<IEnemy>();
enemy.CurrentHP.OnNext(enemy.CurrentHP.Value - input.Damage);
GD.Print("Current HP: " + enemy.CurrentHP.Value);
Output(new Output.HitByPlayer());
Input(new Input.Alerted());
return ToSelf();
}
public Transition On(in Input.AttackTimer input)
{
Output(new Output.Attack());
Output(new Output.TakeAction());
return ToSelf();
}

View File

@@ -16,16 +16,7 @@ namespace GameJamDungeon
var enemy = Get<IEnemy>();
var gameRepo = Get<IGameRepo>();
var target = gameRepo.PlayerGlobalPosition.Value;
enemy.NavAgent.TargetPosition = target;
var targetPosition = enemy.NavAgent.GetNextPathPosition();
var velocity = (targetPosition - enemy.GlobalTransform.Origin).Normalized() * 2f * (float)delta;
var lookAtDir = enemy.GlobalTransform.Origin - velocity;
var lookAtPosition = new Vector3(lookAtDir.X, enemy.GlobalPosition.Y, lookAtDir.Z);
if (enemy.GlobalPosition.DistanceTo(target) > 1.0f && !velocity.IsEqualApprox(Vector3.Zero) && !enemy.GlobalPosition.IsEqualApprox(lookAtPosition))
enemy.LookAt(lookAtPosition);
Output(new Output.MovementComputed(velocity));
enemy.MoveToLocation(target, (float)delta);
return ToSelf();
}

View File

@@ -10,6 +10,7 @@ public partial class EnemyLogic
[Meta, Id("enemy_logic_state_idle")]
public partial record Idle : Alive, IGet<Input.Alerted>, IGet<Input.PhysicsTick>, IGet<Input.PatrolToRandomSpot>
{
private Vector3 _patrolTarget { get; set; }
public Transition On(in Input.Alerted _)
{
return To<FollowPlayer>();
@@ -19,22 +20,13 @@ public partial class EnemyLogic
{
var delta = input.Delta;
var enemy = Get<IEnemy>();
var targetPosition = enemy.NavAgent.GetNextPathPosition();
var velocity = (targetPosition - enemy.GlobalPosition).Normalized() * 1.0f * (float)delta;
var lookAtDir = enemy.GlobalTransform.Origin - velocity;
var lookAtPosition = new Vector3(lookAtDir.X, enemy.GlobalPosition.Y, lookAtDir.Z);
if (enemy.GlobalPosition.DistanceTo(targetPosition) > 1.0f && !velocity.IsEqualApprox(Vector3.Zero) && !enemy.GlobalPosition.IsEqualApprox(lookAtPosition))
enemy.LookAt(lookAtPosition);
Output(new Output.MovementComputed(velocity));
enemy.MoveToLocation(_patrolTarget, (float)delta);
return ToSelf();
}
public Transition On(in Input.PatrolToRandomSpot input)
{
var enemy = Get<IEnemy>();
enemy.NavAgent.TargetPosition = input.PatrolTarget;
_patrolTarget = input.PatrolTarget;
return ToSelf();
}
}

12
src/game/ElementType.cs Normal file
View File

@@ -0,0 +1,12 @@
namespace GameJamDungeon
{
public enum ElementType
{
None,
Aeolic,
Telluric,
Hydric,
Igneous,
Ferrum
}
}

View File

@@ -1,15 +1,15 @@
@startuml GameLogic
state "GameLogic State" as GameJamDungeon_GameLogic_State {
state "Quit" as GameJamDungeon_GameLogic_State_Quit
state "GameStarted" as GameJamDungeon_GameLogic_State_GameStarted
state "Playing" as GameJamDungeon_GameLogic_State_Playing {
state "AskForTeleport" as GameJamDungeon_GameLogic_State_AskForTeleport
state "FloorClearedDecisionState" as GameJamDungeon_GameLogic_State_FloorClearedDecisionState
state "InventoryOpened" as GameJamDungeon_GameLogic_State_InventoryOpened
state "MinimapOpen" as GameJamDungeon_GameLogic_State_MinimapOpen
state "Paused" as GameJamDungeon_GameLogic_State_Paused
state "FloorClearedDecisionState" as GameJamDungeon_GameLogic_State_FloorClearedDecisionState
state "AskForTeleport" as GameJamDungeon_GameLogic_State_AskForTeleport
state "InventoryOpened" as GameJamDungeon_GameLogic_State_InventoryOpened
state "Resuming" as GameJamDungeon_GameLogic_State_Resuming
}
state "GameStarted" as GameJamDungeon_GameLogic_State_GameStarted
state "Quit" as GameJamDungeon_GameLogic_State_Quit
}
GameJamDungeon_GameLogic_State_AskForTeleport --> GameJamDungeon_GameLogic_State_FloorClearedDecisionState : FloorExitReached

View File

@@ -1,5 +1,7 @@
using Chickensoft.GodotNodeInterfaces;
using GameJamDungeon;
using Godot;
using System;
public interface IHitbox : IArea3D
{

View File

@@ -3,7 +3,6 @@ using Chickensoft.Introspection;
using GameJamDungeon;
using Godot;
using System;
using System.ComponentModel;
[Meta(typeof(IAutoNode))]
public partial class ThrowableItem : Node3D, IUsableItem
@@ -27,23 +26,10 @@ public partial class ThrowableItem : Node3D, IUsableItem
[Node] public Area3D Pickup { get; set; } = default!;
private ThrowableItemTag[] _affinityTypes;
private int _affinityIndex = 0;
public void OnResolved()
{
Sprite.Texture = ThrowableItemInfo.Texture;
Pickup.BodyEntered += OnEntered;
_affinityTypes =
[
ThrowableItemTag.InflictBaseDamage,
ThrowableItemTag.InflictHydricDamage,
ThrowableItemTag.InflictIgneousDamage,
ThrowableItemTag.InflictTelluricDamage,
ThrowableItemTag.InflictAeolicDamage,
ThrowableItemTag.InflictFerrumDamage
];
}
public void Use()
@@ -62,13 +48,12 @@ public partial class ThrowableItem : Node3D, IUsableItem
private void ChangeAffinity()
{
ThrowableItemInfo.ThrowableItemTags.Remove(_affinityTypes[_affinityIndex]);
_affinityIndex = (_affinityIndex + 1) % (_affinityTypes.Length);
ThrowableItemInfo.ThrowableItemTags.Add(_affinityTypes[_affinityIndex]);
var maximumElements = Enum.GetNames(typeof(ElementType)).Length;
ThrowableItemInfo.ElementType = ThrowableItemInfo.ElementType + 1 % maximumElements;
// TODO: Make this an inventory animation to cycle through elements.
ThrowableItemInfo.Description =
$"{GetDescription(_affinityTypes[_affinityIndex])} when thrown." +
$"Inflicts {ThrowableItemInfo.ElementType} damage when thrown." +
$"{System.Environment.NewLine}Use item to change Affinity.";
}
@@ -78,19 +63,4 @@ public partial class ThrowableItem : Node3D, IUsableItem
if (isAdded)
QueueFree();
}
private static string GetDescription(ThrowableItemTag enumValue)
{
var field = enumValue.GetType().GetField(enumValue.ToString());
if (field == null)
return enumValue.ToString();
var attributes = field.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attribute)
{
return attribute.Description;
}
return enumValue.ToString();
}
}

View File

@@ -6,6 +6,7 @@
[sub_resource type="Resource" id="Resource_b0s4k"]
script = ExtResource("2_8h2lx")
ThrowableItemTags = Array[int]([])
ElementType = 0
UsableItemTags = Array[int]([])
Name = ""
Description = ""

View File

@@ -8,6 +8,9 @@ public partial class ThrowableItemStats : InventoryItemStats
[Export]
public Godot.Collections.Array<ThrowableItemTag> ThrowableItemTags { get; set; } = new Godot.Collections.Array<ThrowableItemTag>();
[Export]
public ElementType ElementType { get; set; } = ElementType.None;
[Export]
public Godot.Collections.Array<UsableItemTag> UsableItemTags { get; set; } = new Godot.Collections.Array<UsableItemTag>();
}

View File

@@ -2,18 +2,6 @@
public enum ThrowableItemTag
{
[Description("Inflicts basic damage")]
InflictBaseDamage,
[Description("Inflicts Telluric damage")]
InflictTelluricDamage,
[Description("Inflicts Aeolic damage")]
InflictAeolicDamage,
[Description("Inflicts Hydric damage")]
InflictHydricDamage,
[Description("Inflicts Igneous damage")]
InflictIgneousDamage,
[Description("Inflicts Ferrum damage")]
InflictFerrumDamage,
LowerTargetTo1HP,
CanChangeAffinity
}

View File

@@ -38,14 +38,16 @@ public partial class ThrownItem : RigidBody3D
private void CalculateEffect(IEnemy enemy)
{
enemy.EnemyLogic.Input(new EnemyLogic.Input.HitByPlayer(ThrownItemStats.ThrowDamage));
if (ThrownItemStats is ThrowableItemStats throwableItemStats)
{
if (throwableItemStats.ThrowableItemTags.Contains(ThrowableItemTag.LowerTargetTo1HP))
enemy.EnemyLogic.Input(new EnemyLogic.Input.HitByPlayer(enemy.CurrentHP.Value - 1));
enemy.EnemyLogic.Input(new EnemyLogic.Input.HitByPlayer(DamageCalculator.CalculateThrownItemDamage(throwableItemStats.ThrowDamage, enemy.EnemyStatResource, throwableItemStats)));
enemy.TakeDamage(enemy.CurrentHP - 1, ElementType.None, false, true, true);
else
enemy.TakeDamage(throwableItemStats.ThrowDamage, throwableItemStats.ElementType);
}
else
{
enemy.TakeDamage(ThrownItemStats.ThrowDamage, ElementType.None);
}
}
}

View File

@@ -14,19 +14,10 @@ public partial class WeaponStats : InventoryItemStats
public double AttackSpeed { get; set; } = 1;
[Export]
public double TelluricDamageBonus { get; set; } = 0;
public ElementType WeaponElement { get; set; } = ElementType.None;
[Export]
public double AeolicDamageBonus { get; set; } = 0;
[Export]
public double BaseHydricDamageBonus { get; set; } = 0;
[Export]
public double IgneousDamageBonus { get; set; } = 0;
[Export]
public double FerrumDamageBonus { get; set; } = 0;
public double ElementalDamageBonus { get; set; } = 1.0;
[Export]
public Godot.Collections.Array<WeaponTag> WeaponTags { get; set; } = new Godot.Collections.Array<WeaponTag>();

View File

@@ -9,8 +9,6 @@ namespace GameJamDungeon
{
public interface IPlayer : ICharacterBody3D, IKillable
{
PlayerLogic PlayerLogic { get; }
public Vector3 GetGlobalInputVector();
public float GetLeftStrafeInputVector();
@@ -33,6 +31,13 @@ namespace GameJamDungeon
PlayerLogic IProvide<PlayerLogic>.Value() => PlayerLogic;
public PlayerLogic.Settings Settings { get; set; } = default!;
private PlayerLogic PlayerLogic { get; set; } = default!;
private PlayerLogic.IBinding PlayerBinding { get; set; } = default!;
#region Dependencies
[Dependency]
public IAppRepo AppRepo => this.DependOn<IAppRepo>();
@@ -41,14 +46,18 @@ namespace GameJamDungeon
[Dependency]
public ISaveChunk<GameData> GameChunk => this.DependOn<ISaveChunk<GameData>>();
#endregion
#region Event Signals
[Signal]
public delegate void InventoryButtonPressedEventHandler();
[Signal]
public delegate void MinimapButtonHeldEventHandler();
[Signal]
public delegate void PauseButtonPressedEventHandler();
#endregion
#region Exports
[Export]
public PlayerStatResource PlayerStatResource { get; set; } = default!;
@@ -56,22 +65,17 @@ namespace GameJamDungeon
private WeaponStats _defaultWeapon { get; set; } = default!;
[Export]
private ArmorStats _defaultArmor { get; set; } = default!;
#endregion
public PlayerLogic.Settings Settings { get; set; } = default!;
#region Node Dependencies
[Node] private IAnimationPlayer AnimationPlayer { get; set; } = default!;
public PlayerLogic PlayerLogic { get; set; } = default!;
[Node] private AnimatedSprite2D SwordSlashAnimation { get; set; } = default!;
public PlayerLogic.IBinding PlayerBinding { get; set; } = default!;
[Node] private IHitbox Hitbox { get; set; } = default!;
[Node] public IAnimationPlayer AnimationPlayer { get; set; } = default!;
[Node] public AnimatedSprite2D SwordSlashAnimation { get; set; } = default!;
[Node] public IHitbox Hitbox { get; set; } = default!;
[Node] public Timer HealthTimer { get; set; } = default!;
[Node] public IArea3D CollisionDetector { get; set; } = default!;
[Node] private Timer HealthTimer { get; set; } = default!;
#endregion
private PlayerData PlayerData { get; set; } = default!;
@@ -177,8 +181,16 @@ namespace GameJamDungeon
GlobalPosition = GameRepo.PlayerGlobalPosition.Value;
GameRepo.PlayerGlobalPosition.Sync += PlayerGlobalPosition_Sync;
HealthTimer.Timeout += OnHealthTimerTimeout;
CollisionDetector.BodyEntered += CollisionDetector_BodyEntered;
PlayerData.Inventory.AccessoryUnequipped += Inventory_AccessoryUnequipped;
Hitbox.AreaEntered += Hitbox_AreaEntered;
}
private void Hitbox_AreaEntered(Area3D area)
{
var enemy = Hitbox.GetParent<IEnemy>();
enemy.TakeDamage((PlayerStatResource.CurrentAttack + PlayerStatResource.BonusAttack) * PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.ElementalDamageBonus,
PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.WeaponElement,
ignoreElementalResistance: PlayerData.Inventory.EquippedWeapon.Value.WeaponStats.WeaponTags.Contains(WeaponTag.IgnoreAffinity));
}
public void OnReady()
@@ -319,24 +331,6 @@ namespace GameJamDungeon
PlayerData.SetLuck(PlayerData.Luck.Value - unequippedAccessory.LUCKUp);
}
private void CollisionDetector_BodyEntered(Node3D body)
{
if (body is IHitbox hitBox)
{
var enemy = hitBox.GetParent<IEnemy>();
var isCriticalHit = false;
var rng = new RandomNumberGenerator();
rng.Randomize();
var roll = rng.Randf();
if (roll <= enemy.EnemyStatResource.Luck)
isCriticalHit = true;
var damage = DamageCalculator.CalculateEnemyAttackDamage(PlayerData.CurrentDefense.Value + PlayerData.BonusDefense, enemy.EnemyStatResource, GameRepo.PlayerData.Inventory.EquippedArmor.Value.ArmorStats, isCriticalHit);
PlayerData.SetCurrentHP(PlayerData.CurrentHP.Value - Mathf.RoundToInt(damage));
GD.Print($"Player hit for {damage} damage.");
}
}
private void CurrentHP_Sync(int newHealth)
{
if (newHealth <= 0)

View File

@@ -1,11 +1,11 @@
@startuml PlayerLogic
state "PlayerLogic State" as GameJamDungeon_PlayerLogic_State {
state "Disabled" as GameJamDungeon_PlayerLogic_State_Disabled
state "Dead" as GameJamDungeon_PlayerLogic_State_Dead
state "Alive" as GameJamDungeon_PlayerLogic_State_Alive {
state "Attacking" as GameJamDungeon_PlayerLogic_State_Attacking
state "Idle" as GameJamDungeon_PlayerLogic_State_Idle
}
state "Dead" as GameJamDungeon_PlayerLogic_State_Dead
state "Disabled" as GameJamDungeon_PlayerLogic_State_Disabled
}
GameJamDungeon_PlayerLogic_State_Alive --> GameJamDungeon_PlayerLogic_State_Alive : Moved

View File

@@ -0,0 +1,23 @@
namespace GameJamDungeon
{
public interface IHasPrimaryAttack
{
public ElementType PrimaryAttackElementalType { set; }
public double PrimaryAttackElementalDamageBonus { set; }
public void PrimaryAttack();
}
public interface IHasSecondaryAttack
{
public ElementType SecondaryAttackElementalType { set; }
public double SecondaryAttackElementalDamageBonus { set; }
public void SecondaryAttack();
}
public interface IHasPrimarySkill
{
public void PrimarySkill();
}
}

View File

@@ -4,68 +4,10 @@ namespace GameJamDungeon
{
public static class DamageCalculator
{
public static double CalculateWeaponAttackDamage(int totalAttack, EnemyStatResource enemyStatResource, WeaponStats weapon, bool isCriticalHit)
public static double CalculateWeaponAttackDamage(int totalAttack, WeaponStats weapon)
{
var hydricResistance = enemyStatResource.HydricResistance;
var igneousResistance = enemyStatResource.IgneousResistance;
var telluricResistance = enemyStatResource.TelluricResistance;
var aeolicResistance = enemyStatResource.AeolicResistance;
var ferrumResistance = enemyStatResource.FerrumResistance;
if (weapon.WeaponTags.Contains(WeaponTag.IgnoreAffinity))
{
hydricResistance = 0;
igneousResistance = 0;
telluricResistance = 0;
aeolicResistance = 0;
ferrumResistance = 0;
}
var elementADamage = (weapon.BaseHydricDamageBonus > 0 ? weapon.BaseHydricDamageBonus - hydricResistance : 0) / 100;
var elementBDamage = (weapon.IgneousDamageBonus > 0 ? weapon.IgneousDamageBonus - igneousResistance : 0) / 100;
var elementCDamage = (weapon.TelluricDamageBonus > 0 ? weapon.TelluricDamageBonus - telluricResistance : 0) / 100;
var elementDDamage = (weapon.AeolicDamageBonus > 0 ? weapon.AeolicDamageBonus - aeolicResistance : 0) / 100;
var elementEDamage = (weapon.FerrumDamageBonus > 0 ? weapon.FerrumDamageBonus - ferrumResistance : 0) / 100;
var elementalBonusDamage = totalAttack + (totalAttack * elementADamage) + (totalAttack * elementBDamage) + (totalAttack * elementCDamage) + (totalAttack * elementDDamage) + (totalAttack * elementEDamage);
var calculatedDamage = elementalBonusDamage - enemyStatResource.CurrentDefense;
if (isCriticalHit)
calculatedDamage *= 2;
return Mathf.Max(calculatedDamage, 0.0);
}
public static double CalculateEnemyAttackDamage(int playerTotalDefense, EnemyStatResource enemyStatResource, ArmorStats armor, bool isCriticalHit)
{
var totalAttack = enemyStatResource.CurrentAttack;
var elementADamage = (enemyStatResource.BaseHydricDamageBonus > 0 ? enemyStatResource.BaseHydricDamageBonus - armor.HydricResistance : 0) / 100;
var elementBDamage = (enemyStatResource.IgneousDamageBonus > 0 ? enemyStatResource.IgneousDamageBonus - armor.IgneousResistance : 0) / 100;
var elementCDamage = (enemyStatResource.TelluricDamageBonus > 0 ? enemyStatResource.TelluricDamageBonus - armor.TelluricResistance : 0) / 100;
var elementDDamage = (enemyStatResource.AeolicDamageBonus > 0 ? enemyStatResource.AeolicDamageBonus - armor.AeolicResistance : 0) / 100;
var elementEDamage = (enemyStatResource.FerrumDamageBonus > 0 ? enemyStatResource.FerrumDamageBonus - armor.FerrumResistance : 0) / 100;
var elementalBonusDamage = totalAttack + (totalAttack * elementADamage) + (totalAttack * elementBDamage) + (totalAttack * elementCDamage) + (totalAttack * elementDDamage) + (totalAttack * elementEDamage);
var calculatedDamage = elementalBonusDamage - playerTotalDefense - (armor != null ? armor.Defense : 0);
if (isCriticalHit)
calculatedDamage *= 2;
return Mathf.Max(calculatedDamage, 0.0);
}
public static double CalculateThrownItemDamage(int baseThrowDamage, EnemyStatResource enemyStatResource, ThrowableItemStats throwableItemStats)
{
if (throwableItemStats.ThrowableItemTags.Contains(ThrowableItemTag.InflictAeolicDamage))
return Mathf.Max(baseThrowDamage * enemyStatResource.AeolicResistance, 0.0);
if (throwableItemStats.ThrowableItemTags.Contains(ThrowableItemTag.InflictFerrumDamage))
return Mathf.Max(baseThrowDamage * enemyStatResource.FerrumResistance, 0.0);
if (throwableItemStats.ThrowableItemTags.Contains(ThrowableItemTag.InflictHydricDamage))
return Mathf.Max(baseThrowDamage * enemyStatResource.HydricResistance, 0.0);
if (throwableItemStats.ThrowableItemTags.Contains(ThrowableItemTag.InflictIgneousDamage))
return Mathf.Max(baseThrowDamage * enemyStatResource.IgneousResistance, 0.0);
if (throwableItemStats.ThrowableItemTags.Contains(ThrowableItemTag.InflictTelluricDamage))
return Mathf.Max(baseThrowDamage * enemyStatResource.TelluricResistance, 0.0);
return Mathf.Max(baseThrowDamage, 0.0);
var totalDamage = totalAttack * weapon.ElementalDamageBonus;
return Mathf.Max(totalDamage, 0.0);
}
}
}