1 using Microsoft.Xna.Framework;
2 using System.Collections.Generic;
11 private readonly HashSet<(
CharacterPrefab character, Point amountRange)> monsterPrefabs =
new HashSet<(
CharacterPrefab character, Point amountRange)>();
12 private readonly List<Character> monsters =
new List<Character>();
13 private readonly List<Vector2> sonarPositions =
new List<Vector2>();
14 private readonly List<Vector2> tempSonarPositions =
new List<Vector2>();
15 private readonly
float maxSonarMarkerDistance = 10000.0f;
17 private Vector2? spawnPos =
null;
29 foreach (Vector2 sonarPos
in sonarPositions)
31 yield
return (
Prefab.SonarLabel, sonarPos);
38 : base(prefab, locations, sub)
41 if (!speciesName.IsEmpty)
44 if (characterPrefab !=
null)
47 monsterPrefabs.Add((characterPrefab,
new Point(monsterCount)));
51 DebugConsole.ThrowError($
"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
59 if (
string.IsNullOrWhiteSpace(spawnPosTypeStr) ||
60 !Enum.TryParse(spawnPosTypeStr,
true, out spawnPosType))
65 foreach (var monsterElement
in prefab.ConfigElement.GetChildElements(
"monster"))
67 speciesName = monsterElement.GetAttributeIdentifier(
"character", Identifier.Empty);
68 int defaultCount = monsterElement.GetAttributeInt(
"count", -1);
71 defaultCount = monsterElement.GetAttributeInt(
"amount", 1);
73 int min = Math.Min(monsterElement.GetAttributeInt(
"min", defaultCount), 255);
74 int max = Math.Min(Math.Max(min, monsterElement.GetAttributeInt(
"max", defaultCount)), 255);
76 if (characterPrefab !=
null)
78 monsterPrefabs.Add((characterPrefab,
new Point(min, max)));
82 DebugConsole.ThrowError($
"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
83 contentPackage: prefab.ContentPackage);
87 if (monsterPrefabs.Any())
91 TextManager.Get(
"character." + characterParams.SpeciesTranslationOverride).Fallback(
92 TextManager.Get(
"character." + characterParams.SpeciesName)));
98 if (monsters.Count > 0)
101 throw new Exception($
"monsters.Count > 0 ({monsters.Count})");
103 DebugConsole.AddWarning(
"Monster list was not empty at the start of a monster mission. The mission instance may not have been ended correctly on previous rounds.");
108 if (tempSonarPositions.Count > 0)
111 throw new Exception($
"tempSonarPositions.Count > 0 ({tempSonarPositions.Count})");
113 DebugConsole.AddWarning(
"Sonar position list was not empty at the start of a monster mission. The mission instance may not have been ended correctly on previous rounds.");
114 tempSonarPositions.Clear();
120 float minDistBetweenMonsterMissions = 10000;
124 filter: p => monsterMissions.None(m => Vector2.DistanceSquared(p.Position.ToVector2(), m.spawnPos.Value) < minDistBetweenMonsterMissions * minDistBetweenMonsterMissions),
125 suppressWarning:
true))
129 this.spawnPos = spawnPos.Position.ToVector2();
130 foreach (var (character, amountRange) in monsterPrefabs)
132 int amount = Rand.Range(amountRange.X, amountRange.Y + 1);
133 for (
int i = 0; i < amount; i++)
135 monsters.Add(
Character.
Create(character.Identifier,
this.spawnPos.Value, ToolBox.RandomSeed(8), createNetworkEvent:
false));
138 InitializeMonsters(monsters);
142 private void InitializeMonsters(IEnumerable<Character> monsters)
144 foreach (var monster
in monsters)
146 monster.Enabled =
false;
147 if (monster.Params.AI !=
null && monster.Params.AI.EnforceAggressiveBehaviorForMissions)
149 monster.Params.AI.FleeHealthThreshold = 0;
150 foreach (var targetParam
in monster.Params.AI.Targets)
152 if (targetParam.Tag ==
"engine") {
continue; }
153 switch (targetParam.State)
158 case AIState.PassiveAggressive:
159 targetParam.State =
AIState.Attack;
165 SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
166 foreach (Character monster
in monsters)
168 tempSonarPositions.Add(monster.WorldPosition + Rand.Vector(maxSonarMarkerDistance));
170 if (monsters.Count() != tempSonarPositions.Count)
172 throw new Exception($
"monsters.Count != tempSonarPositions.Count ({monsters.Count()} != {tempSonarPositions.Count})");
182 for (
int i = 0; i < tempSonarPositions.Count; i++)
184 if (monsters.Count != tempSonarPositions.Count)
186 throw new Exception($
"monsters.Count != tempSonarPositions.Count ({monsters.Count} != {tempSonarPositions.Count})");
189 if (i < 0 || i >= monsters.Count)
191 throw new Exception($
"Index {i} outside of bounds 0-{monsters.Count} ({tempSonarPositions.Count})");
194 if (monsters[i].Removed || monsters[i].IsDead) {
continue; }
195 Vector2 diff = tempSonarPositions[i] - monsters[i].WorldPosition;
197 float maxDist = maxSonarMarkerDistance;
201 Vector2 refPos = refSub ==
null ? Vector2.Zero : refSub.
WorldPosition;
202 float subDist = Vector2.Distance(refPos, tempSonarPositions[i]) / maxDist;
204 maxDist = Math.Min(subDist * subDist * maxDist, maxDist);
205 maxDist = Math.Min(Vector2.Distance(refPos, monsters[i].WorldPosition), maxDist);
208 if (diff.LengthSquared() > maxDist * maxDist)
210 tempSonarPositions[i] = monsters[i].WorldPosition + Vector2.Normalize(diff) * maxDist;
214 sonarPositions.Clear();
215 for (
int i = 0; i < monsters.Count; i++)
217 if (monsters[i].Removed || monsters[i].IsDead) {
continue; }
219 if (sonarPositions.All(p => Vector2.DistanceSquared(p, tempSonarPositions[i]) > 1000.0f * 1000.0f))
221 sonarPositions.Add(tempSonarPositions[i]);
239 tempSonarPositions.Clear();
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 Character? Controlled
Contains character data that should be editable in the character editor.
static CharacterPrefab FindBySpeciesName(Identifier speciesName)
string? GetAttributeString(string key, string? def)
float GetAttributeFloat(string key, float def)
int GetAttributeInt(string key, int def)
Identifier GetAttributeIdentifier(string key, string def)
static GameSession?? GameSession
IEnumerable< Mission > Missions
Defines a point in the event that GoTo actions can jump to.
bool TryGetInterestingPosition(bool useSyncedRand, PositionType positionType, float minDistFromSubs, out InterestingPosition position, Func< InterestingPosition, bool > filter=null, bool suppressWarning=false)
readonly LevelData LevelData
LocalizedString Replace(Identifier find, LocalizedString replace, StringComparison stringComparison=StringComparison.Ordinal)
LocalizedString description
readonly ContentXElement ConfigElement
override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
override void UpdateMissionSpecific(float deltaTime)
override bool DetermineCompleted()
override void StartMissionSpecific(Level level)
static bool IsEliminated(Character enemy)
MonsterMission(MissionPrefab prefab, Location[] locations, Submarine sub)
override void EndMissionSpecific(bool completed)
ContentPackage? ContentPackage
override Vector2? WorldPosition