Faction Test 100.4.0.0
This commit is contained in:
@@ -110,7 +110,7 @@ namespace Barotrauma
|
||||
state = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) return;
|
||||
if (!Submarine.MainSub.AtEitherExit) { return; }
|
||||
|
||||
Finish();
|
||||
state = 2;
|
||||
|
||||
@@ -25,11 +25,14 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool RequireEquipped { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool Recursive { get; set; }
|
||||
|
||||
[Serialize(-1, IsPropertySaveable.Yes)]
|
||||
public int ItemContainerIndex { get; set; }
|
||||
|
||||
private readonly IReadOnlyList<PropertyConditional> conditionals;
|
||||
|
||||
|
||||
private readonly Identifier[] itemIdentifierSplit;
|
||||
private readonly Identifier[] itemTags;
|
||||
|
||||
@@ -97,7 +100,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (inventory == null) { return false; }
|
||||
int count = 0;
|
||||
foreach (Item item in inventory.FindAllItems(it => itemTags.Any(it.HasTag) || itemIdentifierSplit.Contains(it.Prefab.Identifier)))
|
||||
foreach (Item item in inventory.FindAllItems(it => itemTags.Any(it.HasTag) || itemIdentifierSplit.Contains(it.Prefab.Identifier), recursive: Recursive))
|
||||
{
|
||||
if (!ConditionalsMatch(item, character)) { continue; }
|
||||
count++;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
class CheckMissionAction : BinaryOptionAction
|
||||
{
|
||||
public enum MissionType
|
||||
{
|
||||
Current,
|
||||
Selected,
|
||||
Available
|
||||
}
|
||||
|
||||
[Serialize(MissionType.Current, IsPropertySaveable.Yes)]
|
||||
public MissionType Type { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier MissionIdentifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier MissionTag { get; set; }
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes)]
|
||||
public int MissionCount { get; set; }
|
||||
|
||||
public CheckMissionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
MissionCount = Math.Max(MissionCount, 0);
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
var missions = Type switch
|
||||
{
|
||||
MissionType.Current => GameMain.GameSession?.Missions,
|
||||
MissionType.Selected => GameMain.GameSession?.Campaign?.Missions,
|
||||
MissionType.Available => GameMain.GameSession?.Map?.CurrentLocation?.AvailableMissions,
|
||||
_ => null
|
||||
};
|
||||
if (missions is not null)
|
||||
{
|
||||
if (!MissionIdentifier.IsEmpty)
|
||||
{
|
||||
return missions.Any(m => m.Prefab.Identifier == MissionIdentifier);
|
||||
}
|
||||
else if (!MissionTag.IsEmpty)
|
||||
{
|
||||
return missions.Count(m => m.Prefab.Tags.Contains(MissionTag.Value)) >= MissionCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
return missions.Count() >= MissionCount;
|
||||
}
|
||||
}
|
||||
return MissionIdentifier.IsEmpty && MissionTag.IsEmpty && MissionCount == 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace Barotrauma;
|
||||
|
||||
partial class CheckObjectiveAction : BinaryOptionAction
|
||||
{
|
||||
public CheckObjectiveAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
bool success = false;
|
||||
DetermineSuccessProjSpecific(ref success);
|
||||
return success;
|
||||
}
|
||||
|
||||
partial void DetermineSuccessProjSpecific(ref bool success);
|
||||
}
|
||||
@@ -1,7 +1,15 @@
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class CheckOrderAction : BinaryOptionAction
|
||||
{
|
||||
public enum OrderPriority
|
||||
{
|
||||
Top,
|
||||
Any
|
||||
}
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
@@ -14,35 +22,58 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier OrderTargetTag { get; set; }
|
||||
|
||||
[Serialize(OrderPriority.Top, IsPropertySaveable.Yes)]
|
||||
public OrderPriority Priority { get; set; }
|
||||
|
||||
public CheckOrderAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
Character targetCharacter = null;
|
||||
if (!TargetTag.IsEmpty)
|
||||
var targetCharacters = ParentEvent.GetTargets(TargetTag);
|
||||
if (targetCharacters.None())
|
||||
{
|
||||
foreach (var t in ParentEvent.GetTargets(TargetTag))
|
||||
{
|
||||
if (t is Character c)
|
||||
{
|
||||
targetCharacter = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetCharacter == null)
|
||||
{
|
||||
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target character was found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
|
||||
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckOrderAction but no valid target characters were found for tag \"{TargetTag}\"! This will cause the check to automatically fail.");
|
||||
return false;
|
||||
}
|
||||
var currentOrderInfo = targetCharacter.GetCurrentOrderWithTopPriority();
|
||||
if (currentOrderInfo?.Identifier == OrderIdentifier)
|
||||
foreach (var t in targetCharacters)
|
||||
{
|
||||
if (!OrderTargetTag.IsEmpty)
|
||||
if (t is not Character c)
|
||||
{
|
||||
if (currentOrderInfo.TargetEntity is not Item targetItem || !targetItem.HasTag(OrderTargetTag)) { return false; }
|
||||
continue;
|
||||
}
|
||||
if (Priority == OrderPriority.Top)
|
||||
{
|
||||
if (c.GetCurrentOrderWithTopPriority() is Order topPrioOrder && IsMatch(topPrioOrder))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (Priority == OrderPriority.Any)
|
||||
{
|
||||
foreach (var order in c.CurrentOrders)
|
||||
{
|
||||
if (IsMatch(order))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool IsMatch(Order order)
|
||||
{
|
||||
if (order?.Identifier == OrderIdentifier)
|
||||
{
|
||||
if (!OrderTargetTag.IsEmpty && (order.TargetEntity is not Item targetItem || !targetItem.HasTag(OrderTargetTag)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (OrderOption.IsEmpty || order?.Option == OrderOption)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return OrderOption.IsEmpty || currentOrderInfo?.Option == OrderOption;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma;
|
||||
|
||||
class CheckPurchasedItemsAction : BinaryOptionAction
|
||||
{
|
||||
public enum TransactionType
|
||||
{
|
||||
Purchased,
|
||||
Sold
|
||||
}
|
||||
|
||||
[Serialize(TransactionType.Purchased, IsPropertySaveable.Yes)]
|
||||
public TransactionType Type { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ItemIdentifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ItemTag { get; set; }
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes)]
|
||||
public int MinCount { get; set; }
|
||||
|
||||
public CheckPurchasedItemsAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
MinCount = Math.Max(MinCount, 1);
|
||||
}
|
||||
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
if (ItemIdentifier.IsEmpty && ItemTag.IsEmpty)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (GameMain.GameSession?.Campaign?.CargoManager is not CargoManager cargoManager)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (Type == TransactionType.Purchased)
|
||||
{
|
||||
int totalPurchased = 0;
|
||||
foreach ((Identifier id, var items) in cargoManager.PurchasedItems)
|
||||
{
|
||||
if (!ItemIdentifier.IsEmpty)
|
||||
{
|
||||
totalPurchased += items.Find(i => i.ItemPrefabIdentifier == ItemIdentifier)?.Quantity ?? 0;
|
||||
}
|
||||
else if (!ItemTag.IsEmpty)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (item.ItemPrefab.Tags.Contains(ItemTag))
|
||||
{
|
||||
totalPurchased += item.Quantity;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (totalPurchased >= MinCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int totalSold = 0;
|
||||
foreach ((Identifier id, var items) in cargoManager.SoldItems)
|
||||
{
|
||||
if (!ItemIdentifier.IsEmpty)
|
||||
{
|
||||
totalSold += items.Count(i => i.ItemPrefab.Identifier == ItemIdentifier);
|
||||
}
|
||||
else if (!ItemTag.IsEmpty)
|
||||
{
|
||||
totalSold += items.Count(i => i.ItemPrefab.Tags.Contains(ItemTag));
|
||||
}
|
||||
if (totalSold >= MinCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+40
-35
@@ -59,7 +59,10 @@ namespace Barotrauma
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool ContinueConversation { get; set; }
|
||||
|
||||
public Character speaker
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool IgnoreInterruptDistance { get; set; }
|
||||
|
||||
public Character Speaker
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -124,7 +127,7 @@ namespace Barotrauma
|
||||
#else
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (c.InGame && c.Character != null) { ServerWrite(speaker, c, interrupt); }
|
||||
if (c.InGame && c.Character != null) { ServerWrite(Speaker, c, interrupt); }
|
||||
}
|
||||
#endif
|
||||
ResetSpeaker();
|
||||
@@ -160,7 +163,7 @@ namespace Barotrauma
|
||||
selectedOption = -1;
|
||||
interrupt = false;
|
||||
dialogOpened = false;
|
||||
speaker = null;
|
||||
Speaker = null;
|
||||
}
|
||||
|
||||
public override bool SetGoToTarget(string goTo)
|
||||
@@ -181,15 +184,14 @@ namespace Barotrauma
|
||||
|
||||
private void ResetSpeaker()
|
||||
{
|
||||
if (speaker == null) { return; }
|
||||
speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
speaker.ActiveConversation = null;
|
||||
speaker.SetCustomInteract(null, null);
|
||||
if (Speaker == null) { return; }
|
||||
Speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
|
||||
Speaker.ActiveConversation = null;
|
||||
Speaker.SetCustomInteract(null, null);
|
||||
#if SERVER
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new Character.AssignCampaignInteractionEventData());
|
||||
GameMain.NetworkMember.CreateEntityEvent(Speaker, new Character.AssignCampaignInteractionEventData());
|
||||
#endif
|
||||
var humanAI = speaker.AIController as HumanAIController;
|
||||
if (humanAI != null && !speaker.IsDead && !speaker.Removed)
|
||||
if (Speaker.AIController is HumanAIController humanAI && !Speaker.IsDead && !Speaker.Removed)
|
||||
{
|
||||
humanAI.ClearForcedOrder();
|
||||
if (prevIdleObjective != null) { humanAI.ObjectiveManager.AddObjective(prevIdleObjective); }
|
||||
@@ -207,7 +209,6 @@ namespace Barotrauma
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
lastActiveTime = Timing.TotalTime;
|
||||
if (interrupt)
|
||||
{
|
||||
Interrupted?.Update(deltaTime);
|
||||
@@ -216,6 +217,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (dialogOpened)
|
||||
{
|
||||
lastActiveTime = Timing.TotalTime;
|
||||
#if CLIENT
|
||||
if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "ConversationAction"))
|
||||
{
|
||||
@@ -226,7 +228,7 @@ namespace Barotrauma
|
||||
Reset();
|
||||
}
|
||||
#endif
|
||||
if (ShouldInterrupt())
|
||||
if (ShouldInterrupt(requireTarget: true))
|
||||
{
|
||||
ResetSpeaker();
|
||||
interrupt = true;
|
||||
@@ -236,34 +238,34 @@ namespace Barotrauma
|
||||
|
||||
if (!SpeakerTag.IsEmpty)
|
||||
{
|
||||
if (speaker != null && !speaker.Removed && speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && speaker.ActiveConversation?.ParentEvent != this.ParentEvent) { return; }
|
||||
speaker = ParentEvent.GetTargets(SpeakerTag).FirstOrDefault(e => e is Character) as Character;
|
||||
if (speaker == null || speaker.Removed)
|
||||
if (Speaker != null && !Speaker.Removed && Speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && Speaker.ActiveConversation?.ParentEvent != this.ParentEvent) { 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 && speaker.ActiveConversation?.ParentEvent != this.ParentEvent)
|
||||
if (Speaker.CampaignInteractionType == CampaignMode.InteractionType.Talk && Speaker.ActiveConversation?.ParentEvent != this.ParentEvent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (!WaitForInteraction)
|
||||
{
|
||||
TryStartConversation(speaker);
|
||||
TryStartConversation(Speaker);
|
||||
}
|
||||
else if (speaker.ActiveConversation != this)
|
||||
else if (Speaker.ActiveConversation != this)
|
||||
{
|
||||
speaker.CampaignInteractionType = CampaignMode.InteractionType.Talk;
|
||||
speaker.ActiveConversation = this;
|
||||
Speaker.CampaignInteractionType = CampaignMode.InteractionType.Talk;
|
||||
Speaker.ActiveConversation = this;
|
||||
#if CLIENT
|
||||
speaker.SetCustomInteract(
|
||||
Speaker.SetCustomInteract(
|
||||
TryStartConversation,
|
||||
TextManager.GetWithVariable("CampaignInteraction.Talk", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use)));
|
||||
#else
|
||||
speaker.SetCustomInteract(
|
||||
Speaker.SetCustomInteract(
|
||||
TryStartConversation,
|
||||
TextManager.Get("CampaignInteraction.Talk"));
|
||||
GameMain.NetworkMember.CreateEntityEvent(speaker, new Character.AssignCampaignInteractionEventData());
|
||||
GameMain.NetworkMember.CreateEntityEvent(Speaker, new Character.AssignCampaignInteractionEventData());
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
@@ -275,7 +277,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ShouldInterrupt())
|
||||
//after the conversation has been finished and the target character assigned,
|
||||
//we no longer care if we still have a target
|
||||
if (ShouldInterrupt(requireTarget: false))
|
||||
{
|
||||
ResetSpeaker();
|
||||
interrupt = true;
|
||||
@@ -287,35 +291,36 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldInterrupt()
|
||||
private bool ShouldInterrupt(bool requireTarget)
|
||||
{
|
||||
IEnumerable<Entity> targets = Enumerable.Empty<Entity>();
|
||||
if (!TargetTag.IsEmpty)
|
||||
if (!TargetTag.IsEmpty && requireTarget)
|
||||
{
|
||||
targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e));
|
||||
targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e, requireTarget));
|
||||
if (!targets.Any()) { return true; }
|
||||
}
|
||||
|
||||
if (speaker != null)
|
||||
if (Speaker != null)
|
||||
{
|
||||
if (!TargetTag.IsEmpty)
|
||||
if (!TargetTag.IsEmpty && requireTarget && !IgnoreInterruptDistance)
|
||||
{
|
||||
if (targets.All(t => Vector2.DistanceSquared(t.WorldPosition, speaker.WorldPosition) > InterruptDistance * InterruptDistance)) { return true; }
|
||||
if (targets.All(t => Vector2.DistanceSquared(t.WorldPosition, Speaker.WorldPosition) > InterruptDistance * InterruptDistance)) { return true; }
|
||||
}
|
||||
if (speaker.AIController is HumanAIController humanAI && !humanAI.AllowCampaignInteraction())
|
||||
if (Speaker.AIController is HumanAIController humanAI && !humanAI.AllowCampaignInteraction())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return speaker.Removed || speaker.IsDead || speaker.IsIncapacitated;
|
||||
return Speaker.Removed || Speaker.IsDead || Speaker.IsIncapacitated;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsValidTarget(Entity e)
|
||||
private bool IsValidTarget(Entity e, bool requirePlayerControlled = true)
|
||||
{
|
||||
bool isValid = e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
|
||||
(e == Character.Controlled || character.IsRemotePlayer);
|
||||
bool isValid =
|
||||
e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
|
||||
(character == Character.Controlled || character.IsRemotePlayer || !requirePlayerControlled);
|
||||
#if SERVER
|
||||
if (!dialogOpened)
|
||||
{
|
||||
|
||||
@@ -40,7 +40,8 @@ namespace Barotrauma
|
||||
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));
|
||||
var action = Instantiate(scriptedEvent, e);
|
||||
if (action != null) { Actions.Add(action); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +150,10 @@ namespace Barotrauma
|
||||
ConstructorInfo constructor = actionType.GetConstructor(new[] { typeof(ScriptedEvent), typeof(ContentXElement) });
|
||||
try
|
||||
{
|
||||
if (constructor == null)
|
||||
{
|
||||
throw new Exception($"Error in scripted event \"{scriptedEvent.Prefab.Identifier}\" - could not find a constructor for the EventAction \"{actionType}\".");
|
||||
}
|
||||
return constructor.Invoke(new object[] { scriptedEvent, element }) as EventAction;
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -49,6 +49,12 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ObjectiveTag { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool ObjectiveCanBeCompleted { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ParentObjectiveId { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public MessageBoxAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -14,8 +16,10 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier MissionTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "The type of the location the mission will be unlocked in (if empty, any location can be selected).")]
|
||||
public string LocationType { get; set; }
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier RequiredFaction { get; set; }
|
||||
|
||||
public ImmutableArray<Identifier> LocationTypes { get; }
|
||||
|
||||
[Serialize(0, IsPropertySaveable.Yes, description: "Minimum distance to the location the mission is unlocked in (1 = one path between locations).")]
|
||||
public int MinLocationDistance { get; set; }
|
||||
@@ -38,6 +42,7 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": both MissionIdentifier or MissionTag have been configured. The tag will be ignored.");
|
||||
}
|
||||
LocationTypes = element.GetAttributeIdentifierArray("locationtype", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
@@ -56,14 +61,14 @@ namespace Barotrauma
|
||||
if (GameMain.GameSession.GameMode is CampaignMode campaign)
|
||||
{
|
||||
Mission unlockedMission = null;
|
||||
var unlockLocation = FindUnlockLocation();
|
||||
var unlockLocation = FindUnlockLocation(MinLocationDistance, UnlockFurtherOnMap, LocationTypes);
|
||||
if (unlockLocation == null && CreateLocationIfNotFound)
|
||||
{
|
||||
//find an empty location at least 3 steps away, further on the map
|
||||
var emptyLocation = FindUnlockLocationRecursive(campaign.Map.CurrentLocation, Math.Max(MinLocationDistance, 3), "none", true, new HashSet<Location>());
|
||||
var emptyLocation = FindUnlockLocation(Math.Max(MinLocationDistance, 3), unlockFurtherOnMap: true, "none".ToIdentifier().ToEnumerable());
|
||||
if (emptyLocation != null)
|
||||
{
|
||||
emptyLocation.ChangeType(Barotrauma.LocationType.Prefabs[LocationType]);
|
||||
emptyLocation.ChangeType(campaign, Barotrauma.LocationType.Prefabs[LocationTypes[0]]);
|
||||
unlockLocation = emptyLocation;
|
||||
}
|
||||
}
|
||||
@@ -84,6 +89,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (unlockedMission != null)
|
||||
{
|
||||
campaign.Map.Discover(unlockLocation, checkTalents: false);
|
||||
if (unlockedMission.Locations[0] == unlockedMission.Locations[1] || unlockedMission.Locations[1] ==null)
|
||||
{
|
||||
DebugConsole.NewMessage($"Unlocked mission \"{unlockedMission.Name}\" in the location \"{unlockLocation.Name}\".");
|
||||
@@ -99,46 +105,80 @@ namespace Barotrauma
|
||||
IconColor = unlockedMission.Prefab.IconColor
|
||||
};
|
||||
#else
|
||||
NotifyMissionUnlock(unlockedMission);
|
||||
#endif
|
||||
NotifyMissionUnlock(unlockedMission, unlockLocation);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to find a suitable location to unlock a mission in (LocationType: {LocationType}, MinLocationDistance: {MinLocationDistance}, UnlockFurtherOnMap: {UnlockFurtherOnMap})");
|
||||
DebugConsole.AddWarning($"Failed to find a suitable location to unlock a mission in (LocationType: {LocationTypes}, MinLocationDistance: {MinLocationDistance}, UnlockFurtherOnMap: {UnlockFurtherOnMap})");
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
private Location FindUnlockLocation()
|
||||
private Location FindUnlockLocation(int minDistance, bool unlockFurtherOnMap, IEnumerable<Identifier> locationTypes)
|
||||
{
|
||||
var campaign = GameMain.GameSession.GameMode as CampaignMode;
|
||||
if (string.IsNullOrEmpty(LocationType) && MinLocationDistance <= 1)
|
||||
if (LocationTypes.Length == 0 && minDistance <= 1)
|
||||
{
|
||||
return campaign.Map.CurrentLocation;
|
||||
}
|
||||
|
||||
return FindUnlockLocationRecursive(campaign.Map.CurrentLocation, 0, LocationType, UnlockFurtherOnMap, new HashSet<Location>());
|
||||
var currentLocation = campaign.Map.CurrentLocation;
|
||||
int distance = 0;
|
||||
HashSet<Location> checkedLocations = new HashSet<Location>();
|
||||
HashSet<Location> pendingLocations = new HashSet<Location>() { currentLocation };
|
||||
do
|
||||
{
|
||||
List<Location> currentLocations = pendingLocations.ToList();
|
||||
pendingLocations.Clear();
|
||||
foreach (var location in currentLocations)
|
||||
{
|
||||
checkedLocations.Add(location);
|
||||
if (IsLocationValid(currentLocation, location, unlockFurtherOnMap, distance, minDistance, locationTypes))
|
||||
{
|
||||
return location;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (LocationConnection connection in location.Connections)
|
||||
{
|
||||
var otherLocation = connection.OtherLocation(location);
|
||||
if (checkedLocations.Contains(otherLocation)) { continue; }
|
||||
pendingLocations.Add(otherLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
distance++;
|
||||
} while (pendingLocations.Any());
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Location FindUnlockLocationRecursive(Location currLocation, int currDistance, string locationType, bool unlockFurtherOnMap, HashSet<Location> checkedLocations)
|
||||
private bool IsLocationValid(Location currLocation, Location location, bool unlockFurtherOnMap, int distance, int minDistance, IEnumerable<Identifier> locationTypes)
|
||||
{
|
||||
var campaign = GameMain.GameSession.GameMode as CampaignMode;
|
||||
if (currLocation.Type.Identifier == locationType && currDistance >= MinLocationDistance &&
|
||||
(!unlockFurtherOnMap || currLocation.MapPosition.X > campaign.Map.CurrentLocation.MapPosition.X))
|
||||
if (!RequiredFaction.IsEmpty)
|
||||
{
|
||||
return currLocation;
|
||||
if (location.Faction?.Prefab.Identifier != RequiredFaction &&
|
||||
location.SecondaryFaction?.Prefab.Identifier != RequiredFaction)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
checkedLocations.Add(currLocation);
|
||||
foreach (LocationConnection connection in currLocation.Connections)
|
||||
if (!locationTypes.Contains(location.Type.Identifier) && !(location.HasOutpost() && locationTypes.Contains("AnyOutpost".ToIdentifier())))
|
||||
{
|
||||
var otherLocation = connection.OtherLocation(currLocation);
|
||||
if (checkedLocations.Contains(otherLocation)) { continue; }
|
||||
var unlockLocation = FindUnlockLocationRecursive(otherLocation, ++currDistance, locationType, unlockFurtherOnMap, checkedLocations);
|
||||
if (unlockLocation != null) { return unlockLocation; }
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
if (distance < minDistance)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (unlockFurtherOnMap && location.MapPosition.X < currLocation.MapPosition.X)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
@@ -147,7 +187,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
private void NotifyMissionUnlock(Mission mission)
|
||||
private static void NotifyMissionUnlock(Mission mission, Location unlockLocation)
|
||||
{
|
||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
@@ -155,6 +195,7 @@ namespace Barotrauma
|
||||
outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION);
|
||||
outmsg.WriteByte((byte)EventManager.NetworkEventType.MISSION);
|
||||
outmsg.WriteIdentifier(mission.Prefab.Identifier);
|
||||
outmsg.WriteInt32(GameMain.GameSession?.Map?.Locations.IndexOf(unlockLocation) ?? -1);
|
||||
outmsg.WriteString(mission.Name.Value);
|
||||
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MissionStateAction : EventAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier MissionIdentifier { get; set; }
|
||||
|
||||
public enum OperationType
|
||||
{
|
||||
Set,
|
||||
Add
|
||||
}
|
||||
|
||||
[Serialize(OperationType.Set, IsPropertySaveable.Yes)]
|
||||
public OperationType Operation { get; set; }
|
||||
|
||||
[Serialize(0, IsPropertySaveable.Yes)]
|
||||
public int State { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public MissionStateAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
State = element.GetAttributeInt("value", State);
|
||||
if (MissionIdentifier.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": MissionIdentifier has not been configured.");
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
{
|
||||
return isFinished;
|
||||
}
|
||||
public override void Reset()
|
||||
{
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
|
||||
foreach (Mission mission in GameMain.GameSession.Missions)
|
||||
{
|
||||
if (mission.Prefab.Identifier != MissionIdentifier) { continue; }
|
||||
switch (Operation)
|
||||
{
|
||||
case OperationType.Set:
|
||||
mission.State = State;
|
||||
break;
|
||||
case OperationType.Add:
|
||||
mission.State += 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MissionStateAction)} -> ({(Operation == OperationType.Set ? State : '+' + State)})";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ModifyLocationAction : EventAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier Faction { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier SecondaryFaction { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier Type { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public string Name { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public ModifyLocationAction(ScriptedEvent parentEvent, ContentXElement 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.GameMode is CampaignMode campaign)
|
||||
{
|
||||
var location = campaign.Map.CurrentLocation;
|
||||
if (location != null)
|
||||
{
|
||||
if (!Faction.IsEmpty)
|
||||
{
|
||||
var faction = campaign.Factions.Find(f => f.Prefab.Identifier == Faction);
|
||||
if (faction == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a faction with the identifier \"{Faction}\".");
|
||||
}
|
||||
else
|
||||
{
|
||||
location.Faction = faction;
|
||||
}
|
||||
}
|
||||
if (!SecondaryFaction.IsEmpty)
|
||||
{
|
||||
var secondaryFaction = campaign.Factions.Find(f => f.Prefab.Identifier == SecondaryFaction);
|
||||
if (secondaryFaction == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a faction with the identifier \"{SecondaryFaction}\".");
|
||||
}
|
||||
else
|
||||
{
|
||||
location.SecondaryFaction = secondaryFaction;
|
||||
}
|
||||
}
|
||||
if (!Type.IsEmpty)
|
||||
{
|
||||
var locationType = LocationType.Prefabs.Find(lt => lt.Identifier == Type);
|
||||
if (locationType == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in ModifyLocationAction ({ParentEvent.Prefab.Identifier}): could not find a location type with the identifier \"{Type}\".");
|
||||
}
|
||||
else
|
||||
{
|
||||
location.ChangeType(campaign, locationType);
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(Name))
|
||||
{
|
||||
location.ForceName(TextManager.Get(Name).Fallback(Name).Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(ModifyLocationAction)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
-13
@@ -1,7 +1,6 @@
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -11,7 +10,7 @@ namespace Barotrauma
|
||||
public Identifier NPCTag { get; set; }
|
||||
|
||||
[Serialize(CharacterTeamType.None, IsPropertySaveable.Yes)]
|
||||
public CharacterTeamType TeamTag { get; set; }
|
||||
public CharacterTeamType TeamID { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool AddToCrew { get; set; }
|
||||
@@ -21,7 +20,17 @@ namespace Barotrauma
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public NPCChangeTeamAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
public NPCChangeTeamAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
//backwards compatibility
|
||||
TeamID = element.GetAttributeEnum("teamtag", element.GetAttributeEnum<CharacterTeamType>("team", TeamID));
|
||||
|
||||
var enums = Enum.GetValues(typeof(CharacterTeamType)).Cast<CharacterTeamType>();
|
||||
if (!enums.Contains(TeamID))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in {nameof(NPCChangeTeamAction)} in the event {ParentEvent.Prefab.Identifier}. \"{TeamID}\" is not a valid Team ID. Valid values are {string.Join(',', Enum.GetNames(typeof(CharacterTeamType)))}.");
|
||||
}
|
||||
}
|
||||
|
||||
private List<Character> affectedNpcs = null;
|
||||
|
||||
@@ -33,42 +42,48 @@ namespace Barotrauma
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
// characters will still remain on friendlyNPC team for rest of the tick
|
||||
npc.SetOriginalTeam(TeamTag);
|
||||
|
||||
if (AddToCrew && (TeamTag == CharacterTeamType.Team1 || TeamTag == CharacterTeamType.Team2))
|
||||
npc.SetOriginalTeam(TeamID);
|
||||
foreach (Item item in npc.Inventory.AllItems)
|
||||
{
|
||||
var idCard = item.GetComponent<Items.Components.IdCard>();
|
||||
if (idCard != null)
|
||||
{
|
||||
idCard.TeamID = TeamID;
|
||||
}
|
||||
}
|
||||
if (AddToCrew && (TeamID == CharacterTeamType.Team1 || TeamID == CharacterTeamType.Team2))
|
||||
{
|
||||
npc.Info.StartItemsGiven = true;
|
||||
GameMain.GameSession.CrewManager.AddCharacter(npc);
|
||||
ChangeItemTeam(Submarine.MainSub, true);
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AddToCrewEventData(TeamTag, npc.Inventory.AllItems));
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.AddToCrewEventData(TeamID, npc.Inventory.AllItems));
|
||||
}
|
||||
}
|
||||
else if (RemoveFromCrew && (npc.TeamID == CharacterTeamType.Team1 || npc.TeamID == CharacterTeamType.Team2))
|
||||
{
|
||||
npc.Info.StartItemsGiven = true;
|
||||
GameMain.GameSession.CrewManager.RemoveCharacter(npc, removeInfo: true);
|
||||
var sub = Submarine.Loaded.FirstOrDefault(s => s.TeamID == TeamTag);
|
||||
var sub = Submarine.Loaded.FirstOrDefault(s => s.TeamID == TeamID);
|
||||
ChangeItemTeam(sub, false);
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.RemoveFromCrewEventData(TeamTag, npc.Inventory.AllItems));
|
||||
GameMain.NetworkMember.CreateEntityEvent(npc, new Character.RemoveFromCrewEventData(TeamID, npc.Inventory.AllItems));
|
||||
}
|
||||
}
|
||||
|
||||
void ChangeItemTeam(Submarine sub, bool allowStealing)
|
||||
{
|
||||
foreach (Item item in npc.Inventory.AllItems)
|
||||
foreach (Item item in npc.Inventory.FindAllItems(recursive: true))
|
||||
{
|
||||
item.AllowStealing = allowStealing;
|
||||
if (item.GetComponent<Items.Components.WifiComponent>() is { } wifiComponent)
|
||||
{
|
||||
wifiComponent.TeamID = TeamTag;
|
||||
wifiComponent.TeamID = TeamID;
|
||||
}
|
||||
if (item.GetComponent<Items.Components.IdCard>() is { } idCard)
|
||||
{
|
||||
idCard.TeamID = TeamTag;
|
||||
idCard.SubmarineSpecificID = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,13 +39,14 @@ namespace Barotrauma
|
||||
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 (npc.AIController is not HumanAIController humanAiController) { continue; }
|
||||
|
||||
if (Follow)
|
||||
{
|
||||
var newObjective = new AIObjectiveGoTo(target, npc, humanAiController.ObjectiveManager, repeat: true)
|
||||
{
|
||||
OverridePriority = 100.0f
|
||||
OverridePriority = 100.0f,
|
||||
IsFollowOrderObjective = true
|
||||
};
|
||||
humanAiController.ObjectiveManager.AddObjective(newObjective);
|
||||
humanAiController.ObjectiveManager.WaitTimer = 0.0f;
|
||||
|
||||
@@ -19,8 +19,6 @@ namespace Barotrauma
|
||||
|
||||
private IEnumerable<Character> affectedNpcs;
|
||||
|
||||
private AIObjectiveGoTo gotoObjective;
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
@@ -33,19 +31,17 @@ namespace Barotrauma
|
||||
|
||||
if (Wait)
|
||||
{
|
||||
gotoObjective = new AIObjectiveGoTo(npc, npc, humanAiController.ObjectiveManager, repeat: true)
|
||||
var gotoObjective = new AIObjectiveGoTo(npc, npc, humanAiController.ObjectiveManager, repeat: true)
|
||||
{
|
||||
OverridePriority = 100.0f
|
||||
OverridePriority = 100.0f,
|
||||
SourceEventAction = this
|
||||
};
|
||||
humanAiController.ObjectiveManager.AddObjective(gotoObjective);
|
||||
humanAiController.ObjectiveManager.WaitTimer = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (gotoObjective != null)
|
||||
{
|
||||
gotoObjective.Abandon = true;
|
||||
}
|
||||
AbandonGoToObjectives(humanAiController);
|
||||
}
|
||||
}
|
||||
isFinished = true;
|
||||
@@ -62,17 +58,25 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (var npc in affectedNpcs)
|
||||
{
|
||||
if (npc.Removed || npc.AIController is not HumanAIController) { continue; }
|
||||
if (gotoObjective != null)
|
||||
{
|
||||
gotoObjective.Abandon = true;
|
||||
}
|
||||
if (npc.Removed || npc.AIController is not HumanAIController aiController) { continue; }
|
||||
AbandonGoToObjectives(aiController);
|
||||
}
|
||||
affectedNpcs = null;
|
||||
}
|
||||
isFinished = false;
|
||||
}
|
||||
|
||||
private void AbandonGoToObjectives(HumanAIController aiController)
|
||||
{
|
||||
foreach (var objective in aiController.ObjectiveManager.Objectives)
|
||||
{
|
||||
if (objective is AIObjectiveGoTo gotoObjective && gotoObjective.SourceEventAction?.ParentEvent == ParentEvent)
|
||||
{
|
||||
gotoObjective.Abandon = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(NPCWaitAction)} -> (NPCTag: {NPCTag.ColorizeObject()}, Wait: {Wait.ColorizeObject()})";
|
||||
|
||||
@@ -46,43 +46,29 @@ namespace Barotrauma
|
||||
switch (TargetType)
|
||||
{
|
||||
case ReputationType.Faction:
|
||||
{
|
||||
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier == Identifier);
|
||||
if (faction != null)
|
||||
{
|
||||
faction.Reputation.AddReputation(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.AddReputation(Increase);
|
||||
IEnumerable<Location> locations = location.Connections.SelectMany(c => c.Locations).Distinct().Where(l => l != null && l != location);
|
||||
foreach (Location connectedLocation in locations)
|
||||
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier == Identifier);
|
||||
if (faction != null)
|
||||
{
|
||||
Debug.Assert(connectedLocation.Reputation != null, "connectedLocation.Reputation != null");
|
||||
if (connectedLocation.Reputation != null)
|
||||
{
|
||||
connectedLocation.Reputation.AddReputation(Increase / 4);
|
||||
}
|
||||
faction.Reputation.AddReputation(Increase);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Faction with the identifier \"{Identifier}\" was not found.");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ReputationType.Location:
|
||||
{
|
||||
campaign.Map.CurrentLocation?.Reputation?.AddReputation(Increase);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
DebugConsole.ThrowError("ReputationAction requires a \"TargetType\" but none were specified.");
|
||||
break;
|
||||
}
|
||||
{
|
||||
DebugConsole.ThrowError("ReputationAction requires a \"TargetType\" but none were specified.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -11,6 +10,7 @@ namespace Barotrauma
|
||||
{
|
||||
public enum SpawnLocationType
|
||||
{
|
||||
Any,
|
||||
MainSub,
|
||||
Outpost,
|
||||
MainPath,
|
||||
@@ -40,7 +40,7 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag of an entity with an inventory to spawn the item into.")]
|
||||
public Identifier TargetInventory { get; set; }
|
||||
|
||||
[Serialize(SpawnLocationType.MainSub, IsPropertySaveable.Yes)]
|
||||
[Serialize(SpawnLocationType.Any, IsPropertySaveable.Yes)]
|
||||
public SpawnLocationType SpawnLocation { get; set; }
|
||||
|
||||
[Serialize(SpawnType.Human, IsPropertySaveable.Yes)]
|
||||
@@ -50,7 +50,7 @@ namespace Barotrauma
|
||||
public Identifier SpawnPointTag { get; set; }
|
||||
|
||||
[Serialize(CharacterTeamType.FriendlyNPC, IsPropertySaveable.Yes)]
|
||||
public CharacterTeamType Team { get; protected set; }
|
||||
public CharacterTeamType TeamID { get; protected set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes, description: "Should we spawn the entity even when no spawn points with matching tags were found?")]
|
||||
public bool RequireSpawnPointTag { get; set; }
|
||||
@@ -92,6 +92,8 @@ namespace Barotrauma
|
||||
public SpawnAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
ignoreSpawnPointType = element.GetAttribute("spawnpointtype") == null;
|
||||
//backwards compatibility
|
||||
TeamID = element.GetAttributeEnum("teamtag", element.GetAttributeEnum<CharacterTeamType>("team", TeamID));
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
@@ -118,7 +120,28 @@ namespace Barotrauma
|
||||
|
||||
if (!NPCSetIdentifier.IsEmpty && !NPCIdentifier.IsEmpty)
|
||||
{
|
||||
HumanPrefab humanPrefab = NPCSet.Get(NPCSetIdentifier, NPCIdentifier);
|
||||
HumanPrefab humanPrefab = null;
|
||||
if (Level.Loaded?.StartLocation is Location startLocation)
|
||||
{
|
||||
humanPrefab =
|
||||
TryFindHumanPrefab(startLocation.Faction) ??
|
||||
TryFindHumanPrefab(startLocation.SecondaryFaction);
|
||||
}
|
||||
HumanPrefab TryFindHumanPrefab(Faction faction)
|
||||
{
|
||||
if (faction == null) { return null; }
|
||||
return
|
||||
NPCSet.Get(NPCSetIdentifier,
|
||||
NPCIdentifier.Replace("[faction]".ToIdentifier(), faction.Prefab.Identifier),
|
||||
logError: false) ??
|
||||
//try to spawn a coalition NPC if a correct one can't be found
|
||||
NPCSet.Get(NPCSetIdentifier,
|
||||
NPCIdentifier.Replace("[faction]".ToIdentifier(), "coalition".ToIdentifier()),
|
||||
logError: false);
|
||||
}
|
||||
|
||||
humanPrefab ??= NPCSet.Get(NPCSetIdentifier, NPCIdentifier, logError: true);
|
||||
|
||||
if (humanPrefab != null)
|
||||
{
|
||||
if (!AllowDuplicates &&
|
||||
@@ -130,11 +153,11 @@ namespace Barotrauma
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Offset), humanPrefab.CreateCharacterInfo(), onSpawn: newCharacter =>
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), humanPrefab.CreateCharacterInfo(), onSpawn: newCharacter =>
|
||||
{
|
||||
if (newCharacter == null) { return; }
|
||||
newCharacter.HumanPrefab = humanPrefab;
|
||||
newCharacter.TeamID = Team;
|
||||
newCharacter.TeamID = TeamID;
|
||||
newCharacter.EnableDespawn = false;
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine);
|
||||
if (LootingIsStealing)
|
||||
@@ -151,6 +174,14 @@ namespace Barotrauma
|
||||
ParentEvent.AddTarget(TargetTag, newCharacter);
|
||||
}
|
||||
spawnedEntity = newCharacter;
|
||||
if (Level.Loaded?.StartOutpost?.Info is { } outPostInfo)
|
||||
{
|
||||
outPostInfo.AddOutpostNPCIdentifierOrTag(newCharacter, humanPrefab.Identifier);
|
||||
foreach (Identifier tag in humanPrefab.GetTags())
|
||||
{
|
||||
outPostInfo.AddOutpostNPCIdentifierOrTag(newCharacter, tag);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -165,7 +196,7 @@ namespace Barotrauma
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Offset), onSpawn: newCharacter =>
|
||||
Entity.Spawner.AddCharacterToSpawnQueue(SpeciesName, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), onSpawn: newCharacter =>
|
||||
{
|
||||
if (!TargetTag.IsEmpty && newCharacter != null)
|
||||
{
|
||||
@@ -177,7 +208,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (!ItemIdentifier.IsEmpty)
|
||||
{
|
||||
if (!(MapEntityPrefab.FindByIdentifier(ItemIdentifier) is ItemPrefab itemPrefab))
|
||||
if (MapEntityPrefab.FindByIdentifier(ItemIdentifier) is not ItemPrefab itemPrefab)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SpawnAction (item prefab \"" + ItemIdentifier + "\" not found)");
|
||||
}
|
||||
@@ -211,7 +242,7 @@ namespace Barotrauma
|
||||
ISpatialEntity spawnPos = GetSpawnPos();
|
||||
if (spawnPos != null)
|
||||
{
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, Offset), onSpawned: onSpawned);
|
||||
Entity.Spawner.AddItemToSpawnQueue(itemPrefab, OffsetSpawnPos(spawnPos.WorldPosition, Rand.Range(0.0f, Offset)), onSpawned: onSpawned);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -239,10 +270,10 @@ namespace Barotrauma
|
||||
spawned = true;
|
||||
}
|
||||
|
||||
public static Vector2 OffsetSpawnPos(Vector2 pos, float offsetAmount)
|
||||
public static Vector2 OffsetSpawnPos(Vector2 pos, float offset)
|
||||
{
|
||||
Hull hull = Hull.FindHull(pos);
|
||||
pos += Rand.Vector(offsetAmount);
|
||||
Hull hull = Hull.FindHull(pos);
|
||||
pos += Rand.Vector(offset);
|
||||
if (hull != null)
|
||||
{
|
||||
float margin = 50.0f;
|
||||
@@ -257,21 +288,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (!SpawnPointTag.IsEmpty)
|
||||
{
|
||||
List<Item> potentialItems = SpawnLocation switch
|
||||
{
|
||||
SpawnLocationType.MainSub => Item.ItemList.FindAll(it => it.Submarine == Submarine.MainSub),
|
||||
SpawnLocationType.MainPath => Item.ItemList.FindAll(it => it.Submarine == null),
|
||||
SpawnLocationType.Outpost => Item.ItemList.FindAll(it => it.Submarine?.Info != null && it.Submarine.Info.IsOutpost),
|
||||
SpawnLocationType.Wreck => Item.ItemList.FindAll(it => it.Submarine?.Info != null && it.Submarine.Info.IsWreck),
|
||||
SpawnLocationType.Ruin => Item.ItemList.FindAll(it => it.Submarine?.Info != null && it.Submarine.Info.IsRuin),
|
||||
SpawnLocationType.BeaconStation => Item.ItemList.FindAll(it => it.Submarine?.Info != null && it.Submarine.Info.IsBeacon),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
List<Item> potentialItems = Item.ItemList.FindAll(it => IsValidSubmarineType(SpawnLocation, it.Submarine));
|
||||
var item = potentialItems.Where(it => it.HasTag(SpawnPointTag)).GetRandomUnsynced();
|
||||
if (item != null) { return item; }
|
||||
|
||||
var target = ParentEvent.GetTargets(SpawnPointTag).GetRandomUnsynced();
|
||||
var target = ParentEvent.GetTargets(SpawnPointTag).Where(t => IsValidSubmarineType(SpawnLocation, t.Submarine)).GetRandomUnsynced();
|
||||
if (target != null) { return target; }
|
||||
}
|
||||
|
||||
@@ -281,19 +302,26 @@ namespace Barotrauma
|
||||
return GetSpawnPos(SpawnLocation, spawnPointType, targetModuleTags, SpawnPointTag.ToEnumerable(), requireTaggedSpawnPoint: RequireSpawnPointTag);
|
||||
}
|
||||
|
||||
private static bool IsValidSubmarineType(SpawnLocationType spawnLocation, Submarine submarine)
|
||||
{
|
||||
return spawnLocation switch
|
||||
{
|
||||
SpawnLocationType.Any => true,
|
||||
SpawnLocationType.MainSub => submarine == Submarine.MainSub,
|
||||
SpawnLocationType.MainPath => submarine == null,
|
||||
SpawnLocationType.Outpost => submarine is { Info: { IsOutpost: true } },
|
||||
SpawnLocationType.Wreck => submarine is { Info: { IsWreck: true } },
|
||||
SpawnLocationType.Ruin => submarine is { Info: { IsRuin: true } },
|
||||
SpawnLocationType.BeaconStation => submarine?.Info?.BeaconStationInfo != null,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
|
||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<Identifier> moduleFlags = null, IEnumerable<Identifier> spawnpointTags = null, bool asFarAsPossibleFromAirlock = false, bool requireTaggedSpawnPoint = false)
|
||||
{
|
||||
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),
|
||||
SpawnLocationType.Outpost => WayPoint.WayPointList.FindAll(wp => wp.Submarine?.Info != null && wp.CurrentHull != null && wp.Submarine.Info.IsOutpost),
|
||||
SpawnLocationType.Wreck => WayPoint.WayPointList.FindAll(wp => wp.Submarine?.Info != null && wp.Submarine.Info.IsWreck),
|
||||
SpawnLocationType.Ruin => WayPoint.WayPointList.FindAll(wp => wp.Submarine?.Info != null && wp.Submarine.Info.IsRuin),
|
||||
SpawnLocationType.BeaconStation => WayPoint.WayPointList.FindAll(wp => wp.Submarine?.Info != null && wp.Submarine.Info.IsBeacon),
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
bool requireHull = spawnLocation == SpawnLocationType.MainSub || spawnLocation == SpawnLocationType.Outpost;
|
||||
List<WayPoint> potentialSpawnPoints = WayPoint.WayPointList.FindAll(wp => IsValidSubmarineType(spawnLocation, wp.Submarine) && (wp.CurrentHull != null || !requireHull));
|
||||
|
||||
potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor == null && wp.Ladders == null && !wp.isObstructed);
|
||||
|
||||
if (moduleFlags != null && moduleFlags.Any())
|
||||
|
||||
+25
-11
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -7,7 +6,7 @@ namespace Barotrauma
|
||||
{
|
||||
private readonly List<StatusEffect> effects = new List<StatusEffect>();
|
||||
|
||||
private int actionIndex;
|
||||
private readonly int actionIndex;
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetTag { get; set; }
|
||||
@@ -46,25 +45,40 @@ namespace Barotrauma
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) { return; }
|
||||
var targets = ParentEvent.GetTargets(TargetTag);
|
||||
var eventTargets = ParentEvent.GetTargets(TargetTag);
|
||||
foreach (StatusEffect effect in effects)
|
||||
{
|
||||
foreach (var target in targets)
|
||||
foreach (var target in eventTargets)
|
||||
{
|
||||
if (target is Item targetItem)
|
||||
if (effect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, target, targetItem.AllPropertyObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, target, target as ISerializableEntity);
|
||||
List<ISerializableEntity> nearbyTargets = new List<ISerializableEntity>();
|
||||
effect.AddNearbyTargets(target.WorldPosition, nearbyTargets);
|
||||
foreach (var nearbyTarget in nearbyTargets)
|
||||
{
|
||||
ApplyOnTarget(nearbyTarget as Entity, effect);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
ApplyOnTarget(target, effect);
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
ServerWrite(targets);
|
||||
ServerWrite(eventTargets);
|
||||
#endif
|
||||
isFinished = true;
|
||||
|
||||
void ApplyOnTarget(Entity target, StatusEffect effect)
|
||||
{
|
||||
if (target is Item targetItem)
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, target, targetItem.AllPropertyObjects);
|
||||
}
|
||||
else
|
||||
{
|
||||
effect.Apply(effect.type, deltaTime, target, target as ISerializableEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
|
||||
@@ -32,11 +32,13 @@ namespace Barotrauma
|
||||
("bot", v => TagBots(playerCrewOnly: false)),
|
||||
("crew", v => TagCrew()),
|
||||
("humanprefabidentifier", TagHumansByIdentifier),
|
||||
("jobidentifier", TagHumansByJobIdentifier),
|
||||
("structureidentifier", TagStructuresByIdentifier),
|
||||
("structurespecialtag", TagStructuresBySpecialTag),
|
||||
("itemidentifier", TagItemsByIdentifier),
|
||||
("itemtag", TagItemsByTag),
|
||||
("hullname", TagHullsByName)
|
||||
("hullname", TagHullsByName),
|
||||
("submarine", TagSubmarinesByType),
|
||||
}.Select(t => (t.k.ToIdentifier(), t.v)).ToImmutableDictionary();
|
||||
}
|
||||
|
||||
@@ -93,6 +95,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TagHumansByJobIdentifier(Identifier jobIdentifier)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.HasJob(jobIdentifier))
|
||||
{
|
||||
ParentEvent.AddTarget(Tag, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TagStructuresByIdentifier(Identifier identifier)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Structure s && SubmarineTypeMatches(s.Submarine) && s.Prefab.Identifier == identifier);
|
||||
@@ -118,6 +132,11 @@ namespace Barotrauma
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Hull h && SubmarineTypeMatches(h.Submarine) && h.RoomName.Contains(name.Value, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
private void TagSubmarinesByType(Identifier type)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Submarine s && SubmarineTypeMatches(s) && (type.IsEmpty || type == s.Info?.Type.ToIdentifier()));
|
||||
}
|
||||
|
||||
private bool SubmarineTypeMatches(Submarine sub)
|
||||
{
|
||||
if (SubmarineType == SubType.Any) { return true; }
|
||||
|
||||
@@ -8,6 +8,12 @@ namespace Barotrauma
|
||||
{
|
||||
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; }
|
||||
|
||||
@@ -23,7 +29,10 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "Tag to apply to the second entity when the trigger check succeeds.")]
|
||||
public Identifier ApplyToTarget2 { get; set; }
|
||||
|
||||
[Serialize(0.0f, IsPropertySaveable.Yes, description: "Range both entities must be within to activate the trigger.")]
|
||||
[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.")]
|
||||
@@ -38,6 +47,9 @@ namespace Barotrauma
|
||||
[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; }
|
||||
|
||||
private float distance;
|
||||
|
||||
public TriggerAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
@@ -57,6 +69,8 @@ namespace Barotrauma
|
||||
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)
|
||||
{
|
||||
@@ -66,18 +80,44 @@ namespace Barotrauma
|
||||
|
||||
var targets1 = ParentEvent.GetTargets(Target1Tag);
|
||||
if (!targets1.Any()) { return; }
|
||||
|
||||
|
||||
triggerers.Clear();
|
||||
foreach (Entity e1 in targets1)
|
||||
{
|
||||
if (DisableInCombat && IsInCombat(e1)) { continue; }
|
||||
if (DisableIfTargetIncapacitated && e1 is Character character1 && (character1.IsDead || character1.IsIncapacitated)) { continue; }
|
||||
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 (IsCloseEnoughToHull(e1, out Hull hull))
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -85,9 +125,26 @@ namespace Barotrauma
|
||||
|
||||
foreach (Entity e2 in targets2)
|
||||
{
|
||||
if (e1 == e2) { continue; }
|
||||
if (DisableInCombat && IsInCombat(e2)) { continue; }
|
||||
if (DisableIfTargetIncapacitated && e2 is Character character2 && (character2.IsDead || character2.IsIncapacitated)) { continue; }
|
||||
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)
|
||||
{
|
||||
@@ -173,16 +230,35 @@ namespace Barotrauma
|
||||
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)
|
||||
if ((Type == TriggerType.Inside) == IsWithinRadius())
|
||||
{
|
||||
if (!CheckAllTargets)
|
||||
{
|
||||
Trigger(e1, e2);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
triggerers.Add((e1, e2));
|
||||
}
|
||||
}
|
||||
else if (CheckAllTargets)
|
||||
{
|
||||
Trigger(e1, e2);
|
||||
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()
|
||||
@@ -205,7 +281,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsCloseEnoughToHull(Entity e, out Hull hull)
|
||||
private bool CheckDistanceToHull(Entity e, out Hull hull)
|
||||
{
|
||||
hull = null;
|
||||
if (Radius <= 0)
|
||||
@@ -213,36 +289,35 @@ namespace Barotrauma
|
||||
if (e is Character character && character.CurrentHull != null && character.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
|
||||
{
|
||||
hull = character.CurrentHull;
|
||||
return true;
|
||||
return Type == TriggerType.Inside;
|
||||
}
|
||||
else if (e is Item item && item.CurrentHull != null && item.CurrentHull.OutpostModuleTags.Contains(TargetModuleType))
|
||||
{
|
||||
hull = item.CurrentHull;
|
||||
return true;
|
||||
return Type == TriggerType.Inside;
|
||||
}
|
||||
return false;
|
||||
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 true;
|
||||
return Type == TriggerType.Inside;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return Type == TriggerType.Outside;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsInCombat(Entity entity)
|
||||
private static bool IsInCombat(Entity entity)
|
||||
{
|
||||
if (!(entity is Character character)) { return false; }
|
||||
if (entity is not Character character) { return false; }
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.IsDead || c.Removed || c.IsIncapacitated || !c.Enabled) { continue; }
|
||||
|
||||
@@ -13,6 +13,12 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ObjectiveTag { get; set; }
|
||||
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool CanBeCompleted { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ParentObjectiveId { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool AutoPlayVideo { get; set; }
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class WaitAction : EventAction
|
||||
|
||||
@@ -71,8 +71,9 @@ namespace Barotrauma
|
||||
|
||||
private readonly List<Event> activeEvents = new List<Event>();
|
||||
|
||||
private readonly HashSet<Event> finishedEvents = new HashSet<Event>();
|
||||
private readonly HashSet<EventPrefab> nonRepeatableEvents = new HashSet<EventPrefab>();
|
||||
private readonly HashSet<Identifier> finishedEvents = new HashSet<Identifier>();
|
||||
private readonly HashSet<Identifier> nonRepeatableEvents = new HashSet<Identifier>();
|
||||
private readonly HashSet<EventSet> usedUniqueSets = new HashSet<EventSet>();
|
||||
|
||||
|
||||
#if DEBUG && SERVER
|
||||
@@ -150,17 +151,24 @@ namespace Barotrauma
|
||||
seed = ToolBox.StringToInt(level.Seed);
|
||||
foreach (var previousEvent in level.LevelData.EventHistory)
|
||||
{
|
||||
seed ^= ToolBox.IdentifierToInt(previousEvent.Identifier);
|
||||
seed ^= ToolBox.IdentifierToInt(previousEvent);
|
||||
}
|
||||
}
|
||||
rand = new MTRandom(seed);
|
||||
|
||||
EventSet initialEventSet = SelectRandomEvents(EventSet.Prefabs.ToList(), requireCampaignSet: GameMain.GameSession?.GameMode is CampaignMode, rand);
|
||||
bool playingCampaign = GameMain.GameSession?.GameMode is CampaignMode;
|
||||
EventSet initialEventSet = SelectRandomEvents(
|
||||
EventSet.Prefabs.ToList(),
|
||||
requireCampaignSet: playingCampaign,
|
||||
random: rand);
|
||||
EventSet additiveSet = null;
|
||||
if (initialEventSet != null && initialEventSet.Additive)
|
||||
{
|
||||
additiveSet = initialEventSet;
|
||||
initialEventSet = SelectRandomEvents(EventSet.Prefabs.Where(e => !e.Additive).ToList(), requireCampaignSet: GameMain.GameSession?.GameMode is CampaignMode, rand);
|
||||
initialEventSet = SelectRandomEvents(
|
||||
EventSet.Prefabs.Where(e => !e.Additive).ToList(),
|
||||
requireCampaignSet: playingCampaign,
|
||||
random: rand);
|
||||
}
|
||||
if (initialEventSet != null)
|
||||
{
|
||||
@@ -180,14 +188,7 @@ namespace Barotrauma
|
||||
//if the outpost is connected to a locked connection, create an event to unlock it
|
||||
if (level.StartLocation?.Connections.Any(c => c.Locked && level.StartLocation.MapPosition.X < c.OtherLocation(level.StartLocation).MapPosition.X) ?? false)
|
||||
{
|
||||
var unlockPathPrefabs = EventPrefab.Prefabs.Where(e => e.UnlockPathEvent);
|
||||
var unlockPathPrefabsForBiome = unlockPathPrefabs.Where(e =>
|
||||
e.BiomeIdentifier.IsEmpty ||
|
||||
e.BiomeIdentifier == level.LevelData.Biome.Identifier);
|
||||
|
||||
var unlockPathEventPrefab = unlockPathPrefabsForBiome.Any() ?
|
||||
ToolBox.SelectWeightedRandom(unlockPathPrefabsForBiome, b => b.Commonness, rand) :
|
||||
ToolBox.SelectWeightedRandom(unlockPathPrefabs, b => b.Commonness, rand);
|
||||
var unlockPathEventPrefab = EventPrefab.GetUnlockPathEvent(level.LevelData.Biome.Identifier, level.StartLocation.Faction);
|
||||
if (unlockPathEventPrefab != null)
|
||||
{
|
||||
var newEvent = unlockPathEventPrefab.CreateInstance();
|
||||
@@ -201,6 +202,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
AddChildEvents(initialEventSet);
|
||||
|
||||
void AddChildEvents(EventSet eventSet)
|
||||
{
|
||||
if (eventSet == null) { return; }
|
||||
@@ -208,7 +210,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs))
|
||||
{
|
||||
nonRepeatableEvents.Add(ep);
|
||||
nonRepeatableEvents.Add(ep.Identifier);
|
||||
}
|
||||
}
|
||||
foreach (EventSet childSet in eventSet.ChildSets)
|
||||
@@ -351,6 +353,7 @@ namespace Barotrauma
|
||||
QueuedEvents.Clear();
|
||||
finishedEvents.Clear();
|
||||
nonRepeatableEvents.Clear();
|
||||
usedUniqueSets.Clear();
|
||||
|
||||
preloadedSprites.ForEach(s => s.Remove());
|
||||
preloadedSprites.Clear();
|
||||
@@ -364,15 +367,25 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public void RegisterEventHistory()
|
||||
{
|
||||
level.LevelData.EventsExhausted = true;
|
||||
if (level?.LevelData != null && level.LevelData.Type == LevelData.LevelType.Outpost)
|
||||
if (level?.LevelData != null)
|
||||
{
|
||||
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
|
||||
if (level.LevelData.EventHistory.Count > MaxEventHistory)
|
||||
level.LevelData.EventsExhausted = true;
|
||||
if (level.LevelData.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
|
||||
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab.Identifier).Where(e => !level.LevelData.EventHistory.Contains(e)));
|
||||
if (level.LevelData.EventHistory.Count > MaxEventHistory)
|
||||
{
|
||||
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
|
||||
}
|
||||
level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e)));
|
||||
}
|
||||
foreach (var usedUniqueSet in usedUniqueSets)
|
||||
{
|
||||
if (!level.LevelData.UsedUniqueSets.Contains(usedUniqueSet.Identifier))
|
||||
{
|
||||
level.LevelData.UsedUniqueSets.Add(usedUniqueSet.Identifier);
|
||||
}
|
||||
}
|
||||
level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,9 +396,9 @@ namespace Barotrauma
|
||||
|
||||
private float CalculateCommonness(EventPrefab eventPrefab, float baseCommonness)
|
||||
{
|
||||
if (level.LevelData.NonRepeatableEvents.Contains(eventPrefab)) { return 0.0f; }
|
||||
if (level.LevelData.NonRepeatableEvents.Contains(eventPrefab.Identifier)) { return 0.0f; }
|
||||
float retVal = baseCommonness;
|
||||
if (level.LevelData.EventHistory.Contains(eventPrefab)) { retVal *= 0.1f; }
|
||||
if (level.LevelData.EventHistory.Contains(eventPrefab.Identifier)) { retVal *= 0.1f; }
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@@ -398,6 +411,11 @@ namespace Barotrauma
|
||||
|
||||
DebugConsole.NewMessage($"Loading event set {eventSet.Identifier}", Color.LightBlue, debugOnly: true);
|
||||
|
||||
if (eventSet.Unique && !usedUniqueSets.Contains(eventSet))
|
||||
{
|
||||
usedUniqueSets.Add(eventSet);
|
||||
}
|
||||
|
||||
int applyCount = 1;
|
||||
List<Func<Level.InterestingPosition, bool>> spawnPosFilter = new List<Func<Level.InterestingPosition, bool>>();
|
||||
if (eventSet.PerRuin)
|
||||
@@ -426,9 +444,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool isPrefabSuitable(EventPrefab e)
|
||||
=> e.BiomeIdentifier.IsEmpty ||
|
||||
e.BiomeIdentifier == level.LevelData?.Biome?.Identifier;
|
||||
bool isPrefabSuitable(EventPrefab e) =>
|
||||
(e.BiomeIdentifier.IsEmpty || e.BiomeIdentifier == level.LevelData?.Biome?.Identifier) &&
|
||||
isFactionSuitable(e.Faction);
|
||||
|
||||
bool isFactionSuitable(Identifier factionId) =>
|
||||
factionId.IsEmpty || factionId == level.StartLocation?.Faction?.Prefab.Identifier || factionId == level.StartLocation?.SecondaryFaction?.Prefab.Identifier;
|
||||
|
||||
foreach (var subEventPrefab in eventSet.EventPrefabs)
|
||||
{
|
||||
@@ -437,9 +458,9 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Error in event set \"{eventSet.Identifier}\" ({eventSet.ContentFile?.ContentPackage?.Name ?? "null"}) - could not find an event prefab with the identifier \"{missingId}\".");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var suitablePrefabSubsets = eventSet.EventPrefabs.Where(
|
||||
e => e.EventPrefabs.Any(isPrefabSuitable)).ToArray();
|
||||
e => isFactionSuitable(e.Faction) && e.EventPrefabs.Any(isPrefabSuitable)).ToArray();
|
||||
|
||||
for (int i = 0; i < applyCount; i++)
|
||||
{
|
||||
@@ -496,12 +517,12 @@ namespace Barotrauma
|
||||
selectedEvents[eventSet].Add(newEvent);
|
||||
}
|
||||
|
||||
Location location = (GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation ?? level?.StartLocation;
|
||||
var location = GetEventLocation();
|
||||
foreach (EventSet childEventSet in eventSet.ChildSets)
|
||||
{
|
||||
if (!IsValidForLevel(childEventSet, level)) { continue; }
|
||||
if (location != null && !IsValidForLocation(childEventSet, location)) { continue; }
|
||||
CreateEvents(childEventSet);
|
||||
if (!IsValidForLocation(childEventSet, location)) { continue; }
|
||||
CreateEvents(childEventSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -536,10 +557,32 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
Location location = (GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation ?? level?.StartLocation;
|
||||
if (location != null)
|
||||
var location = GetEventLocation();
|
||||
allowedEventSets = allowedEventSets.Where(set => IsValidForLocation(set, location));
|
||||
|
||||
allowedEventSets = allowedEventSets.Where(set => !set.CampaignTutorialOnly ||
|
||||
(GameMain.IsSingleplayer && GameMain.GameSession?.Campaign?.Settings is { TutorialEnabled: true }));
|
||||
|
||||
int? discoveryIndex = GameMain.GameSession?.Map?.GetDiscoveryIndex(location);
|
||||
int? visitIndex = GameMain.GameSession?.Map?.GetVisitIndex(location);
|
||||
if (discoveryIndex is not null && discoveryIndex >= 0 && allowedEventSets.Any(set => set.ForceAtDiscoveredNr == discoveryIndex))
|
||||
{
|
||||
allowedEventSets = allowedEventSets.Where(set => IsValidForLocation(set, location));
|
||||
allowedEventSets = allowedEventSets.Where(set => set.ForceAtDiscoveredNr == discoveryIndex);
|
||||
}
|
||||
else if (visitIndex is not null && visitIndex >= 0 && allowedEventSets.Any(set => set.ForceAtVisitedNr == visitIndex))
|
||||
{
|
||||
allowedEventSets = allowedEventSets.Where(set => set.ForceAtVisitedNr == visitIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
// When there are no forced sets, only allow sets that aren't forced at any specific location
|
||||
allowedEventSets = allowedEventSets.Where(set => set.ForceAtDiscoveredNr < 0 && set.ForceAtVisitedNr < 0);
|
||||
}
|
||||
|
||||
if (allowedEventSets.Count() == 1)
|
||||
{
|
||||
// When there's only a single set available, just select it directly
|
||||
return allowedEventSets.First();
|
||||
}
|
||||
|
||||
float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level));
|
||||
@@ -558,18 +601,31 @@ namespace Barotrauma
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool IsValidForLevel(EventSet eventSet, Level level)
|
||||
private static bool IsValidForLevel(EventSet eventSet, Level level)
|
||||
{
|
||||
return
|
||||
level.Difficulty >= eventSet.MinLevelDifficulty && level.Difficulty <= eventSet.MaxLevelDifficulty &&
|
||||
level.LevelData.Type == eventSet.LevelType &&
|
||||
(eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier);
|
||||
(eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier) &&
|
||||
(!eventSet.Unique || !level.LevelData.UsedUniqueSets.Contains(eventSet.Identifier));
|
||||
}
|
||||
|
||||
private bool IsValidForLocation(EventSet eventSet, Location location)
|
||||
{
|
||||
return eventSet.LocationTypeIdentifiers == null ||
|
||||
eventSet.LocationTypeIdentifiers.Any(identifier => identifier == location.GetLocationType().Identifier);
|
||||
if (location is null) { return true; }
|
||||
if (!eventSet.Faction.IsEmpty)
|
||||
{
|
||||
if (eventSet.Faction != location.Faction?.Prefab.Identifier && eventSet.Faction != location.SecondaryFaction?.Prefab.Identifier) { return false; }
|
||||
}
|
||||
var locationType = location.GetLocationType();
|
||||
bool includeGenericEvents = level.Type == LevelData.LevelType.LocationConnection || !locationType.IgnoreGenericEvents;
|
||||
if (includeGenericEvents && eventSet.LocationTypeIdentifiers == null) { return true; }
|
||||
return eventSet.LocationTypeIdentifiers != null && eventSet.LocationTypeIdentifiers.Any(identifier => identifier == locationType.Identifier);
|
||||
}
|
||||
|
||||
private Location GetEventLocation()
|
||||
{
|
||||
return GameMain.GameSession?.Campaign?.Map?.CurrentLocation ?? level?.StartLocation;
|
||||
}
|
||||
|
||||
private bool CanStartEventSet(EventSet eventSet)
|
||||
@@ -688,53 +744,50 @@ namespace Barotrauma
|
||||
calculateDistanceTraveledTimer = CalculateDistanceTraveledInterval;
|
||||
}
|
||||
|
||||
if (currentIntensity < eventThreshold)
|
||||
bool recheck = false;
|
||||
do
|
||||
{
|
||||
bool recheck = false;
|
||||
do
|
||||
recheck = false;
|
||||
//activate pending event sets that can be activated
|
||||
for (int i = pendingEventSets.Count - 1; i >= 0; i--)
|
||||
{
|
||||
recheck = false;
|
||||
//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 (currentIntensity > eventThreshold && !eventSet.IgnoreIntensity) { continue; }
|
||||
if (!CanStartEventSet(eventSet)) { continue; }
|
||||
|
||||
pendingEventSets.RemoveAt(i);
|
||||
|
||||
if (selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
var eventSet = pendingEventSets[i];
|
||||
if (eventCoolDown > 0.0f && !eventSet.IgnoreCoolDown) { continue; }
|
||||
|
||||
if (!CanStartEventSet(eventSet)) { continue; }
|
||||
|
||||
pendingEventSets.RemoveAt(i);
|
||||
|
||||
if (selectedEvents.ContainsKey(eventSet))
|
||||
//start events in this set
|
||||
foreach (Event ev in selectedEvents[eventSet])
|
||||
{
|
||||
//start events in this set
|
||||
foreach (Event ev in selectedEvents[eventSet])
|
||||
activeEvents.Add(ev);
|
||||
eventThreshold = settings.DefaultEventThreshold;
|
||||
if (eventSet.TriggerEventCooldown && selectedEvents[eventSet].Any(e => e.Prefab.TriggerEventCooldown))
|
||||
{
|
||||
activeEvents.Add(ev);
|
||||
eventThreshold = settings.DefaultEventThreshold;
|
||||
if (eventSet.TriggerEventCooldown && selectedEvents[eventSet].Any(e => e.Prefab.TriggerEventCooldown))
|
||||
eventCoolDown = settings.EventCooldown;
|
||||
}
|
||||
if (eventSet.ResetTime > 0)
|
||||
{
|
||||
ev.Finished += () =>
|
||||
{
|
||||
eventCoolDown = settings.EventCooldown;
|
||||
}
|
||||
if (eventSet.ResetTime > 0)
|
||||
{
|
||||
ev.Finished += () =>
|
||||
{
|
||||
pendingEventSets.Add(eventSet);
|
||||
CreateEvents(eventSet);
|
||||
};
|
||||
}
|
||||
pendingEventSets.Add(eventSet);
|
||||
CreateEvents(eventSet);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//add child event sets to pending
|
||||
foreach (EventSet childEventSet in eventSet.ChildSets)
|
||||
{
|
||||
pendingEventSets.Add(childEventSet);
|
||||
recheck = true;
|
||||
}
|
||||
}
|
||||
} while (recheck);
|
||||
}
|
||||
|
||||
//add child event sets to pending
|
||||
foreach (EventSet childEventSet in eventSet.ChildSets)
|
||||
{
|
||||
pendingEventSets.Add(childEventSet);
|
||||
recheck = true;
|
||||
}
|
||||
}
|
||||
} while (recheck);
|
||||
|
||||
foreach (Event ev in activeEvents)
|
||||
{
|
||||
@@ -742,13 +795,13 @@ namespace Barotrauma
|
||||
{
|
||||
ev.Update(deltaTime);
|
||||
}
|
||||
else if (!finishedEvents.Contains(ev))
|
||||
else if (ev.Prefab != null && !finishedEvents.Contains(ev.Prefab.Identifier))
|
||||
{
|
||||
if (level?.LevelData != null && level.LevelData.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
if (!level.LevelData.EventHistory.Contains(ev.Prefab)) { level.LevelData.EventHistory.Add(ev.Prefab); }
|
||||
if (!level.LevelData.EventHistory.Contains(ev.Prefab.Identifier)) { level.LevelData.EventHistory.Add(ev.Prefab.Identifier); }
|
||||
}
|
||||
finishedEvents.Add(ev);
|
||||
finishedEvents.Add(ev.Prefab.Identifier);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -14,12 +14,12 @@ namespace Barotrauma
|
||||
public readonly bool TriggerEventCooldown;
|
||||
public readonly float Commonness;
|
||||
public readonly Identifier BiomeIdentifier;
|
||||
public readonly Identifier Faction;
|
||||
public readonly float SpawnDistance;
|
||||
|
||||
public readonly bool UnlockPathEvent;
|
||||
public readonly string UnlockPathTooltip;
|
||||
public readonly int UnlockPathReputation;
|
||||
public readonly string UnlockPathFaction;
|
||||
|
||||
public EventPrefab(ContentXElement element, RandomEventsFile file, Identifier fallbackIdentifier = default)
|
||||
: base(file, element.GetAttributeIdentifier("identifier", fallbackIdentifier))
|
||||
@@ -40,6 +40,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
BiomeIdentifier = ConfigElement.GetAttributeIdentifier("biome", Identifier.Empty);
|
||||
Faction = ConfigElement.GetAttributeIdentifier("faction", Identifier.Empty);
|
||||
Commonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
Probability = Math.Clamp(element.GetAttributeFloat(1.0f, "probability", "spawnprobability"), 0, 1);
|
||||
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", EventType != typeof(ScriptedEvent));
|
||||
@@ -47,7 +48,6 @@ namespace Barotrauma
|
||||
UnlockPathEvent = element.GetAttributeBool("unlockpathevent", false);
|
||||
UnlockPathTooltip = element.GetAttributeString("unlockpathtooltip", "lockedpathtooltip");
|
||||
UnlockPathReputation = element.GetAttributeInt("unlockpathreputation", 0);
|
||||
UnlockPathFaction = element.GetAttributeString("unlockpathfaction", "");
|
||||
|
||||
SpawnDistance = element.GetAttributeFloat("spawndistance", 0);
|
||||
}
|
||||
@@ -80,5 +80,17 @@ namespace Barotrauma
|
||||
{
|
||||
return $"EventPrefab ({Identifier})";
|
||||
}
|
||||
|
||||
public static EventPrefab GetUnlockPathEvent(Identifier biomeIdentifier, Faction faction)
|
||||
{
|
||||
var unlockPathEvents = Prefabs.OrderBy(p => p.Identifier).Where(e => e.UnlockPathEvent);
|
||||
if (faction != null && unlockPathEvents.Any(e => e.Faction == faction.Prefab.Identifier))
|
||||
{
|
||||
unlockPathEvents = unlockPathEvents.Where(e => e.Faction == faction.Prefab.Identifier);
|
||||
}
|
||||
return
|
||||
unlockPathEvents.FirstOrDefault(ep => ep.BiomeIdentifier == biomeIdentifier) ??
|
||||
unlockPathEvents.FirstOrDefault(ep => ep.BiomeIdentifier == Identifier.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -89,7 +88,9 @@ namespace Barotrauma
|
||||
public readonly LevelData.LevelType LevelType;
|
||||
|
||||
public readonly ImmutableArray<Identifier> LocationTypeIdentifiers;
|
||||
|
||||
|
||||
public readonly Identifier Faction;
|
||||
|
||||
public readonly bool ChooseRandom;
|
||||
|
||||
private readonly int eventCount = 1;
|
||||
@@ -110,11 +111,22 @@ namespace Barotrauma
|
||||
|
||||
public readonly bool IgnoreCoolDown;
|
||||
|
||||
public readonly bool IgnoreIntensity;
|
||||
|
||||
public readonly bool PerRuin, PerCave, PerWreck;
|
||||
public readonly bool DisableInHuntingGrounds;
|
||||
|
||||
/// <summary>
|
||||
/// If true, events from this set shouldn't be selected again as long as they remain in <see cref="LevelData.NonRepeatableEvents"/> which has a limited size.
|
||||
/// Use <see cref="Unique"/> to prevent selecting the whole set again altogether.
|
||||
/// </summary>
|
||||
public readonly bool OncePerOutpost;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the whole set can only be selected once for a level.
|
||||
/// </summary>
|
||||
public readonly bool Unique;
|
||||
|
||||
public readonly bool DelayWhenCrewAway;
|
||||
|
||||
public readonly bool TriggerEventCooldown;
|
||||
@@ -126,13 +138,26 @@ namespace Barotrauma
|
||||
|
||||
public readonly float ResetTime;
|
||||
|
||||
/// <summary>
|
||||
/// Used to force an event set based on how many other locations have been discovered before this. (Used for campaign tutorial event sets.)
|
||||
/// </summary>
|
||||
public readonly int ForceAtDiscoveredNr;
|
||||
|
||||
/// <summary>
|
||||
/// Used to force an event set based on how many other outposts have been visited before this. (Used for campaign tutorial event sets.)
|
||||
/// </summary>
|
||||
public readonly int ForceAtVisitedNr;
|
||||
|
||||
public readonly bool CampaignTutorialOnly;
|
||||
|
||||
public readonly struct SubEventPrefab
|
||||
{
|
||||
public SubEventPrefab(Either<Identifier[], EventPrefab> prefabOrIdentifiers, float? commonness, float? probability)
|
||||
public SubEventPrefab(Either<Identifier[], EventPrefab> prefabOrIdentifiers, float? commonness, float? probability, Identifier factionId)
|
||||
{
|
||||
PrefabOrIdentifier = prefabOrIdentifiers;
|
||||
SelfCommonness = commonness;
|
||||
SelfProbability = probability;
|
||||
Faction = factionId;
|
||||
}
|
||||
|
||||
public readonly Either<Identifier[], EventPrefab> PrefabOrIdentifier;
|
||||
@@ -163,6 +188,8 @@ namespace Barotrauma
|
||||
public readonly float? SelfProbability;
|
||||
public float Probability => SelfProbability ?? EventPrefabs.MaxOrNull(p => p.Probability) ?? 0.0f;
|
||||
|
||||
public readonly Identifier Faction;
|
||||
|
||||
public void Deconstruct(out IEnumerable<EventPrefab> eventPrefabs, out float commonness, out float probability)
|
||||
{
|
||||
eventPrefabs = EventPrefabs;
|
||||
@@ -245,6 +272,8 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Error in event set \"{Identifier}\". \"{levelTypeStr}\" is not a valid level type.");
|
||||
}
|
||||
|
||||
Faction = element.GetAttributeIdentifier(nameof(Faction), Identifier.Empty);
|
||||
|
||||
Identifier[] locationTypeStr = element.GetAttributeIdentifierArray("locationtype", null);
|
||||
if (locationTypeStr != null)
|
||||
{
|
||||
@@ -267,11 +296,21 @@ namespace Barotrauma
|
||||
PerWreck = element.GetAttributeBool("perwreck", false);
|
||||
DisableInHuntingGrounds = element.GetAttributeBool("disableinhuntinggrounds", false);
|
||||
IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? (PerRuin || PerCave || PerWreck));
|
||||
IgnoreIntensity = element.GetAttributeBool("ignoreintensity", parentSet?.IgnoreIntensity ?? false);
|
||||
DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", !PerRuin && !PerCave && !PerWreck);
|
||||
OncePerOutpost = element.GetAttributeBool("onceperoutpost", false);
|
||||
Unique = element.GetAttributeBool("unique", false);
|
||||
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
|
||||
IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost || (parentSet?.IsCampaignSet ?? false));
|
||||
ResetTime = element.GetAttributeFloat("resettime", 0);
|
||||
CampaignTutorialOnly = element.GetAttributeBool(nameof(CampaignTutorialOnly), false);
|
||||
|
||||
ForceAtDiscoveredNr = element.GetAttributeInt(nameof(ForceAtDiscoveredNr), -1);
|
||||
ForceAtVisitedNr = element.GetAttributeInt(nameof(ForceAtVisitedNr), -1);
|
||||
if (ForceAtDiscoveredNr >= 0 && ForceAtVisitedNr >= 0)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error with event set \"{Identifier}\" - both ForceAtDiscoveredNr and ForceAtVisitedNr are defined, this could lead to unexpected behavior");
|
||||
}
|
||||
|
||||
DefaultCommonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
foreach (var subElement in element.Elements())
|
||||
@@ -309,15 +348,17 @@ namespace Barotrauma
|
||||
Identifier[] identifiers = subElement.GetAttributeIdentifierArray("identifier", Array.Empty<Identifier>());
|
||||
float commonness = subElement.GetAttributeFloat("commonness", -1f);
|
||||
float probability = subElement.GetAttributeFloat("probability", -1f);
|
||||
Identifier factionId = subElement.GetAttributeIdentifier(nameof(Faction), Identifier.Empty);
|
||||
eventPrefabs.Add(new SubEventPrefab(
|
||||
identifiers,
|
||||
commonness >= 0f ? commonness : (float?)null,
|
||||
probability >= 0f ? probability : (float?)null));
|
||||
probability >= 0f ? probability : (float?)null,
|
||||
factionId));
|
||||
}
|
||||
else
|
||||
{
|
||||
var prefab = new EventPrefab(subElement, file, $"{Identifier}-{subElement.ElementsBeforeSelf().Count()}".ToIdentifier());
|
||||
eventPrefabs.Add(new SubEventPrefab(prefab, prefab.Commonness, prefab.Probability));
|
||||
eventPrefabs.Add(new SubEventPrefab(prefab, prefab.Commonness, prefab.Probability, prefab.Faction));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -342,8 +383,22 @@ namespace Barotrauma
|
||||
|
||||
public float GetCommonness(Level level)
|
||||
{
|
||||
Identifier key = level.GenerationParams?.Identifier ?? Identifier.Empty;
|
||||
return OverrideCommonness.ContainsKey(key) ? OverrideCommonness[key] : DefaultCommonness;
|
||||
if (level.GenerationParams?.Identifier != null &&
|
||||
OverrideCommonness.TryGetValue(level.GenerationParams.Identifier, out float generationParamsCommonness))
|
||||
{
|
||||
return generationParamsCommonness;
|
||||
}
|
||||
else if (level.StartOutpost?.Info.OutpostGenerationParams?.Identifier != null &&
|
||||
OverrideCommonness.TryGetValue(level.StartOutpost.Info.OutpostGenerationParams.Identifier, out float startOutpostParamsCommonness))
|
||||
{
|
||||
return startOutpostParamsCommonness;
|
||||
}
|
||||
else if (level.EndOutpost?.Info.OutpostGenerationParams?.Identifier != null &&
|
||||
OverrideCommonness.TryGetValue(level.EndOutpost.Info.OutpostGenerationParams.Identifier, out float endOutpostParamsCommonness))
|
||||
{
|
||||
return endOutpostParamsCommonness;
|
||||
}
|
||||
return DefaultCommonness;
|
||||
}
|
||||
|
||||
public int GetEventCount(Level level)
|
||||
@@ -489,6 +544,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{base.ToString()} ({Identifier.Value})";
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
+24
-9
@@ -1,5 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -28,6 +27,8 @@ namespace Barotrauma
|
||||
private const float EndDelay = 5.0f;
|
||||
private float endTimer;
|
||||
|
||||
private bool allowOrderingRescuees;
|
||||
|
||||
public override bool AllowRespawn => false;
|
||||
|
||||
public override bool AllowUndocking
|
||||
@@ -39,17 +40,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
if (State == 0)
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
return Targets.Select(t => (Prefab.SonarLabel, t.WorldPosition));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Targets.Select(t => t.WorldPosition);
|
||||
return Enumerable.Empty<(LocalizedString Label, Vector2 Position)>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,6 +84,8 @@ namespace Barotrauma
|
||||
{
|
||||
characterConfig = prefab.ConfigElement.GetChildElement("Characters");
|
||||
|
||||
allowOrderingRescuees = prefab.ConfigElement.GetAttributeBool(nameof(allowOrderingRescuees), true);
|
||||
|
||||
string msgTag = prefab.ConfigElement.GetAttributeString("hostageskilledmessage", "");
|
||||
hostagesKilledMessage = TextManager.Get(msgTag).Fallback(msgTag);
|
||||
|
||||
@@ -214,8 +217,9 @@ namespace Barotrauma
|
||||
{
|
||||
Identifier[] moduleFlags = element.GetAttributeIdentifierArray("moduleflags", null);
|
||||
Identifier[] spawnPointTags = element.GetAttributeIdentifierArray("spawnpointtags", null);
|
||||
var spawnPointType = element.GetAttributeEnum("spawnpointtype", SpawnType.Human);
|
||||
ISpatialEntity spawnPos = SpawnAction.GetSpawnPos(
|
||||
SpawnAction.SpawnLocationType.Outpost, SpawnType.Human,
|
||||
SpawnAction.SpawnLocationType.Outpost, spawnPointType,
|
||||
moduleFlags ?? humanPrefab.GetModuleFlags(),
|
||||
spawnPointTags ?? humanPrefab.GetSpawnPointTags(),
|
||||
element.GetAttributeBool("asfaraspossible", false));
|
||||
@@ -226,8 +230,16 @@ namespace Barotrauma
|
||||
|
||||
bool requiresRescue = element.GetAttributeBool("requirerescue", false);
|
||||
|
||||
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, submarine, requiresRescue ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None, spawnPos, giveTags: true);
|
||||
|
||||
var teamId = element.GetAttributeEnum("teamid", requiresRescue ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None);
|
||||
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, submarine, teamId, spawnPos, giveTags: true);
|
||||
if (Level.Loaded?.StartOutpost?.Info is { } outPostInfo)
|
||||
{
|
||||
outPostInfo.AddOutpostNPCIdentifierOrTag(spawnedCharacter, humanPrefab.Identifier);
|
||||
foreach (Identifier tag in humanPrefab.GetTags())
|
||||
{
|
||||
outPostInfo.AddOutpostNPCIdentifierOrTag(spawnedCharacter, tag);
|
||||
}
|
||||
}
|
||||
if (spawnPos is WayPoint wp)
|
||||
{
|
||||
spawnedCharacter.GiveIdCardTags(wp);
|
||||
@@ -237,7 +249,10 @@ namespace Barotrauma
|
||||
{
|
||||
requireRescue.Add(spawnedCharacter);
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.AddCharacterToCrewList(spawnedCharacter);
|
||||
if (allowOrderingRescuees)
|
||||
{
|
||||
GameMain.GameSession.CrewManager.AddCharacterToCrewList(spawnedCharacter);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -18,17 +18,19 @@ namespace Barotrauma
|
||||
|
||||
private Ruin TargetRuin { get; set; }
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State == 0)
|
||||
{
|
||||
return allTargets.Where(t => (t is Item i && !IsItemDestroyed(i)) || (t is Character c && !IsEnemyDefeated(c))).Select(t => t.WorldPosition);
|
||||
return allTargets
|
||||
.Where(t => (t is Item i && !IsItemDestroyed(i)) || (t is Character c && !IsEnemyDefeated(c)))
|
||||
.Select(t => (Prefab.SonarLabel, t.WorldPosition));
|
||||
}
|
||||
else
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
return Enumerable.Empty<(LocalizedString Label, Vector2 Position)>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,7 +166,7 @@ namespace Barotrauma
|
||||
{
|
||||
bool exitingLevel = GameMain.GameSession?.GameMode is CampaignMode campaign ?
|
||||
campaign.GetAvailableTransition() != CampaignMode.TransitionType.None :
|
||||
Submarine.MainSub is { } sub && (sub.AtEndExit || sub.AtStartExit);
|
||||
Submarine.MainSub is { } sub && sub.AtEitherExit;
|
||||
|
||||
return State > 0 && exitingLevel;
|
||||
}
|
||||
|
||||
@@ -69,15 +69,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override LocalizedString SonarLabel
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.SonarLabel.IsNullOrEmpty() ? sonarLabel : base.SonarLabel;
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
|
||||
{
|
||||
get
|
||||
{
|
||||
@@ -85,7 +77,12 @@ namespace Barotrauma
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
yield return level.BeaconStation.WorldPosition;
|
||||
else
|
||||
{
|
||||
yield return (
|
||||
Prefab.SonarLabel.IsNullOrEmpty() ? sonarLabel : Prefab.SonarLabel,
|
||||
level.BeaconStation.WorldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class EndMission : Mission
|
||||
{
|
||||
enum MissionPhase
|
||||
{
|
||||
Initial,
|
||||
NoItemsDestroyed,
|
||||
SomeItemsDestroyed,
|
||||
AllItemsDestroyed,
|
||||
BossKilled
|
||||
}
|
||||
|
||||
private readonly CharacterPrefab bossPrefab;
|
||||
private readonly CharacterPrefab minionPrefab;
|
||||
|
||||
private readonly Identifier spawnPointTag;
|
||||
private readonly Identifier destructibleItemTag;
|
||||
|
||||
private ImmutableArray<Character> minions;
|
||||
private readonly int minionCount;
|
||||
private readonly float minionScatter;
|
||||
|
||||
private Character boss;
|
||||
|
||||
private readonly ItemPrefab projectilePrefab;
|
||||
|
||||
private float projectileTimer = 30.0f;
|
||||
|
||||
private readonly float startCinematicDistance = 30.0f;
|
||||
|
||||
private float endCinematicTimer;
|
||||
|
||||
private readonly List<Item> destructibleItems = new List<Item>();
|
||||
|
||||
protected readonly float wakeUpCinematicDelay = 5.0f;
|
||||
protected readonly float bossWakeUpDelay = 7.0f;
|
||||
protected readonly float cameraWaitDuration = 7.0f;
|
||||
|
||||
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
|
||||
{
|
||||
get { return destructibleItems.Where(it => it.Condition > 0.0f).Select(it => (Prefab.SonarLabel, it.WorldPosition)); }
|
||||
}
|
||||
|
||||
public override int State
|
||||
{
|
||||
get { return base.State; }
|
||||
set
|
||||
{
|
||||
|
||||
if (state != value)
|
||||
{
|
||||
base.State = value;
|
||||
OnStateChangedProjSpecific();
|
||||
if (Phase == MissionPhase.AllItemsDestroyed)
|
||||
{
|
||||
CoroutineManager.Invoke(() =>
|
||||
{
|
||||
if (boss != null && !boss.Removed)
|
||||
{
|
||||
boss.AnimController.ColliderIndex = 1;
|
||||
}
|
||||
}, delay: wakeUpCinematicDelay + bossWakeUpDelay + 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MissionPhase Phase
|
||||
{
|
||||
get
|
||||
{
|
||||
//state 0: nothing happens yet, play a cinematic and skip to the next state when close enough to the boss
|
||||
//state 1: start cinematic played
|
||||
//state 2: first destructibleItems destroyed
|
||||
//state 3: 2nd destructibleItems destroyed
|
||||
//state 4: all destructibleItems destroyed
|
||||
//state 5: boss killed
|
||||
if (state == 0) { return MissionPhase.Initial; }
|
||||
if (state == 1) { return MissionPhase.NoItemsDestroyed; }
|
||||
if (state < destructibleItems.Count + 1) { return MissionPhase.SomeItemsDestroyed; }
|
||||
if (state < destructibleItems.Count + 2) { return MissionPhase.AllItemsDestroyed; }
|
||||
return MissionPhase.BossKilled;
|
||||
}
|
||||
}
|
||||
|
||||
public EndMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
Identifier speciesName = prefab.ConfigElement.GetAttributeIdentifier("bossfile", Identifier.Empty);
|
||||
if (!speciesName.IsEmpty)
|
||||
{
|
||||
bossPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
if (bossPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Monster file not set.");
|
||||
}
|
||||
|
||||
Identifier minionName = prefab.ConfigElement.GetAttributeIdentifier("minionfile", Identifier.Empty);
|
||||
if (!minionName.IsEmpty)
|
||||
{
|
||||
minionPrefab = CharacterPrefab.FindBySpeciesName(minionName);
|
||||
if (minionPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
|
||||
}
|
||||
}
|
||||
|
||||
minionCount = Math.Min(prefab.ConfigElement.GetAttributeInt(nameof(minionCount), 0), 255);
|
||||
minionScatter = Math.Min(prefab.ConfigElement.GetAttributeFloat(nameof(minionScatter), 0), 10000);
|
||||
|
||||
Identifier projectileId = prefab.ConfigElement.GetAttributeIdentifier("projectile", Identifier.Empty);
|
||||
if (!projectileId.IsEmpty)
|
||||
{
|
||||
projectilePrefab = MapEntityPrefab.FindByIdentifier(projectileId) as ItemPrefab;
|
||||
if (projectilePrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in end mission \"{prefab.Identifier}\". Could not find an item prefab with the name \"{projectileId}\".");
|
||||
}
|
||||
}
|
||||
|
||||
spawnPointTag = prefab.ConfigElement.GetAttributeIdentifier(nameof(spawnPointTag), Identifier.Empty);
|
||||
destructibleItemTag = prefab.ConfigElement.GetAttributeIdentifier(nameof(destructibleItemTag), Identifier.Empty);
|
||||
|
||||
startCinematicDistance = prefab.ConfigElement.GetAttributeFloat(nameof(startCinematicDistance), 0);
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
{
|
||||
var spawnPoint = WayPoint.WayPointList.FirstOrDefault(wp => wp.Tags.Contains(spawnPointTag));
|
||||
if (spawnPoint == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find a spawn point \"{spawnPointTag}\".");
|
||||
return;
|
||||
}
|
||||
if (!IsClient)
|
||||
{
|
||||
boss = Character.Create(bossPrefab.Identifier, spawnPoint.WorldPosition, ToolBox.RandomSeed(8), createNetworkEvent: false);
|
||||
var minionList = new List<Character>();
|
||||
float angle = 0;
|
||||
float angleStep = MathHelper.TwoPi / Math.Max(minionCount, 1);
|
||||
for (int i = 0; i < minionCount; i++)
|
||||
{
|
||||
minionList.Add(Character.Create(minionPrefab.Identifier, MathUtils.GetPointOnCircumference(spawnPoint.WorldPosition, minionScatter, angle), ToolBox.RandomSeed(8), createNetworkEvent: false));
|
||||
angle += angleStep;
|
||||
}
|
||||
SwarmBehavior.CreateSwarm(minionList.Cast<AICharacter>());
|
||||
minions = minionList.ToImmutableArray();
|
||||
}
|
||||
if (destructibleItemTag.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Destructible item tag not set.");
|
||||
return;
|
||||
}
|
||||
destructibleItems.Clear();
|
||||
destructibleItems.AddRange(Item.ItemList.FindAll(it => it.HasTag(destructibleItemTag)));
|
||||
if (destructibleItems.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find any destructible items with the tag \"{spawnPointTag}\".");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
UpdateProjSpecific();
|
||||
|
||||
if (state == 0)
|
||||
{
|
||||
if (startCinematicDistance <= 0.0f ||
|
||||
boss == null || Submarine.MainSub == null ||
|
||||
Vector2.DistanceSquared(Submarine.MainSub.WorldPosition, boss.WorldPosition) <= startCinematicDistance * startCinematicDistance)
|
||||
{
|
||||
State = 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsClient && State > 0)
|
||||
{
|
||||
State = Math.Max(State, destructibleItems.Count(it => it.Condition <= 0.0f) + 1);
|
||||
}
|
||||
|
||||
if (Phase == MissionPhase.AllItemsDestroyed)
|
||||
{
|
||||
if (projectilePrefab != null && boss != null && !boss.IsDead && !boss.Removed)
|
||||
{
|
||||
projectileTimer -= deltaTime;
|
||||
if (projectileTimer <= 0.0f)
|
||||
{
|
||||
int projectileAmount = Rand.Range(3, 6);
|
||||
float spread = MathHelper.ToRadians(Rand.Range(20.0f, 180.0f));
|
||||
for (int i = 0; i < projectileAmount; i++)
|
||||
{
|
||||
int index = i;
|
||||
Entity.Spawner.AddItemToSpawnQueue(projectilePrefab, boss.WorldPosition, onSpawned: it =>
|
||||
{
|
||||
var projectile = it.GetComponent<Projectile>();
|
||||
float angle = MathUtils.VectorToAngle(Submarine.MainSub.WorldPosition - boss.WorldPosition);
|
||||
if (projectileAmount > 1)
|
||||
{
|
||||
angle += (index / (float)(projectileAmount - 1) - 0.5f) * spread;
|
||||
}
|
||||
it.body.SetTransform(it.SimPosition, angle);
|
||||
it.UpdateTransform();
|
||||
projectile.Use();
|
||||
});
|
||||
}
|
||||
|
||||
float dist = Vector2.Distance(Submarine.MainSub.WorldPosition, boss.WorldPosition);
|
||||
//the closer the sub is, more likely it is to shoot frequently
|
||||
float shortIntervalProbability = MathHelper.Lerp(0.9f, 0.05f, dist / 15000.0f);
|
||||
if (Rand.Range(0.0f, 1.0f) < shortIntervalProbability)
|
||||
{
|
||||
projectileTimer = Rand.Range(3.0f, 5.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
projectileTimer = Rand.Range(15f, 30f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
State = Math.Max(destructibleItems.Count + 2, State);
|
||||
}
|
||||
}
|
||||
else if (Phase == MissionPhase.BossKilled)
|
||||
{
|
||||
const float EndCinematicDuration = 20.0f;
|
||||
|
||||
endCinematicTimer += deltaTime;
|
||||
#if CLIENT
|
||||
Screen.Selected.Cam.Shake = MathHelper.Clamp(MathF.Pow(endCinematicTimer, 3), 5.0f, 200.0f);
|
||||
|
||||
|
||||
Screen.Selected.Cam.Rotation =
|
||||
Math.Max((endCinematicTimer - 5.0f) * 0.05f, 0.0f)
|
||||
+ (PerlinNoise.GetPerlin(endCinematicTimer * 0.1f, endCinematicTimer * 0.05f) - 0.5f) * 0.5f * (endCinematicTimer / EndCinematicDuration);
|
||||
if (Rand.Range(0.0f, 100.0f) < endCinematicTimer)
|
||||
{
|
||||
Level.Loaded.Renderer.Flash();
|
||||
}
|
||||
Level.Loaded.Renderer.ChromaticAberrationStrength = endCinematicTimer * 5;
|
||||
Level.Loaded.Renderer.CollapseEffectOrigin = boss.WorldPosition;
|
||||
Level.Loaded.Renderer.CollapseEffectStrength = endCinematicTimer / EndCinematicDuration;
|
||||
#endif
|
||||
if (endCinematicTimer > 5 && !IsClient)
|
||||
{
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c.AIController is EnemyAIController enemyAI && enemyAI.PetBehavior == null)
|
||||
{
|
||||
c.SetAllDamage(200.0f, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (endCinematicTimer > EndCinematicDuration && !IsClient)
|
||||
{
|
||||
//endCinematicTimer = 0;
|
||||
GameMain.GameSession.Campaign?.LoadNewLevel();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific();
|
||||
|
||||
partial void OnStateChangedProjSpecific();
|
||||
|
||||
protected override bool DetermineCompleted()
|
||||
{
|
||||
return Phase == MissionPhase.BossKilled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,12 @@ namespace Barotrauma
|
||||
{
|
||||
partial class EscortMission : Mission
|
||||
{
|
||||
private readonly XElement characterConfig;
|
||||
private readonly XElement itemConfig;
|
||||
private readonly ContentXElement characterConfig;
|
||||
private readonly ContentXElement itemConfig;
|
||||
|
||||
private readonly List<Character> characters = new List<Character>();
|
||||
private readonly Dictionary<Character, List<Item>> characterItems = new Dictionary<Character, List<Item>>();
|
||||
private readonly Dictionary<HumanPrefab, List<StatusEffect>> characterStatusEffects = new Dictionary<HumanPrefab, List<StatusEffect>>();
|
||||
|
||||
private readonly int baseEscortedCharacters;
|
||||
private readonly float scalingEscortedCharacters;
|
||||
@@ -28,7 +29,8 @@ namespace Barotrauma
|
||||
private readonly List<Character> terroristCharacters = new List<Character>();
|
||||
private bool terroristsShouldAct = false;
|
||||
private float terroristDistanceSquared;
|
||||
private const string TerroristTeamChangeIdentifier = "terrorist";
|
||||
private const string TerroristTeamChangeIdentifier = "terrorist";
|
||||
private readonly string terroristAnnounceDialogTag = string.Empty;
|
||||
|
||||
public EscortMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
@@ -39,6 +41,7 @@ namespace Barotrauma
|
||||
scalingEscortedCharacters = prefab.ConfigElement.GetAttributeFloat("scalingescortedcharacters", 0);
|
||||
terroristChance = prefab.ConfigElement.GetAttributeFloat("terroristchance", 0);
|
||||
itemConfig = prefab.ConfigElement.GetChildElement("TerroristItems");
|
||||
terroristAnnounceDialogTag = prefab.ConfigElement.GetAttributeString("terroristannouncedialogtag", string.Empty);
|
||||
CalculateReward();
|
||||
}
|
||||
|
||||
@@ -96,14 +99,27 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
List<HumanPrefab> humanPrefabsToSpawn = new List<HumanPrefab>();
|
||||
foreach (XElement element in characterConfig.Elements())
|
||||
foreach (ContentXElement characterElement in characterConfig.Elements())
|
||||
{
|
||||
int count = CalculateScalingEscortedCharacterCount(inMission: true);
|
||||
var humanPrefab = GetHumanPrefabFromElement(element);
|
||||
var humanPrefab = GetHumanPrefabFromElement(characterElement);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
humanPrefabsToSpawn.Add(humanPrefab);
|
||||
}
|
||||
foreach (var element in characterElement.Elements())
|
||||
{
|
||||
if (element.NameAsIdentifier() == "statuseffect")
|
||||
{
|
||||
var newEffect = StatusEffect.Load(element, parentDebugName: Prefab.Name.Value);
|
||||
if (newEffect == null) { continue; }
|
||||
if (!characterStatusEffects.ContainsKey(humanPrefab))
|
||||
{
|
||||
characterStatusEffects[humanPrefab] = new List<StatusEffect> { newEffect };
|
||||
}
|
||||
characterStatusEffects[humanPrefab].Add(newEffect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//if any of the escortees have a job defined, try to use a spawnpoint designated for that job
|
||||
@@ -128,6 +144,13 @@ namespace Barotrauma
|
||||
{
|
||||
humanAI.InitMentalStateManager();
|
||||
}
|
||||
if (characterStatusEffects.TryGetValue(humanPrefab, out var statusEffectList))
|
||||
{
|
||||
foreach (var statusEffect in statusEffectList)
|
||||
{
|
||||
statusEffect.Apply(statusEffect.type, 1.0f, spawnedCharacter, spawnedCharacter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +185,7 @@ namespace Barotrauma
|
||||
}
|
||||
int i = 0;
|
||||
|
||||
foreach (XElement element in characterConfig.Elements())
|
||||
foreach (ContentXElement element in characterConfig.Elements())
|
||||
{
|
||||
string escortIdentifier = element.GetAttributeString("escortidentifier", string.Empty);
|
||||
string colorIdentifier = element.GetAttributeString("color", string.Empty);
|
||||
@@ -231,7 +254,10 @@ namespace Barotrauma
|
||||
if (IsAlive(character) && !character.IsIncapacitated && !character.LockHands)
|
||||
{
|
||||
character.TryAddNewTeamChange(TerroristTeamChangeIdentifier, new ActiveTeamChange(CharacterTeamType.None, ActiveTeamChange.TeamChangePriorities.Willful, aggressiveBehavior: true));
|
||||
character.Speak(TextManager.Get("dialogterroristannounce").Value, null, Rand.Range(0.5f, 3f));
|
||||
if (!string.IsNullOrEmpty(terroristAnnounceDialogTag))
|
||||
{
|
||||
character.Speak(TextManager.Get("dialogterroristannounce").Value, null, Rand.Range(0.5f, 3f));
|
||||
}
|
||||
XElement randomElement = itemConfig.Elements().GetRandomUnsynced(e => e.GetAttributeFloat(0f, "mindifficulty") <= Level.Loaded.Difficulty);
|
||||
if (randomElement != null)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Barotrauma
|
||||
using System;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class GoToMission : Mission
|
||||
{
|
||||
@@ -11,7 +13,7 @@
|
||||
{
|
||||
if (Level.Loaded?.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
State = 1;
|
||||
State = Math.Max(1, State);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,13 +50,13 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
private readonly float resourceHandoverAmount;
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
|
||||
{
|
||||
get
|
||||
{
|
||||
return missionClusterPositions
|
||||
.Where(p => spawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(spawnedResources[p.Item1]))
|
||||
.Select(p => p.Item2);
|
||||
.Where(p => spawnedResources.ContainsKey(p.Identifier) && AnyAreUncollected(spawnedResources[p.Identifier]))
|
||||
.Select(p => (ModifyMessage(Prefab.SonarLabel, color: false), p.Position));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,6 @@ namespace Barotrauma
|
||||
public override LocalizedString FailureMessage => ModifyMessage(base.FailureMessage);
|
||||
public override LocalizedString Description => ModifyMessage(description);
|
||||
public override LocalizedString Name => ModifyMessage(base.Name, false);
|
||||
public override LocalizedString SonarLabel => ModifyMessage(base.SonarLabel, false);
|
||||
|
||||
public MineralMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
|
||||
{
|
||||
@@ -175,7 +174,7 @@ namespace Barotrauma
|
||||
State = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) { return; }
|
||||
if (!Submarine.MainSub.AtEitherExit) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Barotrauma
|
||||
public virtual int State
|
||||
{
|
||||
get { return state; }
|
||||
protected set
|
||||
set
|
||||
{
|
||||
if (state != value)
|
||||
{
|
||||
@@ -30,6 +30,11 @@ namespace Barotrauma
|
||||
TryTriggerEvents(state);
|
||||
#if SERVER
|
||||
GameMain.Server?.UpdateMissionState(this);
|
||||
#elif CLIENT
|
||||
if (Prefab.ShowProgressBar)
|
||||
{
|
||||
CharacterHUD.ShowMissionProgressBar(this);
|
||||
}
|
||||
#endif
|
||||
ShowMessage(State);
|
||||
OnMissionStateChanged?.Invoke(this);
|
||||
@@ -113,13 +118,11 @@ namespace Barotrauma
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public virtual IEnumerable<Vector2> SonarPositions
|
||||
public virtual IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
|
||||
{
|
||||
get { return Enumerable.Empty<Vector2>(); }
|
||||
get { return Enumerable.Empty<(LocalizedString Label, Vector2 Position)>(); }
|
||||
}
|
||||
|
||||
public virtual LocalizedString SonarLabel => Prefab.SonarLabel;
|
||||
|
||||
public Identifier SonarIconIdentifier => Prefab.SonarIconIdentifier;
|
||||
|
||||
public readonly Location[] Locations;
|
||||
@@ -307,7 +310,7 @@ namespace Barotrauma
|
||||
private void TryTriggerEvent(MissionPrefab.TriggerEvent trigger)
|
||||
{
|
||||
if (trigger.CampaignOnly && GameMain.GameSession?.Campaign == null) { return; }
|
||||
if (trigger.Delay > 0)
|
||||
if (trigger.Delay > 0 || trigger.State == 0)
|
||||
{
|
||||
if (!delayedTriggerEvents.Any(t => t.TriggerEvent == trigger))
|
||||
{
|
||||
@@ -378,7 +381,7 @@ namespace Barotrauma
|
||||
IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
|
||||
// use multipliers here so that we can easily add them together without introducing multiplicative XP stacking
|
||||
var experienceGainMultiplier = new AbilityExperienceGainMultiplier(1f);
|
||||
var experienceGainMultiplier = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnAllyGainMissionExperience, experienceGainMultiplier));
|
||||
crewCharacters.ForEach(c => experienceGainMultiplier.Value += c.GetStatValue(StatTypes.MissionExperienceGainMultiplier));
|
||||
|
||||
@@ -386,13 +389,20 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
foreach (Character character in crewCharacters)
|
||||
{
|
||||
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
||||
character.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
|
||||
character.Info?.GiveExperience(experienceGain, isMissionExperience: true);
|
||||
}
|
||||
#else
|
||||
foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
//give the experience to the stored characterinfo if the client isn't currently controlling a character
|
||||
(c.Character?.Info ?? c.CharacterInfo)?.GiveExperience(experienceGain, isMissionExperience: true);
|
||||
CharacterInfo info = c.Character?.Info ?? c.CharacterInfo;
|
||||
|
||||
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
||||
info?.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
|
||||
|
||||
info?.GiveExperience((int)(experienceGain * experienceGainMultiplier.Value), isMissionExperience: true);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -422,8 +432,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (reputationReward.Key == "location")
|
||||
{
|
||||
Locations[0].Reputation.AddReputation(reputationReward.Value);
|
||||
Locations[1].Reputation.AddReputation(reputationReward.Value);
|
||||
Locations[0].Reputation?.AddReputation(reputationReward.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -484,7 +493,7 @@ namespace Barotrauma
|
||||
protected void ChangeLocationType(LocationTypeChange change)
|
||||
{
|
||||
if (change == null) { throw new ArgumentException(); }
|
||||
if (GameMain.GameSession.GameMode is CampaignMode && !IsClient)
|
||||
if (GameMain.GameSession.GameMode is CampaignMode campaign && !IsClient)
|
||||
{
|
||||
int srcIndex = -1;
|
||||
for (int i = 0; i < Locations.Length; i++)
|
||||
@@ -504,7 +513,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
location.ChangeType(LocationType.Prefabs[change.ChangeToType]);
|
||||
location.ChangeType(campaign, LocationType.Prefabs[change.ChangeToType]);
|
||||
location.LocationTypeChangeCooldown = change.CooldownAfterChange;
|
||||
}
|
||||
}
|
||||
@@ -518,7 +527,6 @@ namespace Barotrauma
|
||||
if (element.Attribute("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in mission \"" + Name + "\" - use character identifiers instead of names to configure the characters.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -527,7 +535,7 @@ namespace Barotrauma
|
||||
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
|
||||
if (humanPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn character for mission: character prefab \"" + characterIdentifier + "\" not found");
|
||||
DebugConsole.ThrowError($"Couldn't spawn character for mission: character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\".");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -619,4 +627,16 @@ namespace Barotrauma
|
||||
public Mission Mission { get; set; }
|
||||
}
|
||||
|
||||
class AbilityMissionExperienceGainMultiplier : AbilityObject, IAbilityValue, IAbilityMission
|
||||
{
|
||||
public AbilityMissionExperienceGainMultiplier(Mission mission, float missionExperienceGainMultiplier)
|
||||
{
|
||||
Value = missionExperienceGainMultiplier;
|
||||
Mission = mission;
|
||||
}
|
||||
|
||||
public float Value { get; set; }
|
||||
public Mission Mission { get; set; }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,8 @@ namespace Barotrauma
|
||||
GoTo = 0x400,
|
||||
ScanAlienRuins = 0x800,
|
||||
ClearAlienRuins = 0x1000,
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | AbandonedOutpost | Escort | Pirate | GoTo | ScanAlienRuins | ClearAlienRuins
|
||||
End = 0x2000,
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | AbandonedOutpost | Escort | Pirate | GoTo | ScanAlienRuins | ClearAlienRuins | End
|
||||
}
|
||||
|
||||
partial class MissionPrefab : PrefabWithUintIdentifier
|
||||
@@ -45,14 +46,15 @@ namespace Barotrauma
|
||||
{ MissionType.Pirate, typeof(PirateMission) },
|
||||
{ MissionType.GoTo, typeof(GoToMission) },
|
||||
{ MissionType.ScanAlienRuins, typeof(ScanMission) },
|
||||
{ MissionType.ClearAlienRuins, typeof(AlienRuinMission) }
|
||||
{ MissionType.ClearAlienRuins, typeof(AlienRuinMission) },
|
||||
{ MissionType.End, typeof(EndMission) }
|
||||
};
|
||||
public static readonly Dictionary<MissionType, Type> PvPMissionClasses = new Dictionary<MissionType, Type>()
|
||||
{
|
||||
{ MissionType.Combat, typeof(CombatMission) }
|
||||
};
|
||||
|
||||
public static readonly HashSet<MissionType> HiddenMissionClasses = new HashSet<MissionType>() { MissionType.GoTo };
|
||||
public static readonly HashSet<MissionType> HiddenMissionClasses = new HashSet<MissionType>() { MissionType.GoTo, MissionType.End };
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
|
||||
@@ -62,11 +64,7 @@ namespace Barotrauma
|
||||
|
||||
public readonly Identifier TextIdentifier;
|
||||
|
||||
private readonly string[] tags;
|
||||
public IEnumerable<string> Tags
|
||||
{
|
||||
get { return tags; }
|
||||
}
|
||||
public readonly ImmutableHashSet<Identifier> Tags;
|
||||
|
||||
public readonly LocalizedString Name;
|
||||
public readonly LocalizedString Description;
|
||||
@@ -93,10 +91,18 @@ namespace Barotrauma
|
||||
|
||||
public readonly bool AllowRetry;
|
||||
|
||||
public readonly bool ShowInMenus, ShowStartMessage;
|
||||
|
||||
public readonly bool IsSideObjective;
|
||||
|
||||
public readonly bool AllowOtherMissionsInLevel;
|
||||
|
||||
public readonly bool RequireWreck, RequireRuin;
|
||||
|
||||
public readonly bool ShowProgressBar;
|
||||
public readonly int MaxProgressState;
|
||||
public readonly LocalizedString ProgressBarLabel;
|
||||
|
||||
/// <summary>
|
||||
/// The mission can only be received when travelling from a location of the first type to a location of the second type
|
||||
/// </summary>
|
||||
@@ -144,7 +150,7 @@ namespace Barotrauma
|
||||
|
||||
TextIdentifier = element.GetAttributeIdentifier("textidentifier", Identifier);
|
||||
|
||||
tags = element.GetAttributeStringArray("tags", Array.Empty<string>(), convertToLowerInvariant: true);
|
||||
Tags = element.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>()).ToImmutableHashSet();
|
||||
|
||||
string nameTag = element.GetAttributeString("name", "");
|
||||
Name = TextManager.Get($"MissionName.{TextIdentifier}");
|
||||
@@ -167,16 +173,24 @@ namespace Barotrauma
|
||||
|
||||
Reward = element.GetAttributeInt("reward", 1);
|
||||
AllowRetry = element.GetAttributeBool("allowretry", false);
|
||||
ShowInMenus = element.GetAttributeBool("showinmenus", true);
|
||||
ShowStartMessage = element.GetAttributeBool("showstartmessage", true);
|
||||
IsSideObjective = element.GetAttributeBool("sideobjective", false);
|
||||
RequireWreck = element.GetAttributeBool("requirewreck", false);
|
||||
RequireRuin = element.GetAttributeBool("requireruin", false);
|
||||
Commonness = element.GetAttributeInt("commonness", 1);
|
||||
AllowOtherMissionsInLevel = element.GetAttributeBool("allowothermissionsinlevel", true);
|
||||
if (element.GetAttribute("difficulty") != null)
|
||||
{
|
||||
int difficulty = element.GetAttributeInt("difficulty", MinDifficulty);
|
||||
Difficulty = Math.Clamp(difficulty, MinDifficulty, MaxDifficulty);
|
||||
}
|
||||
|
||||
ShowProgressBar = element.GetAttributeBool(nameof(ShowProgressBar), false);
|
||||
MaxProgressState = element.GetAttributeInt(nameof(MaxProgressState), 1);
|
||||
string progressBarLabel = element.GetAttributeString(nameof(ProgressBarLabel), "");
|
||||
ProgressBarLabel = TextManager.Get(progressBarLabel).Fallback(progressBarLabel);
|
||||
|
||||
string successMessageTag = element.GetAttributeString("successmessage", "");
|
||||
SuccessMessage = TextManager.Get($"MissionSuccess.{TextIdentifier}");
|
||||
if (!string.IsNullOrEmpty(successMessageTag))
|
||||
@@ -350,6 +364,7 @@ namespace Barotrauma
|
||||
{
|
||||
return
|
||||
AllowedLocationTypes.Any(lt => lt == "any") ||
|
||||
AllowedLocationTypes.Any(lt => lt == "anyoutpost" && from.HasOutpost()) ||
|
||||
AllowedLocationTypes.Any(lt => lt == from.Type.Identifier);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,17 +16,20 @@ namespace Barotrauma
|
||||
private readonly Level.PositionType spawnPosType;
|
||||
private Vector2? spawnPos = null;
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
yield break;
|
||||
}
|
||||
else
|
||||
{
|
||||
return sonarPositions;
|
||||
foreach (Vector2 sonarPos in sonarPositions)
|
||||
{
|
||||
yield return (Prefab.SonarLabel, sonarPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,17 +31,17 @@ namespace Barotrauma
|
||||
private Vector2 nestPosition;
|
||||
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
Enumerable.Empty<Vector2>();
|
||||
yield break;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return nestPosition;
|
||||
yield return (Prefab.SonarLabel, nestPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,7 +274,7 @@ namespace Barotrauma
|
||||
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) { return; }
|
||||
if (!Submarine.MainSub.AtEitherExit) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -36,23 +36,32 @@ namespace Barotrauma
|
||||
|
||||
private readonly List<Vector2> patrolPositions = new List<Vector2>();
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
|
||||
{
|
||||
get
|
||||
{
|
||||
var empty = Enumerable.Empty<Vector2>();
|
||||
if (outsideOfSonarRange)
|
||||
if (!outsideOfSonarRange || state > 1)
|
||||
{
|
||||
return State switch
|
||||
{
|
||||
0 => patrolPositions,
|
||||
1 => lastSighting.HasValue ? lastSighting.Value.ToEnumerable() : empty,
|
||||
_ => empty,
|
||||
};
|
||||
yield break;
|
||||
|
||||
}
|
||||
else
|
||||
else if (state == 0)
|
||||
{
|
||||
return empty;
|
||||
foreach (Vector2 patrolPos in patrolPositions)
|
||||
{
|
||||
yield return (Prefab.SonarLabel, patrolPos);
|
||||
}
|
||||
}
|
||||
else if (state == 1)
|
||||
{
|
||||
if (lastSighting.HasValue)
|
||||
{
|
||||
yield return (Prefab.SonarLabel, lastSighting.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +94,31 @@ namespace Barotrauma
|
||||
characterTypeConfig = prefab.ConfigElement.GetChildElement("CharacterTypes");
|
||||
addedMissionDifficultyPerPlayer = prefab.ConfigElement.GetAttributeFloat("addedmissiondifficultyperplayer", 0);
|
||||
|
||||
//make sure all referenced character types are defined
|
||||
foreach (XElement characterElement in characterConfig.Elements())
|
||||
{
|
||||
var characterId = characterElement.GetAttributeString("typeidentifier", string.Empty);
|
||||
var characterTypeElement = characterTypeConfig.Elements().FirstOrDefault(e => e.GetAttributeString("typeidentifier", string.Empty) == characterId);
|
||||
if (characterTypeElement == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission \"{prefab.Identifier}\". Could not find a character type element for the character \"{characterId}\".");
|
||||
}
|
||||
}
|
||||
//make sure all defined character types can be found from human prefabs
|
||||
foreach (XElement characterTypeElement in characterTypeConfig.Elements())
|
||||
{
|
||||
foreach (XElement characterElement in characterTypeElement.Elements())
|
||||
{
|
||||
Identifier characterIdentifier = characterElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
Identifier characterFrom = characterElement.GetAttributeIdentifier("from", Identifier.Empty);
|
||||
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
|
||||
if (humanPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission \"{prefab.Identifier}\". Character prefab \"{characterIdentifier}\" not found in the NPC set \"{characterFrom}\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for campaign missions, set level at construction
|
||||
LevelData levelData = locations[0].Connections.Where(c => c.Locations.Contains(locations[1])).FirstOrDefault()?.LevelData ?? locations[0]?.LevelData;
|
||||
if (levelData != null)
|
||||
@@ -100,6 +134,7 @@ namespace Barotrauma
|
||||
//level already set
|
||||
return;
|
||||
}
|
||||
submarineInfo = null;
|
||||
|
||||
levelData = level;
|
||||
missionDifficulty = level?.Difficulty ?? 0;
|
||||
@@ -117,8 +152,15 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"No path used for submarine for the pirate mission \"{Prefab.Identifier}\"!");
|
||||
return;
|
||||
}
|
||||
// maybe a little redundant
|
||||
var contentFile = ContentPackageManager.EnabledPackages.All.SelectMany(p => p.GetFiles<EnemySubmarineFile>()).FirstOrDefault(x => x.Path == submarinePath);
|
||||
|
||||
BaseSubFile contentFile =
|
||||
GetSubFile<EnemySubmarineFile>(submarinePath) ??
|
||||
GetSubFile<SubmarineFile>(submarinePath);
|
||||
BaseSubFile GetSubFile<T>(ContentPath path) where T : BaseSubFile
|
||||
{
|
||||
return ContentPackageManager.EnabledPackages.All.SelectMany(p => p.GetFiles<T>()).FirstOrDefault(f => f.Path == submarinePath);
|
||||
}
|
||||
|
||||
if (contentFile == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"No submarine file found from the path {submarinePath}!");
|
||||
@@ -241,9 +283,10 @@ namespace Barotrauma
|
||||
// it is possible to get more than the "max" amount of characters if the modified difficulty is high enough; this is intentional
|
||||
// if necessary, another "hard max" value could be used to clamp the value for performance/gameplay concerns
|
||||
int amountCreated = GetDifficultyModifiedAmount(element.GetAttributeInt("minamount", 0), element.GetAttributeInt("maxamount", 0), enemyCreationDifficulty, rand);
|
||||
var characterId = element.GetAttributeString("typeidentifier", string.Empty);
|
||||
for (int i = 0; i < amountCreated; i++)
|
||||
{
|
||||
XElement characterType = characterTypeConfig.Elements().Where(e => e.GetAttributeString("typeidentifier", string.Empty) == element.GetAttributeString("typeidentifier", string.Empty)).FirstOrDefault();
|
||||
XElement characterType = characterTypeConfig.Elements().Where(e => e.GetAttributeString("typeidentifier", string.Empty) == characterId).FirstOrDefault();
|
||||
|
||||
if (characterType == null)
|
||||
{
|
||||
@@ -253,7 +296,10 @@ namespace Barotrauma
|
||||
|
||||
XElement variantElement = GetRandomDifficultyModifiedElement(characterType, enemyCreationDifficulty, RandomnessModifier);
|
||||
|
||||
Character spawnedCharacter = CreateHuman(GetHumanPrefabFromElement(variantElement), characters, characterItems, enemySub, CharacterTeamType.None, null);
|
||||
var humanPrefab = GetHumanPrefabFromElement(variantElement);
|
||||
if (humanPrefab == null) { continue; }
|
||||
|
||||
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, enemySub, CharacterTeamType.None, null);
|
||||
if (!commanderAssigned)
|
||||
{
|
||||
bool isCommander = variantElement.GetAttributeBool("iscommander", false);
|
||||
@@ -305,8 +351,9 @@ namespace Barotrauma
|
||||
|
||||
if (enemySub == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Enemy Submarine was not created. SubmarineInfo is likely not defined.");
|
||||
// TODO: should we set the state to something here?
|
||||
DebugConsole.ThrowError(submarineInfo == null ?
|
||||
$"Error in PirateMission: enemy sub was not created (submarineInfo == null)." :
|
||||
$"Error in PirateMission: enemy sub was not created.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -345,10 +392,11 @@ namespace Barotrauma
|
||||
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (state >= 2) { return; }
|
||||
if (state >= 2 || enemySub == null) { return; }
|
||||
|
||||
float sqrSonarRange = MathUtils.Pow2(Sonar.DefaultSonarRange);
|
||||
outsideOfSonarRange = Vector2.DistanceSquared(enemySub.WorldPosition, Submarine.MainSub.WorldPosition) > sqrSonarRange;
|
||||
|
||||
if (CheckWinState())
|
||||
{
|
||||
State = 2;
|
||||
@@ -411,6 +459,7 @@ namespace Barotrauma
|
||||
characters.Clear();
|
||||
characterItems.Clear();
|
||||
failed = !completed;
|
||||
submarineInfo = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,40 +5,165 @@ using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class SalvageMission : Mission
|
||||
{
|
||||
private readonly ItemPrefab itemPrefab;
|
||||
|
||||
private Item item;
|
||||
|
||||
private readonly Level.PositionType spawnPositionType;
|
||||
|
||||
private readonly string containerTag;
|
||||
|
||||
private readonly string existingItemTag;
|
||||
|
||||
private readonly bool showMessageWhenPickedUp;
|
||||
|
||||
/// <summary>
|
||||
/// Status effects executed on the target item when the mission starts. A random effect is chosen from each child list.
|
||||
/// </summary>
|
||||
private readonly List<List<StatusEffect>> statusEffects = new List<List<StatusEffect>>();
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
private class Target
|
||||
{
|
||||
get
|
||||
public Item Item;
|
||||
|
||||
public enum RetrievalState
|
||||
{
|
||||
if (item == null)
|
||||
None = 0,
|
||||
PickedUp = 1,
|
||||
RetrievedToSub = 2
|
||||
}
|
||||
|
||||
public readonly ItemPrefab ItemPrefab;
|
||||
public readonly Level.PositionType SpawnPositionType;
|
||||
public readonly string ContainerTag;
|
||||
public readonly string ExistingItemTag;
|
||||
|
||||
public readonly bool RemoveItem;
|
||||
|
||||
public readonly LocalizedString SonarLabel;
|
||||
|
||||
public readonly bool AllowContinueBeforeRetrieved;
|
||||
|
||||
/// <summary>
|
||||
/// Does the target need to be picked up or brought to the sub for mission to be considered successful.
|
||||
/// If None, the target has no effect on the completion of the mission.
|
||||
/// </summary>
|
||||
public readonly RetrievalState RequiredRetrievalState;
|
||||
|
||||
public readonly bool HideLabelAfterRetrieved;
|
||||
|
||||
public bool Retrieved =>
|
||||
RequiredRetrievalState == RetrievalState.RetrievedToSub ?
|
||||
State == RetrievalState.RetrievedToSub :
|
||||
State != RetrievalState.None;
|
||||
|
||||
private RetrievalState state;
|
||||
public RetrievalState State
|
||||
{
|
||||
get { return state; }
|
||||
set
|
||||
{
|
||||
Enumerable.Empty<Vector2>();
|
||||
if (value == state) { return; }
|
||||
state = value;
|
||||
#if SERVER
|
||||
GameMain.Server?.UpdateMissionState(mission);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private readonly SalvageMission mission;
|
||||
|
||||
/// <summary>
|
||||
/// Status effects executed on the target item when the mission starts. A random effect is chosen from each child list.
|
||||
/// </summary>
|
||||
public readonly List<List<StatusEffect>> StatusEffects = new List<List<StatusEffect>>();
|
||||
|
||||
public Target(ContentXElement element, SalvageMission mission)
|
||||
{
|
||||
this.mission = mission;
|
||||
ContainerTag = element.GetAttributeString("containertag", "");
|
||||
RequiredRetrievalState = element.GetAttributeEnum("requireretrieval", RetrievalState.RetrievedToSub);
|
||||
AllowContinueBeforeRetrieved = element.GetAttributeBool("allowcontinuebeforeretrieved", false);
|
||||
HideLabelAfterRetrieved = element.GetAttributeBool("hidelabelafterretrieved", false);
|
||||
|
||||
string sonarLabelTag = element.GetAttributeString("sonarlabel", "");
|
||||
if (!string.IsNullOrEmpty(sonarLabelTag))
|
||||
{
|
||||
SonarLabel =
|
||||
TextManager.Get($"MissionSonarLabel.{sonarLabelTag}")
|
||||
.Fallback(TextManager.Get(sonarLabelTag))
|
||||
.Fallback(element.GetAttributeString("sonarlabel", ""));
|
||||
}
|
||||
ExistingItemTag = element.GetAttributeString("existingitemtag", "");
|
||||
|
||||
RemoveItem = element.GetAttributeBool("removeitem", true);
|
||||
|
||||
if (element.GetAttribute("itemname") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission - use item identifier instead of the name of the item.");
|
||||
string itemName = element.GetAttributeString("itemname", "");
|
||||
ItemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
|
||||
if (ItemPrefab == null && ExistingItemTag.IsNullOrEmpty())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in SalvageMission: couldn't find an item prefab with the name \"{itemName}\"");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return item.GetRootInventoryOwner()?.WorldPosition ?? item.WorldPosition;
|
||||
Identifier itemIdentifier = element.GetAttributeIdentifier("itemidentifier", Identifier.Empty);
|
||||
if (!itemIdentifier.IsEmpty)
|
||||
{
|
||||
ItemPrefab = MapEntityPrefab.FindByIdentifier(itemIdentifier.ToIdentifier()) as ItemPrefab;
|
||||
}
|
||||
if (ItemPrefab == null)
|
||||
{
|
||||
string itemTag = element.GetAttributeString("itemtag", "");
|
||||
ItemPrefab = MapEntityPrefab.GetRandom(p => p.Tags.Contains(itemTag), Rand.RandSync.Unsynced) as ItemPrefab;
|
||||
}
|
||||
if (ItemPrefab == null && ExistingItemTag.IsNullOrEmpty())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in SalvageMission - couldn't find an item prefab with the identifier \"{itemIdentifier}\"");
|
||||
}
|
||||
}
|
||||
|
||||
SpawnPositionType = element.GetAttributeEnum("spawntype", Level.PositionType.Cave | Level.PositionType.Ruin);
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "statuseffect":
|
||||
{
|
||||
var newEffect = StatusEffect.Load(subElement, parentDebugName: mission.Prefab.Name.Value);
|
||||
if (newEffect == null) { continue; }
|
||||
StatusEffects.Add(new List<StatusEffect> { newEffect });
|
||||
break;
|
||||
}
|
||||
case "chooserandom":
|
||||
StatusEffects.Add(new List<StatusEffect>());
|
||||
foreach (var effectElement in subElement.Elements())
|
||||
{
|
||||
var newEffect = StatusEffect.Load(effectElement, parentDebugName: mission.Prefab.Name.Value);
|
||||
if (newEffect == null) { continue; }
|
||||
StatusEffects.Last().Add(newEffect);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
state = RetrievalState.None;
|
||||
Item = null;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Target> targets = new List<Target>();
|
||||
|
||||
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target.Retrieved && target.HideLabelAfterRetrieved) { continue; }
|
||||
if (target.Item != null)
|
||||
{
|
||||
yield return (
|
||||
target.SonarLabel ?? Prefab.SonarLabel,
|
||||
target.Item.GetRootInventoryOwner()?.WorldPosition ?? target.Item.WorldPosition);
|
||||
}
|
||||
if (!target.AllowContinueBeforeRetrieved && !target.Retrieved) { break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,225 +171,227 @@ namespace Barotrauma
|
||||
public SalvageMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
containerTag = prefab.ConfigElement.GetAttributeString("containertag", "");
|
||||
|
||||
if (prefab.ConfigElement.GetAttribute("itemname") != null)
|
||||
foreach (ContentXElement subElement in prefab.ConfigElement.Elements())
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission - use item identifier instead of the name of the item.");
|
||||
string itemName = prefab.ConfigElement.GetAttributeString("itemname", "");
|
||||
itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
if (subElement.NameAsIdentifier() == "target")
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission: couldn't find an item prefab with the name " + itemName);
|
||||
targets.Add(new Target(subElement, this));
|
||||
}
|
||||
}
|
||||
else
|
||||
if (!targets.Any())
|
||||
{
|
||||
string itemIdentifier = prefab.ConfigElement.GetAttributeString("itemidentifier", null);
|
||||
if (itemIdentifier != null)
|
||||
{
|
||||
itemPrefab = MapEntityPrefab.FindByIdentifier(itemIdentifier.ToIdentifier()) as ItemPrefab;
|
||||
}
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
string itemTag = prefab.ConfigElement.GetAttributeString("itemtag", "");
|
||||
itemPrefab = MapEntityPrefab.GetRandom(p => p.Tags.Contains(itemTag), Rand.RandSync.Unsynced) as ItemPrefab;
|
||||
}
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission - couldn't find an item prefab with the identifier " + itemIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
existingItemTag = prefab.ConfigElement.GetAttributeString("existingitemtag", "");
|
||||
showMessageWhenPickedUp = prefab.ConfigElement.GetAttributeBool("showmessagewhenpickedup", false);
|
||||
|
||||
string spawnPositionTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
|
||||
if (string.IsNullOrWhiteSpace(spawnPositionTypeStr) ||
|
||||
!Enum.TryParse(spawnPositionTypeStr, true, out spawnPositionType))
|
||||
{
|
||||
spawnPositionType = Level.PositionType.Cave | Level.PositionType.Ruin;
|
||||
}
|
||||
|
||||
foreach (var element in prefab.ConfigElement.Elements())
|
||||
{
|
||||
switch (element.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "statuseffect":
|
||||
{
|
||||
var newEffect = StatusEffect.Load(element, parentDebugName: prefab.Name.Value);
|
||||
if (newEffect == null) { continue; }
|
||||
statusEffects.Add(new List<StatusEffect> { newEffect });
|
||||
break;
|
||||
}
|
||||
case "chooserandom":
|
||||
statusEffects.Add(new List<StatusEffect>());
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
var newEffect = StatusEffect.Load(subElement, parentDebugName: prefab.Name.Value);
|
||||
if (newEffect == null) { continue; }
|
||||
statusEffects.Last().Add(newEffect);
|
||||
}
|
||||
break;
|
||||
}
|
||||
targets.Add(new Target(prefab.ConfigElement, this));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
{
|
||||
#if SERVER
|
||||
originalInventoryID = Entity.NullEntityID;
|
||||
spawnInfo.Clear();
|
||||
#endif
|
||||
item = null;
|
||||
if (!IsClient)
|
||||
foreach (var target in targets)
|
||||
{
|
||||
//ruin/cave/wreck items are allowed to spawn close to the sub
|
||||
float minDistance = spawnPositionType == Level.PositionType.Ruin || spawnPositionType == Level.PositionType.Cave || spawnPositionType == Level.PositionType.Wreck ?
|
||||
0.0f : Level.Loaded.Size.X * 0.3f;
|
||||
Vector2 position = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f);
|
||||
|
||||
if (!string.IsNullOrEmpty(existingItemTag))
|
||||
bool usedExistingItem = false;
|
||||
UInt16 originalInventoryID = 0;
|
||||
byte originalItemContainerIndex = 0;
|
||||
int originalSlotIndex = 0;
|
||||
var executedEffectIndices = new List<(int listIndex, int effectIndex)>();
|
||||
|
||||
target.Reset();
|
||||
if (!IsClient)
|
||||
{
|
||||
var suitableItems = Item.ItemList.Where(it => it.HasTag(existingItemTag));
|
||||
switch (spawnPositionType)
|
||||
//ruin/cave/wreck items are allowed to spawn close to the sub
|
||||
float minDistance = target.SpawnPositionType switch
|
||||
{
|
||||
case Level.PositionType.Cave:
|
||||
case Level.PositionType.MainPath:
|
||||
case Level.PositionType.SidePath:
|
||||
item = suitableItems.FirstOrDefault(it => Vector2.DistanceSquared(it.WorldPosition, position) < 1000.0f);
|
||||
break;
|
||||
case Level.PositionType.Ruin:
|
||||
case Level.PositionType.Wreck:
|
||||
foreach (Item it in suitableItems)
|
||||
{
|
||||
if (it.Submarine?.Info == null) { continue; }
|
||||
if (spawnPositionType == Level.PositionType.Ruin && it.Submarine.Info.Type != SubmarineType.Ruin) { continue; }
|
||||
if (spawnPositionType == Level.PositionType.Wreck && it.Submarine.Info.Type != SubmarineType.Wreck) { continue; }
|
||||
Rectangle worldBorders = it.Submarine.Borders;
|
||||
worldBorders.Location += it.Submarine.WorldPosition.ToPoint();
|
||||
if (Submarine.RectContains(worldBorders, it.WorldPosition))
|
||||
{
|
||||
item = it;
|
||||
#if SERVER
|
||||
usedExistingItem = true;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
Level.PositionType.Ruin or
|
||||
Level.PositionType.Cave or
|
||||
Level.PositionType.Wreck or
|
||||
Level.PositionType.Outpost => 0.0f,
|
||||
_ => Level.Loaded.Size.X * 0.3f,
|
||||
};
|
||||
Vector2 position =
|
||||
target.SpawnPositionType == Level.PositionType.None ?
|
||||
Vector2.Zero :
|
||||
Level.Loaded.GetRandomItemPos(target.SpawnPositionType, 100.0f, minDistance, 30.0f);
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
item = new Item(itemPrefab, position, null);
|
||||
item.body.SetTransformIgnoreContacts(item.body.SimPosition, item.body.Rotation);
|
||||
item.body.FarseerBody.BodyType = BodyType.Kinematic;
|
||||
}
|
||||
|
||||
for (int i = 0; i < statusEffects.Count; i++)
|
||||
{
|
||||
List<StatusEffect> effectList = statusEffects[i];
|
||||
if (effectList.Count == 0) { continue; }
|
||||
int effectIndex = Rand.Int(effectList.Count);
|
||||
var selectedEffect = effectList[effectIndex];
|
||||
item.ApplyStatusEffect(selectedEffect, selectedEffect.type, deltaTime: 1.0f, worldPosition: item.Position);
|
||||
#if SERVER
|
||||
executedEffectIndices.Add(new Pair<int, int>(i, effectIndex));
|
||||
#endif
|
||||
}
|
||||
|
||||
//try to find a container and place the item inside it
|
||||
if (!string.IsNullOrEmpty(containerTag) && item.ParentInventory == null)
|
||||
{
|
||||
List<ItemContainer> validContainers = new List<ItemContainer>();
|
||||
foreach (Item it in Item.ItemList)
|
||||
if (!string.IsNullOrEmpty(target.ExistingItemTag))
|
||||
{
|
||||
if (!it.HasTag(containerTag)) { continue; }
|
||||
if (!it.IsPlayerTeamInteractable) { continue; }
|
||||
switch (spawnPositionType)
|
||||
var suitableItems = Item.ItemList.Where(it => it.HasTag(target.ExistingItemTag));
|
||||
switch (target.SpawnPositionType)
|
||||
{
|
||||
case Level.PositionType.Cave:
|
||||
case Level.PositionType.MainPath:
|
||||
if (it.Submarine != null) { continue; }
|
||||
case Level.PositionType.SidePath:
|
||||
target.Item = suitableItems.FirstOrDefault(it => Vector2.DistanceSquared(it.WorldPosition, position) < 1000.0f);
|
||||
#if SERVER
|
||||
usedExistingItem = target.Item != null;
|
||||
#endif
|
||||
break;
|
||||
case Level.PositionType.Ruin:
|
||||
if (it.Submarine?.Info == null || !it.Submarine.Info.IsRuin) { continue; }
|
||||
break;
|
||||
case Level.PositionType.Wreck:
|
||||
if (it.Submarine == null || it.Submarine.Info.Type != SubmarineType.Wreck) { continue; }
|
||||
case Level.PositionType.Outpost:
|
||||
foreach (Item it in suitableItems)
|
||||
{
|
||||
if (it.Submarine?.Info == null) { continue; }
|
||||
if (target.SpawnPositionType == Level.PositionType.Ruin && it.Submarine.Info.Type != SubmarineType.Ruin) { continue; }
|
||||
if (target.SpawnPositionType == Level.PositionType.Wreck && it.Submarine.Info.Type != SubmarineType.Wreck) { continue; }
|
||||
if (target.SpawnPositionType == Level.PositionType.Outpost && it.Submarine.Info.Type != SubmarineType.Outpost) { continue; }
|
||||
Rectangle worldBorders = it.Submarine.Borders;
|
||||
worldBorders.Location += it.Submarine.WorldPosition.ToPoint();
|
||||
if (Submarine.RectContains(worldBorders, it.WorldPosition))
|
||||
{
|
||||
target.Item = it;
|
||||
#if SERVER
|
||||
usedExistingItem = true;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
target.Item = suitableItems.FirstOrDefault();
|
||||
#if SERVER
|
||||
usedExistingItem = target.Item != null;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
var itemContainer = it.GetComponent<ItemContainer>();
|
||||
if (itemContainer != null && itemContainer.Inventory.CanBePut(item)) { validContainers.Add(itemContainer); }
|
||||
}
|
||||
if (validContainers.Any())
|
||||
|
||||
if (target.Item == null)
|
||||
{
|
||||
var selectedContainer = validContainers.GetRandomUnsynced();
|
||||
if (selectedContainer.Combine(item, user: null))
|
||||
if (target.ItemPrefab == null && string.IsNullOrEmpty(target.ContainerTag))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to find a target item for the mission \"{Prefab.Identifier}\". Item tag: {target.ExistingItemTag ?? "null"}");
|
||||
continue;
|
||||
}
|
||||
target.Item = new Item(target.ItemPrefab, position, null);
|
||||
target.Item.body.SetTransformIgnoreContacts(target.Item.body.SimPosition, target.Item.body.Rotation);
|
||||
target.Item.body.FarseerBody.BodyType = BodyType.Kinematic;
|
||||
}
|
||||
for (int i = 0; i < target.StatusEffects.Count; i++)
|
||||
{
|
||||
List<StatusEffect> effectList = target.StatusEffects[i];
|
||||
if (effectList.Count == 0) { continue; }
|
||||
int effectIndex = Rand.Int(effectList.Count);
|
||||
var selectedEffect = effectList[effectIndex];
|
||||
target.Item.ApplyStatusEffect(selectedEffect, selectedEffect.type, deltaTime: 1.0f, worldPosition: target.Item.Position);
|
||||
#if SERVER
|
||||
originalInventoryID = selectedContainer.Item.ID;
|
||||
originalItemContainerIndex = (byte)selectedContainer.Item.GetComponentIndex(selectedContainer);
|
||||
originalSlotIndex = item.ParentInventory?.FindIndex(item) ?? -1;
|
||||
executedEffectIndices.Add((i, effectIndex));
|
||||
#endif
|
||||
} // Placement successful
|
||||
}
|
||||
|
||||
//try to find a container and place the item inside it
|
||||
if (!string.IsNullOrEmpty(target.ContainerTag) && target.Item.ParentInventory == null)
|
||||
{
|
||||
List<ItemContainer> validContainers = new List<ItemContainer>();
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (!it.HasTag(target.ContainerTag)) { continue; }
|
||||
if (!it.IsPlayerTeamInteractable) { continue; }
|
||||
switch (target.SpawnPositionType)
|
||||
{
|
||||
case Level.PositionType.Cave:
|
||||
case Level.PositionType.MainPath:
|
||||
if (it.Submarine != null) { continue; }
|
||||
break;
|
||||
case Level.PositionType.Ruin:
|
||||
if (it.Submarine?.Info == null || !it.Submarine.Info.IsRuin) { continue; }
|
||||
break;
|
||||
case Level.PositionType.Wreck:
|
||||
if (it.Submarine?.Info == null || it.Submarine.Info.Type != SubmarineType.Wreck) { continue; }
|
||||
break;
|
||||
}
|
||||
var itemContainer = it.GetComponent<ItemContainer>();
|
||||
if (itemContainer != null && itemContainer.Inventory.CanBePut(target.Item)) { validContainers.Add(itemContainer); }
|
||||
}
|
||||
if (validContainers.Any())
|
||||
{
|
||||
var selectedContainer = validContainers.GetRandomUnsynced();
|
||||
if (selectedContainer.Combine(target.Item, user: null))
|
||||
{
|
||||
#if SERVER
|
||||
originalInventoryID = selectedContainer.Item.ID;
|
||||
originalItemContainerIndex = (byte)selectedContainer.Item.GetComponentIndex(selectedContainer);
|
||||
originalSlotIndex = target.Item.ParentInventory?.FindIndex(target.Item) ?? -1;
|
||||
#endif
|
||||
} // Placement successful
|
||||
}
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
spawnInfo.Add(
|
||||
target,
|
||||
new SpawnInfo(usedExistingItem, originalInventoryID, originalItemContainerIndex, originalSlotIndex, executedEffectIndices));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (item == null)
|
||||
//make body dynamic when picked up
|
||||
foreach (var target in targets)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Error in salvage mission " + Prefab.Identifier + " (item was null)");
|
||||
#endif
|
||||
return;
|
||||
var root = target.Item?.GetRootContainer() ?? target.Item;
|
||||
if (root == null) { continue; }
|
||||
if (target.Item.ParentInventory != null && target.Item.body != null) { target.Item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
}
|
||||
|
||||
if (IsClient)
|
||||
if (IsClient) { return; }
|
||||
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
{
|
||||
if (item.ParentInventory != null && item.body != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
return;
|
||||
}
|
||||
switch (State)
|
||||
{
|
||||
case 0:
|
||||
if (item.ParentInventory != null && item.body != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
if (showMessageWhenPickedUp)
|
||||
{
|
||||
if (!(item.GetRootInventoryOwner() is Character)) { return; }
|
||||
}
|
||||
else
|
||||
{
|
||||
Submarine parentSub = item.CurrentHull?.Submarine ?? item.GetRootInventoryOwner()?.Submarine;
|
||||
if (parentSub == null || parentSub.Info.Type != SubmarineType.Player)
|
||||
var target = targets[i];
|
||||
if (i > 0 && !targets[i - 1].AllowContinueBeforeRetrieved && !targets[i - 1].Retrieved) { break; }
|
||||
if (target.Item == null)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Error in salvage mission " + Prefab.Identifier + " (item was null)");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
switch (target.State)
|
||||
{
|
||||
case Target.RetrievalState.None:
|
||||
var root = target.Item?.GetRootContainer() ?? target.Item;
|
||||
if (root.ParentInventory?.Owner is Character character && character.TeamID == CharacterTeamType.Team1)
|
||||
{
|
||||
return;
|
||||
target.State = Target.RetrievalState.PickedUp;
|
||||
if (target.Retrieved) { State = i + 1 ; }
|
||||
}
|
||||
}
|
||||
State = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
break;
|
||||
case Target.RetrievalState.PickedUp:
|
||||
Submarine parentSub = target.Item.CurrentHull?.Submarine ?? target.Item.GetRootInventoryOwner()?.Submarine;
|
||||
if (parentSub != null && parentSub.Info.Type == SubmarineType.Player)
|
||||
{
|
||||
target.State = Target.RetrievalState.RetrievedToSub;
|
||||
if (target.Retrieved) { State = i + 1; }
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (targets.All(t => t.Retrieved))
|
||||
{
|
||||
State = targets.Count + 1;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool DetermineCompleted()
|
||||
{
|
||||
var root = item?.GetRootContainer() ?? item;
|
||||
return root?.CurrentHull?.Submarine != null && (root.CurrentHull.Submarine.AtEndExit || root.CurrentHull.Submarine.AtStartExit) && !item.Removed;
|
||||
return targets.All(t => t.State >= t.RequiredRetrievalState);
|
||||
}
|
||||
|
||||
protected override void EndMissionSpecific(bool completed)
|
||||
{
|
||||
item?.Remove();
|
||||
item = null;
|
||||
failed = !completed && state > 0;
|
||||
//consider failed (can't attempt again) if we picked up any of the items but failed to bring them out of the level
|
||||
failed = !completed && targets.Any(t => t.State != Target.RetrievalState.None);
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target.RemoveItem)
|
||||
{
|
||||
target.Item?.Remove();
|
||||
target.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,25 +32,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
public override IEnumerable<(LocalizedString Label, Vector2 Position)> SonarLabels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
if (State > 0 || scanTargets.None())
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
}
|
||||
else if (scanTargets.Any())
|
||||
{
|
||||
return scanTargets
|
||||
.Where(kvp => !kvp.Value)
|
||||
.Select(kvp => kvp.Key.WorldPosition);
|
||||
return Enumerable.Empty<(LocalizedString Label, Vector2 Position)>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
}
|
||||
|
||||
return scanTargets
|
||||
.Where(kvp => !kvp.Value)
|
||||
.Select(kvp => (Prefab.SonarLabel, kvp.Key.WorldPosition));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user