mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-16 21:17:39 +03:00
<!-- This is a semi-strict format, you can add/remove sections as needed but the order/format should be kept the same Remove these comments before submitting --> # Description <!-- Explain this PR in as much detail as applicable Some example prompts to consider: How might this affect the game? The codebase? What might be some alternatives to this? How/Who does this benefit/hurt [the game/codebase]? --> This PR does not have any effects on the game from a player-perspective. It does, however, allow us to add CharacterRequirements to ExtendDescriptions, which allows us to add contextual information to items that only show up if characters know about them, for example. It has an optional field that can also show text if your character does _not_ meet requirements. --- # TODO <!-- A list of everything you have to do before this PR is "complete" You probably won't have to complete everything before merging but it's good to leave future references --> - [x] Add a bunch of CharacterRequirements to new and existing ExtendDescriptions for contraband or other neat info --- <!-- This is default collapsed, readers click to expand it and see all your media The PR media section can get very large at times, so this is a good way to keep it clean The title is written using HTML tags The title must be within the <summary> tags or you won't see it --> <details><summary><h1>Media</h1></summary> <p> Example of how to add a requirement:  https://github.com/user-attachments/assets/67ad6ecd-1886-4f71-85c0-fdd035a9f5c9  </p> </details> --- # Changelog <!-- You can add an author after the `🆑` to change the name that appears in the changelog (ex: `🆑 Death`) Leaving it blank will default to your GitHub display name This includes all available types for the changelog --> 🆑 - tweak: Tweaked Extended Descriptions to be able to require CharacterRequirements before being shown to the player. Currently not actually implemented anywhere except for the emag and some posters. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit - **New Features** - Enhanced character creation and role-validation systems now incorporate playtime tracking and additional criteria, providing a more tailored experience. - In-game items—such as hacking devices, weapons, and posters—feature extended, lore-rich descriptions that adjust based on character attributes. - New localized texts enrich the narrative by offering clear feedback when character requirements are or aren’t met. - New character requirements related to antagonists and mindshields have been introduced, enhancing gameplay dynamics. - A new method for validating character requirements has been added, improving the accuracy of checks during character creation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: VMSolidus <evilexecutive@gmail.com> (cherry picked from commit 0640f1f54619a95a4360a79b870654b2c4a1e433)
424 lines
13 KiB
C#
424 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, remove one from the count
|
|
if (items.ToList().Contains(prototype.ID))
|
|
count--;
|
|
|
|
reason = Loc.GetString(
|
|
"character-item-group-requirement",
|
|
("inverted", Inverted),
|
|
("group", Loc.GetString($"character-item-group-{Group}")),
|
|
("max", group.MaxItems));
|
|
|
|
return count < group.MaxItems;
|
|
}
|
|
}
|