public void UpdateProjectiles(GameRoom room, GameRound round) { foreach (var tower in room.Towers.Values) { if (tower.Damage > 0 && tower.ReadyAt <= DateTime.UtcNow) { foreach (Mob mob in round.Mobs) { if (Point.IsNear(tower.Location, mob.CurrentLocation, tower.Range)) { round.Projectiles.Add(new Projectile(tower, mob)); tower.ReadyAt = DateTime.UtcNow.AddMilliseconds(tower.Speed); // TODO: Should have a Game constant scale modifier here break; } } } } foreach (Projectile projectile in round.Projectiles.Reverse()) { var span = DateTime.UtcNow.Subtract(projectile.LastUpdated); projectile.Location = Point.TrackTo(projectile.Location, projectile.Target.CurrentLocation, (span.Milliseconds * (projectile.Speed / Constants.GameSpeed))); if (Point.IsNear(projectile.Location, projectile.Target.CurrentLocation, 0.1)) // projectile.Location.X == projectile.Target.CurrentLocation) { TowerType towerType; switch (projectile.TowerType) { case Constants.TowerList.Slowing: towerType = GameDataUtils.GetTowerTypeFromTowerList(projectile.TowerType); projectile.Target.Status.slow = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(towerType.Effects.slow)); projectile.Target.Status.slow.remaining = DateTime.UtcNow.AddMilliseconds((int)towerType.Effects.slow.duration); break; case Constants.TowerList.Dot: towerType = GameDataUtils.GetTowerTypeFromTowerList(projectile.TowerType); projectile.Target.Status.dot = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(towerType.Effects.dot)); if (!((projectile.Target.Status.dot as IDictionary <string, object>)?.ContainsKey("lastUpdated") ?? false)) { projectile.Target.Status.dot.lastUpdated = DateTime.UtcNow; } projectile.Target.Status.dot.remaining = DateTime.UtcNow.AddMilliseconds((int)towerType.Effects.dot.duration); break; default: // No-op break; } var abilities = projectile.Target.Type.Abilities; var damage = projectile.Damage; // Stoneskin game mechanic if (abilities?.Stoneskin.HasValue == true) { damage -= abilities.Stoneskin.Value; } // Evasion game mechanic if (abilities?.Evasion.HasValue == true) { var roll = random.Value.Next(100); if (roll < abilities.Evasion.Value) { damage = 0; } } projectile.Target.Health -= Math.Max(damage, 0); round.Projectiles.Remove(projectile); if (projectile.Target.Health <= 0) { // Fracture game mechanic if (abilities?.Fracture != null) { for (var i = 0; i < abilities.Fracture.Count; i++) { round.Mobs.Add(new Mob() { Type = projectile.Target.Type.Abilities.Fracture.Shard, CurrentLocation = new Point(projectile.Target.CurrentLocation), EndingLocation = new Point(projectile.Target.EndingLocation), Health = projectile.Target.Type.Abilities.Fracture.Shard.StartingHealth, CurrentSpeed = projectile.Target.Type.Abilities.Fracture.Shard.MoveSpeed }); } } // Avenger game mechanic if (abilities?.Avenger != null) { var range = projectile.Target.Type.Abilities.Avenger.Range; foreach (var mob in round.Mobs.Reverse()) { if (Point.IsNear(projectile.Target.CurrentLocation, mob.CurrentLocation, range)) { mob.CurrentSpeed *= (1 + projectile.Target.Type.Abilities.Avenger.Bonus); } } } round.Mobs.Remove(projectile.Target); } } projectile.LastUpdated = DateTime.UtcNow; } }