(61d00a474) v0.9.7.1

This commit is contained in:
Regalis
2020-03-04 13:04:10 +01:00
parent 3c50efa5c9
commit 3c09ebe02f
5086 changed files with 786063 additions and 295871 deletions
@@ -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++;
}
}
}
}
}