Skip to content

Commit

Permalink
Traits V2 (#1182)
Browse files Browse the repository at this point in the history
# Description

Traits Version 2 implements a much needed overhaul of the Trait System,
changing it so that it no longer uses hardcoded nullable lists of
various entries required for the trait functions, and instead accepts a
*single* Modular List of Functions, and then iterates through the list
like a set of instructions on how to modify the character. This PR also
features a few new Functions, such as AddImplant.

Anyway, rather than hardcoding a new codepath for every new function,
new functions can simply be made separate from the codepath entirely,
and will only be called if they are included in the trait itself.

# TODO

- [x] Remove ALL pre-existing trait functions and move them over to the
new system.
- [x] Evaluate which System Dependencies are essential for a majority of
functions, and make sure that TraitSystem is caching them on behalf of
the functions that need them for performance reasons. This system has a
fairly high startup cost for frametime to begin with.
- [x] Update all pre-existing traits such that they use the new System.

# BREAKING CHANGES

ALL DOWNSTREAM TRAITS WILL NEED TO BE MODIFIED TO USE THE NEW SYSTEM. 

# Changelog

:cl:
- add: Traits can now add Implants directly.

---------

Signed-off-by: VMSolidus <[email protected]>
Co-authored-by: gluesniffler <[email protected]>
Co-authored-by: DEATHB4DEFEAT <[email protected]>
  • Loading branch information
3 people authored Nov 8, 2024
1 parent d521d46 commit e7d40d1
Show file tree
Hide file tree
Showing 14 changed files with 850 additions and 675 deletions.
3 changes: 1 addition & 2 deletions Content.Server/Jobs/AddComponentSpecial.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ namespace Content.Server.Jobs
[UsedImplicitly]
public sealed partial class AddComponentSpecial : JobSpecial
{
[DataField("components")]
[AlwaysPushInheritance]
[DataField, AlwaysPushInheritance]
public ComponentRegistry Components { get; private set; } = new();

public override void AfterEquip(EntityUid mob)
Expand Down
265 changes: 265 additions & 0 deletions Content.Server/Traits/TraitSystem.Functions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
using Content.Shared.Traits;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.Manager;
using Content.Shared.Implants;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
using Content.Shared.Actions;
using Content.Server.Abilities.Psionics;
using Content.Shared.Psionics;
using Content.Server.Language;
using Content.Shared.Mood;
using Content.Server.NPC.Systems;

namespace Content.Server.Traits;

/// Used for traits that add a Component upon spawning in, overwriting the pre-existing component if it already exists.
[UsedImplicitly]
public sealed partial class TraitReplaceComponent : TraitFunction
{
[DataField, AlwaysPushInheritance]
public ComponentRegistry Components { get; private set; } = new();

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
ISerializationManager serializationManager)
{
foreach (var (name, data) in Components)
{
var component = (Component) factory.GetComponent(name);

var temp = (object) component;
serializationManager.CopyTo(data.Component, ref temp);
entityManager.RemoveComponent(uid, temp!.GetType());
entityManager.AddComponent(uid, (Component) temp, true);
}
}
}

/// <summary>
/// Used for traits that add a Component upon spawning in.
/// This will do nothing if the Component already exists.
/// </summary>
[UsedImplicitly]
public sealed partial class TraitAddComponent : TraitFunction
{
[DataField, AlwaysPushInheritance]
public ComponentRegistry Components { get; private set; } = new();

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
ISerializationManager serializationManager)
{
foreach (var (name, _) in Components)
{
var component = (Component) factory.GetComponent(name);

entityManager.AddComponent(uid, component, true);
}
}
}

/// Used for traits that remove a component upon a player spawning in.
[UsedImplicitly]
public sealed partial class TraitRemoveComponent : TraitFunction
{
[DataField, AlwaysPushInheritance]
public ComponentRegistry Components { get; private set; } = new();

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
ISerializationManager serializationManager)
{
foreach (var (name, _) in Components)
entityManager.RemoveComponent(uid, (Component) factory.GetComponent(name));
}
}

/// Used for traits that add an action upon a player spawning in.
[UsedImplicitly]
public sealed partial class TraitAddActions : TraitFunction
{
[DataField, AlwaysPushInheritance]
public List<EntProtoId> Actions { get; private set; } = new();

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
ISerializationManager serializationManager)
{
var actionSystem = entityManager.System<SharedActionsSystem>();

foreach (var id in Actions)
{
EntityUid? actionId = null;
if (actionSystem.AddAction(uid, ref actionId, id))
actionSystem.StartUseDelay(actionId);
}
}
}

/// Used for traits that add an Implant upon spawning in.
[UsedImplicitly]
public sealed partial class TraitAddImplant : TraitFunction
{
[DataField(customTypeSerializer: typeof(PrototypeIdHashSetSerializer<EntityPrototype>))]
[AlwaysPushInheritance]
public HashSet<string> Implants { get; private set; } = new();

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
ISerializationManager serializationManager)
{
var implantSystem = entityManager.System<SharedSubdermalImplantSystem>();
implantSystem.AddImplants(uid, Implants);
}
}

