Unstable v0.1300.0.0 (February 19th 2021)

This commit is contained in:
Joonas Rikkonen
2021-02-25 13:44:23 +02:00
parent b772654326
commit 24cbef485a
441 changed files with 21343 additions and 8562 deletions
@@ -25,11 +25,12 @@ namespace Barotrauma
subs.ForEach(s => s.Info.InitialSuppliesSpawned = true);
}
foreach (var wreck in Submarine.Loaded)
foreach (var sub in Submarine.Loaded)
{
if (wreck.Info.IsWreck)
if (sub.Info.Type == SubmarineType.Wreck ||
sub.Info.Type == SubmarineType.BeaconStation)
{
Place(wreck.ToEnumerable());
Place(sub.ToEnumerable());
}
}
@@ -195,11 +196,12 @@ namespace Barotrauma
int amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.Server);
for (int i = 0; i < amount; i++)
{
if (validContainer.Key.Inventory.IsFull())
if (validContainer.Key.Inventory.IsFull(takeStacksIntoAccount: true))
{
containers.Remove(validContainer.Key);
break;
}
if (!validContainer.Key.Inventory.CanBePut(itemPrefab)) { break; }
var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine)
{
SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost,
@@ -103,6 +103,10 @@ namespace Barotrauma
public void PurchaseItems(List<PurchasedItem> itemsToPurchase, bool removeFromCrate)
{
// Check all the prices before starting the transaction
// to make sure the modifiers stay the same for the whole transaction
Dictionary<ItemPrefab, int> buyValues = GetBuyValuesAtCurrentLocation(itemsToPurchase.Select(i => i.ItemPrefab));
foreach (PurchasedItem item in itemsToPurchase)
{
// Add to the purchased items
@@ -118,7 +122,7 @@ namespace Barotrauma
}
// Exchange money
var itemValue = GetBuyValueAtCurrentLocation(item);
var itemValue = item.Quantity * buyValues[item.ItemPrefab];
campaign.Money -= itemValue;
Location.StoreCurrentBalance += itemValue;
@@ -136,23 +140,47 @@ namespace Barotrauma
OnPurchasedItemsChanged?.Invoke();
}
public int GetBuyValueAtCurrentLocation(PurchasedItem item) => item?.ItemPrefab != null && Location != null ?
item.Quantity * Location.GetAdjustedItemBuyPrice(item.ItemPrefab) : 0;
public Dictionary<ItemPrefab, int> GetBuyValuesAtCurrentLocation(IEnumerable<ItemPrefab> items)
{
var buyValues = new Dictionary<ItemPrefab, int>();
foreach (var item in items)
{
if (item == null) { continue; }
if (!buyValues.ContainsKey(item))
{
var buyValue = Location?.GetAdjustedItemBuyPrice(item) ?? 0;
buyValues.Add(item, buyValue);
}
}
return buyValues;
}
public int GetSellValueAtCurrentLocation(ItemPrefab itemPrefab, int quantity = 1) => itemPrefab != null && Location != null ?
quantity * Location.GetAdjustedItemSellPrice(itemPrefab) : 0;
public Dictionary<ItemPrefab, int> GetSellValuesAtCurrentLocation(IEnumerable<ItemPrefab> items)
{
var sellValues = new Dictionary<ItemPrefab, int>();
foreach (var item in items)
{
if (item == null) { continue; }
if (!sellValues.ContainsKey(item))
{
var sellValue = Location?.GetAdjustedItemSellPrice(item) ?? 0;
sellValues.Add(item, sellValue);
}
}
return sellValues;
}
public void CreatePurchasedItems()
{
CreateItems(PurchasedItems);
CreateItems(PurchasedItems, Submarine.MainSub);
OnPurchasedItemsChanged?.Invoke();
}
public static void CreateItems(List<PurchasedItem> itemsToSpawn)
public static void CreateItems(List<PurchasedItem> itemsToSpawn, Submarine sub)
{
if (itemsToSpawn.Count == 0) { return; }
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub);
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, sub);
if (wp == null)
{
DebugConsole.ThrowError("The submarine must have a waypoint marked as Cargo for bought items to be placed correctly!");
@@ -160,85 +188,73 @@ namespace Barotrauma
}
Hull cargoRoom = Hull.FindHull(wp.WorldPosition);
if (cargoRoom == null)
{
DebugConsole.ThrowError("A waypoint marked as Cargo must be placed inside a room!");
return;
}
#if CLIENT
new GUIMessageBox("", TextManager.GetWithVariable("CargoSpawnNotification", "[roomname]", cargoRoom.DisplayName, true), new string[0], type: GUIMessageBox.Type.InGame, iconStyle: "StoreShoppingCrateIcon");
#else
foreach (Client client in GameMain.Server.ConnectedClients)
if (sub == Submarine.MainSub)
{
ChatMessage msg = ChatMessage.Create("", $"CargoSpawnNotification~[roomname]=§{cargoRoom.RoomName}", ChatMessageType.ServerMessageBoxInGame, null);
msg.IconStyle = "StoreShoppingCrateIcon";
GameMain.Server.SendDirectChatMessage(msg, client);
}
#if CLIENT
new GUIMessageBox("", TextManager.GetWithVariable("CargoSpawnNotification", "[roomname]", cargoRoom.DisplayName, true), new string[0], type: GUIMessageBox.Type.InGame, iconStyle: "StoreShoppingCrateIcon");
#else
foreach (Client client in GameMain.Server.ConnectedClients)
{
ChatMessage msg = ChatMessage.Create("",
TextManager.ContainsTag(cargoRoom.RoomName) ? $"CargoSpawnNotification~[roomname]=§{cargoRoom.RoomName}" : $"CargoSpawnNotification~[roomname]={cargoRoom.RoomName}",
ChatMessageType.ServerMessageBoxInGame, null);
msg.IconStyle = "StoreShoppingCrateIcon";
GameMain.Server.SendDirectChatMessage(msg, client);
}
#endif
}
Dictionary<ItemContainer, int> availableContainers = new Dictionary<ItemContainer, int>();
List<ItemContainer> availableContainers = new List<ItemContainer>();
ItemPrefab containerPrefab = null;
foreach (PurchasedItem pi in itemsToSpawn)
{
float floorPos = cargoRoom.Rect.Y - cargoRoom.Rect.Height;
Vector2 position = GetCargoPos(cargoRoom, pi.ItemPrefab);
Vector2 position = new Vector2(
cargoRoom.Rect.Width > 40 ? Rand.Range(cargoRoom.Rect.X + 20, cargoRoom.Rect.Right - 20) : cargoRoom.Rect.Center.X,
floorPos);
//check where the actual floor structure is in case the bottom of the hull extends below it
if (Submarine.PickBody(
ConvertUnits.ToSimUnits(new Vector2(position.X, cargoRoom.Rect.Y - cargoRoom.Rect.Height / 2)),
ConvertUnits.ToSimUnits(position),
collisionCategory: Physics.CollisionWall) != null)
{
float floorStructurePos = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition.Y);
if (floorStructurePos > floorPos)
{
floorPos = floorStructurePos;
}
}
position.Y = floorPos + pi.ItemPrefab.Size.Y / 2;
ItemContainer itemContainer = null;
if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier))
{
itemContainer = availableContainers.Keys.ToList().Find(ac =>
ac.Item.Prefab.Identifier == pi.ItemPrefab.CargoContainerIdentifier ||
ac.Item.Prefab.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant()));
if (itemContainer == null)
{
containerPrefab = ItemPrefab.Prefabs.Find(ep =>
ep.Identifier == pi.ItemPrefab.CargoContainerIdentifier ||
(ep.Tags != null && ep.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant())));
if (containerPrefab == null)
{
DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + pi.ItemPrefab.CargoContainerIdentifier + "\"!");
continue;
}
Item containerItem = new Item(containerPrefab, position, wp.Submarine);
itemContainer = containerItem.GetComponent<ItemContainer>();
if (itemContainer == null)
{
DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!");
continue;
}
availableContainers.Add(itemContainer, itemContainer.Capacity);
#if SERVER
if (GameMain.Server != null)
{
Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
}
#endif
}
}
for (int i = 0; i < pi.Quantity; i++)
{
ItemContainer itemContainer = null;
if (!string.IsNullOrEmpty(pi.ItemPrefab.CargoContainerIdentifier))
{
itemContainer = availableContainers.Find(ac =>
ac.Inventory.CanBePut(pi.ItemPrefab) &&
(ac.Item.Prefab.Identifier == pi.ItemPrefab.CargoContainerIdentifier ||
ac.Item.Prefab.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant())));
if (itemContainer == null)
{
containerPrefab = ItemPrefab.Prefabs.Find(ep =>
ep.Identifier == pi.ItemPrefab.CargoContainerIdentifier ||
(ep.Tags != null && ep.Tags.Contains(pi.ItemPrefab.CargoContainerIdentifier.ToLowerInvariant())));
if (containerPrefab == null)
{
DebugConsole.ThrowError("Cargo spawning failed - could not find the item prefab for container \"" + pi.ItemPrefab.CargoContainerIdentifier + "\"!");
continue;
}
Item containerItem = new Item(containerPrefab, position, wp.Submarine);
itemContainer = containerItem.GetComponent<ItemContainer>();
if (itemContainer == null)
{
DebugConsole.ThrowError("Cargo spawning failed - container \"" + containerItem.Name + "\" does not have an ItemContainer component!");
continue;
}
availableContainers.Add(itemContainer);
#if SERVER
if (GameMain.Server != null)
{
Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
}
#endif
}
}
if (itemContainer == null)
{
//no container, place at the waypoint
@@ -253,20 +269,6 @@ namespace Barotrauma
}
continue;
}
//if the intial container has been removed due to it running out of space, add a new container
//of the same type and begin filling it
if (!availableContainers.ContainsKey(itemContainer))
{
Item containerItemOverFlow = new Item(containerPrefab, position, wp.Submarine);
itemContainer = containerItemOverFlow.GetComponent<ItemContainer>();
availableContainers.Add(itemContainer, itemContainer.Capacity);
#if SERVER
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
Entity.Spawner.CreateNetworkEvent(itemContainer.Item, false);
}
#endif
}
//place in the container
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
@@ -290,23 +292,38 @@ namespace Barotrauma
wifiComponent.TeamID = sub.TeamID;
}
}
}
//reduce the number of available slots in the container
//if there is a container
if (availableContainers.ContainsKey(itemContainer))
{
availableContainers[itemContainer]--;
}
if (availableContainers.ContainsKey(itemContainer) && availableContainers[itemContainer] <= 0)
{
availableContainers.Remove(itemContainer);
}
}
}
}
itemsToSpawn.Clear();
}
public static Vector2 GetCargoPos(Hull hull, ItemPrefab itemPrefab)
{
float floorPos = hull.Rect.Y - hull.Rect.Height;
Vector2 position = new Vector2(
hull.Rect.Width > 40 ? Rand.Range(hull.Rect.X + 20, hull.Rect.Right - 20) : hull.Rect.Center.X,
floorPos);
//check where the actual floor structure is in case the bottom of the hull extends below it
if (Submarine.PickBody(
ConvertUnits.ToSimUnits(new Vector2(position.X, hull.Rect.Y - hull.Rect.Height / 2)),
ConvertUnits.ToSimUnits(position),
collisionCategory: Physics.CollisionWall) != null)
{
float floorStructurePos = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition.Y);
if (floorStructurePos > floorPos)
{
floorPos = floorStructurePos;
}
}
position.Y = floorPos + itemPrefab.Size.Y / 2;
return position;
}
public void SavePurchasedItems(XElement parentElement)
{
var itemsElement = new XElement("cargo");
@@ -48,20 +48,43 @@ namespace Barotrauma
return false;
}
Pair<Order, float?> existingOrder =
ActiveOrders.Find(o => o.First.Prefab == order.Prefab && o.First.TargetEntity == order.TargetEntity &&
(o.First.TargetType != Order.OrderTargetType.WallSection || o.First.WallSectionIndex == order.WallSectionIndex));
// Ignore orders work a bit differently since the "unignore" order counters the "ignore" order
var isUnignoreOrder = order.Identifier == "unignorethis";
var orderPrefab = !isUnignoreOrder ? order.Prefab : Order.GetPrefab("ignorethis");
Pair<Order, float?> existingOrder = ActiveOrders.Find(o =>
o.First.Prefab == orderPrefab && MatchesTarget(o.First.TargetEntity, order.TargetEntity) &&
(o.First.TargetType != Order.OrderTargetType.WallSection || o.First.WallSectionIndex == order.WallSectionIndex));
if (existingOrder != null)
{
existingOrder.Second = fadeOutTime;
return false;
if (!isUnignoreOrder)
{
existingOrder.Second = fadeOutTime;
return false;
}
else
{
ActiveOrders.Remove(existingOrder);
return true;
}
}
else
else if (!isUnignoreOrder)
{
ActiveOrders.Add(new Pair<Order, float?>(order, fadeOutTime));
return true;
}
bool MatchesTarget(Entity existingTarget, Entity newTarget)
{
if (existingTarget == newTarget) { return true; }
if (existingTarget is Hull existingHullTarget && newTarget is Hull newHullTarget)
{
return existingHullTarget.linkedTo.Contains(newHullTarget);
}
return false;
}
return false;
}
public void AddCharacterElements(XElement element)
@@ -122,14 +145,22 @@ namespace Barotrauma
}
#if CLIENT
AddCharacterToCrewList(character);
AddCurrentOrderIcon(character, character.CurrentOrder, character.CurrentOrderOption);
#endif
var idleObjective = character.AIController?.ObjectiveManager?.GetObjective<AIObjectiveIdle>();
if (idleObjective != null)
if (character.CurrentOrders != null)
{
idleObjective.Behavior = character.Info.Job.Prefab.IdleBehavior;
foreach (var order in character.CurrentOrders)
{
AddCurrentOrderIcon(character, order);
}
}
#endif
if (character.AIController is HumanAIController humanAI)
{
var idleObjective = humanAI.ObjectiveManager.GetObjective<AIObjectiveIdle>();
if (idleObjective != null)
{
idleObjective.Behavior = character.Info.Job.Prefab.IdleBehavior;
}
}
}
public void AddCharacterInfo(CharacterInfo characterInfo)
@@ -150,7 +181,7 @@ namespace Barotrauma
List<WayPoint> spawnWaypoints = null;
List<WayPoint> mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub).ToList();
if (Level.IsLoadedOutpost)
if (Level.IsLoadedOutpost && Submarine.Loaded.Any(s => s.Info.Type == SubmarineType.Outpost && (s.Info.OutpostGenerationParams?.SpawnCrewInsideOutpost ?? false)))
{
spawnWaypoints = WayPoint.WayPointList.FindAll(wp =>
wp.SpawnType == SpawnType.Human &&
@@ -177,7 +208,7 @@ namespace Barotrauma
for (int i = 0; i < spawnWaypoints.Count; i++)
{
var info = characterInfos[i];
info.TeamID = Character.TeamType.Team1;
info.TeamID = CharacterTeamType.Team1;
Character character = Character.Create(info, spawnWaypoints[i].WorldPosition, info.Name);
if (character.Info != null)
{
@@ -222,7 +253,8 @@ namespace Barotrauma
{
if (order.Second.HasValue) { order.Second -= deltaTime; }
}
ActiveOrders.RemoveAll(o => o.Second.HasValue && o.Second <= 0.0f);
ActiveOrders.RemoveAll(o => (o.Second.HasValue && o.Second <= 0.0f) ||
(o.First.TargetEntity != null && o.First.TargetEntity.Removed));
UpdateConversations(deltaTime);
UpdateProjectSpecific(deltaTime);
@@ -262,8 +294,8 @@ namespace Barotrauma
{
foreach (Character npc in Character.CharacterList)
{
if (npc.TeamID != Character.TeamType.FriendlyNPC || npc.CurrentHull == null || npc.IsIncapacitated) { continue; }
if (npc.AIController?.ObjectiveManager != null && (npc.AIController.ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>() || npc.AIController.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()))
if (npc.TeamID != CharacterTeamType.FriendlyNPC || npc.CurrentHull == null || npc.IsIncapacitated) { continue; }
if (npc.AIController is HumanAIController humanAI && (humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveFindSafety>() || humanAI.ObjectiveManager.IsCurrentObjective<AIObjectiveCombat>()))
{
continue;
}
@@ -7,6 +7,7 @@ namespace Barotrauma
public const float HostileThreshold = 0.1f;
public const float ReputationLossPerNPCDamage = 0.1f;
public const float ReputationLossPerStolenItemPrice = 0.01f;
public const float ReputationLossPerWallDamage = 0.1f;
public const float MinReputationLossPerStolenItem = 0.5f;
public const float MaxReputationLossPerStolenItem = 10.0f;
@@ -11,8 +11,7 @@ namespace Barotrauma
abstract partial class CampaignMode : GameMode
{
const int MaxMoney = int.MaxValue / 2; //about 1 billion
const int InitialMoney = 2500;
public const int MaxInitialSubmarinePrice = 6000;
public const int InitialMoney = 8500;
//duration of the cinematic + credits at the end of the campaign
protected const float EndCinematicDuration = 240.0f;
@@ -32,6 +31,8 @@ namespace Barotrauma
protected XElement petsElement;
private List<Mission> extraMissions = new List<Mission>();
public enum TransitionType
{
None,
@@ -75,11 +76,18 @@ namespace Barotrauma
get { return map; }
}
public override Mission Mission
public override IEnumerable<Mission> Missions
{
get
{
return Map.CurrentLocation?.SelectedMission;
if (Map.CurrentLocation?.SelectedMission != null)
{
yield return Map.CurrentLocation.SelectedMission;
}
foreach (Mission mission in extraMissions)
{
yield return mission;
}
}
}
@@ -146,7 +154,7 @@ namespace Barotrauma
{
for (int i = 0; i < wall.SectionCount; i++)
{
wall.AddDamage(i, -wall.MaxHealth);
wall.SetDamage(i, 0, createNetworkEvent: false);
}
}
}
@@ -186,6 +194,53 @@ namespace Barotrauma
/// </summary>
public event Action BeforeLevelLoading;
public override void AddExtraMissions(LevelData levelData)
{
extraMissions.Clear();
var currentLocation = Map.CurrentLocation;
if (levelData.Type == LevelData.LevelType.Outpost)
{
//if there's an available mission that takes place in the outpost, select it
var availableMissionsInLocation = currentLocation.AvailableMissions.Where(m => m.Locations[0] == currentLocation && m.Locations[1] == currentLocation);
if (availableMissionsInLocation.Any())
{
currentLocation.SelectedMission = availableMissionsInLocation.FirstOrDefault();
}
else
{
currentLocation.SelectedMission = null;
}
}
else
{
//if we had selected a mission that takes place in the outpost, deselect it when leaving the outpost
if (currentLocation.SelectedMission?.Locations[0] == currentLocation &&
currentLocation.SelectedMission?.Locations[1] == currentLocation)
{
currentLocation.SelectedMission = null;
}
if (levelData.HasBeaconStation && !levelData.IsBeaconActive)
{
var beaconMissionPrefab = MissionPrefab.List.Find(m => m.Identifier.Equals("beaconnoreward", StringComparison.OrdinalIgnoreCase));
if (beaconMissionPrefab != null && !Missions.Any(m => m.Prefab.Type == beaconMissionPrefab.Type))
{
extraMissions.Add(beaconMissionPrefab.Instantiate(Map.SelectedConnection.Locations));
}
}
if (levelData.HasHuntingGrounds)
{
var huntingGroundsMissionPrefab = MissionPrefab.List.Find(m => m.Identifier.Equals("huntinggroundsnoreward", StringComparison.OrdinalIgnoreCase));
if (huntingGroundsMissionPrefab != null && !Missions.Any(m => m.Prefab.Type == huntingGroundsMissionPrefab.Type))
{
extraMissions.Add(huntingGroundsMissionPrefab.Instantiate(Map.SelectedConnection.Locations));
}
}
}
}
public void LoadNewLevel()
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
@@ -287,7 +342,7 @@ namespace Barotrauma
nextLevel = map.StartLocation.LevelData;
return TransitionType.End;
}
if (Level.Loaded.EndLocation != null && Level.Loaded.EndLocation.Type.HasOutpost && Level.Loaded.EndOutpost != null)
if (Level.Loaded.EndLocation != null && Level.Loaded.EndLocation.HasOutpost() && Level.Loaded.EndOutpost != null)
{
nextLevel = Level.Loaded.EndLocation.LevelData;
return TransitionType.ProgressToNextLocation;
@@ -306,13 +361,13 @@ namespace Barotrauma
}
else if (leavingSub.AtStartPosition)
{
if (map.CurrentLocation.Type.HasOutpost && Level.Loaded.StartOutpost != null)
if (map.CurrentLocation.HasOutpost() && Level.Loaded.StartOutpost != null)
{
nextLevel = map.CurrentLocation.LevelData;
return TransitionType.ReturnToPreviousLocation;
}
else if (map.SelectedLocation != null && map.SelectedLocation != map.CurrentLocation && !map.CurrentLocation.Type.HasOutpost &&
(Level.Loaded.LevelData != map.SelectedConnection.LevelData))
else if (map.SelectedLocation != null && map.SelectedLocation != map.CurrentLocation && !map.CurrentLocation.HasOutpost() &&
map.SelectedConnection != null && Level.Loaded.LevelData != map.SelectedConnection.LevelData)
{
nextLevel = map.SelectedConnection.LevelData;
return TransitionType.LeaveLocation;
@@ -481,7 +536,7 @@ namespace Barotrauma
{
CrewManager.RemoveCharacterInfo(ci);
}
ci?.ResetCurrentOrder();
ci?.ClearCurrentOrders();
}
foreach (DockingPort port in DockingPort.List)
@@ -511,7 +566,18 @@ namespace Barotrauma
}
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
Map.SelectLocation(-1);
Map.Radiation.Amount = Map.Radiation.Params.StartingRadiation;
foreach (Location location in Map.Locations)
{
location.TurnsInRadiation = 0;
}
EndCampaignProjSpecific();
if (CampaignMetadata != null)
{
int loops = CampaignMetadata.GetInt("campaign.endings", 0);
CampaignMetadata.SetValue("campaign.endings", loops + 1);
}
}
protected virtual void EndCampaignProjSpecific() { }
@@ -547,18 +613,14 @@ namespace Barotrauma
HumanAIController humanAI = npc.AIController as HumanAIController;
if (humanAI == null) { yield return CoroutineStatus.Failure; }
OrderInfo? prevSpeakerOrder = null;
if (humanAI.CurrentOrder != null)
{
prevSpeakerOrder = new OrderInfo(humanAI.CurrentOrder, humanAI.CurrentOrderOption);
}
var waitOrder = Order.PrefabList.Find(o => o.Identifier.Equals("wait", StringComparison.OrdinalIgnoreCase));
humanAI.SetOrder(waitOrder, option: string.Empty, orderGiver: null, speak: false);
humanAI.SetForcedOrder(waitOrder, string.Empty, null);
var waitObjective = humanAI.ObjectiveManager.ForcedOrder;
humanAI.FaceTarget(interactor);
while (!npc.Removed && !interactor.Removed &&
Vector2.DistanceSquared(npc.WorldPosition, interactor.WorldPosition) < 300.0f * 300.0f &&
humanAI.CurrentOrder == waitOrder &&
humanAI.ObjectiveManager.ForcedOrder == waitObjective &&
humanAI.AllowCampaignInteraction() &&
!interactor.IsIncapacitated)
{
@@ -569,17 +631,7 @@ namespace Barotrauma
ShowCampaignUI = false;
#endif
if (humanAI.CurrentOrder == waitOrder)
{
if (prevSpeakerOrder != null)
{
humanAI.SetOrder(prevSpeakerOrder.Value.Order, prevSpeakerOrder.Value.OrderOption, orderGiver: null, speak: false);
}
else
{
humanAI.SetOrder(null, string.Empty, orderGiver: null, speak: false);
}
}
humanAI.ClearForcedOrder();
yield return CoroutineStatus.Success;
}
@@ -694,7 +746,7 @@ namespace Barotrauma
public void OutpostNPCAttacked(Character npc, Character attacker, AttackResult attackResult)
{
if (npc == null || attacker == null || npc.IsDead || npc.IsInstigator) { return; }
if (npc.TeamID != Character.TeamType.FriendlyNPC) { return; }
if (npc.TeamID != CharacterTeamType.FriendlyNPC) { return; }
if (!attacker.IsRemotePlayer && attacker != Character.Controlled) { return; }
Location location = Map?.CurrentLocation;
if (location != null)
@@ -1,10 +1,11 @@
using System;
using System.Collections.Generic;
namespace Barotrauma
{
class CoOpMode : MissionMode
{
public CoOpMode(GameModePreset preset, MissionPrefab missionPrefab) : base(preset, ValidateMissionPrefab(missionPrefab, MissionPrefab.CoOpMissionClasses)) { }
public CoOpMode(GameModePreset preset, IEnumerable<MissionPrefab> missionPrefabs) : base(preset, ValidateMissionPrefabs(missionPrefabs, MissionPrefab.CoOpMissionClasses)) { }
public CoOpMode(GameModePreset preset, MissionType missionType, string seed) : base(preset, ValidateMissionType(missionType, MissionPrefab.CoOpMissionClasses), seed) { }
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
@@ -16,9 +17,9 @@ namespace Barotrauma
get { return GameMain.GameSession?.CrewManager; }
}
public virtual Mission Mission
public virtual IEnumerable<Mission> Missions
{
get { return null; }
get { return Enumerable.Empty<Mission>(); }
}
public bool IsSinglePlayer
@@ -54,6 +55,8 @@ namespace Barotrauma
}
public virtual void ShowStartMessage() { }
public virtual void AddExtraMissions(LevelData levelData) { }
public virtual void AddToGUIUpdateList()
{
@@ -5,37 +5,43 @@ namespace Barotrauma
{
abstract partial class MissionMode : GameMode
{
private readonly Mission mission;
private readonly List<Mission> missions = new List<Mission>();
public override Mission Mission
public override IEnumerable<Mission> Missions
{
get
{
return mission;
return missions;
}
}
public MissionMode(GameModePreset preset, MissionPrefab missionPrefab)
public MissionMode(GameModePreset preset, IEnumerable<MissionPrefab> missionPrefabs)
: base(preset)
{
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
mission = missionPrefab.Instantiate(locations);
foreach (MissionPrefab missionPrefab in missionPrefabs)
{
missions.Add(missionPrefab.Instantiate(locations));
}
}
public MissionMode(GameModePreset preset, MissionType missionType, string seed)
: base(preset)
{
Location[] locations = { GameMain.GameSession.StartLocation, GameMain.GameSession.EndLocation };
mission = Mission.LoadRandom(locations, seed, false, missionType);
missions.Add(Mission.LoadRandom(locations, seed, false, missionType));
}
protected static MissionPrefab ValidateMissionPrefab(MissionPrefab missionPrefab, Dictionary<MissionType, Type> missionClasses)
protected static IEnumerable<MissionPrefab> ValidateMissionPrefabs(IEnumerable<MissionPrefab> missionPrefabs, Dictionary<MissionType, Type> missionClasses)
{
if (ValidateMissionType(missionPrefab.Type, missionClasses) != missionPrefab.Type)
foreach (MissionPrefab missionPrefab in missionPrefabs)
{
throw new InvalidOperationException("Cannot start gamemode with mission type " + missionPrefab.Type);
if (ValidateMissionType(missionPrefab.Type, missionClasses) != missionPrefab.Type)
{
throw new InvalidOperationException("Cannot start gamemode with mission type " + missionPrefab.Type);
}
}
return missionPrefab;
return missionPrefabs;
}
protected static MissionType ValidateMissionType(MissionType missionType, Dictionary<MissionType, Type> missionClasses)
@@ -8,6 +8,8 @@ namespace Barotrauma
{
partial class MultiPlayerCampaign : CampaignMode
{
public const int MinimumInitialMoney = 500;
private UInt16 lastUpdateID;
public UInt16 LastUpdateID
{
@@ -57,7 +59,7 @@ namespace Barotrauma
InitCampaignData();
}
public static MultiPlayerCampaign StartNew(string mapSeed)
public static MultiPlayerCampaign StartNew(string mapSeed, SubmarineInfo selectedSub)
{
MultiPlayerCampaign campaign = new MultiPlayerCampaign();
//only the server generates the map, the clients load it from a save file
@@ -96,6 +98,9 @@ namespace Barotrauma
private void Load(XElement element)
{
Money = element.GetAttributeInt("money", 0);
PurchasedLostShuttles = element.GetAttributeBool("purchasedlostshuttles", false);
PurchasedHullRepairs = element.GetAttributeBool("purchasedhullrepairs", false);
PurchasedItemRepairs = element.GetAttributeBool("purchaseditemrepairs", false);
CheatsEnabled = element.GetAttributeBool("cheatsenabled", false);
if (CheatsEnabled)
{
@@ -1,11 +1,49 @@
using System;
using Barotrauma.Networking;
using System.Collections.Generic;
namespace Barotrauma
{
class PvPMode : MissionMode
{
public PvPMode(GameModePreset preset, MissionPrefab missionPrefab) : base(preset, ValidateMissionPrefab(missionPrefab, MissionPrefab.PvPMissionClasses)) { }
public PvPMode(GameModePreset preset, IEnumerable<MissionPrefab> missionPrefabs) : base(preset, ValidateMissionPrefabs(missionPrefabs, MissionPrefab.PvPMissionClasses)) { }
public PvPMode(GameModePreset preset, MissionType missionType, string seed) : base(preset, ValidateMissionType(missionType, MissionPrefab.PvPMissionClasses), seed) { }
public void AssignTeamIDs(IEnumerable<Client> clients)
{
int teamWeight = 0;
List<Client> randList = new List<Client>(clients);
for (int i = 0; i < randList.Count; i++)
{
if (randList[i].PreferredTeam == CharacterTeamType.Team1 ||
randList[i].PreferredTeam == CharacterTeamType.Team2)
{
randList[i].TeamID = randList[i].PreferredTeam;
teamWeight += randList[i].PreferredTeam == CharacterTeamType.Team1 ? -1 : 1;
randList.RemoveAt(i);
i--;
}
}
for (int i = 0; i<randList.Count; i++)
{
Client a = randList[i];
int oi = Rand.Range(0, randList.Count - 1);
Client b = randList[oi];
randList[i] = b;
randList[oi] = a;
}
int halfPlayers = (randList.Count / 2) + teamWeight;
for (int i = 0; i < randList.Count; i++)
{
if (i < halfPlayers)
{
randList[i].TeamID = CharacterTeamType.Team1;
}
else
{
randList[i].TeamID = CharacterTeamType.Team2;
}
}
}
}
}
@@ -22,9 +22,10 @@ namespace Barotrauma
public double RoundStartTime;
public Mission Mission { get; private set; }
private readonly List<Mission> missions = new List<Mission>();
public IEnumerable<Mission> Missions { get { return missions; } }
public Character.TeamType? WinningTeam;
public CharacterTeamType? WinningTeam;
public bool IsRunning { get; private set; }
@@ -107,17 +108,17 @@ namespace Barotrauma
{
this.SavePath = savePath;
CrewManager = new CrewManager(gameModePreset != null && gameModePreset.IsSinglePlayer);
GameMode = InstantiateGameMode(gameModePreset, seed, missionType: missionType);
GameMode = InstantiateGameMode(gameModePreset, seed, submarineInfo, missionType: missionType);
}
/// <summary>
/// Start a new GameSession with a specific pre-selected mission.
/// </summary>
public GameSession(SubmarineInfo submarineInfo, GameModePreset gameModePreset, string seed = null, MissionPrefab missionPrefab = null)
public GameSession(SubmarineInfo submarineInfo, GameModePreset gameModePreset, string seed = null, IEnumerable<MissionPrefab> missionPrefabs = null)
: this(submarineInfo)
{
CrewManager = new CrewManager(gameModePreset != null && gameModePreset.IsSinglePlayer);
GameMode = InstantiateGameMode(gameModePreset, seed, missionPrefab: missionPrefab);
GameMode = InstantiateGameMode(gameModePreset, seed, submarineInfo, missionPrefabs: missionPrefabs);
}
/// <summary>
@@ -158,28 +159,38 @@ namespace Barotrauma
}
}
private GameMode InstantiateGameMode(GameModePreset gameModePreset, string seed, MissionPrefab missionPrefab = null, MissionType missionType = MissionType.None)
private GameMode InstantiateGameMode(GameModePreset gameModePreset, string seed, SubmarineInfo selectedSub, IEnumerable<MissionPrefab> missionPrefabs = null, MissionType missionType = MissionType.None)
{
if (gameModePreset.GameModeType == typeof(CoOpMode))
{
return missionPrefab != null ?
new CoOpMode(gameModePreset, missionPrefab) :
return missionPrefabs != null ?
new CoOpMode(gameModePreset, missionPrefabs) :
new CoOpMode(gameModePreset, missionType, seed ?? ToolBox.RandomSeed(8));
}
else if (gameModePreset.GameModeType == typeof(PvPMode))
{
return missionPrefab != null ?
new PvPMode(gameModePreset, missionPrefab) :
return missionPrefabs != null ?
new PvPMode(gameModePreset, missionPrefabs) :
new PvPMode(gameModePreset, missionType, seed ?? ToolBox.RandomSeed(8));
}
else if (gameModePreset.GameModeType == typeof(MultiPlayerCampaign))
{
return MultiPlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8));
var campaign = MultiPlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), selectedSub);
if (campaign != null && selectedSub != null)
{
campaign.Money = Math.Max(MultiPlayerCampaign.MinimumInitialMoney, campaign.Money - selectedSub.Price);
}
return campaign;
}
#if CLIENT
else if (gameModePreset.GameModeType == typeof(SinglePlayerCampaign))
{
return SinglePlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8));
var campaign = SinglePlayerCampaign.StartNew(seed ?? ToolBox.RandomSeed(8), selectedSub);
if (campaign != null && selectedSub != null)
{
campaign.Money = Math.Max(SinglePlayerCampaign.MinimumInitialMoney, campaign.Money - selectedSub.Price);
}
return campaign;
}
else if (gameModePreset.GameModeType == typeof(TutorialMode))
{
@@ -200,7 +211,7 @@ namespace Barotrauma
}
}
private void CreateDummyLocations()
private void CreateDummyLocations(LocationType? forceLocationType = null)
{
dummyLocations = new Location[2];
@@ -217,7 +228,7 @@ namespace Barotrauma
MTRandom rand = new MTRandom(ToolBox.StringToInt(seed));
for (int i = 0; i < 2; i++)
{
dummyLocations[i] = Location.CreateRandom(new Vector2((float)rand.NextDouble() * 10000.0f, (float)rand.NextDouble() * 10000.0f), null, rand, requireOutpost: true);
dummyLocations[i] = Location.CreateRandom(new Vector2((float)rand.NextDouble() * 10000.0f, (float)rand.NextDouble() * 10000.0f), null, rand, requireOutpost: true, forceLocationType: forceLocationType);
}
}
@@ -230,7 +241,7 @@ namespace Barotrauma
/// <summary>
/// Switch to another submarine. The sub is loaded when the next round starts.
/// </summary>
public void SwitchSubmarine(SubmarineInfo newSubmarine, int cost)
public SubmarineInfo SwitchSubmarine(SubmarineInfo newSubmarine, int cost)
{
if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name))
{
@@ -252,6 +263,7 @@ namespace Barotrauma
Campaign.Money -= cost;
((CampaignMode)GameMode).PendingSubmarineSwitch = newSubmarine;
return newSubmarine;
}
public void PurchaseSubmarine(SubmarineInfo newSubmarine)
@@ -271,9 +283,38 @@ namespace Barotrauma
(OwnedSubmarines != null && OwnedSubmarines.Any(os => os.Name == query.Name));
}
public bool IsCurrentLocationRadiated()
{
if (Map?.CurrentLocation == null || Campaign == null) { return false; }
bool isRadiated = Map.CurrentLocation.IsRadiated();
if (Level.Loaded?.EndLocation is { } endLocation)
{
isRadiated |= endLocation.IsRadiated();
}
return isRadiated;
}
public void StartRound(string levelSeed, float? difficulty = null)
{
StartRound(LevelData.CreateRandom(levelSeed, difficulty));
LevelData randomLevel = null;
foreach (Mission mission in Missions.Union(GameMode.Missions))
{
MissionPrefab missionPrefab = mission.Prefab;
if (missionPrefab != null &&
missionPrefab.AllowedLocationTypes.Any() &&
!missionPrefab.AllowedConnectionTypes.Any())
{
LocationType locationType = LocationType.List.FirstOrDefault(lt => missionPrefab.AllowedLocationTypes.Any(m => m.Equals(lt.Identifier, StringComparison.OrdinalIgnoreCase)));
CreateDummyLocations(locationType);
randomLevel = LevelData.CreateRandom(levelSeed, difficulty, requireOutpost: true);
break;
}
}
randomLevel ??= LevelData.CreateRandom(levelSeed, difficulty);
StartRound(randomLevel);
}
public void StartRound(LevelData levelData, bool mirrorLevel = false, SubmarineInfo startOutpost = null, SubmarineInfo endOutpost = null)
@@ -296,17 +337,11 @@ namespace Barotrauma
LevelData = levelData;
if (GameMode is CampaignMode campaignMode && GameMode.Mission != null &&
LevelData != null && LevelData.Type == LevelData.LevelType.Outpost)
{
campaignMode.Map.CurrentLocation.SelectedMission = null;
}
Submarine.Unload();
Submarine = Submarine.MainSub = new Submarine(SubmarineInfo);
foreach (Submarine sub in Submarine.GetConnectedSubs())
{
sub.TeamID = Character.TeamType.Team1;
sub.TeamID = CharacterTeamType.Team1;
foreach (Item item in Item.ItemList)
{
if (item.Submarine != sub) { continue; }
@@ -316,7 +351,7 @@ namespace Barotrauma
}
}
}
if (GameMode.Mission != null && GameMode.Mission.TeamCount > 1 && Submarine.MainSubs[1] == null)
if (GameMode is PvPMode && Submarine.MainSubs[1] == null)
{
Submarine.MainSubs[1] = new Submarine(SubmarineInfo, true);
}
@@ -329,11 +364,6 @@ namespace Barotrauma
InitializeLevel(level);
GameAnalyticsManager.AddDesignEvent("Submarine:" + Submarine.Info.Name);
GameAnalyticsManager.AddDesignEvent("Level", ToolBox.StringToInt(levelData?.Seed ?? "[NO_LEVEL]"));
GameAnalyticsManager.AddProgressionEvent(GameAnalyticsSDK.Net.EGAProgressionStatus.Start,
GameMode.Preset.Identifier, (Mission == null ? "None" : Mission.GetType().ToString()));
#if CLIENT
if (GameMode is CampaignMode) { SteamAchievementManager.OnBiomeDiscovered(levelData.Biome); }
@@ -343,7 +373,7 @@ namespace Barotrauma
existingRoundSummary.ContinueButton.Visible = true;
}
RoundSummary = new RoundSummary(Submarine.Info, GameMode, Mission, StartLocation, EndLocation);
RoundSummary = new RoundSummary(Submarine.Info, GameMode, Missions, StartLocation, EndLocation);
if (!(GameMode is TutorialMode) && !(GameMode is TestGameMode))
{
@@ -352,7 +382,16 @@ namespace Barotrauma
{
GUI.AddMessage(levelData.Biome.DisplayName, Color.Lerp(Color.CadetBlue, Color.DarkRed, levelData.Difficulty / 100.0f), 5.0f, playSound: false);
GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Destination"), EndLocation.Name), Color.CadetBlue, playSound: false);
GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), (Mission == null ? TextManager.Get("None") : Mission.Name)), Color.CadetBlue, playSound: false);
if (missions.Count > 1)
{
string joinedMissionNames = string.Join(", ", missions.Select(m => m.Name));
GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), joinedMissionNames), Color.CadetBlue, playSound: false);
}
else
{
var mission = missions.FirstOrDefault();
GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), mission?.Name ?? TextManager.Get("None")), Color.CadetBlue, playSound: false);
}
}
else
{
@@ -389,16 +428,18 @@ namespace Barotrauma
Entity.Spawner = new EntitySpawner();
if (GameMode.Mission != null) { Mission = GameMode.Mission; }
if (GameMode != null) { GameMode.Start(); }
if (GameMode.Mission != null)
missions.Clear();
GameMode.AddExtraMissions(LevelData);
missions.AddRange(GameMode.Missions);
GameMode.Start();
foreach (Mission mission in missions)
{
int prevEntityCount = Entity.GetEntities().Count();
Mission.Start(Level.Loaded);
mission.Start(Level.Loaded);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && Entity.GetEntities().Count() != prevEntityCount)
{
DebugConsole.ThrowError(
"Entity count has changed after starting a mission as a client. " +
$"Entity count has changed after starting a mission ({mission.Prefab.Identifier}) as a client. " +
"The clients should not instantiate entities themselves when starting the mission," +
" but instead the server should inform the client of the spawned entities using Mission.ServerWriteInitial.");
}
@@ -438,6 +479,8 @@ namespace Barotrauma
}
}
GameMain.Config.RecentlyEncounteredCreatures.Clear();
GameMain.GameScreen.Cam.Position = Character.Controlled?.WorldPosition ?? Submarine.MainSub.WorldPosition;
RoundStartTime = Timing.TotalTime;
GameMain.ResetFrameTime();
@@ -501,7 +544,7 @@ namespace Barotrauma
{
Submarine.SetPosition(spawnPos);
myPort.Dock(outPostPort);
myPort.Lock(true);
myPort.Lock(true, forcePosition: true, applyEffects: false);
}
else
{
@@ -540,21 +583,32 @@ namespace Barotrauma
{
EventManager?.Update(deltaTime);
GameMode?.Update(deltaTime);
Mission?.Update(deltaTime);
foreach (Mission mission in missions)
{
mission.Update(deltaTime);
}
UpdateProjSpecific(deltaTime);
}
public Mission GetMission(int index)
{
if (index < 0 || index >= missions.Count) { return null; }
return missions[index];
}
public int GetMissionIndex(Mission mission)
{
return missions.IndexOf(mission);
}
partial void UpdateProjSpecific(float deltaTime);
public void EndRound(string endMessage, List<TraitorMissionResult> traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
{
if (Mission != null) { Mission.End(); }
GameAnalyticsManager.AddProgressionEvent(
(Mission == null || Mission.Completed) ? GameAnalyticsSDK.Net.EGAProgressionStatus.Complete : GameAnalyticsSDK.Net.EGAProgressionStatus.Fail,
GameMode.Preset.Identifier,
Mission == null ? "None" : Mission.GetType().ToString());
foreach (Mission mission in missions)
{
mission.End();
}
#if CLIENT
if (GUI.PauseMenuOpen)
{
@@ -573,14 +627,14 @@ namespace Barotrauma
if (GameMain.NetLobbyScreen != null) GameMain.NetLobbyScreen.OnRoundEnded();
TabMenu.OnRoundEnded();
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction");
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb));
#endif
SteamAchievementManager.OnRoundEnded(this);
GameMode?.End(transitionType);
EventManager?.EndRound();
StatusEffect.StopAll();
Mission = null;
missions.Clear();
IsRunning = false;
}
@@ -531,7 +531,7 @@ namespace Barotrauma
List<int> levels = new List<int>();
foreach (XElement subElement in elements)
{
if (!category.CanBeApplied(subElement)) { continue; }
if (!category.CanBeApplied(subElement, prefab)) { continue; }
foreach (XElement component in subElement.Elements())
{