Unstable v0.1300.0.0 (February 19th 2021)

This commit is contained in:
Joonas Rikkonen
2021-02-25 13:44:23 +02:00
parent b772654326
commit 24cbef485a
441 changed files with 21343 additions and 8562 deletions
@@ -59,7 +59,7 @@ namespace Barotrauma
(Rand.Value(Rand.RandSync.Server) < 0.5f) ?
Level.PositionType.MainPath | Level.PositionType.SidePath :
Level.PositionType.Cave | Level.PositionType.Ruin,
500.0f, 10000.0f, 30.0f);
500.0f, 10000.0f, 30.0f, SpawnPosFilter);
spawnPending = true;
}
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
namespace Barotrauma
@@ -11,6 +12,8 @@ namespace Barotrauma
public EventPrefab Prefab => prefab;
public Func<Level.InterestingPosition, bool> SpawnPosFilter;
public bool IsFinished
{
get { return isFinished; }
@@ -56,5 +59,10 @@ namespace Barotrauma
{
return true;
}
public virtual bool LevelMeetsRequirements()
{
return true;
}
}
}
@@ -0,0 +1,59 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
internal class CheckAfflictionAction : BinaryOptionAction
{
[Serialize("", true)]
public string Identifier { get; set; } = "";
[Serialize("", true)]
public string TargetTag { get; set; } = "";
[Serialize(LimbType.None, true, "Only check afflictions on the specified limb type")]
public LimbType TargetLimb { get; set; }
[Serialize(true, true, "When set to false when TargetLimb is not specified prevent checking limb-specific afflictions")]
public bool AllowLimbAfflictions { get; set; }
public CheckAfflictionAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
protected override bool? DetermineSuccess()
{
if (string.IsNullOrWhiteSpace(Identifier) || string.IsNullOrWhiteSpace(TargetTag)) { return false; }
List<Character> targets = ParentEvent.GetTargets(TargetTag).OfType<Character>().ToList();
if (!(targets.FirstOrDefault() is { } target)) { return false; }
if (TargetLimb == LimbType.None)
{
Affliction? affliction = target.CharacterHealth?.GetAffliction(Identifier, AllowLimbAfflictions);
return affliction != null;
}
if (target.CharacterHealth == null) { return false; }
IEnumerable<Affliction> afflictions = target.CharacterHealth.GetAllAfflictions().Where(affliction =>
{
LimbType? limbType = target.CharacterHealth.GetAfflictionLimb(affliction)?.type;
if (limbType == null) { return false; }
return limbType == TargetLimb || true;
});
return afflictions.Any(a => a.Identifier.Equals(Identifier, StringComparison.OrdinalIgnoreCase));
}
public override string ToDebugString()
{
return $"{ToolBox.GetDebugSymbol(HasBeenDetermined())} {nameof(CheckAfflictionAction)} -> (TargetTag: {TargetTag.ColorizeObject()}, " +
$"AfflictionIdentifier: {Identifier.ColorizeObject()}, " +
$"TargetLimb: {TargetLimb.ColorizeObject()}, " +
$"Succeeded: {succeeded.ColorizeObject()})";
}
}
}
@@ -1,4 +1,5 @@
#nullable enable
using System;
using System.Xml.Linq;
namespace Barotrauma
@@ -11,6 +12,12 @@ namespace Barotrauma
[Serialize("", true)]
public string Condition { get; set; } = null!;
[Serialize(false, true, "Forces the comparison to use string instead of attempting to parse it as a boolean or a float first")]
public bool ForceString { get; set; }
[Serialize(false, true, "Performs the comparison against a metadata by identifier instead of a constant value")]
public bool CheckAgainstMetadata { get; set; }
protected object? value2;
protected object? value1;
@@ -41,13 +48,52 @@ namespace Barotrauma
Operator = PropertyConditional.GetOperatorType(op);
if (Operator == PropertyConditional.OperatorType.None) { return false; }
bool? tryBoolean = TryBoolean(campaignMode, value);
if (tryBoolean != null) { return tryBoolean; }
if (CheckAgainstMetadata)
{
object? metadata1 = campaignMode.CampaignMetadata.GetValue(Identifier);
object? metadata2 = campaignMode.CampaignMetadata.GetValue(value);
bool? tryFloat = TryFloat(campaignMode, value);
if (tryFloat != null) { return tryFloat; }
if (metadata1 == null || metadata2 == null)
{
return Operator switch
{
PropertyConditional.OperatorType.Equals => metadata1 == metadata2,
PropertyConditional.OperatorType.NotEquals => metadata1 != metadata2,
_ => false
};
}
if (!ForceString)
{
switch (metadata1)
{
case bool bool1 when metadata2 is bool bool2:
return CompareBool(bool1, bool2) ?? false;
case float float1 when metadata2 is float float2:
return CompareFloat(float1, float2) ?? false;
}
}
if (metadata1 is string string1 && metadata2 is string string2)
{
return CompareString(string1, string2) ?? false;
}
return false;
}
if (!ForceString)
{
bool? tryBoolean = TryBoolean(campaignMode, value);
if (tryBoolean != null) { return tryBoolean; }
bool? tryFloat = TryFloat(campaignMode, value);
if (tryFloat != null) { return tryFloat; }
}
bool? tryString = TryString(campaignMode, value);
if (tryString != null) { return tryString; }
DebugConsole.ThrowError($"{value2} ({Condition}) did not match a boolean or a float.");
return false;
}
@@ -55,53 +101,85 @@ namespace Barotrauma
{
if (bool.TryParse(value, out bool b))
{
bool target = GetBool(campaignMode);
value1 = target;
value2 = b;
switch (Operator)
{
case PropertyConditional.OperatorType.Equals:
return target == b;
case PropertyConditional.OperatorType.NotEquals:
return target != b;
default:
DebugConsole.Log($"Only \"Equals\" and \"Not equals\" operators are allowed for a boolean (was {Operator} for {value}).");
return false;
}
return CompareBool(GetBool(campaignMode), b);
}
DebugConsole.Log($"{value} != bool");
return null;
}
private bool? CompareBool(bool val1, bool val2)
{
value1 = val1;
value2 = val2;
switch (Operator)
{
case PropertyConditional.OperatorType.Equals:
return val1 == val2;
case PropertyConditional.OperatorType.NotEquals:
return val1 != val2;
default:
DebugConsole.Log($"Only \"Equals\" and \"Not equals\" operators are allowed for a boolean (was {Operator} for {val2}).");
return false;
}
}
private bool? TryFloat(CampaignMode campaignMode, string value)
{
if (float.TryParse(value, out float f))
{
float target = GetFloat(campaignMode);
value1 = target;
value2 = f;
switch (Operator)
{
case PropertyConditional.OperatorType.Equals:
return MathUtils.NearlyEqual(target, f);
case PropertyConditional.OperatorType.GreaterThan:
return target > f;
case PropertyConditional.OperatorType.GreaterThanEquals:
return target >= f;
case PropertyConditional.OperatorType.LessThan:
return target < f;
case PropertyConditional.OperatorType.LessThanEquals:
return target <= f;
case PropertyConditional.OperatorType.NotEquals:
return !MathUtils.NearlyEqual(target, f);
}
return CompareFloat(GetFloat(campaignMode), f);
}
DebugConsole.Log($"{value} != float");
return null;
}
private bool? CompareFloat(float val1, float val2)
{
value1 = val1;
value2 = val2;
switch (Operator)
{
case PropertyConditional.OperatorType.Equals:
return MathUtils.NearlyEqual(val1, val2);
case PropertyConditional.OperatorType.GreaterThan:
return val1 > val2;
case PropertyConditional.OperatorType.GreaterThanEquals:
return val1 >= val2;
case PropertyConditional.OperatorType.LessThan:
return val1 < val2;
case PropertyConditional.OperatorType.LessThanEquals:
return val1 <= val2;
case PropertyConditional.OperatorType.NotEquals:
return !MathUtils.NearlyEqual(val1, val2);
}
return null;
}
private bool? TryString(CampaignMode campaignMode, string value)
{
return CompareString(GetString(campaignMode), value);
}
private bool? CompareString(string val1, string val2)
{
value1 = val1;
value2 = val2;
bool equals = string.Equals(val1, val2, StringComparison.OrdinalIgnoreCase);
switch (Operator)
{
case PropertyConditional.OperatorType.Equals:
return equals;
case PropertyConditional.OperatorType.NotEquals:
return !equals;
default:
DebugConsole.Log($"Only \"Equals\" and \"Not equals\" operators are allowed for a string (was {Operator} for {val2}).");
return null;
}
}
protected virtual bool GetBool(CampaignMode campaignMode)
{
return campaignMode.CampaignMetadata.GetBoolean(Identifier);
@@ -112,6 +190,11 @@ namespace Barotrauma
return campaignMode.CampaignMetadata.GetFloat(Identifier);
}
private string GetString(CampaignMode campaignMode)
{
return campaignMode.CampaignMetadata.GetString(Identifier);
}
public override string ToDebugString()
{
string condition = "?";
@@ -0,0 +1,38 @@
using System.Xml.Linq;
using NLog.Targets;
namespace Barotrauma
{
class ClearTagAction : EventAction
{
[Serialize("", true)]
public string Tag { get; set; }
private bool isFinished;
public ClearTagAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
public override bool IsFinished(ref string goToLabel) => isFinished;
public override void Reset()
{
isFinished = false;
}
public override void Update(float deltaTime)
{
if (isFinished) { return; }
if (!string.IsNullOrWhiteSpace(Tag) && ParentEvent.Targets.ContainsKey(Tag))
{
ParentEvent.Targets.Remove(Tag);
}
isFinished = true;
}
public override string ToDebugString()
{
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(ClearTagAction)} -> (Tag: {Tag.ColorizeObject()})";
}
}
}
@@ -1,3 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
@@ -40,9 +41,15 @@ namespace Barotrauma
[Serialize(true, true)]
public bool WaitForInteraction { get; set; }
[Serialize("", true, "Tag to assign to whoever invokes the conversation")]
public string InvokerTag { get; set; }
[Serialize(false, true)]
public bool FadeToBlack { get; set; }
[Serialize(true, true, "Should the event end if the conversations is interrupted (e.g. if the speaker dies or falls unconscious mid-conversation). Defaults to true.")]
public bool EndEventIfInterrupted { get; set; }
[Serialize("", true)]
public string EventSprite { get; set; }
@@ -54,7 +61,6 @@ namespace Barotrauma
private Character speaker;
private OrderInfo? prevSpeakerOrder;
private AIObjective prevIdleObjective, prevGotoObjective;
public List<SubactionGroup> Options { get; private set; }
@@ -104,19 +110,26 @@ namespace Barotrauma
{
#if CLIENT
dialogBox?.Close();
GUIMessageBox.MessageBoxes.ForEachMod(mb =>
{
if (mb.UserData as string == "ConversationAction")
{
(mb as GUIMessageBox)?.Close();
}
});
#else
foreach (Client c in GameMain.Server.ConnectedClients)
{
if (c.InGame && c.Character != null) { ServerWrite(speaker, c); }
}
# endif
#endif
ResetSpeaker();
dialogOpened = false;
}
if (Interrupted == null)
{
goTo = "_end";
if (EndEventIfInterrupted) { goTo = "_end"; }
return true;
}
else
@@ -171,16 +184,9 @@ namespace Barotrauma
GameMain.NetworkMember.CreateEntityEvent(speaker, new object[] { NetEntityEvent.Type.AssignCampaignInteraction });
#endif
var humanAI = speaker.AIController as HumanAIController;
if (humanAI != null)
if (humanAI != null && !speaker.IsDead && !speaker.Removed)
{
if (prevSpeakerOrder != null)
{
humanAI.SetOrder(prevSpeakerOrder.Value.Order, prevSpeakerOrder.Value.OrderOption, orderGiver: null, speak: false);
}
else
{
humanAI.SetOrder(null, string.Empty, orderGiver: null, speak: false);
}
humanAI.ClearForcedOrder();
if (prevIdleObjective != null) { humanAI.ObjectiveManager.AddObjective(prevIdleObjective); }
if (prevGotoObjective != null) { humanAI.ObjectiveManager.AddObjective(prevGotoObjective); }
}
@@ -255,7 +261,12 @@ namespace Barotrauma
}
else
{
if (Options.Any())
if (ShouldInterrupt())
{
ResetSpeaker();
interrupt = true;
}
else if (Options.Any())
{
Options[selectedOption].Update(deltaTime);
}
@@ -305,16 +316,11 @@ namespace Barotrauma
if (speaker?.AIController is HumanAIController humanAI)
{
prevSpeakerOrder = null;
if (humanAI.CurrentOrder != null)
{
prevSpeakerOrder = new OrderInfo(humanAI.CurrentOrder, humanAI.CurrentOrderOption);
}
prevIdleObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveIdle>();
prevGotoObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveGoTo>();
humanAI.SetOrder(
Order.PrefabList.Find(o => o.Identifier.Equals("wait", StringComparison.OrdinalIgnoreCase)),
option: string.Empty, orderGiver: null, speak: false);
humanAI.SetForcedOrder(
Order.PrefabList.Find(o => o.Identifier.Equals("wait", StringComparison.OrdinalIgnoreCase)),
option: string.Empty, orderGiver: null);
if (targets.Any())
{
Entity closestTarget = null;
@@ -335,6 +341,11 @@ namespace Barotrauma
}
}
if (targetCharacter != null && !string.IsNullOrWhiteSpace(InvokerTag))
{
ParentEvent.AddTarget(InvokerTag, targetCharacter);
}
ShowDialog(speaker, targetCharacter);
dialogOpened = true;
@@ -42,7 +42,7 @@ namespace Barotrauma
var targets = ParentEvent.GetTargets(TargetTag).Where(e => e is Character).Select(e => e as Character);
foreach (var target in targets)
{
target.Info?.IncreaseSkillLevel(Skill?.ToLowerInvariant(), Amount, target.WorldPosition + Vector2.UnitY * 150.0f);
target.Info?.IncreaseSkillLevel(Skill?.ToLowerInvariant(), Amount, target.Position + Vector2.UnitY * 150.0f);
}
isFinished = true;
}
@@ -68,6 +68,9 @@ namespace Barotrauma
}
}
[Serialize(false, true, description: "Should the AI ignore this item. This will prevent outpost NPCs cleaning up or otherwise using important items intended to be left for the players.")]
public bool IgnoreByAI { get; set; }
private bool spawned;
private Entity spawnedEntity;
@@ -106,38 +109,17 @@ namespace Barotrauma
ISpatialEntity spawnPos = GetSpawnPos();
Entity.Spawner.AddToSpawnQueue(CharacterPrefab.HumanSpeciesName, OffsetSpawnPos(spawnPos?.WorldPosition ?? Vector2.Zero, 100.0f), onSpawn: newCharacter =>
{
newCharacter.TeamID = Character.TeamType.FriendlyNPC;
newCharacter.TeamID = CharacterTeamType.FriendlyNPC;
newCharacter.EnableDespawn = false;
humanPrefab.GiveItems(newCharacter, newCharacter.Submarine);
if (LootingIsStealing)
{
foreach (Item item in newCharacter.Inventory.Items)
foreach (Item item in newCharacter.Inventory.AllItems)
{
if (item != null) { item.SpawnedInOutpost = true; }
}
}
newCharacter.CharacterHealth.MaxVitality *= humanPrefab.HealthMultiplier;
var humanAI = newCharacter.AIController as HumanAIController;
if (humanAI != null)
{
var idleObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveIdle>();
if (idleObjective != null)
{
idleObjective.Behavior = humanPrefab.Behavior;
foreach (string moduleType in humanPrefab.PreferredOutpostModuleTypes)
{
idleObjective.PreferredOutpostModuleTypes.Add(moduleType);
}
}
}
if (humanPrefab.CampaignInteractionType != CampaignMode.InteractionType.None)
{
(GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(newCharacter, humanPrefab.CampaignInteractionType);
if (spawnPos != null && humanAI != null)
{
humanAI.ObjectiveManager.SetOrder(new AIObjectiveGoTo(spawnPos, newCharacter, humanAI.ObjectiveManager, repeat: true, getDivingGearIfNeeded: false, closeEnough: 200));
item.SpawnedInOutpost = true;
}
}
humanPrefab.InitializeCharacter(newCharacter, spawnPos);
if (!string.IsNullOrEmpty(TargetTag) && newCharacter != null)
{
ParentEvent.AddTarget(TargetTag, newCharacter);
@@ -197,9 +179,16 @@ namespace Barotrauma
}
void onSpawned(Item newItem)
{
if (!string.IsNullOrEmpty(TargetTag) && newItem != null)
if (newItem != null)
{
ParentEvent.AddTarget(TargetTag, newItem);
if (!string.IsNullOrEmpty(TargetTag))
{
ParentEvent.AddTarget(TargetTag, newItem);
}
if (IgnoreByAI)
{
newItem.AddTag("ignorebyai");
}
}
spawnedEntity = newItem;
}
@@ -12,6 +12,9 @@ namespace Barotrauma
[Serialize("", true)]
public string Tag { get; set; }
[Serialize(true, true)]
public bool IgnoreIncapacitatedCharacters { get; set; }
private bool isFinished = false;
public TagAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
@@ -27,12 +30,26 @@ namespace Barotrauma
private void TagPlayers()
{
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsPlayer);
if (IgnoreIncapacitatedCharacters)
{
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsPlayer && !c.IsIncapacitated);
}
else
{
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsPlayer);
}
}
private void TagBots()
{
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsBot);
if (IgnoreIncapacitatedCharacters)
{
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsBot && !c.IsIncapacitated);
}
else
{
ParentEvent.AddTargetPredicate(Tag, e => e is Character c && c.IsBot);
}
}
private void TagCrew()
@@ -24,9 +24,12 @@ namespace Barotrauma
[Serialize(0.0f, true, description: "Range both entities must be within to activate the trigger.")]
public float Radius { get; set; }
[Serialize(true, true, description: "If true, characters who are being targeted by some enemy cannot trigger the event.")]
[Serialize(true, true, description: "If true, characters who are being targeted by some enemy cannot trigger the action.")]
public bool DisableInCombat { get; set; }
[Serialize(true, true, description: "If true, dead/unconscious characters cannot trigger the action.")]
public bool DisableIfTargetIncapacitated { get; set; }
private float distance;
public TriggerAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
@@ -59,6 +62,7 @@ namespace Barotrauma
foreach (Entity e1 in targets1)
{
if (DisableInCombat && IsInCombat(e1)) { continue; }
if (DisableIfTargetIncapacitated && e1 is Character character1 && (character1.IsDead || character1.IsIncapacitated)) { continue; }
if (!string.IsNullOrEmpty(TargetModuleType))
{
if (IsCloseEnoughToHull(e1, out Hull hull))
@@ -75,6 +79,7 @@ namespace Barotrauma
{
if (e1 == e2) { continue; }
if (DisableInCombat && IsInCombat(e2)) { continue; }
if (DisableIfTargetIncapacitated && e2 is Character character2 && (character2.IsDead || character2.IsIncapacitated)) { continue; }
Vector2 pos1 = e1.WorldPosition;
Vector2 pos2 = e2.WorldPosition;
@@ -33,7 +33,11 @@ namespace Barotrauma
}
else
{
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(eventPrefab.CreateInstance());
var ev = eventPrefab.CreateInstance();
if (ev != null)
{
GameMain.GameSession.EventManager.QueuedEvents.Enqueue(ev);
}
}
}
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -51,6 +52,12 @@ namespace Barotrauma
private float roundDuration;
private bool isCrewAway;
//how long it takes after the crew returns for the event manager to resume normal operation
const float CrewAwayResetDelay = 60.0f;
private float crewAwayResetTimer;
private float crewAwayDuration;
private readonly List<EventSet> pendingEventSets = new List<EventSet>();
private readonly Dictionary<EventSet, List<Event>> selectedEvents = new Dictionary<EventSet, List<Event>>();
@@ -86,6 +93,8 @@ namespace Barotrauma
public void StartRound(Level level)
{
this.level = level;
if (isClient) { return; }
pendingEventSets.Clear();
@@ -100,7 +109,6 @@ namespace Barotrauma
totalPathLength = steeringPath.TotalLength;
}
this.level = level;
SelectSettings();
var initialEventSet = SelectRandomEvents(EventSet.List);
@@ -144,6 +152,9 @@ namespace Barotrauma
PreloadContent(GetFilesToPreload());
roundDuration = 0.0f;
isCrewAway = false;
crewAwayDuration = 0.0f;
crewAwayResetTimer = 0.0f;
intensityUpdateTimer = 0.0f;
CalculateCurrentIntensity(0.0f);
currentIntensity = targetIntensity;
@@ -258,26 +269,23 @@ namespace Barotrauma
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}");
}
}
mainElement.GetChildElements("sound").ForEach(e => Submarine.LoadRoundSound(e));
if (!CharacterPrefab.CheckSpeciesName(mainElement, file.Path, out string speciesName)) { continue; }
bool humanoid = mainElement.GetAttributeBool("humanoid", false);
CharacterPrefab originalCharacter;
if (characterPrefab.VariantOf != null)
{
originalCharacter = CharacterPrefab.FindBySpeciesName(characterPrefab.VariantOf);
var originalRoot = originalCharacter.XDocument.Root;
var originalMainElement = originalRoot.IsOverride() ? originalRoot.FirstElement() : originalRoot;
originalMainElement.GetChildElements("sound").ForEach(e => Submarine.LoadRoundSound(e));
if (!CharacterPrefab.CheckSpeciesName(mainElement, file.Path, out string name)) { continue; }
speciesName = name;
if (mainElement.Attribute("humanoid") == null)
{
humanoid = originalMainElement.GetAttributeBool("humanoid", false);
}
}
RagdollParams ragdollParams;
if (humanoid)
{
@@ -335,13 +343,31 @@ namespace Barotrauma
{
if (level == null) { return; }
int applyCount = 1;
List<Func<Level.InterestingPosition, bool>> spawnPosFilter = new List<Func<Level.InterestingPosition, bool>>();
if (eventSet.PerRuin)
{
applyCount = Level.Loaded.Ruins.Count();
foreach (var ruin in Level.Loaded.Ruins)
{
spawnPosFilter.Add((Level.InterestingPosition pos) => { return pos.Ruin == ruin; });
}
}
else if (eventSet.PerCave)
{
applyCount = Level.Loaded.Caves.Count();
foreach (var cave in Level.Loaded.Caves)
{
spawnPosFilter.Add((Level.InterestingPosition pos) => { return pos.Cave == cave; });
}
}
else if (eventSet.PerWreck)
{
applyCount = Submarine.Loaded.Count(s => s.Info.IsWreck && (s.WreckAI == null || !s.WreckAI.IsAlive));
var wrecks = Submarine.Loaded.Where(s => s.Info.IsWreck && (s.WreckAI == null || !s.WreckAI.IsAlive));
applyCount = wrecks.Count();
foreach (var wreck in wrecks)
{
spawnPosFilter.Add((Level.InterestingPosition pos) => { return pos.Submarine == wreck; });
}
}
for (int i = 0; i < applyCount; i++)
{
@@ -356,7 +382,9 @@ namespace Barotrauma
if (eventPrefab != null)
{
var newEvent = eventPrefab.First.CreateInstance();
if (newEvent == null) { continue; }
newEvent.Init(true);
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
DebugConsole.Log("Initialized event " + newEvent.ToString());
if (!selectedEvents.ContainsKey(eventSet))
{
@@ -378,6 +406,7 @@ namespace Barotrauma
foreach (Pair<EventPrefab, float> eventPrefab in eventSet.EventPrefabs)
{
var newEvent = eventPrefab.First.CreateInstance();
if (newEvent == null) { continue; }
newEvent.Init(true);
DebugConsole.Log("Initialized event " + newEvent.ToString());
if (!selectedEvents.ContainsKey(eventSet))
@@ -402,10 +431,11 @@ namespace Barotrauma
var allowedEventSets =
eventSets.Where(es => level.Difficulty >= es.MinLevelDifficulty && level.Difficulty <= es.MaxLevelDifficulty && level.LevelData.Type == es.LevelType);
if (GameMain.GameSession?.GameMode is CampaignMode campaign && campaign.Map?.CurrentLocation?.Type != null)
LocationType locationType = (GameMain.GameSession?.GameMode as CampaignMode)?.Map?.CurrentLocation?.Type ?? level?.StartLocation?.Type;
if (locationType != null)
{
allowedEventSets = allowedEventSets.Where(set => set.LocationTypeIdentifiers == null || set.LocationTypeIdentifiers.Any(identifier => string.Equals(identifier, campaign.Map.CurrentLocation.Type.Identifier, StringComparison.OrdinalIgnoreCase)));
allowedEventSets = allowedEventSets.Where(set => set.LocationTypeIdentifiers == null || set.LocationTypeIdentifiers.Any(identifier => string.Equals(identifier, locationType.Identifier, StringComparison.OrdinalIgnoreCase)));
}
float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level));
@@ -440,6 +470,14 @@ namespace Barotrauma
}
}
if (eventSet.DelayWhenCrewAway)
{
if ((isCrewAway && crewAwayDuration < settings.FreezeDurationWhenCrewAway) || crewAwayResetTimer > 0.0f)
{
return false;
}
}
if ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) &&
roundDuration < eventSet.MinMissionTime)
{
@@ -491,6 +529,25 @@ namespace Barotrauma
}
}
if (IsCrewAway())
{
isCrewAway = true;
crewAwayResetTimer = CrewAwayResetDelay;
crewAwayDuration += deltaTime;
}
else if (crewAwayResetTimer > 0.0f)
{
isCrewAway = false;
crewAwayResetTimer -= deltaTime;
}
else
{
isCrewAway = false;
crewAwayDuration = 0.0f;
eventThreshold += settings.EventThresholdIncrease * deltaTime;
eventCoolDown -= deltaTime;
}
calculateDistanceTraveledTimer -= deltaTime;
if (calculateDistanceTraveledTimer <= 0.0f)
{
@@ -498,9 +555,6 @@ namespace Barotrauma
calculateDistanceTraveledTimer = CalculateDistanceTraveledInterval;
}
eventThreshold += settings.EventThresholdIncrease * deltaTime;
eventCoolDown -= deltaTime;
if (currentIntensity < eventThreshold)
{
bool recheck = false;
@@ -524,7 +578,10 @@ namespace Barotrauma
{
activeEvents.Add(ev);
eventThreshold = settings.DefaultEventThreshold;
eventCoolDown = settings.EventCooldown;
if (eventSet.TriggerEventCooldown && selectedEvents[eventSet].Any(e => e.Prefab.TriggerEventCooldown))
{
eventCoolDown = settings.EventCooldown;
}
}
}
@@ -561,7 +618,7 @@ namespace Barotrauma
int characterCount = 0;
foreach (Character character in Character.CharacterList)
{
if (character.IsDead || character.TeamID == Character.TeamType.FriendlyNPC) { continue; }
if (character.IsDead || character.TeamID == CharacterTeamType.FriendlyNPC) { continue; }
if (character.AIController is HumanAIController || character.IsRemotePlayer)
{
avgCrewHealth += character.Vitality / character.MaxVitality * (character.IsUnconscious ? 0.5f : 1.0f);
@@ -584,9 +641,8 @@ namespace Barotrauma
{
if (character.IsDead || character.IsIncapacitated || !character.Enabled || character.IsPet || character.Params.CompareGroup("human")) { continue; }
EnemyAIController enemyAI = character.AIController as EnemyAIController;
if (enemyAI == null) continue;
if (!(character.AIController is EnemyAIController enemyAI)) { continue; }
if (character.CurrentHull?.Submarine != null &&
(character.CurrentHull.Submarine == Submarine.MainSub || Submarine.MainSub.DockedTo.Contains(character.CurrentHull.Submarine)))
{
@@ -679,7 +735,6 @@ namespace Barotrauma
}
}
/// <summary>
/// Finds all actions in a ScriptedEvent
/// </summary>
@@ -748,5 +803,74 @@ namespace Barotrauma
#endif
return refEntity;
}
private bool IsCrewAway()
{
#if CLIENT
return Character.Controlled != null && IsCharacterAway(Character.Controlled);
#else
int playerCount = 0;
int awayPlayerCount = 0;
foreach (Barotrauma.Networking.Client client in GameMain.Server.ConnectedClients)
{
if (client.Character == null || client.Character.IsDead || client.Character.IsIncapacitated) { continue; }
playerCount++;
if (IsCharacterAway(client.Character)) { awayPlayerCount++; }
}
return playerCount > 0 && awayPlayerCount / (float)playerCount > 0.5f;
#endif
}
private bool IsCharacterAway(Character character)
{
if (character.Submarine != null)
{
switch (character.Submarine.Info.Type)
{
case SubmarineType.Player:
case SubmarineType.Outpost:
case SubmarineType.OutpostModule:
return false;
case SubmarineType.Wreck:
case SubmarineType.BeaconStation:
return true;
}
}
const int maxDist = 1000;
if (Level.Loaded != null)
{
foreach (var ruin in Level.Loaded.Ruins)
{
Rectangle area = ruin.Area;
area.Inflate(maxDist, maxDist);
if (area.Contains(character.WorldPosition)) { return true; }
}
foreach (var cave in Level.Loaded.Caves)
{
Rectangle area = cave.Area;
area.Inflate(maxDist, maxDist);
if (area.Contains(character.WorldPosition)) { return true; }
}
}
foreach (Submarine sub in Submarine.Loaded)
{
if (sub.Info.Type != SubmarineType.BeaconStation && sub.Info.Type != SubmarineType.Wreck) { continue; }
Rectangle worldBorders = new Rectangle(
sub.Borders.X + (int)sub.WorldPosition.X - maxDist,
sub.Borders.Y + (int)sub.WorldPosition.Y + maxDist,
sub.Borders.Width + maxDist * 2,
sub.Borders.Height + maxDist * 2);
if (Submarine.RectContains(worldBorders, character.WorldPosition))
{
return true;
}
}
return false;
}
}
}
@@ -24,6 +24,8 @@ namespace Barotrauma
public readonly float MinLevelDifficulty = 0.0f;
public readonly float MaxLevelDifficulty = 100.0f;
public readonly float FreezeDurationWhenCrewAway = 60.0f * 10.0f;
public static void Init()
{
List.Clear();
@@ -77,6 +79,8 @@ namespace Barotrauma
MinLevelDifficulty = element.GetAttributeFloat("MinLevelDifficulty", 0.0f);
MaxLevelDifficulty = element.GetAttributeFloat("MaxLevelDifficulty", 100.0f);
FreezeDurationWhenCrewAway = element.GetAttributeFloat("FreezeDurationWhenCrewAway", 10.0f * 60.0f);
}
}
}
@@ -7,9 +7,9 @@ namespace Barotrauma
class EventPrefab
{
public readonly XElement ConfigElement;
public readonly Type EventType;
public readonly string MusicType;
public readonly Type EventType;
public readonly float SpawnProbability;
public readonly bool TriggerEventCooldown;
public float Commonness;
public string Identifier;
@@ -17,8 +17,6 @@ namespace Barotrauma
{
ConfigElement = element;
MusicType = element.GetAttributeString("musictype", "default");
try
{
EventType = Type.GetType("Barotrauma." + ConfigElement.Name, true, true);
@@ -35,6 +33,7 @@ namespace Barotrauma
Identifier = ConfigElement.GetAttributeString("identifier", string.Empty);
Commonness = element.GetAttributeFloat("commonness", 1.0f);
SpawnProbability = Math.Clamp(element.GetAttributeFloat("spawnprobability", 1.0f), 0, 1);
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
}
public Event CreateInstance()
@@ -50,6 +49,9 @@ namespace Barotrauma
DebugConsole.ThrowError(ex.InnerException != null ? ex.InnerException.ToString() : ex.ToString());
}
Event ev = (Event)instance;
if (!ev.LevelMeetsRequirements()) { return null; }
return (Event)instance;
}
}
@@ -83,11 +83,14 @@ namespace Barotrauma
public readonly bool IgnoreCoolDown;
public readonly bool PerRuin;
public readonly bool PerWreck;
public readonly bool PerRuin, PerCave, PerWreck;
public readonly bool OncePerOutpost;
public readonly bool DelayWhenCrewAway;
public readonly bool TriggerEventCooldown;
public readonly Dictionary<string, float> Commonness;
//Pair.First: event prefab, Pair.Second: commonness
@@ -133,10 +136,13 @@ namespace Barotrauma
MinMissionTime = element.GetAttributeFloat("minmissiontime", 0.0f);
AllowAtStart = element.GetAttributeBool("allowatstart", false);
IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? false);
PerRuin = element.GetAttributeBool("perruin", false);
PerCave = element.GetAttributeBool("percave", false);
PerWreck = element.GetAttributeBool("perwreck", false);
IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? (PerRuin || PerCave || PerWreck));
DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", !PerRuin && !PerCave && !PerWreck);
OncePerOutpost = element.GetAttributeBool("perwreck", false);
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
Commonness[""] = 1.0f;
foreach (XElement subElement in element.Elements())
@@ -0,0 +1,112 @@
using Barotrauma.Extensions;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
partial class AbandonedOutpostMission : Mission
{
private readonly XElement characterConfig;
private readonly List<Character> characters = new List<Character>();
private readonly Dictionary<Character, List<Item>> characterItems = new Dictionary<Character, List<Item>>();
private readonly string itemTag;
private Item itemToDestroy;
public AbandonedOutpostMission(MissionPrefab prefab, Location[] locations) :
base(prefab, locations)
{
characterConfig = prefab.ConfigElement.Element("Characters");
itemTag = prefab.ConfigElement.GetAttributeString("targetitem", "");
if (string.IsNullOrEmpty(itemTag))
{
DebugConsole.ThrowError($"Error in mission prefab \"{prefab.Identifier}\". Target item not defined.");
}
}
protected override void StartMissionSpecific(Level level)
{
itemToDestroy = null;
itemToDestroy = Item.ItemList.Find(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag));
if (itemToDestroy == null)
{
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".");
}
if (!IsClient)
{
InitCharacters();
}
}
private void InitCharacters()
{
characters.Clear();
characterItems.Clear();
if (characterConfig == null) { return; }
var submarine = Submarine.Loaded.Find(s => s.Info.Type == SubmarineType.Outpost) ?? Submarine.MainSub;
if (submarine.Info.Type == SubmarineType.Outpost)
{
submarine.TeamID = CharacterTeamType.None;
}
foreach (XElement element in characterConfig.Elements())
{
string characterIdentifier = element.GetAttributeString("identifier", "");
string characterFrom = element.GetAttributeString("from", "");
HumanPrefab humanPrefab = NPCSet.Get(characterFrom, characterIdentifier);
if (humanPrefab == null)
{
DebugConsole.ThrowError("Couldn't spawn character for abandoned outpost mission: character prefab \"" + characterIdentifier + "\" not found");
return;
}
string[] moduleFlags = element.GetAttributeStringArray("moduleflags", null);
string[] spawnPointTags = element.GetAttributeStringArray("spawnpointtags", null);
ISpatialEntity spawnPos = SpawnAction.GetSpawnPos(
SpawnAction.SpawnLocationType.Outpost, SpawnType.Human,
moduleFlags ?? humanPrefab.GetModuleFlags(),
spawnPointTags ?? humanPrefab.GetSpawnPointTags());
if (spawnPos == null)
{
spawnPos = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom();
}
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: humanPrefab.GetJobPrefab(Rand.RandSync.Server), randSync: Rand.RandSync.Server);
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, spawnPos.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
spawnedCharacter.TeamID = CharacterTeamType.None;
humanPrefab.InitializeCharacter(spawnedCharacter, spawnPos);
humanPrefab.GiveItems(spawnedCharacter, Submarine.MainSub, Rand.RandSync.Server, createNetworkEvents: false);
characters.Add(spawnedCharacter);
characterItems.Add(spawnedCharacter, spawnedCharacter.Inventory.FindAllItems(recursive: true));
}
}
public override void Update(float deltaTime)
{
if (State == 0 && itemToDestroy != null && itemToDestroy.Condition <= 0.0f)
{
State = 1;
}
}
public override void End()
{
completed = itemToDestroy == null || itemToDestroy.Condition <= 0.0f;
if (completed)
{
if (Prefab.LocationTypeChangeOnCompleted != null)
{
ChangeLocationType(Prefab.LocationTypeChangeOnCompleted);
}
GiveReward();
}
}
}
}
@@ -1,5 +1,3 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -11,11 +9,9 @@ namespace Barotrauma
partial class BeaconMission : Mission
{
private bool swarmSpawned;
private string monsterSpeciesName;
private readonly string monsterSpeciesName;
private Point monsterCountRange;
private Level level;
private Location[] locations;
private string sonarLabel;
private readonly string sonarLabel;
public BeaconMission(MissionPrefab prefab, Location[] locations) : base(prefab, locations)
{
@@ -34,8 +30,6 @@ namespace Barotrauma
monsterCountRange = new Point(min, max);
this.locations = locations;
sonarLabel = TextManager.Get("beaconstationsonarlabel");
}
@@ -51,27 +45,58 @@ namespace Barotrauma
{
get
{
yield return level.BeaconStation.WorldPosition;
if (level.BeaconStation == null)
{
yield break;
}
yield return level.BeaconStation.WorldPosition;
}
}
public override void Start(Level level)
{
this.level = level;
}
public override void Update(float deltaTime)
{
if (IsClient) { return; }
if (!swarmSpawned && level.CheckBeaconActive())
{
State = 1;
Vector2 spawnPos = level.BeaconStation.WorldPosition;
spawnPos.Y += level.BeaconStation.GetDockedBorders().Height * 1.5f;
var availablePositions = Level.Loaded.PositionsOfInterest.FindAll(p =>
p.PositionType == Level.PositionType.MainPath ||
p.PositionType == Level.PositionType.SidePath);
availablePositions.RemoveAll(p => Level.Loaded.ExtraWalls.Any(w => w.IsPointInside(p.Position.ToVector2())));
availablePositions.RemoveAll(p => Submarine.FindContaining(p.Position.ToVector2()) != null);
if (availablePositions.Any())
{
Level.InterestingPosition? closestPos = null;
float closestDist = float.PositiveInfinity;
foreach (var pos in availablePositions)
{
float dist = Vector2.DistanceSquared(pos.Position.ToVector2(), level.BeaconStation.WorldPosition);
if (dist < closestDist)
{
closestDist = dist;
closestPos = pos;
}
}
if (closestPos.HasValue)
{
spawnPos = closestPos.Value.Position.ToVector2();
}
}
int amount = Rand.Range(monsterCountRange.X, monsterCountRange.Y + 1);
for (int i = 0; i < amount; i++)
{
Entity.Spawner.AddToSpawnQueue(monsterSpeciesName, spawnPos);
CoroutineManager.InvokeAfter(() =>
{
//round ended before the coroutine finished
if (GameMain.GameSession == null || Level.Loaded == null) { return; }
Entity.Spawner.AddToSpawnQueue(monsterSpeciesName, spawnPos);
}, Rand.Range(0f, amount));
}
swarmSpawned = true;
}
@@ -82,13 +107,15 @@ namespace Barotrauma
completed = level.CheckBeaconActive();
if (completed)
{
if (GameMain.GameSession.GameMode is CampaignMode)
if (Prefab.LocationTypeChangeOnCompleted != null)
{
int naturalFormationIndex = locations[0].Type.Identifier.Equals("None", StringComparison.OrdinalIgnoreCase) ? 0 : 1;
var upgradeLocation = locations[naturalFormationIndex];
upgradeLocation.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals("Explored", StringComparison.OrdinalIgnoreCase)));
ChangeLocationType(Prefab.LocationTypeChangeOnCompleted);
}
GiveReward();
if (level?.LevelData != null)
{
level.LevelData.IsBeaconActive = true;
}
}
}
@@ -94,7 +94,10 @@ namespace Barotrauma
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);
var item = new Item(itemPrefab, position, cargoRoom.Submarine)
{
SpawnedInOutpost = true
};
item.FindHull();
items.Add(item);
@@ -115,7 +118,7 @@ namespace Barotrauma
}
}
public override void Start(Level level)
protected override void StartMissionSpecific(Level level)
{
items.Clear();
parentInventoryIDs.Clear();
@@ -135,6 +138,10 @@ namespace Barotrauma
{
GiveReward();
completed = true;
if (Prefab.LocationTypeChangeOnCompleted != null)
{
ChangeLocationType(Prefab.LocationTypeChangeOnCompleted);
}
}
}
@@ -16,11 +16,11 @@ namespace Barotrauma
get { return false; }
}
private Character.TeamType Winner
private CharacterTeamType Winner
{
get
{
if (GameMain.GameSession?.WinningTeam == null) { return Character.TeamType.None; }
if (GameMain.GameSession?.WinningTeam == null) { return CharacterTeamType.None; }
return GameMain.GameSession.WinningTeam.Value;
}
}
@@ -29,14 +29,14 @@ namespace Barotrauma
{
get
{
if (Winner == Character.TeamType.None || string.IsNullOrEmpty(base.SuccessMessage)) { return ""; }
if (Winner == CharacterTeamType.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;
var loser = Winner == CharacterTeamType.Team1 ?
CharacterTeamType.Team2 :
CharacterTeamType.Team1;
return base.SuccessMessage
.Replace("[loser]", GetTeamName(loser))
@@ -44,11 +44,6 @@ namespace Barotrauma
}
}
public override int TeamCount
{
get { return 2; }
}
public CombatMission(MissionPrefab prefab, Location[] locations)
: base(prefab, locations)
{
@@ -74,13 +69,13 @@ namespace Barotrauma
};
}
public static string GetTeamName(Character.TeamType teamID)
public static string GetTeamName(CharacterTeamType teamID)
{
if (teamID == Character.TeamType.Team1)
if (teamID == CharacterTeamType.Team1)
{
return teamNames.Length > 0 ? teamNames[0] : "Team 1";
}
else if (teamID == Character.TeamType.Team2)
else if (teamID == CharacterTeamType.Team2)
{
return teamNames.Length > 1 ? teamNames[1] : "Team 2";
}
@@ -91,11 +86,11 @@ namespace Barotrauma
public bool IsInWinningTeam(Character character)
{
return character != null &&
Winner != Character.TeamType.None &&
Winner != CharacterTeamType.None &&
Winner == character.TeamID;
}
public override void Start(Level level)
protected override void StartMissionSpecific(Level level)
{
if (GameMain.NetworkMember == null)
{
@@ -104,7 +99,7 @@ namespace Barotrauma
}
subs = new Submarine[] { Submarine.MainSubs[0], Submarine.MainSubs[1] };
subs[0].TeamID = Character.TeamType.Team1; subs[1].TeamID = Character.TeamType.Team2;
subs[0].TeamID = CharacterTeamType.Team1; subs[1].TeamID = CharacterTeamType.Team2;
subs[0].NeutralizeBallast(); subs[1].NeutralizeBallast();
subs[1].SetPosition(subs[1].FindSpawnPos(Level.Loaded.EndPosition));
subs[1].FlipX();
@@ -120,9 +115,9 @@ namespace Barotrauma
public override void End()
{
if (GameMain.NetworkMember == null) return;
if (GameMain.NetworkMember == null) { return; }
if (Winner != Character.TeamType.None)
if (Winner != CharacterTeamType.None)
{
GiveReward();
completed = true;
@@ -14,6 +14,8 @@ namespace Barotrauma
private Dictionary<string, Item[]> RelevantLevelResources { get; } = new Dictionary<string, Item[]>();
private List<Tuple<string, Vector2>> MissionClusterPositions { get; } = new List<Tuple<string, Vector2>>();
private readonly HashSet<Level.Cave> caves = new HashSet<Level.Cave>();
public override IEnumerable<Vector2> SonarPositions
{
get
@@ -42,17 +44,72 @@ namespace Barotrauma
}
}
public override void Start(Level level)
protected override void StartMissionSpecific(Level level)
{
if (SpawnedResources.Any())
{
#if DEBUG
throw new Exception($"SpawnedResources.Count > 0 ({SpawnedResources.Count})");
#else
DebugConsole.AddWarning("Spawned resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
SpawnedResources.Clear();
#endif
}
if (RelevantLevelResources.Any())
{
#if DEBUG
throw new Exception($"RelevantLevelResources.Count > 0 ({RelevantLevelResources.Count})");
#else
DebugConsole.AddWarning("Relevant level resources list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
RelevantLevelResources.Clear();
#endif
}
if (MissionClusterPositions.Any())
{
#if DEBUG
throw new Exception($"MissionClusterPositions.Count > 0 ({MissionClusterPositions.Count})");
#else
DebugConsole.AddWarning("Mission cluster positions list was not empty at the start of a mineral mission. The mission instance may not have been ended correctly on previous rounds.");
MissionClusterPositions.Clear();
#endif
}
caves.Clear();
if (IsClient) { return; }
foreach (var kvp in ResourceClusters)
{
var prefab = ItemPrefab.Find(null, kvp.Key);
if (prefab == null) { continue; }
if (prefab == null)
{
DebugConsole.ThrowError("Error in MineralMission - " +
"couldn't find an item prefab with the identifier " + kvp.Key);
continue;
}
var spawnedResources = level.GenerateMissionResources(prefab, kvp.Value.First, out float rotation);
if (spawnedResources.Count < kvp.Value.First)
{
DebugConsole.ThrowError("Error in MineralMission - " +
"spawned " + spawnedResources.Count + "/" + kvp.Value.First + " of " + prefab.Name);
}
if (spawnedResources.None()) { continue; }
SpawnedResources.Add(kvp.Key, spawnedResources);
kvp.Value.Second = rotation;
foreach (Level.Cave cave in Level.Loaded.Caves)
{
foreach (Item spawnedResource in spawnedResources)
{
if (cave.Area.Contains(spawnedResource.WorldPosition))
{
cave.DisplayOnSonar = true;
caves.Add(cave);
break;
}
}
}
}
CalculateMissionClusterPositions();
FindRelevantLevelResources();
@@ -76,9 +133,29 @@ namespace Barotrauma
public override void End()
{
if (!EnoughHaveBeenCollected()) { return; }
GiveReward();
completed = true;
if (EnoughHaveBeenCollected())
{
if (Prefab.LocationTypeChangeOnCompleted != null)
{
ChangeLocationType(Prefab.LocationTypeChangeOnCompleted);
}
GiveReward();
completed = true;
}
foreach (var kvp in SpawnedResources)
{
foreach (var i in kvp.Value)
{
if (i != null && !i.Removed && !HasBeenCollected(i))
{
i.Remove();
}
}
}
SpawnedResources.Clear();
RelevantLevelResources.Clear();
MissionClusterPositions.Clear();
failed = !completed && state > 0;
}
private void FindRelevantLevelResources()
@@ -1,9 +1,7 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Barotrauma
{
@@ -11,6 +9,9 @@ namespace Barotrauma
{
public readonly MissionPrefab Prefab;
protected bool completed, failed;
protected Level level;
protected int state;
public int State
{
@@ -21,7 +22,7 @@ namespace Barotrauma
{
state = value;
#if SERVER
GameMain.Server?.UpdateMissionState(state);
GameMain.Server?.UpdateMissionState(this, state);
#endif
ShowMessage(State);
}
@@ -85,11 +86,6 @@ namespace Barotrauma
get { return true; }
}
public virtual int TeamCount
{
get { return 1; }
}
public virtual IEnumerable<Vector2> SonarPositions
{
get { return Enumerable.Empty<Vector2>(); }
@@ -180,15 +176,23 @@ namespace Barotrauma
return null;
}
public virtual void Start(Level level) { }
public void Start(Level level)
{
foreach (string categoryToShow in Prefab.UnhideEntitySubCategories)
{
foreach (MapEntity entityToShow in MapEntity.mapEntityList.Where(me => me.prefab.HasSubCategory(categoryToShow)))
{
entityToShow.HiddenInGame = false;
}
}
this.level = level;
StartMissionSpecific(level);
}
protected virtual void StartMissionSpecific(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);
@@ -202,7 +206,10 @@ namespace Barotrauma
public virtual void End()
{
completed = true;
if (Prefab.LocationTypeChangeOnCompleted != null)
{
ChangeLocationType(Prefab.LocationTypeChangeOnCompleted);
}
GiveReward();
}
@@ -234,6 +241,35 @@ namespace Barotrauma
}
}
protected void ChangeLocationType(LocationTypeChange change)
{
if (change == null) { throw new ArgumentException(); }
if (GameMain.GameSession.GameMode is CampaignMode && !IsClient)
{
int srcIndex = -1;
for (int i = 0; i < Locations.Length; i++)
{
if (Locations[i].Type.Identifier.Equals(change.CurrentType, StringComparison.OrdinalIgnoreCase))
{
srcIndex = i;
break;
}
}
if (srcIndex == -1) { return; }
var location = Locations[srcIndex];
if (change.RequiredDurationRange.X > 0)
{
location.PendingLocationTypeChange = (change, Rand.Range(change.RequiredDurationRange.X, change.RequiredDurationRange.Y), Prefab);
}
else
{
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(change.ChangeToType, StringComparison.OrdinalIgnoreCase)));
location.LocationTypeChangeCooldown = change.CooldownAfterChange;
}
}
}
public virtual void AdjustLevelData(LevelData levelData) { }
}
}
@@ -18,7 +18,9 @@ namespace Barotrauma
Nest = 0x10,
Mineral = 0x20,
Combat = 0x40,
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat
AbandonedOutpost = 0x80,
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | AbandonedOutpost
}
partial class MissionPrefab
@@ -33,6 +35,7 @@ namespace Barotrauma
{ MissionType.Beacon, typeof(BeaconMission) },
{ MissionType.Nest, typeof(NestMission) },
{ MissionType.Mineral, typeof(MineralMission) },
{ MissionType.AbandonedOutpost, typeof(AbandonedOutpostMission) },
};
public static readonly Dictionary<MissionType, Type> PvPMissionClasses = new Dictionary<MissionType, Type>()
{
@@ -73,8 +76,26 @@ namespace Barotrauma
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 bool AllowRetry;
public readonly bool IsSideObjective;
/// <summary>
/// The mission can only be received when travelling from Pair.First to Pair.Second
/// </summary>
public readonly List<Pair<string, string>> AllowedConnectionTypes;
/// <summary>
/// The mission can only be received in these location types
/// </summary>
public readonly List<string> AllowedLocationTypes = new List<string>();
/// <summary>
/// Show entities belonging to these sub categories when the mission starts
/// </summary>
public readonly List<string> UnhideEntitySubCategories = new List<string>();
public LocationTypeChange LocationTypeChangeOnCompleted;
public readonly XElement ConfigElement;
@@ -130,7 +151,8 @@ namespace Barotrauma
Name = TextManager.Get("MissionName." + TextIdentifier, true) ?? element.GetAttributeString("name", "");
Description = TextManager.Get("MissionDescription." + TextIdentifier, true) ?? element.GetAttributeString("description", "");
Reward = element.GetAttributeInt("reward", 1);
AllowRetry = element.GetAttributeBool("allowretry", false);
IsSideObjective = element.GetAttributeBool("sideobjective", false);
Commonness = element.GetAttributeInt("commonness", 1);
SuccessMessage = TextManager.Get("MissionSuccess." + TextIdentifier, true) ?? element.GetAttributeString("successmessage", "Mission completed successfully");
@@ -152,9 +174,11 @@ namespace Barotrauma
AchievementIdentifier = element.GetAttributeString("achievementidentifier", "");
UnhideEntitySubCategories = element.GetAttributeStringArray("unhideentitysubcategories", new string[0]).ToList();
Headers = new List<string>();
Messages = new List<string>();
AllowedLocationTypes = new List<Pair<string, string>>();
AllowedConnectionTypes = new List<Pair<string, string>>();
for (int i = 0; i < 100; i++)
{
@@ -183,9 +207,20 @@ namespace Barotrauma
messageIndex++;
break;
case "locationtype":
AllowedLocationTypes.Add(new Pair<string, string>(
subElement.GetAttributeString("from", ""),
subElement.GetAttributeString("to", "")));
case "connectiontype":
if (subElement.Attribute("identifier") != null)
{
AllowedLocationTypes.Add(subElement.GetAttributeString("identifier", ""));
}
else
{
AllowedConnectionTypes.Add(new Pair<string, string>(
subElement.GetAttributeString("from", ""),
subElement.GetAttributeString("to", "")));
}
break;
case "locationtypechange":
LocationTypeChangeOnCompleted = new LocationTypeChange(subElement.GetAttributeString("from", ""), subElement, requireChangeMessages: false, defaultProbability: 1.0f);
break;
case "reputation":
case "reputationreward":
@@ -257,19 +292,32 @@ namespace Barotrauma
public bool IsAllowed(Location from, Location to)
{
foreach (Pair<string, string> allowedLocationType in AllowedLocationTypes)
if (from == to)
{
if (allowedLocationType.First.Equals("any", StringComparison.OrdinalIgnoreCase) ||
allowedLocationType.First.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase))
return
AllowedLocationTypes.Any(lt => lt.Equals("any", StringComparison.OrdinalIgnoreCase)) ||
AllowedLocationTypes.Any(lt => lt.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase));
}
foreach (Pair<string, string> allowedConnectionType in AllowedConnectionTypes)
{
if (allowedConnectionType.First.Equals("any", StringComparison.OrdinalIgnoreCase) ||
allowedConnectionType.First.Equals(from.Type.Identifier, StringComparison.OrdinalIgnoreCase))
{
if (allowedLocationType.Second.Equals("any", StringComparison.OrdinalIgnoreCase) ||
allowedLocationType.Second.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase))
if (allowedConnectionType.Second.Equals("any", StringComparison.OrdinalIgnoreCase) ||
allowedConnectionType.Second.Equals(to.Type.Identifier, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
if (Type == MissionType.Beacon)
{
var connection = from.Connections.Find(c => c.Locations.Contains(from) && c.Locations.Contains(to));
if (connection?.LevelData == null || !connection.LevelData.HasBeaconStation || connection.LevelData.IsBeaconActive) { return false; }
}
return false;
}
@@ -16,6 +16,7 @@ namespace Barotrauma
private readonly float maxSonarMarkerDistance = 10000.0f;
private readonly Level.PositionType spawnPosType;
public override IEnumerable<Vector2> SonarPositions
{
@@ -52,6 +53,13 @@ namespace Barotrauma
maxSonarMarkerDistance = prefab.ConfigElement.GetAttributeFloat("maxsonarmarkerdistance", 10000.0f);
var spawnPosTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
if (string.IsNullOrWhiteSpace(spawnPosTypeStr) ||
!Enum.TryParse(spawnPosTypeStr, true, out spawnPosType))
{
spawnPosType = Level.PositionType.MainPath | Level.PositionType.SidePath;
}
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
{
speciesName = monsterElement.GetAttributeString("character", string.Empty);
@@ -81,22 +89,32 @@ namespace Barotrauma
TextManager.Get("character." + characterParams.SpeciesName));
}
}
public override void Start(Level level)
protected override void StartMissionSpecific(Level level)
{
if (monsters.Count > 0)
{
#if DEBUG
throw new Exception($"monsters.Count > 0 ({monsters.Count})");
#else
DebugConsole.AddWarning("Monster list was not empty at the start of a monster mission. The mission instance may not have been ended correctly on previous rounds.");
monsters.Clear();
#endif
}
if (tempSonarPositions.Count > 0)
{
#if DEBUG
throw new Exception($"tempSonarPositions.Count > 0 ({tempSonarPositions.Count})");
#else
DebugConsole.AddWarning("Sonar position list was not empty at the start of a monster mission. The mission instance may not have been ended correctly on previous rounds.");
tempSonarPositions.Clear();
#endif
}
if (!IsClient)
{
Level.Loaded.TryGetInterestingPosition(true, Level.PositionType.MainPath | Level.PositionType.SidePath, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
Level.Loaded.TryGetInterestingPosition(true, spawnPosType, Level.Loaded.Size.X * 0.3f, out Vector2 spawnPos);
foreach (var monster in monsterPrefabs)
{
int amount = Rand.Range(monster.Item2.X, monster.Item2.Y + 1);
@@ -115,7 +133,7 @@ namespace Barotrauma
foreach (var monster in monsters)
{
monster.Enabled = false;
if (monster.Params.AI.EnforceAggressiveBehaviorForMissions)
if (monster.Params.AI != null && monster.Params.AI.EnforceAggressiveBehaviorForMissions)
{
foreach (var targetParam in monster.Params.AI.Targets)
{
@@ -203,9 +221,17 @@ namespace Barotrauma
tempSonarPositions.Clear();
monsters.Clear();
if (State < 1) { return; }
if (Prefab.LocationTypeChangeOnCompleted != null)
{
ChangeLocationType(Prefab.LocationTypeChangeOnCompleted);
}
GiveReward();
completed = true;
if (level?.LevelData != null && Prefab.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase)))
{
level.LevelData.HasHuntingGrounds = false;
}
}
public bool IsEliminated(Character enemy) =>
@@ -20,7 +20,9 @@ namespace Barotrauma
private readonly float itemSpawnRadius = 800.0f;
private readonly float approachItemsRadius = 1000.0f;
private readonly float nestObjectRadius = 1000.0f;
private readonly float monsterSpawnRadius = 3000.0f;
private readonly int nestObjectAmount = 10;
private readonly bool requireDelivery;
@@ -33,7 +35,14 @@ namespace Barotrauma
{
get
{
yield return nestPosition;
if (State > 0)
{
Enumerable.Empty<Vector2>();
}
else
{
yield return nestPosition;
}
}
}
@@ -46,6 +55,9 @@ namespace Barotrauma
approachItemsRadius = prefab.ConfigElement.GetAttributeFloat("approachitemsradius", itemSpawnRadius * 2.0f);
monsterSpawnRadius = prefab.ConfigElement.GetAttributeFloat("monsterspawnradius", approachItemsRadius * 2.0f);
nestObjectRadius = prefab.ConfigElement.GetAttributeFloat("nestobjectradius", itemSpawnRadius * 2.0f);
nestObjectAmount = prefab.ConfigElement.GetAttributeInt("nestobjectamount", 10);
requireDelivery = prefab.ConfigElement.GetAttributeBool("requiredelivery", false);
string spawnPositionTypeStr = prefab.ConfigElement.GetAttributeString("spawntype", "");
@@ -55,7 +67,6 @@ namespace Barotrauma
spawnPositionType = Level.PositionType.Cave | Level.PositionType.Ruin;
}
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
{
string speciesName = monsterElement.GetAttributeString("character", string.Empty);
@@ -79,8 +90,18 @@ namespace Barotrauma
}
public override void Start(Level level)
protected override void StartMissionSpecific(Level level)
{
if (items.Any())
{
#if DEBUG
throw new Exception($"items.Count > 0 ({items.Count})");
#else
DebugConsole.AddWarning("Item list was not empty at the start of a nest mission. The mission instance may not have been ended correctly on previous rounds.");
items.Clear();
#endif
}
if (!IsClient)
{
//ruin/cave/wreck items are allowed to spawn close to the sub
@@ -90,6 +111,25 @@ namespace Barotrauma
List<GraphEdge> spawnEdges = new List<GraphEdge>();
if (spawnPositionType == Level.PositionType.Cave)
{
Level.Cave closestCave = null;
float closestCaveDist = float.PositiveInfinity;
foreach (var cave in Level.Loaded.Caves)
{
float dist = Vector2.DistanceSquared(nestPosition, cave.Area.Center.ToVector2());
if (dist < closestCaveDist)
{
closestCave = cave;
closestCaveDist = dist;
}
}
if (closestCave != null)
{
closestCave.DisplayOnSonar = true;
SpawnNestObjects(level, closestCave);
#if SERVER
selectedCave = closestCave;
#endif
}
var nearbyCells = Level.Loaded.GetCells(nestPosition, searchDepth: 3);
if (nearbyCells.Any())
{
@@ -171,6 +211,11 @@ namespace Barotrauma
}
}
private void SpawnNestObjects(Level level, Level.Cave cave)
{
level.LevelObjectManager.PlaceNestObjects(level, cave, nestPosition, nestObjectRadius, nestObjectAmount);
}
public override void Update(float deltaTime)
{
if (IsClient)
@@ -258,9 +303,17 @@ namespace Barotrauma
public override void End()
{
if (!AllItemsDestroyedOrRetrieved())
if (AllItemsDestroyedOrRetrieved())
{
return;
GiveReward();
completed = true;
if (completed)
{
if (Prefab.LocationTypeChangeOnCompleted != null)
{
ChangeLocationType(Prefab.LocationTypeChangeOnCompleted);
}
}
}
foreach (Item item in items)
{
@@ -270,8 +323,6 @@ namespace Barotrauma
}
}
items.Clear();
GiveReward();
completed = true;
failed = !completed && state > 0;
}
}
@@ -1,4 +1,5 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
@@ -101,7 +102,7 @@ namespace Barotrauma
}
}
public override void Start(Level level)
protected override void StartMissionSpecific(Level level)
{
#if SERVER
originalInventoryID = Entity.NullEntityID;
@@ -168,10 +169,11 @@ namespace Barotrauma
//try to find a container and place the item inside it
if (!string.IsNullOrEmpty(containerTag) && item.ParentInventory == null)
{
List<ItemContainer> validContainers = new List<ItemContainer>();
foreach (Item it in Item.ItemList)
{
if (!it.HasTag(containerTag)) { continue; }
if (it.NonInteractable) { continue; }
if (!it.IsPlayerTeamInteractable) { continue; }
switch (spawnPositionType)
{
case Level.PositionType.Cave:
@@ -185,15 +187,18 @@ namespace Barotrauma
if (it.Submarine == null || it.Submarine.Info.Type != SubmarineType.Wreck) { continue; }
break;
}
var itemContainer = it.GetComponent<Items.Components.ItemContainer>();
if (itemContainer == null) { continue; }
if (itemContainer.Combine(item, user: null))
var itemContainer = it.GetComponent<ItemContainer>();
if (itemContainer != null && itemContainer.Inventory.CanBePut(item)) { validContainers.Add(itemContainer); }
}
if (validContainers.Any())
{
var selectedContainer = validContainers.GetRandom();
if (selectedContainer.Combine(item, user: null))
{
#if SERVER
originalInventoryID = it.ID;
originalItemContainerIndex = (byte)it.GetComponentIndex(itemContainer);
originalInventoryID = selectedContainer.Item.ID;
originalItemContainerIndex = (byte)selectedContainer.Item.GetComponentIndex(selectedContainer);
#endif
break;
} // Placement successful
}
}
@@ -248,6 +253,11 @@ namespace Barotrauma
return;
}
if (Prefab.LocationTypeChangeOnCompleted != null)
{
ChangeLocationType(Prefab.LocationTypeChangeOnCompleted);
}
item?.Remove();
item = null;
GiveReward();
@@ -16,8 +16,6 @@ namespace Barotrauma
private readonly float scatter;
private readonly float offset;
private readonly bool spawnDeep;
private Vector2? spawnPos;
private readonly bool disallowed;
@@ -73,14 +71,18 @@ namespace Barotrauma
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);
//backwards compatibility
if (prefab.ConfigElement.GetAttributeBool("spawndeep", false))
{
spawnPosType = Level.PositionType.Abyss;
}
offset = prefab.ConfigElement.GetAttributeFloat("offset", 0);
scatter = Math.Clamp(prefab.ConfigElement.GetAttributeFloat("scatter", 1000), 0, 3000);
@@ -138,6 +140,11 @@ namespace Barotrauma
var removals = new List<Level.InterestingPosition>();
foreach (var position in availablePositions)
{
if (SpawnPosFilter != null && !SpawnPosFilter(position))
{
removals.Add(position);
continue;
}
if (position.Submarine != null)
{
if (position.Submarine.WreckAI != null && position.Submarine.WreckAI.IsAlive)
@@ -154,19 +161,10 @@ namespace Barotrauma
{
continue;
}
if (Level.Loaded.ExtraWalls.Any(w => w.Cells.Any(c => c.IsPointInside(position.Position.ToVector2()))))
if (Level.Loaded.ExtraWalls.Any(w => w.IsPointInside(position.Position.ToVector2())))
{
removals.Add(position);
}
if (spawnDeep)
{
for (int i = 0; i < availablePositions.Count; i++)
{
var pos = availablePositions[i].Position;
pos = new Point(pos.X, pos.Y - Level.Loaded.Size.Y);
availablePositions[i] = new Level.InterestingPosition(pos, availablePositions[i].PositionType);
}
}
if (position.Position.Y < Level.Loaded.GetBottomPosition(position.Position.X).Y)
{
removals.Add(position);
@@ -180,33 +178,36 @@ namespace Barotrauma
{
if (disallowed) { return; }
if (Rand.Value(Rand.RandSync.Server) > prefab.SpawnProbability)
{
spawnPos = null;
Finished();
return;
}
spawnPos = Vector2.Zero;
var availablePositions = GetAvailableSpawnPositions();
var chosenPosition = new Level.InterestingPosition(Point.Zero, Level.PositionType.MainPath, isValid: false);
var removedPositions = new List<Level.InterestingPosition>();
foreach (var position in availablePositions)
{
if (Rand.Value(Rand.RandSync.Server) > prefab.SpawnProbability)
{
removedPositions.Add(position);
}
}
removedPositions.ForEach(p => availablePositions.Remove(p));
bool isSubOrWreck = spawnPosType == Level.PositionType.Ruin || spawnPosType == Level.PositionType.Wreck;
if (affectSubImmediately && !isSubOrWreck)
if (affectSubImmediately && !isSubOrWreck && spawnPosType != Level.PositionType.Abyss)
{
if (availablePositions.None())
{
//no suitable position found, disable the event
spawnPos = null;
Finished();
return;
}
Submarine refSub = GetReferenceSub();
if (Submarine.MainSubs.Length == 2 && Submarine.MainSubs[1] != null)
{
refSub = Submarine.MainSubs.GetRandom(Rand.RandSync.Unsynced);
}
float closestDist = float.PositiveInfinity;
//find the closest spawnposition that isn't too close to any of the subs
foreach (var position in availablePositions)
{
Vector2 pos = position.Position.ToVector2();
Submarine refSub = GetReferenceSub();
float dist = Vector2.DistanceSquared(pos, refSub.WorldPosition);
foreach (Submarine sub in Submarine.Loaded)
{
@@ -248,7 +249,7 @@ namespace Barotrauma
{
foreach (var position in availablePositions)
{
float dist = Vector2.DistanceSquared(position.Position.ToVector2(), GetReferenceSub().WorldPosition);
float dist = Vector2.DistanceSquared(position.Position.ToVector2(), refSub.WorldPosition);
if (dist < closestDist)
{
closestDist = dist;
@@ -262,11 +263,21 @@ namespace Barotrauma
if (!isSubOrWreck)
{
float minDistance = 20000;
availablePositions.RemoveAll(p => Vector2.DistanceSquared(GetReferenceSub().WorldPosition, p.Position.ToVector2()) < minDistance * minDistance);
var refSub = GetReferenceSub();
availablePositions.RemoveAll(p => Vector2.DistanceSquared(refSub.WorldPosition, p.Position.ToVector2()) < minDistance * minDistance);
if (Submarine.MainSubs.Length > 1)
{
for (int i = 1; i < Submarine.MainSubs.Length; i++)
{
if (Submarine.MainSubs[i] == null) { continue; }
availablePositions.RemoveAll(p => Vector2.DistanceSquared(Submarine.MainSubs[i].WorldPosition, p.Position.ToVector2()) < minDistance * minDistance);
}
}
}
if (availablePositions.None())
{
//no suitable position found, disable the event
spawnPos = null;
Finished();
return;
}
@@ -335,6 +346,8 @@ namespace Barotrauma
if (spawnPos == null)
{
FindSpawnPosition(affectSubImmediately: true);
//the event gets marked as finished if a spawn point is not found
if (isFinished) { return; }
spawnPending = true;
}
@@ -342,7 +355,7 @@ namespace Barotrauma
if (spawnPending)
{
//wait until there are no submarines at the spawnpos
if (spawnPosType == Level.PositionType.MainPath)
if (spawnPosType == Level.PositionType.MainPath || spawnPosType == Level.PositionType.SidePath || spawnPosType == Level.PositionType.Abyss)
{
foreach (Submarine submarine in Submarine.Loaded)
{
@@ -381,6 +394,19 @@ namespace Barotrauma
if (!someoneNearby) { return; }
}
if (spawnPosType == Level.PositionType.Abyss || spawnPosType == Level.PositionType.AbyssCave)
{
foreach (Submarine submarine in Submarine.Loaded)
{
if (submarine.Info.Type != SubmarineType.Player) { continue; }
if (submarine.WorldPosition.Y > Level.Loaded.AbyssStart)
{
return;
}
}
}
spawnPending = false;
//+1 because Range returns an integer less than the max value
@@ -412,7 +438,16 @@ namespace Barotrauma
}
}
monsters.Add(Character.Create(speciesName, pos, seed, characterInfo: null, isRemotePlayer: false, hasAi: true, createNetworkEvent: true));
Character createdCharacter = Character.Create(speciesName, pos, seed, characterInfo: null, isRemotePlayer: false, hasAi: true, createNetworkEvent: true);
if (GameMain.GameSession.IsCurrentLocationRadiated())
{
AfflictionPrefab radiationPrefab = AfflictionPrefab.RadiationSickness;
Affliction affliction = new Affliction(radiationPrefab, radiationPrefab.MaxStrength);
createdCharacter?.CharacterHealth.ApplyAffliction(null, affliction);
// TODO test multiplayer
createdCharacter?.Kill(CauseOfDeathType.Affliction, affliction, log: false);
}
monsters.Add(createdCharacter);
if (monsters.Count == amount)
{
@@ -421,7 +456,7 @@ namespace Barotrauma
//otherwise it'll make the spawned characters act as a swarm
SwarmBehavior.CreateSwarm(monsters.Cast<AICharacter>());
}
}, Rand.Range(0f, amount / 2));
}, Rand.Range(0f, amount / 2f));
}
}
@@ -13,6 +13,9 @@ namespace Barotrauma
private int prevEntityCount;
private int prevPlayerCount, prevBotCount;
private readonly string[] requiredDestinationTypes;
public readonly bool RequireBeaconStation;
public int CurrentActionIndex { get; private set; }
public List<EventAction> Actions { get; } = new List<EventAction>();
public Dictionary<string, List<Entity>> Targets { get; } = new Dictionary<string, List<Entity>>();
@@ -39,6 +42,9 @@ namespace Barotrauma
{
DebugConsole.ThrowError($"Scripted event \"{prefab.Identifier}\" has no actions. The event will do nothing.");
}
requiredDestinationTypes = prefab.ConfigElement.GetAttributeStringArray("requireddestinationtypes", null);
RequireBeaconStation = prefab.ConfigElement.GetAttributeBool("requirebeaconstation", false);
}
public void AddTarget(string tag, Entity target)
@@ -199,5 +205,21 @@ namespace Barotrauma
currentAction.Update(deltaTime);
}
}
public override bool LevelMeetsRequirements()
{
if (requiredDestinationTypes == null) { return true; }
var currLocation = GameMain.GameSession?.Campaign?.Map.CurrentLocation;
if (currLocation?.Connections == null) { return true; }
foreach (LocationConnection c in currLocation.Connections)
{
if (RequireBeaconStation && !c.LevelData.HasBeaconStation) { continue; }
if (requiredDestinationTypes.Any(t => c.OtherLocation(currLocation).Type.Identifier.Equals(t, StringComparison.OrdinalIgnoreCase)))
{
return true;
}
}
return false;
}
}
}