(6eeea9b7c) v0.9.10.0.0

This commit is contained in:
Joonas Rikkonen
2020-06-04 16:41:07 +03:00
parent ce4ccd99ac
commit eeac247a8e
366 changed files with 7772 additions and 3692 deletions
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -9,6 +10,8 @@ namespace Barotrauma
{
const float IntensityUpdateInterval = 5.0f;
const float CalculateDistanceTraveledInterval = 5.0f;
private Level level;
private readonly List<Sprite> preloadedSprites = new List<Sprite>();
@@ -30,6 +33,11 @@ namespace Barotrauma
private float intensityUpdateTimer;
private PathFinder pathFinder;
private float totalPathLength;
private float calculateDistanceTraveledTimer;
private float distanceTraveled;
private float avgCrewHealth, avgHullIntegrity, floodingAmount, fireAmount, enemyDanger;
private float roundDuration;
@@ -72,6 +80,10 @@ namespace Barotrauma
pendingEventSets.Clear();
selectedEvents.Clear();
pathFinder = new PathFinder(WayPoint.WayPointList, indoorsSteering: false);
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(Level.Loaded.StartPosition), ConvertUnits.ToSimUnits(Level.Loaded.EndPosition));
totalPathLength = steeringPath.TotalLength;
this.level = level;
SelectSettings();
@@ -137,7 +149,44 @@ namespace Barotrauma
public void PreloadContent(IEnumerable<ContentFile> contentFiles)
{
foreach (ContentFile file in contentFiles)
var filesToPreload = new List<ContentFile>(contentFiles);
foreach (Submarine sub in Submarine.Loaded)
{
if (sub.WreckAI == null) { continue; }
if (!string.IsNullOrEmpty(sub.WreckAI.Config.DefensiveAgent))
{
var prefab = CharacterPrefab.FindBySpeciesName(sub.WreckAI.Config.DefensiveAgent);
if (prefab != null && !filesToPreload.Any(f => f.Path == prefab.FilePath))
{
filesToPreload.Add(new ContentFile(prefab.FilePath, ContentType.Character));
}
}
foreach (Item item in Item.ItemList)
{
if (item.Submarine != sub) { continue; }
foreach (Items.Components.ItemComponent component in item.Components)
{
if (component.statusEffectLists == null) { continue; }
foreach (var statusEffectList in component.statusEffectLists.Values)
{
foreach (StatusEffect statusEffect in statusEffectList)
{
foreach (var spawnInfo in statusEffect.SpawnCharacters)
{
var prefab = CharacterPrefab.FindBySpeciesName(spawnInfo.SpeciesName);
if (prefab != null && !filesToPreload.Any(f => f.Path == prefab.FilePath))
{
filesToPreload.Add(new ContentFile(prefab.FilePath, ContentType.Character));
}
}
}
}
}
}
}
foreach (ContentFile file in filesToPreload)
{
switch (file.Type)
{
@@ -299,12 +348,9 @@ namespace Barotrauma
private bool CanStartEventSet(ScriptedEventSet eventSet)
{
float distFromStart = Vector2.Distance(Submarine.MainSub.WorldPosition, level.StartPosition);
float distFromEnd = Vector2.Distance(Submarine.MainSub.WorldPosition, level.EndPosition);
float distanceTraveled = MathHelper.Clamp(
(Submarine.MainSub.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X),
0.0f, 1.0f);
ISpatialEntity refEntity = GetRefEntity();
float distFromStart = Vector2.Distance(refEntity.WorldPosition, level.StartPosition);
float distFromEnd = Vector2.Distance(refEntity.WorldPosition, level.EndPosition);
//don't create new events if within 50 meters of the start/end of the level
if (!eventSet.AllowAtStart)
@@ -367,6 +413,13 @@ namespace Barotrauma
}
}
calculateDistanceTraveledTimer -= deltaTime;
if (calculateDistanceTraveledTimer <= 0.0f)
{
distanceTraveled = CalculateDistanceTraveled();
calculateDistanceTraveledTimer = CalculateDistanceTraveledInterval;
}
eventThreshold += settings.EventThresholdIncrease * deltaTime;
if (eventCoolDown > 0.0f)
{
@@ -514,5 +567,62 @@ namespace Barotrauma
currentIntensity = MathHelper.Max(0.0025f * IntensityUpdateInterval, targetIntensity);
}
}
private float CalculateDistanceTraveled()
{
var refEntity = GetRefEntity();
Vector2 target = ConvertUnits.ToSimUnits(Level.Loaded.EndPosition);
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(refEntity.WorldPosition), target);
if (steeringPath.Unreachable || float.IsPositiveInfinity(totalPathLength))
{
//use horizontal position in the level as a fallback if a path can't be found
return MathHelper.Clamp((refEntity.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X), 0.0f, 1.0f);
}
else
{
return MathHelper.Clamp(1.0f - steeringPath.TotalLength / totalPathLength, 0.0f, 1.0f);
}
}
/// <summary>
/// Get the entity that should be used in determining how far the player has progressed in the level.
/// = The submarine or player character that has progressed the furthest.
/// </summary>
private ISpatialEntity GetRefEntity()
{
ISpatialEntity refEntity = Submarine.MainSub;
#if CLIENT
if (Character.Controlled != null)
{
if (Character.Controlled.Submarine != null &&
Character.Controlled.Submarine.Info.Type == SubmarineInfo.SubmarineType.Player)
{
refEntity = Character.Controlled.Submarine;
}
else
{
refEntity = Character.Controlled;
}
}
#else
foreach (Barotrauma.Networking.Client client in GameMain.Server.ConnectedClients)
{
if (client.Character == null) { continue; }
//only take the players inside a player sub into account.
//Otherwise the system could be abused by for example making a respawned player wait
//close to the destination outpost
if (client.Character.Submarine != null &&
client.Character.Submarine.Info.Type == SubmarineInfo.SubmarineType.Player)
{
if (client.Character.Submarine.WorldPosition.X > refEntity.WorldPosition.X)
{
refEntity = client.Character.Submarine;
}
}
}
#endif
return refEntity;
}
}
}
@@ -24,8 +24,9 @@ namespace Barotrauma
public readonly float MinLevelDifficulty = 0.0f;
public readonly float MaxLevelDifficulty = 100.0f;
static EventManagerSettings()
public static void Init()
{
List.Clear();
foreach (ContentFile file in GameMain.Instance.GetFilesOfType(ContentType.EventManagerSettings))
{
Load(file);
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -10,6 +11,8 @@ namespace Barotrauma
private readonly XElement itemConfig;
private readonly List<Item> items = new List<Item>();
private readonly Dictionary<Item, UInt16> itemIDs = new Dictionary<Item, UInt16>();
private readonly Dictionary<Item, UInt16> parentInventoryIDs = new Dictionary<Item, UInt16>();
private int requiredDeliveryAmount;
@@ -22,8 +25,6 @@ namespace Barotrauma
private void InitItems()
{
items.Clear();
if (itemConfig == null)
{
DebugConsole.ThrowError("Failed to initialize items for cargo mission (itemConfig == null)");
@@ -91,8 +92,13 @@ namespace Barotrauma
var item = new Item(itemPrefab, position, cargoRoom.Submarine);
item.FindHull();
items.Add(item);
if (parent != null) parent.Combine(item, user: null);
itemIDs.Add(item, item.ID);
if (parent != null)
{
parentInventoryIDs.Add(item, parent.ID);
parent.Combine(item, user: null);
}
foreach (XElement subElement in element.Elements())
{
@@ -106,6 +112,10 @@ namespace Barotrauma
public override void Start(Level level)
{
items.Clear();
itemIDs.Clear();
parentInventoryIDs.Clear();
if (!IsClient)
{
InitItems();
@@ -108,23 +108,6 @@ namespace Barotrauma
subs[1].SetPosition(subs[1].FindSpawnPos(Level.Loaded.EndPosition));
subs[1].FlipX();
//prevent wifi components from communicating between subs
List<WifiComponent> wifiComponents = new List<WifiComponent>();
foreach (Item item in Item.ItemList)
{
wifiComponents.AddRange(item.GetComponents<WifiComponent>());
}
foreach (WifiComponent wifiComponent in wifiComponents)
{
for (int i = 0; i < 2; i++)
{
if (wifiComponent.Item.Submarine == subs[i] || subs[i].ConnectedDockingPorts.ContainsKey(wifiComponent.Item.Submarine))
{
wifiComponent.TeamID = subs[i].TeamID;
}
}
}
crews = new List<Character>[] { new List<Character>(), new List<Character>() };
foreach (Submarine submarine in Submarine.Loaded)
@@ -70,7 +70,7 @@ namespace Barotrauma
monsterFiles.Add(new Tuple<string, Point>(monster, new Point(min, max)));
}
description = description.Replace("[monster]",
TextManager.Get("character." + System.IO.Path.GetFileNameWithoutExtension(monsterFileName)));
TextManager.Get("character." + Barotrauma.IO.Path.GetFileNameWithoutExtension(monsterFileName)));
}
public override void Start(Level level)
@@ -103,6 +103,10 @@ namespace Barotrauma
public override void Start(Level level)
{
#if SERVER
originalItemID = Entity.NullEntityID;
originalInventoryID = Entity.NullEntityID;
#endif
if (!IsClient)
{
//ruin/wreck items are allowed to spawn close to the sub
@@ -147,6 +151,9 @@ namespace Barotrauma
item.body.FarseerBody.BodyType = BodyType.Kinematic;
item.FindHull();
}
#if SERVER
originalItemID = item.ID;
#endif
for (int i = 0; i < statusEffects.Count; i++)
{
@@ -166,6 +173,7 @@ namespace Barotrauma
foreach (Item it in Item.ItemList)
{
if (!it.HasTag(containerTag)) { continue; }
if (it.NonInteractable) { continue; }
switch (spawnPositionType)
{
case Level.PositionType.Cave:
@@ -181,7 +189,13 @@ namespace Barotrauma
}
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
if (itemContainer == null) { continue; }
if (itemContainer.Combine(item, user: null)) { break; } // Placement successful
if (itemContainer.Combine(item, user: null))
{
#if SERVER
originalInventoryID = it.ID;
#endif
break;
} // Placement successful
}
}
}
@@ -13,6 +13,9 @@ namespace Barotrauma
private readonly int minAmount, maxAmount;
private List<Character> monsters;
private readonly float scatter;
private readonly float offset;
private readonly bool spawnDeep;
private Vector2? spawnPos;
@@ -72,6 +75,8 @@ namespace Barotrauma
}
spawnDeep = prefab.ConfigElement.GetAttributeBool("spawndeep", false);
offset = prefab.ConfigElement.GetAttributeFloat("offset", 0);
scatter = Math.Clamp(prefab.ConfigElement.GetAttributeFloat("scatter", 1000), 0, 3000);
if (GameMain.NetworkMember != null)
{
@@ -118,7 +123,7 @@ namespace Barotrauma
private List<Level.InterestingPosition> GetAvailableSpawnPositions()
{
var availablePositions = Level.Loaded.PositionsOfInterest.FindAll(p => spawnPosType.HasFlag(p.PositionType) && !Level.Loaded.UsedPositions.Contains(p));
var availablePositions = Level.Loaded.PositionsOfInterest.FindAll(p => spawnPosType.HasFlag(p.PositionType));
var removals = new List<Level.InterestingPosition>();
foreach (var position in availablePositions)
{
@@ -169,10 +174,6 @@ namespace Barotrauma
if (Rand.Value(Rand.RandSync.Server) > prefab.SpawnProbability)
{
removedPositions.Add(position);
if (prefab.AllowOnlyOnce)
{
Level.Loaded.UsedPositions.Add(position);
}
}
}
removedPositions.ForEach(p => availablePositions.Remove(p));
@@ -245,11 +246,34 @@ namespace Barotrauma
spawnPos = spawnPoint.WorldPosition;
}
}
spawnPending = true;
if (prefab.AllowOnlyOnce)
else if (chosenPosition.PositionType == Level.PositionType.MainPath && offset > 0)
{
Level.Loaded.UsedPositions.Add(chosenPosition);
Vector2 dir;
var waypoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == null);
var nearestWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, spawnPos.Value)).FirstOrDefault();
if (nearestWaypoint != null)
{
int currentIndex = waypoints.IndexOf(nearestWaypoint);
var nextWaypoint = waypoints[Math.Min(currentIndex + 20, waypoints.Count - 1)];
dir = Vector2.Normalize(nextWaypoint.WorldPosition - nearestWaypoint.WorldPosition);
// Ensure that the spawn position is not offset to the left.
if (dir.X < 0)
{
dir.X = 0;
}
}
else
{
dir = new Vector2(1, Rand.Range(-1, 1));
}
Vector2 targetPos = spawnPos.Value + dir * offset;
var targetWaypoint = waypoints.OrderBy(wp => Vector2.DistanceSquared(wp.WorldPosition, targetPos)).FirstOrDefault();
if (targetWaypoint != null)
{
spawnPos = targetWaypoint.WorldPosition;
}
}
spawnPending = true;
}
}
@@ -278,11 +302,14 @@ namespace Barotrauma
if (spawnPending)
{
//wait until there are no submarines at the spawnpos
foreach (Submarine submarine in Submarine.Loaded)
if (spawnPosType == Level.PositionType.MainPath)
{
if (submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
float minDist = GetMinDistanceToSub(submarine);
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist) { return; }
foreach (Submarine submarine in Submarine.Loaded)
{
if (submarine.Info.Type != SubmarineInfo.SubmarineType.Player) { continue; }
float minDist = GetMinDistanceToSub(submarine);
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist) { return; }
}
}
//if spawning in a ruin/cave, wait for someone to be close to it to spawning
@@ -319,7 +346,7 @@ 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 ? 1000 : 100;
float offsetAmount = spawnPosType == Level.PositionType.MainPath ? scatter : 100;
for (int i = 0; i < amount; i++)
{
CoroutineManager.InvokeAfter(() =>
@@ -329,7 +356,22 @@ namespace Barotrauma
System.Diagnostics.Debug.Assert(GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer, "Clients should not create monster events.");
monsters.Add(Character.Create(speciesName, spawnPos.Value + Rand.Vector(offsetAmount), Level.Loaded.Seed + i.ToString(), null, false, true, true));
Vector2 pos = spawnPos.Value + Rand.Vector(offsetAmount);
if (spawnPosType == Level.PositionType.MainPath)
{
if (Submarine.Loaded.Any(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(pos)))
{
// Can't use the offset position, let's use the exact spawn position.
pos = spawnPos.Value;
}
else if (Level.Loaded.Ruins.Any(r => ToolBox.GetWorldBounds(r.Area.Center, r.Area.Size).ContainsWorld(pos)))
{
// Can't use the offset position, let's use the exact spawn position.
pos = spawnPos.Value;
}
}
monsters.Add(Character.Create(speciesName, pos, Level.Loaded.Seed + i.ToString(), null, false, true, true));
if (monsters.Count == amount)
{
@@ -11,7 +11,6 @@ namespace Barotrauma
public readonly Type EventType;
public readonly string MusicType;
public readonly float SpawnProbability;
public readonly bool AllowOnlyOnce;
public float Commonness;
public ScriptedEventPrefab(XElement element)
@@ -34,7 +33,6 @@ namespace Barotrauma
}
Commonness = element.GetAttributeFloat("commonness", 1.0f);
SpawnProbability = Math.Clamp(element.GetAttributeFloat("spawnprobability", 1.0f), 0, 1);
AllowOnlyOnce = element.GetAttributeBool("allowonlyonce", false);
}
public ScriptedEvent CreateInstance()
@@ -1,13 +1,28 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Xml.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
{
class ScriptedEventSet
{
internal class EventDebugStats
{
public readonly ScriptedEventSet RootSet;
public readonly Dictionary<string, int> MonsterCounts = new Dictionary<string, int>();
public EventDebugStats(ScriptedEventSet rootSet)
{
RootSet = rootSet;
}
}
public static List<ScriptedEventSet> List
{
get;
@@ -131,5 +146,115 @@ namespace Barotrauma
}
}
}
public static List<string> GetDebugStatistics(int simulatedRoundCount = 100)
{
List<string> debugLines = new List<string>();
foreach (var eventSet in List)
{
List<EventDebugStats> stats = new List<EventDebugStats>();
for (int i = 0; i < simulatedRoundCount; i++)
{
var newStats = new EventDebugStats(eventSet);
CheckEventSet(newStats, eventSet);
stats.Add(newStats);
}
debugLines.Add($"Event stats ({eventSet.DebugIdentifier}): ");
LogEventStats(stats, debugLines);
}
for (int difficulty = 0; difficulty <= 100; difficulty += 10)
{
debugLines.Add($"Event stats on difficulty level {difficulty}: ");
List<EventDebugStats> stats = new List<EventDebugStats>();
for (int i = 0; i < simulatedRoundCount; i++)
{
ScriptedEventSet selectedSet = List.Where(s => difficulty >= s.MinLevelDifficulty && difficulty <= s.MaxLevelDifficulty).GetRandom();
if (selectedSet == null) { continue; }
var newStats = new EventDebugStats(selectedSet);
CheckEventSet(newStats, selectedSet);
stats.Add(newStats);
}
LogEventStats(stats, debugLines);
}
return debugLines;
static void CheckEventSet(EventDebugStats stats, ScriptedEventSet thisSet)
{
if (thisSet.ChooseRandom)
{
var eventPrefab = ToolBox.SelectWeightedRandom(thisSet.EventPrefabs, thisSet.EventPrefabs.Select(e => e.Commonness).ToList(), Rand.RandSync.Unsynced);
if (eventPrefab != null)
{
AddEvent(stats, eventPrefab);
}
}
else
{
foreach (var eventPrefab in thisSet.EventPrefabs)
{
AddEvent(stats, eventPrefab);
}
}
foreach (var childSet in thisSet.ChildSets)
{
CheckEventSet(stats, childSet);
}
}
static void AddEvent(EventDebugStats stats, ScriptedEventPrefab eventPrefab)
{
if (eventPrefab.EventType == typeof(MonsterEvent))
{
float spawnProbability = eventPrefab.ConfigElement.GetAttributeFloat("spawnprobability", 1.0f);
if (Rand.Value(Rand.RandSync.Server) > spawnProbability)
{
return;
}
string character = eventPrefab.ConfigElement.GetAttributeString("characterfile", "");
System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(character));
int amount = eventPrefab.ConfigElement.GetAttributeInt("amount", 0);
int minAmount = eventPrefab.ConfigElement.GetAttributeInt("minamount", amount);
int maxAmount = eventPrefab.ConfigElement.GetAttributeInt("maxamount", amount);
int count = Rand.Range(minAmount, maxAmount + 1);
if (count <= 0) { return; }
if (!stats.MonsterCounts.ContainsKey(character)) { stats.MonsterCounts[character] = 0; }
stats.MonsterCounts[character] += count;
}
}
static void LogEventStats(List<EventDebugStats> stats, List<string> debugLines)
{
if (stats.Count == 0 || stats.All(s => s.MonsterCounts.Values.Sum() == 0))
{
debugLines.Add(" No monster spawns");
debugLines.Add($" ");
}
else
{
stats.Sort((s1, s2) => { return s1.MonsterCounts.Values.Sum().CompareTo(s2.MonsterCounts.Values.Sum()); });
EventDebugStats minStats = stats.First();
EventDebugStats maxStats = stats.First();
debugLines.Add($" Minimum monster spawns: {stats.First().MonsterCounts.Values.Sum()}");
debugLines.Add($" {LogMonsterCounts(stats.First())}");
debugLines.Add($" Median monster spawns: {stats[stats.Count / 2].MonsterCounts.Values.Sum()}");
debugLines.Add($" {LogMonsterCounts(stats[stats.Count / 2])}");
debugLines.Add($" Maximum monster spawns: {stats.Last().MonsterCounts.Values.Sum()}");
debugLines.Add($" {LogMonsterCounts(stats.Last())}");
debugLines.Add($" ");
}
}
static string LogMonsterCounts(EventDebugStats stats)
{
return string.Join(", ", stats.MonsterCounts.Select(mc => mc.Key + " x " + mc.Value));
}
}
}
}