Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaShared/SharedSource/Events/EventActions/TriggerAction.cs
2024-04-24 18:09:05 +03:00

419 lines
17 KiB
C#

using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
/// <summary>
/// Waits for a player to trigger the action before continuing. Triggering can mean entering a specific trigger area, or interacting with a specific entity.
/// </summary>
class TriggerAction : EventAction
{
public enum TriggerType
{
Inside,
Outside
}
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the first entity that will be used for trigger checks.")]
public Identifier Target1Tag { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Tag of the second entity that will be used for trigger checks.")]
public Identifier Target2Tag { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "If set, the first target has to be within an outpost module of this type.")]
public Identifier TargetModuleType { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the first entity when the trigger check succeeds.")]
public Identifier ApplyToTarget1 { get; set; }
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the second entity when the trigger check succeeds.")]
public Identifier ApplyToTarget2 { get; set; }
[Serialize(TriggerType.Inside, IsPropertySaveable.Yes, description: "Determines if the targets must be inside or outside of the radius.")]
public TriggerType Type { get; set; }
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Range to activate the trigger.")]
public float Radius { get; set; }
[Serialize(true, IsPropertySaveable.Yes, description: "If true, characters who are being targeted by some enemy cannot trigger the action.")]
public bool DisableInCombat { get; set; }
[Serialize(true, IsPropertySaveable.Yes, description: "If true, dead/unconscious characters cannot trigger the action.")]
public bool DisableIfTargetIncapacitated { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "If true, one target must interact with the other to trigger the action.")]
public bool WaitForInteraction { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "If true, the action can be triggered by interacting with any matching target (not just the 1st one).")]
public bool AllowMultipleTargets { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "If true and using multiple targets, all targets must be inside/outside the radius.")]
public bool CheckAllTargets { get; set; }
[Serialize(false, IsPropertySaveable.Yes, description: "If true, interacting with the target will make the character select it.")]
public bool SelectOnTrigger { get; set; }
private float distance;
public TriggerAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
private bool isFinished = false;
public override bool IsFinished(ref string goTo)
{
return isFinished;
}
public override void Reset()
{
ResetTargetIcons();
isRunning = false;
isFinished = false;
}
public bool isRunning = false;
private readonly List<Either<Character, Item>> npcsOrItems = new List<Either<Character, Item>>();
private readonly List<(Entity e1, Entity e2)> triggerers = new List<(Entity e1, Entity e2)>();
public override void Update(float deltaTime)
{
if (isFinished) { return; }
isRunning = true;
var targets1 = ParentEvent.GetTargets(Target1Tag);
if (!targets1.Any()) { return; }
triggerers.Clear();
foreach (Entity e1 in targets1)
{
if (DisableInCombat && IsInCombat(e1))
{
if (CheckAllTargets)
{
return;
}
continue;
}
if (DisableIfTargetIncapacitated && e1 is Character character1 && (character1.IsDead || character1.IsIncapacitated))
{
if (CheckAllTargets)
{
return;
}
continue;
}
if (!TargetModuleType.IsEmpty)
{
if (!CheckAllTargets && CheckDistanceToHull(e1, out Hull hull))
{
Trigger(e1, hull);
return;
}
else if (CheckAllTargets)
{
if (CheckDistanceToHull(e1, out hull))
{
triggerers.Add((e1, hull));
}
else
{
return;
}
}
continue;
}
var targets2 = ParentEvent.GetTargets(Target2Tag);
foreach (Entity e2 in targets2)
{
if (e1 == e2)
{
continue;
}
if (DisableInCombat && IsInCombat(e2))
{
if (CheckAllTargets)
{
return;
}
continue;
}
if (DisableIfTargetIncapacitated && e2 is Character character2 && (character2.IsDead || character2.IsIncapacitated))
{
if (CheckAllTargets)
{
return;
}
continue;
}
if (WaitForInteraction)
{
Character player = null;
Character npc = null;
Item item = null;
if (e1 is Character char1)
{
if (char1.IsBot)
{
npc ??= char1;
}
else
{
player = char1;
}
}
else
{
item ??= e1 as Item;
}
if (e2 is Character char2)
{
if (char2.IsBot)
{
npc ??= char2;
}
else
{
player = char2;
}
}
else
{
item ??= e2 as Item;
}
if (player != null)
{
if (npc != null)
{
if (npc.CampaignInteractionType == CampaignMode.InteractionType.Talk)
{
//if the NPC has a conversation available, don't assign the trigger until the conversation is done
continue;
}
else if (npc.CampaignInteractionType != CampaignMode.InteractionType.Examine)
{
if (!npcsOrItems.Any(n => n.TryGet(out Character npc2) && npc2 == npc))
{
npcsOrItems.Add(npc);
}
npc.CampaignInteractionType = CampaignMode.InteractionType.Examine;
npc.RequireConsciousnessForCustomInteract = DisableIfTargetIncapacitated;
#if CLIENT
npc.SetCustomInteract(
(speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use)));
#else
npc.SetCustomInteract(
(speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
TextManager.Get("CampaignInteraction.Talk"));
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AssignCampaignInteractionEventData());
#endif
}
if (!AllowMultipleTargets) { return; }
}
else if (item != null)
{
if (!npcsOrItems.Any(n => n.TryGet(out Item item2) && item2 == item))
{
npcsOrItems.Add(item);
}
item.AssignCampaignInteractionType(CampaignMode.InteractionType.Examine,
GameMain.NetworkMember?.ConnectedClients.Where(c => c.Character != null && targets2.Contains(c.Character)));
if (player.SelectedItem == item ||
player.SelectedSecondaryItem == item ||
(player.Inventory != null && player.Inventory.Contains(item)) ||
(player.FocusedItem == item && player.IsKeyHit(InputType.Use)))
{
Trigger(e1, e2);
return;
}
}
}
}
else
{
Vector2 pos1 = e1.WorldPosition;
Vector2 pos2 = e2.WorldPosition;
distance = Vector2.Distance(pos1, pos2);
if ((Type == TriggerType.Inside) == IsWithinRadius())
{
if (!CheckAllTargets)
{
Trigger(e1, e2);
return;
}
else
{
triggerers.Add((e1, e2));
}
}
else if (CheckAllTargets)
{
return;
}
bool IsWithinRadius() =>
((e1 is MapEntity m1) && Submarine.RectContains(m1.WorldRect, pos2)) ||
((e2 is MapEntity m2) && Submarine.RectContains(m2.WorldRect, pos1)) ||
Vector2.DistanceSquared(pos1, pos2) < Radius * Radius;
}
}
}
foreach (var (e1, e2) in triggerers)
{
Trigger(e1, e2);
}
}
private void ResetTargetIcons()
{
foreach (var npcOrItem in npcsOrItems)
{
if (npcOrItem.TryGet(out Character npc))
{
npc.CampaignInteractionType = CampaignMode.InteractionType.None;
npc.SetCustomInteract(null, null);
npc.RequireConsciousnessForCustomInteract = true;
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AssignCampaignInteractionEventData());
#endif
}
else if (npcOrItem.TryGet(out Item item))
{
item.AssignCampaignInteractionType(CampaignMode.InteractionType.None);
}
}
}
private bool CheckDistanceToHull(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 Type == TriggerType.Inside;
}
else if (e is Item item && item.CurrentHull != null && item.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
{
hull = item.CurrentHull;
return Type == TriggerType.Inside;
}
return Type == TriggerType.Outside;
}
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 Type == TriggerType.Inside;
}
}
return Type == TriggerType.Outside;
}
}
private static bool IsInCombat(Entity entity)
{
if (entity is not 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)
{
ResetTargetIcons();
if (!ApplyToTarget1.IsEmpty)
{
ParentEvent.AddTarget(ApplyToTarget1, entity1);
}
if (!ApplyToTarget2.IsEmpty)
{
ParentEvent.AddTarget(ApplyToTarget2, entity2);
}
Character player = null;
Entity target = null;
if (entity1 is Character { IsPlayer: true })
{
player = entity1 as Character;
target = entity2;
}
else if (entity2 is Character { IsPlayer: true })
{
player = entity2 as Character;
target = entity1;
}
if (player != null && SelectOnTrigger)
{
if (target is Character targetCharacter)
{
player.SelectCharacter(targetCharacter);
}
else if (target is Item targetItem)
{
if (targetItem.IsSecondaryItem)
{
player.SelectedSecondaryItem = targetItem;
}
else
{
player.SelectedItem = targetItem;
}
}
}
isRunning = false;
isFinished = true;
}
public override string ToDebugString()
{
if (TargetModuleType.IsEmpty)
{
return
$"{ToolBox.GetDebugSymbol(isFinished, isRunning)} {nameof(TriggerAction)} -> (" +
(WaitForInteraction ?
$"Selected non-player target: {(npcsOrItems?.ToString() ?? "<null>").ColorizeObject()}, " :
$"Distance: {((int)distance).ColorizeObject()}, ") +
$"Radius: {Radius.ColorizeObject()}, " +
$"TargetTags: {Target1Tag.ColorizeObject()}, " +
$"{Target2Tag.ColorizeObject()})";
}
else
{
return $"{ToolBox.GetDebugSymbol(isFinished, isRunning)} {nameof(TriggerAction)} -> (TargetTags: {Target1Tag.ColorizeObject()}, {TargetModuleType.ColorizeObject()})";
}
}
}
}