diff --git a/DepthsBelow/DepthsBelow/Camera.cs b/DepthsBelow/DepthsBelow/Camera.cs index bd187f4..80850aa 100644 --- a/DepthsBelow/DepthsBelow/Camera.cs +++ b/DepthsBelow/DepthsBelow/Camera.cs @@ -35,11 +35,27 @@ namespace DepthsBelow return Vector2.Transform(screenPos, Matrix.Invert(Transform)); } + public Rectangle ScreenToWorld(Rectangle screenRectangle) + { + var pos = ScreenToWorld(new Vector2(screenRectangle.X, screenRectangle.Y)); + screenRectangle.X = (int)pos.X; + screenRectangle.Y = (int)pos.Y; + return screenRectangle; + } + public Vector2 WorldToScreen(Vector2 worldPos) { return Vector2.Transform(worldPos, Transform); } + public Rectangle WorldToScreen(Rectangle screenRectangle) + { + var pos = WorldToScreen(new Vector2(screenRectangle.X, screenRectangle.Y)); + screenRectangle.X = (int)pos.X; + screenRectangle.Y = (int)pos.Y; + return screenRectangle; + } + public void Update(GameTime gameTime) { float elapsed = gameTime.ElapsedGameTime.Milliseconds / 1000.0f; @@ -85,6 +101,11 @@ namespace DepthsBelow Transform *= Matrix.CreateTranslation(new Vector3(change.X, change.Y, 0)); } + Transform = Matrix.Identity + * Matrix.CreateRotationZ(Rotation) + * Matrix.CreateScale(Zoom) + * Matrix.CreateTranslation(new Vector3(Position.X, Position.Y, 0)); + lastMouseState = ms; } } diff --git a/DepthsBelow/DepthsBelow/Component/Stat.cs b/DepthsBelow/DepthsBelow/Component/Stat.cs index dc1afa3..9fb5e45 100644 --- a/DepthsBelow/DepthsBelow/Component/Stat.cs +++ b/DepthsBelow/DepthsBelow/Component/Stat.cs @@ -5,9 +5,28 @@ using System.Text; namespace DepthsBelow.Component { - public class Stat : Component - { - protected int hp = 0; + public class Stat : Component + { + /// + /// The current health points of the unit + /// + public float HP = 0; + public float _MaxHP = 100; + /// + /// Gets or sets the max HP. + /// + public float MaxHP + { + get { return _MaxHP; } + set + { + _MaxHP = value; + if (HP == 0) + HP = value; + } + } + + protected int hp = 0; protected int defence = 0; protected int stepsTaken = 0; protected int distanceToTarget = 0; @@ -65,7 +84,8 @@ namespace DepthsBelow.Component return panic + GetPenalty(stepsTaken) + (int)(GetPenalty(distance) / 2); } - public Stat(Entity parent) : base (parent) + public Stat(Entity parent) + : base (parent) { } diff --git a/DepthsBelow/DepthsBelow/Core.cs b/DepthsBelow/DepthsBelow/Core.cs index 27e7292..2c64b2e 100755 --- a/DepthsBelow/DepthsBelow/Core.cs +++ b/DepthsBelow/DepthsBelow/Core.cs @@ -32,7 +32,8 @@ namespace DepthsBelow public static MouseInput MouseInput; public static KeyboardInput KeyboardInput; - public List Squad; + public List Squad; + public DynamicGroupManager GroupManager; public List Swarm; public List Volley; public static Map Map; @@ -42,20 +43,16 @@ namespace DepthsBelow public Core() { GraphicsDeviceManager = new GraphicsDeviceManager(this); - Content.RootDirectory = "Content"; - GraphicsDeviceManager.PreferredBackBufferWidth = 1280; GraphicsDeviceManager.PreferredBackBufferHeight = 720; GraphicsDeviceManager.IsFullScreen = false; - //graphics.SynchronizeWithVerticalRetrace = false; - this.IsFixedTimeStep = false; GraphicsDeviceManager.ApplyChanges(); + this.IsFixedTimeStep = false; this.IsMouseVisible = true; - GameServices.AddService(GraphicsDevice); - GameServices.AddService(Content); + Content.RootDirectory = "Content"; } /// @@ -66,19 +63,21 @@ namespace DepthsBelow /// protected override void Initialize() { - Lua = new Lua(); - - // TODO: Add your initialization logic here - EntityManager = new EntityManager(); - TurnManager = new TurnManager(new string[] { "Player", "Computer" }); - + GameServices.AddService(GraphicsDevice); + GameServices.AddService(Content); + + Lua = new Lua(); + + EntityManager = new EntityManager(); + TurnManager = new TurnManager(new string[] { "Player", "Computer" }); + Camera = new Camera(this); - Interface = new Interface(this); - - Squad = new List(); - Swarm = new List(); - Volley = new List(); - + Interface = new Interface(this); + + Squad = new List(); + Swarm = new List(); + Volley = new List(); + base.Initialize(); } @@ -94,6 +93,8 @@ namespace DepthsBelow Map = Content.Load("maps/Cave.Level1"); // Load map objects Map.ParseObjects(this); + GroupManager = new DynamicGroupManager(Squad.Cast().ToList(), (float)Grid.TileSize * 1.5f); + Interface.CreateUnitFrames(Squad); // TODO: use this.Content to load your game content here Soldier.LoadContent(); @@ -139,6 +140,7 @@ namespace DepthsBelow EntityManager.Update(gameTime); + Interface.Update(gameTime); GUIManager.Update(gameTime); base.Update(gameTime); diff --git a/DepthsBelow/DepthsBelow/DepthsBelow.csproj b/DepthsBelow/DepthsBelow/DepthsBelow.csproj index ff30942..e908eb5 100644 --- a/DepthsBelow/DepthsBelow/DepthsBelow.csproj +++ b/DepthsBelow/DepthsBelow/DepthsBelow.csproj @@ -121,6 +121,7 @@ + diff --git a/DepthsBelow/DepthsBelow/DynamicGroupManager.cs b/DepthsBelow/DepthsBelow/DynamicGroupManager.cs new file mode 100755 index 0000000..a81dc6c --- /dev/null +++ b/DepthsBelow/DepthsBelow/DynamicGroupManager.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Xna.Framework; + +namespace DepthsBelow +{ + public class DynamicGroupManager + { + public class Group + { + public List Entities; + public float Panic; + } + + private List entities; + + public float MaxRange; + + public List Groups; + + public DynamicGroupManager(List entities, float maxRange) + { + this.entities = entities; + this.MaxRange = maxRange; + + UpdateGroups(); + } + + public void UpdateGroups() + { + if (Groups == null) + Groups = new List(); + + var newGroups = CreateGroups(MaxRange); + + foreach (var newGroup in newGroups) + { + foreach (var entity in newGroup.Entities) + { + Group oldGroup = null; + // Find the group the entity belonged to before + foreach (var group in Groups) + { + if (group.Entities.Contains(entity)) + { + oldGroup = group; + break; + } + } + + if (oldGroup != null) + newGroup.Panic += oldGroup.Panic / oldGroup.Entities.Count; + else + newGroup.Panic = 10; + } + } + + Groups = newGroups; + } + + private List CreateGroups(float maxRange) + { + var alreadyGrouped = new List(); + var groups = new List(); + + for (int index = 0; index < entities.Count; index++) + { + var soldier = entities[index]; + + if (!alreadyGrouped.Contains(soldier)) + { + var group = new List(); + GetNearChain(ref group, soldier, maxRange); + alreadyGrouped.AddRange(group); + + if (group.Count > 0) + groups.Add(new Group() { Entities = group }); + } + } + + return groups; + } + + // Get all entities who are close to each other in a chain + private void GetNearChain(ref List group, Entity entity, float maxRange) + { + for (int index = 0; index < entities.Count; index++) + { + var nearSoldier = entities[index]; + + if (!group.Contains(nearSoldier)) + { + float distance = Vector2.Distance(nearSoldier.Transform.World + nearSoldier.Transform.World.Origin, + entity.Transform.World + entity.Transform.World.Origin); + if (distance <= maxRange) + { + group.Add(nearSoldier); + GetNearChain(ref group, nearSoldier, maxRange); + } + } + } + } + } +} diff --git a/DepthsBelow/DepthsBelow/Entity.cs b/DepthsBelow/DepthsBelow/Entity.cs index d7722eb..f0fcbc9 100644 --- a/DepthsBelow/DepthsBelow/Entity.cs +++ b/DepthsBelow/DepthsBelow/Entity.cs @@ -45,6 +45,19 @@ namespace DepthsBelow set { this.Transform.Grid.Y = value; } } + /// + /// Storage for property data + /// + public Dictionary Properties = new Dictionary(); + /// + /// Shorthand for frame properties. + /// + public object this[string key] + { + get { return Properties[key]; } + set { Properties[key] = value; } + } + /// /// Creates a game object and adds it to the referenced entity manager. /// diff --git a/DepthsBelow/DepthsBelow/GUI/Button.cs b/DepthsBelow/DepthsBelow/GUI/Button.cs index 0756a89..54a6b74 100755 --- a/DepthsBelow/DepthsBelow/GUI/Button.cs +++ b/DepthsBelow/DepthsBelow/GUI/Button.cs @@ -29,10 +29,10 @@ namespace DepthsBelow.GUI } - public Button(Frame parent) - : this() + public Button(Frame parent) + : base(parent) { - this.Parent = parent; + } public override void Update(GameTime gameTime) diff --git a/DepthsBelow/DepthsBelow/GUI/Frame.cs b/DepthsBelow/DepthsBelow/GUI/Frame.cs index 843b49f..ef73e92 100755 --- a/DepthsBelow/DepthsBelow/GUI/Frame.cs +++ b/DepthsBelow/DepthsBelow/GUI/Frame.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using System.Reflection; using System.Text; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Content; @@ -81,16 +83,17 @@ namespace DepthsBelow.GUI /// public Rectangle Rectangle; /// - /// Absolute rectangle of the frame. Takes parent frame positions in consideration. + /// Absolute rectangle of the frame. + /// Takes parent frame positions in consideration and converts negative widths and heights. /// public Rectangle AbsoluteRectangle { get { if (Parent != null) - return new Rectangle(Parent.AbsoluteRectangle.X + Rectangle.X, Parent.AbsoluteRectangle.Y + Rectangle.Y, Rectangle.Width, Rectangle.Height); + return Utility.MakeRectanglePositive(new Rectangle(Parent.AbsoluteRectangle.X + Rectangle.X, Parent.AbsoluteRectangle.Y + Rectangle.Y, Rectangle.Width, Rectangle.Height)); else - return Rectangle; + return Utility.MakeRectanglePositive(Rectangle); } } /// @@ -101,10 +104,16 @@ namespace DepthsBelow.GUI /// Texture color tint. /// public Color Color; + + public bool _Visible = true; /// /// If the frame is visible or not. /// - public bool Visible = true; + public bool Visible + { + get { return (Parent != null) ? _Visible && Parent.Visible : _Visible; } + set { _Visible = value; } + } /// /// The layer of the frame. @@ -132,47 +141,142 @@ namespace DepthsBelow.GUI /// public List Children; + /// + /// Storage for property data + /// + public Dictionary Properties = new Dictionary(); + /// + /// Shorthand for frame properties. + /// + public object this[string key] + { + get { return Properties[key]; } + set { Properties[key] = value; } + } + + /// + /// Controls whether mouse events can be handled by the frame. + /// + public bool MouseEnabled = false; + #region Events + + /// + /// Handler for click events. + /// + /// The frame. + /// The instance containing the event data. + public delegate void OnClickHandler(Frame frame, GUIManager.MouseEventArgs args); /// /// Occurs when the frame is clicked inside it's bounding rectangle. /// public event OnClickHandler OnClick; - /// - /// Gets the number of subscribers to the event. - /// - public int OnClickCount - { - get { return (OnClick != null) ? OnClick.GetInvocationList().Length : 0; } - } - /// - /// event arguments. - /// - public class OnClickArgs : EventArgs - { - /// - /// The position of the click relative to the frame position. - /// - public Point Position; - /// - /// The time of the click, since the start of the program. - /// - public TimeSpan Time; - } /// - /// Handler for click events. + /// Handler for press events. /// - /// The clicked frame. - /// The instance containing the event data. - public delegate void OnClickHandler(Frame frame, OnClickArgs e); + /// The frame. + /// The instance containing the event data. + public delegate void OnPressHandler(Frame frame, GUIManager.MouseEventArgs args); /// - /// Raise an event on the frame. + /// Occurs when the frame is clicked inside it's bounding rectangle. /// - /// The instance containing the event data. - public void Click(OnClickArgs e) + public event OnPressHandler OnPress; + + /// + /// Handler for release events. + /// + /// The frame. + /// The instance containing the event data. + public delegate void OnReleaseHandler(Frame frame, GUIManager.MouseEventArgs args); + /// + /// Occurs when the frame is clicked inside it's bounding rectangle. + /// + public event OnReleaseHandler OnRelease; + + /// + /// Handler for mouse over events. + /// + /// The frame. + /// The instance containing the event data. + public delegate void OnMouseOverHandler(Frame frame, GUIManager.MouseEventArgs args); + /// + /// Occurs when the frame is clicked inside it's bounding rectangle. + /// + public event OnMouseOverHandler OnMouseOver; + + /// + /// Handler for mouse out events. + /// + /// The frame. + /// The instance containing the event data. + public delegate void OnMouseOutHandler(Frame frame, GUIManager.MouseEventArgs args); + /// + /// Occurs when the frame is clicked inside it's bounding rectangle. + /// + public event OnMouseOutHandler OnMouseOut; + + + /// + /// Raises the specified event by name. + /// + /// Name of the event. + /// The instance containing the event data. + /// Returns true if any event was raised. + public bool Raise(string eventName, EventArgs eventArgs) { - if (OnClick != null) - OnClick(this, e); + // Get the event field info + var fieldInfo = this.GetType().GetField(eventName, BindingFlags.NonPublic | BindingFlags.Instance); + // If the event exists + if (fieldInfo != null) + { + var eventDelegate = (MulticastDelegate)fieldInfo.GetValue(this); + // If there's any subscribed events... + if (eventDelegate != null) + { + // Invoke their raise methods + foreach (var handler in eventDelegate.GetInvocationList()) + { + try + { + handler.Method.Invoke(handler.Target, new object[] { this, eventArgs }); + } + catch (Exception) + { + return false; + } + } + + return true; + } + + return false; + } + + return false; + } + + /// + /// Gets the subscriber count for a specific event. + /// + /// Name of the event. + /// Returns the number of subscribers of the event. + public int GetSubscriberCount(string eventName) + { + // Get the event field info + var fieldInfo = this.GetType().GetField(eventName, BindingFlags.NonPublic | BindingFlags.Instance); + // If the event exists + if (fieldInfo != null) + { + var eventDelegate = (MulticastDelegate)fieldInfo.GetValue(this); + // If there's any subscribed events... + if (eventDelegate != null) + return eventDelegate.GetInvocationList().Length; + + return 0; + } + + return 0; } #endregion @@ -189,7 +293,7 @@ namespace DepthsBelow.GUI this.Children = new List(); - UID = GUIManager.Add(this); + this.UID = GUIManager.Add(this); } /// @@ -258,7 +362,7 @@ namespace DepthsBelow.GUI { if (OnClick != null) { - var args = new OnClickArgs() + var args = new MouseEventArgs() { Position = new Point(ms.X - AbsoluteRectangle.X, ms.Y - AbsoluteRectangle.Y), Time = gameTime.TotalGameTime @@ -270,19 +374,19 @@ namespace DepthsBelow.GUI lastMouseState = ms;*/ - foreach (var child in Children) - child.Update(gameTime); + /*foreach (var child in Children) + child.Update(gameTime);*/ } public virtual void Draw(SpriteBatch spriteBatch) { if (Visible) - { + { if (Texture != null) spriteBatch.Draw(Texture, AbsoluteRectangle, Color); - foreach (var child in Children) - child.Draw(spriteBatch); + /*foreach (var child in Children) + child.Draw(spriteBatch);*/ } } } diff --git a/DepthsBelow/DepthsBelow/GUI/Text.cs b/DepthsBelow/DepthsBelow/GUI/Text.cs index 1c7f212..090f73f 100755 --- a/DepthsBelow/DepthsBelow/GUI/Text.cs +++ b/DepthsBelow/DepthsBelow/GUI/Text.cs @@ -29,9 +29,9 @@ namespace DepthsBelow.GUI } public Text(Frame parent) - : this() + : base(parent) { - this.Parent = parent; + this.Color = Color.White; } /// @@ -47,8 +47,8 @@ namespace DepthsBelow.GUI { base.Draw(spriteBatch); - if (Parent.Visible && this.Visible && Font != null) - spriteBatch.DrawString(Font, Value, new Vector2(Rectangle.X + Parent.Rectangle.X, Rectangle.Y + Parent.Rectangle.Y), Color); + if (Visible && Font != null) + spriteBatch.DrawString(Font, Value, new Vector2(Rectangle.X + Parent.AbsoluteRectangle.X, Rectangle.Y + Parent.AbsoluteRectangle.Y), Color); } } } diff --git a/DepthsBelow/DepthsBelow/GUIManager.cs b/DepthsBelow/DepthsBelow/GUIManager.cs index b5cfe48..7edc7bb 100755 --- a/DepthsBelow/DepthsBelow/GUIManager.cs +++ b/DepthsBelow/DepthsBelow/GUIManager.cs @@ -6,18 +6,35 @@ using System.Text; using DepthsBelow.GUI; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; namespace DepthsBelow { - static class GUIManager + public static class GUIManager { + /// + /// GUI mouse event arguments. + /// + public class MouseEventArgs : EventArgs + { + /// + /// The XNA mouse state. + /// + public MouseState MouseState; + + /// + /// The time of the click, since the start of the program. + /// + public TimeSpan Time; + } + public static List Frames = new List(); private static int uidIndex = 0; public static int Add(Frame frame) { Frames.Add(frame); - return uidIndex++; + return CreateUID(); } public static void Remove(Frame frame) @@ -25,24 +42,35 @@ namespace DepthsBelow Frames.Remove(frame); } - public static void Click(Point position, TimeSpan time) + public static int CreateUID() { - var clickRectangle = new Rectangle(position.X, position.Y, 1, 1); + return uidIndex++; + } - // Create a list of all frames which have an OnClick handler and intersects with the click position - var intersections = Frames.Where(frame => frame.OnClickCount > 0).Where(frame => clickRectangle.Intersects(frame.AbsoluteRectangle)).ToList(); + /// + /// Calculates which frames are intersecting a point. + /// + /// A point on the screen. + /// Returns a list of frames intersecting the point. + public static List FramesIntersectingPoint(Point point) + { + var rect = new Rectangle(point.X, point.Y, 1, 1); + return Frames.Where(frame => rect.Intersects(frame.AbsoluteRectangle)).ToList(); + } + + public static bool RaiseAt(string eventName, EventArgs eventArgs, Point position) + { + // Create a list of all frames which intersects with the click position + var intersections = FramesIntersectingPoint(position); + // Filter it by event + intersections = intersections.Where(frame => frame.GetSubscriberCount(eventName) > 0).ToList(); // Get the topmost frame of that list var topFrame = GetTopFrame(intersections); if (topFrame != null) - { - var e = new Frame.OnClickArgs() - { - Position = position, - Time = time - }; - topFrame.Click(e); - } + return topFrame.Raise(eventName, eventArgs); + else + return false; } /// diff --git a/DepthsBelow/DepthsBelow/Interface.cs b/DepthsBelow/DepthsBelow/Interface.cs index 808cab3..7749f59 100755 --- a/DepthsBelow/DepthsBelow/Interface.cs +++ b/DepthsBelow/DepthsBelow/Interface.cs @@ -6,6 +6,7 @@ using DepthsBelow.GUI; using Microsoft.Xna.Framework; using System.Diagnostics; using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; namespace DepthsBelow { @@ -13,10 +14,132 @@ namespace DepthsBelow { Core core; + /// + /// The master frame covering the whole game screen. Parent of all frames. + /// This frame handles all mouse interaction with the game world + /// + public Frame UIParent; + + public List PanicFrames = new List(); + public List UnitFrames = new List(); + public Dictionary SoldierToFrame = new Dictionary(); + + Random random = new Random(); + public Interface(Core core) { this.core = core; + UIParent = new Frame(); + UIParent.Width = GameServices.GetService().Viewport.Width; + UIParent.Height = GameServices.GetService().Viewport.Height; + + // Selection rectangle + var selectionFrame = new Frame(UIParent); + selectionFrame.Texture = Utility.GetSolidTexture(); + selectionFrame.Color = Color.Red * 0.5f; + selectionFrame.Visible = false; + UIParent["selectionFrame"] = selectionFrame; + + // OnClick + UIParent.OnClick += delegate(Frame frame, GUIManager.MouseEventArgs args) + { + KeyboardState ks = Keyboard.GetState(); + MouseState ms = args.MouseState; + + var mousePos = new Point(ms.X, ms.Y); + var mouseRectangle = new Rectangle(mousePos.X, mousePos.Y, 1, 1); + var mouseWorldPos = core.Camera.ScreenToWorld(new Vector2(mousePos.X, mousePos.Y)); + var mouseWorldRectangle = new Rectangle((int)mouseWorldPos.X, (int)mouseWorldPos.Y, 1, 1); + + // Deselect all units + if (!ks.IsKeyDown(Keys.LeftControl)) + { + foreach (var unit in core.Squad) + unit.Selected = false; + + foreach (var unitFrame in UnitFrames) + unitFrame.Color = Color.Black; + } + + // Select all units in the rectangle + foreach (var soldier in core.Squad) + { + if (mouseWorldRectangle.Intersects(soldier.GetComponent().Rectangle)) + { + soldier.Selected = true; + + foreach (var unitFrame in UnitFrames) + { + if (unitFrame["soldier"] == soldier) + unitFrame.Color = Color.Blue; + } + } + } + + // Send orders with right click + if (ks.IsKeyDown(Keys.LeftControl)) + foreach (var unit in core.Squad) + if (unit.Selected) + unit.GetComponent().Goal = Grid.WorldToGrid(mouseWorldPos); + + + // Hide the selection rectangle + //selectionRectangle = Rectangle.Empty; + }; + + // OnPress + UIParent.OnPress += delegate(Frame frame, GUIManager.MouseEventArgs args) + { + //KeyboardState ks = Keyboard.GetState(); + MouseState ms = args.MouseState; + + var mousePos = new Point(ms.X, ms.Y); + var mouseRectangle = new Rectangle(mousePos.X, mousePos.Y, 1, 1); + var mouseWorldPos = core.Camera.ScreenToWorld(new Vector2(mousePos.X, mousePos.Y)); + var mouseWorldRectangle = new Rectangle((int)mouseWorldPos.X, (int)mouseWorldPos.Y, 1, 1); + + // Show the selection frame + var selectFrame = (GUI.Frame)frame["selectionFrame"]; + if (!selectFrame.Visible) + { + selectFrame.X = (int)mousePos.X; + selectFrame.Y = (int)mousePos.Y; + selectFrame.Visible = true; + } + }; + + // OnRelease + UIParent.OnRelease += delegate(Frame frame, GUIManager.MouseEventArgs args) + { + // Show the selection frame + var selectFrame = (GUI.Frame)frame["selectionFrame"]; + if (selectFrame.Visible) + { + var intersectionRectangle = core.Camera.ScreenToWorld(selectFrame.AbsoluteRectangle); + + // Select all units in the rectangle + foreach (var soldier in core.Squad) + { + if (intersectionRectangle.Intersects(soldier.GetComponent().Rectangle)) + { + soldier.Selected = true; + foreach (var unitFrame in UnitFrames) + { + if (unitFrame["soldier"] == soldier) + unitFrame.Color = Color.Blue; + } + } + } + + selectFrame.Visible = false; + } + }; + + var leftEdge = new Frame(UIParent); + leftEdge.Width = 20; + leftEdge.Height = GameServices.GetService().Viewport.Height; + // Test GUI frames /*var frame = new GUI.Frame() { @@ -39,30 +162,29 @@ namespace DepthsBelow text.Value = "Hello world!";*/ // Create some unit frames - /*var panicFrame = CreatePanicFrame(); var unit1 = CreateUnitFrame("Flight captain Rainbow"); unit1.X = panicFrame.X; unit1.Y = panicFrame.Y + panicFrame.Height; - unit1.OnClick += delegate(Frame frame, Frame.OnClickArgs args) + unit1.OnClick += delegate(Frame frame, Frame.MouseEventArgs args) { frame.Color = Color.Red; }; var unit2 = CreateUnitFrame("Mr. Sparkle"); unit2.X = unit1.X; unit2.Y = unit1.Y + unit1.Height; - unit2.OnClick += delegate(Frame frame, Frame.OnClickArgs args) + unit2.OnClick += delegate(Frame frame, Frame.MouseEventArgs args) { frame.Color = Color.Red; };*/ - var frame1 = new GUI.Frame(); + /*var frame1 = new GUI.Frame(); frame1.Layer = 0; frame1.SetTexture("images/Enter"); frame1.Color = Color.Red; frame1.Width = 100; frame1.Height = 100; - frame1.OnClick += delegate(Frame frame, Frame.OnClickArgs args) + frame1.OnClick += delegate(Frame frame, Frame.MouseEventArgs args) { if (frame.Color == Color.Red) frame.Color = Color.Green; @@ -76,13 +198,6 @@ namespace DepthsBelow frame1_child.Height = 60; frame1_child.X = 20; frame1_child.Y = 20; - frame1_child.OnClick += delegate(Frame frame, Frame.OnClickArgs args) - { - if (frame.Color == Color.Purple) - frame.Color = Color.Pink; - else - frame.Color = Color.Purple; - }; var frame2 = new GUI.Frame(); frame2.Layer = 0; @@ -92,71 +207,208 @@ namespace DepthsBelow frame2.Height = 100; frame2.X = 50; frame2.Y = 50; - frame2.OnClick += delegate(Frame frame, Frame.OnClickArgs args) + frame2.OnClick += delegate(Frame frame, Frame.MouseEventArgs args) { if (frame.Color == Color.Blue) frame.Color = Color.Yellow; else frame.Color = Color.Blue; - }; + };*/ } private GUI.Frame CreatePanicFrame() { - var frame = new GUI.Frame(); + var frame = new GUI.Frame(UIParent); frame.SetTexture("images/GUI/unit_background"); frame.Color = Color.Black; frame.Width = 164; frame.Height = 19; - - var healthBg = new GUI.Frame(frame); - healthBg.SetTexture("images/GUI/health_background"); - healthBg.Width = 136; - healthBg.Height = 13; - healthBg.X = 3; - healthBg.Y = 3; - var healthBar = new GUI.Frame(healthBg); - healthBar.SetTexture("images/GUI/bar_solid"); - healthBar.Color = new Color(87, 55, 253); - healthBar.Width = (int)(healthBg.Width * 0.5); - healthBar.Height = 11; - healthBar.X = 1; - healthBar.Y = 1; + var panicBarBg = new GUI.Frame(frame); + panicBarBg.SetTexture("images/GUI/health_background"); + panicBarBg.Width = 136; + panicBarBg.Height = 13; + panicBarBg.X = 3; + panicBarBg.Y = 3; + frame["panicBarBg"] = panicBarBg; + + var panicBar = new GUI.Frame(panicBarBg); + panicBar.SetTexture("images/GUI/bar_solid"); + panicBar.Color = new Color(87, 55, 253); + panicBar.Width = (int)(panicBarBg.Width * 0.5); + panicBar.Height = 11; + panicBar.X = 1; + panicBar.Y = 1; + frame["panicBar"] = panicBar; return frame; } - private GUI.Frame CreateUnitFrame(string unitName) + private GUI.Frame CreateUnitFrame(Soldier soldier) { - var frame = new GUI.Frame(); + var frame = new GUI.Frame(UIParent); frame.SetTexture("images/GUI/unit_background"); frame.Color = Color.Black; frame.Width = 164; frame.Height = 35; - var nameText = new GUI.Text(frame); - nameText.SetFont("fonts/UnitName"); - nameText.Value = unitName; - nameText.X = 4; - nameText.Y = 1; + var nameText = new GUI.Text(frame); + nameText.SetFont("fonts/UnitName"); + nameText.Value = soldier.Name + " " + soldier.GetHashCode(); + nameText.X = 4; + nameText.Y = 1; + frame["nameText"] = nameText; - var healthBg = new GUI.Frame(frame); - healthBg.SetTexture("images/GUI/health_background"); - healthBg.Width = 136; - healthBg.Height = 13; - healthBg.X = 3; - healthBg.Y = 19; + var healthBarBg = new GUI.Frame(frame); + healthBarBg.SetTexture("images/GUI/health_background"); + healthBarBg.Width = 136; + healthBarBg.Height = 13; + healthBarBg.X = 3; + healthBarBg.Y = 19; + frame["healthBarBg"] = healthBarBg; - var healthBar = new GUI.Frame(healthBg); - healthBar.SetTexture("images/GUI/bar_solid"); - healthBar.Color = Color.Red; - healthBar.Width = (int)(healthBg.Width * 0.5f); - healthBar.Height = 11; - healthBar.X = 1; - healthBar.Y = 1; + var healthBar = new GUI.Frame(healthBarBg); + healthBar.SetTexture("images/GUI/bar_solid"); + healthBar.Color = Color.Red; + healthBar.Width = (int)((healthBarBg.Width - 2) * (soldier.GetComponent().HP / soldier.GetComponent().MaxHP)); + healthBar.Height = 11; + healthBar.X = 1; + healthBar.Y = 1; + frame["healthBar"] = healthBar; return frame; } + + public void CreateUnitFrames(List squad) + { + // TODO: ForEach squad in Squads... + /*GUI.Frame lastFrame = null; + foreach (Soldier soldier in squad) + { + // TODO: Remove this test ;) + soldier.GetComponent().HP = random.Next(0, (int) soldier.GetComponent().MaxHP - 1); + + var uFrame = CreateUnitFrame(soldier); + uFrame["soldier"] = soldier; + soldier["unitFrame"] = uFrame; + if (lastFrame != null) + { + uFrame.X = lastFrame.X; + uFrame.Y = lastFrame.Y + lastFrame.Height; + } + uFrame.OnClick += delegate(Frame f, GUIManager.MouseEventArgs e) + { + var s = (Soldier) f["soldier"]; + KeyboardState ks = Keyboard.GetState(); + + if (!ks.IsKeyDown(Keys.LeftControl)) + { + // Deselect all units + foreach (var unitFrame in UnitFrames) + { + ((Soldier) unitFrame["soldier"]).Selected = false; + unitFrame.Color = Color.Black; + } + } + + if (!s.Selected) + { + s.Selected = true; + uFrame.Color = Color.Blue; + } + }; + UnitFrames.Add(uFrame); + lastFrame = uFrame; + }*/ + + foreach (var soldier in squad) + { + PanicFrames.Add(CreatePanicFrame()); + + // TODO: Remove this test ;) + soldier.GetComponent().HP = random.Next(0, (int)soldier.GetComponent().MaxHP - 1); + + var uFrame = CreateUnitFrame(soldier); + uFrame["soldier"] = soldier; + soldier["unitFrame"] = uFrame; + uFrame.OnClick += delegate(Frame f, GUIManager.MouseEventArgs e) + { + var s = (Soldier)f["soldier"]; + KeyboardState ks = Keyboard.GetState(); + + if (!ks.IsKeyDown(Keys.LeftControl)) + { + // Deselect all units + foreach (var unitFrame in UnitFrames) + { + ((Soldier)unitFrame["soldier"]).Selected = false; + unitFrame.Color = Color.Black; + } + } + + if (!s.Selected) + { + s.Selected = true; + uFrame.Color = Color.Blue; + } + }; + UnitFrames.Add(uFrame); + } + } + + public void UpdateUnitFrames() + { + core.GroupManager.UpdateGroups(); + var groups = core.GroupManager.Groups; + + foreach (var panicFrame in PanicFrames) + panicFrame.Visible = false; + + GUI.Frame lastFrame = null; + for (int index = 0; index < groups.Count; index++) + { + var group = groups[index]; + + var pFrame = PanicFrames[index]; + var pFrameBarBg = (GUI.Frame)pFrame["panicBarBg"]; + var pFrameBar = (GUI.Frame)pFrame["panicBar"]; + pFrameBar.Width = (int)(pFrameBarBg.Width * (group.Panic / 100f)); + + pFrame.Visible = true; + if (lastFrame != null) + { + pFrame.X = lastFrame.X; + pFrame.Y = lastFrame.Y + lastFrame.Height + 15; + } + lastFrame = pFrame; + + foreach (Soldier soldier in @group.Entities) + { + var frame = (GUI.Frame) soldier["unitFrame"]; + frame.X = lastFrame.X; + frame.Y = lastFrame.Y + lastFrame.Height; + lastFrame = frame; + } + } + } + + public void Update(GameTime gameTime) + { + MouseState ms = Mouse.GetState(); + var mousePos = new Point(ms.X, ms.Y); + var mouseRectangle = new Rectangle(mousePos.X, mousePos.Y, 1, 1); + var mouseWorldPos = core.Camera.ScreenToWorld(new Vector2(mousePos.X, mousePos.Y)); + var mouseWorldRectangle = new Rectangle((int)mouseWorldPos.X, (int)mouseWorldPos.Y, 1, 1); + + // Update the selection rectangle + var selectionFrame = (GUI.Frame)UIParent["selectionFrame"]; + if (selectionFrame.Visible) + { + selectionFrame.Width = (int)mousePos.X - selectionFrame.X; + selectionFrame.Height = (int)mousePos.Y - selectionFrame.Y; + } + + UpdateUnitFrames(); + } } } diff --git a/DepthsBelow/DepthsBelow/KeyboardInput.cs b/DepthsBelow/DepthsBelow/KeyboardInput.cs index 02818da..68702af 100644 --- a/DepthsBelow/DepthsBelow/KeyboardInput.cs +++ b/DepthsBelow/DepthsBelow/KeyboardInput.cs @@ -1,5 +1,6 @@ using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using Microsoft.Xna.Framework; @@ -22,7 +23,85 @@ namespace DepthsBelow this.core = core; } - public void Update(GameTime gameTime) + /*public List> GetGroups(List soldiers) + { + var ungrouped = soldiers.ToList(); + var groups = new List>(); + + for (int index = 0; index < soldiers.Count; index++) + { + var checkingSoldier = soldiers[index]; + var group = new List(); + + for (int i = 0; i < soldiers.Count; i++) + { + var soldier = soldiers[i]; + if (ungrouped.Contains(soldier)) + { + float distance = Vector2.Distance(checkingSoldier.Transform.World + checkingSoldier.Transform.World.Origin, + soldier.Transform.World + soldier.Transform.World.Origin); + if (distance <= ((float) Grid.TileSize * 1f)) + { + ungrouped.Remove(soldier); + group.Add(soldier); + } + } + } + + if (group.Count > 0) + groups.Add(group); + } + + return groups; + }*/ + + // Get a list of dynamic soldier groups + public List> GetGroups(List soldiers, float maxRange) + { + var alreadyGrouped = new List(); + var groups = new List>(); + + for (int index = 0; index < soldiers.Count; index++) + { + var soldier = soldiers[index]; + + if (!alreadyGrouped.Contains(soldier)) + { + var group = new List(); + GetNearChain(ref group, soldiers, soldier, maxRange); + alreadyGrouped.AddRange(group); + + if (group.Count > 0) + groups.Add(group); + } + } + + return groups; + } + + // Get all soldiers who are close to each other in a chain + public void GetNearChain(ref List group, List soldiers, Soldier soldier, float maxRange) + { + for (int index = 0; index < soldiers.Count; index++) + { + var nearSoldier = soldiers[index]; + + if (!group.Contains(nearSoldier)) + { + float distance = Vector2.Distance(nearSoldier.Transform.World + nearSoldier.Transform.World.Origin, + soldier.Transform.World + soldier.Transform.World.Origin); + if (distance <= maxRange) + { + group.Add(nearSoldier); + GetNearChain(ref group, soldiers, nearSoldier, maxRange); + } + } + } + } + + private DynamicGroupManager groupManager; + + public void Update(GameTime gameTime) { KeyboardState ks = Keyboard.GetState(); @@ -30,6 +109,25 @@ namespace DepthsBelow if (ks.IsKeyUp(Keys.R) && lastKeyboardState.IsKeyDown(Keys.R)) { core.Lua.Reload(); + } + + // Debug test: grouping + if (ks.IsKeyUp(Keys.K) && lastKeyboardState.IsKeyDown(Keys.K)) + { + if (groupManager == null) + groupManager = new DynamicGroupManager(core.Squad.Cast().ToList(), Grid.TileSize * 1.5f); + groupManager.UpdateGroups(); + //var groups = GetGroups(core.Squad, (float)Grid.TileSize * 1.5f); + var groups = groupManager.Groups; + foreach (var group in groups) + { + Debug.WriteLine("Group #" + groups.IndexOf(group) + " Panic: " + group.Panic); + foreach (var entity in group.Entities) + { + var soldier = (Soldier)entity; + Debug.WriteLine(soldier.Name + " " + soldier.GetHashCode()); + } + } } // Next turn diff --git a/DepthsBelow/DepthsBelow/Map.cs b/DepthsBelow/DepthsBelow/Map.cs index e089083..9beaeea 100755 --- a/DepthsBelow/DepthsBelow/Map.cs +++ b/DepthsBelow/DepthsBelow/Map.cs @@ -73,6 +73,7 @@ namespace DepthsBelow if (mapObject.Type == "SquadStart") { var soldier = new Soldier(core.EntityManager, ref core.Squad); + soldier.Name = "Derp"; // HACK: For some reason, the tile object coordinates are offset by one tile on the Y-axis in the Tiled map file (https://github.com/bjorn/tiled/issues/91) var mapObjectPos = new Vector2(mapObject.Bounds.X, mapObject.Bounds.Y - Grid.TileSize); var mapObjectGridPos = Grid.WorldToGrid(mapObjectPos); diff --git a/DepthsBelow/DepthsBelow/MouseInput.cs b/DepthsBelow/DepthsBelow/MouseInput.cs index 553eab5..b41043e 100755 --- a/DepthsBelow/DepthsBelow/MouseInput.cs +++ b/DepthsBelow/DepthsBelow/MouseInput.cs @@ -25,6 +25,8 @@ namespace DepthsBelow bool checkingDirection = false; bool readjust = false; + + private Point pressLocation; public MouseInput(Core core) { @@ -43,9 +45,50 @@ namespace DepthsBelow gridTexture.SetData(new Color[] { Color.White }); } + public void OnPress(MouseState ms, GameTime gameTime) + { + pressLocation = new Point(ms.X, ms.Y); + var mouseEvent = new GUIManager.MouseEventArgs() + { + MouseState = ms, + Time = gameTime.TotalGameTime + }; + bool handledByUI = GUIManager.RaiseAt("OnPress", mouseEvent, pressLocation); + + if (handledByUI) + return; + + // If the mouse event is above a GUI frame that handles mouse events, do nothing + //if (GUIManager.FramesIntersectingPoint(mousePos).Any(frame => frame.MouseEnabled)) + // return; + } + + public void OnRelease(MouseState ms, GameTime gameTime) + { + var mouseEvent = new GUIManager.MouseEventArgs() + { + MouseState = ms, + Time = gameTime.TotalGameTime + }; + + bool handledByUI = GUIManager.RaiseAt("OnRelease", mouseEvent, pressLocation); + + if (handledByUI) + return; + } + public void OnClick(MouseState ms, GameTime gameTime) { - GUIManager.Click(new Point(ms.X, ms.Y), gameTime.TotalGameTime); + var mouseEvent = new GUIManager.MouseEventArgs() + { + MouseState = ms, + Time = gameTime.TotalGameTime + }; + + bool handledByUI = GUIManager.RaiseAt("OnClick", mouseEvent, new Point(ms.X, ms.Y)); + + if (handledByUI) + return; } public void Update(GameTime gameTime) @@ -54,14 +97,24 @@ namespace DepthsBelow Vector2 mouseWorldPos = core.Camera.ScreenToWorld(new Vector2(ms.X, ms.Y)); Rectangle mouseWorldRectangle = new Rectangle((int)mouseWorldPos.X, (int)mouseWorldPos.Y, 1, 1); KeyboardState ks = Keyboard.GetState(); + + // OnPress event + if ( + (ms.LeftButton == ButtonState.Pressed && lastMouseState.LeftButton == ButtonState.Released) + || (ms.RightButton == ButtonState.Pressed && lastMouseState.RightButton == ButtonState.Released) + ) + OnPress(ms, gameTime); - // OnClick event + // OnRelease event if ( (ms.LeftButton == ButtonState.Released && lastMouseState.LeftButton == ButtonState.Pressed) || (ms.RightButton == ButtonState.Released && lastMouseState.RightButton == ButtonState.Pressed) ) + { OnClick(ms, gameTime); - + OnRelease(ms, gameTime); + } + // Tooltip stuff var tooltip = core.Lua.GetObject("ToolTip"); @@ -87,7 +140,7 @@ namespace DepthsBelow if (core.TurnManager.CurrentTurn == core.TurnManager["Player"]) { // Selection rectangle - if (ms.LeftButton == ButtonState.Pressed) + /*if (ms.LeftButton == ButtonState.Pressed) { if (selectionRectangle == Rectangle.Empty) @@ -118,9 +171,10 @@ namespace DepthsBelow readjust = false; } } - } + }*/ + // When the mouse is released - if (ms.LeftButton == ButtonState.Released && selectionRectangle != Rectangle.Empty) + /*if (ms.LeftButton == ButtonState.Released && selectionRectangle != Rectangle.Empty) { // Deselect all units if (!ks.IsKeyDown(Keys.LeftControl)) @@ -138,7 +192,7 @@ namespace DepthsBelow // Hide the selection rectangle selectionRectangle = Rectangle.Empty; - } + }*/ // Send orders with right click if (ms.RightButton == ButtonState.Released && lastMouseState.RightButton == ButtonState.Pressed) diff --git a/DepthsBelow/DepthsBelow/Soldier.cs b/DepthsBelow/DepthsBelow/Soldier.cs index 21e9a96..d6e04e7 100644 --- a/DepthsBelow/DepthsBelow/Soldier.cs +++ b/DepthsBelow/DepthsBelow/Soldier.cs @@ -11,8 +11,8 @@ namespace DepthsBelow public class Soldier : Entity { public static Texture2D Texture; - public Color Color; - public static Point Origin; + public Color Color; + public string Name; private bool _selected; public List Squad; @@ -66,7 +66,8 @@ namespace DepthsBelow var stat = new Component.Stat(this) { - Life = 10, + MaxHP = 100, + //Life = 10, Defence = 10, Strength = 10, GetAim = 10, diff --git a/DepthsBelow/DepthsBelow/Utility.cs b/DepthsBelow/DepthsBelow/Utility.cs index 56cb736..7eb3952 100644 --- a/DepthsBelow/DepthsBelow/Utility.cs +++ b/DepthsBelow/DepthsBelow/Utility.cs @@ -63,5 +63,43 @@ namespace DepthsBelow angle, Vector2.Zero, new Vector2(length, width), SpriteEffects.None, 0); } + + private static Texture2D solidTexture = null; + /// + /// Creates a 1x1 solid white XNA texture. + /// + /// Returns a 1x1 solid white Texture2D object. + public static Texture2D GetSolidTexture() + { + if (solidTexture == null) + { + solidTexture = new Texture2D(GameServices.GetService(), 1, 1); + solidTexture.SetData(new Color[] { Color.White }); + } + + return solidTexture; + } + + /// + /// Converts rectangles with negative sizes to positive sizes with its position offset. + /// + /// The rectangle with potentially negative sizes. + /// Returns a rectangle with positive sizes. + public static Rectangle MakeRectanglePositive(Rectangle rect) + { + if (rect.Width < 0) + { + rect.X += rect.Width; + rect.Width = -rect.Width; + } + + if (rect.Height < 0) + { + rect.Y += rect.Height; + rect.Height = -rect.Height; + } + + return rect; + } } }