Build 1.1.4.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;
|
||||
|
||||
@@ -9,6 +9,8 @@ namespace Barotrauma
|
||||
public event Action Finished;
|
||||
protected bool isFinished;
|
||||
|
||||
public int RandomSeed;
|
||||
|
||||
protected readonly EventPrefab prefab;
|
||||
|
||||
public EventPrefab Prefab => prefab;
|
||||
|
||||
+3
-9
@@ -1,3 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -15,20 +16,13 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no target tag! This will cause the check to automatically succeed.");
|
||||
}
|
||||
foreach (var attribute in element.Attributes())
|
||||
{
|
||||
if (PropertyConditional.IsValid(attribute) && !IsTargetTagAttribute(attribute))
|
||||
{
|
||||
Conditional = new PropertyConditional(attribute);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Conditional = PropertyConditional.FromXElement(element, IsNotTargetTagAttribute).FirstOrDefault();
|
||||
if (Conditional == null)
|
||||
{
|
||||
DebugConsole.LogError($"CheckConditionalAction error: {GetEventName()} uses a CheckConditionalAction with no valid PropertyConditional! This will cause the check to automatically succeed.");
|
||||
}
|
||||
|
||||
static bool IsTargetTagAttribute(XAttribute attribute) => attribute.NameAsIdentifier() == "targettag";
|
||||
static bool IsNotTargetTagAttribute(XAttribute attribute) => attribute.NameAsIdentifier() != "targettag";
|
||||
}
|
||||
|
||||
private string GetEventName()
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Barotrauma
|
||||
protected object? value2;
|
||||
protected object? value1;
|
||||
|
||||
protected PropertyConditional.OperatorType Operator { get; set; }
|
||||
protected PropertyConditional.ComparisonOperatorType Operator { get; set; }
|
||||
|
||||
public CheckDataAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
@@ -56,23 +56,13 @@ namespace Barotrauma
|
||||
{
|
||||
if (GameMain.GameSession?.GameMode is not CampaignMode campaignMode) { return false; }
|
||||
|
||||
string[] splitString = Condition.Split(' ');
|
||||
string value;
|
||||
if (splitString.Length > 0)
|
||||
(Operator, string value) = PropertyConditional.ExtractComparisonOperatorFromConditionString(Condition);
|
||||
if (Operator == PropertyConditional.ComparisonOperatorType.None)
|
||||
{
|
||||
//the first part of the string is the operator, skip it
|
||||
value = string.Join(" ", splitString.Skip(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"{Condition} is too short, it should start with an operator followed by a boolean or a floating point value.");
|
||||
DebugConsole.ThrowError($"{Condition} is invalid, it should start with an operator followed by a boolean or a floating point value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
string op = splitString[0];
|
||||
Operator = PropertyConditional.GetOperatorType(op);
|
||||
if (Operator == PropertyConditional.OperatorType.None) { return false; }
|
||||
|
||||
if (CheckAgainstMetadata)
|
||||
{
|
||||
object? metadata1 = campaignMode.CampaignMetadata.GetValue(Identifier);
|
||||
@@ -82,8 +72,8 @@ namespace Barotrauma
|
||||
{
|
||||
return Operator switch
|
||||
{
|
||||
PropertyConditional.OperatorType.Equals => metadata1 == metadata2,
|
||||
PropertyConditional.OperatorType.NotEquals => metadata1 != metadata2,
|
||||
PropertyConditional.ComparisonOperatorType.Equals => metadata1 == metadata2,
|
||||
PropertyConditional.ComparisonOperatorType.NotEquals => metadata1 != metadata2,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
@@ -139,9 +129,9 @@ namespace Barotrauma
|
||||
value2 = val2;
|
||||
switch (Operator)
|
||||
{
|
||||
case PropertyConditional.OperatorType.Equals:
|
||||
case PropertyConditional.ComparisonOperatorType.Equals:
|
||||
return val1 == val2;
|
||||
case PropertyConditional.OperatorType.NotEquals:
|
||||
case PropertyConditional.ComparisonOperatorType.NotEquals:
|
||||
return val1 != val2;
|
||||
default:
|
||||
DebugConsole.Log($"Only \"Equals\" and \"Not equals\" operators are allowed for a boolean (was {Operator} for {val2}).");
|
||||
@@ -166,17 +156,17 @@ namespace Barotrauma
|
||||
value2 = val2;
|
||||
switch (Operator)
|
||||
{
|
||||
case PropertyConditional.OperatorType.Equals:
|
||||
case PropertyConditional.ComparisonOperatorType.Equals:
|
||||
return MathUtils.NearlyEqual(val1, val2);
|
||||
case PropertyConditional.OperatorType.GreaterThan:
|
||||
case PropertyConditional.ComparisonOperatorType.GreaterThan:
|
||||
return val1 > val2;
|
||||
case PropertyConditional.OperatorType.GreaterThanEquals:
|
||||
case PropertyConditional.ComparisonOperatorType.GreaterThanEquals:
|
||||
return val1 >= val2;
|
||||
case PropertyConditional.OperatorType.LessThan:
|
||||
case PropertyConditional.ComparisonOperatorType.LessThan:
|
||||
return val1 < val2;
|
||||
case PropertyConditional.OperatorType.LessThanEquals:
|
||||
case PropertyConditional.ComparisonOperatorType.LessThanEquals:
|
||||
return val1 <= val2;
|
||||
case PropertyConditional.OperatorType.NotEquals:
|
||||
case PropertyConditional.ComparisonOperatorType.NotEquals:
|
||||
return !MathUtils.NearlyEqual(val1, val2);
|
||||
}
|
||||
|
||||
@@ -195,9 +185,9 @@ namespace Barotrauma
|
||||
bool equals = string.Equals(val1, val2, StringComparison.OrdinalIgnoreCase);
|
||||
switch (Operator)
|
||||
{
|
||||
case PropertyConditional.OperatorType.Equals:
|
||||
case PropertyConditional.ComparisonOperatorType.Equals:
|
||||
return equals;
|
||||
case PropertyConditional.OperatorType.NotEquals:
|
||||
case PropertyConditional.ComparisonOperatorType.NotEquals:
|
||||
return !equals;
|
||||
default:
|
||||
DebugConsole.Log($"Only \"Equals\" and \"Not equals\" operators are allowed for a string (was {Operator} for {val2}).");
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Barotrauma
|
||||
public int ItemContainerIndex { get; set; }
|
||||
|
||||
private readonly IReadOnlyList<PropertyConditional> conditionals;
|
||||
|
||||
|
||||
private readonly Identifier[] itemIdentifierSplit;
|
||||
private readonly Identifier[] itemTags;
|
||||
|
||||
@@ -44,13 +44,7 @@ namespace Barotrauma
|
||||
var conditionalList = new List<PropertyConditional>();
|
||||
foreach (ContentXElement subElement in element.GetChildElements("conditional"))
|
||||
{
|
||||
foreach (XAttribute attribute in subElement.Attributes())
|
||||
{
|
||||
if (PropertyConditional.IsValid(attribute))
|
||||
{
|
||||
conditionalList.Add(new PropertyConditional(attribute));
|
||||
}
|
||||
}
|
||||
conditionalList.AddRange(PropertyConditional.FromXElement(subElement));
|
||||
break;
|
||||
}
|
||||
conditionals = conditionalList;
|
||||
|
||||
@@ -200,7 +200,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private int[] GetEndingOptions()
|
||||
public int[] GetEndingOptions()
|
||||
{
|
||||
List<int> endings = Options.Where(group => !group.Actions.Any() || group.EndConversation).Select(group => Options.IndexOf(group)).ToList();
|
||||
if (!ContinueConversation) { endings.Add(-1); }
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MissionAction : EventAction
|
||||
partial class MissionAction : EventAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier MissionIdentifier { get; set; }
|
||||
@@ -14,8 +15,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; }
|
||||
@@ -28,6 +31,8 @@ namespace Barotrauma
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
private readonly Random random;
|
||||
|
||||
public MissionAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (MissionIdentifier.IsEmpty && MissionTag.IsEmpty)
|
||||
@@ -38,6 +43,8 @@ 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();
|
||||
random = new MTRandom(parentEvent.RandomSeed);
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
@@ -56,14 +63,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, LocationType.Prefabs[LocationTypes[0]]);
|
||||
unlockLocation = emptyLocation;
|
||||
}
|
||||
}
|
||||
@@ -72,11 +79,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (!MissionIdentifier.IsEmpty)
|
||||
{
|
||||
unlockedMission = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier);
|
||||
unlockedMission = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier);
|
||||
}
|
||||
else if (!MissionTag.IsEmpty)
|
||||
{
|
||||
unlockedMission = unlockLocation.UnlockMissionByTag(MissionTag);
|
||||
unlockedMission = unlockLocation.UnlockMissionByTag(MissionTag, random);
|
||||
}
|
||||
if (campaign is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
@@ -84,7 +91,9 @@ namespace Barotrauma
|
||||
}
|
||||
if (unlockedMission != null)
|
||||
{
|
||||
if (unlockedMission.Locations[0] == unlockedMission.Locations[1] || unlockedMission.Locations[1] ==null)
|
||||
unlockedMission.OriginLocation = campaign.Map.CurrentLocation;
|
||||
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,66 +108,86 @@ namespace Barotrauma
|
||||
IconColor = unlockedMission.Prefab.IconColor
|
||||
};
|
||||
#else
|
||||
missionsUnlockedThisRound.Add(unlockedMission);
|
||||
NotifyMissionUnlock(unlockedMission);
|
||||
#endif
|
||||
#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()
|
||||
{
|
||||
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(MissionAction)} -> ({(MissionIdentifier.IsEmpty ? MissionTag : MissionIdentifier)})";
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
private void NotifyMissionUnlock(Mission mission)
|
||||
{
|
||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||
outmsg.WriteByte((byte)ServerPacketHeader.EVENTACTION);
|
||||
outmsg.WriteByte((byte)EventManager.NetworkEventType.MISSION);
|
||||
outmsg.WriteIdentifier(mission.Prefab.Identifier);
|
||||
outmsg.WriteString(mission.Name.Value);
|
||||
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -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 if (!location.LocationTypeChangesBlocked)
|
||||
{
|
||||
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)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-13
@@ -1,8 +1,6 @@
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -12,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; }
|
||||
@@ -24,10 +22,13 @@ namespace Barotrauma
|
||||
|
||||
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(TeamTag))
|
||||
if (!enums.Contains(TeamID))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in {nameof(NPCChangeTeamAction)} in the event {ParentEvent.Prefab.Identifier}. \"{TeamTag}\" is not a valid Team ID. Valid values are {string.Join(',', Enum.GetNames(typeof(CharacterTeamType)))}.");
|
||||
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)))}.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,27 +42,34 @@ 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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,11 +80,10 @@ namespace Barotrauma
|
||||
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,18 @@ namespace Barotrauma
|
||||
|
||||
if (Wait)
|
||||
{
|
||||
gotoObjective = new AIObjectiveGoTo(npc, npc, humanAiController.ObjectiveManager, repeat: true)
|
||||
var gotoObjective = new AIObjectiveGoTo(
|
||||
AIObjectiveGoTo.GetTargetHull(npc) as ISpatialEntity ?? 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 +59,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()})";
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -11,17 +9,20 @@ namespace Barotrauma
|
||||
public Identifier TargetTag { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ItemIdentifier { get; set; }
|
||||
public string ItemIdentifiers { get; set; }
|
||||
|
||||
[Serialize(1, IsPropertySaveable.Yes)]
|
||||
public int Amount { get; set; }
|
||||
|
||||
public RemoveItemAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (ItemIdentifier.IsEmpty)
|
||||
private readonly ImmutableHashSet<Identifier> itemIdentifierSplit;
|
||||
|
||||
public RemoveItemAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ItemIdentifiers))
|
||||
{
|
||||
ItemIdentifier = element.GetAttributeIdentifier("itemidentifiers", element.GetAttributeIdentifier("identifier", Identifier.Empty));
|
||||
ItemIdentifiers = element.GetAttributeString("itemidentifier", element.GetAttributeString("identifier", string.Empty));
|
||||
}
|
||||
itemIdentifierSplit = ItemIdentifiers.Split(',').ToIdentifiers().ToImmutableHashSet();
|
||||
}
|
||||
|
||||
private bool isFinished = false;
|
||||
@@ -62,7 +63,7 @@ namespace Barotrauma
|
||||
var item = inventory.FindItem(it =>
|
||||
it != null &&
|
||||
!removedItems.Contains(it) &&
|
||||
(ItemIdentifier.IsEmpty || it.Prefab.Identifier == ItemIdentifier), recursive: true);
|
||||
(itemIdentifierSplit.Count == 0 || itemIdentifierSplit.Contains(it.Prefab.Identifier)), recursive: true);
|
||||
if (item == null) { break; }
|
||||
Entity.Spawner.AddItemToRemoveQueue(item);
|
||||
removedItems.Add(item);
|
||||
@@ -70,7 +71,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (target is Item item)
|
||||
{
|
||||
if (ItemIdentifier.IsEmpty || item.Prefab.Identifier == ItemIdentifier)
|
||||
if (itemIdentifierSplit.Count == 0 || itemIdentifierSplit.Contains(item.Prefab.Identifier))
|
||||
{
|
||||
Entity.Spawner.AddItemToRemoveQueue(item);
|
||||
removedItems.Add(item);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,14 @@ namespace Barotrauma
|
||||
public SpawnAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
{
|
||||
ignoreSpawnPointType = element.GetAttribute("spawnpointtype") == null;
|
||||
//backwards compatibility
|
||||
TeamID = element.GetAttributeEnum("teamtag", element.GetAttributeEnum("team", TeamID));
|
||||
if (element.GetAttribute("submarinetype") != null)
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
$"Error in even \"{(parentEvent.Prefab?.Identifier.ToString() ?? "unknown")}\". " +
|
||||
$"The attribute \"submarinetype\" is not valid in {nameof(SpawnAction)}. Did you mean {nameof(SpawnLocation)}?");
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsFinished(ref string goTo)
|
||||
@@ -118,7 +126,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,13 +159,13 @@ 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);
|
||||
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine, spawnPos as WayPoint);
|
||||
if (LootingIsStealing)
|
||||
{
|
||||
foreach (Item item in newCharacter.Inventory.FindAllItems(recursive: true))
|
||||
@@ -151,6 +180,18 @@ 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);
|
||||
}
|
||||
}
|
||||
#if SERVER
|
||||
newCharacter.LoadTalents();
|
||||
GameMain.NetworkMember.CreateEntityEvent(newCharacter, new Character.UpdateTalentsEventData());
|
||||
#endif
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -165,7 +206,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)
|
||||
{
|
||||
@@ -211,7 +252,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 +280,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;
|
||||
@@ -289,30 +330,24 @@ namespace Barotrauma
|
||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<Identifier> moduleFlags = null, IEnumerable<Identifier> spawnpointTags = null, bool asFarAsPossibleFromAirlock = false, bool requireTaggedSpawnPoint = false)
|
||||
{
|
||||
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);
|
||||
|
||||
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.IsTraversable);
|
||||
if (moduleFlags != null && moduleFlags.Any())
|
||||
{
|
||||
List<WayPoint> spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags.Any(moduleFlags.Contains) ?? false).ToList();
|
||||
var spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull is Hull h && h.OutpostModuleTags.Any(moduleFlags.Contains));
|
||||
if (spawnPoints.Any())
|
||||
{
|
||||
potentialSpawnPoints = spawnPoints;
|
||||
potentialSpawnPoints = spawnPoints.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnpointTags != null && spawnpointTags.Any())
|
||||
{
|
||||
var spawnPoints = potentialSpawnPoints
|
||||
.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag) && wp.ConnectedDoor == null && !wp.isObstructed));
|
||||
|
||||
var spawnPoints = potentialSpawnPoints.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag) && wp.ConnectedDoor == null && wp.IsTraversable));
|
||||
if (requireTaggedSpawnPoint || spawnPoints.Any())
|
||||
{
|
||||
potentialSpawnPoints = spawnPoints.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
if (potentialSpawnPoints.None())
|
||||
{
|
||||
if (requireTaggedSpawnPoint && spawnpointTags != null && spawnpointTags.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()
|
||||
|
||||
@@ -21,6 +21,9 @@ namespace Barotrauma
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool IgnoreIncapacitatedCharacters { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool AllowHiddenItems { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public TagAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||
@@ -119,12 +122,12 @@ namespace Barotrauma
|
||||
|
||||
private void TagItemsByIdentifier(Identifier identifier)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && SubmarineTypeMatches(it.Submarine) && it.Prefab.Identifier == identifier);
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && IsValidItem(it) && it.Prefab.Identifier == identifier);
|
||||
}
|
||||
|
||||
private void TagItemsByTag(Identifier tag)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && SubmarineTypeMatches(it.Submarine) && it.HasTag(tag));
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Item it && IsValidItem(it) && it.HasTag(tag));
|
||||
}
|
||||
|
||||
private void TagHullsByName(Identifier name)
|
||||
@@ -137,6 +140,11 @@ namespace Barotrauma
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Submarine s && SubmarineTypeMatches(s) && (type.IsEmpty || type == s.Info?.Type.ToIdentifier()));
|
||||
}
|
||||
|
||||
private bool IsValidItem(Item it)
|
||||
{
|
||||
return (!it.HiddenInGame || AllowHiddenItems) && SubmarineTypeMatches(it.Submarine);
|
||||
}
|
||||
|
||||
private bool SubmarineTypeMatches(Submarine sub)
|
||||
{
|
||||
if (SubmarineType == SubType.Any) { return true; }
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
namespace Barotrauma
|
||||
{
|
||||
class TriggerEventAction : EventAction
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier Identifier { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool NextRound { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public TriggerEventAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
@@ -26,17 +27,24 @@ namespace Barotrauma
|
||||
|
||||
if (GameMain.GameSession?.EventManager != null)
|
||||
{
|
||||
var eventPrefab = EventSet.GetEventPrefab(Identifier);
|
||||
if (eventPrefab == null)
|
||||
if (NextRound)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in TriggerEventAction - could not find an event with the identifier {Identifier}.");
|
||||
GameMain.GameSession.EventManager.QueuedEventsForNextRound.Enqueue(Identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ev = eventPrefab.CreateInstance();
|
||||
if (ev != null)
|
||||
var eventPrefab = EventSet.GetEventPrefab(Identifier);
|
||||
if (eventPrefab == null)
|
||||
{
|
||||
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(ev);
|
||||
DebugConsole.ThrowError($"Error in TriggerEventAction - could not find an event with the identifier {Identifier}.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var ev = eventPrefab.CreateInstance();
|
||||
if (ev != null)
|
||||
{
|
||||
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class WaitAction : EventAction
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -12,6 +14,7 @@ namespace Barotrauma
|
||||
public enum NetworkEventType
|
||||
{
|
||||
CONVERSATION,
|
||||
CONVERSATION_SELECTED_OPTION,
|
||||
STATUSEFFECT,
|
||||
MISSION,
|
||||
UNLOCKPATH
|
||||
@@ -72,8 +75,7 @@ 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<EventSet> usedUniqueSets = new HashSet<EventSet>();
|
||||
private readonly HashSet<Identifier> nonRepeatableEvents = new HashSet<Identifier>();
|
||||
|
||||
|
||||
#if DEBUG && SERVER
|
||||
@@ -100,7 +102,9 @@ namespace Barotrauma
|
||||
|
||||
public readonly Queue<Event> QueuedEvents = new Queue<Event>();
|
||||
|
||||
private struct TimeStamp
|
||||
public readonly Queue<Identifier> QueuedEventsForNextRound = new Queue<Identifier>();
|
||||
|
||||
private readonly struct TimeStamp
|
||||
{
|
||||
public readonly double Time;
|
||||
public readonly Event Event;
|
||||
@@ -122,7 +126,8 @@ namespace Barotrauma
|
||||
|
||||
public bool Enabled = true;
|
||||
|
||||
private MTRandom rand;
|
||||
private MTRandom random;
|
||||
private int randomSeed;
|
||||
|
||||
public void StartRound(Level level)
|
||||
{
|
||||
@@ -134,7 +139,9 @@ namespace Barotrauma
|
||||
pendingEventSets.Clear();
|
||||
selectedEvents.Clear();
|
||||
activeEvents.Clear();
|
||||
|
||||
#if SERVER
|
||||
MissionAction.ResetMissionsUnlockedThisRound();
|
||||
#endif
|
||||
pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
||||
totalPathLength = 0.0f;
|
||||
if (level != null)
|
||||
@@ -144,23 +151,22 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SelectSettings();
|
||||
|
||||
int seed = 0;
|
||||
|
||||
if (level != null)
|
||||
{
|
||||
seed = ToolBox.StringToInt(level.Seed);
|
||||
randomSeed = ToolBox.StringToInt(level.Seed);
|
||||
foreach (var previousEvent in level.LevelData.EventHistory)
|
||||
{
|
||||
seed ^= ToolBox.IdentifierToInt(previousEvent.Identifier);
|
||||
randomSeed ^= ToolBox.IdentifierToInt(previousEvent);
|
||||
}
|
||||
}
|
||||
rand = new MTRandom(seed);
|
||||
random = new MTRandom(randomSeed);
|
||||
|
||||
bool playingCampaign = GameMain.GameSession?.GameMode is CampaignMode;
|
||||
EventSet initialEventSet = SelectRandomEvents(
|
||||
EventSet.Prefabs.ToList(),
|
||||
requireCampaignSet: playingCampaign,
|
||||
random: rand);
|
||||
random: random);
|
||||
EventSet additiveSet = null;
|
||||
if (initialEventSet != null && initialEventSet.Additive)
|
||||
{
|
||||
@@ -168,7 +174,7 @@ namespace Barotrauma
|
||||
initialEventSet = SelectRandomEvents(
|
||||
EventSet.Prefabs.Where(e => !e.Additive).ToList(),
|
||||
requireCampaignSet: playingCampaign,
|
||||
random: rand);
|
||||
random: random);
|
||||
}
|
||||
if (initialEventSet != null)
|
||||
{
|
||||
@@ -188,14 +194,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();
|
||||
@@ -216,7 +215,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)
|
||||
@@ -226,6 +225,21 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
while (QueuedEventsForNextRound.Count > 0 && QueuedEventsForNextRound.Dequeue() is Identifier id)
|
||||
{
|
||||
var eventPrefab = EventSet.GetEventPrefab(id);
|
||||
if (eventPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in EventManager.StartRound - could not find an event with the identifier {id}.");
|
||||
continue;
|
||||
}
|
||||
var ev = eventPrefab.CreateInstance();
|
||||
if (ev != null)
|
||||
{
|
||||
QueuedEvents.Enqueue(ev);
|
||||
}
|
||||
}
|
||||
|
||||
PreloadContent(GetFilesToPreload());
|
||||
|
||||
roundDuration = 0.0f;
|
||||
@@ -358,7 +372,6 @@ namespace Barotrauma
|
||||
QueuedEvents.Clear();
|
||||
finishedEvents.Clear();
|
||||
nonRepeatableEvents.Clear();
|
||||
usedUniqueSets.Clear();
|
||||
|
||||
preloadedSprites.ForEach(s => s.Remove());
|
||||
preloadedSprites.Clear();
|
||||
@@ -370,20 +383,49 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Registers the exhaustible events in the level as exhausted, and adds the current events to the event history
|
||||
/// </summary>
|
||||
public void RegisterEventHistory()
|
||||
public void RegisterEventHistory(bool registerFinishedOnly = false)
|
||||
{
|
||||
if (level?.LevelData == null) { return; }
|
||||
|
||||
level.LevelData.EventsExhausted = true;
|
||||
level.LevelData.EventsExhausted = !registerFinishedOnly;
|
||||
|
||||
if (level.LevelData.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
|
||||
if (registerFinishedOnly)
|
||||
{
|
||||
foreach (var finishedEvent in finishedEvents)
|
||||
{
|
||||
var key = finishedEvent.ParentSet;
|
||||
if (key == null) { continue; }
|
||||
if (level.LevelData.FinishedEvents.ContainsKey(key))
|
||||
{
|
||||
level.LevelData.FinishedEvents[key] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
level.LevelData.FinishedEvents.Add(key, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
level.LevelData.EventHistory.AddRange(selectedEvents.Values
|
||||
.SelectMany(v => v)
|
||||
.Select(e => e.Prefab.Identifier)
|
||||
.Where(eventId => Register(eventId) && !level.LevelData.EventHistory.Contains(eventId)));
|
||||
|
||||
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)));
|
||||
level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(eventId => Register(eventId) && !level.LevelData.NonRepeatableEvents.Contains(eventId)));
|
||||
|
||||
if (!registerFinishedOnly)
|
||||
{
|
||||
level.LevelData.FinishedEvents.Clear();
|
||||
}
|
||||
|
||||
bool Register(Identifier eventId) => !registerFinishedOnly || finishedEvents.Any(fe => fe.Prefab.Identifier == eventId);
|
||||
}
|
||||
|
||||
public void SkipEventCooldown()
|
||||
@@ -393,9 +435,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;
|
||||
}
|
||||
|
||||
@@ -436,9 +478,13 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
bool isPrefabSuitable(EventPrefab e)
|
||||
=> (e.BiomeIdentifier.IsEmpty || e.BiomeIdentifier == level.LevelData?.Biome?.Identifier) &&
|
||||
!level.LevelData.NonRepeatableEvents.Contains(e);
|
||||
bool isPrefabSuitable(EventPrefab e) =>
|
||||
(e.BiomeIdentifier.IsEmpty || e.BiomeIdentifier == level.LevelData?.Biome?.Identifier) &&
|
||||
!level.LevelData.NonRepeatableEvents.Contains(e.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)
|
||||
{
|
||||
@@ -447,9 +493,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++)
|
||||
{
|
||||
@@ -462,14 +508,14 @@ namespace Barotrauma
|
||||
for (int j = 0; j < eventCount; j++)
|
||||
{
|
||||
if (unusedEvents.All(e => e.EventPrefabs.All(p => CalculateCommonness(p, e.Commonness) <= 0.0f))) { break; }
|
||||
EventSet.SubEventPrefab subEventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, e => e.EventPrefabs.Max(p => CalculateCommonness(p, e.Commonness)), rand);
|
||||
EventSet.SubEventPrefab subEventPrefab = ToolBox.SelectWeightedRandom(unusedEvents, e => e.EventPrefabs.Max(p => CalculateCommonness(p, e.Commonness)), random);
|
||||
(IEnumerable<EventPrefab> eventPrefabs, float commonness, float probability) = subEventPrefab;
|
||||
if (eventPrefabs != null && rand.NextDouble() <= probability)
|
||||
if (eventPrefabs != null && random.NextDouble() <= probability)
|
||||
{
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(isPrefabSuitable), e => e.Commonness, rand);
|
||||
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(isPrefabSuitable), e => e.Commonness, random);
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.RandomSeed = randomSeed;
|
||||
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
|
||||
DebugConsole.NewMessage($"Initialized event {newEvent}", debugOnly: true);
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
@@ -483,7 +529,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (eventSet.ChildSets.Any())
|
||||
{
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets, random: rand);
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets, random: random);
|
||||
if (newEventSet != null)
|
||||
{
|
||||
CreateEvents(newEventSet);
|
||||
@@ -494,9 +540,9 @@ namespace Barotrauma
|
||||
{
|
||||
foreach ((IEnumerable<EventPrefab> eventPrefabs, float commonness, float probability) in suitablePrefabSubsets)
|
||||
{
|
||||
if (rand.NextDouble() > probability) { continue; }
|
||||
if (random.NextDouble() > probability) { continue; }
|
||||
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(isPrefabSuitable), e => e.Commonness, rand);
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventPrefabs.Where(isPrefabSuitable), e => e.Commonness, random);
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
@@ -601,6 +647,10 @@ namespace Barotrauma
|
||||
private bool IsValidForLocation(EventSet eventSet, Location location)
|
||||
{
|
||||
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; }
|
||||
@@ -728,53 +778,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)
|
||||
{
|
||||
@@ -782,11 +829,11 @@ namespace Barotrauma
|
||||
{
|
||||
ev.Update(deltaTime);
|
||||
}
|
||||
else if (!finishedEvents.Contains(ev))
|
||||
else if (ev.Prefab != null && !finishedEvents.Any(e => e.Prefab == ev.Prefab))
|
||||
{
|
||||
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);
|
||||
}
|
||||
@@ -832,30 +879,43 @@ namespace Barotrauma
|
||||
monsterStrength = 0;
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsIncapacitated || !character.Enabled || character.IsPet || character.Params.CompareGroup(CharacterPrefab.HumanSpeciesName)) { continue; }
|
||||
if (character.IsIncapacitated || !character.Enabled || character.IsPet) { continue; }
|
||||
|
||||
if (!(character.AIController is EnemyAIController enemyAI)) { continue; }
|
||||
if (character.AIController is EnemyAIController enemyAI)
|
||||
{
|
||||
if (!enemyAI.AIParams.StayInAbyss)
|
||||
{
|
||||
// Ignore abyss monsters because they can stay active for quite great distances. They'll be taken into account when they target the sub.
|
||||
monsterStrength += enemyAI.CombatStrength;
|
||||
}
|
||||
|
||||
if (!enemyAI.AIParams.StayInAbyss)
|
||||
if (character.CurrentHull?.Submarine?.Info != null &&
|
||||
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)) &&
|
||||
character.CurrentHull.Submarine.Info.Type == SubmarineType.Player)
|
||||
{
|
||||
// Enemy onboard -> Crawler inside the sub adds 0.2 to enemy danger, Mudraptor 0.42
|
||||
enemyDanger += enemyAI.CombatStrength / 500.0f;
|
||||
}
|
||||
else if (enemyAI.SelectedAiTarget?.Entity?.Submarine != null)
|
||||
{
|
||||
// Enemy outside targeting the sub or something in it
|
||||
// -> One Crawler adds 0.02, a Mudraptor 0.042, a Hammerhead 0.1, and a Moloch 0.25.
|
||||
enemyDanger += enemyAI.CombatStrength / 5000.0f;
|
||||
}
|
||||
}
|
||||
else if (character.AIController is HumanAIController humanAi && !character.IsOnFriendlyTeam(CharacterTeamType.Team1))
|
||||
{
|
||||
// Ignore abyss monsters because they can stay active for quite great distances. They'll be taken into account when they target the sub.
|
||||
monsterStrength += enemyAI.CombatStrength;
|
||||
}
|
||||
|
||||
if (character.CurrentHull?.Submarine?.Info != null &&
|
||||
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)) &&
|
||||
character.CurrentHull.Submarine.Info.Type == SubmarineType.Player)
|
||||
{
|
||||
// Enemy onboard -> Crawler inside the sub adds 0.2 to enemy danger, Mudraptor 0.42
|
||||
enemyDanger += enemyAI.CombatStrength / 500.0f;
|
||||
}
|
||||
else if (enemyAI.SelectedAiTarget?.Entity?.Submarine != null)
|
||||
{
|
||||
// Enemy outside targeting the sub or something in it
|
||||
// -> One Crawler adds 0.02, a Mudraptor 0.042, a Hammerhead 0.1, and a Moloch 0.25.
|
||||
enemyDanger += enemyAI.CombatStrength / 5000.0f;
|
||||
if (character.Submarine != null &&
|
||||
Vector2.DistanceSquared(character.Submarine.WorldPosition, Submarine.MainSub.WorldPosition) < Sonar.DefaultSonarRange * Sonar.DefaultSonarRange)
|
||||
{
|
||||
//we have no easy way to define the strength of a human enemy (depends more on the sub and it's state than the character),
|
||||
//so let's just go with a fixed value.
|
||||
//5 living enemy characters in an enemy sub in sonar range is enough to bump the intensity to max
|
||||
enemyDanger += 0.2f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a portion of the total strength of active monsters to the enemy danger so that we don't spawn too many monsters around the sub.
|
||||
// On top of the existing value, so if 10 crawlers are targeting the sub simultaneously from outside, the final value would be: 0.02 x 10 + 0.2 = 0.4.
|
||||
// And if they get inside, we add 0.1 per crawler on that.
|
||||
@@ -1108,5 +1168,20 @@ namespace Barotrauma
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Load(XElement element)
|
||||
{
|
||||
foreach (var id in element.GetAttributeIdentifierArray(nameof(QueuedEventsForNextRound), Array.Empty<Identifier>()))
|
||||
{
|
||||
QueuedEventsForNextRound.Enqueue(id);
|
||||
}
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
return new XElement("eventmanager",
|
||||
new XAttribute(nameof(QueuedEventsForNextRound),
|
||||
string.Join(',', QueuedEventsForNextRound)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using System.Reflection.Emit;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -14,12 +15,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 +41,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 +49,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 +81,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,6 +111,8 @@ namespace Barotrauma
|
||||
|
||||
public readonly bool IgnoreCoolDown;
|
||||
|
||||
public readonly bool IgnoreIntensity;
|
||||
|
||||
public readonly bool PerRuin, PerCave, PerWreck;
|
||||
public readonly bool DisableInHuntingGrounds;
|
||||
|
||||
@@ -143,11 +146,12 @@ namespace Barotrauma
|
||||
|
||||
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;
|
||||
@@ -178,6 +182,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;
|
||||
@@ -260,6 +266,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)
|
||||
{
|
||||
@@ -282,6 +290,7 @@ 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);
|
||||
OncePerLevel = element.GetAttributeBool("onceperlevel", element.GetAttributeBool("onceperoutpost", false));
|
||||
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
|
||||
@@ -332,15 +341,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;
|
||||
}
|
||||
@@ -365,14 +376,36 @@ 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)
|
||||
{
|
||||
if (level?.StartLocation == null || !overrideEventCount.TryGetValue(level.StartLocation.Type.Identifier, out int count)) { return eventCount; }
|
||||
return count;
|
||||
int finishedEventCount = 0;
|
||||
if (level is not null)
|
||||
{
|
||||
level.LevelData.FinishedEvents.TryGetValue(this, out finishedEventCount);
|
||||
}
|
||||
if (level.StartLocation == null || !overrideEventCount.TryGetValue(level.StartLocation.Type.Identifier, out int count))
|
||||
{
|
||||
return eventCount - finishedEventCount;
|
||||
}
|
||||
return count - finishedEventCount;
|
||||
}
|
||||
|
||||
public static List<string> GetDebugStatistics(int simulatedRoundCount = 100, Func<MonsterEvent, bool> filter = null, bool fullLog = false)
|
||||
|
||||
+41
-23
@@ -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);
|
||||
|
||||
@@ -144,10 +147,7 @@ namespace Barotrauma
|
||||
ISpatialEntity spawnPoint = SpawnAction.GetSpawnPos(
|
||||
SpawnAction.SpawnLocationType.Outpost, SpawnType.Human | SpawnType.Enemy,
|
||||
moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false));
|
||||
if (spawnPoint == null)
|
||||
{
|
||||
spawnPoint = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandomUnsynced();
|
||||
}
|
||||
spawnPoint ??= submarine.GetHulls(alsoFromConnectedSubs: false).GetRandomUnsynced();
|
||||
Vector2 spawnPos = spawnPoint.WorldPosition;
|
||||
if (spawnPoint is WayPoint wp && wp.CurrentHull != null && wp.CurrentHull.Rect.Width > 100)
|
||||
{
|
||||
@@ -186,7 +186,12 @@ namespace Barotrauma
|
||||
|
||||
if (element.Attribute("identifier") != null && element.Attribute("from") != null)
|
||||
{
|
||||
HumanPrefab humanPrefab = GetHumanPrefabFromElement(element);
|
||||
HumanPrefab humanPrefab = GetHumanPrefabFromElement(element);
|
||||
if (humanPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't spawn a human character for abandoned outpost mission: human prefab \"{element.GetAttributeString("identifier", string.Empty)}\" not found");
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
LoadHuman(humanPrefab, element, submarine);
|
||||
@@ -198,7 +203,7 @@ namespace Barotrauma
|
||||
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
if (characterPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn a character for abandoned outpost mission: character prefab \"" + speciesName + "\" not found");
|
||||
DebugConsole.ThrowError($"Couldn't spawn a character for abandoned outpost mission: character prefab \"{speciesName}\" not found");
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
@@ -214,19 +219,25 @@ 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));
|
||||
if (spawnPos == null)
|
||||
{
|
||||
spawnPos = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandomUnsynced();
|
||||
}
|
||||
spawnPos ??= submarine.GetHulls(alsoFromConnectedSubs: false).GetRandomUnsynced();
|
||||
|
||||
bool requiresRescue = element.GetAttributeBool("requirerescue", false);
|
||||
|
||||
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, submarine, requiresRescue ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None, spawnPos);
|
||||
var teamId = element.GetAttributeEnum("teamid", requiresRescue ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None);
|
||||
Character spawnedCharacter = CreateHuman(humanPrefab, characters, characterItems, submarine, teamId, spawnPos);
|
||||
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)
|
||||
{
|
||||
@@ -237,9 +248,19 @@ namespace Barotrauma
|
||||
{
|
||||
requireRescue.Add(spawnedCharacter);
|
||||
#if CLIENT
|
||||
GameMain.GameSession.CrewManager.AddCharacterToCrewList(spawnedCharacter);
|
||||
if (allowOrderingRescuees)
|
||||
{
|
||||
GameMain.GameSession.CrewManager.AddCharacterToCrewList(spawnedCharacter);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (TimesAttempted > 0 && spawnedCharacter.AIController is HumanAIController humanAi)
|
||||
{
|
||||
var order = OrderPrefab.Prefabs["fightintruders"]
|
||||
.CreateInstance(OrderPrefab.OrderTargetType.Entity, orderGiver: spawnedCharacter)
|
||||
.WithManualPriority(CharacterInfo.HighestManualOrderPriority);
|
||||
spawnedCharacter.SetOrder(order, isNewOrder: true, speak: false);
|
||||
}
|
||||
|
||||
if (element.GetAttributeBool("requirekill", false))
|
||||
{
|
||||
@@ -252,10 +273,7 @@ namespace Barotrauma
|
||||
Identifier[] moduleFlags = element.GetAttributeIdentifierArray("moduleflags", null);
|
||||
Identifier[] spawnPointTags = element.GetAttributeIdentifierArray("spawnpointtags", null);
|
||||
ISpatialEntity spawnPos = SpawnAction.GetSpawnPos(SpawnAction.SpawnLocationType.Outpost, SpawnType.Enemy, moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false));
|
||||
if (spawnPos == null)
|
||||
{
|
||||
spawnPos = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandomUnsynced();
|
||||
}
|
||||
spawnPos ??= submarine.GetHulls(alsoFromConnectedSubs: false).GetRandomUnsynced();
|
||||
Character spawnedCharacter = Character.Create(monsterPrefab.Identifier, spawnPos.WorldPosition, ToolBox.RandomSeed(8), createNetworkEvent: false);
|
||||
characters.Add(spawnedCharacter);
|
||||
if (element.GetAttributeBool("requirekill", false))
|
||||
|
||||
@@ -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,295 @@
|
||||
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 readonly string endCinematicSound;
|
||||
|
||||
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);
|
||||
endCinematicSound = prefab.ConfigElement.GetAttributeString(nameof(endCinematicSound), string.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)
|
||||
{
|
||||
float dist = Vector2.Distance(Submarine.MainSub.WorldPosition, boss.WorldPosition);
|
||||
float distanceFactor = Math.Min(dist / 10000.0f, 1.0f);
|
||||
int projectileAmount = Rand.Range(3, 6);
|
||||
//more concentrated shots the further the sub is
|
||||
float spread = MathHelper.ToRadians(Rand.Range(20.0f, 180.0f)) * Math.Max(1.0f - distanceFactor, 0.2f);
|
||||
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();
|
||||
//faster launch velocity the further the sub is
|
||||
projectile.Use(launchImpulseModifier: MathHelper.Lerp(0, 5, distanceFactor));
|
||||
});
|
||||
}
|
||||
|
||||
//the closer the sub is, more likely it is to shoot frequently
|
||||
float shortIntervalProbability = MathHelper.Lerp(0.9f, 0.05f, distanceFactor);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,20 +11,7 @@ namespace Barotrauma
|
||||
{
|
||||
partial class MineralMission : Mission
|
||||
{
|
||||
private struct ResourceCluster
|
||||
{
|
||||
public int Amount;
|
||||
public float Rotation;
|
||||
|
||||
public ResourceCluster(int amount, float rotation)
|
||||
{
|
||||
Amount = amount;
|
||||
Rotation = rotation;
|
||||
}
|
||||
|
||||
public static implicit operator ResourceCluster((int amount, float rotation) tuple) => new ResourceCluster(tuple.amount, tuple.rotation);
|
||||
}
|
||||
private readonly Dictionary<Identifier, ResourceCluster> resourceClusters = new Dictionary<Identifier, ResourceCluster>();
|
||||
private readonly Dictionary<Identifier, int> resourceAmounts = new Dictionary<Identifier, int>();
|
||||
private readonly Dictionary<Identifier, List<Item>> spawnedResources = new Dictionary<Identifier, List<Item>>();
|
||||
private readonly Dictionary<Identifier, Item[]> relevantLevelResources = new Dictionary<Identifier, Item[]>();
|
||||
private readonly List<(Identifier Identifier, Vector2 Position)> missionClusterPositions = new List<(Identifier Identifier, Vector2 Position)>();
|
||||
@@ -50,13 +37,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 +51,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)
|
||||
{
|
||||
@@ -82,13 +68,13 @@ namespace Barotrauma
|
||||
{
|
||||
var identifier = c.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
if (identifier.IsEmpty) { continue; }
|
||||
if (resourceClusters.ContainsKey(identifier))
|
||||
if (resourceAmounts.ContainsKey(identifier))
|
||||
{
|
||||
resourceClusters[identifier] = (resourceClusters[identifier].Amount + 1, resourceClusters[identifier].Rotation);
|
||||
resourceAmounts[identifier]++;
|
||||
}
|
||||
else
|
||||
{
|
||||
resourceClusters.Add(identifier, (1, 0.0f));
|
||||
resourceAmounts.Add(identifier, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,7 +115,7 @@ namespace Barotrauma
|
||||
|
||||
if (IsClient) { return; }
|
||||
|
||||
foreach ((Identifier identifier, ResourceCluster cluster) in resourceClusters)
|
||||
foreach ((Identifier identifier, int amount) in resourceAmounts)
|
||||
{
|
||||
if (MapEntityPrefab.FindByIdentifier(identifier) is not ItemPrefab prefab)
|
||||
{
|
||||
@@ -137,10 +123,10 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
|
||||
var spawnedResources = level.GenerateMissionResources(prefab, cluster.Amount, positionType, out float rotation, caves);
|
||||
if (spawnedResources.Count < cluster.Amount)
|
||||
var spawnedResources = level.GenerateMissionResources(prefab, amount, positionType, caves);
|
||||
if (spawnedResources.Count < amount)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{cluster.Amount} of {prefab.Name}");
|
||||
DebugConsole.ThrowError($"Error in MineralMission: spawned only {spawnedResources.Count}/{amount} of {prefab.Name}");
|
||||
}
|
||||
|
||||
if (spawnedResources.None()) { continue; }
|
||||
@@ -175,7 +161,7 @@ namespace Barotrauma
|
||||
State = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) { return; }
|
||||
if (!Submarine.MainSub.AtEitherExit) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
}
|
||||
@@ -195,7 +181,7 @@ namespace Barotrauma
|
||||
{
|
||||
// When mission is completed successfully, half of the resources will be removed from the player (i.e. given to the outpost as a part of the mission)
|
||||
var handoverResources = new List<Item>();
|
||||
foreach (Identifier identifier in resourceClusters.Keys)
|
||||
foreach (Identifier identifier in resourceAmounts.Keys)
|
||||
{
|
||||
if (relevantLevelResources.TryGetValue(identifier, out var availableResources))
|
||||
{
|
||||
@@ -232,11 +218,11 @@ namespace Barotrauma
|
||||
private void FindRelevantLevelResources()
|
||||
{
|
||||
relevantLevelResources.Clear();
|
||||
foreach (var identifier in resourceClusters.Keys)
|
||||
foreach (var identifier in resourceAmounts.Keys)
|
||||
{
|
||||
var items = Item.ItemList.Where(i => i.Prefab.Identifier == identifier &&
|
||||
i.Submarine == null && i.ParentInventory == null &&
|
||||
(!(i.GetComponent<Holdable>() is Holdable h) || (h.Attachable && h.Attached)))
|
||||
(i.GetComponent<Holdable>() is not Holdable h || (h.Attachable && h.Attached)))
|
||||
.ToArray();
|
||||
relevantLevelResources.Add(identifier, items);
|
||||
}
|
||||
@@ -244,12 +230,12 @@ namespace Barotrauma
|
||||
|
||||
private bool EnoughHaveBeenCollected()
|
||||
{
|
||||
foreach (var kvp in resourceClusters)
|
||||
foreach (var kvp in resourceAmounts)
|
||||
{
|
||||
if (relevantLevelResources.TryGetValue(kvp.Key, out var availableResources))
|
||||
{
|
||||
var collected = availableResources.Count(HasBeenCollected);
|
||||
var needed = kvp.Value.Amount;
|
||||
var needed = kvp.Value;
|
||||
if (collected < needed) { return false; }
|
||||
}
|
||||
else
|
||||
@@ -300,10 +286,10 @@ namespace Barotrauma
|
||||
protected override LocalizedString ModifyMessage(LocalizedString message, bool color = true)
|
||||
{
|
||||
int i = 1;
|
||||
foreach ((Identifier identifier, ResourceCluster cluster) in resourceClusters)
|
||||
foreach ((Identifier identifier, int amount) in resourceAmounts)
|
||||
{
|
||||
Replace($"[resourcename{i}]", ItemPrefab.FindByIdentifier(identifier)?.Name.Value ?? "");
|
||||
Replace($"[resourcequantity{i}]", cluster.Amount.ToString());
|
||||
Replace($"[resourcequantity{i}]", amount.ToString());
|
||||
i++;
|
||||
}
|
||||
Replace("[handoverpercentage]", ToolBox.GetFormattedPercentage(resourceHandoverAmount));
|
||||
|
||||
@@ -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);
|
||||
@@ -37,6 +42,8 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public int TimesAttempted { get; set; }
|
||||
|
||||
protected static bool IsClient => GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
|
||||
private readonly CheckDataAction completeCheckDataAction;
|
||||
@@ -44,6 +51,12 @@ namespace Barotrauma
|
||||
public readonly ImmutableArray<LocalizedString> Headers;
|
||||
public readonly ImmutableArray<LocalizedString> Messages;
|
||||
|
||||
/// <summary>
|
||||
/// The reward that was actually given from completing the mission, taking any talent bonuses into account
|
||||
/// (some of which may not be possible to determine in advance)
|
||||
/// </summary>
|
||||
private int? finalReward;
|
||||
|
||||
public virtual LocalizedString Name => Prefab.Name;
|
||||
|
||||
private readonly LocalizedString successMessage;
|
||||
@@ -113,15 +126,19 @@ 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;
|
||||
|
||||
/// <summary>
|
||||
/// Where was this mission received from? Affects which faction we give reputation for if the mission is configured to give reputation for the faction that gave the mission.
|
||||
/// Defaults to Locations[0]
|
||||
/// </summary>
|
||||
public Location OriginLocation;
|
||||
|
||||
public readonly Location[] Locations;
|
||||
|
||||
public int? Difficulty
|
||||
@@ -141,7 +158,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private List<DelayedTriggerEvent> delayedTriggerEvents = new List<DelayedTriggerEvent>();
|
||||
private readonly List<DelayedTriggerEvent> delayedTriggerEvents = new List<DelayedTriggerEvent>();
|
||||
|
||||
public Action<Mission> OnMissionStateChanged;
|
||||
|
||||
@@ -157,12 +174,13 @@ namespace Barotrauma
|
||||
Headers = prefab.Headers;
|
||||
var messages = prefab.Messages.ToArray();
|
||||
|
||||
OriginLocation = locations[0];
|
||||
Locations = locations;
|
||||
|
||||
var endConditionElement = prefab.ConfigElement.GetChildElement(nameof(completeCheckDataAction));
|
||||
if (endConditionElement != null)
|
||||
{
|
||||
completeCheckDataAction = new CheckDataAction(endConditionElement, $"Mission ({prefab.Identifier.ToString()})");
|
||||
completeCheckDataAction = new CheckDataAction(endConditionElement, $"Mission ({prefab.Identifier})");
|
||||
}
|
||||
|
||||
for (int n = 0; n < 2; n++)
|
||||
@@ -307,7 +325,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))
|
||||
{
|
||||
@@ -357,6 +375,8 @@ namespace Barotrauma
|
||||
GiveReward();
|
||||
}
|
||||
|
||||
TimesAttempted++;
|
||||
|
||||
EndMissionSpecific(completed);
|
||||
}
|
||||
|
||||
@@ -364,6 +384,27 @@ namespace Barotrauma
|
||||
|
||||
protected virtual void EndMissionSpecific(bool completed) { }
|
||||
|
||||
/// <summary>
|
||||
/// Get the final reward, taking talent bonuses into account if the mission has concluded and the talents modified the reward accordingly.
|
||||
/// </summary>
|
||||
public int GetFinalReward(Submarine sub)
|
||||
{
|
||||
return finalReward ?? GetReward(sub);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the final reward after talent bonuses have been applied. Note that this triggers talent effects of the type OnGainMissionMoney,
|
||||
/// and should only be called once when the mission is completed!
|
||||
/// </summary>
|
||||
private void CalculateFinalReward(Submarine sub)
|
||||
{
|
||||
int reward = GetReward(sub);
|
||||
IEnumerable<Character> crewCharacters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
var missionMoneyGainMultiplier = new AbilityMissionMoneyGainMultiplier(this, 1f);
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, missionMoneyGainMultiplier));
|
||||
crewCharacters.ForEach(c => missionMoneyGainMultiplier.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier));
|
||||
finalReward = (int)(reward * missionMoneyGainMultiplier.Value);
|
||||
}
|
||||
|
||||
private void GiveReward()
|
||||
{
|
||||
@@ -407,39 +448,35 @@ namespace Barotrauma
|
||||
info?.GiveExperience((int)((experienceGain * experienceGainMultiplier.Value) * experienceGainMultiplierIndividual.Value));
|
||||
}
|
||||
|
||||
// apply money gains afterwards to prevent them from affecting XP gains
|
||||
var missionMoneyGainMultiplier = new AbilityMissionMoneyGainMultiplier(this, 1f);
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, missionMoneyGainMultiplier));
|
||||
crewCharacters.ForEach(c => missionMoneyGainMultiplier.Value += c.GetStatValue(StatTypes.MissionMoneyGainMultiplier));
|
||||
|
||||
int totalReward = (int)(reward * missionMoneyGainMultiplier.Value);
|
||||
GameAnalyticsManager.AddMoneyGainedEvent(totalReward, GameAnalyticsManager.MoneySource.MissionReward, Prefab.Identifier.Value);
|
||||
|
||||
CalculateFinalReward(Submarine.MainSub);
|
||||
#if SERVER
|
||||
totalReward = DistributeRewardsToCrew(GameSession.GetSessionCrewCharacters(CharacterType.Player), totalReward);
|
||||
finalReward = DistributeRewardsToCrew(GameSession.GetSessionCrewCharacters(CharacterType.Player), finalReward.Value);
|
||||
#endif
|
||||
bool isSingleplayerOrServer = GameMain.IsSingleplayer || GameMain.NetworkMember is { IsServer: true };
|
||||
if (isSingleplayerOrServer && totalReward > 0)
|
||||
if (isSingleplayerOrServer)
|
||||
{
|
||||
campaign.Bank.Give(totalReward);
|
||||
}
|
||||
|
||||
foreach (Character character in crewCharacters)
|
||||
{
|
||||
character.Info.MissionsCompletedSinceDeath++;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<Identifier, float> reputationReward in ReputationRewards)
|
||||
{
|
||||
if (reputationReward.Key == "location")
|
||||
if (finalReward > 0)
|
||||
{
|
||||
Locations[0].Reputation.AddReputation(reputationReward.Value);
|
||||
Locations[1].Reputation.AddReputation(reputationReward.Value);
|
||||
campaign.Bank.Give(finalReward.Value);
|
||||
}
|
||||
else
|
||||
|
||||
foreach (Character character in crewCharacters)
|
||||
{
|
||||
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier == reputationReward.Key);
|
||||
if (faction != null) { faction.Reputation.AddReputation(reputationReward.Value); }
|
||||
character.Info.MissionsCompletedSinceDeath++;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<Identifier, float> reputationReward in ReputationRewards)
|
||||
{
|
||||
if (reputationReward.Key == "location")
|
||||
{
|
||||
OriginLocation.Reputation?.AddReputation(reputationReward.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Faction faction = campaign.Factions.Find(faction1 => faction1.Prefab.Identifier == reputationReward.Key);
|
||||
float prevValue = faction.Reputation.Value;
|
||||
faction?.Reputation.AddReputation(reputationReward.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,18 +521,15 @@ namespace Barotrauma
|
||||
float rewardWeight = sum > 100 ? rewardDistribution / sum : rewardDistribution / 100f;
|
||||
int rewardPercentage = (int)(rewardWeight * 100);
|
||||
|
||||
return reward switch
|
||||
{
|
||||
Some<int> { Value: var amount } => ((int)(amount * rewardWeight), rewardPercentage, sum),
|
||||
None<int> _ => (0, rewardPercentage, sum),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
int amount = reward.TryUnwrap(out var a) ? a : 0;
|
||||
|
||||
return ((int)(amount * rewardWeight), rewardPercentage, sum);
|
||||
}
|
||||
|
||||
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++)
|
||||
@@ -509,13 +543,15 @@ namespace Barotrauma
|
||||
if (srcIndex == -1) { return; }
|
||||
var location = Locations[srcIndex];
|
||||
|
||||
if (location.LocationTypeChangesBlocked) { return; }
|
||||
|
||||
if (change.RequiredDurationRange.X > 0)
|
||||
{
|
||||
location.PendingLocationTypeChange = (change, Rand.Range(change.RequiredDurationRange.X, change.RequiredDurationRange.Y), Prefab);
|
||||
}
|
||||
else
|
||||
{
|
||||
location.ChangeType(LocationType.Prefabs[change.ChangeToType]);
|
||||
location.ChangeType(campaign, LocationType.Prefabs[change.ChangeToType]);
|
||||
location.LocationTypeChangeCooldown = change.CooldownAfterChange;
|
||||
}
|
||||
}
|
||||
@@ -529,7 +565,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;
|
||||
}
|
||||
|
||||
@@ -538,7 +573,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;
|
||||
}
|
||||
|
||||
@@ -557,8 +592,7 @@ namespace Barotrauma
|
||||
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
|
||||
spawnedCharacter.HumanPrefab = humanPrefab;
|
||||
humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn);
|
||||
humanPrefab.GiveItems(spawnedCharacter, submarine, Rand.RandSync.ServerAndClient, createNetworkEvents: false);
|
||||
|
||||
humanPrefab.GiveItems(spawnedCharacter, submarine, positionToStayIn as WayPoint, Rand.RandSync.ServerAndClient, createNetworkEvents: false);
|
||||
characters.Add(spawnedCharacter);
|
||||
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
|
||||
|
||||
|
||||
@@ -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,24 @@ namespace Barotrauma
|
||||
|
||||
public readonly bool AllowRetry;
|
||||
|
||||
public readonly bool ShowInMenus, ShowStartMessage;
|
||||
|
||||
public readonly bool IsSideObjective;
|
||||
|
||||
public readonly bool AllowOtherMissionsInLevel;
|
||||
|
||||
public readonly bool RequireWreck, RequireRuin;
|
||||
|
||||
/// <summary>
|
||||
/// If enabled, locations this mission takes place in cannot change their type
|
||||
/// </summary>
|
||||
public readonly bool BlockLocationTypeChanges;
|
||||
|
||||
public readonly bool ShowProgressBar;
|
||||
public readonly bool ShowProgressInNumbers;
|
||||
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 +156,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 +179,26 @@ 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);
|
||||
BlockLocationTypeChanges = element.GetAttributeBool(nameof(BlockLocationTypeChanges), 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);
|
||||
ShowProgressInNumbers = element.GetAttributeBool(nameof(ShowProgressInNumbers), 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 +372,7 @@ namespace Barotrauma
|
||||
{
|
||||
return
|
||||
AllowedLocationTypes.Any(lt => lt == "any") ||
|
||||
AllowedLocationTypes.Any(lt => lt == "anyoutpost" && from.HasOutpost()) ||
|
||||
AllowedLocationTypes.Any(lt => lt == from.Type.Identifier);
|
||||
}
|
||||
|
||||
@@ -357,11 +380,11 @@ namespace Barotrauma
|
||||
{
|
||||
if (fromType == "any" ||
|
||||
fromType == from.Type.Identifier ||
|
||||
(fromType == "anyoutpost" && from.HasOutpost()))
|
||||
(fromType == "anyoutpost" && from.HasOutpost() && from.Type.Identifier != "abandoned"))
|
||||
{
|
||||
if (toType == "any" ||
|
||||
toType == to.Type.Identifier ||
|
||||
(toType == "anyoutpost" && to.HasOutpost()))
|
||||
(toType == "anyoutpost" && to.HasOutpost() && to.Type.Identifier != "abandoned"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,9 +260,25 @@ namespace Barotrauma
|
||||
int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
Character.Create(monster.Item1.Identifier, nestPosition + Rand.Vector(100.0f), ToolBox.RandomSeed(8), createNetworkEvent: true);
|
||||
Vector2 offsetPosition;
|
||||
int tries = 0;
|
||||
do
|
||||
{
|
||||
offsetPosition = nestPosition + Rand.Vector(100.0f);
|
||||
tries++;
|
||||
if (tries > 10)
|
||||
{
|
||||
offsetPosition = nestPosition;
|
||||
break;
|
||||
}
|
||||
} while (Level.Loaded.IsPositionInsideWall(offsetPosition));
|
||||
Character.Create(monster.Item1.Identifier, offsetPosition, ToolBox.RandomSeed(8), createNetworkEvent: true);
|
||||
}
|
||||
}
|
||||
if (Level.Loaded.IsPositionInsideWall(nestPosition))
|
||||
{
|
||||
DebugConsole.AddWarning($"Error in nest mission \"{Prefab.Identifier}\": nest position was inside a wall ({nestPosition}).");
|
||||
}
|
||||
monsterPrefabs.Clear();
|
||||
break;
|
||||
}
|
||||
@@ -274,7 +290,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,182 @@ 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;
|
||||
|
||||
/// <summary>
|
||||
/// Note that the integer values matter here: the state of the target can't go back to a smaller value,
|
||||
/// and a larger or equal value than the <see href="RequiredRetrievalState">RequiredRetrievalState</see> means the item counts as retrieved
|
||||
/// (if the item needs to be picked up to be considered retrieved, it's also considered retrieved if it's in the sub)
|
||||
/// </summary>
|
||||
public enum RetrievalState
|
||||
{
|
||||
None = 0,
|
||||
Interact = 1,
|
||||
PickedUp = 2,
|
||||
RetrievedToSub = 3
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
if (item == null)
|
||||
get
|
||||
{
|
||||
Enumerable.Empty<Vector2>();
|
||||
return RequiredRetrievalState switch
|
||||
{
|
||||
RetrievalState.None => true,
|
||||
RetrievalState.Interact or RetrievalState.PickedUp => State >= RequiredRetrievalState,
|
||||
RetrievalState.RetrievedToSub => State == RetrievalState.RetrievedToSub,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private RetrievalState state;
|
||||
public RetrievalState State
|
||||
{
|
||||
get { return state; }
|
||||
set
|
||||
{
|
||||
if (value == state) { return; }
|
||||
state = value;
|
||||
#if SERVER
|
||||
GameMain.Server?.UpdateMissionState(mission);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public bool Interacted;
|
||||
|
||||
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 +188,254 @@ 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));
|
||||
if (GameMain.GameSession?.Missions != null)
|
||||
{
|
||||
//don't choose an item that was already chosen as the target for another salvage mission
|
||||
suitableItems = suitableItems.Where(it =>
|
||||
GameMain.GameSession.Missions.None(m => m != this && m is SalvageMission salvageMission && salvageMission.targets.Any(t => t.Item == it)));
|
||||
}
|
||||
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;
|
||||
}
|
||||
else if (target.RequiredRetrievalState == Target.RetrievalState.Interact)
|
||||
{
|
||||
target.Item.OnInteract += () =>
|
||||
{
|
||||
target.Interacted = true;
|
||||
};
|
||||
}
|
||||
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:
|
||||
if (target.Interacted)
|
||||
{
|
||||
return;
|
||||
TrySetRetrievalState(Target.RetrievalState.Interact);
|
||||
}
|
||||
}
|
||||
State = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndExit && !Submarine.MainSub.AtStartExit) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
var root = target.Item?.GetRootContainer() ?? target.Item;
|
||||
if (root.ParentInventory?.Owner is Character character && character.TeamID == CharacterTeamType.Team1)
|
||||
{
|
||||
TrySetRetrievalState(Target.RetrievalState.PickedUp);
|
||||
}
|
||||
break;
|
||||
case Target.RetrievalState.PickedUp:
|
||||
Submarine parentSub = target.Item.CurrentHull?.Submarine ?? target.Item.GetRootInventoryOwner()?.Submarine;
|
||||
if (parentSub != null)
|
||||
{
|
||||
if (parentSub.Info.Type == SubmarineType.Player || Level.IsLoadedFriendlyOutpost)
|
||||
{
|
||||
TrySetRetrievalState(Target.RetrievalState.RetrievedToSub);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
void TrySetRetrievalState(Target.RetrievalState retrievalState)
|
||||
{
|
||||
if (retrievalState < target.State) { return; }
|
||||
bool wasRetrieved = false;
|
||||
target.State = retrievalState;
|
||||
//increment the mission state if the target became retrieved
|
||||
if (!wasRetrieved && target.Retrieved) { State = i + 1; }
|
||||
}
|
||||
}
|
||||
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.PickedUp);
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -244,7 +244,12 @@ namespace Barotrauma
|
||||
float dist = Vector2.DistanceSquared(pos, refSub.WorldPosition);
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.Info.Type != SubmarineType.Player && sub != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle) { continue; }
|
||||
if (sub.Info.Type != SubmarineType.Player &&
|
||||
sub.Info.Type != SubmarineType.EnemySubmarine &&
|
||||
sub != GameMain.NetworkMember?.RespawnManager?.RespawnShuttle)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float minDistToSub = GetMinDistanceToSub(sub);
|
||||
if (dist < minDistToSub * minDistToSub) { continue; }
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Entity entity in Entity.GetEntities())
|
||||
{
|
||||
if (targetPredicates[tag].Any(p => p(entity)))
|
||||
if (targetPredicates[tag].Any(p => p(entity)) && !targetsToReturn.Contains(entity))
|
||||
{
|
||||
targetsToReturn.Add(entity);
|
||||
}
|
||||
@@ -131,7 +131,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Character npc in outpostNPCs)
|
||||
{
|
||||
if (npc.Removed) { continue; }
|
||||
if (npc.Removed || targetsToReturn.Contains(npc)) { continue; }
|
||||
targetsToReturn.Add(npc);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user