2 using Microsoft.Xna.Framework;
4 using System.Collections.Generic;
12 private readonly XElement characterConfig;
14 protected readonly List<Character>
characters =
new List<Character>();
15 private readonly Dictionary<Character, List<Item>> characterItems =
new Dictionary<Character, List<Item>>();
16 protected readonly HashSet<Character>
requireKill =
new HashSet<Character>();
17 protected readonly HashSet<Character>
requireRescue =
new HashSet<Character>();
19 private readonly Identifier itemTag;
20 private readonly XElement itemConfig;
21 private readonly List<Item> items =
new List<Item>();
27 private const float EndDelay = 5.0f;
28 private float endTimer;
30 private bool allowOrderingRescuees;
49 return Targets.Select(t => (
Prefab.SonarLabel, t.WorldPosition));
58 private IEnumerable<Entity> Targets
64 return Enumerable.Empty<
Entity>();
70 return items.Where(it => !it.Removed && it.Condition > 0.0f).Cast<Entity>().Concat(
requireKill.Where(c => !c.Removed && !c.IsDead)).Concat(
requireRescue);
83 base(prefab, locations, sub)
90 hostagesKilledMessage = TextManager.Get(msgTag).Fallback(msgTag);
101 characterItems.Clear();
106 spawnedItems.Clear();
110 InitItems(submarine);
113 InitCharacters(submarine);
119 private void InitItems(
Submarine submarine)
121 if (!itemTag.IsEmpty)
124 if (!itemsToDestroy.Any())
126 DebugConsole.ThrowError($
"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".",
131 items.AddRange(itemsToDestroy);
135 if (itemConfig !=
null && !
IsClient)
137 foreach (XElement element
in itemConfig.Elements())
139 Identifier itemIdentifier = element.GetAttributeIdentifier(
"identifier", Identifier.Empty);
140 if (MapEntityPrefab.FindByIdentifier(itemIdentifier) is not ItemPrefab itemPrefab)
142 DebugConsole.ThrowError(
"Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier +
"\" not found",
147 Identifier[] moduleFlags = element.GetAttributeIdentifierArray(
"moduleflags",
null);
148 Identifier[] spawnPointTags = element.GetAttributeIdentifierArray(
"spawnpointtags",
null);
149 ISpatialEntity spawnPoint = SpawnAction.GetSpawnPos(
151 moduleFlags, spawnPointTags, element.GetAttributeBool(
"asfaraspossible",
false));
152 spawnPoint ??= submarine.
GetHulls(alsoFromConnectedSubs:
false).GetRandomUnsynced();
153 Vector2 spawnPos = spawnPoint.WorldPosition;
154 if (spawnPoint is WayPoint wp && wp.CurrentHull !=
null && wp.CurrentHull.Rect.Width > 100)
156 spawnPos =
new Vector2(
157 MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 201), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50),
158 wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 16.0f);
160 var item =
new Item(itemPrefab, spawnPos,
null);
163 spawnedItems.Add(item);
169 private void InitCharacters(Submarine submarine)
172 characterItems.Clear();
174 if (characterConfig !=
null)
176 foreach (XElement element
in characterConfig.Elements())
178 if (GameMain.NetworkMember ==
null && element.GetAttributeBool(
"multiplayeronly",
false)) {
continue; }
180 int defaultCount = element.GetAttributeInt(
"count", -1);
181 if (defaultCount < 0)
183 defaultCount = element.GetAttributeInt(
"amount", 1);
185 int min = Math.Min(element.GetAttributeInt(
"min", defaultCount), 255);
186 int max = Math.Min(Math.Max(min, element.GetAttributeInt(
"max", defaultCount)), 255);
187 int count = Rand.Range(min, max + 1);
189 if (element.Attribute(
"identifier") !=
null && element.Attribute(
"from") !=
null)
192 if (humanPrefab ==
null)
194 DebugConsole.ThrowError($
"Couldn't spawn a human character for abandoned outpost mission: human prefab \"{element.GetAttributeString("identifier
", string.Empty)}\" not found",
198 for (
int i = 0; i < count; i++)
200 LoadHuman(humanPrefab, element, submarine);
205 Identifier speciesName = element.GetAttributeIdentifier(
"character", element.GetAttributeIdentifier(
"identifier", Identifier.Empty));
206 var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
207 if (characterPrefab ==
null)
209 DebugConsole.ThrowError($
"Couldn't spawn a character for abandoned outpost mission: character prefab \"{speciesName}\" not found",
213 for (
int i = 0; i < count; i++)
215 LoadMonster(characterPrefab, element, submarine);
222 private void LoadHuman(HumanPrefab humanPrefab, XElement element, Submarine submarine)
224 Identifier[] moduleFlags = element.GetAttributeIdentifierArray(
"moduleflags",
null);
225 Identifier[] spawnPointTags = element.GetAttributeIdentifierArray(
"spawnpointtags",
null);
226 var spawnPointType = element.GetAttributeEnum(
"spawnpointtype",
SpawnType.Human);
227 ISpatialEntity spawnPos = SpawnAction.GetSpawnPos(
228 SpawnAction.SpawnLocationType.Outpost, spawnPointType,
229 moduleFlags ?? humanPrefab.GetModuleFlags(),
230 spawnPointTags ?? humanPrefab.GetSpawnPointTags(),
231 element.GetAttributeBool(
"asfaraspossible",
false));
232 spawnPos ??= submarine.GetHulls(alsoFromConnectedSubs:
false).GetRandomUnsynced();
234 bool requiresRescue = element.GetAttributeBool(
"requirerescue",
false);
237 if (Level.Loaded?.StartOutpost?.Info is { } outPostInfo)
239 outPostInfo.AddOutpostNPCIdentifierOrTag(spawnedCharacter, humanPrefab.Identifier);
240 foreach (Identifier tag
in humanPrefab.GetTags())
242 outPostInfo.AddOutpostNPCIdentifierOrTag(spawnedCharacter, tag);
246 if (spawnPos is WayPoint wp)
248 spawnedCharacter.GiveIdCardTags(wp);
255 if (allowOrderingRescuees)
257 GameMain.GameSession.CrewManager.AddCharacterToCrewList(spawnedCharacter);
261 else if (
TimesAttempted > 0 && spawnedCharacter.AIController is HumanAIController humanAi)
263 var order = OrderPrefab.Prefabs[
"fightintruders"]
264 .CreateInstance(OrderPrefab.OrderTargetType.Entity, orderGiver: spawnedCharacter)
265 .WithManualPriority(CharacterInfo.HighestManualOrderPriority);
266 spawnedCharacter.SetOrder(order, isNewOrder:
true, speak:
false);
269 if (element.GetAttributeBool(
"requirekill",
false))
275 private void LoadMonster(CharacterPrefab monsterPrefab, XElement element, Submarine submarine)
277 Identifier[] moduleFlags = element.GetAttributeIdentifierArray(
"moduleflags",
null);
278 Identifier[] spawnPointTags = element.GetAttributeIdentifierArray(
"spawnpointtags",
null);
279 ISpatialEntity spawnPos = SpawnAction.GetSpawnPos(SpawnAction.SpawnLocationType.Outpost,
SpawnType.Enemy, moduleFlags, spawnPointTags, element.GetAttributeBool(
"asfaraspossible",
false));
280 spawnPos ??= submarine.GetHulls(alsoFromConnectedSubs:
false).GetRandomUnsynced();
281 Character spawnedCharacter =
Character.Create(monsterPrefab.Identifier, spawnPos.WorldPosition, ToolBox.RandomSeed(8), createNetworkEvent:
false);
283 if (element.GetAttributeBool(
"requirekill",
false))
287 if (spawnedCharacter.Inventory !=
null)
289 characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive:
true));
291 if (submarine !=
null && spawnedCharacter.AIController is EnemyAIController enemyAi)
293 enemyAi.UnattackableSubmarines.Add(submarine);
294 enemyAi.UnattackableSubmarines.Add(
Submarine.MainSub);
295 foreach (Submarine sub
in Submarine.MainSub.DockedTo)
297 enemyAi.UnattackableSubmarines.Add(sub);
315 endTimer += deltaTime;
316 if (endTimer > EndDelay)
331 if (items.All(it => it.Removed || it.Condition <= 0.0f) &&
override bool DetermineCompleted()
readonly List< Character > characters
override bool AllowRespawn
readonly HashSet< Character > requireRescue
override bool AllowUndocking
override void StartMissionSpecific(Level level)
readonly HashSet< Character > requireKill
override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
const int HostagesKilledState
AbandonedOutpostMission(MissionPrefab prefab, Location[] locations, Submarine sub)
override void UpdateMissionSpecific(float deltaTime)
override void EndMissionSpecific(bool completed)
string? GetAttributeString(string key, string? def)
ContentXElement? GetChildElement(string name)
bool GetAttributeBool(string key, bool def)
Identifier GetAttributeIdentifier(string key, string def)
static GameSession?? GameSession
static readonly List< Item > ItemList
Defines a point in the event that GoTo actions can jump to.
readonly MissionPrefab Prefab
static Character CreateHuman(HumanPrefab humanPrefab, List< Character > characters, Dictionary< Character, List< Item >> characterItems, Submarine submarine, CharacterTeamType teamType, ISpatialEntity positionToStayIn=null, Rand.RandSync humanPrefabRandSync=Rand.RandSync.ServerAndClient)
HumanPrefab GetHumanPrefabFromElement(XElement element)
readonly ContentXElement ConfigElement
ContentPackage? ContentPackage
IEnumerable< Submarine > DockedTo
static List< Submarine > Loaded
List< Hull > GetHulls(bool alsoFromConnectedSubs)