using Barotrauma.Items.Components; using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Xml.Linq; namespace Barotrauma { partial class BeaconMission : Mission { private class MonsterSet { public readonly HashSet<(CharacterPrefab character, Point amountRange)> MonsterPrefabs = new HashSet<(CharacterPrefab character, Point amountRange)>(); public float Commonness; public MonsterSet(XElement element) { Commonness = element.GetAttributeFloat("commonness", 100.0f); } } private bool swarmSpawned; private readonly List monsterSets = new List(); private readonly LocalizedString sonarLabel; private readonly ImmutableArray beaconTags; public BeaconMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub) { swarmSpawned = false; beaconTags = prefab.ConfigElement.GetAttributeIdentifierArray("beacontags", []).ToImmutableArray(); foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster")) { if (!monsterSets.Any()) { monsterSets.Add(new MonsterSet(monsterElement)); } LoadMonsters(monsterElement, monsterSets[0]); } foreach (var monsterSetElement in prefab.ConfigElement.GetChildElements("monsters")) { monsterSets.Add(new MonsterSet(monsterSetElement)); foreach (var monsterElement in monsterSetElement.GetChildElements("monster")) { LoadMonsters(monsterElement, monsterSets.Last()); } } sonarLabel = TextManager.Get("beaconstationsonarlabel"); DebugConsole.NewMessage("Initialized beacon mission: " + prefab.Identifier, Color.LightSkyBlue, debugOnly: true); } private void LoadMonsters(XElement monsterElement, MonsterSet set) { Identifier speciesName = monsterElement.GetAttributeIdentifier("character", Identifier.Empty); int defaultCount = monsterElement.GetAttributeInt("count", -1); if (defaultCount < 0) { defaultCount = monsterElement.GetAttributeInt("amount", 1); } int min = Math.Min(monsterElement.GetAttributeInt("min", defaultCount), 255); int max = Math.Min(Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)), 255); var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName); if (characterPrefab != null) { set.MonsterPrefabs.Add((characterPrefab, new Point(min, max))); } else { DebugConsole.ThrowError($"Error in beacon mission \"{Prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".", contentPackage: Prefab.ContentPackage); } } public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels { get { if (level.BeaconStation == null || state > 0) { yield break; } else { yield return ( Prefab.SonarLabel.IsNullOrEmpty() ? sonarLabel : Prefab.SonarLabel, level.BeaconStation.WorldPosition); } } } protected override void UpdateMissionSpecific(float deltaTime) { if (IsClient) { return; } if (!swarmSpawned && level.CheckBeaconActive()) { IEnumerable connectedSubs = level.BeaconStation.GetConnectedSubs(); foreach (Item item in Item.ItemList) { if (!connectedSubs.Contains(item.Submarine) || item.Submarine?.Info is { IsPlayer: true }) { continue; } bool isReactor = item.GetComponent() != null; if ((isReactor && GameMain.GameSession is not { TraitorsEnabled: true }) || item.GetComponent() != null || item.GetComponent() != null || item.GetComponent() != null) { item.InvulnerableToDamage = true; } } State = 1; Vector2 spawnPos = level.BeaconStation.WorldPosition; spawnPos.Y += level.BeaconStation.GetDockedBorders().Height * 1.5f; var availablePositions = Level.Loaded.PositionsOfInterest.FindAll(p => p.PositionType == Level.PositionType.MainPath || p.PositionType == Level.PositionType.SidePath); availablePositions.RemoveAll(p => Level.Loaded.ExtraWalls.Any(w => w.IsPointInside(p.Position.ToVector2()))); availablePositions.RemoveAll(p => Submarine.FindContaining(p.Position.ToVector2()) != null); if (availablePositions.Any()) { Level.InterestingPosition? closestPos = null; float closestDist = float.PositiveInfinity; foreach (var pos in availablePositions) { float dist = Vector2.DistanceSquared(pos.Position.ToVector2(), level.BeaconStation.WorldPosition); if (dist < closestDist) { closestDist = dist; closestPos = pos; } } if (closestPos.HasValue) { spawnPos = closestPos.Value.Position.ToVector2(); } } if (monsterSets.Any()) { var monsterSet = ToolBox.SelectWeightedRandom(monsterSets, m => m.Commonness, Rand.RandSync.Unsynced); foreach ((CharacterPrefab monsterSpecies, Point monsterCountRange) in monsterSet.MonsterPrefabs) { int amount = Rand.Range(monsterCountRange.X, monsterCountRange.Y + 1); for (int i = 0; i < amount; i++) { CoroutineManager.Invoke(() => { //round ended before the coroutine finished if (GameMain.GameSession == null || Level.Loaded == null) { return; } Entity.Spawner.AddCharacterToSpawnQueue(monsterSpecies.Identifier, spawnPos); }, Rand.Range(0f, amount)); } } } swarmSpawned = true; } #if DEBUG || UNSTABLE if (State == 1 && !level.CheckBeaconActive()) { DebugConsole.ThrowError( "Debug/unstable only error message: beacon became inactive mid-mission after it had been activated! If this happened unexpectedly while you were away from the beacon, it may be a sign of a bug."+ " If possible, please try to check what caused the beacon to go inactive."); State = 2; } #endif } protected override bool DetermineCompleted() { return level.CheckBeaconActive(); } protected override void EndMissionSpecific(bool completed) { if (completed && level.LevelData != null) { level.LevelData.IsBeaconActive = true; } } public override void AdjustLevelData(LevelData levelData) { levelData.HasBeaconStation = true; levelData.IsBeaconActive = false; if (beaconTags.Length > 0) { var selectedBeacon = GetRandomBeaconByTags(beaconTags, levelData); if (selectedBeacon != null) { levelData.ForceBeaconStation = selectedBeacon; } else { DebugConsole.ThrowError($"Beacon mission \"{Prefab.Identifier}\" could not find a suitable beacon station with beacontags \"{string.Join(", ", beaconTags)}\" for level difficulty {levelData.Difficulty:F1}.", contentPackage: Prefab.ContentPackage); } } } private static SubmarineInfo GetRandomBeaconByTags(ImmutableArray tags, LevelData levelData) { return GetRandomSubmarineByTagsAndDifficulty( tags, levelData, s => s.IsBeacon, "beacon station"); } } }