v1.6.17.0 (Unto the Breach update)
This commit is contained in:
+40
-15
@@ -1,4 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -27,9 +27,9 @@ namespace Barotrauma
|
||||
private const float EndDelay = 5.0f;
|
||||
private float endTimer;
|
||||
|
||||
private bool allowOrderingRescuees;
|
||||
private readonly bool allowOrderingRescuees;
|
||||
|
||||
public override bool AllowRespawn => false;
|
||||
public override bool AllowRespawning => false;
|
||||
|
||||
public override bool AllowUndocking
|
||||
{
|
||||
@@ -233,7 +233,18 @@ namespace Barotrauma
|
||||
|
||||
bool requiresRescue = element.GetAttributeBool("requirerescue", false);
|
||||
var teamId = element.GetAttributeEnum("teamid", requiresRescue ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None);
|
||||
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, submarine, teamId, spawnPos);
|
||||
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 (Level.Loaded?.StartOutpost?.Info is { } outPostInfo)
|
||||
{
|
||||
outPostInfo.AddOutpostNPCIdentifierOrTag(spawnedCharacter, humanPrefab.Identifier);
|
||||
@@ -265,11 +276,7 @@ namespace Barotrauma
|
||||
.WithManualPriority(CharacterInfo.HighestManualOrderPriority);
|
||||
spawnedCharacter.SetOrder(order, isNewOrder: true, speak: false);
|
||||
}
|
||||
|
||||
if (element.GetAttributeBool("requirekill", false))
|
||||
{
|
||||
requireKill.Add(spawnedCharacter);
|
||||
}
|
||||
InitCharacter(spawnedCharacter, element);
|
||||
}
|
||||
|
||||
private void LoadMonster(CharacterPrefab monsterPrefab, XElement element, Submarine submarine)
|
||||
@@ -280,10 +287,6 @@ namespace Barotrauma
|
||||
spawnPos ??= submarine.GetHulls(alsoFromConnectedSubs: false).GetRandomUnsynced();
|
||||
Character spawnedCharacter = Character.Create(monsterPrefab.Identifier, spawnPos.WorldPosition, ToolBox.RandomSeed(8), createNetworkEvent: false);
|
||||
characters.Add(spawnedCharacter);
|
||||
if (element.GetAttributeBool("requirekill", false))
|
||||
{
|
||||
requireKill.Add(spawnedCharacter);
|
||||
}
|
||||
if (spawnedCharacter.Inventory != null)
|
||||
{
|
||||
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
|
||||
@@ -297,9 +300,31 @@ namespace Barotrauma
|
||||
enemyAi.UnattackableSubmarines.Add(sub);
|
||||
}
|
||||
}
|
||||
InitCharacter(spawnedCharacter, element);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void InitCharacter(Character character, XElement element)
|
||||
{
|
||||
if (element.GetAttributeBool("requirekill", false))
|
||||
{
|
||||
requireKill.Add(character);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (State != HostagesKilledState)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -10,12 +11,59 @@ namespace Barotrauma
|
||||
private readonly LocalizedString[] descriptions;
|
||||
private static LocalizedString[] teamNames = { "Team A", "Team B" };
|
||||
|
||||
public override bool AllowRespawn
|
||||
private readonly bool allowRespawning;
|
||||
|
||||
enum WinCondition
|
||||
{
|
||||
get { return false; }
|
||||
/// <summary>
|
||||
/// The winner is the team with the last living player(s)
|
||||
/// </summary>
|
||||
LastManStanding,
|
||||
/// <summary>
|
||||
/// The team who reaches a specific number of kills (determined by WinScore) is the winner
|
||||
/// </summary>
|
||||
KillCount,
|
||||
/// <summary>
|
||||
/// The team who controls a specific submarine (can be a ruin, outpost or a beacon station too) for some time (determined by WinScore) is the winner
|
||||
/// </summary>
|
||||
ControlSubmarine
|
||||
}
|
||||
|
||||
private CharacterTeamType Winner
|
||||
private readonly WinCondition winCondition;
|
||||
|
||||
public override bool AllowRespawning
|
||||
{
|
||||
get => allowRespawning;
|
||||
}
|
||||
|
||||
private Submarine targetSubmarine;
|
||||
|
||||
private LocalizedString targetSubmarineSonarLabel;
|
||||
|
||||
/// <summary>
|
||||
/// Which type of submarine the team needs to stay in control of to win
|
||||
/// </summary>
|
||||
public TagAction.SubType TargetSubmarineType { get; set; }
|
||||
|
||||
public readonly int PointsPerKill;
|
||||
|
||||
/// <summary>
|
||||
/// The score required to win the mission.
|
||||
/// </summary>
|
||||
public int WinScore => GameMain.NetworkMember?.ServerSettings.WinScorePvP ?? 10;
|
||||
|
||||
/// <summary>
|
||||
/// Is the winner determined by some kind of a scoring mechanism?
|
||||
/// </summary>
|
||||
public bool HasWinScore =>
|
||||
winCondition != WinCondition.LastManStanding || PointsPerKill != 0;
|
||||
|
||||
/// <summary>
|
||||
/// Scores of both teams. What the scoring represents depends on how the mission is configured (kills, time in control of a beacon station?)
|
||||
/// </summary>
|
||||
public readonly int[] Scores = new int[2];
|
||||
|
||||
public static CharacterTeamType Winner
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -46,6 +94,27 @@ namespace Barotrauma
|
||||
public CombatMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
allowRespawning = prefab.ConfigElement.GetAttributeBool(nameof(AllowRespawning), false);
|
||||
|
||||
winCondition = prefab.ConfigElement.GetAttributeEnum(nameof(WinCondition),
|
||||
allowRespawning ? WinCondition.KillCount : WinCondition.LastManStanding);
|
||||
|
||||
PointsPerKill = prefab.ConfigElement.GetAttributeInt(nameof(PointsPerKill), 0);
|
||||
|
||||
TargetSubmarineType = prefab.ConfigElement.GetAttributeEnum(nameof(TargetSubmarineType), TagAction.SubType.Any);
|
||||
|
||||
string sonarTag = prefab.ConfigElement.GetAttributeString(nameof(targetSubmarineSonarLabel), string.Empty);
|
||||
if (!sonarTag.IsNullOrEmpty())
|
||||
{
|
||||
targetSubmarineSonarLabel = TextManager.Get(sonarTag);
|
||||
}
|
||||
|
||||
if (allowRespawning && winCondition == WinCondition.LastManStanding)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission {prefab.Identifier}: win condition cannot be \"last man standing\" when respawning is enabled.",
|
||||
contentPackage: prefab.ContentPackage);
|
||||
}
|
||||
|
||||
descriptions = new LocalizedString[]
|
||||
{
|
||||
TextManager.Get("MissionDescriptionNeutral." + prefab.TextIdentifier).Fallback(prefab.ConfigElement.GetAttributeString("descriptionneutral", "")),
|
||||
@@ -57,15 +126,23 @@ namespace Barotrauma
|
||||
{
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
descriptions[i] = descriptions[i].Replace("[location" + (n + 1) + "]", locations[n].DisplayName);
|
||||
descriptions[i] =
|
||||
descriptions[i]
|
||||
.Replace($"[location{n + 1}]", locations[n].DisplayName)
|
||||
.Replace("[winscore]", WinScore.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
teamNames = new LocalizedString[]
|
||||
{
|
||||
TextManager.Get("MissionTeam1." + prefab.TextIdentifier).Fallback(prefab.ConfigElement.GetAttributeString("teamname1", "Team A")),
|
||||
TextManager.Get("MissionTeam2." + prefab.TextIdentifier).Fallback(prefab.ConfigElement.GetAttributeString("teamname2", "Team B"))
|
||||
TextManager.Get("MissionTeam1." + prefab.TextIdentifier).Fallback(TextManager.Get(prefab.ConfigElement.GetAttributeString("teamname1", "missionteam1.pvpmission"))),
|
||||
TextManager.Get("MissionTeam2." + prefab.TextIdentifier).Fallback(TextManager.Get(prefab.ConfigElement.GetAttributeString("teamname2", "missionteam2.pvpmission"))),
|
||||
};
|
||||
|
||||
if (winCondition == WinCondition.KillCount && PointsPerKill == 0)
|
||||
{
|
||||
DebugConsole.AddWarning($"Potential error in mission {Prefab.Identifier}: win condition is kill count, but {nameof(PointsPerKill)} is set to 0.");
|
||||
}
|
||||
}
|
||||
|
||||
public static LocalizedString GetTeamName(CharacterTeamType teamID)
|
||||
@@ -82,9 +159,9 @@ namespace Barotrauma
|
||||
return "Invalid Team";
|
||||
}
|
||||
|
||||
public bool IsInWinningTeam(Character character)
|
||||
public static bool IsInWinningTeam(Character character)
|
||||
{
|
||||
return character != null &&
|
||||
return character != null &&
|
||||
Winner != CharacterTeamType.None &&
|
||||
Winner == character.TeamID;
|
||||
}
|
||||
@@ -99,19 +176,32 @@ namespace Barotrauma
|
||||
|
||||
subs = new Submarine[] { Submarine.MainSubs[0], Submarine.MainSubs[1] };
|
||||
|
||||
subs[0].NeutralizeBallast();
|
||||
subs[0].TeamID = CharacterTeamType.Team1;
|
||||
subs[0].GetConnectedSubs().ForEach(s => s.TeamID = CharacterTeamType.Team1);
|
||||
if (Prefab.LoadSubmarines)
|
||||
{
|
||||
subs[0].NeutralizeBallast();
|
||||
subs[0].TeamID = CharacterTeamType.Team1;
|
||||
subs[0].GetConnectedSubs().ForEach(s => s.TeamID = CharacterTeamType.Team1);
|
||||
|
||||
subs[1].NeutralizeBallast();
|
||||
subs[1].TeamID = CharacterTeamType.Team2;
|
||||
subs[1].GetConnectedSubs().ForEach(s => s.TeamID = CharacterTeamType.Team2);
|
||||
subs[1].SetPosition(subs[1].FindSpawnPos(Level.Loaded.EndPosition));
|
||||
subs[1].FlipX();
|
||||
subs[1].NeutralizeBallast();
|
||||
subs[1].TeamID = CharacterTeamType.Team2;
|
||||
subs[1].GetConnectedSubs().ForEach(s => s.TeamID = CharacterTeamType.Team2);
|
||||
GameSession.PlaceSubAtInitialPosition(subs[1], level, placeAtStart: false);
|
||||
subs[1].FlipX();
|
||||
}
|
||||
#if SERVER
|
||||
crews = new List<Character>[] { new List<Character>(), new List<Character>() };
|
||||
roundEndTimer = RoundEndDuration;
|
||||
#endif
|
||||
|
||||
if (TargetSubmarineType != TagAction.SubType.Any)
|
||||
{
|
||||
targetSubmarine = Submarine.Loaded.FirstOrDefault(s => TagAction.SubmarineTypeMatches(s, TargetSubmarineType));
|
||||
if (targetSubmarine == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission {Prefab.Identifier}: could not find a submarine of the type {TargetSubmarineType}.",
|
||||
contentPackage: Prefab.ContentPackage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool DetermineCompleted()
|
||||
|
||||
@@ -192,7 +192,6 @@ namespace Barotrauma
|
||||
foreach (ContentXElement element in characterConfig.Elements())
|
||||
{
|
||||
string escortIdentifier = element.GetAttributeString("escortidentifier", string.Empty);
|
||||
string colorIdentifier = element.GetAttributeString("color", string.Empty);
|
||||
for (int k = 0; k < scalingCharacterCount; k++)
|
||||
{
|
||||
// for each element defined, we need to initialize that type of character equal to the scaling escorted character count
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Barotrauma
|
||||
get { return failed; }
|
||||
}
|
||||
|
||||
public virtual bool AllowRespawn
|
||||
public virtual bool AllowRespawning
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
@@ -211,21 +211,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)
|
||||
@@ -350,11 +350,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)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
@@ -7,52 +8,42 @@ using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[Flags]
|
||||
public enum MissionType
|
||||
{
|
||||
None = 0x0,
|
||||
Salvage = 0x1,
|
||||
Monster = 0x2,
|
||||
Cargo = 0x4,
|
||||
Beacon = 0x8,
|
||||
Nest = 0x10,
|
||||
Mineral = 0x20,
|
||||
Combat = 0x40,
|
||||
AbandonedOutpost = 0x80,
|
||||
Escort = 0x100,
|
||||
Pirate = 0x200,
|
||||
GoTo = 0x400,
|
||||
ScanAlienRuins = 0x800,
|
||||
EliminateTargets = 0x1000,
|
||||
End = 0x2000,
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | AbandonedOutpost | Escort | Pirate | GoTo | ScanAlienRuins | EliminateTargets | End
|
||||
}
|
||||
|
||||
partial class MissionPrefab : PrefabWithUintIdentifier
|
||||
{
|
||||
public static readonly PrefabCollection<MissionPrefab> Prefabs = new PrefabCollection<MissionPrefab>();
|
||||
|
||||
public static readonly Dictionary<MissionType, Type> CoOpMissionClasses = new Dictionary<MissionType, Type>()
|
||||
/// <summary>
|
||||
/// The keys here are for backwards compatibility, tying the old mission types to the appropriate class.
|
||||
/// Now the mission class is defined by the name of the mission element, and the type can be any arbitrary string.
|
||||
/// </summary>
|
||||
public static readonly Dictionary<Identifier, Type> CoOpMissionClasses = new Dictionary<Identifier, Type>()
|
||||
{
|
||||
{ MissionType.Salvage, typeof(SalvageMission) },
|
||||
{ MissionType.Monster, typeof(MonsterMission) },
|
||||
{ MissionType.Cargo, typeof(CargoMission) },
|
||||
{ MissionType.Beacon, typeof(BeaconMission) },
|
||||
{ MissionType.Nest, typeof(NestMission) },
|
||||
{ MissionType.Mineral, typeof(MineralMission) },
|
||||
{ MissionType.AbandonedOutpost, typeof(AbandonedOutpostMission) },
|
||||
{ MissionType.Escort, typeof(EscortMission) },
|
||||
{ MissionType.Pirate, typeof(PirateMission) },
|
||||
{ MissionType.GoTo, typeof(GoToMission) },
|
||||
{ MissionType.ScanAlienRuins, typeof(ScanMission) },
|
||||
{ MissionType.EliminateTargets, typeof(EliminateTargetsMission) },
|
||||
{ MissionType.End, typeof(EndMission) }
|
||||
{ "Salvage".ToIdentifier(), typeof(SalvageMission) },
|
||||
{ "Monster".ToIdentifier(), typeof(MonsterMission) },
|
||||
{ "Cargo".ToIdentifier(), typeof(CargoMission) },
|
||||
{ "Beacon".ToIdentifier(), typeof(BeaconMission) },
|
||||
{ "Nest".ToIdentifier(), typeof(NestMission) },
|
||||
{ "Mineral".ToIdentifier(), typeof(MineralMission) },
|
||||
{ "AbandonedOutpost".ToIdentifier(), typeof(AbandonedOutpostMission) },
|
||||
{ "Escort".ToIdentifier(), typeof(EscortMission) },
|
||||
{ "Pirate".ToIdentifier(), typeof(PirateMission) },
|
||||
{ "GoTo".ToIdentifier(), typeof(GoToMission) },
|
||||
{ "ScanAlienRuins".ToIdentifier(), typeof(ScanMission) },
|
||||
{ "EliminateTargets".ToIdentifier(), typeof(EliminateTargetsMission) },
|
||||
{ "End".ToIdentifier(), typeof(EndMission) }
|
||||
};
|
||||
public static readonly Dictionary<MissionType, Type> PvPMissionClasses = new Dictionary<MissionType, Type>()
|
||||
|
||||
/// <summary>
|
||||
/// The keys here are for backwards compatibility, tying the old mission types to the appropriate class.
|
||||
/// Now the mission class is defined by the name of the mission element, and the type can be any arbitrary string.
|
||||
/// </summary>
|
||||
public static readonly Dictionary<Identifier, Type> PvPMissionClasses = new Dictionary<Identifier, Type>()
|
||||
{
|
||||
{ MissionType.Combat, typeof(CombatMission) }
|
||||
{ "Combat".ToIdentifier(), typeof(CombatMission) }
|
||||
};
|
||||
|
||||
public static readonly HashSet<Identifier> HiddenMissionTypes = new HashSet<Identifier>() { "GoTo".ToIdentifier(), "End".ToIdentifier() };
|
||||
|
||||
public class ReputationReward
|
||||
{
|
||||
public readonly Identifier FactionIdentifier;
|
||||
@@ -67,11 +58,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly HashSet<MissionType> HiddenMissionClasses = new HashSet<MissionType>() { MissionType.GoTo, MissionType.End };
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
|
||||
public readonly MissionType Type;
|
||||
public readonly Identifier Type;
|
||||
|
||||
public readonly Type MissionClass;
|
||||
|
||||
public readonly bool MultiplayerOnly, SingleplayerOnly;
|
||||
|
||||
@@ -122,7 +113,21 @@ namespace Barotrauma
|
||||
|
||||
public readonly bool AllowOtherMissionsInLevel;
|
||||
|
||||
public readonly bool RequireWreck, RequireRuin, RequireThalamusWreck;
|
||||
public readonly bool RequireWreck, RequireRuin, RequireBeaconStation, RequireThalamusWreck;
|
||||
public readonly bool SpawnBeaconStationInMiddle;
|
||||
|
||||
public readonly bool AllowOutpostNPCs;
|
||||
|
||||
public readonly Identifier ForceOutpostGenerationParameters;
|
||||
|
||||
public readonly RespawnMode? ForceRespawnMode;
|
||||
|
||||
/// <summary>
|
||||
/// If set, the players can choose which outpost is used for the mission (selected from the outposts that have this tag). Only works in multiplayer.
|
||||
/// </summary>
|
||||
public readonly Identifier AllowOutpostSelectionFromTag;
|
||||
|
||||
public readonly bool LoadSubmarines = true;
|
||||
|
||||
/// <summary>
|
||||
/// If enabled, locations this mission takes place in cannot change their type
|
||||
@@ -157,7 +162,10 @@ namespace Barotrauma
|
||||
public class TriggerEvent
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string EventIdentifier { get; private set; }
|
||||
public Identifier EventIdentifier { get; private set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier EventTag { get; private set; }
|
||||
|
||||
[Serialize(0, IsPropertySaveable.Yes)]
|
||||
public int State { get; private set; }
|
||||
@@ -214,12 +222,16 @@ namespace Barotrauma
|
||||
ShowInMenus = element.GetAttributeBool("showinmenus", true);
|
||||
ShowStartMessage = element.GetAttributeBool("showstartmessage", true);
|
||||
IsSideObjective = element.GetAttributeBool("sideobjective", false);
|
||||
RequireWreck = element.GetAttributeBool("requirewreck", false);
|
||||
RequireRuin = element.GetAttributeBool("requireruin", false);
|
||||
RequireThalamusWreck = element.GetAttributeBool("requirethalamuswreck", false);
|
||||
|
||||
RequireWreck = element.GetAttributeBool(nameof(RequireWreck), false);
|
||||
RequireThalamusWreck = element.GetAttributeBool(nameof(RequireThalamusWreck), false);
|
||||
RequireRuin = element.GetAttributeBool(nameof(RequireRuin), false);
|
||||
RequireBeaconStation = element.GetAttributeBool(nameof(RequireBeaconStation), false);
|
||||
SpawnBeaconStationInMiddle = element.GetAttributeBool(nameof(SpawnBeaconStationInMiddle), false);
|
||||
if (RequireThalamusWreck) { RequireWreck = true; }
|
||||
|
||||
LoadSubmarines = element.GetAttributeBool(nameof(LoadSubmarines), true);
|
||||
|
||||
BlockLocationTypeChanges = element.GetAttributeBool(nameof(BlockLocationTypeChanges), false);
|
||||
RequiredLocationFaction = element.GetAttributeIdentifier(nameof(RequiredLocationFaction), Identifier.Empty);
|
||||
Commonness = element.GetAttributeInt("commonness", 1);
|
||||
@@ -234,6 +246,15 @@ namespace Barotrauma
|
||||
MinLevelDifficulty = Math.Clamp(MinLevelDifficulty, 0, Math.Min(MaxLevelDifficulty, 100));
|
||||
MaxLevelDifficulty = Math.Clamp(MaxLevelDifficulty, Math.Max(MinLevelDifficulty, 0), 100);
|
||||
|
||||
AllowOutpostNPCs = element.GetAttributeBool(nameof(AllowOutpostNPCs), true);
|
||||
ForceOutpostGenerationParameters = element.GetAttributeIdentifier(nameof(ForceOutpostGenerationParameters), Identifier.Empty);
|
||||
AllowOutpostSelectionFromTag = element.GetAttributeIdentifier(nameof(AllowOutpostSelectionFromTag), Identifier.Empty);
|
||||
|
||||
if (element.GetAttribute(nameof(ForceRespawnMode)) != null)
|
||||
{
|
||||
ForceRespawnMode = element.GetAttributeEnum(nameof(ForceRespawnMode), RespawnMode.MidRound);
|
||||
}
|
||||
|
||||
ShowProgressBar = element.GetAttributeBool(nameof(ShowProgressBar), false);
|
||||
ShowProgressInNumbers = element.GetAttributeBool(nameof(ShowProgressInNumbers), false);
|
||||
MaxProgressState = element.GetAttributeInt(nameof(MaxProgressState), 1);
|
||||
@@ -362,47 +383,23 @@ namespace Barotrauma
|
||||
Messages = messages.ToImmutableArray();
|
||||
ReputationRewards = reputationRewards.ToImmutableList();
|
||||
|
||||
Identifier missionTypeName = element.GetAttributeIdentifier("type", Identifier.Empty);
|
||||
//backwards compatibility
|
||||
|
||||
if (missionTypeName == "outpostdestroy" || missionTypeName == "outpostrescue")
|
||||
{
|
||||
missionTypeName = nameof(MissionType.AbandonedOutpost).ToIdentifier();
|
||||
}
|
||||
else if (missionTypeName == "clearalienruins")
|
||||
{
|
||||
missionTypeName = nameof(MissionType.EliminateTargets).ToIdentifier();
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(missionTypeName.Value, true, out Type))
|
||||
{
|
||||
DebugConsole.ThrowErrorLocalized("Error in mission prefab \"" + Name + "\" - \"" + missionTypeName + "\" is not a valid mission type.");
|
||||
return;
|
||||
}
|
||||
if (Type == MissionType.None)
|
||||
{
|
||||
DebugConsole.ThrowErrorLocalized("Error in mission prefab \"" + Name + "\" - mission type cannot be none.");
|
||||
return;
|
||||
}
|
||||
MissionClass = FindMissionClass(element);
|
||||
Type = element.GetAttributeIdentifier(nameof(Type), Identifier.Empty);
|
||||
|
||||
#if DEBUG
|
||||
if (Type == MissionType.Monster && SonarLabel.IsNullOrEmpty())
|
||||
if (MissionClass == typeof(MonsterMission) && SonarLabel.IsNullOrEmpty())
|
||||
{
|
||||
DebugConsole.AddWarning($"Potential error in mission prefab \"{Identifier}\" - sonar label not set.");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (CoOpMissionClasses.ContainsKey(Type))
|
||||
if (!LoadSubmarines && MissionClass != typeof(CombatMission))
|
||||
{
|
||||
constructor = CoOpMissionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]), typeof(Submarine) });
|
||||
}
|
||||
else if (PvPMissionClasses.ContainsKey(Type))
|
||||
{
|
||||
constructor = PvPMissionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]), typeof(Submarine) });
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowErrorLocalized("Error in mission prefab \"" + Name + "\" - unsupported mission type \"" + Type.ToString() + "\"");
|
||||
DebugConsole.AddWarning($"Potential error in mission {Identifier}: Disabling submarines is only intended for combat missions taking place in an outpost, and may lead to issues in other types of missions.",
|
||||
contentPackage: element.ContentPackage);
|
||||
}
|
||||
|
||||
constructor = FindMissionConstructor(element, MissionClass);
|
||||
if (constructor == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to find a constructor for the mission type \"{Type}\"!",
|
||||
@@ -411,6 +408,67 @@ namespace Barotrauma
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
private Type FindMissionClass(ContentXElement element)
|
||||
{
|
||||
Type type;
|
||||
Identifier typeName = element.NameAsIdentifier();
|
||||
type = TryGetClass(typeName.RemoveFromEnd("Mission"));
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
//backwards compatibility: the actual mission class used to be defined by the "type" attribute,
|
||||
//Now the mission class is defined by the name of the mission element, and the type can be any arbitrary string,
|
||||
//but if we failed to find the class based on the name, let's try the type attribute.
|
||||
Identifier typeNameLegacy = (element.GetAttributeIdentifier("type", Identifier.Empty)).ToIdentifier();
|
||||
if (typeNameLegacy == "OutpostDestroy" || typeNameLegacy == "OutpostRescue")
|
||||
{
|
||||
typeNameLegacy = "AbandonedOutpost".ToIdentifier();
|
||||
}
|
||||
else if (typeNameLegacy == "clearalienruins")
|
||||
{
|
||||
typeNameLegacy = "EliminateTargets".ToIdentifier();
|
||||
}
|
||||
type = TryGetClass(typeNameLegacy) ?? TryGetClass(typeNameLegacy.AppendIfMissing("Mission"));
|
||||
if (type == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to find the mission type \"{typeNameLegacy}\" for the mission {Identifier}.",
|
||||
contentPackage: element.ContentPackage);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Type TryGetClass(Identifier typeName)
|
||||
{
|
||||
if (CoOpMissionClasses.TryGetValue(typeName, out Type coOpMissionClass))
|
||||
{
|
||||
return coOpMissionClass;
|
||||
}
|
||||
else if (PvPMissionClasses.TryGetValue(typeName, out Type pvpMissionClass))
|
||||
{
|
||||
return pvpMissionClass;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
private ConstructorInfo FindMissionConstructor(ContentXElement element, Type missionClass)
|
||||
{
|
||||
ConstructorInfo constructor;
|
||||
if (missionClass == null) { return null; }
|
||||
if (missionClass != typeof(Mission) && !missionClass.IsSubclassOf(typeof(Mission))) { return null; }
|
||||
constructor = missionClass.GetConstructor(new Type[] { typeof(MissionPrefab), typeof(Location[]), typeof(Submarine) });
|
||||
if (constructor == null)
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
$"Could not find the constructor of the mission type \"{missionClass}\" for the mission {Identifier}",
|
||||
contentPackage: element.ContentPackage);
|
||||
return null;
|
||||
}
|
||||
return constructor;
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element);
|
||||
|
||||
@@ -424,7 +482,7 @@ namespace Barotrauma
|
||||
}
|
||||
return
|
||||
AllowedLocationTypes.Any(lt => lt == "any") ||
|
||||
AllowedLocationTypes.Any(lt => lt == "anyoutpost" && from.HasOutpost()) ||
|
||||
AllowedLocationTypes.Any(lt => lt == Barotrauma.Tags.AnyOutpost && from.HasOutpost() && from.Type.IsAnyOutpost) ||
|
||||
AllowedLocationTypes.Any(lt => lt == from.Type.Identifier);
|
||||
}
|
||||
|
||||
@@ -432,11 +490,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (fromType == "any" ||
|
||||
fromType == from.Type.Identifier ||
|
||||
(fromType == "anyoutpost" && from.HasOutpost() && from.Type.Identifier != "abandoned"))
|
||||
(fromType == Barotrauma.Tags.AnyOutpost && from.HasOutpost() && from.Type.IsAnyOutpost && from.Type.Identifier != "abandoned"))
|
||||
{
|
||||
if (toType == "any" ||
|
||||
toType == to.Type.Identifier ||
|
||||
(toType == "anyoutpost" && to.HasOutpost() && to.Type.Identifier != "abandoned"))
|
||||
(toType == Barotrauma.Tags.AnyOutpost && to.HasOutpost() && to.Type.IsAnyOutpost && to.Type.Identifier != "abandoned"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -461,5 +519,28 @@ namespace Barotrauma
|
||||
{
|
||||
DisposeProjectSpecific();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all mission types that can be selected e.g. in the server lobby, excluding any special, hidden ones like EndMission
|
||||
/// (the mission at the end of the campaign)
|
||||
/// </summary>
|
||||
public static IEnumerable<Identifier> GetAllMultiplayerSelectableMissionTypes()
|
||||
{
|
||||
List<Identifier> missionTypes = new List<Identifier>();
|
||||
foreach (var missionPrefab in Prefabs)
|
||||
{
|
||||
if (missionPrefab.Commonness <= 0.0f) { continue; }
|
||||
if (missionPrefab.SingleplayerOnly) { continue; }
|
||||
if (HiddenMissionTypes.Contains(missionPrefab.Type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!missionTypes.Contains(missionPrefab.Type))
|
||||
{
|
||||
missionTypes.Add(missionPrefab.Type);
|
||||
}
|
||||
}
|
||||
return missionTypes.OrderBy(t => t.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace Barotrauma
|
||||
private readonly List<Character> characters = new List<Character>();
|
||||
private readonly Dictionary<Character, List<Item>> characterItems = new Dictionary<Character, List<Item>>();
|
||||
|
||||
private readonly Dictionary<HumanPrefab, List<StatusEffect>> characterStatusEffects = new Dictionary<HumanPrefab, List<StatusEffect>>();
|
||||
|
||||
// Update the last sighting periodically so that the players can find the pirate sub even if they have lost the track of it.
|
||||
private readonly float pirateSightingUpdateFrequency = 30;
|
||||
private float pirateSightingUpdateTimer;
|
||||
@@ -96,14 +98,26 @@ namespace Barotrauma
|
||||
characterTypeConfig = prefab.ConfigElement.GetChildElement("CharacterTypes");
|
||||
addedMissionDifficultyPerPlayer = prefab.ConfigElement.GetAttributeFloat("addedmissiondifficultyperplayer", 0);
|
||||
|
||||
factionIdentifier = prefab.ConfigElement.GetAttributeIdentifier("faction", Identifier.Empty);
|
||||
|
||||
//make sure all referenced character types are defined
|
||||
foreach (XElement characterElement in characterConfig.Elements())
|
||||
{
|
||||
var characterId = characterElement.GetAttributeString("typeidentifier", string.Empty);
|
||||
var characterTypeElement = characterTypeConfig.Elements().FirstOrDefault(e => e.GetAttributeString("typeidentifier", string.Empty) == characterId);
|
||||
Identifier typeId = characterElement.GetAttributeIdentifier("typeidentifier", Identifier.Empty);
|
||||
if (typeId.IsEmpty)
|
||||
{
|
||||
if (characterElement.GetAttributeIdentifier("identifier", Identifier.Empty).IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission \"{prefab.Identifier}\". Character element with neither a typeidentifier or identifier ({characterElement.ToString()}).",
|
||||
contentPackage: Prefab.ContentPackage);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
var characterTypeElement = characterTypeConfig.Elements().FirstOrDefault(e =>
|
||||
e.GetAttributeIdentifier("typeidentifier", Identifier.Empty) == typeId);
|
||||
if (characterTypeElement == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission \"{prefab.Identifier}\". Could not find a character type element for the character \"{characterId}\".",
|
||||
DebugConsole.ThrowError($"Error in mission \"{prefab.Identifier}\". Could not find a character type element for the character \"{typeId}\".",
|
||||
contentPackage: Prefab.ContentPackage);
|
||||
}
|
||||
}
|
||||
@@ -143,37 +157,47 @@ namespace Barotrauma
|
||||
levelData = level;
|
||||
missionDifficulty = level?.Difficulty ?? 0;
|
||||
|
||||
XElement submarineConfig = GetRandomDifficultyModifiedElement(submarineTypeConfig, missionDifficulty, ShipRandomnessModifier);
|
||||
alternateReward = submarineConfig.GetAttributeInt("alternatereward", Reward);
|
||||
factionIdentifier = submarineConfig.GetAttributeIdentifier("faction", Identifier.Empty);
|
||||
//no specific sub configured, choose a random one
|
||||
if (submarineTypeConfig == null)
|
||||
{
|
||||
submarineInfo = GetRandomDifficultyModifiedSubmarine(missionDifficulty, ShipRandomnessModifier);
|
||||
alternateReward = (int)submarineInfo.EnemySubmarineInfo.Reward;
|
||||
}
|
||||
else
|
||||
{
|
||||
XElement submarineConfig = GetRandomDifficultyModifiedElement(submarineTypeConfig, missionDifficulty, ShipRandomnessModifier);
|
||||
alternateReward = submarineConfig.GetAttributeInt("alternatereward", Reward);
|
||||
factionIdentifier = submarineConfig.GetAttributeIdentifier("faction", factionIdentifier);
|
||||
|
||||
ContentPath submarinePath = submarineConfig.GetAttributeContentPath("path", Prefab.ContentPackage);
|
||||
if (submarinePath.IsNullOrEmpty())
|
||||
{
|
||||
DebugConsole.ThrowError($"No path used for submarine for the pirate mission \"{Prefab.Identifier}\"!",
|
||||
contentPackage: Prefab.ContentPackage);
|
||||
return;
|
||||
}
|
||||
|
||||
BaseSubFile contentFile =
|
||||
GetSubFile<EnemySubmarineFile>(submarinePath) ??
|
||||
GetSubFile<SubmarineFile>(submarinePath);
|
||||
BaseSubFile GetSubFile<T>(ContentPath path) where T : BaseSubFile
|
||||
{
|
||||
return ContentPackageManager.EnabledPackages.All.SelectMany(p => p.GetFiles<T>()).FirstOrDefault(f => f.Path == submarinePath);
|
||||
}
|
||||
|
||||
if (contentFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"No submarine file found from the path {submarinePath}!",
|
||||
contentPackage: Prefab.ContentPackage);
|
||||
return;
|
||||
}
|
||||
|
||||
submarineInfo = new SubmarineInfo(contentFile.Path.Value);
|
||||
}
|
||||
|
||||
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", alternateReward)}‖end‖";
|
||||
if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
|
||||
|
||||
ContentPath submarinePath = submarineConfig.GetAttributeContentPath("path", Prefab.ContentPackage);
|
||||
if (submarinePath.IsNullOrEmpty())
|
||||
{
|
||||
DebugConsole.ThrowError($"No path used for submarine for the pirate mission \"{Prefab.Identifier}\"!",
|
||||
contentPackage: Prefab.ContentPackage);
|
||||
return;
|
||||
}
|
||||
|
||||
BaseSubFile contentFile =
|
||||
GetSubFile<EnemySubmarineFile>(submarinePath) ??
|
||||
GetSubFile<SubmarineFile>(submarinePath);
|
||||
BaseSubFile GetSubFile<T>(ContentPath path) where T : BaseSubFile
|
||||
{
|
||||
return ContentPackageManager.EnabledPackages.All.SelectMany(p => p.GetFiles<T>()).FirstOrDefault(f => f.Path == submarinePath);
|
||||
}
|
||||
|
||||
if (contentFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"No submarine file found from the path {submarinePath}!",
|
||||
contentPackage: Prefab.ContentPackage);
|
||||
return;
|
||||
}
|
||||
|
||||
submarineInfo = new SubmarineInfo(contentFile.Path.Value);
|
||||
}
|
||||
|
||||
private static float GetDifficultyModifiedValue(float preferredDifficulty, float levelDifficulty, float randomnessModifier, Random rand)
|
||||
@@ -185,6 +209,33 @@ namespace Barotrauma
|
||||
return Math.Max((int)Math.Round(minAmount + (maxAmount - minAmount) * (levelDifficulty + MathHelper.Lerp(-RandomnessModifier, RandomnessModifier, (float)rand.NextDouble())) / MaxDifficulty), minAmount);
|
||||
}
|
||||
|
||||
private SubmarineInfo GetRandomDifficultyModifiedSubmarine(float levelDifficulty, float randomnessModifier)
|
||||
{
|
||||
Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
|
||||
// look for the saved submarine that is closest to our difficulty, with some randomness
|
||||
SubmarineInfo bestSubmarine = null;
|
||||
float bestValue = float.MaxValue;
|
||||
var submarineInfos = SubmarineInfo.SavedSubmarines.Where(i => i.IsEnemySubmarine);
|
||||
foreach (SubmarineInfo submarineInfo in submarineInfos)
|
||||
{
|
||||
if (!Prefab.Tags.Any(t => submarineInfo.EnemySubmarineInfo.MissionTags.Contains(t))) { continue; }
|
||||
float applicabilityValue = GetDifficultyModifiedValue(submarineInfo.EnemySubmarineInfo.PreferredDifficulty, levelDifficulty, randomnessModifier, rand);
|
||||
if (applicabilityValue < bestValue)
|
||||
{
|
||||
bestSubmarine = submarineInfo;
|
||||
bestValue = applicabilityValue;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestSubmarine == null)
|
||||
{
|
||||
DebugConsole.ThrowError("No EnemySubmarine found that matches the mission's tags!");
|
||||
return SubmarineInfo.SavedSubmarines.First(i => i.IsEnemySubmarine);
|
||||
}
|
||||
|
||||
return bestSubmarine;
|
||||
}
|
||||
|
||||
private XElement GetRandomDifficultyModifiedElement(XElement parentElement, float levelDifficulty, float randomnessModifier)
|
||||
{
|
||||
Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
|
||||
@@ -300,30 +351,54 @@ namespace Barotrauma
|
||||
bool commanderAssigned = false;
|
||||
foreach (ContentXElement element in characterConfig.Elements())
|
||||
{
|
||||
//there's two ways to define the characters in pirate missions
|
||||
//1. "the normal way", referring to a human prefab
|
||||
Identifier humanPrefabId = element.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
//2. the strange way it was initially implemented and the way the vanilla missions work: using a reference to a "character type" in the mission, which refers to a human prefab
|
||||
Identifier characterTypeId = element.GetAttributeIdentifier("typeidentifier", Identifier.Empty);
|
||||
|
||||
int minAmount = element.GetAttributeInt("minamount", 0);
|
||||
int maxAmount = element.GetAttributeInt("maxamount", 0);
|
||||
// it is possible to get more than the "max" amount of characters if the modified difficulty is high enough; this is intentional
|
||||
// if necessary, another "hard max" value could be used to clamp the value for performance/gameplay concerns
|
||||
int amountCreated = GetDifficultyModifiedAmount(element.GetAttributeInt("minamount", 0), element.GetAttributeInt("maxamount", 0), enemyCreationDifficulty, rand);
|
||||
var characterId = element.GetAttributeString("typeidentifier", string.Empty);
|
||||
int amountCreated = minAmount == 0 && maxAmount == 0 ?
|
||||
//default to 1 character if amount is not defined
|
||||
1 :
|
||||
//otherwise choose a value between min and max based on difficulty
|
||||
GetDifficultyModifiedAmount(minAmount, maxAmount, enemyCreationDifficulty, rand);
|
||||
|
||||
for (int i = 0; i < amountCreated; i++)
|
||||
{
|
||||
XElement characterType = characterTypeConfig.Elements().Where(e => e.GetAttributeString("typeidentifier", string.Empty) == characterId).FirstOrDefault();
|
||||
|
||||
if (characterType == null)
|
||||
HumanPrefab humanPrefab = null;
|
||||
bool isCommander = false;
|
||||
if (!characterTypeId.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"No character types defined in CharacterTypes for a declared type identifier in mission \"{Prefab.Identifier}\".",
|
||||
contentPackage: element.ContentPackage);
|
||||
return;
|
||||
XElement characterType = characterTypeConfig.Elements().Where(e => e.GetAttributeIdentifier("typeidentifier", Identifier.Empty) == characterTypeId).FirstOrDefault();
|
||||
if (characterType == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"No character types defined in CharacterTypes for a declared type identifier in mission \"{Prefab.Identifier}\".",
|
||||
contentPackage: element.ContentPackage);
|
||||
return;
|
||||
}
|
||||
XElement variantElement = GetRandomDifficultyModifiedElement(characterType, enemyCreationDifficulty, RandomnessModifier);
|
||||
humanPrefab = GetHumanPrefabFromElement(variantElement);
|
||||
isCommander = variantElement.GetAttributeBool("iscommander", false);
|
||||
}
|
||||
else if (!humanPrefabId.IsEmpty)
|
||||
{
|
||||
humanPrefab = GetHumanPrefabFromElement(element);
|
||||
isCommander = element.GetAttributeBool("iscommander", false);
|
||||
}
|
||||
|
||||
XElement variantElement = GetRandomDifficultyModifiedElement(characterType, enemyCreationDifficulty, RandomnessModifier);
|
||||
|
||||
var humanPrefab = GetHumanPrefabFromElement(variantElement);
|
||||
if (humanPrefab == null) { continue; }
|
||||
|
||||
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, enemySub, CharacterTeamType.None, null);
|
||||
if (element.GetAttribute("color") != null)
|
||||
{
|
||||
spawnedCharacter.UniqueNameColor = element.GetAttributeColor("color", Color.Red);
|
||||
}
|
||||
if (!commanderAssigned)
|
||||
{
|
||||
bool isCommander = variantElement.GetAttributeBool("iscommander", false);
|
||||
if (isCommander && spawnedCharacter.AIController is HumanAIController humanAIController)
|
||||
{
|
||||
humanAIController.InitShipCommandManager();
|
||||
@@ -335,6 +410,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (subElement.NameAsIdentifier() == "statuseffect")
|
||||
{
|
||||
var newEffect = StatusEffect.Load(subElement, parentDebugName: Prefab.Name.Value);
|
||||
newEffect?.Apply(newEffect.type, 1.0f, spawnedCharacter, spawnedCharacter);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Item item in spawnedCharacter.Inventory.AllItems)
|
||||
{
|
||||
if (item?.GetComponent<IdCard>() != null)
|
||||
|
||||
@@ -311,7 +311,11 @@ namespace Barotrauma
|
||||
targets.Add(target);
|
||||
foreach (ContentXElement subElement in chosenElement.Elements())
|
||||
{
|
||||
LoadTarget(subElement, parentTarget: target);
|
||||
if (subElement.NameAsIdentifier() == "target" ||
|
||||
subElement.NameAsIdentifier() == "chooserandom")
|
||||
{
|
||||
LoadTarget(subElement, parentTarget: target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user