Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/BeaconMission.cs
2025-09-17 13:44:21 +03:00

216 lines
9.0 KiB
C#

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<MonsterSet> monsterSets = new List<MonsterSet>();
private readonly LocalizedString sonarLabel;
private readonly ImmutableArray<Identifier> 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<Submarine> 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<Reactor>() != null;
if ((isReactor && GameMain.GameSession is not { TraitorsEnabled: true }) ||
item.GetComponent<PowerTransfer>() != null ||
item.GetComponent<PowerContainer>() != null ||
item.GetComponent<Sonar>() != 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<Identifier> tags, LevelData levelData)
{
return GetRandomSubmarineByTagsAndDifficulty(
tags,
levelData,
s => s.IsBeacon,
"beacon station");
}
}
}