Eventy upraveny pro podporu dvou hráčů. Všechny dveře se zobrazují na správných pozicích pro oba hráče.

This commit is contained in:
Perry 2026-02-25 17:05:15 +01:00
parent cea56112ea
commit 3049417914
18 changed files with 157 additions and 75 deletions

View file

@ -5,4 +5,7 @@
<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_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_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></wpf:ResourceDictionary>
<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_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_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>

View file

@ -19,7 +19,9 @@ public class Client {
private static NetDataWriter writer;
private static NetPacketProcessor processor;
public static ClientPlayer Player { get; } = new();
public static ClientPlayer Opponent { get; } = new();
public static ClientPlayer GetPlayer(int pid) => Player.state.pid == pid ? Player : Opponent;
public static void Connect(string endPoint, int port) {
writer = new NetDataWriter();
@ -30,6 +32,7 @@ public class Client {
processor.SubscribeReusable<JoinAcceptPacket>(OnJoinAccept);
processor.SubscribeReusable<UpdatePlayerPacket>(OnPlayerUpdate);
processor.SubscribeReusable<MapInitPacket>(OnMapInit);
processor.SubscribeReusable<OpponentInitPacket>(packet => Opponent.state = packet.state);
client = new NetManager(listener){
AutoRecycle = true
@ -76,10 +79,13 @@ public class Client {
for (int i = 0; i < packet.Connectors.Length / 3; i++){
connectorsData[i] = (packet.Connectors[i * 3], packet.Connectors[i * 3 + 1], (ConnectorType)packet.Connectors[i * 3 + 2]);
}
ClientMapManager.InitMap();
ClientMapManager.InitMap(packet.UpsideDown);
TileConnectorProjection[] connectors = connectorsData.Select(c => new TileConnectorProjection(ClientMapManager.Get(c.id1), ClientMapManager.Get(c.id2), c.type)).ToArray();
ClientMapManager.InitConnectors(connectors);
UIManager.InitUI();
UIManager.SpawnDoors(connectors.Where(c => c.Type == ConnectorType.DOOR_REMOTE).ToArray());
}
public static void Update() {
@ -87,43 +93,10 @@ public class Client {
}
public static void OnPlayerUpdate(UpdatePlayerPacket packet) { // TODO: move this to a separate class
EventProcessor.Evaluate(packet.events);
//Player.state = Player.state.pid == 0 ? packet.stateP1 : packet.stateP2;
foreach (var e in packet.events){
switch (e.ID){
case 0: // join
Console.WriteLine("E: Player joined");
break;
case 1: // leave
Console.WriteLine("E: Player left");
break;
case 2: // switch cam
if (Player.state.pid != e.Args[0]) return;
if (Player.state.camera != e.Args[1]) Console.WriteLine("!!! DESYNC: CAMERA STATE");
Console.WriteLine($"E: player {e.Args[0]} switched to camera {e.Args[1]}");
break;
case 3: // toggle cam
if (Player.state.monitorUp != (e.Args[1] == 1)) Console.WriteLine("!!! DESYNC: MONITOR STATE");
UIManager.ChangeMonitorState(e.Args[1] == 1);
Console.WriteLine($"E: Player {e.Args[0]} toggled monitor {(e.Args[1] == 0 ? "off" : "on")}");
break;
case 4: // toggle door
if (Player.state.doorStates[e.Args[1]] != (e.Args[2] == 1)) Console.WriteLine("!!! DESYNC: DOOR STATE");
// UIManager.ChangeDoorState((Direction)e.Args[1], e.Args[2] == 1);
Console.WriteLine($"E: Player {e.Args[0]} {(e.Args[2] == 1 ? "closed" : "opened")} door {e.Args[1]}");
break;
case 5: // toggle remote door
if (ClientMapManager.GetConnector((e.Args[1], e.Args[2])).Blocked != (e.Args[3] == 1)) Console.WriteLine("!!! DESYNC: REMOTE DOOR STATE");
Console.WriteLine($"E: Player {e.Args[0]} {(e.Args[3] == 1 ? "closed" : "opened")} door {e.Args[1]}-{e.Args[2]}");
break;
case -1: // movement
throw new NotImplementedException();
}
}
}

View file

@ -27,6 +27,7 @@ public class CommandManager {
private static void SendToggleCamera() {
Client.Player.state.monitorUp = !Client.Player.state.monitorUp;
UIManager.ChangeMonitorState(Client.Player.state.monitorUp);
Client.SendCommands([PlayerCommand.SET_MONITOR(Client.Player.state.monitorUp)]);
}

View file

@ -0,0 +1,64 @@
using System;
using FNAF_Clone.GUI;
using FNAF_Clone.Map;
using GlobalClassLib;
using PacketLib;
namespace FNAF_Clone;
public class EventProcessor {
public static void Evaluate(GameEvent[] events) {
foreach (var e in events){
switch (e.ID){
case 0: // join
Console.WriteLine("E: Player joined");
break;
case 1: // leave
Console.WriteLine("E: Player left");
break;
case 2: // switch cam
if (Client.Player.state.pid != e.Args[0]) return;
if (Client.Player.state.camera != e.Args[1]) Console.WriteLine("!!! DESYNC: CAMERA STATE");
Console.WriteLine($"E: player {e.Args[0]} switched to camera {e.Args[1]}");
break;
case 3: // toggle cam
if (e.Args[0] == Client.Player.state.pid && Client.Player.state.monitorUp != (e.Args[1] == 1)) Console.WriteLine("!!! DESYNC: MONITOR STATE");
// UIManager.ChangeMonitorState(e.Args[1] == 1);
Console.WriteLine($"E: Player {e.Args[0]} toggled monitor {(e.Args[1] == 0 ? "off" : "on")}");
break;
case 4: // toggle door
Console.WriteLine($"E: Player {e.Args[0]} {(e.Args[2] == 1 ? "closed" : "opened")} door {e.Args[1]}");
if (e.Args[0] == Client.Player.state.pid){
if (Client.Player.state.doorStates[e.Args[1]] != (e.Args[2] == 1)) Console.WriteLine("!!! DESYNC: DOOR STATE");
break;
}
Client.Opponent.state.doorStates[e.Args[1]] = e.Args[2] == 1;
UIManager.ChangeDoorStateOpponent((Direction)e.Args[1], e.Args[2] == 1);
break;
case 5: // toggle remote door
Console.WriteLine($"E: Player {e.Args[0]} {(e.Args[3] == 1 ? "closed" : "opened")} door {e.Args[1]}-{e.Args[2]}");
if (e.Args[0] == Client.Player.state.pid){
if (ClientMapManager.GetConnector((e.Args[1], e.Args[2])).Blocked != (e.Args[3] == 1)) Console.WriteLine("!!! DESYNC: REMOTE DOOR STATE");
break;
}
ClientMapManager.GetConnector((e.Args[1], e.Args[2])).Blocked = e.Args[3] == 1;
UIManager.ChangeRemoteDoorState((e.Args[1], e.Args[2]), e.Args[3] == 1);
break;
case -1: // movement
throw new NotImplementedException();
}
}
}
}

View file

@ -9,7 +9,7 @@ namespace FNAF_Clone.GUI;
public class Screen {
public static Dictionary<string, Screen> Screens = new();
public static Screen CurrentScreen{ get; private set; }
public static Screen CurrentScreen{ get; private set; } = Empty;
public static void AddScreens(Screen[] screens) {
foreach (var screen in screens){
@ -33,6 +33,8 @@ public class Screen {
Screens.Remove(id);
}
public static Screen Empty => new("");
public string Label;
private Dictionary<string, UIElement> elements = new();

View file

@ -55,8 +55,8 @@ public class UIManager {
int j1 = j;
Point point1 = new Point(336 + (32 * i), 144 + (32 * j));
Point point2 = new Point(367 + (32 * i), 175 + (32 * j));
monitorScreen.AddElement($"room{MapTileProjection.CoordsToId(i, 4 - j)}", new UIElement(point1, point2)
{Pressable = true, OnMousePress = (() => CommandManager.SendChangeCamera(MapTileProjection.CoordsToId(i1, 4 - j1)))});
monitorScreen.AddElement($"room{ClientMapManager.CoordsToId(i, 4 - j)}", new UIElement(point1, point2)
{Pressable = true, OnMousePress = (() => CommandManager.SendChangeCamera(ClientMapManager.Get((i1, 4 - j1)).Id))});
//
// if (doorPositions.ContainsKey((i, j))){
// monitorScreen.AddElement("door"+doorPositions[(i, j)], new UIElement([monitorAtlas[5], monitorAtlas[6]], point1));
@ -75,7 +75,7 @@ public class UIManager {
int targetId = door.Tiles.tile1.GridPosition.y > door.Tiles.tile2.GridPosition.y ? door.Tiles.tile1.Id : door.Tiles.tile2.Id;
UIElement tile = monitorScreen["room"+targetId];
monitorScreen.AddElement("door"+targetId+"-"+(targetId == door.Tiles.tile1.Id ? door.Tiles.tile2.Id : door.Tiles.tile1.Id), new UIElement([monitorAtlas[5], monitorAtlas[6]], tile.Bounds.Item1));
monitorScreen.AddElement("door"+Math.Max(door.Tiles.tile1.Id, door.Tiles.tile2.Id)+"-"+Math.Min(door.Tiles.tile1.Id, door.Tiles.tile2.Id), new UIElement([monitorAtlas[5], monitorAtlas[6]], tile.Bounds.Item1));
}
}
@ -88,7 +88,7 @@ public class UIManager {
}
public static void ChangeDoorState(Direction dir, bool state) { // TODO: make this also change for p2
public static void ChangeDoorState(Direction dir, bool state) {
int stateInt = state ? 1 : 0;
switch ((int)dir){
@ -107,6 +107,22 @@ public class UIManager {
}
}
public static void ChangeDoorStateOpponent(Direction dir, bool state) { // TODO: overload to avoid excessive casting
int stateInt = state ? 1 : 0;
switch ((int)dir){
case 0:
monitorScreen["p2-office-door-left"].SetTexture(stateInt);
break;
case 1:
monitorScreen["p2-office-door-centre"].SetTexture(stateInt);
break;
case 2:
monitorScreen["p2-office-door-right"].SetTexture(stateInt);
break;
}
}
public static void ChangeRemoteDoorState((int, int) id, bool state) {
monitorScreen["door"+Math.Max(id.Item1, id.Item2)+"-"+Math.Min(id.Item1, id.Item2)].SetTexture(state ? 1 : 0);
}

View file

@ -24,7 +24,7 @@ public class GameMain() : Core("fnafkooo", 1280, 720, false) {
base.Initialize();
UIManager.InitUI();
// UIManager.InitUI();
}
protected override void LoadContent() {

View file

@ -1,3 +1,5 @@
using System;
using System.Runtime.InteropServices.JavaScript;
using GlobalClassLib;
namespace FNAF_Clone.Map;
@ -5,18 +7,27 @@ namespace FNAF_Clone.Map;
public class ClientMapManager {
private static MapTileProjection[,] map = new MapTileProjection[5, 5];
public static MapTileProjection Get((int x, int y) coords) => map[coords.x, coords.y];
public static MapTileProjection Get(int tileId) => Get(MapTileProjection.IdToCoords(tileId));
public static void InitMap() {
public static MapTileProjection Get(int tileId) => Get(IdToCoords(tileId));
private static bool inverted;
public static void InitMap(bool invert = false) {
inverted = invert;
IdToCoords = invert ? _IdToCoordsInverse : _IdToCoords;
CoordsToId = invert ? _CoordsToIdInverse : _CoordsToId;
for (int i = 0; i < 5; i++){
for (int j = 0; j < 2; j++){
map[i, j] = new MapTileProjection(MapTileProjection.CoordsToId(i, j)); // TODO: implement ownership
map[i, j] = new MapTileProjection(CoordsToId(i, j)); // TODO: implement ownership
}
map[i, 2] = new MapTileProjection(MapTileProjection.CoordsToId(i, 2));
map[i, 2] = new MapTileProjection(CoordsToId(i, 2));
for (int j = 3; j < 5; j++){
map[i, j] = new MapTileProjection(MapTileProjection.CoordsToId(i, j));
map[i, j] = new MapTileProjection(CoordsToId(i, j));
}
}
}
public static void InitConnectors(TileConnectorProjection[] connectors) {
@ -29,5 +40,16 @@ public class ClientMapManager {
public static TileConnectorProjection GetConnector((int, int) id) => Get(id.Item1).GetConnector(id.Item2);
public static TileConnectorProjection[] GetConnectors(int tileId) => Get(MapTileProjection.IdToCoords(tileId)).GetAllConnectors();
public static TileConnectorProjection[] GetConnectors(int tileId) => Get(IdToCoords(tileId)).GetAllConnectors();
public const int ID_X_OFFSET = 5; // map grid height
public static Func<int, int, int> CoordsToId{ get; private set; }
public static Func<int, (int x, int y)> IdToCoords{ get; private set; }
private static Func<int, (int x, int y)> _IdToCoords = id => (id / ID_X_OFFSET, id % ID_X_OFFSET);
private static Func<int, (int x, int y)> _IdToCoordsInverse = id => (ID_X_OFFSET - 1 - (id / ID_X_OFFSET), ID_X_OFFSET - 1 - (id % ID_X_OFFSET));
private static Func<int, int, int> _CoordsToId = (x, y) => x * ID_X_OFFSET + y;
private static Func<int, int, int> _CoordsToIdInverse = (x, y) => (ID_X_OFFSET - 1 - x) * ID_X_OFFSET + (ID_X_OFFSET - 1 - y);
}

View file

@ -3,6 +3,6 @@ using GlobalClassLib;
namespace FNAF_Clone.Map;
public class MapTileProjection : GlobalMapTile<TileConnectorProjection, MapTileProjection> {
public MapTileProjection(int id) : base(id) {
public MapTileProjection(int id) : base(id, ClientMapManager.IdToCoords(id)) {
}
}

View file

@ -18,8 +18,8 @@ public class GameLogic {
connectorsConverted[i * 3 + 1] = connectors[i].Tiles.tile2.Id;
connectorsConverted[i * 3 + 2] = (int)connectors[i].Type;
}
Server.SendPacketToAll(new MapInitPacket{Connectors = connectorsConverted});
Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = true}, Server.P1.peer);
Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = false}, Server.P2.peer);
}
public static void Update() {

View file

@ -6,7 +6,7 @@ public static class MapManager {
private static MapTile[,] map = new MapTile[5, 5];
public static MapTile Get((int x, int y) coords) => map[coords.x, coords.y];
public static MapTile Get(int tileId) => Get(MapTile.IdToCoords(tileId));
public static MapTile Get(int tileId) => Get(IdToCoords(tileId));
private static Dictionary<(int x1, int y1), (int x2, int y2, int value, ConnectorType type)[]> halfConnectors = new(){
@ -30,11 +30,11 @@ public static class MapManager {
public static void InitMap() { // TODO: make map size not hardcoded
for (int i = 0; i < 5; i++){
for (int j = 0; j < 2; j++){
map[i, j] = new MapTile(MapTile.CoordsToId(i, j), null); // TODO: implement ownership
map[i, j] = new MapTile(CoordsToId(i, j), null); // TODO: implement ownership
}
map[i, 2] = new MapTile(MapTile.CoordsToId(i, 2), null);
map[i, 2] = new MapTile(CoordsToId(i, 2), null);
for (int j = 3; j < 5; j++){
map[i, j] = new MapTile(MapTile.CoordsToId(i, j), null);
map[i, j] = new MapTile(CoordsToId(i, j), null);
}
}
@ -69,4 +69,7 @@ public static class MapManager {
}
public const int ID_X_OFFSET = 5; // map grid height
public static int CoordsToId(int x, int y) => x * ID_X_OFFSET + y;
public static (int, int) IdToCoords(int id) => (id / ID_X_OFFSET, id % ID_X_OFFSET);
}

View file

@ -6,7 +6,7 @@ namespace FNAF_Server.Map;
public class MapTile : GlobalMapTile<TileConnector, MapTile> {
public ServerPlayer Owner{ get; private set; }
public MapTile(int id, ServerPlayer owner) : base(id) {
public MapTile(int id, ServerPlayer owner) : base(id, MapManager.IdToCoords(id)) {
Owner = owner;
}

View file

@ -115,14 +115,10 @@ public class Server {
}
else{
P2 = newPlayer;
}
SendPacket(new OpponentInitPacket{state = newPlayer.state}, P1.peer);
SendPacket(new OpponentInitPacket{state = P1.state}, P2.peer);
GameLogic.Init(); // TODO: move this to the condition above to wait for the other player
}
}
public static void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) {

View file

@ -7,9 +7,9 @@ public abstract class GlobalMapTile<TCon, TTile> where TCon : GlobalTileConnecto
private List<TCon> connectors = new();
public GlobalMapTile(int id) {
public GlobalMapTile(int id, (int x, int y) gridPosition) {
Id = id;
GridPosition = IdToCoords(id);
GridPosition = gridPosition;
}
public void AddConnector(TCon connector) { // tile1 is ignored when provided
connector = connector.Clone();
@ -41,8 +41,4 @@ public abstract class GlobalMapTile<TCon, TTile> where TCon : GlobalTileConnecto
public TCon[] GetAllConnectors() => connectors.ToArray();
public const int ID_X_OFFSET = 5; // map grid height
public static int CoordsToId(int x, int y) => x * ID_X_OFFSET + y;
public static (int, int) IdToCoords(int id) => (id / ID_X_OFFSET, id % ID_X_OFFSET);
}

View file

@ -5,7 +5,7 @@ namespace PacketLib;
#nullable disable
public struct GameEvent : INetSerializable{
public static GameEvent PLAYER_JOIN(int pid) => new(){ID = 0, Args = [pid] };
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 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]};

View file

@ -2,4 +2,5 @@ namespace PacketLib;
public class JoinAcceptPacket {
public PlayerState state { get; set; }
// public PlayerState otherPlayerState { get; set; }
}

View file

@ -2,5 +2,5 @@ namespace PacketLib;
public class MapInitPacket {
public int[] Connectors { get; set; } // triplets (tile1 id, tile2 id, type)
public bool UpsideDown { get; set; }
}

View file

@ -0,0 +1,5 @@
namespace PacketLib;
public class OpponentInitPacket {
public PlayerState state { get; set; }
}