Unstable 1.2.1.0

This commit is contained in:
Markus Isberg
2023-11-10 17:45:19 +02:00
parent 2ea58c58a7
commit 8a2e2ea0ae
268 changed files with 4076 additions and 1843 deletions
@@ -34,7 +34,8 @@ namespace Barotrauma
{
if (prefab.ConfigElement.GetAttribute("itemname") != null)
{
DebugConsole.ThrowError("Error in ArtifactEvent - use item identifier instead of the name of the item.");
DebugConsole.ThrowError("Error in ArtifactEvent - use item identifier instead of the name of the item.",
contentPackage: prefab?.ContentPackage);
string itemName = prefab.ConfigElement.GetAttributeString("itemname", "");
itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
if (itemPrefab == null)
@@ -44,11 +45,12 @@ namespace Barotrauma
}
else
{
string itemIdentifier = prefab.ConfigElement.GetAttributeString("itemidentifier", "");
itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
Identifier itemIdentifier = prefab.ConfigElement.GetAttributeIdentifier("itemidentifier", Identifier.Empty);
itemPrefab = MapEntityPrefab.FindByIdentifier(itemIdentifier) as ItemPrefab;
if (itemPrefab == null)
{
DebugConsole.ThrowError("Error in ArtifactEvent - couldn't find an item prefab with the identifier " + itemIdentifier);
DebugConsole.ThrowError("Error in ArtifactEvent - couldn't find an item prefab with the identifier " + itemIdentifier,
contentPackage: prefab?.ContentPackage);
}
}
}
@@ -39,7 +39,7 @@ namespace Barotrauma
public Event(EventPrefab prefab)
{
this.prefab = prefab;
this.prefab = prefab ?? throw new ArgumentNullException(nameof(prefab));
}
public virtual IEnumerable<ContentFile> GetFilesToPreload()
@@ -14,12 +14,14 @@ namespace Barotrauma
{
if (TargetTag.IsEmpty)
{
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no target tag! This will cause the check to automatically succeed.");
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no target tag! This will cause the check to automatically succeed.",
contentPackage: parentEvent.Prefab.ContentPackage);
}
Conditional = PropertyConditional.FromXElement(element, IsNotTargetTagAttribute).FirstOrDefault();
if (Conditional == null)
{
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no valid PropertyConditional! This will cause the check to automatically succeed.");
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no valid PropertyConditional! This will cause the check to automatically succeed.",
contentPackage: parentEvent.Prefab.ContentPackage);
}
static bool IsNotTargetTagAttribute(XAttribute attribute) => attribute.NameAsIdentifier() != "targettag";
@@ -46,7 +48,8 @@ namespace Barotrauma
}
if (target == null)
{
DebugConsole.LogError($"{nameof(CheckConditionalAction)} error: {GetEventName()} uses a {nameof(CheckConditionalAction)} but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed.");
DebugConsole.LogError($"{nameof(CheckConditionalAction)} error: {GetEventName()} uses a {nameof(CheckConditionalAction)} but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed.",
contentPackage: ParentEvent.Prefab.ContentPackage);
}
if (target == null || Conditional == null)
{
@@ -30,7 +30,8 @@ namespace Barotrauma
Condition = element.GetAttributeString("value", string.Empty)!;
if (string.IsNullOrEmpty(Condition))
{
DebugConsole.ThrowError($"Error in scripted event \"{parentEvent.Prefab.Identifier}\". CheckDataAction with no condition set ({element}).");
DebugConsole.ThrowError($"Error in scripted event \"{parentEvent.Prefab.Identifier}\". CheckDataAction with no condition set ({element}).",
contentPackage: element?.ContentPackage);
}
}
}
@@ -42,7 +43,8 @@ namespace Barotrauma
Condition = element.GetAttributeString("value", string.Empty)!;
if (string.IsNullOrEmpty(Condition))
{
DebugConsole.ThrowError($"Error in scripted event \"{parentDebugString}\". CheckDataAction with no condition set ({element}).");
DebugConsole.ThrowError($"Error in scripted event \"{parentDebugString}\". CheckDataAction with no condition set ({element}).",
contentPackage: element?.ContentPackage);
}
}
}
@@ -59,7 +61,8 @@ namespace Barotrauma
(Operator, string value) = PropertyConditional.ExtractComparisonOperatorFromConditionString(Condition);
if (Operator == PropertyConditional.ComparisonOperatorType.None)
{
DebugConsole.ThrowError($"{Condition} is invalid, it should start with an operator followed by a boolean or a floating point value.");
DebugConsole.ThrowError($"{Condition} is invalid, it should start with an operator followed by a boolean or a floating point value.",
contentPackage: ParentEvent?.Prefab?.ContentPackage);
return false;
}
@@ -77,7 +77,8 @@ namespace Barotrauma
ItemIdentifiers.None() &&
TargetTag.IsEmpty)
{
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(CheckItemAction)} does't define either tags or identifiers of the item to check.");
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(CheckItemAction)} does't define either tags or identifiers of the item to check.",
contentPackage: element.ContentPackage);
}
checkPercentage = element.GetAttribute(nameof(RequiredConditionalMatchPercentage)) is not null;
if (checkPercentage && conditionals.None())
@@ -86,7 +87,8 @@ namespace Barotrauma
}
if (Amount != 1 && checkPercentage)
{
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". Cannot define both '{Amount}' and '{RequiredConditionalMatchPercentage}' in {nameof(CheckItemAction)}.");
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". Cannot define both '{Amount}' and '{RequiredConditionalMatchPercentage}' in {nameof(CheckItemAction)}.",
contentPackage: element.ContentPackage);
}
}
@@ -32,7 +32,8 @@ namespace Barotrauma
var targetCharacters = ParentEvent.GetTargets(TargetTag);
if (targetCharacters.None())
{
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target characters were found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target characters were found for tag \"{TargetTag}\"! This will cause the check to automatically fail.",
contentPackage: ParentEvent.Prefab.ContentPackage);
return false;
}
foreach (var t in targetCharacters)
@@ -1,7 +1,5 @@
#nullable enable
using System;
using System.Diagnostics;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -31,7 +29,8 @@ namespace Barotrauma
}
default:
{
DebugConsole.ThrowError("CheckReputationAction requires a \"TargetType\" but none were specified.");
DebugConsole.ThrowError("CheckReputationAction requires a \"TargetType\" but none were specified.",
contentPackage: ParentEvent.Prefab.ContentPackage);
break;
}
}
@@ -41,7 +40,8 @@ namespace Barotrauma
protected override bool GetBool(CampaignMode campaignMode)
{
DebugConsole.ThrowError("Boolean comparison cannot be applied to reputations.");
DebugConsole.ThrowError("Boolean comparison cannot be applied to reputations.",
contentPackage: ParentEvent.Prefab.ContentPackage);
return false;
}
@@ -87,13 +87,13 @@ namespace Barotrauma
#if DEBUG
void Error(string errorMsg)
{
DebugConsole.ThrowError(errorMsg);
DebugConsole.ThrowError(errorMsg, contentPackage: ParentEvent.Prefab.ContentPackage);
}
#else
void Error(string errorMsg)
{
DebugConsole.LogError(errorMsg);
DebugConsole.LogError(errorMsg, contentPackage: ParentEvent.Prefab.ContentPackage);
}
#endif
}
@@ -17,7 +17,8 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"Cannot use the action {nameof(CheckTraitorEventStateAction)} in the event \"{parentEvent.Prefab.Identifier}\" because it's not a traitor event.");
DebugConsole.ThrowError($"Cannot use the action {nameof(CheckTraitorEventStateAction)} in the event \"{parentEvent.Prefab.Identifier}\" because it's not a traitor event.",
contentPackage: element.ContentPackage);
}
}
@@ -16,7 +16,8 @@ namespace Barotrauma
{
if (parentEvent is not TraitorEvent)
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\" - {nameof(CheckTraitorVoteAction)} can only be used in traitor events.");
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\" - {nameof(CheckTraitorVoteAction)} can only be used in traitor events.",
contentPackage: element.ContentPackage);
}
}
@@ -116,7 +116,8 @@ namespace Barotrauma
{
DebugConsole.ThrowError(
$"Error in {nameof(EventObjectiveAction)} in the event \"{parentEvent.Prefab.Identifier}\"" +
$" - unrecognized child element \"Replace\".");
$" - unrecognized child element \"Replace\".",
contentPackage: element.ContentPackage);
}
}
@@ -61,14 +61,16 @@ namespace Barotrauma
}
if (MinAmount > MaxAmount && MaxAmount > -1)
{
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {MinAmount} is larger than {MaxAmount} in {nameof(CountTargetsAction)}.");
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {MinAmount} is larger than {MaxAmount} in {nameof(CountTargetsAction)}.",
contentPackage: element.ContentPackage);
}
}
else
{
if (MinPercentageRelativeToTarget < 0.0f && MaxPercentageRelativeToTarget < 0.0f)
{
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". Comparing to another target, but neither {nameof(MinPercentageRelativeToTarget)} or {nameof(MaxPercentageRelativeToTarget)} is set.");
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". Comparing to another target, but neither {nameof(MinPercentageRelativeToTarget)} or {nameof(MaxPercentageRelativeToTarget)} is set.",
contentPackage: element.ContentPackage);
}
}
}
@@ -36,7 +36,8 @@ namespace Barotrauma
{
if (e.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase))
{
DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". Status effect configured as a sub action (text: \"{Text}\"). Please configure status effects as child elements of a StatusEffectAction.");
DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". Status effect configured as a sub action (text: \"{Text}\"). Please configure status effects as child elements of a StatusEffectAction.",
contentPackage: elem.ContentPackage);
continue;
}
var action = Instantiate(scriptedEvent, e);
@@ -102,7 +103,7 @@ namespace Barotrauma
public EventAction(ScriptedEvent parentEvent, ContentXElement element)
{
ParentEvent = parentEvent;
ParentEvent = parentEvent ?? throw new ArgumentNullException(nameof(parentEvent));
SerializableProperty.DeserializeProperties(this, element);
}
@@ -147,7 +148,8 @@ namespace Barotrauma
}
catch
{
DebugConsole.ThrowError($"Could not find an {nameof(EventAction)} class of the type \"{element.Name}\".");
DebugConsole.ThrowError($"Could not find an {nameof(EventAction)} class of the type \"{element.Name}\".",
contentPackage: element.ContentPackage);
return null;
}
@@ -162,7 +164,8 @@ namespace Barotrauma
}
catch (Exception ex)
{
DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString());
DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString(),
contentPackage: element.ContentPackage);
return null;
}
}
@@ -24,7 +24,8 @@ namespace Barotrauma
{
if (Id == Identifier.Empty)
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\". {nameof(EventLogAction)} with no id.");
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\". {nameof(EventLogAction)} with no id.",
contentPackage: element.ContentPackage);
}
//append the target tag so logs targeted to different players don't interfere with each other even if they use the same Id
Id = (Id.ToString() + TargetTag).ToIdentifier();
@@ -42,7 +43,8 @@ namespace Barotrauma
{
if (Text.IsNullOrEmpty())
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\". {nameof(EventLogAction)} with no text set ({element}).");
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\". {nameof(EventLogAction)} with no text set ({element}).",
contentPackage: element.ContentPackage);
}
else
{
@@ -49,13 +49,15 @@ namespace Barotrauma
{
DebugConsole.ThrowError(
$"Error in {nameof(EventObjectiveAction)} in the event \"{parentEvent.Prefab.Identifier}\""+
$" - {nameof(TextTag)} will do nothing unless the action triggers a message box or a video.");
$" - {nameof(TextTag)} will do nothing unless the action triggers a message box or a video.",
contentPackage: element.ContentPackage);
}
if (element.GetChildElement("Replace") != null)
{
DebugConsole.ThrowError(
$"Error in {nameof(EventObjectiveAction)} in the event \"{parentEvent.Prefab.Identifier}\"" +
$" - unrecognized child element \"Replace\".");
$" - unrecognized child element \"Replace\".",
contentPackage: element.ContentPackage);
}
}
@@ -14,7 +14,8 @@ namespace Barotrauma
{
if (TargetTag.IsEmpty)
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(GiveExpAction)} without a target tag (the action needs to know whose skill to check).");
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(GiveExpAction)} without a target tag (the action needs to know whose skill to check).",
contentPackage: element.ContentPackage);
}
}
@@ -17,7 +17,8 @@ namespace Barotrauma
{
if (TargetTag.IsEmpty)
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(GiveSkillExpAction)} without a target tag (the action needs to know whose skill to check).");
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(GiveSkillExpAction)} without a target tag (the action needs to know whose skill to check).",
contentPackage: element.ContentPackage);
}
}
@@ -37,11 +37,13 @@ namespace Barotrauma
{
if (MissionIdentifier.IsEmpty && MissionTag.IsEmpty)
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": neither MissionIdentifier or MissionTag has been configured.");
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": neither MissionIdentifier or MissionTag has been configured.",
contentPackage: element.ContentPackage);
}
if (!MissionIdentifier.IsEmpty && !MissionTag.IsEmpty)
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": both MissionIdentifier or MissionTag have been configured. The tag will be ignored.");
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": both MissionIdentifier or MissionTag have been configured. The tag will be ignored.",
contentPackage: element.ContentPackage);
}
LocationTypes = element.GetAttributeIdentifierArray("locationtype", Array.Empty<Identifier>()).ToImmutableArray();
random = new MTRandom(parentEvent.RandomSeed);
@@ -103,11 +105,13 @@ namespace Barotrauma
{
if (!MissionIdentifier.IsEmpty)
{
unlockedMission = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier);
unlockedMission = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier,
invokingContentPackage: ParentEvent.Prefab.ContentPackage);
}
else if (!MissionTag.IsEmpty)
{
unlockedMission = unlockLocation.UnlockMissionByTag(MissionTag, random);
unlockedMission = unlockLocation.UnlockMissionByTag(MissionTag, random,
invokingContentPackage: ParentEvent.Prefab.ContentPackage);
}
if (campaign is MultiPlayerCampaign mpCampaign)
{
@@ -139,7 +143,8 @@ namespace Barotrauma
}
else
{
DebugConsole.AddWarning($"Failed to find a suitable location to unlock the mission \"{missionDebugId}\" (LocationType: {string.Join(", ", LocationTypes)}, MinLocationDistance: {MinLocationDistance}, UnlockFurtherOnMap: {UnlockFurtherOnMap})");
DebugConsole.AddWarning($"Failed to find a suitable location to unlock the mission \"{missionDebugId}\" (LocationType: {string.Join(", ", LocationTypes)}, MinLocationDistance: {MinLocationDistance}, UnlockFurtherOnMap: {UnlockFurtherOnMap})",
ParentEvent.Prefab.ContentPackage);
}
}
isFinished = true;
@@ -24,7 +24,8 @@ namespace Barotrauma
State = element.GetAttributeInt("value", State);
if (MissionIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": MissionIdentifier has not been configured.");
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": MissionIdentifier has not been configured.",
contentPackage: element.ContentPackage);
}
}
@@ -43,7 +43,8 @@ namespace Barotrauma
var faction = campaign.Factions.Find(f => f.Prefab.Identifier == Faction);
if (faction == null)
{
DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a faction with the identifier \"{Faction}\".");
DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a faction with the identifier \"{Faction}\".",
contentPackage: ParentEvent?.Prefab?.ContentPackage);
}
else
{
@@ -55,7 +56,8 @@ namespace Barotrauma
var secondaryFaction = campaign.Factions.Find(f => f.Prefab.Identifier == SecondaryFaction);
if (secondaryFaction == null)
{
DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a faction with the identifier \"{SecondaryFaction}\".");
DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a faction with the identifier \"{SecondaryFaction}\".",
contentPackage: ParentEvent.Prefab.ContentPackage);
}
else
{
@@ -67,7 +69,8 @@ namespace Barotrauma
var locationType = LocationType.Prefabs.Find(lt => lt.Identifier == Type);
if (locationType == null)
{
DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a location type with the identifier \"{Type}\".");
DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a location type with the identifier \"{Type}\".",
contentPackage: ParentEvent.Prefab.ContentPackage);
}
else if (!location.LocationTypeChangesBlocked)
{
@@ -30,7 +30,8 @@ namespace Barotrauma
var enums = Enum.GetValues(typeof(CharacterTeamType)).Cast<CharacterTeamType>();
if (!enums.Contains(TeamID))
{
DebugConsole.ThrowError($"Error in {nameof(NPCChangeTeamAction)} in the event {ParentEvent.Prefab.Identifier}. \"{TeamID}\" is not a valid Team ID. Valid values are {string.Join(',', Enum.GetNames(typeof(CharacterTeamType)))}.");
DebugConsole.ThrowError($"Error in {nameof(NPCChangeTeamAction)} in the event {ParentEvent.Prefab.Identifier}. \"{TeamID}\" is not a valid Team ID. Valid values are {string.Join(',', Enum.GetNames(typeof(CharacterTeamType)))}.",
contentPackage: element.ContentPackage);
}
}
@@ -13,11 +13,13 @@ namespace Barotrauma
{
if (Chance >= 1.0f)
{
DebugConsole.ThrowError($"Incorrectly configured RNG Action in event \"{parentEvent.Prefab.Identifier}\". Probability is 1.0 (100%) or more, the action will always succeed.");
DebugConsole.ThrowError($"Incorrectly configured RNG Action in event \"{parentEvent.Prefab.Identifier}\". Probability is 1.0 (100%) or more, the action will always succeed.",
contentPackage: element.ContentPackage);
}
else if (Chance <= 0.0f)
{
DebugConsole.ThrowError($"Incorrectly configured RNG Action in event \"{parentEvent.Prefab.Identifier}\". Probability is 0 or less, the action will never succeed.");
DebugConsole.ThrowError($"Incorrectly configured RNG Action in event \"{parentEvent.Prefab.Identifier}\". Probability is 0 or less, the action will never succeed.",
contentPackage: element.ContentPackage);
}
}
@@ -54,7 +54,8 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"Faction with the identifier \"{Identifier}\" was not found.");
DebugConsole.ThrowError($"Faction with the identifier \"{Identifier}\" was not found.",
contentPackage: ParentEvent.Prefab.ContentPackage);
}
break;
@@ -66,7 +67,8 @@ namespace Barotrauma
}
default:
{
DebugConsole.ThrowError("ReputationAction requires a \"TargetType\" but none were specified.");
DebugConsole.ThrowError("ReputationAction requires a \"TargetType\" but none were specified.",
contentPackage: ParentEvent.Prefab.ContentPackage);
break;
}
}
@@ -14,7 +14,8 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"Cannot use the action {nameof(SetTraitorEventStateAction)} in the event \"{parentEvent.Prefab.Identifier}\" because it's not a traitor event.");
DebugConsole.ThrowError($"Cannot use the action {nameof(SetTraitorEventStateAction)} in the event \"{parentEvent.Prefab.Identifier}\" because it's not a traitor event.",
contentPackage: element.ContentPackage);
}
}
@@ -23,7 +23,8 @@ namespace Barotrauma
{
if (TargetTag.IsEmpty)
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": SkillCheckAction without a target tag (the action needs to know whose skill to check).");
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": SkillCheckAction without a target tag (the action needs to know whose skill to check).",
contentPackage: element.ContentPackage);
}
}
@@ -105,7 +105,8 @@ namespace Barotrauma
{
DebugConsole.ThrowError(
$"Error in even \"{(parentEvent.Prefab?.Identifier.ToString() ?? "unknown")}\". " +
$"The attribute \"submarinetype\" is not valid in {nameof(SpawnAction)}. Did you mean {nameof(SpawnLocation)}?");
$"The attribute \"submarinetype\" is not valid in {nameof(SpawnAction)}. Did you mean {nameof(SpawnLocation)}?",
contentPackage: ParentEvent.Prefab.ContentPackage);
}
}
@@ -233,7 +234,8 @@ namespace Barotrauma
{
if (MapEntityPrefab.FindByIdentifier(ItemIdentifier) is not ItemPrefab itemPrefab)
{
DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)");
DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)",
contentPackage: ParentEvent.Prefab.ContentPackage);
}
else
{
@@ -256,7 +258,8 @@ namespace Barotrauma
if (spawnInventory == null)
{
DebugConsole.ThrowError($"Could not spawn \"{ItemIdentifier}\" in target inventory \"{TargetInventory}\" - matching target not found.");
DebugConsole.ThrowError($"Could not spawn \"{ItemIdentifier}\" in target inventory \"{TargetInventory}\" - matching target not found.",
contentPackage: ParentEvent.Prefab.ContentPackage);
}
}
@@ -36,7 +36,19 @@ namespace Barotrauma
private bool isFinished = false;
private bool targetNotFound = false;
/// <summary>
/// If the action tags some entities directly (not trying to find targets on the fly),
/// we may be able to determine that targets can not be found even if we'd recheck
/// </summary>
private bool cantFindTargets = false;
/// <summary>
/// If the TagAction adds a target predicate (a criteria that keeps finding targets on the fly),
/// we must keep checking if targets have been found to determine if the action can continue or not
/// </summary>
private bool mustRecheckTargets = false;
private bool taggingDone = false;
public TagAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
{
@@ -198,6 +210,7 @@ namespace Barotrauma
else
{
ParentEvent.AddTargetPredicate(tag, predicate);
mustRecheckTargets = true;
}
}
@@ -205,7 +218,7 @@ namespace Barotrauma
{
if (entities.None())
{
targetNotFound = true;
cantFindTargets = true;
return;
}
if (ChoosePercentage > 0.0f)
@@ -230,7 +243,7 @@ namespace Barotrauma
{
if (entities.None())
{
targetNotFound = true;
cantFindTargets = true;
return;
}
@@ -250,7 +263,7 @@ namespace Barotrauma
{
if (entities.None())
{
targetNotFound = true;
cantFindTargets = true;
return;
}
ParentEvent.AddTarget(tag, entities.GetRandomUnsynced());
@@ -260,26 +273,30 @@ namespace Barotrauma
public override void Update(float deltaTime)
{
if (isFinished || targetNotFound) { return; }
if (isFinished || cantFindTargets) { return; }
string[] criteriaSplit = Criteria.Split(';');
targetNotFound = false;
foreach (string entry in criteriaSplit)
if (!taggingDone)
{
string[] kvp = entry.Split(':');
Identifier key = kvp[0].Trim().ToIdentifier();
Identifier value = kvp.Length > 1 ? kvp[1].Trim().ToIdentifier() : Identifier.Empty;
if (Taggers.TryGetValue(key, out Action<Identifier> tagger))
cantFindTargets = false;
string[] criteriaSplit = Criteria.Split(';');
foreach (string entry in criteriaSplit)
{
tagger(value);
}
else
{
string errorMessage = $"Error in TagAction (event \"{ParentEvent.Prefab.Identifier}\") - unrecognized target criteria \"{key}\".";
DebugConsole.ThrowError(errorMessage);
GameAnalyticsManager.AddErrorEventOnce($"TagAction.Update:InvalidCriteria_{ParentEvent.Prefab.Identifier}_{key}", GameAnalyticsManager.ErrorSeverity.Error, errorMessage);
string[] kvp = entry.Split(':');
Identifier key = kvp[0].Trim().ToIdentifier();
Identifier value = kvp.Length > 1 ? kvp[1].Trim().ToIdentifier() : Identifier.Empty;
if (Taggers.TryGetValue(key, out Action<Identifier> tagger))
{
tagger(value);
}
else
{
string errorMessage = $"Error in TagAction (event \"{ParentEvent.Prefab.Identifier}\") - unrecognized target criteria \"{key}\".";
DebugConsole.ThrowError(errorMessage,
contentPackage: ParentEvent.Prefab?.ContentPackage);
GameAnalyticsManager.AddErrorEventOnce($"TagAction.Update:InvalidCriteria_{ParentEvent.Prefab.Identifier}_{key}", GameAnalyticsManager.ErrorSeverity.Error, errorMessage);
}
}
taggingDone = true;
}
if (ContinueIfNoTargetsFound)
@@ -288,7 +305,14 @@ namespace Barotrauma
}
else
{
isFinished = !targetNotFound;
if (mustRecheckTargets)
{
isFinished = ParentEvent.GetTargets(Tag).Any();
}
else
{
isFinished = !cantFindTargets;
}
}
}
@@ -36,7 +36,8 @@
var eventPrefab = EventSet.GetEventPrefab(Identifier);
if (eventPrefab == null)
{
DebugConsole.ThrowError($"Error in TriggerEventAction - could not find an event with the identifier {Identifier}.");
DebugConsole.ThrowError($"Error in TriggerEventAction - could not find an event with the identifier {Identifier}.",
contentPackage: ParentEvent.Prefab.ContentPackage);
}
else
{
@@ -14,7 +14,8 @@ partial class TutorialHighlightAction : EventAction
{
if (GameMain.NetworkMember != null)
{
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(TutorialHighlightAction)} is not supported in multiplayer.");
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(TutorialHighlightAction)} is not supported in multiplayer.",
contentPackage: element.ContentPackage);
}
}
@@ -28,7 +28,8 @@ namespace Barotrauma
{
if (ItemTag.IsEmpty && ItemIdentifier.IsEmpty)
{
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(WaitForItemFabricatedAction)} does't define either a tag or an identifier of the item to check.");
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(WaitForItemFabricatedAction)} does't define either a tag or an identifier of the item to check.",
contentPackage: element.ContentPackage);
}
foreach (var item in Item.ItemList)
{
@@ -518,7 +518,8 @@ namespace Barotrauma
{
foreach (Identifier missingId in subEventPrefab.GetMissingIdentifiers())
{
DebugConsole.ThrowError($"Error in event set \"{eventSet.Identifier}\" ({eventSet.ContentFile?.ContentPackage?.Name ?? "null"}) - could not find an event prefab with the identifier \"{missingId}\".");
DebugConsole.ThrowError($"Error in event set \"{eventSet.Identifier}\" ({eventSet.ContentFile?.ContentPackage?.Name ?? "null"}) - could not find an event prefab with the identifier \"{missingId}\".",
contentPackage: eventSet.ContentPackage);
}
}
@@ -10,16 +10,48 @@ namespace Barotrauma
public readonly ContentXElement ConfigElement;
public readonly Type EventType;
/// <summary>
/// The probability for the event to do something if it gets selected. For example, the probability for a MonsterEvent to spawn the monster(s).
/// </summary>
public readonly float Probability;
/// <summary>
/// When this event occurs, should it trigger the event cooldown during which no new events are triggered?
/// </summary>
public readonly bool TriggerEventCooldown;
/// <summary>
/// The commonness of the event (i.e. how likely it is for this specific event to be chosen from the event set it's configured in).
/// Only valid if the event set is configured to choose a random event (as opposed to just executing all the events in the set).
/// </summary>
public readonly float Commonness;
/// <summary>
/// If set, the event set can only be chosen in this biome.
/// </summary>
public readonly Identifier BiomeIdentifier;
/// <summary>
/// If set, the event set can only be chosen in locations that belong to this faction.
/// </summary>
public readonly Identifier Faction;
public readonly LocalizedString Name;
/// <summary>
/// If set, this event is used as an event that can unlock a path to the next biome.
/// </summary>
public readonly bool UnlockPathEvent;
/// <summary>
/// Only valid if UnlockPathEvent is set to true. The tooltip displayed on the pathway this event is blocking.
/// </summary>
public readonly string UnlockPathTooltip;
/// <summary>
/// Only valid if UnlockPathEvent is set to true. The reputation requirement displayed on the pathway this event is blocking.
/// </summary>
public readonly int UnlockPathReputation;
public static EventPrefab Create(ContentXElement element, RandomEventsFile file, Identifier fallbackIdentifier = default)
@@ -44,12 +76,14 @@ namespace Barotrauma
EventType = Type.GetType("Barotrauma." + ConfigElement.Name, true, true);
if (EventType == null)
{
DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\".");
DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\".",
contentPackage: element.ContentPackage);
}
}
catch
{
DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\".");
DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\".",
contentPackage: element.ContentPackage);
}
Name = TextManager.Get($"eventname.{Identifier}").Fallback(Identifier.ToString());
@@ -23,6 +23,10 @@ namespace Barotrauma
}
#endif
/// <summary>
/// Event sets are sets of random events that occur within a level (most commonly, monster spawns and scripted events).
/// Event sets can also be nested: a "parent set" can choose from several "subsets", either randomly or by some kind of criteria.
/// </summary>
sealed class EventSet : Prefab
{
internal class EventDebugStats
@@ -78,21 +82,48 @@ namespace Barotrauma
return GetAllEventPrefabs().Find(prefab => prefab.Identifier == identifier);
}
/// <summary>
/// If enabled, this set can only be chosen in the campaign mode.
/// </summary>
public readonly bool IsCampaignSet;
//0-100
public readonly float MinLevelDifficulty, MaxLevelDifficulty;
/// <summary>
/// The difficulty of the current level must be equal to or higher than this for this set to be chosen.
/// </summary>
public readonly float MinLevelDifficulty;
/// <summary>
/// The difficulty of the current level must be equal to or less than this for this set to be chosen.
/// </summary>
public readonly float MaxLevelDifficulty;
/// <summary>
/// If set, the event set can only be chosen in this biome.
/// </summary>
public readonly Identifier BiomeIdentifier;
/// <summary>
/// If set, the event set can only be chosen in this type of level (outpost level or a connection between outpost levels).
/// </summary>
public readonly LevelData.LevelType LevelType;
/// <summary>
/// If set, the event set can only be chosen in locations of this type.
/// </summary>
public readonly ImmutableArray<Identifier> LocationTypeIdentifiers;
/// <summary>
/// If set, the event set can only be chosen in locations that belong to this faction.
/// </summary>
public readonly Identifier Faction;
/// <summary>
/// If set, one event, or a sub event set, is chosen randomly from this set.
/// </summary>
public readonly bool ChooseRandom;
/// <summary>
/// Only valid if ChooseRandom is enabled. How many random events to choose from the set?
/// </summary>
private readonly int eventCount = 1;
public readonly int SubSetCount = 1;
private readonly Dictionary<Identifier, int> overrideEventCount = new Dictionary<Identifier, int>();
@@ -102,47 +133,100 @@ namespace Barotrauma
/// </summary>
public readonly bool Exhaustible;
/// <summary>
/// The event set won't become active until the submarine has travelled at least this far. A value between 0-1, where 0 is the beginning of the level and 1 the end of the level (e.g. 0.5 would mean the sub needs to be half-way through the level).
/// </summary>
public readonly float MinDistanceTraveled;
/// <summary>
/// The event set won't become active until the round has lasted at least this many seconds.
/// </summary>
public readonly float MinMissionTime;
//the events in this set are delayed if the current EventManager intensity is not between these values
public readonly float MinIntensity, MaxIntensity;
/// <summary>
/// If the event is not allowed at start, it won't become active until the submarine has moved at least 50 meters away from the beginning of the level. Only valid in LocationConnections (levels between locations).
/// </summary>
public readonly bool AllowAtStart;
/// <summary>
/// Normally an event (such as a monster spawn) triggers a cooldown during which no new events are created. This can be used to ignore the cooldown.
/// </summary>
public readonly bool IgnoreCoolDown;
/// <summary>
/// Should this event set trigger the event cooldown (during which no new events are created) when it becomes active?
/// </summary>
public readonly bool TriggerEventCooldown;
/// <summary>
/// Normally events can only trigger if the intensity of the situation is low enough (e.g. you won't get new monster spawns if the submarine is already facing a disaster). This can be used to ignore the intensity.
/// </summary>
public readonly bool IgnoreIntensity;
public readonly bool PerRuin, PerCave, PerWreck;
/// <summary>
/// The set is applied once per each ruin in the level. Can be used to ensure there's a consistent amount of monster spawns in the ruins in the level regardless of how many there are (and that no ruin monsters spawn if there are no ruins).
/// </summary>
public readonly bool PerRuin;
/// <summary>
/// The set is applied once per each cave in the level. Can be used to ensure there's a consistent amount of monster spawns in the cave in the level regardless of how many there are (and that no cave monsters spawn if there are no caves).
/// </summary>
public readonly bool PerCave;
/// <summary>
/// The set is applied once per each wreck in the level. Can be used to ensure there's a consistent amount of monster spawns in the wreck in the level regardless of how many there are (and that no wreck monsters spawn if there are no wreck).
/// </summary>
public readonly bool PerWreck;
/// <summary>
/// If enabled, this event will not be applied if the level contains hunting grounds.
/// </summary>
public readonly bool DisableInHuntingGrounds;
/// <summary>
/// If true, events from this set can only occur once in the level.
/// If enabled, events from this set can only occur once in the level.
/// </summary>
public readonly bool OncePerLevel;
/// <summary>
/// Should the event set be delayed if at least half of the crew is away from the submarine? The maximum amount of time the events can get delayed is defined in event manager settings (<see cref="EventManagerSettings.FreezeDurationWhenCrewAway"/>)
/// </summary>
public readonly bool DelayWhenCrewAway;
public readonly bool TriggerEventCooldown;
/// <summary>
/// Additive sets are important to be aware of when creating custom event sets! If an additive set gets chosen for a level, the game will also select a non-additive one.
/// This means you can for example configure an additive set that spawns custom monsters (and make it very common if you want the monsters to spawn frequently), which will spawn those custom
/// monsters in addition to the vanilla monsters spawned by vanilla sets, without you having to add your custom monsters to every single vanilla set.
/// </summary>
public readonly bool Additive;
/// <summary>
/// The commonness of the event set (i.e. how likely it is for this specific set to be chosen).
/// </summary>
public readonly float DefaultCommonness;
public readonly ImmutableDictionary<Identifier, float> OverrideCommonness;
/// <summary>
/// If set, the event set can trigger again after this amount of seconds has passed since it last triggered.
/// </summary>
public readonly float ResetTime;
/// <summary>
/// Used to force an event set based on how many other locations have been discovered before this. (Used for campaign tutorial event sets.)
/// Used to force an event set based on how many other locations have been discovered before this (used for campaign tutorial event sets).
/// </summary>
public readonly int ForceAtDiscoveredNr;
/// <summary>
/// Used to force an event set based on how many other outposts have been visited before this. (Used for campaign tutorial event sets.)
/// Used to force an event set based on how many other outposts have been visited before this (used for campaign tutorial event sets).
/// </summary>
public readonly int ForceAtVisitedNr;
/// <summary>
/// If enabled, this set can only occur when the campaign tutorial is enabled (generally used for the tutorial events).
/// </summary>
public readonly bool CampaignTutorialOnly;
public readonly struct SubEventPrefab
@@ -224,7 +308,8 @@ namespace Barotrauma
}
else
{
DebugConsole.AddWarning($"{file.Path}: All root EventSets should have an identifier");
DebugConsole.AddWarning($"{file.Path}: All root EventSets should have an identifier",
file.ContentPackage);
}
}
@@ -264,7 +349,8 @@ namespace Barotrauma
string levelTypeStr = element.GetAttributeString("leveltype", parentSet?.LevelType.ToString() ?? "LocationConnection");
if (!Enum.TryParse(levelTypeStr, true, out LevelType))
{
DebugConsole.ThrowError($"Error in event set \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.");
DebugConsole.ThrowError($"Error in event set \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.",
contentPackage: element.ContentPackage);
}
Faction = element.GetAttributeIdentifier(nameof(Faction), Identifier.Empty);
@@ -304,7 +390,8 @@ namespace Barotrauma
ForceAtVisitedNr = element.GetAttributeInt(nameof(ForceAtVisitedNr), -1);
if (ForceAtDiscoveredNr >= 0 && ForceAtVisitedNr >= 0)
{
DebugConsole.ThrowError($"Error with event set \"{Identifier}\" - both ForceAtDiscoveredNr and ForceAtVisitedNr are defined, this could lead to unexpected behavior");
DebugConsole.ThrowError($"Error with event set \"{Identifier}\" - both ForceAtDiscoveredNr and ForceAtVisitedNr are defined, this could lead to unexpected behavior",
contentPackage: element.ContentPackage);
}
DefaultCommonness = element.GetAttributeFloat("commonness", 1.0f);
@@ -123,7 +123,8 @@ namespace Barotrauma
var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag));
if (!itemsToDestroy.Any())
{
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".");
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".",
contentPackage: Prefab.ContentPackage);
}
else
{
@@ -135,10 +136,11 @@ namespace Barotrauma
{
foreach (XElement element in itemConfig.Elements())
{
string itemIdentifier = element.GetAttributeString("identifier", "");
if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab))
Identifier itemIdentifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
if (MapEntityPrefab.FindByIdentifier(itemIdentifier) is not ItemPrefab itemPrefab)
{
DebugConsole.ThrowError("Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier + "\" not found");
DebugConsole.ThrowError("Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier + "\" not found",
contentPackage: Prefab.ContentPackage);
continue;
}
@@ -189,7 +191,8 @@ namespace Barotrauma
HumanPrefab humanPrefab = GetHumanPrefabFromElement(element);
if (humanPrefab == null)
{
DebugConsole.ThrowError($"Couldn't spawn a human character for abandoned outpost mission: human prefab \"{element.GetAttributeString("identifier", string.Empty)}\" not found");
DebugConsole.ThrowError($"Couldn't spawn a human character for abandoned outpost mission: human prefab \"{element.GetAttributeString("identifier", string.Empty)}\" not found",
contentPackage: Prefab.ContentPackage);
continue;
}
for (int i = 0; i < count; i++)
@@ -203,7 +206,8 @@ namespace Barotrauma
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
if (characterPrefab == null)
{
DebugConsole.ThrowError($"Couldn't spawn a character for abandoned outpost mission: character prefab \"{speciesName}\" not found");
DebugConsole.ThrowError($"Couldn't spawn a character for abandoned outpost mission: character prefab \"{speciesName}\" not found",
contentPackage: Prefab.ContentPackage);
continue;
}
for (int i = 0; i < count; i++)
@@ -51,12 +51,14 @@ namespace Barotrauma
TargetRuin = Level.Loaded?.Ruins?.GetRandom(randSync: Rand.RandSync.ServerAndClient);
if (TargetRuin == null)
{
DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): level contains no alien ruins");
DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): level contains no alien ruins",
contentPackage: Prefab.ContentPackage);
return;
}
if (targetItemIdentifiers.Length < 1 && targetEnemyIdentifiers.Length < 1)
{
DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): no target identifiers set in the mission definition");
DebugConsole.ThrowError($"Failed to initialize an Alien Ruin mission (\"{Prefab.Identifier}\"): no target identifiers set in the mission definition",
contentPackage: Prefab.ContentPackage);
return;
}
foreach (var item in Item.ItemList)
@@ -88,12 +90,14 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): could not find a character prefab with the species \"{identifier}\"");
DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): could not find a character prefab with the species \"{identifier}\"",
contentPackage: Prefab.ContentPackage);
}
}
if (enemyPrefabs.None())
{
DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no enemy species defined that could be used to spawn more ({minEnemyCount - existingEnemyCount}) enemies");
DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no enemy species defined that could be used to spawn more ({minEnemyCount - existingEnemyCount}) enemies",
contentPackage: Prefab.ContentPackage);
return;
}
for (int i = 0; i < (minEnemyCount - existingEnemyCount); i++)
@@ -102,7 +106,8 @@ namespace Barotrauma
var spawnPos = TargetRuin.Submarine.GetWaypoints(false).GetRandomUnsynced(w => w.CurrentHull != null)?.WorldPosition;
if (!spawnPos.HasValue)
{
DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no valid spawn positions could be found for the additional ({minEnemyCount - existingEnemyCount}) enemies to be spawned");
DebugConsole.ThrowError($"Error in an Alien Ruin mission (\"{Prefab.Identifier}\"): no valid spawn positions could be found for the additional ({minEnemyCount - existingEnemyCount}) enemies to be spawned",
contentPackage: Prefab.ContentPackage);
return;
}
var newEnemy = Character.Create(prefab.Identifier, spawnPos.Value, ToolBox.RandomSeed(8), createNetworkEvent: false);
@@ -151,7 +156,8 @@ namespace Barotrauma
#if DEBUG
else
{
DebugConsole.ThrowError($"Error in Alien Ruin mission (\"{Prefab.Identifier}\"): unexpected target of type {target?.GetType()?.ToString()}");
DebugConsole.ThrowError($"Error in Alien Ruin mission (\"{Prefab.Identifier}\"): unexpected target of type {target?.GetType()?.ToString()}",
contentPackage: Prefab.ContentPackage);
}
#endif
}
@@ -46,6 +46,7 @@ namespace Barotrauma
}
sonarLabel = TextManager.Get("beaconstationsonarlabel");
DebugConsole.NewMessage("Initialized beacon mission: " + prefab.Identifier, Color.LightSkyBlue, debugOnly: true);
}
private void LoadMonsters(XElement monsterElement, MonsterSet set)
@@ -65,7 +66,8 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"Error in beacon mission \"{Prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
DebugConsole.ThrowError($"Error in beacon mission \"{Prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
contentPackage: Prefab.ContentPackage);
}
}
@@ -238,7 +238,8 @@ namespace Barotrauma
if (itemConfig == null)
{
DebugConsole.ThrowError("Failed to initialize items for cargo mission (itemConfig == null)");
DebugConsole.ThrowError("Failed to initialize items for cargo mission (itemConfig == null)",
contentPackage: Prefab.ContentPackage);
return;
}
@@ -262,7 +263,7 @@ namespace Barotrauma
SpawnedInCurrentOutpost = true,
AllowStealing = false
};
item.AddTag("cargomission");
item.AddTag(Tags.CargoMissionItem);
item.AddTag(Prefab.Identifier);
foreach (var tag in Prefab.Tags)
{
@@ -110,12 +110,14 @@ namespace Barotrauma
bossPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
if (bossPrefab == null)
{
DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
contentPackage: Prefab.ContentPackage);
}
}
else
{
DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Monster file not set.");
DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Monster file not set.",
contentPackage: Prefab.ContentPackage);
}
Identifier minionName = prefab.ConfigElement.GetAttributeIdentifier("minionfile", Identifier.Empty);
@@ -124,7 +126,8 @@ namespace Barotrauma
minionPrefab = CharacterPrefab.FindBySpeciesName(minionName);
if (minionPrefab == null)
{
DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
contentPackage: Prefab.ContentPackage);
}
}
@@ -137,7 +140,8 @@ namespace Barotrauma
projectilePrefab = MapEntityPrefab.FindByIdentifier(projectileId) as ItemPrefab;
if (projectilePrefab == null)
{
DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find an item prefab with the name \"{projectileId}\".");
DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find an item prefab with the name \"{projectileId}\".",
contentPackage: Prefab.ContentPackage);
}
}
@@ -152,7 +156,8 @@ namespace Barotrauma
bossSpawnPoint = WayPoint.WayPointList.FirstOrDefault(wp => wp.Tags.Contains(spawnPointTag));
if (bossSpawnPoint == null)
{
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find a spawn point \"{spawnPointTag}\".");
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find a spawn point \"{spawnPointTag}\".",
contentPackage: Prefab.ContentPackage);
return;
}
if (!IsClient)
@@ -171,14 +176,16 @@ namespace Barotrauma
}
if (destructibleItemTag.IsEmpty)
{
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Destructible item tag not set.");
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Destructible item tag not set.",
contentPackage: Prefab.ContentPackage);
return;
}
destructibleItems.Clear();
destructibleItems.AddRange(Item.ItemList.FindAll(it => it.HasTag(destructibleItemTag)));
if (destructibleItems.None())
{
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find any destructible items with the tag \"{spawnPointTag}\".");
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find any destructible items with the tag \"{spawnPointTag}\".",
contentPackage: Prefab.ContentPackage);
return;
}
}
@@ -77,7 +77,8 @@ namespace Barotrauma
{
if (inMission)
{
DebugConsole.ThrowError("MainSub was null when trying to retrieve submarine size for determining escorted character count!");
DebugConsole.ThrowError("MainSub was null when trying to retrieve submarine size for determining escorted character count!",
contentPackage: Prefab.ContentPackage);
}
return 1;
}
@@ -180,7 +181,8 @@ namespace Barotrauma
if (scalingCharacterCount * characterConfig.Elements().Count() != characters.Count)
{
DebugConsole.AddWarning("Character count did not match expected character count in InitCharacters of EscortMission");
DebugConsole.AddWarning("Character count did not match expected character count in InitCharacters of EscortMission",
Prefab.ContentPackage);
return;
}
int i = 0;
@@ -220,7 +222,8 @@ namespace Barotrauma
if (characterConfig == null)
{
DebugConsole.ThrowError("Failed to initialize characters for escort mission (characterConfig == null)");
DebugConsole.ThrowError("Failed to initialize characters for escort mission (characterConfig == null)",
contentPackage: Prefab.ContentPackage);
return;
}
@@ -258,7 +261,7 @@ namespace Barotrauma
{
character.Speak(TextManager.Get("dialogterroristannounce").Value, null, Rand.Range(0.5f, 3f));
}
XElement randomElement = itemConfig.Elements().GetRandomUnsynced(e => e.GetAttributeFloat(0f, "mindifficulty") <= Level.Loaded.Difficulty);
ContentXElement randomElement = itemConfig.Elements().GetRandomUnsynced(e => e.GetAttributeFloat(0f, "mindifficulty") <= Level.Loaded.Difficulty);
if (randomElement != null)
{
HumanPrefab.InitializeItem(character, randomElement, character.Submarine, humanPrefab: null, createNetworkEvents: true);
@@ -119,14 +119,16 @@ namespace Barotrauma
{
if (MapEntityPrefab.FindByIdentifier(identifier) is not ItemPrefab prefab)
{
DebugConsole.ThrowError($"Error in MineralMission: couldn't find an item prefab (identifier: \"{identifier}\")");
DebugConsole.ThrowError($"Error in MineralMission: couldn't find an item prefab (identifier: \"{identifier}\")",
contentPackage: Prefab.ContentPackage);
continue;
}
var spawnedResources = level.GenerateMissionResources(prefab, amount, positionType, caves);
if (spawnedResources.Count < amount)
{
DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{amount} of {prefab.Name}");
DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{amount} of {prefab.Name}",
contentPackage: Prefab.ContentPackage);
}
if (spawnedResources.None()) { continue; }
@@ -347,7 +347,8 @@ namespace Barotrauma
var eventPrefab = EventSet.GetAllEventPrefabs().Find(p => p.Identifier == trigger.EventIdentifier);
if (eventPrefab == null)
{
DebugConsole.ThrowError($"Mission \"{Name}\" failed to trigger an event (couldn't find an event with the identifier \"{trigger.EventIdentifier}\").");
DebugConsole.ThrowError($"Mission \"{Name}\" failed to trigger an event (couldn't find an event with the identifier \"{trigger.EventIdentifier}\").",
contentPackage: Prefab.ContentPackage);
return;
}
if (GameMain.GameSession?.EventManager != null)
@@ -378,7 +379,7 @@ namespace Barotrauma
catch (Exception e)
{
string errorMsg = "Unknown error while giving mission rewards.";
DebugConsole.ThrowError(errorMsg, e);
DebugConsole.ThrowError(errorMsg, e, contentPackage: Prefab.ContentPackage);
GameAnalyticsManager.AddErrorEventOnce("Mission.End:GiveReward", GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + e.StackTrace);
#if SERVER
GameMain.Server?.SendChatMessage(errorMsg + "\n" + e.StackTrace, Networking.ChatMessageType.Error);
@@ -547,7 +548,8 @@ namespace Barotrauma
{
if (element.Attribute("name") != null)
{
DebugConsole.ThrowError("Error in mission \"" + Name + "\" - use character identifiers instead of names to configure the characters.");
DebugConsole.ThrowError($"Error in mission \"{Name}\" - use character identifiers instead of names to configure the characters.",
contentPackage: Prefab.ContentPackage);
return null;
}
@@ -556,7 +558,8 @@ namespace Barotrauma
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
if (humanPrefab == null)
{
DebugConsole.ThrowError($"Couldn't spawn character for mission: character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\".");
DebugConsole.ThrowError($"Couldn't spawn character for mission: character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\".",
contentPackage: Prefab.ContentPackage);
return null;
}
@@ -587,12 +590,14 @@ namespace Barotrauma
ItemPrefab itemPrefab;
if (element.Attribute("name") != null)
{
DebugConsole.ThrowError($"Error in mission \"{Name}\" - use item identifiers instead of names to configure the items");
DebugConsole.ThrowError($"Error in mission \"{Name}\" - use item identifiers instead of names to configure the items",
contentPackage: Prefab.ContentPackage);
string itemName = element.GetAttributeString("name", "");
itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
if (itemPrefab == null)
{
DebugConsole.ThrowError($"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemName}\" not found");
DebugConsole.ThrowError($"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemName}\" not found",
contentPackage: Prefab.ContentPackage);
}
}
else
@@ -601,7 +606,8 @@ namespace Barotrauma
itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
if (itemPrefab == null)
{
DebugConsole.ThrowError($"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemIdentifier}\" not found");
DebugConsole.ThrowError($"Couldn't spawn item for mission \"{Name}\": item prefab \"{itemIdentifier}\" not found",
contentPackage: Prefab.ContentPackage);
}
}
return itemPrefab;
@@ -614,14 +620,16 @@ namespace Barotrauma
WayPoint cargoSpawnPos = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub, useSyncedRand: true);
if (cargoSpawnPos == null)
{
DebugConsole.ThrowError($"Couldn't spawn items for mission \"{Name}\": no waypoints marked as Cargo were found");
DebugConsole.ThrowError($"Couldn't spawn items for mission \"{Name}\": no waypoints marked as Cargo were found",
contentPackage: Prefab.ContentPackage);
return null;
}
var cargoRoom = cargoSpawnPos.CurrentHull;
if (cargoRoom == null)
{
DebugConsole.ThrowError($"Couldn't spawn items for mission \"{Name}\": waypoints marked as Cargo must be placed inside a room");
DebugConsole.ThrowError($"Couldn't spawn items for mission \"{Name}\": waypoints marked as Cargo must be placed inside a room",
contentPackage: Prefab.ContentPackage);
return null;
}
@@ -94,8 +94,19 @@ namespace Barotrauma
DataRewards = new List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)>();
public readonly int Commonness;
/// <summary>
/// Displayed difficulty (indicator)
/// </summary>
public readonly int? Difficulty;
public const int MinDifficulty = 1, MaxDifficulty = 4;
/// <summary>
/// The actual minimum difficulty of the level allowed for this mission to trigger.
/// </summary>
public readonly int MinLevelDifficulty = 0;
/// <summary>
/// The actual maximum difficulty of the level allowed for this mission to trigger.
/// </summary>
public readonly int MaxLevelDifficulty = 100;
public readonly int Reward;
@@ -211,6 +222,10 @@ namespace Barotrauma
int difficulty = element.GetAttributeInt("difficulty", MinDifficulty);
Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty);
}
MinLevelDifficulty = element.GetAttributeInt(nameof(MinLevelDifficulty), MinLevelDifficulty);
MaxLevelDifficulty = element.GetAttributeInt(nameof(MaxLevelDifficulty), MaxLevelDifficulty);
MinLevelDifficulty = Math.Clamp(MinLevelDifficulty, 0, Math.Min(MaxLevelDifficulty, 100));
MaxLevelDifficulty = Math.Clamp(MaxLevelDifficulty, Math.Max(MinLevelDifficulty, 0), 100);
ShowProgressBar = element.GetAttributeBool(nameof(ShowProgressBar), false);
ShowProgressInNumbers = element.GetAttributeBool(nameof(ShowProgressInNumbers), false);
@@ -372,7 +387,8 @@ namespace Barotrauma
}
if (constructor == null)
{
DebugConsole.ThrowError($"Failed to find a constructor for the mission type \"{Type}\"!");
DebugConsole.ThrowError($"Failed to find a constructor for the mission type \"{Type}\"!",
contentPackage: element.ContentPackage);
}
InitProjSpecific(element);
@@ -48,7 +48,8 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
contentPackage: prefab.ContentPackage);
}
}
@@ -78,7 +79,8 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
contentPackage: prefab.ContentPackage);
}
}
@@ -118,19 +120,19 @@ namespace Barotrauma
float minDistBetweenMonsterMissions = 10000;
float mindDistFromSub = Level.Loaded.Size.X * 0.3f;
var monsterMissions = GameMain.GameSession.Missions.Select(e => e as MonsterMission).Where(m => m != null && m != this && m.spawnPos.HasValue);
if (!Level.Loaded.TryGetInterestingPosition(useSyncedRand: true, spawnPosType, mindDistFromSub, out Vector2 spawnPos,
if (!Level.Loaded.TryGetInterestingPosition(useSyncedRand: true, spawnPosType, mindDistFromSub, out Level.InterestingPosition spawnPos,
filter: p => monsterMissions.None(m => Vector2.DistanceSquared(p.Position.ToVector2(), m.spawnPos.Value) < minDistBetweenMonsterMissions * minDistBetweenMonsterMissions),
suppressWarning: true))
{
Level.Loaded.TryGetInterestingPosition(useSyncedRand: true, spawnPosType, mindDistFromSub, out spawnPos);
}
this.spawnPos = spawnPos;
this.spawnPos = spawnPos.Position.ToVector2();
foreach (var (character, amountRange) in monsterPrefabs)
{
int amount = Rand.Range(amountRange.X, amountRange.Y + 1);
for (int i = 0; i < amount; i++)
{
monsters.Add(Character.Create(character.Identifier, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false));
monsters.Add(Character.Create(character.Identifier, this.spawnPos.Value, ToolBox.RandomSeed(8), createNetworkEvent: false));
}
}
InitializeMonsters(monsters);
@@ -85,7 +85,8 @@ namespace Barotrauma
}
else
{
DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".",
contentPackage: Prefab.ContentPackage);
}
}
@@ -174,7 +175,8 @@ namespace Barotrauma
var itemIdentifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
if (MapEntityPrefab.FindByIdentifier(itemIdentifier) is not ItemPrefab itemPrefab)
{
DebugConsole.ThrowError("Couldn't spawn item for nest mission: item prefab \"" + itemIdentifier + "\" not found");
DebugConsole.ThrowError("Couldn't spawn item for nest mission: item prefab \"" + itemIdentifier + "\" not found",
contentPackage: Prefab.ContentPackage);
continue;
}
@@ -285,7 +287,8 @@ namespace Barotrauma
}
if (Level.Loaded.IsPositionInsideWall(nestPosition))
{
DebugConsole.AddWarning($"Error in nest mission \"{Prefab.Identifier}\": nest position was inside a wall ({nestPosition}).");
DebugConsole.AddWarning($"Error in nest mission \"{Prefab.Identifier}\": nest position was inside a wall ({nestPosition}).",
Prefab.ContentPackage);
}
monsterPrefabs.Clear();
break;
@@ -11,9 +11,9 @@ namespace Barotrauma
{
partial class PirateMission : Mission
{
private readonly XElement submarineTypeConfig;
private readonly XElement characterConfig;
private readonly XElement characterTypeConfig;
private readonly ContentXElement submarineTypeConfig;
private readonly ContentXElement characterConfig;
private readonly ContentXElement characterTypeConfig;
private readonly float addedMissionDifficultyPerPlayer;
private float missionDifficulty;
@@ -103,7 +103,8 @@ namespace Barotrauma
var characterTypeElement = characterTypeConfig.Elements().FirstOrDefault(e => e.GetAttributeString("typeidentifier", string.Empty) == characterId);
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 \"{characterId}\".",
contentPackage: Prefab.ContentPackage);
}
}
//make sure all defined character types can be found from human prefabs
@@ -116,7 +117,8 @@ namespace Barotrauma
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
if (humanPrefab == null)
{
DebugConsole.ThrowError($"Error in mission \"{prefab.Identifier}\". Character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\".");
DebugConsole.ThrowError($"Error in mission \"{prefab.Identifier}\". Character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\".",
contentPackage: Prefab.ContentPackage);
}
}
}
@@ -151,7 +153,8 @@ namespace Barotrauma
ContentPath submarinePath = submarineConfig.GetAttributeContentPath("path", Prefab.ContentPackage);
if (submarinePath.IsNullOrEmpty())
{
DebugConsole.ThrowError($"No path used for submarine for the pirate mission \"{Prefab.Identifier}\"!");
DebugConsole.ThrowError($"No path used for submarine for the pirate mission \"{Prefab.Identifier}\"!",
contentPackage: Prefab.ContentPackage);
return;
}
@@ -165,7 +168,8 @@ namespace Barotrauma
if (contentFile == null)
{
DebugConsole.ThrowError($"No submarine file found from the path {submarinePath}!");
DebugConsole.ThrowError($"No submarine file found from the path {submarinePath}!",
contentPackage: Prefab.ContentPackage);
return;
}
@@ -201,16 +205,28 @@ namespace Barotrauma
private void CreateMissionPositions(out Vector2 preferredSpawnPos)
{
Vector2 patrolPos = enemySub.WorldPosition;
Vector2 patrolPos = Level.Loaded.EndPosition;
Point subSize = enemySub.GetDockedBorders().Size;
if (!Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out preferredSpawnPos))
preferredSpawnPos = Level.Loaded.EndPosition;
if (Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out var potentialSpawnPos))
{
DebugConsole.ThrowError("Could not spawn pirate submarine in an interesting location! " + this);
preferredSpawnPos = potentialSpawnPos.Position.ToVector2();
}
if (!Level.Loaded.TryGetInterestingPositionAwayFromPoint(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out patrolPos, preferredSpawnPos, minDistFromPoint: 10000f))
else
{
DebugConsole.ThrowError("Could not give pirate submarine an interesting location to patrol to! " + this);
DebugConsole.ThrowError("Could not spawn pirate submarine in an interesting location! " + this,
contentPackage: Prefab.ContentPackage);
}
if (Level.Loaded.TryGetInterestingPositionAwayFromPoint(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out var potentialPatrolPos, preferredSpawnPos, minDistFromPoint: 10000f))
{
patrolPos = potentialPatrolPos.Position.ToVector2();
}
else
{
DebugConsole.ThrowError("Could not give pirate submarine an interesting location to patrol to! " + this,
contentPackage: Prefab.ContentPackage);
}
patrolPos = enemySub.FindSpawnPos(patrolPos, subSize);
@@ -266,7 +282,8 @@ namespace Barotrauma
if (characterConfig == null)
{
DebugConsole.ThrowError("Failed to initialize characters for escort mission (characterConfig == null)");
DebugConsole.ThrowError("Failed to initialize characters for escort mission (characterConfig == null)",
contentPackage: Prefab.ContentPackage);
return;
}
@@ -281,7 +298,7 @@ namespace Barotrauma
Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
bool commanderAssigned = false;
foreach (XElement element in characterConfig.Elements())
foreach (ContentXElement element in characterConfig.Elements())
{
// 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
@@ -293,7 +310,8 @@ namespace Barotrauma
if (characterType == null)
{
DebugConsole.ThrowError($"No character types defined in CharacterTypes for a declared type identifier in mission \"{Prefab.Identifier}\".");
DebugConsole.ThrowError($"No character types defined in CharacterTypes for a declared type identifier in mission \"{Prefab.Identifier}\".",
contentPackage: element.ContentPackage);
return;
}
@@ -356,12 +374,12 @@ namespace Barotrauma
{
DebugConsole.ThrowError(submarineInfo == null ?
$"Error in PirateMission: enemy sub was not created (submarineInfo == null)." :
$"Error in PirateMission: enemy sub was not created.");
$"Error in PirateMission: enemy sub was not created.",
contentPackage: Prefab.ContentPackage);
return;
}
Vector2 spawnPos = Level.Loaded.EndPosition; // in case TryGetInterestingPosition fails, though this should not happen
CreateMissionPositions(out spawnPos); // patrol positions are not explicitly replicated, instead they are acquired the same way the server acquires them
CreateMissionPositions(out Vector2 spawnPos); // patrol positions are not explicitly replicated, instead they are acquired the same way the server acquires them
#if DEBUG
if (IsClient)
{
@@ -106,12 +106,14 @@ namespace Barotrauma
if (element.GetAttribute("itemname") != null)
{
DebugConsole.ThrowError("Error in SalvageMission - use item identifier instead of the name of the item.");
DebugConsole.ThrowError("Error in SalvageMission - use item identifier instead of the name of the item.",
contentPackage: element.ContentPackage);
string itemName = element.GetAttributeString("itemname", "");
ItemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
if (ItemPrefab == null && ExistingItemTag.IsEmpty)
{
DebugConsole.ThrowError($"Error in SalvageMission: couldn't find an item prefab with the name \"{itemName}\"");
DebugConsole.ThrowError($"Error in SalvageMission: couldn't find an item prefab with the name \"{itemName}\"",
contentPackage: element.ContentPackage);
}
}
else
@@ -128,7 +130,8 @@ namespace Barotrauma
}
if (ItemPrefab == null && ExistingItemTag.IsEmpty)
{
DebugConsole.ThrowError($"Error in SalvageMission - couldn't find an item prefab with the identifier \"{itemIdentifier}\"");
DebugConsole.ThrowError($"Error in SalvageMission - couldn't find an item prefab with the identifier \"{itemIdentifier}\"",
contentPackage: element.ContentPackage);
}
}
@@ -286,7 +289,8 @@ namespace Barotrauma
{
if (target.ItemPrefab == null && target.ContainerTag.IsEmpty)
{
DebugConsole.ThrowError($"Failed to find a target item for the mission \"{Prefab.Identifier}\". Item tag: {target.ExistingItemTag}");
DebugConsole.ThrowError($"Failed to find a target item for the mission \"{Prefab.Identifier}\". Item tag: {target.ExistingItemTag}",
contentPackage: Prefab.ContentPackage);
continue;
}
target.Item = new Item(target.ItemPrefab, position, null);
@@ -379,7 +383,8 @@ namespace Barotrauma
if (target.Item == null)
{
#if DEBUG
DebugConsole.ThrowError("Error in salvage mission " + Prefab.Identifier + " (item was null)");
DebugConsole.ThrowError("Error in salvage mission " + Prefab.Identifier + " (item was null)",
contentPackage: Prefab.ContentPackage);
#endif
return;
}
@@ -64,7 +64,8 @@ namespace Barotrauma
if (itemConfig == null)
{
DebugConsole.ThrowError("Failed to initialize a Scan mission: item config is not set");
DebugConsole.ThrowError("Failed to initialize a Scan mission: item config is not set",
contentPackage: Prefab.ContentPackage);
return;
}
@@ -77,7 +78,8 @@ namespace Barotrauma
TargetRuin = Level.Loaded?.Ruins?.GetRandom(randSync: Rand.RandSync.ServerAndClient);
if (TargetRuin == null)
{
DebugConsole.ThrowError("Failed to initialize a Scan mission: level contains no alien ruins");
DebugConsole.ThrowError("Failed to initialize a Scan mission: level contains no alien ruins",
contentPackage: Prefab.ContentPackage);
return;
}
@@ -85,7 +87,8 @@ namespace Barotrauma
ruinWaypoints.RemoveAll(wp => wp.CurrentHull == null);
if (ruinWaypoints.Count < targetsToScan)
{
DebugConsole.ThrowError($"Failed to initialize a Scan mission: target ruin has less waypoints than required as scan targets ({ruinWaypoints.Count} < {targetsToScan})");
DebugConsole.ThrowError($"Failed to initialize a Scan mission: target ruin has less waypoints than required as scan targets ({ruinWaypoints.Count} < {targetsToScan})",
contentPackage: Prefab.ContentPackage);
return;
}
var availableWaypoints = new List<WayPoint>();
@@ -107,7 +110,8 @@ namespace Barotrauma
if (availableWaypoints.None())
{
#if DEBUG
DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets available on try #{tries + 1} to reach the required scan target count (current targets: {scanTargets.Count}, required targets: {targetsToScan})");
DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets available on try #{tries + 1} to reach the required scan target count (current targets: {scanTargets.Count}, required targets: {targetsToScan})",
contentPackage: Prefab.ContentPackage);
#endif
break;
}
@@ -131,7 +135,8 @@ namespace Barotrauma
}
if (scanTargets.Count < targetsToScan)
{
DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets (current targets: {scanTargets.Count}, required targets: {targetsToScan})");
DebugConsole.ThrowError($"Error initializing a Scan mission: not enough targets (current targets: {scanTargets.Count}, required targets: {targetsToScan})",
contentPackage: Prefab.ContentPackage);
}
}
@@ -10,14 +10,45 @@ namespace Barotrauma
{
class MonsterEvent : Event
{
/// <summary>
/// The name of the species to spawn
/// </summary>
public readonly Identifier SpeciesName;
public readonly int MinAmount, MaxAmount;
/// <summary>
/// Minimum amount of monsters to spawn. You can also use "Amount" if you want to spawn a fixed number of monsters.
/// </summary>
public readonly int MinAmount;
/// <summary>
/// Maximum amount of monsters to spawn. You can also use "Amount" if you want to spawn a fixed number of monsters.
/// </summary>
public readonly int MaxAmount;
private readonly List<Character> monsters = new List<Character>();
/// <summary>
/// The monsters are spawned at least this distance away from the players and submarines.
/// </summary>
public readonly float SpawnDistance;
/// <summary>
/// Amount of random variance in the spawn position, in pixels. Can be used to prevent all the monsters from spawning at the exact same position.
/// </summary>
private readonly float scatter;
/// <summary>
/// Used for offsetting the spawns towards the end position of the level, so that they spawn farther afront the sub than normally. In pixels.
/// </summary>
private readonly float offset;
/// <summary>
/// Delay between spawning the monsters. Only relevant if the event spawns more than one monster.
/// </summary>
private readonly float delayBetweenSpawns;
/// <summary>
/// Number seconds before the event resets after all the monsters are dead. Can be used to make the event spawn monsters multiple times.
/// </summary>
private float resetTime;
private float resetTimer;
@@ -25,11 +56,22 @@ namespace Barotrauma
private bool disallowed;
/// <summary>
/// Where should the monster spawn?
/// </summary>
public readonly Level.PositionType SpawnPosType;
/// <summary>
/// If set, the monsters will spawn at a spawnpoint that has this tag. Only relevant for events that spawn monsters in a submarine, beacon station, wreck, outpost or ruin.
/// </summary>
private readonly string spawnPointTag;
private bool spawnPending, spawnReady;
/// <summary>
/// Maximum number of the specific type of monster in the entire level. Can be used to prevent the event from spawning more monsters if there's
/// already enough of that type of monster, e.g. spawned by another event or by a mission.
/// </summary>
public readonly int MaxAmountPerLevel = int.MaxValue;
public IReadOnlyList<Character> Monsters => monsters;
@@ -82,13 +124,7 @@ namespace Barotrauma
MaxAmountPerLevel = prefab.ConfigElement.GetAttributeInt("maxamountperlevel", int.MaxValue);
var spawnPosTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
if (string.IsNullOrWhiteSpace(spawnPosTypeStr) ||
!Enum.TryParse(spawnPosTypeStr, true, out SpawnPosType))
{
SpawnPosType = Level.PositionType.MainPath;
}
SpawnPosType = prefab.ConfigElement.GetAttributeEnum("spawntype", Level.PositionType.MainPath);
//backwards compatibility
if (prefab.ConfigElement.GetAttributeBool("spawndeep", false))
{
@@ -127,7 +163,8 @@ namespace Barotrauma
var file = CharacterPrefab.FindBySpeciesName(SpeciesName)?.ContentFile;
if (file == null)
{
DebugConsole.ThrowError($"Failed to find config file for species \"{SpeciesName}\"");
DebugConsole.ThrowError($"Failed to find config file for species \"{SpeciesName}\".",
contentPackage: Prefab.ContentPackage);
yield break;
}
else
@@ -159,7 +196,8 @@ namespace Barotrauma
Character createdCharacter = Character.Create(SpeciesName, Vector2.Zero, seed, characterInfo: null, isRemotePlayer: false, hasAi: true, createNetworkEvent: true, throwErrorIfNotFound: false);
if (createdCharacter == null)
{
DebugConsole.AddWarning($"Error in MonsterEvent: failed to spawn the character \"{SpeciesName}\". Content package: \"{prefab.ConfigElement?.ContentPackage?.Name ?? "unknown"}\".");
DebugConsole.AddWarning($"Error in MonsterEvent: failed to spawn the character \"{SpeciesName}\". Content package: \"{prefab.ConfigElement?.ContentPackage?.Name ?? "unknown"}\".",
Prefab.ContentPackage);
disallowed = true;
continue;
}
@@ -355,29 +393,28 @@ namespace Barotrauma
{
if (offset > 0)
{
Vector2 dir;
var waypoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == null && wp.Ruin == null);
var nearestWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, spawnPos.Value)).FirstOrDefault();
if (nearestWaypoint != null)
var tunnelType = chosenPosition.PositionType == Level.PositionType.MainPath ? Level.TunnelType.MainPath : Level.TunnelType.SidePath;
var waypoints = WayPoint.WayPointList.FindAll(wp =>
wp.Submarine == null &&
wp.Ruin == null &&
wp.Tunnel?.Type == tunnelType &&
wp.WorldPosition.X > spawnPos.Value.X);
if (waypoints.None())
{
int currentIndex = waypoints.IndexOf(nearestWaypoint);
var nextWaypoint = waypoints[Math.Min(currentIndex + 20, waypoints.Count - 1)];
dir = Vector2.Normalize(nextWaypoint.WorldPosition - nearestWaypoint.WorldPosition);
// Ensure that the spawn position is not offset to the left.
if (dir.X < 0)
{
dir.X = 0;
}
DebugConsole.AddWarning($"Failed to find a spawn position offset from {spawnPos.Value}.",
Prefab.ContentPackage);
}
else
{
dir = new Vector2(1, Rand.Range(-1f, 1f));
}
Vector2 targetPos = spawnPos.Value + dir * offset;
var targetWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, targetPos)).FirstOrDefault();
if (targetWaypoint != null)
{
spawnPos = targetWaypoint.WorldPosition;
float offsetSqr = offset * offset;
//find the waypoint whose distance from the spawnPos is closest to the desired offset
var targetWaypoint = waypoints.OrderBy(wp =>
Math.Abs(Vector2.DistanceSquared(wp.WorldPosition, spawnPos.Value) - offsetSqr)).FirstOrDefault();
if (targetWaypoint != null)
{
spawnPos = targetWaypoint.WorldPosition;
}
}
}
// Ensure that the position is not inside a submarine (in practice wrecks).
@@ -50,7 +50,8 @@ namespace Barotrauma
}
if (elementId == "statuseffect")
{
DebugConsole.ThrowError($"Error in event prefab \"{prefab.Identifier}\". Status effect configured as an action. Please configure status effects as child elements of a StatusEffectAction.");
DebugConsole.ThrowError($"Error in event prefab \"{prefab.Identifier}\". Status effect configured as an action. Please configure status effects as child elements of a StatusEffectAction.",
contentPackage: prefab.ContentPackage);
continue;
}
var action = EventAction.Instantiate(this, element);
@@ -59,7 +60,8 @@ namespace Barotrauma
if (!Actions.Any())
{
DebugConsole.ThrowError($"Scripted event \"{prefab.Identifier}\" has no actions. The event will do nothing.");
DebugConsole.ThrowError($"Scripted event \"{prefab.Identifier}\" has no actions. The event will do nothing.",
contentPackage: prefab.ContentPackage);
}
requiredDestinationTypes = prefab.ConfigElement.GetAttributeStringArray("requireddestinationtypes", null);
@@ -69,8 +71,9 @@ namespace Barotrauma
foreach (var gotoAction in allActions.OfType<GoTo>())
{
if (allActions.None(a => a is Label label && label.Name == gotoAction.Name))
{
DebugConsole.ThrowError($"Error in event \"{prefab.Identifier}\". Could not find a label matching the GoTo \"{gotoAction.Name}\".");
{
DebugConsole.ThrowError($"Error in event \"{prefab.Identifier}\". Could not find a label matching the GoTo \"{gotoAction.Name}\".",
contentPackage: prefab.ContentPackage);
}
}
@@ -108,7 +111,8 @@ namespace Barotrauma
if (target is Character character) { return character.Name; }
if (target is Hull hull) { return hull.DisplayName.Value; }
if (target is Submarine sub) { return sub.Info.DisplayName.Value; }
DebugConsole.AddWarning($"Failed to get the name of the event target {target} as a replacement for the tag {tag} in an event text.");
DebugConsole.AddWarning($"Failed to get the name of the event target {target} as a replacement for the tag {tag} in an event text.",
prefab.ContentPackage);
return target.ToString();
}
else
@@ -349,7 +353,8 @@ namespace Barotrauma
}
if (CurrentActionIndex == -1)
{
DebugConsole.AddWarning($"Could not find the GoTo label \"{goTo}\" in the event \"{Prefab.Identifier}\". Ending the event.");
DebugConsole.AddWarning($"Could not find the GoTo label \"{goTo}\" in the event \"{Prefab.Identifier}\". Ending the event.",
prefab.ContentPackage);
}
}