(61d00a474) v0.9.7.1
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ArtifactEvent : ScriptedEvent
|
||||
{
|
||||
private ItemPrefab itemPrefab;
|
||||
|
||||
private Item item;
|
||||
|
||||
private int state;
|
||||
|
||||
private Vector2 spawnPos;
|
||||
|
||||
private bool spawnPending;
|
||||
|
||||
public override Vector2 DebugDrawPos
|
||||
{
|
||||
get { return spawnPos; }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "ArtifactEvent (" + (itemPrefab == null ? "null" : itemPrefab.Name) + ")";
|
||||
}
|
||||
|
||||
public ArtifactEvent(ScriptedEventPrefab prefab)
|
||||
: base(prefab)
|
||||
{
|
||||
if (prefab.ConfigElement.Attribute("itemname") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in ArtifactEvent - use item identifier instead of the name of the item.");
|
||||
string itemName = prefab.ConfigElement.GetAttributeString("itemname", "");
|
||||
itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission: couldn't find an item prefab with the name " + itemName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string itemIdentifier = prefab.ConfigElement.GetAttributeString("itemidentifier", "");
|
||||
itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in ArtifactEvent - couldn't find an item prefab with the identifier " + itemIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Init(bool affectSubImmediately)
|
||||
{
|
||||
spawnPos = Level.Loaded.GetRandomItemPos(
|
||||
(Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) < 0.5f) ? Level.PositionType.MainPath : Level.PositionType.Cave | Level.PositionType.Ruin,
|
||||
500.0f, 10000.0f, 30.0f);
|
||||
|
||||
spawnPending = true;
|
||||
}
|
||||
|
||||
private void SpawnItem()
|
||||
{
|
||||
item = new Item(itemPrefab, spawnPos, null);
|
||||
item.body.FarseerBody.BodyType = FarseerPhysics.BodyType.Kinematic;
|
||||
|
||||
//try to find an artifact holder and place the artifact inside it
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != null || !it.HasTag("artifactholder")) continue;
|
||||
|
||||
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
|
||||
if (itemContainer == null) continue;
|
||||
if (itemContainer.Combine(item, user: null)) break; // Placement successful
|
||||
}
|
||||
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.NewMessage("Initialized ArtifactEvent (" + item.Name + ")", Color.White);
|
||||
}
|
||||
|
||||
#if SERVER
|
||||
if (GameMain.Server != null)
|
||||
{
|
||||
Entity.Spawner.CreateNetworkEvent(item, false);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (spawnPending)
|
||||
{
|
||||
SpawnItem();
|
||||
spawnPending = false;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
if (item.ParentInventory != null) { item.body.FarseerBody.BodyType = FarseerPhysics.BodyType.Dynamic; }
|
||||
if (item.CurrentHull == null) return;
|
||||
|
||||
state = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndPosition && !Submarine.MainSub.AtStartPosition) return;
|
||||
|
||||
Finished();
|
||||
state = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,517 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class EventManager
|
||||
{
|
||||
const float IntensityUpdateInterval = 5.0f;
|
||||
|
||||
private Level level;
|
||||
|
||||
private readonly List<Sprite> preloadedSprites = new List<Sprite>();
|
||||
|
||||
//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 readonly List<ScriptedEventSet> pendingEventSets = new List<ScriptedEventSet>();
|
||||
|
||||
private readonly Dictionary<ScriptedEventSet, List<ScriptedEvent>> selectedEvents = new Dictionary<ScriptedEventSet, List<ScriptedEvent>>();
|
||||
|
||||
private readonly List<ScriptedEvent> activeEvents = new List<ScriptedEvent>();
|
||||
|
||||
#if DEBUG && SERVER
|
||||
private DateTime nextIntensityLogTime;
|
||||
#endif
|
||||
|
||||
private EventManagerSettings settings;
|
||||
|
||||
private readonly bool isClient;
|
||||
|
||||
public float CurrentIntensity
|
||||
{
|
||||
get { return currentIntensity; }
|
||||
}
|
||||
|
||||
public List<ScriptedEvent> ActiveEvents
|
||||
{
|
||||
get { return activeEvents; }
|
||||
}
|
||||
|
||||
public EventManager()
|
||||
{
|
||||
isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
}
|
||||
|
||||
public bool Enabled = true;
|
||||
|
||||
public void StartRound(Level level)
|
||||
{
|
||||
if (isClient) { return; }
|
||||
|
||||
pendingEventSets.Clear();
|
||||
selectedEvents.Clear();
|
||||
|
||||
this.level = level;
|
||||
SelectSettings();
|
||||
|
||||
var initialEventSet = SelectRandomEvents(ScriptedEventSet.List);
|
||||
if (initialEventSet != null)
|
||||
{
|
||||
pendingEventSets.Add(initialEventSet);
|
||||
CreateEvents(initialEventSet);
|
||||
}
|
||||
|
||||
PreloadContent(GetFilesToPreload());
|
||||
|
||||
roundDuration = 0.0f;
|
||||
intensityUpdateTimer = 0.0f;
|
||||
CalculateCurrentIntensity(0.0f);
|
||||
currentIntensity = targetIntensity;
|
||||
eventCoolDown = 0.0f;
|
||||
}
|
||||
|
||||
private void SelectSettings()
|
||||
{
|
||||
if (EventManagerSettings.List.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException("Could not select EventManager settings (no settings loaded).");
|
||||
}
|
||||
if (level == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not select EventManager settings (level not set).");
|
||||
}
|
||||
|
||||
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)];
|
||||
}
|
||||
if (settings != null)
|
||||
{
|
||||
eventThreshold = settings.DefaultEventThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<ContentFile> GetFilesToPreload()
|
||||
{
|
||||
foreach (List<ScriptedEvent> eventList in selectedEvents.Values)
|
||||
{
|
||||
foreach (ScriptedEvent scriptedEvent in eventList)
|
||||
{
|
||||
foreach (ContentFile contentFile in scriptedEvent.GetFilesToPreload())
|
||||
{
|
||||
yield return contentFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void PreloadContent(IEnumerable<ContentFile> contentFiles)
|
||||
{
|
||||
foreach (ContentFile file in contentFiles)
|
||||
{
|
||||
switch (file.Type)
|
||||
{
|
||||
case ContentType.Character:
|
||||
#if CLIENT
|
||||
CharacterPrefab characterPrefab = CharacterPrefab.FindByFilePath(file.Path);
|
||||
if (characterPrefab?.XDocument == null)
|
||||
{
|
||||
throw new Exception($"Failed to load the character config file from {file.Path}!");
|
||||
}
|
||||
var doc = characterPrefab.XDocument;
|
||||
var rootElement = doc.Root;
|
||||
var mainElement = rootElement.IsOverride() ? rootElement.FirstElement() : rootElement;
|
||||
|
||||
foreach (var soundElement in mainElement.GetChildElements("sound"))
|
||||
{
|
||||
var sound = Submarine.LoadRoundSound(soundElement);
|
||||
}
|
||||
string speciesName = mainElement.GetAttributeString("speciesname", null);
|
||||
if (string.IsNullOrWhiteSpace(speciesName))
|
||||
{
|
||||
speciesName = mainElement.GetAttributeString("name", null);
|
||||
if (!string.IsNullOrWhiteSpace(speciesName))
|
||||
{
|
||||
DebugConsole.NewMessage($"Error in {file.Path}: 'name' is deprecated! Use 'speciesname' instead.", Color.Orange);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Species name null in {file.Path}");
|
||||
}
|
||||
}
|
||||
|
||||
bool humanoid = mainElement.GetAttributeBool("humanoid", false);
|
||||
RagdollParams ragdollParams;
|
||||
if (humanoid)
|
||||
{
|
||||
ragdollParams = RagdollParams.GetRagdollParams<HumanRagdollParams>(speciesName);
|
||||
}
|
||||
else
|
||||
{
|
||||
ragdollParams = RagdollParams.GetRagdollParams<FishRagdollParams>(speciesName);
|
||||
}
|
||||
if (ragdollParams != null)
|
||||
{
|
||||
HashSet<string> texturePaths = new HashSet<string>
|
||||
{
|
||||
ragdollParams.Texture
|
||||
};
|
||||
foreach (RagdollParams.LimbParams limb in ragdollParams.Limbs)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(limb.normalSpriteParams?.Texture)) { texturePaths.Add(limb.normalSpriteParams.Texture); }
|
||||
if (!string.IsNullOrEmpty(limb.deformSpriteParams?.Texture)) { texturePaths.Add(limb.deformSpriteParams.Texture); }
|
||||
if (!string.IsNullOrEmpty(limb.damagedSpriteParams?.Texture)) { texturePaths.Add(limb.damagedSpriteParams.Texture); }
|
||||
foreach (var decorativeSprite in limb.decorativeSpriteParams)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(decorativeSprite.Texture)) { texturePaths.Add(decorativeSprite.Texture); }
|
||||
}
|
||||
}
|
||||
foreach (string texturePath in texturePaths)
|
||||
{
|
||||
preloadedSprites.Add(new Sprite(texturePath, Vector2.Zero));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void EndRound()
|
||||
{
|
||||
pendingEventSets.Clear();
|
||||
selectedEvents.Clear();
|
||||
|
||||
preloadedSprites.ForEach(s => s.Remove());
|
||||
preloadedSprites.Clear();
|
||||
}
|
||||
|
||||
private void CreateEvents(ScriptedEventSet eventSet)
|
||||
{
|
||||
int applyCount = 1;
|
||||
if (eventSet.PerRuin)
|
||||
{
|
||||
applyCount = Level.Loaded.Ruins.Count();
|
||||
}
|
||||
for (int i = 0; i < applyCount; i++)
|
||||
{
|
||||
if (eventSet.ChooseRandom)
|
||||
{
|
||||
if (eventSet.EventPrefabs.Count > 0)
|
||||
{
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
|
||||
var eventPrefab = ToolBox.SelectWeightedRandom(eventSet.EventPrefabs, eventSet.EventPrefabs.Select(e => e.Commonness).ToList(), rand);
|
||||
if (eventPrefab != null)
|
||||
{
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
newEvent.Init(true);
|
||||
DebugConsole.Log("Initialized event " + newEvent.ToString());
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
selectedEvents.Add(eventSet, new List<ScriptedEvent>());
|
||||
}
|
||||
selectedEvents[eventSet].Add(newEvent);
|
||||
}
|
||||
}
|
||||
if (eventSet.ChildSets.Count > 0)
|
||||
{
|
||||
var newEventSet = SelectRandomEvents(eventSet.ChildSets);
|
||||
if (newEventSet != null) { CreateEvents(newEventSet); }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (ScriptedEventPrefab eventPrefab in eventSet.EventPrefabs)
|
||||
{
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
newEvent.Init(true);
|
||||
DebugConsole.Log("Initialized event " + newEvent.ToString());
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
selectedEvents.Add(eventSet, new List<ScriptedEvent>());
|
||||
}
|
||||
selectedEvents[eventSet].Add(newEvent);
|
||||
}
|
||||
|
||||
foreach (ScriptedEventSet childEventSet in eventSet.ChildSets)
|
||||
{
|
||||
CreateEvents(childEventSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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);
|
||||
|
||||
//don't create new events if within 50 meters of the start/end of the level
|
||||
if (!eventSet.AllowAtStart)
|
||||
{
|
||||
if (distanceTraveled <= 0.0f ||
|
||||
distFromStart * Physics.DisplayToRealWorldRatio < 50.0f ||
|
||||
distFromEnd * Physics.DisplayToRealWorldRatio < 50.0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) &&
|
||||
roundDuration < eventSet.MinMissionTime)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CurrentIntensity < eventSet.MinIntensity || CurrentIntensity > eventSet.MaxIntensity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
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 DEBUG && SERVER
|
||||
if (DateTime.Now > nextIntensityLogTime)
|
||||
{
|
||||
DebugConsole.NewMessage("EventManager intensity: " + (int)Math.Round(currentIntensity * 100) + " %");
|
||||
nextIntensityLogTime = DateTime.Now + new TimeSpan(0, minutes: 1, seconds: 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (isClient) { return; }
|
||||
|
||||
roundDuration += deltaTime;
|
||||
|
||||
if (settings == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Event settings not set before updating EventManager. Attempting to select...");
|
||||
SelectSettings();
|
||||
if (settings == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not select EventManager settings. Disabling EventManager for the round...");
|
||||
#if SERVER
|
||||
GameMain.Server?.SendChatMessage("Could not select EventManager settings. Disabling EventManager for the round...", Networking.ChatMessageType.Error);
|
||||
#endif
|
||||
Enabled = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
eventThreshold += settings.EventThresholdIncrease * deltaTime;
|
||||
if (eventCoolDown > 0.0f)
|
||||
{
|
||||
eventCoolDown -= deltaTime;
|
||||
}
|
||||
else if (currentIntensity < eventThreshold)
|
||||
{
|
||||
//activate pending event sets that can be activated
|
||||
for (int i = pendingEventSets.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var eventSet = pendingEventSets[i];
|
||||
if (!CanStartEventSet(eventSet)) { continue; }
|
||||
|
||||
pendingEventSets.RemoveAt(i);
|
||||
|
||||
if (!selectedEvents.ContainsKey(eventSet))
|
||||
{
|
||||
//no events selected from this event set
|
||||
continue;
|
||||
}
|
||||
|
||||
//start events in this set
|
||||
foreach (ScriptedEvent scriptedEvent in selectedEvents[eventSet])
|
||||
{
|
||||
activeEvents.Add(scriptedEvent);
|
||||
}
|
||||
//add child event sets to pending
|
||||
foreach (ScriptedEventSet childEventSet in eventSet.ChildSets)
|
||||
{
|
||||
if (selectedEvents.ContainsKey(childEventSet))
|
||||
{
|
||||
pendingEventSets.Add(childEventSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
eventThreshold = settings.DefaultEventThreshold;
|
||||
eventCoolDown = settings.EventCooldown;
|
||||
}
|
||||
|
||||
foreach (ScriptedEvent ev in activeEvents)
|
||||
{
|
||||
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 || character.TeamID == Character.TeamType.FriendlyNPC) { continue; }
|
||||
if (character.AIController is HumanAIController || character.IsRemotePlayer)
|
||||
{
|
||||
avgCrewHealth += character.Vitality / character.MaxVitality * (character.IsUnconscious ? 0.5f : 1.0f);
|
||||
characterCount++;
|
||||
}
|
||||
}
|
||||
if (characterCount > 0)
|
||||
{
|
||||
avgCrewHealth /= characterCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
avgCrewHealth = 0.5f;
|
||||
}
|
||||
|
||||
// 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;
|
||||
int hullCount = 0;
|
||||
foreach (Hull hull in Hull.hullList)
|
||||
{
|
||||
if (hull.Submarine == null || hull.Submarine.IsOutpost) { continue; }
|
||||
hullCount++;
|
||||
foreach (Gap gap in hull.ConnectedGaps)
|
||||
{
|
||||
if (!gap.IsRoomToRoom) holeCount += gap.Open;
|
||||
}
|
||||
floodingAmount += hull.WaterVolume / hull.Volume;
|
||||
fireAmount += hull.FireSources.Sum(fs => fs.Size.X);
|
||||
}
|
||||
if (hullCount > 0)
|
||||
{
|
||||
floodingAmount = floodingAmount / hullCount;
|
||||
}
|
||||
|
||||
//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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class EventManagerSettings
|
||||
{
|
||||
public static readonly List<EventManagerSettings> List = new List<EventManagerSettings>();
|
||||
|
||||
public readonly string Identifier;
|
||||
public readonly string Name;
|
||||
|
||||
//How much the event threshold increases per second. 0.0005f = 0.03f per minute
|
||||
public readonly float EventThresholdIncrease = 0.0005f;
|
||||
|
||||
//The threshold is reset to this value after an event has been triggered.
|
||||
public readonly float DefaultEventThreshold = 0.2f;
|
||||
|
||||
public readonly float EventCooldown = 360.0f;
|
||||
|
||||
public readonly float MinLevelDifficulty = 0.0f;
|
||||
public readonly float MaxLevelDifficulty = 100.0f;
|
||||
|
||||
static EventManagerSettings()
|
||||
{
|
||||
foreach (ContentFile file in GameMain.Instance.GetFilesOfType(ContentType.EventManagerSettings))
|
||||
{
|
||||
Load(file);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Load(ContentFile file)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { return; }
|
||||
var mainElement = doc.Root;
|
||||
bool allowOverriding = false;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
mainElement = doc.Root.FirstElement();
|
||||
allowOverriding = true;
|
||||
}
|
||||
foreach (XElement subElement in mainElement.Elements())
|
||||
{
|
||||
var element = subElement.IsOverride() ? subElement.FirstElement() : subElement;
|
||||
string identifier = element.Name.ToString();
|
||||
var duplicate = List.FirstOrDefault(e => e.Identifier.ToString().Equals(identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (duplicate != null)
|
||||
{
|
||||
if (allowOverriding || subElement.IsOverride())
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding the existing preset '{identifier}' in the event manager settings using the file '{file.Path}'", Color.Yellow);
|
||||
List.Remove(duplicate);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in '{file.Path}': Another element with the name '{identifier}' found! Each element must have a unique name. Use <override></override> tags if you want to override an existing preset.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
List.Add(new EventManagerSettings(element));
|
||||
}
|
||||
List.Sort((x, y) => { return Math.Sign((x.MinLevelDifficulty + x.MaxLevelDifficulty) / 2.0f - (y.MinLevelDifficulty + y.MaxLevelDifficulty) / 2.0f); });
|
||||
}
|
||||
|
||||
public EventManagerSettings(XElement element)
|
||||
{
|
||||
Identifier = element.Name.ToString();
|
||||
Name = TextManager.Get("difficulty." + Identifier, returnNull: true) ?? Identifier;
|
||||
EventThresholdIncrease = element.GetAttributeFloat("EventThresholdIncrease", 0.0005f);
|
||||
DefaultEventThreshold = element.GetAttributeFloat("DefaultEventThreshold", 0.2f);
|
||||
EventCooldown = element.GetAttributeFloat("EventCooldown", 360.0f);
|
||||
|
||||
MinLevelDifficulty = element.GetAttributeFloat("MinLevelDifficulty", 0.0f);
|
||||
MaxLevelDifficulty = element.GetAttributeFloat("MaxLevelDifficulty", 100.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MalfunctionEvent : ScriptedEvent
|
||||
{
|
||||
private string[] targetItemIdentifiers;
|
||||
|
||||
private List<Item> targetItems;
|
||||
|
||||
private int minItemAmount, maxItemAmount;
|
||||
|
||||
private float decreaseConditionAmount;
|
||||
|
||||
private float duration;
|
||||
|
||||
private float timer;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "MalfunctionEvent (" + string.Join(", ", targetItemIdentifiers) + ")";
|
||||
}
|
||||
|
||||
public MalfunctionEvent(ScriptedEventPrefab prefab)
|
||||
: base(prefab)
|
||||
{
|
||||
targetItems = new List<Item>();
|
||||
|
||||
minItemAmount = prefab.ConfigElement.GetAttributeInt("minitemamount", 1);
|
||||
maxItemAmount = prefab.ConfigElement.GetAttributeInt("maxitemamount", minItemAmount);
|
||||
|
||||
decreaseConditionAmount = prefab.ConfigElement.GetAttributeFloat("decreaseconditionamount", 0.0f);
|
||||
duration = prefab.ConfigElement.GetAttributeFloat("duration", 0.0f);
|
||||
|
||||
targetItemIdentifiers = prefab.ConfigElement.GetAttributeStringArray("itemidentifiers", new string[0]);
|
||||
}
|
||||
|
||||
public override bool CanAffectSubImmediately(Level level)
|
||||
{
|
||||
return Item.ItemList.Count(i => i.Condition > 0.0f && targetItemIdentifiers.Contains(i.Prefab.Identifier)) >= maxItemAmount;
|
||||
}
|
||||
|
||||
public override void Init(bool affectSubImmediately)
|
||||
{
|
||||
var matchingItems = Item.ItemList.FindAll(i => i.Condition > 0.0f && targetItemIdentifiers.Contains(i.Prefab.Identifier));
|
||||
int itemAmount = Rand.Range(minItemAmount, maxItemAmount, Rand.RandSync.Server);
|
||||
for (int i = 0; i < itemAmount; i++)
|
||||
{
|
||||
if (matchingItems.Count == 0) break;
|
||||
targetItems.Add(matchingItems[Rand.Int(matchingItems.Count, Rand.RandSync.Server)]);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (isFinished) return;
|
||||
if (targetItems.Count == 0 || timer >= duration)
|
||||
{
|
||||
Finished();
|
||||
return;
|
||||
}
|
||||
|
||||
targetItems.RemoveAll(i => i.Removed || i.Condition <= 0.0f);
|
||||
foreach (Item item in targetItems)
|
||||
{
|
||||
if (duration <= 0.0f)
|
||||
{
|
||||
item.Condition = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Condition -= decreaseConditionAmount / duration * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
timer += deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class CargoMission : Mission
|
||||
{
|
||||
private readonly XElement itemConfig;
|
||||
|
||||
private readonly List<Item> items = new List<Item>();
|
||||
|
||||
private int requiredDeliveryAmount;
|
||||
|
||||
public CargoMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
{
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
requiredDeliveryAmount = prefab.ConfigElement.GetAttributeInt("requireddeliveryamount", 0);
|
||||
}
|
||||
|
||||
private void InitItems()
|
||||
{
|
||||
items.Clear();
|
||||
|
||||
if (itemConfig == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Failed to initialize items for cargo mission (itemConfig == null)");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (XElement subElement in itemConfig.Elements())
|
||||
{
|
||||
LoadItemAsChild(subElement, null);
|
||||
}
|
||||
|
||||
if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; }
|
||||
}
|
||||
|
||||
private void LoadItemAsChild(XElement element, Item parent)
|
||||
{
|
||||
ItemPrefab itemPrefab;
|
||||
if (element.Attribute("name") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in cargo mission \"" + Name + "\" - use item identifiers instead of names to configure the items.");
|
||||
string itemName = element.GetAttributeString("name", "");
|
||||
itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + itemName + "\" not found");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string itemIdentifier = element.GetAttributeString("identifier", "");
|
||||
itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + itemIdentifier + "\" not found");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for cargo mission: item prefab \"" + element.Name.ToString() + "\" not found");
|
||||
return;
|
||||
}
|
||||
|
||||
WayPoint cargoSpawnPos = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub, true);
|
||||
if (cargoSpawnPos == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn items for cargo mission, cargo spawnpoint not found");
|
||||
return;
|
||||
}
|
||||
|
||||
var cargoRoom = cargoSpawnPos.CurrentHull;
|
||||
|
||||
if (cargoRoom == null)
|
||||
{
|
||||
DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!");
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 position = new Vector2(
|
||||
cargoSpawnPos.Position.X + Rand.Range(-20.0f, 20.0f, Rand.RandSync.Server),
|
||||
cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2);
|
||||
|
||||
var item = new Item(itemPrefab, position, cargoRoom.Submarine);
|
||||
item.FindHull();
|
||||
items.Add(item);
|
||||
|
||||
if (parent != null) parent.Combine(item, user: null);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
int amount = subElement.GetAttributeInt("amount", 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
LoadItemAsChild(subElement, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
if (!IsClient)
|
||||
{
|
||||
InitItems();
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtEndPosition)
|
||||
{
|
||||
int deliveredItemCount = items.Count(i => i.CurrentHull != null && !i.Removed && i.Condition > 0.0f);
|
||||
|
||||
if (deliveredItemCount >= requiredDeliveryAmount)
|
||||
{
|
||||
GiveReward();
|
||||
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (!item.Removed) { item.Remove(); }
|
||||
}
|
||||
items.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class CombatMission : Mission
|
||||
{
|
||||
private Submarine[] subs;
|
||||
private List<Character>[] crews;
|
||||
|
||||
private readonly string[] descriptions;
|
||||
private static string[] teamNames = { "Team A", "Team B" };
|
||||
|
||||
public override bool AllowRespawn
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
private Character.TeamType Winner
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GameMain.GameSession?.WinningTeam == null) { return Character.TeamType.None; }
|
||||
return GameMain.GameSession.WinningTeam.Value;
|
||||
}
|
||||
}
|
||||
|
||||
public override string SuccessMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Winner == Character.TeamType.None || string.IsNullOrEmpty(base.SuccessMessage)) { return ""; }
|
||||
|
||||
//disable success message for now if it hasn't been translated
|
||||
if (!TextManager.ContainsTag("MissionSuccess." + Prefab.TextIdentifier)) { return ""; }
|
||||
|
||||
var loser = Winner == Character.TeamType.Team1 ?
|
||||
Character.TeamType.Team2 :
|
||||
Character.TeamType.Team1;
|
||||
|
||||
return base.SuccessMessage
|
||||
.Replace("[loser]", GetTeamName(loser))
|
||||
.Replace("[winner]", GetTeamName(Winner));
|
||||
}
|
||||
}
|
||||
|
||||
public override int TeamCount
|
||||
{
|
||||
get { return 2; }
|
||||
}
|
||||
|
||||
public CombatMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
{
|
||||
descriptions = new string[]
|
||||
{
|
||||
TextManager.Get("MissionDescriptionNeutral." + prefab.TextIdentifier, true) ?? prefab.ConfigElement.GetAttributeString("descriptionneutral", ""),
|
||||
TextManager.Get("MissionDescription1." + prefab.TextIdentifier, true) ?? prefab.ConfigElement.GetAttributeString("description1", ""),
|
||||
TextManager.Get("MissionDescription2." + prefab.TextIdentifier, true) ?? prefab.ConfigElement.GetAttributeString("description2", "")
|
||||
};
|
||||
|
||||
for (int i = 0; i < descriptions.Length; i++)
|
||||
{
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
descriptions[i] = descriptions[i].Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
}
|
||||
}
|
||||
|
||||
teamNames = new string[]
|
||||
{
|
||||
TextManager.Get("MissionTeam1." + prefab.TextIdentifier, true) ?? prefab.ConfigElement.GetAttributeString("teamname1", "Team A"),
|
||||
TextManager.Get("MissionTeam2." + prefab.TextIdentifier, true) ?? prefab.ConfigElement.GetAttributeString("teamname2", "Team B")
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetTeamName(Character.TeamType teamID)
|
||||
{
|
||||
if (teamID == Character.TeamType.Team1)
|
||||
{
|
||||
return teamNames.Length > 0 ? teamNames[0] : "Team 1";
|
||||
}
|
||||
else if (teamID == Character.TeamType.Team2)
|
||||
{
|
||||
return teamNames.Length > 1 ? teamNames[1] : "Team 2";
|
||||
}
|
||||
|
||||
return "Invalid Team";
|
||||
}
|
||||
|
||||
public bool IsInWinningTeam(Character character)
|
||||
{
|
||||
return character != null &&
|
||||
Winner != Character.TeamType.None &&
|
||||
Winner == character.TeamID;
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
if (GameMain.NetworkMember == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Combat missions cannot be played in the single player mode.");
|
||||
return;
|
||||
}
|
||||
|
||||
subs = new Submarine[] { Submarine.MainSubs[0], Submarine.MainSubs[1] };
|
||||
subs[0].TeamID = Character.TeamType.Team1; subs[1].TeamID = Character.TeamType.Team2;
|
||||
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)
|
||||
{
|
||||
//hide all subs from sonar to make sneak attacks possible
|
||||
submarine.ShowSonarMarker = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (GameMain.NetworkMember == null) return;
|
||||
|
||||
if (Winner != Character.TeamType.None)
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
abstract partial class Mission
|
||||
{
|
||||
public readonly MissionPrefab Prefab;
|
||||
protected bool completed;
|
||||
protected int state;
|
||||
public int State
|
||||
{
|
||||
get { return state; }
|
||||
protected set
|
||||
{
|
||||
if (state != value)
|
||||
{
|
||||
state = value;
|
||||
#if SERVER
|
||||
GameMain.Server?.UpdateMissionState(state);
|
||||
#endif
|
||||
ShowMessage(State);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected bool IsClient => GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
|
||||
public readonly List<string> Headers;
|
||||
public readonly List<string> Messages;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return Prefab.Name; }
|
||||
}
|
||||
|
||||
private string successMessage;
|
||||
public virtual string SuccessMessage
|
||||
{
|
||||
get { return successMessage; }
|
||||
private set { successMessage = value; }
|
||||
}
|
||||
|
||||
private string failureMessage;
|
||||
public virtual string FailureMessage
|
||||
{
|
||||
get { return failureMessage; }
|
||||
private set { failureMessage = value; }
|
||||
}
|
||||
|
||||
protected string description;
|
||||
public virtual string Description
|
||||
{
|
||||
get { return description; }
|
||||
private set { description = value; }
|
||||
}
|
||||
|
||||
public int Reward
|
||||
{
|
||||
get { return Prefab.Reward; }
|
||||
}
|
||||
|
||||
public bool Completed
|
||||
{
|
||||
get { return completed; }
|
||||
set { completed = value; }
|
||||
}
|
||||
|
||||
public virtual bool AllowRespawn
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
public virtual int TeamCount
|
||||
{
|
||||
get { return 1; }
|
||||
}
|
||||
|
||||
public virtual IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get { return Enumerable.Empty<Vector2>(); }
|
||||
}
|
||||
|
||||
public string SonarLabel
|
||||
{
|
||||
get { return Prefab.SonarLabel; }
|
||||
}
|
||||
public string SonarIconIdentifier
|
||||
{
|
||||
get { return Prefab.SonarIconIdentifier; }
|
||||
}
|
||||
|
||||
public readonly Location[] Locations;
|
||||
|
||||
public Mission(MissionPrefab prefab, Location[] locations)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(locations.Length == 2);
|
||||
|
||||
Prefab = prefab;
|
||||
|
||||
description = prefab.Description;
|
||||
successMessage = prefab.SuccessMessage;
|
||||
FailureMessage = prefab.FailureMessage;
|
||||
Headers = new List<string>(prefab.Headers);
|
||||
Messages = new List<string>(prefab.Messages);
|
||||
|
||||
Locations = locations;
|
||||
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
if (description != null) description = description.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
if (successMessage != null) successMessage = successMessage.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
if (failureMessage != null) failureMessage = failureMessage.Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
for (int m = 0; m < Messages.Count; m++)
|
||||
{
|
||||
Messages[m] = Messages[m].Replace("[location" + (n + 1) + "]", locations[n].Name);
|
||||
}
|
||||
}
|
||||
if (description != null) description = description.Replace("[reward]", Reward.ToString("N0"));
|
||||
if (successMessage != null) successMessage = successMessage.Replace("[reward]", Reward.ToString("N0"));
|
||||
if (failureMessage != null) failureMessage = failureMessage.Replace("[reward]", Reward.ToString("N0"));
|
||||
for (int m = 0; m < Messages.Count; m++)
|
||||
{
|
||||
Messages[m] = Messages[m].Replace("[reward]", Reward.ToString("N0"));
|
||||
}
|
||||
}
|
||||
public static Mission LoadRandom(Location[] locations, string seed, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer = false)
|
||||
{
|
||||
return LoadRandom(locations, new MTRandom(ToolBox.StringToInt(seed)), requireCorrectLocationType, missionType, isSinglePlayer);
|
||||
}
|
||||
|
||||
public static Mission LoadRandom(Location[] locations, MTRandom rand, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer = false)
|
||||
{
|
||||
List<MissionPrefab> allowedMissions = new List<MissionPrefab>();
|
||||
if (missionType == MissionType.None)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
allowedMissions.AddRange(MissionPrefab.List.Where(m => ((int)(missionType & m.Type)) != 0));
|
||||
}
|
||||
|
||||
allowedMissions.RemoveAll(m => isSinglePlayer ? m.MultiplayerOnly : m.SingleplayerOnly);
|
||||
if (requireCorrectLocationType)
|
||||
{
|
||||
allowedMissions.RemoveAll(m => !m.IsAllowed(locations[0], locations[1]));
|
||||
}
|
||||
|
||||
if (allowedMissions.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
int probabilitySum = allowedMissions.Sum(m => m.Commonness);
|
||||
int randomNumber = rand.NextInt32() % probabilitySum;
|
||||
foreach (MissionPrefab missionPrefab in allowedMissions)
|
||||
{
|
||||
if (randomNumber <= missionPrefab.Commonness)
|
||||
{
|
||||
return missionPrefab.Instantiate(locations);
|
||||
}
|
||||
randomNumber -= missionPrefab.Commonness;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public virtual void Start(Level level) { }
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
|
||||
public virtual void AssignTeamIDs(List<Networking.Client> clients)
|
||||
{
|
||||
clients.ForEach(c => c.TeamID = Character.TeamType.Team1);
|
||||
}
|
||||
|
||||
protected void ShowMessage(int missionState)
|
||||
{
|
||||
ShowMessageProjSpecific(missionState);
|
||||
}
|
||||
|
||||
partial void ShowMessageProjSpecific(int missionState);
|
||||
|
||||
/// <summary>
|
||||
/// End the mission and give a reward if it was completed successfully
|
||||
/// </summary>
|
||||
public virtual void End()
|
||||
{
|
||||
completed = true;
|
||||
|
||||
GiveReward();
|
||||
}
|
||||
|
||||
public void GiveReward()
|
||||
{
|
||||
if (!(GameMain.GameSession.GameMode is CampaignMode mode)) { return; }
|
||||
mode.Money += Reward;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
[Flags]
|
||||
public enum MissionType
|
||||
{
|
||||
None = 0x0,
|
||||
Salvage = 0x1,
|
||||
Monster = 0x2,
|
||||
Cargo = 0x4,
|
||||
Combat = 0x8,
|
||||
All = 0xf
|
||||
}
|
||||
|
||||
partial class MissionPrefab
|
||||
{
|
||||
public static readonly List<MissionPrefab> List = new List<MissionPrefab>();
|
||||
|
||||
private static readonly Dictionary<MissionType, Type> missionClasses = new Dictionary<MissionType, Type>()
|
||||
{
|
||||
{ MissionType.Salvage, typeof(SalvageMission) },
|
||||
{ MissionType.Monster, typeof(MonsterMission) },
|
||||
{ MissionType.Cargo, typeof(CargoMission) },
|
||||
{ MissionType.Combat, typeof(CombatMission) },
|
||||
};
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
|
||||
public readonly MissionType Type;
|
||||
|
||||
public readonly bool MultiplayerOnly, SingleplayerOnly;
|
||||
|
||||
public readonly string Identifier;
|
||||
|
||||
public readonly string TextIdentifier;
|
||||
|
||||
public readonly string Name;
|
||||
public readonly string Description;
|
||||
public readonly string SuccessMessage;
|
||||
public readonly string FailureMessage;
|
||||
public readonly string SonarLabel;
|
||||
public readonly string SonarIconIdentifier;
|
||||
|
||||
public readonly string AchievementIdentifier;
|
||||
|
||||
public readonly int Commonness;
|
||||
|
||||
public readonly int Reward;
|
||||
|
||||
public readonly List<string> Headers;
|
||||
public readonly List<string> Messages;
|
||||
|
||||
//the mission can only be received when travelling from Pair.First to Pair.Second
|
||||
public readonly List<Pair<string, string>> AllowedLocationTypes;
|
||||
|
||||
public readonly XElement ConfigElement;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
List.Clear();
|
||||
var files = GameMain.Instance.GetFilesOfType(ContentType.Missions);
|
||||
foreach (ContentFile file in files)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
|
||||
if (doc == null) { continue; }
|
||||
bool allowOverride = false;
|
||||
var mainElement = doc.Root;
|
||||
if (mainElement.IsOverride())
|
||||
{
|
||||
allowOverride = true;
|
||||
mainElement = mainElement.FirstElement();
|
||||
}
|
||||
|
||||
foreach (XElement sourceElement in mainElement.Elements())
|
||||
{
|
||||
var element = sourceElement.IsOverride() ? sourceElement.FirstElement() : sourceElement;
|
||||
var identifier = element.GetAttributeString("identifier", string.Empty);
|
||||
var duplicate = List.Find(m => m.Identifier == identifier);
|
||||
if (duplicate != null)
|
||||
{
|
||||
if (allowOverride || sourceElement.IsOverride())
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding a mission with the identifier '{identifier}' using the file '{file.Path}'", Color.Yellow);
|
||||
List.Remove(duplicate);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Duplicate mission found with the identifier '{identifier}' in file '{file.Path}'! Add <override></override> tags as the parent of the mission definition to allow overriding.");
|
||||
// TODO: Don't allow adding duplicates when the issue with multiple missions is solved.
|
||||
//continue;
|
||||
}
|
||||
}
|
||||
List.Add(new MissionPrefab(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MissionPrefab(XElement element)
|
||||
{
|
||||
ConfigElement = element;
|
||||
|
||||
Identifier = element.GetAttributeString("identifier", "");
|
||||
TextIdentifier = element.GetAttributeString("textidentifier", null) ?? Identifier;
|
||||
|
||||
Name = TextManager.Get("MissionName." + TextIdentifier, true) ?? element.GetAttributeString("name", "");
|
||||
Description = TextManager.Get("MissionDescription." + TextIdentifier, true) ?? element.GetAttributeString("description", "");
|
||||
Reward = element.GetAttributeInt("reward", 1);
|
||||
|
||||
Commonness = element.GetAttributeInt("commonness", 1);
|
||||
|
||||
SuccessMessage = TextManager.Get("MissionSuccess." + TextIdentifier, true) ?? element.GetAttributeString("successmessage", "Mission completed successfully");
|
||||
FailureMessage = TextManager.Get("MissionFailure." + TextIdentifier, true) ?? "";
|
||||
if (string.IsNullOrEmpty(FailureMessage) && TextManager.ContainsTag("missionfailed"))
|
||||
{
|
||||
FailureMessage = TextManager.Get("missionfailed", returnNull: true) ?? "";
|
||||
}
|
||||
if (string.IsNullOrEmpty(FailureMessage) && GameMain.Config.Language == "English")
|
||||
{
|
||||
FailureMessage = element.GetAttributeString("failuremessage", "");
|
||||
}
|
||||
|
||||
SonarLabel = TextManager.Get("MissionSonarLabel." + TextIdentifier, true) ?? element.GetAttributeString("sonarlabel", "");
|
||||
SonarIconIdentifier = element.GetAttributeString("sonaricon", "");
|
||||
|
||||
MultiplayerOnly = element.GetAttributeBool("multiplayeronly", false);
|
||||
SingleplayerOnly = element.GetAttributeBool("singleplayeronly", false);
|
||||
|
||||
AchievementIdentifier = element.GetAttributeString("achievementidentifier", "");
|
||||
|
||||
Headers = new List<string>();
|
||||
Messages = new List<string>();
|
||||
AllowedLocationTypes = new List<Pair<string, string>>();
|
||||
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", ""));
|
||||
break;
|
||||
case "locationtype":
|
||||
AllowedLocationTypes.Add(new Pair<string, string>(
|
||||
subElement.GetAttributeString("from", ""),
|
||||
subElement.GetAttributeString("to", "")));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string missionTypeName = element.GetAttributeString("type", "");
|
||||
if (!Enum.TryParse(missionTypeName, out Type))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - \"" + missionTypeName + "\" is not a valid mission type.");
|
||||
return;
|
||||
}
|
||||
if (Type == MissionType.None)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - mission type cannot be none.");
|
||||
return;
|
||||
}
|
||||
|
||||
constructor = missionClasses[Type].GetConstructor(new[] { typeof(MissionPrefab), typeof(Location[]) });
|
||||
|
||||
InitProjSpecific(element);
|
||||
}
|
||||
|
||||
partial void InitProjSpecific(XElement element);
|
||||
|
||||
public bool IsAllowed(Location from, Location to)
|
||||
{
|
||||
foreach (Pair<string, string> allowedLocationType in AllowedLocationTypes)
|
||||
{
|
||||
if (allowedLocationType.First.Equals("any", StringComparison.OrdinalIgnoreCase) ||
|
||||
allowedLocationType.First.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (allowedLocationType.Second.Equals("any", StringComparison.OrdinalIgnoreCase) ||
|
||||
allowedLocationType.Second.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public Mission Instantiate(Location[] locations)
|
||||
{
|
||||
return constructor?.Invoke(new object[] { this, locations }) as Mission;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class MonsterMission : Mission
|
||||
{
|
||||
private readonly string monsterFile;
|
||||
private readonly int monsterCount;
|
||||
|
||||
//string = filename, point = min,max
|
||||
private readonly HashSet<Tuple<string, Point>> monsterFiles = new HashSet<Tuple<string, Point>>();
|
||||
private readonly List<Character> monsters = new List<Character>();
|
||||
private readonly List<Vector2> sonarPositions = new List<Vector2>();
|
||||
|
||||
private readonly List<Vector2> tempSonarPositions = new List<Vector2>();
|
||||
|
||||
private readonly float maxSonarMarkerDistance = 10000.0f;
|
||||
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return sonarPositions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MonsterMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
{
|
||||
monsterFile = prefab.ConfigElement.GetAttributeString("monsterfile", null);
|
||||
|
||||
if (!string.IsNullOrEmpty(monsterFile))
|
||||
{
|
||||
var characterPrefab = CharacterPrefab.FindByFilePath(monsterFile);
|
||||
if (characterPrefab != null)
|
||||
{
|
||||
monsterFile = characterPrefab.Identifier;
|
||||
}
|
||||
}
|
||||
|
||||
maxSonarMarkerDistance = prefab.ConfigElement.GetAttributeFloat("maxsonarmarkerdistance", 10000.0f);
|
||||
|
||||
monsterCount = Math.Min(prefab.ConfigElement.GetAttributeInt("monstercount", 1), 255);
|
||||
string monsterFileName = monsterFile;
|
||||
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
|
||||
{
|
||||
string monster = monsterElement.GetAttributeString("character", string.Empty);
|
||||
if (monsterFileName == null)
|
||||
{
|
||||
monsterFileName = monster;
|
||||
}
|
||||
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);
|
||||
monsterFiles.Add(new Tuple<string, Point>(monster, new Point(min, max)));
|
||||
}
|
||||
description = description.Replace("[monster]",
|
||||
TextManager.Get("character." + System.IO.Path.GetFileNameWithoutExtension(monsterFileName)));
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
if (monsters.Count > 0)
|
||||
{
|
||||
throw new Exception($"monsters.Count > 0 ({monsters.Count})");
|
||||
}
|
||||
|
||||
if (tempSonarPositions.Count > 0)
|
||||
{
|
||||
throw new Exception($"tempSonarPositions.Count > 0 ({tempSonarPositions.Count})");
|
||||
}
|
||||
|
||||
if (!IsClient)
|
||||
{
|
||||
Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
|
||||
if (!string.IsNullOrEmpty(monsterFile))
|
||||
{
|
||||
for (int i = 0; i < monsterCount; i++)
|
||||
{
|
||||
monsters.Add(Character.Create(monsterFile, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false));
|
||||
}
|
||||
}
|
||||
foreach (var monster in monsterFiles)
|
||||
{
|
||||
int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1);
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
monsters.Add(Character.Create(monster.Item1, spawnPos, ToolBox.RandomSeed(8), createNetworkEvent: false));
|
||||
}
|
||||
}
|
||||
|
||||
InitializeMonsters(monsters);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeMonsters(IEnumerable<Character> monsters)
|
||||
{
|
||||
monsters.ForEach(m => m.Enabled = false);
|
||||
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
|
||||
foreach (Character monster in monsters)
|
||||
{
|
||||
tempSonarPositions.Add(monster.WorldPosition + Rand.Vector(maxSonarMarkerDistance));
|
||||
}
|
||||
if (monsters.Count() != tempSonarPositions.Count)
|
||||
{
|
||||
throw new Exception($"monsters.Count != tempSonarPositions.Count ({monsters.Count()} != {tempSonarPositions.Count})");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
case 0:
|
||||
//keep sonar markers within maxSonarMarkerDistance from the monster(s)
|
||||
for (int i = 0; i < tempSonarPositions.Count; i++)
|
||||
{
|
||||
if (monsters.Count != tempSonarPositions.Count)
|
||||
{
|
||||
throw new Exception($"monsters.Count != tempSonarPositions.Count ({monsters.Count} != {tempSonarPositions.Count})");
|
||||
}
|
||||
|
||||
if (i < 0 || i >= monsters.Count)
|
||||
{
|
||||
throw new Exception($"Index {i} outside of bounds 0-{monsters.Count} ({tempSonarPositions.Count})");
|
||||
}
|
||||
|
||||
if (monsters[i].Removed || monsters[i].IsDead) { continue; }
|
||||
Vector2 diff = tempSonarPositions[i] - monsters[i].Position;
|
||||
|
||||
float maxDist = maxSonarMarkerDistance;
|
||||
Submarine refSub = Character.Controlled?.Submarine ?? Submarine.MainSub;
|
||||
if (refSub != null)
|
||||
{
|
||||
Vector2 refPos = refSub == null ? Vector2.Zero : refSub.WorldPosition;
|
||||
float subDist = Vector2.Distance(refPos, tempSonarPositions[i]) / maxDist;
|
||||
|
||||
maxDist = Math.Min(subDist * subDist * maxDist, maxDist);
|
||||
maxDist = Math.Min(Vector2.Distance(refPos, monsters[i].Position), maxDist);
|
||||
}
|
||||
|
||||
if (diff.LengthSquared() > maxDist * maxDist)
|
||||
{
|
||||
tempSonarPositions[i] = monsters[i].Position + Vector2.Normalize(diff) * maxDist;
|
||||
}
|
||||
}
|
||||
|
||||
sonarPositions.Clear();
|
||||
for (int i = 0; i < monsters.Count; i++)
|
||||
{
|
||||
if (monsters[i].Removed || monsters[i].IsDead) { continue; }
|
||||
//don't add another label if there's another monster roughly at the same spot
|
||||
if (sonarPositions.All(p => Vector2.DistanceSquared(p, tempSonarPositions[i]) > 1000.0f * 1000.0f))
|
||||
{
|
||||
sonarPositions.Add(tempSonarPositions[i]);
|
||||
}
|
||||
}
|
||||
if (!IsClient && monsters.All(m => IsEliminated(m)))
|
||||
{
|
||||
State = 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
tempSonarPositions.Clear();
|
||||
monsters.Clear();
|
||||
if (State < 1) { return; }
|
||||
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
|
||||
public bool IsEliminated(Character enemy) => enemy.Removed || enemy.IsDead || enemy.AIController is EnemyAIController ai && ai.State == AIState.Flee;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class SalvageMission : Mission
|
||||
{
|
||||
private readonly ItemPrefab itemPrefab;
|
||||
|
||||
private Item item;
|
||||
|
||||
private readonly Level.PositionType spawnPositionType;
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
Enumerable.Empty<Vector2>();
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return ConvertUnits.ToDisplayUnits(item.SimPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SalvageMission(MissionPrefab prefab, Location[] locations)
|
||||
: base(prefab, locations)
|
||||
{
|
||||
if (prefab.ConfigElement.Attribute("itemname") != null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission - use item identifier instead of the name of the item.");
|
||||
string itemName = prefab.ConfigElement.GetAttributeString("itemname", "");
|
||||
itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission: couldn't find an item prefab with the name " + itemName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string itemIdentifier = prefab.ConfigElement.GetAttributeString("itemidentifier", "");
|
||||
itemPrefab = MapEntityPrefab.Find(null, itemIdentifier) as ItemPrefab;
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in SalvageMission - couldn't find an item prefab with the identifier " + itemIdentifier);
|
||||
}
|
||||
}
|
||||
|
||||
string spawnPositionTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
|
||||
if (string.IsNullOrWhiteSpace(spawnPositionTypeStr) ||
|
||||
!Enum.TryParse(spawnPositionTypeStr, true, out spawnPositionType))
|
||||
{
|
||||
spawnPositionType = Level.PositionType.Cave | Level.PositionType.Ruin;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Start(Level level)
|
||||
{
|
||||
if (!IsClient)
|
||||
{
|
||||
//ruin items are allowed to spawn close to the sub
|
||||
float minDistance = spawnPositionType == Level.PositionType.Ruin ? 0.0f : Level.Loaded.Size.X * 0.3f;
|
||||
Vector2 position = Level.Loaded.GetRandomItemPos(spawnPositionType, 100.0f, minDistance, 30.0f);
|
||||
|
||||
item = new Item(itemPrefab, position, null);
|
||||
item.body.FarseerBody.BodyType = BodyType.Kinematic;
|
||||
|
||||
if (item.HasTag("alien"))
|
||||
{
|
||||
//try to find an artifact holder and place the artifact inside it
|
||||
foreach (Item it in Item.ItemList)
|
||||
{
|
||||
if (it.Submarine != null || !it.HasTag("artifactholder")) continue;
|
||||
|
||||
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
|
||||
if (itemContainer == null) { continue; }
|
||||
if (itemContainer.Combine(item, user: null)) { break; } // Placement successful
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (IsClient)
|
||||
{
|
||||
if (item.ParentInventory != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
return;
|
||||
}
|
||||
switch (State)
|
||||
{
|
||||
case 0:
|
||||
if (item.ParentInventory != null) { item.body.FarseerBody.BodyType = BodyType.Dynamic; }
|
||||
if (item.CurrentHull?.Submarine == null) { return; }
|
||||
State = 1;
|
||||
break;
|
||||
case 1:
|
||||
if (!Submarine.MainSub.AtEndPosition && !Submarine.MainSub.AtStartPosition) { return; }
|
||||
State = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void End()
|
||||
{
|
||||
if (item.CurrentHull?.Submarine == null || !item.CurrentHull.Submarine.AtEndPosition || item.Removed) { return; }
|
||||
|
||||
item?.Remove();
|
||||
item = null;
|
||||
GiveReward();
|
||||
completed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class MonsterEvent : ScriptedEvent
|
||||
{
|
||||
private readonly string speciesName;
|
||||
private readonly int minAmount, maxAmount;
|
||||
private List<Character> monsters;
|
||||
|
||||
private readonly bool spawnDeep;
|
||||
|
||||
private Vector2? spawnPos;
|
||||
|
||||
private readonly bool disallowed;
|
||||
|
||||
private readonly Level.PositionType spawnPosType;
|
||||
|
||||
private bool spawnPending;
|
||||
|
||||
public override Vector2 DebugDrawPos
|
||||
{
|
||||
get { return spawnPos ?? Vector2.Zero; }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (maxAmount <= 1)
|
||||
{
|
||||
return "MonsterEvent (" + speciesName + ")";
|
||||
}
|
||||
else if (minAmount < maxAmount)
|
||||
{
|
||||
return "MonsterEvent (" + speciesName + " x" + minAmount + "-" + maxAmount + ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "MonsterEvent (" + speciesName + " x" + maxAmount + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public MonsterEvent(ScriptedEventPrefab prefab)
|
||||
: base (prefab)
|
||||
{
|
||||
speciesName = prefab.ConfigElement.GetAttributeString("characterfile", "");
|
||||
CharacterPrefab characterPrefab = CharacterPrefab.FindByFilePath(speciesName);
|
||||
if (characterPrefab != null)
|
||||
{
|
||||
speciesName = characterPrefab.Identifier;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(speciesName))
|
||||
{
|
||||
throw new Exception("speciesname is null!");
|
||||
}
|
||||
|
||||
int defaultAmount = prefab.ConfigElement.GetAttributeInt("amount", 1);
|
||||
minAmount = prefab.ConfigElement.GetAttributeInt("minamount", defaultAmount);
|
||||
maxAmount = Math.Max(prefab.ConfigElement.GetAttributeInt("maxamount", 1), minAmount);
|
||||
|
||||
var spawnPosTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
|
||||
|
||||
if (string.IsNullOrWhiteSpace(spawnPosTypeStr) ||
|
||||
!Enum.TryParse(spawnPosTypeStr, true, out spawnPosType))
|
||||
{
|
||||
spawnPosType = Level.PositionType.MainPath;
|
||||
}
|
||||
|
||||
spawnDeep = prefab.ConfigElement.GetAttributeBool("spawndeep", false);
|
||||
|
||||
if (GameMain.NetworkMember != null)
|
||||
{
|
||||
List<string> monsterNames = GameMain.NetworkMember.ServerSettings.MonsterEnabled.Keys.ToList();
|
||||
string tryKey = monsterNames.Find(s => speciesName.ToLower() == s.ToLower());
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(tryKey))
|
||||
{
|
||||
if (!GameMain.NetworkMember.ServerSettings.MonsterEnabled[tryKey]) disallowed = true; //spawn was disallowed by host
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<ContentFile> GetFilesToPreload()
|
||||
{
|
||||
string path = CharacterPrefab.FindBySpeciesName(speciesName)?.FilePath;
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to find config file for species \"{speciesName}\"");
|
||||
yield break;
|
||||
}
|
||||
else
|
||||
{
|
||||
yield return new ContentFile(path, ContentType.Character);
|
||||
}
|
||||
}
|
||||
|
||||
public override bool CanAffectSubImmediately(Level level)
|
||||
{
|
||||
float maxRange = Items.Components.Sonar.DefaultSonarRange * 0.8f;
|
||||
|
||||
List<Vector2> positions = GetAvailableSpawnPositions();
|
||||
foreach (Vector2 position in positions)
|
||||
{
|
||||
if (Vector2.DistanceSquared(position, Submarine.MainSub.WorldPosition) < maxRange * maxRange)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void Init(bool affectSubImmediately)
|
||||
{
|
||||
if (GameSettings.VerboseLogging)
|
||||
{
|
||||
DebugConsole.NewMessage("Initialized MonsterEvent (" + speciesName + ")", Color.White);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Vector2> GetAvailableSpawnPositions()
|
||||
{
|
||||
var availablePositions = Level.Loaded.PositionsOfInterest.FindAll(p => spawnPosType.HasFlag(p.PositionType));
|
||||
|
||||
List<Vector2> positions = new List<Vector2>();
|
||||
foreach (var allowedPosition in availablePositions)
|
||||
{
|
||||
if (Level.Loaded.ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(allowedPosition.Position.ToVector2())))) { continue; }
|
||||
positions.Add(allowedPosition.Position.ToVector2());
|
||||
}
|
||||
|
||||
if (spawnDeep)
|
||||
{
|
||||
for (int i = 0; i < positions.Count; i++)
|
||||
{
|
||||
positions[i] = new Vector2(positions[i].X, positions[i].Y - Level.Loaded.Size.Y);
|
||||
}
|
||||
}
|
||||
|
||||
positions.RemoveAll(pos => pos.Y < Level.Loaded.GetBottomPosition(pos.X).Y);
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
private void FindSpawnPosition(bool affectSubImmediately)
|
||||
{
|
||||
if (disallowed) { return; }
|
||||
|
||||
spawnPos = Vector2.Zero;
|
||||
var availablePositions = GetAvailableSpawnPositions();
|
||||
if (affectSubImmediately && spawnPosType != Level.PositionType.Ruin)
|
||||
{
|
||||
if (availablePositions.Count == 0)
|
||||
{
|
||||
//no suitable position found, disable the event
|
||||
Finished();
|
||||
return;
|
||||
}
|
||||
|
||||
float closestDist = float.PositiveInfinity;
|
||||
//find the closest spawnposition that isn't too close to any of the subs
|
||||
foreach (Vector2 position in availablePositions)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(position, Submarine.MainSub.WorldPosition);
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
if (sub.IsOutpost) { continue; }
|
||||
float minDistToSub = GetMinDistanceToSub(sub);
|
||||
if (dist > minDistToSub * minDistToSub && dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
spawnPos = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//only found a spawnpos that's very far from the sub, pick one that's closer
|
||||
//and wait for the sub to move further before spawning
|
||||
if (closestDist > 15000.0f * 15000.0f)
|
||||
{
|
||||
foreach (Vector2 position in availablePositions)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(position, Submarine.MainSub.WorldPosition);
|
||||
if (dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
spawnPos = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
float minDist = spawnPosType == Level.PositionType.Ruin ? 0.0f : 20000.0f;
|
||||
availablePositions.RemoveAll(p => Vector2.Distance(Submarine.MainSub.WorldPosition, p) < minDist);
|
||||
if (availablePositions.Count == 0)
|
||||
{
|
||||
//no suitable position found, disable the event
|
||||
Finished();
|
||||
return;
|
||||
}
|
||||
|
||||
spawnPos = availablePositions[Rand.Int(availablePositions.Count, Rand.RandSync.Server)];
|
||||
}
|
||||
spawnPending = true;
|
||||
}
|
||||
|
||||
private float GetMinDistanceToSub(Submarine submarine)
|
||||
{
|
||||
//9000 units is slightly less than the default range of the sonar
|
||||
return Math.Max(Math.Max(submarine.Borders.Width, submarine.Borders.Height), 9000.0f);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (disallowed)
|
||||
{
|
||||
Finished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFinished) { return; }
|
||||
|
||||
if (spawnPos == null)
|
||||
{
|
||||
FindSpawnPosition(affectSubImmediately: true);
|
||||
spawnPending = true;
|
||||
}
|
||||
|
||||
bool spawnReady = false;
|
||||
if (spawnPending)
|
||||
{
|
||||
//wait until there are no submarines at the spawnpos
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
if (submarine.IsOutpost) { 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
|
||||
//unnecessary monsters in places the players might never visit during the round
|
||||
if (spawnPosType == Level.PositionType.Ruin ||
|
||||
spawnPosType == Level.PositionType.Cave)
|
||||
{
|
||||
bool someoneNearby = false;
|
||||
float minDist = Items.Components.Sonar.DefaultSonarRange * 0.8f;
|
||||
foreach (Submarine submarine in Submarine.Loaded)
|
||||
{
|
||||
if (submarine.IsOutpost) { continue; }
|
||||
if (Vector2.DistanceSquared(submarine.WorldPosition, spawnPos.Value) < minDist * minDist)
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach (Character c in Character.CharacterList)
|
||||
{
|
||||
if (c == Character.Controlled || c.IsRemotePlayer)
|
||||
{
|
||||
if (Vector2.DistanceSquared(c.WorldPosition, spawnPos.Value) < minDist * minDist)
|
||||
{
|
||||
someoneNearby = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!someoneNearby) { return; }
|
||||
}
|
||||
|
||||
spawnPending = false;
|
||||
|
||||
//+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;
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
CoroutineManager.InvokeAfter(() =>
|
||||
{
|
||||
//round ended before the coroutine finished
|
||||
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
|
||||
|
||||
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));
|
||||
|
||||
if (monsters.Count == amount)
|
||||
{
|
||||
spawnReady = true;
|
||||
//this will do nothing if the monsters have no swarm behavior defined,
|
||||
//otherwise it'll make the spawned characters act as a swarm
|
||||
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
|
||||
}
|
||||
}, Rand.Range(0f, amount / 2));
|
||||
}
|
||||
}
|
||||
|
||||
if (!spawnReady) { return; }
|
||||
|
||||
Entity targetEntity = Submarine.FindClosest(GameMain.GameScreen.Cam.WorldViewCenter);
|
||||
#if CLIENT
|
||||
if (Character.Controlled != null) { targetEntity = Character.Controlled; }
|
||||
#endif
|
||||
|
||||
bool monstersDead = true;
|
||||
foreach (Character monster in monsters)
|
||||
{
|
||||
if (!monster.IsDead)
|
||||
{
|
||||
monstersDead = false;
|
||||
|
||||
if (targetEntity != null && Vector2.DistanceSquared(monster.WorldPosition, targetEntity.WorldPosition) < 5000.0f * 5000.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (monstersDead) { Finished(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ScriptedEvent
|
||||
{
|
||||
protected bool isFinished;
|
||||
|
||||
private readonly ScriptedEventPrefab prefab;
|
||||
|
||||
public bool IsFinished
|
||||
{
|
||||
get { return isFinished; }
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "ScriptedEvent (" + prefab.EventType.ToString() +")";
|
||||
}
|
||||
|
||||
public virtual Vector2 DebugDrawPos
|
||||
{
|
||||
get
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public ScriptedEvent(ScriptedEventPrefab prefab)
|
||||
{
|
||||
this.prefab = prefab;
|
||||
}
|
||||
|
||||
public virtual IEnumerable<ContentFile> GetFilesToPreload()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
public virtual void Init(bool affectSubImmediately)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Finished()
|
||||
{
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
public virtual bool CanAffectSubImmediately(Level level)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/*public static List<ScriptedEvent> GenerateInitialEvents(Random random, Level level)
|
||||
{
|
||||
if (ScriptedEventPrefab.List == null)
|
||||
{
|
||||
ScriptedEventPrefab.LoadPrefabs();
|
||||
}
|
||||
|
||||
List<ScriptedEvent> events = new List<ScriptedEvent>();
|
||||
foreach (ScriptedEventPrefab scriptedEvent in ScriptedEventPrefab.List)
|
||||
{
|
||||
int minCount = scriptedEvent.MinEventCount.ContainsKey(level.GenerationParams.Name) ?
|
||||
scriptedEvent.MinEventCount[level.GenerationParams.Name] : scriptedEvent.MinEventCount[""];
|
||||
int maxCount = scriptedEvent.MaxEventCount.ContainsKey(level.GenerationParams.Name) ?
|
||||
scriptedEvent.MaxEventCount[level.GenerationParams.Name] : scriptedEvent.MaxEventCount[""];
|
||||
|
||||
minCount = Math.Min(minCount, maxCount);
|
||||
int count = random.Next(maxCount - minCount) + minCount;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ScriptedEvent eventInstance = scriptedEvent.CreateInstance();
|
||||
events.Add(eventInstance);
|
||||
}
|
||||
}
|
||||
|
||||
return events;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ScriptedEventPrefab
|
||||
{
|
||||
public readonly XElement ConfigElement;
|
||||
|
||||
public readonly Type EventType;
|
||||
|
||||
public readonly string MusicType;
|
||||
|
||||
public float Commonness;
|
||||
|
||||
public ScriptedEventPrefab(XElement element)
|
||||
{
|
||||
ConfigElement = element;
|
||||
|
||||
MusicType = element.GetAttributeString("musictype", "default");
|
||||
|
||||
try
|
||||
{
|
||||
EventType = Type.GetType("Barotrauma." + ConfigElement.Name, true, true);
|
||||
if (EventType == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\".");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
DebugConsole.ThrowError("Could not find an event class of the type \"" + ConfigElement.Name + "\".");
|
||||
}
|
||||
Commonness = element.GetAttributeFloat("commonness", 1.0f);
|
||||
}
|
||||
|
||||
public ScriptedEvent CreateInstance()
|
||||
{
|
||||
ConstructorInfo constructor = EventType.GetConstructor(new[] { typeof(ScriptedEventPrefab) });
|
||||
object instance = null;
|
||||
try
|
||||
{
|
||||
instance = constructor.Invoke(new object[] { this });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString());
|
||||
}
|
||||
|
||||
return (ScriptedEvent)instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Xna.Framework;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class ScriptedEventSet
|
||||
{
|
||||
public static List<ScriptedEventSet> List
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
//0-100
|
||||
public readonly float MinLevelDifficulty, MaxLevelDifficulty;
|
||||
|
||||
public readonly bool ChooseRandom;
|
||||
|
||||
public readonly float MinDistanceTraveled;
|
||||
public readonly float MinMissionTime;
|
||||
|
||||
//the events in this set are delayed if the current EventManager intensity is not between these values
|
||||
public readonly float MinIntensity, MaxIntensity;
|
||||
|
||||
public readonly bool AllowAtStart;
|
||||
|
||||
public readonly bool PerRuin;
|
||||
|
||||
public readonly Dictionary<string, float> Commonness;
|
||||
|
||||
public readonly List<ScriptedEventPrefab> EventPrefabs;
|
||||
|
||||
public readonly List<ScriptedEventSet> ChildSets;
|
||||
|
||||
public string DebugIdentifier
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = "";
|
||||
|
||||
private ScriptedEventSet(XElement element, string debugIdentifier)
|
||||
{
|
||||
DebugIdentifier = element.GetAttributeString("identifier", null) ?? debugIdentifier;
|
||||
Commonness = new Dictionary<string, float>();
|
||||
EventPrefabs = new List<ScriptedEventPrefab>();
|
||||
ChildSets = new List<ScriptedEventSet>();
|
||||
|
||||
MinLevelDifficulty = element.GetAttributeFloat("minleveldifficulty", 0);
|
||||
MaxLevelDifficulty = Math.Max(element.GetAttributeFloat("maxleveldifficulty", 100), MinLevelDifficulty);
|
||||
|
||||
MinIntensity = element.GetAttributeFloat("minintensity", 0.0f);
|
||||
MaxIntensity = Math.Max(element.GetAttributeFloat("maxintensity", 100.0f), MinIntensity);
|
||||
|
||||
ChooseRandom = element.GetAttributeBool("chooserandom", false);
|
||||
MinDistanceTraveled = element.GetAttributeFloat("mindistancetraveled", 0.0f);
|
||||
MinMissionTime = element.GetAttributeFloat("minmissiontime", 0.0f);
|
||||
|
||||
AllowAtStart = element.GetAttributeBool("allowatstart", false);
|
||||
PerRuin = element.GetAttributeBool("perruin", false);
|
||||
|
||||
Commonness[""] = 1.0f;
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "commonness":
|
||||
Commonness[""] = subElement.GetAttributeFloat("commonness", 0.0f);
|
||||
foreach (XElement overrideElement in subElement.Elements())
|
||||
{
|
||||
if (overrideElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string levelType = overrideElement.GetAttributeString("leveltype", "");
|
||||
if (!Commonness.ContainsKey(levelType))
|
||||
{
|
||||
Commonness.Add(levelType, overrideElement.GetAttributeFloat("commonness", 0.0f));
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "eventset":
|
||||
ChildSets.Add(new ScriptedEventSet(subElement, this.DebugIdentifier + "-" + ChildSets.Count));
|
||||
break;
|
||||
default:
|
||||
EventPrefabs.Add(new ScriptedEventPrefab(subElement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public float GetCommonness(Level level)
|
||||
{
|
||||
string key = level.GenerationParams?.Name ?? "";
|
||||
return Commonness.ContainsKey(key) ?
|
||||
Commonness[key] : Commonness[""];
|
||||
}
|
||||
|
||||
public static void LoadPrefabs()
|
||||
{
|
||||
List = new List<ScriptedEventSet>();
|
||||
var configFiles = GameMain.Instance.GetFilesOfType(ContentType.RandomEvents);
|
||||
|
||||
if (!configFiles.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("No config files for random events found in the selected content package");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ContentFile configFile in configFiles)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(configFile.Path);
|
||||
if (doc == null) { continue; }
|
||||
|
||||
var mainElement = doc.Root.IsOverride() ? doc.Root.FirstElement() : doc.Root;
|
||||
if (doc.Root.IsOverride())
|
||||
{
|
||||
DebugConsole.NewMessage($"Overriding all random events using the file {configFile.Path}", Color.Yellow);
|
||||
List.Clear();
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
if (!element.Name.ToString().Equals("eventset", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
List.Add(new ScriptedEventSet(element, i.ToString()));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user