using Content.Server.Spawners.Components; using Content.Server.Spawners.EntitySystems; using Content.Server.Station.Systems; using Content.Shared.Roles; using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Random; namespace Content.Server._Sunrise.Spawners.PreferredSpawn; /// /// Обрабатывает спавн для ролей, имеющих особое поведение спавна. /// Эта система запускается перед стандартной SpawnPointSystem, чтобы переопределить /// логику спавна по умолчанию для этих ролей. /// public sealed class PreferredSpawnSystem : EntitySystem { [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StationSpawningSystem _stationSpawning = default!; public override void Initialize() { // Подписываемся на событие перед SpawnPointSystem, чтобы наша логика обработки // ролей срабатывала первой и могла переопределить стандартное поведение. SubscribeLocalEvent(OnPlayerSpawning, before: new[] { typeof(SpawnPointSystem) }); } private void OnPlayerSpawning(PlayerSpawningEvent args) { // Если другая система уже обработала спавн, пропускаем. if (args.SpawnResult != null) return; // Обрабатываем спавн, находя подходящие позиции. var spawnPositions = FindPreferredSpawnPositions(args); // Если подходящих позиций не найдено, позволяем другим системам обработать спавн. if (spawnPositions.Count == 0) return; // Выбираем случайную позицию из найденных и спавним игрока. var spawnLocation = _random.Pick(spawnPositions); args.SpawnResult = _stationSpawning.SpawnPlayerMob( spawnLocation, args.Job, args.HumanoidCharacterProfile, args.Station); } private List FindPreferredSpawnPositions(PlayerSpawningEvent args) { // Если DesiredSpawnPointType равно Unset, это означает, что стандартная SpawnPointSystem // попытается найти спавнер самостоятельно. В этом случае мы не должны мешать ей, // поэтому не будем пытаться найти спавнеры. if (args.DesiredSpawnPointType == SpawnPointType.Unset) return new(); // Сначала пытаемся найти спавнеры для желаемого типа спавна. var positions = GetValidPreferredSpawners(args, args.DesiredSpawnPointType); // Запасной вариант: если для позднего присоединения (DesiredSpawnPointType == LateJoin) // не найдено спавнеров, предназначенных именно для позднего присоединения, // то в качестве отката пытаемся найти обычные спавнеры типа "Job" (предназначенные для старта раунда) // для этой же роли. Это гарантирует, что игроки всегда смогут заспавниться, // даже если специализированные LateJoin-спавнеры отсутствуют. if (positions.Count == 0 && args.DesiredSpawnPointType == SpawnPointType.LateJoin) { positions = GetValidPreferredSpawners(args, SpawnPointType.Job); } return positions; } /// /// Получает список действительных точек спавна для заданного типа спавна. /// private List GetValidPreferredSpawners(PlayerSpawningEvent args, SpawnPointType targetSpawnPointType) { var validPositions = new List(); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var preferredComp, out var spawnPoint, out var xform)) { // Пропускаем спавнеры, находящиеся не на той станции. if (args.Station != null && _station.GetOwningStation(uid, xform) != args.Station) continue; // Проверяем, подходит ли этот спавнер для текущей работы и желаемого типа спавна. if (IsValidPreferredSpawnerInternal(spawnPoint, preferredComp, args.Job, targetSpawnPointType)) { validPositions.Add(xform.Coordinates); } } return validPositions; } /// /// Внутренний вспомогательный метод для проверки действительности preferred спавнера. /// /// Логика двойной проверки типов спавна: /// 1. SpawnPoint.SpawnType - базовая категория спавнера (Job/LateJoin/Whatever) /// 2. PreferredSpawnTypes - opt-in фильтр для PreferredSpawnSystem /// /// Спавнер участвует в preferred спавне ТОЛЬКО ЕСЛИ: /// - Его базовый SpawnType соответствует искомому типу /// - И этот тип явно указан в PreferredSpawnTypes /// private bool IsValidPreferredSpawnerInternal(SpawnPointComponent spawnPoint, PreferredSpawnComponent preferredComp, ProtoId? job, SpawnPointType targetSpawnPointType) { // Проверяем базовую категорию спавнера // Например: для Job спавна ищем спавнеры с SpawnType = Job if (spawnPoint.SpawnType != targetSpawnPointType) return false; // Дополнительная фильтрация по конкретной роли (опционально) if (spawnPoint.Job != null && job != null && spawnPoint.Job != job) return false; // Opt-in проверка: спавнер должен явно поддерживать этот тип через PreferredSpawnTypes // Это предотвращает случайное использование обычных спавнеров в preferred логике return preferredComp.PreferredSpawnTypes.Contains(targetSpawnPointType); } }