using System; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Converters; using System.Collections.Generic; using System.Text; namespace GeoSus.Client { #region Základní typy public struct Position { [JsonProperty("lat")] public double Lat { get; set; } [JsonProperty("lon")] public double Lon { get; set; } public Position(double lat, double lon) { Lat = lat; Lon = lon; } // Haversine vzdálenost v metrech public double DistanceTo(Position other) { const double R = 6371000; var lat1 = Lat * Math.PI / 180; var lat2 = other.Lat * Math.PI / 180; var dLat = (other.Lat - Lat) * Math.PI / 180; var dLon = (other.Lon - Lon) * Math.PI / 180; var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Sin(dLon / 2) * Math.Sin(dLon / 2); var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); return R * c; } public static bool operator ==(Position left, Position right) { if (left.Lat == right.Lat && left.Lon == right.Lon) { return true; } else { return false; } } public static bool operator !=(Position left, Position right) { return !(left == right); } } [JsonConverter(typeof(StringEnumConverter))] public enum PlayerRole { Crew, Impostor } [JsonConverter(typeof(StringEnumConverter))] public enum PlayerState { Alive, Dead } [JsonConverter(typeof(StringEnumConverter))] public enum GamePhase { Lobby, Loading, Playing, Meeting, Voting, Ended } [JsonConverter(typeof(StringEnumConverter))] public enum TaskType { Instant } [JsonConverter(typeof(StringEnumConverter))] public enum MeetingType { BodyReport, Emergency } [JsonConverter(typeof(StringEnumConverter))] public enum SabotageType { CommsBlackout, CriticalMeltdown } [JsonConverter(typeof(StringEnumConverter))] public enum SabotageState { Inactive, Active, Repaired } // Map data types for Overpass integration [JsonConverter(typeof(StringEnumConverter))] public enum PathType { Footway, Path, Steps, Cycleway, Pedestrian, Road, Service, Residential, Track, Other } [JsonConverter(typeof(StringEnumConverter))] public enum MapAreaType { Park, Garden, Playground, Forest, Grass, Water, Other } [JsonConverter(typeof(StringEnumConverter))] public enum MapPOIType { FoodDrink, // Restaurants, cafes, bars Shop, // Shops Health, // Pharmacies, hospitals Transport, // Bus stops, parking Culture, // Museums, theaters Landmark, // Churches, monuments Recreation, // Parks, playgrounds Other } #endregion #region Zprávy public abstract class Message { [JsonProperty("type")] public abstract string Type { get; } [JsonProperty("clientSeq")] public int ClientSeq { get; set; } [JsonProperty("actionId")] public string? ActionId { get; set; } } // Handshake public class ClientHello : Message { public override string Type => "ClientHello"; [JsonProperty("protocolVersion")] public string ProtocolVersion { get; set; } = "1.0"; [JsonProperty("clientUuid")] public string ClientUuid { get; set; } = ""; [JsonProperty("displayName")] public string? DisplayName { get; set; } } public class ServerHello : Message { public override string Type => "ServerHello"; [JsonProperty("rsaPublicKeyPem")] public string RsaPublicKeyPem { get; set; } = ""; [JsonProperty("serverId")] public string ServerId { get; set; } = ""; } public class KeyExchange : Message { public override string Type => "KeyExchange"; [JsonProperty("encryptedSessionKey")] public string EncryptedSessionKey { get; set; } = ""; [JsonProperty("encryptedIV")] public string EncryptedIV { get; set; } = ""; } public class KeyExchangeAck : Message { public override string Type => "KeyExchangeAck"; [JsonProperty("status")] public string Status { get; set; } = ""; } // Lobby public class CreateLobby : Message { public override string Type => "CreateLobby"; [JsonProperty("password")] public string? Password { get; set; } [JsonProperty("playAreaCenter")] public Position? PlayAreaCenter { get; set; } [JsonProperty("playAreaRadius")] public double PlayAreaRadius { get; set; } = 500; [JsonProperty("impostorCount")] public int ImpostorCount { get; set; } = 1; [JsonProperty("taskCount")] public int TaskCount { get; set; } = 5; } public class CreateLobbyResponse : Message { public override string Type => "CreateLobbyResponse"; [JsonProperty("success")] public bool Success { get; set; } [JsonProperty("joinCode")] public string? JoinCode { get; set; } [JsonProperty("lobbyId")] public string? LobbyId { get; set; } [JsonProperty("error")] public string? Error { get; set; } [JsonProperty("lobbyState")] public LobbyState? LobbyState { get; set; } } public class JoinLobby : Message { public override string Type => "JoinLobby"; [JsonProperty("joinCode")] public string JoinCode { get; set; } = ""; [JsonProperty("password")] public string? Password { get; set; } } public class JoinLobbyResponse : Message { public override string Type => "JoinLobbyResponse"; [JsonProperty("success")] public bool Success { get; set; } [JsonProperty("lobbyId")] public string? LobbyId { get; set; } [JsonProperty("error")] public string? Error { get; set; } [JsonProperty("lobbyState")] public LobbyState? LobbyState { get; set; } } public class LeaveLobby : Message { public override string Type => "LeaveLobby"; } public class ReturnToLobby : Message { public override string Type => "ReturnToLobby"; } public class StartGame : Message { public override string Type => "StartGame"; } // Client confirmation that map data was received public class MapDataReceived : Message { public override string Type => "MapDataReceived"; } // Hra public class UpdatePosition : Message { public override string Type => "UpdatePosition"; [JsonProperty("position")] public Position Position { get; set; } } public class PositionBroadcast : Message { public override string Type => "PositionBroadcast"; [JsonProperty("players")] public List Players { get; set; } = new List(); } public class PlayerPositionInfo { [JsonProperty("clientUuid")] public string ClientUuid { get; set; } = ""; [JsonProperty("position")] public Position Position { get; set; } [JsonProperty("state")] public PlayerState State { get; set; } } public class KillAttempt : Message { public override string Type => "KillAttempt"; [JsonProperty("targetClientUuid")] public string TargetClientUuid { get; set; } = ""; } public class ReportBody : Message { public override string Type => "ReportBody"; [JsonProperty("bodyId")] public string BodyId { get; set; } = ""; } public class CallEmergencyMeeting : Message { public override string Type => "CallEmergencyMeeting"; } public class CastVote : Message { public override string Type => "CastVote"; [JsonProperty("targetClientUuid")] public string? TargetClientUuid { get; set; } } public class TaskStart : Message { public override string Type => "TaskStart"; [JsonProperty("taskId")] public string TaskId { get; set; } = ""; } public class TaskProgress : Message { public override string Type => "TaskProgress"; [JsonProperty("taskId")] public string TaskId { get; set; } = ""; [JsonProperty("step")] public int Step { get; set; } = 1; } public class TaskComplete : Message { public override string Type => "TaskComplete"; [JsonProperty("taskId")] public string TaskId { get; set; } = ""; } public class Ping : Message { public override string Type => "Ping"; [JsonProperty("clientTime")] public long ClientTime { get; set; } } public class Pong : Message { public override string Type => "Pong"; [JsonProperty("clientTime")] public long ClientTime { get; set; } [JsonProperty("serverTime")] public long ServerTime { get; set; } } public class Reconnect : Message { public override string Type => "Reconnect"; [JsonProperty("lobbyId")] public string LobbyId { get; set; } = ""; [JsonProperty("lastEventId")] public long LastEventId { get; set; } } public class Ack : Message { public override string Type => "Ack"; [JsonProperty("ackedSeq")] public int AckedSeq { get; set; } [JsonProperty("success")] public bool Success { get; set; } [JsonProperty("error")] public string? Error { get; set; } } public class ErrorMessage : Message { public override string Type => "Error"; [JsonProperty("errorCode")] public string ErrorCode { get; set; } = ""; [JsonProperty("errorText")] public string ErrorText { get; set; } = ""; } // Sabotage messages public class StartSabotage : Message { public override string Type => "StartSabotage"; [JsonProperty("sabotageType")] public SabotageType SabotageType { get; set; } } public class ActivateRepairStation : Message { public override string Type => "ActivateRepairStation"; [JsonProperty("stationId")] public string StationId { get; set; } = ""; } public class DeactivateRepairStation : Message { public override string Type => "DeactivateRepairStation"; [JsonProperty("stationId")] public string StationId { get; set; } = ""; } #endregion #region Eventy public class GameEvent : Message { public override string Type => "GameEvent"; [JsonProperty("eventId")] public long EventId { get; set; } [JsonProperty("serverSeq")] public long ServerSeq { get; set; } [JsonProperty("timestamp")] public DateTime Timestamp { get; set; } [JsonProperty("actor")] public string? Actor { get; set; } [JsonProperty("eventType")] public string EventType { get; set; } = ""; [JsonProperty("payload")] public JObject? Payload { get; set; } public T? GetPayload() where T : class { if (Payload == null) return null; return Payload.ToObject(JsonSerializer.Create(JsonOptions.Default)); } } // Payload typy public class HostChangedPayload { [JsonProperty("newHostId")] public string NewHostId { get; set; } = ""; [JsonProperty("previousHostId")] public string PreviousHostId { get; set; } = ""; } public class PlayerJoinedPayload { [JsonProperty("clientUuid")] public string ClientUuid { get; set; } = ""; [JsonProperty("displayName")] public string DisplayName { get; set; } = ""; } public class PlayerLeftPayload { [JsonProperty("clientUuid")] public string ClientUuid { get; set; } = ""; [JsonProperty("reason")] public string? Reason { get; set; } } // Loading phase payloads public class GameStartingPayload { [JsonProperty("message")] public string Message { get; set; } = ""; } public class MapDataReadyPayload { [JsonProperty("mapData")] public MapDataPayload? MapData { get; set; } } public class PlayerMapDataReceivedPayload { [JsonProperty("clientUuid")] public string ClientUuid { get; set; } = ""; [JsonProperty("displayName")] public string DisplayName { get; set; } = ""; [JsonProperty("playersReady")] public int PlayersReady { get; set; } [JsonProperty("totalPlayers")] public int TotalPlayers { get; set; } } public class GameStartedPayload { [JsonProperty("impostorCount")] public int ImpostorCount { get; set; } [JsonProperty("taskCount")] public int TaskCount { get; set; } } public class RoleAssignedPayload { [JsonProperty("clientUuid")] public string ClientUuid { get; set; } = ""; [JsonProperty("role")] public PlayerRole Role { get; set; } [JsonProperty("tasks")] public List? Tasks { get; set; } } public class PlayerKilledPayload { [JsonProperty("victimId")] public string VictimId { get; set; } = ""; [JsonProperty("killerId")] public string KillerId { get; set; } = ""; [JsonProperty("bodyId")] public string BodyId { get; set; } = ""; [JsonProperty("location")] public Position Location { get; set; } } public class BodyReportedPayload { [JsonProperty("reporterId")] public string ReporterId { get; set; } = ""; [JsonProperty("bodyId")] public string BodyId { get; set; } = ""; [JsonProperty("victimId")] public string VictimId { get; set; } = ""; } public class EmergencyMeetingCalledPayload { [JsonProperty("callerId")] public string CallerId { get; set; } = ""; } public class MeetingStartedPayload { [JsonProperty("meetingId")] public string MeetingId { get; set; } = ""; [JsonProperty("type")] public MeetingType Type { get; set; } [JsonProperty("meetingLocation")] public Position MeetingLocation { get; set; } [JsonProperty("arrivalDeadline")] public DateTime ArrivalDeadline { get; set; } [JsonProperty("discussionEndTime")] public DateTime? DiscussionEndTime { get; set; } [JsonProperty("votingEndTime")] public DateTime VotingEndTime { get; set; } } public class PlayerArrivedAtMeetingPayload { [JsonProperty("clientUuid")] public string ClientUuid { get; set; } = ""; [JsonProperty("meetingId")] public string MeetingId { get; set; } = ""; } public class PlayerVotedPayload { [JsonProperty("voterId")] public string VoterId { get; set; } = ""; [JsonProperty("targetId")] public string? TargetId { get; set; } } public class VotingClosedPayload { [JsonProperty("voteCounts")] public Dictionary VoteCounts { get; set; } = new Dictionary(); [JsonProperty("ejectedPlayerId")] public string? EjectedPlayerId { get; set; } [JsonProperty("wasTie")] public bool WasTie { get; set; } } public class PlayerEjectedPayload { [JsonProperty("clientUuid")] public string ClientUuid { get; set; } = ""; [JsonProperty("role")] public PlayerRole Role { get; set; } } public class TaskCompletedPayload { [JsonProperty("clientUuid")] public string ClientUuid { get; set; } = ""; [JsonProperty("taskId")] public string TaskId { get; set; } = ""; [JsonProperty("totalCompleted")] public int TotalCompleted { get; set; } [JsonProperty("totalTasks")] public int TotalTasks { get; set; } } public class GameEndedPayload { [JsonProperty("winningFaction")] public string WinningFaction { get; set; } = ""; [JsonProperty("reason")] public string Reason { get; set; } = ""; [JsonProperty("winners")] public List Winners { get; set; } = new List(); } public class ReturnedToLobbyPayload { [JsonProperty("message")] public string Message { get; set; } = ""; } // System message payload (admin broadcast) public class SystemMessagePayload { [JsonProperty("message")] public string Message { get; set; } = ""; [JsonProperty("timestamp")] public DateTime Timestamp { get; set; } } // Sabotage event payloads public class SabotageStartedPayload { [JsonProperty("sabotageId")] public string SabotageId { get; set; } = ""; [JsonProperty("type")] public SabotageType Type { get; set; } [JsonProperty("initiatorId")] public string InitiatorId { get; set; } = ""; [JsonProperty("deadline")] public DateTime? Deadline { get; set; } [JsonProperty("repairStations")] public List RepairStations { get; set; } = new List(); [JsonProperty("requiredSimultaneousRepairs")] public int RequiredSimultaneousRepairs { get; set; } } public class RepairStationInfo { [JsonProperty("stationId")] public string StationId { get; set; } = ""; [JsonProperty("name")] public string Name { get; set; } = ""; [JsonProperty("location")] public Position Location { get; set; } [JsonProperty("repairDurationMs")] public int RepairDurationMs { get; set; } /// /// Track locally if this station has been repaired /// [JsonIgnore] public bool IsRepaired { get; set; } } public class RepairStartedPayload { [JsonProperty("sabotageId")] public string SabotageId { get; set; } = ""; [JsonProperty("stationId")] public string StationId { get; set; } = ""; [JsonProperty("playerId")] public string PlayerId { get; set; } = ""; } public class RepairStoppedPayload { [JsonProperty("sabotageId")] public string SabotageId { get; set; } = ""; [JsonProperty("stationId")] public string StationId { get; set; } = ""; [JsonProperty("playerId")] public string PlayerId { get; set; } = ""; } public class SabotageRepairedPayload { [JsonProperty("sabotageId")] public string SabotageId { get; set; } = ""; [JsonProperty("type")] public SabotageType Type { get; set; } [JsonProperty("repairerIds")] public List RepairerIds { get; set; } = new List(); } public class SabotageMeltdownPayload { [JsonProperty("sabotageId")] public string SabotageId { get; set; } = ""; } #endregion #region State public class LobbyState { [JsonProperty("lobbyId")] public string LobbyId { get; set; } = ""; [JsonProperty("joinCode")] public string JoinCode { get; set; } = ""; [JsonProperty("ownerId")] public string? OwnerId { get; set; } [JsonProperty("phase")] public GamePhase Phase { get; set; } [JsonProperty("players")] public List Players { get; set; } = new List(); [JsonProperty("playAreaCenter")] public Position PlayAreaCenter { get; set; } [JsonProperty("playAreaRadius")] public double PlayAreaRadius { get; set; } [JsonProperty("impostorCount")] public int ImpostorCount { get; set; } [JsonProperty("hasPassword")] public bool HasPassword { get; set; } [JsonProperty("mapData")] public MapDataPayload? MapData { get; set; } /// True if map data has been loaded (or Overpass is disabled) [JsonProperty("mapDataReady")] public bool MapDataReady { get; set; } = true; } // Map data classes for rendering - compact format from server public class MapDataPayload { [JsonProperty("center")] public Position Center { get; set; } [JsonProperty("radiusMeters")] public double RadiusMeters { get; set; } /// Buildings: [[lat,lon,lat,lon,...], ...] [JsonProperty("buildings")] public List Buildings { get; set; } = new List(); /// Building types: ["residential", "commercial", ...] [JsonProperty("buildingTypes")] public List BuildingTypes { get; set; } = new List(); /// Pathways: [[lat,lon,lat,lon,...], ...] [JsonProperty("pathways")] public List Pathways { get; set; } = new List(); /// Pathway types: [0=footway, 1=steps, ...] [JsonProperty("pathwayTypes")] public List PathwayTypes { get; set; } = new List(); /// Areas (parks): [[lat,lon,lat,lon,...], ...] [JsonProperty("areas")] public List Areas { get; set; } = new List(); /// Area types [JsonProperty("areaTypes")] public List AreaTypes { get; set; } = new List(); /// POIs: [lat, lon, type, lat, lon, type, ...] [JsonProperty("pOIs")] public List POIs { get; set; } = new List(); // Helper methods for extracting structured data public List GetBuildings() { var result = new List(); for (int i = 0; i < Buildings.Count; i++) { var coords = Buildings[i]; var outline = new List(); for (int j = 0; j < coords.Length - 1; j += 2) { outline.Add(new Position(coords[j], coords[j + 1])); } result.Add(new MapBuilding { Id = i, Outline = outline, BuildingType = i < BuildingTypes.Count ? BuildingTypes[i] : "yes" }); } return result; } public List GetPathways() { var result = new List(); for (int i = 0; i < Pathways.Count; i++) { var coords = Pathways[i]; var points = new List(); for (int j = 0; j < coords.Length - 1; j += 2) { points.Add(new Position(coords[j], coords[j + 1])); } result.Add(new MapPathway { Id = i, Points = points, PathType = i < PathwayTypes.Count ? (PathType)PathwayTypes[i] : PathType.Other }); } return result; } public List GetAreas() { var result = new List(); for (int i = 0; i < Areas.Count; i++) { var coords = Areas[i]; var outline = new List(); for (int j = 0; j < coords.Length - 1; j += 2) { outline.Add(new Position(coords[j], coords[j + 1])); } result.Add(new MapArea { Id = i, Outline = outline, AreaType = i < AreaTypes.Count ? (MapAreaType)AreaTypes[i] : MapAreaType.Other }); } return result; } public List GetPOIs() { var result = new List(); for (int i = 0; i < POIs.Count - 2; i += 3) { result.Add(new MapPOI { Id = i / 3, Location = new Position(POIs[i], POIs[i + 1]), POIType = (MapPOIType)(int)POIs[i + 2] }); } return result; } } public class MapBuilding { public long Id { get; set; } public List Outline { get; set; } = new List(); public string? Name { get; set; } public string? BuildingType { get; set; } } public class MapPathway { public long Id { get; set; } public List Points { get; set; } = new List(); public PathType PathType { get; set; } public string? Name { get; set; } } public class MapArea { public long Id { get; set; } public List Outline { get; set; } = new List(); public MapAreaType AreaType { get; set; } public string? Name { get; set; } } public class MapPOI { public long Id { get; set; } public Position Location { get; set; } public string? Name { get; set; } public MapPOIType POIType { get; set; } } public class PlayerInfo { [JsonProperty("clientUuid")] public string ClientUuid { get; set; } = ""; [JsonProperty("displayName")] public string DisplayName { get; set; } = ""; [JsonProperty("isOwner")] public bool IsOwner { get; set; } [JsonProperty("isReady")] public bool IsReady { get; set; } [JsonProperty("state")] public PlayerState State { get; set; } } public class GameTask { [JsonProperty("taskId")] public string TaskId { get; set; } = ""; [JsonProperty("name")] public string Name { get; set; } = ""; [JsonProperty("location")] public Position Location { get; set; } [JsonProperty("type")] public TaskType Type { get; set; } = TaskType.Instant; } public class Body { [JsonProperty("bodyId")] public string BodyId { get; set; } = ""; [JsonProperty("victimId")] public string VictimId { get; set; } = ""; [JsonProperty("location")] public Position Location { get; set; } } #endregion #region Serializace public static class JsonOptions { public static readonly JsonSerializerSettings Default = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Converters = { new StringEnumConverter() } }; } public static class MessageSerializer { private static readonly Dictionary MessageTypes = new Dictionary { ["ClientHello"] = typeof(ClientHello), ["ServerHello"] = typeof(ServerHello), ["KeyExchange"] = typeof(KeyExchange), ["KeyExchangeAck"] = typeof(KeyExchangeAck), ["CreateLobby"] = typeof(CreateLobby), ["CreateLobbyResponse"] = typeof(CreateLobbyResponse), ["JoinLobby"] = typeof(JoinLobby), ["JoinLobbyResponse"] = typeof(JoinLobbyResponse), ["LeaveLobby"] = typeof(LeaveLobby), ["ReturnToLobby"] = typeof(ReturnToLobby), ["StartGame"] = typeof(StartGame), ["MapDataReceived"] = typeof(MapDataReceived), ["UpdatePosition"] = typeof(UpdatePosition), ["PositionBroadcast"] = typeof(PositionBroadcast), ["KillAttempt"] = typeof(KillAttempt), ["ReportBody"] = typeof(ReportBody), ["CallEmergencyMeeting"] = typeof(CallEmergencyMeeting), ["CastVote"] = typeof(CastVote), ["TaskStart"] = typeof(TaskStart), ["TaskProgress"] = typeof(TaskProgress), ["TaskComplete"] = typeof(TaskComplete), ["Ping"] = typeof(Ping), ["Pong"] = typeof(Pong), ["Reconnect"] = typeof(Reconnect), ["Ack"] = typeof(Ack), ["Error"] = typeof(ErrorMessage), ["GameEvent"] = typeof(GameEvent) }; public static byte[] Serialize(Message msg) { var json = JsonConvert.SerializeObject(msg, JsonOptions.Default); return Encoding.UTF8.GetBytes(json); } public static Message? Deserialize(byte[] data) { var json = Encoding.UTF8.GetString(data); var jObj = JObject.Parse(json); var typeName = jObj["type"]?.Value(); if (typeName == null || !MessageTypes.TryGetValue(typeName, out var type)) return null; return (Message?)JsonConvert.DeserializeObject(json, type, JsonOptions.Default); } } #endregion }