3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
14 private readonly List<Item> items =
new List<Item>();
15 private readonly Dictionary<Item, StatusEffect> statusEffectOnApproach =
new Dictionary<Item, StatusEffect>();
18 private readonly HashSet<Tuple<CharacterPrefab, Point>> monsterPrefabs =
new HashSet<Tuple<CharacterPrefab, Point>>();
20 private float itemSpawnRadius = 800.0f;
21 private readonly
float approachItemsRadius = 1000.0f;
22 private readonly
float nestObjectRadius = 1000.0f;
23 private readonly
float monsterSpawnRadius = 3000.0f;
24 private readonly
int nestObjectAmount = 10;
26 private readonly
bool requireDelivery;
30 private Vector2 nestPosition;
45 yield
return (
Prefab.SonarLabel, nestPosition);
51 : base(prefab, locations, sub)
65 if (
string.IsNullOrWhiteSpace(spawnPositionTypeStr) ||
66 !Enum.TryParse(spawnPositionTypeStr,
true, out spawnPositionType))
71 foreach (var monsterElement
in prefab.ConfigElement.GetChildElements(
"monster"))
73 Identifier speciesName = monsterElement.GetAttributeIdentifier(
"character", Identifier.Empty);
74 int defaultCount = monsterElement.GetAttributeInt(
"count", -1);
77 defaultCount = monsterElement.GetAttributeInt(
"amount", 1);
79 int min = Math.Min(monsterElement.GetAttributeInt(
"min", defaultCount), 255);
80 int max = Math.Min(Math.Max(min, monsterElement.GetAttributeInt(
"max", defaultCount)), 255);
82 if (characterPrefab !=
null)
84 monsterPrefabs.Add(
new Tuple<CharacterPrefab, Point>(characterPrefab,
new Point(min, max)));
88 DebugConsole.ThrowError($
"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
100 throw new Exception($
"items.Count > 0 ({items.Count})");
102 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.");
114 List<GraphEdge> spawnEdges =
new List<GraphEdge>();
118 float closestCaveDist =
float.PositiveInfinity;
121 float dist = Vector2.DistanceSquared(nestPosition, cave.Area.Center.ToVector2());
122 if (dist < closestCaveDist)
125 closestCaveDist = dist;
128 if (closestCave !=
null)
130 selectedCave = closestCave;
131 selectedCave.MissionsToDisplayOnSonar.Add(
this);
132 SpawnNestObjects(
level, closestCave);
135 if (nearbyCells.Any())
137 List<GraphEdge> validEdges =
new List<GraphEdge>();
138 foreach (var edge
in nearbyCells.SelectMany(c => c.Edges))
140 if (!edge.NextToCave || !edge.IsSolid) {
continue; }
141 if (
Level.
Loaded.
ExtraWalls.Any(w => w.IsPointInside(edge.Center + edge.GetNormal(edge.Cell1 ?? edge.Cell2) * 100.0f))) {
continue; }
142 validEdges.Add(edge);
145 if (validEdges.Any())
147 spawnEdges.AddRange(validEdges.Where(e => MathUtils.LineSegmentToPointDistanceSquared(e.Point1.ToPoint(), e.Point2.ToPoint(), nestPosition.ToPoint()) < itemSpawnRadius * itemSpawnRadius).Distinct());
150 if (!spawnEdges.Any())
153 float closestDistSqr =
float.PositiveInfinity;
154 foreach (var edge
in nearbyCells.SelectMany(c => c.Edges))
156 if (!edge.NextToCave || !edge.IsSolid) {
continue; }
157 float dist = Vector2.DistanceSquared(edge.Center, nestPosition);
158 if (dist < closestDistSqr)
161 closestDistSqr = dist;
164 if (closestEdge !=
null)
166 spawnEdges.Add(closestEdge);
167 itemSpawnRadius = Math.Max(itemSpawnRadius, (
float)Math.Sqrt(closestDistSqr) * 1.5f);
173 foreach (var subElement
in itemConfig.Elements())
175 var itemIdentifier = subElement.GetAttributeIdentifier(
"identifier", Identifier.Empty);
178 DebugConsole.ThrowError(
"Couldn't spawn item for nest mission: item prefab \"" + itemIdentifier +
"\" not found",
183 Vector2 spawnPos = nestPosition;
184 float rotation = 0.0f;
185 if (spawnEdges.Any())
187 const float MinDistanceFromOtherItems = 30.0f;
188 const int MaxTries = 10;
189 for (
int i = 0; i < MaxTries; i++)
191 var edge = spawnEdges.GetRandom(Rand.RandSync.ServerAndClient);
192 spawnPos = Vector2.Lerp(edge.Point1, edge.Point2, Rand.Range(0.1f, 0.9f, Rand.RandSync.ServerAndClient));
193 Vector2 normal = Vector2.UnitY;
194 if (edge.Cell1 !=
null && edge.Cell1.CellType ==
CellType.Solid)
198 else if (edge.Cell2 !=
null && edge.Cell2.CellType ==
CellType.Solid)
200 normal = edge.GetNormal(edge.Cell2);
202 spawnPos += normal * 10.0f;
203 rotation = MathUtils.VectorToAngle(normal) - MathHelper.PiOver2;
205 if (items.All(it => Vector2.DistanceSquared(it.WorldPosition, spawnPos) > MinDistanceFromOtherItems)) {
break; }
209 var item =
new Item(itemPrefab, spawnPos,
null);
210 item.body.FarseerBody.BodyType = BodyType.Kinematic;
211 item.body.SetTransformIgnoreContacts(item.body.SimPosition, rotation);
213 item.AddTag(
"nestmission");
217 var statusEffectElement =
218 subElement.GetChildElement(
"StatusEffectOnApproach")
219 ?? subElement.GetChildElement(
"statuseffectonapproach");
220 if (statusEffectElement !=
null)
237 foreach (
Item item
in items)
246 foreach (
Item item
in items)
249 if (statusEffectOnApproach.ContainsKey(item))
253 if (character.
IsPlayer && Vector2.DistanceSquared(nestPosition, character.
WorldPosition) < approachItemsRadius * approachItemsRadius)
255 statusEffectOnApproach[item].Apply(statusEffectOnApproach[item].type, 1.0f, item, item);
256 statusEffectOnApproach.Remove(item);
262 if (monsterPrefabs.Any())
266 if (character.
IsPlayer && Vector2.DistanceSquared(nestPosition, character.
WorldPosition) < monsterSpawnRadius * monsterSpawnRadius)
268 foreach (var monster
in monsterPrefabs)
270 int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1);
271 for (
int i = 0; i < amount; i++)
273 Vector2 offsetPosition;
277 offsetPosition = nestPosition + Rand.Vector(100.0f);
281 offsetPosition = nestPosition;
285 Character.
Create(monster.Item1.Identifier, offsetPosition, ToolBox.RandomSeed(8), createNetworkEvent:
true);
290 DebugConsole.AddWarning($
"Error in nest mission \"{Prefab.Identifier}\": nest position was inside a wall ({nestPosition}).",
293 monsterPrefabs.Clear();
300 if (AllItemsDestroyedOrRetrieved())
313 private bool AllItemsDestroyedOrRetrieved()
317 foreach (
Item item
in items)
320 if (parentSub?.Info?.Type ==
SubmarineType.Player) {
continue; }
326 foreach (Item item
in items)
328 if (item.Removed || item.Condition <= 0.0f) {
continue; }
329 if (Vector2.Distance(item.WorldPosition, nestPosition) > Math.Max(itemSpawnRadius * 2, 3000.0f)) {
continue; }
330 Submarine parentSub = item.CurrentHull?.
Submarine ?? item.GetRootInventoryOwner()?.Submarine;
331 if (parentSub?.Info?.Type ==
SubmarineType.Player) {
continue; }
340 return AllItemsDestroyedOrRetrieved();
345 foreach (
Item item
in items)
347 if (item !=
null && !item.
Removed)
static Character Create(CharacterInfo characterInfo, Vector2 position, string seed, ushort id=Entity.NullEntityID, bool isRemotePlayer=false, bool hasAi=true, RagdollParams ragdoll=null, bool spawnInitialItems=true)
Create a new character
static readonly List< Character > CharacterList
static CharacterPrefab FindBySpeciesName(Identifier speciesName)
string? GetAttributeString(string key, string? def)
float GetAttributeFloat(string key, float def)
ContentXElement? GetChildElement(string name)
bool GetAttributeBool(string key, bool def)
int GetAttributeInt(string key, int def)
virtual Vector2 WorldPosition
Inventory ParentInventory
Entity GetRootInventoryOwner()
Defines a point in the event that GoTo actions can jump to.
LevelObjectManager LevelObjectManager
Vector2 GetRandomItemPos(PositionType spawnPosType, float randomSpread, float minDistFromSubs, float offsetFromWall=10.0f, Func< InterestingPosition, bool > filter=null)
bool IsPositionInsideWall(Vector2 worldPosition)
List< VoronoiCell > GetCells(Vector2 worldPos, int searchDepth=2)
List< LevelWall > ExtraWalls
void PlaceNestObjects(Level level, Level.Cave cave, Vector2 nestPosition, float nestRadius, int objectAmount)
static MapEntityPrefab FindByIdentifier(Identifier identifier)
readonly ContentXElement ConfigElement
NestMission(MissionPrefab prefab, Location[] locations, Submarine sub)
override bool DetermineCompleted()
override void StartMissionSpecific(Level level)
override void UpdateMissionSpecific(float deltaTime)
override void EndMissionSpecific(bool completed)
override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
ContentPackage? ContentPackage
readonly Identifier Identifier
StatusEffects can be used to execute various kinds of effects: modifying the state of some entity in ...
static StatusEffect Load(ContentXElement element, string parentDebugName)
Submarine(SubmarineInfo info, bool showErrorMessages=true, Func< Submarine, List< MapEntity >> loadEntities=null, IdRemap linkedRemap=null)
Vector2 GetNormal(VoronoiCell cell)
Returns the normal of the edge that points outwards from the specified cell