public void RemoveMobsAtEnding(Mob mob, GameRoom room, GameRound round) { if (Point.IsNear(mob.CurrentLocation, mob.EndingLocation, 0.5)) { foreach (var player in room.Players) { if (mob.EndingLocation.X == player.EndingLocation.X && mob.EndingLocation.Y == player.EndingLocation.Y) { player.CurrentLife -= 1; round.Mobs.Remove(mob); this.scaleoutService.Store(Persist.GameRoom, room.Id, room); break; } } } }
public void UpdateMobLocation(Mob mob, GameRoom room, GameRound round) { var span = DateTime.UtcNow.Subtract(mob.LastUpdated); if (span.Milliseconds > 0) { var next = mob.Path?.FirstOrDefault(); if(next != null && Point.IsNear(mob.CurrentLocation, next, 0.1)) { mob.Path = mob.Path.Skip(1); next = mob.Path?.FirstOrDefault(); } if(mob.Path == null || next == null) { next = this.CalculateMobPath(mob, room); } if (next != null) { var moveSpeed = mob.CurrentSpeed; // Adjust mob speed for slow effects if ((mob.Status as IDictionary<string, object>)?.ContainsKey("slow") ?? false) { moveSpeed = (int)(moveSpeed * (double)mob.Status.slow.speed); if (DateTime.UtcNow > (DateTime)mob.Status.slow.remaining) { var status = (IDictionary<string, object>)mob.Status; status.Remove("slow"); } } mob.CurrentLocation = Point.TrackTo(mob.CurrentLocation, next, (span.TotalMilliseconds * moveSpeed / Constants.GameSpeed)); // mob.Path = path?.ToList(); // Debugging } else { Console.WriteLine("No valid path found for mob"); mob.CurrentLocation = new Point(mob.EndingLocation.X, mob.EndingLocation.Y); } mob.LastUpdated = DateTime.UtcNow; } }
public void TickDots(Mob mob, GameRoom room, GameRound round) { if((mob.Status as IDictionary<string, object>)?.ContainsKey("dot") ?? false) { var span = DateTime.UtcNow.Subtract((DateTime)mob.Status.dot.lastUpdated); if(span.TotalMilliseconds > 500) { mob.Health -= (int)mob.Status.dot.damage; mob.Status.dot.lastUpdated = DateTime.UtcNow; if(DateTime.UtcNow > (DateTime)mob.Status.dot.remaining) { (mob.Status as IDictionary<string, object>).Remove("dot"); } if(mob.Health <= 0) { round.Mobs.Remove(mob); } } } }
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; } }
public async Task<bool> ProcessRoundAsync(string roomId) { var currentRound = this.scaleoutService.Get(Persist.GameRound, roomId) as GameRound; if(currentRound == null) { currentRound = new GameRound() { Mobs = new List<Mob>(), Projectiles = new List<Projectile>(), RemainingMobs = Constants.RoundSize }; this.scaleoutService.Store(Persist.GameRound, roomId, currentRound); // Move new Thread(async () => { int frame = 0; GameRoom room = this.scaleoutService.Get(Persist.GameRoom, roomId) as GameRoom; var startTime = DateTime.UtcNow; var endTime = startTime.AddMinutes(60); var round = this.scaleoutService.Get(Persist.GameRound, roomId) as GameRound; if (round != null) { while (round.Mobs.Count > 0 || round.RemainingMobs > 0) { if (DateTime.UtcNow > endTime) { return; // Prevents runaway threads } lock (room) { round = this.scaleoutService.Get(Persist.GameRound, roomId) as GameRound; foreach (var mob in round.Mobs.Reverse()) // Iterate every mob { this.mobMovementService.RemoveMobsAtEnding(mob, room, round); this.mobMovementService.UpdateMobLocation(mob, room, round); this.towerProjectileService.TickDots(mob, room, round); } // Iterates towers and projectile count this.towerProjectileService.UpdateProjectiles(room, round); this.scaleoutService.Store(Persist.GameRound, roomId, round); } frame++; if(frame > 30) { frame = 0; // frame zero occurs roughly once a second, used for updates that don't need to occur as often } Thread.Sleep(30); // Makes ~30 FPS } lock (room) { round.Projectiles.Clear(); this.scaleoutService.Store(Persist.GameRound, roomId, round); } await this.scaleoutService.Remove(Persist.GameRound, roomId); } else { Debug.WriteLine("Mob spawning thread ended early."); return; } }).Start(); // Spawn mobs new Thread(() => { var totalMobs = currentRound.RemainingMobs; // snapshot of total count for(int i = 0; i < totalMobs; i++) { GameRoom room = this.scaleoutService.Get(Persist.GameRoom, roomId) as GameRoom; lock(room) { var round = this.scaleoutService.Get(Persist.GameRound, roomId) as GameRound; for(var j = 0; j < room.Players.Count; j++) { round.Mobs.Add(new Mob() { Health = Constants.MobTypes[0].StartingHealth, Type = Constants.MobTypes[0], CurrentLocation = room.Players[j].StartingLocation, EndingLocation = room.Players[j].EndingLocation, CurrentSpeed = Constants.MobTypes[0].MoveSpeed }); } round.RemainingMobs--; } Thread.Sleep(Constants.RoundPauseMs); } }).Start(); return true; } return false; }