(965c31410a) Unstable v0.10.4.0
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class AfflictionAction : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string Affliction { get; set; }
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float Strength { get; set; }
|
||||
|
||||
[Serialize(LimbType.None, true)]
|
||||
public LimbType LimbType { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
public AfflictionAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
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 afflictionPrefab = AfflictionPrefab.List.FirstOrDefault(p => p.Identifier.Equals(Affliction, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (afflictionPrefab != null)
|
||||
{
|
||||
var targets = ParentEvent.GetTargets(TargetTag);
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target != null && target is Character character)
|
||||
{
|
||||
var limb = LimbType != LimbType.None ? character.AnimController.GetLimb(LimbType) : null;
|
||||
if (Strength > 0.0f)
|
||||
{
|
||||
character.CharacterHealth.ApplyAffliction(limb, afflictionPrefab.Instantiate(Strength));
|
||||
}
|
||||
else if (Strength < 0.0f)
|
||||
{
|
||||
character.CharacterHealth.ReduceAffliction(limb, Affliction, -Strength);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(AfflictionAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
|
||||
$"Affliction: {Affliction.ColorizeObject()}, Strength: {Strength.ColorizeObject()}, " +
|
||||
$"LimbType: {LimbType.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract class BinaryOptionAction : EventAction
|
||||
{
|
||||
public SubactionGroup Success = null;
|
||||
public SubactionGroup Failure = null;
|
||||
protected bool? succeeded = null;
|
||||
|
||||
public BinaryOptionAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
foreach (XElement elem in element.Elements())
|
||||
{
|
||||
string elemName = elem.Name.LocalName;
|
||||
if (elemName.Equals("success", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
Success ??= new SubactionGroup(ParentEvent, elem);
|
||||
}
|
||||
else if (elemName.Equals("failure", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
Failure ??= new SubactionGroup(ParentEvent, elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<EventAction> GetSubActions()
|
||||
{
|
||||
IEnumerable<EventAction> actions = Success?.Actions ?? Enumerable.Empty<EventAction>();
|
||||
actions = actions.Concat(Failure?.Actions ?? Enumerable.Empty<EventAction>());
|
||||
return actions;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return DetermineFinished(ref goTo);
|
||||
}
|
||||
|
||||
protected bool DetermineFinished()
|
||||
{
|
||||
string throwaway = null;
|
||||
return DetermineFinished(ref throwaway);
|
||||
}
|
||||
|
||||
protected bool DetermineFinished(ref string goTo)
|
||||
{
|
||||
if (succeeded.HasValue)
|
||||
{
|
||||
if (succeeded.Value)
|
||||
{
|
||||
if (Success == null || Success.IsFinished(ref goTo))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Failure == null || Failure.IsFinished(ref goTo))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool SetGoToTarget(string goTo)
|
||||
{
|
||||
if (Success != null && Success.SetGoToTarget(goTo))
|
||||
{
|
||||
succeeded = true;
|
||||
return true;
|
||||
}
|
||||
else if (Failure != null && Failure.SetGoToTarget(goTo))
|
||||
{
|
||||
succeeded = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
Success?.Reset();
|
||||
Failure?.Reset();
|
||||
succeeded = null;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (succeeded.HasValue)
|
||||
{
|
||||
if (succeeded.Value)
|
||||
{
|
||||
Success?.Update(deltaTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
Failure?.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
succeeded = DetermineSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract bool? DetermineSuccess();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
#nullable enable
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CheckDataAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string Identifier { get; set; } = null!;
|
||||
|
||||
[Serialize("", true)]
|
||||
public string Condition { get; set; } = null!;
|
||||
|
||||
protected object? value2;
|
||||
protected object? value1;
|
||||
|
||||
protected PropertyConditional.OperatorType Operator { get; set; }
|
||||
|
||||
public CheckDataAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
if (!(GameMain.GameSession?.GameMode is CampaignMode campaignMode)) { return false; }
|
||||
|
||||
string[] splitString = Condition.Split(' ');
|
||||
string value = Condition;
|
||||
if (splitString.Length > 0)
|
||||
{
|
||||
for (int i = 1; i < splitString.Length; i++)
|
||||
{
|
||||
value = splitString[i] + (i > 1 && i < splitString.Length ? " " : "");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"{Condition} is too short, it should start with an operator followed by a boolean or a floating point value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
string op = splitString[0];
|
||||
Operator = PropertyConditional.GetOperatorType(op);
|
||||
if (Operator == PropertyConditional.OperatorType.None) { return false; }
|
||||
|
||||
bool? tryBoolean = TryBoolean(campaignMode, value);
|
||||
if (tryBoolean != null) { return tryBoolean; }
|
||||
|
||||
bool? tryFloat = TryFloat(campaignMode, value);
|
||||
if (tryFloat != null) { return tryFloat; }
|
||||
|
||||
DebugConsole.ThrowError($"{value2} ({Condition}) did not match a boolean or a float.");
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool? TryBoolean(CampaignMode campaignMode, string value)
|
||||
{
|
||||
if (bool.TryParse(value, out bool b))
|
||||
{
|
||||
bool target = GetBool(campaignMode);
|
||||
value1 = target;
|
||||
value2 = b;
|
||||
switch (Operator)
|
||||
{
|
||||
case PropertyConditional.OperatorType.Equals:
|
||||
return target == b;
|
||||
case PropertyConditional.OperatorType.NotEquals:
|
||||
return target != b;
|
||||
default:
|
||||
DebugConsole.Log($"Only \"Equals\" and \"Not equals\" operators are allowed for a boolean (was {Operator} for {value}).");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DebugConsole.Log($"{value} != bool");
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool? TryFloat(CampaignMode campaignMode, string value)
|
||||
{
|
||||
if (float.TryParse(value, out float f))
|
||||
{
|
||||
float target = GetFloat(campaignMode);
|
||||
value1 = target;
|
||||
value2 = f;
|
||||
switch (Operator)
|
||||
{
|
||||
case PropertyConditional.OperatorType.Equals:
|
||||
return MathUtils.NearlyEqual(target, f);
|
||||
case PropertyConditional.OperatorType.GreaterThan:
|
||||
return target > f;
|
||||
case PropertyConditional.OperatorType.GreaterThanEquals:
|
||||
return target >= f;
|
||||
case PropertyConditional.OperatorType.LessThan:
|
||||
return target < f;
|
||||
case PropertyConditional.OperatorType.LessThanEquals:
|
||||
return target <= f;
|
||||
case PropertyConditional.OperatorType.NotEquals:
|
||||
return !MathUtils.NearlyEqual(target, f);
|
||||
}
|
||||
}
|
||||
|
||||
DebugConsole.Log($"{value} != float");
|
||||
return null;
|
||||
}
|
||||
|
||||
protected virtual bool GetBool(CampaignMode campaignMode)
|
||||
{
|
||||
return campaignMode.CampaignMetadata.GetBoolean(Identifier);
|
||||
}
|
||||
|
||||
protected virtual float GetFloat(CampaignMode campaignMode)
|
||||
{
|
||||
return campaignMode.CampaignMetadata.GetFloat(Identifier);
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
string condition = "?";
|
||||
if (value2 != null && value1 != null)
|
||||
{
|
||||
condition = $"{value1.ColorizeObject()} {Operator.ColorizeObject()} {value2.ColorizeObject()}";
|
||||
}
|
||||
|
||||
return $"{ToolBox.GetDebugSymbol(succeeded.HasValue)} {nameof(CheckDataAction)} -> (Data: {Identifier.ColorizeObject()}, Success: {succeeded.ColorizeObject()}, Expression: {condition})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CheckItemAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string ItemIdentifiers { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string ItemTags { get; set; }
|
||||
|
||||
private readonly string[] itemIdentifierSplit;
|
||||
private readonly string[] itemTags;
|
||||
|
||||
public CheckItemAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
itemIdentifierSplit = ItemIdentifiers.Split(',');
|
||||
itemTags = ItemTags.Split(",");
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
var targets = ParentEvent.GetTargets(TargetTag);
|
||||
if (!targets.Any()) { return null; }
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (!(target is Character chr)) { continue; }
|
||||
if (chr.Inventory == null) { continue; }
|
||||
|
||||
if (itemTags.Any(tag => chr.Inventory.Items.Any(item => item != null && item.HasTag(tag)))) { return true; }
|
||||
|
||||
foreach (var identifier in itemIdentifierSplit)
|
||||
{
|
||||
if (chr.Inventory.Items.Any(it => it != null && it.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
string subActionStr = "";
|
||||
if (succeeded.HasValue)
|
||||
{
|
||||
subActionStr = $"\n Sub action: {(succeeded.Value ? Success : Failure)?.CurrentSubAction.ColorizeObject()}";
|
||||
}
|
||||
return $"{ToolBox.GetDebugSymbol(DetermineFinished())} {nameof(CheckItemAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
|
||||
$"ItemIdentifiers: {ItemIdentifiers.ColorizeObject()}" +
|
||||
$"Succeeded: {(succeeded.HasValue ? succeeded.Value.ToString() : "not determined").ColorizeObject()})" +
|
||||
subActionStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CheckReputationAction : CheckDataAction
|
||||
{
|
||||
[Serialize(ReputationAction.ReputationType.None, true)]
|
||||
public ReputationAction.ReputationType TargetType { get; set; }
|
||||
|
||||
public CheckReputationAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override float GetFloat(CampaignMode campaignMode)
|
||||
{
|
||||
switch (TargetType)
|
||||
{
|
||||
case ReputationAction.ReputationType.Faction:
|
||||
{
|
||||
Faction? faction = campaignMode.Factions.Find(f => f.Prefab.Identifier.Equals(Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (faction != null) { return faction.Reputation.Value; }
|
||||
break;
|
||||
}
|
||||
case ReputationAction.ReputationType.Location:
|
||||
{
|
||||
Location? location = campaignMode.Map.CurrentLocation;
|
||||
Debug.Assert(location?.Reputation != null, "location?.Reputation != null");
|
||||
if (location?.Reputation != null) { return location.Reputation.Value; }
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
DebugConsole.ThrowError("CheckReputationAction requires a \"TargetType\" but none were specified.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
protected override bool GetBool(CampaignMode campaignMode)
|
||||
{
|
||||
DebugConsole.ThrowError("Boolean comparison cannot be applied to reputations.");
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
string condition = "?";
|
||||
if (value2 != null && value1 != null)
|
||||
{
|
||||
condition = $"{value1.ColorizeObject()} {Operator.ColorizeObject()} {value2.ColorizeObject()}";
|
||||
}
|
||||
|
||||
return $"{ToolBox.GetDebugSymbol(succeeded.HasValue)} {nameof(CheckReputationAction)} -> (Type: {TargetType.ColorizeObject()}, " +
|
||||
$"{(string.IsNullOrWhiteSpace(Identifier) ? string.Empty : $"Identifier: {Identifier.ColorizeObject()}, ")}" +
|
||||
$"Success: {succeeded.ColorizeObject()}, Expression: {condition})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CombatAction : EventAction
|
||||
{
|
||||
[Serialize(AIObjectiveCombat.CombatMode.Offensive, true)]
|
||||
public AIObjectiveCombat.CombatMode CombatMode { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string NPCTag { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string EnemyTag { get; set; }
|
||||
|
||||
[Serialize(120.0f, true)]
|
||||
public float CoolDown { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
|
||||
private IEnumerable<Character> affectedNpcs = null;
|
||||
|
||||
public CombatAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(e => e is Character).Select(e => e as Character);
|
||||
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (!(npc.AIController is HumanAIController humanAiController)) { continue; }
|
||||
|
||||
Character enemy = null;
|
||||
float closestDist = float.MaxValue;
|
||||
foreach (Entity target in ParentEvent.GetTargets(EnemyTag))
|
||||
{
|
||||
if (!(target is Character character)) { continue; }
|
||||
float dist = Vector2.DistanceSquared(npc.WorldPosition, target.WorldPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
enemy = character;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
if (enemy == null) { continue; }
|
||||
|
||||
npc.TurnedHostileByEvent = true;
|
||||
var objectiveManager = humanAiController.ObjectiveManager;
|
||||
foreach (var goToObjective in objectiveManager.GetActiveObjectives<AIObjectiveGoTo>())
|
||||
{
|
||||
goToObjective.Abandon = true;
|
||||
}
|
||||
objectiveManager.AddObjective(new AIObjectiveCombat(npc, enemy, CombatMode, objectiveManager, coolDown: CoolDown));
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
if (affectedNpcs != null)
|
||||
{
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.Removed || !(npc.AIController is HumanAIController humanAiController)) { continue; }
|
||||
foreach (var combatObjective in humanAiController.ObjectiveManager.GetActiveObjectives<AIObjectiveCombat>())
|
||||
{
|
||||
combatObjective.Abandon = true;
|
||||
}
|
||||
}
|
||||
affectedNpcs = null;
|
||||
}
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(CombatAction)} -> (Cooldown: {CoolDown.ColorizeObject()}, CombatMode: {CombatMode.ColorizeObject()}, NPCTag: {NPCTag.ColorizeObject()}, EnemyTag: {EnemyTag.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class ConversationAction : EventAction
|
||||
{
|
||||
|
||||
public enum DialogTypes
|
||||
{
|
||||
Regular,
|
||||
Small,
|
||||
Mission
|
||||
}
|
||||
|
||||
const float InterruptDistance = 300.0f;
|
||||
|
||||
/// <summary>
|
||||
/// Other events can't trigger conversations if some other event has triggered one within this time.
|
||||
/// Intended to prevent multiple events from triggering conversations at the same time.
|
||||
/// </summary>
|
||||
const float BlockOtherConversationsDuration = 5.0f;
|
||||
|
||||
[Serialize("", true)]
|
||||
public string Text { get; set; }
|
||||
|
||||
[Serialize(0, true)]
|
||||
public int DefaultOption { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string SpeakerTag { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool WaitForInteraction { get; set; }
|
||||
|
||||
[Serialize(false, true)]
|
||||
public bool FadeToBlack { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string EventSprite { get; set; }
|
||||
|
||||
[Serialize(DialogTypes.Regular, true)]
|
||||
public DialogTypes DialogType { get; set; }
|
||||
|
||||
[Serialize(false, true)]
|
||||
public bool ContinueConversation { get; set; }
|
||||
|
||||
private Character speaker;
|
||||
|
||||
private OrderInfo? prevSpeakerOrder;
|
||||
|
||||
public List<SubactionGroup> Options { get; private set; }
|
||||
|
||||
public SubactionGroup Interrupted { get; private set; }
|
||||
|
||||
private static UInt16 actionCount;
|
||||
|
||||
//an identifier the server uses to identify which ConversationAction a client is responding to
|
||||
public readonly UInt16 Identifier;
|
||||
|
||||
private int selectedOption = -1;
|
||||
private bool dialogOpened = false;
|
||||
|
||||
private double lastActiveTime;
|
||||
|
||||
private bool interrupt;
|
||||
|
||||
public ConversationAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
actionCount++;
|
||||
Identifier = actionCount;
|
||||
Options = new List<SubactionGroup>();
|
||||
foreach (XElement elem in element.Elements())
|
||||
{
|
||||
if (elem.Name.LocalName.Equals("option", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
Options.Add(new SubactionGroup(ParentEvent, elem));
|
||||
}
|
||||
else if (elem.Name.LocalName.Equals("interrupt", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
Interrupted = new SubactionGroup(ParentEvent, elem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<EventAction> GetSubActions()
|
||||
{
|
||||
return Options.SelectMany(group => group.Actions);
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
if (interrupt)
|
||||
{
|
||||
if (dialogOpened)
|
||||
{
|
||||
#if CLIENT
|
||||
dialogBox?.Close();
|
||||
#else
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (c.InGame && c.Character != null) { ServerWrite(speaker, c); }
|
||||
}
|
||||
# endif
|
||||
ResetSpeaker();
|
||||
dialogOpened = false;
|
||||
}
|
||||
|
||||
if (Interrupted == null)
|
||||
{
|
||||
goTo = "_end";
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Interrupted.IsFinished(ref goTo);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedOption >= 0)
|
||||
{
|
||||
if (!Options.Any() || Options[selectedOption].IsFinished(ref goTo))
|
||||
{
|
||||
ResetSpeaker();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
Options.ForEach(a => a.Reset());
|
||||
ResetSpeaker();
|
||||
selectedOption = -1;
|
||||
interrupt = false;
|
||||
dialogOpened = false;
|
||||
speaker = null;
|
||||
}
|
||||
|
||||
public override bool SetGoToTarget(string goTo)
|
||||
{
|
||||
selectedOption = -1;
|
||||
for (int i = 0; i < Options.Count; i++)
|
||||
{
|
||||
if (Options[i].SetGoToTarget(goTo))
|
||||
{
|
||||
selectedOption = i;
|
||||
interrupt = false;
|
||||
dialogOpened = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ResetSpeaker()
|
||||
{
|
||||
if (speaker == null) { return; }
|
||||
speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
speaker.SetCustomInteract(null, null);
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
#endif
|
||||
if (prevSpeakerOrder != null)
|
||||
{
|
||||
(speaker.AIController as HumanAIController)?.SetOrder(prevSpeakerOrder.Value.Order, prevSpeakerOrder.Value.OrderOption, orderGiver: null, speak: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
(speaker.AIController as HumanAIController)?.SetOrder(null, string.Empty, orderGiver: null, speak: false);
|
||||
}
|
||||
}
|
||||
|
||||
private int[] GetEndingOptions()
|
||||
{
|
||||
List<int> endings = Options.Where(group => !group.Actions.Any() || group.EndConversation).Select(group => Options.IndexOf(group)).ToList();
|
||||
if (!ContinueConversation) { endings.Add(-1); }
|
||||
return endings.ToArray();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
lastActiveTime = Timing.TotalTime;
|
||||
if (interrupt)
|
||||
{
|
||||
Interrupted?.Update(deltaTime);
|
||||
}
|
||||
else if (selectedOption < 0)
|
||||
{
|
||||
if (dialogOpened)
|
||||
{
|
||||
#if CLIENT
|
||||
Character.DisableControls = true;
|
||||
#endif
|
||||
if (ShouldInterrupt())
|
||||
{
|
||||
ResetSpeaker();
|
||||
interrupt = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(SpeakerTag))
|
||||
{
|
||||
if (speaker != null && !speaker.Removed && speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk) { return; }
|
||||
speaker = ParentEvent.GetTargets(SpeakerTag).FirstOrDefault(e => e is Character) as Character;
|
||||
if (speaker == null || speaker.Removed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
//some conversation already assigned to the speaker, wait for it to be removed
|
||||
if (speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (!WaitForInteraction)
|
||||
{
|
||||
TryStartConversation(speaker);
|
||||
}
|
||||
else
|
||||
{
|
||||
speaker.CampaignInteractionType = CampaignMode.InteractionType.Talk;
|
||||
#if CLIENT
|
||||
speaker.SetCustomInteract(
|
||||
TryStartConversation,
|
||||
TextManager.GetWithVariable("CampaignInteraction.Talk", "[key]", GameMain.Config.KeyBindText(InputType.Use)));
|
||||
#else
|
||||
speaker.SetCustomInteract(
|
||||
TryStartConversation,
|
||||
TextManager.Get("CampaignInteraction.Talk"));
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
TryStartConversation(null);
|
||||
}
|
||||
}
|
||||
else if (Options.Any())
|
||||
{
|
||||
Options[selectedOption].Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldInterrupt()
|
||||
{
|
||||
IEnumerable<Entity> targets = Enumerable.Empty<Entity>();
|
||||
if (!string.IsNullOrEmpty(TargetTag))
|
||||
{
|
||||
targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e));
|
||||
if (!targets.Any()) { return true; }
|
||||
}
|
||||
|
||||
if (speaker != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(TargetTag))
|
||||
{
|
||||
if (targets.All(t => Vector2.DistanceSquared(t.WorldPosition, speaker.WorldPosition) > InterruptDistance * InterruptDistance)) { return true; }
|
||||
}
|
||||
if (speaker.AIController is HumanAIController humanAI && !humanAI.AllowCampaignInteraction())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return speaker.Removed || speaker.IsDead || speaker.IsIncapacitated;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsValidTarget(Entity e)
|
||||
{
|
||||
return
|
||||
e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
|
||||
(e == Character.Controlled || character.IsRemotePlayer);
|
||||
}
|
||||
|
||||
private void TryStartConversation(Character speaker, Character targetCharacter = null)
|
||||
{
|
||||
IEnumerable<Entity> targets = Enumerable.Empty<Entity>();
|
||||
if (!string.IsNullOrEmpty(TargetTag))
|
||||
{
|
||||
targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e));
|
||||
if (!targets.Any() || IsBlockedByAnotherConversation(targets)) { return; }
|
||||
}
|
||||
|
||||
if (speaker?.AIController is HumanAIController humanAI)
|
||||
{
|
||||
prevSpeakerOrder = null;
|
||||
if (humanAI.CurrentOrder != null)
|
||||
{
|
||||
prevSpeakerOrder = new OrderInfo(humanAI.CurrentOrder, humanAI.CurrentOrderOption);
|
||||
}
|
||||
humanAI.SetOrder(
|
||||
Order.PrefabList.Find(o => o.Identifier.Equals("wait", StringComparison.OrdinalIgnoreCase)),
|
||||
option: string.Empty, orderGiver: null, speak: false);
|
||||
if (targets.Any())
|
||||
{
|
||||
Entity closestTarget = null;
|
||||
float closestDist = float.MaxValue;
|
||||
foreach (Entity entity in targets)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(entity.WorldPosition, speaker.WorldPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestTarget = entity;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
if (closestTarget != null)
|
||||
{
|
||||
humanAI.FaceTarget(closestTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShowDialog(speaker, targetCharacter);
|
||||
|
||||
dialogOpened = true;
|
||||
}
|
||||
|
||||
partial void ShowDialog(Character speaker, Character targetCharacter);
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
if (!interrupt)
|
||||
{
|
||||
SubactionGroup selOtion = null;
|
||||
if (selectedOption >= 0 && Options.Count > selectedOption)
|
||||
{
|
||||
selOtion = Options[selectedOption];
|
||||
}
|
||||
|
||||
EventAction subAction = null;
|
||||
if (selOtion != null)
|
||||
{
|
||||
subAction = selOtion.CurrentSubAction;
|
||||
}
|
||||
|
||||
return $"{ToolBox.GetDebugSymbol(selectedOption > -1)} {nameof(ConversationAction)} -> (Selected option: {selOtion?.Text.ColorizeObject()})\n" +
|
||||
$" Sub action: {subAction.ColorizeObject()}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(true)} {nameof(ConversationAction)} -> (Interrupted)\n" +
|
||||
$" Sub action: {Interrupted?.CurrentSubAction.ColorizeObject()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract class EventAction
|
||||
{
|
||||
public class SubactionGroup
|
||||
{
|
||||
public string Text;
|
||||
public List<EventAction> Actions;
|
||||
public bool EndConversation;
|
||||
|
||||
private int currentSubAction = 0;
|
||||
|
||||
public EventAction CurrentSubAction
|
||||
{
|
||||
get
|
||||
{
|
||||
if (currentSubAction >= 0 && Actions.Count > currentSubAction)
|
||||
{
|
||||
return Actions[currentSubAction];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public SubactionGroup(ScriptedEvent scriptedEvent, XElement elem)
|
||||
{
|
||||
Text = elem.Attribute("text")?.Value ?? "";
|
||||
Actions = new List<EventAction>();
|
||||
EndConversation = elem.GetAttributeBool("endconversation", false);
|
||||
foreach (XElement e in elem.Elements())
|
||||
{
|
||||
if (e.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event prefab \"{scriptedEvent.Prefab.Identifier}\". Status effect configured as a sub action (text: \"{Text}\"). Please configure status effects as child elements of a StatusEffectAction.");
|
||||
continue;
|
||||
}
|
||||
Actions.Add(Instantiate(scriptedEvent, e));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFinished(ref string goTo)
|
||||
{
|
||||
if (currentSubAction < Actions.Count)
|
||||
{
|
||||
string innerGoTo = null;
|
||||
if (Actions[currentSubAction].IsFinished(ref innerGoTo))
|
||||
{
|
||||
if (string.IsNullOrEmpty(innerGoTo))
|
||||
{
|
||||
currentSubAction++;
|
||||
}
|
||||
else
|
||||
{
|
||||
goTo = innerGoTo;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentSubAction >= Actions.Count)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool SetGoToTarget(string goTo)
|
||||
{
|
||||
currentSubAction = 0;
|
||||
for (int i = 0; i < Actions.Count; i++)
|
||||
{
|
||||
if (Actions[i].SetGoToTarget(goTo))
|
||||
{
|
||||
currentSubAction = i;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Actions.ForEach(a => a.Reset());
|
||||
currentSubAction = 0;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (currentSubAction < Actions.Count)
|
||||
{
|
||||
Actions[currentSubAction].Update(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ScriptedEvent ParentEvent;
|
||||
|
||||
public EventAction(ScriptedEvent parentEvent, XElement element)
|
||||
{
|
||||
ParentEvent = parentEvent;
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Has the action finished.
|
||||
/// </summary>
|
||||
/// <param name="goToLabel">If null or empty, the event moves to the next action. Otherwise it moves to the specified label.</param>
|
||||
/// <returns></returns>
|
||||
public abstract bool IsFinished(ref string goToLabel);
|
||||
|
||||
public virtual bool SetGoToTarget(string goTo)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract void Reset();
|
||||
|
||||
public virtual bool CanBeFinished()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<EventAction> GetSubActions()
|
||||
{
|
||||
return Enumerable.Empty<EventAction>();
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
|
||||
public static EventAction Instantiate(ScriptedEvent scriptedEvent, XElement element)
|
||||
{
|
||||
Type actionType = null;
|
||||
try
|
||||
{
|
||||
actionType = Type.GetType("Barotrauma." + element.Name, true, true);
|
||||
if (actionType == null) { throw new NullReferenceException(); }
|
||||
}
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find an event class of the type \"" + element.Name + "\".");
|
||||
return null;
|
||||
}
|
||||
|
||||
ConstructorInfo constructor = actionType.GetConstructor(new[] { typeof(ScriptedEvent), typeof(XElement) });
|
||||
try
|
||||
{
|
||||
return constructor.Invoke(new object[] { scriptedEvent, element }) as EventAction;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rich test to display in debugdraw
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// public override string ToDebugString()
|
||||
/// {
|
||||
/// return $"{ToolBox.GetDebugSymbol(isFinished)} SomeAction -> "(someInfo: {info.ColorizeObject()})";
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <returns></returns>
|
||||
public virtual string ToDebugString()
|
||||
{
|
||||
return $"[?] {GetType().Name}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class FireAction : EventAction
|
||||
{
|
||||
[Serialize(10.0f, true)]
|
||||
public float Size { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
public FireAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
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);
|
||||
foreach (var target in targets)
|
||||
{
|
||||
Vector2 pos = target.WorldPosition;
|
||||
|
||||
var newFire = new FireSource(pos);
|
||||
newFire.Size = new Vector2(Size, Size);
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(FireAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
|
||||
$"Size: {Size.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GiveSkillExpAction : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string Skill { get; set; }
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float Amount { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
public GiveSkillExpAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (string.IsNullOrEmpty(TargetTag))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": GiveSkillExpAction 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?.IncreaseSkillLevel(Skill, Amount, target.WorldPosition + Vector2.UnitY * 150.0f);
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(GiveSkillExpAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
|
||||
$"Skill: {Skill.ColorizeObject()}, Amount: {Amount.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class GoTo : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string Name { get; set; }
|
||||
|
||||
public GoTo(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
goTo = Name;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"[-] Go to label \"{Name}\"";
|
||||
}
|
||||
|
||||
public override void Reset() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Label : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string Name { get; set; }
|
||||
|
||||
public Label(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool SetGoToTarget(string goTo)
|
||||
{
|
||||
return goTo.Equals(Name, System.StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"[-] Label \"{Name}\"";
|
||||
}
|
||||
|
||||
public override void Reset() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MissionAction : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string MissionIdentifier { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string MissionTag { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public MissionAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
//TODO: use event identifier in the error messages
|
||||
if (string.IsNullOrEmpty(MissionIdentifier) && string.IsNullOrEmpty(MissionTag))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{"event identifier goes here"}\": neither MissionIdentifier or MissionTag has been configured.");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(MissionIdentifier) && !string.IsNullOrEmpty(MissionTag))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{"event identifier goes here"}\": both MissionIdentifier or MissionTag have been configured. The tag will be ignored.");
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
if (GameMain.GameSession.GameMode is CampaignMode campaign)
|
||||
{
|
||||
MissionPrefab prefab = null;
|
||||
if (!string.IsNullOrEmpty(MissionIdentifier))
|
||||
{
|
||||
prefab = campaign.Map.CurrentLocation.UnlockMissionByIdentifier(MissionIdentifier);
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(MissionTag))
|
||||
{
|
||||
prefab = campaign.Map.CurrentLocation.UnlockMissionByTag(MissionTag);
|
||||
}
|
||||
if (campaign is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
mpCampaign.LastUpdateID++;
|
||||
}
|
||||
|
||||
if (prefab != null)
|
||||
{
|
||||
#if CLIENT
|
||||
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name),
|
||||
new string[0], type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
|
||||
{
|
||||
IconColor = prefab.IconColor
|
||||
};
|
||||
#else
|
||||
NotifyMissionUnlock(prefab);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MissionAction)} -> ({(string.IsNullOrEmpty(MissionIdentifier) ? MissionTag : MissionIdentifier)})";
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
private void NotifyMissionUnlock(MissionPrefab prefab)
|
||||
{
|
||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||
outmsg.Write((byte) ServerPacketHeader.EVENTACTION);
|
||||
outmsg.Write((byte) EventManager.NetworkEventType.MISSION);
|
||||
outmsg.Write(prefab.Identifier);
|
||||
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MoneyAction : EventAction
|
||||
{
|
||||
public MoneyAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
[Serialize(0, true)]
|
||||
public int Amount { 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) { return; }
|
||||
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
campaign.Money += Amount;
|
||||
#if SERVER
|
||||
(campaign as MultiPlayerCampaign).LastUpdateID++;
|
||||
#endif
|
||||
}
|
||||
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(SetDataAction)} -> (Amount: {Amount.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class NPCFollowAction : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string NPCTag { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool Follow { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public NPCFollowAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
|
||||
private List<Character> affectedNpcs = null;
|
||||
private Entity target = null;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
target = ParentEvent.GetTargets(TargetTag).FirstOrDefault();
|
||||
if (target == null) { return; }
|
||||
|
||||
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList();
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (!(npc.AIController is HumanAIController humanAiController)) { continue; }
|
||||
|
||||
if (Follow)
|
||||
{
|
||||
var newObjective = new AIObjectiveGoTo(target, npc, humanAiController.ObjectiveManager, repeat: true)
|
||||
{
|
||||
OverridePriority = 100.0f
|
||||
};
|
||||
humanAiController.ObjectiveManager.AddObjective(newObjective);
|
||||
humanAiController.ObjectiveManager.WaitTimer = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var goToObjective in humanAiController.ObjectiveManager.GetActiveObjectives<AIObjectiveGoTo>())
|
||||
{
|
||||
if (goToObjective.Target == target)
|
||||
{
|
||||
goToObjective.Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
if (affectedNpcs != null && target != null)
|
||||
{
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.Removed || !(npc.AIController is HumanAIController humanAiController)) { continue; }
|
||||
foreach (var goToObjective in humanAiController.ObjectiveManager.GetActiveObjectives<AIObjectiveGoTo>())
|
||||
{
|
||||
if (goToObjective.Target == target)
|
||||
{
|
||||
goToObjective.Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
target = null;
|
||||
affectedNpcs = null;
|
||||
}
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(NPCFollowAction)} -> (NPCTag: {NPCTag.ColorizeObject()}, TargetTag: {TargetTag.ColorizeObject()}, Follow: {Follow.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class NPCWaitAction : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string NPCTag { get; set; }
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool Wait { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
|
||||
public NPCWaitAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
private List<Character> affectedNpcs = null;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
affectedNpcs = ParentEvent.GetTargets(NPCTag).Where(c => c is Character).Select(c => c as Character).ToList();
|
||||
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (!(npc.AIController is HumanAIController humanAiController)) { continue; }
|
||||
|
||||
if (Wait)
|
||||
{
|
||||
var newObjective = new AIObjectiveGoTo(npc, npc, humanAiController.ObjectiveManager, repeat: true)
|
||||
{
|
||||
OverridePriority = 100.0f
|
||||
};
|
||||
humanAiController.ObjectiveManager.AddObjective(newObjective);
|
||||
humanAiController.ObjectiveManager.WaitTimer = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var goToObjective in humanAiController.ObjectiveManager.GetActiveObjectives<AIObjectiveGoTo>())
|
||||
{
|
||||
if (goToObjective.Target == npc)
|
||||
{
|
||||
goToObjective.Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
if (affectedNpcs != null)
|
||||
{
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.Removed || !(npc.AIController is HumanAIController humanAiController)) { continue; }
|
||||
foreach (var goToObjective in humanAiController.ObjectiveManager.GetActiveObjectives<AIObjectiveGoTo>())
|
||||
{
|
||||
if (goToObjective.Target == npc)
|
||||
{
|
||||
goToObjective.Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
affectedNpcs = null;
|
||||
}
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(NPCWaitAction)} -> (NPCTag: {NPCTag.ColorizeObject()}, Wait: {Wait.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class RNGAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize(0.0f, true)]
|
||||
public float Chance { get; set; }
|
||||
|
||||
public RNGAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
return Rand.Range(0.0, 1.0) <= Chance;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
string subActionStr = "";
|
||||
if (succeeded.HasValue)
|
||||
{
|
||||
subActionStr = $"\n Sub action: {(succeeded.Value ? Success : Failure)?.CurrentSubAction.ColorizeObject()}";
|
||||
}
|
||||
return $"{ToolBox.GetDebugSymbol(DetermineFinished())} {nameof(RNGAction)} -> (Chance: {Chance.ColorizeObject()}, "+
|
||||
$"Succeeded: {(succeeded.HasValue ? succeeded.Value.ToString() : "not determined").ColorizeObject()})" +
|
||||
subActionStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class RemoveItemAction : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string ItemIdentifier { get; set; }
|
||||
|
||||
[Serialize(1, true)]
|
||||
public int Amount { get; set; }
|
||||
|
||||
public RemoveItemAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
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(t => t is Character chr && chr.Inventory != null)
|
||||
.Select(t => t as Character).ToList();
|
||||
if (targets.Count <= 0) { return; }
|
||||
|
||||
int count = Amount;
|
||||
while (count > 0 && targets.Count > 0)
|
||||
{
|
||||
var items = targets[0].Inventory.Items;
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
if (items[i] != null && items[i].Prefab.Identifier.Equals(ItemIdentifier, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
Entity.Spawner.AddToRemoveQueue(items[i]);
|
||||
count--;
|
||||
if (count <= 0) { break; }
|
||||
}
|
||||
}
|
||||
targets.RemoveAt(0);
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ReputationAction : EventAction
|
||||
{
|
||||
public enum ReputationType
|
||||
{
|
||||
None,
|
||||
Location,
|
||||
Faction
|
||||
}
|
||||
|
||||
public ReputationAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float Increase { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string Identifier { get; set; }
|
||||
|
||||
[Serialize(ReputationType.None, true)]
|
||||
public ReputationType TargetType { 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) { return; }
|
||||
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
switch (TargetType)
|
||||
{
|
||||
case ReputationType.Faction:
|
||||
{
|
||||
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier.Equals(Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (faction != null)
|
||||
{
|
||||
faction.Reputation.Value += Increase;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Faction with the identifier \"{Identifier}\" was not found.");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case ReputationType.Location:
|
||||
{
|
||||
Location location = campaign.Map.CurrentLocation;
|
||||
if (location != null)
|
||||
{
|
||||
location.Reputation.Value += Increase;
|
||||
IEnumerable<Location> locations = location.Connections.SelectMany(c => c.Locations).Distinct().Where(l => l != null && l != location);
|
||||
foreach (Location connectedLocation in locations)
|
||||
{
|
||||
Debug.Assert(connectedLocation.Reputation != null, "connectedLocation.Reputation != null");
|
||||
if (connectedLocation.Reputation != null)
|
||||
{
|
||||
connectedLocation.Reputation.Value += (Increase / 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
DebugConsole.ThrowError("ReputationAction requires a \"TargetType\" but none were specified.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(ReputationAction)} -> (FactionIdentifier: {Identifier.ColorizeObject()}, TargetType: {TargetType.ColorizeObject()}, Increase: {Increase.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SetDataAction : EventAction
|
||||
{
|
||||
public enum OperationType
|
||||
{
|
||||
Set,
|
||||
Multiply,
|
||||
Add
|
||||
}
|
||||
|
||||
public SetDataAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
[Serialize(OperationType.Set, true)]
|
||||
public OperationType Operation { get; set; }
|
||||
|
||||
[Serialize(null, true)]
|
||||
public string Value { get; set; } = null!;
|
||||
|
||||
[Serialize("", true)]
|
||||
public string Identifier { 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) { return; }
|
||||
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||
{
|
||||
object currentValue = campaign.CampaignMetadata.GetValue(Identifier);
|
||||
object xmlValue = ConvertXMLValue();
|
||||
|
||||
float? originalValue = ConvertValueToFloat(currentValue ?? 0);
|
||||
float? newValue = ConvertValueToFloat(xmlValue);
|
||||
|
||||
if ((originalValue == null || newValue == null) && Operation != OperationType.Set)
|
||||
{
|
||||
DebugConsole.ThrowError($"Tried to perform numeric operations to a non number via SetDataAction (Existing: {currentValue?.GetType()}, New: {xmlValue.GetType()})");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Identifier != null)
|
||||
{
|
||||
switch (Operation)
|
||||
{
|
||||
case OperationType.Set:
|
||||
campaign.CampaignMetadata.SetValue(Identifier, xmlValue);
|
||||
break;
|
||||
case OperationType.Add:
|
||||
campaign.CampaignMetadata.SetValue(Identifier, originalValue + newValue ?? 0);
|
||||
break;
|
||||
case OperationType.Multiply:
|
||||
campaign.CampaignMetadata.SetValue(Identifier, originalValue * newValue ?? 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
private static float? ConvertValueToFloat(object value)
|
||||
{
|
||||
if (value is float || value is int)
|
||||
{
|
||||
return (float?) Convert.ChangeType(value, typeof(float));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private object ConvertXMLValue()
|
||||
{
|
||||
if (bool.TryParse(Value, out bool b))
|
||||
{
|
||||
return b;
|
||||
}
|
||||
|
||||
if (float.TryParse(Value, out float f))
|
||||
{
|
||||
return f;
|
||||
}
|
||||
|
||||
return Value;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(SetDataAction)} -> (Identifier: {Identifier.ColorizeObject()}, Value: {ConvertXMLValue().ColorizeObject()}, Operation: {Operation.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SetPriceMultiplierAction : EventAction
|
||||
{
|
||||
public enum OperationType
|
||||
{
|
||||
Set,
|
||||
Multiply,
|
||||
Min,
|
||||
Max
|
||||
}
|
||||
|
||||
public enum PriceMultiplierType
|
||||
{
|
||||
Store,
|
||||
Mechanical
|
||||
}
|
||||
|
||||
[Serialize(1.0f, true)]
|
||||
public float Multiplier { get; set; }
|
||||
|
||||
[Serialize(OperationType.Set, true)]
|
||||
public OperationType Operation { get; set; }
|
||||
|
||||
[Serialize(PriceMultiplierType.Store, true)]
|
||||
public PriceMultiplierType TargetMultiplier { get; set; }
|
||||
|
||||
public SetPriceMultiplierAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
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; }
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.Map?.CurrentLocation != null)
|
||||
{
|
||||
float newMultiplier = GetCurrentMultiplier(campaign.Map.CurrentLocation);
|
||||
|
||||
switch (Operation)
|
||||
{
|
||||
case OperationType.Set:
|
||||
newMultiplier = Multiplier;
|
||||
break;
|
||||
case OperationType.Multiply:
|
||||
newMultiplier *= Multiplier;
|
||||
break;
|
||||
case OperationType.Min:
|
||||
newMultiplier = Math.Min(Multiplier, campaign.Map.CurrentLocation.PriceMultiplier);
|
||||
break;
|
||||
case OperationType.Max:
|
||||
newMultiplier = Math.Max(Multiplier, campaign.Map.CurrentLocation.PriceMultiplier);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
SetCurrentMultiplier(campaign.Map.CurrentLocation, newMultiplier);
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
private float GetCurrentMultiplier(Location location)
|
||||
{
|
||||
return TargetMultiplier switch
|
||||
{
|
||||
PriceMultiplierType.Store => location.PriceMultiplier,
|
||||
PriceMultiplierType.Mechanical => location.MechanicalPriceMultiplier,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
|
||||
private void SetCurrentMultiplier(Location location, float value)
|
||||
{
|
||||
switch (TargetMultiplier)
|
||||
{
|
||||
case PriceMultiplierType.Store:
|
||||
location.PriceMultiplier = value;
|
||||
break;
|
||||
case PriceMultiplierType.Mechanical:
|
||||
location.MechanicalPriceMultiplier = value;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(SetPriceMultiplierAction)} -> (Multiplier: {Multiplier.ColorizeObject()}, " +
|
||||
$"Operation: {Operation.ColorizeObject()}, Target: {TargetMultiplier})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SkillCheckAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string RequiredSkill { get; set; }
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float RequiredLevel { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
public SkillCheckAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (string.IsNullOrEmpty(TargetTag))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": SkillCheckAction without a target tag (the action needs to know whose skill to check).");
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
var potentialTargets = ParentEvent.GetTargets(TargetTag).Where(e => e is Character).Select(e => e as Character);
|
||||
return potentialTargets.Any(chr => chr.GetSkillLevel(RequiredSkill?.ToLowerInvariant()) >= RequiredLevel);
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
string subActionStr = "";
|
||||
if (succeeded.HasValue)
|
||||
{
|
||||
subActionStr = $"\n Sub action: {(succeeded.Value ? Success : Failure)?.CurrentSubAction.ColorizeObject()}";
|
||||
}
|
||||
return $"{ToolBox.GetDebugSymbol(DetermineFinished())} {nameof(SkillCheckAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
|
||||
$"Required skill: {RequiredSkill.ColorizeObject()}, Required level: {RequiredLevel.ColorizeObject()}, " +
|
||||
$"Succeeded: {(succeeded.HasValue ? succeeded.Value.ToString() : "not determined").ColorizeObject()})" +
|
||||
subActionStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SpawnAction : EventAction
|
||||
{
|
||||
public enum SpawnLocationType
|
||||
{
|
||||
MainSub,
|
||||
Outpost,
|
||||
MainPath,
|
||||
Ruin,
|
||||
Wreck
|
||||
}
|
||||
|
||||
[Serialize("", true, description: "Species name of the character to spawn.")]
|
||||
public string SpeciesName { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Identifier of the NPC set to choose from.")]
|
||||
public string NPCSetIdentifier { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Identifier of the NPC.")]
|
||||
public string NPCIdentifier { get; set; }
|
||||
|
||||
[Serialize(true, true, description: "Should taking the items of this npc be considered as stealing?")]
|
||||
public bool LootingIsStealing { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Identifier of the item to spawn.")]
|
||||
public string ItemIdentifier { get; set; }
|
||||
|
||||
[Serialize("", true, description: "The spawned entity will be assigned this tag. The tag can be used to refer to the entity by other actions of the event.")]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Tag of an entity with an inventory to spawn the item into.")]
|
||||
public string TargetInventory { get; set; }
|
||||
|
||||
[Serialize(SpawnLocationType.MainSub, true)]
|
||||
public SpawnLocationType SpawnLocation { get; set; }
|
||||
|
||||
[Serialize(SpawnType.Human, true)]
|
||||
public SpawnType SpawnPointType { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string SpawnPointTag { get; set; }
|
||||
|
||||
private readonly HashSet<string> targetModuleTags = new HashSet<string>();
|
||||
|
||||
[Serialize("", true, "What outpost module tags does the entity prefer to spawn in.")]
|
||||
public string TargetModuleTags
|
||||
{
|
||||
get => string.Join(",", targetModuleTags);
|
||||
set
|
||||
{
|
||||
targetModuleTags.Clear();
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
string[] splitTags = value.Split(',');
|
||||
foreach (var s in splitTags)
|
||||
{
|
||||
targetModuleTags.Add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool spawned;
|
||||
private Entity spawnedEntity;
|
||||
|
||||
private readonly bool ignoreSpawnPointType;
|
||||
|
||||
public SpawnAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
ignoreSpawnPointType = !element.Attributes().Any(a => a.Name.ToString().Equals("spawnpointtype", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
if (spawnedEntity != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
spawned = false;
|
||||
spawnedEntity = null;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (spawned) { return; }
|
||||
|
||||
if (!string.IsNullOrEmpty(NPCSetIdentifier) && !string.IsNullOrEmpty(NPCIdentifier))
|
||||
{
|
||||
HumanPrefab humanPrefab = NPCSet.Get(NPCSetIdentifier, NPCIdentifier);
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
Entity.Spawner.AddToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawn: newCharacter =>
|
||||
{
|
||||
newCharacter.TeamID = Character.TeamType.FriendlyNPC;
|
||||
newCharacter.EnableDespawn = false;
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine);
|
||||
if (LootingIsStealing)
|
||||
{
|
||||
foreach (Item item in newCharacter.Inventory.Items)
|
||||
{
|
||||
if (item != null) { item.SpawnedInOutpost = true; }
|
||||
}
|
||||
}
|
||||
newCharacter.CharacterHealth.MaxVitality *= humanPrefab.HealthMultiplier;
|
||||
var humanAI = newCharacter.AIController as HumanAIController;
|
||||
if (humanAI != null)
|
||||
{
|
||||
var idleObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveIdle>();
|
||||
if (idleObjective != null)
|
||||
{
|
||||
idleObjective.Behavior = humanPrefab.BehaviorType;
|
||||
foreach (string moduleType in humanPrefab.PreferredOutpostModuleTypes)
|
||||
{
|
||||
idleObjective.PreferredOutpostModuleTypes.Add(moduleType);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (humanPrefab.CampaignInteractionType != CampaignMode.InteractionType.None)
|
||||
{
|
||||
(GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(newCharacter, humanPrefab.CampaignInteractionType);
|
||||
if (spawnPos != null && humanAI != null)
|
||||
{
|
||||
humanAI.ObjectiveManager.SetOrder(new AIObjectiveGoTo(spawnPos, newCharacter, humanAI.ObjectiveManager, repeat: true, getDivingGearIfNeeded: false, closeEnough: 200));
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(TargetTag) && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
});
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(SpeciesName))
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(SpeciesName, OffsetSpawnPos(GetSpawnPos()?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawn: newCharacter =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(TargetTag) && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
});
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(ItemIdentifier))
|
||||
{
|
||||
if (!(MapEntityPrefab.Find(null, identifier: ItemIdentifier) is ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Inventory spawnInventory = null;
|
||||
if (!string.IsNullOrEmpty(TargetInventory))
|
||||
{
|
||||
var targets = ParentEvent.GetTargets(TargetInventory);
|
||||
if (targets.Any())
|
||||
{
|
||||
var target = targets.First(t => t is Item || t is Character);
|
||||
if (target is Character character)
|
||||
{
|
||||
spawnInventory = character.Inventory;
|
||||
}
|
||||
else if (target is Item item)
|
||||
{
|
||||
spawnInventory = item.OwnInventory;
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnInventory == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not spawn \"{ItemIdentifier}\" in target inventory \"{TargetInventory}\"");
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnInventory == null)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, OffsetSpawnPos(GetSpawnPos()?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawned: onSpawned);
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory, onSpawned: onSpawned);
|
||||
}
|
||||
void onSpawned(Item newItem)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(TargetTag) && newItem != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newItem);
|
||||
}
|
||||
spawnedEntity = newItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spawned = true;
|
||||
|
||||
}
|
||||
|
||||
public static Vector2 OffsetSpawnPos(Vector2 pos, float offsetAmount)
|
||||
{
|
||||
Hull hull = Hull.FindHull(pos);
|
||||
pos += Rand.Vector(offsetAmount);
|
||||
if (hull != null)
|
||||
{
|
||||
float margin = 50.0f;
|
||||
pos = new Vector2(
|
||||
MathHelper.Clamp(pos.X, hull.WorldRect.X + margin, hull.WorldRect.Right - margin),
|
||||
MathHelper.Clamp(pos.Y, hull.WorldRect.Y - hull.WorldRect.Height + margin, hull.WorldRect.Y - margin));
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
private ISpatialEntity GetSpawnPos()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SpawnPointTag))
|
||||
{
|
||||
List<Item> potentialItems = SpawnLocation switch
|
||||
{
|
||||
SpawnLocationType.MainSub => Item.ItemList.FindAll(it => it.Submarine == Submarine.MainSub),
|
||||
SpawnLocationType.MainPath => Item.ItemList.FindAll(it => it.Submarine == null && it.ParentRuin == null),
|
||||
SpawnLocationType.Outpost => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsOutpost),
|
||||
SpawnLocationType.Wreck => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsWreck),
|
||||
SpawnLocationType.Ruin => Item.ItemList.FindAll(it => it.ParentRuin != null),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
var item = potentialItems.Where(it => it.HasTag(SpawnPointTag)).GetRandom();
|
||||
if (item != null) { return item; }
|
||||
|
||||
var target = ParentEvent.GetTargets(SpawnPointTag).GetRandom();
|
||||
if (target != null) { return target; }
|
||||
}
|
||||
|
||||
SpawnType? spawnPointType = null;
|
||||
if (!ignoreSpawnPointType) { spawnPointType = SpawnPointType; }
|
||||
|
||||
return GetSpawnPos(SpawnLocation, spawnPointType, targetModuleTags, SpawnPointTag.ToEnumerable());
|
||||
}
|
||||
|
||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<string> moduleFlags = null, IEnumerable<string> spawnpointTags = null)
|
||||
{
|
||||
List<WayPoint> potentialSpawnPoints = spawnLocation switch
|
||||
{
|
||||
SpawnLocationType.MainSub => WayPoint.WayPointList.FindAll(wp => wp.Submarine == Submarine.MainSub && wp.CurrentHull != null),
|
||||
SpawnLocationType.MainPath => WayPoint.WayPointList.FindAll(wp => wp.Submarine == null && wp.ParentRuin == null),
|
||||
SpawnLocationType.Outpost => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.CurrentHull != null && wp.Submarine.Info.IsOutpost),
|
||||
SpawnLocationType.Wreck => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.Submarine.Info.IsWreck),
|
||||
SpawnLocationType.Ruin => WayPoint.WayPointList.FindAll(wp => wp.ParentRuin != null),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor == null && wp.Ladders == null && !wp.isObstructed);
|
||||
|
||||
if (moduleFlags != null && moduleFlags.Any())
|
||||
{
|
||||
List<WayPoint> spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Any(moduleFlags.Contains) ?? false).ToList();
|
||||
if (spawnPoints.Any())
|
||||
{
|
||||
potentialSpawnPoints = spawnPoints;
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnpointTags != null && spawnpointTags.Any())
|
||||
{
|
||||
var spawnPoints = potentialSpawnPoints.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag)))
|
||||
.Where(wp => wp.ConnectedDoor == null && !wp.isObstructed);
|
||||
if (spawnPoints.Any())
|
||||
{
|
||||
potentialSpawnPoints = spawnPoints.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
if (potentialSpawnPoints.Count == 0)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a spawn point for a SpawnAction (spawn location: {spawnLocation})");
|
||||
return null;
|
||||
}
|
||||
|
||||
IEnumerable<WayPoint> validSpawnPoints;
|
||||
if (spawnPointType.HasValue)
|
||||
{
|
||||
validSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.SpawnType == spawnPointType.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
validSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.SpawnType != SpawnType.Path);
|
||||
if (!validSpawnPoints.Any()) { validSpawnPoints = potentialSpawnPoints; }
|
||||
}
|
||||
|
||||
//don't spawn in an airlock module if there are other options
|
||||
var airlockSpawnPoints = validSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false);
|
||||
if (airlockSpawnPoints.Count() < validSpawnPoints.Count())
|
||||
{
|
||||
validSpawnPoints = validSpawnPoints.Except(airlockSpawnPoints);
|
||||
}
|
||||
|
||||
if (!validSpawnPoints.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a spawn point of the correct type for a SpawnAction (spawn location: {spawnLocation}, type: {spawnPointType}, module flags: {((moduleFlags == null || !moduleFlags.Any()) ? "none" : string.Join(", ", moduleFlags))})");
|
||||
return potentialSpawnPoints.GetRandom();
|
||||
}
|
||||
|
||||
//if not trying to spawn at a tagged spawnpoint, favor spawnpoints without tags
|
||||
if (spawnpointTags == null || !spawnpointTags.Any())
|
||||
{
|
||||
var spawnPoints = validSpawnPoints.Where(wp => !wp.Tags.Any());
|
||||
if (spawnPoints.Any())
|
||||
{
|
||||
validSpawnPoints = spawnPoints.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
return validSpawnPoints.GetRandom();
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(spawned)} {nameof(SpawnAction)} -> (Spawned entity: {spawnedEntity.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class StatusEffectAction : EventAction
|
||||
{
|
||||
private readonly List<StatusEffect> effects = new List<StatusEffect>();
|
||||
|
||||
private int actionIndex;
|
||||
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
public StatusEffectAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
actionIndex = 0;
|
||||
foreach (XElement subElement in parentEvent.Prefab.ConfigElement.Descendants())
|
||||
{
|
||||
if (subElement == element) { break; }
|
||||
actionIndex++;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "statuseffect":
|
||||
effects.Add(StatusEffect.Load(subElement, $"{nameof(StatusEffectAction)} ({parentEvent.Prefab.Identifier})"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
foreach (StatusEffect effect in effects)
|
||||
{
|
||||
foreach (var target in targets)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, target, target as ISerializableEntity);
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
ServerWrite(targets);
|
||||
#endif
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(StatusEffectAction)} -> (TargetTag: {TargetTag.ColorizeObject()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TagAction : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string Criteria { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string Tag { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public TagAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
private void TagPlayers()
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsPlayer);
|
||||
}
|
||||
|
||||
private void TagBots()
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsBot);
|
||||
}
|
||||
|
||||
private void TagCrew()
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.GetCharacters().ForEach(c => ParentEvent.AddTarget(Tag, c));
|
||||
#else
|
||||
TagPlayers(); TagBots(); //TODO: this seems like it would tag more than it should, fix
|
||||
#endif
|
||||
}
|
||||
|
||||
private void TagStructuresByIdentifier(string identifier)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && s.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
private void TagItemsByIdentifier(string identifier)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && it.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
private void TagItemsByTag(string tag)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && it.HasTag(tag));
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
string[] criteriaSplit = Criteria.Split(';');
|
||||
|
||||
foreach (string entry in criteriaSplit)
|
||||
{
|
||||
string[] kvp = entry.Split(':');
|
||||
switch (kvp[0].Trim().ToLowerInvariant())
|
||||
{
|
||||
case "player":
|
||||
TagPlayers();
|
||||
break;
|
||||
case "bot":
|
||||
TagBots();
|
||||
break;
|
||||
case "crew":
|
||||
TagCrew();
|
||||
break;
|
||||
case "structureidentifier":
|
||||
if (kvp.Length > 1) { TagStructuresByIdentifier(kvp[1].Trim()); }
|
||||
break;
|
||||
case "itemidentifier":
|
||||
if (kvp.Length > 1) { TagItemsByIdentifier(kvp[1].Trim()); }
|
||||
break;
|
||||
case "itemtag":
|
||||
if (kvp.Length > 1) { TagItemsByTag(kvp[1].Trim()); }
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TagAction)} -> (Criteria: {Criteria.ColorizeObject()}, Tag: {Tag.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TriggerAction : EventAction
|
||||
{
|
||||
[Serialize("", true, description: "Tag of the first entity that will be used for trigger checks.")]
|
||||
public string Target1Tag { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Tag of the second entity that will be used for trigger checks.")]
|
||||
public string Target2Tag { get; set; }
|
||||
|
||||
[Serialize("", true, description: "If set, the first target has to be within an outpost module of this type.")]
|
||||
public string TargetModuleType { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Tag to apply to the first entity when the trigger check succeeds.")]
|
||||
public string ApplyToTarget1 { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Tag to apply to the second entity when the trigger check succeeds.")]
|
||||
public string ApplyToTarget2 { get; set; }
|
||||
|
||||
[Serialize(0.0f, true, description: "Range both entities must be within to activate the trigger.")]
|
||||
public float Radius { get; set; }
|
||||
|
||||
[Serialize(true, true, description: "If true, characters who are being targeted by some enemy cannot trigger the event.")]
|
||||
public bool DisableInCombat { get; set; }
|
||||
|
||||
private float distance;
|
||||
|
||||
public TriggerAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
TargetModuleType = TargetModuleType?.ToLowerInvariant();
|
||||
}
|
||||
|
||||
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 targets1 = ParentEvent.GetTargets(Target1Tag);
|
||||
if (!targets1.Any()) { return; }
|
||||
|
||||
foreach (Entity e1 in targets1)
|
||||
{
|
||||
if (DisableInCombat && IsInCombat(e1)) { continue; }
|
||||
if (!string.IsNullOrEmpty(TargetModuleType))
|
||||
{
|
||||
if (IsCloseEnoughToHull(e1, out Hull hull))
|
||||
{
|
||||
Trigger(e1, hull);
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var targets2 = ParentEvent.GetTargets(Target2Tag);
|
||||
|
||||
foreach (Entity e2 in targets2)
|
||||
{
|
||||
if (e1 == e2) { continue; }
|
||||
if (DisableInCombat && IsInCombat(e2)) { continue; }
|
||||
|
||||
Vector2 pos1 = e1.WorldPosition;
|
||||
Vector2 pos2 = e2.WorldPosition;
|
||||
distance = Vector2.Distance(pos1, pos2);
|
||||
if (((e1 is MapEntity m1) && Submarine.RectContains(m1.WorldRect, pos2)) ||
|
||||
((e2 is MapEntity m2) && Submarine.RectContains(m2.WorldRect, pos1)) ||
|
||||
Vector2.DistanceSquared(pos1, pos2) < Radius * Radius)
|
||||
{
|
||||
Trigger(e1, e2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsCloseEnoughToHull(Entity e, out Hull hull)
|
||||
{
|
||||
hull = null;
|
||||
if (Radius <= 0)
|
||||
{
|
||||
if (e is Character character && character.CurrentHull != null && character.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
|
||||
{
|
||||
hull = character.CurrentHull;
|
||||
return true;
|
||||
}
|
||||
else if (e is Item item && item.CurrentHull != null && item.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
|
||||
{
|
||||
hull = item.CurrentHull;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Hull potentialHull in Hull.hullList)
|
||||
{
|
||||
if (!potentialHull.OutpostModuleTags.Contains(TargetModuleType)) { continue; }
|
||||
|
||||
Rectangle hullRect = potentialHull.WorldRect;
|
||||
hullRect.Inflate(Radius, Radius);
|
||||
if (Submarine.RectContains(hullRect, e.WorldPosition))
|
||||
{
|
||||
hull = potentialHull;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsInCombat(Entity entity)
|
||||
{
|
||||
if (!(entity is Character character)) { return false; }
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.IsDead || c.Removed || c.IsIncapacitated || !c.Enabled) { continue; }
|
||||
if (c.IsBot && c.AIController is HumanAIController humanAi)
|
||||
{
|
||||
if (humanAi.ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective &&
|
||||
combatObjective.Enemy == character)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (c.AIController is EnemyAIController enemyAI && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack))
|
||||
{
|
||||
if (enemyAI.SelectedAiTarget?.Entity == character || c.CurrentHull == character.CurrentHull)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void Trigger(Entity entity1, Entity entity2)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ApplyToTarget1))
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyToTarget1, entity1);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(ApplyToTarget2))
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyToTarget2, entity2);
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
if (string.IsNullOrEmpty(TargetModuleType))
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TriggerAction)} -> (Distance: {((int)distance).ColorizeObject()}, Radius: {Radius.ColorizeObject()}, TargetTags: {Target1Tag.ColorizeObject()}, {Target2Tag.ColorizeObject()})";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TriggerAction)} -> (TargetTags: {Target1Tag.ColorizeObject()}, {TargetModuleType.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TriggerEventAction : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string Identifier { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TriggerEventAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
if (GameMain.GameSession?.EventManager != null)
|
||||
{
|
||||
var eventPrefab = EventSet.GetEventPrefab(Identifier);
|
||||
if (eventPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in TriggerEventAction - could not find an event with the identifier {Identifier}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(eventPrefab.CreateInstance());
|
||||
}
|
||||
}
|
||||
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TriggerEventAction)} -> (EventPrefab: {Identifier.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class WaitAction : EventAction
|
||||
{
|
||||
[Serialize(0.0f, true)]
|
||||
public float Time { get; set; }
|
||||
|
||||
private float timeRemaining;
|
||||
|
||||
public WaitAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
timeRemaining = Time;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return timeRemaining <= 0;
|
||||
}
|
||||
public override void Reset()
|
||||
{
|
||||
timeRemaining = Time;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
timeRemaining -= deltaTime;
|
||||
if (timeRemaining < 0.0f) { timeRemaining = 0.0f; }
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(timeRemaining <= 0)} {nameof(WaitAction)} -> (Remaining: {timeRemaining.ColorizeObject()}, Time: {Time.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user