using System; using System.Collections.Generic; // МОДУЛЬ ЧАТА: общий чат мира и чат Круга. Документ: Docs/Modules/Chat.md // Без Unity: когда появится сервер, этот класс заменится сетевой реализацией IChat, // а панель чата (слой приложения) менять не придётся. // // История живёт, пока открыта игра: модуль зарегистрирован в GameServices и переживает смену сцен. // В файл сохранения чат не пишется -- переписку будет хранить сервер. public sealed class ChatService : IChat { public const int MaxLength = 200; public const int MaxStoredPerChannel = 100; public int MaxMessageLength => MaxLength; private readonly Dictionary> history = new Dictionary> { { ChatChannel.World, new List() }, { ChatChannel.Circle, new List() } }; public ChatService(long nowMs) { SeedDemoMessages(nowMs); // Канал "Круг" -- переписка текущего Круга игрока (модуль Круга сообщает о вступлении и выходе) EventBus.Subscribe(OnCircleJoined); EventBus.Subscribe(OnCircleLeft); EventBus.Subscribe(_ => history[ChatChannel.Circle].Clear()); // новая игра -- без Круга } public const string SystemSenderId = "system"; // Писать в канал "Круг" можно, только состоя в Круге (если модуль Круга подключён) public bool CanWrite(ChatChannel channel) { if (channel != ChatChannel.Circle) return true; return !GameServices.TryGet(out ICircle circle) || circle.IsInCircle; } void OnCircleJoined(CircleJoinedEvent e) { history[ChatChannel.Circle].Clear(); string text = e.Created ? $"Вы основали Круг «{e.CircleName}». Зовите соратников!" : $"Вы вступили в Круг «{e.CircleName}»."; Add(new ChatMessage { channel = ChatChannel.Circle, senderId = SystemSenderId, senderName = "Круг", text = text, timeMs = NowMs }); } void OnCircleLeft(CircleLeftEvent e) { history[ChatChannel.Circle].Clear(); Add(new ChatMessage { channel = ChatChannel.Circle, senderId = SystemSenderId, senderName = "Круг", text = e.Disbanded ? $"Круг «{e.CircleName}» распущен." : $"Вы покинули Круг «{e.CircleName}».", timeMs = NowMs }); } static long NowMs => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); public IReadOnlyList GetMessages(ChatChannel channel) { return history[channel]; } public bool Send(ChatChannel channel, string text) { text = text?.Trim(); if (string.IsNullOrEmpty(text)) return false; if (!CanWrite(channel)) return false; if (text.Length > MaxLength) text = text.Substring(0, MaxLength); Add(new ChatMessage { channel = channel, senderId = LocalPlayer.Id, senderName = LocalPlayer.Name, text = text, timeMs = NowMs }); return true; } // Входящее сообщение (сейчас -- только свои; позже сюда придут сообщения с сервера) public void Receive(ChatMessage message) { Add(message); } void Add(ChatMessage message) { List list = history[message.channel]; list.Add(message); if (list.Count > MaxStoredPerChannel) list.RemoveAt(0); EventBus.Publish(new ChatMessageReceivedEvent { Message = message }); } // Несколько сообщений "от других игроков", чтобы чат не был пустым. Уберётся с приходом сервера. void SeedDemoMessages(long nowMs) { const long minute = 60_000; AddDemo(ChatChannel.World, "npc_sever", "Север", "Кто идёт на Змея Горыныча?", nowMs - 5 * minute); AddDemo(ChatChannel.World, "npc_dreadwolf", "DreadWolf", "Набор в Круг, нужны активные игроки!", nowMs - 4 * minute); AddDemo(ChatChannel.World, "npc_moroz", "Мороз", "На западе замечен Круг Стальные Волки.", nowMs - 3 * minute); AddDemo(ChatChannel.World, "npc_tairon", "Тайрон", "Собираем отряд на древний храм.", nowMs - 2 * minute); AddDemo(ChatChannel.World, "npc_veles", "Велес", "Всем удачной охоты!", nowMs - minute); } void AddDemo(ChatChannel channel, string id, string name, string text, long timeMs) { history[channel].Add(new ChatMessage { channel = channel, senderId = id, senderName = name, text = text, timeMs = timeMs }); } }