From 6d00b8d6ab74ceb52c796600c59687e57c20eb0d Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Wed, 29 Oct 2025 01:26:35 +0100 Subject: [PATCH] Add weapon acquisition and swapping systems with data handling --- .../Combat/Interfaces/IWeaponDataService.cs | 6 +++ GameCore/Combat/WeaponAcquisitionSystem.cs | 39 +++++++++++++++ GameCore/Combat/WeaponData.cs | 15 ++++++ GameCore/Combat/WeaponSwapSystem.cs | 50 +++++++++++++++++++ GameCore/Combat/WeaponSystem.cs | 2 +- GameCore/Events/EquipWeaponEvent.cs | 10 ++++ GameCore/Events/WeaponFireFailedEvent.cs | 7 +++ GameCore/Input/InputStateComponent.cs | 2 + GameCore/Input/Interfaces/IInputService.cs | 2 + GameCore/Input/PlayerInputSystem.cs | 2 + GameCore/Player/EquipmentComponent.cs | 9 ++++ GameCore/Player/EquipmentSystem.cs | 42 ++++++++++++++++ 12 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 GameCore/Combat/Interfaces/IWeaponDataService.cs create mode 100644 GameCore/Combat/WeaponAcquisitionSystem.cs create mode 100644 GameCore/Combat/WeaponData.cs create mode 100644 GameCore/Combat/WeaponSwapSystem.cs create mode 100644 GameCore/Events/EquipWeaponEvent.cs create mode 100644 GameCore/Events/WeaponFireFailedEvent.cs create mode 100644 GameCore/Player/EquipmentComponent.cs create mode 100644 GameCore/Player/EquipmentSystem.cs diff --git a/GameCore/Combat/Interfaces/IWeaponDataService.cs b/GameCore/Combat/Interfaces/IWeaponDataService.cs new file mode 100644 index 0000000..d8dd04c --- /dev/null +++ b/GameCore/Combat/Interfaces/IWeaponDataService.cs @@ -0,0 +1,6 @@ +namespace GameCore.Combat.Interfaces; + +public interface IWeaponDataService +{ + bool TryGetWeaponData(string weaponId, out WeaponData weaponData); +} \ No newline at end of file diff --git a/GameCore/Combat/WeaponAcquisitionSystem.cs b/GameCore/Combat/WeaponAcquisitionSystem.cs new file mode 100644 index 0000000..1bfc1a8 --- /dev/null +++ b/GameCore/Combat/WeaponAcquisitionSystem.cs @@ -0,0 +1,39 @@ +using GameCore.ECS; +using GameCore.ECS.Interfaces; +using GameCore.Events; +using GameCore.Player; + +namespace GameCore.Combat; + +public class WeaponAcquisitionSystem : ISystem +{ + private const string WeaponItemPrefix = "weapon_"; + private readonly World _world; + + public WeaponAcquisitionSystem(World world) + { + _world = world; + _world.Subscribe(OnItemAdded); + } + + public void Update(World world, float deltaTime) + { + } + + private void OnItemAdded(AddItemToInventoryEvent e) + { + if (!e.Item.ItemId.StartsWith(WeaponItemPrefix)) return; + + var equipment = _world.GetComponent(e.Target); + if (equipment == null) return; + + if (!equipment.EquippableWeaponItemIds.Contains(e.Item.ItemId)) + equipment.EquippableWeaponItemIds.Add(e.Item.ItemId); + + if (equipment.CurrentWeaponIndex == -1) + { + equipment.CurrentWeaponIndex = equipment.EquippableWeaponItemIds.Count - 1; + _world.PublishEvent(new EquipWeaponEvent(e.Target, e.Item.ItemId)); + } + } +} \ No newline at end of file diff --git a/GameCore/Combat/WeaponData.cs b/GameCore/Combat/WeaponData.cs new file mode 100644 index 0000000..7151b45 --- /dev/null +++ b/GameCore/Combat/WeaponData.cs @@ -0,0 +1,15 @@ +using GameCore.Combat.Interfaces; + +namespace GameCore.Combat; + +public class WeaponData( + float fireRate, + List fireCosts, + List onFireEffects, + List onHitEffects) +{ + public float FireRate { get; set; } = fireRate; + public List FireCosts { get; set; } = fireCosts; + public List OnFireEffects { get; set; } = onFireEffects; + public List OnHitEffects { get; set; } = onHitEffects; +} \ No newline at end of file diff --git a/GameCore/Combat/WeaponSwapSystem.cs b/GameCore/Combat/WeaponSwapSystem.cs new file mode 100644 index 0000000..2beafbd --- /dev/null +++ b/GameCore/Combat/WeaponSwapSystem.cs @@ -0,0 +1,50 @@ +using GameCore.ECS; +using GameCore.ECS.Interfaces; +using GameCore.Events; +using GameCore.Input; +using GameCore.Player; + +namespace GameCore.Combat; + +public class WeaponSwapSystem : ISystem +{ + public void Update(World world, float deltaTime) + { + var entities = world.GetEntitiesWith(); + foreach (var entity in entities) + { + var input = world.GetComponent(entity); + var equipment = world.GetComponent(entity); + + if (input == null || equipment == null || equipment.EquippableWeaponItemIds.Count <= 1) + continue; + + var direction = SwapDirection.None; + if (input.IsSwapWeaponNext) direction = SwapDirection.Next; + if (input.IsSwapWeaponPrevious) direction = SwapDirection.Previous; + + if (direction != SwapDirection.None) + { + var newIndex = equipment.CurrentWeaponIndex + (int)direction; + var weaponCount = equipment.EquippableWeaponItemIds.Count; + + if (newIndex >= weaponCount) newIndex = 0; + if (newIndex < 0) newIndex = weaponCount - 1; + + if (newIndex != equipment.CurrentWeaponIndex) + { + equipment.CurrentWeaponIndex = newIndex; + var newWeaponId = equipment.EquippableWeaponItemIds[newIndex]; + world.PublishEvent(new EquipWeaponEvent(entity, newWeaponId)); + } + } + } + } + + private enum SwapDirection + { + None = 0, + Next = 1, + Previous = -1 + } +} \ No newline at end of file diff --git a/GameCore/Combat/WeaponSystem.cs b/GameCore/Combat/WeaponSystem.cs index 44d3eec..89c1a83 100644 --- a/GameCore/Combat/WeaponSystem.cs +++ b/GameCore/Combat/WeaponSystem.cs @@ -34,7 +34,7 @@ public class WeaponSystem : ISystem if (!cost.CanAfford(context)) { canFire = false; - //TODO: Publish an event or notify the player they can't fire + world.PublishEvent(new WeaponFireFailedEvent()); break; } diff --git a/GameCore/Events/EquipWeaponEvent.cs b/GameCore/Events/EquipWeaponEvent.cs new file mode 100644 index 0000000..9e4c9c4 --- /dev/null +++ b/GameCore/Events/EquipWeaponEvent.cs @@ -0,0 +1,10 @@ +using GameCore.ECS; +using GameCore.Events.Interfaces; + +namespace GameCore.Events; + +public readonly struct EquipWeaponEvent(Entity owner, string newWeaponItemId) : IEvent +{ + public readonly Entity Owner = owner; + public readonly string NewWeaponItemId = newWeaponItemId; +} \ No newline at end of file diff --git a/GameCore/Events/WeaponFireFailedEvent.cs b/GameCore/Events/WeaponFireFailedEvent.cs new file mode 100644 index 0000000..7e4c798 --- /dev/null +++ b/GameCore/Events/WeaponFireFailedEvent.cs @@ -0,0 +1,7 @@ +using GameCore.Events.Interfaces; + +namespace GameCore.Events; + +public readonly struct WeaponFireFailedEvent : IEvent +{ +} \ No newline at end of file diff --git a/GameCore/Input/InputStateComponent.cs b/GameCore/Input/InputStateComponent.cs index 3572b1a..d09903e 100644 --- a/GameCore/Input/InputStateComponent.cs +++ b/GameCore/Input/InputStateComponent.cs @@ -13,4 +13,6 @@ public class InputStateComponent : IComponent, IInputService public bool IsJumping { get; set; } public Vector3 MoveDirection { get; set; } public Vector3 LookDirection { get; set; } + public bool IsSwapWeaponNext { get; set; } + public bool IsSwapWeaponPrevious { get; set; } } \ No newline at end of file diff --git a/GameCore/Input/Interfaces/IInputService.cs b/GameCore/Input/Interfaces/IInputService.cs index 6390d09..6cbd01e 100644 --- a/GameCore/Input/Interfaces/IInputService.cs +++ b/GameCore/Input/Interfaces/IInputService.cs @@ -9,4 +9,6 @@ public interface IInputService public bool IsJumping { get; } public Vector3 MoveDirection { get; } public Vector3 LookDirection { get; } + public bool IsSwapWeaponNext { get; } + public bool IsSwapWeaponPrevious { get; } } \ No newline at end of file diff --git a/GameCore/Input/PlayerInputSystem.cs b/GameCore/Input/PlayerInputSystem.cs index de1296f..d1843d2 100644 --- a/GameCore/Input/PlayerInputSystem.cs +++ b/GameCore/Input/PlayerInputSystem.cs @@ -20,5 +20,7 @@ public class PlayerInputSystem : ISystem inputState.IsJumping = world.InputService.IsJumping; inputState.IsInteracting = world.InputService.IsInteracting; inputState.IsFiring = world.InputService.IsFiring; + inputState.IsSwapWeaponNext = world.InputService.IsSwapWeaponNext; + inputState.IsSwapWeaponPrevious = world.InputService.IsSwapWeaponPrevious; } } \ No newline at end of file diff --git a/GameCore/Player/EquipmentComponent.cs b/GameCore/Player/EquipmentComponent.cs new file mode 100644 index 0000000..73abba3 --- /dev/null +++ b/GameCore/Player/EquipmentComponent.cs @@ -0,0 +1,9 @@ +using GameCore.ECS.Interfaces; + +namespace GameCore.Player; + +public class EquipmentComponent : IComponent +{ + public List EquippableWeaponItemIds { get; set; } = []; + public int CurrentWeaponIndex { get; set; } = -1; // -1 indicates no weapon equipped +} \ No newline at end of file diff --git a/GameCore/Player/EquipmentSystem.cs b/GameCore/Player/EquipmentSystem.cs new file mode 100644 index 0000000..564be28 --- /dev/null +++ b/GameCore/Player/EquipmentSystem.cs @@ -0,0 +1,42 @@ +using GameCore.Combat; +using GameCore.Combat.Interfaces; +using GameCore.ECS; +using GameCore.ECS.Interfaces; +using GameCore.Events; + +namespace GameCore.Player; + +public class EquipmentSystem : ISystem +{ + private readonly IWeaponDataService _weaponDataService; + private readonly World _world; + + public EquipmentSystem(World world, IWeaponDataService weaponDataService) + { + _world = world; + _weaponDataService = weaponDataService; + _world.Subscribe(OnEquipWeapon); + } + + public void Update(World world, float deltaTime) + { + } + + private void OnEquipWeapon(EquipWeaponEvent e) + { + if (!_weaponDataService.TryGetWeaponData(e.NewWeaponItemId, out var newData)) return; + + var weaponComponent = _world.GetComponent(e.Owner); + if (weaponComponent == null) + { + weaponComponent = new WeaponComponent(); + _world.AddComponent(e.Owner, weaponComponent); + } + + weaponComponent.FireRate = newData.FireRate; + weaponComponent.FireCosts = newData.FireCosts; + weaponComponent.OnFireEffects = newData.OnFireEffects; + weaponComponent.OnHitEffects = newData.OnHitEffects; + weaponComponent.CooldownTimer = 0f; + } +} \ No newline at end of file