Unstable 1.8.4.0

This commit is contained in:
Markus Isberg
2025-03-12 12:56:27 +00:00
parent a4c3e868e4
commit a4a3427e4e
627 changed files with 29860 additions and 10018 deletions
@@ -1,4 +1,5 @@
using Barotrauma.Items.Components;
using System;
using Barotrauma.Items.Components;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
@@ -21,9 +22,9 @@ namespace Barotrauma
public Skill PrimarySkill { get; private set; }
public Job(JobPrefab jobPrefab) : this(jobPrefab, randSync: Rand.RandSync.Unsynced, variant: 0) { }
public Job(JobPrefab jobPrefab, bool isPvP) : this(jobPrefab, isPvP, randSync: Rand.RandSync.Unsynced, variant: 0) { }
public Job(JobPrefab jobPrefab, Rand.RandSync randSync, int variant, params Skill[] s)
public Job(JobPrefab jobPrefab, bool isPvP, Rand.RandSync randSync, int variant, params Skill[] s)
{
prefab = jobPrefab;
Variant = variant;
@@ -40,7 +41,7 @@ namespace Barotrauma
}
else
{
skill = new Skill(skillPrefab, randSync);
skill = new Skill(skillPrefab, isPvP, randSync);
skills.Add(skillPrefab.Identifier, skill);
}
if (skillPrefab.IsPrimarySkill) { PrimarySkill = skill; }
@@ -74,11 +75,11 @@ namespace Barotrauma
}
}
public static Job Random(Rand.RandSync randSync)
public static Job Random(bool isPvP, Rand.RandSync randSync)
{
var prefab = JobPrefab.Random(randSync);
var variant = Rand.Range(0, prefab.Variants, randSync);
return new Job(prefab, randSync, variant);
int variant = Rand.Range(0, prefab.Variants, randSync);
return new Job(prefab, isPvP, randSync, variant);
}
public IEnumerable<Skill> GetSkills()
@@ -127,14 +128,24 @@ namespace Barotrauma
new Skill(skillIdentifier, increase));
}
}
/// <summary>
/// Note: Does not automatically filter items by team or by game mode. See <see cref="JobItem.GetItemIdentifier(CharacterTeamType?, JobItem.GameModeType)"/>
/// </summary>
public bool HasJobItem(Func<JobPrefab.JobItem, bool> predicate) => prefab.HasJobItem(Variant, predicate);
public void GiveJobItems(Character character, WayPoint spawnPoint = null)
public void GiveJobItems(Character character, bool isPvPMode, WayPoint spawnPoint = null)
{
if (!prefab.ItemSets.TryGetValue(Variant, out var spawnItems)) { return; }
if (!prefab.JobItems.TryGetValue(Variant, out var spawnItems)) { return; }
foreach (XElement itemElement in spawnItems.GetChildElements("Item"))
foreach (JobPrefab.JobItem jobItem in spawnItems)
{
InitializeJobItem(character, itemElement, spawnPoint);
//spawn the "root items" here, InitializeJobItem goes through the children recursively
if (jobItem.ParentItem != null) { continue; }
for (int i = 0; i < jobItem.Amount; i++)
{
InitializeJobItem(character, isPvPMode, jobItem, spawnItems, spawnPoint);
}
}
if (GameMain.GameSession is { TraitorsEnabled: true } && character.IsSecurity)
@@ -144,29 +155,14 @@ namespace Barotrauma
}
}
private void InitializeJobItem(Character character, XElement itemElement, WayPoint spawnPoint = null, Item parentItem = null)
private void InitializeJobItem(Character character, bool isPvPMode, JobPrefab.JobItem jobItem, IEnumerable<JobPrefab.JobItem> allJobItems, WayPoint spawnPoint = null, Item parentItem = null)
{
ItemPrefab itemPrefab;
if (itemElement.Attribute("name") != null)
Identifier itemIdentifier = jobItem.GetItemIdentifier(character.TeamID, isPvPMode);
if (itemIdentifier.IsEmpty) { return; }
if ((MapEntityPrefab.FindByIdentifier(itemIdentifier) ?? MapEntityPrefab.FindByName(itemIdentifier.Value)) is not ItemPrefab itemPrefab)
{
string itemName = itemElement.Attribute("name").Value;
DebugConsole.ThrowErrorLocalized("Error in Job config (" + Name + ") - use item identifiers instead of names to configure the items.");
itemPrefab = MapEntityPrefab.FindByName(itemName) as ItemPrefab;
if (itemPrefab == null)
{
DebugConsole.ThrowErrorLocalized("Tried to spawn \"" + Name + "\" with the item \"" + itemName + "\". Matching item prefab not found.");
return;
}
}
else
{
string itemIdentifier = itemElement.GetAttributeString("identifier", "");
itemPrefab = MapEntityPrefab.FindByIdentifier(itemIdentifier.ToIdentifier()) as ItemPrefab;
if (itemPrefab == null)
{
DebugConsole.ThrowErrorLocalized("Tried to spawn \"" + Name + "\" with the item \"" + itemIdentifier + "\". Matching item prefab not found.");
return;
}
DebugConsole.ThrowErrorLocalized($"Tried to spawn \"{Name}\" with the item \"{itemIdentifier}\". Matching item prefab not found.");
return;
}
Item item = new Item(itemPrefab, character.Position, null);
@@ -187,7 +183,7 @@ namespace Barotrauma
}
#endif
if (itemElement.GetAttributeBool("equip", false))
if (jobItem.Equip)
{
//if the item is both pickable and wearable, try to wear it instead of picking it up
List<InvSlotType> allowedSlots =
@@ -229,12 +225,18 @@ namespace Barotrauma
wifiComponent.TeamID = character.TeamID;
}
if (parentItem != null) { parentItem.Combine(item, user: null); }
parentItem?.Combine(item, user: null);
foreach (XElement childItemElement in itemElement.Elements())
foreach (JobPrefab.JobItem childItem in allJobItems)
{
InitializeJobItem(character, childItemElement, spawnPoint, item);
}
if (childItem.ParentItem == jobItem)
{
for (int i = 0; i < childItem.Amount; i++)
{
InitializeJobItem(character, isPvPMode, childItem, allJobItems, spawnPoint, parentItem: item);
}
}
}
}
public XElement Save(XElement parentElement)
@@ -94,21 +94,73 @@ namespace Barotrauma
return null;
}
}
/// <summary>
/// Note: Does not automatically filter items by team or by game mode. See <see cref="JobItem.GetItemIdentifier(CharacterTeamType, bool)"/>
/// </summary>
public IEnumerable<JobItem> GetJobItems(int jobVariant, Func<JobItem, bool> predicate)
=> JobItems.TryGetValue(jobVariant, out ImmutableArray<JobItem> items) ? items.Where(predicate) : Enumerable.Empty<JobItem>();
/// <summary>
/// Note: Does not automatically filter items by team or by game mode. See <see cref="JobItem.GetItemIdentifier(CharacterTeamType, bool)"/>
/// </summary>
public bool HasJobItem(int jobVariant, Func<JobItem, bool> predicate)
=> JobItems.TryGetValue(jobVariant, out ImmutableArray<JobItem> items) && items.Any(predicate);
public class PreviewItem
public class JobItem
{
public readonly Identifier ItemIdentifier;
public readonly bool ShowPreview;
public PreviewItem(Identifier itemIdentifier, bool showPreview)
public enum GameModeType
{
ItemIdentifier = itemIdentifier;
ShowPreview = showPreview;
Any, PvP, PvE
}
public readonly Identifier ItemIdentifier;
public readonly Identifier ItemIdentifierTeam2;
public readonly bool ShowPreview;
public readonly bool Equip;
public readonly bool Outfit;
public readonly int Amount;
public readonly bool Infinite;
public readonly JobItem ParentItem;
public readonly GameModeType GameMode;
public JobItem(ContentXElement element, JobItem parentItem)
{
ItemIdentifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
ItemIdentifierTeam2 = element.GetAttributeIdentifier("identifierteam2", Identifier.Empty);
ShowPreview = element.GetAttributeBool(nameof(ShowPreview), true);
GameMode = element.GetAttributeEnum(nameof(GameMode), parentItem?.GameMode ?? GameModeType.Any);
Amount = element.GetAttributeInt(nameof(Amount), 1);
Equip = element.GetAttributeBool(nameof(Equip), false);
Outfit = element.GetAttributeBool(nameof(Outfit), false);
Infinite = element.GetAttributeBool(nameof(Infinite), false);
ParentItem = parentItem;
}
public Identifier GetItemIdentifier(CharacterTeamType team, bool isPvPMode)
{
switch (GameMode)
{
case GameModeType.PvP:
if (!isPvPMode) { return Identifier.Empty; }
break;
case GameModeType.PvE:
if (isPvPMode) { return Identifier.Empty; }
break;
}
return
team == CharacterTeamType.Team2 && !ItemIdentifierTeam2.IsEmpty ?
ItemIdentifierTeam2 :
ItemIdentifier;
}
}
public readonly Dictionary<int, ContentXElement> ItemSets = new Dictionary<int, ContentXElement>();
public readonly ImmutableDictionary<int, ImmutableArray<PreviewItem>> PreviewItems;
/// <summary>
/// The items the character can get when spawning. The key is the index of the job variant.
/// </summary>
public readonly ImmutableDictionary<int, ImmutableArray<JobItem>> JobItems;
public readonly List<SkillPrefab> Skills = new List<SkillPrefab>();
public readonly List<AutonomousObjective> AutonomousObjectives = new List<AutonomousObjective>();
public readonly List<Identifier> AppropriateOrders = new List<Identifier>();
@@ -145,6 +197,13 @@ namespace Barotrauma
private set;
}
[Serialize(10, IsPropertySaveable.No, description: "Determines the order of the characters in the campaign setup ui.")]
public int CampaignSetupUIOrder
{
get;
private set;
}
[Serialize(false, IsPropertySaveable.No, description: "If set to true, a client that has chosen this as their preferred job will get it regardless of the maximum number or the amount of spawnpoints in the sub.")]
public bool AllowAlways
{
@@ -211,7 +270,7 @@ namespace Barotrauma
Description = TextManager.Get("JobDescription." + Identifier);
Element = element;
var previewItems = new Dictionary<int, List<PreviewItem>>();
var jobItems = new Dictionary<int, List<JobItem>>();
int variant = 0;
foreach (var subElement in element.Elements())
@@ -219,9 +278,8 @@ namespace Barotrauma
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "itemset":
ItemSets.Add(variant, subElement);
previewItems[variant] = new List<PreviewItem>();
loadItemIdentifiers(subElement, variant);
jobItems[variant] = new List<JobItem>();
loadJobItems(subElement, variant, parentItem: null);
variant++;
break;
case "skills":
@@ -246,35 +304,39 @@ namespace Barotrauma
}
}
void loadItemIdentifiers(XElement parentElement, int variant)
void loadJobItems(ContentXElement parentElement, int variant, JobItem parentItem)
{
foreach (XElement itemElement in parentElement.GetChildElements("Item"))
foreach (ContentXElement itemElement in parentElement.GetChildElements("Item"))
{
if (itemElement.Element("name") != null)
if (itemElement.GetAttribute("name") != null)
{
DebugConsole.ThrowErrorLocalized("Error in job config \"" + Name + "\" - use identifiers instead of names to configure the items.");
DebugConsole.ThrowErrorLocalized("Error in job config \"" + Name + "\" - use identifiers instead of names to configure the items.",
contentPackage: parentElement.ContentPackage);
continue;
}
Identifier itemIdentifier = itemElement.GetAttributeIdentifier("identifier", Identifier.Empty);
JobItem jobItem = null;
if (itemIdentifier.IsEmpty)
{
DebugConsole.ThrowErrorLocalized("Error in job config \"" + Name + "\" - item with no identifier.");
DebugConsole.ThrowErrorLocalized("Error in job config \"" + Name + "\" - item with no identifier.",
contentPackage: parentElement.ContentPackage);
}
else
{
previewItems[variant].Add(new PreviewItem(itemIdentifier, itemElement.GetAttributeBool("showpreview", true)));
jobItem = new JobItem(itemElement, parentItem);
jobItems[variant].Add(jobItem);
}
loadItemIdentifiers(itemElement, variant);
loadJobItems(itemElement, variant, parentItem: jobItem);
}
}
PreviewItems = previewItems.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray()))
JobItems = jobItems.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray()))
.ToImmutableDictionary();
Variants = variant;
Skills.Sort((x,y) => y.LevelRange.Start.CompareTo(x.LevelRange.Start));
Skills.Sort((x,y) => y.GetLevelRange(isPvP: false).Start.CompareTo(x.GetLevelRange(isPvP: false).Start));
}
public static JobPrefab Random(Rand.RandSync sync, Func<JobPrefab, bool> predicate = null) => Prefabs.GetRandom(p => !p.HiddenJob && (predicate == null || predicate(p)), sync);
@@ -6,7 +6,10 @@ namespace Barotrauma
{
public readonly Identifier Identifier;
public const float MaximumSkill = 100.0f;
/// <summary>
/// The "normal" maximum skill level. It's possible to go above this with certain talents, see <see cref="SkillSettings.MaximumSkillWithTalents"/>.
/// </summary>
public const float DefaultMaximumSkill = 100.0f;
private float level;
@@ -27,9 +30,21 @@ namespace Barotrauma
public LocalizedString DisplayName { get; private set; }
public void IncreaseSkill(float value, bool increasePastMax)
/// <summary>
/// Increase the skill level by a value. Handles clamping the level above the maximum.
/// Note that if the skill level is already above maximum (if it for example has been set by console commands), it's allowed to stay at that level, but not to increase further.
/// </summary>
/// <param name="value">How much to increase the skill.</param>
/// <param name="canIncreasePastDefaultMaximumSkill">Can the skill level increase above <see cref="DefaultMaximumSkill"/>, or can it go all the way to <see cref="SkillSettings.MaximumSkillWithTalents"/>?</param>
public void IncreaseSkill(float value, bool canIncreasePastDefaultMaximumSkill)
{
Level = MathHelper.Clamp(level + value, 0.0f, increasePastMax ? SkillSettings.Current.MaximumSkillWithTalents : MaximumSkill);
float currentMaximum = canIncreasePastDefaultMaximumSkill ? SkillSettings.Current.MaximumSkillWithTalents : DefaultMaximumSkill;
if (Level > currentMaximum && value > 0)
{
//level above max already (set with console commands?), don't allow increasing it further and don't clamp it below max either
return;
}
Level = MathHelper.Clamp(level + value, 0.0f, currentMaximum);
}
private readonly Identifier iconJobId;
@@ -40,10 +55,12 @@ namespace Barotrauma
public readonly float PriceMultiplier = 1.0f;
public Skill(SkillPrefab prefab, Rand.RandSync randSync)
public Skill(SkillPrefab prefab, bool isPvP, Rand.RandSync randSync)
{
Identifier = prefab.Identifier;
Level = Rand.Range(prefab.LevelRange.Start, prefab.LevelRange.End, randSync);
var levelRange = prefab.GetLevelRange(isPvP);
Level = Rand.Range(levelRange.Start, levelRange.End, randSync);
iconJobId = GetIconJobId();
PriceMultiplier = prefab.PriceMultiplier;
DisplayName = TextManager.Get("SkillName." + Identifier);
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework;
using System.Xml.Linq;
using System.Globalization;
namespace Barotrauma
{
@@ -7,7 +6,8 @@ namespace Barotrauma
{
public readonly Identifier Identifier;
public Range<float> LevelRange { get; private set; }
private readonly Range<float> levelRange;
private readonly Range<float> levelRangePvP;
/// <summary>
/// How much this skill affects characters' hiring cost
@@ -19,20 +19,33 @@ namespace Barotrauma
public SkillPrefab(ContentXElement element)
{
Identifier = element.GetAttributeIdentifier("identifier", "");
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 25.0f);
var levelString = element.GetAttributeString("level", "");
if (levelString.Contains(","))
{
var rangeVector2 = XMLExtensions.ParseVector2(levelString, false);
LevelRange = new Range<float>(rangeVector2.X, rangeVector2.Y);
}
else
{
float skillLevel = float.Parse(levelString, System.Globalization.CultureInfo.InvariantCulture);
LevelRange = new Range<float>(skillLevel, skillLevel);
}
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 15.0f);
levelRange = GetSkillRange("level", element, defaultValue: new Range<float>(0, 0));
levelRangePvP = GetSkillRange("pvplevel", element, defaultValue: levelRange);
IsPrimarySkill = element.GetAttributeBool("primary", false);
static Range<float> GetSkillRange(string attributeName, ContentXElement element, Range<float> defaultValue)
{
string levelString = element.GetAttributeString(attributeName, string.Empty);
if (levelString.Contains(','))
{
var rangeVector2 = XMLExtensions.ParseVector2(levelString, false);
return new Range<float>(rangeVector2.X, rangeVector2.Y);
}
else if (float.TryParse(levelString, NumberStyles.Any, CultureInfo.InvariantCulture, out float skillLevel))
{
return new Range<float>(skillLevel, skillLevel);
}
else
{
return defaultValue;
}
}
}
public Range<float> GetLevelRange(bool isPvP)
{
return isPvP ? levelRangePvP : levelRange;
}
}
}