From 9bfe63a166d3381e594a3d0b17708d9da610971c Mon Sep 17 00:00:00 2001 From: Perry Date: Sun, 8 Mar 2026 16:55:49 +0100 Subject: [PATCH] =?UTF-8?q?Prvn=C3=AD=203=20monstra=20z=20pl=C3=A1novan?= =?UTF-8?q?=C3=BDch=20p=C4=9Bti.=20Kompletn=C3=AD=20pathfinding=20i=20zrca?= =?UTF-8?q?dlen=C3=AD=20do=20clienta.=20=C3=9Atoky=20implementovan=C3=A9?= =?UTF-8?q?=20nejsou.=20Lurk=20a=20Neko=20jsou=20hardcoded=20aby=20=C3=BAt?= =?UTF-8?q?o=C4=8Dili=20na=20P1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FNAF_Clone.sln.DotSettings.user | 4 + FNAF_Clone/ClientEnemy.cs | 21 ++++ FNAF_Clone/ClientEnemyManager.cs | 53 ++++++++ FNAF_Clone/CommandManager.cs | 2 + FNAF_Clone/Content/Content.mgcb | 15 +++ .../Content/images/SpriteSheet_enemies.png | Bin 0 -> 15263 bytes .../Content/images/enemies-definition.xml | 15 +++ FNAF_Clone/EventProcessor.cs | 27 +++- FNAF_Clone/GUI/Screen.cs | 3 +- FNAF_Clone/GUI/UIElement.cs | 4 +- FNAF_Clone/GUI/UIManager.cs | 29 ++++- FNAF_Server/CommandProcessor.cs | 5 +- FNAF_Server/Enemies/Enemy.cs | 26 ++++ FNAF_Server/Enemies/EnemyManager.cs | 29 +++++ FNAF_Server/Enemies/LurkEnemy.cs | 118 ++++++++++++++++++ FNAF_Server/Enemies/MovementOpportunity.cs | 60 +++++++++ FNAF_Server/Enemies/NekoEnemy.cs | 106 ++++++++++++++++ FNAF_Server/Enemies/Pathfinder.cs | 36 ++++++ FNAF_Server/Enemies/RoamingPathfinder.cs | 29 +++++ FNAF_Server/Enemies/SpotEnemy.cs | 95 ++++++++++++++ FNAF_Server/GameLogic.cs | 25 +++- FNAF_Server/Server.cs | 35 ++---- GlobalClassLib/EnemyType.cs | 11 ++ GlobalClassLib/GlobalEnemy.cs | 35 ++++++ GlobalClassLib/GlobalMapTile.cs | 15 ++- PacketLib/GameEvent.cs | 12 +- PacketLib/PlayerState.cs | 9 +- 27 files changed, 772 insertions(+), 47 deletions(-) create mode 100644 FNAF_Clone/ClientEnemy.cs create mode 100644 FNAF_Clone/ClientEnemyManager.cs create mode 100644 FNAF_Clone/Content/images/SpriteSheet_enemies.png create mode 100755 FNAF_Clone/Content/images/enemies-definition.xml create mode 100644 FNAF_Server/Enemies/Enemy.cs create mode 100644 FNAF_Server/Enemies/EnemyManager.cs create mode 100644 FNAF_Server/Enemies/LurkEnemy.cs create mode 100644 FNAF_Server/Enemies/MovementOpportunity.cs create mode 100644 FNAF_Server/Enemies/NekoEnemy.cs create mode 100644 FNAF_Server/Enemies/Pathfinder.cs create mode 100644 FNAF_Server/Enemies/RoamingPathfinder.cs create mode 100644 FNAF_Server/Enemies/SpotEnemy.cs create mode 100644 GlobalClassLib/EnemyType.cs create mode 100644 GlobalClassLib/GlobalEnemy.cs diff --git a/FNAF_Clone.sln.DotSettings.user b/FNAF_Clone.sln.DotSettings.user index f8595f6..f51aa32 100644 --- a/FNAF_Clone.sln.DotSettings.user +++ b/FNAF_Clone.sln.DotSettings.user @@ -1,11 +1,15 @@  True + ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded \ No newline at end of file diff --git a/FNAF_Clone/ClientEnemy.cs b/FNAF_Clone/ClientEnemy.cs new file mode 100644 index 0000000..7a2b982 --- /dev/null +++ b/FNAF_Clone/ClientEnemy.cs @@ -0,0 +1,21 @@ +using FNAF_Clone.GUI; +using FNAF_Clone.Map; +using GlobalClassLib; +using MonoGameLibrary.Graphics; + +namespace FNAF_Clone; + +public class ClientEnemy : GlobalEnemy { + public ClientEnemy(int typeId, string name, int id, UIElement sprite, int difficulty, MapTileProjection location) : base(difficulty, id) { + Name = name; + TypeId = typeId; + Sprite = sprite; + Location = location; + } + + // public TextureRegion Sprite { get; set; } + + public UIElement Sprite { get; set; } + public override string Name{ get; } + public override int TypeId{ get; } +} \ No newline at end of file diff --git a/FNAF_Clone/ClientEnemyManager.cs b/FNAF_Clone/ClientEnemyManager.cs new file mode 100644 index 0000000..bf7ebbd --- /dev/null +++ b/FNAF_Clone/ClientEnemyManager.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Data; +using System.Linq; +using FNAF_Clone.GUI; +using FNAF_Clone.Map; +using GlobalClassLib; +using Microsoft.Xna.Framework; + +namespace FNAF_Clone; + +public class ClientEnemyManager { + private static Dictionary enemies = new(); + private static Point cameraCorner = new Point(64, 64); + + public static void AddEnemy(ClientEnemy enemy) { + enemies.Add(enemy.Id, enemy); + UIManager.AddEnemySprite(enemy.Id, enemy.Sprite); + } + + public static void AddEnemyByTemplate(EnemyType type, int id, int difficulty, MapTileProjection location) { + switch (type){ + case EnemyType.LURK: + AddEnemy(new ClientEnemy((int)type, "Lurk", id, new UIElement(UIManager.enemyAtlas[0], cameraCorner), difficulty, location)); + break; + case EnemyType.NEKO: + AddEnemy(new ClientEnemy((int)type, "Neko", id, new UIElement(UIManager.enemyAtlas[1], cameraCorner), difficulty, location)); + break; + case EnemyType.SPOT: + UIElement element = + new UIElement([UIManager.enemyAtlas[2], UIManager.enemyAtlas[3], UIManager.enemyAtlas[4], UIManager.enemyAtlas[5]], cameraCorner); + element.SetTexture(2); + AddEnemy(new ClientEnemy((int)type, "Spot", id, element, difficulty, location)); + break; + } + } + + public static void Move(int id, MapTileProjection tile) { + enemies[id].Location = tile; + } + + public static ClientEnemy Get(int id) => enemies[id]; + + public static ClientEnemy[] GetByLocation(MapTileProjection tile) { + List output = new(); + foreach (var e in enemies.Values){ + if (e.Location == tile){ + output.Add(e); + } + } + + return output.ToArray(); + } +} \ No newline at end of file diff --git a/FNAF_Clone/CommandManager.cs b/FNAF_Clone/CommandManager.cs index 5f2999f..dd48958 100644 --- a/FNAF_Clone/CommandManager.cs +++ b/FNAF_Clone/CommandManager.cs @@ -43,6 +43,8 @@ public class CommandManager { SendToggleRemoteDoor(direction); return; } + + if (direction == Direction.SOUTH) return; Client.Player.state.doorStates[(int)direction] = !Client.Player.state.doorStates[(int)direction]; UIManager.ChangeDoorState(direction, Client.Player.state.doorStates[(int)direction]); Client.SendCommands([PlayerCommand.SET_DOOR_OFFICE(direction, Client.Player.state.doorStates[(int)direction])]); diff --git a/FNAF_Clone/Content/Content.mgcb b/FNAF_Clone/Content/Content.mgcb index 261666d..362ea42 100644 --- a/FNAF_Clone/Content/Content.mgcb +++ b/FNAF_Clone/Content/Content.mgcb @@ -13,12 +13,27 @@ #---------------------------------- Content ---------------------------------# +#begin images/enemies-definition.xml +/copy:images/enemies-definition.xml + #begin images/monitor-definition.xml /copy:images/monitor-definition.xml #begin images/office-definition.xml /copy:images/office-definition.xml +#begin images/SpriteSheet_enemies.png +/importer:TextureImporter +/processor:TextureProcessor +/processorParam:ColorKeyColor=255,0,255,255 +/processorParam:ColorKeyEnabled=True +/processorParam:GenerateMipmaps=False +/processorParam:PremultiplyAlpha=True +/processorParam:ResizeToPowerOfTwo=False +/processorParam:MakeSquare=False +/processorParam:TextureFormat=Color +/build:images/SpriteSheet_enemies.png + #begin images/SpriteSheet_monitor.png /importer:TextureImporter /processor:TextureProcessor diff --git a/FNAF_Clone/Content/images/SpriteSheet_enemies.png b/FNAF_Clone/Content/images/SpriteSheet_enemies.png new file mode 100644 index 0000000000000000000000000000000000000000..174c6928c3382bd17f68b3a770069bf979069489 GIT binary patch literal 15263 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 a>;x^|)Qt(znB(2=eO}jCr%=l- + + images/SpriteSheet_enemies + + + + + + + + + + + + diff --git a/FNAF_Clone/EventProcessor.cs b/FNAF_Clone/EventProcessor.cs index 6b3b0bb..a6fa8e6 100644 --- a/FNAF_Clone/EventProcessor.cs +++ b/FNAF_Clone/EventProcessor.cs @@ -64,9 +64,30 @@ public class EventProcessor { UIManager.ChangeRemoteDoorState((e.Args[1], e.Args[2]), e.Args[3] == 1); break; - case -1: // movement - throw new NotImplementedException(); - + case 6: // spawn + Console.WriteLine($"E: Spawned enemy {e.Args[0]} at {e.Args[3]}"); + ClientEnemyManager.AddEnemyByTemplate((EnemyType)e.Args[0], e.Args[1], e.Args[2], ClientMapManager.Get(e.Args[3])); + UIManager.UpdateCameras([e.Args[3]]); + break; + + case 7: // movement + Console.WriteLine($"E: Enemy {e.Args[0]} moved to {e.Args[1]}"); + int oldPos = ClientEnemyManager.Get(e.Args[0]).Location!.Id; + ClientEnemyManager.Move(e.Args[0], ClientMapManager.Get(e.Args[1])); + UIManager.UpdateCameras([oldPos, e.Args[1]]); + break; + + case 9: + Console.WriteLine($"E: Enemy {e.Args[0]} reset to {e.Args[1]}"); + int preResetPos = ClientEnemyManager.Get(e.Args[0]).Location!.Id; + ClientEnemyManager.Move(e.Args[0], ClientMapManager.Get(e.Args[1])); + UIManager.UpdateCameras([preResetPos, e.Args[1]]); + break; + + case 10: + Console.WriteLine($"E: Spot:{e.Args[0]} turned {(e.Args[1] == 1 ? "on" : " off")}"); + ClientEnemyManager.Get(e.Args[0]).Sprite.SetTexture(e.Args[1] == 1 ? 0 : 2); + break; } } } diff --git a/FNAF_Clone/GUI/Screen.cs b/FNAF_Clone/GUI/Screen.cs index 1b29afe..10e0460 100644 --- a/FNAF_Clone/GUI/Screen.cs +++ b/FNAF_Clone/GUI/Screen.cs @@ -52,6 +52,7 @@ public class Screen { } public UIElement this[string id] => elements[id]; + public UIElement TryGetElement(string id) => elements.TryGetValue(id, out var val) ? val : null; public void AddElement(string id, UIElement element) { elements.Add(id, element); @@ -80,8 +81,6 @@ public class Screen { } public void Update() { - - foreach (var keyValuePair in elements){ keyValuePair.Value.Update(); } diff --git a/FNAF_Clone/GUI/UIElement.cs b/FNAF_Clone/GUI/UIElement.cs index 7166e29..9b7f0f9 100644 --- a/FNAF_Clone/GUI/UIElement.cs +++ b/FNAF_Clone/GUI/UIElement.cs @@ -69,9 +69,7 @@ public class UIElement { currentTextureId = textureId; } - public void Update() { - - } + public virtual void Update() { } public bool IsWithinBounds(Point pos) { return pos.X >= Math.Min(screenSpaceBounds.Item1.X, screenSpaceBounds.Item2.X) && pos.X <= Math.Max(screenSpaceBounds.Item1.X, screenSpaceBounds.Item2.X) && diff --git a/FNAF_Clone/GUI/UIManager.cs b/FNAF_Clone/GUI/UIManager.cs index 4afd137..2c44baf 100644 --- a/FNAF_Clone/GUI/UIManager.cs +++ b/FNAF_Clone/GUI/UIManager.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; using FNAF_Clone.Map; using GlobalClassLib; using Microsoft.Xna.Framework; @@ -21,15 +23,19 @@ public class UIManager { private static TextureAtlas testAtlas; private static TextureAtlas officeAtlas; private static TextureAtlas monitorAtlas; + public static TextureAtlas enemyAtlas; + public static int GlobalPixelMultiplier{ get; private set; } private Dictionary<(int, int), UIElement> doorElements = new(); + private static Dictionary enemyElements = new(); public static void InitUI() { GlobalPixelMultiplier = Core.graphicsDevice.Viewport.Height / 360; testAtlas = TextureAtlas.FromFile(Core.content, "images/testBlocks-definition.xml"); officeAtlas = TextureAtlas.FromFile(Core.content, "images/office-definition.xml"); monitorAtlas = TextureAtlas.FromFile(Core.content, "images/monitor-definition.xml"); + enemyAtlas = TextureAtlas.FromFile(Core.content, "images/enemies-definition.xml"); Screen.AddScreens([officeScreen, monitorScreen]); Screen.SetScreen(ScreenTypes.OFFICE); @@ -67,6 +73,7 @@ public class UIManager { monitorScreen.AddElement("eye-player", new UIElement(monitorAtlas[24], monitorScreen["room"+Client.Player.state.camera].Bounds.Item1)); monitorScreen.AddElement("eye-opponent", new UIElement([monitorAtlas[23], monitorAtlas[22]], monitorScreen["room"+Client.Opponent.state.camera].Bounds.Item1)); + UpdateCameras([Client.Player.state.camera]); } public static void SpawnDoors(TileConnectorProjection[] doors) { @@ -91,6 +98,13 @@ public class UIManager { monitorScreen.AddElement("p2-office-door-left", new UIElement([monitorAtlas[17], monitorAtlas[18]], new Point(400, 144))); } + + public static void AddEnemySprite(int id, UIElement sprite) { + monitorScreen.AddElement($"enemy{id}", sprite); + enemyElements.Add(id, sprite); + sprite.Visible = false; + } + public static void ChangeDoorState(Direction dir, bool state) { int stateInt = state ? 1 : 0; @@ -141,12 +155,25 @@ public class UIManager { public static void ChangeCamera(int id) { monitorScreen["eye-player"].SetPosition(monitorScreen["room"+id].Bounds.Item1); + UpdateCameras([id]); } + + public static void UpdateCameras(int[] camIds) { + if (camIds.Contains(Client.Player.state.camera)){ + enemyElements.Values.Where(e => e.Visible).ToList().ForEach(e => e.Visible = false); + ClientEnemy[] enemies = ClientEnemyManager.GetByLocation(ClientMapManager.Get(Client.Player.state.camera));; + foreach (var enemy in enemies){ + enemyElements.TryGetValue(enemy.Id, out var element); + if (element == null) continue; + element.Visible = true; + } + } + } + public static void ChangeCameraOpponent(int id) { monitorScreen["eye-opponent"].SetPosition(monitorScreen["room"+id].Bounds.Item1); } - // private static Point GetRoomUIPos((int x, int y) pos) { // return new Point(336 + (32 * pos.x), 144 + (32 * pos.y)); // } diff --git a/FNAF_Server/CommandProcessor.cs b/FNAF_Server/CommandProcessor.cs index 0565a67..e6d32d9 100644 --- a/FNAF_Server/CommandProcessor.cs +++ b/FNAF_Server/CommandProcessor.cs @@ -11,6 +11,7 @@ public class CommandProcessor { switch (playerCommand.ID){ case 0: Console.WriteLine($"C: Player {pid} switched to camera {playerCommand.Args[0]}"); + currentPlayer.state.camera = playerCommand.Args[0]; Server.SendUpdateToAll([GameEvent.SWITCH_CAM(pid, playerCommand.Args[0])]); break; case 1: @@ -20,8 +21,10 @@ public class CommandProcessor { Server.SendUpdateToAll([GameEvent.TOGGLE_MONITOR(pid, monitorState)]); break; case 2: - bool doorState = playerCommand.Args[1] == 1; + bool doorState = playerCommand.Args[1] == 1; // TODO: block office doors currentPlayer.state.doorStates[playerCommand.Args[0]] = doorState; + MapManager.Get(currentPlayer.state.officeTileId).GetConnector(currentPlayer.state.neighbouringTiles[playerCommand.Args[0]]).Blocked = doorState; + Console.WriteLine($"C: Player {pid} {(doorState ? "closed" : "opened")} door {playerCommand.Args[0]}"); Server.SendUpdateToAll([GameEvent.TOGGLE_DOOR_OFFICE(pid,playerCommand.Args[0] ,doorState)]); break; diff --git a/FNAF_Server/Enemies/Enemy.cs b/FNAF_Server/Enemies/Enemy.cs new file mode 100644 index 0000000..1620c47 --- /dev/null +++ b/FNAF_Server/Enemies/Enemy.cs @@ -0,0 +1,26 @@ +using FNAF_Server.Map; +using GlobalClassLib; + +namespace FNAF_Server.Enemies; + +public abstract class Enemy : GlobalEnemy { + protected Enemy(int difficulty) : base(difficulty) { + } + + public abstract bool BlocksTile { get; set; } + public bool Spawned { get; set; } + + public virtual void SpawnSilent(MapTile location) { + Console.WriteLine($"!!! Silent spawn not implemented for enemy {TypeId} ({Name}), reverting to regular spawn"); + Spawn(location); + } + + public override void Spawn(MapTile location) { + base.Spawn(location); + Spawned = true; + // EnemyManager.AddEnemy(this); + } + + public abstract void Reset(); + public abstract void Attack(ServerPlayer player); +} \ No newline at end of file diff --git a/FNAF_Server/Enemies/EnemyManager.cs b/FNAF_Server/Enemies/EnemyManager.cs new file mode 100644 index 0000000..0eba22c --- /dev/null +++ b/FNAF_Server/Enemies/EnemyManager.cs @@ -0,0 +1,29 @@ +using FNAF_Server.Map; + +namespace FNAF_Server.Enemies; + +public class EnemyManager { + private static Dictionary enemies = new(); + + public static void Update() { + foreach (var pair in enemies){ + if (pair.Value.Spawned) pair.Value.Update(); + } + } + + public static Enemy AddEnemy(Enemy enemy) { + enemies.Add(enemy.Id, enemy); + return enemy; + } + + public static Enemy[] GetByLocation(MapTile tile) { + List output = new(); + foreach (var e in enemies.Values){ + if (e.Location == tile){ + output.Add(e); + } + } + + return output.ToArray(); + } +} \ No newline at end of file diff --git a/FNAF_Server/Enemies/LurkEnemy.cs b/FNAF_Server/Enemies/LurkEnemy.cs new file mode 100644 index 0000000..569f807 --- /dev/null +++ b/FNAF_Server/Enemies/LurkEnemy.cs @@ -0,0 +1,118 @@ +using System.Diagnostics; +using System.Reflection; +using FNAF_Server.Map; +using GlobalClassLib; +using PacketLib; + +namespace FNAF_Server.Enemies; + +public class LurkEnemy : Enemy { + public override string Name{ get; } = "Lurk"; + public override int TypeId{ get; } = (int)EnemyType.LURK; + + public override bool BlocksTile{ get; set; } = true; + + private LurkPathfinder pathfinder; + + // private int movementRollInterval = 5000; + // private static readonly double CHANCE_DENOMINATOR = 2 + Math.Pow(1.5f, 10); // d10 Lurk will have a 100% chance to move + // private double movementChance => (2 + Math.Pow(1.5f, Difficulty)) / CHANCE_DENOMINATOR; // chance scales exponentially using a constant multiplier + + private MovementOpportunity movementOpportunity; + + public LurkEnemy(int difficulty) : base(difficulty) { + pathfinder = new LurkPathfinder(this, 1); + } + 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(); + pathfinder.takenPath.Add(Location); + + 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 Attack(ServerPlayer player) { + throw new NotImplementedException(); + } + + + 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: + throw new NotImplementedException(); + case Pathfinder.Decision.ResetType: + Reset(); + break; + case Pathfinder.Decision.WaitType: + break; + } + } + } + + private class LurkPathfinder : RoamingPathfinder { + // public override List FindPath(MapTile start, MapTile end) { + // throw new NotImplementedException(); + // } + + private Random random = new(); + + private int tolerance; + public List takenPath = new(); + + public LurkPathfinder(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(); + } + } +} \ No newline at end of file diff --git a/FNAF_Server/Enemies/MovementOpportunity.cs b/FNAF_Server/Enemies/MovementOpportunity.cs new file mode 100644 index 0000000..6e1a1f2 --- /dev/null +++ b/FNAF_Server/Enemies/MovementOpportunity.cs @@ -0,0 +1,60 @@ +using System.Diagnostics; + +namespace FNAF_Server.Enemies; + +public class MovementOpportunity { + public int Interval{ get; set; } + // public double ChanceDenominator; + public double MovementChance{ get; set; } + + public bool Running => stopwatch.IsRunning; + + public bool ConstantChance{ get; private set; } + + private Stopwatch stopwatch = new(); + private int stopwatchOffset = 0; + + private Random random = new(); + + public MovementOpportunity(int intervalMs, double movementChance) { + Interval = intervalMs; + MovementChance = movementChance; + GuaranteeSuccess(false); + ConstantChance = true; // unused + } + + public MovementOpportunity(int intervalMs) { + Interval = intervalMs; + GuaranteeSuccess(true); + MovementChance = 1; + } + + public void Start() { + stopwatch.Start(); + } + public void Stop() { + stopwatch.Stop(); + } + + public bool Check() { + if (stopwatch.ElapsedMilliseconds + stopwatchOffset >= Interval){ + stopwatchOffset = (int)(stopwatch.ElapsedMilliseconds - Interval); + stopwatch.Restart(); + return true; + } + return false; + } + + public bool Roll() => roll(); + private Func roll = () => true; + + public bool CheckAndRoll() { + if (Check()) return Roll(); + return false; + } + + public void GuaranteeSuccess(bool value) { + roll = value ? () => true : () => random.NextDouble() <= MovementChance; + } + +} \ No newline at end of file diff --git a/FNAF_Server/Enemies/NekoEnemy.cs b/FNAF_Server/Enemies/NekoEnemy.cs new file mode 100644 index 0000000..34a90ac --- /dev/null +++ b/FNAF_Server/Enemies/NekoEnemy.cs @@ -0,0 +1,106 @@ +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 Attack(ServerPlayer player) { + throw new NotImplementedException(); + } + + + 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: + throw new NotImplementedException(); + 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(); + } + } +} \ No newline at end of file diff --git a/FNAF_Server/Enemies/Pathfinder.cs b/FNAF_Server/Enemies/Pathfinder.cs new file mode 100644 index 0000000..6c658c8 --- /dev/null +++ b/FNAF_Server/Enemies/Pathfinder.cs @@ -0,0 +1,36 @@ +using FNAF_Server.Map; + +namespace FNAF_Server.Enemies; + +public abstract class Pathfinder { + public Enemy Enemy; + public Pathfinder(Enemy enemy) { + Enemy = enemy; + } + public abstract Decision DecideNext(MapTile goal); + // protected abstract Dictionary Crawl(MapTile tile); // fill Distances with all reachable tiles + + protected virtual List GetNeighbours(MapTile tile, Predicate connectorFilter, Predicate tileFilter) { + return tile.GetAllConnectors().Where(c => connectorFilter(c)).Select(c => c.OtherTile(tile)).Where(t => tileFilter(t)).ToList(); + } + + public class Decision { + public int type; + + public MapTile? nextTile; + public ServerPlayer? attackTarget; + + private Decision() { } + + public static Decision Move(MapTile tile) => new() { type = MoveType, nextTile = tile }; + public static Decision Attack(ServerPlayer player) => new(){ type = AttackType, attackTarget = player }; + public static Decision Reset() => new(){ type = ResetType }; + public static Decision Wait() => new(){ type = WaitType }; + + public const int MoveType = 0; + public const int AttackType = 1; + public const int ResetType = 2; + public const int WaitType = 3; + + } +} \ No newline at end of file diff --git a/FNAF_Server/Enemies/RoamingPathfinder.cs b/FNAF_Server/Enemies/RoamingPathfinder.cs new file mode 100644 index 0000000..5260d58 --- /dev/null +++ b/FNAF_Server/Enemies/RoamingPathfinder.cs @@ -0,0 +1,29 @@ +using FNAF_Server.Map; +using GlobalClassLib; + +namespace FNAF_Server.Enemies; + +public abstract class RoamingPathfinder : Pathfinder{ + protected RoamingPathfinder(Enemy enemy) : base(enemy) { + } + + protected Dictionary Crawl(MapTile tile) { + Dictionary distances = new(){ [tile] = 0 }; + CrawlStep(tile, distances); + return distances; + } + + private void CrawlStep(MapTile tile, Dictionary distances) { + List neighbours = GetNeighbours(tile, + c => (!c.Blocked || c.Type == ConnectorType.DOOR_OFFICE) && tile != Enemy.Location, + t => + (!distances.ContainsKey(t) || distances[t] > distances[tile]) && + (!Enemy.BlocksTile || EnemyManager.GetByLocation(t).All(e => !e.BlocksTile || e == Enemy))); + + neighbours.ForEach(t => distances[t] = distances[tile] + tile.GetConnector(t)!.Value); + + if (neighbours.Count != 0){ + neighbours.ForEach(t => CrawlStep(t, distances)); + } + } +} \ No newline at end of file diff --git a/FNAF_Server/Enemies/SpotEnemy.cs b/FNAF_Server/Enemies/SpotEnemy.cs new file mode 100644 index 0000000..a95e691 --- /dev/null +++ b/FNAF_Server/Enemies/SpotEnemy.cs @@ -0,0 +1,95 @@ +using FNAF_Server.Map; +using GlobalClassLib; +using PacketLib; + +namespace FNAF_Server.Enemies; + +public class SpotEnemy : Enemy { + public SpotEnemy(int difficulty) : base(difficulty) { + path = [MapManager.Get(10), MapManager.Get(11), MapManager.Get(12), MapManager.Get(13), MapManager.Get(14)]; + pathId = 2; + movementOpportunity = new(6000, (2 * Math.Sign(difficulty) + difficulty) / 12.0); + } + + public override string Name{ get; } = "Spot"; + public override int TypeId{ get; } = (int)EnemyType.SPOT; + public override bool BlocksTile{ get; set; } = false; + + public bool Active{ get; set; } = false; + + private MovementOpportunity movementOpportunity; + private MapTile[] path; + private int pathId; + + private int p1WatchCounter = 0; + private int p2WatchCounter = 0; + + public override void Reset() { + pathId = 2; + Location = path[pathId]; + Server.SendUpdateToAll([GameEvent.ENEMY_RESET(Id, Location.Id)]); + } + + public override void Attack(ServerPlayer player) { + throw new NotImplementedException(); + } + + public override void Update() { + if (GameLogic.NSecondUpdate){ + if(!movementOpportunity.Running) + movementOpportunity.Start(); + + if (Active){ + if (Server.P1.state.monitorUp && Server.P1.state.camera == Location.Id) p1WatchCounter++; + if (Server.P2.state.monitorUp && Server.P2.state.camera == Location.Id) p2WatchCounter++; + + Console.WriteLine($"P1: {p1WatchCounter} | P2: {p2WatchCounter}"); + } + } + + if (movementOpportunity.CheckAndRoll()){ + if(!Active) { + Active = true; + movementOpportunity.Interval = 12_000; + movementOpportunity.GuaranteeSuccess(true); + Server.SendUpdateToAll([GameEvent.SPOT_SET_ACTIVE(Id, true)]); + } + else{ + movementOpportunity.Interval = 6000; + movementOpportunity.GuaranteeSuccess(false); + movementOpportunity.Stop(); + + if (p1WatchCounter > p2WatchCounter){ + pathId++; + if (Location.GetConnector(path[pathId])!.Blocked){ + Reset(); + } + else if (pathId == path.Length - 1){ + Attack(Server.P1); + } + } + else if (p2WatchCounter > p1WatchCounter){ + pathId--; + if (Location.GetConnector(path[pathId])!.Blocked){ + Reset(); + } + else if (pathId == 0){ + Attack(Server.P2); + } + } + + Location = path[pathId]; + Active = false; + p1WatchCounter = 0; + p2WatchCounter = 0; + Server.SendUpdateToAll([GameEvent.ENEMY_MOVEMENT(Id, Location.Id), GameEvent.SPOT_SET_ACTIVE(Id, false)]); + } + } + } + + public override void Spawn(MapTile location) { + base.Spawn(location); + Server.SendUpdateToAll([GameEvent.ENEMY_SPAWN(TypeId, Id, Difficulty, Location.Id)]); + + } +} \ No newline at end of file diff --git a/FNAF_Server/GameLogic.cs b/FNAF_Server/GameLogic.cs index 550303f..84cbcf1 100644 --- a/FNAF_Server/GameLogic.cs +++ b/FNAF_Server/GameLogic.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; +using FNAF_Server.Enemies; using FNAF_Server.Map; using GlobalClassLib; using PacketLib; @@ -7,10 +9,19 @@ namespace FNAF_Server; public class GameLogic { public const int START_CAMERA = 12; + + private static MovementOpportunity secondCycleTimer = new(1000); + public static bool NSecondUpdate{ get; private set; } = false; + public static MapTile P1Office; + public static MapTile P2Office; + public static MapTile[] Offices =>[P1Office, P2Office]; + public static void Init() { // Create map MapManager.InitMap(); + P1Office = MapManager.Get(10); + P2Office = MapManager.Get(14); //Send map to client TileConnector[] connectors = MapManager.GetAllConnectors(); @@ -20,13 +31,21 @@ public class GameLogic { connectorsConverted[i * 3 + 1] = connectors[i].Tiles.tile2.Id; connectorsConverted[i * 3 + 2] = (int)connectors[i].Type; } - Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = true}, Server.P1.peer); - Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = false}, Server.P2.peer); + Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = false}, Server.P1.peer); + Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = true}, Server.P2.peer); + // EnemyManager.AddEnemy(new LurkEnemy(0)).Spawn(MapManager.Get(12)); + EnemyManager.AddEnemy(new NekoEnemy(10)).Spawn(MapManager.Get(2)); + EnemyManager.AddEnemy(new SpotEnemy(0)).Spawn(MapManager.Get(12)); + + secondCycleTimer.Start(); } public static void Update() { + if(secondCycleTimer.Check()) NSecondUpdate = true; + EnemyManager.Update(); + + NSecondUpdate = false; } - } \ No newline at end of file diff --git a/FNAF_Server/Server.cs b/FNAF_Server/Server.cs index c9e991d..4b31125 100644 --- a/FNAF_Server/Server.cs +++ b/FNAF_Server/Server.cs @@ -107,18 +107,26 @@ public class Server { }, username = packet.username }); - - SendPacket(new JoinAcceptPacket { state = newPlayer.state }, peer); - + + if (Players.Count == 1){ + newPlayer.state.officeTileId = 10; + newPlayer.state.neighbouringTiles = [5, 11, 15]; + SendPacket(new JoinAcceptPacket { state = newPlayer.state }, peer); P1 = newPlayer; } else{ + newPlayer.state.officeTileId = 14; + newPlayer.state.neighbouringTiles = [19, 13, 9]; + + SendPacket(new JoinAcceptPacket { state = newPlayer.state }, peer); P2 = newPlayer; + SendPacket(new OpponentInitPacket{state = newPlayer.state}, P1.peer); SendPacket(new OpponentInitPacket{state = P1.state}, P2.peer); - GameLogic.Init(); // TODO: move this to the condition above to wait for the other player + GameLogic.Init(); } + } public static void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) { @@ -133,29 +141,12 @@ public class Server { public static void OnCommandReceived(PlayerCommandPacket packet, NetPeer peer) { CommandProcessor.Evaluate(packet.commands, peer.Id); - - // PlayerCommand[] commands = packet.commands; - - // foreach (var playerCommand in commands){ - // switch (playerCommand.ID){ - // case 0: - // Console.WriteLine($"C: Player {peer.Id} switched to camera {playerCommand.Args[0]}"); - // SendUpdateToAll([GameEvent.SWITCH_CAM(peer.Id, playerCommand.Args[0])]); - // break; - // case 1: - // bool newState = !Players[(uint)peer.Id].state.monitorUp; - // Players[(uint)peer.Id].state.monitorUp = newState; - // Console.WriteLine($"C: Player {peer.Id} toggled camera {(newState ? "on" : "off")}"); - // SendUpdateToAll([GameEvent.TOGGLE_MONITOR(peer.Id, newState)]); - // break; - // } - // } } public static void Update() { server.PollEvents(); // Console.WriteLine("update"); - GameLogic.Update(); // TODO: add a parameter for player input + GameLogic.Update(); } public static void Run(CancellationToken cancellationToken = default) diff --git a/GlobalClassLib/EnemyType.cs b/GlobalClassLib/EnemyType.cs new file mode 100644 index 0000000..d873f4a --- /dev/null +++ b/GlobalClassLib/EnemyType.cs @@ -0,0 +1,11 @@ +using System.Diagnostics.Contracts; + +namespace GlobalClassLib; + +public enum EnemyType { + LURK, + NEKO, + SPOT, + DASH, + MARE +} \ No newline at end of file diff --git a/GlobalClassLib/GlobalEnemy.cs b/GlobalClassLib/GlobalEnemy.cs new file mode 100644 index 0000000..f722a1d --- /dev/null +++ b/GlobalClassLib/GlobalEnemy.cs @@ -0,0 +1,35 @@ +namespace GlobalClassLib; + +public abstract class GlobalEnemy where TTile : GlobalMapTile where TCon : GlobalTileConnector { + public int Difficulty { get; set; } + public TTile? Location { get; set; } + public bool Visible { get; set; } + public abstract string Name{ get; } + + public abstract int TypeId{ get; } + public int Id { get; } + + private static int nextId = 0; + + + // public static Dictionary> PresentEnemies = new(); + + public GlobalEnemy(int difficulty) { + Difficulty = difficulty; + Id = nextId++; + } + + public GlobalEnemy(int difficulty, int id) { + Difficulty = difficulty; + Id = id; + } + public virtual void Spawn(TTile location) { + // PresentEnemies.Add(location, this); + Location = location; + Visible = true; + Console.WriteLine($"Enemy {Id} ({Name}) spawned at {location.Id} {location.GridPosition}"); + } + + public virtual void Update() { } + +} \ No newline at end of file diff --git a/GlobalClassLib/GlobalMapTile.cs b/GlobalClassLib/GlobalMapTile.cs index dcc44fa..719be66 100644 --- a/GlobalClassLib/GlobalMapTile.cs +++ b/GlobalClassLib/GlobalMapTile.cs @@ -1,7 +1,7 @@ namespace GlobalClassLib; public abstract class GlobalMapTile where TCon : GlobalTileConnector where TTile : GlobalMapTile { // TTile should be the inheriting class - public int Id { get; private set; } + public int Id { get; } public bool Lit { get; set; } = false; public (int x, int y) GridPosition { get; private set; } @@ -28,17 +28,22 @@ public abstract class GlobalMapTile where TCon : GlobalTileConnecto public void AddConnectors(TCon[] connectors) => Array.ForEach(connectors, AddConnector); - public override string ToString() => $"{PositionAsString} -> {string.Join(", ", connectors.Select(c => c.Tiles.Item2.PositionAsString))}"; + public override string ToString() => $"[{Id}] -> {string.Join(", ", connectors.Select(c => $"[{c.OtherTile((TTile)this).Id}]"))}"; + public override int GetHashCode() => Id.GetHashCode(); public string PositionAsString => $"[{Id}]"; // for debug purposes public TCon? GetConnector(int id) { foreach (var con in connectors){ - if (con.Tiles.Item2.Id == id) return con; + if (con.OtherTile((TTile)this).Id == id) return con; + } + return null; + } + public TCon? GetConnector(TTile tile) { + foreach (var con in connectors){ + if (con.OtherTile((TTile)this) == tile) return con; } - return null; } public TCon[] GetAllConnectors() => connectors.ToArray(); - } \ No newline at end of file diff --git a/PacketLib/GameEvent.cs b/PacketLib/GameEvent.cs index 66dbc6c..46a8eb5 100644 --- a/PacketLib/GameEvent.cs +++ b/PacketLib/GameEvent.cs @@ -6,16 +6,18 @@ namespace PacketLib; #nullable disable public struct GameEvent : INetSerializable{ public static GameEvent PLAYER_JOIN(int pid) => new(){ID = 0, Args = [pid] }; // TODO: username sync - public static GameEvent PLAYER_LEAVE(int pid) => new(){ID = 1, Args = [pid] }; + public static GameEvent PLAYER_LEAVE(int pid) => new(){ID = 1, Args = [pid] }; // TODO: id constants public static GameEvent SWITCH_CAM(int pid, int id) => new(){ID = 2, Args = [pid, id] }; public static GameEvent TOGGLE_MONITOR(int pid, bool state) => new(){ID = 3, Args = [pid, state ? 1 : 0]}; public static GameEvent TOGGLE_DOOR_OFFICE(int pid, int doorId, bool state) => new(){ID = 4, Args = [pid, doorId, state ? 1 : 0]}; public static GameEvent TOGGLE_DOOR_REMOTE(int pid, (int, int) doorId, bool state) => new(){ID = 5, Args = [pid, doorId.Item1, doorId.Item2, state ? 1 : 0]}; + + public static GameEvent ENEMY_SPAWN(int enemyTypeId, int enemyId, int difficulty, int camId) => new(){ ID = 6, Args = [enemyTypeId, enemyId, difficulty, camId] }; + public static GameEvent ENEMY_MOVEMENT(int enemyId, int camId) => new(){ID = 7, Args = [enemyId, camId]}; + public static GameEvent ENEMY_ATTACK(int enemyId, int pid) => new(){ ID = 8, Args =[enemyId, pid] }; + public static GameEvent ENEMY_RESET(int enemyId, int camId) => new(){ID = 9, Args = [enemyId, camId]}; - - - - public static GameEvent ENEMY_MOVEMENT(int enemyId, int camId) => new(){ID = -1, Args = [enemyId, camId]}; + public static GameEvent SPOT_SET_ACTIVE(int enemyId, bool state) => new(){ ID = 10, Args = [enemyId, state ? 1 : 0] }; public int ID{ get; set; } public bool Hideable => ID < 0; diff --git a/PacketLib/PlayerState.cs b/PacketLib/PlayerState.cs index be66414..8e4abcc 100644 --- a/PacketLib/PlayerState.cs +++ b/PacketLib/PlayerState.cs @@ -3,19 +3,22 @@ using LiteNetLib.Utils; namespace PacketLib; -public struct PlayerState : INetSerializable { +public struct PlayerState : INetSerializable { // TODO: separate mutable and immutable data public int pid; public int camera; public bool monitorUp; + public int officeTileId; public bool[] doorStates; - + public int[] neighbouringTiles; // the indexes should correspond in both arrays public void Serialize(NetDataWriter writer) { writer.Put(pid); writer.Put(camera); writer.Put(monitorUp); writer.PutArray(doorStates); + writer.Put(officeTileId); + writer.PutArray(neighbouringTiles); } public void Deserialize(NetDataReader reader) { @@ -23,6 +26,8 @@ public struct PlayerState : INetSerializable { camera = reader.GetInt(); monitorUp = reader.GetBool(); doorStates = reader.GetBoolArray(); + officeTileId = reader.GetInt(); + neighbouringTiles = reader.GetIntArray(); } }