Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Events/Missions/NestMission.cs
T
2023-11-10 17:45:19 +02:00

357 lines
16 KiB
C#

using Barotrauma.Extensions;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Voronoi2;
namespace Barotrauma
{
partial class NestMission : Mission
{
private readonly ContentXElement itemConfig;
private readonly List<Item> items = new List<Item>();
private readonly Dictionary<Item, StatusEffect> statusEffectOnApproach = new Dictionary<Item, StatusEffect>();
//string = filename, point = min,max
private readonly HashSet<Tuple<CharacterPrefab, Point>> monsterPrefabs = new HashSet<Tuple<CharacterPrefab, Point>>();
private float itemSpawnRadius = 800.0f;
private readonly float approachItemsRadius = 1000.0f;
private readonly float nestObjectRadius = 1000.0f;
private readonly float monsterSpawnRadius = 3000.0f;
private readonly int nestObjectAmount = 10;
private readonly bool requireDelivery;
private readonly Level.PositionType spawnPositionType;
private Vector2 nestPosition;
private Level.Cave selectedCave;
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
{
get
{
if (State > 0)
{
yield break;
}
else
{
yield return (Prefab.SonarLabel, nestPosition);
}
}
}
public NestMission(MissionPrefab prefab, Location[] locations, Submarine sub)
: base(prefab, locations, sub)
{
itemConfig = prefab.ConfigElement.GetChildElement("Items");
itemSpawnRadius = prefab.ConfigElement.GetAttributeFloat("itemspawnradius", 800.0f);
approachItemsRadius = prefab.ConfigElement.GetAttributeFloat("approachitemsradius", itemSpawnRadius * 2.0f);
monsterSpawnRadius = prefab.ConfigElement.GetAttributeFloat("monsterspawnradius", approachItemsRadius * 2.0f);
nestObjectRadius = prefab.ConfigElement.GetAttributeFloat("nestobjectradius", itemSpawnRadius * 2.0f);
nestObjectAmount = prefab.ConfigElement.GetAttributeInt("nestobjectamount", 10);
requireDelivery = prefab.ConfigElement.GetAttributeBool("requiredelivery", false);
string spawnPositionTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
if (string.IsNullOrWhiteSpace(spawnPositionTypeStr) ||
!Enum.TryParse(spawnPositionTypeStr, true, out spawnPositionType))
{
spawnPositionType = Level.PositionType.Cave | Level.PositionType.Ruin;
}
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
{
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)
{
monsterPrefabs.Add(new Tuple<CharacterPrefab, Point>(characterPrefab, new Point(min, max)));
}
else
{
DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
contentPackage: Prefab.ContentPackage);
}
}
}
protected override void StartMissionSpecific(Level level)
{
if (items.Any())
{
#if DEBUG
throw new Exception($"items.Count > 0 ({items.Count})");
#else
DebugConsole.AddWarning("Item list was not empty at the start of a nest mission. The mission instance may not have been ended correctly on previous rounds.");
items.Clear();
#endif
}
if (!IsClient)
{
//ruin/cave/wreck items are allowed to spawn close to the sub
float minDistance = spawnPositionType == Level.PositionType.Ruin || spawnPositionType == Level.PositionType.Cave || spawnPositionType == Level.PositionType.Wreck ?
0.0f : Level.Loaded.Size.X * 0.3f;
nestPosition = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f);
List<GraphEdge> spawnEdges = new List<GraphEdge>();
if (spawnPositionType == Level.PositionType.Cave)
{
Level.Cave closestCave = null;
float closestCaveDist = float.PositiveInfinity;
foreach (var cave in Level.Loaded.Caves)
{
float dist = Vector2.DistanceSquared(nestPosition, cave.Area.Center.ToVector2());
if (dist < closestCaveDist)
{
closestCave = cave;
closestCaveDist = dist;
}
}
if (closestCave != null)
{
selectedCave = closestCave;
selectedCave.MissionsToDisplayOnSonar.Add(this);
SpawnNestObjects(level, closestCave);
}
var nearbyCells = Level.Loaded.GetCells(nestPosition, searchDepth: 3);
if (nearbyCells.Any())
{
List<GraphEdge> validEdges = new List<GraphEdge>();
foreach (var edge in nearbyCells.SelectMany(c => c.Edges))
{
if (!edge.NextToCave || !edge.IsSolid) { continue; }
if (Level.Loaded.ExtraWalls.Any(w => w.IsPointInside(edge.Center + edge.GetNormal(edge.Cell1 ?? edge.Cell2) * 100.0f))) { continue; }
validEdges.Add(edge);
}
if (validEdges.Any())
{
spawnEdges.AddRange(validEdges.Where(e => MathUtils.LineSegmentToPointDistanceSquared(e.Point1.ToPoint(), e.Point2.ToPoint(), nestPosition.ToPoint()) < itemSpawnRadius * itemSpawnRadius).Distinct());
}
//no valid edges found close enough to the nest position, find the closest one
if (!spawnEdges.Any())
{
GraphEdge closestEdge = null;
float closestDistSqr = float.PositiveInfinity;
foreach (var edge in nearbyCells.SelectMany(c => c.Edges))
{
if (!edge.NextToCave || !edge.IsSolid) { continue; }
float dist = Vector2.DistanceSquared(edge.Center, nestPosition);
if (dist < closestDistSqr)
{
closestEdge = edge;
closestDistSqr = dist;
}
}
if (closestEdge != null)
{
spawnEdges.Add(closestEdge);
itemSpawnRadius = Math.Max(itemSpawnRadius, (float)Math.Sqrt(closestDistSqr) * 1.5f);
}
}
}
}
foreach (var subElement in itemConfig.Elements())
{
var itemIdentifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
if (MapEntityPrefab.FindByIdentifier(itemIdentifier) is not ItemPrefab itemPrefab)
{
DebugConsole.ThrowError("Couldn't spawn item for nest mission: item prefab \"" + itemIdentifier + "\" not found",
contentPackage: Prefab.ContentPackage);
continue;
}
Vector2 spawnPos = nestPosition;
float rotation = 0.0f;
if (spawnEdges.Any())
{
const float MinDistanceFromOtherItems = 30.0f;
const int MaxTries = 10;
for (int i = 0; i < MaxTries; i++)
{
var edge = spawnEdges.GetRandom(Rand.RandSync.ServerAndClient);
spawnPos = Vector2.Lerp(edge.Point1, edge.Point2, Rand.Range(0.1f, 0.9f, Rand.RandSync.ServerAndClient));
Vector2 normal = Vector2.UnitY;
if (edge.Cell1 != null && edge.Cell1.CellType == CellType.Solid)
{
normal = edge.GetNormal(edge.Cell1);
}
else if (edge.Cell2 != null && edge.Cell2.CellType == CellType.Solid)
{
normal = edge.GetNormal(edge.Cell2);
}
spawnPos += normal * 10.0f;
rotation = MathUtils.VectorToAngle(normal) - MathHelper.PiOver2;
if (items.All(it => Vector2.DistanceSquared(it.WorldPosition, spawnPos) > MinDistanceFromOtherItems)) { break; }
}
}
var item = new Item(itemPrefab, spawnPos, null);
item.body.FarseerBody.BodyType = BodyType.Kinematic;
item.body.SetTransformIgnoreContacts(item.body.SimPosition, rotation);
item.FindHull();
item.AddTag("nestmission");
item.AddTag(Prefab.Identifier);
items.Add(item);
var statusEffectElement =
subElement.GetChildElement("StatusEffectOnApproach")
?? subElement.GetChildElement("statuseffectonapproach");
if (statusEffectElement != null)
{
statusEffectOnApproach.Add(item, StatusEffect.Load(statusEffectElement, Prefab.Identifier.Value));
}
}
}
}
private void SpawnNestObjects(Level level, Level.Cave cave)
{
level.LevelObjectManager.PlaceNestObjects(level, cave, nestPosition, nestObjectRadius, nestObjectAmount);
}
protected override void UpdateMissionSpecific(float deltaTime)
{
if (IsClient)
{
foreach (Item item in items)
{
if (item.ParentInventory != null && item.body != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
}
return;
}
switch (State)
{
case 0:
foreach (Item item in items)
{
if (item.ParentInventory != null && item.body != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
if (statusEffectOnApproach.ContainsKey(item))
{
foreach (Character character in Character.CharacterList)
{
if (character.IsPlayer && Vector2.DistanceSquared(nestPosition, character.WorldPosition) < approachItemsRadius * approachItemsRadius)
{
statusEffectOnApproach[item].Apply(statusEffectOnApproach[item].type, 1.0f, item, item);
statusEffectOnApproach.Remove(item);
break;
}
}
}
}
if (monsterPrefabs.Any())
{
foreach (Character character in Character.CharacterList)
{
if (character.IsPlayer && Vector2.DistanceSquared(nestPosition, character.WorldPosition) < monsterSpawnRadius * monsterSpawnRadius)
{
foreach (var monster in monsterPrefabs)
{
int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1);
for (int i = 0; i < amount; i++)
{
Vector2 offsetPosition;
int tries = 0;
do
{
offsetPosition = nestPosition + Rand.Vector(100.0f);
tries++;
if (tries > 10)
{
offsetPosition = nestPosition;
break;
}
} while (Level.Loaded.IsPositionInsideWall(offsetPosition));
Character.Create(monster.Item1.Identifier, offsetPosition, ToolBox.RandomSeed(8), createNetworkEvent: true);
}
}
if (Level.Loaded.IsPositionInsideWall(nestPosition))
{
DebugConsole.AddWarning($"Error in nest mission \"{Prefab.Identifier}\": nest position was inside a wall ({nestPosition}).",
Prefab.ContentPackage);
}
monsterPrefabs.Clear();
break;
}
}
}
//continue when all items are in the sub or destroyed
if (AllItemsDestroyedOrRetrieved())
{
State = 1;
}
break;
case 1:
if (!Submarine.MainSub.AtEitherExit) { return; }
State = 2;
break;
}
}
private bool AllItemsDestroyedOrRetrieved()
{
if (requireDelivery)
{
foreach (Item item in items)
{
Submarine parentSub = item.CurrentHull?.Submarine ?? item.GetRootInventoryOwner()?.Submarine;
if (parentSub?.Info?.Type == SubmarineType.Player) { continue; }
return false;
}
}
else
{
foreach (Item item in items)
{
if (item.Removed || item.Condition <= 0.0f) { continue; }
if (Vector2.Distance(item.WorldPosition, nestPosition) > Math.Max(itemSpawnRadius * 2, 3000.0f)) { continue; }
Submarine parentSub = item.CurrentHull?.Submarine ?? item.GetRootInventoryOwner()?.Submarine;
if (parentSub?.Info?.Type == SubmarineType.Player) { continue; }
return false;
}
}
return true;
}
protected override bool DetermineCompleted()
{
return AllItemsDestroyedOrRetrieved();
}
protected override void EndMissionSpecific(bool completed)
{
foreach (Item item in items)
{
if (item != null && !item.Removed)
{
item.Remove();
}
}
items.Clear();
failed = !completed && state > 0;
}
}
}