Unstable 1.8.4.0

This commit is contained in:
Markus Isberg
2025-03-12 12:56:27 +00:00
parent a4c3e868e4
commit a4a3427e4e
627 changed files with 29860 additions and 10018 deletions
@@ -0,0 +1,84 @@
namespace Barotrauma
{
/// <summary>
/// Modifies the win score of a team in the PvP mode.
/// </summary>
class AddScoreAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes, description: "Tag of a target (character) whose team the score should be given to.")]
public Identifier TargetTag { get; set; }
[Serialize(CharacterTeamType.None, IsPropertySaveable.Yes, description: $"Which team's score to add to? Ignored if {nameof(TargetTag)} is set.")]
public CharacterTeamType Team { get; set; }
[Serialize(1, IsPropertySaveable.Yes, description: "How much to add to the score? Can also be negative.")]
public int Amount { get; set; }
public AddScoreAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
{
if (Amount == 0)
{
DebugConsole.ThrowError($"Error in {nameof(AddScoreAction)}, event {parentEvent.Prefab.Identifier}: score set to 0, the action will do nothing.", contentPackage: element.ContentPackage);
}
if (TargetTag.IsEmpty && Team == CharacterTeamType.None)
{
DebugConsole.ThrowError($"Error in {nameof(AddScoreAction)}, event {parentEvent.Prefab.Identifier}: neither {nameof(Team)} or {nameof(TargetTag)} is set.", contentPackage: element.ContentPackage);
}
}
private bool isFinished = false;
public override bool IsFinished(ref string goTo)
{
return isFinished;
}
public override void Reset()
{
isFinished = false;
}
public override void Update(float deltaTime)
{
if (isFinished) { return; }
CharacterTeamType targetTeam = CharacterTeamType.None;
if (TargetTag.IsEmpty)
{
targetTeam = Team;
}
else
{
foreach (var target in ParentEvent.GetTargets(TargetTag))
{
if (target is Character character)
{
targetTeam = character.TeamID;
break;
}
}
}
if (targetTeam == CharacterTeamType.None) { return; }
#if SERVER
if (GameMain.GameSession?.Missions is { } missions)
{
foreach (var mission in missions)
{
if (mission is CombatMission combatMission)
{
combatMission.AddToScore(targetTeam, Amount);
}
}
}
#endif
isFinished = true;
}
public override string ToDebugString()
{
string target = TargetTag.IsEmpty ? $"team: {Team.ColorizeObject()}" : $"target: {TargetTag}";
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(AddScoreAction)} -> ({target}, amount: {Amount.ColorizeObject()})";
}
}
}
@@ -25,6 +25,9 @@ namespace Barotrauma
[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; }
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the target (or all targets if there's multiple) when the check succeeds.")]
public Identifier ApplyTagToTarget { get; set; }
public CheckConditionalAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
{
@@ -45,7 +48,6 @@ namespace Barotrauma
foreach (ContentXElement subElement in conditionalElements)
{
conditionalList.AddRange(PropertyConditional.FromXElement(subElement));
break;
}
Conditionals = conditionalList.ToImmutableArray();
}
@@ -85,7 +87,7 @@ namespace Barotrauma
{
foreach (var target in targets)
{
ApplyTagsToHulls(target as Entity, ApplyTagToHull, ApplyTagToLinkedHulls);
ApplyTagsToTarget(target);
}
return true;
}
@@ -96,12 +98,21 @@ namespace Barotrauma
{
if (ConditionalsMatch(target))
{
ApplyTagsToTarget(target);
success = true;
ApplyTagsToHulls(target as Entity, ApplyTagToHull, ApplyTagToLinkedHulls);
}
}
return success;
}
void ApplyTagsToTarget(ISerializableEntity target)
{
if (!ApplyTagToTarget.IsEmpty)
{
ParentEvent.AddTarget(ApplyTagToTarget, target as Entity);
}
ApplyTagsToHulls(target as Entity, ApplyTagToHull, ApplyTagToLinkedHulls);
}
}
private bool ConditionalsMatch(ISerializableEntity target)
@@ -200,6 +200,10 @@ namespace Barotrauma
{
condition = $"{value1.ColorizeObject()} {Operator.ColorizeObject()} {value2.ColorizeObject()}";
}
else if (!Identifier.IsEmpty)
{
condition = $"{Identifier} {Condition}".ColorizeObject();
}
return $"{ToolBox.GetDebugSymbol(succeeded.HasValue)} {nameof(CheckDataAction)} -> (Data: {Identifier.ColorizeObject()}, Success: {succeeded.ColorizeObject()}, Expression: {condition})";
}
@@ -66,13 +66,12 @@ namespace Barotrauma
public CheckItemAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
{
itemIdentifierSplit = ItemIdentifiers.Split(',').ToIdentifiers();
itemTags = ItemTags.Split(",").ToIdentifiers();
itemIdentifierSplit = ItemIdentifiers.ToIdentifiers().ToArray();
itemTags = ItemTags.ToIdentifiers().ToArray();
var conditionalList = new List<PropertyConditional>();
foreach (ContentXElement subElement in element.GetChildElements("conditional"))
{
conditionalList.AddRange(PropertyConditional.FromXElement(subElement));
break;
}
conditionals = conditionalList;
@@ -50,7 +50,7 @@ namespace Barotrauma
{
if (!AllowSameEntity && entity == target) { continue; }
if (Vector2.DistanceSquared(target.WorldPosition, entity.WorldPosition) > MaxDistance * MaxDistance) { continue; }
if (Character.IsTargetVisible(target, entity, seeThroughWindows: true, CheckFacing))
if (ISpatialEntity.IsTargetVisible(target, entity, seeThroughWindows: true, CheckFacing))
{
if (!ApplyTagToEntity.IsEmpty)
{
@@ -84,6 +84,8 @@ namespace Barotrauma
//an identifier the server uses to identify which ConversationAction a client is responding to
public readonly UInt16 Identifier;
private float startDelay;
private int selectedOption = -1;
private bool dialogOpened = false;
@@ -113,13 +115,15 @@ namespace Barotrauma
Text = elem.GetAttributeString("tag", string.Empty);
textElement = elem;
}
}
if (element.GetChildElement("Replace") != null)
{
DebugConsole.ThrowError(
$"Error in {nameof(EventObjectiveAction)} in the event \"{parentEvent.Prefab.Identifier}\"" +
$" - unrecognized child element \"Replace\".",
contentPackage: element.ContentPackage);
else
{
string thisName = nameof(ConversationAction);
DebugConsole.ThrowError(
$"Error in {thisName} in the event \"{parentEvent.Prefab.Identifier}\"" +
$" - unrecognized child element \"{elem.Name}\". If it's an action intended to execute after the {thisName}, " +
$"it should be after the {thisName}, not inside it.",
contentPackage: element.ContentPackage);
}
}
}
@@ -165,7 +169,11 @@ namespace Barotrauma
#else
foreach (Client c in GameMain.Server.ConnectedClients)
{
if (c.InGame && c.Character != null) { ServerWrite(Speaker, c, interrupt); }
if (c.InGame && c.Character != null)
{
DebugConsole.Log($"Conversation {ParentEvent.Prefab.Identifier} finished, communicating to clients...");
ServerWrite(Speaker, c, interrupt);
}
}
#endif
ResetSpeaker();
@@ -209,6 +217,16 @@ namespace Barotrauma
Speaker = null;
}
/// <summary>
/// Retriggers the conversation after the specified delay.
/// </summary>
public void RetriggerAfter(float delay)
{
startDelay = delay;
dialogOpened = false;
selectedOption = -1;
}
public override bool SetGoToTarget(string goTo)
{
selectedOption = -1;
@@ -238,20 +256,33 @@ namespace Barotrauma
{
humanAI.ClearForcedOrder();
if (prevIdleObjective != null) { humanAI.ObjectiveManager.AddObjective(prevIdleObjective); }
if (prevGotoObjective != null) { humanAI.ObjectiveManager.AddObjective(prevGotoObjective); }
if (prevGotoObjective != null && !prevGotoObjective.Abandon) { humanAI.ObjectiveManager.AddObjective(prevGotoObjective); }
humanAI.ObjectiveManager.SortObjectives();
}
}
public int[] GetEndingOptions()
{
List<int> endings = Options.Where(group => !group.Actions.Any() || group.EndConversation).Select(group => Options.IndexOf(group)).ToList();
List<int> endings = Options
.Where(group =>
group.EndConversation ||
//no actions = safe to assume this must end the conversation
!group.Actions.Any() ||
//no follow-up conversation and a goto makes the event jump somewhere else
//we cannot easily determine whether that goto will lead to a follow-up conversation,
//so it's safest to close this conversation to prevent it from getting stuck (the potential follow-up will open a new one)
(group.Actions.None(a => a is ConversationAction) && group.Actions.Any(a => a is GoTo { EndConversation: true })))
.Select(group => Options.IndexOf(group))
.ToList();
if (!ContinueConversation) { endings.Add(-1); }
return endings.ToArray();
}
public override void Update(float deltaTime)
{
startDelay -= deltaTime;
if (startDelay > 0) { return; }
if (interrupt)
{
Interrupted?.Update(deltaTime);
@@ -388,9 +419,22 @@ namespace Barotrauma
{
targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e));
if (!targets.Any() || IsBlockedByAnotherConversation(targets, BlockOtherConversationsDuration)) { return; }
//some specific character tried to start the convo, but not included in the targets for this conversation -> disallow
if (targetCharacter != null && !targets.Contains(targetCharacter)) { return; }
}
else
{
#if SERVER
if (GameMain.NetworkMember != null)
{
//conversation targeted to everyone, but no-one present yet who could potentially hear it -> don't start yet
UpdateIgnoredClients();
if (GameMain.NetworkMember.ConnectedClients.None(c => CanClientReceive(c))) { return; }
}
#endif
if (IsBlockedByAnotherConversation(targetCharacter?.ToEnumerable(), BlockOtherConversationsDuration)) { return; }
}
if (targetCharacter != null && IsBlockedByAnotherConversation(targetCharacter.ToEnumerable(), 0.1f)) { return; }
if (speaker?.AIController is HumanAIController humanAI)
{
@@ -11,6 +11,10 @@ namespace Barotrauma
{
public string Text;
public List<EventAction> Actions;
/// <summary>
/// Should this option end the conversation (closing the conversation prompt?). By default, options that don't have any actions inside them, or that only have a GoTo action, end the conversation.
/// But if there are other actions inside the option, the game assumes there may be some kind of a follow-up coming to the conversation, and by default leaves it open.
/// </summary>
public bool EndConversation;
private int currentSubAction = 0;
@@ -1,4 +1,4 @@
namespace Barotrauma
namespace Barotrauma
{
/// <summary>
/// Makes the event jump to a <see cref="Label"/> somewhere else in the event.
@@ -11,6 +11,9 @@ namespace Barotrauma
[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; }
[Serialize(true, IsPropertySaveable.Yes, description: "By default, jumping to another part in the event closes the active conversation prompt. Use this if if you want to keep it open instead.")]
public bool EndConversation { get; set; }
private int counter;
public GoTo(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
@@ -1,4 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -213,7 +213,7 @@ namespace Barotrauma
return false;
}
}
if (!locationTypes.Contains(location.Type.Identifier) && !(location.HasOutpost() && locationTypes.Contains("AnyOutpost".ToIdentifier())))
if (!locationTypes.Contains(location.Type.Identifier) && !(location.HasOutpost() && locationTypes.Contains(Tags.AnyOutpost)))
{
return false;
}
@@ -1,4 +1,4 @@
namespace Barotrauma
namespace Barotrauma
{
/// <summary>
@@ -31,6 +31,11 @@ namespace Barotrauma
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": MissionIdentifier has not been configured.",
contentPackage: element.ContentPackage);
}
if (Operation == OperationType.Add && State == 0)
{
DebugConsole.AddWarning($"Potential error in event \"{parentEvent.Prefab.Identifier}\": {nameof(MissionStateAction)} is set to add 0 to the mission state, which will do nothing.",
contentPackage: element.ContentPackage);
}
}
public override bool IsFinished(ref string goTo)
@@ -55,7 +60,7 @@ namespace Barotrauma
mission.State = State;
break;
case OperationType.Add:
mission.State += 1;
mission.State += State;
break;
}
}
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -42,13 +42,13 @@ namespace Barotrauma
{
if (isFinished) { return; }
bool isPlayerTeam = TeamID == CharacterTeamType.Team1 || TeamID == CharacterTeamType.Team2;
bool isPlayerTeam = TeamID is CharacterTeamType.Team1 or CharacterTeamType.Team2;
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList();
foreach (var npc in affectedNpcs)
affectedNpcs = ParentEvent.GetTargets(NPCTag).OfType<Character>().ToList();
foreach (Character npc in affectedNpcs)
{
// characters will still remain on friendlyNPC team for rest of the tick
npc.SetOriginalTeam(TeamID);
npc.SetOriginalTeamAndChangeTeam(TeamID);
foreach (Item item in npc.Inventory.AllItems)
{
var idCard = item.GetComponent<Items.Components.IdCard>();
@@ -61,26 +61,43 @@ namespace Barotrauma
}
}
}
if (AddToCrew && isPlayerTeam)
if (GameMain.GameSession.CrewManager is CrewManager crewManager)
{
npc.Info.StartItemsGiven = true;
GameMain.GameSession.CrewManager.AddCharacter(npc);
ChangeItemTeam(Submarine.MainSub, true);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
if (AddToCrew && isPlayerTeam)
{
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AddToCrewEventData(TeamID, npc.Inventory.AllItems));
}
}
else if (RemoveFromCrew && (npc.TeamID == CharacterTeamType.Team1 || npc.TeamID == CharacterTeamType.Team2))
{
npc.Info.StartItemsGiven = true;
GameMain.GameSession.CrewManager.RemoveCharacter(npc, removeInfo: true);
var sub = Submarine.Loaded.FirstOrDefault(s => s.TeamID == TeamID);
ChangeItemTeam(sub, false);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.RemoveFromCrewEventData(TeamID, npc.Inventory.AllItems));
if (npc.Info is CharacterInfo info)
{
info.StartItemsGiven = true;
crewManager.AddCharacter(npc);
}
else
{
DebugConsole.AddWarning($"Attempted to change the team of a character ({npc.Name}) that doesn't have Character Info. Can't add to the crew.");
}
ChangeItemTeam(Submarine.MainSub ?? Submarine.Loaded.FirstOrDefault(s => s.TeamID == TeamID), allowStealing: true);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AddToCrewEventData(TeamID, npc.Inventory.FindAllItems(recursive: true)));
}
}
else if (RemoveFromCrew && npc.TeamID is CharacterTeamType.Team1 or CharacterTeamType.Team2)
{
if (npc.Info is CharacterInfo info)
{
info.StartItemsGiven = true;
crewManager.RemoveCharacter(npc, removeInfo: true);
}
else
{
DebugConsole.AddWarning($"Attempted to change the team of a character ({npc.Name}) that doesn't have Character Info. Can't remove from the crew.");
}
Submarine sub = Submarine.Loaded.FirstOrDefault(s => s.TeamID == TeamID);
ChangeItemTeam(sub, false);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.RemoveFromCrewEventData(TeamID, npc.Inventory.FindAllItems(recursive: true)));
}
}
}
void ChangeItemTeam(Submarine sub, bool allowStealing)
@@ -98,7 +115,7 @@ namespace Barotrauma
}
}
WayPoint subWaypoint =
WayPoint.WayPointList.Find(wp => wp.Submarine == sub && wp.SpawnType == SpawnType.Human && wp.AssignedJob == npc.Info.Job?.Prefab) ??
WayPoint.WayPointList.Find(wp => wp.Submarine == sub && wp.SpawnType == SpawnType.Human && wp.AssignedJob == npc.Info?.Job?.Prefab) ??
WayPoint.WayPointList.Find(wp => wp.Submarine == sub && wp.SpawnType == SpawnType.Human);
if (subWaypoint != null)
{
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
@@ -22,6 +23,20 @@ namespace Barotrauma
[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; }
[Serialize(AIObjectiveManager.MaxObjectivePriority, IsPropertySaveable.Yes, description: "AI priority for the action. Uses 100 by default, which is the absolute maximum for any objectives, " +
"meaning nothing can be prioritized over it, including the emergency objectives, such as find safety and combat." +
"Setting the priority to 70 would function like a regular order, but with the highest priority." +
"A priority of 60 would make the objective work like a lowest priority order." +
"So, if we'll want the character to follow, but still be able to find safety, defend themselves when attacked, or flee from dangers," +
"it's better to use e.g. 70 instead of 100.")]
public float Priority
{
get => _priority;
set => _priority = Math.Clamp(value, AIObjectiveManager.LowestOrderPriority, AIObjectiveManager.MaxObjectivePriority);
}
private float _priority;
private bool isFinished = false;
@@ -39,7 +54,7 @@ namespace Barotrauma
if (target == null) { return; }
int targetCount = 0;
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character);
affectedNpcs = ParentEvent.GetTargets(NPCTag).OfType<Character>();
foreach (var npc in affectedNpcs)
{
if (npc.Removed) { continue; }
@@ -49,7 +64,7 @@ namespace Barotrauma
{
var newObjective = new AIObjectiveGoTo(target, npc, humanAiController.ObjectiveManager, repeat: true)
{
OverridePriority = 100.0f,
OverridePriority = Priority,
IsFollowOrder = true
};
humanAiController.ObjectiveManager.AddObjective(newObjective);
@@ -2,6 +2,7 @@
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System;
namespace Barotrauma
{
@@ -31,8 +32,19 @@ namespace Barotrauma
[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(100, IsPropertySaveable.Yes, description: "Priority of operating the item (0-100). Higher values will make the AI prefer operating the item over other orders (priority 60-70) or e.g. reacting to emergencies (priority 90).")]
public int Priority { get; set; }
[Serialize(AIObjectiveManager.MaxObjectivePriority, IsPropertySaveable.Yes, description: "AI priority for the action. Uses 100 by default, which is the absolute maximum for any objectives, " +
"meaning nothing can be prioritized over it, including the emergency objectives, such as find safety and combat." +
"Setting the priority to 70 would function like a regular order, but with the highest priority." +
"A priority of 60 would make the objective work like a lowest priority order." +
"So, if we'll want the character to operate the item, but still be able to find safety, defend themselves when attacked, or flee from dangers," +
"it's better to use e.g. 70 instead of 100.")]
public float Priority
{
get => _priority;
set => _priority = Math.Clamp(value, AIObjectiveManager.LowestOrderPriority, AIObjectiveManager.MaxObjectivePriority);
}
private float _priority;
[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; }
@@ -86,7 +98,7 @@ namespace Barotrauma
{
foreach (var objective in humanAiController.ObjectiveManager.Objectives)
{
if (objective is AIObjectiveOperateItem operateItemObjective && operateItemObjective.OperateTarget == target)
if (objective is AIObjectiveOperateItem operateItemObjective && operateItemObjective.Component.Item == target)
{
objective.Abandon = true;
}
@@ -115,7 +127,7 @@ namespace Barotrauma
if (npc.Removed || npc.AIController is not HumanAIController humanAiController) { continue; }
foreach (var operateItemObjective in humanAiController.ObjectiveManager.GetActiveObjectives<AIObjectiveOperateItem>())
{
if (operateItemObjective.OperateTarget == target)
if (operateItemObjective.Component.Item == target)
{
operateItemObjective.Abandon = true;
}
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
@@ -13,6 +14,20 @@ namespace Barotrauma
[Serialize(true, IsPropertySaveable.Yes, description: "Should the NPC start or stop waiting?")]
public bool Wait { get; set; }
[Serialize(AIObjectiveManager.MaxObjectivePriority, IsPropertySaveable.Yes, description: "AI priority for the action. Uses 100 by default, which is the absolute maximum for any objectives, " +
"meaning nothing can be prioritized over it, including the emergency objectives, such as find safety and combat." +
"Setting the priority to 70 would function like a regular order, but with the highest priority." +
"A priority of 60 would make the objective work like a lowest priority order." +
"So, if we'll want the character to wait, but still be able to find safety, defend themselves when attacked, or flee from dangers," +
"it's better to use e.g. 70 instead of 100.")]
public float Priority
{
get => _priority;
set => _priority = Math.Clamp(value, AIObjectiveManager.LowestOrderPriority, AIObjectiveManager.MaxObjectivePriority);
}
private float _priority;
private bool isFinished = false;
@@ -25,7 +40,7 @@ namespace Barotrauma
{
if (isFinished) { return; }
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character);
affectedNpcs = ParentEvent.GetTargets(NPCTag).OfType<Character>();
foreach (var npc in affectedNpcs)
{
@@ -38,7 +53,7 @@ namespace Barotrauma
AIObjectiveGoTo.GetTargetHull(npc) as ISpatialEntity ?? npc, npc, humanAiController.ObjectiveManager, repeat: true)
{
FaceTargetOnCompleted = false,
OverridePriority = 100.0f,
OverridePriority = Priority,
SourceEventAction = this,
IsWaitOrder = true,
CloseEnough = 100
@@ -25,7 +25,7 @@ namespace Barotrauma
{
ItemIdentifiers = element.GetAttributeString("itemidentifier", element.GetAttributeString("identifier", string.Empty));
}
itemIdentifierSplit = ItemIdentifiers.Split(',').ToIdentifiers().ToImmutableHashSet();
itemIdentifierSplit = ItemIdentifiers.ToIdentifiers().ToImmutableHashSet();
}
private bool isFinished = false;
@@ -17,6 +17,8 @@ namespace Barotrauma
MainSub,
Outpost,
MainPath,
Cave,
AbyssCave,
Ruin,
Wreck,
BeaconStation,
@@ -38,6 +40,9 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the item to spawn.")]
public Identifier ItemIdentifier { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the item to spawn.")]
public Identifier ItemTag { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "The spawned entity will be assigned this tag. The tag can be used to refer to the entity by other actions of the event.")]
public Identifier TargetTag { get; set; }
@@ -67,6 +72,9 @@ namespace Barotrauma
[Serialize(1, IsPropertySaveable.Yes, description: "Number of entities to spawn.")]
public int Amount { get; set; }
[Serialize(true, IsPropertySaveable.Yes, description: "Should the item be spawned even if the target inventory is full (just spawning it at the position of the target)? Only valid if spawning an item in an inventory.")]
public bool SpawnIfInventoryFull { get; set; }
[Serialize(100.0f, IsPropertySaveable.Yes, description: "Random offset to add to the spawn position.")]
public float Offset { get; set; }
@@ -94,6 +102,9 @@ namespace Barotrauma
[Serialize(true, IsPropertySaveable.Yes, description: "If disabled, the action will choose a spawn position away from players' views if one is available.")]
public bool AllowInPlayerView { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "Should the event continue even if the entity failed to spawn for whatever reason?")]
public bool ContinueIfFailedToSpawn { get; set; }
private bool spawned;
private Entity spawnedEntity;
@@ -115,9 +126,9 @@ namespace Barotrauma
public override bool IsFinished(ref string goTo)
{
if (spawnedEntity != null)
if (spawnedEntity != null || ContinueIfFailedToSpawn)
{
return true;
return spawned;
}
else
{
@@ -157,7 +168,7 @@ namespace Barotrauma
logError: false);
}
humanPrefab ??= NPCSet.Get(NPCSetIdentifier, NPCIdentifier, logError: true);
humanPrefab ??= NPCSet.Get(NPCSetIdentifier, NPCIdentifier, logError: true, contentPackageToLogInError: ParentEvent.Prefab.ContentPackage);
if (humanPrefab != null)
{
@@ -176,7 +187,11 @@ namespace Barotrauma
{
if (newCharacter == null) { return; }
newCharacter.HumanPrefab = humanPrefab;
newCharacter.TeamID = TeamID;
//don't set the TeamID directly: we want to leave the character's original team untouched,
//so they can behave offensively (and otherwise act "normally") if we spawn them in a hostile team inside a sub/outpost that doesn't belong to that team
//process the team change immediately in case the character is killed or made unconscious by the event (in which case the team change would not be processed)
newCharacter.SetOriginalTeamAndChangeTeam(TeamID, processImmediately: true);
newCharacter.EnableDespawn = false;
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine, spawnPos as WayPoint);
if (LootingIsStealing)
@@ -233,74 +248,82 @@ namespace Barotrauma
}
}
}
else if (!ItemIdentifier.IsEmpty)
else if (!ItemIdentifier.IsEmpty || !ItemTag.IsEmpty)
{
if (MapEntityPrefab.FindByIdentifier(ItemIdentifier) is not ItemPrefab itemPrefab)
ItemPrefab itemPrefab = null;
if (!ItemIdentifier.IsEmpty)
{
DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)",
contentPackage: ParentEvent.Prefab.ContentPackage);
}
else
{
Inventory spawnInventory = null;
if (!TargetInventory.IsEmpty)
itemPrefab = MapEntityPrefab.FindByIdentifier(ItemIdentifier) as ItemPrefab;
if (itemPrefab == null)
{
var targets = ParentEvent.GetTargets(TargetInventory);
if (targets.Any())
{
var target = targets.First(t => t is Item || t is Character);
if (target is Character character)
{
spawnInventory = character.Inventory;
}
else if (target is Item item)
{
spawnInventory = item.OwnInventory;
}
}
DebugConsole.ThrowError($"Error in SpawnAction (item prefab \"{ItemIdentifier}\" not found)",
contentPackage: ParentEvent.Prefab.ContentPackage);
}
}
else if (!ItemTag.IsEmpty)
{
itemPrefab = ItemPrefab.Prefabs.Where(ip => ip.Tags.Contains(ItemTag)).GetRandom(Rand.RandSync.Unsynced);
}
if (spawnInventory == null)
Inventory spawnInventory = null;
if (!TargetInventory.IsEmpty)
{
var targets = ParentEvent.GetTargets(TargetInventory);
if (targets.Any())
{
var target = targets.First(t => t is Item || t is Character);
if (target is Character character)
{
DebugConsole.ThrowError($"Could not spawn \"{ItemIdentifier}\" in target inventory \"{TargetInventory}\" - matching target not found.",
contentPackage: ParentEvent.Prefab.ContentPackage);
spawnInventory = character.Inventory;
}
else if (target is Item item)
{
spawnInventory = item.OwnInventory;
}
}
if (spawnInventory == null)
{
ISpatialEntity spawnPos = GetSpawnPos();
if (spawnPos != null)
{
for (int i = 0; i < Amount; i++)
{
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), onSpawned: onSpawned);
}
}
DebugConsole.ThrowError($"Could not spawn \"{ItemIdentifier}\" in target inventory \"{TargetInventory}\" - matching target not found.",
contentPackage: ParentEvent.Prefab.ContentPackage);
}
else
}
if (spawnInventory == null)
{
ISpatialEntity spawnPos = GetSpawnPos();
if (spawnPos != null)
{
for (int i = 0; i < Amount; i++)
{
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, spawnInventory, onSpawned: onSpawned);
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), onSpawned: onSpawned);
}
}
void onSpawned(Item newItem)
{
if (newItem != null)
{
if (!TargetTag.IsEmpty)
{
ParentEvent.AddTarget(TargetTag, newItem);
}
if (IgnoreByAI)
{
newItem.AddTag("ignorebyai");
}
}
spawnedEntity = newItem;
}
}
else
{
for (int i = 0; i < Amount; i++)
{
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, spawnInventory, spawnIfInventoryFull: SpawnIfInventoryFull, onSpawned: onSpawned);
}
}
void onSpawned(Item newItem)
{
if (newItem != null)
{
if (!TargetTag.IsEmpty)
{
ParentEvent.AddTarget(TargetTag, newItem);
}
if (IgnoreByAI)
{
newItem.AddTag("ignorebyai");
}
}
spawnedEntity = newItem;
}
}
spawned = true;
@@ -353,8 +376,7 @@ namespace Barotrauma
{
SpawnLocationType.Any => true,
SpawnLocationType.MainSub => submarine == Submarine.MainSub,
SpawnLocationType.NearMainSub => submarine == null,
SpawnLocationType.MainPath => submarine == null,
SpawnLocationType.NearMainSub or SpawnLocationType.MainPath or SpawnLocationType.Cave or SpawnLocationType.AbyssCave => submarine == null,
SpawnLocationType.Outpost => submarine is { Info.IsOutpost: true },
SpawnLocationType.Wreck => submarine is { Info.IsWreck: true },
SpawnLocationType.Ruin => submarine is { Info.IsRuin: true },
@@ -443,10 +465,25 @@ namespace Barotrauma
return potentialSpawnPoints.GetRandomUnsynced();
}
if (spawnLocation == SpawnLocationType.MainPath || spawnLocation == SpawnLocationType.NearMainSub)
switch (spawnLocation)
{
validSpawnPoints = validSpawnPoints.Where(p =>
Submarine.Loaded.None(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(p.WorldPosition)));
case SpawnLocationType.MainPath:
case SpawnLocationType.NearMainSub:
validSpawnPoints = validSpawnPoints.Where(p =>
Submarine.Loaded.None(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(p.WorldPosition)));
if (Level.Loaded != null)
{
validSpawnPoints = validSpawnPoints.Where(p =>
p.WorldPosition.Y > Level.Loaded.AbyssStart &&
p.Cave == null && p.Ruin == null);
}
break;
case SpawnLocationType.Cave:
validSpawnPoints = validSpawnPoints.Where(p => p.WorldPosition.Y > Level.Loaded.AbyssStart && p.Cave != null);
break;
case SpawnLocationType.AbyssCave:
validSpawnPoints = validSpawnPoints.Where(p => p.WorldPosition.Y < Level.Loaded.AbyssStart && p.Cave != null);
break;
}
//avoid using waypoints if there's any actual spawnpoints available
@@ -11,9 +11,10 @@ namespace Barotrauma
/// </summary>
class TagAction : EventAction
{
public enum SubType { Any = 0, Player = 1, Outpost = 2, Wreck = 4, BeaconStation = 8 }
public enum SubType { Any = 0, Player = 1, Outpost = 2, Wreck = 4, BeaconStation = 8, Enemy = 16, Ruin = 32 }
public enum CharacterTeam { Any = 0, None = 1, Team1 = 2, Team2 = 4, FriendlyNPC = 8 }
[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].")]
[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], speciesname:[id].")]
public string Criteria { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "The tag to apply to the target.")]
@@ -22,6 +23,9 @@ namespace Barotrauma
[Serialize(SubType.Any, IsPropertySaveable.Yes, description: "The type of submarine the target needs to be in.")]
public SubType SubmarineType { get; set; }
[Serialize(CharacterTeam.Any, IsPropertySaveable.Yes, description: "The team the target needs to be on.")]
public CharacterTeam Team { get; set; }
[Serialize("", IsPropertySaveable.Yes, "If set, the target must be in an outpost module that has this tag.")]
public Identifier RequiredModuleTag { get; set; }
@@ -34,6 +38,9 @@ namespace Barotrauma
[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("", IsPropertySaveable.Yes, description: "If choosing a random target, targets with this tag can optionally be excluded.")]
public Identifier ChooseRandomExcludingTag { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "Should the event continue if the TagAction can't find any valid targets?")]
public bool ContinueIfNoTargetsFound { get; set; }
@@ -78,6 +85,7 @@ namespace Barotrauma
("hullname", TagHullsByName),
("submarine", TagSubmarinesByType),
("eventtag", TagByEventTag),
("speciesname", TagBySpeciesName)
}.Select(t => (t.k.ToIdentifier(), t.v)).ToImmutableDictionary();
}
@@ -87,9 +95,16 @@ namespace Barotrauma
}
public override void Reset()
{
taggingDone = false;
cantFindTargets = false;
isFinished = false;
}
private void TagBySpeciesName(Identifier speciesName)
{
AddTarget(Tag, Character.CharacterList.Where(c => c.SpeciesName == speciesName && CharacterTeamMatches(c)));
}
private void TagByEventTag(Identifier eventTag)
{
AddTarget(Tag, ParentEvent.GetTargets(eventTag).Where(t => MatchesRequirements(t)));
@@ -100,7 +115,7 @@ namespace Barotrauma
AddTargetPredicate(
Tag,
ScriptedEvent.TargetPredicate.EntityType.Character,
e => e is Character c && c.IsPlayer && (!c.IsIncapacitated || !IgnoreIncapacitatedCharacters));
e => e is Character c && c.IsPlayer && (!c.IsIncapacitated || !IgnoreIncapacitatedCharacters) && CharacterTeamMatches(c));
}
private void TagTraitors()
@@ -151,7 +166,7 @@ namespace Barotrauma
private void TagHumansByIdentifier(Identifier identifier)
{
AddTarget(Tag, Character.CharacterList.Where(c => c.HumanPrefab?.Identifier == identifier));
AddTarget(Tag, Character.CharacterList.Where(c => c.HumanPrefab?.Identifier == identifier && CharacterTeamMatches(c)));
}
private void TagHumansByTag(Identifier tag)
@@ -161,7 +176,7 @@ namespace Barotrauma
private void TagHumansByJobIdentifier(Identifier jobIdentifier)
{
AddTarget(Tag, Character.CharacterList.Where(c => c.HasJob(jobIdentifier)));
AddTarget(Tag, Character.CharacterList.Where(c => c.HasJob(jobIdentifier) && CharacterTeamMatches(c)));
}
private void TagStructuresByIdentifier(Identifier identifier)
@@ -233,7 +248,7 @@ namespace Barotrauma
private bool MatchesRequirements(Entity e)
{
return ModuleTagMatches(e) && SubmarineTypeMatches(e.Submarine);
return ModuleTagMatches(e) && SubmarineTypeMatches(e as Submarine ?? e.Submarine);
}
private bool ModuleTagMatches(Entity e)
@@ -267,6 +282,23 @@ namespace Barotrauma
return hull != null && hull.OutpostModuleTags.Contains(RequiredModuleTag);
}
private bool CharacterTeamMatches(Character character)
{
if (Team == CharacterTeam.Any) { return true; }
switch (Team)
{
case CharacterTeam.None:
return character.TeamID == CharacterTeamType.None;
case CharacterTeam.Team1:
return character.TeamID == CharacterTeamType.Team1;
case CharacterTeam.Team2:
return character.TeamID == CharacterTeamType.Team2;
case CharacterTeam.FriendlyNPC:
return character.TeamID == CharacterTeamType.FriendlyNPC;
default:
return false;
}
}
private bool SubmarineTypeMatches(Submarine sub)
{
@@ -280,7 +312,7 @@ namespace Barotrauma
switch (sub.Info.Type)
{
case Barotrauma.SubmarineType.Player:
return submarineType.HasFlag(SubType.Player) && sub != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle;
return submarineType.HasFlag(SubType.Player) && !sub.IsRespawnShuttle;
case Barotrauma.SubmarineType.Outpost:
case Barotrauma.SubmarineType.OutpostModule:
return submarineType.HasFlag(SubType.Outpost);
@@ -288,6 +320,10 @@ namespace Barotrauma
return submarineType.HasFlag(SubType.Wreck);
case Barotrauma.SubmarineType.BeaconStation:
return submarineType.HasFlag(SubType.BeaconStation);
case Barotrauma.SubmarineType.EnemySubmarine:
return submarineType.HasFlag(SubType.Enemy);
case Barotrauma.SubmarineType.Ruin:
return submarineType.HasFlag(SubType.Ruin);
default:
return false;
}
@@ -357,6 +393,11 @@ namespace Barotrauma
private void TagRandom(Identifier tag, IEnumerable<Entity> entities)
{
if (!ChooseRandomExcludingTag.IsEmpty)
{
var excludedTargets = ParentEvent.GetTargets(ChooseRandomExcludingTag);
entities = entities.Except(excludedTargets);
}
if (entities.None())
{
cantFindTargets = true;
@@ -414,7 +455,7 @@ namespace Barotrauma
public override string ToDebugString()
{
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TagAction)} -> (Criteria: {Criteria.ColorizeObject()}, Tag: {Tag.ColorizeObject()}, Sub: {SubmarineType.ColorizeObject()})";
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TagAction)} -> (Criteria: {Criteria.ColorizeObject()}, Tag: {Tag.ColorizeObject()}, Sub: {SubmarineType.ColorizeObject()}, Team: {Team.ColorizeObject()})";
}
}
}
@@ -1,4 +1,4 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
@@ -56,7 +56,16 @@ namespace Barotrauma
private float distance;
public TriggerAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
public TriggerAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
{
if (element.GetAttribute(nameof(TagAction.IgnoreIncapacitatedCharacters)) != null)
{
DebugConsole.AddWarning(
$"Potential error in {nameof(TriggerAction)}, event \"{parentEvent.Prefab.Identifier}\": "+
$"{nameof(TagAction.IgnoreIncapacitatedCharacters)} is a property of {nameof(TagAction)}, did you mean {nameof(DisableIfTargetIncapacitated)}?",
contentPackage: element.ContentPackage);
}
}
private bool isFinished = false;
public override bool IsFinished(ref string goTo)
@@ -157,13 +166,13 @@ namespace Barotrauma
Item item = null;
if (e1 is Character char1)
{
if (char1.IsBot)
{
npc ??= char1;
if (char1.IsPlayer)
{
player = char1;
}
else
{
player = char1;
else
{
npc ??= char1;
}
}
else
@@ -172,13 +181,13 @@ namespace Barotrauma
}
if (e2 is Character char2)
{
if (char2.IsBot)
{
npc ??= char2;
}
else
if (char2.IsPlayer)
{
player = char2;
player = char2;
}
else
{
npc ??= char2;
}
}
else
@@ -190,6 +199,10 @@ namespace Barotrauma
{
if (npc != null)
{
if (!npcsOrItems.Any(n => n.TryGet(out Character npc2) && npc2 == npc))
{
npcsOrItems.Add(npc);
}
if (npc.CampaignInteractionType == CampaignMode.InteractionType.Talk)
{
//if the NPC has a conversation available, don't assign the trigger until the conversation is done
@@ -197,19 +210,26 @@ namespace Barotrauma
}
else if (npc.CampaignInteractionType != CampaignMode.InteractionType.Examine)
{
if (!npcsOrItems.Any(n => n.TryGet(out Character npc2) && npc2 == npc))
{
npcsOrItems.Add(npc);
}
npc.CampaignInteractionType = CampaignMode.InteractionType.Examine;
npc.RequireConsciousnessForCustomInteract = DisableIfTargetIncapacitated;
#if CLIENT
npc.SetCustomInteract(
(speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
(Character npc, Character interactor) =>
{
//the first character in the CustomInteract callback is always the NPC and the 2nd the character who interacted with it
//but the TriggerAction can configure the 1st and 2nd entity in either order,
//let's make sure we pass the NPC and the interactor in the intended order
if (e1 == npc && targets2.Contains(interactor))
{
Trigger(npc, interactor);
}
else if (targets1.Contains(interactor) && e2 == npc)
{
Trigger(interactor, npc);
}
},
#if CLIENT
TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use)));
#else
npc.SetCustomInteract(
(speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
TextManager.Get("CampaignInteraction.Talk"));
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AssignCampaignInteractionEventData());
#endif
@@ -339,7 +359,7 @@ namespace Barotrauma
return true;
}
}
else if (c.AIController is EnemyAIController enemyAI && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack))
else if (c.AIController is EnemyAIController { State: AIState.Aggressive or AIState.Attack } enemyAI)
{
if (enemyAI.SelectedAiTarget?.Entity == character || c.CurrentHull == character.CurrentHull)
{
@@ -401,10 +421,18 @@ namespace Barotrauma
{
if (TargetModuleType.IsEmpty)
{
string targetStr = "none";
if (npcsOrItems.Any())
{
targetStr = string.Join(", ",
npcsOrItems.Select(npcOrItem =>
npcOrItem.TryGet(out Character character) ? character.Name : (npcOrItem.TryGet(out Item item) ? item.Name : "none")));
}
return
$"{ToolBox.GetDebugSymbol(isFinished, isRunning)} {nameof(TriggerAction)} -> (" +
(WaitForInteraction ?
$"Selected non-player target: {(npcsOrItems?.ToString() ?? "<null>").ColorizeObject()}, " :
$"Selected non-player target: {targetStr.ColorizeObject()}, " :
$"Distance: {((int)distance).ColorizeObject()}, ") +
$"Radius: {Radius.ColorizeObject()}, " +
$"TargetTags: {Target1Tag.ColorizeObject()}, " +
@@ -1,13 +1,16 @@
namespace Barotrauma
{
/// <summary>
/// Triggers another scripted event.
/// Triggers another event (can also trigger things other than scripted events, for example monster events).
/// </summary>
class TriggerEventAction : EventAction
{
[Serialize("", IsPropertySaveable.Yes, description: "Identifier of the event to trigger.")]
public Identifier Identifier { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the event to trigger.")]
public Identifier EventTag { get; set; }
[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; }
@@ -36,13 +39,8 @@
}
else
{
var eventPrefab = EventSet.GetEventPrefab(Identifier);
if (eventPrefab == null)
{
DebugConsole.ThrowError($"Error in TriggerEventAction - could not find an event with the identifier {Identifier}.",
contentPackage: ParentEvent.Prefab.ContentPackage);
}
else
EventPrefab eventPrefab = EventPrefab.FindEventPrefab(Identifier, EventTag, ParentEvent.Prefab.ContentPackage);
if (eventPrefab != null)
{
var ev = eventPrefab.CreateInstance(GameMain.GameSession.EventManager.RandomSeed);
if (ev != null)
@@ -1,6 +1,7 @@
using Barotrauma.Networking;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
namespace Barotrauma
{
@@ -9,6 +10,13 @@ namespace Barotrauma
/// </summary>
class UnlockPathAction : EventAction
{
private static readonly HashSet<LocationConnection> pathsUnlockedThisRound = new HashSet<LocationConnection>();
public static void ResetPathsUnlockedThisRound()
{
pathsUnlockedThisRound.Clear();
}
public UnlockPathAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
private bool isFinished = false;
@@ -32,6 +40,7 @@ namespace Barotrauma
{
if (!connection.Locked) { continue; }
connection.Locked = false;
pathsUnlockedThisRound.Add(connection);
#if SERVER
NotifyUnlock(connection);
#else
@@ -50,17 +59,30 @@ namespace Barotrauma
}
#if SERVER
private void NotifyUnlock(LocationConnection connection)
public static void NotifyPathsUnlockedThisRound(Client client)
{
foreach (LocationConnection connection in pathsUnlockedThisRound)
{
NotifyUnlock(connection, client);
}
}
private static void NotifyUnlock(LocationConnection connection)
{
foreach (Client client in GameMain.Server.ConnectedClients)
{
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION);
outmsg.WriteByte((byte)EventManager.NetworkEventType.UNLOCKPATH);
outmsg.WriteUInt16((UInt16)GameMain.GameSession.Map.Connections.IndexOf(connection));
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
NotifyUnlock(connection, client);
}
}
private static void NotifyUnlock(LocationConnection connection, Client client)
{
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION);
outmsg.WriteByte((byte)EventManager.NetworkEventType.UNLOCKPATH);
outmsg.WriteUInt16((UInt16)GameMain.GameSession.Map.Connections.IndexOf(connection));
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
}
#endif
}
}
@@ -1,4 +1,4 @@
#nullable enable
#nullable enable
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
@@ -12,6 +12,11 @@ namespace Barotrauma
/// </summary>
class WaitForItemUsedAction : EventAction
{
/// <summary>
/// Counter used to ensure we have a unique identifier to use for the ItemComponent.OnUsed event
/// </summary>
private static int IdCounter;
[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; }
@@ -50,7 +55,8 @@ namespace Barotrauma
{
if (onUseEventIdentifier.IsEmpty)
{
onUseEventIdentifier = (ParentEvent.Prefab.Identifier + ParentEvent.Actions.IndexOf(this).ToString()).ToIdentifier();
onUseEventIdentifier = (ParentEvent.Prefab.Identifier + ParentEvent.Actions.IndexOf(this).ToString() + IdCounter).ToIdentifier();
IdCounter++;
}
return onUseEventIdentifier;
}