diff --git a/Resources/Items/ICE_SHOTGUN_T1_3D_Item_3D.tres b/Resources/Items/ICE_SHOTGUN_T1_3D_Item_3D.tres new file mode 100644 index 00000000..5978422c --- /dev/null +++ b/Resources/Items/ICE_SHOTGUN_T1_3D_Item_3D.tres @@ -0,0 +1,20 @@ +[gd_resource type="Resource" script_class="LootItem" format=3 uid="uid://dx6dwdk3nhsl3"] + +[ext_resource type="Texture2D" uid="uid://dceyytbfpbywp" path="res://Sprites/Items/Ice_Shotgun_T1.png" id="1_be46j"] +[ext_resource type="Resource" uid="uid://cc82lnukilbaw" path="res://Resources/Weapons/ICE_SHOTGUN_T1_3D_3D.tres" id="2_n084c"] +[ext_resource type="Script" uid="uid://epnwjptvks3t" path="res://Scripts/Resources/LootItem.cs" id="3_eaig5"] + +[resource] +script = ExtResource("3_eaig5") +ItemName = &"Improved Ice Shotgun 3D" +ShortName = &"IC-28" +ItemDescription = &"Shoots ice pellets in a wide spread (T1)" +ItemKey = &"ICE_SHOTGUN_T1_3D" +Item = 9 +WeaponData3D = ExtResource("2_n084c") +Amount = 1 +Max = 1 +Selectable = true +InventorySprite = ExtResource("1_be46j") +DropScenePath = null +DropScenePath3D = &"res://Scenes/Items/GenericItem3D.tscn" diff --git a/Resources/Items/IceShotgun_Sawed_T1.tres b/Resources/Items/IceShotgun_Sawed_T1.tres index a040eb92..3e3f72de 100644 --- a/Resources/Items/IceShotgun_Sawed_T1.tres +++ b/Resources/Items/IceShotgun_Sawed_T1.tres @@ -1,4 +1,4 @@ -[gd_resource type="Resource" script_class="LootItem" load_steps=4 format=3 uid="uid://btk4kojtepwv"] +[gd_resource type="Resource" script_class="LootItem" format=3 uid="uid://btk4kojtepwv"] [ext_resource type="Texture2D" uid="uid://7r50e2264cnr" path="res://Sprites/Items/Ice_Shotgun_Sawed_T1.png" id="1_qo2ul"] [ext_resource type="Resource" uid="uid://cea6xftma1sd3" path="res://Resources/Weapons/Ice_Shotgun_Sawed_T1.tres" id="2_d8yv1"] @@ -12,15 +12,11 @@ ItemDescription = &"Shoots ice pellets in a wide spread" ItemKey = &"ICE_SHOTGUN_SAWED_T1" Item = 9 Tier = 1 -Price = 0 WeaponData = ExtResource("2_d8yv1") Amount = 1 Max = 1 -PickupIfMaxed = false -ConsumeOnUse = false -UiType = 0 +UiType = 14 Selectable = true -AutoPickup = false InventorySprite = ExtResource("1_qo2ul") DropScenePath = &"res://Scenes/Items/GenericItem.tscn" DropScenePath3D = &"uid://cnot7sft7lpf3" diff --git a/Resources/ItemsDatabase.tres b/Resources/ItemsDatabase.tres index 9ff501c4..8f237feb 100644 --- a/Resources/ItemsDatabase.tres +++ b/Resources/ItemsDatabase.tres @@ -36,8 +36,9 @@ [ext_resource type="Resource" uid="uid://dwwbyyy3fo4bt" path="res://Resources/Items/IcicleRepeater_Advanced.tres" id="33_q4lxx"] [ext_resource type="Resource" uid="uid://cecyd50cwsc8q" path="res://Resources/Items/DARK_MACHINE_GUN_Item.tres" id="34_5stko"] [ext_resource type="Resource" uid="uid://dsysp3umxyuva" path="res://Resources/Items/SPAGHETTI_Item.tres" id="35_5x77y"] +[ext_resource type="Resource" uid="uid://dx6dwdk3nhsl3" path="res://Resources/Items/ICE_SHOTGUN_T1_3D_Item_3D.tres" id="36_p1hpt"] [resource] script = ExtResource("1_al8ea") -LootItems = Array[ExtResource("1_nodmt")]([ExtResource("1_1s15f"), ExtResource("2_nsx5p"), ExtResource("3_oark3"), ExtResource("4_yj5fs"), ExtResource("5_wigtd"), ExtResource("6_ador3"), ExtResource("7_xxd6q"), ExtResource("8_33up1"), ExtResource("9_a23i7"), ExtResource("10_0hw56"), ExtResource("11_nodmt"), ExtResource("12_5stko"), ExtResource("13_5x77y"), ExtResource("14_p1hpt"), ExtResource("15_b6dqw"), ExtResource("16_a5xwj"), ExtResource("17_44qof"), ExtResource("18_i5d5q"), ExtResource("19_42kyh"), ExtResource("20_q4lxx"), ExtResource("21_detwd"), ExtResource("22_w8yo3"), ExtResource("23_nq6hj"), ExtResource("25_5stko"), ExtResource("26_5x77y"), ExtResource("27_p1hpt"), ExtResource("28_b6dqw"), ExtResource("29_a5xwj"), ExtResource("30_44qof"), ExtResource("31_i5d5q"), ExtResource("32_42kyh"), ExtResource("33_q4lxx"), ExtResource("34_5stko"), ExtResource("35_5x77y")]) +LootItems = Array[ExtResource("1_nodmt")]([ExtResource("1_1s15f"), ExtResource("2_nsx5p"), ExtResource("3_oark3"), ExtResource("4_yj5fs"), ExtResource("5_wigtd"), ExtResource("6_ador3"), ExtResource("7_xxd6q"), ExtResource("8_33up1"), ExtResource("9_a23i7"), ExtResource("10_0hw56"), ExtResource("11_nodmt"), ExtResource("12_5stko"), ExtResource("13_5x77y"), ExtResource("14_p1hpt"), ExtResource("15_b6dqw"), ExtResource("16_a5xwj"), ExtResource("17_44qof"), ExtResource("18_i5d5q"), ExtResource("19_42kyh"), ExtResource("20_q4lxx"), ExtResource("21_detwd"), ExtResource("22_w8yo3"), ExtResource("23_nq6hj"), ExtResource("25_5stko"), ExtResource("26_5x77y"), ExtResource("27_p1hpt"), ExtResource("28_b6dqw"), ExtResource("29_a5xwj"), ExtResource("30_44qof"), ExtResource("31_i5d5q"), ExtResource("32_42kyh"), ExtResource("33_q4lxx"), ExtResource("34_5stko"), ExtResource("35_5x77y"), ExtResource("36_p1hpt")]) metadata/_custom_type_script = "uid://c23prvgfitlpd" diff --git a/Resources/Weapons/ICE_SHOTGUN_T1_3D_3D.tres b/Resources/Weapons/ICE_SHOTGUN_T1_3D_3D.tres new file mode 100644 index 00000000..83d06a27 --- /dev/null +++ b/Resources/Weapons/ICE_SHOTGUN_T1_3D_3D.tres @@ -0,0 +1,23 @@ +[gd_resource type="Resource" script_class="WeaponResource" format=3 uid="uid://cc82lnukilbaw"] + +[ext_resource type="Resource" uid="uid://c2ptnbivq3ioj" path="res://Resources/Bullets/3D/icicle_repeater_bullets_3D.tres" id="1_hlpog"] +[ext_resource type="Script" uid="uid://b6fmrnipv88bk" path="res://Scripts/Resources/WeaponResource.cs" id="2_fsb4a"] +[ext_resource type="AudioStream" uid="uid://jsv3yjluv1au" path="res://SFX/Weapons/Reload_01.wav" id="2_xo8lg"] +[ext_resource type="AudioStream" uid="uid://oyjbk3qjp5cr" path="res://SFX/Chiptone_Source/Shotgun.wav" id="3_e875s"] + +[resource] +script = ExtResource("2_fsb4a") +Name = &"Improved Ice Shotgun 3D" +BulletData = ExtResource("1_hlpog") +Priority = 30 +RateOfFire = 0.4 +BulletCapacity = 4 +ReloadTime = 0.8 +InfiniteAmmo = false +ItemKey = &"ICE_SHOTGUN_T1_3D" +AmmoKey = &"ICE_AMMO" +BulletsPerShot = 3 +SpreadAngle = 8.0 +RandomSpread = 12.0 +ReloadSound = ExtResource("2_xo8lg") +ShootSound = ExtResource("3_e875s") diff --git a/Resources/Weapons/Ice_Shotgun_T0_3D.tres b/Resources/Weapons/Ice_Shotgun_T0_3D.tres index 270aa040..b66a3104 100644 --- a/Resources/Weapons/Ice_Shotgun_T0_3D.tres +++ b/Resources/Weapons/Ice_Shotgun_T0_3D.tres @@ -1,6 +1,7 @@ -[gd_resource type="Resource" script_class="WeaponResource" load_steps=5 format=3 uid="uid://bsdi1iudx5431"] +[gd_resource type="Resource" script_class="WeaponResource" format=3 uid="uid://bsdi1iudx5431"] [ext_resource type="Resource" uid="uid://c2ptnbivq3ioj" path="res://Resources/Bullets/3D/icicle_repeater_bullets_3D.tres" id="1_ublmp"] +[ext_resource type="Resource" uid="uid://cc82lnukilbaw" path="res://Resources/Weapons/ICE_SHOTGUN_T1_3D_3D.tres" id="2_uxcop"] [ext_resource type="AudioStream" uid="uid://jsv3yjluv1au" path="res://SFX/Weapons/Reload_01.wav" id="2_vgotw"] [ext_resource type="AudioStream" uid="uid://oyjbk3qjp5cr" path="res://SFX/Chiptone_Source/Shotgun.wav" id="3_uxcop"] [ext_resource type="Script" uid="uid://b6fmrnipv88bk" path="res://Scripts/Resources/WeaponResource.cs" id="4_u3gqe"] @@ -21,3 +22,5 @@ SpreadAngle = 8.0 RandomSpread = 15.0 ReloadSound = ExtResource("2_vgotw") ShootSound = ExtResource("3_uxcop") +ExperienceToNextLevel = 10 +NextLevelWeapon = ExtResource("2_uxcop") diff --git a/Scripts/Components/Actors/DamageReceiver3D.cs b/Scripts/Components/Actors/DamageReceiver3D.cs index e9258d9b..49fc59f9 100644 --- a/Scripts/Components/Actors/DamageReceiver3D.cs +++ b/Scripts/Components/Actors/DamageReceiver3D.cs @@ -2,6 +2,7 @@ using Cirno.Scripts.Resources; using Cirno.Scripts.Utils; using Cirno.Scripts.Weapons; +using Cirno.Scripts.Components.FSM.Enemy._3D; using Godot; using Godot.Collections; @@ -55,14 +56,24 @@ public partial class DamageReceiver3D : Area3D, IHittable return; } - ; - if (BulletGroup is BulletOwner.None) { this.Hit(bullet.Damage, bullet.DamageType); EmitSignalBulletHit(bullet, area.GlobalPosition, (this.GlobalPosition - area.GlobalPosition).Normalized()); + // Attribute XP if this hit was lethal + if (HealthProvider.CurrentResource <= 0 && bullet.BulletInfo?.SourceWeapon != null) + { + int xp = 0; + if (GetParent() is EnemyProxy3D enemyProxy && enemyProxy.EnemyResource is not null) + { + xp = (int)System.Math.Round(enemyProxy.EnemyResource.MotivationReward); + } + + bullet.BulletInfo.SourceWeapon.GainExperience(xp); + } + bullet.RequestCollisionDestruction(); return; } @@ -73,6 +84,18 @@ public partial class DamageReceiver3D : Area3D, IHittable EmitSignalBulletHit(bullet, area.GlobalPosition, (this.GlobalPosition - area.GlobalPosition).Normalized()); + // Attribute XP on lethal hit + if (HealthProvider.CurrentResource <= 0 && bullet.BulletInfo?.SourceWeapon != null) + { + int xp = 0; + if (GetParent() is EnemyProxy3D enemyProxy && enemyProxy.EnemyResource is not null) + { + xp = (int)System.Math.Round(enemyProxy.EnemyResource.MotivationReward); + } + + bullet.BulletInfo.SourceWeapon.GainExperience(xp); + } + bullet.RequestCollisionDestruction(); } diff --git a/Scripts/Components/Actors/DamageReceiverActorModule.cs b/Scripts/Components/Actors/DamageReceiverActorModule.cs index 3b3d3fd6..9fafd7ca 100644 --- a/Scripts/Components/Actors/DamageReceiverActorModule.cs +++ b/Scripts/Components/Actors/DamageReceiverActorModule.cs @@ -48,6 +48,15 @@ public partial class DamageReceiverActorModule : ActorModule, IHittable if (BulletGroup is BulletOwner.None) { this.Hit(bullet.Damage, bullet.DamageType); + + // If this hit killed the actor, attribute XP to the source weapon if present + if (HealthProvider.CurrentResource <= 0 && bullet.BulletInfo?.SourceWeapon != null) + { + // Award XP equal to actor's MotivationReward rounded to int, or a fixed value. + var xp = (int)System.Math.Round(_actor.EnemyData?.MotivationReward ?? 0f); + bullet.BulletInfo.SourceWeapon.GainExperience(xp); + } + bullet.RequestCollisionDestruction(); return; } @@ -55,6 +64,14 @@ public partial class DamageReceiverActorModule : ActorModule, IHittable if (bullet.BulletInfo.Owner == BulletGroup) return; this.Hit(bullet.Damage, bullet.DamageType); + + // Attribute XP on lethal hit + if (HealthProvider.CurrentResource <= 0 && bullet.BulletInfo?.SourceWeapon != null) + { + var xp = (int)System.Math.Round(_actor.EnemyData?.MotivationReward ?? 0f); + bullet.BulletInfo.SourceWeapon.GainExperience(xp); + } + bullet.RequestCollisionDestruction(); } diff --git a/Scripts/Components/BulletInfo.cs b/Scripts/Components/BulletInfo.cs index 2cf0ef78..99282505 100644 --- a/Scripts/Components/BulletInfo.cs +++ b/Scripts/Components/BulletInfo.cs @@ -2,6 +2,7 @@ using Cirno.Scripts.Actors._3D; using Cirno.Scripts.Resources; using Cirno.Scripts.Utils; +using Cirno.Scripts.Weapons; using Godot; namespace Cirno.Scripts.Components; @@ -49,4 +50,9 @@ public class BulletInfo(BulletResource originalBulletResource) public Color PreFireColor { get; set; } = new Color(1, 0, 0, 0.5f); public Color LethalColor { get; set; } = new Color(1, 0, 0, 1.0f); #endregion + + // Runtime: reference to the Weapon instance that fired this bullet. + // This is NOT a Resource and will not be serialized; it simply lets + // hit handlers attribute kills to a particular weapon instance. + public Weapon3D SourceWeapon { get; set; } } \ No newline at end of file diff --git a/Scripts/Resources/WeaponResource.cs b/Scripts/Resources/WeaponResource.cs index be40e15c..55293bcf 100644 --- a/Scripts/Resources/WeaponResource.cs +++ b/Scripts/Resources/WeaponResource.cs @@ -8,44 +8,47 @@ namespace Cirno.Scripts.Resources; [GlobalClass] [Tool] -public partial class WeaponResource : Resource +public partial class WeaponResource : Resource { - [Export] - public StringName Name { get; set; } - + [Export] public StringName Name { get; set; } + [Export] public BulletResource BulletData { get; private set; } - + //[Export] - //public PackedScene BulletScene { get; set; } - - //[Export] public PackedScene DestructionParticlesScene { get; set; } - [Export] public int Priority { get; set; } = 0; - [Export] public int AmmoPerShot { get; set; } = 1; + //public PackedScene BulletScene { get; set; } + + //[Export] public PackedScene DestructionParticlesScene { get; set; } + [Export] public int Priority { get; set; } = 0; + [Export] public int AmmoPerShot { get; set; } = 1; [Export] public double RateOfFire = 0.4f; - - [Export] public int BulletCapacity = 20; - - [Export] public double ReloadTime = 1.0f; - - [Export] public bool AutoReload = true; - - [Export] public bool InfiniteAmmo = true; - + + [Export] public int BulletCapacity = 20; + + [Export] public double ReloadTime = 1.0f; + + [Export] public bool AutoReload = true; + + [Export] public bool InfiniteAmmo = true; + [Export] public StringName ItemKey; [Export] public StringName AmmoKey; - [ExportCategory("Battery Recharge")] - [Export] public double RechargeTime = 0.5d; - [Export]public int RechargeAmount = 1; - [Export] public bool StopToShoot = false; - - #region Bullet spawn data - [ExportCategory("Bullet Spawn Data")] - [Export] public int BulletsPerShot = 1; + [ExportCategory("Battery Recharge")] [Export] + public double RechargeTime = 0.5d; + + [Export] public int RechargeAmount = 1; + [Export] public bool StopToShoot = false; + + #region Bullet spawn data + + [ExportCategory("Bullet Spawn Data")] [Export] + public int BulletsPerShot = 1; + + [Export] public float SpreadAngle = 0f; + + [Export] public float RandomSpread = 0f; - [Export] public float SpreadAngle = 0f; - [Export] public float RandomSpread = 0f; //[Export] public float BulletSpeed = 100f; //[Export] public float BulletDamage = 1; //[Export] public float LifeTime = 10f; @@ -56,33 +59,43 @@ public partial class WeaponResource : Resource //[Export] private Array _timeModifiers; #endregion - - [ExportCategory("Sounds")] - [Export] public AudioStream ReloadSound { get; set; } + + [ExportCategory("Sounds")] [Export] public AudioStream ReloadSound { get; set; } [Export] public AudioStream ShootSound { get; set; } [Export] public AudioStream EmptySound { get; set; } + // -------------------------------------------------- + // Upgrade / progression blueprint data + // Resources must remain blueprint-only; these fields describe + // how this weapon progresses to the next tier. + // -------------------------------------------------- + + [Export] + public int ExperienceToNextLevel { get; set; } = 0; // XP required to evolve to NextLevelWeapon (0 = no progression) + + [Export] public WeaponResource NextLevelWeapon { get; set; } // Reference to the next-tier weapon resource + public BulletInfo MakeBullet(Vector2 position) { - return BulletData.MakeBullet(position, BulletsPerShot, SpreadAngle, _rotationOffset); - - // return new BulletInfo() - // { - // Position = position, - // Direction = Vector2.Right, - // Speed = BulletSpeed, - // Owner = owner, - // DamageType = _damageType, - // Damage = BulletDamage, - // BulletCount = BulletsPerShot, - // Spread = SpreadAngle, - // BulletScene = BulletScene, - // RotationOffset = _rotationOffset, - // Modifier = _modifier as IBulletModifier, - // LifeTime = LifeTime, - // DestructionParticlesScene = DestructionParticlesScene, - // TimeModifiers = _timeModifiers?.Where(mod => mod is TimeModifier).Cast().ToList() ?? - // new List() - // }; + return BulletData.MakeBullet(position, BulletsPerShot, SpreadAngle, _rotationOffset); + + // return new BulletInfo() + // { + // Position = position, + // Direction = Vector2.Right, + // Speed = BulletSpeed, + // Owner = owner, + // DamageType = _damageType, + // Damage = BulletDamage, + // BulletCount = BulletsPerShot, + // Spread = SpreadAngle, + // BulletScene = BulletScene, + // RotationOffset = _rotationOffset, + // Modifier = _modifier as IBulletModifier, + // LifeTime = LifeTime, + // DestructionParticlesScene = DestructionParticlesScene, + // TimeModifiers = _timeModifiers?.Where(mod => mod is TimeModifier).Cast().ToList() ?? + // new List() + // }; } } \ No newline at end of file diff --git a/Scripts/Weapons/Weapon3D.cs b/Scripts/Weapons/Weapon3D.cs index 9620601e..b88396e2 100644 --- a/Scripts/Weapons/Weapon3D.cs +++ b/Scripts/Weapons/Weapon3D.cs @@ -32,6 +32,9 @@ public partial class Weapon3D : Node3D [Signal] public delegate void InitializedEventHandler(); + [Signal] + public delegate void EvolvedEventHandler(WeaponResource newWeaponResource); + public int Ammo { get; set; } = 0; private int _loadedAmmo; @@ -59,6 +62,9 @@ public partial class Weapon3D : Node3D private WeaponAmmoType _ammoType = WeaponAmmoType.Infinite; + // Runtime experience for this weapon instance (NOT stored in resources) + private int _currentExperience = 0; + // Called when the node enters the scene tree for the first time. public override void _Ready() @@ -150,6 +156,42 @@ public partial class Weapon3D : Node3D } } + /// + /// Add experience to this weapon instance. When reaching the threshold defined on the WeaponResource, + /// evolve into the configured NextLevelWeapon. Resources are not mutated; the instance swaps its reference + /// to the next-tier Resource to reflect the new behavior. + /// + /// Amount of experience to add (positive integer) + public void GainExperience(int amount) + { + if (amount <= 0) return; + if (WeaponData == null) return; + + // If weapon has no progression, ignore + if (WeaponData.ExperienceToNextLevel <= 0 || WeaponData.NextLevelWeapon == null) return; + + _currentExperience += amount; + + while (WeaponData.ExperienceToNextLevel > 0 && _currentExperience >= WeaponData.ExperienceToNextLevel) + { + // Evolve + _currentExperience -= WeaponData.ExperienceToNextLevel; + + var next = WeaponData.NextLevelWeapon; + if (next == null) break; + + WeaponData = next; + + // Re-init to apply new capacities / rates + Init(); + + EmitSignalEvolved(next); + + // Continue loop in case the new tier also has immediate threshold + if (WeaponData.ExperienceToNextLevel <= 0 || WeaponData.NextLevelWeapon == null) break; + } + } + private bool HandlePreShoot() { // Waiting on reload or Rate of Fire cooldown? @@ -243,6 +285,9 @@ public partial class Weapon3D : Node3D GetBulletStrengthMultiplier(bulletData.Damage, bulletData.OriginalBulletResource.MaxDamage, 20); } + // Associate the bullet with this weapon instance so kills can be attributed + bulletData.SourceWeapon = this; + bullet.Initialize(bulletData); //bullet.SetDirection(ShootDirection); @@ -252,12 +297,6 @@ public partial class Weapon3D : Node3D - //_inventoryManager.NotifyLoadedAmmoChange(WeaponData.ItemKey, LoadedAmmo); - // if (!string.IsNullOrWhiteSpace(WeaponData?.AmmoKey)) - // { - // // Notify hud to decrease weapon - // - // } if (_ammoType is WeaponAmmoType.Ammo && WeaponData.AutoReload && LoadedAmmo < WeaponData.AmmoPerShot) { @@ -280,11 +319,11 @@ public partial class Weapon3D : Node3D return Mathf.Lerp(minMultiplier, maxMultiplier, normalizedPower); } - public void Hide() + public new void Hide() { } - public void Show() + public new void Show() { } } \ No newline at end of file