/// <summary> /// Trains a unit /// </summary> /// <param name="uid">The UID to build</param> /// <returns>The built unit</returns> public Unit TrainUnit(string uid) { Unit trained = UnitType.CreateUnit(uid, Position + Vector2.Transform(new Vector2(0f, 4f * Type.SelectionCircleSize), Matrix.CreateRotationZ(MathHelper.ToRadians(League.Random.Next(0, 360)))), Owner); if (RallyPoint != null && trained.Type.Moves) { // We have a rally point, so we might as well use it trained.State = UnitState.Moving; Engine.CurrentMap.FindPath(trained, Engine.CurrentMap.GetNode(RallyPoint ?? Vector2.Zero)); } return(trained); }
/// <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); }