Files
wwdpublic/Content.Server/Traits/TraitSystem.cs
VMSolidus 19ca517509 Non-Destructive Universal Language (#899)
# Description

UniversalLanguageSpeakerComponent has NUMEROUS issues, which previously
were easy to ignore since it was a language only obtainable by Admins.
But now that it's both a Psionic Power(And a trait that adds said
power), the issues with it have been highlighted. Here's just a FEW

1. UniversalLanguageSpeaker overwrites all known languages, preventing
you from speaking anything else
2. It overwrites the ability to use sign language.
3. It also overwrites the Mute trait.

To fix that, I've made it follow *MOSTLY* the logic all the other
languages use, so that it's less of a special snowflake case. Now if you
have access to it, it will appear in your language list alongside other
languages, rather than fully replacing the entire list. That way you can
intentionally choose not to speak in a language understood by all.

Fuck it, I also added the ability for the TraitSystem to just call
LanguageSystem and directly add arbitrarily any desired language, rather
than needing a component to do so.

# Changelog

🆑
- fix: UniversalLanguageSpeaker(And Xenoglossy by extension) will now
appear in your language menu alongside other known languages, rather
than replace all known languages. You can effectively now choose whether
or not to speak it, or to use a normal language.
- add: Traits can now add Languages directly.
- add: Traits can now remove Languages directly.

---------

Signed-off-by: VMSolidus <evilexecutive@gmail.com>
Co-authored-by: Mnemotechnican <69920617+Mnemotechnician@users.noreply.github.com>
Co-authored-by: FoxxoTrystan <45297731+FoxxoTrystan@users.noreply.github.com>
Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com>
2024-10-19 13:08:20 +07:00

229 lines
8.5 KiB
C#

