Unstable 1.8.4.0
This commit is contained in:
@@ -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()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-3
@@ -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;
|
||||
|
||||
|
||||
+1
-1
@@ -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)
|
||||
{
|
||||
|
||||
+55
-11
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+40
-23
@@ -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);
|
||||
|
||||
+16
-4
@@ -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
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user