Release 1.9.7.0 - Summer Update 2025

This commit is contained in:
Regalis11
2025-06-17 16:38:11 +03:00
parent 22227f13e5
commit ea5a2bc693
297 changed files with 7344 additions and 2421 deletions
@@ -156,9 +156,34 @@ namespace Barotrauma
}
}
}
private int previousKillTargetsRemaining = -1;
private void TrackKillTargetCount()
{
if (requireKill.Count == 0) { return; }
if (previousKillTargetsRemaining == -1)
{
previousKillTargetsRemaining = requireKill.Count();
}
int killTargetsRemaining = requireKill.Count(c => !c.Removed && !c.IsDead && !(c.LockHands && c.Submarine == Submarine.MainSub));
// at least one of the targets have been eliminated
if (killTargetsRemaining < previousKillTargetsRemaining)
{
#if CLIENT
SteamTimelineManager.OnOutpostTargetEliminated(this);
#endif
}
previousKillTargetsRemaining = killTargetsRemaining;
}
protected override void UpdateMissionSpecific(float deltaTime)
{
TrackKillTargetCount();
if (State != HostagesKilledState)
{
if (requireRescue.Any(r => r.Removed || r.IsDead))
@@ -9,14 +9,30 @@ namespace Barotrauma
{
partial class EscortMission : Mission
{
private readonly ContentXElement itemConfig;
private readonly ContentXElement terroristItemConfig;
private readonly Dictionary<HumanPrefab, List<StatusEffect>> characterStatusEffects = new Dictionary<HumanPrefab, List<StatusEffect>>();
/// <summary>
/// Number of escorted characters by default.
/// </summary>
private readonly int baseEscortedCharacters;
/// <summary>
/// A scaling factor for the number of escorted characters, relative to the recommended crew size of the sub. The total amount of escorted characters is calculated as
/// baseEscortedCharacters + scalingEscortedCharacters * (RecommendedCrewSizeMin + RecommendedCrewSizeMax) / 2
/// </summary>
private readonly float scalingEscortedCharacters;
/// <summary>
/// The probability for the escorted characters to be "terrorists" (turning them hostile when the sub has progressed enough in the level).
/// A value of 0.5 would mean about half of the characters are terrorist, 1 would mean they all are. There's 20% of randomness applied to the value to make it less predictable.
/// </summary>
private readonly float terroristChance;
/// <summary>
/// Dialog tag the terrorists use in their dialog when they become hostile.
/// </summary>
private readonly string terroristAnnounceDialogTag;
private int calculatedReward;
private Submarine missionSub;
@@ -26,7 +42,6 @@ namespace Barotrauma
private bool terroristsShouldAct = false;
private float terroristDistanceSquared;
private const string TerroristTeamChangeIdentifier = "terrorist";
private readonly string terroristAnnounceDialogTag = string.Empty;
public EscortMission(MissionPrefab prefab, Location[] locations, Submarine sub)
: base(prefab, locations, sub)
@@ -35,8 +50,10 @@ namespace Barotrauma
baseEscortedCharacters = prefab.ConfigElement.GetAttributeInt("baseescortedcharacters", 1);
scalingEscortedCharacters = prefab.ConfigElement.GetAttributeFloat("scalingescortedcharacters", 0);
terroristChance = prefab.ConfigElement.GetAttributeFloat("terroristchance", 0);
itemConfig = prefab.ConfigElement.GetChildElement("TerroristItems");
terroristAnnounceDialogTag = prefab.ConfigElement.GetAttributeString("terroristannouncedialogtag", string.Empty);
terroristItemConfig = prefab.ConfigElement.GetChildElement("TerroristItems");
terroristAnnounceDialogTag =
prefab.ConfigElement.GetAttributeString("dialogterroristannounce",
prefab.ConfigElement.GetAttributeString("terroristAnnounceDialogTag", string.Empty));
CalculateReward();
}
@@ -94,35 +111,29 @@ namespace Barotrauma
randSync = Rand.RandSync.Unsynced;
}
List<HumanPrefab> humanPrefabsToSpawn = new List<HumanPrefab>();
List<(HumanPrefab humanPrefab, List<StatusEffect> statusEffects)> humanPrefabsToSpawn = new List<(HumanPrefab humanPrefab, List<StatusEffect> statusEffects)>();
foreach (ContentXElement characterElement in characterConfig.Elements())
{
int count = CalculateScalingEscortedCharacterCount(inMission: true);
var humanPrefab = GetHumanPrefabFromElement(characterElement);
for (int i = 0; i < count; i++)
{
humanPrefabsToSpawn.Add(humanPrefab);
}
foreach (var element in characterElement.Elements())
{
if (element.NameAsIdentifier() == "statuseffect")
List<StatusEffect> characterStatusEffects = new List<StatusEffect>();
foreach (var element in characterElement.Elements())
{
var newEffect = StatusEffect.Load(element, parentDebugName: Prefab.Name.Value);
if (newEffect == null) { continue; }
if (!characterStatusEffects.ContainsKey(humanPrefab))
if (element.NameAsIdentifier() == "statuseffect")
{
characterStatusEffects[humanPrefab] = new List<StatusEffect> { newEffect };
var newEffect = StatusEffect.Load(element, parentDebugName: Prefab.Name.Value);
if (newEffect == null) { continue; }
characterStatusEffects.Add(newEffect);
}
else
{
characterStatusEffects[humanPrefab].Add(newEffect);
}
}
humanPrefabsToSpawn.Add((humanPrefab, characterStatusEffects));
}
}
//if any of the escortees have a job defined, try to use a spawnpoint designated for that job
foreach (var humanPrefab in humanPrefabsToSpawn)
foreach ((var humanPrefab, var statusEffectList) in humanPrefabsToSpawn)
{
if (humanPrefab == null || humanPrefab.Job.IsEmpty || humanPrefab.Job == "any") { continue; }
var jobPrefab = humanPrefab.GetJobPrefab(randSync);
@@ -136,23 +147,19 @@ namespace Barotrauma
}
}
}
foreach (var humanPrefab in humanPrefabsToSpawn)
foreach ((var humanPrefab, var statusEffectList) in humanPrefabsToSpawn)
{
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, Submarine.MainSub, CharacterTeamType.FriendlyNPC, explicitStayInHullPos, humanPrefabRandSync: randSync);
if (spawnedCharacter.AIController is HumanAIController humanAI)
{
humanAI.InitMentalStateManager();
}
if (characterStatusEffects.TryGetValue(humanPrefab, out var statusEffectList))
foreach (var statusEffect in statusEffectList)
{
foreach (var statusEffect in statusEffectList)
{
statusEffect.Apply(statusEffect.type, 1.0f, spawnedCharacter, spawnedCharacter);
}
}
statusEffect.Apply(statusEffect.type, 1.0f, spawnedCharacter, spawnedCharacter);
}
}
if (terroristChance > 0f)
{
int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count);
@@ -256,13 +263,23 @@ namespace Barotrauma
character.TryAddNewTeamChange(TerroristTeamChangeIdentifier, new ActiveTeamChange(CharacterTeamType.None, ActiveTeamChange.TeamChangePriorities.Willful, aggressiveBehavior: true));
if (!string.IsNullOrEmpty(terroristAnnounceDialogTag))
{
character.Speak(TextManager.Get("dialogterroristannounce").Value, null, Rand.Range(0.5f, 3f));
character.Speak(TextManager.Get(terroristAnnounceDialogTag).Value, null, Rand.Range(0.5f, 3f));
}
ContentXElement randomElement = itemConfig.Elements().GetRandomUnsynced(e => e.GetAttributeFloat(0f, "mindifficulty") <= Level.Loaded.Difficulty);
if (randomElement != null)
foreach (var itemElement in terroristItemConfig.Elements())
{
HumanPrefab.InitializeItem(character, randomElement, character.Submarine, humanPrefab: null, createNetworkEvents: true);
float levelDifficulty = Level.Loaded?.Difficulty ?? 0.0f;
var selectedItemElement = itemElement;
if (itemElement.NameAsIdentifier() == "chooserandom".ToIdentifier())
{
selectedItemElement = itemElement.Elements().GetRandomUnsynced(e => e.GetAttributeFloat(0f, "mindifficulty") <= levelDifficulty);
}
if (selectedItemElement != null)
{
if (levelDifficulty < selectedItemElement.GetAttributeFloat(0f, "mindifficulty")) { continue; }
HumanPrefab.InitializeItem(character, selectedItemElement, character.Submarine, humanPrefab: null, createNetworkEvents: true);
}
}
}
}
}
@@ -26,6 +26,7 @@ namespace Barotrauma
{
if (state != value)
{
int previousState = state;
state = value;
TryTriggerEvents(state);
#if SERVER
@@ -38,6 +39,7 @@ namespace Barotrauma
#endif
ShowMessage(State);
OnMissionStateChanged?.Invoke(this);
MissionStateChanged(previousState);
}
}
}
@@ -198,6 +200,11 @@ namespace Barotrauma
Messages = messages.ToImmutableArray();
characterConfig = prefab.ConfigElement.GetChildElement("Characters");
if (prefab.ConfigElement.GetChildElements("Characters").Count() > 1)
{
DebugConsole.AddWarning($"Error in mission {Prefab.Identifier}: multiple <Characters> elements found. Only the first one will be used.",
contentPackage: prefab.ContentPackage);
}
}
public LocalizedString ReplaceVariablesInMissionMessage(LocalizedString message, Submarine sub, bool replaceReward = true)
@@ -214,6 +221,8 @@ namespace Barotrauma
}
return message;
}
protected virtual void MissionStateChanged(int previousState) {}
public virtual void SetLevel(LevelData level) { }
@@ -109,6 +109,8 @@ namespace Barotrauma
public readonly bool AllowRetry;
public readonly bool ShowSonarLabels;
public readonly bool ShowInMenus, ShowStartMessage;
public readonly bool IsSideObjective;
@@ -219,11 +221,12 @@ namespace Barotrauma
}
}
Reward = element.GetAttributeInt("reward", 1);
ExperienceMultiplier = element.GetAttributeFloat("experiencemultiplier", 1.0f);
AllowRetry = element.GetAttributeBool("allowretry", false);
ShowInMenus = element.GetAttributeBool("showinmenus", true);
ShowStartMessage = element.GetAttributeBool("showstartmessage", true);
Reward = element.GetAttributeInt(nameof(Reward), 1);
ExperienceMultiplier = element.GetAttributeFloat(nameof(ExperienceMultiplier), 1.0f);
AllowRetry = element.GetAttributeBool(nameof(AllowRetry), false);
ShowSonarLabels = element.GetAttributeBool(nameof(ShowSonarLabels), true);
ShowInMenus = element.GetAttributeBool(nameof(ShowInMenus), true);
ShowStartMessage = element.GetAttributeBool(nameof(ShowStartMessage), true);
IsSideObjective = element.GetAttributeBool("sideobjective", false);
RequireWreck = element.GetAttributeBool(nameof(RequireWreck), false);
@@ -237,11 +240,12 @@ namespace Barotrauma
BlockLocationTypeChanges = element.GetAttributeBool(nameof(BlockLocationTypeChanges), false);
RequiredLocationFaction = element.GetAttributeIdentifier(nameof(RequiredLocationFaction), Identifier.Empty);
Commonness = element.GetAttributeInt("commonness", 1);
AllowOtherMissionsInLevel = element.GetAttributeBool("allowothermissionsinlevel", true);
Commonness = element.GetAttributeInt(nameof(Commonness), 1);
AllowOtherMissionsInLevel = element.GetAttributeBool(nameof(AllowOtherMissionsInLevel), true);
if (element.GetAttribute("difficulty") != null)
{
int difficulty = element.GetAttributeInt("difficulty", MinDifficulty);
int difficulty = element.GetAttributeInt(nameof(Difficulty), MinDifficulty);
Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty);
}
MinLevelDifficulty = element.GetAttributeInt(nameof(MinLevelDifficulty), MinLevelDifficulty);
@@ -173,6 +173,17 @@ namespace Barotrauma
}
}
protected override void MissionStateChanged(int previousState)
{
// state of 1+ here means the mission is completed
if (previousState == 0 && State >= 1)
{
#if CLIENT
SteamTimelineManager.OnMonsterMissionTargetsKilled(this);
#endif
}
}
protected override void UpdateMissionSpecific(float deltaTime)
{
switch (State)
@@ -92,13 +92,38 @@ namespace Barotrauma
set
{
if (value == state) { return; }
bool wasRetrieved = Retrieved;
state = value;
#if SERVER
GameMain.Server?.UpdateMissionState(mission);
#endif
if (!wasRetrieved && Retrieved)
{
OnTargetRetrieved();
}
else if (state == RetrievalState.PickedUp)
{
OnTargetPickedUp();
}
}
}
private void OnTargetRetrieved()
{
if (Item == null) { return; }
#if CLIENT
SteamTimelineManager.OnMissionTargetRetrieved(Item, mission);
#endif
}
private void OnTargetPickedUp()
{
if (Item == null) { return; }
#if CLIENT
SteamTimelineManager.OnMissionTargetPickedUp(Item, mission);
#endif
}
public bool Interacted;
private readonly SalvageMission mission;
@@ -469,13 +494,13 @@ namespace Barotrauma
target.Item.ExternalHighlight = true;
#endif
target.Item.UpdateTransform();
if (target.Item.CurrentHull == null)
if (target.Item.CurrentHull == null && target.Item.body != null)
{
//prevent the body from moving if it spawned outside the hulls (we don't want it e.g. falling to the bottom of a cave or into the abyss)
target.Item.body.FarseerBody.BodyType = BodyType.Kinematic;
}
}
else if (target.RequiredRetrievalState == Target.RetrievalState.Interact)
if (target.RequiredRetrievalState == Target.RetrievalState.Interact)
{
target.Item.OnInteract += () =>
{
@@ -168,6 +168,17 @@ namespace Barotrauma
}
}
protected override void MissionStateChanged(int previousState)
{
// detect successful scanned targets increasing after scan is completed
if (previousState < State)
{
#if CLIENT
SteamTimelineManager.OnScanSuccessful(this);
#endif
}
}
private void GetScanners()
{
foreach (var startingItem in startingItems)