První 3 monstra z plánovaných pěti. Kompletní pathfinding i zrcadlení do clienta. Útoky implementované nejsou. Lurk a Neko jsou hardcoded aby útočili na P1.
This commit is contained in:
parent
4484b127c5
commit
9bfe63a166
27 changed files with 772 additions and 47 deletions
|
|
@ -1,11 +1,15 @@
|
||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=_002Fhome_002Fperry_002FRiderProjects_002FFNAF_005FClone_002FFNAF_005FClone_002Flib_002FMonoGameLibrary_002Edll/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=_002Fhome_002Fperry_002FRiderProjects_002FFNAF_005FClone_002FFNAF_005FClone_002Flib_002FMonoGameLibrary_002Edll/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AContentManager_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa69b121f85054691b2a8d28643003c23136600_003F49_003F5cd111e0_003FContentManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4e0fe18725844db38e9480edfd0e34983e00_003Fb5_003F8dc24573_003FCore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4e0fe18725844db38e9480edfd0e34983e00_003Fb5_003F8dc24573_003FCore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<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_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_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_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_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_003AKeyValuePair_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F384e456bc26f45f5a6a6a20ae50c6e0dd1a400_003Fe8_003F7272ae72_003FKeyValuePair_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AKeyValuePair_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F384e456bc26f45f5a6a6a20ae50c6e0dd1a400_003Fe8_003F7272ae72_003FKeyValuePair_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AList_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5e56f40aa42e4fc4b94786ec57da7544d1a400_003Fe4_003F9c3fcf71_003FList_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANetManager_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3d47105f47a240929625d0f531812b9e1c000_003Fd6_003Fec041615_003FNetManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANetManager_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3d47105f47a240929625d0f531812b9e1c000_003Fd6_003Fec041615_003FNetManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANetSerializer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3d47105f47a240929625d0f531812b9e1c000_003Fcc_003F8a34584a_003FNetSerializer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANetSerializer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3d47105f47a240929625d0f531812b9e1c000_003Fcc_003F8a34584a_003FNetSerializer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5e56f40aa42e4fc4b94786ec57da7544d1a400_003Ffb_003Fa0fd6fc3_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5e56f40aa42e4fc4b94786ec57da7544d1a400_003Ffb_003Fa0fd6fc3_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATitleContainer_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa69b121f85054691b2a8d28643003c23136600_003F62_003F374a72ad_003FTitleContainer_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValueTuple_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5e56f40aa42e4fc4b94786ec57da7544d1a400_003F09_003F312b9770_003FValueTuple_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValueTuple_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F5e56f40aa42e4fc4b94786ec57da7544d1a400_003F09_003F312b9770_003FValueTuple_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||||
21
FNAF_Clone/ClientEnemy.cs
Normal file
21
FNAF_Clone/ClientEnemy.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
using FNAF_Clone.GUI;
|
||||||
|
using FNAF_Clone.Map;
|
||||||
|
using GlobalClassLib;
|
||||||
|
using MonoGameLibrary.Graphics;
|
||||||
|
|
||||||
|
namespace FNAF_Clone;
|
||||||
|
|
||||||
|
public class ClientEnemy : GlobalEnemy<MapTileProjection, TileConnectorProjection> {
|
||||||
|
public ClientEnemy(int typeId, string name, int id, UIElement sprite, int difficulty, MapTileProjection location) : base(difficulty, id) {
|
||||||
|
Name = name;
|
||||||
|
TypeId = typeId;
|
||||||
|
Sprite = sprite;
|
||||||
|
Location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
// public TextureRegion Sprite { get; set; }
|
||||||
|
|
||||||
|
public UIElement Sprite { get; set; }
|
||||||
|
public override string Name{ get; }
|
||||||
|
public override int TypeId{ get; }
|
||||||
|
}
|
||||||
53
FNAF_Clone/ClientEnemyManager.cs
Normal file
53
FNAF_Clone/ClientEnemyManager.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Linq;
|
||||||
|
using FNAF_Clone.GUI;
|
||||||
|
using FNAF_Clone.Map;
|
||||||
|
using GlobalClassLib;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace FNAF_Clone;
|
||||||
|
|
||||||
|
public class ClientEnemyManager {
|
||||||
|
private static Dictionary<int, ClientEnemy> enemies = new();
|
||||||
|
private static Point cameraCorner = new Point(64, 64);
|
||||||
|
|
||||||
|
public static void AddEnemy(ClientEnemy enemy) {
|
||||||
|
enemies.Add(enemy.Id, enemy);
|
||||||
|
UIManager.AddEnemySprite(enemy.Id, enemy.Sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddEnemyByTemplate(EnemyType type, int id, int difficulty, MapTileProjection location) {
|
||||||
|
switch (type){
|
||||||
|
case EnemyType.LURK:
|
||||||
|
AddEnemy(new ClientEnemy((int)type, "Lurk", id, new UIElement(UIManager.enemyAtlas[0], cameraCorner), difficulty, location));
|
||||||
|
break;
|
||||||
|
case EnemyType.NEKO:
|
||||||
|
AddEnemy(new ClientEnemy((int)type, "Neko", id, new UIElement(UIManager.enemyAtlas[1], cameraCorner), difficulty, location));
|
||||||
|
break;
|
||||||
|
case EnemyType.SPOT:
|
||||||
|
UIElement element =
|
||||||
|
new UIElement([UIManager.enemyAtlas[2], UIManager.enemyAtlas[3], UIManager.enemyAtlas[4], UIManager.enemyAtlas[5]], cameraCorner);
|
||||||
|
element.SetTexture(2);
|
||||||
|
AddEnemy(new ClientEnemy((int)type, "Spot", id, element, difficulty, location));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Move(int id, MapTileProjection tile) {
|
||||||
|
enemies[id].Location = tile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ClientEnemy Get(int id) => enemies[id];
|
||||||
|
|
||||||
|
public static ClientEnemy[] GetByLocation(MapTileProjection tile) {
|
||||||
|
List<ClientEnemy> output = new();
|
||||||
|
foreach (var e in enemies.Values){
|
||||||
|
if (e.Location == tile){
|
||||||
|
output.Add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -43,6 +43,8 @@ public class CommandManager {
|
||||||
SendToggleRemoteDoor(direction);
|
SendToggleRemoteDoor(direction);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (direction == Direction.SOUTH) return;
|
||||||
Client.Player.state.doorStates[(int)direction] = !Client.Player.state.doorStates[(int)direction];
|
Client.Player.state.doorStates[(int)direction] = !Client.Player.state.doorStates[(int)direction];
|
||||||
UIManager.ChangeDoorState(direction, Client.Player.state.doorStates[(int)direction]);
|
UIManager.ChangeDoorState(direction, Client.Player.state.doorStates[(int)direction]);
|
||||||
Client.SendCommands([PlayerCommand.SET_DOOR_OFFICE(direction, Client.Player.state.doorStates[(int)direction])]);
|
Client.SendCommands([PlayerCommand.SET_DOOR_OFFICE(direction, Client.Player.state.doorStates[(int)direction])]);
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,27 @@
|
||||||
|
|
||||||
#---------------------------------- Content ---------------------------------#
|
#---------------------------------- Content ---------------------------------#
|
||||||
|
|
||||||
|
#begin images/enemies-definition.xml
|
||||||
|
/copy:images/enemies-definition.xml
|
||||||
|
|
||||||
#begin images/monitor-definition.xml
|
#begin images/monitor-definition.xml
|
||||||
/copy:images/monitor-definition.xml
|
/copy:images/monitor-definition.xml
|
||||||
|
|
||||||
#begin images/office-definition.xml
|
#begin images/office-definition.xml
|
||||||
/copy:images/office-definition.xml
|
/copy:images/office-definition.xml
|
||||||
|
|
||||||
|
#begin images/SpriteSheet_enemies.png
|
||||||
|
/importer:TextureImporter
|
||||||
|
/processor:TextureProcessor
|
||||||
|
/processorParam:ColorKeyColor=255,0,255,255
|
||||||
|
/processorParam:ColorKeyEnabled=True
|
||||||
|
/processorParam:GenerateMipmaps=False
|
||||||
|
/processorParam:PremultiplyAlpha=True
|
||||||
|
/processorParam:ResizeToPowerOfTwo=False
|
||||||
|
/processorParam:MakeSquare=False
|
||||||
|
/processorParam:TextureFormat=Color
|
||||||
|
/build:images/SpriteSheet_enemies.png
|
||||||
|
|
||||||
#begin images/SpriteSheet_monitor.png
|
#begin images/SpriteSheet_monitor.png
|
||||||
/importer:TextureImporter
|
/importer:TextureImporter
|
||||||
/processor:TextureProcessor
|
/processor:TextureProcessor
|
||||||
|
|
|
||||||
BIN
FNAF_Clone/Content/images/SpriteSheet_enemies.png
Normal file
BIN
FNAF_Clone/Content/images/SpriteSheet_enemies.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
15
FNAF_Clone/Content/images/enemies-definition.xml
Executable file
15
FNAF_Clone/Content/images/enemies-definition.xml
Executable file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextureAtlas count="9">
|
||||||
|
<Texture>images/SpriteSheet_enemies</Texture>
|
||||||
|
<Regions>
|
||||||
|
<Region id = "0" name="lurk" x="0" y="0" width="240" height="240"/>
|
||||||
|
<Region id = "1" name="neko" x="0" y="240" width="240" height="240"/>
|
||||||
|
<Region id = "2" name="spot-awake-lit" x="0" y="480" width="240" height="240"/>
|
||||||
|
<Region id = "3" name="spot-awake-unlit" x="240" y="480" width="240" height="240"/>
|
||||||
|
<Region id = "4" name="spot-asleep-lit" x="0" y="720" width="240" height="240"/>
|
||||||
|
<Region id = "5" name="spot-asleep-unlit" x="240" y="720" width="240" height="240"/>
|
||||||
|
<Region id = "6" name="dash" x="0" y="960" width="240" height="240"/>
|
||||||
|
<Region id = "7" name="mare-lit" x="0" y="1200" width="240" height="240"/>
|
||||||
|
<Region id = "8" name="mare-unlit" x="240" y="1200" width="240" height="240"/>
|
||||||
|
</Regions>
|
||||||
|
</TextureAtlas>
|
||||||
|
|
@ -64,9 +64,30 @@ public class EventProcessor {
|
||||||
UIManager.ChangeRemoteDoorState((e.Args[1], e.Args[2]), e.Args[3] == 1);
|
UIManager.ChangeRemoteDoorState((e.Args[1], e.Args[2]), e.Args[3] == 1);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case -1: // movement
|
case 6: // spawn
|
||||||
throw new NotImplementedException();
|
Console.WriteLine($"E: Spawned enemy {e.Args[0]} at {e.Args[3]}");
|
||||||
|
ClientEnemyManager.AddEnemyByTemplate((EnemyType)e.Args[0], e.Args[1], e.Args[2], ClientMapManager.Get(e.Args[3]));
|
||||||
|
UIManager.UpdateCameras([e.Args[3]]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 7: // movement
|
||||||
|
Console.WriteLine($"E: Enemy {e.Args[0]} moved to {e.Args[1]}");
|
||||||
|
int oldPos = ClientEnemyManager.Get(e.Args[0]).Location!.Id;
|
||||||
|
ClientEnemyManager.Move(e.Args[0], ClientMapManager.Get(e.Args[1]));
|
||||||
|
UIManager.UpdateCameras([oldPos, e.Args[1]]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 9:
|
||||||
|
Console.WriteLine($"E: Enemy {e.Args[0]} reset to {e.Args[1]}");
|
||||||
|
int preResetPos = ClientEnemyManager.Get(e.Args[0]).Location!.Id;
|
||||||
|
ClientEnemyManager.Move(e.Args[0], ClientMapManager.Get(e.Args[1]));
|
||||||
|
UIManager.UpdateCameras([preResetPos, e.Args[1]]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 10:
|
||||||
|
Console.WriteLine($"E: Spot:{e.Args[0]} turned {(e.Args[1] == 1 ? "on" : " off")}");
|
||||||
|
ClientEnemyManager.Get(e.Args[0]).Sprite.SetTexture(e.Args[1] == 1 ? 0 : 2);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ public class Screen {
|
||||||
}
|
}
|
||||||
|
|
||||||
public UIElement this[string id] => elements[id];
|
public UIElement this[string id] => elements[id];
|
||||||
|
public UIElement TryGetElement(string id) => elements.TryGetValue(id, out var val) ? val : null;
|
||||||
|
|
||||||
public void AddElement(string id, UIElement element) {
|
public void AddElement(string id, UIElement element) {
|
||||||
elements.Add(id, element);
|
elements.Add(id, element);
|
||||||
|
|
@ -80,8 +81,6 @@ public class Screen {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update() {
|
public void Update() {
|
||||||
|
|
||||||
|
|
||||||
foreach (var keyValuePair in elements){
|
foreach (var keyValuePair in elements){
|
||||||
keyValuePair.Value.Update();
|
keyValuePair.Value.Update();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,9 +69,7 @@ public class UIElement {
|
||||||
|
|
||||||
currentTextureId = textureId;
|
currentTextureId = textureId;
|
||||||
}
|
}
|
||||||
public void Update() {
|
public virtual void Update() { }
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsWithinBounds(Point pos) {
|
public bool IsWithinBounds(Point pos) {
|
||||||
return pos.X >= Math.Min(screenSpaceBounds.Item1.X, screenSpaceBounds.Item2.X) && pos.X <= Math.Max(screenSpaceBounds.Item1.X, screenSpaceBounds.Item2.X) &&
|
return pos.X >= Math.Min(screenSpaceBounds.Item1.X, screenSpaceBounds.Item2.X) && pos.X <= Math.Max(screenSpaceBounds.Item1.X, screenSpaceBounds.Item2.X) &&
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using FNAF_Clone.Map;
|
using FNAF_Clone.Map;
|
||||||
using GlobalClassLib;
|
using GlobalClassLib;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
|
@ -21,15 +23,19 @@ public class UIManager {
|
||||||
private static TextureAtlas testAtlas;
|
private static TextureAtlas testAtlas;
|
||||||
private static TextureAtlas officeAtlas;
|
private static TextureAtlas officeAtlas;
|
||||||
private static TextureAtlas monitorAtlas;
|
private static TextureAtlas monitorAtlas;
|
||||||
|
public static TextureAtlas enemyAtlas;
|
||||||
|
|
||||||
public static int GlobalPixelMultiplier{ get; private set; }
|
public static int GlobalPixelMultiplier{ get; private set; }
|
||||||
|
|
||||||
private Dictionary<(int, int), UIElement> doorElements = new();
|
private Dictionary<(int, int), UIElement> doorElements = new();
|
||||||
|
private static Dictionary<int, UIElement> enemyElements = new();
|
||||||
public static void InitUI() {
|
public static void InitUI() {
|
||||||
GlobalPixelMultiplier = Core.graphicsDevice.Viewport.Height / 360;
|
GlobalPixelMultiplier = Core.graphicsDevice.Viewport.Height / 360;
|
||||||
|
|
||||||
testAtlas = TextureAtlas.FromFile(Core.content, "images/testBlocks-definition.xml");
|
testAtlas = TextureAtlas.FromFile(Core.content, "images/testBlocks-definition.xml");
|
||||||
officeAtlas = TextureAtlas.FromFile(Core.content, "images/office-definition.xml");
|
officeAtlas = TextureAtlas.FromFile(Core.content, "images/office-definition.xml");
|
||||||
monitorAtlas = TextureAtlas.FromFile(Core.content, "images/monitor-definition.xml");
|
monitorAtlas = TextureAtlas.FromFile(Core.content, "images/monitor-definition.xml");
|
||||||
|
enemyAtlas = TextureAtlas.FromFile(Core.content, "images/enemies-definition.xml");
|
||||||
|
|
||||||
Screen.AddScreens([officeScreen, monitorScreen]);
|
Screen.AddScreens([officeScreen, monitorScreen]);
|
||||||
Screen.SetScreen(ScreenTypes.OFFICE);
|
Screen.SetScreen(ScreenTypes.OFFICE);
|
||||||
|
|
@ -67,6 +73,7 @@ public class UIManager {
|
||||||
monitorScreen.AddElement("eye-player", new UIElement(monitorAtlas[24], monitorScreen["room"+Client.Player.state.camera].Bounds.Item1));
|
monitorScreen.AddElement("eye-player", new UIElement(monitorAtlas[24], monitorScreen["room"+Client.Player.state.camera].Bounds.Item1));
|
||||||
monitorScreen.AddElement("eye-opponent", new UIElement([monitorAtlas[23], monitorAtlas[22]], monitorScreen["room"+Client.Opponent.state.camera].Bounds.Item1));
|
monitorScreen.AddElement("eye-opponent", new UIElement([monitorAtlas[23], monitorAtlas[22]], monitorScreen["room"+Client.Opponent.state.camera].Bounds.Item1));
|
||||||
|
|
||||||
|
UpdateCameras([Client.Player.state.camera]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SpawnDoors(TileConnectorProjection[] doors) {
|
public static void SpawnDoors(TileConnectorProjection[] doors) {
|
||||||
|
|
@ -92,6 +99,13 @@ public class UIManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AddEnemySprite(int id, UIElement sprite) {
|
||||||
|
monitorScreen.AddElement($"enemy{id}", sprite);
|
||||||
|
enemyElements.Add(id, sprite);
|
||||||
|
sprite.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void ChangeDoorState(Direction dir, bool state) {
|
public static void ChangeDoorState(Direction dir, bool state) {
|
||||||
int stateInt = state ? 1 : 0;
|
int stateInt = state ? 1 : 0;
|
||||||
|
|
||||||
|
|
@ -141,12 +155,25 @@ public class UIManager {
|
||||||
|
|
||||||
public static void ChangeCamera(int id) {
|
public static void ChangeCamera(int id) {
|
||||||
monitorScreen["eye-player"].SetPosition(monitorScreen["room"+id].Bounds.Item1);
|
monitorScreen["eye-player"].SetPosition(monitorScreen["room"+id].Bounds.Item1);
|
||||||
|
UpdateCameras([id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void UpdateCameras(int[] camIds) {
|
||||||
|
if (camIds.Contains(Client.Player.state.camera)){
|
||||||
|
enemyElements.Values.Where(e => e.Visible).ToList().ForEach(e => e.Visible = false);
|
||||||
|
ClientEnemy[] enemies = ClientEnemyManager.GetByLocation(ClientMapManager.Get(Client.Player.state.camera));;
|
||||||
|
foreach (var enemy in enemies){
|
||||||
|
enemyElements.TryGetValue(enemy.Id, out var element);
|
||||||
|
if (element == null) continue;
|
||||||
|
element.Visible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void ChangeCameraOpponent(int id) {
|
public static void ChangeCameraOpponent(int id) {
|
||||||
monitorScreen["eye-opponent"].SetPosition(monitorScreen["room"+id].Bounds.Item1);
|
monitorScreen["eye-opponent"].SetPosition(monitorScreen["room"+id].Bounds.Item1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// private static Point GetRoomUIPos((int x, int y) pos) {
|
// private static Point GetRoomUIPos((int x, int y) pos) {
|
||||||
// return new Point(336 + (32 * pos.x), 144 + (32 * pos.y));
|
// return new Point(336 + (32 * pos.x), 144 + (32 * pos.y));
|
||||||
// }
|
// }
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ public class CommandProcessor {
|
||||||
switch (playerCommand.ID){
|
switch (playerCommand.ID){
|
||||||
case 0:
|
case 0:
|
||||||
Console.WriteLine($"C: Player {pid} switched to camera {playerCommand.Args[0]}");
|
Console.WriteLine($"C: Player {pid} switched to camera {playerCommand.Args[0]}");
|
||||||
|
currentPlayer.state.camera = playerCommand.Args[0];
|
||||||
Server.SendUpdateToAll([GameEvent.SWITCH_CAM(pid, playerCommand.Args[0])]);
|
Server.SendUpdateToAll([GameEvent.SWITCH_CAM(pid, playerCommand.Args[0])]);
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
|
|
@ -20,8 +21,10 @@ public class CommandProcessor {
|
||||||
Server.SendUpdateToAll([GameEvent.TOGGLE_MONITOR(pid, monitorState)]);
|
Server.SendUpdateToAll([GameEvent.TOGGLE_MONITOR(pid, monitorState)]);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
bool doorState = playerCommand.Args[1] == 1;
|
bool doorState = playerCommand.Args[1] == 1; // TODO: block office doors
|
||||||
currentPlayer.state.doorStates[playerCommand.Args[0]] = doorState;
|
currentPlayer.state.doorStates[playerCommand.Args[0]] = doorState;
|
||||||
|
MapManager.Get(currentPlayer.state.officeTileId).GetConnector(currentPlayer.state.neighbouringTiles[playerCommand.Args[0]]).Blocked = doorState;
|
||||||
|
|
||||||
Console.WriteLine($"C: Player {pid} {(doorState ? "closed" : "opened")} door {playerCommand.Args[0]}");
|
Console.WriteLine($"C: Player {pid} {(doorState ? "closed" : "opened")} door {playerCommand.Args[0]}");
|
||||||
Server.SendUpdateToAll([GameEvent.TOGGLE_DOOR_OFFICE(pid,playerCommand.Args[0] ,doorState)]);
|
Server.SendUpdateToAll([GameEvent.TOGGLE_DOOR_OFFICE(pid,playerCommand.Args[0] ,doorState)]);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
26
FNAF_Server/Enemies/Enemy.cs
Normal file
26
FNAF_Server/Enemies/Enemy.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
using FNAF_Server.Map;
|
||||||
|
using GlobalClassLib;
|
||||||
|
|
||||||
|
namespace FNAF_Server.Enemies;
|
||||||
|
|
||||||
|
public abstract class Enemy : GlobalEnemy<MapTile, TileConnector> {
|
||||||
|
protected Enemy(int difficulty) : base(difficulty) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract bool BlocksTile { get; set; }
|
||||||
|
public bool Spawned { get; set; }
|
||||||
|
|
||||||
|
public virtual void SpawnSilent(MapTile location) {
|
||||||
|
Console.WriteLine($"!!! Silent spawn not implemented for enemy {TypeId} ({Name}), reverting to regular spawn");
|
||||||
|
Spawn(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Spawn(MapTile location) {
|
||||||
|
base.Spawn(location);
|
||||||
|
Spawned = true;
|
||||||
|
// EnemyManager.AddEnemy(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void Reset();
|
||||||
|
public abstract void Attack(ServerPlayer player);
|
||||||
|
}
|
||||||
29
FNAF_Server/Enemies/EnemyManager.cs
Normal file
29
FNAF_Server/Enemies/EnemyManager.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
using FNAF_Server.Map;
|
||||||
|
|
||||||
|
namespace FNAF_Server.Enemies;
|
||||||
|
|
||||||
|
public class EnemyManager {
|
||||||
|
private static Dictionary<int, Enemy> enemies = new();
|
||||||
|
|
||||||
|
public static void Update() {
|
||||||
|
foreach (var pair in enemies){
|
||||||
|
if (pair.Value.Spawned) pair.Value.Update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Enemy AddEnemy(Enemy enemy) {
|
||||||
|
enemies.Add(enemy.Id, enemy);
|
||||||
|
return enemy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Enemy[] GetByLocation(MapTile tile) {
|
||||||
|
List<Enemy> output = new();
|
||||||
|
foreach (var e in enemies.Values){
|
||||||
|
if (e.Location == tile){
|
||||||
|
output.Add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
118
FNAF_Server/Enemies/LurkEnemy.cs
Normal file
118
FNAF_Server/Enemies/LurkEnemy.cs
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using FNAF_Server.Map;
|
||||||
|
using GlobalClassLib;
|
||||||
|
using PacketLib;
|
||||||
|
|
||||||
|
namespace FNAF_Server.Enemies;
|
||||||
|
|
||||||
|
public class LurkEnemy : Enemy {
|
||||||
|
public override string Name{ get; } = "Lurk";
|
||||||
|
public override int TypeId{ get; } = (int)EnemyType.LURK;
|
||||||
|
|
||||||
|
public override bool BlocksTile{ get; set; } = true;
|
||||||
|
|
||||||
|
private LurkPathfinder pathfinder;
|
||||||
|
|
||||||
|
// private int movementRollInterval = 5000;
|
||||||
|
// private static readonly double CHANCE_DENOMINATOR = 2 + Math.Pow(1.5f, 10); // d10 Lurk will have a 100% chance to move
|
||||||
|
// private double movementChance => (2 + Math.Pow(1.5f, Difficulty)) / CHANCE_DENOMINATOR; // chance scales exponentially using a constant multiplier
|
||||||
|
|
||||||
|
private MovementOpportunity movementOpportunity;
|
||||||
|
|
||||||
|
public LurkEnemy(int difficulty) : base(difficulty) {
|
||||||
|
pathfinder = new LurkPathfinder(this, 1);
|
||||||
|
}
|
||||||
|
public override void Spawn(MapTile location) {
|
||||||
|
base.Spawn(location);
|
||||||
|
// stopwatch.Start();
|
||||||
|
movementOpportunity = new MovementOpportunity(5000, ((2 + Math.Pow(1.5f, Difficulty)) * Math.Sign(Difficulty)) / (2 + Math.Pow(1.5f, 10)));
|
||||||
|
movementOpportunity.Start();
|
||||||
|
pathfinder.takenPath.Add(Location);
|
||||||
|
Server.SendUpdateToAll([GameEvent.ENEMY_SPAWN(TypeId, Id, Difficulty, Location.Id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Reset() {
|
||||||
|
pathfinder.takenPath.Clear();
|
||||||
|
pathfinder.takenPath.Add(Location);
|
||||||
|
|
||||||
|
MapTile[] resetLocations = new[]{MapManager.Get(7), MapManager.Get(12), MapManager.Get(17)}.Where(t => EnemyManager.GetByLocation(t).Length == 0).ToArray();
|
||||||
|
Location = resetLocations[new Random().Next(resetLocations.Length)];
|
||||||
|
Server.SendUpdateToAll([GameEvent.ENEMY_RESET(Id, Location.Id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Attack(ServerPlayer player) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override void Update() {
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (movementOpportunity.CheckAndRoll()){
|
||||||
|
Pathfinder.Decision decision = pathfinder.DecideNext(MapManager.Get(10));
|
||||||
|
switch (decision.type){
|
||||||
|
case Pathfinder.Decision.MoveType:
|
||||||
|
Location = decision.nextTile!;
|
||||||
|
Server.SendUpdateToAll([GameEvent.ENEMY_MOVEMENT(Id, Location.Id)]);
|
||||||
|
Console.WriteLine($"Enemy {TypeId} ({Name}) moving to {Location.PositionAsString})");
|
||||||
|
break;
|
||||||
|
case Pathfinder.Decision.AttackType:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
case Pathfinder.Decision.ResetType:
|
||||||
|
Reset();
|
||||||
|
break;
|
||||||
|
case Pathfinder.Decision.WaitType:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LurkPathfinder : RoamingPathfinder {
|
||||||
|
// public override List<MapTile> FindPath(MapTile start, MapTile end) {
|
||||||
|
// throw new NotImplementedException();
|
||||||
|
// }
|
||||||
|
|
||||||
|
private Random random = new();
|
||||||
|
|
||||||
|
private int tolerance;
|
||||||
|
public List<MapTile> takenPath = new();
|
||||||
|
|
||||||
|
public LurkPathfinder(Enemy enemy, int tolerance) : base(enemy) {
|
||||||
|
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(Server.P1); // TODO: un-hardcode this
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closerTiles.Count != 0 && closerTiles.All(t => takenPath.Contains(t))){
|
||||||
|
takenPath.Clear();
|
||||||
|
return DecideNext(goal);
|
||||||
|
}
|
||||||
|
closerTiles.RemoveAll(t => takenPath.Contains(t));
|
||||||
|
|
||||||
|
closerTiles.ForEach(t => distances[t] += Enemy.Location.GetConnector(t)!.Value);
|
||||||
|
double roll = random.NextDouble() * closerTiles.Sum(t => 1.0 / distances[t]);
|
||||||
|
foreach (var tile in closerTiles){
|
||||||
|
if (roll <= 1.0 / distances[tile]){
|
||||||
|
takenPath.Add(tile);
|
||||||
|
return Decision.Move(tile);
|
||||||
|
}
|
||||||
|
roll -= 1.0 / distances[tile];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decision.Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
60
FNAF_Server/Enemies/MovementOpportunity.cs
Normal file
60
FNAF_Server/Enemies/MovementOpportunity.cs
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace FNAF_Server.Enemies;
|
||||||
|
|
||||||
|
public class MovementOpportunity {
|
||||||
|
public int Interval{ get; set; }
|
||||||
|
// public double ChanceDenominator;
|
||||||
|
public double MovementChance{ get; set; }
|
||||||
|
|
||||||
|
public bool Running => stopwatch.IsRunning;
|
||||||
|
|
||||||
|
public bool ConstantChance{ get; private set; }
|
||||||
|
|
||||||
|
private Stopwatch stopwatch = new();
|
||||||
|
private int stopwatchOffset = 0;
|
||||||
|
|
||||||
|
private Random random = new();
|
||||||
|
|
||||||
|
public MovementOpportunity(int intervalMs, double movementChance) {
|
||||||
|
Interval = intervalMs;
|
||||||
|
MovementChance = movementChance;
|
||||||
|
GuaranteeSuccess(false);
|
||||||
|
ConstantChance = true; // unused
|
||||||
|
}
|
||||||
|
|
||||||
|
public MovementOpportunity(int intervalMs) {
|
||||||
|
Interval = intervalMs;
|
||||||
|
GuaranteeSuccess(true);
|
||||||
|
MovementChance = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start() {
|
||||||
|
stopwatch.Start();
|
||||||
|
}
|
||||||
|
public void Stop() {
|
||||||
|
stopwatch.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Check() {
|
||||||
|
if (stopwatch.ElapsedMilliseconds + stopwatchOffset >= Interval){
|
||||||
|
stopwatchOffset = (int)(stopwatch.ElapsedMilliseconds - Interval);
|
||||||
|
stopwatch.Restart();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Roll() => roll();
|
||||||
|
private Func<bool> roll = () => true;
|
||||||
|
|
||||||
|
public bool CheckAndRoll() {
|
||||||
|
if (Check()) return Roll();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void GuaranteeSuccess(bool value) {
|
||||||
|
roll = value ? () => true : () => random.NextDouble() <= MovementChance;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
106
FNAF_Server/Enemies/NekoEnemy.cs
Normal file
106
FNAF_Server/Enemies/NekoEnemy.cs
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
using FNAF_Server.Map;
|
||||||
|
using GlobalClassLib;
|
||||||
|
using PacketLib;
|
||||||
|
|
||||||
|
namespace FNAF_Server.Enemies;
|
||||||
|
|
||||||
|
public class NekoEnemy : Enemy {
|
||||||
|
private MovementOpportunity movementOpportunity;
|
||||||
|
private NekoPathfinder pathfinder;
|
||||||
|
|
||||||
|
public NekoEnemy(int difficulty) : base(difficulty) {
|
||||||
|
pathfinder = new NekoPathfinder(this, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name{ get; } = "Neko";
|
||||||
|
public override int TypeId{ get; } = 1;
|
||||||
|
public override bool BlocksTile{ get; set; } = true;
|
||||||
|
|
||||||
|
|
||||||
|
public override void Spawn(MapTile location) {
|
||||||
|
base.Spawn(location);
|
||||||
|
// stopwatch.Start();
|
||||||
|
movementOpportunity = new MovementOpportunity(5000, ((2 + Math.Pow(1.5f, Difficulty)) * Math.Sign(Difficulty)) / (2 + Math.Pow(1.5f, 10)));
|
||||||
|
movementOpportunity.Start();
|
||||||
|
pathfinder.takenPath.Add(Location);
|
||||||
|
Server.SendUpdateToAll([GameEvent.ENEMY_SPAWN(TypeId, Id, Difficulty, Location.Id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Reset() {
|
||||||
|
pathfinder.takenPath.Clear();
|
||||||
|
MapTile[] resetLocations = new[]{MapManager.Get(7), MapManager.Get(12), MapManager.Get(17)}.Where(t => EnemyManager.GetByLocation(t).Length == 0).ToArray();
|
||||||
|
Location = resetLocations[new Random().Next(resetLocations.Length)];
|
||||||
|
Server.SendUpdateToAll([GameEvent.ENEMY_RESET(Id, Location.Id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Attack(ServerPlayer player) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public override void Update() {
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (movementOpportunity.CheckAndRoll()){
|
||||||
|
Pathfinder.Decision decision = pathfinder.DecideNext(MapManager.Get(10));
|
||||||
|
switch (decision.type){
|
||||||
|
case Pathfinder.Decision.MoveType:
|
||||||
|
Location = decision.nextTile!;
|
||||||
|
Server.SendUpdateToAll([GameEvent.ENEMY_MOVEMENT(Id, Location.Id)]);
|
||||||
|
Console.WriteLine($"Enemy {TypeId} ({Name}) moving to {Location.PositionAsString})");
|
||||||
|
break;
|
||||||
|
case Pathfinder.Decision.AttackType:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
case Pathfinder.Decision.ResetType:
|
||||||
|
Reset();
|
||||||
|
break;
|
||||||
|
case Pathfinder.Decision.WaitType:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NekoPathfinder : RoamingPathfinder {
|
||||||
|
private Random random = new();
|
||||||
|
|
||||||
|
private int tolerance;
|
||||||
|
public List<MapTile> takenPath = new();
|
||||||
|
|
||||||
|
public NekoPathfinder(Enemy enemy, int tolerance) : base(enemy) {
|
||||||
|
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(Server.P1); // TODO: un-hardcode this
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closerTiles.Count != 0 && closerTiles.All(t => takenPath.Contains(t))){
|
||||||
|
takenPath.Clear();
|
||||||
|
return DecideNext(goal);
|
||||||
|
}
|
||||||
|
closerTiles.RemoveAll(t => takenPath.Contains(t));
|
||||||
|
|
||||||
|
closerTiles.ForEach(t => distances[t] += Enemy.Location.GetConnector(t)!.Value);
|
||||||
|
double roll = random.NextDouble() * closerTiles.Sum(t => 1.0 / distances[t]);
|
||||||
|
foreach (var tile in closerTiles){
|
||||||
|
if (roll <= 1.0 / distances[tile]){
|
||||||
|
takenPath.Add(tile);
|
||||||
|
return Decision.Move(tile);
|
||||||
|
}
|
||||||
|
roll -= 1.0 / distances[tile];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decision.Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
FNAF_Server/Enemies/Pathfinder.cs
Normal file
36
FNAF_Server/Enemies/Pathfinder.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
using FNAF_Server.Map;
|
||||||
|
|
||||||
|
namespace FNAF_Server.Enemies;
|
||||||
|
|
||||||
|
public abstract class Pathfinder {
|
||||||
|
public Enemy Enemy;
|
||||||
|
public Pathfinder(Enemy enemy) {
|
||||||
|
Enemy = enemy;
|
||||||
|
}
|
||||||
|
public abstract Decision DecideNext(MapTile goal);
|
||||||
|
// protected abstract Dictionary<MapTile, int> Crawl(MapTile tile); // fill Distances with all reachable tiles
|
||||||
|
|
||||||
|
protected virtual List<MapTile> GetNeighbours(MapTile tile, Predicate<TileConnector> connectorFilter, Predicate<MapTile> tileFilter) {
|
||||||
|
return tile.GetAllConnectors().Where(c => connectorFilter(c)).Select(c => c.OtherTile(tile)).Where(t => tileFilter(t)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Decision {
|
||||||
|
public int type;
|
||||||
|
|
||||||
|
public MapTile? nextTile;
|
||||||
|
public ServerPlayer? attackTarget;
|
||||||
|
|
||||||
|
private Decision() { }
|
||||||
|
|
||||||
|
public static Decision Move(MapTile tile) => new() { type = MoveType, nextTile = tile };
|
||||||
|
public static Decision Attack(ServerPlayer player) => new(){ type = AttackType, attackTarget = player };
|
||||||
|
public static Decision Reset() => new(){ type = ResetType };
|
||||||
|
public static Decision Wait() => new(){ type = WaitType };
|
||||||
|
|
||||||
|
public const int MoveType = 0;
|
||||||
|
public const int AttackType = 1;
|
||||||
|
public const int ResetType = 2;
|
||||||
|
public const int WaitType = 3;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
29
FNAF_Server/Enemies/RoamingPathfinder.cs
Normal file
29
FNAF_Server/Enemies/RoamingPathfinder.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
using FNAF_Server.Map;
|
||||||
|
using GlobalClassLib;
|
||||||
|
|
||||||
|
namespace FNAF_Server.Enemies;
|
||||||
|
|
||||||
|
public abstract class RoamingPathfinder : Pathfinder{
|
||||||
|
protected RoamingPathfinder(Enemy enemy) : base(enemy) {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Dictionary<MapTile, int> Crawl(MapTile tile) {
|
||||||
|
Dictionary<MapTile, int> distances = new(){ [tile] = 0 };
|
||||||
|
CrawlStep(tile, distances);
|
||||||
|
return distances;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CrawlStep(MapTile tile, Dictionary<MapTile, int> distances) {
|
||||||
|
List<MapTile> neighbours = GetNeighbours(tile,
|
||||||
|
c => (!c.Blocked || c.Type == ConnectorType.DOOR_OFFICE) && tile != Enemy.Location,
|
||||||
|
t =>
|
||||||
|
(!distances.ContainsKey(t) || distances[t] > distances[tile]) &&
|
||||||
|
(!Enemy.BlocksTile || EnemyManager.GetByLocation(t).All(e => !e.BlocksTile || e == Enemy)));
|
||||||
|
|
||||||
|
neighbours.ForEach(t => distances[t] = distances[tile] + tile.GetConnector(t)!.Value);
|
||||||
|
|
||||||
|
if (neighbours.Count != 0){
|
||||||
|
neighbours.ForEach(t => CrawlStep(t, distances));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
95
FNAF_Server/Enemies/SpotEnemy.cs
Normal file
95
FNAF_Server/Enemies/SpotEnemy.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
using FNAF_Server.Map;
|
||||||
|
using GlobalClassLib;
|
||||||
|
using PacketLib;
|
||||||
|
|
||||||
|
namespace FNAF_Server.Enemies;
|
||||||
|
|
||||||
|
public class SpotEnemy : Enemy {
|
||||||
|
public SpotEnemy(int difficulty) : base(difficulty) {
|
||||||
|
path = [MapManager.Get(10), MapManager.Get(11), MapManager.Get(12), MapManager.Get(13), MapManager.Get(14)];
|
||||||
|
pathId = 2;
|
||||||
|
movementOpportunity = new(6000, (2 * Math.Sign(difficulty) + difficulty) / 12.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Name{ get; } = "Spot";
|
||||||
|
public override int TypeId{ get; } = (int)EnemyType.SPOT;
|
||||||
|
public override bool BlocksTile{ get; set; } = false;
|
||||||
|
|
||||||
|
public bool Active{ get; set; } = false;
|
||||||
|
|
||||||
|
private MovementOpportunity movementOpportunity;
|
||||||
|
private MapTile[] path;
|
||||||
|
private int pathId;
|
||||||
|
|
||||||
|
private int p1WatchCounter = 0;
|
||||||
|
private int p2WatchCounter = 0;
|
||||||
|
|
||||||
|
public override void Reset() {
|
||||||
|
pathId = 2;
|
||||||
|
Location = path[pathId];
|
||||||
|
Server.SendUpdateToAll([GameEvent.ENEMY_RESET(Id, Location.Id)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Attack(ServerPlayer player) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Update() {
|
||||||
|
if (GameLogic.NSecondUpdate){
|
||||||
|
if(!movementOpportunity.Running)
|
||||||
|
movementOpportunity.Start();
|
||||||
|
|
||||||
|
if (Active){
|
||||||
|
if (Server.P1.state.monitorUp && Server.P1.state.camera == Location.Id) p1WatchCounter++;
|
||||||
|
if (Server.P2.state.monitorUp && Server.P2.state.camera == Location.Id) p2WatchCounter++;
|
||||||
|
|
||||||
|
Console.WriteLine($"P1: {p1WatchCounter} | P2: {p2WatchCounter}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (movementOpportunity.CheckAndRoll()){
|
||||||
|
if(!Active) {
|
||||||
|
Active = true;
|
||||||
|
movementOpportunity.Interval = 12_000;
|
||||||
|
movementOpportunity.GuaranteeSuccess(true);
|
||||||
|
Server.SendUpdateToAll([GameEvent.SPOT_SET_ACTIVE(Id, true)]);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
movementOpportunity.Interval = 6000;
|
||||||
|
movementOpportunity.GuaranteeSuccess(false);
|
||||||
|
movementOpportunity.Stop();
|
||||||
|
|
||||||
|
if (p1WatchCounter > p2WatchCounter){
|
||||||
|
pathId++;
|
||||||
|
if (Location.GetConnector(path[pathId])!.Blocked){
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
else if (pathId == path.Length - 1){
|
||||||
|
Attack(Server.P1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (p2WatchCounter > p1WatchCounter){
|
||||||
|
pathId--;
|
||||||
|
if (Location.GetConnector(path[pathId])!.Blocked){
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
else if (pathId == 0){
|
||||||
|
Attack(Server.P2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Location = path[pathId];
|
||||||
|
Active = false;
|
||||||
|
p1WatchCounter = 0;
|
||||||
|
p2WatchCounter = 0;
|
||||||
|
Server.SendUpdateToAll([GameEvent.ENEMY_MOVEMENT(Id, Location.Id), GameEvent.SPOT_SET_ACTIVE(Id, false)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Spawn(MapTile location) {
|
||||||
|
base.Spawn(location);
|
||||||
|
Server.SendUpdateToAll([GameEvent.ENEMY_SPAWN(TypeId, Id, Difficulty, Location.Id)]);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using FNAF_Server.Enemies;
|
||||||
using FNAF_Server.Map;
|
using FNAF_Server.Map;
|
||||||
using GlobalClassLib;
|
using GlobalClassLib;
|
||||||
using PacketLib;
|
using PacketLib;
|
||||||
|
|
@ -8,9 +10,18 @@ public class GameLogic {
|
||||||
|
|
||||||
public const int START_CAMERA = 12;
|
public const int START_CAMERA = 12;
|
||||||
|
|
||||||
|
private static MovementOpportunity secondCycleTimer = new(1000);
|
||||||
|
public static bool NSecondUpdate{ get; private set; } = false;
|
||||||
|
|
||||||
|
public static MapTile P1Office;
|
||||||
|
public static MapTile P2Office;
|
||||||
|
public static MapTile[] Offices =>[P1Office, P2Office];
|
||||||
|
|
||||||
public static void Init() {
|
public static void Init() {
|
||||||
// Create map
|
// Create map
|
||||||
MapManager.InitMap();
|
MapManager.InitMap();
|
||||||
|
P1Office = MapManager.Get(10);
|
||||||
|
P2Office = MapManager.Get(14);
|
||||||
|
|
||||||
//Send map to client
|
//Send map to client
|
||||||
TileConnector[] connectors = MapManager.GetAllConnectors();
|
TileConnector[] connectors = MapManager.GetAllConnectors();
|
||||||
|
|
@ -20,13 +31,21 @@ public class GameLogic {
|
||||||
connectorsConverted[i * 3 + 1] = connectors[i].Tiles.tile2.Id;
|
connectorsConverted[i * 3 + 1] = connectors[i].Tiles.tile2.Id;
|
||||||
connectorsConverted[i * 3 + 2] = (int)connectors[i].Type;
|
connectorsConverted[i * 3 + 2] = (int)connectors[i].Type;
|
||||||
}
|
}
|
||||||
Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = true}, Server.P1.peer);
|
Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = false}, Server.P1.peer);
|
||||||
Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = false}, Server.P2.peer);
|
Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = true}, Server.P2.peer);
|
||||||
|
|
||||||
|
// EnemyManager.AddEnemy(new LurkEnemy(0)).Spawn(MapManager.Get(12));
|
||||||
|
EnemyManager.AddEnemy(new NekoEnemy(10)).Spawn(MapManager.Get(2));
|
||||||
|
EnemyManager.AddEnemy(new SpotEnemy(0)).Spawn(MapManager.Get(12));
|
||||||
|
|
||||||
|
secondCycleTimer.Start();
|
||||||
}
|
}
|
||||||
public static void Update() {
|
public static void Update() {
|
||||||
|
if(secondCycleTimer.Check()) NSecondUpdate = true;
|
||||||
|
|
||||||
|
EnemyManager.Update();
|
||||||
|
|
||||||
|
NSecondUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -108,17 +108,25 @@ public class Server {
|
||||||
username = packet.username
|
username = packet.username
|
||||||
});
|
});
|
||||||
|
|
||||||
SendPacket(new JoinAcceptPacket { state = newPlayer.state }, peer);
|
|
||||||
|
|
||||||
if (Players.Count == 1){
|
if (Players.Count == 1){
|
||||||
|
newPlayer.state.officeTileId = 10;
|
||||||
|
newPlayer.state.neighbouringTiles = [5, 11, 15];
|
||||||
|
SendPacket(new JoinAcceptPacket { state = newPlayer.state }, peer);
|
||||||
P1 = newPlayer;
|
P1 = newPlayer;
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
newPlayer.state.officeTileId = 14;
|
||||||
|
newPlayer.state.neighbouringTiles = [19, 13, 9];
|
||||||
|
|
||||||
|
SendPacket(new JoinAcceptPacket { state = newPlayer.state }, peer);
|
||||||
P2 = newPlayer;
|
P2 = newPlayer;
|
||||||
|
|
||||||
SendPacket(new OpponentInitPacket{state = newPlayer.state}, P1.peer);
|
SendPacket(new OpponentInitPacket{state = newPlayer.state}, P1.peer);
|
||||||
SendPacket(new OpponentInitPacket{state = P1.state}, P2.peer);
|
SendPacket(new OpponentInitPacket{state = P1.state}, P2.peer);
|
||||||
GameLogic.Init(); // TODO: move this to the condition above to wait for the other player
|
GameLogic.Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) {
|
public static void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) {
|
||||||
|
|
@ -133,29 +141,12 @@ public class Server {
|
||||||
|
|
||||||
public static void OnCommandReceived(PlayerCommandPacket packet, NetPeer peer) {
|
public static void OnCommandReceived(PlayerCommandPacket packet, NetPeer peer) {
|
||||||
CommandProcessor.Evaluate(packet.commands, peer.Id);
|
CommandProcessor.Evaluate(packet.commands, peer.Id);
|
||||||
|
|
||||||
// PlayerCommand[] commands = packet.commands;
|
|
||||||
|
|
||||||
// foreach (var playerCommand in commands){
|
|
||||||
// switch (playerCommand.ID){
|
|
||||||
// case 0:
|
|
||||||
// Console.WriteLine($"C: Player {peer.Id} switched to camera {playerCommand.Args[0]}");
|
|
||||||
// SendUpdateToAll([GameEvent.SWITCH_CAM(peer.Id, playerCommand.Args[0])]);
|
|
||||||
// break;
|
|
||||||
// case 1:
|
|
||||||
// bool newState = !Players[(uint)peer.Id].state.monitorUp;
|
|
||||||
// Players[(uint)peer.Id].state.monitorUp = newState;
|
|
||||||
// Console.WriteLine($"C: Player {peer.Id} toggled camera {(newState ? "on" : "off")}");
|
|
||||||
// SendUpdateToAll([GameEvent.TOGGLE_MONITOR(peer.Id, newState)]);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Update() {
|
public static void Update() {
|
||||||
server.PollEvents();
|
server.PollEvents();
|
||||||
// Console.WriteLine("update");
|
// Console.WriteLine("update");
|
||||||
GameLogic.Update(); // TODO: add a parameter for player input
|
GameLogic.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Run(CancellationToken cancellationToken = default)
|
public static void Run(CancellationToken cancellationToken = default)
|
||||||
|
|
|
||||||
11
GlobalClassLib/EnemyType.cs
Normal file
11
GlobalClassLib/EnemyType.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
|
||||||
|
namespace GlobalClassLib;
|
||||||
|
|
||||||
|
public enum EnemyType {
|
||||||
|
LURK,
|
||||||
|
NEKO,
|
||||||
|
SPOT,
|
||||||
|
DASH,
|
||||||
|
MARE
|
||||||
|
}
|
||||||
35
GlobalClassLib/GlobalEnemy.cs
Normal file
35
GlobalClassLib/GlobalEnemy.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
namespace GlobalClassLib;
|
||||||
|
|
||||||
|
public abstract class GlobalEnemy<TTile, TCon> where TTile : GlobalMapTile<TCon, TTile> where TCon : GlobalTileConnector<TTile, TCon> {
|
||||||
|
public int Difficulty { get; set; }
|
||||||
|
public TTile? Location { get; set; }
|
||||||
|
public bool Visible { get; set; }
|
||||||
|
public abstract string Name{ get; }
|
||||||
|
|
||||||
|
public abstract int TypeId{ get; }
|
||||||
|
public int Id { get; }
|
||||||
|
|
||||||
|
private static int nextId = 0;
|
||||||
|
|
||||||
|
|
||||||
|
// public static Dictionary<TTile, GlobalEnemy<TTile, TCon>> PresentEnemies = new();
|
||||||
|
|
||||||
|
public GlobalEnemy(int difficulty) {
|
||||||
|
Difficulty = difficulty;
|
||||||
|
Id = nextId++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GlobalEnemy(int difficulty, int id) {
|
||||||
|
Difficulty = difficulty;
|
||||||
|
Id = id;
|
||||||
|
}
|
||||||
|
public virtual void Spawn(TTile location) {
|
||||||
|
// PresentEnemies.Add(location, this);
|
||||||
|
Location = location;
|
||||||
|
Visible = true;
|
||||||
|
Console.WriteLine($"Enemy {Id} ({Name}) spawned at {location.Id} {location.GridPosition}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Update() { }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
namespace GlobalClassLib;
|
namespace GlobalClassLib;
|
||||||
|
|
||||||
public abstract class GlobalMapTile<TCon, TTile> where TCon : GlobalTileConnector<TTile, TCon> where TTile : GlobalMapTile<TCon, TTile> { // TTile should be the inheriting class
|
public abstract class GlobalMapTile<TCon, TTile> where TCon : GlobalTileConnector<TTile, TCon> where TTile : GlobalMapTile<TCon, TTile> { // TTile should be the inheriting class
|
||||||
public int Id { get; private set; }
|
public int Id { get; }
|
||||||
public bool Lit { get; set; } = false;
|
public bool Lit { get; set; } = false;
|
||||||
public (int x, int y) GridPosition { get; private set; }
|
public (int x, int y) GridPosition { get; private set; }
|
||||||
|
|
||||||
|
|
@ -28,17 +28,22 @@ public abstract class GlobalMapTile<TCon, TTile> where TCon : GlobalTileConnecto
|
||||||
public void AddConnectors(TCon[] connectors) =>
|
public void AddConnectors(TCon[] connectors) =>
|
||||||
Array.ForEach(connectors, AddConnector);
|
Array.ForEach(connectors, AddConnector);
|
||||||
|
|
||||||
public override string ToString() => $"{PositionAsString} -> {string.Join(", ", connectors.Select(c => c.Tiles.Item2.PositionAsString))}";
|
public override string ToString() => $"[{Id}] -> {string.Join(", ", connectors.Select(c => $"[{c.OtherTile((TTile)this).Id}]"))}";
|
||||||
|
public override int GetHashCode() => Id.GetHashCode();
|
||||||
public string PositionAsString => $"[{Id}]"; // for debug purposes
|
public string PositionAsString => $"[{Id}]"; // for debug purposes
|
||||||
|
|
||||||
public TCon? GetConnector(int id) {
|
public TCon? GetConnector(int id) {
|
||||||
foreach (var con in connectors){
|
foreach (var con in connectors){
|
||||||
if (con.Tiles.Item2.Id == id) return con;
|
if (con.OtherTile((TTile)this).Id == id) return con;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
public TCon? GetConnector(TTile tile) {
|
||||||
|
foreach (var con in connectors){
|
||||||
|
if (con.OtherTile((TTile)this) == tile) return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TCon[] GetAllConnectors() => connectors.ToArray();
|
public TCon[] GetAllConnectors() => connectors.ToArray();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -6,16 +6,18 @@ namespace PacketLib;
|
||||||
#nullable disable
|
#nullable disable
|
||||||
public struct GameEvent : INetSerializable{
|
public struct GameEvent : INetSerializable{
|
||||||
public static GameEvent PLAYER_JOIN(int pid) => new(){ID = 0, Args = [pid] }; // TODO: username sync
|
public static GameEvent PLAYER_JOIN(int pid) => new(){ID = 0, Args = [pid] }; // TODO: username sync
|
||||||
public static GameEvent PLAYER_LEAVE(int pid) => new(){ID = 1, Args = [pid] };
|
public static GameEvent PLAYER_LEAVE(int pid) => new(){ID = 1, Args = [pid] }; // TODO: id constants
|
||||||
public static GameEvent SWITCH_CAM(int pid, int id) => new(){ID = 2, Args = [pid, id] };
|
public static GameEvent SWITCH_CAM(int pid, int id) => new(){ID = 2, Args = [pid, id] };
|
||||||
public static GameEvent TOGGLE_MONITOR(int pid, bool state) => new(){ID = 3, Args = [pid, state ? 1 : 0]};
|
public static GameEvent TOGGLE_MONITOR(int pid, bool state) => new(){ID = 3, Args = [pid, state ? 1 : 0]};
|
||||||
public static GameEvent TOGGLE_DOOR_OFFICE(int pid, int doorId, bool state) => new(){ID = 4, Args = [pid, doorId, state ? 1 : 0]};
|
public static GameEvent TOGGLE_DOOR_OFFICE(int pid, int doorId, bool state) => new(){ID = 4, Args = [pid, doorId, state ? 1 : 0]};
|
||||||
public static GameEvent TOGGLE_DOOR_REMOTE(int pid, (int, int) doorId, bool state) => new(){ID = 5, Args = [pid, doorId.Item1, doorId.Item2, state ? 1 : 0]};
|
public static GameEvent TOGGLE_DOOR_REMOTE(int pid, (int, int) doorId, bool state) => new(){ID = 5, Args = [pid, doorId.Item1, doorId.Item2, state ? 1 : 0]};
|
||||||
|
|
||||||
|
public static GameEvent ENEMY_SPAWN(int enemyTypeId, int enemyId, int difficulty, int camId) => new(){ ID = 6, Args = [enemyTypeId, enemyId, difficulty, camId] };
|
||||||
|
public static GameEvent ENEMY_MOVEMENT(int enemyId, int camId) => new(){ID = 7, Args = [enemyId, camId]};
|
||||||
|
public static GameEvent ENEMY_ATTACK(int enemyId, int pid) => new(){ ID = 8, Args =[enemyId, pid] };
|
||||||
|
public static GameEvent ENEMY_RESET(int enemyId, int camId) => new(){ID = 9, Args = [enemyId, camId]};
|
||||||
|
|
||||||
|
public static GameEvent SPOT_SET_ACTIVE(int enemyId, bool state) => new(){ ID = 10, Args = [enemyId, state ? 1 : 0] };
|
||||||
|
|
||||||
public static GameEvent ENEMY_MOVEMENT(int enemyId, int camId) => new(){ID = -1, Args = [enemyId, camId]};
|
|
||||||
|
|
||||||
public int ID{ get; set; }
|
public int ID{ get; set; }
|
||||||
public bool Hideable => ID < 0;
|
public bool Hideable => ID < 0;
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,22 @@ using LiteNetLib.Utils;
|
||||||
|
|
||||||
namespace PacketLib;
|
namespace PacketLib;
|
||||||
|
|
||||||
public struct PlayerState : INetSerializable {
|
public struct PlayerState : INetSerializable { // TODO: separate mutable and immutable data
|
||||||
public int pid;
|
public int pid;
|
||||||
public int camera;
|
public int camera;
|
||||||
public bool monitorUp;
|
public bool monitorUp;
|
||||||
|
|
||||||
|
public int officeTileId;
|
||||||
public bool[] doorStates;
|
public bool[] doorStates;
|
||||||
|
public int[] neighbouringTiles; // the indexes should correspond in both arrays
|
||||||
|
|
||||||
public void Serialize(NetDataWriter writer) {
|
public void Serialize(NetDataWriter writer) {
|
||||||
writer.Put(pid);
|
writer.Put(pid);
|
||||||
writer.Put(camera);
|
writer.Put(camera);
|
||||||
writer.Put(monitorUp);
|
writer.Put(monitorUp);
|
||||||
writer.PutArray(doorStates);
|
writer.PutArray(doorStates);
|
||||||
|
writer.Put(officeTileId);
|
||||||
|
writer.PutArray(neighbouringTiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Deserialize(NetDataReader reader) {
|
public void Deserialize(NetDataReader reader) {
|
||||||
|
|
@ -23,6 +26,8 @@ public struct PlayerState : INetSerializable {
|
||||||
camera = reader.GetInt();
|
camera = reader.GetInt();
|
||||||
monitorUp = reader.GetBool();
|
monitorUp = reader.GetBool();
|
||||||
doorStates = reader.GetBoolArray();
|
doorStates = reader.GetBoolArray();
|
||||||
|
officeTileId = reader.GetInt();
|
||||||
|
neighbouringTiles = reader.GetIntArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue