Release v0.15.12.0

This commit is contained in:
Joonas Rikkonen
2021-10-27 18:50:57 +03:00
parent bf95e82d80
commit 234fb6bc06
450 changed files with 26042 additions and 10457 deletions
@@ -8,14 +8,17 @@ namespace Barotrauma
{
static class AutoItemPlacer
{
private static readonly List<Item> spawnedItems = new List<Item>();
public static bool OutputDebugInfo = false;
/// <summary>
/// If we are spawning in an area where difficulty should not be a factor, assume difficulty is at the exact "middle"
/// </summary>
public const float DefaultDifficultyModifier = 0f;
public static void PlaceIfNeeded()
{
if (GameMain.NetworkMember != null && !GameMain.NetworkMember.IsServer) { return; }
for (int i = 0; i < Submarine.MainSubs.Length; i++)
{
if (Submarine.MainSubs[i] == null || Submarine.MainSubs[i].Info.InitialSuppliesSpawned) { continue; }
@@ -24,14 +27,18 @@ namespace Barotrauma
Place(subs);
subs.ForEach(s => s.Info.InitialSuppliesSpawned = true);
}
float difficultyModifier = GetLevelDifficultyModifier();
foreach (var sub in Submarine.Loaded)
{
if (sub.Info.Type == SubmarineType.Wreck ||
sub.Info.Type == SubmarineType.BeaconStation)
if (sub.Info.Type == SubmarineType.Player ||
sub.Info.Type == SubmarineType.Outpost ||
sub.Info.Type == SubmarineType.OutpostModule ||
sub.Info.Type == SubmarineType.EnemySubmarine)
{
Place(sub.ToEnumerable());
continue;
}
Place(sub.ToEnumerable(), difficultyModifier: difficultyModifier);
}
if (Level.Loaded?.StartOutpost != null && Level.Loaded.Type == LevelData.LevelType.Outpost)
@@ -41,7 +48,23 @@ namespace Barotrauma
}
}
private static void Place(IEnumerable<Submarine> subs)
private const float MaxDifficultyModifier = 0.2f;
/// <summary>
/// Spawn probability of loot is modified by difficulty, -20% less loot at 0% difficulty and +20% loot at 100% difficulty.
/// </summary>
private static float GetLevelDifficultyModifier()
{
return Math.Clamp(Level.Loaded?.Difficulty is float difficulty ? (difficulty / 100f) * (MaxDifficultyModifier * 2) - MaxDifficultyModifier : DefaultDifficultyModifier, -MaxDifficultyModifier, MaxDifficultyModifier);
}
public static void RegenerateLoot(Submarine sub, ItemContainer regeneratedContainer)
{
// Level difficulty currently doesn't affect regenerated loot for the sake of simplicity
Place(sub.ToEnumerable(), regeneratedContainer: regeneratedContainer);
}
private static void Place(IEnumerable<Submarine> subs, ItemContainer regeneratedContainer = null, float difficultyModifier = DefaultDifficultyModifier)
{
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
{
@@ -49,19 +72,29 @@ namespace Barotrauma
return;
}
List<Item> spawnedItems = new List<Item>(100);
int itemCountApprox = MapEntityPrefab.List.Count() / 3;
var containers = new List<ItemContainer>(70 + 30 * subs.Count());
var prefabsWithContainer = new List<ItemPrefab>(itemCountApprox / 3);
var prefabsWithoutContainer = new List<ItemPrefab>(itemCountApprox);
var removals = new List<ItemPrefab>();
foreach (Item item in Item.ItemList)
// generate loot only for a specific container if defined
if (regeneratedContainer != null)
{
if (!subs.Contains(item.Submarine)) { continue; }
if (item.GetRootInventoryOwner() is Character) { continue; }
containers.AddRange(item.GetComponents<ItemContainer>());
containers.Add(regeneratedContainer);
}
else
{
foreach (Item item in Item.ItemList)
{
if (!subs.Contains(item.Submarine)) { continue; }
if (item.GetRootInventoryOwner() is Character) { continue; }
containers.AddRange(item.GetComponents<ItemContainer>());
}
containers.Shuffle(Rand.RandSync.Server);
}
containers.Shuffle(Rand.RandSync.Server);
foreach (MapEntityPrefab prefab in MapEntityPrefab.List)
{
@@ -77,7 +110,6 @@ namespace Barotrauma
}
}
spawnedItems.Clear();
var validContainers = new Dictionary<ItemContainer, PreferredContainer>();
prefabsWithContainer.Shuffle(Rand.RandSync.Server);
// Spawn items that have an ItemContainer component first so we can fill them up with items if needed (oxygen tanks inside the spawned diving masks, etc)
@@ -152,8 +184,10 @@ namespace Barotrauma
}
foreach (var validContainer in validContainers)
{
if (SpawnItem(itemPrefab, containers, validContainer))
var newItems = SpawnItem(itemPrefab, containers, validContainer, difficultyModifier);
if (newItems.Any())
{
spawnedItems.AddRange(newItems);
success = true;
}
}
@@ -184,14 +218,22 @@ namespace Barotrauma
return validContainers;
}
private static bool SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer)
private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1]
{
bool success = false;
if (Rand.Value(Rand.RandSync.Server) > validContainer.Value.SpawnProbability) { return false; }
(0, 0.85f),
(1, 0.125f),
(2, 0.0225f),
(3, 0.0025f),
};
private static List<Item> SpawnItem(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer, float difficultyModifier)
{
List<Item> spawnedItems = new List<Item>();
if (Rand.Value(Rand.RandSync.Server) > validContainer.Value.SpawnProbability * (1f + difficultyModifier)) { return spawnedItems; }
// Don't add dangerously reactive materials in thalamus wrecks
if (validContainer.Key.Item.Submarine.WreckAI != null && itemPrefab.Tags.Contains("explodesinwater"))
{
return false;
return spawnedItems;
}
int amount = Rand.Range(validContainer.Value.MinAmount, validContainer.Value.MaxAmount + 1, Rand.RandSync.Server);
for (int i = 0; i < amount; i++)
@@ -201,11 +243,20 @@ namespace Barotrauma
containers.Remove(validContainer.Key);
break;
}
if (!validContainer.Key.Inventory.CanBePut(itemPrefab)) { break; }
var existingItem = validContainer.Key.Inventory.AllItems.FirstOrDefault(it => it.prefab == itemPrefab);
int quality =
existingItem?.Quality ??
ToolBox.SelectWeightedRandom(
qualityCommonnesses.Select(q => q.quality).ToList(),
qualityCommonnesses.Select(q => q.commonness).ToList(),
Rand.RandSync.Server);
if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; }
var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine)
{
SpawnedInOutpost = validContainer.Key.Item.SpawnedInOutpost,
AllowStealing = validContainer.Key.Item.AllowStealing,
Quality = quality,
OriginalModuleIndex = validContainer.Key.Item.OriginalModuleIndex,
OriginalContainerIndex =
Item.ItemList.Where(it => it.Submarine == validContainer.Key.Item.Submarine && it.OriginalModuleIndex == validContainer.Key.Item.OriginalModuleIndex).ToList().IndexOf(validContainer.Key.Item)
@@ -217,9 +268,8 @@ namespace Barotrauma
spawnedItems.Add(item);
validContainer.Key.Inventory.TryPutItem(item, null, createNetworkEvent: false);
containers.AddRange(item.GetComponents<ItemContainer>());
success = true;
}
return success;
return spawnedItems;
}
}
}
@@ -237,6 +237,9 @@ namespace Barotrauma
{
CharacterInfo.ApplyHealthData(character, character.Info.HealthData);
}
character.LoadTalents();
character.GiveIdCardTags(spawnWaypoints[i]);
character.Info.StartItemsGiven = true;
if (character.Info.OrderData != null)
@@ -486,6 +489,11 @@ namespace Barotrauma
continue;
}
}
if (orderInfo.Order.TargetEntity == null || (orderInfo.Order.IsIgnoreOrder && ignoreTarget == null))
{
// The order target doesn't exist anymore, just discard the loaded order
continue;
}
if (ignoreTarget != null)
{
ignoreTarget.OrderedToBeIgnored = true;
@@ -1,5 +1,6 @@
using Microsoft.Xna.Framework;
using System;
using System.Linq;
namespace Barotrauma
{
@@ -31,7 +32,7 @@ namespace Barotrauma
public float Value
{
get => Math.Min(MaxReputation, Metadata.GetFloat(metaDataIdentifier, InitialReputation));
set
private set
{
if (MathUtils.NearlyEqual(Value, value)) { return; }
Metadata.SetValue(metaDataIdentifier, Math.Clamp(value, MinReputation, MaxReputation));
@@ -40,6 +41,25 @@ namespace Barotrauma
}
}
public void SetReputation(float newReputation)
{
Value = newReputation;
}
public void AddReputation(float reputationChange)
{
if (reputationChange > 0f)
{
float reputationGainMultiplier = 1f;
foreach (Character character in GameSession.GetSessionCrewCharacters())
{
reputationGainMultiplier += character.GetStatValue(StatTypes.ReputationGainMultiplier);
}
reputationChange *= reputationGainMultiplier;
}
Value += reputationChange;
}
public Action OnReputationValueChanged;
public static Action OnAnyReputationValueChanged;
@@ -1,12 +1,11 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Networking;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -17,7 +16,15 @@ namespace Barotrauma
// Anything that uses this field I wasn't sure if actually needed the proper campaign settings to be passed down
public static CampaignSettings Unsure = Empty;
public bool RadiationEnabled { get; set; }
public int MaxMissionCount { get; set; }
public int TotalMaxMissionCount => MaxMissionCount + GetAddedMissionCount();
private int maxMissionCount;
public int MaxMissionCount
{
get { return maxMissionCount; }
set { maxMissionCount = MathHelper.Clamp(value, MinMissionCountLimit, MaxMissionCountLimit); }
}
public const int DefaultMaxMissionCount = 2;
public const int MaxMissionCountLimit = 10;
@@ -25,14 +32,16 @@ namespace Barotrauma
public CampaignSettings(IReadMessage inc)
{
maxMissionCount = DefaultMaxMissionCount;
RadiationEnabled = inc.ReadBoolean();
MaxMissionCount = inc.ReadInt32();
}
public CampaignSettings(XElement element)
{
RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLower(), true);
MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLower(), DefaultMaxMissionCount);
maxMissionCount = DefaultMaxMissionCount;
RadiationEnabled = element.GetAttributeBool(nameof(RadiationEnabled).ToLowerInvariant(), true);
MaxMissionCount = element.GetAttributeInt(nameof(MaxMissionCount).ToLowerInvariant(), DefaultMaxMissionCount);
}
public void Serialize(IWriteMessage msg)
@@ -41,9 +50,19 @@ namespace Barotrauma
msg.Write(MaxMissionCount);
}
public int GetAddedMissionCount()
{
int count = 0;
foreach (Character character in GameSession.GetSessionCrewCharacters())
{
count += (int)character.GetStatValue(StatTypes.ExtraMissionCount);
}
return count;
}
public XElement Save()
{
return new XElement(nameof(CampaignSettings), new XAttribute(nameof(RadiationEnabled).ToLower(), RadiationEnabled), new XAttribute(nameof(MaxMissionCount).ToLower().ToLower(), MaxMissionCount));
return new XElement(nameof(CampaignSettings), new XAttribute(nameof(RadiationEnabled).ToLowerInvariant(), RadiationEnabled), new XAttribute(nameof(MaxMissionCount).ToLowerInvariant(), MaxMissionCount));
}
}
@@ -226,6 +245,8 @@ namespace Barotrauma
PurchasedLostShuttles = false;
var connectedSubs = Submarine.MainSub.GetConnectedSubs();
wasDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost);
ResetTalentData();
}
public void InitCampaignData()
@@ -522,6 +543,7 @@ namespace Barotrauma
if (Level.Loaded.EndOutpost == null)
{
Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndExitPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: leavingPlayers.FirstOrDefault()?.TeamID);
if (closestSub == null) { return null; }
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
}
else
@@ -546,13 +568,16 @@ namespace Barotrauma
public override void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
{
List<Item> takenItems = new List<Item>();
foreach (Item item in Item.ItemList)
if (Level.Loaded?.Type == LevelData.LevelType.Outpost)
{
if (!item.SpawnedInOutpost || item.OriginalModuleIndex < 0) { continue; }
var owner = item.GetRootInventoryOwner();
if ((!(owner?.Submarine?.Info?.IsOutpost ?? false)) || (owner is Character character && character.TeamID == CharacterTeamType.Team1) || item.Submarine == null || !item.Submarine.Info.IsOutpost)
foreach (Item item in Item.ItemList)
{
takenItems.Add(item);
if (!item.SpawnedInOutpost || item.OriginalModuleIndex < 0) { continue; }
var owner = item.GetRootInventoryOwner();
if ((!(owner?.Submarine?.Info?.IsOutpost ?? false)) || (owner is Character character && character.TeamID == CharacterTeamType.Team1) || item.Submarine == null || !item.Submarine.Info.IsOutpost)
{
takenItems.Add(item);
}
}
}
if (map != null && CargoManager != null)
@@ -642,16 +667,7 @@ namespace Barotrauma
}
foreach (Location location in Map.Locations)
{
if (location.Type != location.OriginalType)
{
location.ChangeType(location.OriginalType);
location.PendingLocationTypeChange = null;
}
location.CreateStore(force: true);
location.ClearMissions();
location.Discovered = false;
location.LevelData?.EventHistory?.Clear();
location.UnlockInitialMissions();
location.Reset();
}
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
Map.SelectLocation(-1);
@@ -846,7 +862,7 @@ namespace Barotrauma
Location location = Map?.CurrentLocation;
if (location != null)
{
location.Reputation.Value -= attackResult.Damage * Reputation.ReputationLossPerNPCDamage;
location.Reputation.AddReputation(-attackResult.Damage * Reputation.ReputationLossPerNPCDamage);
}
}
@@ -898,15 +914,24 @@ namespace Barotrauma
{
foreach (Location location in currentLocation.Connections.Select(c => c.OtherLocation(currentLocation)))
{
if (NumberOfMissionsAtLocation(location) > Settings.MaxMissionCount)
if (NumberOfMissionsAtLocation(location) > Settings.TotalMaxMissionCount)
{
DebugConsole.AddWarning($"Client {sender.Name} had too many missions selected for location {location.Name}! Count was {NumberOfMissionsAtLocation(location)}. Deselecting extra missions.");
foreach (Mission mission in currentLocation.SelectedMissions.Where(m => m.Locations[1] == location).Skip(Settings.MaxMissionCount).ToList())
foreach (Mission mission in currentLocation.SelectedMissions.Where(m => m.Locations[1] == location).Skip(Settings.TotalMaxMissionCount).ToList())
{
currentLocation.DeselectMission(mission);
}
}
}
}
// Talent relevant data, only stored for the duration of the mission
private void ResetTalentData()
{
CrewHasDied = false;
}
public bool CrewHasDied { get; set; }
}
}
@@ -1,6 +1,4 @@
using Barotrauma.Networking;
using System.Globalization;
using System.Xml.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
@@ -29,65 +27,6 @@ namespace Barotrauma
private XElement healthData;
public XElement OrderData { get; private set; }
partial void InitProjSpecific(Client client);
public CharacterCampaignData(Client client, bool giveRespawnPenaltyAffliction = false)
{
Name = client.Name;
InitProjSpecific(client);
healthData = new XElement("health");
client.Character?.CharacterHealth?.Save(healthData);
if (giveRespawnPenaltyAffliction)
{
var respawnPenaltyAffliction = RespawnManager.GetRespawnPenaltyAffliction();
healthData.Add(new XElement("Affliction",
new XAttribute("identifier", respawnPenaltyAffliction.Identifier),
new XAttribute("strength", respawnPenaltyAffliction.Strength.ToString("G", CultureInfo.InvariantCulture))));
}
if (client.Character?.Inventory != null)
{
itemData = new XElement("inventory");
Character.SaveInventory(client.Character.Inventory, itemData);
}
OrderData = new XElement("orders");
if (client.Character != null)
{
CharacterInfo.SaveOrderData(client.Character.Info, OrderData);
}
}
public CharacterCampaignData(XElement element)
{
Name = element.GetAttributeString("name", "Unnamed");
ClientEndPoint = element.GetAttributeString("endpoint", null) ?? element.GetAttributeString("ip", "");
string steamID = element.GetAttributeString("steamid", "");
if (!string.IsNullOrEmpty(steamID))
{
ulong.TryParse(steamID, out ulong parsedID);
SteamID = parsedID;
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "character":
case "characterinfo":
CharacterInfo = new CharacterInfo(subElement);
break;
case "inventory":
itemData = subElement;
break;
case "health":
healthData = subElement;
break;
case "orders":
OrderData = subElement;
break;
}
}
}
public void Refresh(Character character)
{
healthData = new XElement("health");
@@ -173,6 +173,12 @@ namespace Barotrauma
if (matchingSub != null) { availableSubs.Add(matchingSub); }
}
break;
case "savedexperiencepoints":
foreach (XElement savedExp in subElement.Elements())
{
savedExperiencePoints.Add(new SavedExperiencePoints(savedExp));
}
break;
#endif
}
}
@@ -29,6 +29,8 @@ namespace Barotrauma
public bool IsRunning { get; private set; }
public bool RoundEnding { get; private set; }
public Level Level { get; private set; }
public LevelData LevelData { get; private set; }
@@ -450,9 +452,11 @@ namespace Barotrauma
StatusEffect.StopAll();
#if CLIENT
#if !DEBUG
GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null;
#endif
if (GameMain.LightManager.LosEnabled) { GameMain.LightManager.LosAlpha = 1f; }
if (GameMain.Client == null) GameMain.LightManager.LosMode = GameMain.Config.LosMode;
if (GameMain.Client == null) { GameMain.LightManager.LosMode = GameMain.Config.LosMode; }
#endif
LevelData = level?.LevelData;
Level = level;
@@ -654,43 +658,89 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime);
public static IEnumerable<Character> GetSessionCrewCharacters()
{
#if SERVER
return GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead);
#else
if (GameMain.GameSession == null) { return Enumerable.Empty<Character>(); }
return GameMain.GameSession.CrewManager.GetCharacters().Where(c => c?.Info != null && !c.IsDead);
#endif
}
public void EndRound(string endMessage, List<TraitorMissionResult> traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
{
foreach (Mission mission in missions)
{
mission.End();
}
#if CLIENT
if (GUI.PauseMenuOpen)
{
GUI.TogglePauseMenu();
}
GUI.PreventPauseMenuToggle = true;
RoundEnding = true;
if (!(GameMode is TestGameMode) && Screen.Selected == GameMain.GameScreen && RoundSummary != null)
try
{
GUI.ClearMessages();
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary);
GUIFrame summaryFrame = RoundSummary.CreateSummaryFrame(this, endMessage, traitorResults, transitionType);
GUIMessageBox.MessageBoxes.Add(summaryFrame);
RoundSummary.ContinueButton.OnClicked = (_, __) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; };
}
IEnumerable<Character> crewCharacters = GetSessionCrewCharacters();
if (GameMain.NetLobbyScreen != null) GameMain.NetLobbyScreen.OnRoundEnded();
TabMenu.OnRoundEnded();
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb));
#endif
SteamAchievementManager.OnRoundEnded(this);
foreach (Mission mission in missions)
{
mission.End();
}
GameMode?.End(transitionType);
EventManager?.EndRound();
StatusEffect.StopAll();
missions.Clear();
IsRunning = false;
foreach (Character character in crewCharacters)
{
character.CheckTalents(AbilityEffectType.OnRoundEnd);
}
if (missions.Any())
{
if (missions.Any(m => m.Completed))
{
foreach (Character character in crewCharacters)
{
character.CheckTalents(AbilityEffectType.OnAnyMissionCompleted);
}
}
if (missions.All(m => m.Completed))
{
foreach (Character character in crewCharacters)
{
character.CheckTalents(AbilityEffectType.OnAllMissionsCompleted);
}
}
}
#if CLIENT
HintManager.OnRoundEnded();
if (GUI.PauseMenuOpen)
{
GUI.TogglePauseMenu();
}
GUI.PreventPauseMenuToggle = true;
if (!(GameMode is TestGameMode) && Screen.Selected == GameMain.GameScreen && RoundSummary != null)
{
GUI.ClearMessages();
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary);
GUIFrame summaryFrame = RoundSummary.CreateSummaryFrame(this, endMessage, traitorResults, transitionType);
GUIMessageBox.MessageBoxes.Add(summaryFrame);
RoundSummary.ContinueButton.OnClicked = (_, __) => { GUIMessageBox.MessageBoxes.Remove(summaryFrame); return true; };
}
if (GameMain.NetLobbyScreen != null) { GameMain.NetLobbyScreen.OnRoundEnded(); }
TabMenu.OnRoundEnded();
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb));
#endif
SteamAchievementManager.OnRoundEnded(this);
GameMode?.End(transitionType);
EventManager?.EndRound();
StatusEffect.StopAll();
missions.Clear();
IsRunning = false;
#if CLIENT
HintManager.OnRoundEnded();
#endif
}
finally
{
RoundEnding = false;
}
}
public void KillCharacter(Character character)