diff --git a/FNAF_Clone.sln.DotSettings.user b/FNAF_Clone.sln.DotSettings.user index 5e3ead0..e95bd55 100644 --- a/FNAF_Clone.sln.DotSettings.user +++ b/FNAF_Clone.sln.DotSettings.user @@ -5,6 +5,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/FNAF_Clone/Client.cs b/FNAF_Clone/Client.cs index f37260a..b44d782 100644 --- a/FNAF_Clone/Client.cs +++ b/FNAF_Clone/Client.cs @@ -37,7 +37,7 @@ public class Client { public static ClientPlayer Opponent { get; } = new(); public static ClientPlayer GetPlayer(int pid) => Player.state.pid == pid ? Player : Opponent; - + public static void Connect(string endPoint, int port) { State = ConnectionState.CONNECTING; writer = new NetDataWriter(); diff --git a/FNAF_Clone/CommandManager.cs b/FNAF_Clone/CommandManager.cs index 6487893..8c77e92 100644 --- a/FNAF_Clone/CommandManager.cs +++ b/FNAF_Clone/CommandManager.cs @@ -34,6 +34,7 @@ public class CommandManager { Client.Player.state.monitorUp = !Client.Player.state.monitorUp; UIManager.ChangeMonitorState(Client.Player.state.monitorUp); Client.SendCommands([PlayerCommand.SET_MONITOR(Client.Player.state.monitorUp)]); + SoundManager.PlayMonitorFlip(); } private static void ToggleDoorLeft() => SendToggleDoor(Direction.EAST); @@ -54,6 +55,7 @@ public class CommandManager { 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]); + SoundManager.PlayDoor(Client.Player.state.doorStates[(int)direction]); Client.SendCommands([PlayerCommand.SET_DOOR_OFFICE(direction, Client.Player.state.doorStates[(int)direction])]); } @@ -63,6 +65,7 @@ public class CommandManager { connector.Blocked = !connector.Blocked; Client.SendCommands([PlayerCommand.SET_DOOR_REMOTE(connector.Id, connector.Blocked)]); + SoundManager.PlayDoorRemote(connector.Blocked); UIManager.ChangeRemoteDoorState(connector.Id, connector.Blocked); // Console.WriteLine("Changed door state to: " + (connector.Blocked ? "blocked" : "open") + " for door " + connector); // add UIManager toggle door @@ -73,6 +76,7 @@ public class CommandManager { Client.Player.state.camera = id; UIManager.ChangeCamera(id); + SoundManager.PlayCameraSwitch(); Client.SendCommands([PlayerCommand.SWITCH_CAM(id)]); MapTileProjection tile = ClientMapManager.Get(id); // add UIManager switch camera @@ -88,6 +92,7 @@ public class CommandManager { if(!Client.Player.state.monitorUp || ClientMapManager.Get(id).Owner != Client.Player) return; ClientMapManager.Get(id).Lit = state; UIManager.UpdateCameras([id]); + SoundManager.PlayLight(state); Client.SendCommands([PlayerCommand.SET_LIGHT(id, state)]); } } \ No newline at end of file diff --git a/FNAF_Clone/Content/Content.mgcb b/FNAF_Clone/Content/Content.mgcb index 32d5772..65915be 100644 --- a/FNAF_Clone/Content/Content.mgcb +++ b/FNAF_Clone/Content/Content.mgcb @@ -95,3 +95,123 @@ /processorParam:TextureFormat=Compressed /build:ponderosa.spritefont +#begin sounds/ambience.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/ambience.wav + +#begin sounds/camera-switch.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/camera-switch.wav + +#begin sounds/dash-move.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/dash-move.wav + +#begin sounds/dash-sounds-raw.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/dash-sounds-raw.wav + +#begin sounds/door-close-remote.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/door-close-remote.wav + +#begin sounds/door-close.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/door-close.wav + +#begin sounds/door-open-remote.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/door-open-remote.wav + +#begin sounds/door-open.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/door-open.wav + +#begin sounds/jumpscare.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/jumpscare.wav + +#begin sounds/light-off.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/light-off.wav + +#begin sounds/light-on.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/light-on.wav + +#begin sounds/mare-move.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/mare-move.wav + +#begin sounds/monitor-flip.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/monitor-flip.wav + +#begin sounds/neko-angry.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/neko-angry.wav + +#begin sounds/neko-move1.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/neko-move1.wav + +#begin sounds/neko-purr.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/neko-purr.wav + +#begin sounds/powerout.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/powerout.wav + +#begin sounds/spot-activate.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/spot-activate.wav + +#begin sounds/spot-move.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/spot-move.wav + +#begin sounds/vent-walk.wav +/importer:WavImporter +/processor:SoundEffectProcessor +/processorParam:Quality=Best +/build:sounds/vent-walk.wav + diff --git a/FNAF_Clone/Content/sounds/ambience.wav b/FNAF_Clone/Content/sounds/ambience.wav new file mode 100644 index 0000000..4750c1e Binary files /dev/null and b/FNAF_Clone/Content/sounds/ambience.wav differ diff --git a/FNAF_Clone/Content/sounds/camera-switch.wav b/FNAF_Clone/Content/sounds/camera-switch.wav new file mode 100644 index 0000000..4e53b39 Binary files /dev/null and b/FNAF_Clone/Content/sounds/camera-switch.wav differ diff --git a/FNAF_Clone/Content/sounds/dash-move.wav b/FNAF_Clone/Content/sounds/dash-move.wav new file mode 100644 index 0000000..727130b Binary files /dev/null and b/FNAF_Clone/Content/sounds/dash-move.wav differ diff --git a/FNAF_Clone/Content/sounds/dash-sounds-raw.wav b/FNAF_Clone/Content/sounds/dash-sounds-raw.wav new file mode 100644 index 0000000..76bf6ed Binary files /dev/null and b/FNAF_Clone/Content/sounds/dash-sounds-raw.wav differ diff --git a/FNAF_Clone/Content/sounds/door-close-remote.wav b/FNAF_Clone/Content/sounds/door-close-remote.wav new file mode 100644 index 0000000..736b61e Binary files /dev/null and b/FNAF_Clone/Content/sounds/door-close-remote.wav differ diff --git a/FNAF_Clone/Content/sounds/door-close.wav b/FNAF_Clone/Content/sounds/door-close.wav new file mode 100644 index 0000000..c284ed3 Binary files /dev/null and b/FNAF_Clone/Content/sounds/door-close.wav differ diff --git a/FNAF_Clone/Content/sounds/door-open-remote.wav b/FNAF_Clone/Content/sounds/door-open-remote.wav new file mode 100644 index 0000000..2fb60c9 Binary files /dev/null and b/FNAF_Clone/Content/sounds/door-open-remote.wav differ diff --git a/FNAF_Clone/Content/sounds/door-open.wav b/FNAF_Clone/Content/sounds/door-open.wav new file mode 100644 index 0000000..fea78ba Binary files /dev/null and b/FNAF_Clone/Content/sounds/door-open.wav differ diff --git a/FNAF_Clone/Content/sounds/jumpscare.wav b/FNAF_Clone/Content/sounds/jumpscare.wav new file mode 100644 index 0000000..09f7267 Binary files /dev/null and b/FNAF_Clone/Content/sounds/jumpscare.wav differ diff --git a/FNAF_Clone/Content/sounds/light-off.wav b/FNAF_Clone/Content/sounds/light-off.wav new file mode 100644 index 0000000..6c1f9ac Binary files /dev/null and b/FNAF_Clone/Content/sounds/light-off.wav differ diff --git a/FNAF_Clone/Content/sounds/light-on.wav b/FNAF_Clone/Content/sounds/light-on.wav new file mode 100644 index 0000000..a5b6b88 Binary files /dev/null and b/FNAF_Clone/Content/sounds/light-on.wav differ diff --git a/FNAF_Clone/Content/sounds/mare-move.wav b/FNAF_Clone/Content/sounds/mare-move.wav new file mode 100644 index 0000000..5965d1f Binary files /dev/null and b/FNAF_Clone/Content/sounds/mare-move.wav differ diff --git a/FNAF_Clone/Content/sounds/monitor-flip.wav b/FNAF_Clone/Content/sounds/monitor-flip.wav new file mode 100644 index 0000000..cde0929 Binary files /dev/null and b/FNAF_Clone/Content/sounds/monitor-flip.wav differ diff --git a/FNAF_Clone/Content/sounds/neko-angry.wav b/FNAF_Clone/Content/sounds/neko-angry.wav new file mode 100644 index 0000000..6a317ca Binary files /dev/null and b/FNAF_Clone/Content/sounds/neko-angry.wav differ diff --git a/FNAF_Clone/Content/sounds/neko-move1.wav b/FNAF_Clone/Content/sounds/neko-move1.wav new file mode 100644 index 0000000..52a53c8 Binary files /dev/null and b/FNAF_Clone/Content/sounds/neko-move1.wav differ diff --git a/FNAF_Clone/Content/sounds/neko-purr.wav b/FNAF_Clone/Content/sounds/neko-purr.wav new file mode 100644 index 0000000..9d9bb1b Binary files /dev/null and b/FNAF_Clone/Content/sounds/neko-purr.wav differ diff --git a/FNAF_Clone/Content/sounds/powerout.wav b/FNAF_Clone/Content/sounds/powerout.wav new file mode 100644 index 0000000..e69ce46 Binary files /dev/null and b/FNAF_Clone/Content/sounds/powerout.wav differ diff --git a/FNAF_Clone/Content/sounds/spot-activate.wav b/FNAF_Clone/Content/sounds/spot-activate.wav new file mode 100644 index 0000000..dfadecc Binary files /dev/null and b/FNAF_Clone/Content/sounds/spot-activate.wav differ diff --git a/FNAF_Clone/Content/sounds/spot-move.wav b/FNAF_Clone/Content/sounds/spot-move.wav new file mode 100644 index 0000000..aee503f Binary files /dev/null and b/FNAF_Clone/Content/sounds/spot-move.wav differ diff --git a/FNAF_Clone/Content/sounds/vent-walk.wav b/FNAF_Clone/Content/sounds/vent-walk.wav new file mode 100644 index 0000000..a256e06 Binary files /dev/null and b/FNAF_Clone/Content/sounds/vent-walk.wav differ diff --git a/FNAF_Clone/EventProcessor.cs b/FNAF_Clone/EventProcessor.cs index 21c86dc..c242c25 100644 --- a/FNAF_Clone/EventProcessor.cs +++ b/FNAF_Clone/EventProcessor.cs @@ -77,25 +77,29 @@ public class EventProcessor { 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]]); + SoundManager.PlayEnemyMove(ClientEnemyManager.Get(e.Args[0])); break; case 8: // attack Console.WriteLine($"E: Enemy {e.Args[0]} attacked player {e.Args[1]}"); // TODO: add an arg to indicate lethality if (e.Args[1] == Client.Player.state.pid) { UIManager.Jumpscare(ClientEnemyManager.Get(e.Args[0])); + SoundManager.PlayJumpscare(); } break; - case 9: + case 9: // reset 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]]); + SoundManager.PlayEnemyReset(ClientEnemyManager.Get(e.Args[0])); 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 : 1); + SoundManager.PlaySpotActivate(); break; case 11: @@ -103,10 +107,11 @@ public class EventProcessor { if(Client.Player.state.pid == e.Args[0]) UIManager.ShowVictoryScreen(); break; - case 12: + case 12: // game start Console.WriteLine("E: Game started"); UIManager.DisplayGameUI(); UIManager.StartTimer(); + SoundManager.StartAmbience(); break; case 13: @@ -140,6 +145,7 @@ public class EventProcessor { UIManager.ChangeDoorState(Direction.WEST, false); CommandManager.AllowGameControls(false); UIManager.ChangeMonitorState(false); + SoundManager.PlayPowerOut(); } else{ UIManager.ChangeDoorStateOpponent(Direction.EAST, false); @@ -159,8 +165,17 @@ public class EventProcessor { } ClientMapManager.Get(e.Args[1]).Lit = lightState; + UIManager.UpdateCameras([e.Args[1]]); break; - + + case 16: // neko anger + SoundManager.PlayNekoAnger(); + break; + + case 17: + SoundManager.PlayVentWalk(); + break; + } } } diff --git a/FNAF_Clone/GUI/UIManager.cs b/FNAF_Clone/GUI/UIManager.cs index 5cf1de3..99864f9 100644 --- a/FNAF_Clone/GUI/UIManager.cs +++ b/FNAF_Clone/GUI/UIManager.cs @@ -49,6 +49,8 @@ public class UIManager { private static InputListenerHook monitorSwitchHook; + private static bool fullBright = false; // Debug + public static void LoadAssets() { testAtlas = TextureAtlas.FromFile(Core.content, "images/testBlocks-definition.xml"); OfficeAtlas = TextureAtlas.FromFile(Core.content, "images/office-definition.xml"); @@ -202,7 +204,7 @@ public class UIManager { int stateInt = state ? 1 : 0; switch ((int)dir){ - case 0: + case 0: officeScreen["office_left"].SetTexture(stateInt); monitorScreen["p1-office-door-left"].SetTexture(stateInt); break; @@ -214,7 +216,7 @@ public class UIManager { officeScreen["office_right"].SetTexture(stateInt); monitorScreen["p1-office-door-right"].SetTexture(stateInt); break; - } + } } public static void ChangeDoorStateOpponent(Direction dir, bool state) { // TODO: overload to avoid excessive casting @@ -239,6 +241,7 @@ public class UIManager { public static void ChangeMonitorState(bool state) { Screen.SetScreen(state ? ScreenTypes.CAMERAS : ScreenTypes.OFFICE); + UpdateCameras([Client.Player.state.camera]); } public static void ChangeMonitorStateOpponent(bool state) { @@ -259,7 +262,7 @@ public class UIManager { } if (camIds.Contains(Client.Player.state.camera)){ - bool lit = ClientMapManager.Get(Client.Player.state.camera).Lit; + bool lit = ClientMapManager.Get(Client.Player.state.camera).Lit || fullBright; cameraView.Visible = lit; enemyElements.Values.Where(e => e.Visible).ToList().ForEach(e => e.Visible = false); ClientEnemy[] enemies = ClientEnemyManager.GetByLocation(ClientMapManager.Get(Client.Player.state.camera)); @@ -270,6 +273,13 @@ public class UIManager { enemyElement.Visible = true; enemyElement.SetTexture(lit); } + + if (!lit && Client.Player.state.monitorUp && enemies.Any(e => e.TypeId == (int)EnemyType.NEKO)){ + SoundManager.StartNekoPurr(); + } + else{ + SoundManager.StopNekoPurr(); + } } } @@ -293,6 +303,7 @@ public class UIManager { Screen.SetScreen(ScreenTypes.WIN); Screen.DisableOverlay(); CommandManager.AllowGameControls(false); + SoundManager.StopAmbience(); InputManager.AddListener(Keys.Space, DisplayMainMenu, InputTiming.PRESS, new InputListenerHook(true, true)); } @@ -300,6 +311,7 @@ public class UIManager { Screen.SetScreen(ScreenTypes.LOSE); Screen.DisableOverlay(); CommandManager.AllowGameControls(false); + SoundManager.StopAmbience(); InputManager.AddListener(Keys.Space, DisplayMainMenu, InputTiming.PRESS, new InputListenerHook(true, true)); } diff --git a/FNAF_Clone/GameMain.cs b/FNAF_Clone/GameMain.cs index 362a266..0b5cac1 100644 --- a/FNAF_Clone/GameMain.cs +++ b/FNAF_Clone/GameMain.cs @@ -29,6 +29,7 @@ public class GameMain() : Core("fnafkooo", 1280, 720, false) { protected override void LoadContent() { UIManager.LoadAssets(); + SoundManager.LoadSounds(); // spriteBatch = new SpriteBatch(GraphicsDevice); // font = Content.Load("font"); } diff --git a/FNAF_Clone/SoundManager.cs b/FNAF_Clone/SoundManager.cs new file mode 100644 index 0000000..28ce4fc --- /dev/null +++ b/FNAF_Clone/SoundManager.cs @@ -0,0 +1,164 @@ +using System; +using GlobalClassLib; +using Microsoft.Xna.Framework.Audio; +using MonoGameLibrary; + +namespace FNAF_Clone; + +public static class SoundManager { + private static SoundEffect ambience; + private static SoundEffect ventWalk; + private static SoundEffect camSwitch; + private static SoundEffect lightOff; + private static SoundEffect lightOn; + private static SoundEffect doorClose; + private static SoundEffect doorCloseRemote; + private static SoundEffect doorOpen; + private static SoundEffect doorOpenRemote; + private static SoundEffect monitorFlip; + private static SoundEffect powerOut; + private static SoundEffect mareMove; + private static SoundEffect dashMove; + private static SoundEffect spotMove; + private static SoundEffect spotActivate; + private static SoundEffect nekoPurr; + private static SoundEffect jumpscare; + private static SoundEffect nekoAnger; + private static SoundEffect nekoMove; + + private static SoundEffectInstance ambienceInstance; + private static SoundEffectInstance nekoPurrInstance; + + private static Random random = new(); + + public static void LoadSounds() { + ambience = Core.content.Load("sounds/ambience"); + ventWalk = Core.content.Load("sounds/vent-walk"); + camSwitch = Core.content.Load("sounds/camera-switch"); + lightOff = Core.content.Load("sounds/light-off"); + lightOn = Core.content.Load("sounds/light-on"); + doorClose = Core.content.Load("sounds/door-close"); + doorCloseRemote = Core.content.Load("sounds/door-close-remote"); + doorOpen = Core.content.Load("sounds/door-open"); + doorOpenRemote = Core.content.Load("sounds/door-open-remote"); + monitorFlip = Core.content.Load("sounds/monitor-flip"); + powerOut = Core.content.Load("sounds/powerout"); + mareMove = Core.content.Load("sounds/mare-move"); + dashMove = Core.content.Load("sounds/dash-move"); + spotMove = Core.content.Load("sounds/spot-move"); + spotActivate = Core.content.Load("sounds/spot-activate"); + nekoPurr = Core.content.Load("sounds/neko-purr"); + jumpscare = Core.content.Load("sounds/jumpscare"); + nekoAnger = Core.content.Load("sounds/neko-angry"); + nekoMove = Core.content.Load("sounds/neko-move1"); + + nekoPurrInstance = nekoPurr.CreateInstance(); + ambienceInstance = ambience.CreateInstance(); + } + + public static void StartAmbience() { + ambienceInstance = ambience.CreateInstance(); + ambienceInstance.IsLooped = true; + ambienceInstance.Volume = 0.75f; + ambienceInstance.Play(); + } + public static void StopAmbience() { + ambienceInstance.Stop(); + } + + public static void PlayDoor(bool doorState) { + if (doorState){ + doorClose.Play(); + } + else{ + doorOpen.Play(); + } + } + public static void PlayDoorRemote(bool doorState) { + if (doorState){ + doorCloseRemote.Play(); + } + else{ + doorOpenRemote.Play(); + } + } + + public static void PlayLight(bool lightState) { + if (lightState){ + lightOn.Play(); + } + else{ + lightOff.Play(); + } + } + + public static void PlayJumpscare() => jumpscare.Play(); + + public static void PlayPowerOut() => powerOut.Play(); + + public static void PlayNekoMove() => nekoMove.Play(); + + public static void PlayNekoAnger() => nekoAnger.Play(); + + public static void PlaySpotActivate() => spotActivate.Play(); + + public static void PlaySpotMove() => spotMove.Play(); + + public static void PlayDashMove() => dashMove.Play(); + + public static void PlayMareMove() => mareMove.Play(); + + public static void PlayMonitorFlip() => monitorFlip.Play(); + + public static void PlayCameraSwitch() => camSwitch.Play(); + + public static void PlayVentWalk() => ventWalk.Play(); + + public static void StartNekoPurr() { + nekoPurrInstance = nekoPurr.CreateInstance(); + nekoPurrInstance.IsLooped = true; + nekoPurrInstance.Play(); + } + + public static void StopNekoPurr() { + nekoPurrInstance.Stop(); + } + + private static SoundEffectInstance GetRandomisedPitchInstance(SoundEffect effect) { + SoundEffectInstance instance = effect.CreateInstance(); + instance.Pitch = (float)(random.NextDouble() - 0.5) / 5; + instance.Play(); + return instance; + } + + public static void PlayEnemyMove(ClientEnemy enemy) { + switch ((EnemyType)enemy.TypeId){ + case EnemyType.NEKO: + PlayNekoMove(); + break; + case EnemyType.SPOT: + PlaySpotMove(); + break; + case EnemyType.MARE: + PlayMareMove(); + break; + } + } + + public static void PlayEnemyReset(ClientEnemy enemy) { + switch ((EnemyType)enemy.TypeId){ + case EnemyType.NEKO: + PlayNekoMove(); + break; + case EnemyType.SPOT: + PlaySpotMove(); + break; + case EnemyType.MARE: + PlayMareMove(); + break; + case EnemyType.DASH: + PlayDashMove(); + break; + } + } +} \ No newline at end of file diff --git a/FNAF_Server/Enemies/ITargetingEnemy.cs b/FNAF_Server/Enemies/ITargetingEnemy.cs new file mode 100644 index 0000000..07be438 --- /dev/null +++ b/FNAF_Server/Enemies/ITargetingEnemy.cs @@ -0,0 +1,5 @@ +namespace FNAF_Server.Enemies; + +public interface ITargetingEnemy { + ServerPlayer Target { get; set; } +} \ No newline at end of file diff --git a/FNAF_Server/Enemies/LurkEnemy.cs b/FNAF_Server/Enemies/LurkEnemy.cs index 273581d..f7fe077 100644 --- a/FNAF_Server/Enemies/LurkEnemy.cs +++ b/FNAF_Server/Enemies/LurkEnemy.cs @@ -32,13 +32,13 @@ public class LurkEnemy : Enemy { base.Spawn(location); // stopwatch.Start(); movementOpportunity.Start(); - pathfinder.takenPath.Add(Location); + 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); + pathfinder.TakenPath.Clear(); + pathfinder.TakenPath.Add(Location); Target.state.power -= 30; @@ -69,6 +69,9 @@ public class LurkEnemy : Enemy { Pathfinder.Decision decision = pathfinder.DecideNext(MapManager.Get(Target.state.officeTileId)); switch (decision.type){ case Pathfinder.Decision.MoveType: + if (Location.GetConnector(decision.nextTile!)!.Type == ConnectorType.VENT){ + Server.SendUpdateToAll([GameEvent.VENT_USED()]); + } Location = decision.nextTile!; Server.SendUpdateToAll([GameEvent.ENEMY_MOVEMENT(Id, Location.Id)]); Console.WriteLine($"Enemy {TypeId} ({Name}) moving to {Location.PositionAsString})"); @@ -86,46 +89,43 @@ public class LurkEnemy : Enemy { } private class LurkPathfinder : RoamingPathfinder { - private Random random = new(); + // private Random random = new(); - private int tolerance; - public List takenPath = new(); - - public LurkPathfinder(Enemy enemy, int tolerance) : base(enemy) { + public LurkPathfinder(Enemy enemy, int tolerance) : base(enemy, tolerance) { 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(((LurkEnemy)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(); - } + // 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(((LurkEnemy)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/MareEnemy.cs b/FNAF_Server/Enemies/MareEnemy.cs index ced2b3a..91f9a1c 100644 --- a/FNAF_Server/Enemies/MareEnemy.cs +++ b/FNAF_Server/Enemies/MareEnemy.cs @@ -31,7 +31,7 @@ public class MareEnemy : Enemy { } public override void Reset() { - pathfinder.takenPath.Clear(); + pathfinder.TakenPath.Clear(); Target = Server.OtherPlayer(Target); Pathfinder.Decision decision = pathfinder.DecideNext(MapManager.Get(Target.state.officeTileId)); if (decision.type == Pathfinder.Decision.MoveType){ @@ -53,7 +53,7 @@ public class MareEnemy : Enemy { // stopwatch.Start(); movementOpportunity.Start(); - pathfinder.takenPath.Add(Location); + pathfinder.TakenPath.Add(Location); Server.SendUpdateToAll([GameEvent.ENEMY_SPAWN(TypeId, Id, Difficulty, Location.Id)]); } @@ -87,46 +87,44 @@ public class MareEnemy : Enemy { private class MarePathfinder : RoamingPathfinder { - private Random random = new(); - - private int tolerance; - public List takenPath = new(); - - public MarePathfinder(Enemy enemy, int tolerance) : base(enemy) { + // private Random random = new(); + + public MarePathfinder(Enemy enemy, int tolerance) : base(enemy, tolerance) { this.tolerance = tolerance; + AdditionalConnectorFilter = c => c.Type != ConnectorType.VENT; } - 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(); - } + // 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/NekoEnemy.cs b/FNAF_Server/Enemies/NekoEnemy.cs index 0c41afe..1f4b492 100644 --- a/FNAF_Server/Enemies/NekoEnemy.cs +++ b/FNAF_Server/Enemies/NekoEnemy.cs @@ -6,10 +6,10 @@ namespace FNAF_Server.Enemies; public class NekoEnemy : Enemy { private MovementOpportunity movementOpportunity; - private NekoPathfinder pathfinder; + private RoamingPathfinder pathfinder; - public NekoEnemy(int difficulty) : base(difficulty) { - pathfinder = new NekoPathfinder(this, 1); + public NekoEnemy(int difficulty) : base(difficulty) { + pathfinder = new RoamingPathfinder(this, 1){AdditionalTileFilter = t => Aggressive || t.Owner == null || !t.Lit}; movementOpportunity = new MovementOpportunity(5000); SetDifficulty(difficulty); @@ -28,15 +28,16 @@ public class NekoEnemy : Enemy { base.Spawn(location); // stopwatch.Start(); movementOpportunity.Start(); - pathfinder.takenPath.Add(Location); + pathfinder.TakenPath.Add(Location); Server.SendUpdateToAll([GameEvent.ENEMY_SPAWN(TypeId, Id, Difficulty, Location.Id)]); } public override void Reset() { - pathfinder.takenPath.Clear(); + 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; + Target = Server.OtherPlayer(Target); Server.SendUpdateToAll([GameEvent.ENEMY_RESET(Id, Location.Id)]); } @@ -49,7 +50,7 @@ public class NekoEnemy : Enemy { public override void Update() { base.Update(); - if (Location.Owner != null && Location.Lit){ + if (Location.Owner != null && Location.Lit && !Aggressive){ Aggressive = true; Server.SendUpdateToAll([GameEvent.NEKO_ANGERED(Location.Owner.state.pid, Id)]); } @@ -70,6 +71,9 @@ public class NekoEnemy : Enemy { Pathfinder.Decision decision = pathfinder.DecideNext(MapManager.Get(Target.state.officeTileId)); switch (decision.type){ case Pathfinder.Decision.MoveType: + if (Location.GetConnector(decision.nextTile!)!.Type == ConnectorType.VENT){ + Server.SendUpdateToAll([GameEvent.VENT_USED()]); + } Location = decision.nextTile!; Server.SendUpdateToAll([GameEvent.ENEMY_MOVEMENT(Id, Location.Id)]); Console.WriteLine($"Enemy {TypeId} ({Name}) moving to {Location.PositionAsString})"); @@ -88,49 +92,46 @@ public class NekoEnemy : Enemy { } private class NekoPathfinder : RoamingPathfinder { - private Random random = new(); + // private Random random = new(); - private int tolerance; - public List takenPath = new(); - - public NekoPathfinder(Enemy enemy, int tolerance) : base(enemy) { + public NekoPathfinder(Enemy enemy, int tolerance) : base(enemy, tolerance) { this.tolerance = tolerance; AdditionalTileFilter = t => ((NekoEnemy)Enemy).Aggressive || t.Owner == null || !t.Lit; } - 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(((NekoEnemy)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(); - } + // 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(((NekoEnemy)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/RoamingPathfinder.cs b/FNAF_Server/Enemies/RoamingPathfinder.cs index 3b43dd2..185c765 100644 --- a/FNAF_Server/Enemies/RoamingPathfinder.cs +++ b/FNAF_Server/Enemies/RoamingPathfinder.cs @@ -3,13 +3,18 @@ using GlobalClassLib; namespace FNAF_Server.Enemies; -public abstract class RoamingPathfinder : Pathfinder { +public class RoamingPathfinder : Pathfinder { - protected Predicate AdditionalTileFilter = _ => true; - protected Predicate AdditionalConnectorFilter = _ => true; + public Predicate AdditionalTileFilter{ get; set; } = _ => true; + public Predicate AdditionalConnectorFilter{ get; set; } = _ => true; + protected int tolerance; + public List TakenPath { get; } = new(); + + private Random random = new(); // private Func defaultConnectorFilter; - protected RoamingPathfinder(Enemy enemy) : base(enemy) { + public RoamingPathfinder(Enemy enemy, int tolerance) : base(enemy) { + this.tolerance = tolerance; } protected Dictionary Crawl(MapTile tile) { @@ -23,10 +28,10 @@ public abstract class RoamingPathfinder : Pathfinder { c => AdditionalConnectorFilter(c) && (!c.Blocked || c.Type == ConnectorType.DOOR_OFFICE) && - tile != Enemy.Location, + tile != Enemy.Location && + (!distances.ContainsKey(c.OtherTile(tile)) || distances[c.OtherTile(tile)] > distances[tile] + c.Value), 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)); @@ -36,4 +41,42 @@ public abstract class RoamingPathfinder : Pathfinder { neighbours.ForEach(t => CrawlStep(t, distances)); } } + + public override Decision DecideNext(MapTile goal) { + Dictionary distances = Crawl(goal); + + List closerTiles = GetNeighbours(Enemy.Location, + c => AdditionalConnectorFilter(c) && !c.Blocked || c.Type == ConnectorType.DOOR_OFFICE, + t => AdditionalTileFilter(t) && distances.ContainsKey(t) && distances[t] + t.GetConnector(Enemy.Location).Value <= distances[Enemy.Location] + tolerance); + if (closerTiles.Contains(goal)){ + if (Enemy.Location.GetConnector(goal)!.Blocked){ + return Decision.Reset(); + } + + IEnumerable players = Server.Players.Values.Where(p => p.state.officeTileId == goal.Id); + if (players.Count() == 1){ + return Decision.Attack(players.First()); + } + return Decision.Move(goal); + } + + 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){ + double value = 1.0 / distances[tile]; + if (roll <= value){ + TakenPath.Add(tile); + return Decision.Move(tile); + } + roll -= value; + } + + return Decision.Wait(); + } } \ No newline at end of file diff --git a/FNAF_Server/GameLogic.cs b/FNAF_Server/GameLogic.cs index 0cf9d65..6c2cef6 100644 --- a/FNAF_Server/GameLogic.cs +++ b/FNAF_Server/GameLogic.cs @@ -20,6 +20,8 @@ public class GameLogic { public static int OfficeDoorUsage{ get; set; } = 10; public static int RemoteDoorUsage{ get; set; } = 3; public static int LightUsage{ get; set; } = 2; + + private static bool unlimitedPower = false; // Debug // public const int POWER_MAX = 1000; // public static int P1Power{ get; set; } = Power.MAX_POWER_VALUE; @@ -64,11 +66,11 @@ public class GameLogic { secondCycleTimer.Start(); Server.SendUpdateToAll([GameEvent.GAME_START()]); - EnemyManager.AddEnemy(new SpotEnemy(10)).Spawn(MapManager.Get(12)); + // 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 NekoEnemy(10)).Spawn(MapManager.Get(2)); // EnemyManager.AddEnemy(new DashEnemy(10)).Spawn(MapManager.Get(12)); - // EnemyManager.AddEnemy(new MareEnemy(0)).Spawn(MapManager.Get(22)); + // EnemyManager.AddEnemy(new MareEnemy(10)).Spawn(MapManager.Get(22)); } public static void Update() { if(secondCycleTimer.Check()) NSecondUpdate = true; @@ -86,7 +88,12 @@ public class GameLogic { } // Server.SendUpdateToAll([GameEvent.POWER_OUT(player.state.pid)]); } - + + if (unlimitedPower){ // Debug + Server.P1.state.power = Power.MAX_POWER_VALUE; + Server.P2.state.power = Power.MAX_POWER_VALUE; + } + Server.SendUpdateToAll([GameEvent.POWER_TICK(Server.P1.state.pid, Server.P1.state.power), GameEvent.POWER_TICK(Server.P2.state.pid, Server.P2.state.power)]); } diff --git a/FNAF_Server/Map/MapManager.cs b/FNAF_Server/Map/MapManager.cs index 5104316..7c43ab1 100644 --- a/FNAF_Server/Map/MapManager.cs +++ b/FNAF_Server/Map/MapManager.cs @@ -19,7 +19,7 @@ public static class MapManager { [(4, 0)] =[(4, 1, 1, ConnectorType.DOOR_REMOTE)], [(0, 1)] =[(1, 1, 1, ConnectorType.HALL)], [(1, 1)] =[(1, 2, 1, ConnectorType.DOOR_REMOTE)], - [(2, 1)] =[(2, 2, 1, ConnectorType.HALL), (2, 0, 1, ConnectorType.DOOR_OFFICE)], + [(2, 1)] =[(2, 2, 2, ConnectorType.HALL), (2, 0, 1, ConnectorType.DOOR_OFFICE)], [(3, 1)] =[(3, 2, 1, ConnectorType.DOOR_REMOTE), (4, 1, 1, ConnectorType.HALL)] }; @@ -29,6 +29,8 @@ public static class MapManager { [(2,2)] = [(3,2,1)], [(3,2)] = [(4,2,1)] }; + + private static (int x, int y)[] ventTiles =[(0, 1), (4, 1), (2, 2), (0, 3), (4, 3)]; public static void InitMap() { for (int i = 0; i < 5; i++){ @@ -56,6 +58,15 @@ public static class MapManager { doors = GetAllConnectors().Where(c => c.Type == ConnectorType.DOOR_OFFICE || c.Type == ConnectorType.DOOR_REMOTE).ToList(); doorsP1 = doors.Where(c => c.Owner == Server.P1).ToList(); doorsP2 = doors.Where(c => c.Owner == Server.P2).ToList(); + + foreach (var tile1Coords in ventTiles){ + MapTile tile1 = map[tile1Coords.x, tile1Coords.y]; + foreach (var tile2Coords in ventTiles){ + MapTile tile2 = map[tile2Coords.x, tile2Coords.y]; + if(tile1.GetConnector(tile2) != null || tile1 == tile2) continue; + tile1.AddConnector(new TileConnector(tile2, ConnectorType.VENT, 2)); + } + } } public static TileConnector[] GetAllConnectors() { diff --git a/PacketLib/GameEvent.cs b/PacketLib/GameEvent.cs index 76b0590..8d07c33 100644 --- a/PacketLib/GameEvent.cs +++ b/PacketLib/GameEvent.cs @@ -25,6 +25,7 @@ public struct GameEvent : INetSerializable{ 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 static GameEvent VENT_USED() => new(){ID = 17}; public int ID{ get; set; } public bool Hideable => ID < 0;