Unstable 1.8.4.0

This commit is contained in:
Markus Isberg
2025-03-12 12:56:27 +00:00
parent a4c3e868e4
commit a4a3427e4e
627 changed files with 29860 additions and 10018 deletions
@@ -111,7 +111,7 @@ namespace Barotrauma
get { return failed; }
}
public virtual bool AllowRespawn
public virtual bool AllowRespawning
{
get { return true; }
}
@@ -161,6 +161,10 @@ namespace Barotrauma
private readonly List<DelayedTriggerEvent> delayedTriggerEvents = new List<DelayedTriggerEvent>();
public Action<Mission> OnMissionStateChanged;
protected readonly ContentXElement characterConfig;
protected readonly List<Character> characters = new List<Character>();
protected readonly Dictionary<Character, List<Item>> characterItems = new Dictionary<Character, List<Item>>();
public Mission(MissionPrefab prefab, Location[] locations, Submarine sub)
{
@@ -192,6 +196,8 @@ namespace Barotrauma
messages[m] = ReplaceVariablesInMissionMessage(messages[m], sub);
}
Messages = messages.ToImmutableArray();
characterConfig = prefab.ConfigElement.GetChildElement("Characters");
}
public LocalizedString ReplaceVariablesInMissionMessage(LocalizedString message, Submarine sub, bool replaceReward = true)
@@ -211,21 +217,21 @@ namespace Barotrauma
public virtual void SetLevel(LevelData level) { }
public static Mission LoadRandom(Location[] locations, string seed, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer = false, float? difficultyLevel = null)
public static Mission LoadRandom(Location[] locations, string seed, bool requireCorrectLocationType, IEnumerable<Identifier> missionTypes, bool isSinglePlayer = false, float? difficultyLevel = null)
{
return LoadRandom(locations, new MTRandom(ToolBox.StringToInt(seed)), requireCorrectLocationType, missionType, isSinglePlayer, difficultyLevel);
return LoadRandom(locations, new MTRandom(ToolBox.StringToInt(seed)), requireCorrectLocationType, missionTypes, isSinglePlayer, difficultyLevel);
}
public static Mission LoadRandom(Location[] locations, MTRandom rand, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer = false, float? difficultyLevel = null)
public static Mission LoadRandom(Location[] locations, MTRandom rand, bool requireCorrectLocationType, IEnumerable<Identifier> missionTypes, bool isSinglePlayer = false, float? difficultyLevel = null)
{
List<MissionPrefab> allowedMissions = new List<MissionPrefab>();
if (missionType == MissionType.None)
if (missionTypes.None())
{
return null;
}
else
{
allowedMissions.AddRange(MissionPrefab.Prefabs.Where(m => m.Type.HasAnyFlag(missionType)));
allowedMissions.AddRange(MissionPrefab.Prefabs.Where(m => missionTypes.Contains(m.Type)));
}
allowedMissions.RemoveAll(m => isSinglePlayer ? m.MultiplayerOnly : m.SingleplayerOnly);
if (requireCorrectLocationType)
@@ -244,25 +250,179 @@ namespace Barotrauma
/// <summary>
/// Calculates the base reward, can be overridden for different mission types
/// </summary>
public virtual int GetBaseReward(Submarine sub)
public virtual float GetBaseReward(Submarine sub)
{
return Prefab.Reward;
}
/// <summary>
/// Calculates the available reward, taking into account universal modifiers such as campaign settings
/// Calculates the available monetary reward, taking into account universal modifiers such as campaign settings.
/// </summary>
public int GetReward(Submarine sub)
{
int reward = GetBaseReward(sub);
float reward = GetBaseReward(sub);
// Some modifiers should apply universally to all implementations of GetBaseReward
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
{
reward = (int)Math.Round(reward * campaign.Settings.MissionRewardMultiplier);
reward *= campaign.Settings.MissionRewardMultiplier;
}
return (int)Math.Round(reward);
}
/// <summary>
/// Call to load character elements to be spawned. Has to be implemented (and synced) separately per each mission.
/// </summary>
protected void InitCharacters(Submarine submarine)
{
characters.Clear();
characterItems.Clear();
return reward;
if (characterConfig != null)
{
foreach (XElement element in characterConfig.Elements())
{
if (GameMain.NetworkMember == null && element.GetAttributeBool("multiplayeronly", false)) { continue; }
int defaultCount = element.GetAttributeInt("count", -1);
if (defaultCount < 0)
{
defaultCount = element.GetAttributeInt("amount", 1);
}
int min = Math.Min(element.GetAttributeInt("min", defaultCount), 255);
int max = Math.Min(Math.Max(min, element.GetAttributeInt("max", defaultCount)), 255);
int count = Rand.Range(min, max + 1);
if (element.Attribute("identifier") != null && element.Attribute("from") != null)
{
HumanPrefab humanPrefab = GetHumanPrefabFromElement(element);
if (humanPrefab == null)
{
DebugConsole.ThrowError($"Couldn't spawn a human character for a mission: human prefab \"{element.GetAttributeString("identifier", string.Empty)}\" not found",
contentPackage: Prefab.ContentPackage);
continue;
}
for (int i = 0; i < count; i++)
{
LoadHuman(humanPrefab, element, submarine);
}
}
else
{
Identifier speciesName = element.GetAttributeIdentifier("character", element.GetAttributeIdentifier("identifier", Identifier.Empty));
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
if (characterPrefab == null)
{
DebugConsole.ThrowError($"Couldn't spawn a character for a mission: character prefab \"{speciesName}\" not found",
contentPackage: Prefab.ContentPackage);
continue;
}
for (int i = 0; i < count; i++)
{
LoadMonster(characterPrefab, element, submarine);
}
}
}
}
}
private SpawnAction.SpawnLocationType GetSpawnLocationTypeFromSubmarineType(Submarine sub)
{
return sub.Info.Type switch
{
SubmarineType.Outpost or SubmarineType.OutpostModule => SpawnAction.SpawnLocationType.Outpost,
SubmarineType.Wreck => SpawnAction.SpawnLocationType.Wreck,
SubmarineType.Ruin => SpawnAction.SpawnLocationType.Ruin,
SubmarineType.BeaconStation => SpawnAction.SpawnLocationType.BeaconStation,
SubmarineType.Player => SpawnAction.SpawnLocationType.MainSub,
_ => SpawnAction.SpawnLocationType.Any
};
}
protected virtual Character LoadHuman(HumanPrefab humanPrefab, XElement element, Submarine submarine)
{
Identifier[] moduleFlags = element.GetAttributeIdentifierArray("moduleflags", null);
Identifier[] spawnPointTags = element.GetAttributeIdentifierArray("spawnpointtags", null);
var spawnPointType = element.GetAttributeEnum("spawnpointtype", SpawnType.Human);
ISpatialEntity spawnPos = SpawnAction.GetSpawnPos(
GetSpawnLocationTypeFromSubmarineType(submarine), spawnPointType,
moduleFlags ?? humanPrefab.GetModuleFlags(),
spawnPointTags ?? humanPrefab.GetSpawnPointTags(),
element.GetAttributeBool("asfaraspossible", false));
spawnPos ??= submarine.GetHulls(alsoFromConnectedSubs: false).GetRandomUnsynced();
var teamId = element.GetAttributeEnum("teamid", CharacterTeamType.None);
var originalTeam = Level.Loaded.StartOutpost?.TeamID ?? teamId;
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, submarine, originalTeam, spawnPos);
//consider the NPC to be "originally" from the team of the outpost it spawns in, and change it to the desired (hostile) team afterwards
//that allows the NPC to fight intruders and otherwise function in the outpost if the mission is configured to spawn the hostile NPCs in a friendly outpost
if (teamId != originalTeam)
{
spawnedCharacter.SetOriginalTeamAndChangeTeam(teamId);
}
if (element.GetAttribute("color") != null)
{
spawnedCharacter.UniqueNameColor = element.GetAttributeColor("color", Color.Red);
}
if (submarine.Info is { IsOutpost: true } outPostInfo)
{
outPostInfo.AddOutpostNPCIdentifierOrTag(spawnedCharacter, humanPrefab.Identifier);
foreach (Identifier tag in humanPrefab.GetTags())
{
outPostInfo.AddOutpostNPCIdentifierOrTag(spawnedCharacter, tag);
}
}
if (spawnPos is WayPoint wp)
{
spawnedCharacter.GiveIdCardTags(wp);
}
InitCharacter(spawnedCharacter, element);
return spawnedCharacter;
}
protected virtual Character LoadMonster(CharacterPrefab monsterPrefab, XElement element, Submarine submarine)
{
Identifier[] moduleFlags = element.GetAttributeIdentifierArray("moduleflags", null);
Identifier[] spawnPointTags = element.GetAttributeIdentifierArray("spawnpointtags", null);
ISpatialEntity spawnPos = SpawnAction.GetSpawnPos(SpawnAction.SpawnLocationType.Outpost, SpawnType.Enemy, moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false));
spawnPos ??= submarine.GetHulls(alsoFromConnectedSubs: false).GetRandomUnsynced();
Character spawnedCharacter = Character.Create(monsterPrefab.Identifier, spawnPos.WorldPosition, ToolBox.RandomSeed(8), createNetworkEvent: false);
characters.Add(spawnedCharacter);
if (spawnedCharacter.Inventory != null)
{
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
}
if (submarine != null && spawnedCharacter.AIController is EnemyAIController enemyAi)
{
enemyAi.UnattackableSubmarines.Add(submarine);
enemyAi.UnattackableSubmarines.Add(Submarine.MainSub);
foreach (Submarine sub in Submarine.MainSub.DockedTo)
{
enemyAi.UnattackableSubmarines.Add(sub);
}
}
InitCharacter(spawnedCharacter, element);
return spawnedCharacter;
}
protected virtual void InitCharacter(Character character, XElement element)
{
if (element.GetAttributeBool(Tags.IgnoredByAI.Value, false))
{
character.AddAbilityFlag(AbilityFlags.IgnoredByEnemyAI);
}
float playDeadProbability = element.GetAttributeFloat("playdeadprobability", -1);
if (playDeadProbability >= 0)
{
character.EvaluatePlayDeadProbability(playDeadProbability);
}
float huskProbability = element.GetAttributeFloat("huskprobability", 0);
if (huskProbability > 0 && Rand.Value() <= huskProbability)
{
character.TurnIntoHusk();
}
else if (element.GetAttributeBool("corpse", false))
{
character.Kill(CauseOfDeathType.Unknown, causeOfDeathAffliction: null, log: false);
}
}
public void Start(Level level)
@@ -350,11 +510,12 @@ namespace Barotrauma
private void TriggerEvent(MissionPrefab.TriggerEvent trigger)
{
if (trigger.CampaignOnly && GameMain.GameSession?.Campaign == null) { return; }
var eventPrefab = EventSet.GetAllEventPrefabs().Find(p => p.Identifier == trigger.EventIdentifier);
//clients are not allowed to trigger events, they're handled by the server
if (GameMain.NetworkMember is { IsClient: true }) { return; }
EventPrefab eventPrefab = EventPrefab.FindEventPrefab(trigger.EventIdentifier, trigger.EventTag, Prefab.ContentPackage);
if (eventPrefab == null)
{
DebugConsole.ThrowError($"Mission \"{Name}\" failed to trigger an event (couldn't find an event with the identifier \"{trigger.EventIdentifier}\").",
contentPackage: Prefab.ContentPackage);
DebugConsole.ThrowError($"Mission {Prefab.Identifier} failed to trigger an event (identifier: {trigger.EventIdentifier}, tag: {trigger.EventTag}).", contentPackage: Prefab.ContentPackage);
return;
}
if (GameMain.GameSession?.EventManager != null)
@@ -427,15 +588,23 @@ namespace Barotrauma
finalReward = (int)(reward * missionMoneyGainMultiplier.Value);
}
private float CalculateDifficultyXPMultiplier()
{
const float minMissionDifficulty = 1;
const float maxMissionDifficulty = 4;
const float maxXpBonus = 1.3f;
float selectedMissionDifficulty = MathUtils.InverseLerp(minMissionDifficulty, maxMissionDifficulty, Prefab.Difficulty.GetValueOrDefault());
float xpBonusMultiplier = MathHelper.Lerp(1.0f, maxXpBonus, selectedMissionDifficulty);
return xpBonusMultiplier;
}
private void GiveReward()
{
if (GameMain.GameSession.GameMode is not CampaignMode campaign) { return; }
int reward = GetReward(Submarine.MainSub);
float baseExperienceGain = reward * 0.09f;
float difficultyMultiplier = 1 + level.Difficulty / 100f;
baseExperienceGain *= difficultyMultiplier;
float xpReward = GetBaseReward(Submarine.MainSub) * Prefab.ExperienceMultiplier * campaign.Settings.ExperienceRewardMultiplier;
float xpGain = xpReward * level.LevelData.Biome.ExperienceFromMissionRewards * CalculateDifficultyXPMultiplier();
IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
@@ -443,7 +612,7 @@ namespace Barotrauma
var experienceGainMultiplier = new AbilityMissionExperienceGainMultiplier(this, 1f, character: null);
crewCharacters.ForEach(c => experienceGainMultiplier.Value += c.GetStatValue(StatTypes.MissionExperienceGainMultiplier));
DistributeExperienceToCrew(crewCharacters, (int)(baseExperienceGain * experienceGainMultiplier.Value));
DistributeExperienceToCrew(crewCharacters, (int)(xpGain * experienceGainMultiplier.Value));
CalculateFinalReward(Submarine.MainSub);
#if SERVER
@@ -567,7 +736,7 @@ namespace Barotrauma
Identifier characterIdentifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
Identifier characterFrom = element.GetAttributeIdentifier("from", Identifier.Empty);
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier, contentPackageToLogInError: Prefab.ContentPackage);
if (humanPrefab == null)
{
DebugConsole.ThrowError($"Couldn't spawn character for mission: character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\".",