summaryrefslogtreecommitdiff
path: root/source/game
diff options
context:
space:
mode:
Diffstat (limited to 'source/game')
-rw-r--r--source/game/WorldManager.cs33
-rw-r--r--source/game/components/CameraFollow.cs5
-rw-r--r--source/game/components/EntityAttributes.cs35
-rw-r--r--source/game/components/TargetPosition.cs7
-rw-r--r--source/game/components/entity/GameWorldEntity.cs14
-rw-r--r--source/game/components/items/Inventory.cs96
-rw-r--r--source/game/components/items/ItemStack.cs34
-rw-r--r--source/game/components/physics/CollisionBox.cs36
-rw-r--r--source/game/components/physics/PhysicsEntity.cs38
-rw-r--r--source/game/components/player/LocalPlayer.cs5
-rw-r--r--source/game/components/player/PlayerInput.cs14
-rw-r--r--source/game/components/skybox/SkyboxRotateZ.cs17
-rw-r--r--source/game/ecs/EntityFactory.cs111
-rw-r--r--source/game/ecs/GameWorld.cs41
-rw-r--r--source/game/input/GamepadHelper.cs59
-rw-r--r--source/game/input/InputManager.cs33
-rw-r--r--source/game/input/KeyboardHelper.cs53
-rw-r--r--source/game/input/MouseHelper.cs37
-rw-r--r--source/game/input/conditions/AllCondition.cs15
-rw-r--r--source/game/input/conditions/AnyCondition.cs15
-rw-r--r--source/game/input/conditions/AverageCondition.cs16
-rw-r--r--source/game/input/conditions/ICondition.cs5
-rw-r--r--source/game/input/definitions/InputDefinition.cs16
-rw-r--r--source/game/input/definitions/gamepad/BinaryGamepadDefinition.cs10
-rw-r--r--source/game/input/definitions/gamepad/SensorGamepadDefinition.cs13
-rw-r--r--source/game/input/definitions/keyboard/BinaryKeyboardDefinition.cs11
-rw-r--r--source/game/input/definitions/keyboard/TrinaryKeyboardDefinition.cs18
-rw-r--r--source/game/input/definitions/mouse/BinaryMouseDefinition.cs10
-rw-r--r--source/game/items/BlockItemActions.cs73
-rw-r--r--source/game/items/CooldownItemActions.cs17
-rw-r--r--source/game/items/FoliageItemActions.cs18
-rw-r--r--source/game/items/IItemActions.cs11
-rw-r--r--source/game/items/PickaxeItemActions.cs63
-rw-r--r--source/game/items/TorchItemActions.cs15
-rw-r--r--source/game/items/UpgradeItemActions.cs48
-rw-r--r--source/game/music/MusicManager.cs54
-rw-r--r--source/game/planets/BlockState.cs43
-rw-r--r--source/game/planets/Chunk.cs158
-rw-r--r--source/game/planets/ChunkMap.cs99
-rw-r--r--source/game/planets/GeneratedPlanet.cs35
-rw-r--r--source/game/planets/generation/IWorldGenerator.cs18
-rw-r--r--source/game/planets/generation/TerranPlanetGenerator.cs275
-rw-r--r--source/game/systems/CameraRenderSystem.cs43
-rw-r--r--source/game/systems/CameraSystem.cs39
-rw-r--r--source/game/systems/ChunkMapRenderSystem.cs63
-rw-r--r--source/game/systems/EntityDebugSystem.cs44
-rw-r--r--source/game/systems/LightingSystem.cs100
-rw-r--r--source/game/systems/LocalPlayerSystem.cs246
-rw-r--r--source/game/systems/TargetPositionSystem.cs37
-rw-r--r--source/game/systems/mainmenu/MainMenuBackgroundSystem.cs33
-rw-r--r--source/game/systems/mainmenu/MainMenuRenderSystem.cs43
-rw-r--r--source/game/systems/physics/PhysicsCollisionDebugSystem.cs61
-rw-r--r--source/game/systems/physics/PhysicsSystem.cs42
-rw-r--r--source/game/systems/physics/PhysicsWorldCollisionSystem.cs83
-rw-r--r--source/game/systems/ui/GameGUIDrawSystem.cs12
55 files changed, 2570 insertions, 0 deletions
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<GameWorld> LoadNewWorld(Action<string> progressReport = null, int? seed = null) {
+ // Asynchronously generate the world.
+ GameWorld generatedWorld = await Task.Run<GameWorld>(() => {
+ 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<EntityAttribute>().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<float> Horizontal;
+ public ICondition<bool> Run;
+ public ICondition<bool> Jump;
+ public ICondition<bool> Inventory;
+ public ICondition<bool> Crafting;
+ public ICondition<bool> Pause;
+ public ICondition<bool> PrimaryUse;
+ public ICondition<bool> 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<Buttons>())
+ 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<Keys> exemptedFromAny = new List<Keys> {
+ { 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<bool> {
+ 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<bool> {
+ 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<float> {
+ 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> {
+ 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> {
+ T Test();
+ }
+
+ public interface IBinaryInputDefinition : IInputDefinition<bool> {}
+ public interface IFloatInputDefinition : IInputDefinition<float> {}
+
+ 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<Transform2>() || !user.Has<EntityAttributes>()) return false;
+
+ Transform2 entityTransform = user.Get<Transform2>();
+ EntityAttributes.EntityAttributeMap attributes = user.Get<EntityAttributes>().Attributes;
+
+ if (Vector2.Distance(entityTransform.Position, cursor.ToVector2()) > attributes.Get(EntityAttribute.BlockRange)) return false;
+
+ if (!forWall && user.Has<CollisionBox>()) {
+ Rectangle box = user.Get<CollisionBox>().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<Transform2>() || !user.Has<EntityAttributes>()) return false;
+
+ Transform2 entityTransform = user.Get<Transform2>();
+ EntityAttributes.EntityAttributeMap attributes = user.Get<EntityAttributes>().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<Inventory>())
+ user.Get<Inventory>().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<EntityAttributes>()) return false;
+
+ EntityAttributes.EntityAttributeMap attributes = user.Get<EntityAttributes>().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<EntityAttributes>().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<string> progressReport = null) {
+ Action<string> 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<string> 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<string> 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<double, int> foliageDistribution = new Dictionary<double, int> {
+ { 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<Transform2> transformMapper;
+ private ComponentMapper<EntityFrames> 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<Transform2>();
+ entityFramesMapper = mapperService.GetMapper<EntityFrames>();
+ }
+
+ 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<Transform2> transformMapper;
+
+ public CameraSystem(Camera2D camera) : base(Aspect.All(typeof(Transform2), typeof(CameraFollow)))
+ => _camera = camera;
+
+ public override void Initialize(IComponentMapperService mapperService)
+ => transformMapper = mapperService.GetMapper<Transform2>();
+
+ 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<ChunkVector> activeChunks = new List<ChunkVector>();
+ 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<Transform2> 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<Transform2>();
+
+ _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<byte, BlockLightProperties> 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<byte, BlockLightProperties>();
+ }
+
+ 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<Color>(_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<LocalPlayer>();
+ targetPosition = _player.Get<TargetPosition>();
+ physicsEntity = _player.Get<PhysicsEntity>();
+ frames = _player.Get<EntityFrames>();
+ attributes = _player.Get<EntityAttributes>();
+ input = _player.Get<PlayerInput>();
+ inventory = _player.Get<Inventory>();
+ }
+ }
+ 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<int, int> hotbarMappings = new Dictionary<int, int>() {
+ { (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<Transform2> transformMapper;
+ private ComponentMapper<TargetPosition> targetPositionMapper;
+
+ public TargetPositionSystem(ChunkMap chunkMap) : base(Aspect.All(typeof(Transform2), typeof(TargetPosition)))
+ => _chunkMap = chunkMap;
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ transformMapper = mapperService.GetMapper<Transform2>();
+ targetPositionMapper = mapperService.GetMapper<TargetPosition>();
+ }
+
+ 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<Transform2> transformMapper;
+ private ComponentMapper<SkyboxRotateZ> rotatorMapper;
+
+ public MainMenuBackgroundSystem() : base(Aspect.All(typeof(Transform2), typeof(SkyboxRotateZ))) {}
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ transformMapper = mapperService.GetMapper<Transform2>();
+ rotatorMapper = mapperService.GetMapper<SkyboxRotateZ>();
+ }
+
+ 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<Transform2> transformMapper;
+ private ComponentMapper<SkyboxPortionFrames> 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<Transform2>();
+ framesMapper = mapperService.GetMapper<SkyboxPortionFrames>();
+ }
+
+ 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<Transform2> transformMapper;
+ private ComponentMapper<CollisionBox> 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<Transform2>();
+ collisionBoxMapper = mapperService.GetMapper<CollisionBox>();
+ }
+
+ 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<TargetPosition> targetPositionMapper;
+ private ComponentMapper<PhysicsEntity> physicsEntityMapper;
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ targetPositionMapper = mapperService.GetMapper<TargetPosition>();
+ physicsEntityMapper = mapperService.GetMapper<PhysicsEntity>();
+ }
+
+ 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<Transform2> transformMapper;
+ private ComponentMapper<TargetPosition> targetPositionMapper;
+ private ComponentMapper<PhysicsEntity> physicsEntityMapper;
+ private ComponentMapper<CollisionBox> collisionBoxMapper;
+
+ public override void Initialize(IComponentMapperService mapperService)
+ {
+ transformMapper = mapperService.GetMapper<Transform2>();
+ targetPositionMapper = mapperService.GetMapper<TargetPosition>();
+ physicsEntityMapper = mapperService.GetMapper<PhysicsEntity>();
+ collisionBoxMapper = mapperService.GetMapper<CollisionBox>();
+ }
+
+ 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