Unstable 0.1400.1.0

This commit is contained in:
Markus Isberg
2021-05-20 16:12:54 +03:00
parent 92f0264af2
commit 5bc850cddb
181 changed files with 2475 additions and 1588 deletions
@@ -303,9 +303,15 @@ namespace Barotrauma
private bool IsValidTarget(Entity e)
{
return
e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
bool isValid = e is Character character && !character.Removed && !character.IsDead && !character.IsIncapacitated &&
(e == Character.Controlled || character.IsRemotePlayer);
#if SERVER
UpdateIgnoredClients();
isValid &= !ignoredClients.Keys.Any(c => c.Character == e);
#elif CLIENT
isValid &= (e != Character.Controlled || !GUI.InputBlockingMenuOpen);
#endif
return isValid;
}
private void TryStartConversation(Character speaker, Character targetCharacter = null)
@@ -348,7 +354,7 @@ namespace Barotrauma
{
ParentEvent.AddTarget(InvokerTag, targetCharacter);
}
ShowDialog(speaker, targetCharacter);
dialogOpened = true;
@@ -14,6 +14,18 @@ namespace Barotrauma
[Serialize("", true)]
public string MissionTag { get; set; }
[Serialize("", true, description: "The type of the location the mission will be unlocked in (if empty, any location can be selected).")]
public string LocationType { get; set; }
[Serialize(0, true, description: "Minimum distance to the location the mission is unlocked in (1 = one path between locations).")]
public int MinLocationDistance { get; set; }
[Serialize(true, true, description: "If true, the mission has to be unlocked in a location further on the campaign map.")]
public bool UnlockFurtherOnMap { get; set; }
[Serialize(false, true, description: "If true, a suitable location is forced on the map if one isn't found.")]
public bool CreateLocationIfNotFound { get; set; }
private bool isFinished;
public MissionAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element)
@@ -44,33 +56,82 @@ namespace Barotrauma
if (GameMain.GameSession.GameMode is CampaignMode campaign)
{
MissionPrefab prefab = null;
if (!string.IsNullOrEmpty(MissionIdentifier))
var unlockLocation = FindUnlockLocation();
if (unlockLocation == null && CreateLocationIfNotFound)
{
prefab = campaign.Map.CurrentLocation.UnlockMissionByIdentifier(MissionIdentifier);
}
else if (!string.IsNullOrEmpty(MissionTag))
{
prefab = campaign.Map.CurrentLocation.UnlockMissionByTag(MissionTag);
}
if (campaign is MultiPlayerCampaign mpCampaign)
{
mpCampaign.LastUpdateID++;
//find an empty location at least 3 steps away, further on the map
var emptyLocation = FindUnlockLocationRecursive(campaign.Map.CurrentLocation, Math.Max(MinLocationDistance, 3), "none", true, new HashSet<Location>());
if (emptyLocation != null)
{
emptyLocation.ChangeType(Barotrauma.LocationType.List.Find(lt => lt.Identifier.Equals(LocationType, StringComparison.OrdinalIgnoreCase)));
unlockLocation = emptyLocation;
}
}
if (prefab != null)
if (unlockLocation != null)
{
#if CLIENT
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name),
new string[0], type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
if (!string.IsNullOrEmpty(MissionIdentifier))
{
IconColor = prefab.IconColor
};
#else
NotifyMissionUnlock(prefab);
#endif
prefab = unlockLocation.UnlockMissionByIdentifier(MissionIdentifier);
}
else if (!string.IsNullOrEmpty(MissionTag))
{
prefab = unlockLocation.UnlockMissionByTag(MissionTag);
}
if (campaign is MultiPlayerCampaign mpCampaign)
{
mpCampaign.LastUpdateID++;
}
if (prefab != null)
{
DebugConsole.NewMessage($"Unlocked mission \"{prefab.Name}\" in the location \"{unlockLocation.Name}\".");
#if CLIENT
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name),
new string[0], type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
{
IconColor = prefab.IconColor
};
#else
NotifyMissionUnlock(prefab);
#endif
}
}
else
{
DebugConsole.AddWarning($"Failed to find a suitable location to unlock a mission in (LocationType: {LocationType}, MinLocationDistance: {MinLocationDistance}, UnlockFurtherOnMap: {UnlockFurtherOnMap})");
}
}
isFinished = true;
isFinished = true;
}
private Location FindUnlockLocation()
{
var campaign = GameMain.GameSession.GameMode as CampaignMode;
if (string.IsNullOrEmpty(LocationType) && MinLocationDistance <= 1)
{
return campaign.Map.CurrentLocation;
}
return FindUnlockLocationRecursive(campaign.Map.CurrentLocation, 0, LocationType, UnlockFurtherOnMap, new HashSet<Location>());
}
private Location FindUnlockLocationRecursive(Location currLocation, int currDistance, string locationType, bool unlockFurtherOnMap, HashSet<Location> checkedLocations)
{
var campaign = GameMain.GameSession.GameMode as CampaignMode;
if (currLocation.Type.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase) && currDistance >= MinLocationDistance &&
(!unlockFurtherOnMap || currLocation.MapPosition.X > campaign.Map.CurrentLocation.MapPosition.X))
{
return currLocation;
}
checkedLocations.Add(currLocation);
foreach (LocationConnection connection in currLocation.Connections)
{
var otherLocation = connection.OtherLocation(currLocation);
if (checkedLocations.Contains(otherLocation)) { continue; }
var unlockLocation = FindUnlockLocationRecursive(otherLocation, ++currDistance, locationType, unlockFurtherOnMap, checkedLocations);
if (unlockLocation != null) { return unlockLocation; }
}
return null;
}
public override string ToDebugString()
@@ -84,8 +145,8 @@ namespace Barotrauma
foreach (Client client in GameMain.Server.ConnectedClients)
{
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte) ServerPacketHeader.EVENTACTION);
outmsg.Write((byte) EventManager.NetworkEventType.MISSION);
outmsg.Write((byte)ServerPacketHeader.EVENTACTION);
outmsg.Write((byte)EventManager.NetworkEventType.MISSION);
outmsg.Write(prefab.Identifier);
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
}
@@ -41,9 +41,14 @@ namespace Barotrauma
foreach (Item item in npc.Inventory.AllItems)
{
item.AllowStealing = true;
var wifiComponent = item.GetComponent<Items.Components.WifiComponent>();
if (wifiComponent != null)
{
wifiComponent.TeamID = newTeam;
}
}
#if SERVER
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew });
GameMain.NetworkMember.CreateEntityEvent(npc, new object[] { NetEntityEvent.Type.AddToCrew, newTeam, npc.Inventory.AllItems.Select(it => it.ID).ToArray() });
#endif
}
}
@@ -15,7 +15,8 @@ namespace Barotrauma
Outpost,
MainPath,
Ruin,
Wreck
Wreck,
BeaconStation
}
[Serialize("", true, description: "Species name of the character to spawn.")]
@@ -225,6 +226,7 @@ namespace Barotrauma
SpawnLocationType.Outpost => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsOutpost),
SpawnLocationType.Wreck => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsWreck),
SpawnLocationType.Ruin => Item.ItemList.FindAll(it => it.ParentRuin != null),
SpawnLocationType.BeaconStation => Item.ItemList.FindAll(it => it.Submarine != null && it.Submarine.Info.IsBeacon),
_ => throw new NotImplementedException()
};
@@ -250,6 +252,7 @@ namespace Barotrauma
SpawnLocationType.Outpost => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.CurrentHull != null && wp.Submarine.Info.IsOutpost),
SpawnLocationType.Wreck => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.Submarine.Info.IsWreck),
SpawnLocationType.Ruin => WayPoint.WayPointList.FindAll(wp => wp.ParentRuin != null),
SpawnLocationType.BeaconStation => WayPoint.WayPointList.FindAll(wp => wp.Submarine != null && wp.Submarine.Info.IsBeacon),
_ => throw new NotImplementedException()
};
@@ -51,7 +51,14 @@ namespace Barotrauma
{
foreach (var target in targets)
{
effect.Apply(effect.type, deltaTime, target, target as ISerializableEntity);
if (target is Item targetItem)
{
effect.Apply(effect.type, deltaTime, target, targetItem.AllPropertyObjects);
}
else
{
effect.Apply(effect.type, deltaTime, target, target as ISerializableEntity);
}
}
}
#if SERVER
@@ -124,7 +124,7 @@ namespace Barotrauma
npc.CampaignInteractionType = CampaignMode.InteractionType.Examine;
#if CLIENT
npc.SetCustomInteract(
Trigger,
(speaker, player) => { if (e1 == speaker) { Trigger(speaker, player); } else { Trigger(player, speaker); } },
TextManager.GetWithVariable("CampaignInteraction.Examine", "[key]", GameMain.Config.KeyBindText(InputType.Use)));
#else
npc.SetCustomInteract(
@@ -107,7 +107,7 @@ namespace Barotrauma
totalPathLength = 0.0f;
if (level != null)
{
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(Level.Loaded.StartPosition), ConvertUnits.ToSimUnits(Level.Loaded.EndPosition));
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(level.StartPosition), ConvertUnits.ToSimUnits(level.EndPosition));
totalPathLength = steeringPath.TotalLength;
}
@@ -124,7 +124,7 @@ namespace Barotrauma
}
MTRandom rand = new MTRandom(seed);
var initialEventSet = SelectRandomEvents(EventSet.List);
var initialEventSet = SelectRandomEvents(EventSet.List, rand);
if (initialEventSet != null)
{
pendingEventSets.Add(initialEventSet);
@@ -386,21 +386,25 @@ namespace Barotrauma
{
if (level == null) { return; }
if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; }
#if DEBUG
DebugConsole.NewMessage($"Loading event set {eventSet.DebugIdentifier}", Color.LightBlue);
#else
DebugConsole.Log($"Loading event set {eventSet.DebugIdentifier}");
#endif
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)
applyCount = level.Ruins.Count();
foreach (var ruin in level.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)
applyCount = level.Caves.Count();
foreach (var cave in level.Caves)
{
spawnPosFilter.Add((Level.InterestingPosition pos) => { return pos.Cave == cave; });
}
@@ -417,7 +421,8 @@ namespace Barotrauma
var suitablePrefabs = eventSet.EventPrefabs.FindAll(e =>
string.IsNullOrEmpty(e.First.BiomeIdentifier) ||
e.First.BiomeIdentifier.Equals(Level.Loaded.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase));
e.First.BiomeIdentifier.Equals(level.LevelData?.Biome?.Identifier, StringComparison.OrdinalIgnoreCase));
for (int i = 0; i < applyCount; i++)
{
if (eventSet.ChooseRandom)
@@ -435,7 +440,11 @@ namespace Barotrauma
if (newEvent == null) { continue; }
newEvent.Init(true);
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
DebugConsole.Log("Initialized event " + newEvent.ToString());
#if DEBUG
DebugConsole.NewMessage($"Initialized event {newEvent}");
#else
DebugConsole.Log($"Initialized event {newEvent}");
#endif
if (!selectedEvents.ContainsKey(eventSet))
{
selectedEvents.Add(eventSet, new List<Event>());
@@ -447,8 +456,11 @@ namespace Barotrauma
}
if (eventSet.ChildSets.Count > 0)
{
var newEventSet = SelectRandomEvents(eventSet.ChildSets);
if (newEventSet != null) { CreateEvents(newEventSet, rand); }
var newEventSet = SelectRandomEvents(eventSet.ChildSets, rand);
if (newEventSet != null)
{
CreateEvents(newEventSet, rand);
}
}
}
else
@@ -458,7 +470,11 @@ namespace Barotrauma
var newEvent = eventPrefab.First.CreateInstance();
if (newEvent == null) { continue; }
newEvent.Init(true);
DebugConsole.Log("Initialized event " + newEvent.ToString());
#if DEBUG
DebugConsole.NewMessage($"Initialized event {newEvent}");
#else
DebugConsole.Log($"Initialized event {newEvent}");
#endif
if (!selectedEvents.ContainsKey(eventSet))
{
selectedEvents.Add(eventSet, new List<Event>());
@@ -474,10 +490,10 @@ namespace Barotrauma
}
}
private EventSet SelectRandomEvents(List<EventSet> eventSets)
private EventSet SelectRandomEvents(List<EventSet> eventSets, Random random = null)
{
if (level == null) { return null; }
MTRandom rand = new MTRandom(ToolBox.StringToInt(level.Seed));
Random rand = random ?? new MTRandom(ToolBox.StringToInt(level.Seed));
var allowedEventSets =
eventSets.Where(es =>
@@ -496,7 +512,8 @@ namespace Barotrauma
}
float totalCommonness = allowedEventSets.Sum(e => e.GetCommonness(level));
float randomNumber = (float)rand.NextDouble() * totalCommonness;
float randomNumber = (float)rand.NextDouble();
randomNumber *= totalCommonness;
foreach (EventSet eventSet in allowedEventSets)
{
float commonness = eventSet.GetCommonness(level);
@@ -835,7 +852,7 @@ namespace Barotrauma
{
if (level == null) { return 0.0f; }
var refEntity = GetRefEntity();
Vector2 target = ConvertUnits.ToSimUnits(Level.Loaded.EndPosition);
Vector2 target = ConvertUnits.ToSimUnits(level.EndPosition);
var steeringPath = pathFinder.FindPath(ConvertUnits.ToSimUnits(refEntity.WorldPosition), target);
if (steeringPath.Unreachable || float.IsPositiveInfinity(totalPathLength))
{
@@ -953,15 +970,15 @@ namespace Barotrauma
const int maxDist = 1000;
if (Level.Loaded != null)
if (level != null)
{
foreach (var ruin in Level.Loaded.Ruins)
foreach (var ruin in level.Ruins)
{
Rectangle area = ruin.Area;
area.Inflate(maxDist, maxDist);
if (area.Contains(character.WorldPosition)) { return true; }
}
foreach (var cave in Level.Loaded.Caves)
foreach (var cave in level.Caves)
{
Rectangle area = cave.Area;
area.Inflate(maxDist, maxDist);
@@ -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)
{
@@ -442,7 +442,7 @@ namespace Barotrauma
}
else if (!spawnPosType.HasFlag(Level.PositionType.MainPath))
{
scatterAmount = 100;
scatterAmount = 0;
}
for (int i = 0; i < amount; i++)
{
@@ -455,7 +455,7 @@ namespace Barotrauma
System.Diagnostics.Debug.Assert(GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer, "Clients should not create monster events.");
Vector2 pos = spawnPos.Value + Rand.Vector(scatterAmount);
if (scatterAmount > 100)
if (scatterAmount > 0)
{
if (Submarine.Loaded.Any(s => ToolBox.GetWorldBounds(s.Borders.Center, s.Borders.Size).ContainsWorld(pos)))
{