3 using Microsoft.Xna.Framework;
5 using System.Collections.Generic;
6 using System.Collections.Immutable;
7 using System.Globalization;
16 private bool completed;
30 TryTriggerEvents(
state);
32 GameMain.Server?.UpdateMissionState(
this);
34 if (
Prefab.ShowProgressBar)
51 public readonly ImmutableArray<LocalizedString>
Headers;
52 public readonly ImmutableArray<LocalizedString>
Messages;
58 private int? finalReward;
65 get {
return successMessage; }
72 get {
return failureMessage; }
100 get {
return Prefab.ReputationRewards; }
105 get {
return completed; }
106 set { completed = value; }
146 get {
return Prefab.Difficulty; }
149 private class DelayedTriggerEvent
156 TriggerEvent = triggerEvent;
161 private readonly List<DelayedTriggerEvent> delayedTriggerEvents =
new List<DelayedTriggerEvent>();
167 System.Diagnostics.Debug.Assert(locations.Length == 2);
175 var messages = prefab.
Messages.ToArray();
181 if (endConditionElement !=
null)
183 completeCheckDataAction =
new CheckDataAction(endConditionElement, $
"Mission ({prefab.Identifier})");
190 for (
int m = 0; m < messages.Length; m++)
194 Messages = messages.ToImmutableArray();
199 for (
int locationIndex = 0; locationIndex < 2; locationIndex++)
201 string locationName = $
"‖color:gui.orange‖{Locations[locationIndex].DisplayName}‖end‖";
202 message = message.
Replace(
"[location" + (locationIndex + 1) +
"]", locationName);
206 string rewardText = $
"‖color:gui.orange‖{string.Format(CultureInfo.InvariantCulture, "{0:N0}
", GetReward(sub))}‖end‖";
207 message = message.
Replace(
"[reward]", rewardText);
216 return LoadRandom(locations,
new MTRandom(ToolBox.StringToInt(seed)), requireCorrectLocationType, missionType, isSinglePlayer, difficultyLevel);
221 List<MissionPrefab> allowedMissions =
new List<MissionPrefab>();
230 allowedMissions.RemoveAll(m => isSinglePlayer ? m.MultiplayerOnly : m.SingleplayerOnly);
231 if (requireCorrectLocationType)
233 allowedMissions.RemoveAll(m => !m.IsAllowed(locations[0], locations[1]));
235 if (difficultyLevel.HasValue)
237 allowedMissions.RemoveAll(m => !m.IsAllowedDifficulty(difficultyLevel.Value));
239 if (allowedMissions.Count == 0) {
return null; }
240 MissionPrefab missionPrefab = ToolBox.SelectWeightedRandom(allowedMissions, m => m.Commonness, rand);
262 reward = (int)Math.Round(reward * campaign.Settings.MissionRewardMultiplier);
272 shownMessages.Clear();
274 delayedTriggerEvents.Clear();
275 foreach (
string categoryToShow
in Prefab.UnhideEntitySubCategories)
291 for (
int i = delayedTriggerEvents.Count - 1; i>=0;i--)
293 delayedTriggerEvents[i].Delay -= deltaTime;
294 if (delayedTriggerEvents[i].Delay <= 0.0f)
296 TriggerEvent(delayedTriggerEvents[i].TriggerEvent);
297 delayedTriggerEvents.RemoveAt(i);
307 ShowMessageProjSpecific(missionState);
310 partial
void ShowMessageProjSpecific(
int missionState);
317 private void TryTriggerEvents(
int state)
319 foreach (var triggerEvent
in Prefab.TriggerEvents)
321 if (triggerEvent.State ==
state)
323 TryTriggerEvent(triggerEvent);
331 private void TryTriggerEvent(MissionPrefab.TriggerEvent trigger)
333 if (trigger.CampaignOnly && GameMain.GameSession?.Campaign ==
null) {
return; }
334 if (trigger.Delay > 0 || trigger.State == 0)
336 if (!delayedTriggerEvents.Any(t => t.TriggerEvent == trigger))
338 delayedTriggerEvents.Add(
new DelayedTriggerEvent(trigger, trigger.Delay));
343 TriggerEvent(trigger);
350 private void TriggerEvent(MissionPrefab.TriggerEvent trigger)
352 if (trigger.CampaignOnly && GameMain.GameSession?.Campaign ==
null) {
return; }
353 var eventPrefab = EventSet.GetAllEventPrefabs().Find(p => p.Identifier == trigger.EventIdentifier);
354 if (eventPrefab ==
null)
356 DebugConsole.ThrowError($
"Mission \"{Name}\" failed to trigger an event (couldn't find an event with the identifier \"{trigger.EventIdentifier}\").",
360 if (GameMain.GameSession?.EventManager !=
null)
362 var newEvent = eventPrefab.CreateInstance(GameMain.GameSession.EventManager.RandomSeed);
363 GameMain.GameSession.EventManager.ActivateEvent(newEvent);
376 (completeCheckDataAction ==
null ||completeCheckDataAction.GetSuccess());
380 if (
Prefab.LocationTypeChangeOnCompleted !=
null)
390 string errorMsg =
"Unknown error while giving mission rewards.";
392 GameAnalyticsManager.AddErrorEventOnce(
"Mission.End:GiveReward", GameAnalyticsManager.ErrorSeverity.Error, errorMsg +
"\n" + e.StackTrace);
394 GameMain.Server?.SendChatMessage(errorMsg +
"\n" + e.StackTrace, Networking.ChatMessageType.Error);
420 private void CalculateFinalReward(
Submarine sub)
426 crewCharacters.ForEach(c => missionMoneyGainMultiplier.Value += c.GetStatValue(
StatTypes.MissionMoneyGainMultiplier));
427 finalReward = (int)(reward * missionMoneyGainMultiplier.Value);
430 private void GiveReward()
432 if (GameMain.GameSession.GameMode is not CampaignMode campaign) {
return; }
435 float baseExperienceGain = reward * 0.09f;
438 baseExperienceGain *= difficultyMultiplier;
440 IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters(
CharacterType.Both);
443 var experienceGainMultiplier =
new AbilityMissionExperienceGainMultiplier(
this, 1f, character:
null);
444 crewCharacters.ForEach(c => experienceGainMultiplier.Value += c.GetStatValue(
StatTypes.MissionExperienceGainMultiplier));
446 DistributeExperienceToCrew(crewCharacters, (
int)(baseExperienceGain * experienceGainMultiplier.Value));
450 finalReward = DistributeRewardsToCrew(GameSession.GetSessionCrewCharacters(
CharacterType.Player), finalReward.Value);
452 bool isSingleplayerOrServer = GameMain.IsSingleplayer || GameMain.NetworkMember is { IsServer:
true };
453 if (isSingleplayerOrServer)
457 campaign.Bank.Give(finalReward.Value);
460 foreach (Character character
in crewCharacters)
462 character.Info.MissionsCompletedSinceDeath++;
467 var reputationGainMultiplier =
new AbilityMissionReputationGainMultiplier(
this, 1f, character:
null);
468 foreach (var c
in crewCharacters) { c.CheckTalents(
AbilityEffectType.OnCrewGainMissionReputation, reputationGainMultiplier); }
469 float amount = reputationReward.Amount * reputationGainMultiplier.Value;
471 if (reputationReward.FactionIdentifier ==
"location")
474 TryGiveReputationForOpposingFaction(
OriginLocation.
Faction, reputationReward.AmountForOpposingFaction);
478 Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier == reputationReward.FactionIdentifier);
481 faction.Reputation.AddReputation(amount);
482 TryGiveReputationForOpposingFaction(faction, reputationReward.AmountForOpposingFaction);
487 void TryGiveReputationForOpposingFaction(Faction thisFaction,
float amount)
489 if (MathUtils.NearlyEqual(amount, 0.0f)) {
return; }
490 if (thisFaction?.
Prefab !=
null &&
491 !thisFaction.
Prefab.OpposingFaction.IsEmpty)
493 Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier == thisFaction.Prefab.OpposingFaction);
494 faction?.Reputation.AddReputation(amount);
503 SetDataAction.PerformOperation(campaign.CampaignMetadata, identifier, value, operation);
508 partial
void DistributeExperienceToCrew(IEnumerable<Character> crew,
int experienceGain);
510 public static int GetRewardDistibutionSum(IEnumerable<Character> crew,
int rewardDistribution = 0) => crew.Sum(c => c.Wallet.RewardDistribution) + rewardDistribution;
512 public static (
int Amount,
int Percentage,
float Sum) GetRewardShare(
int rewardDistribution, IEnumerable<Character> crew, Option<int> reward)
515 if (MathUtils.NearlyEqual(sum, 0)) {
return (0, 0, sum); }
517 float rewardWeight = sum > 100 ? rewardDistribution / sum : rewardDistribution / 100f;
518 int rewardPercentage = (int)(rewardWeight * 100);
520 int amount = reward.TryUnwrap(out var a) ? a : 0;
522 return ((
int)(amount * rewardWeight), rewardPercentage, sum);
527 if (change ==
null) {
throw new ArgumentException(); }
531 for (
int i = 0; i <
Locations.Length; i++)
539 if (srcIndex == -1) {
return; }
542 if (location.LocationTypeChangesBlocked) {
return; }
561 if (element.Attribute(
"name") !=
null)
563 DebugConsole.ThrowError($
"Error in mission \"{Name}\" - use character identifiers instead of names to configure the characters.",
568 Identifier characterIdentifier = element.GetAttributeIdentifier(
"identifier", Identifier.Empty);
569 Identifier characterFrom = element.GetAttributeIdentifier(
"from", Identifier.Empty);
570 HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
571 if (humanPrefab ==
null)
573 DebugConsole.ThrowError($
"Couldn't spawn character for mission: character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\".",
584 characterInfo.
TeamID = teamType;
590 Character spawnedCharacter =
Character.
Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent:
false);
593 humanPrefab.
GiveItems(spawnedCharacter, submarine, positionToStayIn as
WayPoint, Rand.RandSync.ServerAndClient, createNetworkEvents:
false);
594 characters.Add(spawnedCharacter);
597 return spawnedCharacter;
603 if (element.Attribute(
"name") !=
null)
605 DebugConsole.ThrowError($
"Error in mission \"{Name}\" - use item identifiers instead of names to configure the items",
607 string itemName = element.GetAttributeString(
"name",
"");
609 if (itemPrefab ==
null)
611 DebugConsole.ThrowError($
"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemName}\" not found",
617 string itemIdentifier = element.GetAttributeString(
"identifier",
"");
619 if (itemPrefab ==
null)
621 DebugConsole.ThrowError($
"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemIdentifier}\" not found",
633 if (cargoSpawnPos ==
null)
635 DebugConsole.ThrowError($
"Couldn't spawn items for mission \"{Name}\": no waypoints marked as Cargo were found",
641 if (cargoRoom ==
null)
643 DebugConsole.ThrowError($
"Couldn't spawn items for mission \"{Name}\": waypoints marked as Cargo must be placed inside a room",
651 cargoSpawnPos.
Position.X + Rand.Range(-20.0f, 20.0f, Rand.RandSync.ServerAndClient),
652 cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.
Size.Y / 2);
660 Value = moneyGainMultiplier;
671 Value = missionExperienceGainMultiplier;
685 Value = reputationMultiplier;
AbilityMissionExperienceGainMultiplier(Mission mission, float missionExperienceGainMultiplier, Character character)
AbilityMissionMoneyGainMultiplier(Mission mission, float moneyGainMultiplier)
AbilityMissionReputationGainMultiplier(Mission mission, float reputationMultiplier, Character character)
static void ShowMissionProgressBar(Mission mission)
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
CharacterInventory Inventory
static void CheckTalentsForCrew(IEnumerable< Character > crew, AbilityEffectType type, AbilityObject abilityObject)
Checks talents for a given AbilityObject taking into account non-stackable talents.
Can be used to check arbitrary campaign metadata set using SetDataAction.
ContentXElement? GetChildElement(string name)
static GameSession?? GameSession
static NetworkMember NetworkMember
static ImmutableHashSet< Character > GetSessionCrewCharacters(CharacterType type)
Returns a list of crew characters currently in the game with a given filter.
CharacterInfo CreateCharacterInfo(Rand.RandSync randSync=Rand.RandSync.Unsynced)
Creates a character info from the human prefab. If there are custom character infos defined,...
void InitializeCharacter(Character npc, ISpatialEntity positionToStayIn=null)
bool GiveItems(Character character, Submarine submarine, WayPoint spawnPoint, Rand.RandSync randSync=Rand.RandSync.Unsynced, bool createNetworkEvents=true)
List< Item > FindAllItems(Func< Item, bool > predicate=null, bool recursive=false, List< Item > list=null)
Defines a point in the event that GoTo actions can jump to.
LocalizedString Replace(Identifier find, LocalizedString replace, StringComparison stringComparison=StringComparison.Ordinal)
readonly Identifier CurrentType
readonly int CooldownAfterChange
The location can't change it's type for this many turns after this location type changes occurs
readonly Identifier ChangeToType
readonly Point RequiredDurationRange
static readonly PrefabCollection< LocationType > Prefabs
Mersenne Twister based random
static readonly List< MapEntity > MapEntityList
override Vector2 Position
bool IsLayerHidden
Is the layer this entity is in currently hidden? If it is, the entity is not updated and should do no...
static MapEntityPrefab Find(string name, string identifier=null, bool showErrorMessages=true)
Find a matching map entity prefab
Action< Mission > OnMissionStateChanged
virtual LocalizedString FailureMessage
virtual bool AllowRespawn
virtual void AdjustLevelData(LevelData levelData)
LocalizedString ReplaceVariablesInMissionMessage(LocalizedString message, Submarine sub, bool replaceReward=true)
int GetReward(Submarine sub)
Calculates the available reward, taking into account universal modifiers such as campaign settings
void ShowMessage(int missionState)
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)
virtual LocalizedString ModifyMessage(LocalizedString message, bool color=true)
static Mission LoadRandom(Location[] locations, MTRandom rand, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer=false, float? difficultyLevel=null)
readonly ImmutableArray< LocalizedString > Headers
void ChangeLocationType(LocationTypeChange change)
virtual bool AllowUndocking
HumanPrefab GetHumanPrefabFromElement(XElement element)
virtual void StartMissionSpecific(Level level)
LocalizedString descriptionWithoutReward
static int GetRewardDistibutionSum(IEnumerable< Character > crew, int rewardDistribution=0)
virtual LocalizedString Name
abstract bool DetermineCompleted()
virtual IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
Location OriginLocation
Where was this mission received from? Affects which faction we give reputation for if the mission is ...
static Mission LoadRandom(Location[] locations, string seed, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer=false, float? difficultyLevel=null)
virtual LocalizedString Description
void Update(float deltaTime)
ImmutableList< MissionPrefab.ReputationReward > ReputationRewards
LocalizedString description
Mission(MissionPrefab prefab, Location[] locations, Submarine sub)
Vector2? GetCargoSpawnPosition(ItemPrefab itemPrefab, out Submarine cargoRoomSub)
virtual void UpdateMissionSpecific(float deltaTime)
int GetFinalReward(Submarine sub)
Get the final reward, taking talent bonuses into account if the mission has concluded and the talents...
virtual SubmarineInfo EnemySubmarineInfo
readonly ImmutableArray< LocalizedString > Messages
Identifier SonarIconIdentifier
virtual LocalizedString SuccessMessage
virtual int GetBaseReward(Submarine sub)
Calculates the base reward, can be overridden for different mission types
ItemPrefab FindItemPrefab(XElement element)
virtual void EndMissionSpecific(bool completed)
virtual void SetLevel(LevelData level)
void End()
End the mission and give a reward if it was completed successfully
readonly Location[] Locations
static readonly PrefabCollection< MissionPrefab > Prefabs
readonly ImmutableArray< LocalizedString > Headers
readonly List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)> DataRewards
readonly LocalizedString Description
readonly ImmutableArray< LocalizedString > Messages
readonly LocalizedString FailureMessage
readonly LocalizedString SuccessMessage
readonly ContentXElement ConfigElement
Mission Instantiate(Location[] locations, Submarine sub)
Prefab(ContentFile file, Identifier identifier)
ContentPackage? ContentPackage
void AddReputation(float reputationChange, float maxReputationChangePerRound=float.MaxValue)
static WayPoint GetRandom(SpawnType spawnType=SpawnType.Human, JobPrefab assignedJob=null, Submarine sub=null, bool useSyncedRand=false, string spawnPointTag=null, bool ignoreSubmarine=false)
StatTypes
StatTypes are used to alter several traits of a character. They are mostly used by talents.