325 lines
13 KiB
C#
325 lines
13 KiB
C#
using Microsoft.Xna.Framework;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace Barotrauma
|
|
{
|
|
partial class EventManager
|
|
{
|
|
const float IntensityUpdateInterval = 5.0f;
|
|
|
|
private List<ScriptedEvent> events;
|
|
|
|
private Level level;
|
|
|
|
//The "intensity" of the current situation (a value between 0.0 - 1.0).
|
|
//High when a disaster has struck, low when nothing special is going on.
|
|
private float currentIntensity;
|
|
//The exact intensity of the current situation, current intensity is lerped towards this value
|
|
private float targetIntensity;
|
|
|
|
//How low the intensity has to be for an event to be triggered.
|
|
//Gradually increases with time, so additional problems can still appear eventually even if
|
|
//the sub is laying broken on the ocean floor or if the players are trying to abuse the system
|
|
//by intentionally keeping the intensity high by causing breaches, damaging themselves or such
|
|
private float eventThreshold = 0.2f;
|
|
|
|
//New events can't be triggered when the cooldown is active.
|
|
private float eventCoolDown;
|
|
|
|
private float intensityUpdateTimer;
|
|
|
|
private float avgCrewHealth, avgHullIntegrity, floodingAmount, fireAmount, enemyDanger;
|
|
|
|
private float roundDuration;
|
|
|
|
private List<ScriptedEventSet> selectedEventSets;
|
|
|
|
private EventManagerSettings settings;
|
|
|
|
public float CurrentIntensity
|
|
{
|
|
get { return currentIntensity; }
|
|
}
|
|
|
|
public List<ScriptedEvent> Events
|
|
{
|
|
get { return events; }
|
|
}
|
|
|
|
public EventManager(GameSession session)
|
|
{
|
|
events = new List<ScriptedEvent>();
|
|
selectedEventSets = new List<ScriptedEventSet>();
|
|
}
|
|
|
|
public bool Enabled = true;
|
|
|
|
public void StartRound(Level level)
|
|
{
|
|
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
|
|
|
|
var suitableSettings = EventManagerSettings.List.FindAll(s =>
|
|
level.Difficulty >= s.MinLevelDifficulty &&
|
|
level.Difficulty <= s.MaxLevelDifficulty);
|
|
|
|
if (suitableSettings.Count == 0)
|
|
{
|
|
DebugConsole.ThrowError("No suitable event manager settings found for the selected level (difficulty " + level.Difficulty + ")");
|
|
settings = EventManagerSettings.List[Rand.Int(EventManagerSettings.List.Count, Rand.RandSync.Server)];
|
|
}
|
|
else
|
|
{
|
|
settings = suitableSettings[Rand.Int(suitableSettings.Count, Rand.RandSync.Server)];
|
|
}
|
|
|
|
this.level = level;
|
|
var initialEventSet = SelectRandomEvents(ScriptedEventSet.List);
|
|
if (initialEventSet != null) selectedEventSets.Add(initialEventSet);
|
|
/*CreateInitialEvents();
|
|
foreach (ScriptedEvent ev in events)
|
|
{
|
|
ev.Init(false);
|
|
}*/
|
|
|
|
roundDuration = 0.0f;
|
|
intensityUpdateTimer = 0.0f;
|
|
CalculateCurrentIntensity(0.0f);
|
|
currentIntensity = targetIntensity;
|
|
eventThreshold = settings.DefaultEventThreshold;
|
|
eventCoolDown = 0.0f;
|
|
}
|
|
|
|
public void EndRound()
|
|
{
|
|
selectedEventSets.Clear();
|
|
events.Clear();
|
|
}
|
|
|
|
private ScriptedEventSet SelectRandomEvents(List<ScriptedEventSet> eventSets)
|
|
{
|
|
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
|
|
|
|
var allowedEventSets =
|
|
eventSets.Where(es => level.Difficulty >= es.MinLevelDifficulty && level.Difficulty <= es.MaxLevelDifficulty);
|
|
|
|
float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level));
|
|
float randomNumber = (float)rand.NextDouble() * totalCommonness;
|
|
foreach (ScriptedEventSet eventSet in allowedEventSets)
|
|
{
|
|
float commonness = eventSet.GetCommonness(level);
|
|
if (randomNumber <= commonness)
|
|
{
|
|
return eventSet;
|
|
}
|
|
randomNumber -= commonness;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void CreateEvents()
|
|
{
|
|
//don't create new events if docked to the start oupost
|
|
if (Level.Loaded?.StartOutpost != null &&
|
|
Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost))
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (int i = selectedEventSets.Count - 1; i >= 0; i--)
|
|
{
|
|
ScriptedEventSet eventSet = selectedEventSets[i];
|
|
|
|
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);
|
|
|
|
//don't create new events if within 50 meters of the start/end of the level
|
|
if (distanceTraveled <= 0.0f ||
|
|
distFromStart * Physics.DisplayToRealWorldRatio < 50.0f ||
|
|
distFromEnd * Physics.DisplayToRealWorldRatio < 50.0f)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) &&
|
|
roundDuration < eventSet.MinMissionTime)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (CurrentIntensity < eventSet.MinIntensity || CurrentIntensity > eventSet.MaxIntensity)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
selectedEventSets.RemoveAt(i);
|
|
|
|
if (eventSet.ChooseRandom)
|
|
{
|
|
if (eventSet.EventPrefabs.Count > 0)
|
|
{
|
|
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
|
|
var newEvent = eventSet.EventPrefabs[rand.NextInt32() % eventSet.EventPrefabs.Count].CreateInstance();
|
|
newEvent.Init(true);
|
|
DebugConsole.Log("Initialized event " + newEvent.ToString());
|
|
events.Add(newEvent);
|
|
}
|
|
if (eventSet.ChildSets.Count > 0)
|
|
{
|
|
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
|
|
var newEventSet = SelectRandomEvents(eventSet.ChildSets);
|
|
if (newEventSet != null) selectedEventSets.Add(newEventSet);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
foreach (ScriptedEventPrefab eventPrefab in eventSet.EventPrefabs)
|
|
{
|
|
var newEvent = eventPrefab.CreateInstance();
|
|
newEvent.Init(true);
|
|
DebugConsole.Log("Initialized event " + newEvent.ToString());
|
|
events.Add(newEvent);
|
|
}
|
|
|
|
selectedEventSets.AddRange(eventSet.ChildSets);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Update(float deltaTime)
|
|
{
|
|
if (!Enabled) return;
|
|
|
|
//clients only calculate the intensity but don't create any events
|
|
//(the intensity is used for controlling the background music)
|
|
CalculateCurrentIntensity(deltaTime);
|
|
|
|
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) return;
|
|
|
|
roundDuration += deltaTime;
|
|
|
|
eventThreshold += settings.EventThresholdIncrease * deltaTime;
|
|
if (eventCoolDown > 0.0f)
|
|
{
|
|
eventCoolDown -= deltaTime;
|
|
}
|
|
else if (currentIntensity < eventThreshold)
|
|
{
|
|
CreateEvents();
|
|
eventThreshold = settings.DefaultEventThreshold;
|
|
eventCoolDown = settings.EventCooldown;
|
|
}
|
|
|
|
foreach (ScriptedEvent ev in events)
|
|
{
|
|
if (!ev.IsFinished)
|
|
{
|
|
ev.Update(deltaTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CalculateCurrentIntensity(float deltaTime)
|
|
{
|
|
intensityUpdateTimer -= deltaTime;
|
|
if (intensityUpdateTimer > 0.0f) return;
|
|
intensityUpdateTimer = IntensityUpdateInterval;
|
|
|
|
// crew health --------------------------------------------------------
|
|
|
|
avgCrewHealth = 0.0f;
|
|
int characterCount = 0;
|
|
foreach (Character character in Character.CharacterList)
|
|
{
|
|
if (character.IsDead) continue;
|
|
#if CLIENT
|
|
if ((character.AIController is HumanAIController || character.IsRemotePlayer || character == Character.Controlled) &&
|
|
(GameMain.Client?.Character == null || GameMain.Client.Character.TeamID == character.TeamID))
|
|
{
|
|
avgCrewHealth += character.Vitality / character.MaxVitality * (character.IsUnconscious ? 0.5f : 1.0f);
|
|
characterCount++;
|
|
}
|
|
#endif
|
|
}
|
|
if (characterCount > 0)
|
|
{
|
|
avgCrewHealth = avgCrewHealth / characterCount;
|
|
}
|
|
|
|
// enemy amount --------------------------------------------------------
|
|
|
|
enemyDanger = 0.0f;
|
|
foreach (Character character in Character.CharacterList)
|
|
{
|
|
if (character.IsDead || character.IsUnconscious || !character.Enabled) continue;
|
|
|
|
EnemyAIController enemyAI = character.AIController as EnemyAIController;
|
|
if (enemyAI == null) continue;
|
|
|
|
if (character.CurrentHull?.Submarine != null &&
|
|
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)))
|
|
{
|
|
//crawler inside the sub adds 0.1f to enemy danger, mantis 0.25f
|
|
enemyDanger += enemyAI.CombatStrength / 1000.0f;
|
|
}
|
|
else if (enemyAI.SelectedAiTarget?.Entity?.Submarine != null)
|
|
{
|
|
//enemy outside and targeting the sub or something in it
|
|
//moloch adds 0.24 to enemy danger, a crawler 0.02
|
|
enemyDanger += enemyAI.CombatStrength / 5000.0f;
|
|
}
|
|
}
|
|
enemyDanger = MathHelper.Clamp(enemyDanger, 0.0f, 1.0f);
|
|
|
|
// hull status (gaps, flooding, fire) --------------------------------------------------------
|
|
|
|
float holeCount = 0.0f;
|
|
floodingAmount = 0.0f;
|
|
foreach (Hull hull in Hull.hullList)
|
|
{
|
|
if (hull.Submarine == null || hull.Submarine.IsOutpost) { continue; }
|
|
foreach (Gap gap in hull.ConnectedGaps)
|
|
{
|
|
if (!gap.IsRoomToRoom) holeCount += gap.Open;
|
|
}
|
|
floodingAmount += hull.WaterVolume / hull.Volume / Hull.hullList.Count;
|
|
fireAmount += hull.FireSources.Sum(fs => fs.Size.X);
|
|
}
|
|
//hull integrity at 0.0 if there are 10 or more wide-open holes
|
|
avgHullIntegrity = MathHelper.Clamp(1.0f - holeCount / 10.0f, 0.0f, 1.0f);
|
|
|
|
//a fire of any size bumps up the fire amount to 20%
|
|
//if the total width of the fires is 1000 or more, the fire amount is considered to be at 100%
|
|
fireAmount = MathHelper.Clamp(fireAmount / 1000.0f, fireAmount > 0.0f ? 0.2f : 0.0f, 1.0f);
|
|
|
|
//flooding less than 10% of the sub is ignored
|
|
//to prevent ballast tanks from affecting the intensity
|
|
if (floodingAmount < 0.1f) floodingAmount = 0.0f;
|
|
|
|
// calculate final intensity --------------------------------------------------------
|
|
|
|
targetIntensity =
|
|
((1.0f - avgCrewHealth) + (1.0f - avgHullIntegrity) + floodingAmount) / 3.0f;
|
|
targetIntensity += fireAmount * 0.5f;
|
|
targetIntensity += enemyDanger;
|
|
targetIntensity = MathHelper.Clamp(targetIntensity, 0.0f, 1.0f);
|
|
|
|
if (targetIntensity > currentIntensity)
|
|
{
|
|
//50 seconds for intensity to go from 0.0 to 1.0
|
|
currentIntensity = MathHelper.Min(currentIntensity + 0.02f * IntensityUpdateInterval, targetIntensity);
|
|
}
|
|
else
|
|
{
|
|
//400 seconds for intensity to go from 1.0 to 0.0
|
|
currentIntensity = MathHelper.Max(0.0025f * IntensityUpdateInterval, targetIntensity);
|
|
}
|
|
}
|
|
}
|
|
}
|