diff --git a/GameCore/Combat/Effects/MeleeEffect.cs b/GameCore/Combat/Effects/MeleeEffect.cs new file mode 100644 index 0000000..dea506a --- /dev/null +++ b/GameCore/Combat/Effects/MeleeEffect.cs @@ -0,0 +1,29 @@ +using GameCore.Combat.Interfaces; +using GameCore.Input; + +namespace GameCore.Combat.Effects; + +public class MeleeEffect(float range, float radius) : IEffect +{ + public void Execute(EffectContext context) + { + var input = context.World.GetComponent(context.Owner); + var weapon = context.World.GetComponent(context.Owner); + if (input == null || weapon == null) return; + + var checkPosition = input.MuzzlePosition + input.MuzzleDirection * range; + var hits = context.World.WorldQuery.OverlapSphere(checkPosition, radius, context.Owner); + + foreach (var hitEntity in hits) + { + var hitContext = new EffectContext + { + World = context.World, + Owner = context.Owner, + Target = hitEntity + }; + + foreach (var effect in weapon.OnHitEffects) effect.Execute(hitContext); + } + } +} \ No newline at end of file diff --git a/GameCore/ECS/Interfaces/IWorldQuery.cs b/GameCore/ECS/Interfaces/IWorldQuery.cs index 697ef80..81d2223 100644 --- a/GameCore/ECS/Interfaces/IWorldQuery.cs +++ b/GameCore/ECS/Interfaces/IWorldQuery.cs @@ -6,4 +6,5 @@ public interface IWorldQuery { HitResult Raycast(Vector3 from, Vector3 to, Entity? ownerToExclude = null); Vector3 RotateVectorByYaw(Vector3 vector, float yawRadians); + IEnumerable OverlapSphere(Vector3 position, float radius, Entity? ownerToExclude = null); } \ No newline at end of file diff --git a/GameCore/ECS/World.cs b/GameCore/ECS/World.cs index 876d609..3380c19 100644 --- a/GameCore/ECS/World.cs +++ b/GameCore/ECS/World.cs @@ -5,6 +5,8 @@ using GameCore.Events.Interfaces; using GameCore.Input.Interfaces; using GameCore.Interaction; using GameCore.Logging.Interfaces; +using GameCore.Services; +using GameCore.State; namespace GameCore.ECS; @@ -13,18 +15,37 @@ namespace GameCore.ECS; /// It manages all entities, components, and systems, and orchestrates the main game loop. /// This class is the primary point of interaction for the Engine/Presentation layer. /// -public class World(IInputService inputService, IWorldQuery worldQuery, SimulationConfig config, ILogger logger) +public class World { private readonly Dictionary> _componentsByEntityId = new(); private readonly EventBus _eventBus = new(); private readonly List _systems = []; + public readonly IAudioService AudioService; - public readonly SimulationConfig Config = config; - public readonly IInputService InputService = inputService; - public readonly ILogger Logger = logger; - public readonly IWorldQuery WorldQuery = worldQuery; + public readonly SimulationConfig Config; + public readonly IGameStateService GameState; + public readonly IInputService InputService; + public readonly ILogger Logger; + public readonly IParticleService ParticleService; + public readonly IWorldQuery WorldQuery; private int _nextEntityId; + public World(IInputService inputService, + IWorldQuery worldQuery, + SimulationConfig config, + ILogger logger, + IAudioService audioService, + IParticleService particleService) + { + AudioService = audioService; + Config = config; + InputService = inputService; + Logger = logger; + ParticleService = particleService; + WorldQuery = worldQuery; + GameState = new GameStateService(this); + } + /// /// Creates a new, unique entity. /// diff --git a/GameCore/Events/GameStatusChangedEvent.cs b/GameCore/Events/GameStatusChangedEvent.cs new file mode 100644 index 0000000..0da36bd --- /dev/null +++ b/GameCore/Events/GameStatusChangedEvent.cs @@ -0,0 +1,9 @@ +using GameCore.Events.Interfaces; +using GameCore.State; + +namespace GameCore.Events; + +public readonly struct GameStatusChangedEvent(GameStatus newStatus) : IEvent +{ + public readonly GameStatus NewStatus = newStatus; +} \ No newline at end of file diff --git a/GameCore/Services/IAudioService.cs b/GameCore/Services/IAudioService.cs new file mode 100644 index 0000000..23feada --- /dev/null +++ b/GameCore/Services/IAudioService.cs @@ -0,0 +1,9 @@ +using GameCore.Math; + +namespace GameCore.Services; + +public interface IAudioService +{ + void Play(string soundId); + void PlayAtPoint(string soundId, Vector3 position); +} \ No newline at end of file diff --git a/GameCore/Services/IParticleService.cs b/GameCore/Services/IParticleService.cs new file mode 100644 index 0000000..736724a --- /dev/null +++ b/GameCore/Services/IParticleService.cs @@ -0,0 +1,9 @@ +using GameCore.Math; + +namespace GameCore.Services; + +public interface IParticleService +{ + void Trigger(string particleId, Vector3 position); + void Trigger(string particleId, Vector3 position, Vector3 normal); +} \ No newline at end of file diff --git a/GameCore/State/GameStateService.cs b/GameCore/State/GameStateService.cs new file mode 100644 index 0000000..26f0880 --- /dev/null +++ b/GameCore/State/GameStateService.cs @@ -0,0 +1,40 @@ +using GameCore.ECS; +using GameCore.Events; + +namespace GameCore.State; + +public class GameStateService(World world) : IGameStateService +{ + private readonly HashSet _unlockedLevelIds = []; + private readonly World _world = world; + public GameStatus CurrentStatus { get; private set; } = GameStatus.InMainMenu; + public string CurrentLevelId { get; private set; } + public IReadOnlyCollection UnlockedLevelIds => _unlockedLevelIds; + + public void SetStatus(GameStatus newStatus) + { + if (CurrentStatus == newStatus) return; + + CurrentStatus = newStatus; + _world.PublishEvent(new GameStatusChangedEvent(newStatus)); + } + + public void StartGame(string startingLevelId) + { + CurrentLevelId = startingLevelId; + _unlockedLevelIds.Add(startingLevelId); + SetStatus(GameStatus.InGame); + } + + public void UnlockLevel(string levelId) + { + _unlockedLevelIds.Add(levelId); + } + + public void ResetProgress() + { + _unlockedLevelIds.Clear(); + CurrentLevelId = null!; + SetStatus(GameStatus.InMainMenu); + } +} \ No newline at end of file diff --git a/GameCore/State/GameStatus.cs b/GameCore/State/GameStatus.cs new file mode 100644 index 0000000..8300ed8 --- /dev/null +++ b/GameCore/State/GameStatus.cs @@ -0,0 +1,9 @@ +namespace GameCore.State; + +public enum GameStatus +{ + InMainMenu, + InGame, + Paused, + GameOver +} \ No newline at end of file diff --git a/GameCore/State/IGameStateService.cs b/GameCore/State/IGameStateService.cs new file mode 100644 index 0000000..b296f50 --- /dev/null +++ b/GameCore/State/IGameStateService.cs @@ -0,0 +1,13 @@ +namespace GameCore.State; + +public interface IGameStateService +{ + GameStatus CurrentStatus { get; } + string CurrentLevelId { get; } + IReadOnlyCollection UnlockedLevelIds { get; } + + void SetStatus(GameStatus newStatus); + void StartGame(string startingLevelId); + void UnlockLevel(string levelId); + void ResetProgress(); +} \ No newline at end of file