using FNAF_Server.Map; using GlobalClassLib; using PacketLib; namespace FNAF_Server.Enemies; public class NekoEnemy : Enemy { private MovementOpportunity movementOpportunity; private NekoPathfinder pathfinder; public NekoEnemy(int difficulty) : base(difficulty) { pathfinder = new NekoPathfinder(this, 1); } public override string Name{ get; } = "Neko"; public override int TypeId{ get; } = 1; public override bool BlocksTile{ get; set; } = true; public override void Spawn(MapTile location) { base.Spawn(location); // stopwatch.Start(); movementOpportunity = new MovementOpportunity(5000, ((2 + Math.Pow(1.5f, Difficulty)) * Math.Sign(Difficulty)) / (2 + Math.Pow(1.5f, 10))); movementOpportunity.Start(); pathfinder.takenPath.Add(Location); Server.SendUpdateToAll([GameEvent.ENEMY_SPAWN(TypeId, Id, Difficulty, Location.Id)]); } public override void Reset() { pathfinder.takenPath.Clear(); MapTile[] resetLocations = new[]{MapManager.Get(7), MapManager.Get(12), MapManager.Get(17)}.Where(t => EnemyManager.GetByLocation(t).Length == 0).ToArray(); Location = resetLocations[new Random().Next(resetLocations.Length)]; Server.SendUpdateToAll([GameEvent.ENEMY_RESET(Id, Location.Id)]); } public override void Update() { base.Update(); if (movementOpportunity.CheckAndRoll()){ Pathfinder.Decision decision = pathfinder.DecideNext(MapManager.Get(10)); switch (decision.type){ case Pathfinder.Decision.MoveType: Location = decision.nextTile!; Server.SendUpdateToAll([GameEvent.ENEMY_MOVEMENT(Id, Location.Id)]); Console.WriteLine($"Enemy {TypeId} ({Name}) moving to {Location.PositionAsString})"); break; case Pathfinder.Decision.AttackType: Attack(decision.attackTarget!); break; case Pathfinder.Decision.ResetType: Reset(); break; case Pathfinder.Decision.WaitType: break; } } } private class NekoPathfinder : RoamingPathfinder { private Random random = new(); private int tolerance; public List takenPath = new(); public NekoPathfinder(Enemy enemy, int tolerance) : base(enemy) { this.tolerance = tolerance; } public override Decision DecideNext(MapTile goal) { Dictionary distances = Crawl(goal); List closerTiles = GetNeighbours(Enemy.Location, c => !c.Blocked || c.Type == ConnectorType.DOOR_OFFICE, t => distances.ContainsKey(t) && distances[t] <= distances[Enemy.Location] + tolerance); if (closerTiles.Contains(goal)){ if (Enemy.Location.GetConnector(goal)!.Blocked){ return Decision.Reset(); } return Decision.Attack(Server.P1); // TODO: un-hardcode this } if (closerTiles.Count != 0 && closerTiles.All(t => takenPath.Contains(t))){ takenPath.Clear(); return DecideNext(goal); } closerTiles.RemoveAll(t => takenPath.Contains(t)); closerTiles.ForEach(t => distances[t] += Enemy.Location.GetConnector(t)!.Value); double roll = random.NextDouble() * closerTiles.Sum(t => 1.0 / distances[t]); foreach (var tile in closerTiles){ if (roll <= 1.0 / distances[tile]){ takenPath.Add(tile); return Decision.Move(tile); } roll -= 1.0 / distances[tile]; } return Decision.Wait(); } } }