mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-16 21:17:39 +03:00
# Description This PR adds a dedicated "Psionics" tab to the traits menu, where every single Psionics related trait has been moved to. It includes subcategories for Psicaster Type, Feats, and Powers. There are currently 3 different variants of "Psicaster", two of which are new. **Latent Psychic**: Exactly as before with Psionics V3. No changes here. **PsychoHistorian**: A new variant psicaster that enjoys significantly faster power development, with a heavy focus on "Utility" powers. As a tradeoff, they can ONLY obtain utility powers. **Elementalist**: The second new variant psicaster type. Elementalists have the option to buy powers directly from the Anomalist power category. The tradeoff here is that these are the only powers they will ever have. Elementalists will never generate new powers during a round, and cannot gain Potentia. All 3 Psicaster Types have their own dedicated "Shop" that is accessed via the Powers tab in the traits menu. They each have their own separate costs and availabilities. They also all 3 have their own random power charts. # TODO <details><summary><h1>Media</h1></summary> <p>  </p> </details> # Changelog 🆑 - add: Added a dedicated Psionics tab to the Traits menu. - add: Added PsychoHistorian as a new psionic variant. PsychoHistorians have significantly faster new power generation, but are strictly limited to the Mentalics power category. They essentially only get "Utility" powers. PsychoHistorians have Telepathy as a free bonus power. - add: Added Elementalist as a new psionic variant. Elementalists do not generate new powers at all during the round. Instead, they purchase powers directly at character creation, using the Anomalist powers category. - add: @#$%(*&FAREWELL, FRIEND. @#&#^!@*(&^$I @*#$&^@#$% WAS @#$@#$*&^@#$ALWAYS A *&#^@$@#THOUSAND *(&@^#$TIMES *(@#$(*&MORE *(&^*&(EVIL ^&*((*&^THAN (*&^&*(^%*(&THOU! (cherry picked from commit 7cc86ec5558c6fbe42cdfc4cd61ac31e4bd69232)
431 lines
13 KiB
C#
431 lines
13 KiB
C#
using System.Linq;
|
|
using Content.Shared.Clothing.Loadouts.Prototypes;
|
|
using Content.Shared.Humanoid;
|
|
using Content.Shared.Humanoid.Prototypes;
|
|
using Content.Shared.Mind;
|
|
using Content.Shared.Preferences;
|
|
using Content.Shared.Prototypes;
|
|
using Content.Shared.Roles;
|
|
using Content.Shared.Traits;
|
|
using JetBrains.Annotations;
|
|
using Robust.Shared.Configuration;
|
|
using Robust.Shared.Enums;
|
|
using Robust.Shared.Physics;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Serialization;
|
|
|
|
namespace Content.Shared.Customization.Systems;
|
|
|
|
|
|
/// <summary>
|
|
/// Requires the profile to be within an age range
|
|
/// </summary>
|
|
[UsedImplicitly, Serializable, NetSerializable]
|
|
public sealed partial class CharacterAgeRequirement : CharacterRequirement
|
|
{
|
|
[DataField(required: true)]
|
|
public int Min;
|
|
|
|
[DataField]
|
|
public int Max = Int32.MaxValue;
|
|
|
|
public override bool IsValid(
|
|
JobPrototype job,
|
|
HumanoidCharacterProfile profile,
|
|
Dictionary<string, TimeSpan> playTimes,
|
|
bool whitelisted,
|
|
IPrototype prototype,
|
|
IEntityManager entityManager,
|
|
IPrototypeManager prototypeManager,
|
|
IConfigurationManager configManager,
|
|
out string? reason,
|
|
int depth = 0,
|
|
MindComponent? mind = null
|
|
)
|
|
{
|
|
var localeString = "";
|
|
|
|
if (Max == Int32.MaxValue || Min <= 0)
|
|
localeString = Max == Int32.MaxValue ? "character-age-requirement-minimum-only" : "character-age-requirement-maximum-only";
|
|
else
|
|
localeString = "character-age-requirement-range";
|
|
|
|
reason = Loc.GetString(
|
|
localeString,
|
|
("inverted", Inverted),
|
|
("min", Min),
|
|
("max", Max));
|
|
return profile.Age >= Min && profile.Age <= Max;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requires the profile to be a certain gender
|
|
/// </summary>
|
|
[UsedImplicitly, Serializable, NetSerializable]
|
|
public sealed partial class CharacterGenderRequirement : CharacterRequirement
|
|
{
|
|
[DataField(required: true)]
|
|
public Gender Gender;
|
|
|
|
public override bool IsValid(
|
|
JobPrototype job,
|
|
HumanoidCharacterProfile profile,
|
|
Dictionary<string, TimeSpan> playTimes,
|
|
bool whitelisted,
|
|
IPrototype prototype,
|
|
IEntityManager entityManager,
|
|
IPrototypeManager prototypeManager,
|
|
IConfigurationManager configManager,
|
|
out string? reason,
|
|
int depth = 0,
|
|
MindComponent? mind = null
|
|
)
|
|
{
|
|
reason = Loc.GetString(
|
|
"character-gender-requirement",
|
|
("inverted", Inverted),
|
|
("gender", Loc.GetString($"humanoid-profile-editor-pronouns-{Gender.ToString().ToLower()}-text")));
|
|
return profile.Gender == Gender;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requires the profile to be a certain sex
|
|
/// </summary>
|
|
[UsedImplicitly, Serializable, NetSerializable]
|
|
public sealed partial class CharacterSexRequirement : CharacterRequirement
|
|
{
|
|
[DataField(required: true)]
|
|
public Sex Sex;
|
|
|
|
public override bool IsValid(
|
|
JobPrototype job,
|
|
HumanoidCharacterProfile profile,
|
|
Dictionary<string, TimeSpan> playTimes,
|
|
bool whitelisted,
|
|
IPrototype prototype,
|
|
IEntityManager entityManager,
|
|
IPrototypeManager prototypeManager,
|
|
IConfigurationManager configManager,
|
|
out string? reason,
|
|
int depth = 0,
|
|
MindComponent? mind = null
|
|
)
|
|
{
|
|
reason = Loc.GetString(
|
|
"character-sex-requirement",
|
|
("inverted", Inverted),
|
|
("sex", Loc.GetString($"humanoid-profile-editor-sex-{Sex.ToString().ToLower()}-text")));
|
|
return profile.Sex == Sex;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requires the profile to be a certain species
|
|
/// </summary>
|
|
[UsedImplicitly, Serializable, NetSerializable]
|
|
public sealed partial class CharacterSpeciesRequirement : CharacterRequirement
|
|
{
|
|
[DataField(required: true)]
|
|
public List<ProtoId<SpeciesPrototype>> Species;
|
|
|
|
public override bool IsValid(
|
|
JobPrototype job,
|
|
HumanoidCharacterProfile profile,
|
|
Dictionary<string, TimeSpan> playTimes,
|
|
bool whitelisted,
|
|
IPrototype prototype,
|
|
IEntityManager entityManager,
|
|
IPrototypeManager prototypeManager,
|
|
IConfigurationManager configManager,
|
|
out string? reason,
|
|
int depth = 0,
|
|
MindComponent? mind = null
|
|
)
|
|
{
|
|
const string color = "green";
|
|
reason = Loc.GetString(
|
|
"character-species-requirement",
|
|
("inverted", Inverted),
|
|
("species", $"[color={color}]{string.Join($"[/color], [color={color}]",
|
|
Species.Select(s => Loc.GetString(prototypeManager.Index(s).Name)))}[/color]"));
|
|
|
|
return Species.Contains(profile.Species);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requires the profile to be within a certain height range
|
|
/// </summary>
|
|
[UsedImplicitly, Serializable, NetSerializable]
|
|
public sealed partial class CharacterHeightRequirement : CharacterRequirement
|
|
{
|
|
/// <summary>
|
|
/// The minimum height of the profile in centimeters
|
|
/// </summary>
|
|
[DataField]
|
|
public float Min = int.MinValue;
|
|
|
|
/// <summary>
|
|
/// The maximum height of the profile in centimeters
|
|
/// </summary>
|
|
[DataField]
|
|
public float Max = int.MaxValue;
|
|
|
|
public override bool IsValid(
|
|
JobPrototype job,
|
|
HumanoidCharacterProfile profile,
|
|
Dictionary<string, TimeSpan> playTimes,
|
|
bool whitelisted,
|
|
IPrototype prototype,
|
|
IEntityManager entityManager,
|
|
IPrototypeManager prototypeManager,
|
|
IConfigurationManager configManager,
|
|
out string? reason,
|
|
int depth = 0,
|
|
MindComponent? mind = null
|
|
)
|
|
{
|
|
const string color = "yellow";
|
|
var species = prototypeManager.Index<SpeciesPrototype>(profile.Species);
|
|
|
|
reason = Loc.GetString(
|
|
"character-height-requirement",
|
|
("inverted", Inverted),
|
|
("color", color),
|
|
("min", Min),
|
|
("max", Max));
|
|
|
|
var height = profile.Height * species.AverageHeight;
|
|
return height >= Min && height <= Max;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requires the profile to be within a certain width range
|
|
/// </summary>
|
|
[UsedImplicitly, Serializable, NetSerializable]
|
|
public sealed partial class CharacterWidthRequirement : CharacterRequirement
|
|
{
|
|
/// <summary>
|
|
/// The minimum width of the profile in centimeters
|
|
/// </summary>
|
|
[DataField]
|
|
public float Min = int.MinValue;
|
|
|
|
/// <summary>
|
|
/// The maximum width of the profile in centimeters
|
|
/// </summary>
|
|
[DataField]
|
|
public float Max = int.MaxValue;
|
|
|
|
public override bool IsValid(
|
|
JobPrototype job,
|
|
HumanoidCharacterProfile profile,
|
|
Dictionary<string, TimeSpan> playTimes,
|
|
bool whitelisted,
|
|
IPrototype prototype,
|
|
IEntityManager entityManager,
|
|
IPrototypeManager prototypeManager,
|
|
IConfigurationManager configManager,
|
|
out string? reason,
|
|
int depth = 0,
|
|
MindComponent? mind = null
|
|
)
|
|
{
|
|
const string color = "yellow";
|
|
var species = prototypeManager.Index<SpeciesPrototype>(profile.Species);
|
|
|
|
reason = Loc.GetString(
|
|
"character-width-requirement",
|
|
("inverted", Inverted),
|
|
("color", color),
|
|
("min", Min),
|
|
("max", Max));
|
|
|
|
var width = profile.Width * species.AverageWidth;
|
|
return width >= Min && width <= Max;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requires the profile to be within a certain weight range
|
|
/// </summary>
|
|
[UsedImplicitly, Serializable, NetSerializable]
|
|
public sealed partial class CharacterWeightRequirement : CharacterRequirement
|
|
{
|
|
/// <summary>
|
|
/// Minimum weight of the profile in kilograms
|
|
/// </summary>
|
|
[DataField]
|
|
public float Min = int.MinValue;
|
|
|
|
/// <summary>
|
|
/// Maximum weight of the profile in kilograms
|
|
/// </summary>
|
|
[DataField]
|
|
public float Max = int.MaxValue;
|
|
|
|
public override bool IsValid(
|
|
JobPrototype job,
|
|
HumanoidCharacterProfile profile,
|
|
Dictionary<string, TimeSpan> playTimes,
|
|
bool whitelisted,
|
|
IPrototype prototype,
|
|
IEntityManager entityManager,
|
|
IPrototypeManager prototypeManager,
|
|
IConfigurationManager configManager,
|
|
out string? reason,
|
|
int depth = 0,
|
|
MindComponent? mind = null
|
|
)
|
|
{
|
|
const string color = "green";
|
|
var species = prototypeManager.Index<SpeciesPrototype>(profile.Species);
|
|
prototypeManager.Index(species.Prototype).TryGetComponent<FixturesComponent>(out var fixture);
|
|
|
|
if (fixture == null)
|
|
{
|
|
reason = null;
|
|
return false;
|
|
}
|
|
|
|
var weight = MathF.Round(
|
|
MathF.PI * MathF.Pow(
|
|
fixture.Fixtures["fix1"].Shape.Radius
|
|
* ((profile.Width + profile.Height) / 2),
|
|
2)
|
|
* fixture.Fixtures["fix1"].Density);
|
|
|
|
reason = Loc.GetString(
|
|
"character-weight-requirement",
|
|
("inverted", Inverted),
|
|
("color", color),
|
|
("min", Min),
|
|
("max", Max));
|
|
|
|
return weight >= Min && weight <= Max;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requires the profile to have one of the specified traits
|
|
/// </summary>
|
|
[UsedImplicitly, Serializable, NetSerializable]
|
|
public sealed partial class CharacterTraitRequirement : CharacterRequirement
|
|
{
|
|
[DataField(required: true)]
|
|
public List<ProtoId<TraitPrototype>> Traits;
|
|
|
|
public override bool IsValid(
|
|
JobPrototype job,
|
|
HumanoidCharacterProfile profile,
|
|
Dictionary<string, TimeSpan> playTimes,
|
|
bool whitelisted,
|
|
IPrototype prototype,
|
|
IEntityManager entityManager,
|
|
IPrototypeManager prototypeManager,
|
|
IConfigurationManager configManager,
|
|
out string? reason,
|
|
int depth = 0,
|
|
MindComponent? mind = null
|
|
)
|
|
{
|
|
const string color = "lightblue";
|
|
reason = Loc.GetString(
|
|
"character-trait-requirement",
|
|
("inverted", Inverted),
|
|
("traits", $"[color={color}]{string.Join($"[/color], [color={color}]",
|
|
Traits.Select(t => Loc.GetString($"trait-name-{t}")))}[/color]"));
|
|
|
|
return Traits.Any(t => profile.TraitPreferences.Contains(t.ToString()));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requires the profile to have one of the specified loadouts
|
|
/// </summary>
|
|
[UsedImplicitly, Serializable, NetSerializable]
|
|
public sealed partial class CharacterLoadoutRequirement : CharacterRequirement
|
|
{
|
|
[DataField(required: true)]
|
|
public List<ProtoId<LoadoutPrototype>> Loadouts;
|
|
|
|
public override bool IsValid(
|
|
JobPrototype job,
|
|
HumanoidCharacterProfile profile,
|
|
Dictionary<string, TimeSpan> playTimes,
|
|
bool whitelisted,
|
|
IPrototype prototype,
|
|
IEntityManager entityManager,
|
|
IPrototypeManager prototypeManager,
|
|
IConfigurationManager configManager,
|
|
out string? reason,
|
|
int depth = 0,
|
|
MindComponent? mind = null
|
|
)
|
|
{
|
|
const string color = "lightblue";
|
|
reason = Loc.GetString(
|
|
"character-loadout-requirement",
|
|
("inverted", Inverted),
|
|
("loadouts", $"[color={color}]{string.Join($"[/color], [color={color}]",
|
|
Loadouts.Select(l => Loc.GetString($"loadout-name-{l}")))}[/color]"));
|
|
|
|
return Loadouts.Any(l => profile.LoadoutPreferences.Select(l => l.LoadoutName).Contains(l.ToString()));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requires the profile to not have any more than X of the specified traits, loadouts, etc, in a group
|
|
/// </summary>
|
|
[UsedImplicitly, Serializable, NetSerializable]
|
|
public sealed partial class CharacterItemGroupRequirement : CharacterRequirement
|
|
{
|
|
[DataField(required: true)]
|
|
public ProtoId<CharacterItemGroupPrototype> Group;
|
|
|
|
public override bool IsValid(
|
|
JobPrototype job,
|
|
HumanoidCharacterProfile profile,
|
|
Dictionary<string, TimeSpan> playTimes,
|
|
bool whitelisted,
|
|
IPrototype prototype,
|
|
IEntityManager entityManager,
|
|
IPrototypeManager prototypeManager,
|
|
IConfigurationManager configManager,
|
|
out string? reason,
|
|
int depth = 0,
|
|
MindComponent? mind = null
|
|
)
|
|
{
|
|
var group = prototypeManager.Index(Group);
|
|
|
|
// Get the count of items in the group that are in the profile
|
|
var items = group.Items.Select(item => item.TryGetValue(profile, prototypeManager, out _) ? item.ID : null)
|
|
.Where(id => id != null)
|
|
.ToList();
|
|
var count = items.Count;
|
|
|
|
// If prototype is selected, decrease the count. Or increase it via negative number. Not my monkey, not my circus.
|
|
if (items.ToList().Contains(prototype.ID))
|
|
{
|
|
// This disgusting ELIF nest requires an engine PR to make less terrible.
|
|
if (prototypeManager.TryIndex<LoadoutPrototype>(prototype.ID, out var loadoutPrototype))
|
|
count -= loadoutPrototype.Slots;
|
|
else if (prototypeManager.TryIndex<TraitPrototype>(prototype.ID, out var traitPrototype))
|
|
count -= traitPrototype.ItemGroupSlots;
|
|
else count--;
|
|
}
|
|
|
|
reason = Loc.GetString(
|
|
"character-item-group-requirement",
|
|
("inverted", Inverted),
|
|
("group", Loc.GetString($"character-item-group-{Group}")),
|
|
("max", group.MaxItems));
|
|
|
|
return !Inverted ? count < group.MaxItems : count >= group.MaxItems - 1;
|
|
}
|
|
}
|