using System.Linq;
using Content.Shared.Actions;
using Content.Server.GameTicking;
using Content.Server.Players.PlayTimeTracking;
using Content.Shared.Customization.Systems;
using Content.Shared.Players;
using Content.Shared.Roles;
using Content.Shared.Traits;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Robust.Shared.Utility;
using Content.Server.Abilities.Psionics;
using Content.Shared.Psionics;
using Content.Server.Language;
using Content.Shared.Mood;
namespace Content.Server.Traits;
public sealed class TraitSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly ISerializationManager _serialization = default!;
[Dependency] private readonly CharacterRequirementsSystem _characterRequirements = default!;
[Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!;
[Dependency] private readonly IConfigurationManager _configuration = default!;
[Dependency] private readonly SharedActionsSystem _actions = default!;
[Dependency] private readonly PsionicAbilitiesSystem _psionicAbilities = default!;
[Dependency] private readonly IComponentFactory _componentFactory = default!;
[Dependency] private readonly LanguageSystem _languageSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawnComplete);
}
// When the player is spawned in, add all trait components selected during character creation
private void OnPlayerSpawnComplete(PlayerSpawnCompleteEvent args)
{
foreach (var traitId in args.Profile.TraitPreferences)
{
if (!_prototype.TryIndex<TraitPrototype>(traitId, out var traitPrototype))
{
DebugTools.Assert($"No trait found with ID {traitId}!");
return;
}
if (!_characterRequirements.CheckRequirementsValid(
traitPrototype.Requirements,
_prototype.Index<JobPrototype>(args.JobId ?? _prototype.EnumeratePrototypes<JobPrototype>().First().ID),
args.Profile, _playTimeTracking.GetTrackerTimes(args.Player), args.Player.ContentData()?.Whitelisted ?? false, traitPrototype,
EntityManager, _prototype, _configuration,
out _))
continue;
AddTrait(args.Mob, traitPrototype);
}
}
/// <summary>
/// Adds a single Trait Prototype to an Entity.
/// </summary>
public void AddTrait(EntityUid uid, TraitPrototype traitPrototype)
{
RemoveTraitComponents(uid, traitPrototype);
AddTraitComponents(uid, traitPrototype);
AddTraitActions(uid, traitPrototype);
AddTraitPsionics(uid, traitPrototype);
AddTraitLanguage(uid, traitPrototype);
RemoveTraitLanguage(uid, traitPrototype);
AddTraitMoodlets(uid, traitPrototype);
}
/// <summary>
/// Removes all components defined by a Trait. It's not possible to validate component removals,
/// so if an incorrect string is given, it's basically a skill issue.
/// </summary>
/// <remarks>
/// This comes before AddTraitComponents for a good reason.
/// It allows for a component to optionally be fully wiped and replaced with a new component.
/// </remarks>
public void RemoveTraitComponents(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.ComponentRemovals is null)
return;
foreach (var entry in traitPrototype.ComponentRemovals)
{
if (!_componentFactory.TryGetRegistration(entry, out var comp))
continue;
EntityManager.RemoveComponent(uid, comp.Type);
}
}
/// <summary>
/// Adds all Components included with a Trait.
/// </summary>
public void AddTraitComponents(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.Components is null)
return;
foreach (var entry in traitPrototype.Components.Values)
{
if (HasComp(uid, entry.Component.GetType()))
continue;
var comp = (Component) _serialization.CreateCopy(entry.Component, notNullableOverride: true);
comp.Owner = uid;
EntityManager.AddComponent(uid, comp);
}
}
/// <summary>
/// Add all actions associated with a specific Trait
/// </summary>
public void AddTraitActions(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.Actions is null)
return;
foreach (var id in traitPrototype.Actions)
{
EntityUid? actionId = null;
if (_actions.AddAction(uid, ref actionId, id))
{
_actions.StartUseDelay(actionId);
}
}
}
/// <summary>
/// If a trait includes any Psionic Powers, this enters the powers into PsionicSystem to be initialized.
/// If the lack of logic here seems startling, it's okay. All of the logic necessary for adding Psionics is handled by InitializePsionicPower.
/// </summary>
public void AddTraitPsionics(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.PsionicPowers is null)
return;
foreach (var powerProto in traitPrototype.PsionicPowers)
if (_prototype.TryIndex<PsionicPowerPrototype>(powerProto, out var psionicPower))
_psionicAbilities.InitializePsionicPower(uid, psionicPower, false);
}
/// <summary>
/// Initialize languages given by a Trait.
/// </summary>
private void AddTraitLanguage(EntityUid uid, TraitPrototype traitPrototype)
{
AddTraitLanguagesSpoken(uid, traitPrototype);
AddTraitLanguagesUnderstood(uid, traitPrototype);
}
/// <summary>
/// If a trait includes any Spoken Languages, this sends them to LanguageSystem to be initialized.
/// </summary>
public void AddTraitLanguagesSpoken(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.LanguagesSpoken is null)
return;
foreach (var language in traitPrototype.LanguagesSpoken)
_languageSystem.AddLanguage(uid, language, true, false);
}
/// <summary>
/// If a trait includes any Understood Languages, this sends them to LanguageSystem to be initialized.
/// </summary>
public void AddTraitLanguagesUnderstood(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.LanguagesUnderstood is null)
return;
foreach (var language in traitPrototype.LanguagesUnderstood)
_languageSystem.AddLanguage(uid, language, false, true);
}
/// <summary>
/// Remove Languages given by a Trait.
/// </summary>
private void RemoveTraitLanguage(EntityUid uid, TraitPrototype traitPrototype)
{
RemoveTraitLanguagesSpoken(uid, traitPrototype);
RemoveTraitLanguagesUnderstood(uid, traitPrototype);
}
/// <summary>
/// Removes any Spoken Languages if defined by a trait.
/// </summary>
public void RemoveTraitLanguagesSpoken(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.RemoveLanguagesSpoken is null)
return;
foreach (var language in traitPrototype.RemoveLanguagesSpoken)
_languageSystem.RemoveLanguage(uid, language, true, false);
}
/// <summary>
/// Removes any Understood Languages if defined by a trait.
/// </summary>
public void RemoveTraitLanguagesUnderstood(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.RemoveLanguagesUnderstood is null)
return;
foreach (var language in traitPrototype.RemoveLanguagesUnderstood)
_languageSystem.RemoveLanguage(uid, language, false, true);
}
/// <summary>
/// If a trait includes any moodlets, this adds the moodlets to the receiving entity.
/// While I can't stop you, you shouldn't use this to add temporary moodlets.
/// </summary>
public void AddTraitMoodlets(EntityUid uid, TraitPrototype traitPrototype)
{
if (traitPrototype.MoodEffects is null)
return;
foreach (var moodProto in traitPrototype.MoodEffects)
if (_prototype.TryIndex(moodProto, out var moodlet))
RaiseLocalEvent(uid, new MoodEffectEvent(moodlet.ID));
}
}