Files
GeoSusGame/Assets/ClientSDK/Protocol.cs
Bandwidth d886f97e14 GeoSus
2026-04-26 20:49:32 +02:00

1234 lines
32 KiB
C#

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))]
// NOTE: `Voting` is reserved-but-unused on the wire as of 2026. The server
// keeps the entire vote cycle inside `Meeting` and uses MeetingStartedPayload
// timestamps (DiscussionEndTime / VotingEndTime) to distinguish sub-phases.
// The enum value is preserved here for serialization compatibility with old
// saves; new code should not assign it.
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;
/// <summary>
/// P13b: optional per-lobby settings overrides supplied by the host.
/// Any field left null falls through to the server's current default
/// (snapshotted at lobby creation, immutable thereafter for this lobby).
/// </summary>
[JsonProperty("settings")]
public GameSettingsOverrides? Settings { get; set; }
}
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<PlayerPositionInfo> Players { get; set; } = new List<PlayerPositionInfo>();
}
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<T>() where T : class
{
if (Payload == null) return null;
return Payload.ToObject<T>(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<GameTask>? 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<string, int> VoteCounts { get; set; } = new Dictionary<string, int>();
[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 TaskStartedPayload
{
[JsonProperty("clientUuid")]
public string ClientUuid { get; set; } = "";
[JsonProperty("taskId")]
public string TaskId { 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<string> Winners { get; set; } = new List<string>();
}
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<RepairStationInfo> RepairStations { get; set; } = new List<RepairStationInfo>();
[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; }
/// <summary>
/// Track locally if this station has been repaired
/// </summary>
[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<string> RepairerIds { get; set; } = new List<string>();
}
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<PlayerInfo> Players { get; set; } = new List<PlayerInfo>();
[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; }
/// <summary>True if map data has been loaded (or Overpass is disabled)</summary>
[JsonProperty("mapDataReady")]
public bool MapDataReady { get; set; } = true;
/// <summary>
/// P13b: full per-lobby settings snapshot. Clients use this for HUD
/// (button visibility, countdown timings, etc.) instead of hardcoded
/// values. Always populated for new server builds; old client builds
/// can ignore the field.
/// </summary>
[JsonProperty("settings")]
public GameSettings? Settings { get; set; }
}
/// <summary>
/// P13b: per-lobby gameplay settings on the wire. Server populates this from
/// its per-lobby snapshot so clients can drive HUD logic from authoritative
/// values rather than hardcoded constants.
/// </summary>
public class GameSettings
{
// Round shape
[JsonProperty("maxPlayers")]
public int MaxPlayers { get; set; }
[JsonProperty("impostorCount")]
public int ImpostorCount { get; set; }
[JsonProperty("taskCount")]
public int TaskCount { get; set; }
[JsonProperty("tiePolicy")]
public string TiePolicy { get; set; } = "NoEject";
// Distances (m)
[JsonProperty("killDistanceM")]
public double KillDistanceM { get; set; }
[JsonProperty("reportDistanceM")]
public double ReportDistanceM { get; set; }
[JsonProperty("taskStartDistanceM")]
public double TaskStartDistanceM { get; set; }
[JsonProperty("meetingArrivalRadiusM")]
public double MeetingArrivalRadiusM { get; set; }
[JsonProperty("emergencyMeetingCallRadiusM")]
public double EmergencyMeetingCallRadiusM { get; set; }
[JsonProperty("repairStationDistanceM")]
public double RepairStationDistanceM { get; set; }
// Cooldowns / counts
[JsonProperty("killCooldownMs")]
public int KillCooldownMs { get; set; }
[JsonProperty("emergencyMeetingCooldownMs")]
public int EmergencyMeetingCooldownMs { get; set; }
[JsonProperty("maxEmergencyMeetingsPerPlayer")]
public int MaxEmergencyMeetingsPerPlayer { get; set; }
// Meeting phases (ms)
[JsonProperty("arrivalBaseMs")]
public int ArrivalBaseMs { get; set; }
[JsonProperty("allowedLateMs")]
public int AllowedLateMs { get; set; }
[JsonProperty("discussionPhaseMs")]
public int DiscussionPhaseMs { get; set; }
[JsonProperty("votingPhaseMs")]
public int VotingPhaseMs { get; set; }
// Sabotage
[JsonProperty("sabotageCooldownMs")]
public int SabotageCooldownMs { get; set; }
[JsonProperty("commsBlackoutDurationMs")]
public int CommsBlackoutDurationMs { get; set; }
[JsonProperty("criticalMeltdownDeadlineMs")]
public int CriticalMeltdownDeadlineMs { get; set; }
[JsonProperty("repairStationHoldMs")]
public int RepairStationHoldMs { get; set; }
}
/// <summary>
/// P13b: host-supplied overrides at CreateLobby. Every field is nullable so
/// the host can opt into changing only what they care about; null = use the
/// server's current default at the moment of lobby creation.
/// </summary>
public class GameSettingsOverrides
{
[JsonProperty("maxPlayers")]
public int? MaxPlayers { get; set; }
[JsonProperty("impostorCount")]
public int? ImpostorCount { get; set; }
[JsonProperty("taskCount")]
public int? TaskCount { get; set; }
[JsonProperty("tiePolicy")]
public string? TiePolicy { get; set; }
[JsonProperty("killDistanceM")]
public double? KillDistanceM { get; set; }
[JsonProperty("reportDistanceM")]
public double? ReportDistanceM { get; set; }
[JsonProperty("taskStartDistanceM")]
public double? TaskStartDistanceM { get; set; }
[JsonProperty("meetingArrivalRadiusM")]
public double? MeetingArrivalRadiusM { get; set; }
[JsonProperty("emergencyMeetingCallRadiusM")]
public double? EmergencyMeetingCallRadiusM { get; set; }
[JsonProperty("repairStationDistanceM")]
public double? RepairStationDistanceM { get; set; }
[JsonProperty("killCooldownMs")]
public int? KillCooldownMs { get; set; }
[JsonProperty("emergencyMeetingCooldownMs")]
public int? EmergencyMeetingCooldownMs { get; set; }
[JsonProperty("maxEmergencyMeetingsPerPlayer")]
public int? MaxEmergencyMeetingsPerPlayer { get; set; }
[JsonProperty("arrivalBaseMs")]
public int? ArrivalBaseMs { get; set; }
[JsonProperty("allowedLateMs")]
public int? AllowedLateMs { get; set; }
[JsonProperty("discussionPhaseMs")]
public int? DiscussionPhaseMs { get; set; }
[JsonProperty("votingPhaseMs")]
public int? VotingPhaseMs { get; set; }
[JsonProperty("sabotageCooldownMs")]
public int? SabotageCooldownMs { get; set; }
[JsonProperty("commsBlackoutDurationMs")]
public int? CommsBlackoutDurationMs { get; set; }
[JsonProperty("criticalMeltdownDeadlineMs")]
public int? CriticalMeltdownDeadlineMs { get; set; }
[JsonProperty("repairStationHoldMs")]
public int? RepairStationHoldMs { get; set; }
}
// 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; }
/// <summary>Buildings: [[lat,lon,lat,lon,...], ...]</summary>
[JsonProperty("buildings")]
public List<double[]> Buildings { get; set; } = new List<double[]>();
/// <summary>Building types: ["residential", "commercial", ...]</summary>
[JsonProperty("buildingTypes")]
public List<string> BuildingTypes { get; set; } = new List<string>();
/// <summary>Pathways: [[lat,lon,lat,lon,...], ...]</summary>
[JsonProperty("pathways")]
public List<double[]> Pathways { get; set; } = new List<double[]>();
/// <summary>Pathway types: [0=footway, 1=steps, ...]</summary>
[JsonProperty("pathwayTypes")]
public List<int> PathwayTypes { get; set; } = new List<int>();
/// <summary>Areas (parks): [[lat,lon,lat,lon,...], ...]</summary>
[JsonProperty("areas")]
public List<double[]> Areas { get; set; } = new List<double[]>();
/// <summary>Area types</summary>
[JsonProperty("areaTypes")]
public List<int> AreaTypes { get; set; } = new List<int>();
/// <summary>POIs: [lat, lon, type, lat, lon, type, ...]</summary>
[JsonProperty("pOIs")]
public List<double> POIs { get; set; } = new List<double>();
// Helper methods for extracting structured data
public List<MapBuilding> GetBuildings()
{
var result = new List<MapBuilding>();
for (int i = 0; i < Buildings.Count; i++)
{
var coords = Buildings[i];
var outline = new List<Position>();
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<MapPathway> GetPathways()
{
var result = new List<MapPathway>();
for (int i = 0; i < Pathways.Count; i++)
{
var coords = Pathways[i];
var points = new List<Position>();
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<MapArea> GetAreas()
{
var result = new List<MapArea>();
for (int i = 0; i < Areas.Count; i++)
{
var coords = Areas[i];
var outline = new List<Position>();
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<MapPOI> GetPOIs()
{
var result = new List<MapPOI>();
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<Position> Outline { get; set; } = new List<Position>();
public string? Name { get; set; }
public string? BuildingType { get; set; }
}
public class MapPathway
{
public long Id { get; set; }
public List<Position> Points { get; set; } = new List<Position>();
public PathType PathType { get; set; }
public string? Name { get; set; }
}
public class MapArea
{
public long Id { get; set; }
public List<Position> Outline { get; set; } = new List<Position>();
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<string, Type> MessageTypes = new Dictionary<string, Type>
{
["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<string>();
if (typeName == null || !MessageTypes.TryGetValue(typeName, out var type))
return null;
return (Message?)JsonConvert.DeserializeObject(json, type, JsonOptions.Default);
}
}
#endregion
}