v0.11.0.9

This commit is contained in:
Joonas Rikkonen
2020-12-09 16:34:16 +02:00
parent bbf06f0984
commit f433a7ba10
325 changed files with 13947 additions and 3652 deletions
@@ -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)));
}
}
}