v0.11.0.9
This commit is contained in:
@@ -56,7 +56,9 @@ namespace Barotrauma
|
||||
public override void Init(bool affectSubImmediately)
|
||||
{
|
||||
spawnPos = Level.Loaded.GetRandomItemPos(
|
||||
(Rand.Value(Rand.RandSync.Server) < 0.5f) ? Level.PositionType.MainPath : Level.PositionType.Cave | Level.PositionType.Ruin,
|
||||
(Rand.Value(Rand.RandSync.Server) < 0.5f) ?
|
||||
Level.PositionType.MainPath | Level.PositionType.SidePath :
|
||||
Level.PositionType.Cave | Level.PositionType.Ruin,
|
||||
500.0f, 10000.0f, 30.0f);
|
||||
|
||||
spawnPending = true;
|
||||
|
||||
@@ -56,5 +56,10 @@ namespace Barotrauma
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual bool LevelMeetsRequirements()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace Barotrauma
|
||||
[Serialize(0.0f, true)]
|
||||
public float RequiredLevel { get; set; }
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool ProbabilityBased { get; set; }
|
||||
|
||||
[Serialize("", true)]
|
||||
public string TargetTag { get; set; }
|
||||
|
||||
@@ -27,7 +30,15 @@ namespace Barotrauma
|
||||
protected override bool? DetermineSuccess()
|
||||
{
|
||||
var potentialTargets = ParentEvent.GetTargets(TargetTag).Where(e => e is Character).Select(e => e as Character);
|
||||
return potentialTargets.Any(chr => chr.GetSkillLevel(RequiredSkill?.ToLowerInvariant()) >= RequiredLevel);
|
||||
|
||||
if (ProbabilityBased)
|
||||
{
|
||||
return potentialTargets.Any(chr => chr.GetSkillLevel(RequiredSkill?.ToLowerInvariant()) / RequiredLevel > Rand.Range(0.0f, 1.0f, Rand.RandSync.Unsynced));
|
||||
}
|
||||
else
|
||||
{
|
||||
return potentialTargets.Any(chr => chr.GetSkillLevel(RequiredSkill?.ToLowerInvariant()) >= RequiredLevel);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToDebugString()
|
||||
|
||||
@@ -68,6 +68,9 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
[Serialize(false, true, description: "Should the AI ignore this item. This will prevent outpost NPCs cleaning up or otherwise using important items intended to be left for the players.")]
|
||||
public bool IgnoreByAI { get; set; }
|
||||
|
||||
private bool spawned;
|
||||
private Entity spawnedEntity;
|
||||
|
||||
@@ -123,7 +126,7 @@ namespace Barotrauma
|
||||
var idleObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveIdle>();
|
||||
if (idleObjective != null)
|
||||
{
|
||||
idleObjective.Behavior = humanPrefab.BehaviorType;
|
||||
idleObjective.Behavior = humanPrefab.Behavior;
|
||||
foreach (string moduleType in humanPrefab.PreferredOutpostModuleTypes)
|
||||
{
|
||||
idleObjective.PreferredOutpostModuleTypes.Add(moduleType);
|
||||
@@ -202,6 +205,7 @@ namespace Barotrauma
|
||||
ParentEvent.AddTarget(TargetTag, newItem);
|
||||
}
|
||||
spawnedEntity = newItem;
|
||||
newItem?.SetIgnoreByAI(IgnoreByAI);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,9 @@ namespace Barotrauma
|
||||
[Serialize("", true)]
|
||||
public string Tag { get; set; }
|
||||
|
||||
[Serialize(true, true)]
|
||||
public bool IgnoreIncapacitatedCharacters { get; set; }
|
||||
|
||||
private bool isFinished = false;
|
||||
|
||||
public TagAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
|
||||
@@ -27,12 +30,26 @@ namespace Barotrauma
|
||||
|
||||
private void TagPlayers()
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsPlayer);
|
||||
if (IgnoreIncapacitatedCharacters)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsPlayer && !c.IsIncapacitated);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
private void TagBots()
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsBot);
|
||||
if (IgnoreIncapacitatedCharacters)
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsBot && !c.IsIncapacitated);
|
||||
}
|
||||
else
|
||||
{
|
||||
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsBot);
|
||||
}
|
||||
}
|
||||
|
||||
private void TagCrew()
|
||||
|
||||
@@ -33,7 +33,11 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(eventPrefab.CreateInstance());
|
||||
var ev = eventPrefab.CreateInstance();
|
||||
if (ev != null)
|
||||
{
|
||||
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,13 @@ namespace Barotrauma
|
||||
if (initialEventSet != null)
|
||||
{
|
||||
pendingEventSets.Add(initialEventSet);
|
||||
CreateEvents(initialEventSet);
|
||||
int seed = ToolBox.StringToInt(level.Seed);
|
||||
foreach (var previousEvent in level.LevelData.EventHistory)
|
||||
{
|
||||
seed ^= ToolBox.StringToInt(previousEvent.Identifier);
|
||||
}
|
||||
MTRandom rand = new MTRandom(seed);
|
||||
CreateEvents(initialEventSet, rand);
|
||||
}
|
||||
|
||||
if (level?.LevelData?.Type == LevelData.LevelType.Outpost)
|
||||
@@ -325,7 +331,7 @@ namespace Barotrauma
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void CreateEvents(EventSet eventSet)
|
||||
private void CreateEvents(EventSet eventSet, Random rand)
|
||||
{
|
||||
if (level == null) { return; }
|
||||
int applyCount = 1;
|
||||
@@ -343,13 +349,6 @@ namespace Barotrauma
|
||||
{
|
||||
if (eventSet.EventPrefabs.Count > 0)
|
||||
{
|
||||
int seed = ToolBox.StringToInt(level.Seed);
|
||||
foreach (var previousEvent in level.LevelData.EventHistory)
|
||||
{
|
||||
seed |= ToolBox.StringToInt(previousEvent.Identifier);
|
||||
}
|
||||
|
||||
MTRandom rand = new MTRandom(seed);
|
||||
List<Pair<EventPrefab, float>> unusedEvents = new List<Pair<EventPrefab, float>>(eventSet.EventPrefabs);
|
||||
for (int j = 0; j < eventSet.EventCount; j++)
|
||||
{
|
||||
@@ -357,6 +356,7 @@ namespace Barotrauma
|
||||
if (eventPrefab != null)
|
||||
{
|
||||
var newEvent = eventPrefab.First.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.Init(true);
|
||||
DebugConsole.Log("Initialized event " + newEvent.ToString());
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
@@ -371,7 +371,7 @@ namespace Barotrauma
|
||||
if (eventSet.ChildSets.Count > 0)
|
||||
{
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets);
|
||||
if (newEventSet != null) { CreateEvents(newEventSet); }
|
||||
if (newEventSet != null) { CreateEvents(newEventSet, rand); }
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -379,6 +379,7 @@ namespace Barotrauma
|
||||
foreach (Pair<EventPrefab, float> eventPrefab in eventSet.EventPrefabs)
|
||||
{
|
||||
var newEvent = eventPrefab.First.CreateInstance();
|
||||
if (newEvent == null) { continue; }
|
||||
newEvent.Init(true);
|
||||
DebugConsole.Log("Initialized event " + newEvent.ToString());
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
@@ -390,7 +391,7 @@ namespace Barotrauma
|
||||
|
||||
foreach (EventSet childEventSet in eventSet.ChildSets)
|
||||
{
|
||||
CreateEvents(childEventSet);
|
||||
CreateEvents(childEventSet, rand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString());
|
||||
}
|
||||
|
||||
Event ev = (Event)instance;
|
||||
if (!ev.LevelMeetsRequirements()) { return null; }
|
||||
|
||||
return (Event)instance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class BeaconMission : Mission
|
||||
{
|
||||
private bool swarmSpawned;
|
||||
private readonly string monsterSpeciesName;
|
||||
private Point monsterCountRange;
|
||||
private Level level;
|
||||
private readonly string sonarLabel;
|
||||
|
||||
public BeaconMission(MissionPrefab prefab, Location[] locations) : base(prefab, locations)
|
||||
{
|
||||
swarmSpawned = false;
|
||||
|
||||
XElement monsterElement = prefab.ConfigElement.Element("monster");
|
||||
|
||||
monsterSpeciesName = monsterElement.GetAttributeString("character", string.Empty);
|
||||
int defaultCount = monsterElement.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
{
|
||||
defaultCount = monsterElement.GetAttributeInt("amount", 1);
|
||||
}
|
||||
int min = Math.Min(monsterElement.GetAttributeInt("min", defaultCount), 255);
|
||||
int max = Math.Min(Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)), 255);
|
||||
|
||||
monsterCountRange = new Point(min, max);
|
||||
|
||||
sonarLabel = TextManager.Get("beaconstationsonarlabel");
|
||||
}
|
||||
|
||||
public override string SonarLabel
|
||||
{
|
||||
get
|
||||
{
|
||||
return string.IsNullOrEmpty(base.SonarLabel) ? sonarLabel : base.SonarLabel;
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return level.BeaconStation.WorldPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (IsClient) { return; }
|
||||
if (!swarmSpawned && level.CheckBeaconActive())
|
||||
{
|
||||
State = 1;
|
||||
|
||||
Vector2 spawnPos = level.BeaconStation.WorldPosition;
|
||||
spawnPos.Y += level.BeaconStation.GetDockedBorders().Height * 1.5f;
|
||||
|
||||
var availablePositions = Level.Loaded.PositionsOfInterest.FindAll(p =>
|
||||
p.PositionType == Level.PositionType.MainPath ||
|
||||
p.PositionType == Level.PositionType.SidePath);
|
||||
availablePositions.RemoveAll(p => Level.Loaded.ExtraWalls.Any(w => w.IsPointInside(p.Position.ToVector2())));
|
||||
availablePositions.RemoveAll(p => Submarine.FindContaining(p.Position.ToVector2()) != null);
|
||||
|
||||
if (availablePositions.Any())
|
||||
{
|
||||
Level.InterestingPosition? closestPos = null;
|
||||
float closestDist = float.PositiveInfinity;
|
||||
foreach (var pos in availablePositions)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(pos.Position.ToVector2(), level.BeaconStation.WorldPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
closestPos = pos;
|
||||
}
|
||||
}
|
||||
if (closestPos.HasValue)
|
||||
{
|
||||
spawnPos = closestPos.Value.Position.ToVector2();
|
||||
}
|
||||
}
|
||||
|
||||
int amount = Rand.Range(monsterCountRange.X, monsterCountRange.Y + 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
CoroutineManager.InvokeAfter(() =>
|
||||
{
|
||||
//round ended before the coroutine finished
|
||||
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
|
||||
Entity.Spawner.AddToSpawnQueue(monsterSpeciesName, spawnPos);
|
||||
}, Rand.Range(0f, amount));
|
||||
}
|
||||
swarmSpawned = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
completed = level.CheckBeaconActive();
|
||||
if (completed)
|
||||
{
|
||||
ChangeLocationType("None", "Explored");
|
||||
GiveReward();
|
||||
}
|
||||
}
|
||||
|
||||
public override void AdjustLevelData(LevelData levelData)
|
||||
{
|
||||
levelData.HasBeaconStation = true;
|
||||
levelData.IsBeaconActive = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MineralMission : Mission
|
||||
{
|
||||
private Dictionary<string, Pair<int, float>> ResourceClusters { get; } = new Dictionary<string, Pair<int, float>>();
|
||||
private Dictionary<string, List<Item>> SpawnedResources { get; } = new Dictionary<string, List<Item>>();
|
||||
private Dictionary<string, Item[]> RelevantLevelResources { get; } = new Dictionary<string, Item[]>();
|
||||
private List<Tuple<string, Vector2>> MissionClusterPositions { get; } = new List<Tuple<string, Vector2>>();
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
return MissionClusterPositions
|
||||
.Where(p => SpawnedResources.ContainsKey(p.Item1) && AnyAreUncollected(SpawnedResources[p.Item1]))
|
||||
.Select(p => p.Item2);
|
||||
}
|
||||
}
|
||||
|
||||
public MineralMission(MissionPrefab prefab, Location[] locations) : base(prefab, locations)
|
||||
{
|
||||
var configElement = prefab.ConfigElement.Element("Items");
|
||||
foreach (var c in configElement.GetChildElements("Item"))
|
||||
{
|
||||
var identifier = c.GetAttributeString("identifier", null);
|
||||
if (string.IsNullOrWhiteSpace(identifier)) { continue; }
|
||||
if (ResourceClusters.ContainsKey(identifier))
|
||||
{
|
||||
ResourceClusters[identifier].First++;
|
||||
}
|
||||
else
|
||||
{
|
||||
ResourceClusters.Add(identifier, new Pair<int, float>(1, 0.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
if (SpawnedResources.Any())
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception($"SpawnedResources.Count > 0 ({SpawnedResources.Count})");
|
||||
#else
|
||||
DebugConsole.AddWarning("Spawned resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
|
||||
SpawnedResources.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (RelevantLevelResources.Any())
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception($"RelevantLevelResources.Count > 0 ({RelevantLevelResources.Count})");
|
||||
#else
|
||||
DebugConsole.AddWarning("Relevant level resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
|
||||
RelevantLevelResources.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (MissionClusterPositions.Any())
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception($"MissionClusterPositions.Count > 0 ({MissionClusterPositions.Count})");
|
||||
#else
|
||||
DebugConsole.AddWarning("Mission cluster positions list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
|
||||
MissionClusterPositions.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (IsClient) { return; }
|
||||
foreach (var kvp in ResourceClusters)
|
||||
{
|
||||
var prefab = ItemPrefab.Find(null, kvp.Key);
|
||||
if (prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in MineralMission - " +
|
||||
"couldn't find an item prefab with the identifier " + kvp.Key);
|
||||
continue;
|
||||
}
|
||||
var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.First, out float rotation);
|
||||
if (spawnedResources.Count < kvp.Value.First)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in MineralMission - " +
|
||||
"spawned " + spawnedResources.Count + "/" + kvp.Value.First + " of " + prefab.Name);
|
||||
}
|
||||
if (spawnedResources.None()) { continue; }
|
||||
SpawnedResources.Add(kvp.Key, spawnedResources);
|
||||
kvp.Value.Second = rotation;
|
||||
}
|
||||
CalculateMissionClusterPositions();
|
||||
FindRelevantLevelResources();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (IsClient) { return; }
|
||||
switch (State)
|
||||
{
|
||||
case 0:
|
||||
if (!EnoughHaveBeenCollected()) { return; }
|
||||
State = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndPosition && !Submarine.MainSub.AtStartPosition) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (EnoughHaveBeenCollected())
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
foreach (var kvp in SpawnedResources)
|
||||
{
|
||||
foreach (var i in kvp.Value)
|
||||
{
|
||||
if (i != null && !i.Removed && !HasBeenCollected(i))
|
||||
{
|
||||
i.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
SpawnedResources.Clear();
|
||||
RelevantLevelResources.Clear();
|
||||
MissionClusterPositions.Clear();
|
||||
failed = !completed && state > 0;
|
||||
}
|
||||
|
||||
private void FindRelevantLevelResources()
|
||||
{
|
||||
RelevantLevelResources.Clear();
|
||||
foreach (var identifier in ResourceClusters.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)))
|
||||
.ToArray();
|
||||
RelevantLevelResources.Add(identifier, items);
|
||||
}
|
||||
}
|
||||
|
||||
private bool EnoughHaveBeenCollected()
|
||||
{
|
||||
foreach (var kvp in ResourceClusters)
|
||||
{
|
||||
if (RelevantLevelResources.TryGetValue(kvp.Key, out var availableResources))
|
||||
{
|
||||
var collected = availableResources.Count(r => HasBeenCollected(r));
|
||||
var needed = kvp.Value.First;
|
||||
if (collected < needed) { return false; }
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool HasBeenCollected(Item item)
|
||||
{
|
||||
if (item == null) { return false; }
|
||||
if (item.Removed) { return false; }
|
||||
var owner = item.GetRootInventoryOwner();
|
||||
if (owner.Submarine != null && owner.Submarine.Info.Type == SubmarineType.Player)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (owner is Character c)
|
||||
{
|
||||
return c.Info != null && GameMain.GameSession.CrewManager.CharacterInfos.Contains(c.Info);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool AnyAreUncollected(IEnumerable<Item> items)
|
||||
=> items.Any(i => !HasBeenCollected(i));
|
||||
|
||||
private void CalculateMissionClusterPositions()
|
||||
{
|
||||
MissionClusterPositions.Clear();
|
||||
foreach (var kvp in SpawnedResources)
|
||||
{
|
||||
if (kvp.Value.None()) { continue; }
|
||||
var pos = Vector2.Zero;
|
||||
var itemCount = 0;
|
||||
foreach (var i in kvp.Value.Where(i => i != null && !i.Removed))
|
||||
{
|
||||
pos += i.WorldPosition;
|
||||
itemCount++;
|
||||
}
|
||||
pos /= itemCount;
|
||||
MissionClusterPositions.Add(new Tuple<string, Vector2>(kvp.Key, pos));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,7 @@ namespace Barotrauma
|
||||
get { return Enumerable.Empty<Vector2>(); }
|
||||
}
|
||||
|
||||
public string SonarLabel
|
||||
public virtual string SonarLabel
|
||||
{
|
||||
get { return Prefab.SonarLabel; }
|
||||
}
|
||||
@@ -233,5 +233,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void ChangeLocationType(string from, string to)
|
||||
{
|
||||
if (GameMain.GameSession.GameMode is CampaignMode && !IsClient)
|
||||
{
|
||||
int srcIndex = Locations[0].Type.Identifier.Equals(from, StringComparison.OrdinalIgnoreCase) ? 0 : 1;
|
||||
var upgradeLocation = Locations[srcIndex];
|
||||
upgradeLocation.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(to, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void AdjustLevelData(LevelData levelData) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,20 +14,29 @@ namespace Barotrauma
|
||||
Salvage = 0x1,
|
||||
Monster = 0x2,
|
||||
Cargo = 0x4,
|
||||
Combat = 0x8,
|
||||
All = 0xf
|
||||
Beacon = 0x8,
|
||||
Nest = 0x10,
|
||||
Mineral = 0x20,
|
||||
Combat = 0x40,
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat
|
||||
}
|
||||
|
||||
partial class MissionPrefab
|
||||
{
|
||||
public static readonly List<MissionPrefab> List = new List<MissionPrefab>();
|
||||
|
||||
private static readonly Dictionary<MissionType, Type> missionClasses = new Dictionary<MissionType, Type>()
|
||||
public static readonly Dictionary<MissionType, Type> CoOpMissionClasses = new Dictionary<MissionType, Type>()
|
||||
{
|
||||
{ MissionType.Salvage, typeof(SalvageMission) },
|
||||
{ MissionType.Monster, typeof(MonsterMission) },
|
||||
{ MissionType.Cargo, typeof(CargoMission) },
|
||||
{ MissionType.Combat, typeof(CombatMission) },
|
||||
{ MissionType.Beacon, typeof(BeaconMission) },
|
||||
{ MissionType.Nest, typeof(NestMission) },
|
||||
{ MissionType.Mineral, typeof(MineralMission) },
|
||||
};
|
||||
public static readonly Dictionary<MissionType, Type> PvPMissionClasses = new Dictionary<MissionType, Type>()
|
||||
{
|
||||
{ MissionType.Combat, typeof(CombatMission) }
|
||||
};
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
@@ -146,15 +155,32 @@ namespace Barotrauma
|
||||
Headers = new List<string>();
|
||||
Messages = new List<string>();
|
||||
AllowedLocationTypes = new List<Pair<string, string>>();
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
string header = TextManager.Get("MissionHeader" + i + "." + TextIdentifier, true);
|
||||
string message = TextManager.Get("MissionMessage" + i + "." + TextIdentifier, true);
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
Headers.Add(header);
|
||||
Messages.Add(message);
|
||||
}
|
||||
}
|
||||
|
||||
int messageIndex = 0;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "message":
|
||||
int index = Messages.Count;
|
||||
|
||||
Headers.Add(TextManager.Get("MissionHeader" + index + "." + TextIdentifier, true) ?? subElement.GetAttributeString("header", ""));
|
||||
Messages.Add(TextManager.Get("MissionMessage" + index + "." + TextIdentifier, true) ?? subElement.GetAttributeString("text", ""));
|
||||
if (messageIndex > Headers.Count - 1)
|
||||
{
|
||||
Headers.Add(string.Empty);
|
||||
Messages.Add(string.Empty);
|
||||
}
|
||||
Headers[messageIndex] = TextManager.Get("MissionHeader" + messageIndex + "." + TextIdentifier, true) ?? subElement.GetAttributeString("header", "");
|
||||
Messages[messageIndex] = TextManager.Get("MissionMessage" + messageIndex + "." + TextIdentifier, true) ?? subElement.GetAttributeString("text", "");
|
||||
messageIndex++;
|
||||
break;
|
||||
case "locationtype":
|
||||
AllowedLocationTypes.Add(new Pair<string, string>(
|
||||
@@ -211,7 +237,18 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
constructor = missionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
|
||||
if (CoOpMissionClasses.ContainsKey(Type))
|
||||
{
|
||||
constructor = CoOpMissionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
|
||||
}
|
||||
else if (PvPMissionClasses.ContainsKey(Type))
|
||||
{
|
||||
constructor = PvPMissionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - unsupported mission type \"" + Type.ToString() + "\"");
|
||||
}
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
@@ -86,17 +86,27 @@ namespace Barotrauma
|
||||
{
|
||||
if (monsters.Count > 0)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception($"monsters.Count > 0 ({monsters.Count})");
|
||||
#else
|
||||
DebugConsole.AddWarning("Monster list was not empty at the start of a monster mission. The mission instance may not have been ended correctly on previous rounds.");
|
||||
monsters.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (tempSonarPositions.Count > 0)
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception($"tempSonarPositions.Count > 0 ({tempSonarPositions.Count})");
|
||||
#else
|
||||
DebugConsole.AddWarning("Sonar position list was not empty at the start of a monster mission. The mission instance may not have been ended correctly on previous rounds.");
|
||||
tempSonarPositions.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!IsClient)
|
||||
{
|
||||
Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
|
||||
Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath | Level.PositionType.SidePath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
|
||||
foreach (var monster in monsterPrefabs)
|
||||
{
|
||||
int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1);
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
using Barotrauma.Extensions;
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class NestMission : Mission
|
||||
{
|
||||
private readonly XElement itemConfig;
|
||||
private readonly List<Item> items = new List<Item>();
|
||||
private readonly Dictionary<Item, StatusEffect> statusEffectOnApproach = new Dictionary<Item, StatusEffect>();
|
||||
|
||||
//string = filename, point = min,max
|
||||
private readonly HashSet<Tuple<CharacterPrefab, Point>> monsterPrefabs = new HashSet<Tuple<CharacterPrefab, Point>>();
|
||||
|
||||
private readonly float itemSpawnRadius = 800.0f;
|
||||
private readonly float approachItemsRadius = 1000.0f;
|
||||
private readonly float monsterSpawnRadius = 3000.0f;
|
||||
|
||||
private readonly bool requireDelivery;
|
||||
|
||||
private readonly Level.PositionType spawnPositionType;
|
||||
|
||||
private Vector2 nestPosition;
|
||||
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
Enumerable.Empty<Vector2>();
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return nestPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public NestMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
{
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
|
||||
itemSpawnRadius = prefab.ConfigElement.GetAttributeFloat("itemspawnradius", 800.0f);
|
||||
approachItemsRadius = prefab.ConfigElement.GetAttributeFloat("approachitemsradius", itemSpawnRadius * 2.0f);
|
||||
monsterSpawnRadius = prefab.ConfigElement.GetAttributeFloat("monsterspawnradius", approachItemsRadius * 2.0f);
|
||||
|
||||
requireDelivery = prefab.ConfigElement.GetAttributeBool("requiredelivery", 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 monsterElement in prefab.ConfigElement.GetChildElements("monster"))
|
||||
{
|
||||
string speciesName = monsterElement.GetAttributeString("character", string.Empty);
|
||||
int defaultCount = monsterElement.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
{
|
||||
defaultCount = monsterElement.GetAttributeInt("amount", 1);
|
||||
}
|
||||
int min = Math.Min(monsterElement.GetAttributeInt("min", defaultCount), 255);
|
||||
int max = Math.Min(Math.Max(min, monsterElement.GetAttributeInt("max", defaultCount)), 255);
|
||||
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
if (characterPrefab != null)
|
||||
{
|
||||
monsterPrefabs.Add(new Tuple<CharacterPrefab, Point>(characterPrefab, new Point(min, max)));
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in monster mission \"{prefab.Identifier}\". Could not find a character prefab with the name \"{speciesName}\".");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
if (items.Any())
|
||||
{
|
||||
#if DEBUG
|
||||
throw new Exception($"items.Count > 0 ({items.Count})");
|
||||
#else
|
||||
DebugConsole.AddWarning("Item list was not empty at the start of a nest mission. The mission instance may not have been ended correctly on previous rounds.");
|
||||
items.Clear();
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!IsClient)
|
||||
{
|
||||
//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;
|
||||
nestPosition = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f);
|
||||
List<GraphEdge> spawnEdges = new List<GraphEdge>();
|
||||
if (spawnPositionType == Level.PositionType.Cave)
|
||||
{
|
||||
var nearbyCells = Level.Loaded.GetCells(nestPosition, searchDepth: 3);
|
||||
if (nearbyCells.Any())
|
||||
{
|
||||
List<GraphEdge> validEdges = new List<GraphEdge>();
|
||||
foreach (var edge in nearbyCells.SelectMany(c => c.Edges))
|
||||
{
|
||||
if (!edge.NextToCave || !edge.IsSolid) { continue; }
|
||||
if (Level.Loaded.ExtraWalls.Any(w => w.IsPointInside(edge.Center + edge.GetNormal(edge.Cell1 ?? edge.Cell2) * 100.0f))) { continue; }
|
||||
validEdges.Add(edge);
|
||||
}
|
||||
|
||||
if (validEdges.Any())
|
||||
{
|
||||
spawnEdges.AddRange(validEdges.Where(e => MathUtils.LineSegmentToPointDistanceSquared(e.Point1.ToPoint(), e.Point2.ToPoint(), nestPosition.ToPoint()) < itemSpawnRadius * itemSpawnRadius).Distinct());
|
||||
}
|
||||
//no valid edges found close enough to the nest position, find the closest one
|
||||
if (!spawnEdges.Any())
|
||||
{
|
||||
GraphEdge closestEdge = null;
|
||||
float closestDist = float.PositiveInfinity;
|
||||
foreach (var edge in nearbyCells.SelectMany(c => c.Edges))
|
||||
{
|
||||
if (!edge.NextToCave || !edge.IsSolid) { continue; }
|
||||
float dist = Vector2.DistanceSquared(edge.Center, nestPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestEdge = edge;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
if (closestEdge != null)
|
||||
{
|
||||
spawnEdges.Add(closestEdge);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (XElement subElement in itemConfig.Elements())
|
||||
{
|
||||
string itemIdentifier = subElement.GetAttributeString("identifier", "");
|
||||
if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for nest mission: item prefab \"" + itemIdentifier + "\" not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 spawnPos = nestPosition;
|
||||
float rotation = 0.0f;
|
||||
if (spawnEdges.Any())
|
||||
{
|
||||
var edge = spawnEdges.GetRandom(Rand.RandSync.Server);
|
||||
spawnPos = Vector2.Lerp(edge.Point1, edge.Point2, Rand.Range(0.1f, 0.9f, Rand.RandSync.Server));
|
||||
Vector2 normal = Vector2.UnitY;
|
||||
if (edge.Cell1 != null && edge.Cell1.CellType == CellType.Solid)
|
||||
{
|
||||
normal = edge.GetNormal(edge.Cell1);
|
||||
}
|
||||
else if (edge.Cell2 != null && edge.Cell2.CellType == CellType.Solid)
|
||||
{
|
||||
normal = edge.GetNormal(edge.Cell2);
|
||||
}
|
||||
spawnPos += normal * 10.0f;
|
||||
rotation = MathUtils.VectorToAngle(normal) - MathHelper.PiOver2;
|
||||
}
|
||||
|
||||
var item = new Item(itemPrefab, spawnPos, null);
|
||||
item.body.FarseerBody.BodyType = BodyType.Kinematic;
|
||||
item.body.SetTransformIgnoreContacts(item.body.SimPosition, rotation);
|
||||
item.FindHull();
|
||||
items.Add(item);
|
||||
|
||||
var statusEffectElement = subElement.Element("StatusEffectOnApproach") ?? subElement.Element("statuseffectonapproach");
|
||||
if (statusEffectElement != null)
|
||||
{
|
||||
statusEffectOnApproach.Add(item, StatusEffect.Load(statusEffectElement, Prefab.Identifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (IsClient)
|
||||
{
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (item.ParentInventory != null && item.body != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (State)
|
||||
{
|
||||
case 0:
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (item.ParentInventory != null && item.body != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
if (statusEffectOnApproach.ContainsKey(item))
|
||||
{
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsPlayer && Vector2.DistanceSquared(nestPosition, character.WorldPosition) < approachItemsRadius * approachItemsRadius)
|
||||
{
|
||||
statusEffectOnApproach[item].Apply(statusEffectOnApproach[item].type, 1.0f, item, item);
|
||||
statusEffectOnApproach.Remove(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (monsterPrefabs.Any())
|
||||
{
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsPlayer && Vector2.DistanceSquared(nestPosition, character.WorldPosition) < monsterSpawnRadius * monsterSpawnRadius)
|
||||
{
|
||||
foreach (var monster in monsterPrefabs)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
monsterPrefabs.Clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//continue when all items are in the sub or destroyed
|
||||
if (AllItemsDestroyedOrRetrieved()) { State = 1; }
|
||||
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndPosition && !Submarine.MainSub.AtStartPosition) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool AllItemsDestroyedOrRetrieved()
|
||||
{
|
||||
if (requireDelivery)
|
||||
{
|
||||
foreach (Item item in items)
|
||||
{
|
||||
Submarine parentSub = item.CurrentHull?.Submarine ?? item.GetRootInventoryOwner()?.Submarine;
|
||||
if (parentSub?.Info?.Type == SubmarineType.Player) { continue; }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (item.Removed || item.Condition <= 0.0f) { continue; }
|
||||
if (Vector2.Distance(item.WorldPosition, nestPosition) > Math.Max(itemSpawnRadius * 2, 3000.0f)) { continue; }
|
||||
Submarine parentSub = item.CurrentHull?.Submarine ?? item.GetRootInventoryOwner()?.Submarine;
|
||||
if (parentSub?.Info?.Type == SubmarineType.Player) { continue; }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (AllItemsDestroyedOrRetrieved())
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (item != null && !item.Removed)
|
||||
{
|
||||
item.Remove();
|
||||
}
|
||||
}
|
||||
items.Clear();
|
||||
failed = !completed && state > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,8 +109,8 @@ namespace Barotrauma
|
||||
item = null;
|
||||
if (!IsClient)
|
||||
{
|
||||
//ruin/wreck items are allowed to spawn close to the sub
|
||||
float minDistance = spawnPositionType == Level.PositionType.Ruin || spawnPositionType == Level.PositionType.Wreck ?
|
||||
//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);
|
||||
|
||||
@@ -121,6 +121,7 @@ namespace Barotrauma
|
||||
{
|
||||
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:
|
||||
|
||||
@@ -149,8 +149,12 @@ namespace Barotrauma
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (position.PositionType != Level.PositionType.MainPath) { continue; }
|
||||
if (Level.Loaded.ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(position.Position.ToVector2()))))
|
||||
if (position.PositionType != Level.PositionType.MainPath &&
|
||||
position.PositionType != Level.PositionType.SidePath)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (Level.Loaded.ExtraWalls.Any(w => w.IsPointInside(position.Position.ToVector2())))
|
||||
{
|
||||
removals.Add(position);
|
||||
}
|
||||
@@ -281,7 +285,8 @@ namespace Barotrauma
|
||||
spawnPos = spawnPoint.WorldPosition;
|
||||
}
|
||||
}
|
||||
else if (chosenPosition.PositionType == Level.PositionType.MainPath && offset > 0)
|
||||
else if ((chosenPosition.PositionType == Level.PositionType.MainPath || chosenPosition.PositionType == Level.PositionType.SidePath)
|
||||
&& offset > 0)
|
||||
{
|
||||
Vector2 dir;
|
||||
var waypoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == null);
|
||||
@@ -381,9 +386,10 @@ namespace Barotrauma
|
||||
//+1 because Range returns an integer less than the max value
|
||||
int amount = Rand.Range(minAmount, maxAmount + 1);
|
||||
monsters = new List<Character>();
|
||||
float offsetAmount = spawnPosType == Level.PositionType.MainPath ? scatter : 100;
|
||||
float offsetAmount = spawnPosType == Level.PositionType.MainPath || spawnPosType == Level.PositionType.SidePath ? scatter : 100;
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
string seed = Level.Loaded.Seed + i.ToString();
|
||||
CoroutineManager.InvokeAfter(() =>
|
||||
{
|
||||
//round ended before the coroutine finished
|
||||
@@ -392,7 +398,7 @@ namespace Barotrauma
|
||||
System.Diagnostics.Debug.Assert(GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer, "Clients should not create monster events.");
|
||||
|
||||
Vector2 pos = spawnPos.Value + Rand.Vector(offsetAmount);
|
||||
if (spawnPosType == Level.PositionType.MainPath)
|
||||
if (spawnPosType == Level.PositionType.MainPath || spawnPosType == Level.PositionType.SidePath)
|
||||
{
|
||||
if (Submarine.Loaded.Any(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(pos)))
|
||||
{
|
||||
@@ -406,7 +412,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
monsters.Add(Character.Create(speciesName, pos, Level.Loaded.Seed + i.ToString(), null, false, true, true));
|
||||
monsters.Add(Character.Create(speciesName, pos, seed, characterInfo: null, isRemotePlayer: false, hasAi: true, createNetworkEvent: true));
|
||||
|
||||
if (monsters.Count == amount)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace Barotrauma
|
||||
private int prevEntityCount;
|
||||
private int prevPlayerCount, prevBotCount;
|
||||
|
||||
private string[] requiredDestinationTypes;
|
||||
|
||||
public int CurrentActionIndex { get; private set; }
|
||||
public List<EventAction> Actions { get; } = new List<EventAction>();
|
||||
public Dictionary<string, List<Entity>> Targets { get; } = new Dictionary<string, List<Entity>>();
|
||||
@@ -39,6 +41,8 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.ThrowError($"Scripted event \"{prefab.Identifier}\" has no actions. The event will do nothing.");
|
||||
}
|
||||
|
||||
requiredDestinationTypes = prefab.ConfigElement.GetAttributeStringArray("requireddestinationtypes", null);
|
||||
}
|
||||
|
||||
public void AddTarget(string tag, Entity target)
|
||||
@@ -199,5 +203,14 @@ namespace Barotrauma
|
||||
currentAction.Update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool LevelMeetsRequirements()
|
||||
{
|
||||
if (requiredDestinationTypes == null) { return true; }
|
||||
var currLocation = GameMain.GameSession?.Campaign?.Map.CurrentLocation;
|
||||
if (currLocation == null) { return true; }
|
||||
var locations = currLocation?.Connections?.Select(c => c.Locations.First(l => l != currLocation));
|
||||
return locations.Any(l => requiredDestinationTypes.Any(t => l.Type.Identifier.Equals(t, StringComparison.OrdinalIgnoreCase)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user