Zvuky, změny v pathfindingu, přidány ventilace

This commit is contained in:
Perry 2026-03-19 20:10:45 +01:00
parent 4fdff0a0cc
commit c5adebb2db
36 changed files with 527 additions and 143 deletions

View file

@ -5,6 +5,7 @@
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADictionary_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5e56f40aa42e4fc4b94786ec57da7544d1a400_003F07_003F804b4506_003FDictionary_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F419320464d0849fb89a7e518f7ff8bc978a00_003Fe8_003F1faf66e3_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEventChannel_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F384e456bc26f45f5a6a6a20ae50c6e0dd1a400_003Ffb_003Fca10f160_003FEventChannel_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEventManifestOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffd792102e2a04b889c29784ca9de4981d1a000_003Fe5_003F92feb77a_003FEventManifestOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGame_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa69b121f85054691b2a8d28643003c23136600_003Fc7_003F517f8541_003FGame_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AINetSerializable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3d47105f47a240929625d0f531812b9e1c000_003F0f_003Fe2eeb2cd_003FINetSerializable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInputManager_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4e0fe18725844db38e9480edfd0e34983e00_003F41_003Fc6f0d8ab_003FInputManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>

View file

@ -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)]);
}
}

View file

@ -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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -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,6 +165,15 @@ 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;
}

View file

@ -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");
@ -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));
}

View file

@ -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<SpriteFont>("font");
}

164
FNAF_Clone/SoundManager.cs Normal file
View file

@ -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<SoundEffect>("sounds/ambience");
ventWalk = Core.content.Load<SoundEffect>("sounds/vent-walk");
camSwitch = Core.content.Load<SoundEffect>("sounds/camera-switch");
lightOff = Core.content.Load<SoundEffect>("sounds/light-off");
lightOn = Core.content.Load<SoundEffect>("sounds/light-on");
doorClose = Core.content.Load<SoundEffect>("sounds/door-close");
doorCloseRemote = Core.content.Load<SoundEffect>("sounds/door-close-remote");
doorOpen = Core.content.Load<SoundEffect>("sounds/door-open");
doorOpenRemote = Core.content.Load<SoundEffect>("sounds/door-open-remote");
monitorFlip = Core.content.Load<SoundEffect>("sounds/monitor-flip");
powerOut = Core.content.Load<SoundEffect>("sounds/powerout");
mareMove = Core.content.Load<SoundEffect>("sounds/mare-move");
dashMove = Core.content.Load<SoundEffect>("sounds/dash-move");
spotMove = Core.content.Load<SoundEffect>("sounds/spot-move");
spotActivate = Core.content.Load<SoundEffect>("sounds/spot-activate");
nekoPurr = Core.content.Load<SoundEffect>("sounds/neko-purr");
jumpscare = Core.content.Load<SoundEffect>("sounds/jumpscare");
nekoAnger = Core.content.Load<SoundEffect>("sounds/neko-angry");
nekoMove = Core.content.Load<SoundEffect>("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;
}
}
}

View file

@ -0,0 +1,5 @@
namespace FNAF_Server.Enemies;
public interface ITargetingEnemy {
ServerPlayer Target { get; set; }
}

View file

@ -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<MapTile> 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<MapTile, int> distances = Crawl(goal);
List<MapTile> 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<MapTile, int> distances = Crawl(goal);
//
// List<MapTile> 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();
// }
}
}

View file

@ -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 Random random = new();
private int tolerance;
public List<MapTile> takenPath = new();
public MarePathfinder(Enemy enemy, int tolerance) : base(enemy) {
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<MapTile, int> distances = Crawl(goal);
List<MapTile> 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<MapTile, int> distances = Crawl(goal);
//
// List<MapTile> 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();
// }
}
}

View file

@ -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);
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<MapTile> 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<MapTile, int> distances = Crawl(goal);
List<MapTile> 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<MapTile, int> distances = Crawl(goal);
//
// List<MapTile> 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();
// }
}
}

View file

@ -3,13 +3,18 @@ using GlobalClassLib;
namespace FNAF_Server.Enemies;
public abstract class RoamingPathfinder : Pathfinder {
public class RoamingPathfinder : Pathfinder {
protected Predicate<MapTile> AdditionalTileFilter = _ => true;
protected Predicate<TileConnector> AdditionalConnectorFilter = _ => true;
public Predicate<MapTile> AdditionalTileFilter{ get; set; } = _ => true;
public Predicate<TileConnector> AdditionalConnectorFilter{ get; set; } = _ => true;
protected int tolerance;
public List<MapTile> TakenPath { get; } = new();
private Random random = new();
// private Func<TileConnector, MapTile, bool> defaultConnectorFilter;
protected RoamingPathfinder(Enemy enemy) : base(enemy) {
public RoamingPathfinder(Enemy enemy, int tolerance) : base(enemy) {
this.tolerance = tolerance;
}
protected Dictionary<MapTile, int> 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<MapTile, int> distances = Crawl(goal);
List<MapTile> 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<ServerPlayer> 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();
}
}

View file

@ -21,6 +21,8 @@ public class GameLogic {
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;
// public static int P2Power{ 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;
@ -87,6 +89,11 @@ 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)]);
}

View file

@ -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)]
};
@ -30,6 +30,8 @@ public static class MapManager {
[(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++){
for (int j = 0; j < 2; j++){
@ -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() {

View file

@ -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;