From 4fdff0a0cce3f3507b676b66714c29864fac40d6 Mon Sep 17 00:00:00 2001 From: Perry Date: Tue, 17 Mar 2026 20:14:29 +0100 Subject: [PATCH] =?UTF-8?q?V=C5=A1echna=20monstra,=20dynamick=C3=BD=20targ?= =?UTF-8?q?eting.=20Bugfixy=20u=20monster.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FNAF_Clone/ClientEnemy.cs | 2 +- FNAF_Clone/ClientEnemyManager.cs | 22 +++ .../Content/images/enemies-definition.xml | 3 +- FNAF_Clone/GUI/MenuInputField.cs | 5 +- FNAF_Clone/GUI/UIManager.cs | 8 +- FNAF_Server/Enemies/DashEnemy.cs | 70 ++++++++++ FNAF_Server/Enemies/Enemy.cs | 7 +- FNAF_Server/Enemies/LurkEnemy.cs | 33 +++-- FNAF_Server/Enemies/MareEnemy.cs | 132 ++++++++++++++++++ FNAF_Server/Enemies/MovementOpportunity.cs | 13 +- FNAF_Server/Enemies/NekoEnemy.cs | 46 +++++- FNAF_Server/Enemies/RoamingPathfinder.cs | 13 +- FNAF_Server/Enemies/SpotEnemy.cs | 8 +- FNAF_Server/FNAF_Server.csproj | 1 + FNAF_Server/GameLogic.cs | 10 +- FNAF_Server/Map/MapManager.cs | 2 +- GlobalClassLib/GlobalEnemy.cs | 12 +- PacketLib/GameEvent.cs | 1 + 18 files changed, 345 insertions(+), 43 deletions(-) create mode 100644 FNAF_Server/Enemies/DashEnemy.cs create mode 100644 FNAF_Server/Enemies/MareEnemy.cs diff --git a/FNAF_Clone/ClientEnemy.cs b/FNAF_Clone/ClientEnemy.cs index 8a68128..ca7fee0 100644 --- a/FNAF_Clone/ClientEnemy.cs +++ b/FNAF_Clone/ClientEnemy.cs @@ -6,7 +6,7 @@ using MonoGameLibrary.Graphics; namespace FNAF_Clone; public class ClientEnemy : GlobalEnemy { - public ClientEnemy(int typeId, string name, int id, EnemyUIElement sprite, int difficulty, MapTileProjection location, JumpscareUIElement jumpscareSprite) : base(difficulty, id) { + public ClientEnemy(int typeId, string name, int id, EnemyUIElement sprite, int difficulty, MapTileProjection location, JumpscareUIElement jumpscareSprite) : base(id) { Name = name; TypeId = typeId; Sprite = sprite; diff --git a/FNAF_Clone/ClientEnemyManager.cs b/FNAF_Clone/ClientEnemyManager.cs index 6f83f50..1026ba0 100644 --- a/FNAF_Clone/ClientEnemyManager.cs +++ b/FNAF_Clone/ClientEnemyManager.cs @@ -58,6 +58,28 @@ public static class ClientEnemyManager { new JumpscareUIElement(UIManager.EnemyAtlas["spot-awake-lit"], new(0, 0), 3, 2, 2, 0.1f, afterStop:defaultAfterJumpscare) )); break; + case EnemyType.DASH: + AddEnemy(new ClientEnemy( + (int)type, + "Dash", + id, + new EnemyUIElement(UIManager.EnemyAtlas["dash-lit"], UIManager.EnemyAtlas["dash-unlit"], cameraCorner, 3), + difficulty, + location, + new JumpscareUIElement(UIManager.EnemyAtlas["dash-lit"], new(0, 0), 3, 2, 2, 0.1f, afterStop:defaultAfterJumpscare) + )); + break; + case EnemyType.MARE: + AddEnemy(new ClientEnemy( + (int)type, + "Mare", + id, + new EnemyUIElement(UIManager.EnemyAtlas["mare-lit"], UIManager.EnemyAtlas["mare-unlit"], cameraCorner), + difficulty, + location, + new JumpscareUIElement(UIManager.EnemyAtlas["mare-lit"], new(0, 0), 3, 2, 2, 0.1f, afterStop:defaultAfterJumpscare) + )); + break; } } diff --git a/FNAF_Clone/Content/images/enemies-definition.xml b/FNAF_Clone/Content/images/enemies-definition.xml index 5110c40..64e0b2d 100755 --- a/FNAF_Clone/Content/images/enemies-definition.xml +++ b/FNAF_Clone/Content/images/enemies-definition.xml @@ -10,7 +10,8 @@ - + \ + diff --git a/FNAF_Clone/GUI/MenuInputField.cs b/FNAF_Clone/GUI/MenuInputField.cs index 414ebc1..01af72e 100644 --- a/FNAF_Clone/GUI/MenuInputField.cs +++ b/FNAF_Clone/GUI/MenuInputField.cs @@ -30,7 +30,10 @@ public class MenuInputField : UIElement { textBoxElement.Text = defaultValue; } - public string Text => textBoxElement.Text; + public string Text{ + get => textBoxElement.Text; + set => textBoxElement.Text = value; + } public override void Draw(SpriteBatch spriteBatch) { labelElement.Draw(spriteBatch); diff --git a/FNAF_Clone/GUI/UIManager.cs b/FNAF_Clone/GUI/UIManager.cs index 5ed230b..5cf1de3 100644 --- a/FNAF_Clone/GUI/UIManager.cs +++ b/FNAF_Clone/GUI/UIManager.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; using FNAF_Clone.Map; using GlobalClassLib; using Microsoft.Xna.Framework; @@ -121,7 +119,6 @@ public class UIManager { menuScreen.AddElement("host-button", new TextUIElement(new(connectButton.Bounds.Item1.X, connectButton.Bounds.Item2.Y + 30), PixelMonoFont) {Text = "HOST"}); - loadingScreen.AddElement("loading-text", new LoadingUIElement(new(320, 180), PixelMonoFont, field.Text)); } @@ -129,6 +126,8 @@ public class UIManager { public static void DisplayMainMenu() { Screen.SetScreen(ScreenTypes.MENU); CommandManager.AllowGameControls(false); + // if(Client.Player.username != null) + // ((MenuInputField)menuScreen["username-field"]).Text = Client.Player.username; } @@ -255,7 +254,7 @@ public class UIManager { public static void UpdateCameras(int[] camIds) { foreach (var id in camIds){ MapTileProjection tile = ClientMapManager.Get(id); - if(tile.Owner == null) continue; + if(tile.Owner == null || tile.Id == Client.Player.state.officeTileId || tile.Id == Client.Opponent.state.officeTileId) continue; lightIndicators[id].Visible = tile.Lit; } @@ -283,6 +282,7 @@ public class UIManager { Screen.SetScreen(ScreenTypes.OFFICE); enemy.JumpscareSprite.Play(); timerElement.Stop(); + CommandManager.AllowGameControls(false); // UIElement jumpscareElement = enemy.Sprite.Clone(); // jumpscareElement.ScaleMultiplier = 2; // jumpscareElement.SetPosition(new Point(0, 0)); diff --git a/FNAF_Server/Enemies/DashEnemy.cs b/FNAF_Server/Enemies/DashEnemy.cs new file mode 100644 index 0000000..4e00279 --- /dev/null +++ b/FNAF_Server/Enemies/DashEnemy.cs @@ -0,0 +1,70 @@ +using System.Net.Mime; +using FNAF_Server.Map; +using GlobalClassLib; +using PacketLib; + +namespace FNAF_Server.Enemies; + +public class DashEnemy : Enemy { + public DashEnemy(int difficulty) : base(difficulty) { + movementOpportunity = new(5000); + SetDifficulty(difficulty); + } + + public override string Name{ get; } = "Dash"; + public override int TypeId{ get; } = (int)EnemyType.DASH; + public override bool BlocksTile{ get; set; } = false; + + private MovementOpportunity movementOpportunity; + + private readonly (MapTile tile, Direction p1AttackDir, Direction p2AttackDir)[] positions =[ + (MapManager.Get(7), Direction.EAST, Direction.WEST), + (MapManager.Get(12), Direction.NORTH, Direction.NORTH), + (MapManager.Get(17), Direction.WEST, Direction.EAST) + ]; + + private int currentPosIndex = 0; + private Random random = new(); + + public override void Reset() { + int roll = random.Next(0, positions.Length - 1); + if (roll >= currentPosIndex) roll++; + currentPosIndex = roll; + Location = positions[roll].tile; + Server.SendUpdateToAll([GameEvent.ENEMY_RESET(Id, Location.Id)]); + } + + public override void SetDifficulty(int difficulty) { + Difficulty = difficulty; + movementOpportunity.MovementChance = (2 * Math.Sign(difficulty) + difficulty) / 12.0; + } + + public override void Spawn(MapTile location) { + currentPosIndex = positions.ToList().FindIndex(p => p.tile == location); + if (currentPosIndex == -1){ + Console.WriteLine("Dash failed to spawn on " + location.Id); + return; + } + base.Spawn(location); + + movementOpportunity.Start(); + Server.SendUpdateToAll([GameEvent.ENEMY_SPAWN(TypeId, Id, Difficulty, Location.Id)]); + } + + public override void Update() { + base.Update(); + + if (movementOpportunity.CheckAndRoll()){ + bool attackP1 = !Server.P1.state.doorStates[(int)positions[currentPosIndex].p1AttackDir]; + bool attackP2 = !Server.P2.state.doorStates[(int)positions[currentPosIndex].p2AttackDir]; + + if (attackP1 != attackP2){ + if(attackP1) Attack(Server.P1); + else if(attackP2) Attack(Server.P2); + return; + } + + Reset(); + } + } +} \ No newline at end of file diff --git a/FNAF_Server/Enemies/Enemy.cs b/FNAF_Server/Enemies/Enemy.cs index 4408018..ea360f0 100644 --- a/FNAF_Server/Enemies/Enemy.cs +++ b/FNAF_Server/Enemies/Enemy.cs @@ -5,7 +5,10 @@ using PacketLib; namespace FNAF_Server.Enemies; public abstract class Enemy : GlobalEnemy { - protected Enemy(int difficulty) : base(difficulty) { + public int Difficulty { get; protected set; } + + protected Enemy(int difficulty) { + Difficulty = difficulty; } public abstract bool BlocksTile { get; set; } @@ -27,4 +30,6 @@ public abstract class Enemy : GlobalEnemy { Server.SendUpdateToAll([GameEvent.ENEMY_ATTACK(Id, player.state.pid)]); GameLogic.DeclareWinner(Server.OtherPlayer(player)); } + + public abstract void SetDifficulty(int difficulty); } \ No newline at end of file diff --git a/FNAF_Server/Enemies/LurkEnemy.cs b/FNAF_Server/Enemies/LurkEnemy.cs index 4ad7839..273581d 100644 --- a/FNAF_Server/Enemies/LurkEnemy.cs +++ b/FNAF_Server/Enemies/LurkEnemy.cs @@ -19,14 +19,18 @@ public class LurkEnemy : Enemy { // private double movementChance => (2 + Math.Pow(1.5f, Difficulty)) / CHANCE_DENOMINATOR; // chance scales exponentially using a constant multiplier private MovementOpportunity movementOpportunity; + + public ServerPlayer? Target{ get; set; } public LurkEnemy(int difficulty) : base(difficulty) { pathfinder = new LurkPathfinder(this, 1); + movementOpportunity = new MovementOpportunity(5000); + Target = null; + SetDifficulty(difficulty); } 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)]); @@ -35,17 +39,34 @@ public class LurkEnemy : Enemy { public override void Reset() { pathfinder.takenPath.Clear(); pathfinder.takenPath.Add(Location); + + Target.state.power -= 30; 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 SetDifficulty(int difficulty) { + Difficulty = difficulty; + movementOpportunity.MovementChance = ((2 + Math.Pow(1.5f, Difficulty)) * Math.Sign(Difficulty)) / (2 + Math.Pow(1.5f, 10)); + } + public override void Update() { base.Update(); if (movementOpportunity.CheckAndRoll()){ - Pathfinder.Decision decision = pathfinder.DecideNext(MapManager.Get(10)); + if (Server.P1.state.power > Server.P2.state.power){ + Target = Server.P1; + } + else if (Server.P1.state.power < Server.P2.state.power){ + Target = Server.P2; + } + else{ + return; + } + + Pathfinder.Decision decision = pathfinder.DecideNext(MapManager.Get(Target.state.officeTileId)); switch (decision.type){ case Pathfinder.Decision.MoveType: Location = decision.nextTile!; @@ -65,10 +86,6 @@ public class LurkEnemy : Enemy { } private class LurkPathfinder : RoamingPathfinder { - // public override List FindPath(MapTile start, MapTile end) { - // throw new NotImplementedException(); - // } - private Random random = new(); private int tolerance; @@ -88,8 +105,8 @@ public class LurkEnemy : Enemy { if (Enemy.Location.GetConnector(goal)!.Blocked){ return Decision.Reset(); } - - return Decision.Attack(Server.P1); // TODO: un-hardcode this + + return Decision.Attack(((LurkEnemy)Enemy).Target); } if (closerTiles.Count != 0 && closerTiles.All(t => takenPath.Contains(t))){ diff --git a/FNAF_Server/Enemies/MareEnemy.cs b/FNAF_Server/Enemies/MareEnemy.cs new file mode 100644 index 0000000..ced2b3a --- /dev/null +++ b/FNAF_Server/Enemies/MareEnemy.cs @@ -0,0 +1,132 @@ +using FNAF_Server.Map; +using GlobalClassLib; +using PacketLib; + +namespace FNAF_Server.Enemies; + +public class MareEnemy : Enemy { + + public override string Name{ get; } = "Mare"; + public override int TypeId{ get; } = (int)EnemyType.MARE; + public override bool BlocksTile{ get; set; } = true; + + private MarePathfinder pathfinder; + private MovementOpportunity movementOpportunity; + + public ServerPlayer Target{ get; set; } + public MareEnemy(int difficulty) : base(difficulty) { + pathfinder = new MarePathfinder(this, 1); + if (Server.P1.state.power > Server.P2.state.power){ + Target = Server.P2; + } + else if(Server.P1.state.power < Server.P2.state.power){ + Target = Server.P1; + } + else{ + Target = new Random().Next(2) == 0 ? Server.P1 : Server.P2; + } + + movementOpportunity = new MovementOpportunity(5000); + SetDifficulty(difficulty); + } + + public override void Reset() { + pathfinder.takenPath.Clear(); + Target = Server.OtherPlayer(Target); + Pathfinder.Decision decision = pathfinder.DecideNext(MapManager.Get(Target.state.officeTileId)); + if (decision.type == Pathfinder.Decision.MoveType){ + Location = decision.nextTile!; + } + + Server.SendUpdateToAll([GameEvent.ENEMY_RESET(Id, Location.Id)]);; + } + + public override void SetDifficulty(int difficulty) { + Difficulty = difficulty; + movementOpportunity.MovementChance = + ((2 + Math.Pow(1.5f, Difficulty)) * Math.Sign(Difficulty)) / (2 + Math.Pow(1.5f, 10)); + } + + + public override void Spawn(MapTile location) { + base.Spawn(location); + // stopwatch.Start(); + + movementOpportunity.Start(); + pathfinder.takenPath.Add(Location); + Server.SendUpdateToAll([GameEvent.ENEMY_SPAWN(TypeId, Id, Difficulty, Location.Id)]); + } + + public override void Update() { + base.Update(); + + if (movementOpportunity.CheckAndRoll()){ + if (Location.Owner != null && Location.Lit){ + Attack(Location.Owner); + } + + Pathfinder.Decision decision = pathfinder.DecideNext(MapManager.Get(Target.state.officeTileId)); + 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 MarePathfinder : RoamingPathfinder { + private Random random = new(); + + private int tolerance; + public List takenPath = new(); + + public MarePathfinder(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(((MareEnemy)Enemy).Target); + } + + 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 index efd98f4..794b988 100644 --- a/FNAF_Server/Enemies/MovementOpportunity.cs +++ b/FNAF_Server/Enemies/MovementOpportunity.cs @@ -5,11 +5,15 @@ namespace FNAF_Server.Enemies; public class MovementOpportunity { public int Interval{ get; set; } // public double ChanceDenominator; - public double MovementChance{ get; set; } + public double MovementChance{ + get; + set{ + field = value; + GuaranteeSuccess(value >= 1); + } + } public bool Running => stopwatch.IsRunning; - - public bool ConstantChance{ get; private set; } private Stopwatch stopwatch = new(); private long stopwatchOffset = 0; @@ -19,13 +23,10 @@ public class MovementOpportunity { 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; } diff --git a/FNAF_Server/Enemies/NekoEnemy.cs b/FNAF_Server/Enemies/NekoEnemy.cs index 83a6518..0c41afe 100644 --- a/FNAF_Server/Enemies/NekoEnemy.cs +++ b/FNAF_Server/Enemies/NekoEnemy.cs @@ -10,17 +10,23 @@ public class NekoEnemy : Enemy { public NekoEnemy(int difficulty) : base(difficulty) { pathfinder = new NekoPathfinder(this, 1); + movementOpportunity = new MovementOpportunity(5000); + SetDifficulty(difficulty); + } public override string Name{ get; } = "Neko"; - public override int TypeId{ get; } = 1; + public override int TypeId{ get; } = (int)EnemyType.NEKO; public override bool BlocksTile{ get; set; } = true; + + public bool Aggressive = false; + + public ServerPlayer? Target{ get; set; } 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)]); @@ -30,14 +36,38 @@ public class NekoEnemy : Enemy { 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)]; + Aggressive = false; Server.SendUpdateToAll([GameEvent.ENEMY_RESET(Id, Location.Id)]); } + public override void SetDifficulty(int difficulty) { + Difficulty = difficulty; + movementOpportunity.MovementChance = + ((2 + Math.Pow(1.5f, Difficulty)) * Math.Sign(Difficulty)) / (2 + Math.Pow(1.5f, 10)); + } + public override void Update() { base.Update(); - if (movementOpportunity.CheckAndRoll()){ - Pathfinder.Decision decision = pathfinder.DecideNext(MapManager.Get(10)); + if (Location.Owner != null && Location.Lit){ + Aggressive = true; + Server.SendUpdateToAll([GameEvent.NEKO_ANGERED(Location.Owner.state.pid, Id)]); + } + + if (movementOpportunity.CheckAndRoll() || (Aggressive && GameLogic.NSecondUpdate)){ + if (Target == null){ + if (Server.P1.state.power > Server.P2.state.power){ + Target = Server.P2; + } + else if (Server.P1.state.power < Server.P2.state.power){ + Target = Server.P1; + } + else{ + return; + } + } + + Pathfinder.Decision decision = pathfinder.DecideNext(MapManager.Get(Target.state.officeTileId)); switch (decision.type){ case Pathfinder.Decision.MoveType: Location = decision.nextTile!; @@ -51,6 +81,7 @@ public class NekoEnemy : Enemy { Reset(); break; case Pathfinder.Decision.WaitType: + Aggressive = false; break; } } @@ -64,6 +95,7 @@ public class NekoEnemy : Enemy { public NekoPathfinder(Enemy enemy, int tolerance) : base(enemy) { this.tolerance = tolerance; + AdditionalTileFilter = t => ((NekoEnemy)Enemy).Aggressive || t.Owner == null || !t.Lit; } public override Decision DecideNext(MapTile goal) { @@ -71,13 +103,15 @@ public class NekoEnemy : Enemy { List closerTiles = GetNeighbours(Enemy.Location, c => !c.Blocked || c.Type == ConnectorType.DOOR_OFFICE, - t => distances.ContainsKey(t) && distances[t] <= distances[Enemy.Location] + tolerance); + 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 + return Decision.Attack(((NekoEnemy)Enemy).Target); } if (closerTiles.Count != 0 && closerTiles.All(t => takenPath.Contains(t))){ diff --git a/FNAF_Server/Enemies/RoamingPathfinder.cs b/FNAF_Server/Enemies/RoamingPathfinder.cs index 3aaf023..3b43dd2 100644 --- a/FNAF_Server/Enemies/RoamingPathfinder.cs +++ b/FNAF_Server/Enemies/RoamingPathfinder.cs @@ -3,7 +3,12 @@ using GlobalClassLib; namespace FNAF_Server.Enemies; -public abstract class RoamingPathfinder : Pathfinder{ +public abstract class RoamingPathfinder : Pathfinder { + + protected Predicate AdditionalTileFilter = _ => true; + protected Predicate AdditionalConnectorFilter = _ => true; + + // private Func defaultConnectorFilter; protected RoamingPathfinder(Enemy enemy) : base(enemy) { } @@ -15,8 +20,12 @@ public abstract class RoamingPathfinder : Pathfinder{ private void CrawlStep(MapTile tile, Dictionary distances) { List neighbours = GetNeighbours(tile, - c => (!c.Blocked || c.Type == ConnectorType.DOOR_OFFICE) && tile != Enemy.Location, + c => + AdditionalConnectorFilter(c) && + (!c.Blocked || c.Type == ConnectorType.DOOR_OFFICE) && + tile != Enemy.Location, t => + AdditionalTileFilter(t) && (!distances.ContainsKey(t) || distances[t] > distances[tile]) && (!Enemy.BlocksTile || EnemyManager.GetByLocation(t).All(e => !e.BlocksTile || e == Enemy)) && Server.Players.All(p => p.Value.state.officeTileId != t.Id)); diff --git a/FNAF_Server/Enemies/SpotEnemy.cs b/FNAF_Server/Enemies/SpotEnemy.cs index f86365f..7ea50ae 100644 --- a/FNAF_Server/Enemies/SpotEnemy.cs +++ b/FNAF_Server/Enemies/SpotEnemy.cs @@ -8,7 +8,8 @@ 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); + movementOpportunity = new(6000); + SetDifficulty(difficulty); } public override string Name{ get; } = "Spot"; @@ -30,6 +31,11 @@ public class SpotEnemy : Enemy { Server.SendUpdateToAll([GameEvent.ENEMY_RESET(Id, Location.Id)]); } + public override void SetDifficulty(int difficulty) { + Difficulty = difficulty; + movementOpportunity.MovementChance = (2 * Math.Sign(difficulty) + difficulty) / 12.0; + } + public override void Update() { if (GameLogic.NSecondUpdate){ if(!movementOpportunity.Running) diff --git a/FNAF_Server/FNAF_Server.csproj b/FNAF_Server/FNAF_Server.csproj index a8814b6..72aa55a 100644 --- a/FNAF_Server/FNAF_Server.csproj +++ b/FNAF_Server/FNAF_Server.csproj @@ -5,6 +5,7 @@ net9.0 enable enable + 14 diff --git a/FNAF_Server/GameLogic.cs b/FNAF_Server/GameLogic.cs index 88aa4d4..0cf9d65 100644 --- a/FNAF_Server/GameLogic.cs +++ b/FNAF_Server/GameLogic.cs @@ -60,13 +60,15 @@ public class GameLogic { Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = false, YourTiles = p1Tiles.ToArray(), OpponentTiles = p2Tiles.ToArray(), LitTiles = neutralTiles.ToArray()}, Server.P1.peer); Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = true, YourTiles = p2Tiles.ToArray(), OpponentTiles = p1Tiles.ToArray(), LitTiles = neutralTiles.ToArray()}, Server.P2.peer); - EnemyManager.AddEnemy(new SpotEnemy(0)).Spawn(MapManager.Get(12)); - EnemyManager.AddEnemy(new LurkEnemy(0)).Spawn(MapManager.Get(12)); - EnemyManager.AddEnemy(new NekoEnemy(10)).Spawn(MapManager.Get(2)); - Thread.Sleep(3000); secondCycleTimer.Start(); Server.SendUpdateToAll([GameEvent.GAME_START()]); + + EnemyManager.AddEnemy(new SpotEnemy(10)).Spawn(MapManager.Get(12)); + // EnemyManager.AddEnemy(new LurkEnemy(10)).Spawn(MapManager.Get(12)); + // EnemyManager.AddEnemy(new NekoEnemy(10)).Spawn(MapManager.Get(2)); + // EnemyManager.AddEnemy(new DashEnemy(10)).Spawn(MapManager.Get(12)); + // EnemyManager.AddEnemy(new MareEnemy(0)).Spawn(MapManager.Get(22)); } public static void Update() { if(secondCycleTimer.Check()) NSecondUpdate = true; diff --git a/FNAF_Server/Map/MapManager.cs b/FNAF_Server/Map/MapManager.cs index e7c0f6e..5104316 100644 --- a/FNAF_Server/Map/MapManager.cs +++ b/FNAF_Server/Map/MapManager.cs @@ -35,7 +35,7 @@ public static class MapManager { for (int j = 0; j < 2; j++){ map[i, j] = new MapTile(CoordsToId(i, j)){Owner = Server.P1}; } - map[i, 2] = new MapTile(CoordsToId(i, 2)); + map[i, 2] = new MapTile(CoordsToId(i, 2)){Lit = true}; for (int j = 3; j < 5; j++){ map[i, j] = new MapTile(CoordsToId(i, j)){Owner = Server.P2}; diff --git a/GlobalClassLib/GlobalEnemy.cs b/GlobalClassLib/GlobalEnemy.cs index f722a1d..5824c32 100644 --- a/GlobalClassLib/GlobalEnemy.cs +++ b/GlobalClassLib/GlobalEnemy.cs @@ -1,7 +1,6 @@ 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; } @@ -14,20 +13,19 @@ public abstract class GlobalEnemy where TTile : GlobalMapTile> PresentEnemies = new(); - public GlobalEnemy(int difficulty) { - Difficulty = difficulty; + public GlobalEnemy() { + Id = nextId++; } - public GlobalEnemy(int difficulty, int id) { - Difficulty = difficulty; + public GlobalEnemy(int id) { Id = id; } - public virtual void Spawn(TTile location) { + 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}"); + Console.WriteLine($"Enemy {Id} ({Name}) spawned at {(location == null ? "" : location.Id)} {(location == null ? "" : location.GridPosition)}"); } public virtual void Update() { } diff --git a/PacketLib/GameEvent.cs b/PacketLib/GameEvent.cs index 24586d3..76b0590 100644 --- a/PacketLib/GameEvent.cs +++ b/PacketLib/GameEvent.cs @@ -24,6 +24,7 @@ public struct GameEvent : INetSerializable{ public static GameEvent POWER_TICK(int pid, int power) => new(){ID = 13, Args = [pid, power]}; public static GameEvent POWER_OUT(int pid) => new(){ID = 14, Args = [pid]}; public static GameEvent TOGGLE_LIGHT(int pid, int camId, bool state) => new(){ID = 15, Args = [pid, camId, state ? 1 : 0]}; + public static GameEvent NEKO_ANGERED(int pid, int enemyId) => new(){ID = 16, Args = [pid, enemyId]}; public int ID{ get; set; } public bool Hideable => ID < 0;