v0.12.0.2

This commit is contained in:
Joonas Rikkonen
2021-02-10 17:08:21 +02:00
parent 5c80a59bdd
commit 694cdfee7b
353 changed files with 12897 additions and 5028 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; }
@@ -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; }
@@ -104,19 +111,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,7 +185,7 @@ 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)
{
@@ -255,7 +269,12 @@ namespace Barotrauma
}
else
{
if (Options.Any())
if (ShouldInterrupt())
{
ResetSpeaker();
interrupt = true;
}
else if (Options.Any())
{
Options[selectedOption].Update(deltaTime);
}
@@ -335,6 +354,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;
}
@@ -109,14 +109,14 @@ 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; }
item.SpawnedInOutpost = true;
}
}
newCharacter.CharacterHealth.MaxVitality *= humanPrefab.HealthMultiplier;
@@ -200,12 +200,18 @@ 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;
newItem?.SetIgnoreByAI(IgnoreByAI);
}
}
}
@@ -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;
@@ -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>>();
@@ -144,6 +151,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 +268,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 +342,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++)
{
@@ -358,6 +383,7 @@ namespace Barotrauma
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))
{
@@ -442,6 +468,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)
{
@@ -493,6 +527,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)
{
@@ -500,9 +553,6 @@ namespace Barotrauma
calculateDistanceTraveledTimer = CalculateDistanceTraveledInterval;
}
eventThreshold += settings.EventThresholdIncrease * deltaTime;
eventCoolDown -= deltaTime;
if (currentIntensity < eventThreshold)
{
bool recheck = false;
@@ -526,7 +576,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;
}
}
}
@@ -563,7 +616,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);
@@ -586,9 +639,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)))
{
@@ -681,7 +733,6 @@ namespace Barotrauma
}
}
/// <summary>
/// Finds all actions in a ScriptedEvent
/// </summary>
@@ -750,5 +801,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);
}
}
}
@@ -10,6 +10,7 @@ namespace Barotrauma
public readonly Type EventType;
public readonly string MusicType;
public readonly float SpawnProbability;
public readonly bool TriggerEventCooldown;
public float Commonness;
public string Identifier;
@@ -35,6 +36,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()
@@ -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())
@@ -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);
@@ -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,7 +86,7 @@ namespace Barotrauma
public bool IsInWinningTeam(Character character)
{
return character != null &&
Winner != Character.TeamType.None &&
Winner != CharacterTeamType.None &&
Winner == character.TeamID;
}
@@ -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();
@@ -122,7 +117,7 @@ namespace Barotrauma
{
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
@@ -74,6 +76,8 @@ namespace Barotrauma
#endif
}
caves.Clear();
if (IsClient) { return; }
foreach (var kvp in ResourceClusters)
{
@@ -93,6 +97,19 @@ namespace Barotrauma
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();
@@ -85,11 +85,6 @@ namespace Barotrauma
get { return true; }
}
public virtual int TeamCount
{
get { return 1; }
}
public virtual IEnumerable<Vector2> SonarPositions
{
get { return Enumerable.Empty<Vector2>(); }
@@ -184,11 +179,6 @@ namespace Barotrauma
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);
@@ -238,7 +228,16 @@ namespace Barotrauma
{
if (GameMain.GameSession.GameMode is CampaignMode && !IsClient)
{
int srcIndex = Locations[0].Type.Identifier.Equals(from, StringComparison.OrdinalIgnoreCase) ? 0 : 1;
int srcIndex = -1;
for (int i = 0; i < Locations.Length; i++)
{
if (Locations[i].Type.Identifier.Equals(from, StringComparison.OrdinalIgnoreCase))
{
srcIndex = i;
break;
}
}
if (srcIndex == -1) { return; }
var upgradeLocation = Locations[srcIndex];
upgradeLocation.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(to, StringComparison.OrdinalIgnoreCase)));
}
@@ -125,7 +125,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)
{
@@ -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;
@@ -53,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", "");
@@ -62,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);
@@ -107,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())
{
@@ -188,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)
@@ -279,6 +307,10 @@ namespace Barotrauma
{
GiveReward();
completed = true;
if (completed)
{
ChangeLocationType("None", "Explored");
}
}
foreach (Item item in items)
{
@@ -1,4 +1,5 @@
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
@@ -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
}
}
@@ -138,6 +138,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)
@@ -180,33 +185,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 (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 +256,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 +270,20 @@ 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++)
{
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 +352,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;
}