/// <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>
[UsedImplicitly]
public sealed partial class TraitAddPsionics : TraitFunction
{
[DataField, AlwaysPushInheritance]
public List<ProtoId<PsionicPowerPrototype>> PsionicPowers { get; private set; } = new();

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
ISerializationManager serializationManager)
{
var prototype = IoCManager.Resolve<IPrototypeManager>();
var psionic = entityManager.System<PsionicAbilitiesSystem>();

foreach (var powerProto in PsionicPowers)
if (prototype.TryIndex(powerProto, out var psionicPower))
psionic.InitializePsionicPower(uid, psionicPower, false);
}
}

/// Handles all modification of Known Languages. Removes languages before adding them.
[UsedImplicitly]
public sealed partial class TraitModifyLanguages : TraitFunction
{
/// The list of all Spoken Languages that this trait adds.
[DataField, AlwaysPushInheritance]
public List<string>? LanguagesSpoken { get; private set; } = default!;

/// The list of all Understood Languages that this trait adds.
[DataField, AlwaysPushInheritance]
public List<string>? LanguagesUnderstood { get; private set; } = default!;

/// The list of all Spoken Languages that this trait removes.
[DataField, AlwaysPushInheritance]
public List<string>? RemoveLanguagesSpoken { get; private set; } = default!;

/// The list of all Understood Languages that this trait removes.
[DataField, AlwaysPushInheritance]
public List<string>? RemoveLanguagesUnderstood { get; private set; } = default!;

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
ISerializationManager serializationManager)
{
var language = entityManager.System<LanguageSystem>();

if (RemoveLanguagesSpoken is not null)
foreach (var lang in RemoveLanguagesSpoken)
language.RemoveLanguage(uid, lang, true, false);

if (RemoveLanguagesUnderstood is not null)
foreach (var lang in RemoveLanguagesUnderstood)
language.RemoveLanguage(uid, lang, false, true);

if (LanguagesSpoken is not null)
foreach (var lang in LanguagesSpoken)
language.AddLanguage(uid, lang, true, false);

if (LanguagesUnderstood is not null)
foreach (var lang in LanguagesUnderstood)
language.AddLanguage(uid, lang, false, true);
}
}

/// Handles adding Moodlets to a player character upon spawning in. Typically used for permanent moodlets or drug addictions.
[UsedImplicitly]
public sealed partial class TraitAddMoodlets : TraitFunction
{
/// The list of all Moodlets that this trait adds.
[DataField, AlwaysPushInheritance]
public List<ProtoId<MoodEffectPrototype>> MoodEffects { get; private set; } = new();

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
ISerializationManager serializationManager)
{
var prototype = IoCManager.Resolve<IPrototypeManager>();

foreach (var moodProto in MoodEffects)
if (prototype.TryIndex(moodProto, out var moodlet))
entityManager.EventBus.RaiseLocalEvent(uid, new MoodEffectEvent(moodlet.ID));
}
}

/// Add or remove Factions from a player upon spawning in.
[UsedImplicitly]
public sealed partial class TraitModifyFactions : TraitFunction
{
/// <summary>
/// The list of all Factions that this trait removes.
/// </summary>
/// <remarks>
/// I can't actually Validate these because the proto lives in Shared.
/// </remarks>
[DataField, AlwaysPushInheritance]
public List<string> RemoveFactions { get; private set; } = new();

/// <summary>
/// The list of all Factions that this trait adds.
/// </summary>
/// <remarks>
/// I can't actually Validate these because the proto lives in Shared.
/// </remarks>
[DataField, AlwaysPushInheritance]
public List<string> AddFactions { get; private set; } = new();

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
ISerializationManager serializationManager)
{
var factionSystem = entityManager.System<NpcFactionSystem>();

foreach (var faction in RemoveFactions)
factionSystem.RemoveFaction(uid, faction);

foreach (var faction in AddFactions)
factionSystem.AddFaction(uid, faction);
}
}

/// Only use this if you know what you're doing. This function directly writes to any arbitrary component.
[UsedImplicitly]
public sealed partial class TraitVVEdit : TraitFunction
{
[DataField, AlwaysPushInheritance]
public Dictionary<string, string> VVEdit { get; private set; } = new();

public override void OnPlayerSpawn(EntityUid uid,
IComponentFactory factory,
IEntityManager entityManager,
ISerializationManager serializationManager)
{
var vvm = IoCManager.Resolve<IViewVariablesManager>();
foreach (var (path, value) in VVEdit)
vvm.WritePath(path, value);
}
}
Loading

0 comments on commit e7d40d1

Please sign in to comment.