(965c31410a) Unstable v0.10.4.0
This commit is contained in:
@@ -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()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SetPriceMultiplierAction : EventAction
|
||||
{
|
||||
public enum OperationType
|
||||
{
|
||||
Set,
|
||||
Multiply,
|
||||
Min,
|
||||
Max
|
||||
}
|
||||
|
||||
public enum PriceMultiplierType
|
||||
{
|
||||
Store,
|
||||
Mechanical
|
||||
}
|
||||
|
||||
[Serialize(1.0f, true)]
|
||||
public float Multiplier { get; set; }
|
||||
|
||||
[Serialize(OperationType.Set, true)]
|
||||
public OperationType Operation { get; set; }
|
||||
|
||||
[Serialize(PriceMultiplierType.Store, true)]
|
||||
public PriceMultiplierType TargetMultiplier { get; set; }
|
||||
|
||||
public SetPriceMultiplierAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.Map?.CurrentLocation != null)
|
||||
{
|
||||
float newMultiplier = GetCurrentMultiplier(campaign.Map.CurrentLocation);
|
||||
|
||||
switch (Operation)
|
||||
{
|
||||
case OperationType.Set:
|
||||
newMultiplier = Multiplier;
|
||||
break;
|
||||
case OperationType.Multiply:
|
||||
newMultiplier *= Multiplier;
|
||||
break;
|
||||
case OperationType.Min:
|
||||
newMultiplier = Math.Min(Multiplier, campaign.Map.CurrentLocation.PriceMultiplier);
|
||||
break;
|
||||
case OperationType.Max:
|
||||
newMultiplier = Math.Max(Multiplier, campaign.Map.CurrentLocation.PriceMultiplier);
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
SetCurrentMultiplier(campaign.Map.CurrentLocation, newMultiplier);
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
private float GetCurrentMultiplier(Location location)
|
||||
{
|
||||
return TargetMultiplier switch
|
||||
{
|
||||
PriceMultiplierType.Store => location.PriceMultiplier,
|
||||
PriceMultiplierType.Mechanical => location.MechanicalPriceMultiplier,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
|
||||
private void SetCurrentMultiplier(Location location, float value)
|
||||
{
|
||||
switch (TargetMultiplier)
|
||||
{
|
||||
case PriceMultiplierType.Store:
|
||||
location.PriceMultiplier = value;
|
||||
break;
|
||||
case PriceMultiplierType.Mechanical:
|
||||
location.MechanicalPriceMultiplier = value;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(SetPriceMultiplierAction)} -> (Multiplier: {Multiplier.ColorizeObject()}, " +
|
||||
$"Operation: {Operation.ColorizeObject()}, Target: {TargetMultiplier})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SkillCheckAction : BinaryOptionAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string RequiredSkill { get; set; }
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float RequiredLevel { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
public SkillCheckAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (string.IsNullOrEmpty(TargetTag))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": SkillCheckAction without a target tag (the action needs to know whose skill to check).");
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
var potentialTargets = ParentEvent.GetTargets(TargetTag).Where(e => e is Character).Select(e => e as Character);
|
||||
return potentialTargets.Any(chr => chr.GetSkillLevel(RequiredSkill?.ToLowerInvariant()) >= RequiredLevel);
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
string subActionStr = "";
|
||||
if (succeeded.HasValue)
|
||||
{
|
||||
subActionStr = $"\n Sub action: {(succeeded.Value ? Success : Failure)?.CurrentSubAction.ColorizeObject()}";
|
||||
}
|
||||
return $"{ToolBox.GetDebugSymbol(DetermineFinished())} {nameof(SkillCheckAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
|
||||
$"Required skill: {RequiredSkill.ColorizeObject()}, Required level: {RequiredLevel.ColorizeObject()}, " +
|
||||
$"Succeeded: {(succeeded.HasValue ? succeeded.Value.ToString() : "not determined").ColorizeObject()})" +
|
||||
subActionStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SpawnAction : EventAction
|
||||
{
|
||||
public enum SpawnLocationType
|
||||
{
|
||||
MainSub,
|
||||
Outpost,
|
||||
MainPath,
|
||||
Ruin,
|
||||
Wreck
|
||||
}
|
||||
|
||||
[Serialize("", true, description: "Species name of the character to spawn.")]
|
||||
public string SpeciesName { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Identifier of the NPC set to choose from.")]
|
||||
public string NPCSetIdentifier { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Identifier of the NPC.")]
|
||||
public string NPCIdentifier { get; set; }
|
||||
|
||||
[Serialize(true, true, description: "Should taking the items of this npc be considered as stealing?")]
|
||||
public bool LootingIsStealing { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Identifier of the item to spawn.")]
|
||||
public string ItemIdentifier { get; set; }
|
||||
|
||||
[Serialize("", true, description: "The spawned entity will be assigned this tag. The tag can be used to refer to the entity by other actions of the event.")]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Tag of an entity with an inventory to spawn the item into.")]
|
||||
public string TargetInventory { get; set; }
|
||||
|
||||
[Serialize(SpawnLocationType.MainSub, true)]
|
||||
public SpawnLocationType SpawnLocation { get; set; }
|
||||
|
||||
[Serialize(SpawnType.Human, true)]
|
||||
public SpawnType SpawnPointType { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string SpawnPointTag { get; set; }
|
||||
|
||||
private readonly HashSet<string> targetModuleTags = new HashSet<string>();
|
||||
|
||||
[Serialize("", true, "What outpost module tags does the entity prefer to spawn in.")]
|
||||
public string TargetModuleTags
|
||||
{
|
||||
get => string.Join(",", targetModuleTags);
|
||||
set
|
||||
{
|
||||
targetModuleTags.Clear();
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
string[] splitTags = value.Split(',');
|
||||
foreach (var s in splitTags)
|
||||
{
|
||||
targetModuleTags.Add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool spawned;
|
||||
private Entity spawnedEntity;
|
||||
|
||||
private readonly bool ignoreSpawnPointType;
|
||||
|
||||
public SpawnAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
ignoreSpawnPointType = !element.Attributes().Any(a => a.Name.ToString().Equals("spawnpointtype", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
if (spawnedEntity != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
spawned = false;
|
||||
spawnedEntity = null;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (spawned) { return; }
|
||||
|
||||
if (!string.IsNullOrEmpty(NPCSetIdentifier) && !string.IsNullOrEmpty(NPCIdentifier))
|
||||
{
|
||||
HumanPrefab humanPrefab = NPCSet.Get(NPCSetIdentifier, NPCIdentifier);
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
Entity.Spawner.AddToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawn: newCharacter =>
|
||||
{
|
||||
newCharacter.TeamID = Character.TeamType.FriendlyNPC;
|
||||
newCharacter.EnableDespawn = false;
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine);
|
||||
if (LootingIsStealing)
|
||||
{
|
||||
foreach (Item item in newCharacter.Inventory.Items)
|
||||
{
|
||||
if (item != null) { item.SpawnedInOutpost = true; }
|
||||
}
|
||||
}
|
||||
newCharacter.CharacterHealth.MaxVitality *= humanPrefab.HealthMultiplier;
|
||||
var humanAI = newCharacter.AIController as HumanAIController;
|
||||
if (humanAI != null)
|
||||
{
|
||||
var idleObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveIdle>();
|
||||
if (idleObjective != null)
|
||||
{
|
||||
idleObjective.Behavior = humanPrefab.BehaviorType;
|
||||
foreach (string moduleType in humanPrefab.PreferredOutpostModuleTypes)
|
||||
{
|
||||
idleObjective.PreferredOutpostModuleTypes.Add(moduleType);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (humanPrefab.CampaignInteractionType != CampaignMode.InteractionType.None)
|
||||
{
|
||||
(GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(newCharacter, humanPrefab.CampaignInteractionType);
|
||||
if (spawnPos != null && humanAI != null)
|
||||
{
|
||||
humanAI.ObjectiveManager.SetOrder(new AIObjectiveGoTo(spawnPos, newCharacter, humanAI.ObjectiveManager, repeat: true, getDivingGearIfNeeded: false, closeEnough: 200));
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(TargetTag) && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
});
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(SpeciesName))
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(SpeciesName, OffsetSpawnPos(GetSpawnPos()?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawn: newCharacter =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(TargetTag) && newCharacter != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
});
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(ItemIdentifier))
|
||||
{
|
||||
if (!(MapEntityPrefab.Find(null, identifier: ItemIdentifier) is ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)");
|
||||
}
|
||||
else
|
||||
{
|
||||
Inventory spawnInventory = null;
|
||||
if (!string.IsNullOrEmpty(TargetInventory))
|
||||
{
|
||||
var targets = ParentEvent.GetTargets(TargetInventory);
|
||||
if (targets.Any())
|
||||
{
|
||||
var target = targets.First(t => t is Item || t is Character);
|
||||
if (target is Character character)
|
||||
{
|
||||
spawnInventory = character.Inventory;
|
||||
}
|
||||
else if (target is Item item)
|
||||
{
|
||||
spawnInventory = item.OwnInventory;
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnInventory == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not spawn \"{ItemIdentifier}\" in target inventory \"{TargetInventory}\"");
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnInventory == null)
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, OffsetSpawnPos(GetSpawnPos()?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawned: onSpawned);
|
||||
}
|
||||
else
|
||||
{
|
||||
Entity.Spawner.AddToSpawnQueue(itemPrefab, spawnInventory, onSpawned: onSpawned);
|
||||
}
|
||||
void onSpawned(Item newItem)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(TargetTag) && newItem != null)
|
||||
{
|
||||
ParentEvent.AddTarget(TargetTag, newItem);
|
||||
}
|
||||
spawnedEntity = newItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spawned = true;
|
||||
|
||||
}
|
||||
|
||||
public static Vector2 OffsetSpawnPos(Vector2 pos, float offsetAmount)
|
||||
{
|
||||
Hull hull = Hull.FindHull(pos);
|
||||
pos += Rand.Vector(offsetAmount);
|
||||
if (hull != null)
|
||||
{
|
||||
float margin = 50.0f;
|
||||
pos = new Vector2(
|
||||
MathHelper.Clamp(pos.X, hull.WorldRect.X + margin, hull.WorldRect.Right - margin),
|
||||
MathHelper.Clamp(pos.Y, hull.WorldRect.Y - hull.WorldRect.Height + margin, hull.WorldRect.Y - margin));
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
private ISpatialEntity GetSpawnPos()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SpawnPointTag))
|
||||
{
|
||||
List<Item> potentialItems = SpawnLocation switch
|
||||
{
|
||||
SpawnLocationType.MainSub => Item.ItemList.FindAll(it => it.Submarine == Submarine.MainSub),
|
||||
SpawnLocationType.MainPath => Item.ItemList.FindAll(it => it.Submarine == null && it.ParentRuin == null),
|
||||
SpawnLocationType.Outpost => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsOutpost),
|
||||
SpawnLocationType.Wreck => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsWreck),
|
||||
SpawnLocationType.Ruin => Item.ItemList.FindAll(it => it.ParentRuin != null),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
var item = potentialItems.Where(it => it.HasTag(SpawnPointTag)).GetRandom();
|
||||
if (item != null) { return item; }
|
||||
|
||||
var target = ParentEvent.GetTargets(SpawnPointTag).GetRandom();
|
||||
if (target != null) { return target; }
|
||||
}
|
||||
|
||||
SpawnType? spawnPointType = null;
|
||||
if (!ignoreSpawnPointType) { spawnPointType = SpawnPointType; }
|
||||
|
||||
return GetSpawnPos(SpawnLocation, spawnPointType, targetModuleTags, SpawnPointTag.ToEnumerable());
|
||||
}
|
||||
|
||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<string> moduleFlags = null, IEnumerable<string> spawnpointTags = null)
|
||||
{
|
||||
List<WayPoint> potentialSpawnPoints = spawnLocation switch
|
||||
{
|
||||
SpawnLocationType.MainSub => WayPoint.WayPointList.FindAll(wp => wp.Submarine == Submarine.MainSub && wp.CurrentHull != null),
|
||||
SpawnLocationType.MainPath => WayPoint.WayPointList.FindAll(wp => wp.Submarine == null && wp.ParentRuin == null),
|
||||
SpawnLocationType.Outpost => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.CurrentHull != null && wp.Submarine.Info.IsOutpost),
|
||||
SpawnLocationType.Wreck => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.Submarine.Info.IsWreck),
|
||||
SpawnLocationType.Ruin => WayPoint.WayPointList.FindAll(wp => wp.ParentRuin != null),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor == null && wp.Ladders == null && !wp.isObstructed);
|
||||
|
||||
if (moduleFlags != null && moduleFlags.Any())
|
||||
{
|
||||
List<WayPoint> spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Any(moduleFlags.Contains) ?? false).ToList();
|
||||
if (spawnPoints.Any())
|
||||
{
|
||||
potentialSpawnPoints = spawnPoints;
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnpointTags != null && spawnpointTags.Any())
|
||||
{
|
||||
var spawnPoints = potentialSpawnPoints.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag)))
|
||||
.Where(wp => wp.ConnectedDoor == null && !wp.isObstructed);
|
||||
if (spawnPoints.Any())
|
||||
{
|
||||
potentialSpawnPoints = spawnPoints.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
if (potentialSpawnPoints.Count == 0)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a spawn point for a SpawnAction (spawn location: {spawnLocation})");
|
||||
return null;
|
||||
}
|
||||
|
||||
IEnumerable<WayPoint> validSpawnPoints;
|
||||
if (spawnPointType.HasValue)
|
||||
{
|
||||
validSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.SpawnType == spawnPointType.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
validSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.SpawnType != SpawnType.Path);
|
||||
if (!validSpawnPoints.Any()) { validSpawnPoints = potentialSpawnPoints; }
|
||||
}
|
||||
|
||||
//don't spawn in an airlock module if there are other options
|
||||
var airlockSpawnPoints = validSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false);
|
||||
if (airlockSpawnPoints.Count() < validSpawnPoints.Count())
|
||||
{
|
||||
validSpawnPoints = validSpawnPoints.Except(airlockSpawnPoints);
|
||||
}
|
||||
|
||||
if (!validSpawnPoints.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a spawn point of the correct type for a SpawnAction (spawn location: {spawnLocation}, type: {spawnPointType}, module flags: {((moduleFlags == null || !moduleFlags.Any()) ? "none" : string.Join(", ", moduleFlags))})");
|
||||
return potentialSpawnPoints.GetRandom();
|
||||
}
|
||||
|
||||
//if not trying to spawn at a tagged spawnpoint, favor spawnpoints without tags
|
||||
if (spawnpointTags == null || !spawnpointTags.Any())
|
||||
{
|
||||
var spawnPoints = validSpawnPoints.Where(wp => !wp.Tags.Any());
|
||||
if (spawnPoints.Any())
|
||||
{
|
||||
validSpawnPoints = spawnPoints.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
return validSpawnPoints.GetRandom();
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(spawned)} {nameof(SpawnAction)} -> (Spawned entity: {spawnedEntity.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class StatusEffectAction : EventAction
|
||||
{
|
||||
private readonly List<StatusEffect> effects = new List<StatusEffect>();
|
||||
|
||||
private int actionIndex;
|
||||
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
public StatusEffectAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
actionIndex = 0;
|
||||
foreach (XElement subElement in parentEvent.Prefab.ConfigElement.Descendants())
|
||||
{
|
||||
if (subElement == element) { break; }
|
||||
actionIndex++;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "statuseffect":
|
||||
effects.Add(StatusEffect.Load(subElement, $"{nameof(StatusEffectAction)} ({parentEvent.Prefab.Identifier})"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
var targets = ParentEvent.GetTargets(TargetTag);
|
||||
foreach (StatusEffect effect in effects)
|
||||
{
|
||||
foreach (var target in targets)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, target, target as ISerializableEntity);
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
ServerWrite(targets);
|
||||
#endif
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(StatusEffectAction)} -> (TargetTag: {TargetTag.ColorizeObject()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TagAction : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string Criteria { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string Tag { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public TagAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
private void TagPlayers()
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsPlayer);
|
||||
}
|
||||
|
||||
private void TagBots()
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsBot);
|
||||
}
|
||||
|
||||
private void TagCrew()
|
||||
{
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.GetCharacters().ForEach(c => ParentEvent.AddTarget(Tag, c));
|
||||
#else
|
||||
TagPlayers(); TagBots(); //TODO: this seems like it would tag more than it should, fix
|
||||
#endif
|
||||
}
|
||||
|
||||
private void TagStructuresByIdentifier(string identifier)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && s.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
private void TagItemsByIdentifier(string identifier)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && it.Prefab.Identifier.Equals(identifier, StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
|
||||
private void TagItemsByTag(string tag)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && it.HasTag(tag));
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
string[] criteriaSplit = Criteria.Split(';');
|
||||
|
||||
foreach (string entry in criteriaSplit)
|
||||
{
|
||||
string[] kvp = entry.Split(':');
|
||||
switch (kvp[0].Trim().ToLowerInvariant())
|
||||
{
|
||||
case "player":
|
||||
TagPlayers();
|
||||
break;
|
||||
case "bot":
|
||||
TagBots();
|
||||
break;
|
||||
case "crew":
|
||||
TagCrew();
|
||||
break;
|
||||
case "structureidentifier":
|
||||
if (kvp.Length > 1) { TagStructuresByIdentifier(kvp[1].Trim()); }
|
||||
break;
|
||||
case "itemidentifier":
|
||||
if (kvp.Length > 1) { TagItemsByIdentifier(kvp[1].Trim()); }
|
||||
break;
|
||||
case "itemtag":
|
||||
if (kvp.Length > 1) { TagItemsByTag(kvp[1].Trim()); }
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TagAction)} -> (Criteria: {Criteria.ColorizeObject()}, Tag: {Tag.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TriggerAction : EventAction
|
||||
{
|
||||
[Serialize("", true, description: "Tag of the first entity that will be used for trigger checks.")]
|
||||
public string Target1Tag { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Tag of the second entity that will be used for trigger checks.")]
|
||||
public string Target2Tag { get; set; }
|
||||
|
||||
[Serialize("", true, description: "If set, the first target has to be within an outpost module of this type.")]
|
||||
public string TargetModuleType { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Tag to apply to the first entity when the trigger check succeeds.")]
|
||||
public string ApplyToTarget1 { get; set; }
|
||||
|
||||
[Serialize("", true, description: "Tag to apply to the second entity when the trigger check succeeds.")]
|
||||
public string ApplyToTarget2 { get; set; }
|
||||
|
||||
[Serialize(0.0f, true, description: "Range both entities must be within to activate the trigger.")]
|
||||
public float Radius { get; set; }
|
||||
|
||||
[Serialize(true, true, description: "If true, characters who are being targeted by some enemy cannot trigger the event.")]
|
||||
public bool DisableInCombat { get; set; }
|
||||
|
||||
private float distance;
|
||||
|
||||
public TriggerAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
TargetModuleType = TargetModuleType?.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private bool isFinished = false;
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
var targets1 = ParentEvent.GetTargets(Target1Tag);
|
||||
if (!targets1.Any()) { return; }
|
||||
|
||||
foreach (Entity e1 in targets1)
|
||||
{
|
||||
if (DisableInCombat && IsInCombat(e1)) { continue; }
|
||||
if (!string.IsNullOrEmpty(TargetModuleType))
|
||||
{
|
||||
if (IsCloseEnoughToHull(e1, out Hull hull))
|
||||
{
|
||||
Trigger(e1, hull);
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
var targets2 = ParentEvent.GetTargets(Target2Tag);
|
||||
|
||||
foreach (Entity e2 in targets2)
|
||||
{
|
||||
if (e1 == e2) { continue; }
|
||||
if (DisableInCombat && IsInCombat(e2)) { continue; }
|
||||
|
||||
Vector2 pos1 = e1.WorldPosition;
|
||||
Vector2 pos2 = e2.WorldPosition;
|
||||
distance = Vector2.Distance(pos1, pos2);
|
||||
if (((e1 is MapEntity m1) && Submarine.RectContains(m1.WorldRect, pos2)) ||
|
||||
((e2 is MapEntity m2) && Submarine.RectContains(m2.WorldRect, pos1)) ||
|
||||
Vector2.DistanceSquared(pos1, pos2) < Radius * Radius)
|
||||
{
|
||||
Trigger(e1, e2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsCloseEnoughToHull(Entity e, out Hull hull)
|
||||
{
|
||||
hull = null;
|
||||
if (Radius <= 0)
|
||||
{
|
||||
if (e is Character character && character.CurrentHull != null && character.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
|
||||
{
|
||||
hull = character.CurrentHull;
|
||||
return true;
|
||||
}
|
||||
else if (e is Item item && item.CurrentHull != null && item.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
|
||||
{
|
||||
hull = item.CurrentHull;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Hull potentialHull in Hull.hullList)
|
||||
{
|
||||
if (!potentialHull.OutpostModuleTags.Contains(TargetModuleType)) { continue; }
|
||||
|
||||
Rectangle hullRect = potentialHull.WorldRect;
|
||||
hullRect.Inflate(Radius, Radius);
|
||||
if (Submarine.RectContains(hullRect, e.WorldPosition))
|
||||
{
|
||||
hull = potentialHull;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsInCombat(Entity entity)
|
||||
{
|
||||
if (!(entity is Character character)) { return false; }
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.IsDead || c.Removed || c.IsIncapacitated || !c.Enabled) { continue; }
|
||||
if (c.IsBot && c.AIController is HumanAIController humanAi)
|
||||
{
|
||||
if (humanAi.ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective &&
|
||||
combatObjective.Enemy == character)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (c.AIController is EnemyAIController enemyAI && (enemyAI.State == AIState.Aggressive || enemyAI.State == AIState.Attack))
|
||||
{
|
||||
if (enemyAI.SelectedAiTarget?.Entity == character || c.CurrentHull == character.CurrentHull)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void Trigger(Entity entity1, Entity entity2)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ApplyToTarget1))
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyToTarget1, entity1);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(ApplyToTarget2))
|
||||
{
|
||||
ParentEvent.AddTarget(ApplyToTarget2, entity2);
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
if (string.IsNullOrEmpty(TargetModuleType))
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TriggerAction)} -> (Distance: {((int)distance).ColorizeObject()}, Radius: {Radius.ColorizeObject()}, TargetTags: {Target1Tag.ColorizeObject()}, {Target2Tag.ColorizeObject()})";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TriggerAction)} -> (TargetTags: {Target1Tag.ColorizeObject()}, {TargetModuleType.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TriggerEventAction : EventAction
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string Identifier { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TriggerEventAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
if (GameMain.GameSession?.EventManager != null)
|
||||
{
|
||||
var eventPrefab = EventSet.GetEventPrefab(Identifier);
|
||||
if (eventPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in TriggerEventAction - could not find an event with the identifier {Identifier}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(eventPrefab.CreateInstance());
|
||||
}
|
||||
}
|
||||
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(TriggerEventAction)} -> (EventPrefab: {Identifier.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class WaitAction : EventAction
|
||||
{
|
||||
[Serialize(0.0f, true)]
|
||||
public float Time { get; set; }
|
||||
|
||||
private float timeRemaining;
|
||||
|
||||
public WaitAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
|
||||
{
|
||||
timeRemaining = Time;
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return timeRemaining <= 0;
|
||||
}
|
||||
public override void Reset()
|
||||
{
|
||||
timeRemaining = Time;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
timeRemaining -= deltaTime;
|
||||
if (timeRemaining < 0.0f) { timeRemaining = 0.0f; }
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(timeRemaining <= 0)} {nameof(WaitAction)} -> (Remaining: {timeRemaining.ColorizeObject()}, Time: {Time.ColorizeObject()})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
+8
-6
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+177
-29
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user