/// <summary> /// Determines whether a UnitType can train another looking at one player's assets /// </summary> /// <param name="trainer">The training unit</param> /// <param name="uid">The unit being trained</param> /// <param name="p">The player at whose assets to look</param> /// <returns>Can it train it?</returns> public static bool CanTrainUnit(string trainer, string uid, Player p) { UnitType u = UnitType.GetUnitType(uid); return((UnitType.GetUnitType(trainer).Trains.Contains(uid) && p.MatchesAssets(u.Requirements)) && p.MatchesResources(u.Costs)); }
/// <summary> /// Finishes creating the UnitType. This must be done separately so we don't get stack overflows /// when we add references to other UnitTypes. /// </summary> public void FinishConstruction() { if (!String.IsNullOrEmpty(GameData.GetUnitData <string>(Uid, "trains"))) { if (League.Engine != null) { Actions.Add(ContextButton.Rally); } Trains = new List <string>(); UnitsTrained = GameData.GetUnitData <string>(Uid, "trains").Split(','); if (League.Engine != null) { foreach (string tuid in UnitsTrained) { Actions.Add(UnitType.GetUnitType(tuid)); Trains.Add(tuid); } } } if (!String.IsNullOrEmpty(GameData.GetUnitData <string>(Uid, "builds"))) { List <ContextButton> builds = new List <ContextButton>(); UnitsBuilt = GameData.GetUnitData <string>(Uid, "builds").Split(','); if (League.Engine != null) { foreach (string tuid in UnitsBuilt) { builds.Add(UnitType.GetUnitType(tuid)); } // This is a button which looks like a cancel but actually just resets our buttons builds.Add(new ContextButton(League.Engine.Content.Load <Texture2D>("icons/cancel"), "Cancel", "", null, 4, 2, ContextButton.ChangeActions)); ContextButton button = new ContextButton(League.Engine.Content.Load <Texture2D>("icons/cancel"), "Build Structure", "", null, 0, 2, ContextButton.ChangeActions); button.Tag = new List <ContextButton>(builds.ToArray()); Actions.Add(button); } } if (!String.IsNullOrEmpty(GameData.GetUnitData <string>(Uid, "abilities"))) { Abilities = GameData.GetUnitData <string>(Uid, "abilities").Split(','); if (League.Engine != null) { foreach (string aid in Abilities) { Actions.Add(Ability.GetAbility(aid)); } } } }
/// <summary> /// How to handle unit train clicks /// </summary> /// <param name="name">The button name</param> /// <param name="tag">The button tag - in our case the uid to train</param> private static void Train_Press(string name, object tag) { string uid = (string)tag; foreach (Unit selected in Player.CurrentPlayer.Selected) { if (UnitType.CanTrainUnit(selected.Type.Uid, uid, Player.CurrentPlayer) && selected.Training.Count < 10 && Player.CurrentPlayer.MatchesResources(UnitType.GetUnitType(uid).Costs)) { Player.CurrentPlayer.ChargeResources(UnitType.GetUnitType(uid).Costs); selected.Training.Enqueue(uid); if (selected.Training.Count == 1) { selected.TrainTime = GameData.GetUnitData <int>(uid, "buildTime"); } } } }
/// <summary> /// How to handle building creations /// </summary> /// <param name="name">The button name</param> /// <param name="tag">The button tag - in our case the uid to build</param> private static void Build_Press(string name, object tag) { string uid = (string)tag; if (Player.CurrentPlayer.MatchesResources(UnitType.GetUnitType(uid).Costs)) { Unit selected = Player.CurrentPlayer.Selected[0]; League.Engine.OnClick = new OnClickHandler(delegate(Point screen, Vector3 world, object ctag) { if (screen.Y > 487) { return(false); } selected.OrderBuild(uid, new Vector2(world.X, world.Z)); return(true); }); } League.Engine.Interface.Actions = null; }
/// <summary> /// Draws the GUI /// </summary> /// <param name="gameTime">A snapshot of timing values</param> public override void Draw(GameTime gameTime) { Batch.Begin(SpriteBlendMode.AlphaBlend); MouseState ms = Mouse.GetState(); float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; DrawString(SmallFont, String.Format("|cffcc00FPS:|r {0}", 1 / elapsed), new Vector2(33)); DrawString(SmallFont, String.Format("|cffcc00GC:|r {0}", GCR), new Vector2(33, 45)); // Draw all the lines in our collection. // Why doesn't XNA do this? for (int l = 0; l < Lines.Count; l++) { int i = 0; for (; i < (int)Math.Floor(Lines[l].Length / 100f); i++) { Batch.Draw(Line, Lines[l].Start + Vector2.Transform(new Vector2(i * 100f, 0f), Matrix.CreateRotationZ(Lines[l].Slope)), null, Lines[l].Color, Lines[l].Slope, new Vector2(1, 1), new Vector2(1f, 1f), SpriteEffects.None, 1f); } Batch.Draw(Line, Lines[l].Start + Vector2.Transform(new Vector2(i * 100f, 0f), Matrix.CreateRotationZ(Lines[l].Slope)), null, Lines[l].Color, Lines[l].Slope, new Vector2(1, 1), new Vector2((Lines[l].Length % 100f) / 100f, 1f), SpriteEffects.None, 1f); } // Lines are only valid for one draw Lines.Clear(); //There is a tooltip to be drawn if (TooltipButton != null && TooltipButton.TypeOf == typeof(UnitType) && Tooltip != null) { // Is this a unit and are it's requisists met? Tooltip.Draw(Player.CurrentPlayer.MatchesAssets(UnitType.GetUnitType((string)TooltipButton.Tag).Requirements)); } else if (Tooltip != null) { Tooltip.Draw(true); } Batch.Draw(Interface, new Rectangle(0, 450, 800, 150), Color.White); if (Player.CurrentPlayer.Selected.Count == 1) { if (Player.CurrentPlayer.Selected[0].Training.Count == 0) { // There is only one unit selected so we can draw its stats DrawCenteredString(SmallFont, Player.CurrentPlayer.Selected[0].Type.Name, new Vector2(355, 516)); DrawCenteredString(SmallFont, String.Format("|c66ff66{0}/{1}", Player.CurrentPlayer.Selected[0].Hp, Player.CurrentPlayer.Selected[0].Type.Hp), new Vector2(221, 567)); DrawCenteredString(SmallFont, String.Format("|c66ccff{0}/{1}", Player.CurrentPlayer.Selected[0].Energy, Player.CurrentPlayer.Selected[0].Type.Energy), new Vector2(221, 583)); } else { // The unit is training UnitType type = UnitType.GetUnitType(Player.CurrentPlayer.Selected[0].Training.Peek()); DrawString(SmallFont, String.Format("Training |cffcc00{0}|r...", type.Name), new Vector2(245, 504)); float progress = (type.BuildTime - Player.CurrentPlayer.Selected[0].TrainTime) / type.BuildTime * 148f; Batch.Draw(ProgressBar, new Vector2(245, 520), Color.LightBlue); Batch.Draw(ProgressFill, new Rectangle(246, 521, (int)progress, 10), Color.Green); int x = 0; int y = 0; foreach (string training in Player.CurrentPlayer.Selected[0].Training) { UnitType nowtype = UnitType.GetUnitType(training); Batch.Draw(nowtype.Button.Texture, new Rectangle(173 + x * 36, 504 + y * 36, 36, 36), Color.White); if (y == 0) { y++; } else { x++; } if (x == 9) { x = 0; y++; } } } } else { // There are multiple units selected int x = 0; int y = 0; foreach (Unit selected in Player.CurrentPlayer.Selected) { Batch.Draw(selected.Type.Button.Texture, new Rectangle(173 + x * 36, 481 + y * 36, 36, 36), Color.White); x++; if (x == 9) { x = 0; y++; } } } // Draw the action panel if (Actions != null) { foreach (ContextButton button in Actions) { if ((button.TypeOf == typeof(UnitType) && Player.CurrentPlayer.MatchesAssets(UnitType.GetUnitType((string)button.Tag).Requirements)) || button.TypeOf != typeof(UnitType)) { // The requisits are met Batch.Draw(Template, button.Rectangle, Color.White); Batch.Draw(button.Texture, button.Rectangle, Color.White); if (button.TypeOf == typeof(Ability)) { Ability a = Ability.GetAbility(button.Tag.ToString()); if (Player.CurrentPlayer.Selected[0].AbilityCooldown.ContainsKey(a.Aid) && a.IconX != -1 && a.IconY != -1) { // There is a cooldown to be drawn Batch.Draw(CooldownTextures[a.IconX, a.IconY], button.Rectangle, Color.White); } } } else { // Dependencies are not met Batch.Draw(Template, button.Rectangle, Color.Gray); Batch.Draw(button.Texture, button.Rectangle, Color.Gray); } } } Batch.Draw(Engine.CurrentMap.Minimap, new Rectangle(5, 467, 128, 128), Color.White); // Draw minimap blits for units lock (Engine.Units) { foreach (Unit u in Engine.Units) { Visibility v = Player.CurrentPlayer.GetPointVisibility(u.Position); // Buildings are seen through fog - units are not if (((v & Visibility.Fogged) == Visibility.Fogged && u.Type.IsBuilding) || (!u.Type.IsBuilding && v == Visibility.Visible)) { int x = 69 + (int)(u.Position.X / Engine.CurrentMap.Width * 128); int y = 531 + (int)(u.Position.Y / Engine.CurrentMap.Height * 128); Batch.Draw(MapBlit, new Vector2(x, y), u.Owner.TeamColor); } } } // Draw all of the resources foreach (KeyValuePair <string, Resource> pair in Resource.Resources) { Resource res = pair.Value; DrawString(SmallFont, Player.CurrentPlayer.Resources[pair.Key].ToString(), res.DrawPoint + new Vector2(20, 0)); Batch.Draw(res.Icon, res.DrawPoint, Color.White); } // Draw game messages float start = 400 - (Messages.Count * 14); foreach (GameMessage message in Messages) { // This needs to be changed when we get multiple players // going. Vector2 pos = new Vector2(50, start); start += MeasureString(BigFont, message.Message).Y; DrawString(BigFont, message.Message, pos); } // Put on the cursor Batch.Draw(Cursor, new Vector2(ms.X, ms.Y), Color.White); Batch.End(); }
/// <summary> /// Updates the Gui /// </summary> /// <param name="gameTime">A snapshot of timing values</param> public override void Update(GameTime gameTime) { if (Player.CurrentPlayer.Selected.Count > 0) { // Create a cooldown texture for all abilities which need it foreach (KeyValuePair <string, float> pair in Player.CurrentPlayer.Selected[0].AbilityCooldown) { Ability a = Ability.GetAbility(pair.Key); if (a.IconX != -1 && a.IconY != -1) { float complete = (a.Cooldown - pair.Value) / a.Cooldown; GetCooldownCircle(CooldownTextures[a.IconX, a.IconY], complete); } } } // Increase GC count if (!GCD.IsAlive) { GCR++; GCD = new WeakReference(new object()); } float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; MouseState ms = Mouse.GetState(); Rectangle mouse = new Rectangle(ms.X, ms.Y, 1, 1); Cooldown -= elapsed; KeyboardState ks = Keyboard.GetState(); // There are multiple units selected - show the selection UI if (Player.CurrentPlayer.Selected.Count > 1) { TooltipButton = ContextButton.Null; int x = 0; int y = 0; for (int i = 0; i < Player.CurrentPlayer.Selected.Count; i++) { Unit selected = Player.CurrentPlayer.Selected[i]; // We don't have rectangles for these, so we need to do the math ourselves if (ms.X >= 173 + x * 36 && ms.Y >= 481 + y * 36 && ms.X < 209 + x * 36 && ms.Y < 520 + y * 36) { if (ms.LeftButton == ButtonState.Pressed && Cooldown <= 0f) { if (ks.IsKeyDown(Keys.LeftControl)) { Player.CurrentPlayer.Selected.Remove(selected); } else { if ((Engine.OnSelect != null && Engine.OnSelect.Invoke(selected, Engine.OnSelectTag)) || Engine.OnSelect == null) { Player.CurrentPlayer.Selected.Clear(); Player.CurrentPlayer.Selected.Add(selected); } } Cooldown = 0.2f; return; } // Create a non-ContextButton tooltip TooltipButton = null; Tooltip = new Tooltip(selected.Type.Name, " ", null); return; } x++; if (x == 9) { x = 0; y++; } } } // There is one unit selected and it is training else if (Player.CurrentPlayer.Selected.Count == 1 && Player.CurrentPlayer.Selected[0].Training.Count != 0) { TooltipButton = ContextButton.Null; int x = 0; int y = 0; for (int i = 0; i < Player.CurrentPlayer.Selected[0].Training.Count; i++) { if (ms.X >= 173 + x * 36 && ms.Y >= 504 + y * 36 && ms.X < 209 + x * 36 && ms.Y < 540 + y * 36) { if (ms.LeftButton == ButtonState.Pressed && Cooldown <= 0f) { // Cancel training units Player.CurrentPlayer.Selected[0].Training.DequeueAt(i); if (i == 0 && Player.CurrentPlayer.Selected[0].Training.Count > 0) { Player.CurrentPlayer.Selected[0].TrainTime = GameData.GetUnitData <int>(Player.CurrentPlayer.Selected[0].Training.Peek(), "buildTime"); } Cooldown = 0.2f; return; } TooltipButton = null; Tooltip = new Tooltip(UnitType.GetUnitType(Player.CurrentPlayer.Selected[0].Training.Peek(i)).Name, " ", null); return; } if (y == 0) { y++; } else { x++; } if (x == 9) { x = 0; y++; } } } // There is a tooltip to be shown if (TooltipButton != null) { Tooltip = null; TooltipButton = ContextButton.Null; if (Actions != null) { foreach (ContextButton button in Actions) { if ((ks.IsKeyDown(button.Hotkey) || (button.Rectangle.Intersects(mouse) && ms.LeftButton == ButtonState.Pressed)) && Cooldown <= 0f) { button.Action.Invoke(button.Name, button.Tag); //Player.CurrentPlayer.RefreshActions(); Cooldown = 0.2f; } if (button.Rectangle.Intersects(mouse)) { TooltipButton = button; Tooltip = button.Tooltip; } } } } // Coerce messages count if (Messages.Count > 6) { for (int i = 0; i < Messages.Count - 6; i++) { Messages.RemoveAt(0); } } base.Update(gameTime); }
/// <summary> /// Updates the component - calculates pathing maps, moves, attacks, casts abilities, /// builds, trains /// </summary> /// <param name="gameTime">A snapshot of game timing values</param> public override void Update(GameTime gameTime) { float elapsed = (float)gameTime.ElapsedGameTime.TotalSeconds; // Update times for everything AttackCooldown -= elapsed; if (CastTime != null) { CastTime -= elapsed; } string[] keys = new string[AbilityCooldown.Count]; AbilityCooldown.Keys.CopyTo(keys, 0); for (int i = 0; i < AbilityCooldown.Count; i++) { AbilityCooldown[keys[i]] -= elapsed; if (AbilityCooldown[keys[i]] < 0) { AbilityCooldown.Remove(keys[i]); } } if (TargetUnit != null && !Engine.Units.Contains(TargetUnit)) { // The target unit is dead TargetUnit = null; CastingAbility = null; Path = null; State &= ~(UnitState.Attacking | UnitState.Moving | UnitState.Casting); } if (CurrentlyBuilding) { // This is an incomplete building and should continue to build BuildTime += elapsed; Hp = (int)(BuildTime / Type.BuildTime * Type.Hp); if (BuildTime >= Type.BuildTime) { // The building is complete! CurrentlyBuilding = false; Builder.State &= ~UnitState.Building; Builder = null; if (Player.CurrentPlayer.Selected.Count != 0 && Player.CurrentPlayer.Selected[0] == this) { // If this is the selected object, refresh actions Player.CurrentPlayer.RefreshActions(); } } } if (Hp <= 0 && !(CurrentlyBuilding && BuildTime < 0.5f)) { // Time to DIE Die(); return; } if (QueuedBuilding != null) { // There is a building to build if ((Position - TargetPoint).Length() <= 8 * UnitType.GetUnitType(QueuedBuilding).SelectionCircleSize) { // We are in range to build it if (Owner.MatchesResources(UnitType.GetUnitType(QueuedBuilding).Costs)) { // We are able to build it Path = null; Unit u = UnitType.CreateUnit(QueuedBuilding, TargetPoint, Owner); u.CurrentlyBuilding = true; u.Builder = this; Owner.ChargeResources(u.Type.Costs); QueuedBuilding = null; State = UnitState.Building; } else { // We can't build it :( QueuedBuilding = null; Path = null; } } else if (Path == null) { // We are not moving but need to get to the build site State = UnitState.Moving; Engine.CurrentMap.FindPath(this, Engine.CurrentMap.GetNode(TargetPoint)); } } if (CastingAbility == null) { if (TargetUnit != null && TargetUnit != this) { // We have a target and it's not us and it's not an Ability target if ((TargetUnit.Position - Position).Length() < Type.AttackRange) { // We're in range! if (AttackCooldown <= 0) { DoAttack(); } } else if (Path == null) { // We're not in range so let's get there Engine.CurrentMap.FindPath(this, Engine.CurrentMap.GetNode(TargetUnit.Position)); State = UnitState.Moving; } } } else { // There's magic to be done if (!(TargetUnit == null && TargetPoint == Vector2.Zero) && Path == null && CastTime == null) { // And we need to get there Engine.CurrentMap.FindPath(this, Engine.CurrentMap.GetNode((CastingAbility.Target.Type == TargetType.Point ? TargetPoint : TargetUnit.Position))); State = UnitState.Moving; } if ((TargetUnit == null && TargetPoint == Vector2.Zero) || (Position - (CastingAbility.Target.Type == TargetType.Point ? TargetPoint : TargetUnit.Position)).Length() <= CastingAbility.CastRange) { // We're in range or there's no Target if (CastTime == null) { // And we haven't started casting CastTime = CastingAbility.CastTime; // Start casting ;) Path = null; } else if (CastTime <= 0) { if (TargetUnit == null && TargetPoint == Vector2.Zero) { // Cast against nothing CastingAbility.Invoke(this, null); } else { // Cast against our target CastingAbility.Invoke(this, (CastingAbility.Target.Type == TargetType.Point ? (object)TargetPoint : (object)TargetUnit)); } } } } if (Path != null) { lock (Path) { // Move along our path Vector3 pos = Engine.CurrentMap.GetNodePosition(Path[Node].X, Path[Node].Y); Vector2 intended = new Vector2(pos.X, pos.Z); Vector2 dif = intended - Position; float angle = (float)(Math.Atan2(-dif.Y, dif.X) + MathHelper.PiOver2); if (Rotation.Yaw < angle) { Rotation.Yaw += elapsed * Type.TurnSpeed; } else if (Rotation.Yaw > angle) { Rotation.Yaw -= elapsed * Type.TurnSpeed; } float xdir = Math.Sign(dif.X); float ydir = Math.Sign(dif.Y); Position.X += Type.Speed * elapsed * xdir; Position.Y += Type.Speed * elapsed * ydir; if (TargetUnit != null) { if ((TargetUnit.Position - Position).Length() < Type.AttackRange) { // Our target is in range! Path = null; State = UnitState.Attacking; return; } } if (Math.Abs(dif.X) < Type.Speed * elapsed * 2 && Math.Abs(dif.Y) < Type.Speed * elapsed * 2) { // We're through this node - on to the next! Node++; if (Node == Path.Count && State != UnitState.Patrolling) { Path = null; } else if (State == UnitState.Patrolling) { // If we're patrolling, repeat the path over and over List <Point> newpath = new List <Point>(); for (int i = 1; i <= Path.Count; i++) { newpath.Add(Path[Path.Count - i]); } Path = newpath; Node = 0; } } } } else if (Type.Attacks && TargetUnit == null && !CurrentlyBuilding) { foreach (Unit u in Engine.Units) { if (u.Owner != Owner && (Position - u.Position).Length() <= Type.AttackEngage && u.Owner != Player.NeutralPlayer) { // We can attack and there are enemy units nearby TargetUnit = u; } } } if (Training.Count != 0) { // We're training units TrainTime -= elapsed; if (TrainTime <= 0f) { // Training is complete, let's pop one out TrainUnit(Training.Dequeue()); if (Training.Count != 0) { TrainTime = UnitType.GetUnitType(Training.Peek()).BuildTime; } } } base.Update(gameTime); }
/// <summary> /// Parses a target string into a Target /// </summary> /// <param name="data">The target string. Should be a comma deliminated string of valid targets.</param> public Target(string data) { string[] parsed = data.Split(','); List <string> specifics = new List <string>(); foreach (string target in parsed) { switch (target) { case "terrain": Type = TargetType.Point; break; case "point": Type = TargetType.Point; break; /*case "doodad": * Type = TargetType.Doodad; * break;*/ case "self": Type = TargetType.None; Team = Team.Self; break; case "owner": Type = TargetType.Unit; Team = Team.Owner; break; case "allies": Type = TargetType.Unit; Team = Team.Allies; break; case "friendly": Type = TargetType.Unit; Team = Team.Friendly; break; case "enemy": Type = TargetType.Unit; Team = Team.Enemy; break; case "neutral": Type = TargetType.Unit; Team = Team.Neutral; break; case "structure": ClassificData.Add(Classification.Structure, ClassificationState.Yes); break; case "unit": ClassificData.Add(Classification.Structure, ClassificationState.No); break; case "ground": ClassificData.Add(Classification.Air, ClassificationState.No); break; case "air": ClassificData.Add(Classification.Air, ClassificationState.Yes); break; case "organic": ClassificData.Add(Classification.Biological, ClassificationState.Yes); break; case "biological": ClassificData.Add(Classification.Biological, ClassificationState.Yes); break; case "mechanical": ClassificData.Add(Classification.Biological, ClassificationState.No); break; case "corporeal": ClassificData.Add(Classification.Corporeal, ClassificationState.Yes); break; case "ethereal": ClassificData.Add(Classification.Corporeal, ClassificationState.No); break; case "worker": ClassificData.Add(Classification.Worker, ClassificationState.Yes); break; case "!worker": ClassificData.Add(Classification.Worker, ClassificationState.No); break; case "townhall": ClassificData.Add(Classification.TownHall, ClassificationState.Yes); break; case "!townhall": ClassificData.Add(Classification.TownHall, ClassificationState.No); break; case "melee": ClassificData.Add(Classification.Melee, ClassificationState.Yes); break; case "ranged": ClassificData.Add(Classification.Melee, ClassificationState.No); break; case "attacks": ClassificData.Add(Classification.Attacks, ClassificationState.Yes); break; case "!attacks": ClassificData.Add(Classification.Attacks, ClassificationState.No); break; case "attacksground": ClassificData.Add(Classification.AttacksGround, ClassificationState.Yes); break; case "!attacksground": ClassificData.Add(Classification.AttacksGround, ClassificationState.No); break; case "attacksair": ClassificData.Add(Classification.AttacksAir, ClassificationState.Yes); break; case "!attacksair": ClassificData.Add(Classification.AttacksAir, ClassificationState.No); break; case "summoned": ClassificData.Add(Classification.Summoned, ClassificationState.Yes); break; case "!summoned": ClassificData.Add(Classification.Summoned, ClassificationState.No); break; case "resource": ClassificData.Add(Classification.Resource, ClassificationState.Yes); break; default: // Get a specific uid if (target != "") { try { UnitType.GetUnitType(target); specifics.Add(target); Type = TargetType.Specific; } catch { //throw new Exception("Unknown target `" + target + "`"); } } break; } } if (specifics.Count > 0) { Specific = String.Join(",", specifics.ToArray()); } }
/// <summary> /// Creates a new tooltip /// </summary> /// <param name="name">The title of the tooltip</param> /// <param name="desc">The text of the tooltip</param> /// <param name="reqs">The assets required for the described ContextButton</param> /// <param name="icons">The IconNumbers to slot</param> public Tooltip(string name, string desc, List <string> reqs, params IconNumber[] icons) { Name = name; Description = desc; if (reqs != null) { foreach (string req in reqs) { Requirements.Add(UnitType.GetUnitType(req).Name); } } if (icons.Length > 0) { Icon1 = icons[0]; } else { Icon1 = null; } if (icons.Length > 1) { Icon2 = icons[1]; } else { Icon2 = null; } if (icons.Length > 2) { Icon3 = icons[2]; } else { Icon3 = null; } if (icons.Length > 3) { Icon4 = icons[3]; } else { Icon4 = null; } if (icons.Length > 4) { Icon5 = icons[4]; } else { Icon5 = null; } if (icons.Length > 5) { Icon6 = icons[5]; } else { Icon6 = null; } }