Unstable 0.1400.1.0
This commit is contained in:
+138
-35
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -15,6 +16,10 @@ namespace Barotrauma
|
||||
protected readonly HashSet<Character> requireKill = new HashSet<Character>();
|
||||
protected readonly HashSet<Character> requireRescue = new HashSet<Character>();
|
||||
|
||||
private readonly string itemTag;
|
||||
private readonly XElement itemConfig;
|
||||
private readonly List<Item> items = new List<Item>();
|
||||
|
||||
protected const int HostagesKilledState = 5;
|
||||
|
||||
private readonly string hostagesKilledMessage;
|
||||
@@ -33,6 +38,43 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Targets.Select(t => t.WorldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Entity> Targets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
return Enumerable.Empty<Entity>();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (items.Any())
|
||||
{
|
||||
return items.Where(it => !it.Removed && it.Condition > 0.0f).Cast<Entity>().Concat(requireKill.Where(c => !c.Removed && !c.IsDead)).Concat(requireRescue);
|
||||
}
|
||||
else
|
||||
{
|
||||
return requireKill.Concat(requireRescue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected bool wasDocked;
|
||||
|
||||
public AbandonedOutpostMission(MissionPrefab prefab, Location[] locations, Submarine sub) :
|
||||
@@ -42,6 +84,9 @@ namespace Barotrauma
|
||||
|
||||
string msgTag = prefab.ConfigElement.GetAttributeString("hostageskilledmessage", "");
|
||||
hostagesKilledMessage = TextManager.Get(msgTag, returnNull: true) ?? msgTag;
|
||||
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
itemTag = prefab.ConfigElement.GetAttributeString("targetitem", "");
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
@@ -52,8 +97,13 @@ namespace Barotrauma
|
||||
characterItems.Clear();
|
||||
requireKill.Clear();
|
||||
requireRescue.Clear();
|
||||
items.Clear();
|
||||
#if SERVER
|
||||
spawnedItems.Clear();
|
||||
#endif
|
||||
|
||||
var submarine = Submarine.Loaded.Find(s => s.Info.Type == SubmarineType.Outpost) ?? Submarine.MainSub;
|
||||
InitItems(submarine);
|
||||
if (!IsClient)
|
||||
{
|
||||
InitCharacters(submarine);
|
||||
@@ -62,49 +112,101 @@ namespace Barotrauma
|
||||
wasDocked = Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost);
|
||||
}
|
||||
|
||||
private void InitItems(Submarine submarine)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(itemTag))
|
||||
{
|
||||
var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag));
|
||||
if (!itemsToDestroy.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".");
|
||||
}
|
||||
else
|
||||
{
|
||||
items.AddRange(itemsToDestroy);
|
||||
}
|
||||
}
|
||||
|
||||
if (itemConfig != null && !IsClient)
|
||||
{
|
||||
foreach (XElement element in itemConfig.Elements())
|
||||
{
|
||||
string itemIdentifier = element.GetAttributeString("identifier", "");
|
||||
if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier + "\" not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] moduleFlags = element.GetAttributeStringArray("moduleflags", null);
|
||||
string[] spawnPointTags = element.GetAttributeStringArray("spawnpointtags", null);
|
||||
ISpatialEntity spawnPoint = SpawnAction.GetSpawnPos(
|
||||
SpawnAction.SpawnLocationType.Outpost, SpawnType.Human | SpawnType.Enemy,
|
||||
moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false));
|
||||
if (spawnPoint == null)
|
||||
{
|
||||
spawnPoint = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom();
|
||||
}
|
||||
Vector2 spawnPos = spawnPoint.WorldPosition;
|
||||
if (spawnPoint is WayPoint wp && wp.CurrentHull != null && wp.CurrentHull.Rect.Width > 100)
|
||||
{
|
||||
spawnPos = new Vector2(
|
||||
MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 200), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50),
|
||||
wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 16.0f);
|
||||
}
|
||||
var item = new Item(itemPrefab, spawnPos, null);
|
||||
items.Add(item);
|
||||
#if SERVER
|
||||
spawnedItems.Add(item);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitCharacters(Submarine submarine)
|
||||
{
|
||||
characters.Clear();
|
||||
characterItems.Clear();
|
||||
|
||||
if (characterConfig == null) { return; }
|
||||
|
||||
foreach (XElement element in characterConfig.Elements())
|
||||
{
|
||||
if (GameMain.NetworkMember == null && element.GetAttributeBool("multiplayeronly", false)) { continue; }
|
||||
|
||||
int defaultCount = element.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
if (characterConfig != null)
|
||||
{
|
||||
foreach (XElement element in characterConfig.Elements())
|
||||
{
|
||||
defaultCount = element.GetAttributeInt("amount", 1);
|
||||
}
|
||||
int min = Math.Min(element.GetAttributeInt("min", defaultCount), 255);
|
||||
int max = Math.Min(Math.Max(min, element.GetAttributeInt("max", defaultCount)), 255);
|
||||
int count = Rand.Range(min, max + 1);
|
||||
if (GameMain.NetworkMember == null && element.GetAttributeBool("multiplayeronly", false)) { continue; }
|
||||
|
||||
if (element.Attribute("identifier") != null && element.Attribute("from") != null)
|
||||
{
|
||||
HumanPrefab humanPrefab = CreateHumanPrefabFromElement(element);
|
||||
for (int i = 0; i < count; i++)
|
||||
int defaultCount = element.GetAttributeInt("count", -1);
|
||||
if (defaultCount < 0)
|
||||
{
|
||||
LoadHuman(humanPrefab, element, submarine);
|
||||
defaultCount = element.GetAttributeInt("amount", 1);
|
||||
}
|
||||
int min = Math.Min(element.GetAttributeInt("min", defaultCount), 255);
|
||||
int max = Math.Min(Math.Max(min, element.GetAttributeInt("max", defaultCount)), 255);
|
||||
int count = Rand.Range(min, max + 1);
|
||||
|
||||
if (element.Attribute("identifier") != null && element.Attribute("from") != null)
|
||||
{
|
||||
HumanPrefab humanPrefab = CreateHumanPrefabFromElement(element);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
LoadHuman(humanPrefab, element, submarine);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string speciesName = element.GetAttributeString("character", element.GetAttributeString("identifier", ""));
|
||||
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
if (characterPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn a character for abandoned outpost mission: character prefab \"" + speciesName + "\" not found");
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
LoadMonster(characterPrefab, element, submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string speciesName = element.GetAttributeString("character", element.GetAttributeString("identifier", ""));
|
||||
var characterPrefab = CharacterPrefab.FindBySpeciesName(speciesName);
|
||||
if (characterPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn a character for abandoned outpost mission: character prefab \"" + speciesName + "\" not found");
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
LoadMonster(characterPrefab, element, submarine);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadHuman(HumanPrefab humanPrefab, XElement element, Submarine submarine)
|
||||
@@ -175,7 +277,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (State != HostagesKilledState)
|
||||
{
|
||||
@@ -203,7 +305,8 @@ namespace Barotrauma
|
||||
{
|
||||
case 0:
|
||||
|
||||
if (requireKill.All(c => c.Removed || c.IsDead) &&
|
||||
if (items.All(it => it.Removed || it.Condition <= 0.0f) &&
|
||||
requireKill.All(c => c.Removed || c.IsDead) &&
|
||||
requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player))
|
||||
{
|
||||
State = 1;
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (IsClient) { return; }
|
||||
if (!swarmSpawned && level.CheckBeaconActive())
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Barotrauma
|
||||
private readonly Dictionary<Item, UInt16> parentInventoryIDs = new Dictionary<Item, UInt16>();
|
||||
private readonly Dictionary<Item, byte> parentItemContainerIndices = new Dictionary<Item, byte>();
|
||||
|
||||
private int requiredDeliveryAmount;
|
||||
private float requiredDeliveryAmount;
|
||||
|
||||
private readonly List<(XElement element, ItemContainer container)> itemsToSpawn = new List<(XElement element, ItemContainer container)>();
|
||||
private int? rewardPerCrate;
|
||||
@@ -29,7 +29,7 @@ namespace Barotrauma
|
||||
{
|
||||
this.sub = sub;
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
requiredDeliveryAmount = prefab.ConfigElement.GetAttributeInt("requireddeliveryamount", 0);
|
||||
requiredDeliveryAmount = Math.Min(prefab.ConfigElement.GetAttributeFloat("requireddeliveryamount", 0.9f), 1.0f);
|
||||
DetermineCargo();
|
||||
}
|
||||
|
||||
@@ -123,12 +123,7 @@ namespace Barotrauma
|
||||
LoadItemAsChild(element, container?.Item);
|
||||
}
|
||||
|
||||
if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; }
|
||||
if (requiredDeliveryAmount > items.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Error in mission \"{Prefab.Identifier}\". Required delivery amount is {requiredDeliveryAmount} but there's only {items.Count} items to deliver.");
|
||||
requiredDeliveryAmount = items.Count;
|
||||
}
|
||||
if (requiredDeliveryAmount <= 0.0f) { requiredDeliveryAmount = 1.0f; }
|
||||
}
|
||||
|
||||
private ItemPrefab FindItemPrefab(XElement element)
|
||||
@@ -220,7 +215,7 @@ namespace Barotrauma
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit)
|
||||
{
|
||||
int deliveredItemCount = items.Count(i => i.CurrentHull != null && !i.Removed && i.Condition > 0.0f);
|
||||
if (deliveredItemCount >= requiredDeliveryAmount)
|
||||
if (deliveredItemCount / (float)items.Count >= requiredDeliveryAmount)
|
||||
{
|
||||
GiveReward();
|
||||
completed = true;
|
||||
|
||||
@@ -39,13 +39,10 @@ namespace Barotrauma
|
||||
itemConfig = prefab.ConfigElement.Element("TerroristItems");
|
||||
}
|
||||
|
||||
public override int Reward
|
||||
public override int GetReward(Submarine sub)
|
||||
{
|
||||
get
|
||||
{
|
||||
int multiplier = CalculateScalingEscortedCharacterCount();
|
||||
return Prefab.Reward * multiplier;
|
||||
}
|
||||
int multiplier = CalculateScalingEscortedCharacterCount();
|
||||
return Prefab.Reward * multiplier;
|
||||
}
|
||||
|
||||
int CalculateScalingEscortedCharacterCount(bool inMission = false)
|
||||
@@ -58,7 +55,7 @@ namespace Barotrauma
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return (int)Math.Round(baseEscortedCharacters + scalingEscortedCharacters * Submarine.MainSub.Info.RecommendedCrewSizeMin);
|
||||
return (int)Math.Round(baseEscortedCharacters + scalingEscortedCharacters * (Submarine.MainSub.Info.RecommendedCrewSizeMin + Submarine.MainSub.Info.RecommendedCrewSizeMax) / 2);
|
||||
}
|
||||
|
||||
private void InitEscort()
|
||||
@@ -88,6 +85,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (terroristChance > 0f)
|
||||
{
|
||||
int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count);
|
||||
terroristCount = Math.Clamp(terroristCount, 1, characters.Count);
|
||||
|
||||
terroristCharacters.Clear();
|
||||
characters.GetRange(0, terroristCount).ForEach(c => terroristCharacters.Add(c));
|
||||
|
||||
terroristDistanceSquared = Vector2.DistanceSquared(Level.Loaded.StartPosition, Level.Loaded.EndPosition) * Rand.Range(0.35f, 0.65f);
|
||||
|
||||
#if DEBUG
|
||||
DebugConsole.AddWarning("Terrorists will trigger at range " + Math.Sqrt(terroristDistanceSquared));
|
||||
foreach (Character character in terroristCharacters)
|
||||
{
|
||||
DebugConsole.AddWarning(character.Name + " is a terrorist.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private void InitCharacters()
|
||||
@@ -120,26 +136,6 @@ namespace Barotrauma
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!IsClient && terroristChance > 0f)
|
||||
{
|
||||
int terroristCount = (int)Math.Ceiling(terroristChance * Rand.Range(0.8f, 1.2f) * characters.Count);
|
||||
terroristCount = Math.Clamp(terroristCount, 1, characters.Count);
|
||||
|
||||
terroristCharacters.Clear();
|
||||
characters.Shuffle();
|
||||
characters.GetRange(0, terroristCount).ForEach(c => terroristCharacters.Add(c));
|
||||
|
||||
terroristDistanceSquared = Vector2.DistanceSquared(Level.Loaded.StartPosition, Level.Loaded.EndPosition) * Rand.Range(0.35f, 0.65f);
|
||||
|
||||
#if DEBUG
|
||||
DebugConsole.AddWarning("Terrorists will trigger at range " + Math.Sqrt(terroristDistanceSquared));
|
||||
foreach (Character character in terroristCharacters)
|
||||
{
|
||||
DebugConsole.AddWarning(character.Name + " is a terrorist.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
@@ -207,10 +203,10 @@ namespace Barotrauma
|
||||
|
||||
bool NonTerroristsStillAlive(IEnumerable<Character> characterList)
|
||||
{
|
||||
return characterList.Any(c => !terroristCharacters.Contains(c) && IsAlive(c));
|
||||
return characterList.All(c => terroristCharacters.Contains(c) || IsAlive(c));
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (!IsClient)
|
||||
{
|
||||
@@ -261,9 +257,10 @@ namespace Barotrauma
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.AtEndExit)
|
||||
{
|
||||
bool terroristsSurvived = terroristCharacters.Any(c => Survived(c) && !IsCaptured(c));
|
||||
bool friendliesSurvived = characters.Except(terroristCharacters).Any(c => Survived(c));
|
||||
bool friendliesSurvived = characters.Except(terroristCharacters).All(c => Survived(c));
|
||||
bool vipDied = false;
|
||||
|
||||
// this logic is currently irrelevant, as the mission is failed regardless of who dies
|
||||
if (vipCharacter != null)
|
||||
{
|
||||
vipDied = !Survived(vipCharacter);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class GoToMission : Mission
|
||||
{
|
||||
public GoToMission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
: base(prefab, locations, sub)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
State = 1;
|
||||
}
|
||||
|
||||
#if CLIENT
|
||||
public override void ClientReadInitial(IReadMessage msg)
|
||||
{
|
||||
}
|
||||
#elif SERVER
|
||||
|
||||
public override void ServerWriteInitial(IWriteMessage msg, Client c)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -115,7 +115,7 @@ namespace Barotrauma
|
||||
FindRelevantLevelResources();
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (IsClient) { return; }
|
||||
switch (State)
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Barotrauma
|
||||
if (state != value)
|
||||
{
|
||||
state = value;
|
||||
TryTriggerEvents(state);
|
||||
#if SERVER
|
||||
GameMain.Server?.UpdateMissionState(this, state);
|
||||
#endif
|
||||
@@ -128,6 +129,20 @@ namespace Barotrauma
|
||||
{
|
||||
get { return Prefab.Difficulty; }
|
||||
}
|
||||
|
||||
private class DelayedTriggerEvent
|
||||
{
|
||||
public readonly MissionPrefab.TriggerEvent TriggerEvent;
|
||||
public float Delay;
|
||||
|
||||
public DelayedTriggerEvent(MissionPrefab.TriggerEvent triggerEvent, float delay)
|
||||
{
|
||||
TriggerEvent = triggerEvent;
|
||||
Delay = delay;
|
||||
}
|
||||
}
|
||||
|
||||
private List<DelayedTriggerEvent> delayedTriggerEvents = new List<DelayedTriggerEvent>();
|
||||
|
||||
public Mission(MissionPrefab prefab, Location[] locations, Submarine sub)
|
||||
{
|
||||
@@ -167,6 +182,9 @@ namespace Barotrauma
|
||||
Messages[m] = Messages[m].Replace("[reward]", rewardText);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void SetDifficulty(float difficulty) { }
|
||||
|
||||
public static Mission LoadRandom(Location[] locations, string seed, bool requireCorrectLocationType, MissionType missionType, bool isSinglePlayer = false)
|
||||
{
|
||||
return LoadRandom(locations, new MTRandom(ToolBox.StringToInt(seed)), requireCorrectLocationType, missionType, isSinglePlayer);
|
||||
@@ -216,9 +234,11 @@ namespace Barotrauma
|
||||
|
||||
public void Start(Level level)
|
||||
{
|
||||
state = 0;
|
||||
#if CLIENT
|
||||
shownMessages.Clear();
|
||||
#endif
|
||||
delayedTriggerEvents.Clear();
|
||||
foreach (string categoryToShow in Prefab.UnhideEntitySubCategories)
|
||||
{
|
||||
foreach (MapEntity entityToShow in MapEntity.mapEntityList.Where(me => me.prefab?.HasSubCategory(categoryToShow) ?? false))
|
||||
@@ -227,12 +247,27 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
this.level = level;
|
||||
TryTriggerEvents(0);
|
||||
StartMissionSpecific(level);
|
||||
}
|
||||
|
||||
protected virtual void StartMissionSpecific(Level level) { }
|
||||
|
||||
public virtual void Update(float deltaTime) { }
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
for (int i = delayedTriggerEvents.Count - 1; i>=0;i--)
|
||||
{
|
||||
delayedTriggerEvents[i].Delay -= deltaTime;
|
||||
if (delayedTriggerEvents[i].Delay <= 0.0f)
|
||||
{
|
||||
TriggerEvent(delayedTriggerEvents[i].TriggerEvent);
|
||||
delayedTriggerEvents.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
UpdateMissionSpecific(deltaTime);
|
||||
}
|
||||
|
||||
protected virtual void UpdateMissionSpecific(float deltaTime) { }
|
||||
|
||||
protected void ShowMessage(int missionState)
|
||||
{
|
||||
@@ -241,6 +276,55 @@ namespace Barotrauma
|
||||
|
||||
partial void ShowMessageProjSpecific(int missionState);
|
||||
|
||||
|
||||
private void TryTriggerEvents(int state)
|
||||
{
|
||||
foreach (var triggerEvent in Prefab.TriggerEvents)
|
||||
{
|
||||
if (triggerEvent.State == state)
|
||||
{
|
||||
TryTriggerEvent(triggerEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the event or adds it to the delayedTriggerEvents it if it has a delay
|
||||
/// </summary>
|
||||
private void TryTriggerEvent(MissionPrefab.TriggerEvent trigger)
|
||||
{
|
||||
if (trigger.Delay > 0)
|
||||
{
|
||||
if (!delayedTriggerEvents.Any(t => t.TriggerEvent == trigger))
|
||||
{
|
||||
delayedTriggerEvents.Add(new DelayedTriggerEvent(trigger, trigger.Delay));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TriggerEvent(trigger);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the event immediately, ignoring any delays
|
||||
/// </summary>
|
||||
private void TriggerEvent(MissionPrefab.TriggerEvent trigger)
|
||||
{
|
||||
var eventPrefab = EventSet.GetAllEventPrefabs().Find(p => p.Identifier.Equals(trigger.EventIdentifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (eventPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Mission \"{Name}\" failed to trigger an event (couldn't find an event with the identifier \"{trigger.EventIdentifier}\").");
|
||||
return;
|
||||
}
|
||||
if (GameMain.GameSession?.EventManager != null)
|
||||
{
|
||||
var newEvent = eventPrefab.CreateInstance();
|
||||
GameMain.GameSession.EventManager.ActiveEvents.Add(newEvent);
|
||||
newEvent.Init(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// End the mission and give a reward if it was completed successfully
|
||||
/// </summary>
|
||||
@@ -344,7 +428,7 @@ namespace Barotrauma
|
||||
{
|
||||
positionToStayIn = WayPoint.GetRandom(SpawnType.Human, null, submarine);
|
||||
}
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync);
|
||||
var characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, npcIdentifier: humanPrefab.Identifier, jobPrefab: humanPrefab.GetJobPrefab(humanPrefabRandSync), randSync: humanPrefabRandSync);
|
||||
characterInfo.TeamID = teamType;
|
||||
Character spawnedCharacter = Character.Create(characterInfo.SpeciesName, positionToStayIn.WorldPosition, ToolBox.RandomSeed(8), characterInfo, createNetworkEvent: false);
|
||||
humanPrefab.InitializeCharacter(spawnedCharacter, positionToStayIn);
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace Barotrauma
|
||||
Nest = 0x10,
|
||||
Mineral = 0x20,
|
||||
Combat = 0x40,
|
||||
OutpostDestroy = 0x80,
|
||||
OutpostRescue = 0x100,
|
||||
Escort = 0x200,
|
||||
Pirate = 0x400,
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | OutpostDestroy | OutpostRescue | Escort | Pirate
|
||||
AbandonedOutpost = 0x80,
|
||||
Escort = 0x100,
|
||||
Pirate = 0x200,
|
||||
GoTo = 0x400,
|
||||
All = Salvage | Monster | Cargo | Beacon | Nest | Mineral | Combat | AbandonedOutpost | Escort | Pirate | GoTo
|
||||
}
|
||||
|
||||
partial class MissionPrefab
|
||||
@@ -37,15 +37,17 @@ namespace Barotrauma
|
||||
{ MissionType.Beacon, typeof(BeaconMission) },
|
||||
{ MissionType.Nest, typeof(NestMission) },
|
||||
{ MissionType.Mineral, typeof(MineralMission) },
|
||||
{ MissionType.OutpostDestroy, typeof(OutpostDestroyMission) },
|
||||
{ MissionType.OutpostRescue, typeof(AbandonedOutpostMission) },
|
||||
{ MissionType.AbandonedOutpost, typeof(AbandonedOutpostMission) },
|
||||
{ MissionType.Escort, typeof(EscortMission) },
|
||||
{ MissionType.Pirate, typeof(PirateMission) }
|
||||
{ MissionType.Pirate, typeof(PirateMission) },
|
||||
{ MissionType.GoTo, typeof(GoToMission) }
|
||||
};
|
||||
public static readonly Dictionary<MissionType, Type> PvPMissionClasses = new Dictionary<MissionType, Type>()
|
||||
{
|
||||
{ MissionType.Combat, typeof(CombatMission) }
|
||||
};
|
||||
|
||||
public static readonly HashSet<MissionType> HiddenMissionClasses = new HashSet<MissionType>() { MissionType.GoTo };
|
||||
|
||||
private readonly ConstructorInfo constructor;
|
||||
|
||||
@@ -87,6 +89,8 @@ namespace Barotrauma
|
||||
|
||||
public readonly bool IsSideObjective;
|
||||
|
||||
public readonly bool RequireWreck;
|
||||
|
||||
/// <summary>
|
||||
/// The mission can only be received when travelling from Pair.First to Pair.Second
|
||||
/// </summary>
|
||||
@@ -102,6 +106,25 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public readonly List<string> UnhideEntitySubCategories = new List<string>();
|
||||
|
||||
public class TriggerEvent
|
||||
{
|
||||
[Serialize("", true)]
|
||||
public string EventIdentifier { get; private set; }
|
||||
|
||||
[Serialize(0, true)]
|
||||
public int State { get; private set; }
|
||||
|
||||
[Serialize(0.0f, true)]
|
||||
public float Delay { get; private set; }
|
||||
|
||||
public TriggerEvent(XElement element)
|
||||
{
|
||||
SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly List<TriggerEvent> TriggerEvents = new List<TriggerEvent>();
|
||||
|
||||
public LocationTypeChange LocationTypeChangeOnCompleted;
|
||||
|
||||
public readonly XElement ConfigElement;
|
||||
@@ -160,6 +183,7 @@ namespace Barotrauma
|
||||
Reward = element.GetAttributeInt("reward", 1);
|
||||
AllowRetry = element.GetAttributeBool("allowretry", false);
|
||||
IsSideObjective = element.GetAttributeBool("sideobjective", false);
|
||||
RequireWreck = element.GetAttributeBool("requirewreck", false);
|
||||
Commonness = element.GetAttributeInt("commonness", 1);
|
||||
if (element.GetAttribute("difficulty") != null)
|
||||
{
|
||||
@@ -272,10 +296,19 @@ namespace Barotrauma
|
||||
DataRewards.Add(Tuple.Create(identifier, value, operation));
|
||||
}
|
||||
break;
|
||||
case "triggerevent":
|
||||
TriggerEvents.Add(new TriggerEvent(subElement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
string missionTypeName = element.GetAttributeString("type", "");
|
||||
//backwards compatibility
|
||||
if (missionTypeName.Equals("outpostdestroy", StringComparison.OrdinalIgnoreCase) || missionTypeName.Equals("outpostrescue", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
missionTypeName = "AbandonedOutpost";
|
||||
}
|
||||
|
||||
if (!Enum.TryParse(missionTypeName, out Type))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in mission prefab \"" + Name + "\" - \"" + missionTypeName + "\" is not a valid mission type.");
|
||||
|
||||
@@ -160,7 +160,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
switch (State)
|
||||
{
|
||||
|
||||
@@ -216,7 +216,7 @@ namespace Barotrauma
|
||||
level.LevelObjectManager.PlaceNestObjects(level, cave, nestPosition, nestObjectRadius, nestObjectAmount);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (IsClient)
|
||||
{
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class OutpostDestroyMission : AbandonedOutpostMission
|
||||
{
|
||||
private readonly string itemTag;
|
||||
private readonly XElement itemConfig;
|
||||
private readonly List<Item> items = new List<Item>();
|
||||
|
||||
public override IEnumerable<Vector2> SonarPositions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
return Enumerable.Empty<Vector2>();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Targets.Select(t => t.WorldPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Entity> Targets
|
||||
{
|
||||
get
|
||||
{
|
||||
if (State > 0)
|
||||
{
|
||||
return Enumerable.Empty<Entity>();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (items.Any())
|
||||
{
|
||||
return items.Where(it => !it.Removed && it.Condition > 0.0f).Cast<Entity>().Concat(requireKill.Where(c => !c.Removed && !c.IsDead)).Concat(requireRescue);
|
||||
}
|
||||
else
|
||||
{
|
||||
return requireKill.Concat(requireRescue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public OutpostDestroyMission(MissionPrefab prefab, Location[] locations, Submarine sub) :
|
||||
base(prefab, locations, sub)
|
||||
{
|
||||
itemConfig = prefab.ConfigElement.Element("Items");
|
||||
itemTag = prefab.ConfigElement.GetAttributeString("targetitem", "");
|
||||
}
|
||||
|
||||
protected override void StartMissionSpecific(Level level)
|
||||
{
|
||||
items.Clear();
|
||||
#if SERVER
|
||||
spawnedItems.Clear();
|
||||
#endif
|
||||
if (!string.IsNullOrEmpty(itemTag))
|
||||
{
|
||||
var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag));
|
||||
if (!itemsToDestroy.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".");
|
||||
}
|
||||
else
|
||||
{
|
||||
items.AddRange(itemsToDestroy);
|
||||
}
|
||||
}
|
||||
if (itemConfig != null && !IsClient)
|
||||
{
|
||||
foreach (XElement element in itemConfig.Elements())
|
||||
{
|
||||
string itemIdentifier = element.GetAttributeString("identifier", "");
|
||||
if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab))
|
||||
{
|
||||
DebugConsole.ThrowError("Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier + "\" not found");
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] moduleFlags = element.GetAttributeStringArray("moduleflags", null);
|
||||
string[] spawnPointTags = element.GetAttributeStringArray("spawnpointtags", null);
|
||||
ISpatialEntity spawnPoint = SpawnAction.GetSpawnPos(
|
||||
SpawnAction.SpawnLocationType.Outpost, SpawnType.Human | SpawnType.Enemy,
|
||||
moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false));
|
||||
if (spawnPoint == null)
|
||||
{
|
||||
var submarine = Submarine.Loaded.Find(s => s.Info.Type == SubmarineType.Outpost) ?? Submarine.MainSub;
|
||||
spawnPoint = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom();
|
||||
}
|
||||
Vector2 spawnPos = spawnPoint.WorldPosition;
|
||||
if (spawnPoint is WayPoint wp && wp.CurrentHull != null && wp.CurrentHull.Rect.Width > 100)
|
||||
{
|
||||
spawnPos = new Vector2(
|
||||
MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 200), wp.CurrentHull.WorldRect.X + 50, wp.CurrentHull.WorldRect.Right - 50),
|
||||
wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 16.0f);
|
||||
}
|
||||
var item = new Item(itemPrefab, spawnPos, null);
|
||||
items.Add(item);
|
||||
#if SERVER
|
||||
spawnedItems.Add(item);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
base.StartMissionSpecific(level);
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
if (requireRescue.Any(r => r.Removed || r.IsDead))
|
||||
{
|
||||
#if SERVER
|
||||
if (!(GameMain.GameSession.GameMode is CampaignMode) && GameMain.Server != null)
|
||||
{
|
||||
GameMain.Server.EndGame();
|
||||
}
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
if (items.Any())
|
||||
{
|
||||
if (items.All(it => it.Removed || it.Condition <= 0.0f) &&
|
||||
requireKill.All(c => c.Removed || c.IsDead) &&
|
||||
requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player))
|
||||
{
|
||||
State = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requireKill.All(c => c.Removed || c.IsDead) &&
|
||||
requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player))
|
||||
{
|
||||
State = 1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
#if SERVER
|
||||
case 1:
|
||||
if (!(GameMain.GameSession.GameMode is CampaignMode) && GameMain.Server != null)
|
||||
{
|
||||
if (!Submarine.MainSub.AtStartExit || (wasDocked && !Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost)))
|
||||
{
|
||||
GameMain.Server.EndGame();
|
||||
State = 2;
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,15 @@ namespace Barotrauma
|
||||
{
|
||||
partial class PirateMission : Mission
|
||||
{
|
||||
private readonly XElement submarineTypeConfig;
|
||||
private readonly XElement characterConfig;
|
||||
private readonly XElement submarineConfig;
|
||||
private readonly XElement characterTypeConfig;
|
||||
private readonly float addedMissionDifficultyPerPlayer;
|
||||
|
||||
private float missionDifficulty;
|
||||
private int alternateReward;
|
||||
|
||||
private Submarine enemySub;
|
||||
private Item reactorItem;
|
||||
private readonly List<Character> characters = new List<Character>();
|
||||
private readonly Dictionary<Character, List<Item>> characterDictionary = new Dictionary<Character, List<Item>>();
|
||||
|
||||
@@ -51,17 +55,55 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetReward(Submarine sub)
|
||||
{
|
||||
return alternateReward;
|
||||
}
|
||||
|
||||
private SubmarineInfo submarineInfo;
|
||||
|
||||
public override SubmarineInfo EnemySubmarineInfo => submarineInfo;
|
||||
public override SubmarineInfo EnemySubmarineInfo
|
||||
{
|
||||
get
|
||||
{
|
||||
return submarineInfo;
|
||||
}
|
||||
}
|
||||
|
||||
// these values could also be defined within the mission XML
|
||||
private const float RandomnessModifier = 25;
|
||||
private const float ShipRandomnessModifier = 15;
|
||||
|
||||
private const float MaxDifficulty = 100;
|
||||
|
||||
public PirateMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
|
||||
{
|
||||
submarineConfig = prefab.ConfigElement.Element("Submarine");
|
||||
submarineTypeConfig = prefab.ConfigElement.Element("SubmarineTypes");
|
||||
characterConfig = prefab.ConfigElement.Element("Characters");
|
||||
characterTypeConfig = prefab.ConfigElement.Element("CharacterTypes");
|
||||
addedMissionDifficultyPerPlayer = prefab.ConfigElement.GetAttributeFloat("addedmissiondifficultyperplayer", 0);
|
||||
|
||||
// for campaign missions, set difficulty at construction
|
||||
LevelData levelData = locations[0].Connections.Where(c => c.Locations.Contains(locations[1])).FirstOrDefault()?.LevelData ?? locations[0]?.LevelData;
|
||||
|
||||
SetDifficulty(levelData?.Difficulty ?? Level.Loaded?.Difficulty ?? 0f);
|
||||
}
|
||||
|
||||
public override void SetDifficulty(float difficulty)
|
||||
{
|
||||
if (missionDifficulty > 0f)
|
||||
{
|
||||
// difficulty already set
|
||||
return;
|
||||
}
|
||||
|
||||
missionDifficulty = difficulty;
|
||||
|
||||
XElement submarineConfig = GetRandomDifficultyModifiedElement(submarineTypeConfig, missionDifficulty, ShipRandomnessModifier);
|
||||
|
||||
alternateReward = submarineConfig.GetAttributeInt("alternatereward", Reward);
|
||||
|
||||
string submarineIdentifier = submarineConfig.GetAttributeString("identifier", string.Empty);
|
||||
|
||||
if (submarineIdentifier == string.Empty)
|
||||
{
|
||||
DebugConsole.ThrowError("No identifier used for submarine for pirate mission!");
|
||||
@@ -74,9 +116,36 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError("No submarine file found with the identifier!");
|
||||
return;
|
||||
}
|
||||
|
||||
submarineInfo = new SubmarineInfo(contentFile.Path);
|
||||
}
|
||||
|
||||
private float GetDifficultyModifiedValue(float preferredDifficulty, float levelDifficulty, float randomnessModifier)
|
||||
{
|
||||
return Math.Abs(levelDifficulty - preferredDifficulty + (Rand.Range(-randomnessModifier, randomnessModifier, Rand.RandSync.Server)));
|
||||
}
|
||||
private int GetDifficultyModifiedAmount(int minAmount, int maxAmount, float levelDifficulty)
|
||||
{
|
||||
return Math.Max((int)Math.Round(minAmount + (maxAmount - minAmount) * ((levelDifficulty + Rand.Range(-RandomnessModifier, RandomnessModifier, Rand.RandSync.Server)) / MaxDifficulty)), minAmount);
|
||||
}
|
||||
|
||||
private XElement GetRandomDifficultyModifiedElement(XElement parentElement, float levelDifficulty, float randomnessModifier)
|
||||
{
|
||||
// look for the element that is closest to our difficulty, with some randomness
|
||||
XElement bestElement = null;
|
||||
float bestValue = float.MaxValue;
|
||||
foreach (XElement element in parentElement.Elements())
|
||||
{
|
||||
float applicabilityValue = GetDifficultyModifiedValue(element.GetAttributeFloat(0f, "preferreddifficulty"), levelDifficulty, randomnessModifier);
|
||||
if (applicabilityValue < bestValue)
|
||||
{
|
||||
bestElement = element;
|
||||
bestValue = applicabilityValue;
|
||||
}
|
||||
}
|
||||
return bestElement;
|
||||
}
|
||||
|
||||
private void CreateMissionPositions(out Vector2 preferredSpawnPos)
|
||||
{
|
||||
Vector2 patrolPos = enemySub.WorldPosition;
|
||||
@@ -116,7 +185,6 @@ namespace Barotrauma
|
||||
if (enemySub.GetItems(alsoFromConnectedSubs: false).Find(i => i.HasTag("reactor") && !i.NonInteractable)?.GetComponent<Reactor>() is Reactor reactor)
|
||||
{
|
||||
reactor.PowerUpImmediately();
|
||||
reactorItem = reactor.Item;
|
||||
}
|
||||
enemySub.EnableMaintainPosition();
|
||||
enemySub.SetPosition(spawnPos);
|
||||
@@ -134,21 +202,45 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
int playerCount = 1;
|
||||
|
||||
#if SERVER
|
||||
playerCount = GameMain.Server.ConnectedClients.Where(c => !c.SpectateOnly || !GameMain.Server.ServerSettings.AllowSpectating).Count();
|
||||
#endif
|
||||
|
||||
float enemyCreationDifficulty = missionDifficulty + playerCount * addedMissionDifficultyPerPlayer;
|
||||
|
||||
bool commanderAssigned = false;
|
||||
foreach (XElement element in characterConfig.Elements())
|
||||
{
|
||||
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(element), characters, characterDictionary, enemySub, CharacterTeamType.None, null);
|
||||
if (!commanderAssigned)
|
||||
// it is possible to get more than the "max" amount of characters if the modified difficulty is high enough; this is intentional
|
||||
// if necessary, another "hard max" value could be used to clamp the value for performance/gameplay concerns
|
||||
int amountCreated = GetDifficultyModifiedAmount(element.GetAttributeInt("minamount", 0), element.GetAttributeInt("maxamount", 0), enemyCreationDifficulty);
|
||||
for (int i = 0; i < amountCreated; i++)
|
||||
{
|
||||
bool isCommander = element.GetAttributeBool("iscommander", false);
|
||||
if (isCommander && spawnedCharacter.AIController is HumanAIController humanAIController)
|
||||
XElement characterType = characterTypeConfig.Elements().Where(e => e.GetAttributeString("typeidentifier", string.Empty) == element.GetAttributeString("typeidentifier", string.Empty)).FirstOrDefault();
|
||||
|
||||
if (characterType == null)
|
||||
{
|
||||
humanAIController.InitShipCommandManager();
|
||||
foreach (var patrolPos in patrolPositions)
|
||||
DebugConsole.ThrowError("No character types defined in CharacterTypes for a declared type identifier in mission file " + this);
|
||||
return;
|
||||
}
|
||||
|
||||
XElement variantElement = GetRandomDifficultyModifiedElement(characterType, enemyCreationDifficulty, RandomnessModifier);
|
||||
|
||||
Character spawnedCharacter = CreateHuman(CreateHumanPrefabFromElement(variantElement), characters, characterDictionary, enemySub, CharacterTeamType.None, null);
|
||||
if (!commanderAssigned)
|
||||
{
|
||||
bool isCommander = variantElement.GetAttributeBool("iscommander", false);
|
||||
if (isCommander && spawnedCharacter.AIController is HumanAIController humanAIController)
|
||||
{
|
||||
humanAIController.ShipCommandManager.patrolPositions.Add(patrolPos);
|
||||
humanAIController.InitShipCommandManager();
|
||||
foreach (var patrolPos in patrolPositions)
|
||||
{
|
||||
humanAIController.ShipCommandManager.patrolPositions.Add(patrolPos);
|
||||
}
|
||||
commanderAssigned = true;
|
||||
}
|
||||
commanderAssigned = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +309,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
int newState = State;
|
||||
float sqrSonarRange = MathUtils.Pow2(Sonar.DefaultSonarRange);
|
||||
@@ -268,7 +360,7 @@ namespace Barotrauma
|
||||
State = newState;
|
||||
}
|
||||
|
||||
private bool CheckWinState() => !IsClient && (characters.All(m => !Survived(m)) || reactorItem.Condition <= 0f);
|
||||
private bool CheckWinState() => !IsClient && (characters.All(m => !Survived(m)));
|
||||
|
||||
private bool Survived(Character character)
|
||||
{
|
||||
|
||||
@@ -205,7 +205,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user