206 lines
9.1 KiB
C#
206 lines
9.1 KiB
C#
using System;
|
|||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Text;
|
||
|
|
|
||
|
|
// МОДУЛЬ КРУГА: объединение игроков -- создать, вступить, выйти, роли главы и участника.
|
||
|
|
// Документ: Docs/Modules/Circle.md
|
||
|
|
//
|
||
|
|
// Без Unity: когда появится сервер, список Кругов и состав будут приходить с него,
|
||
|
|
// а этот класс заменится сетевой реализацией ICircle.
|
||
|
|
// Пока других игроков нет -- Круги из рейтинга заполнены выдуманными участниками.
|
||
|
|
public sealed class CircleService : ICircle, ISaveParticipant
|
||
|
|
{
|
||
|
|
public const int MaxMembersPerCircle = 30;
|
||
|
|
public const int MinNameLength = 3;
|
||
|
|
public const int MaxNameLength = 24;
|
||
|
|
|
||
|
|
public int MaxMembers => MaxMembersPerCircle;
|
||
|
|
|
||
|
|
private readonly List<CircleInfo> circles = new List<CircleInfo>();
|
||
|
|
private CircleInfo current;
|
||
|
|
|
||
|
|
public CircleInfo Current => current;
|
||
|
|
public bool IsInCircle => current != null;
|
||
|
|
public bool IsLeader => current != null && FindMember(current, LocalPlayer.Id)?.role == CircleRole.Leader;
|
||
|
|
|
||
|
|
public CircleService()
|
||
|
|
{
|
||
|
|
CreateDemoCircles();
|
||
|
|
}
|
||
|
|
|
||
|
|
public IReadOnlyList<CircleInfo> GetCircles()
|
||
|
|
{
|
||
|
|
return circles;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------- Действия игрока ----------------
|
||
|
|
|
||
|
|
public bool TryCreate(string name, out string error)
|
||
|
|
{
|
||
|
|
name = name?.Trim() ?? "";
|
||
|
|
if (IsInCircle) { error = "Сначала выйдите из своего Круга"; return false; }
|
||
|
|
if (!IsValidName(name, out error)) return false;
|
||
|
|
if (circles.Exists(c => string.Equals(c.name, name, StringComparison.OrdinalIgnoreCase)))
|
||
|
|
{
|
||
|
|
error = "Круг с таким названием уже есть";
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
var circle = new CircleInfo { id = "player_" + Guid.NewGuid().ToString("N").Substring(0, 8), name = name };
|
||
|
|
circle.members.Add(new CircleMember { playerId = LocalPlayer.Id, name = LocalPlayer.Name, role = CircleRole.Leader });
|
||
|
|
circles.Insert(0, circle);
|
||
|
|
current = circle;
|
||
|
|
|
||
|
|
EventBus.Publish(new CircleJoinedEvent { CircleId = circle.id, CircleName = circle.name, Created = true });
|
||
|
|
error = null;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool TryJoin(string circleId, out string error)
|
||
|
|
{
|
||
|
|
if (IsInCircle) { error = "Вы уже состоите в Круге. Сначала выйдите из него"; return false; }
|
||
|
|
CircleInfo circle = circles.Find(c => c.id == circleId);
|
||
|
|
if (circle == null) { error = "Такого Круга нет"; return false; }
|
||
|
|
if (circle.members.Count >= MaxMembersPerCircle) { error = "В Круге нет свободных мест"; return false; }
|
||
|
|
|
||
|
|
circle.members.Add(new CircleMember { playerId = LocalPlayer.Id, name = LocalPlayer.Name, role = CircleRole.Member });
|
||
|
|
current = circle;
|
||
|
|
|
||
|
|
EventBus.Publish(new CircleJoinedEvent { CircleId = circle.id, CircleName = circle.name, Created = false });
|
||
|
|
error = null;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Leave()
|
||
|
|
{
|
||
|
|
if (current == null) return;
|
||
|
|
CircleInfo circle = current;
|
||
|
|
CircleMember me = FindMember(circle, LocalPlayer.Id);
|
||
|
|
circle.members.Remove(me);
|
||
|
|
current = null;
|
||
|
|
|
||
|
|
bool disbanded = circle.members.Count == 0;
|
||
|
|
if (disbanded)
|
||
|
|
{
|
||
|
|
circles.Remove(circle);
|
||
|
|
}
|
||
|
|
else if (me != null && me.role == CircleRole.Leader)
|
||
|
|
{
|
||
|
|
circle.members[0].role = CircleRole.Leader; // главой становится старейший участник
|
||
|
|
}
|
||
|
|
|
||
|
|
EventBus.Publish(new CircleLeftEvent { CircleId = circle.id, CircleName = circle.name, Disbanded = disbanded });
|
||
|
|
}
|
||
|
|
|
||
|
|
public static bool IsValidName(string name, out string error)
|
||
|
|
{
|
||
|
|
name = name?.Trim() ?? "";
|
||
|
|
if (name.Length < MinNameLength) { error = $"Название -- не короче {MinNameLength} символов"; return false; }
|
||
|
|
if (name.Length > MaxNameLength) { error = $"Название -- не длиннее {MaxNameLength} символов"; return false; }
|
||
|
|
if (name.IndexOfAny(new[] { '\n', '\r', '|', '<', '>' }) >= 0) { error = "В названии есть недопустимые символы"; return false; }
|
||
|
|
error = null;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
static CircleMember FindMember(CircleInfo circle, string playerId)
|
||
|
|
{
|
||
|
|
return circle.members.Find(m => m.playerId == playerId);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------- Сохранение (секция "circle") ----------------
|
||
|
|
// Модуль без Unity, поэтому формат -- простые строки, а не JSON:
|
||
|
|
// joined=<id Круга игрока>
|
||
|
|
// created=<id>|<название>|<роль игрока: Leader/Member> -- Круги, созданные игроком
|
||
|
|
// Демо-Круги не сохраняются: они одинаковые при каждом запуске.
|
||
|
|
|
||
|
|
public string SaveKey => "circle";
|
||
|
|
|
||
|
|
public string Capture()
|
||
|
|
{
|
||
|
|
var sb = new StringBuilder();
|
||
|
|
if (current != null) sb.Append("joined=").Append(current.id).Append('\n');
|
||
|
|
foreach (var c in circles)
|
||
|
|
{
|
||
|
|
if (!c.id.StartsWith("player_")) continue;
|
||
|
|
var me = FindMember(c, LocalPlayer.Id);
|
||
|
|
if (me == null) continue; // свой Круг, из которого игрок вышел, уже распущен
|
||
|
|
sb.Append("created=").Append(c.id).Append('|').Append(c.name).Append('|').Append(me.role).Append('\n');
|
||
|
|
}
|
||
|
|
return sb.ToString();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Restore(string data)
|
||
|
|
{
|
||
|
|
ResetState();
|
||
|
|
if (string.IsNullOrEmpty(data)) return;
|
||
|
|
|
||
|
|
string joinedId = null;
|
||
|
|
foreach (var raw in data.Split('\n'))
|
||
|
|
{
|
||
|
|
string line = raw.Trim();
|
||
|
|
if (line.StartsWith("joined=")) joinedId = line.Substring("joined=".Length);
|
||
|
|
else if (line.StartsWith("created="))
|
||
|
|
{
|
||
|
|
string[] parts = line.Substring("created=".Length).Split('|');
|
||
|
|
if (parts.Length < 2) continue;
|
||
|
|
var circle = new CircleInfo { id = parts[0], name = parts[1] };
|
||
|
|
CircleRole role = parts.Length > 2 && parts[2] == nameof(CircleRole.Member) ? CircleRole.Member : CircleRole.Leader;
|
||
|
|
circle.members.Add(new CircleMember { playerId = LocalPlayer.Id, name = LocalPlayer.Name, role = role });
|
||
|
|
circles.Insert(0, circle);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (joinedId == null) return;
|
||
|
|
CircleInfo joined = circles.Find(c => c.id == joinedId);
|
||
|
|
if (joined == null) return; // Круга больше нет
|
||
|
|
if (FindMember(joined, LocalPlayer.Id) == null)
|
||
|
|
joined.members.Add(new CircleMember { playerId = LocalPlayer.Id, name = LocalPlayer.Name, role = CircleRole.Member });
|
||
|
|
current = joined;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Новая игра: игрок ни в каком Круге, свои Круги распущены
|
||
|
|
public void ResetState()
|
||
|
|
{
|
||
|
|
current = null;
|
||
|
|
circles.Clear();
|
||
|
|
CreateDemoCircles();
|
||
|
|
}
|
||
|
|
|
||
|
|
// ---------------- Демо-Круги (до появления сервера) ----------------
|
||
|
|
|
||
|
|
static readonly string[] DemoCircleNames =
|
||
|
|
{
|
||
|
|
"Круг Перуна", "Стальные Волки", "Сыны Руси", "Богатыри", "Северный Страж",
|
||
|
|
"Вольные", "Златые Земли", "Серые Горны", "Речные", "Перунов Круг"
|
||
|
|
};
|
||
|
|
|
||
|
|
static readonly string[] DemoPlayerNames =
|
||
|
|
{
|
||
|
|
"Велес", "Север", "Тайрон", "DreadWolf", "Мороз", "Леший", "Князь", "Варяг", "Сокол", "Ратибор",
|
||
|
|
"Светозар", "Добромир", "Велимир", "Ярополк", "Всеслав", "Мстислав", "Остромир", "Богдан", "Любомир", "Твердислав",
|
||
|
|
"Радим", "Гостомысл", "Вышеслав", "Путята", "Ставр", "Чурила", "Микула", "Василиса", "Забава", "IronBear"
|
||
|
|
};
|
||
|
|
|
||
|
|
// Сколько участников в каждом демо-Круге (последний -- полный, чтобы было видно "мест нет")
|
||
|
|
static readonly int[] DemoMemberCounts = { 28, 24, 22, 19, 17, 14, 12, 9, 6, MaxMembersPerCircle };
|
||
|
|
|
||
|
|
void CreateDemoCircles()
|
||
|
|
{
|
||
|
|
for (int i = 0; i < DemoCircleNames.Length; i++)
|
||
|
|
{
|
||
|
|
var circle = new CircleInfo { id = "circle_" + (i + 1), name = DemoCircleNames[i] };
|
||
|
|
for (int k = 0; k < DemoMemberCounts[i]; k++)
|
||
|
|
{
|
||
|
|
string name = DemoPlayerNames[(i + k) % DemoPlayerNames.Length];
|
||
|
|
circle.members.Add(new CircleMember
|
||
|
|
{
|
||
|
|
playerId = $"npc_{i + 1}_{k + 1}",
|
||
|
|
name = k < DemoPlayerNames.Length ? name : name + " " + (k + 1),
|
||
|
|
role = k == 0 ? CircleRole.Leader : CircleRole.Member
|
||
|
|
});
|
||
|
|
}
|
||
|
|
circles.Add(circle);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|