Unstable 1.1.14.0
This commit is contained in:
@@ -64,7 +64,16 @@ namespace Barotrauma
|
||||
|
||||
spawnPending = true;
|
||||
}
|
||||
|
||||
|
||||
public override string GetDebugInfo()
|
||||
{
|
||||
return
|
||||
$"Finished: {IsFinished.ColorizeObject()}\n" +
|
||||
$"Item: {Item.ColorizeObject()}\n" +
|
||||
$"Spawn pending: {SpawnPending.ColorizeObject()}\n" +
|
||||
$"Spawn position: {SpawnPos.ColorizeObject()}";
|
||||
}
|
||||
|
||||
private void SpawnItem()
|
||||
{
|
||||
item = new Item(itemPrefab, spawnPos, null);
|
||||
@@ -73,7 +82,7 @@ namespace Barotrauma
|
||||
//try to find an artifact holder and place the artifact inside it
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != null || !it.HasTag("artifactholder")) continue;
|
||||
if (it.Submarine != null || !it.HasTag(Tags.ArtifactHolder)) { continue; }
|
||||
|
||||
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
|
||||
if (itemContainer == null) continue;
|
||||
|
||||
@@ -52,6 +52,11 @@ namespace Barotrauma
|
||||
ParentSet = parentSet;
|
||||
}
|
||||
|
||||
public virtual string GetDebugInfo()
|
||||
{
|
||||
return $"Finished: {IsFinished.ColorizeObject()}";
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
+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})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#nullable enable
|
||||
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to store logs of scripted events (a sort of "quest log")
|
||||
/// </summary>
|
||||
partial class EventLog
|
||||
{
|
||||
public class Event
|
||||
{
|
||||
public readonly Identifier EventIdentifier;
|
||||
public readonly List<Entry> Entries = new List<Entry>();
|
||||
|
||||
public Event(Identifier eventPrefabId)
|
||||
{
|
||||
EventIdentifier = eventPrefabId;
|
||||
}
|
||||
}
|
||||
|
||||
public class Entry
|
||||
{
|
||||
public readonly Identifier Identifier;
|
||||
public string Text;
|
||||
|
||||
public Entry(Identifier identifier, string text)
|
||||
{
|
||||
Identifier = identifier;
|
||||
Text = text;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<Identifier, Event> events = new Dictionary<Identifier, Event>();
|
||||
|
||||
private bool TryAddEntryInternal(Identifier eventPrefabId, Identifier entryId, string text)
|
||||
{
|
||||
if (!events.TryGetValue(eventPrefabId, out Event? ev))
|
||||
{
|
||||
ev = new Event(eventPrefabId);
|
||||
events.Add(eventPrefabId, ev);
|
||||
}
|
||||
Entry? entry = ev.Entries.FirstOrDefault(e => e.Identifier == entryId);
|
||||
if (entry == null)
|
||||
{
|
||||
ev.Entries.Add(new Entry(entryId, text));
|
||||
return true;
|
||||
}
|
||||
else if (entry.Text != text)
|
||||
{
|
||||
entry.Text = text;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
events.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,24 @@ namespace Barotrauma
|
||||
CONVERSATION_SELECTED_OPTION,
|
||||
STATUSEFFECT,
|
||||
MISSION,
|
||||
UNLOCKPATH
|
||||
UNLOCKPATH,
|
||||
EVENTLOG,
|
||||
EVENTOBJECTIVE,
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
public readonly record struct NetEventLogEntry(Identifier EventPrefabId, Identifier LogEntryId, string Text) : INetSerializableStruct;
|
||||
|
||||
[NetworkSerialize]
|
||||
public readonly record struct NetEventObjective(
|
||||
EventObjectiveAction.SegmentActionType Type,
|
||||
Identifier Identifier,
|
||||
Identifier ObjectiveTag,
|
||||
Identifier TextTag,
|
||||
Identifier ParentObjectiveId,
|
||||
bool CanBeCompleted) : INetSerializableStruct;
|
||||
|
||||
|
||||
const float IntensityUpdateInterval = 5.0f;
|
||||
|
||||
const float CalculateDistanceTraveledInterval = 5.0f;
|
||||
@@ -95,7 +110,7 @@ namespace Barotrauma
|
||||
get { return musicIntensity; }
|
||||
}
|
||||
|
||||
public List<Event> ActiveEvents
|
||||
public IEnumerable<Event> ActiveEvents
|
||||
{
|
||||
get { return activeEvents; }
|
||||
}
|
||||
@@ -119,6 +134,8 @@ namespace Barotrauma
|
||||
private readonly List<TimeStamp> timeStamps = new List<TimeStamp>();
|
||||
public void AddTimeStamp(Event e) => timeStamps.Add(new TimeStamp(e));
|
||||
|
||||
public readonly EventLog EventLog = new EventLog();
|
||||
|
||||
public EventManager()
|
||||
{
|
||||
isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
@@ -198,7 +215,7 @@ namespace Barotrauma
|
||||
if (unlockPathEventPrefab != null)
|
||||
{
|
||||
var newEvent = unlockPathEventPrefab.CreateInstance();
|
||||
ActiveEvents.Add(newEvent);
|
||||
activeEvents.Add(newEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -256,7 +273,18 @@ namespace Barotrauma
|
||||
CumulativeMonsterStrengthWrecks = 0;
|
||||
CumulativeMonsterStrengthCaves = 0;
|
||||
}
|
||||
|
||||
|
||||
public void ActivateEvent(Event newEvent)
|
||||
{
|
||||
activeEvents.Add(newEvent);
|
||||
newEvent.Init();
|
||||
}
|
||||
|
||||
public void ClearEvents()
|
||||
{
|
||||
activeEvents.Clear();
|
||||
}
|
||||
|
||||
private void SelectSettings()
|
||||
{
|
||||
if (!EventManagerSettings.Prefabs.Any())
|
||||
@@ -364,6 +392,14 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void TriggerOnEndRoundActions()
|
||||
{
|
||||
foreach (var ev in activeEvents)
|
||||
{
|
||||
(ev as ScriptedEvent)?.OnRoundEndAction?.Update(1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public void EndRound()
|
||||
{
|
||||
pendingEventSets.Clear();
|
||||
@@ -478,14 +514,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool isPrefabSuitable(EventPrefab e) =>
|
||||
(e.BiomeIdentifier.IsEmpty || e.BiomeIdentifier == level.LevelData?.Biome?.Identifier) &&
|
||||
!level.LevelData.NonRepeatableEvents.Contains(e.Identifier) &&
|
||||
isFactionSuitable(e.Faction);
|
||||
|
||||
bool isFactionSuitable(Identifier factionId) =>
|
||||
factionId.IsEmpty || factionId == level.StartLocation?.Faction?.Prefab.Identifier || factionId == level.StartLocation?.SecondaryFaction?.Prefab.Identifier;
|
||||
|
||||
foreach (var subEventPrefab in eventSet.EventPrefabs)
|
||||
{
|
||||
foreach (Identifier missingId in subEventPrefab.GetMissingIdentifiers())
|
||||
@@ -495,7 +523,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var suitablePrefabSubsets = eventSet.EventPrefabs.Where(
|
||||
e => isFactionSuitable(e.Faction) && e.EventPrefabs.Any(isPrefabSuitable)).ToArray();
|
||||
e => IsFactionSuitable(e.Faction, level) && e.EventPrefabs.Any(ep => IsSuitable(ep, level))).ToArray();
|
||||
|
||||
for (int i = 0; i < applyCount; i++)
|
||||
{
|
||||
@@ -512,7 +540,7 @@ namespace Barotrauma
|
||||
(IEnumerable<EventPrefab> eventPrefabs, float commonness, float probability) = subEventPrefab;
|
||||
if (eventPrefabs != null && random.NextDouble() <= probability)
|
||||
{
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(isPrefabSuitable), e => e.Commonness, random);
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(e => IsSuitable(e, level)), e => e.Commonness, random);
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.RandomSeed = randomSeed;
|
||||
@@ -529,10 +557,25 @@ namespace Barotrauma
|
||||
}
|
||||
if (eventSet.ChildSets.Any())
|
||||
{
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets, random: random);
|
||||
if (newEventSet != null)
|
||||
int setCount = eventSet.SubSetCount;
|
||||
if (setCount > 1)
|
||||
{
|
||||
CreateEvents(newEventSet);
|
||||
var unusedSets = eventSet.ChildSets.ToList();
|
||||
for (int j = 0; j < setCount; j++)
|
||||
{
|
||||
var newEventSet = SelectRandomEvents(unusedSets, random: random);
|
||||
if (newEventSet == null) { break; }
|
||||
unusedSets.Remove(newEventSet);
|
||||
CreateEvents(newEventSet);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets, random: random);
|
||||
if (newEventSet != null)
|
||||
{
|
||||
CreateEvents(newEventSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -542,7 +585,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (random.NextDouble() > probability) { continue; }
|
||||
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(isPrefabSuitable), e => e.Commonness, random);
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(e => IsSuitable(e, level)), e => e.Commonness, random);
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
@@ -636,6 +679,23 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool IsSuitable(EventPrefab e, Level level)
|
||||
{
|
||||
return IsLevelSuitable(e, level) && IsFactionSuitable(e.Faction, level);
|
||||
}
|
||||
|
||||
public static bool IsLevelSuitable(EventPrefab e, Level level)
|
||||
{
|
||||
return
|
||||
(e.BiomeIdentifier.IsEmpty || e.BiomeIdentifier == level.LevelData?.Biome?.Identifier) &&
|
||||
!level.LevelData.NonRepeatableEvents.Contains(e.Identifier);
|
||||
}
|
||||
|
||||
private static bool IsFactionSuitable(Identifier factionId, Level level)
|
||||
{
|
||||
return factionId.IsEmpty || factionId == level.StartLocation?.Faction?.Prefab.Identifier || factionId == level.StartLocation?.SecondaryFaction?.Prefab.Identifier;
|
||||
}
|
||||
|
||||
private static bool IsValidForLevel(EventSet eventSet, Level level)
|
||||
{
|
||||
return
|
||||
@@ -1031,35 +1091,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all actions in a ScriptedEvent
|
||||
/// </summary>
|
||||
private static List<Tuple<int, EventAction>> FindActions(ScriptedEvent scriptedEvent)
|
||||
{
|
||||
var list = new List<Tuple<int, EventAction>>();
|
||||
foreach (EventAction eventAction in scriptedEvent.Actions)
|
||||
{
|
||||
list.AddRange(FindActionsRecursive(eventAction));
|
||||
}
|
||||
|
||||
return list;
|
||||
|
||||
static List<Tuple<int, EventAction>> FindActionsRecursive(EventAction eventAction, int ident = 1)
|
||||
{
|
||||
var eventActions = new List<Tuple<int, EventAction>> { Tuple.Create(ident, eventAction) };
|
||||
|
||||
ident++;
|
||||
|
||||
foreach (var action in eventAction.GetSubActions())
|
||||
{
|
||||
eventActions.AddRange(FindActionsRecursive(action, ident));
|
||||
}
|
||||
|
||||
return eventActions;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the entity that should be used in determining how far the player has progressed in the level.
|
||||
/// = The submarine or player character that has progressed the furthest.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -16,12 +15,25 @@ namespace Barotrauma
|
||||
public readonly float Commonness;
|
||||
public readonly Identifier BiomeIdentifier;
|
||||
public readonly Identifier Faction;
|
||||
public readonly float SpawnDistance;
|
||||
|
||||
public readonly LocalizedString Name;
|
||||
|
||||
public readonly bool UnlockPathEvent;
|
||||
public readonly string UnlockPathTooltip;
|
||||
public readonly int UnlockPathReputation;
|
||||
|
||||
public static EventPrefab Create(ContentXElement element, RandomEventsFile file, Identifier fallbackIdentifier = default)
|
||||
{
|
||||
if (element.NameAsIdentifier() == nameof(TraitorEvent))
|
||||
{
|
||||
return new TraitorEventPrefab(element, file, fallbackIdentifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new EventPrefab(element, file, fallbackIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
public EventPrefab(ContentXElement element, RandomEventsFile file, Identifier fallbackIdentifier = default)
|
||||
: base(file, element.GetAttributeIdentifier("identifier", fallbackIdentifier))
|
||||
{
|
||||
@@ -40,6 +52,8 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\".");
|
||||
}
|
||||
|
||||
Name = TextManager.Get($"eventname.{Identifier}").Fallback(Identifier.ToString());
|
||||
|
||||
BiomeIdentifier = ConfigElement.GetAttributeIdentifier("biome", Identifier.Empty);
|
||||
Faction = ConfigElement.GetAttributeIdentifier("faction", Identifier.Empty);
|
||||
Commonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
@@ -49,19 +63,17 @@ namespace Barotrauma
|
||||
UnlockPathEvent = element.GetAttributeBool("unlockpathevent", false);
|
||||
UnlockPathTooltip = element.GetAttributeString("unlockpathtooltip", "lockedpathtooltip");
|
||||
UnlockPathReputation = element.GetAttributeInt("unlockpathreputation", 0);
|
||||
|
||||
SpawnDistance = element.GetAttributeFloat("spawndistance", 0);
|
||||
}
|
||||
|
||||
public bool TryCreateInstance<T>(out T instance) where T : Event
|
||||
{
|
||||
instance = CreateInstance() as T;
|
||||
return instance is T;
|
||||
return instance is not null;
|
||||
}
|
||||
|
||||
public Event CreateInstance()
|
||||
{
|
||||
ConstructorInfo constructor = EventType.GetConstructor(new[] { typeof(EventPrefab) });
|
||||
ConstructorInfo constructor = EventType.GetConstructor(new[] { GetType() });
|
||||
Event instance = null;
|
||||
try
|
||||
{
|
||||
@@ -79,7 +91,7 @@ namespace Barotrauma
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"EventPrefab ({Identifier})";
|
||||
return $"{nameof(EventPrefab)} ({Identifier})";
|
||||
}
|
||||
|
||||
public static EventPrefab GetUnlockPathEvent(Identifier biomeIdentifier, Faction faction)
|
||||
|
||||
@@ -94,6 +94,7 @@ namespace Barotrauma
|
||||
public readonly bool ChooseRandom;
|
||||
|
||||
private readonly int eventCount = 1;
|
||||
public readonly int SubSetCount = 1;
|
||||
private readonly Dictionary<Identifier, int> overrideEventCount = new Dictionary<Identifier, int>();
|
||||
|
||||
/// <summary>
|
||||
@@ -280,6 +281,7 @@ namespace Barotrauma
|
||||
|
||||
ChooseRandom = element.GetAttributeBool("chooserandom", false);
|
||||
eventCount = element.GetAttributeInt("eventcount", 1);
|
||||
SubSetCount = element.GetAttributeInt("setcount", 1);
|
||||
Exhaustible = element.GetAttributeBool("exhaustible", false);
|
||||
MinDistanceTraveled = element.GetAttributeFloat("mindistancetraveled", 0.0f);
|
||||
MinMissionTime = element.GetAttributeFloat("minmissiontime", 0.0f);
|
||||
@@ -295,7 +297,7 @@ namespace Barotrauma
|
||||
OncePerLevel = element.GetAttributeBool("onceperlevel", element.GetAttributeBool("onceperoutpost", false));
|
||||
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
|
||||
IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost || (parentSet?.IsCampaignSet ?? false));
|
||||
ResetTime = element.GetAttributeFloat("resettime", 0);
|
||||
ResetTime = element.GetAttributeFloat(nameof(ResetTime), parentSet?.ResetTime ?? 0);
|
||||
CampaignTutorialOnly = element.GetAttributeBool(nameof(CampaignTutorialOnly), false);
|
||||
|
||||
ForceAtDiscoveredNr = element.GetAttributeInt(nameof(ForceAtDiscoveredNr), -1);
|
||||
@@ -474,7 +476,7 @@ namespace Barotrauma
|
||||
if (eventPrefab.EventType == typeof(MonsterEvent) && eventPrefab.TryCreateInstance(out MonsterEvent monsterEvent))
|
||||
{
|
||||
if (filter != null && !filter(monsterEvent)) { return; }
|
||||
float spawnProbability = monsterEvent.Prefab.Probability;
|
||||
float spawnProbability = monsterEvent.Prefab?.Probability ?? 0.0f;
|
||||
if (Rand.Value() > spawnProbability) { return; }
|
||||
int count = Rand.Range(monsterEvent.MinAmount, monsterEvent.MaxAmount + 1);
|
||||
if (count <= 0) { return; }
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Barotrauma
|
||||
protected readonly HashSet<Character> requireKill = new HashSet<Character>();
|
||||
protected readonly HashSet<Character> requireRescue = new HashSet<Character>();
|
||||
|
||||
private readonly string itemTag;
|
||||
private readonly Identifier itemTag;
|
||||
private readonly XElement itemConfig;
|
||||
private readonly List<Item> items = new List<Item>();
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace Barotrauma
|
||||
hostagesKilledMessage = TextManager.Get(msgTag).Fallback(msgTag);
|
||||
|
||||
itemConfig = prefab.ConfigElement.GetChildElement("Items");
|
||||
itemTag = prefab.ConfigElement.GetAttributeString("targetitem", "");
|
||||
itemTag = prefab.ConfigElement.GetAttributeIdentifier("targetitem", Identifier.Empty);
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
@@ -118,7 +118,7 @@ namespace Barotrauma
|
||||
|
||||
private void InitItems(Submarine submarine)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(itemTag))
|
||||
if (!itemTag.IsEmpty)
|
||||
{
|
||||
var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag));
|
||||
if (!itemsToDestroy.Any())
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (level.BeaconStation == null)
|
||||
if (level.BeaconStation == null || state > 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
@@ -95,9 +95,10 @@ namespace Barotrauma
|
||||
foreach (Item item in Item.ItemList)
|
||||
{
|
||||
if (!connectedSubs.Contains(item.Submarine) || item.Submarine?.Info is { IsPlayer: true }) { continue; }
|
||||
if (item.GetComponent<PowerTransfer>() != null ||
|
||||
bool isReactor = item.GetComponent<Reactor>() != null;
|
||||
if ((isReactor && GameMain.GameSession is not { TraitorsEnabled: true }) ||
|
||||
item.GetComponent<PowerTransfer>() != null ||
|
||||
item.GetComponent<PowerContainer>() != null ||
|
||||
item.GetComponent<Reactor>() != null ||
|
||||
item.GetComponent<Sonar>() != null)
|
||||
{
|
||||
item.InvulnerableToDamage = true;
|
||||
|
||||
@@ -262,6 +262,12 @@ namespace Barotrauma
|
||||
SpawnedInCurrentOutpost = true,
|
||||
AllowStealing = false
|
||||
};
|
||||
item.AddTag("cargomission");
|
||||
item.AddTag(Prefab.Identifier);
|
||||
foreach (var tag in Prefab.Tags)
|
||||
{
|
||||
item.AddTag(tag);
|
||||
}
|
||||
item.FindHull();
|
||||
items.Add(item);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Barotrauma
|
||||
private readonly CharacterPrefab minionPrefab;
|
||||
|
||||
private readonly Identifier spawnPointTag;
|
||||
private WayPoint bossSpawnPoint;
|
||||
private readonly Identifier destructibleItemTag;
|
||||
|
||||
private readonly string endCinematicSound;
|
||||
@@ -68,7 +69,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (boss != null && !boss.Removed)
|
||||
{
|
||||
Vector2 prevPos = boss.AnimController.Collider.SimPosition;
|
||||
boss.AnimController.ColliderIndex = 1;
|
||||
if (bossSpawnPoint != null)
|
||||
{
|
||||
//ensure the new collider stays in the same position (the 2nd one has a different shape than the 1st one)
|
||||
boss.AnimController.Collider.SetTransform(prevPos, 0.0f);
|
||||
}
|
||||
}
|
||||
}, delay: wakeUpCinematicDelay + bossWakeUpDelay + 2);
|
||||
}
|
||||
@@ -142,21 +149,21 @@ namespace Barotrauma
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
{
|
||||
var spawnPoint = WayPoint.WayPointList.FirstOrDefault(wp => wp.Tags.Contains(spawnPointTag));
|
||||
if (spawnPoint == null)
|
||||
bossSpawnPoint = WayPoint.WayPointList.FirstOrDefault(wp => wp.Tags.Contains(spawnPointTag));
|
||||
if (bossSpawnPoint == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find a spawn point \"{spawnPointTag}\".");
|
||||
return;
|
||||
}
|
||||
if (!IsClient)
|
||||
{
|
||||
boss = Character.Create(bossPrefab.Identifier, spawnPoint.WorldPosition, ToolBox.RandomSeed(8), createNetworkEvent: false);
|
||||
boss = Character.Create(bossPrefab.Identifier, bossSpawnPoint.WorldPosition, ToolBox.RandomSeed(8), createNetworkEvent: false);
|
||||
var minionList = new List<Character>();
|
||||
float angle = 0;
|
||||
float angleStep = MathHelper.TwoPi / Math.Max(minionCount, 1);
|
||||
for (int i = 0; i < minionCount; i++)
|
||||
{
|
||||
minionList.Add(Character.Create(minionPrefab.Identifier, MathUtils.GetPointOnCircumference(spawnPoint.WorldPosition, minionScatter, angle), ToolBox.RandomSeed(8), createNetworkEvent: false));
|
||||
minionList.Add(Character.Create(minionPrefab.Identifier, MathUtils.GetPointOnCircumference(bossSpawnPoint.WorldPosition, minionScatter, angle), ToolBox.RandomSeed(8), createNetworkEvent: false));
|
||||
angle += angleStep;
|
||||
}
|
||||
SwarmBehavior.CreateSwarm(minionList.Cast<AICharacter>());
|
||||
|
||||
@@ -315,27 +315,21 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool Survived(Character character)
|
||||
private static bool Survived(Character character)
|
||||
{
|
||||
return IsAlive(character) && character.CurrentHull?.Submarine != null &&
|
||||
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine));
|
||||
}
|
||||
|
||||
private bool IsAlive(Character character)
|
||||
private static bool IsAlive(Character character)
|
||||
{
|
||||
return character != null && !character.Removed && !character.IsDead;
|
||||
}
|
||||
|
||||
private bool IsCaptured(Character character)
|
||||
{
|
||||
return character.LockHands && character.HasTeamChange(TerroristTeamChangeIdentifier);
|
||||
}
|
||||
|
||||
protected override bool DetermineCompleted()
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit)
|
||||
{
|
||||
bool terroristsSurvived = terroristCharacters.Any(c => Survived(c) && !IsCaptured(c));
|
||||
bool friendliesSurvived = characters.Except(terroristCharacters).All(c => Survived(c));
|
||||
bool vipDied = false;
|
||||
|
||||
@@ -345,7 +339,7 @@ namespace Barotrauma
|
||||
vipDied = !Survived(vipCharacter);
|
||||
}
|
||||
|
||||
if (friendliesSurvived && !terroristsSurvived && !vipDied)
|
||||
if (friendliesSurvived && !vipDied)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (cave.Area.Contains(spawnedResource.WorldPosition))
|
||||
{
|
||||
cave.DisplayOnSonar = true;
|
||||
cave.MissionsToDisplayOnSonar.Add(this);
|
||||
caves.Add(cave);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<Identifier, float> ReputationRewards
|
||||
public ImmutableList<MissionPrefab.ReputationReward> ReputationRewards
|
||||
{
|
||||
get { return Prefab.ReputationRewards; }
|
||||
}
|
||||
@@ -268,7 +268,7 @@ namespace Barotrauma
|
||||
delayedTriggerEvents.Clear();
|
||||
foreach (string categoryToShow in Prefab.UnhideEntitySubCategories)
|
||||
{
|
||||
foreach (MapEntity entityToShow in MapEntity.mapEntityList.Where(me => me.Prefab?.HasSubCategory(categoryToShow) ?? false))
|
||||
foreach (MapEntity entityToShow in MapEntity.MapEntityList.Where(me => me.Prefab?.HasSubCategory(categoryToShow) ?? false))
|
||||
{
|
||||
entityToShow.HiddenInGame = false;
|
||||
}
|
||||
@@ -353,8 +353,7 @@ namespace Barotrauma
|
||||
if (GameMain.GameSession?.EventManager != null)
|
||||
{
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
GameMain.GameSession.EventManager.ActiveEvents.Add(newEvent);
|
||||
newEvent.Init();
|
||||
GameMain.GameSession.EventManager.ActivateEvent(newEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +371,19 @@ namespace Barotrauma
|
||||
{
|
||||
ChangeLocationType(Prefab.LocationTypeChangeOnCompleted);
|
||||
}
|
||||
GiveReward();
|
||||
try
|
||||
{
|
||||
GiveReward();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
string errorMsg = "Unknown error while giving mission rewards.";
|
||||
DebugConsole.ThrowError(errorMsg, e);
|
||||
GameAnalyticsManager.AddErrorEventOnce("Mission.End:GiveReward", GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + e.StackTrace);
|
||||
#if SERVER
|
||||
GameMain.Server?.SendChatMessage(errorMsg + "\n" + e.StackTrace, Networking.ChatMessageType.Error);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
TimesAttempted++;
|
||||
@@ -423,30 +434,7 @@ namespace Barotrauma
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnAllyGainMissionExperience, experienceGainMultiplier));
|
||||
crewCharacters.ForEach(c => experienceGainMultiplier.Value += c.GetStatValue(StatTypes.MissionExperienceGainMultiplier));
|
||||
|
||||
int experienceGain = (int)(baseExperienceGain * experienceGainMultiplier.Value);
|
||||
#if CLIENT
|
||||
foreach (Character character in crewCharacters)
|
||||
{
|
||||
GiveMissionExperience(character.Info);
|
||||
}
|
||||
#else
|
||||
foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
//give the experience to the stored characterinfo if the client isn't currently controlling a character
|
||||
GiveMissionExperience(c.Character?.Info ?? c.CharacterInfo);
|
||||
}
|
||||
foreach (Character bot in GameSession.GetSessionCrewCharacters(CharacterType.Bot))
|
||||
{
|
||||
GiveMissionExperience(bot.Info);
|
||||
}
|
||||
#endif
|
||||
|
||||
void GiveMissionExperience(CharacterInfo info)
|
||||
{
|
||||
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
||||
info?.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
|
||||
info?.GiveExperience((int)((experienceGain * experienceGainMultiplier.Value) * experienceGainMultiplierIndividual.Value));
|
||||
}
|
||||
DistributeExperienceToCrew(crewCharacters, (int)(baseExperienceGain * experienceGainMultiplier.Value));
|
||||
|
||||
CalculateFinalReward(Submarine.MainSub);
|
||||
#if SERVER
|
||||
@@ -465,17 +453,32 @@ namespace Barotrauma
|
||||
character.Info.MissionsCompletedSinceDeath++;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<Identifier, float> reputationReward in ReputationRewards)
|
||||
foreach (var reputationReward in ReputationRewards)
|
||||
{
|
||||
if (reputationReward.Key == "location")
|
||||
if (reputationReward.FactionIdentifier == "location")
|
||||
{
|
||||
OriginLocation.Reputation?.AddReputation(reputationReward.Value);
|
||||
OriginLocation.Reputation?.AddReputation(reputationReward.Amount);
|
||||
TryGiveReputationForOpposingFaction(OriginLocation.Faction, reputationReward.AmountForOpposingFaction);
|
||||
}
|
||||
else
|
||||
{
|
||||
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier == reputationReward.Key);
|
||||
float prevValue = faction.Reputation.Value;
|
||||
faction?.Reputation.AddReputation(reputationReward.Value);
|
||||
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier == reputationReward.FactionIdentifier);
|
||||
if (faction != null)
|
||||
{
|
||||
faction.Reputation.AddReputation(reputationReward.Amount);
|
||||
TryGiveReputationForOpposingFaction(faction, reputationReward.AmountForOpposingFaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TryGiveReputationForOpposingFaction(Faction thisFaction, float amount)
|
||||
{
|
||||
if (MathUtils.NearlyEqual(amount, 0.0f)) { return; }
|
||||
if (thisFaction?.Prefab != null &&
|
||||
!thisFaction.Prefab.OpposingFaction.IsEmpty)
|
||||
{
|
||||
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier == thisFaction.Prefab.OpposingFaction);
|
||||
faction?.Reputation.AddReputation(amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,30 +492,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
public static int DistributeRewardsToCrew(IEnumerable<Character> crew, int totalReward)
|
||||
{
|
||||
int remainingRewards = totalReward;
|
||||
float sum = GetRewardDistibutionSum(crew);
|
||||
if (MathUtils.NearlyEqual(sum, 0)) { return remainingRewards; }
|
||||
foreach (Character character in crew)
|
||||
{
|
||||
int rewardDistribution = character.Wallet.RewardDistribution;
|
||||
float rewardWeight = sum > 100 ? rewardDistribution / sum : rewardDistribution / 100f;
|
||||
int reward = (int)(totalReward * rewardWeight);
|
||||
reward = Math.Min(remainingRewards, reward);
|
||||
character.Wallet.Give(reward);
|
||||
remainingRewards -= reward;
|
||||
if (remainingRewards <= 0) { break; }
|
||||
}
|
||||
|
||||
return remainingRewards;
|
||||
}
|
||||
#endif
|
||||
partial void DistributeExperienceToCrew(IEnumerable<Character> crew, int experienceGain);
|
||||
|
||||
public static int GetRewardDistibutionSum(IEnumerable<Character> crew, int rewardDistribution = 0) => crew.Sum(c => c.Wallet.RewardDistribution) + rewardDistribution;
|
||||
|
||||
|
||||
public static (int Amount, int Percentage, float Sum) GetRewardShare(int rewardDistribution, IEnumerable<Character> crew, Option<int> reward)
|
||||
{
|
||||
float sum = GetRewardDistibutionSum(crew, rewardDistribution);
|
||||
|
||||
@@ -4,7 +4,6 @@ using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -54,6 +53,20 @@ namespace Barotrauma
|
||||
{ MissionType.Combat, typeof(CombatMission) }
|
||||
};
|
||||
|
||||
public class ReputationReward
|
||||
{
|
||||
public readonly Identifier FactionIdentifier;
|
||||
public readonly float Amount;
|
||||
public readonly float AmountForOpposingFaction;
|
||||
|
||||
public ReputationReward(XElement element)
|
||||
{
|
||||
FactionIdentifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
Amount = element.GetAttributeFloat(nameof(Amount), 0.0f);
|
||||
AmountForOpposingFaction = element.GetAttributeFloat(nameof(AmountForOpposingFaction), 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly HashSet<MissionType> HiddenMissionClasses = new HashSet<MissionType>() { MissionType.GoTo, MissionType.End };
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
@@ -75,7 +88,7 @@ namespace Barotrauma
|
||||
|
||||
public readonly Identifier AchievementIdentifier;
|
||||
|
||||
public readonly Dictionary<Identifier, float> ReputationRewards = new Dictionary<Identifier, float>();
|
||||
public readonly ImmutableList<ReputationReward> ReputationRewards;
|
||||
|
||||
public readonly List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)>
|
||||
DataRewards = new List<(Identifier Identifier, object Value, SetDataAction.OperationType OperationType)>();
|
||||
@@ -252,7 +265,8 @@ namespace Barotrauma
|
||||
messages.Add(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
List<ReputationReward> reputationRewards = new List<ReputationReward>();
|
||||
int messageIndex = 0;
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
@@ -292,14 +306,7 @@ namespace Barotrauma
|
||||
break;
|
||||
case "reputation":
|
||||
case "reputationreward":
|
||||
Identifier factionIdentifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
float amount = subElement.GetAttributeFloat("amount", 0.0f);
|
||||
if (ReputationRewards.ContainsKey(factionIdentifier))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission prefab \"{Identifier}\". Multiple reputation changes defined for the identifier \"{factionIdentifier}\".");
|
||||
continue;
|
||||
}
|
||||
ReputationRewards.Add(factionIdentifier, amount);
|
||||
reputationRewards.Add(new ReputationReward(subElement));
|
||||
break;
|
||||
case "metadata":
|
||||
Identifier identifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
@@ -325,6 +332,7 @@ namespace Barotrauma
|
||||
}
|
||||
Headers = headers.ToImmutableArray();
|
||||
Messages = messages.ToImmutableArray();
|
||||
ReputationRewards = reputationRewards.ToImmutableList();
|
||||
|
||||
Identifier missionTypeName = element.GetAttributeIdentifier("type", Identifier.Empty);
|
||||
//backwards compatibility
|
||||
@@ -399,7 +407,7 @@ namespace Barotrauma
|
||||
else if (Type == MissionType.ScanAlienRuins || Type == MissionType.ClearAlienRuins)
|
||||
{
|
||||
var connection = from.Connections.Find(c => c.Locations.Contains(from) && c.Locations.Contains(to));
|
||||
if (connection?.LevelData == null || connection.LevelData.GenerationParams.RuinCount < 1) { return false; }
|
||||
if (connection?.LevelData == null || connection.LevelData.GenerationParams.GetMaxRuinCount() < 1) { return false; }
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -147,7 +147,7 @@ namespace Barotrauma
|
||||
monster.Params.AI.FleeHealthThreshold = 0;
|
||||
foreach (var targetParam in monster.Params.AI.Targets)
|
||||
{
|
||||
if (targetParam.Tag.Equals("engine", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
if (targetParam.Tag == "engine") { continue; }
|
||||
switch (targetParam.State)
|
||||
{
|
||||
case AIState.Avoid:
|
||||
|
||||
@@ -4,7 +4,6 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -30,6 +29,8 @@ namespace Barotrauma
|
||||
|
||||
private Vector2 nestPosition;
|
||||
|
||||
private Level.Cave selectedCave;
|
||||
|
||||
|
||||
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
|
||||
{
|
||||
@@ -125,11 +126,9 @@ namespace Barotrauma
|
||||
}
|
||||
if (closestCave != null)
|
||||
{
|
||||
closestCave.DisplayOnSonar = true;
|
||||
SpawnNestObjects(level, closestCave);
|
||||
#if SERVER
|
||||
selectedCave = closestCave;
|
||||
#endif
|
||||
selectedCave.MissionsToDisplayOnSonar.Add(this);
|
||||
SpawnNestObjects(level, closestCave);
|
||||
}
|
||||
var nearbyCells = Level.Loaded.GetCells(nestPosition, searchDepth: 3);
|
||||
if (nearbyCells.Any())
|
||||
@@ -172,8 +171,8 @@ namespace Barotrauma
|
||||
|
||||
foreach (var subElement in itemConfig.Elements())
|
||||
{
|
||||
string itemIdentifier = subElement.GetAttributeString("identifier", "");
|
||||
if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab))
|
||||
var itemIdentifier = subElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
if (MapEntityPrefab.FindByIdentifier(itemIdentifier) is not ItemPrefab itemPrefab)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for nest mission: item prefab \"" + itemIdentifier + "\" not found");
|
||||
continue;
|
||||
@@ -183,25 +182,34 @@ namespace Barotrauma
|
||||
float rotation = 0.0f;
|
||||
if (spawnEdges.Any())
|
||||
{
|
||||
var edge = spawnEdges.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
spawnPos = Vector2.Lerp(edge.Point1, edge.Point2, Rand.Range(0.1f, 0.9f, Rand.RandSync.ServerAndClient));
|
||||
Vector2 normal = Vector2.UnitY;
|
||||
if (edge.Cell1 != null && edge.Cell1.CellType == CellType.Solid)
|
||||
const float MinDistanceFromOtherItems = 30.0f;
|
||||
const int MaxTries = 10;
|
||||
for (int i = 0; i < MaxTries; i++)
|
||||
{
|
||||
normal = edge.GetNormal(edge.Cell1);
|
||||
var edge = spawnEdges.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
spawnPos = Vector2.Lerp(edge.Point1, edge.Point2, Rand.Range(0.1f, 0.9f, Rand.RandSync.ServerAndClient));
|
||||
Vector2 normal = Vector2.UnitY;
|
||||
if (edge.Cell1 != null && edge.Cell1.CellType == CellType.Solid)
|
||||
{
|
||||
normal = edge.GetNormal(edge.Cell1);
|
||||
}
|
||||
else if (edge.Cell2 != null && edge.Cell2.CellType == CellType.Solid)
|
||||
{
|
||||
normal = edge.GetNormal(edge.Cell2);
|
||||
}
|
||||
spawnPos += normal * 10.0f;
|
||||
rotation = MathUtils.VectorToAngle(normal) - MathHelper.PiOver2;
|
||||
|
||||
if (items.All(it => Vector2.DistanceSquared(it.WorldPosition, spawnPos) > MinDistanceFromOtherItems)) { break; }
|
||||
}
|
||||
else if (edge.Cell2 != null && edge.Cell2.CellType == CellType.Solid)
|
||||
{
|
||||
normal = edge.GetNormal(edge.Cell2);
|
||||
}
|
||||
spawnPos += normal * 10.0f;
|
||||
rotation = MathUtils.VectorToAngle(normal) - MathHelper.PiOver2;
|
||||
}
|
||||
|
||||
var item = new Item(itemPrefab, spawnPos, null);
|
||||
item.body.FarseerBody.BodyType = BodyType.Kinematic;
|
||||
item.body.SetTransformIgnoreContacts(item.body.SimPosition, rotation);
|
||||
item.FindHull();
|
||||
item.AddTag("nestmission");
|
||||
item.AddTag(Prefab.Identifier);
|
||||
items.Add(item);
|
||||
|
||||
var statusEffectElement =
|
||||
@@ -286,7 +294,10 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//continue when all items are in the sub or destroyed
|
||||
if (AllItemsDestroyedOrRetrieved()) { State = 1; }
|
||||
if (AllItemsDestroyedOrRetrieved())
|
||||
{
|
||||
State = 1;
|
||||
}
|
||||
|
||||
break;
|
||||
case 1:
|
||||
|
||||
@@ -239,7 +239,7 @@ namespace Barotrauma
|
||||
private void InitPirateShip()
|
||||
{
|
||||
enemySub.NeutralizeBallast();
|
||||
if (enemySub.GetItems(alsoFromConnectedSubs: false).Find(i => i.HasTag("reactor") && !i.NonInteractable)?.GetComponent<Reactor>() is Reactor reactor)
|
||||
if (enemySub.GetItems(alsoFromConnectedSubs: false).Find(i => i.HasTag(Tags.Reactor) && !i.NonInteractable)?.GetComponent<Reactor>() is Reactor reactor)
|
||||
{
|
||||
reactor.PowerUpImmediately();
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ namespace Barotrauma
|
||||
|
||||
public readonly ItemPrefab ItemPrefab;
|
||||
public readonly Level.PositionType SpawnPositionType;
|
||||
public readonly string ContainerTag;
|
||||
public readonly string ExistingItemTag;
|
||||
public readonly Identifier ContainerTag;
|
||||
public readonly Identifier ExistingItemTag;
|
||||
|
||||
public readonly bool RemoveItem;
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace Barotrauma
|
||||
public Target(ContentXElement element, SalvageMission mission)
|
||||
{
|
||||
this.mission = mission;
|
||||
ContainerTag = element.GetAttributeString("containertag", "");
|
||||
ContainerTag = element.GetAttributeIdentifier("containertag", Identifier.Empty);
|
||||
RequiredRetrievalState = element.GetAttributeEnum("requireretrieval", RetrievalState.RetrievedToSub);
|
||||
AllowContinueBeforeRetrieved = element.GetAttributeBool("allowcontinuebeforeretrieved", false);
|
||||
HideLabelAfterRetrieved = element.GetAttributeBool("hidelabelafterretrieved", false);
|
||||
@@ -100,7 +100,7 @@ namespace Barotrauma
|
||||
.Fallback(TextManager.Get(sonarLabelTag))
|
||||
.Fallback(element.GetAttributeString("sonarlabel", ""));
|
||||
}
|
||||
ExistingItemTag = element.GetAttributeString("existingitemtag", "");
|
||||
ExistingItemTag = element.GetAttributeIdentifier("existingitemtag", Identifier.Empty);
|
||||
|
||||
RemoveItem = element.GetAttributeBool("removeitem", true);
|
||||
|
||||
@@ -109,7 +109,7 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("Error in SalvageMission - use item identifier instead of the name of the item.");
|
||||
string itemName = element.GetAttributeString("itemname", "");
|
||||
ItemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
|
||||
if (ItemPrefab == null && ExistingItemTag.IsNullOrEmpty())
|
||||
if (ItemPrefab == null && ExistingItemTag.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in SalvageMission: couldn't find an item prefab with the name \"{itemName}\"");
|
||||
}
|
||||
@@ -126,7 +126,7 @@ namespace Barotrauma
|
||||
string itemTag = element.GetAttributeString("itemtag", "");
|
||||
ItemPrefab = MapEntityPrefab.GetRandom(p => p.Tags.Contains(itemTag), Rand.RandSync.Unsynced) as ItemPrefab;
|
||||
}
|
||||
if (ItemPrefab == null && ExistingItemTag.IsNullOrEmpty())
|
||||
if (ItemPrefab == null && ExistingItemTag.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in SalvageMission - couldn't find an item prefab with the identifier \"{itemIdentifier}\"");
|
||||
}
|
||||
@@ -233,7 +233,7 @@ namespace Barotrauma
|
||||
Vector2.Zero :
|
||||
Level.Loaded.GetRandomItemPos(target.SpawnPositionType, 100.0f, minDistance, 30.0f);
|
||||
|
||||
if (!string.IsNullOrEmpty(target.ExistingItemTag))
|
||||
if (!target.ExistingItemTag.IsEmpty)
|
||||
{
|
||||
var suitableItems = Item.ItemList.Where(it => it.HasTag(target.ExistingItemTag));
|
||||
if (GameMain.GameSession?.Missions != null)
|
||||
@@ -284,9 +284,9 @@ namespace Barotrauma
|
||||
|
||||
if (target.Item == null)
|
||||
{
|
||||
if (target.ItemPrefab == null && string.IsNullOrEmpty(target.ContainerTag))
|
||||
if (target.ItemPrefab == null && target.ContainerTag.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to find a target item for the mission \"{Prefab.Identifier}\". Item tag: {target.ExistingItemTag ?? "null"}");
|
||||
DebugConsole.ThrowError($"Failed to find a target item for the mission \"{Prefab.Identifier}\". Item tag: {target.ExistingItemTag}");
|
||||
continue;
|
||||
}
|
||||
target.Item = new Item(target.ItemPrefab, position, null);
|
||||
@@ -312,8 +312,10 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
target.Item.IsSalvageMissionItem = true;
|
||||
|
||||
//try to find a container and place the item inside it
|
||||
if (!string.IsNullOrEmpty(target.ContainerTag) && target.Item.ParentInventory == null)
|
||||
if (!target.ContainerTag.IsEmpty && target.Item.ParentInventory == null)
|
||||
{
|
||||
List<ItemContainer> validContainers = new List<ItemContainer>();
|
||||
foreach (Item it in Item.ItemList)
|
||||
|
||||
@@ -218,7 +218,7 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
|
||||
private bool IsValidScanPosition(Scanner scanner, KeyValuePair<WayPoint, bool> scanStatus, float scanRadiusSquared)
|
||||
private static bool IsValidScanPosition(Scanner scanner, KeyValuePair<WayPoint, bool> scanStatus, float scanRadiusSquared)
|
||||
{
|
||||
if (scanStatus.Value) { return false; }
|
||||
if (scanStatus.Key.Submarine != scanner.Item.Submarine) { return false; }
|
||||
@@ -232,39 +232,15 @@ namespace Barotrauma
|
||||
switch (State)
|
||||
{
|
||||
case 0:
|
||||
if (!AllTargetsScanned) { return; }
|
||||
State = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) { return; }
|
||||
State = 2;
|
||||
if (AllTargetsScanned)
|
||||
{
|
||||
State = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool DetermineCompleted()
|
||||
{
|
||||
return State == 2 && AllScannersReturned();
|
||||
|
||||
bool AllScannersReturned()
|
||||
{
|
||||
foreach (var scanner in scanners)
|
||||
{
|
||||
if (scanner?.Item == null || scanner.Item.Removed) { return false; }
|
||||
var owner = scanner.Item.GetRootInventoryOwner();
|
||||
if (owner.Submarine != null && owner.Submarine.Info.Type == SubmarineType.Player)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (owner is Character c && c.Info != null && GameMain.GameSession.CrewManager.CharacterInfos.Contains(c.Info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
protected override bool DetermineCompleted() => State > 0;
|
||||
|
||||
protected override void EndMissionSpecific(bool completed)
|
||||
{
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -13,6 +14,7 @@ namespace Barotrauma
|
||||
public readonly int MinAmount, MaxAmount;
|
||||
private readonly List<Character> monsters = new List<Character>();
|
||||
|
||||
public readonly float SpawnDistance;
|
||||
private readonly float scatter;
|
||||
private readonly float offset;
|
||||
private readonly float delayBetweenSpawns;
|
||||
@@ -56,7 +58,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public MonsterEvent(EventPrefab prefab)
|
||||
: base (prefab)
|
||||
: base(prefab)
|
||||
{
|
||||
string speciesFile = prefab.ConfigElement.GetAttributeString("characterfile", "");
|
||||
CharacterPrefab characterPrefab = CharacterPrefab.FindByFilePath(speciesFile);
|
||||
@@ -94,7 +96,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
spawnPointTag = prefab.ConfigElement.GetAttributeString("spawnpointtag", string.Empty);
|
||||
|
||||
SpawnDistance = prefab.ConfigElement.GetAttributeFloat("spawndistance", 0);
|
||||
offset = prefab.ConfigElement.GetAttributeFloat("offset", 0);
|
||||
scatter = Math.Clamp(prefab.ConfigElement.GetAttributeFloat("scatter", 500), 0, 3000);
|
||||
delayBetweenSpawns = prefab.ConfigElement.GetAttributeFloat("delaybetweenspawns", 0.1f);
|
||||
@@ -174,6 +176,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override string GetDebugInfo()
|
||||
{
|
||||
return
|
||||
$"Finished: {IsFinished.ColorizeObject()}\n" +
|
||||
$"Amount: {MinAmount.ColorizeObject()} - {MaxAmount.ColorizeObject()}\n" +
|
||||
$"Spawn pending: {SpawnPending.ColorizeObject()}\n" +
|
||||
$"Spawn position: {SpawnPos.ColorizeObject()}";
|
||||
}
|
||||
|
||||
private List<Level.InterestingPosition> GetAvailableSpawnPositions()
|
||||
{
|
||||
var availablePositions = Level.Loaded.PositionsOfInterest.FindAll(p => SpawnPosType.HasFlag(p.PositionType));
|
||||
@@ -214,13 +225,14 @@ namespace Barotrauma
|
||||
return availablePositions;
|
||||
}
|
||||
|
||||
private Level.InterestingPosition chosenPosition;
|
||||
private void FindSpawnPosition(bool affectSubImmediately)
|
||||
{
|
||||
if (disallowed) { return; }
|
||||
|
||||
spawnPos = Vector2.Zero;
|
||||
var availablePositions = GetAvailableSpawnPositions();
|
||||
var chosenPosition = new Level.InterestingPosition(Point.Zero, Level.PositionType.MainPath, isValid: false);
|
||||
chosenPosition = new Level.InterestingPosition(Point.Zero, Level.PositionType.MainPath, isValid: false);
|
||||
bool isRuinOrWreck = SpawnPosType.HasFlag(Level.PositionType.Ruin) || SpawnPosType.HasFlag(Level.PositionType.Wreck);
|
||||
if (affectSubImmediately && !isRuinOrWreck && !SpawnPosType.HasFlag(Level.PositionType.Abyss))
|
||||
{
|
||||
@@ -454,52 +466,109 @@ namespace Barotrauma
|
||||
{
|
||||
if (submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
float minDist = GetMinDistanceToSub(submarine);
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist) { return; }
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist)
|
||||
{
|
||||
// Too close to a player sub.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
float minDistance = Prefab.SpawnDistance;
|
||||
if (minDistance <= 0)
|
||||
float spawnDistance = SpawnDistance;
|
||||
if (spawnDistance <= 0)
|
||||
{
|
||||
if (SpawnPosType.HasFlag(Level.PositionType.Cave))
|
||||
{
|
||||
minDistance = 8000;
|
||||
spawnDistance = 8000;
|
||||
}
|
||||
else if (SpawnPosType.HasFlag(Level.PositionType.Ruin))
|
||||
{
|
||||
minDistance = 5000;
|
||||
spawnDistance = 5000;
|
||||
}
|
||||
else if (SpawnPosType.HasFlag(Level.PositionType.Wreck) || SpawnPosType.HasFlag(Level.PositionType.BeaconStation))
|
||||
{
|
||||
minDistance = 3000;
|
||||
spawnDistance = 3000;
|
||||
}
|
||||
}
|
||||
if (minDistance > 0)
|
||||
if (spawnDistance > 0)
|
||||
{
|
||||
bool someoneNearby = false;
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
if (submarine.Info.Type != SubmarineType.Player) { continue; }
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < MathUtils.Pow2(minDistance))
|
||||
float distanceSquared = Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value);
|
||||
if (distanceSquared < MathUtils.Pow2(spawnDistance))
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
if (chosenPosition.Submarine != null)
|
||||
{
|
||||
Vector2 from = Submarine.GetRelativeSimPositionFromWorldPosition(spawnPos.Value, chosenPosition.Submarine, chosenPosition.Submarine);
|
||||
Vector2 to = Submarine.GetRelativeSimPositionFromWorldPosition(submarine.WorldPosition, chosenPosition.Submarine, submarine);
|
||||
if (CheckLineOfSight(from, to, chosenPosition.Submarine))
|
||||
{
|
||||
// Line of sight to a player sub -> don't spawn yet.
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c == Character.Controlled || c.IsRemotePlayer)
|
||||
{
|
||||
if (Vector2.DistanceSquared(c.WorldPosition, spawnPos.Value) < MathUtils.Pow2(minDistance))
|
||||
float distanceSquared = Vector2.DistanceSquared(c.WorldPosition, spawnPos.Value);
|
||||
if (distanceSquared < MathUtils.Pow2(spawnDistance))
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
if (chosenPosition.Submarine != null)
|
||||
{
|
||||
Vector2 from = Submarine.GetRelativeSimPositionFromWorldPosition(spawnPos.Value, chosenPosition.Submarine, chosenPosition.Submarine);
|
||||
Vector2 to = Submarine.GetRelativeSimPositionFromWorldPosition(c.WorldPosition, chosenPosition.Submarine, c.Submarine);
|
||||
if (CheckLineOfSight(from, to, chosenPosition.Submarine))
|
||||
{
|
||||
// Line of sight to a player character -> don't spawn. Disable the event to prevent monsters "magically" spawning here.
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!someoneNearby) { return; }
|
||||
|
||||
static bool CheckLineOfSight(Vector2 from, Vector2 to, Submarine targetSub)
|
||||
{
|
||||
var bodies = Submarine.PickBodies(from, to, ignoredBodies: null, Physics.CollisionWall);
|
||||
foreach (var b in bodies)
|
||||
{
|
||||
if (b.UserData is ISpatialEntity spatialEntity && spatialEntity.Submarine != targetSub)
|
||||
{
|
||||
// Different sub -> ignore
|
||||
continue;
|
||||
}
|
||||
if (b.UserData is Structure s && !s.IsPlatform && s.CastShadow)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (b.UserData is Item item && item.GetComponent<Door>() is Door door)
|
||||
{
|
||||
if (!door.IsBroken && !door.IsOpen)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (SpawnPosType.HasFlag(Level.PositionType.Abyss) || SpawnPosType.HasFlag(Level.PositionType.AbyssCave))
|
||||
{
|
||||
bool anyInAbyss = false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -9,10 +10,19 @@ namespace Barotrauma
|
||||
private readonly Dictionary<Identifier, List<Predicate<Entity>>> targetPredicates = new Dictionary<Identifier, List<Predicate<Entity>>>();
|
||||
|
||||
private readonly Dictionary<Identifier, List<Entity>> cachedTargets = new Dictionary<Identifier, List<Entity>>();
|
||||
|
||||
/// <summary>
|
||||
/// How many targets were there when they were tagged for the first time? Can be used by some EventActions to check how many entities
|
||||
/// there are still left (e.g. how much of the initial cargo still exists)
|
||||
/// </summary>
|
||||
private readonly Dictionary<Identifier, int> initialAmounts = new Dictionary<Identifier, int>();
|
||||
|
||||
private int prevEntityCount;
|
||||
private int prevPlayerCount, prevBotCount;
|
||||
private Character prevControlled;
|
||||
|
||||
public readonly OnRoundEndAction OnRoundEndAction;
|
||||
|
||||
private readonly string[] requiredDestinationTypes;
|
||||
public readonly bool RequireBeaconStation;
|
||||
|
||||
@@ -20,16 +30,25 @@ namespace Barotrauma
|
||||
public List<EventAction> Actions { get; } = new List<EventAction>();
|
||||
public Dictionary<Identifier, List<Entity>> Targets { get; } = new Dictionary<Identifier, List<Entity>>();
|
||||
|
||||
protected virtual IEnumerable<Identifier> NonActionChildElementNames => Enumerable.Empty<Identifier>();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"ScriptedEvent ({prefab.Identifier})";
|
||||
return $"{nameof(ScriptedEvent)} ({prefab.Identifier})";
|
||||
}
|
||||
|
||||
public ScriptedEvent(EventPrefab prefab) : base(prefab)
|
||||
{
|
||||
foreach (var element in prefab.ConfigElement.Elements())
|
||||
{
|
||||
if (element.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase))
|
||||
Identifier elementId = element.Name.ToIdentifier();
|
||||
if (NonActionChildElementNames.Contains(elementId)) { continue; }
|
||||
if (elementId == nameof(Barotrauma.OnRoundEndAction))
|
||||
{
|
||||
OnRoundEndAction = EventAction.Instantiate(this, element) as OnRoundEndAction;
|
||||
continue;
|
||||
}
|
||||
if (elementId == "statuseffect")
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event prefab \"{prefab.Identifier}\". Status effect configured as an action. Please configure status effects as child elements of a StatusEffectAction.");
|
||||
continue;
|
||||
@@ -46,31 +65,125 @@ namespace Barotrauma
|
||||
requiredDestinationTypes = prefab.ConfigElement.GetAttributeStringArray("requireddestinationtypes", null);
|
||||
RequireBeaconStation = prefab.ConfigElement.GetAttributeBool("requirebeaconstation", false);
|
||||
|
||||
var allActions = GetAllActions().Select(a => a.action);
|
||||
foreach (var gotoAction in allActions.OfType<GoTo>())
|
||||
{
|
||||
if (allActions.None(a => a is Label label && label.Name == gotoAction.Name))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{prefab.Identifier}\". Could not find a label matching the GoTo \"{gotoAction.Name}\".");
|
||||
}
|
||||
}
|
||||
|
||||
GameAnalyticsManager.AddDesignEvent($"ScriptedEvent:{prefab.Identifier}:Start");
|
||||
}
|
||||
|
||||
public override string GetDebugInfo()
|
||||
{
|
||||
EventAction currentAction = !IsFinished ? Actions[CurrentActionIndex] : null;
|
||||
|
||||
string text = $"Finished: {IsFinished.ColorizeObject()}\n" +
|
||||
$"Action index: {CurrentActionIndex.ColorizeObject()}\n" +
|
||||
$"Current action: {currentAction?.ToDebugString() ?? ToolBox.ColorizeObject(null)}\n";
|
||||
|
||||
text += "All actions:\n";
|
||||
text += GetAllActions().Aggregate(string.Empty, (current, action) => current + $"{new string(' ', action.indent * 6)}{action.action.ToDebugString()}\n");
|
||||
|
||||
text += "Targets:\n";
|
||||
foreach (var (key, value) in Targets)
|
||||
{
|
||||
text += $" {key.ColorizeObject()}: {value.Aggregate(string.Empty, (current, entity) => current + $"{entity.ColorizeObject()} ")}\n";
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
public virtual string GetTextForReplacementElement(string tag)
|
||||
{
|
||||
if (tag.StartsWith("eventtag:"))
|
||||
{
|
||||
string targetTag = tag["eventtag:".Length..];
|
||||
Entity target = GetTargets(targetTag.ToIdentifier()).FirstOrDefault();
|
||||
if (target != null)
|
||||
{
|
||||
if (target is Item item) { return item.Name; }
|
||||
if (target is Character character) { return character.Name; }
|
||||
if (target is Hull hull) { return hull.DisplayName.Value; }
|
||||
if (target is Submarine sub) { return sub.Info.DisplayName.Value; }
|
||||
DebugConsole.AddWarning($"Failed to get the name of the event target {target} as a replacement for the tag {tag} in an event text.");
|
||||
return target.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"[target \"{targetTag}\" not found]";
|
||||
}
|
||||
}
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
public virtual LocalizedString ReplaceVariablesInEventText(LocalizedString str)
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds all actions in the ScriptedEvent (recursively going through the subactions as well).
|
||||
/// Returns a list of tuples where the first value is the indentation level (or "how deep in the hierarchy") the action is.
|
||||
/// </summary>
|
||||
public List<(int indent, EventAction action)> GetAllActions()
|
||||
{
|
||||
var list = new List<(int indent, EventAction action)>();
|
||||
foreach (EventAction eventAction in Actions)
|
||||
{
|
||||
list.AddRange(FindActionsRecursive(eventAction));
|
||||
}
|
||||
return list;
|
||||
|
||||
static List<(int indent, EventAction action)> FindActionsRecursive(EventAction eventAction, int indent = 1)
|
||||
{
|
||||
var eventActions = new List<(int indent, EventAction action)> { (indent, eventAction) };
|
||||
indent++;
|
||||
foreach (var action in eventAction.GetSubActions())
|
||||
{
|
||||
eventActions.AddRange(FindActionsRecursive(action, indent));
|
||||
}
|
||||
return eventActions;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddTarget(Identifier tag, Entity target)
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
throw new System.ArgumentException("Target was null");
|
||||
throw new ArgumentException($"Target was null (tag: {tag})");
|
||||
}
|
||||
if (target.Removed)
|
||||
{
|
||||
throw new System.ArgumentException("Target has been removed");
|
||||
throw new ArgumentException($"Target has been removed (tag: {tag})");
|
||||
}
|
||||
if (!Targets.ContainsKey(tag))
|
||||
if (Targets.ContainsKey(tag))
|
||||
{
|
||||
Targets.Add(tag, new List<Entity>());
|
||||
}
|
||||
Targets[tag].Add(target);
|
||||
if (cachedTargets.ContainsKey(tag))
|
||||
{
|
||||
cachedTargets[tag].Add(target);
|
||||
if (!Targets[tag].Contains(target))
|
||||
{
|
||||
Targets[tag].Add(target);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedTargets.Add(tag, new List<Entity> { target });
|
||||
Targets.Add(tag, new List<Entity>() { target });
|
||||
}
|
||||
if (cachedTargets.ContainsKey(tag))
|
||||
{
|
||||
if (!cachedTargets[tag].Contains(target))
|
||||
{
|
||||
cachedTargets[tag].Add(target);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cachedTargets.Add(tag, Targets[tag].ToList());
|
||||
}
|
||||
if (!initialAmounts.ContainsKey(tag))
|
||||
{
|
||||
initialAmounts.Add(tag, cachedTargets[tag].Count);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +201,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public int GetInitialTargetCount(Identifier tag)
|
||||
{
|
||||
if (initialAmounts.TryGetValue(tag, out int count))
|
||||
{
|
||||
return count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public IEnumerable<Entity> GetTargets(Identifier tag)
|
||||
{
|
||||
if (cachedTargets.ContainsKey(tag))
|
||||
@@ -136,10 +258,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
cachedTargets.Add(tag, targetsToReturn);
|
||||
cachedTargets.Add(tag, targetsToReturn);
|
||||
if (!initialAmounts.ContainsKey(tag))
|
||||
{
|
||||
initialAmounts.Add(tag, targetsToReturn.Count);
|
||||
}
|
||||
return targetsToReturn;
|
||||
}
|
||||
|
||||
public void InheritTags(Entity originalEntity, Entity newEntity)
|
||||
{
|
||||
foreach (var kvp in Targets)
|
||||
{
|
||||
if (kvp.Value.Contains(originalEntity))
|
||||
{
|
||||
kvp.Value.Add(newEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTag(Identifier tag)
|
||||
{
|
||||
if (tag.IsEmpty) { return; }
|
||||
@@ -152,8 +289,14 @@ namespace Barotrauma
|
||||
{
|
||||
int botCount = 0;
|
||||
int playerCount = 0;
|
||||
bool forceRefreshTargets = false;
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.Removed)
|
||||
{
|
||||
forceRefreshTargets = true;
|
||||
continue;
|
||||
}
|
||||
if (c.IsPlayer)
|
||||
{
|
||||
playerCount++;
|
||||
@@ -163,7 +306,7 @@ namespace Barotrauma
|
||||
botCount++;
|
||||
}
|
||||
}
|
||||
if (Entity.EntityCount != prevEntityCount || botCount != prevBotCount || playerCount != prevPlayerCount || prevControlled != Character.Controlled)
|
||||
if (forceRefreshTargets || Entity.EntityCount != prevEntityCount || botCount != prevBotCount || playerCount != prevPlayerCount || prevControlled != Character.Controlled)
|
||||
{
|
||||
cachedTargets.Clear();
|
||||
prevEntityCount = Entity.EntityCount;
|
||||
@@ -204,6 +347,10 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (CurrentActionIndex == -1)
|
||||
{
|
||||
DebugConsole.AddWarning($"Could not find the GoTo label \"{goTo}\" in the event \"{Prefab.Identifier}\". Ending the event.");
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentActionIndex >= Actions.Count || CurrentActionIndex < 0)
|
||||
|
||||
Reference in New Issue
Block a user