(965c31410a) Unstable v0.10.4.0

This commit is contained in:
Juan Pablo Arce
2020-07-21 08:57:50 -03:00
parent 4f8bd39789
commit 33d3a41104
546 changed files with 45952 additions and 25762 deletions
@@ -2,7 +2,7 @@
namespace Barotrauma
{
class ArtifactEvent : ScriptedEvent
class ArtifactEvent : Event
{
private ItemPrefab itemPrefab;
@@ -14,6 +14,11 @@ namespace Barotrauma
private bool spawnPending;
public bool SpawnPending => spawnPending;
public int State => state;
public Item Item => item;
public Vector2 SpawnPos => spawnPos;
public override Vector2 DebugDrawPos
{
get { return spawnPos; }
@@ -24,7 +29,7 @@ namespace Barotrauma
return "ArtifactEvent (" + (itemPrefab == null ? "null" : itemPrefab.Name) + ")";
}
public ArtifactEvent(ScriptedEventPrefab prefab)
public ArtifactEvent(EventPrefab prefab)
: base(prefab)
{
if (prefab.ConfigElement.Attribute("itemname") != null)
@@ -0,0 +1,60 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
namespace Barotrauma
{
class Event
{
protected bool isFinished;
protected readonly EventPrefab prefab;
public EventPrefab Prefab => prefab;
public bool IsFinished
{
get { return isFinished; }
}
public override string ToString()
{
return "Event (" + prefab.EventType.ToString() +")";
}
public virtual Vector2 DebugDrawPos
{
get
{
return Vector2.Zero;
}
}
public Event(EventPrefab prefab)
{
this.prefab = prefab;
}
public virtual IEnumerable<ContentFile> GetFilesToPreload()
{
yield break;
}
public virtual void Init(bool affectSubImmediately)
{
}
public virtual void Update(float deltaTime)
{
}
public virtual void Finished()
{
isFinished = true;
}
public virtual bool CanAffectSubImmediately(Level level)
{
return true;
}
}
}
@@ -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()})";
}
}
}
@@ -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()})";
}
}
}
@@ -8,6 +8,13 @@ namespace Barotrauma
{
partial class EventManager
{
public enum NetworkEventType
{
CONVERSATION,
STATUSEFFECT,
MISSION
}
const float IntensityUpdateInterval = 5.0f;
const float CalculateDistanceTraveledInterval = 5.0f;
@@ -42,11 +49,11 @@ namespace Barotrauma
private float roundDuration;
private readonly List<ScriptedEventSet> pendingEventSets = new List<ScriptedEventSet>();
private readonly List<EventSet> pendingEventSets = new List<EventSet>();
private readonly Dictionary<ScriptedEventSet, List<ScriptedEvent>> selectedEvents = new Dictionary<ScriptedEventSet, List<ScriptedEvent>>();
private readonly Dictionary<EventSet, List<Event>> selectedEvents = new Dictionary<EventSet, List<Event>>();
private readonly List<ScriptedEvent> activeEvents = new List<ScriptedEvent>();
private readonly List<Event> activeEvents = new List<Event>();
#if DEBUG && SERVER
private DateTime nextIntensityLogTime;
@@ -61,11 +68,13 @@ namespace Barotrauma
get { return currentIntensity; }
}
public List<ScriptedEvent> ActiveEvents
public List<Event> ActiveEvents
{
get { return activeEvents; }
}
public readonly Queue<Event> QueuedEvents = new Queue<Event>();
public EventManager()
{
isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
@@ -79,20 +88,34 @@ namespace Barotrauma
pendingEventSets.Clear();
selectedEvents.Clear();
activeEvents.Clear();
pathFinder = new PathFinder(WayPoint.WayPointList, indoorsSteering: false);
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(Level.Loaded.StartPosition), ConvertUnits.ToSimUnits(Level.Loaded.EndPosition));
totalPathLength = steeringPath.TotalLength;
totalPathLength = 0.0f;
if (level != null)
{
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(Level.Loaded.StartPosition), ConvertUnits.ToSimUnits(Level.Loaded.EndPosition));
totalPathLength = steeringPath.TotalLength;
}
this.level = level;
SelectSettings();
var initialEventSet = SelectRandomEvents(ScriptedEventSet.List);
var initialEventSet = SelectRandomEvents(EventSet.List);
if (initialEventSet != null)
{
pendingEventSets.Add(initialEventSet);
CreateEvents(initialEventSet);
}
if (level?.LevelData?.Type == LevelData.LevelType.Outpost)
{
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab));
if (level.LevelData.EventHistory.Count > 10)
{
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - 10);
}
}
PreloadContent(GetFilesToPreload());
@@ -102,7 +125,7 @@ namespace Barotrauma
currentIntensity = targetIntensity;
eventCoolDown = 0.0f;
}
private void SelectSettings()
{
if (EventManagerSettings.List.Count == 0)
@@ -111,6 +134,17 @@ namespace Barotrauma
}
if (level == null)
{
#if CLIENT
if (GameMain.GameSession.GameMode is TestGameMode)
{
settings = EventManagerSettings.List[Rand.Int(EventManagerSettings.List.Count, Rand.RandSync.Server)];
if (settings != null)
{
eventThreshold = settings.DefaultEventThreshold;
}
return;
}
#endif
throw new InvalidOperationException("Could not select EventManager settings (level not set).");
}
@@ -135,11 +169,11 @@ namespace Barotrauma
public IEnumerable<ContentFile> GetFilesToPreload()
{
foreach (List<ScriptedEvent> eventList in selectedEvents.Values)
foreach (List<Event> eventList in selectedEvents.Values)
{
foreach (ScriptedEvent scriptedEvent in eventList)
foreach (Event ev in eventList)
{
foreach (ContentFile contentFile in scriptedEvent.GetFilesToPreload())
foreach (ContentFile contentFile in ev.GetFilesToPreload())
{
yield return contentFile;
}
@@ -265,8 +299,16 @@ namespace Barotrauma
preloadedSprites.Clear();
}
private void CreateEvents(ScriptedEventSet eventSet)
private float CalculateCommonness(Pair<EventPrefab, float> eventPrefab)
{
float retVal = eventPrefab.Second;
if (level.LevelData.EventHistory.Contains(eventPrefab.First)) { retVal *= 0.1f; }
return retVal;
}
private void CreateEvents(EventSet eventSet)
{
if (level == null) { return; }
int applyCount = 1;
if (eventSet.PerRuin)
{
@@ -283,17 +325,22 @@ namespace Barotrauma
if (eventSet.EventPrefabs.Count > 0)
{
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
var eventPrefab = ToolBox.SelectWeightedRandom(eventSet.EventPrefabs, eventSet.EventPrefabs.Select(e => e.Commonness).ToList(), rand);
if (eventPrefab != null)
List<Pair<EventPrefab, float>> unusedEvents = new List<Pair<EventPrefab, float>>(eventSet.EventPrefabs);
for (int j = 0; j < eventSet.EventCount; j++)
{
var newEvent = eventPrefab.CreateInstance();
newEvent.Init(true);
DebugConsole.Log("Initialized event " + newEvent.ToString());
if (!selectedEvents.ContainsKey(eventSet))
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => CalculateCommonness(e)).ToList(), rand);
if (eventPrefab != null)
{
selectedEvents.Add(eventSet, new List<ScriptedEvent>());
var newEvent = eventPrefab.First.CreateInstance();
newEvent.Init(true);
DebugConsole.Log("Initialized event " + newEvent.ToString());
if (!selectedEvents.ContainsKey(eventSet))
{
selectedEvents.Add(eventSet, new List<Event>());
}
selectedEvents[eventSet].Add(newEvent);
unusedEvents.Remove(eventPrefab);
}
selectedEvents[eventSet].Add(newEvent);
}
}
if (eventSet.ChildSets.Count > 0)
@@ -304,19 +351,19 @@ namespace Barotrauma
}
else
{
foreach (ScriptedEventPrefab eventPrefab in eventSet.EventPrefabs)
foreach (Pair<EventPrefab, float> eventPrefab in eventSet.EventPrefabs)
{
var newEvent = eventPrefab.CreateInstance();
var newEvent = eventPrefab.First.CreateInstance();
newEvent.Init(true);
DebugConsole.Log("Initialized event " + newEvent.ToString());
if (!selectedEvents.ContainsKey(eventSet))
{
selectedEvents.Add(eventSet, new List<ScriptedEvent>());
selectedEvents.Add(eventSet, new List<Event>());
}
selectedEvents[eventSet].Add(newEvent);
}
foreach (ScriptedEventSet childEventSet in eventSet.ChildSets)
foreach (EventSet childEventSet in eventSet.ChildSets)
{
CreateEvents(childEventSet);
}
@@ -324,16 +371,22 @@ namespace Barotrauma
}
}
private ScriptedEventSet SelectRandomEvents(List<ScriptedEventSet> eventSets)
private EventSet SelectRandomEvents(List<EventSet> eventSets)
{
if (level == null) { return null; }
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
var allowedEventSets =
eventSets.Where(es => level.Difficulty >= es.MinLevelDifficulty && level.Difficulty <= es.MaxLevelDifficulty);
eventSets.Where(es => level.Difficulty >= es.MinLevelDifficulty && level.Difficulty <= es.MaxLevelDifficulty && level.LevelData.Type == es.LevelType);
if (GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.Map?.CurrentLocation?.Type != null)
{
allowedEventSets = allowedEventSets.Where(set => set.LocationTypeIdentifiers == null || set.LocationTypeIdentifiers.Any(identifier => string.Equals(identifier, campaign.Map.CurrentLocation.Type.Identifier, StringComparison.OrdinalIgnoreCase)));
}
float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level));
float randomNumber = (float)rand.NextDouble() * totalCommonness;
foreach (ScriptedEventSet eventSet in allowedEventSets)
foreach (EventSet eventSet in allowedEventSets)
{
float commonness = eventSet.GetCommonness(level);
if (randomNumber <= commonness)
@@ -346,7 +399,7 @@ namespace Barotrauma
return null;
}
private bool CanStartEventSet(ScriptedEventSet eventSet)
private bool CanStartEventSet(EventSet eventSet)
{
ISpatialEntity refEntity = GetRefEntity();
float distFromStart = Vector2.Distance(refEntity.WorldPosition, level.StartPosition);
@@ -380,7 +433,8 @@ namespace Barotrauma
public void Update(float deltaTime)
{
if (!Enabled) { return; }
if (!Enabled || level == null) { return; }
if (GameMain.GameSession.Campaign?.DisableEvents ?? false) { return; }
//clients only calculate the intensity but don't create any events
//(the intensity is used for controlling the background music)
@@ -421,46 +475,49 @@ namespace Barotrauma
}
eventThreshold += settings.EventThresholdIncrease * deltaTime;
if (eventCoolDown > 0.0f)
{
eventCoolDown -= deltaTime;
}
else if (currentIntensity < eventThreshold)
eventCoolDown -= deltaTime;
if (currentIntensity < eventThreshold)
{
//activate pending event sets that can be activated
for (int i = pendingEventSets.Count - 1; i >= 0; i--)
{
var eventSet = pendingEventSets[i];
if (eventCoolDown > 0.0f && !eventSet.IgnoreCoolDown) { continue; }
if (!CanStartEventSet(eventSet)) { continue; }
eventThreshold = settings.DefaultEventThreshold;
eventCoolDown = settings.EventCooldown;
pendingEventSets.RemoveAt(i);
if (selectedEvents.ContainsKey(eventSet))
{
//start events in this set
foreach (ScriptedEvent scriptedEvent in selectedEvents[eventSet])
foreach (Event ev in selectedEvents[eventSet])
{
activeEvents.Add(scriptedEvent);
activeEvents.Add(ev);
}
}
//add child event sets to pending
foreach (ScriptedEventSet childEventSet in eventSet.ChildSets)
foreach (EventSet childEventSet in eventSet.ChildSets)
{
if (selectedEvents.ContainsKey(childEventSet))
{
pendingEventSets.Add(childEventSet);
}
pendingEventSets.Add(childEventSet);
}
}
eventThreshold = settings.DefaultEventThreshold;
eventCoolDown = settings.EventCooldown;
}
foreach (ScriptedEvent ev in activeEvents)
foreach (Event ev in activeEvents)
{
if (!ev.IsFinished) { ev.Update(deltaTime); }
}
if (QueuedEvents.Count > 0)
{
activeEvents.Add(QueuedEvents.Dequeue());
}
}
private void CalculateCurrentIntensity(float deltaTime)
@@ -519,22 +576,23 @@ namespace Barotrauma
// hull status (gaps, flooding, fire) --------------------------------------------------------
float holeCount = 0.0f;
floodingAmount = 0.0f;
int hullCount = 0;
float waterAmount = 0.0f;
float totalHullVolume = 0.0f;
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine == null || hull.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
hullCount++;
if (hull.Submarine == null || hull.Submarine.Info.Type != SubmarineType.Player) { continue; }
if (hull.RoomName != null && hull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
foreach (Gap gap in hull.ConnectedGaps)
{
if (!gap.IsRoomToRoom) holeCount += gap.Open;
}
floodingAmount += hull.WaterVolume / hull.Volume;
waterAmount += hull.WaterVolume;
totalHullVolume += hull.Volume;
fireAmount += hull.FireSources.Sum(fs => fs.Size.X);
}
if (hullCount > 0)
if (totalHullVolume > 0)
{
floodingAmount = floodingAmount / hullCount;
floodingAmount = waterAmount / totalHullVolume;
}
//hull integrity at 0.0 if there are 10 or more wide-open holes
@@ -546,7 +604,14 @@ namespace Barotrauma
//flooding less than 10% of the sub is ignored
//to prevent ballast tanks from affecting the intensity
if (floodingAmount < 0.1f) floodingAmount = 0.0f;
if (floodingAmount < 0.1f)
{
floodingAmount = 0.0f;
}
else
{
floodingAmount *= 1.5f;
}
// calculate final intensity --------------------------------------------------------
@@ -558,8 +623,8 @@ namespace Barotrauma
if (targetIntensity > currentIntensity)
{
//50 seconds for intensity to go from 0.0 to 1.0
currentIntensity = MathHelper.Min(currentIntensity + 0.02f * IntensityUpdateInterval, targetIntensity);
//25 seconds for intensity to go from 0.0 to 1.0
currentIntensity = MathHelper.Min(currentIntensity + 0.04f * IntensityUpdateInterval, targetIntensity);
}
else
{
@@ -570,6 +635,7 @@ namespace Barotrauma
private float CalculateDistanceTraveled()
{
if (level == null) { return 0.0f; }
var refEntity = GetRefEntity();
Vector2 target = ConvertUnits.ToSimUnits(Level.Loaded.EndPosition);
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(refEntity.WorldPosition), target);
@@ -585,18 +651,47 @@ 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.
/// </summary>
private ISpatialEntity GetRefEntity()
public static ISpatialEntity GetRefEntity()
{
ISpatialEntity refEntity = Submarine.MainSub;
#if CLIENT
if (Character.Controlled != null)
{
if (Character.Controlled.Submarine != null &&
Character.Controlled.Submarine.Info.Type == SubmarineInfo.SubmarineType.Player)
Character.Controlled.Submarine.Info.Type == SubmarineType.Player)
{
refEntity = Character.Controlled.Submarine;
}
@@ -613,7 +708,7 @@ namespace Barotrauma
//Otherwise the system could be abused by for example making a respawned player wait
//close to the destination outpost
if (client.Character.Submarine != null &&
client.Character.Submarine.Info.Type == SubmarineInfo.SubmarineType.Player)
client.Character.Submarine.Info.Type == SubmarineType.Player)
{
if (client.Character.Submarine.WorldPosition.X > refEntity.WorldPosition.X)
{
@@ -1,19 +1,19 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml.Linq;
namespace Barotrauma
{
class ScriptedEventPrefab
class EventPrefab
{
public readonly XElement ConfigElement;
public readonly Type EventType;
public readonly string MusicType;
public readonly float SpawnProbability;
public float Commonness;
public string Identifier;
public ScriptedEventPrefab(XElement element)
public EventPrefab(XElement element)
{
ConfigElement = element;
@@ -31,13 +31,15 @@ namespace Barotrauma
{
DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\".");
}
Identifier = ConfigElement.GetAttributeString("identifier", string.Empty);
Commonness = element.GetAttributeFloat("commonness", 1.0f);
SpawnProbability = Math.Clamp(element.GetAttributeFloat("spawnprobability", 1.0f), 0, 1);
}
public ScriptedEvent CreateInstance()
public Event CreateInstance()
{
ConstructorInfo constructor = EventType.GetConstructor(new[] { typeof(ScriptedEventPrefab) });
ConstructorInfo constructor = EventType.GetConstructor(new[] { typeof(EventPrefab) });
object instance = null;
try
{
@@ -48,7 +50,7 @@ namespace Barotrauma
DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString());
}
return (ScriptedEvent)instance;
return (Event)instance;
}
}
}
@@ -1,39 +1,78 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class ScriptedEventSet
class EventSet
{
internal class EventDebugStats
{
public readonly ScriptedEventSet RootSet;
public readonly EventSet RootSet;
public readonly Dictionary<string, int> MonsterCounts = new Dictionary<string, int>();
public EventDebugStats(ScriptedEventSet rootSet)
public EventDebugStats(EventSet rootSet)
{
RootSet = rootSet;
}
}
public static List<ScriptedEventSet> List
public static List<EventSet> List
{
get;
private set;
}
public static readonly List<EventPrefab> PrefabList = new List<EventPrefab>();
#if CLIENT
private static readonly Dictionary<string, Sprite> EventSprites = new Dictionary<string, Sprite>();
public static Sprite GetEventSprite(string identifier)
{
if (string.IsNullOrWhiteSpace(identifier)) { return null; }
foreach (var (key, value) in EventSprites)
{
if (key.Equals(identifier, StringComparison.OrdinalIgnoreCase)) { return value; }
}
return null;
}
#endif
public static List<EventPrefab> GetAllEventPrefabs()
{
List<EventPrefab> eventPrefabs = new List<EventPrefab>(PrefabList);
foreach (var eventSet in List)
{
eventPrefabs.AddRange(eventSet.EventPrefabs.Select(ep => ep.First));
foreach (var childSet in eventSet.ChildSets)
{
eventPrefabs.AddRange(childSet.EventPrefabs.Select(ep => ep.First));
}
}
return eventPrefabs;
}
public static EventPrefab GetEventPrefab(string identifer)
{
return GetAllEventPrefabs().Find(prefab => string.Equals(prefab.Identifier, identifer, StringComparison.Ordinal));
}
//0-100
public readonly float MinLevelDifficulty, MaxLevelDifficulty;
public readonly LevelData.LevelType LevelType;
public readonly string[] LocationTypeIdentifiers;
public readonly bool ChooseRandom;
public readonly int EventCount = 1;
public readonly float MinDistanceTraveled;
public readonly float MinMissionTime;
@@ -42,14 +81,17 @@ namespace Barotrauma
public readonly bool AllowAtStart;
public readonly bool IgnoreCoolDown;
public readonly bool PerRuin;
public readonly bool PerWreck;
public readonly Dictionary<string, float> Commonness;
public readonly List<ScriptedEventPrefab> EventPrefabs;
//Pair.First: event prefab, Pair.Second: commonness
public readonly List<Pair<EventPrefab, float>> EventPrefabs;
public readonly List<ScriptedEventSet> ChildSets;
public readonly List<EventSet> ChildSets;
public string DebugIdentifier
{
@@ -57,24 +99,39 @@ namespace Barotrauma
private set;
} = "";
private ScriptedEventSet(XElement element, string debugIdentifier)
private EventSet(XElement element, string debugIdentifier, EventSet parentSet = null)
{
DebugIdentifier = element.GetAttributeString("identifier", null) ?? debugIdentifier;
Commonness = new Dictionary<string, float>();
EventPrefabs = new List<ScriptedEventPrefab>();
ChildSets = new List<ScriptedEventSet>();
EventPrefabs = new List<Pair<EventPrefab, float>>();
ChildSets = new List<EventSet>();
MinLevelDifficulty = element.GetAttributeFloat("minleveldifficulty", 0);
MaxLevelDifficulty = Math.Max(element.GetAttributeFloat("maxleveldifficulty", 100), MinLevelDifficulty);
string levelTypeStr = element.GetAttributeString("leveltype", "LocationConnection");
if (!Enum.TryParse(levelTypeStr, true, out LevelType))
{
DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\". \"{levelTypeStr}\" is not a valid level type.");
}
string[] locationTypeStr = element.GetAttributeStringArray("locationtype", null);
if (locationTypeStr != null)
{
LocationTypeIdentifiers = locationTypeStr;
if (LocationType.List.Any()) { CheckLocationTypeErrors(); }
}
MinIntensity = element.GetAttributeFloat("minintensity", 0.0f);
MaxIntensity = Math.Max(element.GetAttributeFloat("maxintensity", 100.0f), MinIntensity);
ChooseRandom = element.GetAttributeBool("chooserandom", false);
EventCount = element.GetAttributeInt("eventcount", 1);
MinDistanceTraveled = element.GetAttributeFloat("mindistancetraveled", 0.0f);
MinMissionTime = element.GetAttributeFloat("minmissiontime", 0.0f);
AllowAtStart = element.GetAttributeBool("allowatstart", false);
IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? false);
PerRuin = element.GetAttributeBool("perruin", false);
PerWreck = element.GetAttributeBool("perwreck", false);
@@ -98,25 +155,59 @@ namespace Barotrauma
}
break;
case "eventset":
ChildSets.Add(new ScriptedEventSet(subElement, this.DebugIdentifier + "-" + ChildSets.Count));
ChildSets.Add(new EventSet(subElement, this.DebugIdentifier + "-" + ChildSets.Count, this));
break;
default:
EventPrefabs.Add(new ScriptedEventPrefab(subElement));
//an element with just an identifier = reference to an event prefab
if (!subElement.HasElements && subElement.Attributes().First().Name.ToString().Equals("identifier", StringComparison.OrdinalIgnoreCase))
{
string identifier = subElement.GetAttributeString("identifier", "");
var prefab = PrefabList.Find(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
if (prefab == null)
{
DebugConsole.ThrowError($"Error in event set \"{debugIdentifier}\" - could not find the event prefab \"{identifier}\".");
}
else
{
float commonness = subElement.GetAttributeFloat("commonness", prefab.Commonness);
EventPrefabs.Add(new Pair<EventPrefab, float>( prefab, commonness));
}
}
else
{
var prefab = new EventPrefab(subElement);
EventPrefabs.Add(new Pair<EventPrefab, float>(prefab, prefab.Commonness));
}
break;
}
}
}
public void CheckLocationTypeErrors()
{
if (LocationTypeIdentifiers == null) { return; }
foreach (string locationTypeId in LocationTypeIdentifiers)
{
if (!LocationType.List.Any(lt => lt.Identifier.Equals(locationTypeId, StringComparison.OrdinalIgnoreCase)))
{
DebugConsole.ThrowError($"Error in event set \"{DebugIdentifier}\". Location type \"{locationTypeId}\" not found.");
}
}
}
public float GetCommonness(Level level)
{
string key = level.GenerationParams?.Name ?? "";
return Commonness.ContainsKey(key) ?
Commonness[key] : Commonness[""];
string key = level.GenerationParams?.Identifier ?? "";
return Commonness.ContainsKey(key) ? Commonness[key] : Commonness[""];
}
public static void LoadPrefabs()
{
List = new List<ScriptedEventSet>();
#if CLIENT
EventSprites.ForEach(pair => pair.Value?.Remove());
EventSprites.Clear();
#endif
List = new List<EventSet>();
var configFiles = GameMain.Instance.GetFilesOfType(ContentType.RandomEvents);
if (!configFiles.Any())
@@ -125,6 +216,9 @@ namespace Barotrauma
return;
}
List<XElement> configElements = new List<XElement>();
Dictionary<XElement, string> filePaths = new Dictionary<XElement, string>();
foreach (ContentFile configFile in configFiles)
{
XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
@@ -137,12 +231,61 @@ namespace Barotrauma
List.Clear();
}
int i = 0;
foreach (XElement element in doc.Root.Elements())
{
if (!element.Name.ToString().Equals("eventset", StringComparison.OrdinalIgnoreCase)) { continue; }
List.Add(new ScriptedEventSet(element, i.ToString()));
i++;
configElements.Add(element);
filePaths[element] = configFile.Path;
}
}
//load event prefabs first so we can link to them when loading event sets
foreach (XElement element in configElements)
{
switch (element.Name.ToString().ToLowerInvariant())
{
case "eventprefabs":
foreach (var subElement in element.Elements())
{
// Warn if an event prefab has no identifier as this would make it impossible to refer to
if (!element.GetAttributeBool("suppresswarnings", false) && string.IsNullOrWhiteSpace(subElement.GetAttributeString("identifier", string.Empty)))
{
DebugConsole.AddWarning($"An event prefab {subElement.Name} in {filePaths[element]} is missing an identifier.");
}
PrefabList.Add(new EventPrefab(subElement));
}
break;
case "eventsprites":
#if CLIENT
foreach (var subElement in element.Elements())
{
string identifier = subElement.GetAttributeString("identifier", string.Empty);
if (EventSprites.ContainsKey(identifier))
{
EventSprites[identifier]?.Remove();
EventSprites[identifier] = new Sprite(subElement);
continue;
}
else
{
EventSprites.Add(identifier, new Sprite(subElement));
}
}
#endif
break;
}
}
int i = 0;
foreach (XElement element in configElements)
{
switch (element.Name.ToString().ToLowerInvariant())
{
case "eventset":
List.Add(new EventSet(element, i.ToString()));
i++;
break;
}
}
}
@@ -170,7 +313,7 @@ namespace Barotrauma
List<EventDebugStats> stats = new List<EventDebugStats>();
for (int i = 0; i < simulatedRoundCount; i++)
{
ScriptedEventSet selectedSet = List.Where(s => difficulty >= s.MinLevelDifficulty && difficulty <= s.MaxLevelDifficulty).GetRandom();
EventSet selectedSet = List.Where(s => difficulty >= s.MinLevelDifficulty && difficulty <= s.MaxLevelDifficulty).GetRandom();
if (selectedSet == null) { continue; }
var newStats = new EventDebugStats(selectedSet);
CheckEventSet(newStats, selectedSet);
@@ -181,21 +324,26 @@ namespace Barotrauma
return debugLines;
static void CheckEventSet(EventDebugStats stats, ScriptedEventSet thisSet)
static void CheckEventSet(EventDebugStats stats, EventSet thisSet)
{
if (thisSet.ChooseRandom)
{
var eventPrefab = ToolBox.SelectWeightedRandom(thisSet.EventPrefabs, thisSet.EventPrefabs.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced);
if (eventPrefab != null)
List<Pair<EventPrefab, float>> unusedEvents = new List<Pair<EventPrefab, float>>(thisSet.EventPrefabs);
for (int i = 0; i < thisSet.EventCount; i++)
{
AddEvent(stats, eventPrefab);
var eventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, unusedEvents.Select(e => e.Second).ToList(), Rand.RandSync.Unsynced);
if (eventPrefab != null)
{
AddEvent(stats, eventPrefab.First);
unusedEvents.Remove(eventPrefab);
}
}
}
else
{
foreach (var eventPrefab in thisSet.EventPrefabs)
{
AddEvent(stats, eventPrefab);
AddEvent(stats, eventPrefab.First);
}
}
foreach (var childSet in thisSet.ChildSets)
@@ -204,7 +352,7 @@ namespace Barotrauma
}
}
static void AddEvent(EventDebugStats stats, ScriptedEventPrefab eventPrefab)
static void AddEvent(EventDebugStats stats, EventPrefab eventPrefab)
{
if (eventPrefab.EventType == typeof(MonsterEvent))
{
@@ -6,7 +6,7 @@ using Microsoft.Xna.Framework;
namespace Barotrauma
{
class MalfunctionEvent : ScriptedEvent
class MalfunctionEvent : Event
{
private string[] targetItemIdentifiers;
@@ -25,7 +25,7 @@ namespace Barotrauma
return "MalfunctionEvent (" + string.Join(", ", targetItemIdentifiers) + ")";
}
public MalfunctionEvent(ScriptedEventPrefab prefab)
public MalfunctionEvent(EventPrefab prefab)
: base(prefab)
{
targetItems = new List<Item>();
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -11,8 +12,8 @@ namespace Barotrauma
private readonly XElement itemConfig;
private readonly List<Item> items = new List<Item>();
private readonly Dictionary<Item, UInt16> itemIDs = new Dictionary<Item, UInt16>();
private readonly Dictionary<Item, UInt16> parentInventoryIDs = new Dictionary<Item, UInt16>();
private readonly Dictionary<Item, byte> parentItemContainerIndices = new Dictionary<Item, byte>();
private int requiredDeliveryAmount;
@@ -26,8 +27,8 @@ namespace Barotrauma
private void InitItems()
{
items.Clear();
itemIDs.Clear();
parentInventoryIDs.Clear();
parentItemContainerIndices.Clear();
if (itemConfig == null)
{
@@ -96,12 +97,12 @@ namespace Barotrauma
var item = new Item(itemPrefab, position, cargoRoom.Submarine);
item.FindHull();
items.Add(item);
itemIDs.Add(item, item.ID);
if (parent != null)
if (parent != null && parent.GetComponent<ItemContainer>() != null)
{
parentInventoryIDs.Add(item, parent.ID);
parent.Combine(item, user: null);
parentItemContainerIndices.Add(item, (byte)parent.GetComponentIndex(parent.GetComponent<ItemContainer>()));
parent.Combine(item, user: null);
}
foreach (XElement subElement in element.Elements())
@@ -116,6 +117,9 @@ namespace Barotrauma
public override void Start(Level level)
{
items.Clear();
parentInventoryIDs.Clear();
if (!IsClient)
{
InitItems();
@@ -105,6 +105,7 @@ namespace Barotrauma
subs = new Submarine[] { Submarine.MainSubs[0], Submarine.MainSubs[1] };
subs[0].TeamID = Character.TeamType.Team1; subs[1].TeamID = Character.TeamType.Team2;
subs[0].NeutralizeBallast(); subs[1].NeutralizeBallast();
subs[1].SetPosition(subs[1].FindSpawnPos(Level.Loaded.EndPosition));
subs[1].FlipX();
@@ -1,5 +1,6 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@@ -63,6 +64,11 @@ namespace Barotrauma
get { return Prefab.Reward; }
}
public Dictionary<string, float> ReputationRewards
{
get { return Prefab.ReputationRewards; }
}
public bool Completed
{
get { return completed; }
@@ -197,8 +203,22 @@ namespace Barotrauma
public void GiveReward()
{
if (!(GameMain.GameSession.GameMode is CampaignMode mode)) { return; }
mode.Money += Reward;
if (!(GameMain.GameSession.GameMode is CampaignMode campaign)) { return; }
campaign.Money += Reward;
foreach (KeyValuePair<string, float> reputationReward in ReputationRewards)
{
if (reputationReward.Key.Equals("location", StringComparison.OrdinalIgnoreCase))
{
Locations[0].Reputation.Value += reputationReward.Value;
Locations[1].Reputation.Value += reputationReward.Value;
}
else
{
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier.Equals(reputationReward.Key, StringComparison.OrdinalIgnoreCase));
if (faction != null) { faction.Reputation.Value += reputationReward.Value; }
}
}
}
}
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
@@ -36,9 +37,14 @@ namespace Barotrauma
public readonly bool MultiplayerOnly, SingleplayerOnly;
public readonly string Identifier;
public readonly string TextIdentifier;
private readonly string[] tags;
public IEnumerable<string> Tags
{
get { return tags; }
}
public readonly string Name;
public readonly string Description;
public readonly string SuccessMessage;
@@ -48,6 +54,8 @@ namespace Barotrauma
public readonly string AchievementIdentifier;
public readonly Dictionary<string, float> ReputationRewards = new Dictionary<string, float>();
public readonly int Commonness;
public readonly int Reward;
@@ -107,6 +115,8 @@ namespace Barotrauma
Identifier = element.GetAttributeString("identifier", "");
TextIdentifier = element.GetAttributeString("textidentifier", null) ?? Identifier;
tags = element.GetAttributeStringArray("tags", new string[0], convertToLowerInvariant: true);
Name = TextManager.Get("MissionName." + TextIdentifier, true) ?? element.GetAttributeString("name", "");
Description = TextManager.Get("MissionDescription." + TextIdentifier, true) ?? element.GetAttributeString("description", "");
Reward = element.GetAttributeInt("reward", 1);
@@ -150,6 +160,24 @@ namespace Barotrauma
subElement.GetAttributeString("from", ""),
subElement.GetAttributeString("to", "")));
break;
case "reputation":
case "reputationreward":
string factionIdentifier = subElement.GetAttributeString("identifier", "");
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);
if (!factionIdentifier.Equals("location", StringComparison.OrdinalIgnoreCase))
{
if (FactionPrefab.Prefabs != null && !FactionPrefab.Prefabs.Any(p => p.Identifier.Equals(factionIdentifier, StringComparison.OrdinalIgnoreCase)))
{
DebugConsole.ThrowError($"Error in mission prefab \"{Identifier}\". Could not find a faction with the identifier \"{factionIdentifier}\".");
}
}
break;
}
}
@@ -7,11 +7,8 @@ namespace Barotrauma
{
partial class MonsterMission : Mission
{
private readonly string monsterFile;
private readonly int monsterCount;
//string = filename, point = min,max
private readonly HashSet<Tuple<string, Point>> monsterFiles = new HashSet<Tuple<string, Point>>();
private readonly HashSet<Tuple<CharacterPrefab, Point>> monsterPrefabs = new HashSet<Tuple<CharacterPrefab, Point>>();
private readonly List<Character> monsters = new List<Character>();
private readonly List<Vector2> sonarPositions = new List<Vector2>();
@@ -38,28 +35,26 @@ namespace Barotrauma
public MonsterMission(MissionPrefab prefab, Location[] locations)
: base(prefab, locations)
{
monsterFile = prefab.ConfigElement.GetAttributeString("monsterfile", null);
if (!string.IsNullOrEmpty(monsterFile))
string speciesName = prefab.ConfigElement.GetAttributeString("monsterfile", null);
if (!string.IsNullOrEmpty(speciesName))
{
var characterPrefab = CharacterPrefab.FindByFilePath(monsterFile);
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
if (characterPrefab != null)
{
monsterFile = characterPrefab.Identifier;
int monsterCount = Math.Min(prefab.ConfigElement.GetAttributeInt("monstercount", 1), 255);
monsterPrefabs.Add(new Tuple<CharacterPrefab, Point>(characterPrefab, new Point(monsterCount)));
}
else
{
DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
}
}
maxSonarMarkerDistance = prefab.ConfigElement.GetAttributeFloat("maxsonarmarkerdistance", 10000.0f);
monsterCount = Math.Min(prefab.ConfigElement.GetAttributeInt("monstercount", 1), 255);
string monsterFileName = monsterFile;
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
{
string monster = monsterElement.GetAttributeString("character", string.Empty);
if (monsterFileName == null)
{
monsterFileName = monster;
}
speciesName = monsterElement.GetAttributeString("character", string.Empty);
int defaultCount = monsterElement.GetAttributeInt("count", -1);
if (defaultCount < 0)
{
@@ -67,10 +62,24 @@ namespace Barotrauma
}
int min = Math.Min(monsterElement.GetAttributeInt("min", defaultCount), 255);
int max = Math.Min(Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)), 255);
monsterFiles.Add(new Tuple<string, Point>(monster, new Point(min, max)));
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
if (characterPrefab != null)
{
monsterPrefabs.Add(new Tuple<CharacterPrefab, Point>(characterPrefab, new Point(min, max)));
}
else
{
DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
}
}
if (monsterPrefabs.Any())
{
var characterParams = new CharacterParams(monsterPrefabs.First().Item1.FilePath);
description = description.Replace("[monster]",
TextManager.Get("character." + characterParams.SpeciesTranslationOverride, returnNull: true) ??
TextManager.Get("character." + characterParams.SpeciesName));
}
description = description.Replace("[monster]",
TextManager.Get("character." + Barotrauma.IO.Path.GetFileNameWithoutExtension(monsterFileName)));
}
public override void Start(Level level)
@@ -88,19 +97,12 @@ namespace Barotrauma
if (!IsClient)
{
Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
if (!string.IsNullOrEmpty(monsterFile))
{
for (int i = 0; i < monsterCount; i++)
{
monsters.Add(Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false));
}
}
foreach (var monster in monsterFiles)
foreach (var monster in monsterPrefabs)
{
int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1);
for (int i = 0; i < amount; i++)
{
monsters.Add(Character.Create(monster.Item1, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false));
monsters.Add(Character.Create(monster.Item1.Identifier, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false));
}
}
@@ -104,9 +104,9 @@ namespace Barotrauma
public override void Start(Level level)
{
#if SERVER
originalItemID = Entity.NullEntityID;
originalInventoryID = Entity.NullEntityID;
#endif
item = null;
if (!IsClient)
{
//ruin/wreck items are allowed to spawn close to the sub
@@ -129,7 +129,7 @@ namespace Barotrauma
case Level.PositionType.Wreck:
foreach (Item it in suitableItems)
{
if (it.Submarine == null || it.Submarine.Info.Type != SubmarineInfo.SubmarineType.Wreck) { continue; }
if (it.Submarine == null || it.Submarine.Info.Type != SubmarineType.Wreck) { continue; }
Rectangle worldBorders = it.Submarine.Borders;
worldBorders.Location += it.Submarine.WorldPosition.ToPoint();
if (Submarine.RectContains(worldBorders, it.WorldPosition))
@@ -151,9 +151,6 @@ namespace Barotrauma
item.body.FarseerBody.BodyType = BodyType.Kinematic;
item.FindHull();
}
#if SERVER
originalItemID = item.ID;
#endif
for (int i = 0; i < statusEffects.Count; i++)
{
@@ -173,6 +170,7 @@ namespace Barotrauma
foreach (Item it in Item.ItemList)
{
if (!it.HasTag(containerTag)) { continue; }
if (it.NonInteractable) { continue; }
switch (spawnPositionType)
{
case Level.PositionType.Cave:
@@ -183,7 +181,7 @@ namespace Barotrauma
if (it.ParentRuin == null) { continue; }
break;
case Level.PositionType.Wreck:
if (it.Submarine == null || it.Submarine.Info.Type != SubmarineInfo.SubmarineType.Wreck) { continue; }
if (it.Submarine == null || it.Submarine.Info.Type != SubmarineType.Wreck) { continue; }
break;
}
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
@@ -192,6 +190,7 @@ namespace Barotrauma
{
#if SERVER
originalInventoryID = it.ID;
originalItemContainerIndex = (byte)it.GetComponentIndex(itemContainer);
#endif
break;
} // Placement successful
@@ -221,11 +220,15 @@ namespace Barotrauma
if (item.ParentInventory != null && item.body != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
if (showMessageWhenPickedUp)
{
if (!(item.ParentInventory?.Owner is Character)) { return; }
if (!(item.GetRootInventoryOwner() is Character)) { return; }
}
else
{
if (item.CurrentHull?.Submarine == null || item.CurrentHull.Submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { return; }
Submarine parentSub = item.CurrentHull?.Submarine ?? item.GetRootInventoryOwner()?.Submarine;
if (parentSub == null || parentSub.Info.Type != SubmarineType.Player)
{
return;
}
}
State = 1;
break;
@@ -7,7 +7,7 @@ using Barotrauma.Items.Components;
namespace Barotrauma
{
class MonsterEvent : ScriptedEvent
class MonsterEvent : Event
{
private readonly string speciesName;
private readonly int minAmount, maxAmount;
@@ -26,6 +26,12 @@ namespace Barotrauma
private bool spawnPending;
public List<Character> Monsters => monsters;
public Vector2? SpawnPos => spawnPos;
public bool SpawnPending => spawnPending;
public int MinAmount => minAmount;
public int MaxAmount => maxAmount;
public override Vector2 DebugDrawPos
{
get { return spawnPos ?? Vector2.Zero; }
@@ -47,7 +53,7 @@ namespace Barotrauma
}
}
public MonsterEvent(ScriptedEventPrefab prefab)
public MonsterEvent(EventPrefab prefab)
: base (prefab)
{
speciesName = prefab.ConfigElement.GetAttributeString("characterfile", "");
@@ -93,6 +99,11 @@ namespace Barotrauma
}
}
private Submarine GetReferenceSub()
{
return EventManager.GetRefEntity() as Submarine ?? Submarine.MainSub;
}
public override IEnumerable<ContentFile> GetFilesToPreload()
{
string path = CharacterPrefab.FindBySpeciesName(speciesName)?.FilePath;
@@ -110,7 +121,7 @@ namespace Barotrauma
public override bool CanAffectSubImmediately(Level level)
{
float maxRange = Sonar.DefaultSonarRange * 0.8f;
return GetAvailableSpawnPositions().Any(p => Vector2.DistanceSquared(p.Position.ToVector2(), Submarine.MainSub.WorldPosition) < maxRange * maxRange);
return GetAvailableSpawnPositions().Any(p => Vector2.DistanceSquared(p.Position.ToVector2(), GetReferenceSub().WorldPosition) < maxRange * maxRange);
}
public override void Init(bool affectSubImmediately)
@@ -191,10 +202,10 @@ namespace Barotrauma
foreach (var position in availablePositions)
{
Vector2 pos = position.Position.ToVector2();
float dist = Vector2.DistanceSquared(pos, Submarine.MainSub.WorldPosition);
float dist = Vector2.DistanceSquared(pos, GetReferenceSub().WorldPosition);
foreach (Submarine sub in Submarine.Loaded)
{
if (sub.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
if (sub.Info.Type != SubmarineType.Player) { continue; }
float minDistToSub = GetMinDistanceToSub(sub);
if (dist > minDistToSub * minDistToSub && dist < closestDist)
{
@@ -209,7 +220,7 @@ namespace Barotrauma
{
foreach (var position in availablePositions)
{
float dist = Vector2.DistanceSquared(position.Position.ToVector2(), Submarine.MainSub.WorldPosition);
float dist = Vector2.DistanceSquared(position.Position.ToVector2(), GetReferenceSub().WorldPosition);
if (dist < closestDist)
{
closestDist = dist;
@@ -223,7 +234,7 @@ namespace Barotrauma
if (!isSubOrWreck)
{
float minDistance = 20000;
availablePositions.RemoveAll(p => Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, p.Position.ToVector2()) < minDistance * minDistance);
availablePositions.RemoveAll(p => Vector2.DistanceSquared(GetReferenceSub().WorldPosition, p.Position.ToVector2()) < minDistance * minDistance);
}
if (availablePositions.None())
{
@@ -256,6 +267,11 @@ namespace Barotrauma
int currentIndex = waypoints.IndexOf(nearestWaypoint);
var nextWaypoint = waypoints[Math.Min(currentIndex + 20, waypoints.Count - 1)];
dir = Vector2.Normalize(nextWaypoint.WorldPosition - nearestWaypoint.WorldPosition);
// Ensure that the spawn position is not offset to the left.
if (dir.X < 0)
{
dir.X = 0;
}
}
else
{
@@ -301,7 +317,7 @@ namespace Barotrauma
{
foreach (Submarine submarine in Submarine.Loaded)
{
if (submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
if (submarine.Info.Type != SubmarineType.Player) { continue; }
float minDist = GetMinDistanceToSub(submarine);
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist) { return; }
}
@@ -315,7 +331,7 @@ namespace Barotrauma
float minDist = Sonar.DefaultSonarRange * 0.8f;
foreach (Submarine submarine in Submarine.Loaded)
{
if (submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
if (submarine.Info.Type != SubmarineType.Player) { continue; }
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist)
{
someoneNearby = true;
@@ -1,85 +1,203 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class ScriptedEvent
{
protected bool isFinished;
class ScriptedEvent : Event
{
private readonly Dictionary<string, List<Predicate<Entity>>> targetPredicates = new Dictionary<string, List<Predicate<Entity>>>();
private readonly Dictionary<string, List<Entity>> cachedTargets = new Dictionary<string, List<Entity>>();
private int prevEntityCount;
private int prevPlayerCount, prevBotCount;
public int CurrentActionIndex { get; private set; }
public List<EventAction> Actions { get; } = new List<EventAction>();
public Dictionary<string, List<Entity>> Targets { get; } = new Dictionary<string, List<Entity>>();
protected readonly ScriptedEventPrefab prefab;
public bool IsFinished
{
get { return isFinished; }
}
public override string ToString()
{
return "ScriptedEvent (" + prefab.EventType.ToString() +")";
}
public virtual Vector2 DebugDrawPos
{
get
{
return Vector2.Zero;
}
}
public ScriptedEvent(ScriptedEventPrefab prefab)
public ScriptedEvent(EventPrefab prefab) : base(prefab)
{
this.prefab = prefab;
}
public virtual IEnumerable<ContentFile> GetFilesToPreload()
{
yield break;
}
public virtual void Init(bool affectSubImmediately)
{
}
public virtual void Update(float deltaTime)
{
}
public virtual void Finished()
{
isFinished = true;
}
public virtual bool CanAffectSubImmediately(Level level)
{
return true;
}
/*public static List<ScriptedEvent> GenerateInitialEvents(Random random, Level level)
{
if (ScriptedEventPrefab.List == null)
foreach (XElement element in prefab.ConfigElement.Elements())
{
ScriptedEventPrefab.LoadPrefabs();
}
List<ScriptedEvent> events = new List<ScriptedEvent>();
foreach (ScriptedEventPrefab scriptedEvent in ScriptedEventPrefab.List)
{
int minCount = scriptedEvent.MinEventCount.ContainsKey(level.GenerationParams.Name) ?
scriptedEvent.MinEventCount[level.GenerationParams.Name] : scriptedEvent.MinEventCount[""];
int maxCount = scriptedEvent.MaxEventCount.ContainsKey(level.GenerationParams.Name) ?
scriptedEvent.MaxEventCount[level.GenerationParams.Name] : scriptedEvent.MaxEventCount[""];
minCount = Math.Min(minCount, maxCount);
int count = random.Next(maxCount - minCount) + minCount;
for (int i = 0; i < count; i++)
if (element.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase))
{
ScriptedEvent eventInstance = scriptedEvent.CreateInstance();
events.Add(eventInstance);
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;
}
var action = EventAction.Instantiate(this, element);
if (action != null) { Actions.Add(action); }
}
if (!Actions.Any())
{
DebugConsole.ThrowError($"Scripted event \"{prefab.Identifier}\" has no actions. The event will do nothing.");
}
}
public void AddTarget(string tag, Entity target)
{
if (target == null)
{
throw new System.ArgumentException("Target was null");
}
if (target.Removed)
{
throw new System.ArgumentException("Target has been removed");
}
if (!Targets.ContainsKey(tag))
{
Targets.Add(tag, new List<Entity>());
}
Targets[tag].Add(target);
if (cachedTargets.ContainsKey(tag))
{
cachedTargets[tag].Add(target);
}
else
{
cachedTargets.Add(tag, new List<Entity> { target });
}
}
public void AddTargetPredicate(string tag, Predicate<Entity> predicate)
{
if (!targetPredicates.ContainsKey(tag))
{
targetPredicates.Add(tag, new List<Predicate<Entity>>());
}
targetPredicates[tag].Add(predicate);
// force re-search for this tag
if (cachedTargets.ContainsKey(tag))
{
cachedTargets.Remove(tag);
}
}
public IEnumerable<Entity> GetTargets(string tag)
{
if (cachedTargets.ContainsKey(tag))
{
if (cachedTargets[tag].Any(t => t.Removed))
{
cachedTargets.Clear();
}
else
{
return cachedTargets[tag];
}
}
return events;
}*/
List<Entity> targetsToReturn = new List<Entity>();
if (Targets.ContainsKey(tag))
{
foreach (Entity e in Targets[tag])
{
if (e.Removed) { continue; }
targetsToReturn.Add(e);
}
}
if (targetPredicates.ContainsKey(tag))
{
foreach (Entity entity in Entity.GetEntities())
{
if (targetPredicates[tag].Any(p => p(entity)))
{
targetsToReturn.Add(entity);
}
}
}
foreach (WayPoint wayPoint in WayPoint.WayPointList)
{
if (wayPoint.Tags.Contains(tag)) { targetsToReturn.Add(wayPoint); }
}
if (Level.Loaded?.StartOutpost != null &&
Level.Loaded.StartOutpost.Info.OutpostNPCs.TryGetValue(tag, out List<Character> outpostNPCs))
{
foreach (Character npc in outpostNPCs)
{
if (npc.Removed) { continue; }
targetsToReturn.Add(npc);
}
}
cachedTargets.Add(tag, targetsToReturn);
return targetsToReturn;
}
public override void Update(float deltaTime)
{
int botCount = 0;
int playerCount = 0;
foreach (Character c in Character.CharacterList)
{
if (c.IsPlayer)
{
playerCount++;
}
else if (c.IsBot)
{
botCount++;
}
}
if (Entity.EntityCount != prevEntityCount || botCount != prevBotCount || playerCount != prevPlayerCount)
{
cachedTargets.Clear();
prevEntityCount = Entity.EntityCount;
prevBotCount = botCount;
prevPlayerCount = playerCount;
}
if (!Actions.Any())
{
Finished();
return;
}
var currentAction = Actions[CurrentActionIndex];
if (!currentAction.CanBeFinished())
{
Finished();
return;
}
string goTo = null;
if (currentAction.IsFinished(ref goTo))
{
if (string.IsNullOrEmpty(goTo))
{
CurrentActionIndex++;
}
else
{
CurrentActionIndex = -1;
Actions.ForEach(a => a.Reset());
for (int i = 0; i < Actions.Count; i++)
{
if (Actions[i].SetGoToTarget(goTo))
{
CurrentActionIndex = i;
break;
}
}
}
if (CurrentActionIndex >= Actions.Count || CurrentActionIndex < 0)
{
Finished();
}
}
else
{
currentAction.Update(deltaTime);
}
}
}
}