Unstable 1.8.4.0
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.PerkBehaviors;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
internal sealed class DisembarkPerkPrefab : PrefabWithUintIdentifier
|
||||
{
|
||||
public static readonly PrefabCollection<DisembarkPerkPrefab> Prefabs = new PrefabCollection<DisembarkPerkPrefab>();
|
||||
|
||||
public LocalizedString Name { get; }
|
||||
public LocalizedString Description { get; }
|
||||
public Identifier SortCategory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// After the perks have been sorted by category and cost, they are sorted using this key.
|
||||
/// Use if you want the perks to be arranged in specific order when their cost are the same.
|
||||
/// </summary>
|
||||
public int SortKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// When set to an identifier of another perk, this perk cannot be selected unless the prerequisite perk is selected.
|
||||
/// </summary>
|
||||
public Identifier Prerequisite { get; }
|
||||
|
||||
/// <summary>
|
||||
/// When this perk is selected, the perks in this set cannot be selected at the same time.
|
||||
/// </summary>
|
||||
public ImmutableHashSet<Identifier> MutuallyExclusivePerks { get; }
|
||||
|
||||
public int Cost { get; }
|
||||
|
||||
public ImmutableArray<PerkBase> PerkBehaviors { get; }
|
||||
|
||||
public DisembarkPerkPrefab(ContentXElement element, DisembarkPerkFile prefabFile) : base(prefabFile, element.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
Name = TextManager.Get($"disembarkperk.{Identifier}").Fallback(Identifier.ToString());
|
||||
Description = TextManager.Get($"disembarkperkdescription.{Identifier}").Fallback("");
|
||||
Cost = element.GetAttributeInt("cost", 0);
|
||||
SortCategory = element.GetAttributeIdentifier("sortcategory", Identifier);
|
||||
Prerequisite = element.GetAttributeIdentifier("prerequisite", Identifier.Empty);
|
||||
MutuallyExclusivePerks = element.GetAttributeIdentifierImmutableHashSet("mutuallyexclusiveperks", ImmutableHashSet<Identifier>.Empty);
|
||||
SortKey = element.GetAttributeInt("sortkey", 0);
|
||||
|
||||
var builder = ImmutableArray.CreateBuilder<PerkBase>();
|
||||
foreach (var child in element.Elements())
|
||||
{
|
||||
if (PerkBase.TryLoadFromXml(child, this, out var perk))
|
||||
{
|
||||
builder.Add(perk);
|
||||
}
|
||||
}
|
||||
|
||||
PerkBehaviors = builder.ToImmutable();
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.PerkBehaviors
|
||||
{
|
||||
internal class GiveTalentPointPerk : PerkBase
|
||||
{
|
||||
[Serialize(0, IsPropertySaveable.Yes)]
|
||||
public int Amount { get; set; }
|
||||
|
||||
public GiveTalentPointPerk(ContentXElement element, DisembarkPerkPrefab prefab) : base(element, prefab) { }
|
||||
|
||||
public override void ApplyOnRoundStart(IReadOnlyCollection<Character> teamCharacters, Submarine teamSubmarine)
|
||||
{
|
||||
foreach (Character character in teamCharacters)
|
||||
{
|
||||
character.Info.AdditionalTalentPoints += Amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Barotrauma.PerkBehaviors
|
||||
{
|
||||
internal enum PerkSimulation
|
||||
{
|
||||
/// <summary>
|
||||
/// Perk is only run on the server,
|
||||
/// other parts of the game handle the client-side effects.
|
||||
/// Like serializable properties and affliction syncing.
|
||||
/// </summary>
|
||||
ServerOnly,
|
||||
/// <summary>
|
||||
/// Both the server and clients run the perk.
|
||||
/// </summary>
|
||||
ServerAndClients
|
||||
}
|
||||
|
||||
internal abstract class PerkBase : ISerializableEntity
|
||||
{
|
||||
public string Name { get; }
|
||||
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; }
|
||||
|
||||
public virtual PerkSimulation Simulation => PerkSimulation.ServerOnly;
|
||||
|
||||
public readonly DisembarkPerkPrefab Prefab;
|
||||
|
||||
protected PerkBase(ContentXElement element, DisembarkPerkPrefab prefab)
|
||||
{
|
||||
Name = element.Name.ToString();
|
||||
Prefab = prefab;
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
}
|
||||
|
||||
public virtual bool CanApply(SubmarineInfo submarine)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// You might notice that this function is not virtual.
|
||||
/// It was at first, but there was a misunderstanding in design,
|
||||
/// so I turned it into a kill switch for all perks for now.
|
||||
/// If we ever want to add perks that do work when a submarine is present,
|
||||
/// this function can be made virtual again and set to true in the appropriate perks.
|
||||
/// </summary>
|
||||
public bool CanApplyWithoutSubmarine()
|
||||
=> false;
|
||||
|
||||
public abstract void ApplyOnRoundStart(IReadOnlyCollection<Character> teamCharacters, Submarine? teamSubmarine);
|
||||
|
||||
public static bool TryLoadFromXml(ContentXElement element, DisembarkPerkPrefab prefab, [NotNullWhen(true)] out PerkBase? perk)
|
||||
{
|
||||
Type? type = ReflectionUtils.GetTypeWithBackwardsCompatibility(ToolBox.BarotraumaAssembly, "Barotrauma.PerkBehaviors", element.Name.ToString(), throwOnError: false, ignoreCase: true);
|
||||
if (type is null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a perk behavior of the type \"{element.Name}\".", contentPackage: element.ContentPackage);
|
||||
perk = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
object? instance = Activator.CreateInstance(type, element, prefab);
|
||||
if (instance is PerkBase perkInstance)
|
||||
{
|
||||
perk = perkInstance;
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new InvalidCastException($"Could not cast the instance of type \"{type}\" to a {nameof(PerkBase)}.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError(e.InnerException != null ? e.InnerException.ToString() : e.ToString(), contentPackage: element.ContentPackage);
|
||||
perk = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma.PerkBehaviors
|
||||
{
|
||||
internal class SpawnItemPerk : PerkBase
|
||||
{
|
||||
public SpawnItemPerk(ContentXElement element, DisembarkPerkPrefab prefab) : base(element, prefab) { }
|
||||
|
||||
public override PerkSimulation Simulation
|
||||
=> PerkSimulation.ServerOnly;
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier Identifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier Tag { get; set; }
|
||||
|
||||
[Serialize(0, IsPropertySaveable.Yes)]
|
||||
public int MinAmount { get; set; }
|
||||
|
||||
[Serialize(0f, IsPropertySaveable.Yes)]
|
||||
public float PerPlayer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When set to non-empty value, the perk will prioritize spawning items in containers
|
||||
/// with this tag or identifier over the item's primary and secondary preferred containers.
|
||||
/// </summary>
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier PriorityContainerTag { get; set; }
|
||||
|
||||
public override void ApplyOnRoundStart(IReadOnlyCollection<Character> teamCharacters, Submarine teamSubmarine)
|
||||
{
|
||||
if (teamSubmarine is null) { return; }
|
||||
|
||||
if (Entity.Spawner is null)
|
||||
{
|
||||
DebugConsole.ThrowError($"{nameof(SpawnItemPerk)} ({Prefab.Identifier}) failed to spawn items because EntitySpawner is null.");
|
||||
return;
|
||||
}
|
||||
|
||||
int amount = Math.Max(MinAmount, (int)MathF.Ceiling(PerPlayer * teamCharacters.Count));
|
||||
|
||||
if (Identifier.IsEmpty)
|
||||
{
|
||||
if (Tag.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"{nameof(SpawnItemPerk)} ({Prefab.Identifier}) failed to spawn items: neither identifier or tag is set.",
|
||||
contentPackage: Prefab.ContentPackage);
|
||||
return;
|
||||
}
|
||||
var matchingItems = ItemPrefab.Prefabs.Where(ip => ip.Tags.Contains(Tag));
|
||||
if (matchingItems.None())
|
||||
{
|
||||
DebugConsole.ThrowError($"{nameof(SpawnItemPerk)} ({Prefab.Identifier}) failed to spawn items: no items found with the tag \"{Tag}\".",
|
||||
contentPackage: Prefab.ContentPackage);
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
SpawnItem(matchingItems.GetRandomUnsynced(), amount: 1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
ItemPrefab prefab = ItemPrefab.Find(null, Identifier);
|
||||
if (prefab is null)
|
||||
{
|
||||
DebugConsole.ThrowError($"{nameof(SpawnItemPerk)} ({Prefab.Identifier}) failed to spawn items because the ItemPrefab \"{Identifier}\" was not found.",
|
||||
contentPackage: Prefab.ContentPackage);
|
||||
return;
|
||||
}
|
||||
SpawnItem(prefab, amount);
|
||||
}
|
||||
|
||||
void SpawnItem(ItemPrefab prefab, int amount)
|
||||
{
|
||||
SuitableContainers suitableContainers = FindSuitableContainers(prefab, teamSubmarine);
|
||||
|
||||
if (!suitableContainers.Any())
|
||||
{
|
||||
SpawnItemInCrate(prefab, teamSubmarine, amount);
|
||||
return;
|
||||
}
|
||||
SpawnInContainer(prefab, amount, suitableContainers, teamSubmarine);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct SuitableContainers(
|
||||
ICollection<ItemContainer> PriorityContainers,
|
||||
ICollection<ItemContainer> PreferredContainers,
|
||||
ICollection<ItemContainer> SecondaryContainers)
|
||||
{
|
||||
public bool Any()
|
||||
=> PriorityContainers.Count > 0
|
||||
|| PreferredContainers.Count > 0
|
||||
|| SecondaryContainers.Count > 0;
|
||||
}
|
||||
|
||||
private SuitableContainers FindSuitableContainers(ItemPrefab prefab, Submarine submarine)
|
||||
{
|
||||
HashSet<ItemContainer> priorityContainers = new();
|
||||
HashSet<ItemContainer> primaryContainers = new();
|
||||
HashSet<ItemContainer> secondaryContainers = new();
|
||||
|
||||
foreach (Item item in submarine.GetItems(alsoFromConnectedSubs: true))
|
||||
{
|
||||
if (item.GetComponent<Fabricator>() != null || item.GetComponent<Deconstructor>() != null) { continue; }
|
||||
if (item.NonInteractable || item.NonPlayerTeamInteractable || item.IsHidden) { continue; }
|
||||
|
||||
if (item.GetComponent<ItemContainer>() is { } container)
|
||||
{
|
||||
if (!container.CanBeContained(prefab)) { continue; }
|
||||
|
||||
var tags = item.GetTags();
|
||||
|
||||
if (!PriorityContainerTag.IsEmpty && (tags.Contains(PriorityContainerTag) || item.Prefab.Identifier == PriorityContainerTag))
|
||||
{
|
||||
priorityContainers.Add(container);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prefab.PreferredContainers.Any(pc => pc.Primary.Any(tags.Contains)))
|
||||
{
|
||||
primaryContainers.Add(container);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (prefab.PreferredContainers.Any(pc => pc.Secondary.Any(tags.Contains)))
|
||||
{
|
||||
secondaryContainers.Add(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new SuitableContainers(priorityContainers, primaryContainers, secondaryContainers);
|
||||
}
|
||||
|
||||
private static void SpawnItemInCrate(ItemPrefab prefab, Submarine submarine, int amount)
|
||||
{
|
||||
var purchasedItem = new PurchasedItem(prefab, amount, buyer: null);
|
||||
CargoManager.DeliverItemsToSub(new []{ purchasedItem }, submarine, cargoManager: null, showNotification: false);
|
||||
}
|
||||
|
||||
private static void SpawnInContainer(ItemPrefab prefab, int amount, SuitableContainers containers, Submarine submarine)
|
||||
{
|
||||
Dictionary<ItemContainer, int> containerAllocation = new();
|
||||
|
||||
int remaining = amount;
|
||||
|
||||
TryAllocate(containers.PriorityContainers);
|
||||
if (remaining > 0)
|
||||
{
|
||||
TryAllocate(containers.PreferredContainers);
|
||||
if (remaining > 0)
|
||||
{
|
||||
TryAllocate(containers.SecondaryContainers);
|
||||
}
|
||||
}
|
||||
|
||||
void TryAllocate(ICollection<ItemContainer> targetContainers)
|
||||
=> AllocateContainers(prefab, targetContainers, ref remaining, ref containerAllocation);
|
||||
|
||||
foreach (var (container, howManyToPut) in containerAllocation)
|
||||
{
|
||||
for (int i = 0; i < howManyToPut; i++)
|
||||
{
|
||||
SpawnItem(prefab, container);
|
||||
}
|
||||
}
|
||||
|
||||
if (remaining > 0)
|
||||
{
|
||||
SpawnItemInCrate(prefab, submarine, remaining);
|
||||
}
|
||||
|
||||
static void AllocateContainers(ItemPrefab prefab, ICollection<ItemContainer> containers, ref int remaining, ref Dictionary<ItemContainer, int> containerAllocation)
|
||||
{
|
||||
foreach (ItemContainer ic in containers)
|
||||
{
|
||||
int fit = ic.Inventory.HowManyCanBePut(prefab);
|
||||
if (fit <= 0) { continue; }
|
||||
|
||||
fit = Math.Min(fit, remaining);
|
||||
|
||||
containerAllocation.Add(ic, fit);
|
||||
remaining -= fit;
|
||||
|
||||
if (remaining <= 0) { break; }
|
||||
}
|
||||
}
|
||||
|
||||
static void SpawnItem(ItemPrefab itemPrefab, ItemContainer container)
|
||||
{
|
||||
if (container?.Item is null) { return; }
|
||||
|
||||
Item item = new Item(itemPrefab, container.Item.Position, container.Item.Submarine);
|
||||
container.Inventory.TryPutItem(item, user: null);
|
||||
CargoManager.ItemSpawned(item);
|
||||
#if SERVER
|
||||
Entity.Spawner?.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.PerkBehaviors
|
||||
{
|
||||
internal class SubItemSwapPerk : PerkBase
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier TargetItem { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier ReplacementItem { get; set; }
|
||||
|
||||
public override PerkSimulation Simulation
|
||||
=> PerkSimulation.ServerOnly;
|
||||
|
||||
public SubItemSwapPerk(ContentXElement element, DisembarkPerkPrefab prefab) : base(element, prefab) { }
|
||||
|
||||
public override bool CanApply(SubmarineInfo submarine)
|
||||
{
|
||||
XElement subElement = submarine.SubmarineElement;
|
||||
|
||||
foreach (XElement element in subElement.Elements())
|
||||
{
|
||||
if (!element.Name.ToString().Equals(nameof(Item), StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||
|
||||
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
if (identifier == TargetItem)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override void ApplyOnRoundStart(IReadOnlyCollection<Character> teamCharacters, Submarine teamSubmarine)
|
||||
{
|
||||
if (teamSubmarine is null) { return; }
|
||||
|
||||
List<Item> items = teamSubmarine.GetItems(true);
|
||||
|
||||
ItemPrefab itemToInstall = ItemPrefab.Find(null, ReplacementItem);
|
||||
if (itemToInstall is null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find item \"{ReplacementItem}\" to swap with \"{TargetItem}\".");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (item.Prefab.Identifier == TargetItem)
|
||||
{
|
||||
item.ReplaceWithLinkedItems(itemToInstall);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.PerkBehaviors
|
||||
{
|
||||
internal class UpgradeSubmarinePerk : PerkBase
|
||||
{
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier UpgradeIdentifier { get; set; }
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier CategoryIdentifier { get; set; }
|
||||
|
||||
[Serialize(0, IsPropertySaveable.Yes)]
|
||||
public int Level { get; set; }
|
||||
|
||||
public override PerkSimulation Simulation
|
||||
=> PerkSimulation.ServerAndClients;
|
||||
|
||||
public UpgradeSubmarinePerk(ContentXElement element, DisembarkPerkPrefab prefab) : base(element, prefab) { }
|
||||
|
||||
public override void ApplyOnRoundStart(IReadOnlyCollection<Character> teamCharacters, Submarine teamSubmarine)
|
||||
{
|
||||
if (teamSubmarine is null) { return; }
|
||||
|
||||
bool prefabFound = UpgradePrefab.Prefabs.TryGet(UpgradeIdentifier, out UpgradePrefab upgradePrefab);
|
||||
bool categoryFound = UpgradeCategory.Categories.TryGet(CategoryIdentifier, out UpgradeCategory upgradeCategory);
|
||||
|
||||
if (!prefabFound)
|
||||
{
|
||||
DebugConsole.ThrowError($"{nameof(UpgradeSubmarinePerk)}: Upgrade prefab not found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (upgradePrefab.IsWallUpgrade)
|
||||
{
|
||||
foreach (Structure structure in teamSubmarine.GetWalls(UpgradeManager.UpgradeAlsoConnectedSubs))
|
||||
{
|
||||
structure.AddUpgrade(new Upgrade(structure, upgradePrefab, Level), createNetworkEvent: true);
|
||||
}
|
||||
}
|
||||
else if (categoryFound)
|
||||
{
|
||||
foreach (Item item in teamSubmarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs))
|
||||
{
|
||||
if (upgradeCategory.CanBeApplied(item, upgradePrefab))
|
||||
{
|
||||
item.AddUpgrade(new Upgrade(item, upgradePrefab, Level), createNetworkEvent: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"{nameof(UpgradeSubmarinePerk)}: Upgrade category not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user