Release 1.9.7.0 - Summer Update 2025
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user