v1.4.4.1 (Blood in the Water Update)

This commit is contained in:
Regalis11
2024-04-24 18:09:05 +03:00
parent 89b91d1c3e
commit ff1b8951a7
397 changed files with 15250 additions and 6479 deletions
@@ -29,8 +29,8 @@ namespace Barotrauma
return $"ArtifactEvent ({(itemPrefab == null ? "null" : itemPrefab.Name)})";
}
public ArtifactEvent(EventPrefab prefab)
: base(prefab)
public ArtifactEvent(EventPrefab prefab, int seed)
: base(prefab, seed)
{
if (prefab.ConfigElement.GetAttribute("itemname") != null)
{
@@ -55,9 +55,8 @@ namespace Barotrauma
}
}
public override void Init(EventSet parentSet)
protected override void InitEventSpecific(EventSet parentSet)
{
base.Init(parentSet);
spawnPos = Level.Loaded.GetRandomItemPos(
(Rand.Value(Rand.RandSync.ServerAndClient) < 0.5f) ?
Level.PositionType.MainPath | Level.PositionType.SidePath :
@@ -9,7 +9,7 @@ namespace Barotrauma
public event Action Finished;
protected bool isFinished;
public int RandomSeed;
public readonly int RandomSeed;
protected readonly EventPrefab prefab;
@@ -17,6 +17,8 @@ namespace Barotrauma
public EventSet ParentSet { get; private set; }
public bool Initialized { get; private set; }
public Func<Level.InterestingPosition, bool> SpawnPosFilter;
public bool IsFinished
@@ -37,8 +39,9 @@ namespace Barotrauma
}
}
public Event(EventPrefab prefab)
public Event(EventPrefab prefab, int seed)
{
RandomSeed = seed;
this.prefab = prefab ?? throw new ArgumentNullException(nameof(prefab));
}
@@ -47,9 +50,15 @@ namespace Barotrauma
yield break;
}
public virtual void Init(EventSet parentSet = null)
public void Init(EventSet parentSet = null)
{
Initialized = true;
ParentSet = parentSet;
InitEventSpecific(parentSet);
}
protected virtual void InitEventSpecific(EventSet parentSet = null)
{
}
public virtual string GetDebugInfo()
@@ -1,22 +1,35 @@
using System.Linq;
using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Gives an affliction to a specific character.
/// </summary>
class AfflictionAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the affliction.")]
public Identifier Affliction { get; set; }
[Serialize(0.0f, IsPropertySaveable.Yes)]
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Strength of the affliction.")]
public float Strength { get; set; }
[Serialize(LimbType.None, IsPropertySaveable.Yes)]
[Serialize(LimbType.None, IsPropertySaveable.Yes, description: "Type of the limb(s) to apply the affliction on. Only valid if the affliction is limb-specific.")]
public LimbType LimbType { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character to apply the affliction on.")]
public Identifier TargetTag { get; set; }
public AfflictionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
[Serialize(false, IsPropertySaveable.Yes, description: "Should the strength be multiplied by the maximum vitality of the target?")]
public bool MultiplyByMaxVitality { get; set; }
public AfflictionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
{
if (Affliction.IsEmpty)
{
DebugConsole.ThrowError($"Error in {nameof(AfflictionAction)}: affliction not defined (use the attribute \"{nameof(Affliction)}\").",
contentPackage: element.ContentPackage);
}
}
private bool isFinished = false;
@@ -40,27 +53,32 @@ namespace Barotrauma
{
if (target != null && target is Character character)
{
float strength = Strength;
if (MultiplyByMaxVitality)
{
strength *= character.MaxVitality;
}
if (LimbType != LimbType.None)
{
var limb = character.AnimController.GetLimb(LimbType);
if (Strength > 0.0f)
if (strength > 0.0f)
{
character.CharacterHealth.ApplyAffliction(limb, afflictionPrefab.Instantiate(Strength), ignoreUnkillability: true);
character.CharacterHealth.ApplyAffliction(limb, afflictionPrefab.Instantiate(strength), ignoreUnkillability: true);
}
else if (Strength < 0.0f)
else if (strength < 0.0f)
{
character.CharacterHealth.ReduceAfflictionOnLimb(limb, Affliction, -Strength);
character.CharacterHealth.ReduceAfflictionOnLimb(limb, Affliction, -strength);
}
}
else
{
if (Strength > 0.0f)
if (strength > 0.0f)
{
character.CharacterHealth.ApplyAffliction(null, afflictionPrefab.Instantiate(Strength), ignoreUnkillability: true);
character.CharacterHealth.ApplyAffliction(null, afflictionPrefab.Instantiate(strength), ignoreUnkillability: true);
}
else if (Strength < 0.0f)
else if (strength < 0.0f)
{
character.CharacterHealth.ReduceAfflictionOnAllLimbs(Affliction, -Strength);
character.CharacterHealth.ReduceAfflictionOnAllLimbs(Affliction, -strength);
}
}
}
@@ -4,24 +4,27 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Check whether a target has a specific affliction.
/// </summary>
internal class CheckAfflictionAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the affliction.")]
public Identifier Identifier { get; set; } = Identifier.Empty;
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character to check.")]
public Identifier TargetTag { get; set; } = Identifier.Empty;
[Serialize("", IsPropertySaveable.Yes, description: "Tag referring to the character who caused the affliction.")]
[Serialize("", IsPropertySaveable.Yes, description: "Tag referring to the character who caused the affliction. Can be used to require the affliction to be caused by a specific character.")]
public Identifier SourceCharacter { get; set; } = Identifier.Empty;
[Serialize(LimbType.None, IsPropertySaveable.Yes, "Only check afflictions on the specified limb type")]
[Serialize(LimbType.None, IsPropertySaveable.Yes, "Only check afflictions on the specified limb type.")]
public LimbType TargetLimb { get; set; }
[Serialize(true, IsPropertySaveable.Yes, "When set to false when TargetLimb is not specified prevent checking limb-specific afflictions")]
[Serialize(true, IsPropertySaveable.Yes, "When set to false, limb-specific afflictions are ignored when not checking a specific limb.")]
public bool AllowLimbAfflictions { get; set; }
[Serialize(0.0f, IsPropertySaveable.Yes, "Minimum strength of the affliction")]
[Serialize(0.0f, IsPropertySaveable.Yes, "Minimum strength of the affliction.")]
public float MinStrength { get; set; }
public CheckAfflictionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -6,20 +6,24 @@ using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Checks whether an arbitrary condition is met. The conditionals work the same way as they do in StatusEffects.
/// </summary>
class CheckConditionalAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the target to check.")]
public Identifier TargetTag { get; set; }
[Serialize(PropertyConditional.LogicalOperatorType.Or, IsPropertySaveable.Yes)]
[Serialize(PropertyConditional.LogicalOperatorType.Or, IsPropertySaveable.Yes, description: "Do all of the conditions need to be met, or is it enough if at least one is? Only valid if there are multiple conditionals.")]
public PropertyConditional.LogicalOperatorType LogicalOperator { get; set; }
private ImmutableArray<PropertyConditional> Conditionals { get; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "A tag to apply to the hull the target is currently in when the check succeeds, as well as all the hulls linked to it.")]
public Identifier ApplyTagToLinkedHulls { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the hull the target item is inside when the item is used.")]
[Serialize("", IsPropertySaveable.Yes, description: "A tag to apply to the hull the target is currently in when the check succeeds.")]
public Identifier ApplyTagToHull { get; set; }
public CheckConditionalAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
@@ -4,21 +4,24 @@ using System.Linq;
namespace Barotrauma;
/// <summary>
/// Check whether a specific connection of an item is wired to a specific kind of connection.
/// </summary>
class CheckConnectionAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the item to check.")]
public Identifier ItemTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The name of the connection to check on the target item.")]
public Identifier ConnectionName { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the item the connection must be wired to. If omitted, it doesn't matter what the connection is wired to.")]
public Identifier ConnectedItemTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The name of the other connection the connection must be wired to. If omitted, it doesn't matter what the connection is wired to.")]
public Identifier OtherConnectionName { get; set; }
[Serialize(1, IsPropertySaveable.Yes)]
[Serialize(1, IsPropertySaveable.Yes, description: "Minimum number of matching connections for the check to succeed.")]
public int MinAmount { get; set; }
public CheckConnectionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -37,7 +40,7 @@ class CheckConnectionAction : BinaryOptionAction
if (!IsCorrectConnection(connection, ConnectionName)) { continue; }
if (ConnectedItemTag.IsEmpty && OtherConnectionName.IsEmpty)
{
amount += connection.Wires.Count();
amount += connection.Wires.Count;
if (amount >= MinAmount) { return true; }
continue;
}
@@ -1,21 +1,24 @@
#nullable enable
using System;
using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Can be used to check arbitrary campaign metadata set using <see cref="SetDataAction"/>.
/// </summary>
class CheckDataAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the data to check.")]
public Identifier Identifier { get; set; } = Identifier.Empty;
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The condition that must be met for the check to succeed. Uses the same formatting as conditionals (for example, \"gt 5.2\", \"true\", \"lt 10\".)")]
public string Condition { get; set; } = "";
[Serialize(false, IsPropertySaveable.Yes, "Forces the comparison to use string instead of attempting to parse it as a boolean or a float first")]
[Serialize(false, IsPropertySaveable.Yes, "Forces the comparison to use string instead of attempting to parse it as a boolean or a float first. Use this if you know the value is a string.")]
public bool ForceString { get; set; }
[Serialize(false, IsPropertySaveable.Yes, "Performs the comparison against a metadata by identifier instead of a constant value")]
[Serialize(false, IsPropertySaveable.Yes, "Performs the comparison against a metadata by identifier instead of a constant value. Meaning that you could for example check whether the value of \"progress_of_some_event\" is larger than \"progress_of_some_other_event\".")]
public bool CheckAgainstMetadata { get; set; }
protected object? value2;
@@ -6,18 +6,22 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Can be used to do various kinds of checks on items: whether a specific kind of item exists,
/// if it's in a specific character's inventory or in a container, or whether some conditions are met on the item.
/// </summary>
class CheckItemAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Either the tag of the item(s) we want to check, or a character/container the items are inside.")]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The target item must have one of these identifiers.")]
public string ItemIdentifiers { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The target item must have at least one of these tags.")]
public string ItemTags { get; set; }
[Serialize(1, IsPropertySaveable.Yes)]
[Serialize(1, IsPropertySaveable.Yes, description: "The minimum number of matching items for the check to succeed.")]
public int Amount { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Optional tag of a hull the target must be inside.")]
@@ -29,31 +33,27 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the found item(s) when the check succeeds.")]
public Identifier ApplyTagToItem { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "Does the item need to be equipped for the check to succeed?")]
public bool RequireEquipped { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
[Serialize(true, IsPropertySaveable.Yes, description: "If enabled, the doesn't need to be directly inside the container/character we're checking, but can be nested inside multiple containers (e.g. in a toolbelt in a character's inventory).")]
public bool Recursive { get; set; }
[Serialize(-1, IsPropertySaveable.Yes)]
[Serialize(-1, IsPropertySaveable.Yes, description: "Can be used to require the item to be in a specific ItemContainer of the target container. For example, the input slots of a fabricator (the first ItemContainer of the fabricator, with an index of 0).")]
public int ItemContainerIndex { get; set; }
private readonly bool checkPercentage;
private float requiredConditionalMatchPercentage;
[Serialize(100.0f, IsPropertySaveable.Yes)]
/// <summary>
/// What percentage of targets do the conditionals need to match for the check to succeed?
/// </summary>
[Serialize(100.0f, IsPropertySaveable.Yes, description: "What percentage of targets do the conditionals need to match for the check to succeed?")]
public float RequiredConditionalMatchPercentage
{
get { return requiredConditionalMatchPercentage; }
set { requiredConditionalMatchPercentage = MathHelper.Clamp(value, 0.0f, 100.0f); }
}
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "When enabled, the number of matching items is compared to the number of matching items there were at the start of the round. Only valid if RequiredConditionalMatchPercentage is set.")]
public bool CompareToInitialAmount { get; set; }
private readonly IReadOnlyList<PropertyConditional> conditionals;
@@ -94,12 +94,12 @@ namespace Barotrauma
private bool EnoughTargets(int totalTargets, int targetsWithConditionalsMatched)
{
if (CompareToInitialAmount)
{
totalTargets = ParentEvent.GetInitialTargetCount(TargetTag);
}
if (checkPercentage)
{
if (CompareToInitialAmount)
{
totalTargets = ParentEvent.GetInitialTargetCount(TargetTag);
}
return MathUtils.Percentage(targetsWithConditionalsMatched, totalTargets) >= RequiredConditionalMatchPercentage;
}
else
@@ -3,6 +3,9 @@ using System.Linq;
namespace Barotrauma;
/// <summary>
/// Check whether a specific mission is currently active, selected for the next round or available.
/// </summary>
class CheckMissionAction : BinaryOptionAction
{
public enum MissionType
@@ -12,16 +15,16 @@ class CheckMissionAction : BinaryOptionAction
Available
}
[Serialize(MissionType.Current, IsPropertySaveable.Yes)]
[Serialize(MissionType.Current, IsPropertySaveable.Yes, description: "Does the mission need to be currently active, selected for the next round or available.")]
public MissionType Type { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the mission.")]
public Identifier MissionIdentifier { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the mission. Ignored if MissionIdentifier is set.")]
public Identifier MissionTag { get; set; }
[Serialize(1, IsPropertySaveable.Yes)]
[Serialize(1, IsPropertySaveable.Yes, description: "Minimum number of matching missions for the check to succeed.")]
public int MissionCount { get; set; }
public CheckMissionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
@@ -1,16 +1,18 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
/// <summary>
/// Check whether the crew or a specific player has enough money.
/// </summary>
class CheckMoneyAction : BinaryOptionAction
{
[Serialize(0, IsPropertySaveable.Yes)]
[Serialize(0, IsPropertySaveable.Yes, description: "Minimum amount of money the crew or the player must have.")]
public int Amount { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the player to check. If omitted, the crew's shared wallet is checked instead.")]
public Identifier TargetTag { get; set; }
public CheckMoneyAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
@@ -1,5 +1,8 @@
namespace Barotrauma;
/// <summary>
/// Checks the state of an Objective created using <see cref="EventObjectiveAction"/>.
/// </summary>
partial class CheckObjectiveAction : BinaryOptionAction
{
public CheckObjectiveAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -2,6 +2,9 @@ using Barotrauma.Extensions;
namespace Barotrauma
{
/// <summary>
/// Check whether a specific character has been given a specific order.
/// </summary>
class CheckOrderAction : BinaryOptionAction
{
public enum OrderPriority
@@ -10,19 +13,19 @@ namespace Barotrauma
Any
}
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character to check.")]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the order the target character must have.")]
public Identifier OrderIdentifier { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The option that must be selected for the order. If the order has multiple options (such as turning on or turning off a reactor).")]
public Identifier OrderOption { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the entity the order must be targeting. Only valid for orders that can target a specific entity (such as orders to operate a specific turret).")]
public Identifier OrderTargetTag { get; set; }
[Serialize(OrderPriority.Any, IsPropertySaveable.Yes)]
[Serialize(OrderPriority.Any, IsPropertySaveable.Yes, description: "Does the order need to have top priority, or is any priority fine?")]
public OrderPriority Priority { get; set; }
public CheckOrderAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -3,6 +3,9 @@ using System.Linq;
namespace Barotrauma;
/// <summary>
/// Check whether specific kinds of items have been purchased or sold during the round.
/// </summary>
class CheckPurchasedItemsAction : BinaryOptionAction
{
public enum TransactionType
@@ -11,16 +14,16 @@ class CheckPurchasedItemsAction : BinaryOptionAction
Sold
}
[Serialize(TransactionType.Purchased, IsPropertySaveable.Yes)]
[Serialize(TransactionType.Purchased, IsPropertySaveable.Yes, description: "Do the items need to have been purchased or sold?")]
public TransactionType Type { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the item that must have been purchased or sold.")]
public Identifier ItemIdentifier { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the item that must have been purchased or sold.")]
public Identifier ItemTag { get; set; }
[Serialize(1, IsPropertySaveable.Yes)]
[Serialize(1, IsPropertySaveable.Yes, description: "Minimum number of matching items that must have been purchased or sold.")]
public int MinCount { get; set; }
public CheckPurchasedItemsAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
@@ -3,9 +3,12 @@ using System.Diagnostics;
namespace Barotrauma
{
/// <summary>
/// Check whether the reputation of the crew for a specific faction meets some criteria (e.g. equal to, larger than or less than some value).
/// </summary>
class CheckReputationAction : CheckDataAction
{
[Serialize(ReputationAction.ReputationType.None, IsPropertySaveable.Yes)]
[Serialize(ReputationAction.ReputationType.None, IsPropertySaveable.Yes, description: "Should the action check the reputation for a given faction, or whichever faction owns the current location.")]
public ReputationAction.ReputationType TargetType { get; set; }
public CheckReputationAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -3,17 +3,20 @@ using System.Collections.Generic;
namespace Barotrauma
{
/// <summary>
/// Check whether a specific character has selected a specific kind of item.
/// </summary>
class CheckSelectedAction : BinaryOptionAction
{
public enum SelectedItemType { Primary, Secondary, Any };
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character to check.")]
public Identifier CharacterTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "If specified, only items that have been given this tag using TagAction are considered valid.")]
public Identifier TargetTag { get; set; }
[Serialize(SelectedItemType.Any, IsPropertySaveable.Yes)]
[Serialize(SelectedItemType.Any, IsPropertySaveable.Yes, description: "How does the item need to be selected? Primary item (i.e. any device you're interacting with), secondary item (such as ladders or chairs which allow interacting with a primary item at the same time), or either?")]
public SelectedItemType ItemType { get; set; }
public CheckSelectedAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -2,12 +2,15 @@
namespace Barotrauma
{
/// <summary>
/// Check whether a specific character has a specific talent.
/// </summary>
internal sealed class CheckTalentAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the talent to check for.")]
public Identifier TalentIdentifier { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character to check.")]
public Identifier TargetTag { get; set; }
public CheckTalentAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -2,9 +2,12 @@
namespace Barotrauma
{
/// <summary>
/// Check the state of the traitor event the action is defined in. Only valid for traitor events.
/// </summary>
class CheckTraitorEventStateAction : BinaryOptionAction
{
[Serialize(TraitorEvent.State.Completed, IsPropertySaveable.Yes)]
[Serialize(TraitorEvent.State.Completed, IsPropertySaveable.Yes, description: "What does the state of the event need to be for the check to succeed?")]
public TraitorEvent.State State { get; set; }
private readonly TraitorEvent? traitorEvent;
@@ -9,7 +9,7 @@ namespace Barotrauma
/// </summary>
class CheckTraitorVoteAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character to check.")]
public Identifier Target { get; set; }
public CheckTraitorVoteAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
@@ -4,6 +4,9 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Check whether a specific entity is visible from the perspective of another entity.
/// </summary>
class CheckVisibilityAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the entity to do the visibility check from.")]
@@ -1,10 +1,12 @@
using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Clears the specific tag from the event (i.e. untagging all the entities that have been previously given the tag).
/// </summary>
class ClearTagAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The tag to clear.")]
public Identifier Tag { get; set; }
private bool isFinished;
@@ -1,31 +1,33 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Makes an NPC switch to a combat state (with options for different kinds of behaviors, such as offensive, arresting or retreating).
/// </summary>
class CombatAction : EventAction
{
[Serialize(AIObjectiveCombat.CombatMode.Offensive, IsPropertySaveable.Yes)]
[Serialize(AIObjectiveCombat.CombatMode.Offensive, IsPropertySaveable.Yes, description: $"What kind of combat mode should the NPC switch to (Defensive, Offensive, Arrest, Retreat, None)?")]
public AIObjectiveCombat.CombatMode CombatMode { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "Did this NPC start the fight (as an aggressor)?")]
[Serialize(false, IsPropertySaveable.Yes, description: "Did this NPC start the fight (as an aggressor)? Attacking instigators doesn't reduce reputation or trigger outpost security.")]
public bool IsInstigator { get; set; }
[Serialize(AIObjectiveCombat.CombatMode.None, IsPropertySaveable.Yes)]
[Serialize(AIObjectiveCombat.CombatMode.None, IsPropertySaveable.Yes, description: "How do guards react to this character attacking others?")]
public AIObjectiveCombat.CombatMode GuardReaction { get; set; }
[Serialize(AIObjectiveCombat.CombatMode.None, IsPropertySaveable.Yes)]
[Serialize(AIObjectiveCombat.CombatMode.None, IsPropertySaveable.Yes, description: "How do other NPCs react to this character attacking others?")]
public AIObjectiveCombat.CombatMode WitnessReaction { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The tag of the NPC to switch to combat mode.")]
public Identifier NPCTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character the NPC should attack.")]
public Identifier EnemyTag { get; set; }
[Serialize(120.0f, IsPropertySaveable.Yes)]
[Serialize(120.0f, IsPropertySaveable.Yes, description: "How long it takes for the NPC to \"cool down\" (stop attacking).")]
public float CoolDown { get; set; }
private bool isFinished = false;
@@ -8,6 +8,10 @@ using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Triggers a "conversation popup" with text and support for different branching options.
/// </summary>
partial class ConversationAction : EventAction
{
@@ -26,43 +30,40 @@ namespace Barotrauma
/// </summary>
const float BlockOtherConversationsDuration = 5.0f;
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The text to display in the prompt. Can be the text as-is, or a tag referring to a line in a text file.")]
public string Text { get; set; }
[Serialize(0, IsPropertySaveable.Yes)]
public int DefaultOption { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character who's speaking. Makes a speech bubble icon appear above the character to indicate you can speak with them, and stops the character in place when the conversation triggers. Also allows the conversation to be interrupted if the speaker dies or becomes incapacitated mid-conversation.")]
public Identifier SpeakerTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the player the conversation is shown to. If empty, the conversation is shown to everyone. If SpeakerTag is defined, the conversation is always only shown to the player who interacts with the speaker.")]
public Identifier TargetTag { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
[Serialize(true, IsPropertySaveable.Yes, "Should someone interact with the speaker for the conversation to trigger?")]
public bool WaitForInteraction { get; set; }
[Serialize("", IsPropertySaveable.Yes, "Tag to assign to whoever invokes the conversation")]
[Serialize("", IsPropertySaveable.Yes, "Tag to assign to whoever invokes the conversation.")]
public Identifier InvokerTag { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "Should the screen fade to black when the conversation is active?")]
public bool FadeToBlack { get; set; }
[Serialize(true, IsPropertySaveable.Yes, "Should the event end if the conversations is interrupted (e.g. if the speaker dies or falls unconscious mid-conversation). Defaults to true.")]
public bool EndEventIfInterrupted { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of an event sprite to display in the corner of the conversation prompt.")]
public string EventSprite { get; set; }
[Serialize(DialogTypes.Regular, IsPropertySaveable.Yes)]
[Serialize(DialogTypes.Regular, IsPropertySaveable.Yes, description: "Type of the dialog prompt.")]
public DialogTypes DialogType { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "Does this conversation continue after this ConversationAction? If you have multiple successive ConversationActions, perhaps with some actions happening in between, you can enable this to prevent the dialog prompt from closing between the actions. Not necessary if the ConversationActions are nested inside each other: those are always considered parts of the same conversation, and shown in the same prompt.")]
public bool ContinueConversation { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "If enabled, the event will not stop to wait for the conversation to be dismissed.")]
public bool ContinueAutomatically { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "If SpeakerTag is defined, the conversation is interrupted by default if the speaker and the target end up too far from each other. This can be used to disable that behavior, keeping the dialog prompt open regardless of the distance.")]
public bool IgnoreInterruptDistance { get; set; }
public Character Speaker
@@ -72,6 +73,7 @@ namespace Barotrauma
}
private AIObjective prevIdleObjective, prevGotoObjective;
private AIObjective npcWaitObjective;
public List<SubactionGroup> Options { get; private set; }
@@ -275,6 +277,10 @@ namespace Barotrauma
if (!SpeakerTag.IsEmpty)
{
if (npcWaitObjective != null)
{
npcWaitObjective.ForceHighestPriority = true;
}
if (Speaker != null && !Speaker.Removed && Speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && Speaker.ActiveConversation?.ParentEvent != this.ParentEvent) { return; }
Speaker = ParentEvent.GetTargets(SpeakerTag).FirstOrDefault(e => e is Character) as Character;
if (Speaker == null || Speaker.Removed)
@@ -386,11 +392,11 @@ namespace Barotrauma
{
prevIdleObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveIdle>();
prevGotoObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveGoTo>();
humanAI.SetForcedOrder(
npcWaitObjective = humanAI.SetForcedOrder(
new Order(OrderPrefab.Prefabs["wait"], Barotrauma.Identifier.Empty, null, orderGiver: null));
if (targets.Any())
if (targets.Any() || targetCharacter != null)
{
Entity closestTarget = null;
Entity closestTarget = targetCharacter;
float closestDist = float.MaxValue;
foreach (Entity entity in targets)
{
@@ -5,9 +5,12 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Check whether there's at least / at most some number of entities matching some specific criteria.
/// </summary>
class CountTargetsAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the entities to check.")]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Optional second tag. Can be used if the target must have two different tags.")]
@@ -16,29 +19,19 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes, description: "Optional tag of a hull the target must be inside.")]
public Identifier HullTag { get; set; }
[Serialize(-1, IsPropertySaveable.Yes)]
[Serialize(-1, IsPropertySaveable.Yes, description: "Minimum number of matching entities for the check to succeed. If omitted or negative, there is no minimum amount.")]
public int MinAmount { get; set; }
[Serialize(-1, IsPropertySaveable.Yes)]
[Serialize(-1, IsPropertySaveable.Yes, description: "Maximum number of matching entities for the check to succeed. If omitted or negative, there is no maximum amount.")]
public int MaxAmount { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of some other entities to compare the number of targets to. E.g. you could compare the number of entities tagged as \"discoveredhull\" to entities tagged as \"anyhull\". The minimum/maximum amount of entities there must be relative to the other entities is configured using MinPercentageRelativeToTarget and MaxPercentageRelativeToTarget.")]
public Identifier CompareToTarget { get; set; }
[Serialize(-1.0f, IsPropertySaveable.Yes)]
/// <summary>
/// Minimum amount of targets, as a percentage of the number of entities tagged with CompareToTarget
/// E.g. you could compare the number of entities tagged as "discoveredhull" to entities tagged as "anyhull" to require 50% of hulls to be discovered.
/// </summary>
[Serialize(-1.0f, IsPropertySaveable.Yes, description: "Minimum amount of targets, as a percentage of the number of entities tagged with CompareToTarget. E.g. you could compare the number of entities tagged as \"discoveredhull\" to entities tagged as \"anyhull\" to require 50% of hulls to be discovered.")]
public float MinPercentageRelativeToTarget { get; set; }
[Serialize(-1.0f, IsPropertySaveable.Yes)]
/// <summary>
/// Maximum amount of targets, as a percentage of the number of entities tagged with CompareToTarget
/// E.g. you could compare the number of entities tagged as "floodedhull" to entities tagged as "anyhull" to require less than 50% of hulls to be flooded.
/// </summary>
[Serialize(-1.0f, IsPropertySaveable.Yes, description: "Maximum amount of targets, as a percentage of the number of entities tagged with CompareToTarget. E.g. you could compare the number of entities tagged as \"floodedhull\" to entities tagged as \"anyhull\" to require less than 50% of hulls to be flooded.")]
public float MaxPercentageRelativeToTarget { get; set; }
private readonly IReadOnlyList<PropertyConditional> conditionals;
@@ -5,15 +5,19 @@ using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Adds an entry to the "event log" displayed in the mission tab of the tab menu.
/// </summary>
partial class EventLogAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the entry. If there's already an entry with the same id, it gets overwritten.")]
public Identifier Id { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Text to add to the event log. Can be the text as-is, or a tag referring to a line in a text file.")]
public string Text { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character(s) who should see the entry. If empty, the entry is shown to everyone.")]
public Identifier TargetTag { get; set; }
public bool ShowInServerLog { get; set; }
@@ -1,40 +1,81 @@
using System;
namespace Barotrauma
{
/// <summary>
/// Displays an objective in the top-right corner of the screen, or modifies an existing objective in some way.
/// </summary>
partial class EventObjectiveAction : EventAction
{
public enum SegmentActionType { Trigger, Add, AddIfNotFound, Complete, CompleteAndRemove, Remove, Fail, FailAndRemove };
public enum SegmentActionType
{
/// <summary>
/// Legacy support. Triggers an info box segment, with optional support for video clips.
/// </summary>
[Obsolete]
Trigger,
/// <summary>
/// Adds a new objective to the list.
/// </summary>
Add,
/// <summary>
/// Adds a new objective to the list if there are no existing objectives with the same identifier.
/// </summary>
AddIfNotFound,
/// <summary>
/// Marks the objective as completed.
/// </summary>
Complete,
/// <summary>
/// Marks the objective as completed and removes it from the list.
/// </summary>
CompleteAndRemove,
/// <summary>
/// Removes the objective from the list.
/// </summary>
Remove,
/// <summary>
/// Marks the objective as failed.
/// </summary>
Fail,
/// <summary>
/// Marks the objective as failed and removes it from the list.
/// </summary>
FailAndRemove
};
[Serialize(SegmentActionType.Trigger, IsPropertySaveable.Yes)]
[Serialize(SegmentActionType.Add, IsPropertySaveable.Yes, description: "Should the action add a new objective, or do something to an existing objective?")]
public SegmentActionType Type { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Arbitrary identifier given to the objective. Can be used to complete/remove/fail the objective later. Also used to fetch the text from the text files.")]
public Identifier Identifier { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Obsolete, Serialize("", IsPropertySaveable.Yes, description: "Legacy support. Tag of the text to display as an objective in info box segments.")]
public Identifier ObjectiveTag { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
[Obsolete, Serialize(true, IsPropertySaveable.Yes, description: "Legacy support. Is this objective possible to complete if it's used in an info box segment.")]
public bool CanBeCompleted { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of a parent objective. If set, this objective is displayed as a subobjective under the parent objective.")]
public Identifier ParentObjectiveId { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Obsolete, Serialize(false, IsPropertySaveable.Yes, description: "Legacy support. Should the video defined by VideoFile play automatically, or wait for the user to play it.")]
public bool AutoPlayVideo { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Obsolete, Serialize("", IsPropertySaveable.Yes, description: "Legacy support. Tag of the main text to display in info box segments.")]
public Identifier TextTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Obsolete, Serialize("", IsPropertySaveable.Yes, description: "Legacy support. Path of a video file to display in info box segments.")]
public string VideoFile { get; set; }
[Serialize(450, IsPropertySaveable.Yes)]
[Obsolete, Serialize(450, IsPropertySaveable.Yes, description: "Legacy support. Width of the info box segment.")]
public int Width { get; set; }
[Serialize(80, IsPropertySaveable.Yes)]
[Obsolete, Serialize(80, IsPropertySaveable.Yes, description: "Legacy support. Height of the info box segment.")]
public int Height { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character(s) to show the objective to.")]
public Identifier TargetTag { get; set; }
private bool isFinished;
@@ -1,17 +1,16 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Starts a fire at the position of a specific target.
/// </summary>
class FireAction : EventAction
{
[Serialize(10.0f, IsPropertySaveable.Yes)]
[Serialize(10.0f, IsPropertySaveable.Yes, description: "Size of the fire (width in pixels).")]
public float Size { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the entity to start the fire at.")]
public Identifier TargetTag { get; set; }
public FireAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -2,12 +2,15 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Gives experience to a specific character.
/// </summary>
class GiveExpAction : EventAction
{
[Serialize(0, IsPropertySaveable.Yes)]
[Serialize(0, IsPropertySaveable.Yes, description: "The amount of experience to give. Cannot be negative.")]
public int Amount { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character(s) to give the experience to.")]
public Identifier TargetTag { get; set; }
public GiveExpAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
@@ -2,15 +2,18 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Increases the skill level of a specific character.
/// </summary>
class GiveSkillExpAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the skill to increase.")]
public Identifier Skill { get; set; }
[Serialize(0.0f, IsPropertySaveable.Yes)]
[Serialize(0.0f, IsPropertySaveable.Yes, description: "How much the skill should increase.")]
public float Amount { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character(s) whose skill to increase.")]
public Identifier TargetTag { get; set; }
public GiveSkillExpAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
@@ -1,11 +1,14 @@
namespace Barotrauma
{
/// <summary>
/// Makes the event jump to a <see cref="Label"/> somewhere else in the event.
/// </summary>
class GoTo : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Name of the label to jump to.")]
public string Name { get; set; }
[Serialize(-1, IsPropertySaveable.Yes)]
[Serialize(-1, IsPropertySaveable.Yes, description: "How many times can this GoTo action be repeated? Can be used to make some parts of an event repeat a limited number of times. If negative or zero, there's no limit.")]
public int MaxTimes { get; set; }
private int counter;
@@ -1,14 +1,17 @@
namespace Barotrauma
{
/// <summary>
/// Makes a specific character invulnerable to damage and unable to die.
/// </summary>
class GodModeAction : EventAction
{
[Serialize(true, IsPropertySaveable.Yes)]
[Serialize(true, IsPropertySaveable.Yes, description: "Should the godmode be enabled or disabled?")]
public bool Enabled { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "Should the character's active afflictions be updated (e.g. applying visual effects of the afflictions)")]
public bool UpdateAfflictions { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character whose godmode to enable/disable.")]
public Identifier TargetTag { get; set; }
public GodModeAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -5,17 +5,20 @@ using System.Linq;
namespace Barotrauma;
/// <summary>
/// Highlights a specific entity.
/// </summary>
partial class HighlightAction : EventAction
{
private static readonly Color highlightColor = Color.Orange;
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the entity to highlight.")]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Only the player controlling this character will see the highlight. If empty, all players will see it.")]
public Identifier TargetCharacter { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
[Serialize(true, IsPropertySaveable.Yes, description: "Should the highlight be turned on or off?")]
public bool State { get; set; }
private bool isFinished;
@@ -1,17 +1,20 @@
namespace Barotrauma;
/// <summary>
/// Highlights specific items in a specific inventory.
/// </summary>
partial class InventoryHighlightAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the entity or entities whose inventory the item should be highlighted in. Must be a character or an item with an inventory.")]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the item(s) to highlight.")]
public Identifier ItemIdentifier { get; set; }
[Serialize(-1, IsPropertySaveable.Yes)]
[Serialize(-1, IsPropertySaveable.Yes, description: "If the target is an item with multiple ItemContainer components (i.e. multiple inventories), such as a fabricator, this determines which inventory to highlight the item in (0 = first, 1 = second). If negative, it doesn't matter which inventory the item is in.")]
public int ItemContainerIndex { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "If enabled, the action will go look through all the containers in the target inventory (e.g. highlighting a tank in a welding tool in the target inventory).")]
public bool Recursive { get; set; }
private bool isFinished;
@@ -1,7 +1,8 @@
using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Defines a point in the event that <see cref="GoTo"/> actions can jump to.
/// </summary>
class Label : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
@@ -0,0 +1,61 @@
namespace Barotrauma;
/// <summary>
/// Enable or disable a specific layer in a specific submarine.
/// </summary>
class LayerAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes, description: "Which layer to enable/disable. Use \"All\" to apply it to all layers.")]
public Identifier Layer { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "Whether to enable or disable the layer.")]
public bool Enabled { get; set; }
[Serialize(TagAction.SubType.Any, IsPropertySaveable.Yes, description: "The type of submatine to enable or disable the layer in.")]
public TagAction.SubType SubmarineType { get; set; }
[Serialize(true, IsPropertySaveable.Yes, description: "Should the action continue if it can't find the specified layer in the specified submarine(s).")]
public bool ContinueIfNotFound { get; set; }
public LayerAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
private bool isFinished;
public override bool IsFinished(ref string goTo)
{
return isFinished;
}
public override void Reset()
{
isFinished = false;
}
public override void Update(float deltaTime)
{
if (isFinished) { return; }
bool layerFound = false;
foreach (var submarine in Submarine.Loaded)
{
if (!TagAction.SubmarineTypeMatches(submarine, SubmarineType)) { continue; }
if (submarine.LayerExists(Layer))
{
submarine.SetLayerEnabled(Layer, Enabled, sendNetworkEvent: true);
layerFound = true;
}
}
if (ContinueIfNotFound)
{
isFinished = true;
}
else
{
if (layerFound) { isFinished = true; }
}
}
public override string ToDebugString()
{
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(LayerAction)} -> ({(Enabled ? "Enable" : "Disable")} {Layer.ColorizeObject()})";
}
}
@@ -1,52 +1,55 @@
namespace Barotrauma
{
/// <summary>
/// Displays a message box, or modifies an existing one.
/// </summary>
partial class MessageBoxAction : EventAction
{
public enum ActionType { Create, ConnectObjective, Close, Clear }
[Serialize(ActionType.Create, IsPropertySaveable.Yes)]
[Serialize(ActionType.Create, IsPropertySaveable.Yes, description: "What do you want to do with the message box (Create, ConnectObjective, Close, Clear)?")]
public ActionType Type { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Optional identifier of the tutorial \"segment\" that can be referenced by other event actions.")]
public Identifier Identifier { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "An arbitrary tag given to the message box. Only required if you're intending to close or clear the box with another MessageBoxAction later.")]
public string Tag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Text displayed in the header of the message box. Can be either the text as-is, or a tag referring to a line in a text file.")]
public Identifier Header { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Text displayed in the body of the message box. Can be either the text as-is, or a tag referring to a line in a text file.")]
public Identifier Text { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Style of the icon displayed in the corner of the message box (optional). The style must be defined in a UIStyle file.")]
public string IconStyle { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "Should the button that closes the box be hidden? If it is hidden, you must close the box manually using another MessageBoxAction.")]
public bool HideCloseButton { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character(s) to show the message box to.")]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The message box is automatically closed on some input (e.g. Select, Use, CrewOrders).")]
public string CloseOnInput { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The message box is automatically closed when the user selects an item that has this tag.")]
public Identifier CloseOnSelectTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The message box is automatically closed when the user picks up an item that has this tag.")]
public Identifier CloseOnPickUpTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The message box is automatically closed when the user equips an item that has this tag.")]
public Identifier CloseOnEquipTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The message box is automatically closed when the user exits a room with this name.")]
public Identifier CloseOnExitRoomName { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The message box is automatically closed when the user is in a room with this name.")]
public Identifier CloseOnInRoomName { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Optional tag that will be used to get the text for the objective that is displayed on the screen.")]
public Identifier ObjectiveTag { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
@@ -7,15 +7,18 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Unlocks a mission in a nearby level or location.
/// </summary>
partial class MissionAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the mission to unlock.")]
public Identifier MissionIdentifier { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the mission to unlock. If there are multiple missions with the tag, one is chosen randomly.")]
public Identifier MissionTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The mission can only be unlocked in a location that's occupied by this faction.")]
public Identifier RequiredFaction { get; set; }
public ImmutableArray<Identifier> LocationTypes { get; }
@@ -46,7 +49,14 @@ namespace Barotrauma
contentPackage: element.ContentPackage);
}
LocationTypes = element.GetAttributeIdentifierArray("locationtype", Array.Empty<Identifier>()).ToImmutableArray();
random = new MTRandom(parentEvent.RandomSeed);
//the action chooses the same mission if
// 1. event seed is the same (based on level seed, changes when events are completed)
// 2. event is the same (two different events shouldn't choose the same mission)
// 3. the MissionAction is the same (two different actions in the same event shouldn't choose the same mission)
random = new MTRandom(
parentEvent.RandomSeed +
ToolBox.StringToInt(ParentEvent.Prefab.Identifier.Value) +
ParentEvent.Actions.Count);
}
public override bool IsFinished(ref string goTo)
@@ -1,8 +1,12 @@
namespace Barotrauma
{
/// <summary>
/// Changes the state of a specific active mission. The way the states are used depends on the type of mission.
/// </summary>
class MissionStateAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the mission whose state to change.")]
public Identifier MissionIdentifier { get; set; }
public enum OperationType
@@ -11,10 +15,10 @@ namespace Barotrauma
Add
}
[Serialize(OperationType.Set, IsPropertySaveable.Yes)]
[Serialize(OperationType.Set, IsPropertySaveable.Yes, description: "Should the value be added to the state of the mission, or should the state be set to the specified value.")]
public OperationType Operation { get; set; }
[Serialize(0, IsPropertySaveable.Yes)]
[Serialize(0, IsPropertySaveable.Yes, description: "The state to set the mission to, or how much to add to the state of the mission.")]
public int State { get; set; }
private bool isFinished;
@@ -1,17 +1,20 @@
namespace Barotrauma
{
/// <summary>
/// Modifies the current location in some way (e.g. adjusting the faction, type of name).
/// </summary>
class ModifyLocationAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the faction to set as the location's primary faction (optional).")]
public Identifier Faction { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the faction to set as the location's secondary faction (optional).")]
public Identifier SecondaryFaction { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the location type to set as the location's new type (optional)")]
public Identifier Type { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "New name to give to the location (optional). Can either be the name as-is, or a tag referring to a line in a text file.")]
public Identifier Name { get; set; }
private bool isFinished;
@@ -1,20 +1,21 @@
using System;
using Barotrauma.Networking;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
/// <summary>
/// Give or remove money from the crew or a specific character.
/// </summary>
class MoneyAction : EventAction
{
public MoneyAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
[Serialize(0, IsPropertySaveable.Yes)]
[Serialize(0, IsPropertySaveable.Yes, description: "Amount of money to give or remove.")]
public int Amount { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "If set, the money is removed from character(s) with this tag.")]
public Identifier TargetTag { get; set; }
private bool isFinished;
@@ -4,20 +4,21 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Changes the team of an NPC. Most common use cases are adding a character to the crew, or turning an NPC hostile to the crew by changing their team to a hostile one.
/// </summary>
class NPCChangeTeamAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the NPC(s) whose team to change.")]
public Identifier NPCTag { get; set; }
[Serialize(CharacterTeamType.None, IsPropertySaveable.Yes)]
[Serialize(CharacterTeamType.None, IsPropertySaveable.Yes, description: "The team to move the NPC to. None = unspecified, Team1 = player crew, Team2 = the team opposing Team1 (= hostile to player crew), FriendlyNPC = friendly to all other teams.")]
public CharacterTeamType TeamID { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "Should the NPC be added to the player crew?")]
public bool AddToCrew { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "Should the NPC be removed from the player crew?")]
public bool RemoveFromCrew { get; set; }
private bool isFinished = false;
@@ -3,21 +3,24 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Makes an NPC follow or stop following a specific target.
/// </summary>
class NPCFollowAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the NPC(s) that should follow the target.")]
public Identifier NPCTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the target. Can be any type of entity: if it's a static one like a device or a hull, the NPC will just stay at the position of that target.")]
public Identifier TargetTag { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
[Serialize(true, IsPropertySaveable.Yes, description: "Should the NPC start or stop following the target?")]
public bool Follow { get; set; }
[Serialize(-1, IsPropertySaveable.Yes)]
[Serialize(-1, IsPropertySaveable.Yes, description: "Maximum number of NPCs to target (e.g. you could choose to only make a specific number of security officers follow the player.)")]
public int MaxTargets { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
[Serialize(true, IsPropertySaveable.Yes, description: "The event actions reset when a GoTo action makes the event jump to a different point. Should the NPC stop following the target when the event resets?")]
public bool AbandonOnReset { get; set; }
private bool isFinished = false;
@@ -1,39 +1,43 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Makes an NPC select an item, and operate it if it's something AI characters can operate.
/// </summary>
class NPCOperateItemAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the NPC(s) that should operate the item.")]
public Identifier NPCTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the item to operate. If it's not something AI characters can or know how to operate, such as a cabinet or an engine, the NPC will just select it.")]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("Controller", IsPropertySaveable.Yes, description: "Name of the component to operate. For example, the Controller component of a periscope or the Reactor component of a nuclear reactor.")]
public Identifier ItemComponentName { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the option, if there are several ways the item can be operated. For example, \"powerup\" or \"shutdown\" when operating a reactor.")]
public Identifier OrderOption { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "Should the character equip the item before attempting to operate it (only valid if the item is equippable).")]
public bool RequireEquip { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
[Serialize(true, IsPropertySaveable.Yes, description: "Should the character start or stop operating the item.")]
public bool Operate { get; set; }
[Serialize(-1, IsPropertySaveable.Yes)]
[Serialize(-1, IsPropertySaveable.Yes, description: "Maximum number of NPCs the action can target. For example, you could only make a specific number of security officers man a periscope.")]
public int MaxTargets { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
[Serialize(true, IsPropertySaveable.Yes, description: "The event actions reset when a GoTo action makes the event jump to a different point. Should the NPC stop operating the item when the event resets?")]
public bool AbandonOnReset { get; set; }
private bool isFinished = false;
public NPCOperateItemAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
private List<Character> affectedNpcs = null;
private Item target = null;
@@ -41,7 +45,13 @@ namespace Barotrauma
{
if (isFinished) { return; }
target = ParentEvent.GetTargets(TargetTag).FirstOrDefault() as Item;
var potentialTargets = ParentEvent.GetTargets(TargetTag).OfType<Item>();
var nonSelectedItems = potentialTargets.Where(it => it.GetComponent<Controller>()?.User == null);
target =
nonSelectedItems.Any() ?
nonSelectedItems.GetRandomUnsynced() :
potentialTargets.GetRandomUnsynced();
if (target == null) { return; }
int targetCount = 0;
@@ -53,7 +63,6 @@ namespace Barotrauma
if (Operate)
{
ItemComponentName = "Controller".ToIdentifier();
var itemComponent = target.Components.FirstOrDefault(ic => ItemComponentName == ic.Name);
if (itemComponent == null)
{
@@ -1,15 +1,17 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Makes an NPC stop and wait.
/// </summary>
class NPCWaitAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the NPC(s) that should wait.")]
public Identifier NPCTag { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
[Serialize(true, IsPropertySaveable.Yes, description: "Should the NPC start or stop waiting?")]
public bool Wait { get; set; }
private bool isFinished = false;
@@ -35,6 +37,7 @@ namespace Barotrauma
var gotoObjective = new AIObjectiveGoTo(
AIObjectiveGoTo.GetTargetHull(npc) as ISpatialEntity ?? npc, npc, humanAiController.ObjectiveManager, repeat: true)
{
FaceTargetOnCompleted = false,
OverridePriority = 100.0f,
SourceEventAction = this,
IsWaitOrder = true,
@@ -2,6 +2,9 @@
namespace Barotrauma
{
/// <summary>
/// Executes all the child actions when the round ends.
/// </summary>
class OnRoundEndAction : EventAction
{
private readonly SubactionGroup subActions;
@@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Randomly executes either of the child actions (Success or Failure).
/// </summary>
class RNGAction : BinaryOptionAction
{
[Serialize(0.0f, IsPropertySaveable.Yes)]
[Serialize(0.0f, IsPropertySaveable.Yes, description: "The probability of executing the Success actions. A value between 0-1.")]
public float Chance { get; set; }
public RNGAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
@@ -3,15 +3,18 @@ using System.Collections.Immutable;
namespace Barotrauma
{
/// <summary>
/// Removes (deletes) a specific item or items.
/// </summary>
class RemoveItemAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the item(s) to remove.")]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Optional list of identifiers the item(s) must have. You might for example want to go through all tagged items inside a cabinet, but only remove specific types of items.")]
public string ItemIdentifiers { get; set; }
[Serialize(1, IsPropertySaveable.Yes)]
[Serialize(1, IsPropertySaveable.Yes, description: "Maximum number of items to remove.")]
public int Amount { get; set; }
private readonly ImmutableHashSet<Identifier> itemIdentifierSplit;
@@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
namespace Barotrauma
{
/// <summary>
/// Adjusts the crew's reputation by some value.
/// </summary>
class ReputationAction : EventAction
{
public enum ReputationType
@@ -17,13 +14,13 @@ namespace Barotrauma
public ReputationAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
[Serialize(0.0f, IsPropertySaveable.Yes)]
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Amount of reputation to add or remove.")]
public float Increase { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the faction you want to adjust the reputation for. Ignored if TargetType is set to Location.")]
public Identifier Identifier { get; set; }
[Serialize(ReputationType.None, IsPropertySaveable.Yes)]
[Serialize(ReputationType.None, IsPropertySaveable.Yes, description: "Do you want to adjust the reputation for a specific faction, or whichever faction controls the current location?")]
public ReputationType TargetType { get; set; }
private bool isFinished;
@@ -1,8 +1,10 @@
using System;
using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Sets a campaign metadata value. The metadata can be any arbitrary data you want to save: for example, whether some event has been completed, the number of times something has been done during the campaign, or at what stage of some multi-part event chain the crew is at.
/// </summary>
class SetDataAction : EventAction
{
public enum OperationType
@@ -14,13 +16,13 @@ namespace Barotrauma
public SetDataAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
[Serialize(OperationType.Set, IsPropertySaveable.Yes)]
[Serialize(OperationType.Set, IsPropertySaveable.Yes, description: "Do you want to set the metadata to a specific value, multiply it, or add to it.")]
public OperationType Operation { get; set; }
[Serialize(null, IsPropertySaveable.Yes)]
[Serialize(null, IsPropertySaveable.Yes, description: "Depending on the operation, the value you want to set the metadata to, multiply it with, or add to it.")]
public string Value { get; set; } = null!;
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the metadata to set. Can be any arbitrary identifier, e.g. itemscollected, my_custom_event_state, specialnpckilled...")]
public Identifier Identifier { get; set; }
private bool isFinished;
@@ -1,8 +1,10 @@
using System;
using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Adjusts the price multiplier for stores or mechanical repairs in the current location.
/// </summary>
class SetPriceMultiplierAction : EventAction
{
public enum OperationType
@@ -19,13 +21,13 @@ namespace Barotrauma
Mechanical
}
[Serialize(1.0f, IsPropertySaveable.Yes)]
[Serialize(1.0f, IsPropertySaveable.Yes, description: "Value to set as the multiplier, or to multiply, min or max the current multiplier with.")]
public float Multiplier { get; set; }
[Serialize(OperationType.Set, IsPropertySaveable.Yes)]
[Serialize(OperationType.Set, IsPropertySaveable.Yes, description: "Do you want to set the value as the multiplier, multiply the existing multiplier with it, or take the smaller or larger of the values.")]
public OperationType Operation { get; set; }
[Serialize(PriceMultiplierType.Store, IsPropertySaveable.Yes)]
[Serialize(PriceMultiplierType.Store, IsPropertySaveable.Yes, description: "Do you want to set the price multiplier for stores or for mechanical services (hull and item repairs and restoring lost shuttles)?")]
public PriceMultiplierType TargetMultiplier { get; set; }
public SetPriceMultiplierAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -2,6 +2,9 @@
namespace Barotrauma
{
/// <summary>
/// Sets the state of the traitor event. Only valid in traitor events.
/// </summary>
class SetTraitorEventStateAction : EventAction
{
private readonly TraitorEvent? traitorEvent;
@@ -19,7 +22,7 @@ namespace Barotrauma
}
}
[Serialize(TraitorEvent.State.Completed, IsPropertySaveable.Yes)]
[Serialize(TraitorEvent.State.Completed, IsPropertySaveable.Yes, description: "The state to set the traitor event to (Incomplete, Completed or Failed).")]
public TraitorEvent.State State { get; set; }
private bool isFinished;
@@ -1,22 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Performs a skill check and executes either the Success or Failure child actions depending on whether the check succeeds.
/// </summary>
class SkillCheckAction : BinaryOptionAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The identifier of the skill to check.")]
public Identifier RequiredSkill { get; set; }
[Serialize(0.0f, IsPropertySaveable.Yes)]
[Serialize(0.0f, IsPropertySaveable.Yes, description: "The required skill level for the check to succeed.")]
public float RequiredLevel { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
[Serialize(true, IsPropertySaveable.Yes, description: "Should the skill check be probability-based (i.e. if you have half the required skill level, the chance of success is 50%), or should the check always fail when under the required level and always succeed when above? ")]
public bool ProbabilityBased { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character(s) whose skill to check. If there are multiple targets, the action succeeds if any of their skill checks succeeds.")]
public Identifier TargetTag { get; set; }
public SkillCheckAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
@@ -6,6 +6,9 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Spawns an entity (e.g. item, NPC, monster).
/// </summary>
class SpawnAction : EventAction
{
public enum SpawnLocationType
@@ -41,16 +44,16 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes, description: "Tag of an entity with an inventory to spawn the item into.")]
public Identifier TargetInventory { get; set; }
[Serialize(SpawnLocationType.Any, IsPropertySaveable.Yes)]
[Serialize(SpawnLocationType.Any, IsPropertySaveable.Yes, description: "Where should the entity spawn? This can be restricted further with the other spawn point options.")]
public SpawnLocationType SpawnLocation { get; set; }
[Serialize(SpawnType.Human, IsPropertySaveable.Yes)]
[Serialize(SpawnType.Human, IsPropertySaveable.Yes, description: "Type of spawnpoint to spawn the entity at. Ignored if SpawnPointTag is set.")]
public SpawnType SpawnPointType { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of a spawnpoint to spawn the entity at.")]
public Identifier SpawnPointTag { get; set; }
[Serialize(CharacterTeamType.FriendlyNPC, IsPropertySaveable.Yes)]
[Serialize(CharacterTeamType.FriendlyNPC, IsPropertySaveable.Yes, description: "Team of the NPC to spawn. Only valid when spawning a character.")]
public CharacterTeamType TeamID { get; protected set; }
[Serialize(false, IsPropertySaveable.Yes, description: "Should we spawn the entity even when no spawn points with matching tags were found?")]
@@ -61,10 +64,10 @@ namespace Barotrauma
[Serialize(true, IsPropertySaveable.Yes, description: "If false, we won't spawn another character if one with the same identifier has already been spawned.")]
public bool AllowDuplicates { get; set; }
[Serialize(1, IsPropertySaveable.Yes)]
[Serialize(1, IsPropertySaveable.Yes, description: "Number of entities to spawn.")]
public int Amount { get; set; }
[Serialize(100.0f, IsPropertySaveable.Yes)]
[Serialize(100.0f, IsPropertySaveable.Yes, description: "Random offset to add to the spawn position.")]
public float Offset { get; set; }
[Serialize("", IsPropertySaveable.Yes, "What outpost module tags does the entity prefer to spawn in.")]
@@ -2,13 +2,16 @@ using System.Collections.Generic;
namespace Barotrauma
{
/// <summary>
/// Executes all the StatusEffects defined as child elements of the action.
/// </summary>
partial class StatusEffectAction : EventAction
{
private readonly List<StatusEffect> effects = new List<StatusEffect>();
private readonly int actionIndex;
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the entity or entities the status effect should target.")]
public Identifier TargetTag { get; set; }
public StatusEffectAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
@@ -1,4 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Extensions;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -6,26 +6,32 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Tags a specific entity. Tags are used by other actions to refer to specific entities. The tags are event-specific, i.e. you cannot use a tag that was added by another event to refer to an entity.
/// </summary>
class TagAction : EventAction
{
public enum SubType { Any = 0, Player = 1, Outpost = 2, Wreck = 4, BeaconStation = 8 }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "What criteria to use to select the entities to target. Valid values are players, player, traitor, nontraitor, nontraitorplayer, bot, crew, humanprefabidentifier:[id], jobidentifier:[id], structureidentifier:[id], structurespecialtag:[tag], itemidentifier:[id], itemtag:[tag], hull, hullname:[name], submarine:[type], eventtag:[tag].")]
public string Criteria { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "The tag to apply to the target.")]
public Identifier Tag { get; set; }
[Serialize(SubType.Any, IsPropertySaveable.Yes)]
[Serialize(SubType.Any, IsPropertySaveable.Yes, description: "The type of submarine the target needs to be in.")]
public SubType SubmarineType { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, "If set, the target must be in an outpost module that has this tag.")]
public Identifier RequiredModuleTag { get; set; }
[Serialize(true, IsPropertySaveable.Yes, description: "Should incapacitated (e.g. dead, paralyzed, unconscious) characters be ignored, i.e. not considered valid targets?")]
public bool IgnoreIncapacitatedCharacters { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "Can items that have been set to be hidden in-game be tagged?")]
public bool AllowHiddenItems { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "If there are multiple matching targets, should all of them be tagged or one chosen randomly?")]
public bool ChooseRandom { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "Should the event continue if the TagAction can't find any valid targets?")]
@@ -85,7 +91,7 @@ namespace Barotrauma
private void TagByEventTag(Identifier eventTag)
{
AddTarget(Tag, ParentEvent.GetTargets(eventTag).Where(t => SubmarineTypeMatches(t.Submarine)));
AddTarget(Tag, ParentEvent.GetTargets(eventTag).Where(t => MatchesRequirements(t)));
}
private void TagPlayers()
@@ -157,7 +163,7 @@ namespace Barotrauma
AddTargetPredicate(
Tag,
ScriptedEvent.TargetPredicate.EntityType.Structure,
e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.Prefab.Identifier == identifier);
e => e is Structure s && MatchesRequirements(s) && s.Prefab.Identifier == identifier);
}
private void TagStructuresBySpecialTag(Identifier tag)
@@ -165,7 +171,7 @@ namespace Barotrauma
AddTargetPredicate(
Tag,
ScriptedEvent.TargetPredicate.EntityType.Structure,
e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.SpecialTag.ToIdentifier() == tag);
e => e is Structure s && MatchesRequirements(s) && s.SpecialTag.ToIdentifier() == tag);
}
private void TagItemsByIdentifier(Identifier identifier)
@@ -173,7 +179,7 @@ namespace Barotrauma
AddTargetPredicate(
Tag,
ScriptedEvent.TargetPredicate.EntityType.Item,
e => e is Item it && IsValidItem(it) && it.Prefab.Identifier == identifier);
e => e is Item it && it.Prefab.Identifier == identifier && IsValidItem(it));
}
private void TagItemsByTag(Identifier tag)
@@ -181,7 +187,7 @@ namespace Barotrauma
AddTargetPredicate(
Tag,
ScriptedEvent.TargetPredicate.EntityType.Item,
e => e is Item it && IsValidItem(it) && it.HasTag(tag));
e => e is Item it && it.HasTag(tag) && IsValidItem(it));
}
private void TagHulls()
@@ -189,7 +195,7 @@ namespace Barotrauma
AddTargetPredicate(
Tag,
ScriptedEvent.TargetPredicate.EntityType.Hull,
e => e is Hull h && SubmarineTypeMatches(h.Submarine));
e => e is Hull h && MatchesRequirements(h));
}
private void TagHullsByName(Identifier name)
@@ -197,7 +203,7 @@ namespace Barotrauma
AddTargetPredicate(
Tag,
ScriptedEvent.TargetPredicate.EntityType.Hull,
e => e is Hull h && SubmarineTypeMatches(h.Submarine) && h.RoomName.Contains(name.Value, StringComparison.OrdinalIgnoreCase));
e => e is Hull h && MatchesRequirements(h) && h.RoomName.Contains(name.Value, StringComparison.OrdinalIgnoreCase));
}
private void TagSubmarinesByType(Identifier type)
@@ -205,33 +211,76 @@ namespace Barotrauma
AddTargetPredicate(
Tag,
ScriptedEvent.TargetPredicate.EntityType.Submarine,
e => e is Submarine s && SubmarineTypeMatches(s) && (type.IsEmpty || type == s.Info?.Type.ToIdentifier()));
e => e is Submarine s && MatchesRequirements(s) && (type.IsEmpty || type == s.Info?.Type.ToIdentifier()));
}
private bool IsValidItem(Item it)
{
return
(!it.HiddenInGame || AllowHiddenItems) &&
ModuleTagMatches(it) &&
//if the item has just spawned, it may be in a hull but not moved into the coordinate space of the hull yet
//= it.Submarine still null
SubmarineTypeMatches(it.Submarine ?? it.CurrentHull?.Submarine ?? it.ParentInventory?.Owner?.Submarine);
}
private bool MatchesRequirements(Entity e)
{
return ModuleTagMatches(e) && SubmarineTypeMatches(e.Submarine);
}
private bool ModuleTagMatches(Entity e)
{
if (RequiredModuleTag.IsEmpty) { return true; }
if (e?.Submarine == null) { return false; }
Hull hull;
if (e is Character character)
{
hull = character.CurrentHull;
}
else if (e is Item item)
{
hull = item.CurrentHull;
}
else if (e is WayPoint wp)
{
hull = wp.CurrentHull;
}
else if (e is Hull h)
{
hull = h;
}
else
{
DebugConsole.AddWarning($"Potential error in event \"{ParentEvent.Prefab.Identifier}\": {nameof(TagAction)} cannot check the module tags of an entity of the type {e.GetType()}.");
return false;
}
return hull != null && hull.OutpostModuleTags.Contains(RequiredModuleTag);
}
private bool SubmarineTypeMatches(Submarine sub)
{
if (SubmarineType == SubType.Any) { return true; }
return SubmarineTypeMatches(sub, SubmarineType);
}
public static bool SubmarineTypeMatches(Submarine sub, SubType submarineType)
{
if (submarineType == SubType.Any) { return true; }
if (sub == null) { return false; }
switch (sub.Info.Type)
{
case Barotrauma.SubmarineType.Player:
return SubmarineType.HasFlag(SubType.Player) && sub != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle;
return submarineType.HasFlag(SubType.Player) && sub != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle;
case Barotrauma.SubmarineType.Outpost:
case Barotrauma.SubmarineType.OutpostModule:
return SubmarineType.HasFlag(SubType.Outpost);
return submarineType.HasFlag(SubType.Outpost);
case Barotrauma.SubmarineType.Wreck:
return SubmarineType.HasFlag(SubType.Wreck);
return submarineType.HasFlag(SubType.Wreck);
case Barotrauma.SubmarineType.BeaconStation:
return SubmarineType.HasFlag(SubType.BeaconStation);
return submarineType.HasFlag(SubType.BeaconStation);
default:
return false;
}
@@ -1,19 +1,22 @@
namespace Barotrauma;
/// <summary>
/// Teleports a specific entity to a specific spawn point.
/// </summary>
class TeleportAction : EventAction
{
public enum TeleportPosition { MainSub, Outpost }
[Serialize(TeleportPosition.MainSub, IsPropertySaveable.Yes)]
[Serialize(TeleportPosition.MainSub, IsPropertySaveable.Yes, description: "Should the entity be teleported to the main submarine or the outpost?")]
public TeleportPosition Position { get; set; }
[Serialize(SpawnType.Human, IsPropertySaveable.Yes)]
[Serialize(SpawnType.Human, IsPropertySaveable.Yes, description: "The type of the spawnpoint to teleport the character to.")]
public SpawnType SpawnType { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Optional tag of the spawnpoint.")]
public string SpawnPointTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the target(s) to teleport.")]
public Identifier TargetTag { get; set; }
private bool isFinished;
@@ -1,10 +1,12 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Waits for a player to trigger the action before continuing. Triggering can mean entering a specific trigger area, or interacting with a specific entity.
/// </summary>
class TriggerAction : EventAction
{
public enum TriggerType
@@ -1,11 +1,14 @@
namespace Barotrauma
{
/// <summary>
/// Triggers another scripted event.
/// </summary>
class TriggerEventAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the event to trigger.")]
public Identifier Identifier { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "If set to true, the event will trigger at the beginning of the next round. Useful for e.g. triggering some scripted event in the outpost after you finish a mission.")]
public bool NextRound { get; set; }
private bool isFinished;
@@ -41,7 +44,7 @@
}
else
{
var ev = eventPrefab.CreateInstance();
var ev = eventPrefab.CreateInstance(GameMain.GameSession.EventManager.RandomSeed);
if (ev != null)
{
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(ev);
@@ -1,5 +1,8 @@
namespace Barotrauma
{
/// <summary>
/// Completes the tutorial. Only valid in tutorial events.
/// </summary>
class TutorialCompleteAction : EventAction
{
private bool isFinished;
@@ -2,17 +2,20 @@ using System.Linq;
namespace Barotrauma;
/// <summary>
/// Displays a tutorial icon next to a specific target.
/// </summary>
class TutorialIconAction : EventAction
{
public enum ActionType { Add, Remove, RemoveTarget, RemoveIcon, Clear };
[Serialize(ActionType.Add, IsPropertySaveable.Yes)]
[Serialize(ActionType.Add, IsPropertySaveable.Yes, description: "What to do with the icon. Add = add an icon, Remove = remove the icon that has the specific target and style, RemoveTarget = remove all icons assigned to the specific target, RemoveIcon = remove all icons with the specific style, Remove = remove all icons.")]
public ActionType Type { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the target to assign the icon to.")]
public Identifier TargetTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Style of the icon.")]
public Identifier IconStyle { get; set; }
private bool isFinished;
@@ -1,5 +1,8 @@
namespace Barotrauma;
/// <summary>
/// Highlights an UI element of some kind. Generally used in tutorials.
/// </summary>
partial class UIHighlightAction : EventAction
{
public enum ElementId
@@ -24,28 +27,28 @@ partial class UIHighlightAction : EventAction
MessageBoxCloseButton
}
[Serialize(ElementId.None, IsPropertySaveable.Yes)]
[Serialize(ElementId.None, IsPropertySaveable.Yes, description: "An arbitrary identifier that must match the userdata of the UI element. The userdatas of the element are hard-coded, so this option is generally intended for the developers' use.")]
public ElementId Id { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "If the element's userdata is an entity or an entity prefab, it's identifier must match this value.")]
public Identifier EntityIdentifier { get; set; }
[Serialize(OrderCategory.Emergency, IsPropertySaveable.Yes)]
[Serialize(OrderCategory.Emergency, IsPropertySaveable.Yes, description: "If the element's userdata is an order category, it must match this.")]
public OrderCategory OrderCategory { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "If the element's userdata is an order, it must match this identifier.")]
public Identifier OrderIdentifier { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "If the element's userdata is an order with options, it must match this.")]
public Identifier OrderOption { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "If the element's userdata is an order, the order must target an entity with this tag.")]
public Identifier OrderTargetTag { get; set; }
[Serialize(true, IsPropertySaveable.Yes)]
[Serialize(true, IsPropertySaveable.Yes, description: "Should the element bounce up an down in addition to being highlighted.")]
public bool Bounce { get; set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes, description: "Should the action highlight the first matching element it finds, or all of them?")]
public bool HighlightMultiple { get; set; }
private bool isFinished;
@@ -1,12 +1,12 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
/// <summary>
/// Unlocks a "locked" pathways between locations, if there are any such paths adjacent to the current location.
/// </summary>
class UnlockPathAction : EventAction
{
public UnlockPathAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -1,8 +1,11 @@
namespace Barotrauma
{
/// <summary>
/// Waits for a specific amount of time before continuing the execution of the event.
/// </summary>
class WaitAction : EventAction
{
[Serialize(0.0f, IsPropertySaveable.Yes)]
[Serialize(0.0f, IsPropertySaveable.Yes, description: "How long to wait (in seconds).")]
public float Time { get; set; }
private float timeRemaining;
@@ -1,22 +1,25 @@
#nullable enable
#nullable enable
using Barotrauma.Items.Components;
using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Waits for some item(s) to be fabricated before continuing the execution of the event.
/// </summary>
class WaitForItemFabricatedAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character who must fabricate the item. If empty, it doesn't matter who fabricates it.")]
public Identifier CharacterTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the item that must be fabricated. Optional if ItemTag is set.")]
public Identifier ItemIdentifier { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the item that must be fabricated. Optional if ItemIdentifier is set.")]
public Identifier ItemTag { get; set; }
[Serialize(1, IsPropertySaveable.Yes)]
[Serialize(1, IsPropertySaveable.Yes, description: "Number of items that need to be fabricated.")]
public int Amount { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the fabricated item(s).")]
@@ -48,7 +51,8 @@ namespace Barotrauma
{
if (!ParentEvent.GetTargets(CharacterTag).Contains(character)) { return; }
}
if (item.ContainerIdentifier == ItemTag || item.HasTag(ItemTag))
if ((!ItemIdentifier.IsEmpty && item.Prefab.Identifier == ItemIdentifier) ||
(!ItemTag.IsEmpty && item.HasTag(ItemTag)))
{
if (!ApplyTagToItem.IsEmpty)
{
@@ -7,30 +7,33 @@ using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Waits for some item(s) to be used before continuing the execution of the event.
/// </summary>
class WaitForItemUsedAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the item that must be used. Note that the item needs to have been tagged by the event - this does not refer to the tags that can be set per-item in the sub editor.")]
public Identifier ItemTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the character that must use the item. If there's multiple matching characters, it's enough if any of them use the item. If empty, it doesn't matter who uses the item.")]
public Identifier UserTag { get; set; }
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("", IsPropertySaveable.Yes, description: "Name of the ItemComponent that the character must use. If empty, the character attempts to use all of them.")]
public Identifier TargetItemComponent { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the target item when it's used.")]
[Serialize("", IsPropertySaveable.Yes, description: "Optional tag to apply to the target item when it's used.")]
public Identifier ApplyTagToItem { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the user when the target item is used.")]
[Serialize("", IsPropertySaveable.Yes, description: "Optional tag to apply to the user when the target item is used.")]
public Identifier ApplyTagToUser{ get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the hull the target item is inside when the item is used.")]
[Serialize("", IsPropertySaveable.Yes, description: "Optional tag to apply to the hull the target item is inside when the item is used.")]
public Identifier ApplyTagToHull { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the hull the target item is inside, and all the hulls it's linked to, when the item is used.")]
[Serialize("", IsPropertySaveable.Yes, description: "Optional tag to apply to the hull the target item is inside, and all the hulls it's linked to, when the item is used.")]
public Identifier ApplyTagToLinkedHulls { get; set; }
[Serialize(1, IsPropertySaveable.Yes)]
[Serialize(1, IsPropertySaveable.Yes, description: "How many times does the item need to be used. Defaults to 1.")]
public int RequiredUseCount { get; set; }
private bool isFinished;
@@ -144,7 +144,7 @@ namespace Barotrauma
public bool Enabled = true;
private MTRandom random;
private int randomSeed;
public int RandomSeed { get; private set; }
public void StartRound(Level level)
{
@@ -171,13 +171,13 @@ namespace Barotrauma
if (level != null)
{
randomSeed = ToolBox.StringToInt(level.Seed);
RandomSeed = ToolBox.StringToInt(level.Seed);
foreach (var previousEvent in level.LevelData.EventHistory)
{
randomSeed ^= ToolBox.IdentifierToInt(previousEvent);
RandomSeed ^= ToolBox.IdentifierToInt(previousEvent);
}
}
random = new MTRandom(randomSeed);
random = new MTRandom(RandomSeed);
bool playingCampaign = GameMain.GameSession?.GameMode is CampaignMode;
EventSet initialEventSet = SelectRandomEvents(
@@ -214,7 +214,7 @@ namespace Barotrauma
var unlockPathEventPrefab = EventPrefab.GetUnlockPathEvent(level.LevelData.Biome.Identifier, level.StartLocation.Faction);
if (unlockPathEventPrefab != null)
{
var newEvent = unlockPathEventPrefab.CreateInstance();
var newEvent = unlockPathEventPrefab.CreateInstance(RandomSeed);
activeEvents.Add(newEvent);
}
else
@@ -250,7 +250,7 @@ namespace Barotrauma
DebugConsole.ThrowError($"Error in EventManager.StartRound - could not find an event with the identifier {id}.");
continue;
}
var ev = eventPrefab.CreateInstance();
var ev = eventPrefab.CreateInstance(RandomSeed);
if (ev != null)
{
QueuedEvents.Enqueue(ev);
@@ -543,9 +543,8 @@ namespace Barotrauma
if (eventPrefabs != null && random.NextDouble() <= probability)
{
var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(e => IsSuitable(e, level)), e => e.Commonness, random);
var newEvent = eventPrefab.CreateInstance();
var newEvent = eventPrefab.CreateInstance(RandomSeed);
if (newEvent == null) { continue; }
newEvent.RandomSeed = randomSeed;
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
DebugConsole.NewMessage($"Initialized event {newEvent}", debugOnly: true);
if (!selectedEvents.ContainsKey(eventSet))
@@ -588,8 +587,9 @@ namespace Barotrauma
if (random.NextDouble() > probability) { continue; }
var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(e => IsSuitable(e, level)), e => e.Commonness, random);
var newEvent = eventPrefab.CreateInstance();
var newEvent = eventPrefab.CreateInstance(RandomSeed);
if (newEvent == null) { continue; }
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
if (!selectedEvents.ContainsKey(eventSet))
{
selectedEvents.Add(eventSet, new List<Event>());
@@ -730,11 +730,9 @@ namespace Barotrauma
float distFromStart = (float)Math.Sqrt(MathUtils.LineSegmentToPointDistanceSquared(level.StartExitPosition.ToPoint(), level.StartPosition.ToPoint(), refEntity.WorldPosition.ToPoint()));
float distFromEnd = (float)Math.Sqrt(MathUtils.LineSegmentToPointDistanceSquared(level.EndExitPosition.ToPoint(), level.EndPosition.ToPoint(), refEntity.WorldPosition.ToPoint()));
//don't create new events if within 50 meters of the start/end of the level
if (!eventSet.AllowAtStart)
{
if (distanceTraveled <= 0.0f ||
distFromStart * Physics.DisplayToRealWorldRatio < 50.0f ||
if (distFromStart * Physics.DisplayToRealWorldRatio < 50.0f ||
distFromEnd * Physics.DisplayToRealWorldRatio < 50.0f)
{
return false;
@@ -767,7 +765,7 @@ namespace Barotrauma
public void Update(float deltaTime)
{
if (!Enabled || level == null) { return; }
if (!Enabled) { return; }
if (GameMain.GameSession.Campaign?.DisableEvents ?? false) { return; }
if (!eventsInitialized)
@@ -871,6 +869,10 @@ namespace Barotrauma
{
pendingEventSets.Add(eventSet);
CreateEvents(eventSet);
foreach (Event newEvent in selectedEvents[eventSet])
{
if (!newEvent.Initialized) { newEvent.Init(eventSet); }
}
};
}
}
@@ -99,19 +99,19 @@ namespace Barotrauma
UnlockPathReputation = element.GetAttributeInt("unlockpathreputation", 0);
}
public bool TryCreateInstance<T>(out T instance) where T : Event
public bool TryCreateInstance<T>(int seed, out T instance) where T : Event
{
instance = CreateInstance() as T;
instance = CreateInstance(seed) as T;
return instance is not null;
}
public Event CreateInstance()
public Event CreateInstance(int seed)
{
ConstructorInfo constructor = EventType.GetConstructor(new[] { GetType() });
ConstructorInfo constructor = EventType.GetConstructor(new[] { GetType(), typeof(int) });
Event instance = null;
try
{
instance = constructor.Invoke(new object[] { this }) as Event;
instance = constructor.Invoke(new object[] { this, seed }) as Event;
}
catch (Exception ex)
{
@@ -372,19 +372,19 @@ namespace Barotrauma
MinDistanceTraveled = element.GetAttributeFloat("mindistancetraveled", 0.0f);
MinMissionTime = element.GetAttributeFloat("minmissiontime", 0.0f);
AllowAtStart = element.GetAttributeBool("allowatstart", false);
AllowAtStart = element.GetAttributeBool("allowatstart", parentSet?.AllowAtStart ?? false);
PerRuin = element.GetAttributeBool("perruin", false);
PerCave = element.GetAttributeBool("percave", false);
PerWreck = element.GetAttributeBool("perwreck", false);
DisableInHuntingGrounds = element.GetAttributeBool("disableinhuntinggrounds", false);
DisableInHuntingGrounds = element.GetAttributeBool("disableinhuntinggrounds", parentSet?.DisableInHuntingGrounds ?? false);
IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? (PerRuin || PerCave || PerWreck));
IgnoreIntensity = element.GetAttributeBool("ignoreintensity", parentSet?.IgnoreIntensity ?? false);
DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", !PerRuin && !PerCave && !PerWreck);
OncePerLevel = element.GetAttributeBool("onceperlevel", element.GetAttributeBool("onceperoutpost", false));
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", parentSet?.DelayWhenCrewAway ?? (!PerRuin && !PerCave && !PerWreck));
OncePerLevel = element.GetAttributeBool("onceperlevel", element.GetAttributeBool("onceperoutpost", parentSet?.OncePerLevel ?? false));
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", parentSet?.TriggerEventCooldown ?? true);
IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost || (parentSet?.IsCampaignSet ?? false));
ResetTime = element.GetAttributeFloat(nameof(ResetTime), parentSet?.ResetTime ?? 0);
CampaignTutorialOnly = element.GetAttributeBool(nameof(CampaignTutorialOnly), false);
CampaignTutorialOnly = element.GetAttributeBool(nameof(CampaignTutorialOnly), parentSet?.CampaignTutorialOnly ?? false);
ForceAtDiscoveredNr = element.GetAttributeInt(nameof(ForceAtDiscoveredNr), -1);
ForceAtVisitedNr = element.GetAttributeInt(nameof(ForceAtVisitedNr), -1);
@@ -449,6 +449,11 @@ namespace Barotrauma
EventPrefabs = eventPrefabs.ToImmutableArray();
ChildSets = childSets.ToImmutableArray();
OverrideCommonness = overrideCommonness.ToImmutableDictionary();
if ((PerRuin && PerCave) || (PerWreck && PerCave) || (PerRuin && PerWreck))
{
DebugConsole.AddWarning($"Error in event set \"{Identifier}\". Only one of the settings {nameof(PerRuin)}, {nameof(PerCave)} or {nameof(PerWreck)} can be enabled at the time.");
}
}
public void CheckLocationTypeErrors()
@@ -560,7 +565,8 @@ namespace Barotrauma
static void AddEvent(EventDebugStats stats, EventPrefab eventPrefab, Func<MonsterEvent, bool> filter = null)
{
if (eventPrefab.EventType == typeof(MonsterEvent) && eventPrefab.TryCreateInstance(out MonsterEvent monsterEvent))
if (eventPrefab.EventType == typeof(MonsterEvent) &&
eventPrefab.TryCreateInstance(GameMain.GameSession?.EventManager?.RandomSeed ?? 0, out MonsterEvent monsterEvent))
{
if (filter != null && !filter(monsterEvent)) { return; }
float spawnProbability = monsterEvent.Prefab?.Probability ?? 0.0f;
@@ -25,8 +25,8 @@ namespace Barotrauma
return "MalfunctionEvent (" + string.Join(", ", targetItemIdentifiers) + ")";
}
public MalfunctionEvent(EventPrefab prefab)
: base(prefab)
public MalfunctionEvent(EventPrefab prefab, int seed)
: base(prefab, seed)
{
targetItems = new List<Item>();
@@ -39,9 +39,8 @@ namespace Barotrauma
targetItemIdentifiers = prefab.ConfigElement.GetAttributeIdentifierArray("itemidentifiers", Array.Empty<Identifier>());
}
public override void Init(EventSet parentSet)
protected override void InitEventSpecific(EventSet parentSet)
{
base.Init(parentSet);
var matchingItems = Item.ItemList.FindAll(i => i.Condition > 0.0f && targetItemIdentifiers.Contains(i.Prefab.Identifier));
int itemAmount = Rand.Range(minItemAmount, maxItemAmount, Rand.RandSync.ServerAndClient);
for (int i = 0; i < itemAmount; i++)
@@ -1,4 +1,4 @@
using Barotrauma.Items.Components;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -137,18 +137,21 @@ namespace Barotrauma
}
}
var monsterSet = ToolBox.SelectWeightedRandom(monsterSets, m => m.Commonness, Rand.RandSync.Unsynced);
foreach ((CharacterPrefab monsterSpecies, Point monsterCountRange) in monsterSet.MonsterPrefabs)
if (monsterSets.Any())
{
int amount = Rand.Range(monsterCountRange.X, monsterCountRange.Y + 1);
for (int i = 0; i < amount; i++)
var monsterSet = ToolBox.SelectWeightedRandom(monsterSets, m => m.Commonness, Rand.RandSync.Unsynced);
foreach ((CharacterPrefab monsterSpecies, Point monsterCountRange) in monsterSet.MonsterPrefabs)
{
CoroutineManager.Invoke(() =>
int amount = Rand.Range(monsterCountRange.X, monsterCountRange.Y + 1);
for (int i = 0; i < amount; i++)
{
//round ended before the coroutine finished
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
Entity.Spawner.AddCharacterToSpawnQueue(monsterSpecies.Identifier, spawnPos);
}, Rand.Range(0f, amount));
CoroutineManager.Invoke(() =>
{
//round ended before the coroutine finished
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
Entity.Spawner.AddCharacterToSpawnQueue(monsterSpecies.Identifier, spawnPos);
}, Rand.Range(0f, amount));
}
}
}
@@ -15,6 +15,9 @@ namespace Barotrauma
private readonly Dictionary<Item, int> inventorySlotIndices = new Dictionary<Item, int>();
private readonly Dictionary<Item, byte> parentItemContainerIndices = new Dictionary<Item, byte>();
/// <summary>
/// Percentage of items (0.0 - 1.0) needed to be delivered to complete the mission.
/// </summary>
private float requiredDeliveryAmount;
private readonly List<(ContentXElement element, ItemContainer container)> itemsToSpawn = new List<(ContentXElement element, ItemContainer container)>();
@@ -86,7 +89,7 @@ namespace Barotrauma
bool isPriorMission = true;
foreach (Mission mission in GameMain.GameSession.StartLocation.SelectedMissions)
{
if (!(mission is CargoMission otherMission)) { continue; }
if (mission is not CargoMission otherMission) { continue; }
if (mission == this) { isPriorMission = false; }
previouslySelectedMissions.Add(otherMission);
if (!isPriorMission) { continue; }
@@ -99,7 +102,8 @@ namespace Barotrauma
{
int maxCount = subElement.GetAttributeInt("maxcount", 10);
if (itemsToSpawn.Count(it => it.element == subElement) >= maxCount) { continue; }
ItemPrefab itemPrefab = FindItemPrefab(subElement);
// For logging purposes
FindItemPrefab(subElement);
while (itemsToSpawn.Count < maxItemCount)
{
itemsToSpawn.Add((subElement, null));
@@ -121,7 +125,7 @@ namespace Barotrauma
bool isPriorMission = true;
foreach (Mission mission in GameMain.GameSession.StartLocation.SelectedMissions)
{
if (!(mission is CargoMission otherMission)) { continue; }
if (mission is not CargoMission otherMission) { continue; }
if (mission == this) { isPriorMission = false; }
previouslySelectedMissions.Add(otherMission);
if (!isPriorMission) { continue; }
@@ -161,27 +165,53 @@ namespace Barotrauma
itemsToSpawn.Add((itemConfig.Elements().First(), null));
}
// Calculate the current total reward, since it might differ from the
// prefab total reward depending on the current actual crate count.
calculatedReward = 0;
bool crateValuesUniform = true;
int? prevCrateReward = null;
foreach (var (element, container) in itemsToSpawn)
{
int price = element.GetAttributeInt("reward", Prefab.Reward / itemsToSpawn.Count);
if (rewardPerCrate.HasValue)
int currentCrateReward = element.GetAttributeInt("reward", 0);
calculatedReward += currentCrateReward;
// Apparently crates can have varying values, so we need to check
// here if that is the case, stopping checks on the first discrepancy
if (crateValuesUniform)
{
if (price != rewardPerCrate.Value) { rewardPerCrate = -1; }
if (prevCrateReward.HasValue)
{
if (prevCrateReward.Value != currentCrateReward)
{
crateValuesUniform = false;
}
}
prevCrateReward = currentCrateReward;
}
else
{
rewardPerCrate = price;
}
calculatedReward += price;
}
if (rewardPerCrate.HasValue && rewardPerCrate < 0) { rewardPerCrate = null; }
if (crateValuesUniform)
{
// If rewardPerCrate is set, it will be displayed in the client UI as eg. "123 mk x 5"
rewardPerCrate = calculatedReward / itemsToSpawn.Count;
}
else
{
// If rewardPerCrate is null, the client UI will display just the total reward
rewardPerCrate = null;
}
// Apply the mission reward campaign setting multiplier to the per-crate price, too
if (GameMain.GameSession?.Campaign is CampaignMode campaign && rewardPerCrate is int confirmedRewardPerCrate)
{
rewardPerCrate = (int)Math.Round(confirmedRewardPerCrate * campaign.Settings.MissionRewardMultiplier);
}
string rewardText = $"‖color:gui.orange‖{string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:N0}", GetReward(currentSub))}‖end‖";
if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
}
public override int GetReward(Submarine sub)
public override int GetBaseReward(Submarine sub)
{
// If we are not at the location of the mission, skip the calculation of the reward
if (GameMain.GameSession?.StartLocation != Locations[0])
@@ -272,7 +302,7 @@ namespace Barotrauma
item.FindHull();
items.Add(item);
if (parent != null && parent.GetComponent<ItemContainer>() != null)
if (parent?.GetComponent<ItemContainer>() != null)
{
parentInventoryIDs.Add(item, parent.ID);
parentItemContainerIndices.Add(item, (byte)parent.GetComponentIndex(parent.GetComponent<ItemContainer>()));
@@ -61,7 +61,7 @@ namespace Barotrauma
if (descriptionWithoutReward != null) { description = descriptionWithoutReward.Replace("[reward]", rewardText); }
}
public override int GetReward(Submarine sub)
public override int GetBaseReward(Submarine sub)
{
if (sub != missionSub)
{
@@ -160,14 +160,13 @@ namespace Barotrauma
if (terroristChance > 0f)
{
int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count);
int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count);
terroristCount = Math.Clamp(terroristCount, 1, characters.Count);
terroristCharacters.Clear();
characters.GetRange(0, terroristCount).ForEach(c => terroristCharacters.Add(c));
terroristCharacters.ForEach(c => c.IsHostileEscortee = true);
terroristDistanceSquared = Vector2.DistanceSquared(Level.Loaded.StartPosition, Level.Loaded.EndPosition) * Rand.Range(0.35f, 0.65f);
#if DEBUG
DebugConsole.AddWarning("Terrorists will trigger at range " + Math.Sqrt(terroristDistanceSquared));
foreach (Character character in terroristCharacters)
@@ -251,6 +250,7 @@ namespace Barotrauma
// decoupled from range check to prevent from weirdness if players handcuff a terrorist and move backwards
foreach (Character character in terroristCharacters)
{
character.IsHostileEscortee = true;
if (character.HasTeamChange(TerroristTeamChangeIdentifier))
{
// already triggered
@@ -259,7 +259,7 @@ namespace Barotrauma
}
else if (owner is Character c)
{
return c.Info != null && GameMain.GameSession.CrewManager.CharacterInfos.Contains(c.Info);
return c.Info != null && GameMain.GameSession.CrewManager.GetCharacterInfos().Contains(c.Info);
}
return false;
}
@@ -183,33 +183,32 @@ namespace Barotrauma
completeCheckDataAction = new CheckDataAction(endConditionElement, $"Mission ({prefab.Identifier})");
}
for (int n = 0; n < 2; n++)
{
string locationName = $"‖color:gui.orange‖{locations[n].DisplayName}‖end‖";
if (description != null) { description = description.Replace("[location" + (n + 1) + "]", locationName); }
if (successMessage != null) { successMessage = successMessage.Replace("[location" + (n + 1) + "]", locationName); }
if (failureMessage != null) { failureMessage = failureMessage.Replace("[location" + (n + 1) + "]", locationName); }
for (int m = 0; m < messages.Length; m++)
{
messages[m] = messages[m].Replace("[location" + (n + 1) + "]", locationName);
}
}
string rewardText = $"‖color:gui.orange‖{string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub))}‖end‖";
if (description != null)
{
descriptionWithoutReward = description;
description = description.Replace("[reward]", rewardText);
}
if (successMessage != null) { successMessage = successMessage.Replace("[reward]", rewardText); }
if (failureMessage != null) { failureMessage = failureMessage.Replace("[reward]", rewardText); }
descriptionWithoutReward = ReplaceVariablesInMissionMessage(description, sub, replaceReward: false);
description = ReplaceVariablesInMissionMessage(description, sub);
successMessage = ReplaceVariablesInMissionMessage(successMessage, sub);
failureMessage = ReplaceVariablesInMissionMessage(failureMessage, sub);
for (int m = 0; m < messages.Length; m++)
{
messages[m] = messages[m].Replace("[reward]", rewardText);
messages[m] = ReplaceVariablesInMissionMessage(messages[m], sub);
}
Messages = messages.ToImmutableArray();
}
public LocalizedString ReplaceVariablesInMissionMessage(LocalizedString message, Submarine sub, bool replaceReward = true)
{
for (int locationIndex = 0; locationIndex < 2; locationIndex++)
{
string locationName = $"‖color:gui.orange‖{Locations[locationIndex].DisplayName}‖end‖";
message = message.Replace("[location" + (locationIndex + 1) + "]", locationName);
}
if (replaceReward)
{
string rewardText = $"‖color:gui.orange‖{string.Format(CultureInfo.InvariantCulture, "{0:N0}", GetReward(sub))}‖end‖";
message = message.Replace("[reward]", rewardText);
}
return message;
}
public virtual void SetLevel(LevelData level) { }
public static Mission LoadRandom(Location[] locations, string seed, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer = false)
@@ -254,11 +253,30 @@ namespace Barotrauma
return null;
}
public virtual int GetReward(Submarine sub)
/// <summary>
/// Calculates the base reward, can be overridden for different mission types
/// </summary>
public virtual int GetBaseReward(Submarine sub)
{
return Prefab.Reward;
}
/// <summary>
/// Calculates the available reward, taking into account universal modifiers such as campaign settings
/// </summary>
public int GetReward(Submarine sub)
{
int reward = GetBaseReward(sub);
// Some modifiers should apply universally to all implementations of GetBaseReward
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
{
reward = (int)Math.Round(reward * campaign.Settings.MissionRewardMultiplier);
}
return reward;
}
public void Start(Level level)
{
state = 0;
@@ -353,7 +371,7 @@ namespace Barotrauma
}
if (GameMain.GameSession?.EventManager != null)
{
var newEvent = eventPrefab.CreateInstance();
var newEvent = eventPrefab.CreateInstance(GameMain.GameSession.EventManager.RandomSeed);
GameMain.GameSession.EventManager.ActivateEvent(newEvent);
}
}
@@ -455,9 +473,13 @@ namespace Barotrauma
foreach (var reputationReward in ReputationRewards)
{
var reputationGainMultiplier = new AbilityMissionReputationGainMultiplier(this, 1f, character: null);
foreach (var c in crewCharacters) { c.CheckTalents(AbilityEffectType.OnCrewGainMissionReputation, reputationGainMultiplier); }
float amount = reputationReward.Amount * reputationGainMultiplier.Value;
if (reputationReward.FactionIdentifier == "location")
{
OriginLocation.Reputation?.AddReputation(reputationReward.Amount);
OriginLocation.Reputation?.AddReputation(amount);
TryGiveReputationForOpposingFaction(OriginLocation.Faction, reputationReward.AmountForOpposingFaction);
}
else
@@ -465,7 +487,7 @@ namespace Barotrauma
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier == reputationReward.FactionIdentifier);
if (faction != null)
{
faction.Reputation.AddReputation(reputationReward.Amount);
faction.Reputation.AddReputation(amount);
TryGiveReputationForOpposingFaction(faction, reputationReward.AmountForOpposingFaction);
}
}
@@ -664,5 +686,19 @@ namespace Barotrauma
public Mission Mission { get; set; }
public Character Character { get; set; }
}
class AbilityMissionReputationGainMultiplier : AbilityObject, IAbilityValue, IAbilityMission, IAbilityCharacter
{
public AbilityMissionReputationGainMultiplier(Mission mission, float reputationMultiplier, Character character)
{
Value = reputationMultiplier;
Mission = mission;
Character = character;
}
public float Value { get; set; }
public Mission Mission { get; set; }
public Character Character { get; set; }
}
}
@@ -110,6 +110,7 @@ namespace Barotrauma
public readonly int Reward;
// The titles and bodies of the popup messages during the mission, shown when the state of the mission changes. The order matters.
public readonly ImmutableArray<LocalizedString> Headers;
public readonly ImmutableArray<LocalizedString> Messages;
@@ -187,23 +188,25 @@ namespace Barotrauma
Tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToImmutableHashSet();
string nameTag = element.GetAttributeString("name", "");
Name = TextManager.Get($"MissionName.{TextIdentifier}");
if (!string.IsNullOrEmpty(nameTag))
{
Name = Name
.Fallback(TextManager.Get(nameTag))
.Fallback(nameTag);
}
Name = GetText(element.GetAttributeString("name", ""), "MissionName");
Description = GetText(element.GetAttributeString("description", ""), "MissionDescription");
string descriptionTag = element.GetAttributeString("description", "");
Description =
TextManager.Get($"MissionDescription.{TextIdentifier}");
if (!string.IsNullOrEmpty(descriptionTag))
LocalizedString GetText(string textTag, string textTagPrefix)
{
Description = Description
.Fallback(TextManager.Get(descriptionTag))
.Fallback(descriptionTag);
if (string.IsNullOrEmpty(textTag))
{
return TextManager.Get($"{textTagPrefix}.{TextIdentifier}");
}
else
{
return
//prefer finding a text based on the specific text tag defined in the mission config
TextManager.Get(textTag)
//2nd option: the "default" format (MissionName.SomeMission)
.Fallback(TextManager.Get($"{textTagPrefix}.{TextIdentifier}"))
//last option: use the text in the xml as-is with no localization
.Fallback(textTag);
}
}
Reward = element.GetAttributeInt("reward", 1);
@@ -372,6 +375,12 @@ namespace Barotrauma
DebugConsole.ThrowErrorLocalized("Error in mission prefab \"" + Name + "\" - mission type cannot be none.");
return;
}
#if DEBUG
if (Type == MissionType.Monster && SonarLabel.IsNullOrEmpty())
{
DebugConsole.AddWarning($"Potential error in mission prefab \"{Identifier}\" - sonar label not set.");
}
#endif
if (CoOpMissionClasses.ContainsKey(Type))
{
@@ -68,7 +68,7 @@ namespace Barotrauma
}
}
public override int GetReward(Submarine sub)
public override int GetBaseReward(Submarine sub)
{
return alternateReward;
}
@@ -262,13 +262,13 @@ namespace Barotrauma
enemySub.EnableMaintainPosition();
enemySub.TeamID = CharacterTeamType.None;
//make the enemy sub withstand atleast the same depth as the player sub
enemySub.RealWorldCrushDepth = Math.Max(enemySub.RealWorldCrushDepth, Submarine.MainSub.RealWorldCrushDepth);
enemySub.SetCrushDepth(Math.Max(enemySub.RealWorldCrushDepth, Submarine.MainSub.RealWorldCrushDepth));
if (Level.Loaded != null)
{
//...and the depth of the patrol positions + 1000 m
foreach (var patrolPos in patrolPositions)
{
enemySub.RealWorldCrushDepth = Math.Max(enemySub.RealWorldCrushDepth, Level.Loaded.GetRealWorldDepth(patrolPos.Y) + 1000);
enemySub.SetCrushDepth(Math.Max(enemySub.RealWorldCrushDepth, Level.Loaded.GetRealWorldDepth(patrolPos.Y) + 1000));
}
}
enemySub.ImmuneToBallastFlora = true;
@@ -394,11 +394,11 @@ namespace Barotrauma
DebugConsole.NewMessage("Patrol pos: " + patrolPos);
}
#endif
enemySub.SetPosition(spawnPos);
if (!IsClient)
{
InitPirateShip();
}
enemySub.SetPosition(spawnPos);
// flipping the sub on the frame it is moved into place must be done after it's been moved, or it breaks item connections in the submarine
// creating the pirates has to be done after the sub has been flipped, or it seems to break the AI pathing
@@ -10,10 +10,13 @@ namespace Barotrauma
{
partial class SalvageMission : Mission
{
private class Target
{
public Item Item;
/// <summary>
/// The target this item spawns inside (usually a crate for example).
/// </summary>
public Target ParentTarget;
/// <summary>
/// Note that the integer values matter here:
@@ -29,14 +32,20 @@ namespace Barotrauma
}
public readonly ItemPrefab ItemPrefab;
/// <summary>
/// Where the target can be spawned to. E.g. MainPath or Wreck.
/// </summary>
public readonly Level.PositionType SpawnPositionType;
public readonly Identifier ContainerTag;
public readonly Identifier ExistingItemTag;
public readonly bool RemoveItem;
public readonly LocalizedString SonarLabel;
/// <summary>
/// Can the mission continue before this target has been retrieved? Can be used if you want the targets to be retrieved in a specific order.
/// </summary>
public readonly bool AllowContinueBeforeRetrieved;
/// <summary>
@@ -51,6 +60,13 @@ namespace Barotrauma
{
get
{
//if placing the item inside the parent (e.g. some item inside a crate) failed,
//consider this item retrieved (= essentially ignoring the item, it's not necessary to retrieve)
if (PlacingInsideParentTargetFailed)
{
return true;
}
return RequiredRetrievalState switch
{
RetrievalState.None => true,
@@ -78,20 +94,29 @@ namespace Barotrauma
public bool Interacted;
private readonly SalvageMission mission;
public readonly bool RequireInsideOriginalContainer;
public Item OriginalContainer;
/// <summary>
/// Means that the item could not be placed inside the container it was intended to spawn inside (probably meaning the mission has been misconfigured to e.g. spawn more items inside a crate than what the crate can hold).
/// </summary>
public bool PlacingInsideParentTargetFailed;
/// <summary>
/// Status effects executed on the target item when the mission starts. A random effect is chosen from each child list.
/// </summary>
public readonly List<List<StatusEffect>> StatusEffects = new List<List<StatusEffect>>();
public Target(ContentXElement element, SalvageMission mission)
public Target(ContentXElement element, SalvageMission mission, Target parentTarget)
{
this.mission = mission;
ParentTarget = parentTarget;
ContainerTag = element.GetAttributeIdentifier("containertag", Identifier.Empty);
RequiredRetrievalState = element.GetAttributeEnum("requireretrieval", RetrievalState.RetrievedToSub);
AllowContinueBeforeRetrieved = element.GetAttributeBool("allowcontinuebeforeretrieved", false);
HideLabelAfterRetrieved = element.GetAttributeBool("hidelabelafterretrieved", false);
RequiredRetrievalState = element.GetAttributeEnum("requireretrieval", parentTarget?.RequiredRetrievalState ?? RetrievalState.RetrievedToSub);
AllowContinueBeforeRetrieved = element.GetAttributeBool("allowcontinuebeforeretrieved", parentTarget != null);
HideLabelAfterRetrieved = element.GetAttributeBool("hidelabelafterretrieved", parentTarget?.HideLabelAfterRetrieved ?? false);
RequireInsideOriginalContainer = element.GetAttributeBool("requireinsideoriginalcontainer", false);
string sonarLabelTag = element.GetAttributeString("sonarlabel", "");
if (!string.IsNullOrEmpty(sonarLabelTag))
{
@@ -126,6 +151,7 @@ namespace Barotrauma
if (ItemPrefab == null)
{
string itemTag = element.GetAttributeString("itemtag", "");
//NOTE: using unsynced random here is fine, the clients receive the info of what item spawned from the server
ItemPrefab = MapEntityPrefab.GetRandom(p => p.Tags.Contains(itemTag), Rand.RandSync.Unsynced) as ItemPrefab;
}
if (ItemPrefab == null && ExistingItemTag.IsEmpty)
@@ -135,7 +161,7 @@ namespace Barotrauma
}
}
SpawnPositionType = element.GetAttributeEnum("spawntype", Level.PositionType.Cave | Level.PositionType.Ruin);
SpawnPositionType = element.GetAttributeEnum("spawntype", parentTarget?.SpawnPositionType ?? (Level.PositionType.Cave | Level.PositionType.Ruin));
foreach (var subElement in element.Elements())
{
@@ -149,12 +175,15 @@ namespace Barotrauma
break;
}
case "chooserandom":
StatusEffects.Add(new List<StatusEffect>());
foreach (var effectElement in subElement.Elements())
if (subElement.Elements().Any(static e => e.NameAsIdentifier() == "statuseffect"))
{
var newEffect = StatusEffect.Load(effectElement, parentDebugName: mission.Prefab.Name.Value);
if (newEffect == null) { continue; }
StatusEffects.Last().Add(newEffect);
StatusEffects.Add(new List<StatusEffect>());
foreach (var effectElement in subElement.Elements())
{
var newEffect = StatusEffect.Load(effectElement, parentDebugName: mission.Prefab.Name.Value);
if (newEffect == null) { continue; }
StatusEffects.Last().Add(newEffect);
}
}
break;
}
@@ -170,7 +199,24 @@ namespace Barotrauma
private readonly List<Target> targets = new List<Target>();
public bool AnyTargetNeedsToBeRetrievedToSub => targets.Any(t => t.RequiredRetrievalState == Target.RetrievalState.RetrievedToSub && !t.Retrieved);
/// <summary>
/// What percentage of targets need to be retrieved for the mission to complete (0.0 - 1.0). Defaults to 0.98.
/// </summary>
private readonly float requiredDeliveryAmount;
/// <summary>
/// Message displayed when at least one of the targets is retrieved, but the mission is not complete yet.
/// </summary>
private LocalizedString partiallyRetrievedMessage;
/// <summary>
/// Message displayed when all targets have been retrieved.
/// </summary>
private LocalizedString allRetrievedMessage;
public bool AnyTargetNeedsToBeRetrievedToSub => targets.Any(static t => t.RequiredRetrievalState == Target.RetrievalState.RetrievedToSub && !t.Retrieved);
private readonly MTRandom rng;
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
{
@@ -179,8 +225,23 @@ namespace Barotrauma
foreach (var target in targets)
{
if (target.Retrieved && target.HideLabelAfterRetrieved) { continue; }
if (target.Item != null)
if (target.Item != null && !target.Item.Removed)
{
if (target.Item.ParentInventory?.Owner is Item parentItem)
{
bool insideParentItem = false;
foreach (var parentTarget in targets)
{
if (parentTarget.Item == parentItem && !parentTarget.SonarLabel.IsNullOrEmpty())
{
insideParentItem = true;
break;
}
}
//if the item is inside another target that has it's own sonar label, no need to show one on this item
if (insideParentItem) { continue; }
}
yield return (
target.SonarLabel ?? Prefab.SonarLabel,
target.Item.GetRootInventoryOwner()?.WorldPosition ?? target.Item.WorldPosition);
@@ -193,17 +254,82 @@ namespace Barotrauma
public SalvageMission(MissionPrefab prefab, Location[] locations, Submarine sub)
: base(prefab, locations, sub)
{
requiredDeliveryAmount = prefab.ConfigElement.GetAttributeFloat(nameof(requiredDeliveryAmount), 0.98f);
//LevelData may not be instantiated at this point, in that case use the name identifier of the location
rng = new MTRandom(ToolBox.StringToInt(
locations[0].LevelData?.Seed ?? locations[0].NameIdentifier.Value +
locations[1].LevelData?.Seed ?? locations[1].NameIdentifier.Value));
partiallyRetrievedMessage = GetMessage(nameof(partiallyRetrievedMessage));
allRetrievedMessage = GetMessage(nameof(allRetrievedMessage));
foreach (ContentXElement subElement in prefab.ConfigElement.Elements())
{
if (subElement.NameAsIdentifier() == "target")
if (subElement.NameAsIdentifier() == "target" ||
subElement.NameAsIdentifier() == "chooserandom")
{
targets.Add(new Target(subElement, this));
LoadTarget(subElement, parentTarget: null);
}
}
if (!targets.Any())
{
targets.Add(new Target(prefab.ConfigElement, this));
targets.Add(new Target(prefab.ConfigElement, this, parentTarget: null));
}
LocalizedString GetMessage(string attributeName)
{
if (prefab.ConfigElement.GetAttribute(attributeName) != null)
{
string msgTag = prefab.ConfigElement.GetAttributeString(attributeName, string.Empty);
return ReplaceVariablesInMissionMessage(TextManager.Get(msgTag).Fallback(msgTag), sub);
}
return string.Empty;
}
}
private void LoadTarget(ContentXElement element, Target parentTarget)
{
ContentXElement chosenElement = element;
if (element.NameAsIdentifier() == "chooserandom")
{
/* chooserandom in this context can be used to choose either between targets or status effects to apply to the target,
ensure we don't try to load a statuseffect as a "child target" */
if (element.Elements().Any(static e => e.NameAsIdentifier() == "statuseffect"))
{
return;
}
//this needs to be deterministic, use RNG with a specific seed
chosenElement = element.Elements().ToList().GetRandom(rng);
}
int amount = GetAmount(chosenElement);
for (int i = 0; i < amount; i++)
{
var target = new Target(chosenElement, this, parentTarget);
targets.Add(target);
foreach (ContentXElement subElement in chosenElement.Elements())
{
LoadTarget(subElement, parentTarget: target);
}
}
}
private int GetAmount(ContentXElement targetElement)
{
int amount = targetElement.GetAttributeInt("amount", 1);
int minAmount = targetElement.GetAttributeInt("minamount", amount);
int maxAmount = targetElement.GetAttributeInt("maxamount", amount);
// if the amount is a range, pick a random value between minAmount and maxAmount
if (minAmount < maxAmount)
{
//this needs to be deterministic, use RNG with a specific seed
amount = rng.Next(minAmount, maxAmount + 1);
}
return amount;
}
protected override void StartMissionSpecific(Level level)
@@ -294,8 +420,16 @@ namespace Barotrauma
continue;
}
target.Item = new Item(target.ItemPrefab, position, null);
target.Item.body.SetTransformIgnoreContacts(target.Item.body.SimPosition, target.Item.body.Rotation);
target.Item.body.FarseerBody.BodyType = BodyType.Kinematic;
#if CLIENT
target.Item.HighlightColor = GUIStyle.Orange;
target.Item.ExternalHighlight = true;
#endif
target.Item.UpdateTransform();
if (target.Item.CurrentHull == null)
{
//prevent the body from moving if it spawned outside the hulls (we don't want it e.g. falling to the bottom of a cave or into the abyss)
target.Item.body.FarseerBody.BodyType = BodyType.Kinematic;
}
}
else if (target.RequiredRetrievalState == Target.RetrievalState.Interact)
{
@@ -344,6 +478,7 @@ namespace Barotrauma
}
if (validContainers.Any())
{
//NOTE: using unsynced random here is fine, clients don't run this logic but rely on where the server places the item
var selectedContainer = validContainers.GetRandomUnsynced();
if (selectedContainer.Combine(target.Item, user: null))
{
@@ -362,6 +497,40 @@ namespace Barotrauma
new SpawnInfo(usedExistingItem, originalInventoryID, originalItemContainerIndex, originalSlotIndex, executedEffectIndices));
#endif
}
if (!IsClient)
{
// after spawning all the items from prefabs, need to find all targets where parentTarget is defined, and set the item inside parent target container (if applicable)
foreach (var target in targets)
{
if (target.ParentTarget == null) { continue; }
if (target.Item == null)
{
DebugConsole.ThrowError("Error in salvage mission " + Prefab.Identifier + " (item was null)",
contentPackage: Prefab.ContentPackage);
continue;
}
if (target.ParentTarget.Item == null)
{
DebugConsole.ThrowError("Error in salvage mission " + Prefab.Identifier + " (parent item was null)",
contentPackage: Prefab.ContentPackage);
continue;
}
if (target.ParentTarget.Item.GetComponent<ItemContainer>() is ItemContainer container)
{
if (!container.Inventory.TryPutItem(target.Item, user: null))
{
DebugConsole.ThrowError($"Error in salvage mission {Prefab.Identifier}: failed to put the item {target.Item.Name} inside {target.ParentTarget.Item.Name}.",
contentPackage: Prefab.ContentPackage);
target.PlacingInsideParentTargetFailed = true;
}
target.OriginalContainer = target.ParentTarget.Item;
}
}
}
}
protected override void UpdateMissionSpecific(float deltaTime)
@@ -376,6 +545,7 @@ namespace Barotrauma
if (IsClient) { return; }
bool atLeastOneTargetWasRetrieved = false;
for (int i = 0; i < targets.Count; i++)
{
var target = targets[i];
@@ -388,6 +558,10 @@ namespace Barotrauma
#endif
return;
}
Entity rootInventoryOwner = target.Item.GetRootInventoryOwner();
Submarine parentSub = target.Item.CurrentHull?.Submarine ?? rootInventoryOwner?.Submarine;
bool inPlayerSub = parentSub != null && parentSub.Info.Type == SubmarineType.Player;
switch (target.State)
{
case Target.RetrievalState.None:
@@ -401,16 +575,16 @@ namespace Barotrauma
{
TrySetRetrievalState(Target.RetrievalState.PickedUp);
}
if (inPlayerSub)
{
TrySetRetrievalState(Target.RetrievalState.RetrievedToSub);
}
}
break;
case Target.RetrievalState.PickedUp:
case Target.RetrievalState.RetrievedToSub:
{
Entity rootInventoryOwner = target.Item.GetRootInventoryOwner();
Submarine parentSub = target.Item.CurrentHull?.Submarine ?? rootInventoryOwner?.Submarine;
bool inPlayerSub = parentSub != null && parentSub.Info.Type == SubmarineType.Player;
bool inPlayerInventory = false;
bool playerInFriendlySub = false;
if (rootInventoryOwner is Character character && character.TeamID == CharacterTeamType.Team1)
@@ -441,33 +615,70 @@ namespace Barotrauma
if (retrievalState < target.State || target.State == retrievalState) { return; }
bool wasRetrieved = target.Retrieved;
target.State = retrievalState;
//increment the mission state if the target became retrieved
if (!wasRetrieved && target.Retrieved) { State = i + 1; }
//increment the mission state if the target became retrieved
if (!wasRetrieved && target.Retrieved)
{
State = Math.Max(i + 1, State);
atLeastOneTargetWasRetrieved = true;
}
}
}
#if CLIENT
if (atLeastOneTargetWasRetrieved)
{
TryShowRetrievedMessage();
}
#endif
if (targets.All(t => t.Retrieved))
{
State = targets.Count + 1;
}
}
}
protected override bool DetermineCompleted()
{
return targets.All(t => t.State >= t.RequiredRetrievalState);
if (requiredDeliveryAmount < 1.0f)
{
return targets.Count(t => IsTargetRetrieved(t)) / (float)targets.Count >= requiredDeliveryAmount;
}
else
{
return targets.All(IsTargetRetrieved);
}
static bool IsTargetRetrieved(Target target)
{
if (target.State < target.RequiredRetrievalState) { return false; }
if (target.RequireInsideOriginalContainer)
{
if (target.Item.ParentInventory != target.OriginalContainer?.OwnInventory) { return false; }
}
return true;
}
}
protected override void EndMissionSpecific(bool completed)
{
//consider failed (can't attempt again) if we picked up any of the items but failed to bring them out of the level
failed = !completed && targets.Any(t => t.State >= Target.RetrievalState.PickedUp);
List<Target> targetsToRemove = new List<Target>();
foreach (var target in targets)
{
if (target.RemoveItem)
if (target.RemoveItem ||
/*remove the target if it's inside another target that's set to be removed (e.g. inside the crate it spawned in)*/
targets.Any(t => t.RemoveItem && target.Item?.ParentInventory?.Owner as Item == t.Item))
{
target.Item?.Remove();
target.Reset();
targetsToRemove.Add(target);
}
}
foreach (var target in targetsToRemove)
{
if (target.Item != null && !target.Item.Removed)
{
target.Item.Remove();
}
target.Reset();
}
}
}
}
@@ -99,8 +99,8 @@ namespace Barotrauma
}
}
public MonsterEvent(EventPrefab prefab)
: base(prefab)
public MonsterEvent(EventPrefab prefab, int seed)
: base(prefab, seed)
{
string speciesFile = prefab.ConfigElement.GetAttributeString("characterfile", "");
CharacterPrefab characterPrefab = CharacterPrefab.FindByFilePath(speciesFile);
@@ -173,9 +173,8 @@ namespace Barotrauma
}
}
public override void Init(EventSet parentSet)
protected override void InitEventSpecific(EventSet parentSet)
{
base.Init(parentSet);
if (parentSet != null && resetTime == 0)
{
// Use the parent reset time only if there's no reset time defined for the event.
@@ -192,7 +191,7 @@ namespace Barotrauma
int amount = Rand.Range(MinAmount, MaxAmount + 1);
for (int i = 0; i < amount; i++)
{
string seed = Level.Loaded.Seed + i.ToString();
string seed = i.ToString() + Level.Loaded.Seed;
Character createdCharacter = Character.Create(SpeciesName, Vector2.Zero, seed, characterInfo: null, isRemotePlayer: false, hasAi: true, createNetworkEvent: true, throwErrorIfNotFound: false);
if (createdCharacter == null)
{
@@ -271,14 +270,18 @@ namespace Barotrauma
spawnPos = Vector2.Zero;
var availablePositions = GetAvailableSpawnPositions();
chosenPosition = new Level.InterestingPosition(Point.Zero, Level.PositionType.MainPath, isValid: false);
bool isRuinOrWreck = SpawnPosType.HasFlag(Level.PositionType.Ruin) || SpawnPosType.HasFlag(Level.PositionType.Wreck);
if (affectSubImmediately && !isRuinOrWreck && !SpawnPosType.HasFlag(Level.PositionType.Abyss))
bool isRuinOrWreckOrCave =
SpawnPosType.HasFlag(Level.PositionType.Ruin) ||
SpawnPosType.HasFlag(Level.PositionType.Wreck) ||
SpawnPosType.HasFlag(Level.PositionType.Cave) ||
SpawnPosType.HasFlag(Level.PositionType.AbyssCave);
if (affectSubImmediately && !isRuinOrWreckOrCave && !SpawnPosType.HasFlag(Level.PositionType.Abyss))
{
if (availablePositions.None())
{
//no suitable position found, disable the event
spawnPos = null;
Finish();
disallowed = true;
return;
}
Submarine refSub = GetReferenceSub();
@@ -348,7 +351,7 @@ namespace Barotrauma
}
else
{
if (!isRuinOrWreck)
if (!isRuinOrWreckOrCave)
{
float minDistance = 20000;
for (int i = 0; i < Submarine.MainSubs.Length; i++)
@@ -361,7 +364,7 @@ namespace Barotrauma
{
//no suitable position found, disable the event
spawnPos = null;
Finish();
disallowed = true;
return;
}
chosenPosition = availablePositions.GetRandomUnsynced();
@@ -371,21 +374,17 @@ namespace Barotrauma
spawnPos = chosenPosition.Position.ToVector2();
if (chosenPosition.Submarine != null || chosenPosition.Ruin != null)
{
bool ignoreSubmarine = chosenPosition.Ruin != null;
var spawnPoint = WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine, useSyncedRand: false, spawnPointTag: spawnPointTag, ignoreSubmarine: ignoreSubmarine);
var spawnPoint = WayPoint.GetRandom(SpawnType.Enemy, sub: chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine, useSyncedRand: false, spawnPointTag: spawnPointTag);
if (spawnPoint != null)
{
if (!ignoreSubmarine)
{
System.Diagnostics.Debug.Assert(spawnPoint.Submarine == chosenPosition.Submarine);
}
System.Diagnostics.Debug.Assert(spawnPoint.Submarine == (chosenPosition.Submarine ?? chosenPosition.Ruin?.Submarine));
spawnPos = spawnPoint.WorldPosition;
}
else
{
//no suitable position found, disable the event
spawnPos = null;
Finish();
disallowed = true;
return;
}
}
@@ -422,7 +421,7 @@ namespace Barotrauma
{
//no suitable position found, disable the event
spawnPos = null;
Finish();
disallowed = true;
return;
}
}
@@ -442,11 +441,7 @@ namespace Barotrauma
public override void Update(float deltaTime)
{
if (disallowed)
{
Finish();
return;
}
if (disallowed) { return; }
if (resetTimer > 0)
{
@@ -483,8 +478,8 @@ namespace Barotrauma
}
FindSpawnPosition(affectSubImmediately: true);
//the event gets marked as finished if a spawn point is not found
if (isFinished) { return; }
//the event gets marked as disallowed if a spawn point is not found
if (isFinished || disallowed) { return; }
spawnPending = true;
}
@@ -493,7 +488,7 @@ namespace Barotrauma
System.Diagnostics.Debug.Assert(spawnPos.HasValue);
if (spawnPos == null)
{
Finish();
disallowed = true;
return;
}
//wait until there are no submarines at the spawnpos
@@ -567,7 +562,7 @@ namespace Barotrauma
if (CheckLineOfSight(from, to, chosenPosition.Submarine))
{
// Line of sight to a player character -> don't spawn. Disable the event to prevent monsters "magically" spawning here.
Finish();
disallowed = true;
return;
}
}
@@ -636,7 +631,7 @@ namespace Barotrauma
scatterAmount = scatter;
}
}
else if (!SpawnPosType.HasFlag(Level.PositionType.MainPath))
else if (SpawnPosType.IsIndoorsArea())
{
scatterAmount = 0;
}
@@ -650,22 +645,46 @@ namespace Barotrauma
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
if (monster.Removed) { return; }
System.Diagnostics.Debug.Assert(GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer, "Clients should not create monster events.");
Vector2 pos = spawnPos.Value + Rand.Vector(scatterAmount);
Vector2 pos = spawnPos.Value;
if (scatterAmount > 0)
{
if (Submarine.Loaded.Any(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(pos)))
//try finding an offset position that's not inside a wall
int tries = 10;
do
{
// Can't use the offset position, let's use the exact spawn position.
pos = spawnPos.Value;
}
else if (Level.Loaded.Ruins.Any(r => ToolBox.GetWorldBounds(r.Area.Center, r.Area.Size).ContainsWorld(pos)))
{
// Can't use the offset position, let's use the exact spawn position.
pos = spawnPos.Value;
}
tries--;
pos = spawnPos.Value + Rand.Vector(Rand.Range(0.0f, scatterAmount));
bool isValidPos = true;
if (Submarine.Loaded.Any(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(pos)) ||
Level.Loaded.Ruins.Any(r => ToolBox.GetWorldBounds(r.Area.Center, r.Area.Size).ContainsWorld(pos)) ||
Level.Loaded.IsPositionInsideWall(pos))
{
isValidPos = false;
}
else if (SpawnPosType.HasFlag(Level.PositionType.Cave) || SpawnPosType.HasFlag(Level.PositionType.AbyssCave))
{
//trying to spawn in a cave, but the position is not inside a cave -> not valid
if (Level.Loaded.Caves.None(c => c.Area.Contains(pos)))
{
isValidPos = false;
}
}
if (isValidPos)
{
//not inside anything, all good!
break;
}
// This was the last try and couldn't find an offset position, let's use the exact spawn position.
if (tries == 0)
{
pos = spawnPos.Value;
}
} while (tries > 0);
}
monster.Enabled = true;
@@ -51,7 +51,7 @@ namespace Barotrauma
return $"{nameof(ScriptedEvent)} ({prefab.Identifier})";
}
public ScriptedEvent(EventPrefab prefab) : base(prefab)
public ScriptedEvent(EventPrefab prefab, int seed) : base(prefab, seed)
{
foreach (var element in prefab.ConfigElement.Elements())
{