4 using Microsoft.Xna.Framework;
6 using System.Collections.Generic;
17 private readonly
float addedMissionDifficultyPerPlayer;
19 private float missionDifficulty;
20 private int alternateReward;
22 private Identifier factionIdentifier;
25 private readonly List<Character> characters =
new List<Character>();
26 private readonly Dictionary<Character, List<Item>> characterItems =
new Dictionary<Character, List<Item>>();
29 private readonly
float pirateSightingUpdateFrequency = 30;
30 private float pirateSightingUpdateTimer;
31 private Vector2? lastSighting;
37 private bool outsideOfSonarRange;
39 private readonly List<Vector2> patrolPositions =
new List<Vector2>();
45 if (!outsideOfSonarRange ||
state > 1)
52 foreach (Vector2 patrolPos
in patrolPositions)
54 yield
return (
Prefab.SonarLabel, patrolPos);
59 if (lastSighting.HasValue)
61 yield
return (
Prefab.SonarLabel, lastSighting.Value);
73 return alternateReward;
87 private const float RandomnessModifier = 25;
88 private const float ShipRandomnessModifier = 15;
90 private const float MaxDifficulty = 100;
100 foreach (XElement characterElement
in characterConfig.Elements())
102 var characterId = characterElement.GetAttributeString(
"typeidentifier",
string.Empty);
103 var characterTypeElement = characterTypeConfig.Elements().FirstOrDefault(e => e.GetAttributeString(
"typeidentifier",
string.Empty) == characterId);
104 if (characterTypeElement ==
null)
106 DebugConsole.ThrowError($
"Error in mission \"{prefab.Identifier}\". Could not find a character type element for the character \"{characterId}\".",
111 foreach (XElement characterTypeElement
in characterTypeConfig.Elements())
113 foreach (XElement characterElement
in characterTypeElement.Elements())
115 Identifier characterIdentifier = characterElement.GetAttributeIdentifier(
"identifier", Identifier.Empty);
116 Identifier characterFrom = characterElement.GetAttributeIdentifier(
"from", Identifier.Empty);
117 HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
118 if (humanPrefab ==
null)
120 DebugConsole.ThrowError($
"Error in mission \"{prefab.Identifier}\". Character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\".",
127 LevelData levelData = locations[0].Connections.Where(c => c.Locations.Contains(locations[1])).FirstOrDefault()?.
LevelData ?? locations[0]?.LevelData;
128 if (levelData !=
null)
136 if (levelData !=
null)
141 submarineInfo =
null;
146 XElement submarineConfig = GetRandomDifficultyModifiedElement(submarineTypeConfig, missionDifficulty, ShipRandomnessModifier);
147 alternateReward = submarineConfig.GetAttributeInt(
"alternatereward",
Reward);
148 factionIdentifier = submarineConfig.GetAttributeIdentifier(
"faction", Identifier.Empty);
150 string rewardText = $
"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}
", alternateReward)}‖end‖";
154 if (submarinePath.IsNullOrEmpty())
156 DebugConsole.ThrowError($
"No path used for submarine for the pirate mission \"{Prefab.Identifier}\"!",
162 GetSubFile<EnemySubmarineFile>(submarinePath) ??
163 GetSubFile<SubmarineFile>(submarinePath);
166 return ContentPackageManager.EnabledPackages.All.SelectMany(p => p.GetFiles<T>()).FirstOrDefault(f => f.Path == submarinePath);
169 if (contentFile ==
null)
171 DebugConsole.ThrowError($
"No submarine file found from the path {submarinePath}!",
179 private static float GetDifficultyModifiedValue(
float preferredDifficulty,
float levelDifficulty,
float randomnessModifier, Random rand)
181 return Math.Abs(levelDifficulty - preferredDifficulty + MathHelper.Lerp(-randomnessModifier, randomnessModifier, (
float)rand.NextDouble()));
183 private static int GetDifficultyModifiedAmount(
int minAmount,
int maxAmount,
float levelDifficulty, Random rand)
185 return Math.Max((
int)Math.Round(minAmount + (maxAmount - minAmount) * (levelDifficulty + MathHelper.Lerp(-RandomnessModifier, RandomnessModifier, (
float)rand.NextDouble())) / MaxDifficulty), minAmount);
188 private XElement GetRandomDifficultyModifiedElement(XElement parentElement,
float levelDifficulty,
float randomnessModifier)
190 Random rand =
new MTRandom(ToolBox.StringToInt(levelData.
Seed));
192 XElement bestElement =
null;
193 float bestValue =
float.MaxValue;
194 foreach (XElement element
in parentElement.Elements())
196 float applicabilityValue = GetDifficultyModifiedValue(element.GetAttributeFloat(0f,
"preferreddifficulty"), levelDifficulty, randomnessModifier, rand);
197 if (applicabilityValue < bestValue)
199 bestElement = element;
200 bestValue = applicabilityValue;
206 private void CreateMissionPositions(out Vector2 preferredSpawnPos)
208 Vector2 patrolPos = Level.Loaded.EndPosition;
211 preferredSpawnPos = Level.Loaded.EndPosition;
213 if (Level.Loaded.TryGetInterestingPosition(
true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out var potentialSpawnPos))
215 preferredSpawnPos = potentialSpawnPos.Position.ToVector2();
219 DebugConsole.ThrowError(
"Could not spawn pirate submarine in an interesting location! " +
this,
222 if (Level.Loaded.TryGetInterestingPositionAwayFromPoint(
true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out var potentialPatrolPos, preferredSpawnPos, minDistFromPoint: 10000f))
224 patrolPos = potentialPatrolPos.Position.ToVector2();
228 DebugConsole.ThrowError(
"Could not give pirate submarine an interesting location to patrol to! " +
this,
234 patrolPositions.Add(patrolPos);
235 patrolPositions.Add(preferredSpawnPos);
239 PathFinder pathFinder =
new PathFinder(WayPoint.WayPointList,
false);
240 var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(patrolPos), ConvertUnits.ToSimUnits(preferredSpawnPos));
241 if (!path.Unreachable)
243 var validNodes = path.Nodes.FindAll(n => !Level.Loaded.ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(n.WorldPosition))));
244 if (validNodes.Any())
246 preferredSpawnPos = validNodes.GetRandomUnsynced().WorldPosition;
250 int graceDistance = 500;
251 preferredSpawnPos = enemySub.
FindSpawnPos(preferredSpawnPos,
new Point(subSize.X + graceDistance, subSize.Y + graceDistance));
255 private void InitPirateShip()
258 if (enemySub.
GetItems(alsoFromConnectedSubs:
false).Find(i => i.HasTag(Tags.Reactor) && !i.NonInteractable)?.GetComponent<
Reactor>() is
Reactor reactor)
260 reactor.PowerUpImmediately();
266 if (Level.Loaded !=
null)
269 foreach (var patrolPos
in patrolPositions)
278 private void InitPirates()
281 characterItems.Clear();
283 if (characterConfig ==
null)
285 DebugConsole.ThrowError(
"Failed to initialize characters for escort mission (characterConfig == null)",
293 playerCount = GameMain.Server.ConnectedClients.Where(c => !c.SpectateOnly || !GameMain.Server.ServerSettings.AllowSpectating).Count();
296 float enemyCreationDifficulty = missionDifficulty + playerCount * addedMissionDifficultyPerPlayer;
298 Random rand =
new MTRandom(ToolBox.StringToInt(levelData.
Seed));
300 bool commanderAssigned =
false;
301 foreach (ContentXElement element
in characterConfig.Elements())
305 int amountCreated = GetDifficultyModifiedAmount(element.GetAttributeInt(
"minamount", 0), element.GetAttributeInt(
"maxamount", 0), enemyCreationDifficulty, rand);
306 var characterId = element.GetAttributeString(
"typeidentifier",
string.Empty);
307 for (
int i = 0; i < amountCreated; i++)
309 XElement characterType = characterTypeConfig.Elements().Where(e => e.GetAttributeString(
"typeidentifier",
string.Empty) == characterId).FirstOrDefault();
311 if (characterType ==
null)
313 DebugConsole.ThrowError($
"No character types defined in CharacterTypes for a declared type identifier in mission \"{Prefab.Identifier}\".",
314 contentPackage: element.ContentPackage);
318 XElement variantElement = GetRandomDifficultyModifiedElement(characterType, enemyCreationDifficulty, RandomnessModifier);
321 if (humanPrefab ==
null) {
continue; }
324 if (!commanderAssigned)
326 bool isCommander = variantElement.GetAttributeBool(
"iscommander",
false);
327 if (isCommander && spawnedCharacter.AIController is HumanAIController humanAIController)
329 humanAIController.InitShipCommandManager();
330 foreach (var patrolPos
in patrolPositions)
332 humanAIController.ShipCommandManager.patrolPositions.Add(patrolPos);
334 commanderAssigned =
true;
338 foreach (Item item
in spawnedCharacter.Inventory.AllItems)
340 if (item?.GetComponent<IdCard>() !=
null)
342 item.AddTag(
"id_pirate");
351 if (characters.Count > 0)
354 throw new Exception($
"characters.Count > 0 ({characters.Count})");
356 DebugConsole.AddWarning(
"Character list was not empty at the start of a pirate mission. The mission instance may not have been ended correctly on previous rounds.");
361 if (patrolPositions.Count > 0)
364 throw new Exception($
"patrolPositions.Count > 0 ({patrolPositions.Count})");
366 DebugConsole.AddWarning(
"Patrol point list was not empty at the start of a pirate mission. The mission instance may not have been ended correctly on previous rounds.");
367 patrolPositions.Clear();
373 if (enemySub ==
null)
375 DebugConsole.ThrowError(submarineInfo ==
null ?
376 $
"Error in PirateMission: enemy sub was not created (submarineInfo == null)." :
377 $
"Error in PirateMission: enemy sub was not created.",
382 CreateMissionPositions(out Vector2 spawnPos);
386 DebugConsole.NewMessage(
"The patrol positions set by client were: ");
390 DebugConsole.NewMessage(
"The patrol positions set by server were: ");
392 foreach (var patrolPos
in patrolPositions)
394 DebugConsole.NewMessage(
"Patrol pos: " + patrolPos);
416 if (
state >= 2 || enemySub ==
null) {
return; }
430 for (
int i = patrolPositions.Count - 1; i >= 0; i--)
434 patrolPositions.RemoveAt(i);
437 if (!outsideOfSonarRange || patrolPositions.None())
443 if (outsideOfSonarRange)
449 pirateSightingUpdateTimer -= deltaTime;
450 if (pirateSightingUpdateTimer < 0)
452 pirateSightingUpdateTimer = pirateSightingUpdateFrequency;
459 pirateSightingUpdateTimer = 0;
466 private bool CheckWinState() => !
IsClient && characters.All(m => DeadOrCaptured(m));
468 private static bool DeadOrCaptured(
Character character)
481 characterItems.Clear();
483 submarineInfo =
null;
readonly ContentPath Path
float GetAttributeFloat(string key, float def)
ContentXElement? GetChildElement(string name)
const float DefaultSonarRange
Defines a point in the event that GoTo actions can jump to.
LevelData(string seed, float difficulty, float sizeFactor, LevelGenerationParams generationParams, Biome biome)
LocalizedString Replace(Identifier find, LocalizedString replace, StringComparison stringComparison=StringComparison.Ordinal)
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)
LocalizedString descriptionWithoutReward
LocalizedString description
readonly ContentXElement ConfigElement
override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
override bool DetermineCompleted()
override void StartMissionSpecific(Level level)
override void UpdateMissionSpecific(float deltaTime)
override int GetBaseReward(Submarine sub)
Calculates the base reward, can be overridden for different mission types
override void SetLevel(LevelData level)
override SubmarineInfo EnemySubmarineInfo
override void EndMissionSpecific(bool completed)
PirateMission(MissionPrefab prefab, Location[] locations, Submarine sub)
ContentPackage? ContentPackage
List< Item > GetItems(bool alsoFromConnectedSubs)
static readonly Submarine[] MainSubs
override Vector2? WorldPosition
void EnableMaintainPosition()
void SetCrushDepth(float realWorldCrushDepth)
Normally crush depth is determined by the crush depths of the walls and upgrades applied on them....
void EnableFactionSpecificEntities(Identifier factionIdentifier)
void FlipX(List< Submarine > parents=null)
float RealWorldCrushDepth
bool ImmuneToBallastFlora
Rectangle GetDockedBorders(bool allowDifferentTeam=true)
Returns a rect that contains the borders of this sub and all subs docked to it, excluding outposts
void SetPosition(Vector2 position, List< Submarine > checkd=null, bool forceUndockFromStaticSubmarines=true)
Vector2 FindSpawnPos(Vector2 spawnPos, Point? submarineSize=null, float subDockingPortOffset=0.0f, int verticalMoveDir=0)
Attempt to find a spawn position close to the specified position where the sub doesn't collide with w...