mirror of
https://github.com/WWhiteDreamProject/wwdpublic.git
synced 2026-04-18 22:18:52 +03:00
# Description Adds the Plasmamen as a playable species. Plasmamen are a skeletal species who depend on Plasma to live, and oxygen is highly fatal to them. Being exposed to oxygen will set them on fire, unless they wear their envirosuits. ## Species Guidebook <img width=500px src="https://github.com/user-attachments/assets/a1ef91ef-87b2-4ae0-8b5c-922a0c34777f"> <img width=500px src="https://github.com/user-attachments/assets/110f0fa0-7dc4-410b-a2c0-a517f0311484"> **SPECIAL:** - Plasmamen speak the language Calcic, a language they share with Skeletons. ## Shitmed Integration Plasmamen are the first ever species designed with Shitmed in mind, with one of their core mechanics (self-ignition) powered entirely by Shitmed. Whether or not a Plasmaman ignites from oxygen exposure depends only on their body parts. A Plasmaman with only their head exposed will not burn as much as an entirely naked Plasmaman. You can **transfer** Plasmaman body parts to non-Plasmamen through **surgery** so that they also ignite from oxygen exposure. Meanwhile, a Plasmaman with a non-Plasmaman head can expose their head without self-igniting. https://github.com/user-attachments/assets/0aa33070-04be-4ded-b668-3afb9f4ddd7c ## Technical Details This also cherry-picks https://github.com/space-wizards/space-station-14/pull/28595 as a quality-of-life feature to ensure Plasmamen keep their internals on upon toggling their helmet with a breath mask on. ## TODO ### RELEASE-NECESSARY <details> - [x] Port more envirosuits / enviro helms (job-specific) and their sprites - [x] Remove breath masks from default plasmaman loadouts because the envirohelms already allow them to breathe internals - [x] Change default plasma tank to higher-capacity version - [x] Prevent plasmamen from buying jumpsuits and helmets other than envirosuits - ~~[ ] **Client UI update for loadout groups min/max items and default items**~~ - [x] Plasmaman-specific mask sprites from TG - [x] Disable too cold alert for plasmamen - [x] Create/port sprites for these jobs - [x] Courier - [x] Forensic Mantis - [x] Corpsman (Resprite security envirosuit) - [x] Prison Guard (Resprite security envirosuit) - [x] Magistrate (No Paradise envirosuit so use new colorable envirosuit) - [x] Blueshield (Port from Paradise and tg-ify?) - [x] NanoTrasen Representative (No Paradise envirosuit so use new colorable envirosuit) - [x] Martial Artist (use new colorable envirosuit and make pure white) - [x] Musician (use new colorable envirosuit) - [x] Reporter (use new colorable envirosuit) - [x] Zookeeper (use new colorable envirosuit) - [x] Service Worker (use new colorable envirosuit) - [x] Gladiator - [x] Technical Assistant - [x] Medical Intern - [x] Acolyte / Research Assistant - [x] Security Cadet - [x] Assistant - You know what. These intern jobs are fine. They can use their normal equivalent's envirosuits. - [x] Logistics Officer (use new colorable envirosuit) - [x] Adjust sprites to be closer to actual job - [x] Captain (Shift color to be closer to ss14 captain) - [x] ~~CMO (Remove yellow accents)~~ - [x] Port HoP envirogloves sprite - [x] unique sprite for self-extinguish verb - [x] Refactor conditional gear stuff to live only in StartingGearPrototype with `SubGear` `List<ProtoId<StartingGearPrototype>>` field and `List<Requirement>` field for sub-gear requirements - [x] Add starting gear for paradox anomaly, and antags and ghost roles - [x] Paradox - [x] Nukies - [x] Disaster victims - [x] Listening post operative - [x] Make all envirosuit helmets have a glowing (unshaded) visor - [x] Envirosuit extinguish visuals - [x] JobPrototype: AfterLoadoutSpecial - [x] Set prisoner envirohelm battery to potato, command/sec/dignitary to high-powered - [x] Set base envirosuit extinguishes to 4, sec 6 and command 8 - [x] Improve plasmaman organ extraction experience - [x] Body parts now give 1 plasma sheet each, while Torso gives 3 - [x] Organs can be juiced to get plasma - [x] Make envirohelm flashlights battery-powered - [x] Plasmamen visuals - [x] Grayscale sprites for color customization, and set default skintone color to Plasmaman classic skintone - [x] Plasmaman eye organ sprite - [x] Add basic loadouts - [x] Add way to refill envirosuit charges (refill at medical protolathe after some research) </details> ### Low Importance <details> - [x] Envirogloves - [ ] (SCOPE CREEP) Plasma tanks sprite (only normal emergency/extended, rather low priority) - [ ] (SCOPE CREEP) Modify envirosuit helmet sprites to have a transparent visor - [ ] Glowing eyes/mouth marking - [x] More cargo content with plasma tanks / envirosuits - [x] Plasmaman survival kit like slime - [x] Additional plasma tanks - [ ] (SCOPE CREEP) Plasmaman EVA suits - [x] ~~Add envirosuits to clothesmate~~ - [x] Add more plasma tanks to random lockers and job lockers - [x] Turn envirosuit auto-extinguish into extinguish action - [x] move self-extinguish verb stuff to shared for prediction of the verb - [x] move self-extinguisher stuff away from extinguisher namespace - [x] unique sprite for self-extinguish icon - [x] ~~IDEA: purple glowy fire extinguisher ~~ - [x] on self-extinguish, check for pressure immunity OR ignite from gas immunity properly - [x] See envirosuit extinguish charges in examine - [x] Milk heals on ingestion - [x] Plasma heals on ingestion - [x] Self-ignition doesn't occur on a stasis bed - [x] ~~Self-ignition doesn't occur when dead~~ - [x] Guidebook entry - [x] Make self-ignition ignore damage resistances from fire suits - [x] ~~Make self-ignition ignore damage resistances from armor~~ - [x] ~~Unable to rot?~~ - [x] Make the envirosuit helmet toggle on for the character dummy in lobby - [ ] (SCOPE CREEP) One additional Plasmaman trait - [x] ~~Showers extinguish water as well as water tiles~~ - Unnecessary as stasis beds now prevent ignition, allowing surgery on a plasmaman on stasis beds. - [x] Unique punch animations for Plasmafire Punch/Toxoplasmic Punch traits - [x] Actually remove toxoplasmic it's just slop filler tbh - [ ] Talk sounds - [ ] Normal - [ ] Question - [ ] Yell - [x] Positive moodlet for drinking milk / more positive moodlet for drinking plasma - [x] Increase moodlet bonus and also minimum reagent required for the plasma/milk moodlets - [x] Increase fire rate base stacks on ignite cause putting out your helmet for a few secs isn't that dangerous due to the fire stacks immediately decaying - [x] I think halving firestack fade from -0.1 to -0.05 might work to do the same thing too - [ ] (SCOPE CREEP) Get bone laugh sounds from monke 'monkestation/sound/voice/laugh/skeleton/skeleton_laugh.ogg' - [ ] (SCOPE CREEP) When EVA plasmaman suit is added, 25% caustic resist - [x] Envirosuit helmet - [x] Equivalent of 100% bio / 100% fire / 75% acid resist - [x] Envirosuit - [x] Equivalent of 100% bio / 100% fire / 75% acid resist - [x] Envirogloves - [x] Equivalent of 100% bio / 95% fire / 95% acid resist - [x] Put breath mask back on - [x] Refactor: put body parts covered data into component instead of being hardcoded </details> ## Media **Custom Plasmaman Outfits** All of these use the same **absolutely massive** [envirosuit RSI](0c3af432df/Resources/Textures/Clothing/Uniforms/Envirosuits/color.rsi) and [envirohelm RSI](0c3af432df/Resources/Textures/Clothing/Head/Envirohelms/color.rsi) to quickly create the envirosuits that didn't exist in SS13 where the envirosuit sprites were ported. From Left to Right: Magistrate, Prison Guard, Boxer, Reporter, Logistics Officer <img width=200px src="https://github.com/user-attachments/assets/bf990841-7d9e-4f4e-abae-8f29a3980ca1"> <img width=200px src="https://github.com/user-attachments/assets/07ca7af7-4f43-4504-9eac-4ca9188ae98e"> <img width=200px src="https://github.com/user-attachments/assets/0d20332c-826f-4fec-8396-74e84c23b074"> <img width=200px src="https://github.com/user-attachments/assets/1634364e-7cb3-457b-b638-e1b562b7c0c5"> <img width=200px src="https://github.com/user-attachments/assets/c2881764-f2fa-4e40-9fbf-35d1b717c432"> **Plasmaman Melee Attack** https://github.com/user-attachments/assets/6e694f2c-3e03-40bf-ae27-fc58a3e4cb6c **Chat bubble** <img width=240px src="https://github.com/user-attachments/assets/e3c17e6d-5050-410f-a42c-339f0bfa30a1"> **Plasmaman Body** <img width=140px src="https://github.com/user-attachments/assets/7ed90a47-9c33-487d-bd44-c50cec9f16dd"> With different colors: <img width=140px src="https://github.com/user-attachments/assets/0a28068e-7392-4062-950b-f60d2602da84"> <img width=140px src="https://github.com/user-attachments/assets/9b652311-0305-4ec0-be60-e404697617a2"> **Skeleton Language**  **(Bonus) Skeleton chat bubble** <img width=240px src="https://github.com/user-attachments/assets/a2e2be5c-f3ae-49d9-b655-8688de45b512"> **Self-Extinguish** https://github.com/user-attachments/assets/6c68e2ef-8010-4f00-8c24-dce8a8065be8 The self-extinguish is also accessible as a verb, which also means that others can activate your self-extinguish if they open the strip menu. <img width=200px src="https://github.com/user-attachments/assets/291ab86d-2250-46ec-ae0c-80084ab04407"> The self-extinguish action has different icons depending on the status of the self extinguish. Left to right: Ready, On Cooldown, Out Of Charges <img src="https://github.com/user-attachments/assets/0340de8a-9440-43b1-8bff-1c8f962faa0c"> <img src="https://github.com/user-attachments/assets/11f73558-6dc1-444d-b2ef-2f15f55174ca"> <img src="https://github.com/user-attachments/assets/030ed737-f178-4c60-ba0c-109659e7d9cb"> **Envirosuit Extinguisher Refill** <img width=300px src="https://github.com/user-attachments/assets/9379294b-e3f3-436d-81bc-2584631869ef"> <img width=300px src="https://github.com/user-attachments/assets/807b9e9e-7b4b-4593-aa1f-d9d24ac6985c"> **Loadouts** <img width=400px src="https://github.com/user-attachments/assets/55713b87-29bb-41b3-b7a3-88fbc6e5e797"> <img width=400px src="https://github.com/user-attachments/assets/ab1757fa-9b70-4a66-b5ae-20fd9cabe935"> <img width=400px src="https://github.com/user-attachments/assets/aacc4cf7-9ce1-4099-b8c7-108bef1f3bde"> <img width=400px src="https://github.com/user-attachments/assets/58604dc2-82ef-4d42-b9e2-639548c93f40"> **Plasma Envirosuit Crate** <img width=400px src="https://github.com/user-attachments/assets/fa362387-9c10-47c3-b1af-2c11e6b00163"> <img width=400px src="https://github.com/user-attachments/assets/bf773722-9034-4469-967d-e00dbf8c77a7"> **Internals Crate (Plasma)** <img width=400px src="https://github.com/user-attachments/assets/fcd4ff2e-09e9-423a-9b21-96817f6042a4"> <img width=400px src="https://github.com/user-attachments/assets/bf773722-9034-4469-967d-e00dbf8c77a7"> **Glow In The Dark**  ## Changelog 🆑 Skubman - add: The Plasmaman species has arrived! They need to breathe plasma to live, and a special jumpsuit to prevent oxygen from igniting them. In exchange, they deal formidable unarmed Heat damage, are never hungry nor thirsty, and are immune to cold and radiation damage. Read more about Plasmamen in their Guidebook entry. - tweak: Internals are no longer toggled off if you take your helmet off but still have a gas mask on and vice versa. - tweak: Paradox Anomalies will now spawn with the original person's Loadout items. - fix: Fixed prisoners not being able to have custom Loadout names and descriptions, and heirlooms if they didn't have a backpack when joining. --------- Signed-off-by: Skubman <ba.fallaria@gmail.com> Signed-off-by: VMSolidus <evilexecutive@gmail.com> Co-authored-by: Plykiya <58439124+Plykiya@users.noreply.github.com> Co-authored-by: VMSolidus <evilexecutive@gmail.com> (cherry picked from commit e68e0c3f4b9cf263e07efc888b32a091df62fb51)
680 lines
24 KiB
C#
680 lines
24 KiB
C#
using Content.Shared.FixedPoint;
|
|
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.Shared.Traits.Assorted.Components;
|
|
using Content.Shared.Damage;
|
|
using Content.Shared.Damage.Components;
|
|
using Content.Shared.Chemistry.Components;
|
|
using Content.Shared.Chemistry.EntitySystems;
|
|
using Content.Shared.Mobs.Components;
|
|
using Content.Shared.Mobs.Systems;
|
|
using Content.Shared.Mobs;
|
|
using Content.Shared.Damage.Components;
|
|
using Content.Shared.NPC.Systems;
|
|
using Content.Shared.Weapons.Melee;
|
|
using Robust.Shared.Audio;
|
|
|
|
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 (_, data) in Components)
|
|
{
|
|
var comp = (Component) serializationManager.CreateCopy(data.Component, notNullableOverride: true);
|
|
comp.Owner = uid;
|
|
entityManager.AddComponent(uid, comp, 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 entry in Components.Values)
|
|
{
|
|
if (entityManager.HasComponent(uid, entry.Component.GetType()))
|
|
continue;
|
|
|
|
var comp = (Component) serializationManager.CreateCopy(entry.Component, notNullableOverride: true);
|
|
comp.Owner = uid;
|
|
entityManager.AddComponent(uid, comp);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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.RemoveComponentDeferred(uid, factory.GetComponent(name).GetType());
|
|
}
|
|
}
|
|
|
|
/// 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();
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool PlayFeedback;
|
|
|
|
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, PlayFeedback);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This isn't actually used for any traits, surprise, other systems can use these functions!
|
|
/// This is used by Items of Power to remove a psionic power when unequipped.
|
|
/// </summary>
|
|
[UsedImplicitly]
|
|
public sealed partial class TraitRemovePsionics : TraitFunction
|
|
{
|
|
[DataField, AlwaysPushInheritance]
|
|
public List<ProtoId<PsionicPowerPrototype>> PsionicPowers { get; private set; } = new();
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool Forced = true;
|
|
|
|
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.RemovePsionicPower(uid, psionicPower, Forced);
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
}
|
|
|
|
/// Used for writing to an entity's ExtendDescriptionComponent. If one is not present, it will be added!
|
|
/// Use this to create traits that add special descriptions for when a character is shift-click examined.
|
|
[UsedImplicitly]
|
|
public sealed partial class TraitPushDescription : TraitFunction
|
|
{
|
|
[DataField, AlwaysPushInheritance]
|
|
public List<DescriptionExtension> DescriptionExtensions { get; private set; } = new();
|
|
|
|
public override void OnPlayerSpawn(EntityUid uid,
|
|
IComponentFactory factory,
|
|
IEntityManager entityManager,
|
|
ISerializationManager serializationManager)
|
|
{
|
|
entityManager.EnsureComponent<ExtendDescriptionComponent>(uid, out var descComp);
|
|
foreach (var descExtension in DescriptionExtensions)
|
|
descComp.DescriptionList.Add(descExtension);
|
|
}
|
|
}
|
|
|
|
[UsedImplicitly]
|
|
public sealed partial class TraitAddArmor : TraitFunction
|
|
{
|
|
/// <summary>
|
|
/// The list of prototype ID's of DamageModifierSets to be added to the enumerable damage modifiers of an entity.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Dear Maintainer, I'm well aware that validating protoIds is a thing. Unfortunately, this is for a legacy system that doesn't have validated prototypes.
|
|
/// And refactoring the entire DamageableSystem is way the hell outside of the scope of the PR adding this function.
|
|
/// {FaridaIsCute.png} - Solidus
|
|
/// </remarks>
|
|
[DataField, AlwaysPushInheritance]
|
|
public List<string> DamageModifierSets { get; private set; } = new();
|
|
|
|
public override void OnPlayerSpawn(EntityUid uid,
|
|
IComponentFactory factory,
|
|
IEntityManager entityManager,
|
|
ISerializationManager serializationManager)
|
|
{
|
|
entityManager.EnsureComponent<DamageableComponent>(uid, out var damageableComponent);
|
|
foreach (var modifierSet in DamageModifierSets)
|
|
damageableComponent.DamageModifierSets.Add(modifierSet);
|
|
}
|
|
}
|
|
|
|
[UsedImplicitly]
|
|
public sealed partial class TraitRemoveArmor : TraitFunction
|
|
{
|
|
[DataField, AlwaysPushInheritance]
|
|
public List<string> DamageModifierSets { get; private set; } = new();
|
|
|
|
public override void OnPlayerSpawn(EntityUid uid,
|
|
IComponentFactory factory,
|
|
IEntityManager entityManager,
|
|
ISerializationManager serializationManager)
|
|
{
|
|
if (!entityManager.TryGetComponent<DamageableComponent>(uid, out var damageableComponent))
|
|
return;
|
|
|
|
foreach (var modifierSet in DamageModifierSets)
|
|
damageableComponent.DamageModifierSets.Remove(modifierSet);
|
|
}
|
|
}
|
|
|
|
[UsedImplicitly]
|
|
public sealed partial class TraitAddSolutionContainer : TraitFunction
|
|
{
|
|
[DataField, AlwaysPushInheritance]
|
|
public Dictionary<string, SolutionComponent> Solutions { get; private set; } = new();
|
|
|
|
public override void OnPlayerSpawn(EntityUid uid,
|
|
IComponentFactory factory,
|
|
IEntityManager entityManager,
|
|
ISerializationManager serializationManager)
|
|
{
|
|
var solutionContainer = entityManager.System<SharedSolutionContainerSystem>();
|
|
|
|
foreach (var (containerKey, solution) in Solutions)
|
|
{
|
|
var hasSolution = solutionContainer.EnsureSolution(uid, containerKey, out Solution? newSolution);
|
|
|
|
if (!hasSolution)
|
|
return;
|
|
|
|
newSolution!.AddSolution(solution.Solution, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
[UsedImplicitly]
|
|
public sealed partial class TraitModifyMobThresholds : TraitFunction
|
|
{
|
|
[DataField, AlwaysPushInheritance]
|
|
public int CritThresholdModifier;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public int SoftCritThresholdModifier;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public int DeadThresholdModifier;
|
|
|
|
public override void OnPlayerSpawn(EntityUid uid,
|
|
IComponentFactory factory,
|
|
IEntityManager entityManager,
|
|
ISerializationManager serializationManager)
|
|
{
|
|
if (!entityManager.TryGetComponent<MobThresholdsComponent>(uid, out var threshold))
|
|
return;
|
|
|
|
var thresholdSystem = entityManager.System<MobThresholdSystem>();
|
|
if (CritThresholdModifier != 0)
|
|
{
|
|
var critThreshold = thresholdSystem.GetThresholdForState(uid, MobState.Critical, threshold);
|
|
if (critThreshold != 0)
|
|
thresholdSystem.SetMobStateThreshold(uid, critThreshold + CritThresholdModifier, MobState.Critical);
|
|
}
|
|
|
|
if (SoftCritThresholdModifier != 0)
|
|
{
|
|
var softCritThreshold = thresholdSystem.GetThresholdForState(uid, MobState.SoftCritical, threshold);
|
|
if (softCritThreshold != 0)
|
|
thresholdSystem.SetMobStateThreshold(uid, softCritThreshold + SoftCritThresholdModifier, MobState.SoftCritical);
|
|
}
|
|
|
|
if (DeadThresholdModifier != 0)
|
|
{
|
|
var deadThreshold = thresholdSystem.GetThresholdForState(uid, MobState.Dead, threshold);
|
|
if (deadThreshold != 0)
|
|
thresholdSystem.SetMobStateThreshold(uid, deadThreshold + DeadThresholdModifier, MobState.Dead);
|
|
}
|
|
}
|
|
}
|
|
|
|
[UsedImplicitly]
|
|
public sealed partial class TraitModifyMobState : TraitFunction
|
|
{
|
|
// Three-State Booleans my beloved.
|
|
// :faridabirb.png:
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool? AllowMovementWhileCrit;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool? AllowMovementWhileSoftCrit;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool? AllowMovementWhileDead;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool? AllowTalkingWhileCrit;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool? AllowTalkingWhileSoftCrit;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool? AllowTalkingWhileDead;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool? DownWhenCrit;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool? DownWhenSoftCrit;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool? DownWhenDead;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool? AllowHandInteractWhileCrit;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool? AllowHandInteractWhileSoftCrit;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool? AllowHandInteractWhileDead;
|
|
|
|
public override void OnPlayerSpawn(EntityUid uid,
|
|
IComponentFactory factory,
|
|
IEntityManager entityManager,
|
|
ISerializationManager serializationManager)
|
|
{
|
|
if (!entityManager.TryGetComponent<MobStateComponent>(uid, out var mobStateComponent))
|
|
return;
|
|
|
|
if (AllowMovementWhileCrit is not null)
|
|
mobStateComponent.AllowMovementWhileCrit = AllowMovementWhileCrit.Value;
|
|
|
|
if (AllowMovementWhileSoftCrit is not null)
|
|
mobStateComponent.AllowHandInteractWhileSoftCrit = AllowMovementWhileSoftCrit.Value;
|
|
|
|
if (AllowMovementWhileDead is not null)
|
|
mobStateComponent.AllowMovementWhileDead = AllowMovementWhileDead.Value;
|
|
|
|
if (AllowTalkingWhileCrit is not null)
|
|
mobStateComponent.AllowTalkingWhileCrit = AllowTalkingWhileCrit.Value;
|
|
|
|
if (AllowTalkingWhileSoftCrit is not null)
|
|
mobStateComponent.AllowTalkingWhileSoftCrit = AllowTalkingWhileSoftCrit.Value;
|
|
|
|
if (AllowTalkingWhileDead is not null)
|
|
mobStateComponent.AllowTalkingWhileDead = AllowTalkingWhileDead.Value;
|
|
|
|
if (DownWhenCrit is not null)
|
|
mobStateComponent.DownWhenCrit = DownWhenCrit.Value;
|
|
|
|
if (DownWhenSoftCrit is not null)
|
|
mobStateComponent.DownWhenSoftCrit = DownWhenSoftCrit.Value;
|
|
|
|
if (DownWhenDead is not null)
|
|
mobStateComponent.DownWhenDead = DownWhenDead.Value;
|
|
|
|
if (AllowHandInteractWhileCrit is not null)
|
|
mobStateComponent.AllowHandInteractWhileCrit = AllowHandInteractWhileCrit.Value;
|
|
|
|
if (AllowHandInteractWhileSoftCrit is not null)
|
|
mobStateComponent.AllowHandInteractWhileSoftCrit = AllowHandInteractWhileSoftCrit.Value;
|
|
|
|
if (AllowHandInteractWhileDead is not null)
|
|
mobStateComponent.AllowHandInteractWhileDead = AllowHandInteractWhileDead.Value;
|
|
}
|
|
}
|
|
|
|
[UsedImplicitly]
|
|
public sealed partial class TraitModifyStamina : TraitFunction
|
|
{
|
|
[DataField, AlwaysPushInheritance]
|
|
public float StaminaModifier;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public float DecayModifier;
|
|
|
|
[DataField, AlwaysPushInheritance]
|
|
public float CooldownModifier;
|
|
|
|
public override void OnPlayerSpawn(EntityUid uid,
|
|
IComponentFactory factory,
|
|
IEntityManager entityManager,
|
|
ISerializationManager serializationManager)
|
|
{
|
|
if (!entityManager.TryGetComponent<StaminaComponent>(uid, out var staminaComponent))
|
|
return;
|
|
|
|
staminaComponent.CritThreshold += StaminaModifier;
|
|
staminaComponent.Decay += DecayModifier;
|
|
staminaComponent.Cooldown += CooldownModifier;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used for traits that modify SlowOnDamageComponent.
|
|
/// </summary>
|
|
[UsedImplicitly]
|
|
public sealed partial class TraitModifySlowOnDamage : TraitFunction
|
|
{
|
|
// <summary>
|
|
// A flat modifier to add to all damage threshold keys.
|
|
// </summary>
|
|
[DataField, AlwaysPushInheritance]
|
|
public float DamageThresholdsModifier;
|
|
|
|
// <summary>
|
|
// A multiplier applied to all speed modifier values.
|
|
// The higher the multiplier, the stronger the slowdown.
|
|
// </summary>
|
|
[DataField, AlwaysPushInheritance]
|
|
public float SpeedModifierMultiplier = 1f;
|
|
|
|
public override void OnPlayerSpawn(EntityUid uid,
|
|
IComponentFactory factory,
|
|
IEntityManager entityManager,
|
|
ISerializationManager serializationManager)
|
|
{
|
|
if (!entityManager.TryGetComponent<SlowOnDamageComponent>(uid, out var slowOnDamage))
|
|
return;
|
|
|
|
var newSpeedModifierThresholds = new Dictionary<FixedPoint2, float>();
|
|
|
|
foreach (var (damageThreshold, speedModifier) in slowOnDamage.SpeedModifierThresholds)
|
|
newSpeedModifierThresholds[damageThreshold + DamageThresholdsModifier] = 1 - (1 - speedModifier) * SpeedModifierMultiplier;
|
|
|
|
slowOnDamage.SpeedModifierThresholds = newSpeedModifierThresholds;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Used for traits that modify unarmed damage on MeleeWeaponComponent.
|
|
/// </summary>
|
|
[UsedImplicitly]
|
|
public sealed partial class TraitModifyUnarmed : TraitFunction
|
|
{
|
|
// <summary>
|
|
// The sound played on hitting targets.
|
|
// </summary>
|
|
[DataField, AlwaysPushInheritance]
|
|
public SoundSpecifier? SoundHit;
|
|
|
|
// <summary>
|
|
// The animation to play on hit, for both light and power attacks.
|
|
// </summary>
|
|
[DataField, AlwaysPushInheritance]
|
|
public EntProtoId? Animation;
|
|
|
|
// <summary>
|
|
// Whether to set the power attack animation to be the same as the light attack.
|
|
// </summary>
|
|
[DataField, AlwaysPushInheritance]
|
|
public bool HeavyAnimationFromLight = true;
|
|
|
|
// <summary>
|
|
// The damage values of unarmed damage.
|
|
// </summary>
|
|
[DataField, AlwaysPushInheritance]
|
|
public DamageSpecifier? Damage;
|
|
|
|
// <summary>
|
|
// Additional damage added to the existing damage.
|
|
// </summary>
|
|
[DataField, AlwaysPushInheritance]
|
|
public DamageSpecifier? FlatDamageIncrease;
|
|
|
|
/// <summary>
|
|
/// Turns the left click into a power attack when the light attack misses.
|
|
/// </summary>
|
|
[DataField]
|
|
public bool? HeavyOnLightMiss;
|
|
|
|
// <summary>
|
|
// What to multiply the melee weapon range by.
|
|
// </summary>
|
|
[DataField, AlwaysPushInheritance]
|
|
public float? RangeModifier;
|
|
|
|
// <summary>
|
|
// What to multiply the attack rate by.
|
|
// </summary>
|
|
[DataField, AlwaysPushInheritance]
|
|
public float? AttackRateModifier;
|
|
|
|
public override void OnPlayerSpawn(EntityUid uid,
|
|
IComponentFactory factory,
|
|
IEntityManager entityManager,
|
|
ISerializationManager serializationManager)
|
|
{
|
|
if (!entityManager.TryGetComponent<MeleeWeaponComponent>(uid, out var melee))
|
|
return;
|
|
|
|
if (SoundHit != null)
|
|
melee.SoundHit = SoundHit;
|
|
|
|
if (Animation != null)
|
|
melee.Animation = Animation.Value;
|
|
|
|
if (HeavyAnimationFromLight)
|
|
melee.WideAnimation = melee.Animation;
|
|
|
|
if (Damage != null)
|
|
melee.Damage = Damage;
|
|
|
|
if (FlatDamageIncrease != null)
|
|
melee.Damage += FlatDamageIncrease;
|
|
|
|
if (HeavyOnLightMiss != null)
|
|
melee.HeavyOnLightMiss = HeavyOnLightMiss.Value;
|
|
|
|
if (RangeModifier != null)
|
|
melee.Range *= RangeModifier.Value;
|
|
|
|
if (AttackRateModifier != null)
|
|
melee.AttackRate *= AttackRateModifier.Value;
|
|
|
|
entityManager.Dirty(uid, melee);
|
|
}
|
|
}
|