Unstable 1.1.14.0
This commit is contained in:
+9
-3
@@ -12,6 +12,9 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; } = Identifier.Empty;
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag referring to the character who caused the affliction.")]
|
||||
public Identifier SourceCharacter { get; set; } = Identifier.Empty;
|
||||
|
||||
[Serialize(LimbType.None, IsPropertySaveable.Yes, "Only check afflictions on the specified limb type")]
|
||||
public LimbType TargetLimb { get; set; }
|
||||
|
||||
@@ -33,8 +36,7 @@ namespace Barotrauma
|
||||
if (target.CharacterHealth == null) { continue; }
|
||||
if (TargetLimb == LimbType.None)
|
||||
{
|
||||
var affliction = target.CharacterHealth.GetAffliction(Identifier, AllowLimbAfflictions);
|
||||
if (affliction != null && affliction.Strength >= MinStrength) { return true; }
|
||||
if (target.CharacterHealth.GetAfflictionStrengthByIdentifier(Identifier, AllowLimbAfflictions) >= MinStrength) { return true; }
|
||||
}
|
||||
IEnumerable<Affliction> afflictions = target.CharacterHealth.GetAllAfflictions().Where(affliction =>
|
||||
{
|
||||
@@ -43,9 +45,13 @@ namespace Barotrauma
|
||||
LimbType? limbType = target.CharacterHealth.GetAfflictionLimb(affliction)?.type;
|
||||
if (limbType == null || limbType != TargetLimb) { return false; }
|
||||
}
|
||||
if (!SourceCharacter.IsEmpty)
|
||||
{
|
||||
if (!ParentEvent.GetTargets(SourceCharacter).Contains(affliction.Source)) { return false; }
|
||||
}
|
||||
|
||||
return affliction.Strength >= MinStrength;
|
||||
});
|
||||
|
||||
if (afflictions.Any(a => a.Identifier == Identifier)) { return true; }
|
||||
}
|
||||
return false;
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (target == null)
|
||||
{
|
||||
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed.");
|
||||
DebugConsole.LogError($"{nameof(CheckConditionalAction)} error: {GetEventName()} uses a {nameof(CheckConditionalAction)} but no valid target was found for tag \"{TargetTag}\"! This will cause the check to automatically succeed.");
|
||||
}
|
||||
if (target == null || Conditional == null)
|
||||
{
|
||||
|
||||
@@ -85,7 +85,7 @@ namespace Barotrauma
|
||||
case bool bool1 when metadata2 is bool bool2:
|
||||
return CompareBool(bool1, bool2) ?? false;
|
||||
case float float1 when metadata2 is float float2:
|
||||
return CompareFloat(float1, float2) ?? false;
|
||||
return PropertyConditional.CompareFloat(float1, float2, Operator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,36 +143,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (float.TryParse(value, out float f))
|
||||
{
|
||||
return CompareFloat(GetFloat(campaignMode), f);
|
||||
return PropertyConditional.CompareFloat(GetFloat(campaignMode), f, Operator);
|
||||
}
|
||||
|
||||
DebugConsole.Log($"{value} != float");
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool? CompareFloat(float val1, float val2)
|
||||
{
|
||||
value1 = val1;
|
||||
value2 = val2;
|
||||
switch (Operator)
|
||||
{
|
||||
case PropertyConditional.ComparisonOperatorType.Equals:
|
||||
return MathUtils.NearlyEqual(val1, val2);
|
||||
case PropertyConditional.ComparisonOperatorType.GreaterThan:
|
||||
return val1 > val2;
|
||||
case PropertyConditional.ComparisonOperatorType.GreaterThanEquals:
|
||||
return val1 >= val2;
|
||||
case PropertyConditional.ComparisonOperatorType.LessThan:
|
||||
return val1 < val2;
|
||||
case PropertyConditional.ComparisonOperatorType.LessThanEquals:
|
||||
return val1 <= val2;
|
||||
case PropertyConditional.ComparisonOperatorType.NotEquals:
|
||||
return !MathUtils.NearlyEqual(val1, val2);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool? TryString(CampaignMode campaignMode, string value)
|
||||
{
|
||||
return CompareString(GetString(campaignMode), value);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -20,9 +20,15 @@ namespace Barotrauma
|
||||
[Serialize(1, IsPropertySaveable.Yes)]
|
||||
public int Amount { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Optional tag of a hull the target must be inside.")]
|
||||
public Identifier HullTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the first target when the check succeeds.")]
|
||||
public Identifier ApplyTagToTarget { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the found item(s) when the check succeeds.")]
|
||||
public Identifier ApplyTagToItem { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool RequireEquipped { get; set; }
|
||||
|
||||
@@ -32,6 +38,24 @@ namespace Barotrauma
|
||||
[Serialize(-1, IsPropertySaveable.Yes)]
|
||||
public int ItemContainerIndex { get; set; }
|
||||
|
||||
private readonly bool checkPercentage;
|
||||
|
||||
private float requiredConditionalMatchPercentage;
|
||||
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes)]
|
||||
|
||||
/// <summary>
|
||||
/// What percentage of targets do the conditionals need to match for the check to succeed?
|
||||
/// </summary>
|
||||
public float RequiredConditionalMatchPercentage
|
||||
{
|
||||
get { return requiredConditionalMatchPercentage; }
|
||||
set { requiredConditionalMatchPercentage = MathHelper.Clamp(value, 0.0f, 100.0f); }
|
||||
}
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool CompareToInitialAmount { get; set; }
|
||||
|
||||
private readonly IReadOnlyList<PropertyConditional> conditionals;
|
||||
|
||||
private readonly Identifier[] itemIdentifierSplit;
|
||||
@@ -49,16 +73,84 @@ namespace Barotrauma
|
||||
}
|
||||
conditionals = conditionalList;
|
||||
|
||||
if (itemTags.None() && ItemIdentifiers.None())
|
||||
if (itemTags.None() &&
|
||||
ItemIdentifiers.None() &&
|
||||
TargetTag.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(CheckItemAction)} does't define either tags or identifiers of the item to check.");
|
||||
}
|
||||
|
||||
checkPercentage = element.GetAttribute(nameof(RequiredConditionalMatchPercentage)) is not null;
|
||||
if (Amount != 1 && checkPercentage)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". Cannot define both '{Amount}' and '{RequiredConditionalMatchPercentage}' in {nameof(CheckItemAction)}.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool EnoughTargets(int totalTargets, int targetsWithConditionalsMatched)
|
||||
{
|
||||
if (CompareToInitialAmount)
|
||||
{
|
||||
totalTargets = ParentEvent.GetInitialTargetCount(TargetTag);
|
||||
}
|
||||
if (checkPercentage)
|
||||
{
|
||||
return MathUtils.Percentage(targetsWithConditionalsMatched, totalTargets) >= RequiredConditionalMatchPercentage;
|
||||
}
|
||||
else
|
||||
{
|
||||
return targetsWithConditionalsMatched >= Amount;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Item> tempTargetItems = new List<Item>();
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
var targets = ParentEvent.GetTargets(TargetTag);
|
||||
if (!targets.Any()) { return null; }
|
||||
|
||||
if (!HullTag.IsEmpty)
|
||||
{
|
||||
var hulls = ParentEvent.GetTargets(HullTag).OfType<Hull>();
|
||||
targets = targets.Where(t =>
|
||||
(t is Item it && hulls.Contains(it.CurrentHull)) ||
|
||||
(t is Character c && hulls.Contains(c.CurrentHull)));
|
||||
}
|
||||
|
||||
if (!targets.Any())
|
||||
{
|
||||
if (conditionals.Any())
|
||||
{
|
||||
//conditionals can't be met if there's no targets
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//check if the target(s) are the items we're looking for (instead of characters/containers the items are inside)
|
||||
int targetCount = targets.Count();
|
||||
if (targetCount >= Amount)
|
||||
{
|
||||
tempTargetItems.Clear();
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target is not Item item) { continue; }
|
||||
if (itemTags.Any() && itemTags.None(item.HasTag) &&
|
||||
itemIdentifierSplit.Any() && !itemIdentifierSplit.Contains(item.Prefab.Identifier))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (ConditionalsMatch(item, character: null))
|
||||
{
|
||||
tempTargetItems.Add(item);
|
||||
}
|
||||
}
|
||||
if (EnoughTargets(targetCount, tempTargetItems.Count))
|
||||
{
|
||||
TryApplyTagToItems(tempTargetItems);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target is Character character)
|
||||
@@ -99,8 +191,9 @@ namespace Barotrauma
|
||||
private bool CheckInventory(Inventory inventory, Character character)
|
||||
{
|
||||
if (inventory == null) { return false; }
|
||||
int count = 0;
|
||||
int targetCount = 0;
|
||||
HashSet<Item> eventTargets = new HashSet<Item>();
|
||||
tempTargetItems.Clear();
|
||||
foreach (Identifier tag in itemTags)
|
||||
{
|
||||
foreach (var target in ParentEvent.GetTargets(tag))
|
||||
@@ -117,19 +210,39 @@ namespace Barotrauma
|
||||
eventTargets.Contains(it),
|
||||
recursive: Recursive))
|
||||
{
|
||||
if (!ConditionalsMatch(item, character)) { continue; }
|
||||
count++;
|
||||
if (count >= Amount) { return true; }
|
||||
targetCount++;
|
||||
if (ConditionalsMatch(item, character))
|
||||
{
|
||||
tempTargetItems.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (EnoughTargets(targetCount, tempTargetItems.Count))
|
||||
{
|
||||
TryApplyTagToItems(tempTargetItems);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
private void TryApplyTagToItems(IEnumerable<Item> items)
|
||||
{
|
||||
if (!ApplyTagToItem.IsEmpty)
|
||||
{
|
||||
foreach (var targetItem in items)
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyTagToItem, targetItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ConditionalsMatch(Item item, Character character = null)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
foreach (PropertyConditional conditional in conditionals)
|
||||
{
|
||||
if (!conditional.Matches(item))
|
||||
{
|
||||
if (!item.ConditionalMatches(conditional))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -145,8 +258,8 @@ namespace Barotrauma
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(HasBeenDetermined())} {nameof(CheckItemAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
|
||||
$"ItemIdentifiers: {ItemIdentifiers.ColorizeObject()}" +
|
||||
$"Succeeded: {succeeded.ColorizeObject()})";
|
||||
(ItemTags.Any() ? $"ItemTags: {ItemTags.ColorizeObject()}, " : $"ItemIdentifiers: {ItemIdentifiers.ColorizeObject()}, ") +
|
||||
$"Succeeded: {succeeded.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CheckTraitorEventStateAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize(TraitorEvent.State.Completed, IsPropertySaveable.Yes)]
|
||||
public TraitorEvent.State State { get; set; }
|
||||
|
||||
private readonly TraitorEvent? traitorEvent;
|
||||
|
||||
public CheckTraitorEventStateAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (parentEvent is TraitorEvent traitorEvent)
|
||||
{
|
||||
this.traitorEvent = traitorEvent;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot use the action {nameof(CheckTraitorEventStateAction)} in the event \"{parentEvent.Prefab.Identifier}\" because it's not a traitor event.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
return traitorEvent?.CurrentState == State;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(HasBeenDetermined())} {nameof(CheckTraitorEventStateAction)} -> " +
|
||||
$"State: {State.ColorizeObject()}, Succeeded: {succeeded.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
#nullable enable
|
||||
using Barotrauma.Networking;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks whether the specific target was voted as the traitor.
|
||||
/// </summary>
|
||||
class CheckTraitorVoteAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier Target { get; set; }
|
||||
|
||||
public CheckTraitorVoteAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (parentEvent is not TraitorEvent)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\" - {nameof(CheckTraitorVoteAction)} can only be used in traitor events.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
var targetEntities = ParentEvent.GetTargets(Target);
|
||||
#if SERVER
|
||||
if (GameMain.Server?.TraitorManager?.GetClientAccusedAsTraitor() is Client traitorClient)
|
||||
{
|
||||
return targetEntities.Any(e => e is Character character && traitorClient?.Character == character);
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(succeeded.HasValue)} {nameof(CheckTraitorVoteAction)} -> (TargetTag: {Target.ColorizeObject()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#nullable enable
|
||||
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CheckVisibilityAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the entity to do the visibility check from.")]
|
||||
public Identifier EntityTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the entity to do the visibility check to.")]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Does the entity need to be facing the target? Only valid if the entity is a character.")]
|
||||
public bool CheckFacing { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the entity who saw the target when the check succeeds.")]
|
||||
public Identifier ApplyTagToEntity { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the entity that was seen when the check succeeds.")]
|
||||
public Identifier ApplyTagToTarget { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "If both the seeing entity and the target are the same, does it count as success?")]
|
||||
public bool AllowSameEntity { get; set; }
|
||||
|
||||
public CheckVisibilityAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
foreach (var entity in ParentEvent.GetTargets(EntityTag))
|
||||
{
|
||||
foreach (var target in ParentEvent.GetTargets(TargetTag))
|
||||
{
|
||||
if (!AllowSameEntity && entity == target) { continue; }
|
||||
if (Character.IsTargetVisible(target, entity, CheckFacing))
|
||||
{
|
||||
if (!ApplyTagToEntity.IsEmpty)
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyTagToEntity, entity);
|
||||
}
|
||||
if (!ApplyTagToTarget.IsEmpty)
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyTagToTarget, target);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(HasBeenDetermined())} {nameof(CheckVisibilityAction)} -> (TargetTags: {EntityTag.ColorizeObject()}, {TargetTag.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,13 +43,14 @@ namespace Barotrauma
|
||||
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (!(npc.AIController is HumanAIController humanAiController)) { continue; }
|
||||
if (npc.Removed) { continue; }
|
||||
if (npc.AIController is not HumanAIController humanAiController) { continue; }
|
||||
|
||||
Character enemy = null;
|
||||
float closestDist = float.MaxValue;
|
||||
foreach (Entity target in ParentEvent.GetTargets(EnemyTag))
|
||||
{
|
||||
if (!(target is Character character)) { continue; }
|
||||
if (target is not Character character) { continue; }
|
||||
float dist = Vector2.DistanceSquared(npc.WorldPosition, target.WorldPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
@@ -82,7 +83,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.Removed || !(npc.AIController is HumanAIController humanAiController)) { continue; }
|
||||
if (npc.Removed || npc.AIController is not HumanAIController humanAiController) { continue; }
|
||||
foreach (var combatObjective in humanAiController.ObjectiveManager.GetActiveObjectives<AIObjectiveCombat>())
|
||||
{
|
||||
combatObjective.Abandon = true;
|
||||
|
||||
@@ -59,6 +59,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool ContinueConversation { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "If enabled, the event will not stop to wait for the conversation to be dismissed.")]
|
||||
public bool ContinueAutomatically { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool IgnoreInterruptDistance { get; set; }
|
||||
|
||||
@@ -86,6 +89,8 @@ namespace Barotrauma
|
||||
|
||||
private bool interrupt;
|
||||
|
||||
private readonly XElement textElement;
|
||||
|
||||
public ConversationAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
actionCount++;
|
||||
@@ -93,15 +98,41 @@ namespace Barotrauma
|
||||
Options = new List<SubactionGroup>();
|
||||
foreach (var elem in element.Elements())
|
||||
{
|
||||
if (elem.Name.LocalName.Equals("option", StringComparison.InvariantCultureIgnoreCase))
|
||||
if (elem.Name.LocalName.Equals("option", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Options.Add(new SubactionGroup(ParentEvent, elem));
|
||||
}
|
||||
else if (elem.Name.LocalName.Equals("interrupt", StringComparison.InvariantCultureIgnoreCase))
|
||||
else if (elem.Name.LocalName.Equals("interrupt", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Interrupted = new SubactionGroup(ParentEvent, elem);
|
||||
}
|
||||
else if (elem.Name.LocalName.Equals("text", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
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\".");
|
||||
}
|
||||
}
|
||||
|
||||
public LocalizedString GetDisplayText()
|
||||
{
|
||||
LocalizedString text = string.Empty;
|
||||
|
||||
if (textElement != null)
|
||||
{
|
||||
TextManager.ConstructDescription(ref text, textElement, ParentEvent.GetTextForReplacementElement);
|
||||
}
|
||||
else
|
||||
{
|
||||
text = TextManager.Get(Text).Fallback(Text);
|
||||
}
|
||||
return ParentEvent.ReplaceVariablesInEventText(text);
|
||||
}
|
||||
|
||||
public override IEnumerable<EventAction> GetSubActions()
|
||||
@@ -145,9 +176,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (ContinueAutomatically && Options.None())
|
||||
{
|
||||
return dialogOpened;
|
||||
}
|
||||
|
||||
if (selectedOption >= 0)
|
||||
{
|
||||
if (!Options.Any() || Options[selectedOption].IsFinished(ref goTo))
|
||||
if (Options.None() || Options[selectedOption].IsFinished(ref goTo))
|
||||
{
|
||||
ResetSpeaker();
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
#nullable enable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CountTargetsAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Optional second tag. Can be used if the target must have two different tags.")]
|
||||
public Identifier SecondRequiredTargetTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Optional tag of a hull the target must be inside.")]
|
||||
public Identifier HullTag { get; set; }
|
||||
|
||||
[Serialize(-1, IsPropertySaveable.Yes)]
|
||||
public int MinAmount { get; set; }
|
||||
|
||||
[Serialize(-1, IsPropertySaveable.Yes)]
|
||||
public int MaxAmount { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier CompareToTarget { get; set; }
|
||||
|
||||
[Serialize(-1.0f, IsPropertySaveable.Yes)]
|
||||
|
||||
/// <summary>
|
||||
/// Minimum amount of targets, as a percentage of the number of entities tagged with CompareToTarget
|
||||
/// E.g. you could compare the number of entities tagged as "discoveredhull" to entities tagged as "anyhull" to require 50% of hulls to be discovered.
|
||||
/// </summary>
|
||||
public float MinPercentageRelativeToTarget { get; set; }
|
||||
|
||||
[Serialize(-1.0f, IsPropertySaveable.Yes)]
|
||||
|
||||
/// <summary>
|
||||
/// Maximum amount of targets, as a percentage of the number of entities tagged with CompareToTarget
|
||||
/// E.g. you could compare the number of entities tagged as "floodedhull" to entities tagged as "anyhull" to require less than 50% of hulls to be flooded.
|
||||
/// </summary>
|
||||
public float MaxPercentageRelativeToTarget { get; set; }
|
||||
|
||||
private readonly IReadOnlyList<PropertyConditional> conditionals;
|
||||
|
||||
public CountTargetsAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
var conditionalList = new List<PropertyConditional>();
|
||||
foreach (ContentXElement subElement in element.GetChildElements("conditional"))
|
||||
{
|
||||
conditionalList.AddRange(PropertyConditional.FromXElement(subElement!));
|
||||
}
|
||||
conditionals = conditionalList;
|
||||
|
||||
if (CompareToTarget.IsEmpty)
|
||||
{
|
||||
int amount = element.GetAttributeInt("amount", -1);
|
||||
if (amount > -1)
|
||||
{
|
||||
MinAmount = MaxAmount = amount;
|
||||
}
|
||||
if (MinAmount > MaxAmount && MaxAmount > -1)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {MinAmount} is larger than {MaxAmount} in {nameof(CountTargetsAction)}.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (MinPercentageRelativeToTarget < 0.0f && MaxPercentageRelativeToTarget < 0.0f)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". Comparing to another target, but neither {nameof(MinPercentageRelativeToTarget)} or {nameof(MaxPercentageRelativeToTarget)} is set.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
var potentialTargets = ParentEvent.GetTargets(TargetTag);
|
||||
|
||||
if (!SecondRequiredTargetTag.IsEmpty)
|
||||
{
|
||||
potentialTargets = potentialTargets.Where(t => ParentEvent.GetTargets(SecondRequiredTargetTag).Contains(t));
|
||||
}
|
||||
if (!HullTag.IsEmpty)
|
||||
{
|
||||
var hulls = ParentEvent.GetTargets(HullTag).OfType<Hull>();
|
||||
potentialTargets = potentialTargets.Where(t =>
|
||||
(t is Item it && hulls.Contains(it.CurrentHull)) ||
|
||||
(t is Character c && hulls.Contains(c.CurrentHull)));
|
||||
}
|
||||
|
||||
if (conditionals.Any())
|
||||
{
|
||||
potentialTargets = potentialTargets.Where(t => conditionals.Any(c => c.Matches(t as ISerializableEntity)));
|
||||
}
|
||||
|
||||
int targetCount = potentialTargets.Count();
|
||||
|
||||
if (CompareToTarget.IsEmpty)
|
||||
{
|
||||
if (MinAmount > -1 && targetCount < MinAmount) { return false; }
|
||||
if (MaxAmount > -1 && targetCount > MaxAmount) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
int compareToTargetCount = ParentEvent.GetTargets(CompareToTarget).Count();
|
||||
float percentage = MathUtils.Percentage(targetCount, compareToTargetCount);
|
||||
if (MinPercentageRelativeToTarget > -1 && percentage < MinPercentageRelativeToTarget) { return false; }
|
||||
if (MaxPercentageRelativeToTarget > -1 && percentage > MaxPercentageRelativeToTarget) { return false; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(HasBeenDetermined())} {nameof(CountTargetsAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
|
||||
$"Succeeded: {succeeded.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -138,12 +137,17 @@ namespace Barotrauma
|
||||
Type actionType;
|
||||
try
|
||||
{
|
||||
actionType = Type.GetType("Barotrauma." + element.Name, true, true);
|
||||
Identifier typeName = element.Name.ToString().ToIdentifier();
|
||||
if (typeName == "TutorialSegmentAction")
|
||||
{
|
||||
typeName = "EventObjectiveAction".ToIdentifier();
|
||||
}
|
||||
actionType = Type.GetType("Barotrauma." + typeName, throwOnError: true, ignoreCase: true);
|
||||
if (actionType == null) { throw new NullReferenceException(); }
|
||||
}
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find an event class of the type \"" + element.Name + "\".");
|
||||
DebugConsole.ThrowError($"Could not find an {nameof(EventAction)} class of the type \"{element.Name}\".");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class EventLogAction : EventAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier Id { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string Text { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
public bool ShowInServerLog { get; set; }
|
||||
|
||||
private readonly XElement? textElement;
|
||||
|
||||
public EventLogAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (Id == Identifier.Empty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\". {nameof(EventLogAction)} with no id.");
|
||||
}
|
||||
//append the target tag so logs targeted to different players don't interfere with each other even if they use the same Id
|
||||
Id = (Id.ToString() + TargetTag).ToIdentifier();
|
||||
|
||||
foreach (var elem in element.Elements())
|
||||
{
|
||||
if (elem.Name.LocalName.Equals("text", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
textElement = elem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Text ??= string.Empty;
|
||||
if (textElement == null)
|
||||
{
|
||||
if (Text.IsNullOrEmpty())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\". {nameof(EventLogAction)} with no text set ({element}).");
|
||||
}
|
||||
else
|
||||
{
|
||||
Text = TextManager.Get(Text).Fallback(Text).Value;
|
||||
}
|
||||
}
|
||||
ShowInServerLog = element.GetAttributeBool(nameof(ShowInServerLog), ParentEvent is TraitorEvent);
|
||||
}
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
|
||||
public LocalizedString GetDisplayText()
|
||||
{
|
||||
LocalizedString text = Text;
|
||||
if (textElement != null)
|
||||
{
|
||||
LocalizedString tempDescription = string.Empty;
|
||||
TextManager.ConstructDescription(ref tempDescription, textElement, ParentEvent.GetTextForReplacementElement);
|
||||
text = tempDescription.Value;
|
||||
}
|
||||
return ParentEvent.ReplaceVariablesInEventText(text);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
AddEntryProjSpecific(GameMain.GameSession?.EventManager?.EventLog, GetDisplayText().Value);
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
partial void AddEntryProjSpecific(EventLog? eventLog, string displayText);
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(EventLogAction)} -> (Id: {Id})";
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-3
@@ -1,8 +1,8 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class TutorialSegmentAction : EventAction
|
||||
partial class EventObjectiveAction : EventAction
|
||||
{
|
||||
public enum SegmentActionType { Trigger, Add, Complete, CompleteAndRemove, Remove };
|
||||
public enum SegmentActionType { Trigger, Add, Complete, CompleteAndRemove, Remove, Fail, FailAndRemove };
|
||||
|
||||
[Serialize(SegmentActionType.Trigger, IsPropertySaveable.Yes)]
|
||||
public SegmentActionType Type { get; set; }
|
||||
@@ -34,14 +34,29 @@ namespace Barotrauma
|
||||
[Serialize(80, IsPropertySaveable.Yes)]
|
||||
public int Height { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TutorialSegmentAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
public EventObjectiveAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (Identifier.IsEmpty)
|
||||
{
|
||||
Identifier = element.GetAttributeIdentifier("id", Identifier.Empty);
|
||||
}
|
||||
if (Type != SegmentActionType.Trigger && !TextTag.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
$"Error in {nameof(EventObjectiveAction)} in the event \"{parentEvent.Prefab.Identifier}\""+
|
||||
$" - {nameof(TextTag)} will do nothing unless the action triggers a message box or a video.");
|
||||
}
|
||||
if (element.GetChildElement("Replace") != null)
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
$"Error in {nameof(EventObjectiveAction)} in the event \"{parentEvent.Prefab.Identifier}\"" +
|
||||
$" - unrecognized child element \"Replace\".");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GiveExpAction : EventAction
|
||||
{
|
||||
[Serialize(0, IsPropertySaveable.Yes)]
|
||||
public int Amount { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
public GiveExpAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (TargetTag.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(GiveExpAction)} without a target tag (the action needs to know whose skill to check).");
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
var targets = ParentEvent.GetTargets(TargetTag).Where(e => e is Character).Select(e => e as Character);
|
||||
foreach (var target in targets)
|
||||
{
|
||||
target.Info?.GiveExperience(Amount);
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(GiveExpAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
|
||||
$"Amount: {Amount.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -19,7 +17,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (TargetTag.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": GiveSkillExpAction without a target tag (the action needs to know whose skill to check).");
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(GiveSkillExpAction)} without a target tag (the action needs to know whose skill to check).");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GoTo : EventAction
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace Barotrauma
|
||||
WayPoint.WayPointList.Find(wp => wp.Submarine == sub && wp.SpawnType == SpawnType.Human);
|
||||
if (subWaypoint != null)
|
||||
{
|
||||
npc.GiveIdCardTags(subWaypoint, createNetworkEvent: true);
|
||||
npc.GiveIdCardTags(subWaypoint, requireSpawnPointTagsNotGiven: false, createNetworkEvent: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ namespace Barotrauma
|
||||
public NPCFollowAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
|
||||
private List<Character> affectedNpcs = null;
|
||||
private Entity target = null;
|
||||
private IEnumerable<Character> affectedNpcs;
|
||||
private Entity target;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
@@ -36,9 +36,10 @@ namespace Barotrauma
|
||||
if (target == null) { return; }
|
||||
|
||||
int targetCount = 0;
|
||||
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList();
|
||||
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character);
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.Removed) { continue; }
|
||||
if (npc.AIController is not HumanAIController humanAiController) { continue; }
|
||||
|
||||
if (Follow)
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace Barotrauma
|
||||
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList();
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.Removed) { continue; }
|
||||
if (npc.AIController is not HumanAIController humanAiController) { continue; }
|
||||
|
||||
if (Operate)
|
||||
|
||||
@@ -27,6 +27,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.Removed) { continue; }
|
||||
if (npc.AIController is not HumanAIController humanAiController) { continue; }
|
||||
|
||||
if (Wait)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class OnRoundEndAction : EventAction
|
||||
{
|
||||
private readonly SubactionGroup subActions;
|
||||
|
||||
public OnRoundEndAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
subActions = new SubactionGroup(parentEvent, element);
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goToLabel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
int remainingTries = 100;
|
||||
string? throwaway = null;
|
||||
//normally the ref string goTo passed to IsFinished should be used to jump another place in the event,
|
||||
//but in this case we don't want that (the subactions should just run once when the round ends)
|
||||
while (remainingTries > 0 && !subActions.IsFinished(ref throwaway))
|
||||
{
|
||||
subActions.Update(deltaTime);
|
||||
Entity.Spawner?.Update(createNetworkEvents: false);
|
||||
remainingTries--;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return nameof(OnRoundEndAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
#nullable enable
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SetTraitorEventStateAction : EventAction
|
||||
{
|
||||
private readonly TraitorEvent? traitorEvent;
|
||||
|
||||
public SetTraitorEventStateAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (parentEvent is TraitorEvent traitorEvent)
|
||||
{
|
||||
this.traitorEvent = traitorEvent;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Cannot use the action {nameof(SetTraitorEventStateAction)} in the event \"{parentEvent.Prefab.Identifier}\" because it's not a traitor event.");
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(TraitorEvent.State.Completed, IsPropertySaveable.Yes)]
|
||||
public TraitorEvent.State State { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished || traitorEvent == null) { return; }
|
||||
traitorEvent.CurrentState = State;
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(SetTraitorEventStateAction)} -> (State: {State})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,8 @@ namespace Barotrauma
|
||||
MainPath,
|
||||
Ruin,
|
||||
Wreck,
|
||||
BeaconStation
|
||||
BeaconStation,
|
||||
NearMainSub
|
||||
}
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Species name of the character to spawn.")]
|
||||
@@ -60,6 +61,9 @@ namespace Barotrauma
|
||||
[Serialize(true, IsPropertySaveable.Yes, description: "If false, we won't spawn another character if one with the same identifier has already been spawned.")]
|
||||
public bool AllowDuplicates { get; set; }
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes)]
|
||||
public int Amount { get; set; }
|
||||
|
||||
[Serialize(100.0f, IsPropertySaveable.Yes)]
|
||||
public float Offset { get; set; }
|
||||
|
||||
@@ -84,6 +88,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should the AI ignore this item. This will prevent outpost NPCs cleaning up or otherwise using important items intended to be left for the players.")]
|
||||
public bool IgnoreByAI { get; set; }
|
||||
|
||||
[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; }
|
||||
|
||||
private bool spawned;
|
||||
private Entity spawnedEntity;
|
||||
|
||||
@@ -159,40 +166,43 @@ namespace Barotrauma
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), humanPrefab.CreateCharacterInfo(), onSpawn: newCharacter =>
|
||||
for (int i = 0; i < Amount; i++)
|
||||
{
|
||||
if (newCharacter == null) { return; }
|
||||
newCharacter.HumanPrefab = humanPrefab;
|
||||
newCharacter.TeamID = TeamID;
|
||||
newCharacter.EnableDespawn = false;
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine, spawnPos as WayPoint);
|
||||
if (LootingIsStealing)
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), humanPrefab.CreateCharacterInfo(), onSpawn: newCharacter =>
|
||||
{
|
||||
foreach (Item item in newCharacter.Inventory.FindAllItems(recursive: true))
|
||||
if (newCharacter == null) { return; }
|
||||
newCharacter.HumanPrefab = humanPrefab;
|
||||
newCharacter.TeamID = TeamID;
|
||||
newCharacter.EnableDespawn = false;
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine, spawnPos as WayPoint);
|
||||
if (LootingIsStealing)
|
||||
{
|
||||
item.SpawnedInCurrentOutpost = true;
|
||||
item.AllowStealing = false;
|
||||
foreach (Item item in newCharacter.Inventory.FindAllItems(recursive: true))
|
||||
{
|
||||
item.SpawnedInCurrentOutpost = true;
|
||||
item.AllowStealing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
humanPrefab.InitializeCharacter(newCharacter, spawnPos);
|
||||
if (!TargetTag.IsEmpty && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
if (Level.Loaded?.StartOutpost?.Info is { } outPostInfo)
|
||||
{
|
||||
outPostInfo.AddOutpostNPCIdentifierOrTag(newCharacter, humanPrefab.Identifier);
|
||||
foreach (Identifier tag in humanPrefab.GetTags())
|
||||
humanPrefab.InitializeCharacter(newCharacter, spawnPos);
|
||||
if (!TargetTag.IsEmpty && newCharacter != null)
|
||||
{
|
||||
outPostInfo.AddOutpostNPCIdentifierOrTag(newCharacter, tag);
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
if (Level.Loaded?.StartOutpost?.Info is { } outPostInfo)
|
||||
{
|
||||
outPostInfo.AddOutpostNPCIdentifierOrTag(newCharacter, humanPrefab.Identifier);
|
||||
foreach (Identifier tag in humanPrefab.GetTags())
|
||||
{
|
||||
outPostInfo.AddOutpostNPCIdentifierOrTag(newCharacter, tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
newCharacter.LoadTalents();
|
||||
GameMain.NetworkMember.CreateEntityEvent(newCharacter, new Character.UpdateTalentsEventData());
|
||||
newCharacter.LoadTalents();
|
||||
GameMain.NetworkMember.CreateEntityEvent(newCharacter, new Character.UpdateTalentsEventData());
|
||||
#endif
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,14 +216,17 @@ namespace Barotrauma
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), onSpawn: newCharacter =>
|
||||
for (int i = 0; i < Amount; i++)
|
||||
{
|
||||
if (!TargetTag.IsEmpty && newCharacter != null)
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), onSpawn: newCharacter =>
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
});
|
||||
if (!TargetTag.IsEmpty && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!ItemIdentifier.IsEmpty)
|
||||
@@ -243,7 +256,7 @@ namespace Barotrauma
|
||||
|
||||
if (spawnInventory == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not spawn \"{ItemIdentifier}\" in target inventory \"{TargetInventory}\"");
|
||||
DebugConsole.ThrowError($"Could not spawn \"{ItemIdentifier}\" in target inventory \"{TargetInventory}\" - matching target not found.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,12 +265,19 @@ namespace Barotrauma
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), onSpawned: onSpawned);
|
||||
for (int i = 0; i < Amount; i++)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), onSpawned: onSpawned);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, spawnInventory, onSpawned: onSpawned);
|
||||
for (int i = 0; i < Amount; i++)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, spawnInventory, onSpawned: onSpawned);
|
||||
|
||||
}
|
||||
}
|
||||
void onSpawned(Item newItem)
|
||||
{
|
||||
@@ -298,18 +318,27 @@ namespace Barotrauma
|
||||
{
|
||||
if (!SpawnPointTag.IsEmpty)
|
||||
{
|
||||
List<Item> potentialItems = Item.ItemList.FindAll(it => IsValidSubmarineType(SpawnLocation, it.Submarine));
|
||||
IEnumerable<Item> potentialItems = Item.ItemList.Where(it => IsValidSubmarineType(SpawnLocation, it.Submarine));
|
||||
if (!AllowInPlayerView)
|
||||
{
|
||||
potentialItems = GetEntitiesNotInPlayerView(potentialItems);
|
||||
}
|
||||
var item = potentialItems.Where(it => it.HasTag(SpawnPointTag)).GetRandomUnsynced();
|
||||
if (item != null) { return item; }
|
||||
|
||||
var target = ParentEvent.GetTargets(SpawnPointTag).Where(t => IsValidSubmarineType(SpawnLocation, t.Submarine)).GetRandomUnsynced();
|
||||
var potentialTargets = ParentEvent.GetTargets(SpawnPointTag).Where(t => IsValidSubmarineType(SpawnLocation, t.Submarine));
|
||||
if (!AllowInPlayerView)
|
||||
{
|
||||
potentialTargets = GetEntitiesNotInPlayerView(potentialTargets);
|
||||
}
|
||||
var target = potentialTargets.GetRandomUnsynced();
|
||||
if (target != null) { return target; }
|
||||
}
|
||||
|
||||
SpawnType? spawnPointType = null;
|
||||
if (!ignoreSpawnPointType) { spawnPointType = SpawnPointType; }
|
||||
|
||||
return GetSpawnPos(SpawnLocation, spawnPointType, targetModuleTags, SpawnPointTag.ToEnumerable(), requireTaggedSpawnPoint: RequireSpawnPointTag);
|
||||
return GetSpawnPos(SpawnLocation, spawnPointType, targetModuleTags, SpawnPointTag.ToEnumerable(), requireTaggedSpawnPoint: RequireSpawnPointTag, allowInPlayerView: AllowInPlayerView);
|
||||
}
|
||||
|
||||
private static bool IsValidSubmarineType(SpawnLocationType spawnLocation, Submarine submarine)
|
||||
@@ -318,16 +347,39 @@ namespace Barotrauma
|
||||
{
|
||||
SpawnLocationType.Any => true,
|
||||
SpawnLocationType.MainSub => submarine == Submarine.MainSub,
|
||||
SpawnLocationType.NearMainSub => submarine == null,
|
||||
SpawnLocationType.MainPath => submarine == null,
|
||||
SpawnLocationType.Outpost => submarine is { Info: { IsOutpost: true } },
|
||||
SpawnLocationType.Wreck => submarine is { Info: { IsWreck: true } },
|
||||
SpawnLocationType.Ruin => submarine is { Info: { IsRuin: true } },
|
||||
SpawnLocationType.Outpost => submarine is { Info.IsOutpost: true },
|
||||
SpawnLocationType.Wreck => submarine is { Info.IsWreck: true },
|
||||
SpawnLocationType.Ruin => submarine is { Info.IsRuin: true },
|
||||
SpawnLocationType.BeaconStation => submarine?.Info?.BeaconStationInfo != null,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<Identifier> moduleFlags = null, IEnumerable<Identifier> spawnpointTags = null, bool asFarAsPossibleFromAirlock = false, bool requireTaggedSpawnPoint = false)
|
||||
/// <summary>
|
||||
/// Returns those of the entities that aren't in any player's view. If there are none, all the entities are returned.
|
||||
/// </summary>
|
||||
private static IEnumerable<T> GetEntitiesNotInPlayerView<T>(IEnumerable<T> entities) where T : ISpatialEntity
|
||||
{
|
||||
if (entities.Any(e => !IsInPlayerView(e)))
|
||||
{
|
||||
return entities.Where(e => !IsInPlayerView(e));
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
private static bool IsInPlayerView(ISpatialEntity entity)
|
||||
{
|
||||
foreach (var character in Character.CharacterList)
|
||||
{
|
||||
if (!character.IsPlayer || character.IsDead) { continue; }
|
||||
if (character.CanSeeTarget(entity)) { return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<Identifier> moduleFlags = null, IEnumerable<Identifier> spawnpointTags = null, bool asFarAsPossibleFromAirlock = false, bool requireTaggedSpawnPoint = false, bool allowInPlayerView = true)
|
||||
{
|
||||
bool requireHull = spawnLocation == SpawnLocationType.MainSub || spawnLocation == SpawnLocationType.Outpost;
|
||||
List<WayPoint> potentialSpawnPoints = WayPoint.WayPointList.FindAll(wp => IsValidSubmarineType(spawnLocation, wp.Submarine) && (wp.CurrentHull != null || !requireHull));
|
||||
@@ -385,6 +437,12 @@ namespace Barotrauma
|
||||
return potentialSpawnPoints.GetRandomUnsynced();
|
||||
}
|
||||
|
||||
if (spawnLocation == SpawnLocationType.MainPath || spawnLocation == SpawnLocationType.NearMainSub)
|
||||
{
|
||||
validSpawnPoints = validSpawnPoints.Where(p =>
|
||||
Submarine.Loaded.None(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(p.WorldPosition)));
|
||||
}
|
||||
|
||||
//avoid using waypoints if there's any actual spawnpoints available
|
||||
if (validSpawnPoints.Any(wp => wp.SpawnType != SpawnType.Path))
|
||||
{
|
||||
@@ -401,7 +459,27 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (asFarAsPossibleFromAirlock && airlockSpawnPoints.Any())
|
||||
if (!allowInPlayerView)
|
||||
{
|
||||
validSpawnPoints = GetEntitiesNotInPlayerView(validSpawnPoints);
|
||||
}
|
||||
|
||||
if (spawnLocation == SpawnLocationType.NearMainSub && Submarine.MainSub != null)
|
||||
{
|
||||
WayPoint closestPoint = validSpawnPoints.First();
|
||||
float closestDist = float.PositiveInfinity;
|
||||
foreach (WayPoint wp in validSpawnPoints)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(wp.WorldPosition, Submarine.MainSub.WorldPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
closestPoint = wp;
|
||||
}
|
||||
}
|
||||
return closestPoint;
|
||||
}
|
||||
else if (asFarAsPossibleFromAirlock && airlockSpawnPoints.Any())
|
||||
{
|
||||
WayPoint furthestPoint = validSpawnPoints.First();
|
||||
float furthestDist = 0.0f;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
@@ -24,14 +25,25 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool AllowHiddenItems { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool ChooseRandom { get; set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "If larger than 0, the specified percentage of the matching targets are tagged. Between 0-100.")]
|
||||
public float ChoosePercentage { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
private bool targetNotFound = false;
|
||||
|
||||
public TagAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
Taggers = new (string k, Action<Identifier> v)[]
|
||||
{
|
||||
("players", v => TagPlayers()),
|
||||
("player", v => TagPlayers()),
|
||||
("traitor", v => TagTraitors()),
|
||||
("nontraitor", v => TagNonTraitors()),
|
||||
("nontraitorplayer", v => TagNonTraitorPlayers()),
|
||||
("bot", v => TagBots(playerCrewOnly: false)),
|
||||
("crew", v => TagCrew()),
|
||||
("humanprefabidentifier", TagHumansByIdentifier),
|
||||
@@ -40,8 +52,10 @@ namespace Barotrauma
|
||||
("structurespecialtag", TagStructuresBySpecialTag),
|
||||
("itemidentifier", TagItemsByIdentifier),
|
||||
("itemtag", TagItemsByTag),
|
||||
("hull", v => TagHulls()),
|
||||
("hullname", TagHullsByName),
|
||||
("submarine", TagSubmarinesByType),
|
||||
("eventtag", TagByEventTag),
|
||||
}.Select(t => (t.k.ToIdentifier(), t.v)).ToImmutableDictionary();
|
||||
}
|
||||
|
||||
@@ -54,34 +68,44 @@ namespace Barotrauma
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
private void TagByEventTag(Identifier eventTag)
|
||||
{
|
||||
AddTarget(Tag, ParentEvent.GetTargets(eventTag).Where(t => SubmarineTypeMatches(t.Submarine)));
|
||||
}
|
||||
|
||||
private void TagPlayers()
|
||||
{
|
||||
if (IgnoreIncapacitatedCharacters)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsPlayer && !c.IsIncapacitated);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsPlayer);
|
||||
}
|
||||
AddTargetPredicate(Tag, e => e is Character c && c.IsPlayer && (!c.IsIncapacitated || !IgnoreIncapacitatedCharacters));
|
||||
}
|
||||
|
||||
private void TagTraitors()
|
||||
{
|
||||
AddTargetPredicate(Tags.Traitor, e => e is Character c && (c.IsPlayer || c.IsBot) && c.IsTraitor && !c.IsIncapacitated);
|
||||
}
|
||||
|
||||
private void TagNonTraitors()
|
||||
{
|
||||
AddTargetPredicate(Tags.NonTraitor, e => e is Character c && (c.IsPlayer || c.IsBot) && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated);
|
||||
}
|
||||
|
||||
private void TagNonTraitorPlayers()
|
||||
{
|
||||
AddTargetPredicate(Tags.NonTraitorPlayer, e => e is Character c && c.IsPlayer && !c.IsTraitor && c.IsOnPlayerTeam && !c.IsIncapacitated);
|
||||
}
|
||||
|
||||
private void TagBots(bool playerCrewOnly)
|
||||
{
|
||||
if (IgnoreIncapacitatedCharacters)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsBot && !c.IsIncapacitated && (!playerCrewOnly || c.TeamID == CharacterTeamType.Team1));
|
||||
}
|
||||
else
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsBot && (!playerCrewOnly || c.TeamID == CharacterTeamType.Team1));
|
||||
}
|
||||
AddTargetPredicate(Tag, e =>
|
||||
e is Character c &&
|
||||
c.IsBot &&
|
||||
(!c.IsIncapacitated || !IgnoreIncapacitatedCharacters) &&
|
||||
(!playerCrewOnly || c.TeamID == CharacterTeamType.Team1));
|
||||
}
|
||||
|
||||
private void TagCrew()
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.GetCharacters().ForEach(c => ParentEvent.AddTarget(Tag, c));
|
||||
AddTarget(Tag, GameMain.GameSession.CrewManager.GetCharacters());
|
||||
#else
|
||||
TagPlayers();
|
||||
TagBots(playerCrewOnly: true);
|
||||
@@ -90,54 +114,47 @@ namespace Barotrauma
|
||||
|
||||
private void TagHumansByIdentifier(Identifier identifier)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.HumanPrefab?.Identifier == identifier)
|
||||
{
|
||||
ParentEvent.AddTarget(Tag, c);
|
||||
}
|
||||
}
|
||||
AddTarget(Tag, Character.CharacterList.Where(c => c.HumanPrefab?.Identifier == identifier));
|
||||
}
|
||||
|
||||
private void TagHumansByJobIdentifier(Identifier jobIdentifier)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.HasJob(jobIdentifier))
|
||||
{
|
||||
ParentEvent.AddTarget(Tag, c);
|
||||
}
|
||||
}
|
||||
AddTarget(Tag, Character.CharacterList.Where(c => c.HasJob(jobIdentifier)));
|
||||
}
|
||||
|
||||
private void TagStructuresByIdentifier(Identifier identifier)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.Prefab.Identifier == identifier);
|
||||
AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.Prefab.Identifier == identifier);
|
||||
}
|
||||
|
||||
private void TagStructuresBySpecialTag(Identifier tag)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.SpecialTag.ToIdentifier() == tag);
|
||||
AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.SpecialTag.ToIdentifier() == tag);
|
||||
}
|
||||
|
||||
private void TagItemsByIdentifier(Identifier identifier)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && IsValidItem(it) && it.Prefab.Identifier == identifier);
|
||||
AddTargetPredicate(Tag, e => e is Item it && IsValidItem(it) && it.Prefab.Identifier == identifier);
|
||||
}
|
||||
|
||||
private void TagItemsByTag(Identifier tag)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && IsValidItem(it) && it.HasTag(tag));
|
||||
AddTargetPredicate(Tag, e => e is Item it && IsValidItem(it) && it.HasTag(tag));
|
||||
}
|
||||
|
||||
private void TagHulls()
|
||||
{
|
||||
AddTargetPredicate(Tag, e => e is Hull h && SubmarineTypeMatches(h.Submarine));
|
||||
}
|
||||
|
||||
private void TagHullsByName(Identifier name)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Hull h && SubmarineTypeMatches(h.Submarine) && h.RoomName.Contains(name.Value, StringComparison.OrdinalIgnoreCase));
|
||||
AddTargetPredicate(Tag, e => e is Hull h && SubmarineTypeMatches(h.Submarine) && h.RoomName.Contains(name.Value, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private void TagSubmarinesByType(Identifier type)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Submarine s && SubmarineTypeMatches(s) && (type.IsEmpty || type == s.Info?.Type.ToIdentifier()));
|
||||
AddTargetPredicate(Tag, e => e is Submarine s && SubmarineTypeMatches(s) && (type.IsEmpty || type == s.Info?.Type.ToIdentifier()));
|
||||
}
|
||||
|
||||
private bool IsValidItem(Item it)
|
||||
@@ -152,7 +169,7 @@ namespace Barotrauma
|
||||
switch (sub.Info.Type)
|
||||
{
|
||||
case Barotrauma.SubmarineType.Player:
|
||||
return SubmarineType.HasFlag(SubType.Player);
|
||||
return SubmarineType.HasFlag(SubType.Player) && sub != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle;
|
||||
case Barotrauma.SubmarineType.Outpost:
|
||||
case Barotrauma.SubmarineType.OutpostModule:
|
||||
return SubmarineType.HasFlag(SubType.Outpost);
|
||||
@@ -165,14 +182,86 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTargetPredicate(Identifier tag, Predicate<Entity> predicate)
|
||||
{
|
||||
if (ChoosePercentage > 0.0f)
|
||||
{
|
||||
TagPercentage(tag, Entity.GetEntities().Where(e => predicate(e)));
|
||||
}
|
||||
else if (ChooseRandom)
|
||||
{
|
||||
TagRandom(tag, Entity.GetEntities().Where(e => predicate(e)));
|
||||
}
|
||||
else
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(tag, predicate);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTarget(Identifier tag, IEnumerable<Entity> entities)
|
||||
{
|
||||
if (entities.None())
|
||||
{
|
||||
targetNotFound = true;
|
||||
return;
|
||||
}
|
||||
if (ChoosePercentage > 0.0f)
|
||||
{
|
||||
TagPercentage(tag, entities);
|
||||
}
|
||||
else if (ChooseRandom)
|
||||
{
|
||||
TagRandom(tag, entities);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
ParentEvent.AddTarget(tag, entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Entity> tempEntities;
|
||||
private void TagPercentage(Identifier tag, IEnumerable<Entity> entities)
|
||||
{
|
||||
if (entities.None())
|
||||
{
|
||||
targetNotFound = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int amountToChoose = (int)Math.Ceiling(entities.Count() * (ChoosePercentage / 100.0f));
|
||||
|
||||
tempEntities ??= new List<Entity>();
|
||||
tempEntities.Clear();
|
||||
for (int i = 0; i < amountToChoose; i++)
|
||||
{
|
||||
var entity = entities.GetRandomUnsynced();
|
||||
tempEntities.Remove(entity);
|
||||
ParentEvent.AddTarget(tag, entity);
|
||||
}
|
||||
}
|
||||
|
||||
private void TagRandom(Identifier tag, IEnumerable<Entity> entities)
|
||||
{
|
||||
if (entities.None())
|
||||
{
|
||||
targetNotFound = true;
|
||||
return;
|
||||
}
|
||||
ParentEvent.AddTarget(tag, entities.GetRandomUnsynced());
|
||||
}
|
||||
|
||||
private readonly ImmutableDictionary<Identifier, Action<Identifier>> Taggers;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
if (isFinished || targetNotFound) { return; }
|
||||
|
||||
string[] criteriaSplit = Criteria.Split(';');
|
||||
|
||||
targetNotFound = false;
|
||||
foreach (string entry in criteriaSplit)
|
||||
{
|
||||
string[] kvp = entry.Split(':');
|
||||
@@ -190,7 +279,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
isFinished = true;
|
||||
isFinished = !targetNotFound;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
|
||||
@@ -2,7 +2,6 @@ using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -50,6 +49,9 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "If true and using multiple targets, all targets must be inside/outside the radius.")]
|
||||
public bool CheckAllTargets { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "If true, interacting with the target will make the character select it.")]
|
||||
public bool SelectOnTrigger { get; set; }
|
||||
|
||||
private float distance;
|
||||
|
||||
public TriggerAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
@@ -186,7 +188,12 @@ namespace Barotrauma
|
||||
{
|
||||
if (npc != null)
|
||||
{
|
||||
if (npc.CampaignInteractionType != CampaignMode.InteractionType.Examine)
|
||||
if (npc.CampaignInteractionType == CampaignMode.InteractionType.Talk)
|
||||
{
|
||||
//if the NPC has a conversation available, don't assign the trigger until the conversation is done
|
||||
continue;
|
||||
}
|
||||
else if (npc.CampaignInteractionType != CampaignMode.InteractionType.Examine)
|
||||
{
|
||||
if (!npcsOrItems.Any(n => n.TryGet(out Character npc2) && npc2 == npc))
|
||||
{
|
||||
@@ -213,7 +220,8 @@ namespace Barotrauma
|
||||
{
|
||||
npcsOrItems.Add(item);
|
||||
}
|
||||
item.CampaignInteractionType = CampaignMode.InteractionType.Examine;
|
||||
item.AssignCampaignInteractionType(CampaignMode.InteractionType.Examine,
|
||||
GameMain.NetworkMember?.ConnectedClients.Where(c => c.Character != null && targets2.Contains(c.Character)));
|
||||
if (player.SelectedItem == item ||
|
||||
player.SelectedSecondaryItem == item ||
|
||||
(player.Inventory != null && player.Inventory.Contains(item)) ||
|
||||
@@ -276,7 +284,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (npcOrItem.TryGet(out Item item))
|
||||
{
|
||||
item.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
item.AssignCampaignInteractionType(CampaignMode.InteractionType.None);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,6 +360,37 @@ namespace Barotrauma
|
||||
ParentEvent.AddTarget(ApplyToTarget2, entity2);
|
||||
}
|
||||
|
||||
Character player = null;
|
||||
Entity target = null;
|
||||
if (entity1 is Character { IsPlayer: true })
|
||||
{
|
||||
player = entity1 as Character;
|
||||
target = entity2;
|
||||
}
|
||||
else if (entity2 is Character { IsPlayer: true })
|
||||
{
|
||||
player = entity2 as Character;
|
||||
target = entity1;
|
||||
}
|
||||
if (player != null && SelectOnTrigger)
|
||||
{
|
||||
if (target is Character targetCharacter)
|
||||
{
|
||||
player.SelectCharacter(targetCharacter);
|
||||
}
|
||||
else if (target is Item targetItem)
|
||||
{
|
||||
if (targetItem.IsSecondaryItem)
|
||||
{
|
||||
player.SelectedSecondaryItem = targetItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
player.SelectedItem = targetItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isRunning = false;
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
+7
-1
@@ -10,7 +10,13 @@ partial class TutorialHighlightAction : EventAction
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TutorialHighlightAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
public TutorialHighlightAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": {nameof(TutorialHighlightAction)} is not supported in multiplayer.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
#nullable enable
|
||||
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class WaitForItemFabricatedAction : EventAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier CharacterTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ItemIdentifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ItemTag { get; set; }
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes)]
|
||||
public int Amount { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the fabricated item(s).")]
|
||||
public Identifier ApplyTagToItem { get; set; }
|
||||
|
||||
private int counter;
|
||||
|
||||
public WaitForItemFabricatedAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (ItemTag.IsEmpty && ItemIdentifier.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(WaitForItemFabricatedAction)} does't define either a tag or an identifier of the item to check.");
|
||||
}
|
||||
foreach (var item in Item.ItemList)
|
||||
{
|
||||
var fabricator = item.GetComponent<Fabricator>();
|
||||
if (fabricator != null)
|
||||
{
|
||||
fabricator.OnItemFabricated += OnItemFabricated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnItemFabricated(Item item, Character character)
|
||||
{
|
||||
if (item == null) { return; }
|
||||
if (!CharacterTag.IsEmpty)
|
||||
{
|
||||
if (!ParentEvent.GetTargets(CharacterTag).Contains(character)) { return; }
|
||||
}
|
||||
if (item.ContainerIdentifier == ItemTag || item.HasTag(ItemTag))
|
||||
{
|
||||
if (!ApplyTagToItem.IsEmpty)
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyTagToItem, item);
|
||||
}
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return counter >= Amount;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
counter = 0;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(counter >= Amount)} {nameof(WaitForItemFabricatedAction)} -> ({ItemTag}, {counter}/{Amount})";
|
||||
}
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
#nullable enable
|
||||
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class WaitForItemUsedAction : EventAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ItemTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier UserTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetItemComponent { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the target item when it's used.")]
|
||||
public Identifier ApplyTagToItem { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the user when the target item is used.")]
|
||||
public Identifier ApplyTagToUser{ get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the hull the target item is inside when the item is used.")]
|
||||
public Identifier ApplyTagToHull { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the hull the target item is inside, and all the hulls it's linked to, when the item is used.")]
|
||||
public Identifier ApplyTagToLinkedHulls { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
private readonly HashSet<Entity> targets = new HashSet<Entity>();
|
||||
private readonly HashSet<ItemComponent> targetComponents = new HashSet<ItemComponent>();
|
||||
|
||||
private Identifier onUseEventIdentifier;
|
||||
private Identifier OnUseEventIdentifier
|
||||
{
|
||||
get
|
||||
{
|
||||
if (onUseEventIdentifier.IsEmpty)
|
||||
{
|
||||
onUseEventIdentifier = (ParentEvent.Prefab.Identifier + ParentEvent.Actions.IndexOf(this).ToString()).ToIdentifier();
|
||||
}
|
||||
return onUseEventIdentifier;
|
||||
}
|
||||
}
|
||||
|
||||
public WaitForItemUsedAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (ItemTag.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\". {nameof(ItemTag)} not set in {nameof(WaitForItemUsedAction)}.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnItemUsed(Item item, Character user)
|
||||
{
|
||||
if (!ApplyTagToItem.IsEmpty)
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyTagToItem, item);
|
||||
}
|
||||
if (!ApplyTagToUser.IsEmpty && user != null)
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyTagToUser, user);
|
||||
}
|
||||
if (item.CurrentHull != null)
|
||||
{
|
||||
if (!ApplyTagToHull.IsEmpty)
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyTagToHull, item.CurrentHull);
|
||||
}
|
||||
if (!ApplyTagToLinkedHulls.IsEmpty)
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyTagToLinkedHulls, item.CurrentHull);
|
||||
foreach (var linkedHull in item.CurrentHull.GetLinkedEntities<Hull>())
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyTagToLinkedHulls, linkedHull);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DeregisterTargets();
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
TryRegisterTargets();
|
||||
}
|
||||
|
||||
private void TryRegisterTargets()
|
||||
{
|
||||
foreach (Entity target in ParentEvent.GetTargets(ItemTag))
|
||||
{
|
||||
//already registered, ignore
|
||||
if (targets.Contains(target)) { continue; }
|
||||
if (target is not Item item) { continue; }
|
||||
if (TargetItemComponent.IsEmpty)
|
||||
{
|
||||
item.GetComponents<ItemComponent>().ForEach(ic => Register(ic));
|
||||
}
|
||||
else if (item.Components.FirstOrDefault(ic => ic.Name == TargetItemComponent) is ItemComponent targetItemComponent)
|
||||
{
|
||||
Register(targetItemComponent);
|
||||
}
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"Failed to find the component {TargetItemComponent} on item {item.Prefab.Identifier}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
void Register(ItemComponent ic)
|
||||
{
|
||||
targets.Add(ic.Item);
|
||||
targetComponents.Add(ic);
|
||||
ic.OnUsed.RegisterOverwriteExisting(
|
||||
OnUseEventIdentifier,
|
||||
i => { OnItemUsed(i.Item, i.User); });
|
||||
}
|
||||
}
|
||||
|
||||
private void DeregisterTargets()
|
||||
{
|
||||
foreach (ItemComponent ic in targetComponents)
|
||||
{
|
||||
ic.OnUsed.Deregister(OnUseEventIdentifier);
|
||||
}
|
||||
targetComponents.Clear();
|
||||
targets.Clear();
|
||||
}
|
||||
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
DeregisterTargets();
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(WaitForItemUsedAction)} -> ({ItemTag})";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user