From 567c422f8cd42eba2437f9a8c2522716a1649be7 Mon Sep 17 00:00:00 2001 From: hazel Date: Mon, 26 Jan 2026 22:04:39 +0100 Subject: celesteia archive, last updated april 9th 2024 Signed-off-by: hazel --- source/game/WorldManager.cs | 33 +++ source/game/components/CameraFollow.cs | 5 + source/game/components/EntityAttributes.cs | 35 +++ source/game/components/TargetPosition.cs | 7 + source/game/components/entity/GameWorldEntity.cs | 14 ++ source/game/components/items/Inventory.cs | 96 +++++++ source/game/components/items/ItemStack.cs | 34 +++ source/game/components/physics/CollisionBox.cs | 36 +++ source/game/components/physics/PhysicsEntity.cs | 38 +++ source/game/components/player/LocalPlayer.cs | 5 + source/game/components/player/PlayerInput.cs | 14 ++ source/game/components/skybox/SkyboxRotateZ.cs | 17 ++ source/game/ecs/EntityFactory.cs | 111 +++++++++ source/game/ecs/GameWorld.cs | 41 +++ source/game/input/GamepadHelper.cs | 59 +++++ source/game/input/InputManager.cs | 33 +++ source/game/input/KeyboardHelper.cs | 53 ++++ source/game/input/MouseHelper.cs | 37 +++ source/game/input/conditions/AllCondition.cs | 15 ++ source/game/input/conditions/AnyCondition.cs | 15 ++ source/game/input/conditions/AverageCondition.cs | 16 ++ source/game/input/conditions/ICondition.cs | 5 + source/game/input/definitions/InputDefinition.cs | 16 ++ .../definitions/gamepad/BinaryGamepadDefinition.cs | 10 + .../definitions/gamepad/SensorGamepadDefinition.cs | 13 + .../keyboard/BinaryKeyboardDefinition.cs | 11 + .../keyboard/TrinaryKeyboardDefinition.cs | 18 ++ .../definitions/mouse/BinaryMouseDefinition.cs | 10 + source/game/items/BlockItemActions.cs | 73 ++++++ source/game/items/CooldownItemActions.cs | 17 ++ source/game/items/FoliageItemActions.cs | 18 ++ source/game/items/IItemActions.cs | 11 + source/game/items/PickaxeItemActions.cs | 63 +++++ source/game/items/TorchItemActions.cs | 15 ++ source/game/items/UpgradeItemActions.cs | 48 ++++ source/game/music/MusicManager.cs | 54 ++++ source/game/planets/BlockState.cs | 43 ++++ source/game/planets/Chunk.cs | 158 ++++++++++++ source/game/planets/ChunkMap.cs | 99 ++++++++ source/game/planets/GeneratedPlanet.cs | 35 +++ source/game/planets/generation/IWorldGenerator.cs | 18 ++ .../planets/generation/TerranPlanetGenerator.cs | 275 +++++++++++++++++++++ source/game/systems/CameraRenderSystem.cs | 43 ++++ source/game/systems/CameraSystem.cs | 39 +++ source/game/systems/ChunkMapRenderSystem.cs | 63 +++++ source/game/systems/EntityDebugSystem.cs | 44 ++++ source/game/systems/LightingSystem.cs | 100 ++++++++ source/game/systems/LocalPlayerSystem.cs | 246 ++++++++++++++++++ source/game/systems/TargetPositionSystem.cs | 37 +++ .../systems/mainmenu/MainMenuBackgroundSystem.cs | 33 +++ .../game/systems/mainmenu/MainMenuRenderSystem.cs | 43 ++++ .../systems/physics/PhysicsCollisionDebugSystem.cs | 61 +++++ source/game/systems/physics/PhysicsSystem.cs | 42 ++++ .../systems/physics/PhysicsWorldCollisionSystem.cs | 83 +++++++ source/game/systems/ui/GameGUIDrawSystem.cs | 12 + 55 files changed, 2570 insertions(+) create mode 100644 source/game/WorldManager.cs create mode 100644 source/game/components/CameraFollow.cs create mode 100644 source/game/components/EntityAttributes.cs create mode 100644 source/game/components/TargetPosition.cs create mode 100644 source/game/components/entity/GameWorldEntity.cs create mode 100644 source/game/components/items/Inventory.cs create mode 100644 source/game/components/items/ItemStack.cs create mode 100644 source/game/components/physics/CollisionBox.cs create mode 100644 source/game/components/physics/PhysicsEntity.cs create mode 100644 source/game/components/player/LocalPlayer.cs create mode 100644 source/game/components/player/PlayerInput.cs create mode 100644 source/game/components/skybox/SkyboxRotateZ.cs create mode 100644 source/game/ecs/EntityFactory.cs create mode 100644 source/game/ecs/GameWorld.cs create mode 100644 source/game/input/GamepadHelper.cs create mode 100644 source/game/input/InputManager.cs create mode 100644 source/game/input/KeyboardHelper.cs create mode 100644 source/game/input/MouseHelper.cs create mode 100644 source/game/input/conditions/AllCondition.cs create mode 100644 source/game/input/conditions/AnyCondition.cs create mode 100644 source/game/input/conditions/AverageCondition.cs create mode 100644 source/game/input/conditions/ICondition.cs create mode 100644 source/game/input/definitions/InputDefinition.cs create mode 100644 source/game/input/definitions/gamepad/BinaryGamepadDefinition.cs create mode 100644 source/game/input/definitions/gamepad/SensorGamepadDefinition.cs create mode 100644 source/game/input/definitions/keyboard/BinaryKeyboardDefinition.cs create mode 100644 source/game/input/definitions/keyboard/TrinaryKeyboardDefinition.cs create mode 100644 source/game/input/definitions/mouse/BinaryMouseDefinition.cs create mode 100644 source/game/items/BlockItemActions.cs create mode 100644 source/game/items/CooldownItemActions.cs create mode 100644 source/game/items/FoliageItemActions.cs create mode 100644 source/game/items/IItemActions.cs create mode 100644 source/game/items/PickaxeItemActions.cs create mode 100644 source/game/items/TorchItemActions.cs create mode 100644 source/game/items/UpgradeItemActions.cs create mode 100644 source/game/music/MusicManager.cs create mode 100644 source/game/planets/BlockState.cs create mode 100644 source/game/planets/Chunk.cs create mode 100644 source/game/planets/ChunkMap.cs create mode 100644 source/game/planets/GeneratedPlanet.cs create mode 100644 source/game/planets/generation/IWorldGenerator.cs create mode 100644 source/game/planets/generation/TerranPlanetGenerator.cs create mode 100644 source/game/systems/CameraRenderSystem.cs create mode 100644 source/game/systems/CameraSystem.cs create mode 100644 source/game/systems/ChunkMapRenderSystem.cs create mode 100644 source/game/systems/EntityDebugSystem.cs create mode 100644 source/game/systems/LightingSystem.cs create mode 100644 source/game/systems/LocalPlayerSystem.cs create mode 100644 source/game/systems/TargetPositionSystem.cs create mode 100644 source/game/systems/mainmenu/MainMenuBackgroundSystem.cs create mode 100644 source/game/systems/mainmenu/MainMenuRenderSystem.cs create mode 100644 source/game/systems/physics/PhysicsCollisionDebugSystem.cs create mode 100644 source/game/systems/physics/PhysicsSystem.cs create mode 100644 source/game/systems/physics/PhysicsWorldCollisionSystem.cs create mode 100644 source/game/systems/ui/GameGUIDrawSystem.cs (limited to 'source/game') diff --git a/source/game/WorldManager.cs b/source/game/WorldManager.cs new file mode 100644 index 0000000..31040f8 --- /dev/null +++ b/source/game/WorldManager.cs @@ -0,0 +1,33 @@ +using System; +using System.Threading.Tasks; +using Celesteia.Game.ECS; +using Celesteia.Game.Planets; +using Celesteia.Game.Planets.Generation; +using Microsoft.Xna.Framework; + +namespace Celesteia.Game { + public class WorldManager : GameComponent + { + private new GameInstance Game => (GameInstance) base.Game; + public WorldManager(GameInstance Game) : base(Game) {} + + private GameWorld _loaded; + + private GameWorld LoadWorld(GameWorld gameWorld) { + if (_loaded != null) _loaded.Dispose(); + + return _loaded = gameWorld; + } + + public async Task LoadNewWorld(Action progressReport = null, int? seed = null) { + // Asynchronously generate the world. + GameWorld generatedWorld = await Task.Run(() => { + GeneratedPlanet planet = new GeneratedPlanet(250, 75, seed); + planet.Generate(new TerranPlanetGenerator(planet), progressReport); + return new GameWorld(planet); + }); + + return LoadWorld(generatedWorld); + } + } +} \ No newline at end of file diff --git a/source/game/components/CameraFollow.cs b/source/game/components/CameraFollow.cs new file mode 100644 index 0000000..d6ae2f7 --- /dev/null +++ b/source/game/components/CameraFollow.cs @@ -0,0 +1,5 @@ +namespace Celesteia.Game.Components { + public class CameraFollow { + public float Weight = 1f; + } +} \ No newline at end of file diff --git a/source/game/components/EntityAttributes.cs b/source/game/components/EntityAttributes.cs new file mode 100644 index 0000000..74a8e82 --- /dev/null +++ b/source/game/components/EntityAttributes.cs @@ -0,0 +1,35 @@ +using System; +using System.Linq; + +namespace Celesteia.Game.Components { + public class EntityAttributes { + public EntityAttributeMap Attributes; + + public EntityAttributes(EntityAttributeMap attributes) { + Attributes = attributes; + } + + public EntityAttributes() : this(new EntityAttributeMap()) {} + + public partial class EntityAttributeMap { + private float[] attributes; + + public EntityAttributeMap() { + attributes = new float[Enum.GetValues(typeof(EntityAttribute)).Cast().Count()]; + } + + public EntityAttributeMap Set(EntityAttribute attribute, float value) { + attributes[(int) attribute] = value; + return this; + } + + public float Get(EntityAttribute attribute) { + return attributes[(int) attribute]; + } + } + } + + public enum EntityAttribute { + MovementSpeed, JumpFuel, JumpForce, BlockRange + } +} \ No newline at end of file diff --git a/source/game/components/TargetPosition.cs b/source/game/components/TargetPosition.cs new file mode 100644 index 0000000..f8b1a9c --- /dev/null +++ b/source/game/components/TargetPosition.cs @@ -0,0 +1,7 @@ +using Microsoft.Xna.Framework; + +namespace Celesteia.Game.Components { + public class TargetPosition { + public Vector2 Target; + } +} \ No newline at end of file diff --git a/source/game/components/entity/GameWorldEntity.cs b/source/game/components/entity/GameWorldEntity.cs new file mode 100644 index 0000000..3087865 --- /dev/null +++ b/source/game/components/entity/GameWorldEntity.cs @@ -0,0 +1,14 @@ +using Celesteia.Game.ECS; + +namespace Celesteia.Game.Components.Entity { + public class GameWorldEntity { + private GameWorld _w; + private int _id; + public GameWorldEntity(GameWorld w, int id) { + _w = w; + _id = id; + } + + public void Destroy() => _w.DestroyEntity(_id); + } +} \ No newline at end of file diff --git a/source/game/components/items/Inventory.cs b/source/game/components/items/Inventory.cs new file mode 100644 index 0000000..4309f5d --- /dev/null +++ b/source/game/components/items/Inventory.cs @@ -0,0 +1,96 @@ +using System; +using Celesteia.Resources; + +namespace Celesteia.Game.Components.Items { + public class Inventory { + private ItemStack[] items; + public readonly int Capacity; + + public Inventory(int slots = 27, params ItemStack[] startingItems) { + Capacity = slots; + items = new ItemStack[slots]; + + for (int i = 0; i < startingItems.Length; i++ ) items[i] = startingItems[i]; + } + + // Try adding an item to the inventory, return false if the inventory has no capacity for this action. + public bool AddItem(ItemStack stack) { + ItemStack existingStack = GetLastItemStack(stack.Key); + + // If an item stack with this ID already exists, add the amount to that stack. + if (existingStack != null) { + existingStack.Amount += stack.Amount; + + // If the max stack size was exceeded and a new stack has to be created, create it. + ItemStack newStack = existingStack.NewStack(); + if (newStack != null) { + if (!HasCapacity()) return false; + existingStack.Amount = existingStack.Type.MaxStackSize; + AddItemStack(newStack); + } + } else AddItemStack(stack); + + return true; + } + + public ItemStack GetItemStackWithID(byte id) { + return Array.FindLast(items, x => x != null && x.ID == id && x.Amount < x.Type.MaxStackSize); + } + + public ItemStack GetLastItemStack(NamespacedKey key) { + return Array.FindLast(items, x => x != null && x.Key.Equals(key) && x.Amount < x.Type.MaxStackSize); + } + + public ItemStack GetSlot(int slot) { + if (slot < 0 || slot > Capacity - 1) throw new ArgumentException($"Slot {slot} falls outside of the inventory's capacity."); + return items[slot]; + } + + public void SetSlot(int slot, ItemStack stack) { + if (slot < 0 || slot > Capacity - 1) throw new ArgumentException($"Slot {slot} falls outside of the inventory's capacity."); + items[slot] = stack; + } + + public bool ContainsAmount(NamespacedKey key, int amount) { + return GetAmount(key) >= amount; + } + + public int GetAmount(NamespacedKey key) { + int amount = 0; + + ItemStack[] stacksOfItem = Array.FindAll(items, x => x != null && x.Key.Equals(key)); + foreach (ItemStack stackOfItem in stacksOfItem) amount += stackOfItem.Amount; + + return amount; + } + + public void RemoveAmount(NamespacedKey key, int amount) { + int amountToGo = amount; + + ItemStack lastStack; + while (amountToGo > 0) { + lastStack = Array.FindLast(items, x => x != null && x.Key.Equals(key)); + int toRemove = Math.Min(lastStack.Amount, amountToGo); + + lastStack.Amount -= toRemove; + amountToGo -= toRemove; + + AssertAmounts(); + } + } + + private void AddItemStack(ItemStack newStack) { + if (!HasCapacity()) return; + int i = Array.FindIndex(items, x => x == null); + items[i] = newStack; + } + + private bool HasCapacity() { + return Array.Exists(items, x => x == null); + } + + public void AssertAmounts() { + for (int i = 0; i < items.Length; i++) if (items[i] != null && items[i].Amount <= 0) items[i] = null; + } + } +} \ No newline at end of file diff --git a/source/game/components/items/ItemStack.cs b/source/game/components/items/ItemStack.cs new file mode 100644 index 0000000..360904b --- /dev/null +++ b/source/game/components/items/ItemStack.cs @@ -0,0 +1,34 @@ +using Celesteia.Resources; +using Celesteia.Resources.Types; + +namespace Celesteia.Game.Components.Items { + public class ItemStack { + public byte ID; + public NamespacedKey Key; + public int Amount; + public readonly ItemType Type; + + public ItemStack(NamespacedKey key, int amount) { + Key = key; + Amount = amount; + + Type = ResourceManager.Items.GetResource(key) as ItemType; + } + + public ItemStack NewStack() { + ItemStack stack = null; + if (Amount > Type.MaxStackSize) stack = new ItemStack(Key, Amount - Type.MaxStackSize); + + return stack; + } + + public ItemStack Clone() { + return new ItemStack(Key, Amount); + } + + public override string ToString() + { + return $"{Amount}x {Type.Name}"; + } + } +} \ No newline at end of file diff --git a/source/game/components/physics/CollisionBox.cs b/source/game/components/physics/CollisionBox.cs new file mode 100644 index 0000000..6e678ac --- /dev/null +++ b/source/game/components/physics/CollisionBox.cs @@ -0,0 +1,36 @@ +using System; +using MonoGame.Extended; +using Microsoft.Xna.Framework; + +namespace Celesteia.Game.Components.Physics { + public class CollisionBox { + public RectangleF _bounds; + public RectangleF Bounds { get => _bounds; set => _bounds = value; } + private Rectangle _rounded; + public Rectangle Rounded { get => _rounded; } + + public CollisionBox(float width, float height) { + _bounds = new RectangleF(-width / 2f, -height / 2f, width, height); + _rounded = Round(_bounds); + } + + public void Update(Vector2 position) { + _bounds.X = position.X - (Bounds.Width / 2f); + _bounds.Y = position.Y - (Bounds.Height / 2f); + _rounded = Round(_bounds); + } + + public Rectangle Round(RectangleF floatRect) { + return new Rectangle((int)MathF.Floor(floatRect.X), (int)MathF.Floor(floatRect.Y), (int)MathF.Ceiling(floatRect.Width), (int)MathF.Ceiling(floatRect.Height)); + } + + public RectangleF Intersection(CollisionBox other) { + return Intersection(other.Bounds); + } + + public RectangleF Intersection(RectangleF other) { + RectangleF intersection = other.Intersection(Bounds); + return intersection; + } + } +} \ No newline at end of file diff --git a/source/game/components/physics/PhysicsEntity.cs b/source/game/components/physics/PhysicsEntity.cs new file mode 100644 index 0000000..7214dff --- /dev/null +++ b/source/game/components/physics/PhysicsEntity.cs @@ -0,0 +1,38 @@ +using Microsoft.Xna.Framework; + +namespace Celesteia.Game.Components.Physics { + public class PhysicsEntity { + public float Mass; + + public bool Gravity; + + public bool CollidingUp; + public bool CollidingLeft; + public bool CollidingRight; + public bool CollidingDown; + + public PhysicsEntity(float mass, bool affectedByGravity) { + Mass = mass; + Gravity = affectedByGravity; + } + + private Vector2 _velocity; + public Vector2 Velocity => _velocity; + + public void SetVelocity(Vector2 vector) { + _velocity = vector; + } + + public void SetVelocity(float x, float y) { + SetVelocity(new Vector2(x, y)); + } + + public void AddVelocity(Vector2 vector) { + _velocity += vector; + } + + public void AddVelocity(float x, float y) { + AddVelocity(new Vector2(x, y)); + } + } +} \ No newline at end of file diff --git a/source/game/components/player/LocalPlayer.cs b/source/game/components/player/LocalPlayer.cs new file mode 100644 index 0000000..4780ec1 --- /dev/null +++ b/source/game/components/player/LocalPlayer.cs @@ -0,0 +1,5 @@ +namespace Celesteia.Game.Components.Player { + public class LocalPlayer { + public float JumpRemaining = .5f; + } +} \ No newline at end of file diff --git a/source/game/components/player/PlayerInput.cs b/source/game/components/player/PlayerInput.cs new file mode 100644 index 0000000..96f3a57 --- /dev/null +++ b/source/game/components/player/PlayerInput.cs @@ -0,0 +1,14 @@ +using Celesteia.Game.Input.Conditions; + +namespace Celesteia.Game.Components.Player { + public class PlayerInput { + public ICondition Horizontal; + public ICondition Run; + public ICondition Jump; + public ICondition Inventory; + public ICondition Crafting; + public ICondition Pause; + public ICondition PrimaryUse; + public ICondition SecondaryUse; + } +} \ No newline at end of file diff --git a/source/game/components/skybox/SkyboxRotateZ.cs b/source/game/components/skybox/SkyboxRotateZ.cs new file mode 100644 index 0000000..151aec9 --- /dev/null +++ b/source/game/components/skybox/SkyboxRotateZ.cs @@ -0,0 +1,17 @@ +namespace Celesteia.Game.Components.Skybox { + public class SkyboxRotateZ { + public float _magnitude = 1f; + // Amount to rotate by. + public float Magnitude { get { return _magnitude; } } + + // Current rotation. + public float Current = 0f; + + public SkyboxRotateZ(float magnitude) { + _magnitude = magnitude; + + // Offset the starting rotation. + Current = _magnitude * 256f; + } + } +} \ No newline at end of file diff --git a/source/game/ecs/EntityFactory.cs b/source/game/ecs/EntityFactory.cs new file mode 100644 index 0000000..482b137 --- /dev/null +++ b/source/game/ecs/EntityFactory.cs @@ -0,0 +1,111 @@ +using Celesteia.Resources; +using Microsoft.Xna.Framework; +using MonoGame.Extended; +using MonoGame.Extended.Entities; +using Celesteia.Resources.Sprites; +using Celesteia.Game.Components.Player; +using MonoGame.Extended.TextureAtlases; +using Microsoft.Xna.Framework.Graphics; +using Celesteia.Game.Input; +using Celesteia.Game.Components; +using Microsoft.Xna.Framework.Input; +using Celesteia.Game.Components.Physics; +using Celesteia.Game.Components.Items; +using Celesteia.Game.Components.Skybox; +using Celesteia.Resources.Types; +using Celesteia.Game.Input.Definitions.Keyboard; +using Celesteia.Game.Input.Definitions.Gamepad; +using Celesteia.Game.Input.Definitions.Mouse; +using MonoGame.Extended.Input; +using Celesteia.Game.Input.Definitions; +using Celesteia.Game.Input.Conditions; + +namespace Celesteia.Game.ECS { + /* + Contains various commonly used prefabrications for entities. + Many of the functions were moved to EntityTypes. + */ + + public class EntityFactory { + private readonly GameWorld _gameWorld; + + public EntityFactory(GameWorld gameWorld) => _gameWorld = gameWorld; + + public Entity CreateEntity(NamespacedKey key) => CreateEntity(ResourceManager.Entities.GetResource(key) as EntityType); + + public Entity CreateEntity(EntityType type) + { + Entity entity = _gameWorld.CreateEntity(); + type.Instantiate(entity); + + return entity; + } + + public static void BuildPlayer(Entity entity, Texture2D sprites) { + entity.Attach(new Transform2()); + + entity.Attach(new TargetPosition()); + + entity.Attach(new EntityFrames( + TextureAtlas.Create("player", sprites, 24, 24), + 0, 2, + ResourceManager.SPRITE_SCALING + )); + + entity.Attach(new Inventory(36, + new ItemStack(NamespacedKey.Base("iron_pickaxe"), 1), + new ItemStack(NamespacedKey.Base("wooden_torch"), 10) + )); + + entity.Attach(new PhysicsEntity(1.6f, true)); + + entity.Attach(new CollisionBox(1.5f, 3f)); + + entity.Attach(new PlayerInput() { + Horizontal = new AverageCondition( + new TrinaryKeyboardDefinition() { Negative = Keys.A, Positive = Keys.D, PollType = InputPollType.Held }, + new SensorGamepadDefinition() { Sensor = GamePadSensor.LeftThumbStickX } + ), + Run = new AnyCondition( + new BinaryKeyboardDefinition() { Keys = Keys.LeftShift, PollType = InputPollType.Held }, + new BinaryGamepadDefinition() { Buttons = Buttons.LeftShoulder, PollType = InputPollType.Held } + ), + Jump = new AnyCondition( + new BinaryKeyboardDefinition() { Keys = Keys.Space, PollType = InputPollType.Held }, + new BinaryGamepadDefinition() { Buttons = Buttons.A, PollType = InputPollType.Held } + ), + Inventory = new AnyCondition( + new BinaryKeyboardDefinition() { Keys = Keys.B, PollType = InputPollType.Pressed }, + new BinaryGamepadDefinition() { Buttons = Buttons.Y, PollType = InputPollType.Pressed } + ), + Crafting = new AnyCondition( + new BinaryKeyboardDefinition() { Keys = Keys.C, PollType = InputPollType.Pressed }, + new BinaryGamepadDefinition() { Buttons = Buttons.X, PollType = InputPollType.Pressed } + ), + Pause = new AnyCondition( + new BinaryKeyboardDefinition() { Keys = Keys.Escape, PollType = InputPollType.Pressed }, + new BinaryGamepadDefinition() { Buttons = Buttons.Start, PollType = InputPollType.Pressed } + ), + PrimaryUse = new AnyCondition( + new BinaryMouseDefinition() { Button = MouseButton.Left, PollType = InputPollType.Held }, + new BinaryGamepadDefinition() { Buttons = Buttons.RightTrigger, PollType = InputPollType.Held } + ), + SecondaryUse = new AnyCondition( + new BinaryMouseDefinition() { Button = MouseButton.Right, PollType = InputPollType.Held }, + new BinaryGamepadDefinition() { Buttons = Buttons.LeftTrigger, PollType = InputPollType.Held } + ) + }); + + entity.Attach(new LocalPlayer()); + + entity.Attach(new CameraFollow()); + + entity.Attach(new EntityAttributes(new EntityAttributes.EntityAttributeMap() + .Set(EntityAttribute.MovementSpeed, 12.5f) + .Set(EntityAttribute.JumpFuel, .5f) + .Set(EntityAttribute.JumpForce, 10f) + .Set(EntityAttribute.BlockRange, 7f) + )); + } + } +} \ No newline at end of file diff --git a/source/game/ecs/GameWorld.cs b/source/game/ecs/GameWorld.cs new file mode 100644 index 0000000..9ee516e --- /dev/null +++ b/source/game/ecs/GameWorld.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using Celesteia.Game.Components.Entity; +using Celesteia.Game.Planets; +using Celesteia.Game.Systems; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Entities; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.ECS { + public class GameWorld : IDisposable { + public ChunkMap ChunkMap { get; private set; } + public GameWorld(ChunkMap map) => ChunkMap = map; + + public void Dispose() { + _w.Dispose(); + ChunkMap = null; + } + + private World _w; + private WorldBuilder _builder; + + public WorldBuilder BeginBuilder() => _builder = new WorldBuilder(); + + public WorldBuilder AddSystem(ISystem system) => _builder.AddSystem(system); + + public void EndBuilder() => _w = _builder.Build(); + + public Entity CreateEntity() { + Entity e = _w.CreateEntity(); + e.Attach(new GameWorldEntity(this, e.Id)); + + return e; + } + + public void DestroyEntity(int id) => _w.DestroyEntity(id); + + public void Update(GameTime gt) => _w.Update(gt); + public void Draw(GameTime gt) => _w.Draw(gt); + } +} \ No newline at end of file diff --git a/source/game/input/GamepadHelper.cs b/source/game/input/GamepadHelper.cs new file mode 100644 index 0000000..dfc3175 --- /dev/null +++ b/source/game/input/GamepadHelper.cs @@ -0,0 +1,59 @@ +using System; +using Celesteia.Game.Input.Definitions; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace Celesteia.Game.Input { + public static class GamepadHelper { + private static GamePadState _prev; + private static GamePadState _curr; + + public static void Update() { + _prev = _curr; + _curr = GamePad.GetState(0); + } + + public static bool AnyButton() { + foreach (Buttons b in Enum.GetValues()) + if (IsDown(b)) return true; + + return false; + } + + // Was true, now true. + public static bool Held(Buttons buttons) => _prev.IsButtonDown(buttons) && _curr.IsButtonDown(buttons); + // Is true. + public static bool IsDown(Buttons buttons) => _curr.IsButtonDown(buttons); + // Was false, now true. + public static bool Pressed(Buttons buttons) => !_prev.IsButtonDown(buttons) && _curr.IsButtonDown(buttons); + // Was true, now false. + public static bool Released(Buttons buttons) => _prev.IsButtonDown(buttons) && !_curr.IsButtonDown(buttons); + + public static bool PollButtons(Buttons buttons, InputPollType type) => type switch { + InputPollType.Held => Held(buttons), + InputPollType.IsDown => IsDown(buttons), + InputPollType.Pressed => Pressed(buttons), + InputPollType.Released => Released(buttons), + _ => false + }; + + public static float PollSensor(GamePadSensor sensor) => sensor switch { + GamePadSensor.LeftThumbStickX => _curr.ThumbSticks.Left.X, + GamePadSensor.LeftThumbStickY => _curr.ThumbSticks.Left.Y, + GamePadSensor.LeftTrigger => _curr.Triggers.Left, + GamePadSensor.RightThumbStickX => _curr.ThumbSticks.Right.X, + GamePadSensor.RightThumbStickY => _curr.ThumbSticks.Right.Y, + GamePadSensor.RightTrigger => _curr.Triggers.Right, + _ => 0f + }; + } + + public enum GamePadSensor { + LeftThumbStickX, + LeftThumbStickY, + LeftTrigger, + RightThumbStickX, + RightThumbStickY, + RightTrigger + } +} \ No newline at end of file diff --git a/source/game/input/InputManager.cs b/source/game/input/InputManager.cs new file mode 100644 index 0000000..1bd3e38 --- /dev/null +++ b/source/game/input/InputManager.cs @@ -0,0 +1,33 @@ +using Microsoft.Xna.Framework; + +namespace Celesteia.Game.Input { + public class InputManager : GameComponent { + private new GameInstance Game => (GameInstance) base.Game; + public InputManager(GameInstance Game) : base(Game) {} + + public override void Initialize() + { + base.Initialize(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + } + + public override void Update(GameTime gameTime) + { + if (!Enabled) return; + + KeyboardHelper.Update(); + GamepadHelper.Update(); + MouseHelper.Update(); + + base.Update(gameTime); + } + + public bool GetAny() { + return KeyboardHelper.AnyKey() || GamepadHelper.AnyButton(); + } + } +} \ No newline at end of file diff --git a/source/game/input/KeyboardHelper.cs b/source/game/input/KeyboardHelper.cs new file mode 100644 index 0000000..a8fd4f0 --- /dev/null +++ b/source/game/input/KeyboardHelper.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; +using Celesteia.Game.Input.Definitions; +using Microsoft.Xna.Framework.Input; +using MonoGame.Extended.Input; + +namespace Celesteia.Game.Input { + public static class KeyboardHelper { + private static KeyboardStateExtended _prev; + private static KeyboardStateExtended _curr; + + public static void Update() { + _prev = _curr; + _curr = KeyboardExtended.GetState(); + } + + // Is any key (excluding the exemptions) down in the current state? + public static List exemptedFromAny = new List { + { Keys.F3 }, { Keys.F11 } + }; + + private static Keys[] pressed; + public static bool AnyKey() { + pressed = _curr.GetPressedKeys(); + bool anyKey = pressed.Length > 0; + + if (anyKey) + for (int i = 0; i < exemptedFromAny.Count; i++) + anyKey = !pressed.Contains(exemptedFromAny[i]); + + return anyKey; + } + + // Was true, now true. + public static bool Held(Keys keys) => _prev.IsKeyDown(keys) && _curr.IsKeyDown(keys); + // Is true. + public static bool IsDown(Keys keys) => _curr.IsKeyDown(keys); + // Was false, now true. + public static bool Pressed(Keys keys) => !_prev.IsKeyDown(keys) && _curr.IsKeyDown(keys); + // Was true, now false. + public static bool Released(Keys keys) => _prev.IsKeyDown(keys) && !_curr.IsKeyDown(keys); + + public static bool Poll(Keys keys, InputPollType type) => type switch { + InputPollType.Held => Held(keys), + InputPollType.IsDown => IsDown(keys), + InputPollType.Pressed => Pressed(keys), + InputPollType.Released => Released(keys), + _ => false + }; + } + + +} \ No newline at end of file diff --git a/source/game/input/MouseHelper.cs b/source/game/input/MouseHelper.cs new file mode 100644 index 0000000..3000c6e --- /dev/null +++ b/source/game/input/MouseHelper.cs @@ -0,0 +1,37 @@ +using Celesteia.Game.Input.Definitions; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Input; + +namespace Celesteia.Game.Input { + public static class MouseHelper { + private static MouseStateExtended _prev; + private static MouseStateExtended _curr; + + public static int ScrollDelta => _curr.ScrollWheelValue - _prev.ScrollWheelValue; + + // Get the position of the mouse pointer on the SCREEN, not the VIEWPORT. + public static Point Position => _curr.Position; + + public static void Update() { + _prev = _curr; + _curr = MouseExtended.GetState(); + } + + // Was true, now true. + public static bool Held(MouseButton button) => _prev.IsButtonDown(button) && _curr.IsButtonDown(button); + // Is true. + public static bool IsDown(MouseButton button) => _curr.IsButtonDown(button); + // Was false, now true. + public static bool Pressed(MouseButton button) => !_prev.IsButtonDown(button) && _curr.IsButtonDown(button); + // Was true, now false. + public static bool Released(MouseButton button) => _prev.IsButtonDown(button) && !_curr.IsButtonDown(button); + + public static bool Poll(MouseButton button, InputPollType type) => type switch { + InputPollType.Held => Held(button), + InputPollType.IsDown => IsDown(button), + InputPollType.Pressed => Pressed(button), + InputPollType.Released => Released(button), + _ => false + }; + } +} \ No newline at end of file diff --git a/source/game/input/conditions/AllCondition.cs b/source/game/input/conditions/AllCondition.cs new file mode 100644 index 0000000..183acfb --- /dev/null +++ b/source/game/input/conditions/AllCondition.cs @@ -0,0 +1,15 @@ +using Celesteia.Game.Input.Definitions; + +namespace Celesteia.Game.Input.Conditions { + public class AllCondition : ICondition { + private IBinaryInputDefinition[] _definitions; + + public AllCondition(params IBinaryInputDefinition[] definitions) + => _definitions = definitions; + + public bool Poll() { + for (int i = 0; i < _definitions.Length; i++) if (!_definitions[i].Test()) return false; + return true; + } + } +} \ No newline at end of file diff --git a/source/game/input/conditions/AnyCondition.cs b/source/game/input/conditions/AnyCondition.cs new file mode 100644 index 0000000..a870539 --- /dev/null +++ b/source/game/input/conditions/AnyCondition.cs @@ -0,0 +1,15 @@ +using Celesteia.Game.Input.Definitions; + +namespace Celesteia.Game.Input.Conditions { + public class AnyCondition : ICondition { + private IBinaryInputDefinition[] _definitions; + + public AnyCondition(params IBinaryInputDefinition[] definitions) + => _definitions = definitions; + + public bool Poll() { + for (int i = 0; i < _definitions.Length; i++) if (_definitions[i].Test()) return true; + return false; + } + } +} \ No newline at end of file diff --git a/source/game/input/conditions/AverageCondition.cs b/source/game/input/conditions/AverageCondition.cs new file mode 100644 index 0000000..08abdb7 --- /dev/null +++ b/source/game/input/conditions/AverageCondition.cs @@ -0,0 +1,16 @@ +using Celesteia.Game.Input.Definitions; + +namespace Celesteia.Game.Input.Conditions { + public class AverageCondition : ICondition { + private IFloatInputDefinition[] _definitions; + + public AverageCondition(params IFloatInputDefinition[] definitions) + => _definitions = definitions; + + public float Poll() { + float total = 0f; + for (int i = 0; i < _definitions.Length; i++) total += _definitions[i].Test(); + return total / _definitions.Length; + } + } +} \ No newline at end of file diff --git a/source/game/input/conditions/ICondition.cs b/source/game/input/conditions/ICondition.cs new file mode 100644 index 0000000..32bbc74 --- /dev/null +++ b/source/game/input/conditions/ICondition.cs @@ -0,0 +1,5 @@ +namespace Celesteia.Game.Input.Conditions { + public interface ICondition { + T Poll(); + } +} \ No newline at end of file diff --git a/source/game/input/definitions/InputDefinition.cs b/source/game/input/definitions/InputDefinition.cs new file mode 100644 index 0000000..7d56f99 --- /dev/null +++ b/source/game/input/definitions/InputDefinition.cs @@ -0,0 +1,16 @@ +namespace Celesteia.Game.Input.Definitions { + public interface IInputDefinition { + T Test(); + } + + public interface IBinaryInputDefinition : IInputDefinition {} + public interface IFloatInputDefinition : IInputDefinition {} + + public enum InputType { + Keyboard, Gamepad + } + + public enum InputPollType { + IsDown, Held, Pressed, Released + } +} \ No newline at end of file diff --git a/source/game/input/definitions/gamepad/BinaryGamepadDefinition.cs b/source/game/input/definitions/gamepad/BinaryGamepadDefinition.cs new file mode 100644 index 0000000..5aec7a8 --- /dev/null +++ b/source/game/input/definitions/gamepad/BinaryGamepadDefinition.cs @@ -0,0 +1,10 @@ +using Microsoft.Xna.Framework.Input; + +namespace Celesteia.Game.Input.Definitions.Gamepad { + public class BinaryGamepadDefinition : IBinaryInputDefinition { + public Buttons Buttons; + public InputPollType PollType; + + public bool Test() => GamepadHelper.PollButtons(Buttons, PollType); + } +} \ No newline at end of file diff --git a/source/game/input/definitions/gamepad/SensorGamepadDefinition.cs b/source/game/input/definitions/gamepad/SensorGamepadDefinition.cs new file mode 100644 index 0000000..ebbf2be --- /dev/null +++ b/source/game/input/definitions/gamepad/SensorGamepadDefinition.cs @@ -0,0 +1,13 @@ +using System; + +namespace Celesteia.Game.Input.Definitions.Gamepad { + public class SensorGamepadDefinition : IFloatInputDefinition { + public GamePadSensor Sensor; + private float _current = 0; + + public float Test() { + _current = GamepadHelper.PollSensor(Sensor); + return _current; + } + } +} \ No newline at end of file diff --git a/source/game/input/definitions/keyboard/BinaryKeyboardDefinition.cs b/source/game/input/definitions/keyboard/BinaryKeyboardDefinition.cs new file mode 100644 index 0000000..87ebb77 --- /dev/null +++ b/source/game/input/definitions/keyboard/BinaryKeyboardDefinition.cs @@ -0,0 +1,11 @@ +using System; +using Microsoft.Xna.Framework.Input; + +namespace Celesteia.Game.Input.Definitions.Keyboard { + public class BinaryKeyboardDefinition : IBinaryInputDefinition { + public Keys Keys; + public InputPollType PollType; + + public bool Test() => KeyboardHelper.Poll(Keys, PollType); + } +} \ No newline at end of file diff --git a/source/game/input/definitions/keyboard/TrinaryKeyboardDefinition.cs b/source/game/input/definitions/keyboard/TrinaryKeyboardDefinition.cs new file mode 100644 index 0000000..7439859 --- /dev/null +++ b/source/game/input/definitions/keyboard/TrinaryKeyboardDefinition.cs @@ -0,0 +1,18 @@ +using Microsoft.Xna.Framework.Input; + +namespace Celesteia.Game.Input.Definitions.Keyboard { + public class TrinaryKeyboardDefinition : IFloatInputDefinition { + public Keys Negative; + public Keys Positive; + public InputPollType PollType; + + private float _current = 0; + + public float Test() { + _current = + (KeyboardHelper.Poll(Negative, PollType) ? -1 : 0) + + (KeyboardHelper.Poll(Positive, PollType) ? 1 : 0); + return _current; + } + } +} \ No newline at end of file diff --git a/source/game/input/definitions/mouse/BinaryMouseDefinition.cs b/source/game/input/definitions/mouse/BinaryMouseDefinition.cs new file mode 100644 index 0000000..0008ded --- /dev/null +++ b/source/game/input/definitions/mouse/BinaryMouseDefinition.cs @@ -0,0 +1,10 @@ +using MonoGame.Extended.Input; + +namespace Celesteia.Game.Input.Definitions.Mouse { + public class BinaryMouseDefinition : IBinaryInputDefinition { + public MouseButton Button; + public InputPollType PollType; + + public bool Test() => MouseHelper.Poll(Button, PollType); + } +} \ No newline at end of file diff --git a/source/game/items/BlockItemActions.cs b/source/game/items/BlockItemActions.cs new file mode 100644 index 0000000..94b6c67 --- /dev/null +++ b/source/game/items/BlockItemActions.cs @@ -0,0 +1,73 @@ +using Celesteia.Game.Components; +using Celesteia.Game.Components.Physics; +using Celesteia.Game.Planets; +using Celesteia.Resources; +using Celesteia.Resources.Types; +using Microsoft.Xna.Framework; +using MonoGame.Extended; +using MonoGame.Extended.Entities; + +namespace Celesteia.Game.Items { + public class BlockItemActions : CooldownItemActions { + protected BlockType type; + protected byte id = 0; + + public BlockItemActions(NamespacedKey blockKey) { + UseTime = 0; + type = ResourceManager.Blocks.GetResource(blockKey) as BlockType; + id = type.GetID(); + } + + public override bool Primary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) + => Assert(gameTime, chunkMap, cursor, user, false) && Place(chunkMap, cursor, false); + + public override bool Secondary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) + => Assert(gameTime, chunkMap, cursor, user, true) && Place(chunkMap, cursor, true); + + public virtual bool Assert(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user, bool forWall) { + if (!base.Assert(gameTime)) return false; + if (id == 0) return false; + + UpdateLastUse(gameTime); + + if (!user.Has() || !user.Has()) return false; + + Transform2 entityTransform = user.Get(); + EntityAttributes.EntityAttributeMap attributes = user.Get().Attributes; + + if (Vector2.Distance(entityTransform.Position, cursor.ToVector2()) > attributes.Get(EntityAttribute.BlockRange)) return false; + + if (!forWall && user.Has()) { + Rectangle box = user.Get().Rounded; + RectangleF? rect = chunkMap.TestBoundingBox(cursor.X, cursor.Y, type.BoundingBox); + if (rect.HasValue) { + bool intersect = rect.Intersects(new RectangleF(box.X, box.Y, box.Width, box.Height)); + if (intersect) return false; + } + } + + if (!(forWall ? + chunkMap.GetBackground(cursor.X, cursor.Y) : + chunkMap.GetForeground(cursor.X, cursor.Y)) + .Empty) return false; + + // Require adjacent blocks or a filled tile on the other layer. + return ( + chunkMap.GetAny(cursor.X - 1, cursor.Y) || + chunkMap.GetAny(cursor.X + 1, cursor.Y) || + chunkMap.GetAny(cursor.X, cursor.Y - 1) || + chunkMap.GetAny(cursor.X, cursor.Y + 1) + ) || (forWall ? + chunkMap.GetForeground(cursor.X, cursor.Y) : + chunkMap.GetBackground(cursor.X, cursor.Y)) + .Empty; + } + + public bool Place(ChunkMap chunkMap, Point cursor, bool wall) { + if (wall) chunkMap.SetBackgroundID(cursor, id); + else chunkMap.SetForegroundID(cursor, id); + + return true; + } + } +} \ No newline at end of file diff --git a/source/game/items/CooldownItemActions.cs b/source/game/items/CooldownItemActions.cs new file mode 100644 index 0000000..647661e --- /dev/null +++ b/source/game/items/CooldownItemActions.cs @@ -0,0 +1,17 @@ +using Celesteia.Game.Planets; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Entities; + +namespace Celesteia.Game.Items { + public class CooldownItemActions : IItemActions { + public double UseTime = 1.0; + public double LastUse = 0.0; + public void UpdateLastUse(GameTime gameTime) => LastUse = gameTime.TotalGameTime.TotalSeconds; + public bool CheckUseTime(GameTime gameTime) => gameTime.TotalGameTime.TotalSeconds - LastUse >= UseTime; + + public virtual bool Assert(GameTime gameTime) => CheckUseTime(gameTime); + + public virtual bool Primary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) => false; + public virtual bool Secondary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) => false; + } +} \ No newline at end of file diff --git a/source/game/items/FoliageItemActions.cs b/source/game/items/FoliageItemActions.cs new file mode 100644 index 0000000..54bde6d --- /dev/null +++ b/source/game/items/FoliageItemActions.cs @@ -0,0 +1,18 @@ +using Celesteia.Game.Planets; +using Celesteia.Resources; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Entities; + +namespace Celesteia.Game.Items { + public class FoliageItemActions : BlockItemActions { + byte grown_soil; + public FoliageItemActions(NamespacedKey blockKey) : base(blockKey) { + grown_soil = ResourceManager.Blocks.GetResource(NamespacedKey.Base("grown_soil")).GetID(); + } + + public override bool Secondary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) => false; + + public override bool Assert(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user, bool forWall) + => chunkMap.GetForeground(cursor.X, cursor.Y + 1).BlockID == grown_soil && base.Assert(gameTime, chunkMap, cursor, user, false); + } +} \ No newline at end of file diff --git a/source/game/items/IItemActions.cs b/source/game/items/IItemActions.cs new file mode 100644 index 0000000..5a95f06 --- /dev/null +++ b/source/game/items/IItemActions.cs @@ -0,0 +1,11 @@ +using Celesteia.Game.Planets; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Entities; + +namespace Celesteia.Game.Items { + public interface IItemActions { + + public bool Primary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user); + public bool Secondary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user); + } +} \ No newline at end of file diff --git a/source/game/items/PickaxeItemActions.cs b/source/game/items/PickaxeItemActions.cs new file mode 100644 index 0000000..56e2714 --- /dev/null +++ b/source/game/items/PickaxeItemActions.cs @@ -0,0 +1,63 @@ +using Celesteia.Game.Components; +using Celesteia.Game.Components.Items; +using Celesteia.Game.Planets; +using Celesteia.Resources; +using Microsoft.Xna.Framework; +using MonoGame.Extended; +using MonoGame.Extended.Entities; + +namespace Celesteia.Game.Items { + public class PickaxeItemActions : CooldownItemActions { + private int _power; + + public PickaxeItemActions(int power) { + UseTime = 0.2; + _power = power; + } + + public override bool Primary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) + => Assert(gameTime, chunkMap, cursor, user, false) && Break(chunkMap, cursor, user, false); + + public override bool Secondary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) + => Assert(gameTime, chunkMap, cursor, user, true) && Break(chunkMap, cursor, user, true); + + // Check if the conditions to use this item's action are met. + public bool Assert(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user, bool forWall) { + if (!base.Assert(gameTime)) return false; + + // If the user has no transform or attributes, the rest of the function will not work, so check if they're there first. + if (!user.Has() || !user.Has()) return false; + + Transform2 entityTransform = user.Get(); + EntityAttributes.EntityAttributeMap attributes = user.Get().Attributes; + + // Check if the targeted location is within the entity's block manipulation range. + if (Vector2.Distance(entityTransform.Position, cursor.ToVector2()) > attributes.Get(EntityAttribute.BlockRange)) return false; + + BlockState state = (forWall ? chunkMap.GetBackground(cursor) : chunkMap.GetForeground(cursor)); + + // If there is no tile in the given location, the action will not continue. + if (state.Empty) return false; + + // If the block is unbreakable, don't break it. Duh. + if (state.Type.Strength < 0) return false; + + UpdateLastUse(gameTime); + + return true; + } + + + public bool Break(ChunkMap chunkMap, Point cursor, Entity user, bool wall) { + // Add breaking progress accordingly. + ItemStack drops = null; + if (wall) chunkMap.AddBackgroundBreakProgress(cursor, _power, out drops); + else chunkMap.AddForegroundBreakProgress(cursor, _power, out drops); + + if (drops != null && user.Has()) + user.Get().AddItem(drops); + + return true; + } + } +} \ No newline at end of file diff --git a/source/game/items/TorchItemActions.cs b/source/game/items/TorchItemActions.cs new file mode 100644 index 0000000..12eb8ed --- /dev/null +++ b/source/game/items/TorchItemActions.cs @@ -0,0 +1,15 @@ +using Celesteia.Game.Planets; +using Celesteia.Resources; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Entities; + +namespace Celesteia.Game.Items { + public class TorchItemActions : BlockItemActions { + public TorchItemActions(NamespacedKey blockKey) : base(blockKey) {} + + public override bool Secondary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) => false; + + public override bool Assert(GameTime g, ChunkMap cm, Point c, Entity u, bool wa = false) + => !cm.GetBackground(c).Empty && base.Assert(g, cm, c, u, false); + } +} \ No newline at end of file diff --git a/source/game/items/UpgradeItemActions.cs b/source/game/items/UpgradeItemActions.cs new file mode 100644 index 0000000..18d922d --- /dev/null +++ b/source/game/items/UpgradeItemActions.cs @@ -0,0 +1,48 @@ +using System; +using Celesteia.Game.Components; +using Celesteia.Game.Planets; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Entities; + +namespace Celesteia.Game.Items { + public class UpgradeItemActions : CooldownItemActions { + private float _increase; + private EntityAttribute _attribute; + private float _max; + + public UpgradeItemActions(EntityAttribute attribute, float increase, float max) { + UseTime = 0.2; + _attribute = attribute; + _increase = increase; + _max = max; + } + + public override bool Primary(GameTime gameTime, ChunkMap chunkMap, Point cursor, Entity user) => Assert(gameTime, user) && Use(user); + + // Check if the conditions to use this item's action are met. + public bool Assert(GameTime gameTime, Entity user) { + if (!base.Assert(gameTime)) return false; + + // If the user has no attributes, the rest of the function will not work, so check if they're there first. + if (!user.Has()) return false; + + EntityAttributes.EntityAttributeMap attributes = user.Get().Attributes; + + // If the attribute is maxed out, don't upgrade. + if (attributes.Get(_attribute) >= _max) return false; + + UpdateLastUse(gameTime); + + return true; + } + + + public bool Use(Entity user) { + // Use the upgrade. + EntityAttributes.EntityAttributeMap attributes = user.Get().Attributes; + attributes.Set(_attribute, Math.Clamp(attributes.Get(_attribute) + _increase, 0f, _max)); + + return true; + } + } +} \ No newline at end of file diff --git a/source/game/music/MusicManager.cs b/source/game/music/MusicManager.cs new file mode 100644 index 0000000..8d9f9f5 --- /dev/null +++ b/source/game/music/MusicManager.cs @@ -0,0 +1,54 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Media; + +namespace Celesteia.Game.Music { + public class MusicManager : GameComponent { + private new GameInstance Game => (GameInstance) base.Game; + public MusicManager(GameInstance Game) : base(Game) {} + + private float SetVolume = 0.1f; + private float _volume; + private Song _nextUp; + private float _elapsedTransitionTime; + private float _transitionDuration = 2f; + private bool _transitionComplete = false; + + public override void Update(GameTime gameTime) + { + if (_elapsedTransitionTime >= _transitionDuration) _volume = 1f; + else { + _elapsedTransitionTime += ((float)gameTime.ElapsedGameTime.TotalMilliseconds / 1000f); + + if (_elapsedTransitionTime >= _transitionDuration / 2f && !_transitionComplete) { + if (_nextUp != null) { + MediaPlayer.Play(_nextUp); + _nextUp = null; + } else Stop(); + + _transitionComplete = true; + } + + _volume = Volume(_elapsedTransitionTime); + } + + MediaPlayer.Volume = _volume * SetVolume; + } + + public void PlayNow(Song song, bool repeat = true) { + MediaPlayer.IsRepeating = repeat; + + _elapsedTransitionTime = MediaPlayer.State == MediaState.Playing ? 0f : 1f; + _transitionComplete = false; + _nextUp = song; + } + + public void Stop() { + MediaPlayer.Stop(); + } + + private float Volume(float x) { + return Math.Abs(x - (_transitionDuration / 2f)); + } + } +} \ No newline at end of file diff --git a/source/game/planets/BlockState.cs b/source/game/planets/BlockState.cs new file mode 100644 index 0000000..e7d4be0 --- /dev/null +++ b/source/game/planets/BlockState.cs @@ -0,0 +1,43 @@ +using Celesteia.Game.Components.Items; +using Celesteia.Resources; +using Celesteia.Resources.Sprites; +using Celesteia.Resources.Types; + +namespace Celesteia.Game.Planets { + public struct BlockState { + public static BlockState None = new BlockState() { Empty = true }; + + public bool Empty; + + private byte _id; + public byte BlockID { + get => _id; + set { + _id = value; + Type = ResourceManager.Blocks.GetBlock(BlockID) as BlockType; + + Empty = _id == 0; + Translucent = Type.Translucent; + Frames = Type.Frames; + } + } + + public BlockType Type { get; private set; } + public bool Translucent { get; private set; } + public BlockFrames Frames { get; private set; } + + public int BreakProgress; + + public bool Draw; + public bool HasFrames() => Frames != null; + + public BlockData Data; + public bool HasData() => Data != null; + } + + public class BlockData {} + public class TileEntity : BlockData {} + public class Container : TileEntity { + public Inventory inventory; + } +} \ No newline at end of file diff --git a/source/game/planets/Chunk.cs b/source/game/planets/Chunk.cs new file mode 100644 index 0000000..5fc7618 --- /dev/null +++ b/source/game/planets/Chunk.cs @@ -0,0 +1,158 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Celesteia.Resources; +using Celesteia.Graphics; +using System; +using Celesteia.Resources.Sprites; +using Celesteia.Game.Components.Items; + +namespace Celesteia.Game.Planets { + public class Chunk { + public const int CHUNK_SIZE = 16; + + public Point TruePosition { get; private set; } + private ChunkVector _position; + public ChunkVector Position { + get => _position; + set { _position = value; TruePosition = _position.Resolve(); } + } + + private BlockState[,] foreground; + private BlockState[,] background; + + public Chunk(ChunkVector cv) { + Position = cv; + + foreground = new BlockState[CHUNK_SIZE, CHUNK_SIZE]; + background = new BlockState[CHUNK_SIZE, CHUNK_SIZE]; + } + + public BlockState GetForeground(int x, int y) => foreground[x, y]; + public BlockState GetBackground(int x, int y) => background[x, y]; + + public void SetForeground(int x, int y, byte id) { + foreground[x, y].BlockID = id; + foreground[x, y].BreakProgress = 0; + UpdateDraws(x, y); + } + + public void SetBackground(int x, int y, byte id) { + background[x, y].BlockID = id; + background[x, y].BreakProgress = 0; + UpdateDraws(x, y); + } + + public void UpdateDraws(int x, int y) { + foreground[x, y].Draw = foreground[x, y].HasFrames(); + background[x, y].Draw = background[x, y].HasFrames() && foreground[x, y].Translucent; + } + + private NamespacedKey? dropKey; + public void AddBreakProgress(int x, int y, int power, bool wall, out ItemStack drops) { + dropKey = null; + drops = null; + + if (wall) { + background[x, y].BreakProgress += power; + if (background[x, y].BreakProgress > background[x, y].Type.Strength) { + dropKey = background[x, y].Type.DropKey; + SetBackground(x, y, 0); + } + } else { + foreground[x, y].BreakProgress += power; + if (foreground[x, y].BreakProgress > foreground[x, y].Type.Strength) { + dropKey = foreground[x, y].Type.DropKey; + SetForeground(x, y, 0); + } + } + + if (dropKey.HasValue) drops = new ItemStack(dropKey.Value, 1); + } + + public void Draw(GameTime gameTime, SpriteBatch spriteBatch) { + for (int i = 0; i < CHUNK_SIZE; i++) { + for (int j = 0; j < CHUNK_SIZE; j++) { + DrawAllAt(i, j, gameTime, spriteBatch); + } + } + } + + Vector2 v; + private void DrawAllAt(int x, int y, GameTime gameTime, SpriteBatch spriteBatch) { + v.X = TruePosition.X + x; + v.Y = TruePosition.Y + y; + v.Floor(); + + if (background[x, y].Draw) { + DrawWallTile(background[x, y].Frames.GetFrame(0), spriteBatch, v); + if (background[x, y].BreakProgress > 0) DrawWallTile(ResourceManager.Blocks.BreakAnimation.GetProgressFrame( + // Background block breaking progress. + background[x, y].BreakProgress / (float) background[x, y].Type.Strength + ), spriteBatch, v); + } + if (foreground[x, y].Draw) { + DrawTile(foreground[x, y].Frames.GetFrame(0), spriteBatch, v); + if (foreground[x, y].BreakProgress > 0) DrawTile(ResourceManager.Blocks.BreakAnimation.GetProgressFrame( + // Foreground block breaking progress. + foreground[x, y].BreakProgress / (float) foreground[x, y].Type.Strength + ), spriteBatch, v); + } + } + + public void DrawTile(BlockFrame frame, SpriteBatch spriteBatch, Vector2 v) { + frame.Draw(0, spriteBatch, v, Color.White, 0.4f); + } + + public void DrawWallTile(BlockFrame frame, SpriteBatch spriteBatch, Vector2 v) { + frame.Draw(0, spriteBatch, v, Color.DarkGray, 0.5f); + } + } + + public struct ChunkVector { + public int X; + public int Y; + + public ChunkVector(int x, int y) { + X = x; + Y = y; + } + + public static ChunkVector FromVector2(Vector2 vector) + { + return new ChunkVector( + (int)MathF.Floor(vector.X / Chunk.CHUNK_SIZE), + (int)MathF.Floor(vector.Y / Chunk.CHUNK_SIZE) + ); + } + + public static ChunkVector FromPoint(Point point) + => new ChunkVector(point.X / Chunk.CHUNK_SIZE, point.Y/ Chunk.CHUNK_SIZE); + + public Point Resolve() { + return new Point(X * Chunk.CHUNK_SIZE, Y * Chunk.CHUNK_SIZE); + } + + public static int Distance(ChunkVector a, ChunkVector b) { + return MathHelper.Max(Math.Abs(b.X - a.X), Math.Abs(b.Y - a.Y)); + } + + public static bool operator ==(ChunkVector a, ChunkVector b) { + return a.Equals(b); + } + + public override bool Equals(object obj) + { + if (obj is ChunkVector) return ((ChunkVector)obj).X == this.X && ((ChunkVector)obj).Y == this.Y; + return false; + } + + public static bool operator !=(ChunkVector a, ChunkVector b) { + return !a.Equals(b); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/source/game/planets/ChunkMap.cs b/source/game/planets/ChunkMap.cs new file mode 100644 index 0000000..234d034 --- /dev/null +++ b/source/game/planets/ChunkMap.cs @@ -0,0 +1,99 @@ +using System; +using Celesteia.Game.Components.Items; +using Celesteia.Game.Planets.Generation; +using Celesteia.Resources; +using Microsoft.Xna.Framework; +using MonoGame.Extended; + +namespace Celesteia.Game.Planets { + public class ChunkMap { + public int Width, Height; + public int BlockWidth => Width * Chunk.CHUNK_SIZE; + public int BlockHeight => Height * Chunk.CHUNK_SIZE; + + public Chunk[,] Map; + + public ChunkMap(int w, int h) { + Width = w; + Height = h; + + Map = new Chunk[w, h]; + } + + public Chunk GetChunk(int chunkX, int chunkY) => ChunkIsInMap(chunkX, chunkY) ? Map[chunkX, chunkY] : null; + public Chunk GetChunk(ChunkVector cv) => GetChunk(cv.X, cv.Y); + public Chunk GetChunkAtCoordinates(int x, int y) => GetChunk(x / Chunk.CHUNK_SIZE, y / Chunk.CHUNK_SIZE); + public Chunk GetChunkAtPoint(Point point) => GetChunkAtCoordinates(point.X, point.Y); + + // BACKGROUND MANAGEMENT + public BlockState GetBackground(int blockX, int blockY) { + Chunk c = GetChunkAtCoordinates(blockX, blockY); + if (c != null) return c.GetBackground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE); + + return BlockState.None; + } + public BlockState GetBackground(Point point) => GetBackground(point.X, point.Y); + + public void SetBackgroundID(int blockX, int blockY, byte id) { + Chunk c = GetChunkAtCoordinates(blockX, blockY); + if (c != null) c.SetBackground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE, id); + } + public void SetBackgroundID(Point point, byte id) => SetBackgroundID(point.X, point.Y, id); + + public void AddBackgroundBreakProgress(int blockX, int blockY, int power, out ItemStack drops) { + drops = null; + Chunk c = GetChunkAtCoordinates(blockX, blockY); + if (c != null) c.AddBreakProgress(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE, power, true, out drops); + } + public void AddBackgroundBreakProgress(Point point, int power, out ItemStack drops) => AddBackgroundBreakProgress(point.X, point.Y, power, out drops); + + // FOREGROUND MANAGEMENT + public BlockState GetForeground(int blockX, int blockY) { + Chunk c = GetChunkAtCoordinates(blockX, blockY); + if (c != null) return c.GetForeground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE); + + return BlockState.None; + } + public BlockState GetForeground(Point point) => GetForeground(point.X, point.Y); + + public void SetForegroundID(int blockX, int blockY, byte id) { + Chunk c = GetChunkAtCoordinates(blockX, blockY); + if (c != null) c.SetForeground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE, id); + } + public void SetForegroundID(Point point, byte id) => SetForegroundID(point.X, point.Y, id); + + public void AddForegroundBreakProgress(int blockX, int blockY, int power, out ItemStack drops) { + drops = null; + Chunk c = GetChunkAtCoordinates(blockX, blockY); + if (c != null) c.AddBreakProgress(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE, power, false, out drops); + } + public void AddForegroundBreakProgress(Point point, int power, out ItemStack drops) => AddForegroundBreakProgress(point.X, point.Y, power, out drops); + + // FOR ADJACENCY CHECKS + public bool GetAny(int blockX, int blockY) { + Chunk c = GetChunkAtCoordinates(blockX, blockY); + return c != null && ( + !c.GetForeground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE).Empty || + !c.GetBackground(blockX % Chunk.CHUNK_SIZE, blockY % Chunk.CHUNK_SIZE).Empty + ); + } + + // COLLISION CHECKS + + public RectangleF? TestBoundingBox(int x, int y, RectangleF? box) { + if (!box.HasValue) return null; + + return new RectangleF( + x, y, + box.Value.Width, box.Value.Height + ); + } + public RectangleF? TestBoundingBox(int x, int y) => TestBoundingBox(x, y, GetForeground(x, y).Type.BoundingBox); + + // CHUNK IN MAP CHECKS + public bool ChunkIsInMap(int chunkX, int chunkY) => !(chunkX < 0 || chunkY < 0 || chunkX >= Width || chunkY >= Height); + public bool ChunkIsInMap(ChunkVector cv) => ChunkIsInMap(cv.X, cv.Y); + + public virtual Vector2 GetSpawnpoint() => Vector2.Zero; + } +} \ No newline at end of file diff --git a/source/game/planets/GeneratedPlanet.cs b/source/game/planets/GeneratedPlanet.cs new file mode 100644 index 0000000..c89a6c8 --- /dev/null +++ b/source/game/planets/GeneratedPlanet.cs @@ -0,0 +1,35 @@ +using System; +using Celesteia.Game.Planets.Generation; +using Microsoft.Xna.Framework; + +namespace Celesteia.Game.Planets { + public class GeneratedPlanet : ChunkMap + { + public int Seed { get; private set; } + + private IChunkProvider _provider; + + public GeneratedPlanet(int w, int h, int? seed = null) : base(w, h) + { + Seed = seed.HasValue ? seed.Value : (int) System.DateTime.Now.Ticks; + } + + public void Generate(IChunkProvider provider, Action progressReport = null) { + Action doProgressReport = (s) => { if (progressReport != null) progressReport(s); }; + + _provider = provider; + doProgressReport("Generating chunks..."); + + ChunkVector _cv; + for (_cv.X = 0; _cv.X < Width; _cv.X++) + for (_cv.Y = 0; _cv.Y < Height; _cv.Y++) + provider.ProvideChunk(Map[_cv.X, _cv.Y] = new Chunk(_cv)); + + provider.GenerateStructures(doProgressReport); + + doProgressReport("Planet generated."); + } + + public override Vector2 GetSpawnpoint() => _provider.GetSpawnpoint(); + } +} \ No newline at end of file diff --git a/source/game/planets/generation/IWorldGenerator.cs b/source/game/planets/generation/IWorldGenerator.cs new file mode 100644 index 0000000..92b528b --- /dev/null +++ b/source/game/planets/generation/IWorldGenerator.cs @@ -0,0 +1,18 @@ +using System; +using Microsoft.Xna.Framework; + +namespace Celesteia.Game.Planets.Generation { + public interface IChunkProvider { + // Provide a chunk's tile map. + public void ProvideChunk(Chunk c); + + // Get the natural block and wall at X and Y. + public byte[] GetNaturalBlocks(int x, int y); + + // Get a safe spot to spawn the player. + public Vector2 GetSpawnpoint(); + + // Generate various structures in the world. + public void GenerateStructures(Action progressReport = null); + } +} \ No newline at end of file diff --git a/source/game/planets/generation/TerranPlanetGenerator.cs b/source/game/planets/generation/TerranPlanetGenerator.cs new file mode 100644 index 0000000..9507011 --- /dev/null +++ b/source/game/planets/generation/TerranPlanetGenerator.cs @@ -0,0 +1,275 @@ +using System; +using Microsoft.Xna.Framework; +using Celesteia.Resources; +using System.Collections.Generic; +using System.Linq; + +namespace Celesteia.Game.Planets.Generation { + public class TerranPlanetGenerator : IChunkProvider { + private ChunkMap _chunkMap; + private int _seed; + private FastNoiseLite _noise; + + public TerranPlanetGenerator(GeneratedPlanet chunkMap) { + _chunkMap = chunkMap; + _noise = new FastNoiseLite(_seed = chunkMap.Seed); + + LoadBlockIndices(); + } + + private byte top; + private byte soil; + private byte stone; + private byte deepstone; + private byte log; + private byte leaves; + private byte planks; + private byte coal_ore; + private byte copper_ore; + private byte iron_ore; + private byte[] foliage; + private byte landing_floor; + private byte cc_base; + private byte cc_frame; + private void LoadBlockIndices() { + top = ResourceManager.Blocks.GetResource(NamespacedKey.Base("grown_soil")).GetID(); + soil = ResourceManager.Blocks.GetResource(NamespacedKey.Base("soil")).GetID(); + stone = ResourceManager.Blocks.GetResource(NamespacedKey.Base("stone")).GetID(); + deepstone = ResourceManager.Blocks.GetResource(NamespacedKey.Base("deepstone")).GetID(); + log = ResourceManager.Blocks.GetResource(NamespacedKey.Base("log")).GetID(); + leaves = ResourceManager.Blocks.GetResource(NamespacedKey.Base("leaves")).GetID(); + planks = ResourceManager.Blocks.GetResource(NamespacedKey.Base("wooden_planks")).GetID(); + coal_ore = ResourceManager.Blocks.GetResource(NamespacedKey.Base("coal_ore")).GetID(); + copper_ore = ResourceManager.Blocks.GetResource(NamespacedKey.Base("copper_ore")).GetID(); + iron_ore = ResourceManager.Blocks.GetResource(NamespacedKey.Base("iron_ore")).GetID(); + + foliage = new byte[5] { + 0, + ResourceManager.Blocks.GetResource(NamespacedKey.Base("grass")).GetID(), + ResourceManager.Blocks.GetResource(NamespacedKey.Base("blue_flower")).GetID(), + ResourceManager.Blocks.GetResource(NamespacedKey.Base("red_flower")).GetID(), + ResourceManager.Blocks.GetResource(NamespacedKey.Base("violet_flower")).GetID() + }; + + landing_floor = ResourceManager.Blocks.GetResource(NamespacedKey.Base("scorched_soil")).GetID(); + cc_base = ResourceManager.Blocks.GetResource(NamespacedKey.Base("crashed_capsule_base")).GetID(); + cc_frame = ResourceManager.Blocks.GetResource(NamespacedKey.Base("crashed_capsule_frame")).GetID(); + } + + public void ProvideChunk(Chunk c) { + byte[] natural; + for (int i = 0; i < Chunk.CHUNK_SIZE; i++) + for (int j = 0; j < Chunk.CHUNK_SIZE; j++) { + natural = GetNaturalBlocks(c.TruePosition.X + i, c.TruePosition.Y + j); + c.SetForeground(i, j, natural[0]); + c.SetBackground(i, j, natural[1]); + } + } + + public byte[] GetNaturalBlocks(int x, int y) => ThirdPass(x, y, SecondPass(x, y, FirstPass(x, y))); + + public void GenerateStructures(Action progressReport) { + Random rand = new Random(_seed); + + progressReport("Planting trees..."); + GenerateTrees(rand); + progressReport("Abandoning houses..."); + GenerateAbandonedHomes(rand); + progressReport("Planting foliage..."); + GenerateFoliage(rand); + progressReport("Landing light..."); + GenerateLanding(); + } + + public Vector2 GetSpawnpoint() + { + float x; + return new Vector2( + x = MathF.Floor(_chunkMap.BlockWidth / 2f) + 0.5f, + (_chunkMap.BlockHeight) - GetHeightValue((int)MathF.Floor(x)) - 2f + ); + } + + public byte[] FirstPass(int x, int y) { + if (y > _chunkMap.BlockHeight - 5) return new byte[2] { deepstone, deepstone }; + + byte[] values = new byte[2] { 0, 0 }; + + int h = GetHeightValue(x); + + if (_chunkMap.BlockHeight - y <= h) { + if (_chunkMap.BlockHeight - y == h) { values[0] = top; values[1] = soil; } + else if (_chunkMap.BlockHeight - y >= h - 3) { values[0] = soil; values[1] = soil; } + else { values[0] = stone; values[1] = stone; } + } + + return values; + } + public byte[] SecondPass(int x, int y, byte[] values) { + float threshold = 0.667f; + + if (values[0] == 0 || values[0] == deepstone) return values; + if (values[0] == soil || values[0] == top) threshold += .2f; + + float c = GetCaveValue(x, y); + + if (c > threshold) values[0] = 0; + + return values; + } + + public byte[] ThirdPass(int x, int y, byte[] values) { + if (values[0] != stone) return values; + + float coalValue = GetOreValue(x, y, 498538f, 985898f); + if (coalValue > 0.95f) values[0] = coal_ore; + else { + float copperValue = GetOreValue(x, y, 3089279f, 579486f); + if (copperValue > 0.95f) values[0] = copper_ore; + + else + { + float ironValue = GetOreValue(x, y, 243984f, 223957f); + if (ironValue > 0.95f) values[0] = iron_ore; + } + } + + return values; + } + + private int defaultOffset => _chunkMap.BlockHeight / 3; + public int GetHeightValue(int x) => (int)Math.Round((_noise.GetNoise(x / 1f, 0f) * 24f) + defaultOffset); + public float GetCaveValue(int x, int y) => _noise.GetNoise(x / 0.6f, y / 0.7f); + public float GetOreValue(int x, int y, float offsetX, float offsetY) => (_noise.GetNoise((x + offsetX) * 5f, (y + offsetY) * 5f) + 1) / 2f; + + private int blocksBetweenTrees = 5; + private int treeGrowthSteps = 15; + public void GenerateTrees(Random rand) { + int j = 0; + int randomNumber = 0; + int lastTree = 0; + for (int i = 0; i < _chunkMap.BlockWidth; i++) { + j = _chunkMap.BlockHeight - GetHeightValue(i); + + if (MathF.Abs(i - GetSpawnpoint().X) < 10f) continue; // Don't grow trees too close to spawn. + if (i < 10 || i > _chunkMap.BlockWidth - 10) continue; // Don't grow trees too close to world borders. + if (_chunkMap.GetForeground(i, j).BlockID != top) continue; // Only grow trees on grass. + if (i - lastTree < blocksBetweenTrees) continue; // Force a certain number of blocks between trees. + + lastTree = i; + + randomNumber = rand.Next(0, 6); + + if (randomNumber == 1) GrowTreeRecursively(i, j - 1, treeGrowthSteps - rand.Next(0, 7), rand, false); + } + } + + public void GrowTreeRecursively(int x, int y, int steps, Random rand, bool branch) { + if (steps == 0) { + for (int i = -2; i <= 2; i++) + for (int j = -2; j <= 2; j++) { + if (_chunkMap.GetForeground(x + i, y + j).Empty) _chunkMap.SetForegroundID(x + i, y + j, leaves); + } + return; + } + + if (!_chunkMap.GetForeground(x, y).Empty) return; + + _chunkMap.SetForegroundID(x, y, log); + + if (!branch) GrowTreeRecursively(x, y - 1, steps - 1, rand, false); // Grow upwards. + if (rand.Next(0, 6) > steps) GrowTreeRecursively(x - 1, y, steps - 1, rand, true); // Grow to the left. + if (rand.Next(0, 6) > steps) GrowTreeRecursively(x + 1, y, steps - 1, rand, true); // Grow to the right. + } + + private int blocksBetweenHomes = 150; + public void GenerateAbandonedHomes(Random rand) { + int j = 0; + int randomNumber = 0; + int lastHome = 0; + for (int i = 0; i < _chunkMap.BlockWidth; i++) { + j = _chunkMap.BlockHeight - GetHeightValue(i); + + if (MathF.Abs(i - GetSpawnpoint().X) < 10f) continue; // Don't grow trees too close to spawn. + if (i < 10 || i > _chunkMap.BlockWidth - 10) continue; // Don't grow trees too close to world borders. + if (i - lastHome < blocksBetweenHomes) continue; // Force a certain number of blocks between trees. + + int homeWidth = rand.Next(10, 15); + int homeHeight = rand.Next(6, 10); + int buryAmount = rand.Next(15, 40); + + j -= homeHeight; // Raise the home to be built on the ground first. + j += buryAmount; // Bury the home by a random amount. + + lastHome = i; + + randomNumber = rand.Next(0, 5); + + if (randomNumber == 1) BuildAbandonedHome(i, j, homeWidth, homeHeight, rand); + } + } + + public void BuildAbandonedHome(int originX, int originY, int homeWidth, int homeHeight, Random rand) { + int maxX = originX + homeWidth; + + for (int i = originX; i < maxX; i++) originY = Math.Max(originY, _chunkMap.BlockHeight - GetHeightValue(i)); + int maxY = originY + homeHeight; + + for (int i = originX; i < maxX; i++) + for (int j = originY; j < maxY; j++) { + _chunkMap.SetBackgroundID(i, j, planks); + _chunkMap.SetForegroundID(i, j, 0); + + // Apply some random decay by skipping tiles at random. + if (rand.Next(0, 5) > 3) { + continue; + } + + if (j == originY || j == maxY - 1) _chunkMap.SetForegroundID(i, j, planks); + if (i == originX || i == maxX - 1) _chunkMap.SetForegroundID(i, j, log); + } + } + + private Dictionary foliageDistribution = new Dictionary { + { 0.3, 0 }, + { 0.6, 1 }, + { 0.7, 2 }, + { 0.85, 4 }, + { 0.99, 3 }, + }; + + public void GenerateFoliage(Random rand) { + int j = 0; + + double randomNumber = 0; + int foliageIndex = 0; + for (int i = 0; i < _chunkMap.BlockWidth; i++) { + j = _chunkMap.BlockHeight - GetHeightValue(i); + + if (_chunkMap.GetForeground(i, j).BlockID != top) continue; // If there is anything but foreground grown soil, continue. + if (!_chunkMap.GetForeground(i, j - 1).Empty) continue; // If the foreground is already taken, continue. + + randomNumber = rand.NextDouble(); + for (int f = 0; f < foliageDistribution.Keys.Count; f++) { + if (randomNumber > foliageDistribution.Keys.ElementAt(f)) foliageIndex = foliageDistribution[foliageDistribution.Keys.ElementAt(f)]; + } + + _chunkMap.SetForegroundID(i, j - 1, foliage[foliageIndex]); + } + } + + public void GenerateLanding() { + int x = GetSpawnpoint().ToPoint().X; + int j = _chunkMap.BlockHeight - GetHeightValue(x); + for (int i = -1; i <= 1; i++) { + _chunkMap.SetForegroundID(x + i, j + 1, soil); + _chunkMap.SetForegroundID(x + i, j, landing_floor); + for (int h = 1; h <= 3; h++) { + _chunkMap.SetForegroundID(x + i, j - h, cc_frame); + _chunkMap.SetBackgroundID(x + i, j - h, 0); + } + } + _chunkMap.SetForegroundID(x, j - 1, cc_base); + } + } +} \ No newline at end of file diff --git a/source/game/systems/CameraRenderSystem.cs b/source/game/systems/CameraRenderSystem.cs new file mode 100644 index 0000000..2846fa7 --- /dev/null +++ b/source/game/systems/CameraRenderSystem.cs @@ -0,0 +1,43 @@ +using Celesteia.Graphics; +using Celesteia.Resources.Sprites; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended; +using MonoGame.Extended.Entities; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.Systems { + public class CameraRenderSystem : EntityDrawSystem + { + private readonly Camera2D _camera; + private readonly SpriteBatch _spriteBatch; + + private ComponentMapper transformMapper; + private ComponentMapper entityFramesMapper; + + public CameraRenderSystem(Camera2D camera, SpriteBatch spriteBatch) : base(Aspect.All(typeof(Transform2), typeof(EntityFrames))) { + _camera = camera; + _spriteBatch = spriteBatch; + } + + public override void Initialize(IComponentMapperService mapperService) + { + transformMapper = mapperService.GetMapper(); + entityFramesMapper = mapperService.GetMapper(); + } + + public override void Draw(GameTime gameTime) + { + _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, null, _camera.GetViewMatrix()); + + foreach (int entityId in ActiveEntities) { + Transform2 transform = transformMapper.Get(entityId); + EntityFrames entityFrames = entityFramesMapper.Get(entityId); + + entityFrames.Draw(0, _spriteBatch, transform.Position, transform.Scale, Color.White); + } + + _spriteBatch.End(); + } + } +} \ No newline at end of file diff --git a/source/game/systems/CameraSystem.cs b/source/game/systems/CameraSystem.cs new file mode 100644 index 0000000..be121a5 --- /dev/null +++ b/source/game/systems/CameraSystem.cs @@ -0,0 +1,39 @@ +using System; +using Celesteia.Game.Components; +using Celesteia.Game.Input; +using Celesteia.Graphics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; +using MonoGame.Extended; +using MonoGame.Extended.Entities; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.Systems { + public class CameraSystem : EntityUpdateSystem + { + private Camera2D _camera; + private float smoothing = 128f; + + private ComponentMapper transformMapper; + + public CameraSystem(Camera2D camera) : base(Aspect.All(typeof(Transform2), typeof(CameraFollow))) + => _camera = camera; + + public override void Initialize(IComponentMapperService mapperService) + => transformMapper = mapperService.GetMapper(); + + Vector2 pos; + public override void Update(GameTime gameTime) + { + foreach (int entityId in ActiveEntities) { + pos = transformMapper.Get(entityId).Position * smoothing; + pos.X = MathF.Round(pos.X) / smoothing; + pos.Y = MathF.Round(pos.Y) / smoothing; + _camera.Center = pos; + break; + } + + if (KeyboardHelper.IsDown(Keys.LeftControl)) _camera.Zoom += (int) Math.Clamp(MouseHelper.ScrollDelta, -1f, 1f); + } + } +} \ No newline at end of file diff --git a/source/game/systems/ChunkMapRenderSystem.cs b/source/game/systems/ChunkMapRenderSystem.cs new file mode 100644 index 0000000..afb867c --- /dev/null +++ b/source/game/systems/ChunkMapRenderSystem.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using Celesteia.Game.Planets; +using Celesteia.Graphics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.Systems { + public class ChunkMapRenderSystem : IUpdateSystem, IDrawSystem + { + private readonly Camera2D _camera; + private readonly SpriteBatch _spriteBatch; + private ChunkVector _lastChunkPos; + private ChunkVector _pivotChunkPos => ChunkVector.FromVector2(_camera.Center); + public int RenderDistance = 4; + private ChunkMap _chunkMap; + + public ChunkMapRenderSystem(Camera2D camera, SpriteBatch spriteBatch, ChunkMap chunkMap) { + _camera = camera; + _spriteBatch = spriteBatch; + _chunkMap = chunkMap; + } + + public void Initialize(MonoGame.Extended.Entities.World world) {} + + private ChunkVector _v; + private List activeChunks = new List(); + public void Update(GameTime gameTime) + { + if (_lastChunkPos != _pivotChunkPos) { + activeChunks.Clear(); + for (int i = -RenderDistance; i <= RenderDistance; i++) { + _v.X = _pivotChunkPos.X + i; + for (int j = -RenderDistance; j <= RenderDistance; j++) { + _v.Y = _pivotChunkPos.Y + j; + + if (!_chunkMap.ChunkIsInMap(_v)) continue; + activeChunks.Add(_v); + } + } + + _lastChunkPos = _pivotChunkPos; + } + } + + public void Draw(GameTime gameTime) { + _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointWrap, null, null, null, _camera.GetViewMatrix()); + + // Draw every chunk in view. + foreach (ChunkVector cv in activeChunks) DrawChunk(cv, gameTime, _spriteBatch); + + _spriteBatch.End(); + } + + private void DrawChunk(ChunkVector cv, GameTime gameTime, SpriteBatch spriteBatch) { + Chunk c = _chunkMap.GetChunk(cv); + + if (c != null) c.Draw(gameTime, spriteBatch); + } + + public void Dispose() {} + } +} \ No newline at end of file diff --git a/source/game/systems/EntityDebugSystem.cs b/source/game/systems/EntityDebugSystem.cs new file mode 100644 index 0000000..9b7914a --- /dev/null +++ b/source/game/systems/EntityDebugSystem.cs @@ -0,0 +1,44 @@ +using Celesteia.Graphics; +using Celesteia.Resources; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended; +using MonoGame.Extended.Entities; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.Systems { + public class EntityDebugSystem : EntityDrawSystem + { + private readonly Camera2D _camera; + private readonly SpriteBatch _spriteBatch; + + private ComponentMapper transformMapper; + + private SpriteFont _font; + + public EntityDebugSystem(Camera2D camera, SpriteBatch spriteBatch) : base(Aspect.All(typeof(Transform2))) { + _camera = camera; + _spriteBatch = spriteBatch; + } + + public override void Initialize(IComponentMapperService mapperService) + { + transformMapper = mapperService.GetMapper(); + + _font = ResourceManager.Fonts.GetFontType("Hobo").Font; + } + + public override void Draw(GameTime gameTime) + { + _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, SamplerState.PointClamp, null, null, null, _camera.GetViewMatrix()); + + foreach (int entityId in ActiveEntities) { + Transform2 transform = transformMapper.Get(entityId); + + _spriteBatch.DrawString(_font, transform.Position.ToString(), transform.Position, Color.White, 0f, new Vector2(0.5f, 0.5f), .12f, SpriteEffects.None, 0f); + } + + _spriteBatch.End(); + } + } +} \ No newline at end of file diff --git a/source/game/systems/LightingSystem.cs b/source/game/systems/LightingSystem.cs new file mode 100644 index 0000000..73c6cf8 --- /dev/null +++ b/source/game/systems/LightingSystem.cs @@ -0,0 +1,100 @@ +using Celesteia.Graphics; +using Celesteia.Graphics.Lighting; +using Celesteia.Resources; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended.Entities.Systems; +using Celesteia.Resources.Types; +using Celesteia.Game.Planets; +using System.Threading.Tasks; +using System.Collections.Generic; + +namespace Celesteia.Game.Systems { + public class LightingSystem : IUpdateSystem, IDrawSystem + { + private readonly Camera2D _camera; + private readonly SpriteBatch _spriteBatch; + private readonly ChunkMap _chunkMap; + + public LightingSystem(Camera2D camera, SpriteBatch spriteBatch, ChunkMap chunkMap) { + _camera = camera; + _spriteBatch = spriteBatch; + _chunkMap = chunkMap; + } + public void Dispose() { } + + private int _lightRenderDistance = 5; + + private Dictionary lightingDictionary; + public void Initialize(MonoGame.Extended.Entities.World world) { + int _size = 2 * _lightRenderDistance * Chunk.CHUNK_SIZE; + + _lightMap = new LightMap(_size, _size); + _texture = new Texture2D(_spriteBatch.GraphicsDevice, _size, _size); + + lightingDictionary = new Dictionary(); + } + + private LightMap _lightMap; + private Texture2D _texture; + + private bool drawTexture = false; + private Task _lightUpdate; + public void Update(GameTime gameTime) + { + if (_lightUpdate == null || (_lightUpdate != null && _lightUpdate.IsCompleted)) + _lightUpdate = Task.Factory.StartNew(() => UpdateLight()); + + if (drawTexture) UpdateTexture(); + } + + private Point _position; + private void UpdatePosition() { + _position = ChunkVector.FromVector2(_camera.Center).Resolve() - new Point(_lightRenderDistance * Chunk.CHUNK_SIZE); + } + + private void UpdateTexture() { + _drawPosition = _position.ToVector2(); + _texture.SetData(_lightMap.GetColors(), 0, _lightMap.GetColorCount()); + drawTexture = false; + } + + private void UpdateLight() { + UpdatePosition(); + + UpdateEmission(); + _lightMap.Propagate(); + _lightMap.CreateColorMap(); + + drawTexture = true; + } + + private BlockState _block; + private void UpdateEmission() { + for (int i = 0; i < _lightMap.Width; i++) { + for (int j = 0; j < _lightMap.Height; j++) { + if (!(_block = _chunkMap.GetForeground(i + _position.X, j + _position.Y)).Empty && _lightMap.AddForeground(i, j, _block.Type.Light)) continue; + if (!(_block = _chunkMap.GetBackground(i + _position.X, j + _position.Y)).Empty && _lightMap.AddBackground(i, j, _block.Type.Light)) continue; + + _lightMap.AddLight(i, j, true, LightColor.ambient, 4); + } + } + } + + private BlendState multiply = new BlendState() { + ColorBlendFunction = BlendFunction.Add, + ColorSourceBlend = Blend.DestinationColor, + ColorDestinationBlend = Blend.Zero, + }; + + private Vector2 _drawPosition; + public void Draw(GameTime gameTime) + { + _spriteBatch.Begin(SpriteSortMode.Immediate, multiply, SamplerState.LinearClamp, null, null, null, _camera.GetViewMatrix()); + + _spriteBatch.Draw(_texture, _drawPosition, Color.White); + + _spriteBatch.End(); + } + } +} \ No newline at end of file diff --git a/source/game/systems/LocalPlayerSystem.cs b/source/game/systems/LocalPlayerSystem.cs new file mode 100644 index 0000000..5c17cb4 --- /dev/null +++ b/source/game/systems/LocalPlayerSystem.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using Celesteia.Game.Components; +using Celesteia.Game.Components.Items; +using Celesteia.Game.Components.Physics; +using Celesteia.Game.Components.Player; +using Celesteia.Game.Input; +using Celesteia.Game.Items; +using Celesteia.Game.Planets; +using Celesteia.Graphics; +using Celesteia.GUIs.Game; +using Celesteia.Resources; +using Celesteia.Resources.Sprites; +using Celesteia.Resources.Types; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using MonoGame.Extended; +using MonoGame.Extended.Entities; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.Systems { + public class LocalPlayerSystem : UpdateSystem, IDrawSystem + { + private GameInstance _game; + private GameGUI _gameGui; + private Camera2D _camera; + private ChunkMap _chunkMap; + + private Entity _player; + public Entity Player { + get => _player; + set { + _player = value; + + localPlayer = _player.Get(); + targetPosition = _player.Get(); + physicsEntity = _player.Get(); + frames = _player.Get(); + attributes = _player.Get(); + input = _player.Get(); + inventory = _player.Get(); + } + } + private LocalPlayer localPlayer; + private PlayerInput input; + private PhysicsEntity physicsEntity; + private EntityFrames frames; + private EntityAttributes attributes; + private TargetPosition targetPosition; + private Inventory inventory; + + private SpriteBatch _spriteBatch; + + private BlockFrame _selectionSprite; + + public LocalPlayerSystem(GameInstance game, ChunkMap chunkMap, Camera2D camera, SpriteBatch spriteBatch, GameGUI gameGui) { + _game = game; + _chunkMap = chunkMap; + _camera = camera; + _gameGui = gameGui; + _spriteBatch = spriteBatch; + + _selectionSprite = ResourceManager.Blocks.Selection.GetFrame(0); + } + + private bool IsGameActive => !_gameGui.Paused && (int)_gameGui.State < 1 && _game.IsActive; + + public override void Update(GameTime gameTime) + { + if (_player == null) return; + + bool clicked = false; + UpdateGUI(gameTime, input, out clicked); + + if (IsGameActive) { + UpdateSelectedItem(); + + UpdateMouse(gameTime, input); + UpdateMovement(gameTime, input, physicsEntity, frames, attributes.Attributes, targetPosition); + UpdateJump(gameTime, localPlayer, input, physicsEntity, attributes.Attributes); + + if (!clicked) UpdateClick(gameTime, input); + } else SelectionColor = Color.Transparent; + } + + private static Dictionary hotbarMappings = new Dictionary() { + { (int)Keys.D1, 0 }, + { (int)Keys.D2, 1 }, + { (int)Keys.D3, 2 }, + { (int)Keys.D4, 3 }, + { (int)Keys.D5, 4 }, + { (int)Keys.D6, 5 }, + { (int)Keys.D7, 6 }, + { (int)Keys.D8, 7 }, + { (int)Keys.D9, 8 } + }; + + private void UpdateSelectedItem() { + foreach (int keys in hotbarMappings.Keys) { + if (KeyboardHelper.Pressed((Keys) keys)) { + _gameGui.HotbarSelection = hotbarMappings[keys]; + } + } + + if (!KeyboardHelper.IsDown(Keys.LeftControl) && MouseHelper.ScrollDelta != 0f) { + int change = (int) -Math.Clamp(MouseHelper.ScrollDelta, -1f, 1f); + int selection = _gameGui.HotbarSelection; + + selection += change; + + if (selection < 0) selection = _gameGui.HotbarSlots - 1; + if (selection >= _gameGui.HotbarSlots) selection = 0; + + _gameGui.HotbarSelection = selection; + } + } + + bool _inventoryPress; + bool _craftingPress; + bool _pausePress; + private void UpdateGUI(GameTime gameTime, PlayerInput input, out bool clicked) { + _inventoryPress = input.Inventory.Poll(); + _craftingPress = input.Crafting.Poll(); + _pausePress = input.Pause.Poll(); + + if (_inventoryPress || _craftingPress || _pausePress) { + switch (_gameGui.State) { + case InventoryScreenState.Closed: + if (_craftingPress) _gameGui.State = InventoryScreenState.Crafting; + else if (_inventoryPress) _gameGui.State = InventoryScreenState.Inventory; + else if (_pausePress) _gameGui.TogglePause(); + break; + case InventoryScreenState.Inventory: + if (_craftingPress) _gameGui.State = InventoryScreenState.Crafting; + else _gameGui.State = InventoryScreenState.Closed; + break; + case InventoryScreenState.Crafting: + _gameGui.State = InventoryScreenState.Closed; + break; + default: break; + } + } + + _gameGui.Update(gameTime, out clicked); + } + + float h; + private bool _moving; + private double _startedMoving; + private void UpdateMovement(GameTime gameTime, PlayerInput input, PhysicsEntity physicsEntity, EntityFrames frames, EntityAttributes.EntityAttributeMap attributes, TargetPosition targetPosition) { + h = input.Horizontal.Poll(); + + if (h != 0f) { + // Player has started moving, animate. + if (!_moving) _startedMoving = gameTime.TotalGameTime.TotalSeconds; + + // Flip sprite according to horizontal movement float. + frames.Effects = h < 0f ? SpriteEffects.None : SpriteEffects.FlipHorizontally; + } + + _moving = h != 0f; + + // If the player is moving, change the frame every .25 seconds, else return the standing frame. + frames.Frame = _moving ? (int)((gameTime.TotalGameTime.TotalSeconds - _startedMoving) / 0.25) : 0; + + if (h == 0f) return; + + h *= 1f + (input.Run.Poll() ? 1.5f : 0); + h *= attributes.Get(EntityAttribute.MovementSpeed); + h *= gameTime.GetElapsedSeconds(); + + targetPosition.Target.X += h; + } + + private void UpdateJump(GameTime gameTime, LocalPlayer localPlayer, PlayerInput input, PhysicsEntity physicsEntity, EntityAttributes.EntityAttributeMap attributes) + { + if (localPlayer.JumpRemaining > 0f) { + if (input.Jump.Poll()) { + physicsEntity.SetVelocity(physicsEntity.Velocity.X, -attributes.Get(EntityAttribute.JumpForce)); + localPlayer.JumpRemaining -= gameTime.GetElapsedSeconds(); + } + } else if (physicsEntity.CollidingDown) localPlayer.JumpRemaining = attributes.Get(EntityAttribute.JumpFuel); + } + + private Point? Selection; + private BlockState SelectedBlock; + private Color SelectionColor; + public void SetSelection(Point? selection) { + if (selection == Selection) return; + + Selection = selection; + if (!selection.HasValue) { + SelectionColor = Color.Transparent; + return; + } + + SelectedBlock = _chunkMap.GetForeground(Selection.Value); + if (!SelectedBlock.Draw) SelectedBlock = _chunkMap.GetBackground(Selection.Value); + + SelectionColor = (SelectedBlock.Type.Strength >= 0 ? Color.White : Color.Black); + } + + Vector2 pointV = Vector2.Zero; + Point point = Point.Zero; + private void UpdateMouse(GameTime gameTime, PlayerInput input) { + pointV = _camera.ScreenToWorld(MouseHelper.Position); + pointV.Floor(); + point = pointV.ToPoint(); + + SetSelection(point); + } + + + bool success = false; + ItemStack stack = null; + IItemActions actions = null; + private void UpdateClick(GameTime gameTime, PlayerInput input) { + if (!input.PrimaryUse.Poll() && !input.SecondaryUse.Poll()) return; + if (_gameGui.GetSelectedItem() == null) return; + + stack = _gameGui.GetSelectedItem(); + if(stack.Type == null || stack.Type.Actions == null) return; + + actions = stack.Type.Actions; + + if (input.PrimaryUse.Poll()) success = actions.Primary(gameTime, _chunkMap, point, _player); + if (input.SecondaryUse.Poll()) success = stack.Type.Actions.Secondary(gameTime, _chunkMap, point, _player); + + if (success && stack.Type.ConsumeOnUse) stack.Amount -= 1; + + inventory.AssertAmounts(); + } + + public void Draw(GameTime gameTime) + { + if (!UIReferences.GUIEnabled) return; + + _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.PointClamp, null, null, null, _camera.GetViewMatrix()); + + _selectionSprite.Draw(0, _spriteBatch, pointV, SelectionColor); + + _spriteBatch.End(); + } + } +} \ No newline at end of file diff --git a/source/game/systems/TargetPositionSystem.cs b/source/game/systems/TargetPositionSystem.cs new file mode 100644 index 0000000..7089bc1 --- /dev/null +++ b/source/game/systems/TargetPositionSystem.cs @@ -0,0 +1,37 @@ +using Celesteia.Game.Components; +using Celesteia.Game.Planets; +using Microsoft.Xna.Framework; +using MonoGame.Extended; +using MonoGame.Extended.Entities; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.Systems { + public class TargetPositionSystem : EntityUpdateSystem { + private ChunkMap _chunkMap; + + private ComponentMapper transformMapper; + private ComponentMapper targetPositionMapper; + + public TargetPositionSystem(ChunkMap chunkMap) : base(Aspect.All(typeof(Transform2), typeof(TargetPosition))) + => _chunkMap = chunkMap; + + public override void Initialize(IComponentMapperService mapperService) + { + transformMapper = mapperService.GetMapper(); + targetPositionMapper = mapperService.GetMapper(); + } + + public override void Update(GameTime gameTime) + { + foreach (int entityId in ActiveEntities) { + TargetPosition targetPosition = targetPositionMapper.Get(entityId); + Transform2 transform = transformMapper.Get(entityId); + + if (targetPosition.Target.X < 0 || targetPosition.Target.X > _chunkMap.BlockWidth) + targetPosition.Target.X = MathHelper.Clamp(targetPosition.Target.X, 0f, _chunkMap.BlockWidth); + + transform.Position = targetPosition.Target; + } + } + } +} \ No newline at end of file diff --git a/source/game/systems/mainmenu/MainMenuBackgroundSystem.cs b/source/game/systems/mainmenu/MainMenuBackgroundSystem.cs new file mode 100644 index 0000000..e4d5e28 --- /dev/null +++ b/source/game/systems/mainmenu/MainMenuBackgroundSystem.cs @@ -0,0 +1,33 @@ +using Celesteia.Game.Components.Skybox; +using Microsoft.Xna.Framework; +using MonoGame.Extended; +using MonoGame.Extended.Entities; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.Systems.MainMenu { + public class MainMenuBackgroundSystem : EntityUpdateSystem + { + private ComponentMapper transformMapper; + private ComponentMapper rotatorMapper; + + public MainMenuBackgroundSystem() : base(Aspect.All(typeof(Transform2), typeof(SkyboxRotateZ))) {} + + public override void Initialize(IComponentMapperService mapperService) + { + transformMapper = mapperService.GetMapper(); + rotatorMapper = mapperService.GetMapper(); + } + + public override void Update(GameTime gameTime) + { + foreach (int entityId in ActiveEntities) { + SkyboxRotateZ rotator = rotatorMapper.Get(entityId); + Transform2 transform = transformMapper.Get(entityId); + + rotator.Current += rotator.Magnitude * (gameTime.GetElapsedSeconds() / 1000f) * 20f; + + transform.Rotation = rotator.Current; + } + } + } +} \ No newline at end of file diff --git a/source/game/systems/mainmenu/MainMenuRenderSystem.cs b/source/game/systems/mainmenu/MainMenuRenderSystem.cs new file mode 100644 index 0000000..ada6a77 --- /dev/null +++ b/source/game/systems/mainmenu/MainMenuRenderSystem.cs @@ -0,0 +1,43 @@ +using Celesteia.Graphics; +using Celesteia.Resources.Sprites; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended; +using MonoGame.Extended.Entities; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.Systems.MainMenu { + public class MainMenuRenderSystem : EntityDrawSystem + { + private ComponentMapper transformMapper; + private ComponentMapper framesMapper; + + private Camera2D _camera; + private SpriteBatch _spriteBatch; + + public MainMenuRenderSystem(Camera2D camera, SpriteBatch spriteBatch) : base(Aspect.All(typeof(Transform2), typeof(SkyboxPortionFrames))) { + _camera = camera; + _spriteBatch = spriteBatch; + } + + public override void Initialize(IComponentMapperService mapperService) + { + transformMapper = mapperService.GetMapper(); + framesMapper = mapperService.GetMapper(); + } + + public override void Draw(GameTime gameTime) + { + _spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, SamplerState.PointClamp, null, RasterizerState.CullNone, null, _camera.GetViewMatrix()); + + foreach (int entityId in ActiveEntities) { + SkyboxPortionFrames frames = framesMapper.Get(entityId); + Transform2 transform = transformMapper.Get(entityId); + + frames.Draw(0, _spriteBatch, transform.Position, transform.Rotation, transform.Scale); + } + + _spriteBatch.End(); + } + } +} \ No newline at end of file diff --git a/source/game/systems/physics/PhysicsCollisionDebugSystem.cs b/source/game/systems/physics/PhysicsCollisionDebugSystem.cs new file mode 100644 index 0000000..f605c30 --- /dev/null +++ b/source/game/systems/physics/PhysicsCollisionDebugSystem.cs @@ -0,0 +1,61 @@ +using Celesteia.Game.Components.Physics; +using Celesteia.Game.Planets; +using Celesteia.Graphics; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Extended; +using MonoGame.Extended.Entities; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.Systems.Physics { + public class PhysicsCollisionDebugSystem : EntityDrawSystem + { + private readonly Camera2D _camera; + private readonly SpriteBatch _spriteBatch; + private readonly ChunkMap _chunkMap; + + private ComponentMapper transformMapper; + private ComponentMapper collisionBoxMapper; + + public PhysicsCollisionDebugSystem(Camera2D camera, SpriteBatch spriteBatch, ChunkMap chunkMap) : base(Aspect.All(typeof(Transform2), typeof(CollisionBox))) { + _camera = camera; + _spriteBatch = spriteBatch; + _chunkMap = chunkMap; + } + + public override void Initialize(IComponentMapperService mapperService) + { + transformMapper = mapperService.GetMapper(); + collisionBoxMapper = mapperService.GetMapper(); + } + + public override void Draw(GameTime gameTime) + { + if (!GameInstance.DebugMode) return; + + _spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, SamplerState.PointClamp, null, null, null, _camera.GetViewMatrix()); + + foreach (int entityId in ActiveEntities) { + Rectangle box = collisionBoxMapper.Get(entityId).Rounded; + + int minX = box.X; + int maxX = box.X + box.Width; + + int minY = box.Y; + int maxY = box.Y + box.Height; + + for (int i = minX; i < maxX; i++) + for (int j = minY; j < maxY; j++) { + RectangleF? blockBox = _chunkMap.TestBoundingBox(i, j); + if (blockBox.HasValue) { + _spriteBatch.DrawRectangle(new RectangleF(i, j, blockBox.Value.Width, blockBox.Value.Height), Color.Red, .05f, 0f); + } else { + _spriteBatch.DrawRectangle(new RectangleF(i, j, 1f, 1f), Color.Green, .05f, 0f); + } + } + } + + _spriteBatch.End(); + } + } +} \ No newline at end of file diff --git a/source/game/systems/physics/PhysicsSystem.cs b/source/game/systems/physics/PhysicsSystem.cs new file mode 100644 index 0000000..3963cc1 --- /dev/null +++ b/source/game/systems/physics/PhysicsSystem.cs @@ -0,0 +1,42 @@ +using Celesteia.Game.Components; +using Celesteia.Game.Components.Physics; +using Microsoft.Xna.Framework; +using MonoGame.Extended; +using MonoGame.Extended.Entities; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.Systems.Physics { + public class PhysicsSystem : EntityUpdateSystem { + public const float GRAVITY_CONSTANT = 9.7f; + + public PhysicsSystem() : base(Aspect.All(typeof(PhysicsEntity), typeof(TargetPosition))) {} + + private ComponentMapper targetPositionMapper; + private ComponentMapper physicsEntityMapper; + + public override void Initialize(IComponentMapperService mapperService) + { + targetPositionMapper = mapperService.GetMapper(); + physicsEntityMapper = mapperService.GetMapper(); + } + + public override void Update(GameTime gameTime) + { + foreach (int entityId in ActiveEntities) { + TargetPosition targetPosition = targetPositionMapper.Get(entityId); + PhysicsEntity physicsEntity = physicsEntityMapper.Get(entityId); + + // Apply gravity if applicable + if (physicsEntity.Gravity) { + if (physicsEntity.CollidingDown && physicsEntity.Velocity.Y > 0f) { + physicsEntity.SetVelocity(physicsEntity.Velocity.X, 0.1f); + } + + physicsEntity.AddVelocity(0f, physicsEntity.Mass * PhysicsSystem.GRAVITY_CONSTANT * gameTime.GetElapsedSeconds()); + } + + targetPosition.Target += physicsEntity.Velocity * gameTime.GetElapsedSeconds(); + } + } + } +} \ No newline at end of file diff --git a/source/game/systems/physics/PhysicsWorldCollisionSystem.cs b/source/game/systems/physics/PhysicsWorldCollisionSystem.cs new file mode 100644 index 0000000..ba8da22 --- /dev/null +++ b/source/game/systems/physics/PhysicsWorldCollisionSystem.cs @@ -0,0 +1,83 @@ +using System; +using Celesteia.Game.Components; +using Celesteia.Game.Components.Physics; +using Celesteia.Game.Planets; +using Microsoft.Xna.Framework; +using MonoGame.Extended; +using MonoGame.Extended.Entities; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.Systems.Physics { + public class PhysicsWorldCollisionSystem : EntityUpdateSystem { + private ChunkMap _chunkMap; + + public PhysicsWorldCollisionSystem(ChunkMap chunkMap) : base(Aspect.All(typeof(TargetPosition), typeof(PhysicsEntity), typeof(CollisionBox))) { + _chunkMap = chunkMap; + } + + private ComponentMapper transformMapper; + private ComponentMapper targetPositionMapper; + private ComponentMapper physicsEntityMapper; + private ComponentMapper collisionBoxMapper; + + public override void Initialize(IComponentMapperService mapperService) + { + transformMapper = mapperService.GetMapper(); + targetPositionMapper = mapperService.GetMapper(); + physicsEntityMapper = mapperService.GetMapper(); + collisionBoxMapper = mapperService.GetMapper(); + } + + public override void Update(GameTime gameTime) + { + foreach (int entityId in ActiveEntities) { + Transform2 transform = transformMapper.Get(entityId); + TargetPosition targetPosition = targetPositionMapper.Get(entityId); + PhysicsEntity physicsEntity = physicsEntityMapper.Get(entityId); + CollisionBox collisionBox = collisionBoxMapper.Get(entityId); + + collisionBox.Update(targetPosition.Target); + + int minX = (int)MathF.Floor(collisionBox.Bounds.Center.X - (collisionBox.Bounds.Width / 2f)); + int maxX = (int)MathF.Ceiling(collisionBox.Bounds.Center.X + (collisionBox.Bounds.Width / 2f)); + + int minY = (int)MathF.Floor(collisionBox.Bounds.Center.Y - (collisionBox.Bounds.Height / 2f)); + int maxY = (int)MathF.Ceiling(collisionBox.Bounds.Center.Y + (collisionBox.Bounds.Height / 2f)); + + bool collLeft = false; + bool collRight = false; + bool collUp = false; + bool collDown = false; + + for (int i = minX; i < maxX; i++) + for (int j = minY; j < maxY; j++) { + RectangleF? blockBox = _chunkMap.TestBoundingBox(i, j); + if (blockBox.HasValue) { + RectangleF inter = RectangleF.Intersection(collisionBox.Bounds, blockBox.Value); + + if (inter.IsEmpty) continue; + + if (inter.Width < inter.Height) { + collLeft = blockBox.Value.Center.X < collisionBox.Bounds.Center.X; + collRight = blockBox.Value.Center.X > collisionBox.Bounds.Center.X; + + targetPosition.Target += new Vector2(blockBox.Value.Center.X < collisionBox.Bounds.Center.X ? inter.Width : -inter.Width, 0f); + } else { + collUp = blockBox.Value.Center.Y < collisionBox.Bounds.Center.Y; + collDown = blockBox.Value.Center.Y > collisionBox.Bounds.Center.Y; + + targetPosition.Target += new Vector2(0f, blockBox.Value.Center.Y < collisionBox.Bounds.Center.Y ? inter.Height : -inter.Height); + } + + collisionBox.Update(targetPosition.Target); + } + } + + physicsEntity.CollidingDown = collDown; + physicsEntity.CollidingUp = collUp; + physicsEntity.CollidingLeft = collLeft; + physicsEntity.CollidingRight = collRight; + } + } + } +} \ No newline at end of file diff --git a/source/game/systems/ui/GameGUIDrawSystem.cs b/source/game/systems/ui/GameGUIDrawSystem.cs new file mode 100644 index 0000000..0140786 --- /dev/null +++ b/source/game/systems/ui/GameGUIDrawSystem.cs @@ -0,0 +1,12 @@ +using Celesteia.GUIs.Game; +using Microsoft.Xna.Framework; +using MonoGame.Extended.Entities.Systems; + +namespace Celesteia.Game.Systems.UI { + public class GameGUIDrawSystem : DrawSystem { + private GameGUI _gui; + + public GameGUIDrawSystem(GameGUI gui) => _gui = gui; + public override void Draw(GameTime gameTime) => _gui.Draw(gameTime); + } +} \ No newline at end of file -- cgit v1.2.3