Files
wwdpublic/Content.Server/Traits/TraitSystem.cs
VMSolidus ff6d43c9a5 Chemical Moodlets (And Drug Addictions!) (#896)
# Description

This PR implements a new Reagent Reaction that modifies a character's
mood, done by raising a MoodEvent on that character. It also extends
some of the functionality of the TraitSystem and MoodSystem so as to
support a new serializable Drug Addiction mechanic.

https://www.youtube.com/watch?v=8liPBsUtND4


![image](https://github.com/user-attachments/assets/3962c492-7677-4007-bf31-23e74b2b7382)

<details><summary><h1>Media</h1></summary>
<p>


![image](https://github.com/user-attachments/assets/207210d6-e573-46e2-beb4-fab83a83d8b4)


![image](https://github.com/user-attachments/assets/4e1277e1-a873-4185-98b3-39abe06a8235)


![image](https://github.com/user-attachments/assets/e1a6cefe-82ee-482c-ace1-9fb511385fe4)


![image](https://github.com/user-attachments/assets/454dafa2-ba9e-46a6-9ac0-15e2ed35c4f8)

</p>
</details>

# Changelog

🆑
- add: Drug Addictions! Drug addictions are long lasting Moodlet-Pairs.
The first consisting of a temporary Mood bonus that is refreshed by
consuming the drug, and the second consisting of an extremely long
lasting mood penalty, which replaces the mood bonus should you go a long
enough time between consuming the drug in question.
- add: Nicotine Addiction has been added as a new minor negative trait.
- add: Drugs/Reagents can now affect your character's Mood! Both with,
and without Addiction.
- add: TraitSystem has had functionality added for directly adding
Moodlets to a character upon joining the round, such as drug addictions,
or permanent mood modifications like Sanguine/Saturnine
- add: Lotophagoi Oil now induces an extremely powerful drug addiction,
providing an extremely large mood benefit for a short time. Which when
it wears off, creates an equally extreme mood penalty that lasts for a
very long time(or until you drink more Loto Oil).
2024-10-19 12:52:13 +07:00

159 lines
5.9 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.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!;
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);
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>
/// 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));
}
}