From 61e1c8cc97c1d129c273b852c53206df333a52d0 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sat, 21 Sep 2024 18:50:00 -0400 Subject: [PATCH] Psionic "Heal Other" Powers (#942) # Description This PR introduces two new Psionic Powers, Healing Word, and Breath of Life, both utilizing a new PsionicHealOtherSystem, which operates on datafield event arguments rather than a "hardcoded" component. Thus, any number of powers can be created which share this system. Healing Word is a power that features a short cast time, and heals a small amount of each damage type to a target(while reducing the target's rot timer slightly). It has a relatively short cooldown, and a low glimmer cost. Breath of Life by contrast, is an extremely rare power with a longer cast time, healing a much larger amount of each damage type to a target, reduces rot significantly, and attempts to revive the target. It has a 2 minute cooldown, and a high glimmer cost.

Media

![image](https://github.com/user-attachments/assets/ba01ccce-639f-4b03-84bb-55f96b5aeda3)

# Changelog :cl: - add: Healing Word has been added as a new Psionic Power. When cast on another person, it heals a small amount of every damage type(scaling with Casting Stats), while also reducing rot timers. Healing Word has a very short cooldown, and a fairly low Glimmer cost. - add: Breath of Life has been added as a new extremely rare Psionic Power. When cast on another person, it heals a large amount of damage(scaling with Casting Stats), while also substantially reducing rot timers. Additionally, it will revive the target if it is possible to do so. Breath of Life has an incredibly long cooldown, a long interuptable cast time, and an extraordinarily high glimmer cost(A typical Psion will spike glimmer by more than 50 points when casting it). - add: The Chaplain now starts with the Healing Word power. --------- Signed-off-by: VMSolidus Co-authored-by: DEATHB4DEFEAT <77995199+DEATHB4DEFEAT@users.noreply.github.com> --- .../Abilities/HealOtherPowerSystem.cs | 152 ++++++++++++++++++ .../PsionicHealOtherPowerActionEvent.cs | 59 +++++++ Content.Shared/Psionics/Events.cs | 61 +++++-- Content.Shared/Psionics/PsionicComponent.cs | 11 +- .../Locale/en-US/psionics/psionic-powers.ftl | 23 +++ Resources/Prototypes/Actions/psionics.yml | 73 +++++++++ .../Prototypes/Nyanotrasen/psionicPowers.yml | 2 + Resources/Prototypes/Psionics/psionics.yml | 22 +++ .../Roles/Jobs/Civilian/chaplain.yml | 9 +- .../Actions/psionics.rsi/healing_word.png | Bin 0 -> 2189 bytes .../Interface/Actions/psionics.rsi/meta.json | 17 ++ .../Actions/psionics.rsi/revivify.png | Bin 0 -> 1977 bytes .../Interface/VerbIcons/ATTRIBUTION.txt | 3 + 13 files changed, 412 insertions(+), 20 deletions(-) create mode 100644 Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs create mode 100644 Content.Shared/Actions/Events/PsionicHealOtherPowerActionEvent.cs create mode 100644 Resources/Textures/Interface/Actions/psionics.rsi/healing_word.png create mode 100644 Resources/Textures/Interface/Actions/psionics.rsi/meta.json create mode 100644 Resources/Textures/Interface/Actions/psionics.rsi/revivify.png diff --git a/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs new file mode 100644 index 00000000000..85bae78dc6b --- /dev/null +++ b/Content.Server/Abilities/Psionics/Abilities/HealOtherPowerSystem.cs @@ -0,0 +1,152 @@ +using Robust.Shared.Player; +using Content.Server.DoAfter; +using Content.Shared.Abilities.Psionics; +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.Popups; +using Content.Shared.Psionics.Events; +using Content.Shared.Examine; +using static Content.Shared.Examine.ExamineSystemShared; +using Robust.Shared.Timing; +using Content.Shared.Actions.Events; +using Robust.Server.Audio; +using Content.Server.Atmos.Rotting; +using Content.Shared.Mobs.Systems; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Psionics.Glimmer; + +namespace Content.Server.Abilities.Psionics; + +public sealed class RevivifyPowerSystem : EntitySystem +{ + [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly ExamineSystemShared _examine = default!; + [Dependency] private readonly DamageableSystem _damageable = default!; + [Dependency] private readonly RottingSystem _rotting = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly GlimmerSystem _glimmer = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnPowerUsed); + SubscribeLocalEvent(OnDispelled); + SubscribeLocalEvent(OnDoAfter); + } + + + private void OnPowerUsed(EntityUid uid, PsionicComponent component, PsionicHealOtherPowerActionEvent args) + { + if (component.DoAfter is not null) + return; + + if (!args.Immediate) + AttemptDoAfter(uid, component, args); + else ActivatePower(uid, component, args); + + if (args.PopupText is not null + && _glimmer.Glimmer > args.GlimmerObviousPopupThreshold * component.CurrentDampening) + _popupSystem.PopupEntity(Loc.GetString(args.PopupText, ("entity", uid)), uid, + Filter.Pvs(uid).RemoveWhereAttachedEntity(entity => !_examine.InRangeUnOccluded(uid, entity, ExamineRange, null)), + true, + args.PopupType); + + if (args.PlaySound + && _glimmer.Glimmer > args.GlimmerObviousSoundThreshold * component.CurrentDampening) + _audioSystem.PlayPvs(args.SoundUse, uid, args.AudioParams); + + // Sanitize the Glimmer inputs because otherwise the game will crash if someone makes MaxGlimmer lower than MinGlimmer. + var minGlimmer = (int) Math.Round(MathF.MinMagnitude(args.MinGlimmer, args.MaxGlimmer) + + component.CurrentAmplification - component.CurrentDampening); + var maxGlimmer = (int) Math.Round(MathF.MaxMagnitude(args.MinGlimmer, args.MaxGlimmer) + + component.CurrentAmplification - component.CurrentDampening); + + _psionics.LogPowerUsed(uid, args.PowerName, minGlimmer, maxGlimmer); + args.Handled = true; + } + + private void AttemptDoAfter(EntityUid uid, PsionicComponent component, PsionicHealOtherPowerActionEvent args) + { + var ev = new PsionicHealOtherDoAfterEvent(_gameTiming.CurTime); + ev.HealingAmount = args.HealingAmount; + ev.RotReduction = args.RotReduction; + ev.DoRevive = args.DoRevive; + var doAfterArgs = new DoAfterArgs(EntityManager, uid, args.UseDelay, ev, uid, target: args.Target) + { + BreakOnUserMove = args.BreakOnUserMove, + BreakOnTargetMove = args.BreakOnTargetMove, + }; + + if (!_doAfterSystem.TryStartDoAfter(doAfterArgs, out var doAfterId)) + return; + + component.DoAfter = doAfterId; + } + + private void OnDispelled(EntityUid uid, PsionicComponent component, DispelledEvent args) + { + if (component.DoAfter is null) + return; + + _doAfterSystem.Cancel(component.DoAfter); + component.DoAfter = null; + args.Handled = true; + } + + private void OnDoAfter(EntityUid uid, PsionicComponent component, PsionicHealOtherDoAfterEvent args) + { + // It's entirely possible for the caster to stop being Psionic(due to mindbreaking) mid cast + if (component is null) + return; + component.DoAfter = null; + + // The target can also cease existing mid-cast + if (args.Target is null) + return; + + _rotting.ReduceAccumulator(args.Target.Value, TimeSpan.FromSeconds(args.RotReduction * component.CurrentAmplification)); + + if (!TryComp(args.Target.Value, out var damageableComponent)) + return; + + _damageable.TryChangeDamage(args.Target.Value, args.HealingAmount * component.CurrentAmplification, true, false, damageableComponent, uid); + + if (!args.DoRevive + || !TryComp(args.Target, out var mob) + || !_mobThreshold.TryGetThresholdForState(args.Target.Value, MobState.Dead, out var threshold) + || damageableComponent.TotalDamage > threshold) + return; + + _mobState.ChangeMobState(args.Target.Value, MobState.Critical, mob, uid); + } + + // This would be the same thing as OnDoAfter, except that here the target isn't nullable, so I have to reuse code with different arguments. + private void ActivatePower(EntityUid uid, PsionicComponent component, PsionicHealOtherPowerActionEvent args) + { + if (component is null) + return; + + _rotting.ReduceAccumulator(args.Target, TimeSpan.FromSeconds(args.RotReduction * component.CurrentAmplification)); + + if (!TryComp(args.Target, out var damageableComponent)) + return; + + _damageable.TryChangeDamage(args.Target, args.HealingAmount * component.CurrentAmplification, true, false, damageableComponent, uid); + + if (!args.DoRevive + || !TryComp(args.Target, out var mob) + || !_mobThreshold.TryGetThresholdForState(args.Target, MobState.Dead, out var threshold) + || damageableComponent.TotalDamage > threshold) + return; + + _mobState.ChangeMobState(args.Target, MobState.Critical, mob, uid); + } +} diff --git a/Content.Shared/Actions/Events/PsionicHealOtherPowerActionEvent.cs b/Content.Shared/Actions/Events/PsionicHealOtherPowerActionEvent.cs new file mode 100644 index 00000000000..8cf11b9e66d --- /dev/null +++ b/Content.Shared/Actions/Events/PsionicHealOtherPowerActionEvent.cs @@ -0,0 +1,59 @@ +using Robust.Shared.Audio; +using Content.Shared.Damage; +using Content.Shared.Popups; + +namespace Content.Shared.Actions.Events; +public sealed partial class PsionicHealOtherPowerActionEvent : EntityTargetActionEvent +{ + [DataField] + public DamageSpecifier HealingAmount = default!; + + [DataField] + public string PowerName; + + /// Controls whether or not a power fires immediately and with no DoAfter + [DataField] + public bool Immediate; + + [DataField] + public string? PopupText; + + [DataField] + public float RotReduction; + + [DataField] + public bool DoRevive; + + [DataField] + public bool BreakOnUserMove = true; + + [DataField] + public bool BreakOnTargetMove = false; + + [DataField] + public float UseDelay = 8f; + + [DataField] + public int MinGlimmer = 8; + + [DataField] + public int MaxGlimmer = 12; + + [DataField] + public int GlimmerObviousSoundThreshold; + + [DataField] + public int GlimmerObviousPopupThreshold; + + [DataField] + public PopupType PopupType = PopupType.Medium; + + [DataField] + public AudioParams AudioParams = default!; + + [DataField] + public bool PlaySound; + + [DataField] + public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Psionics/heartbeat_fast.ogg"); +} diff --git a/Content.Shared/Psionics/Events.cs b/Content.Shared/Psionics/Events.cs index cf9a50c6e18..f110c9c405f 100644 --- a/Content.Shared/Psionics/Events.cs +++ b/Content.Shared/Psionics/Events.cs @@ -1,28 +1,59 @@ using Robust.Shared.Serialization; +using Content.Shared.Damage; using Content.Shared.DoAfter; -namespace Content.Shared.Psionics.Events +namespace Content.Shared.Psionics.Events; + +[Serializable, NetSerializable] +public sealed partial class PsionicRegenerationDoAfterEvent : DoAfterEvent { - [Serializable, NetSerializable] - public sealed partial class PsionicRegenerationDoAfterEvent : DoAfterEvent + [DataField("startedAt", required: true)] + public TimeSpan StartedAt; + + public PsionicRegenerationDoAfterEvent(TimeSpan startedAt) { - [DataField("startedAt", required: true)] - public TimeSpan StartedAt; + StartedAt = startedAt; + } + + public override DoAfterEvent Clone() => this; +} - private PsionicRegenerationDoAfterEvent() - { - } +[Serializable, NetSerializable] +public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent { } - public PsionicRegenerationDoAfterEvent(TimeSpan startedAt) - { - StartedAt = startedAt; - } +[Serializable, NetSerializable] +public sealed partial class HealingWordDoAfterEvent : DoAfterEvent +{ + [DataField(required: true)] + public TimeSpan StartedAt; - public override DoAfterEvent Clone() => this; + public HealingWordDoAfterEvent(TimeSpan startedAt) + { + StartedAt = startedAt; } - [Serializable, NetSerializable] - public sealed partial class GlimmerWispDrainDoAfterEvent : SimpleDoAfterEvent + public override DoAfterEvent Clone() => this; +} + +[Serializable, NetSerializable] +public sealed partial class PsionicHealOtherDoAfterEvent : DoAfterEvent +{ + [DataField(required: true)] + public TimeSpan StartedAt; + + [DataField] + public DamageSpecifier HealingAmount = default!; + + [DataField] + public float RotReduction; + + [DataField] + public bool DoRevive; + + public PsionicHealOtherDoAfterEvent(TimeSpan startedAt) { + StartedAt = startedAt; } + + public override DoAfterEvent Clone() => this; } diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 85b7e380fea..37d0a9a7ef4 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -1,3 +1,4 @@ +using Content.Shared.DoAfter; using Content.Shared.Psionics; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -166,6 +167,14 @@ private set /// unneccesary subs for unique psionic entities like e.g. Oracle. /// [DataField] - public List? PsychognomicDescriptors = null; + public List? PsychognomicDescriptors = null; + + /// Used for tracking what spell a Psion is actively casting + [DataField] + public DoAfterId? DoAfter; + + /// Popup to play if a Psion attempts to start casting a power while already casting one + [DataField] + public string AlreadyCasting = "already-casting"; } } diff --git a/Resources/Locale/en-US/psionics/psionic-powers.ftl b/Resources/Locale/en-US/psionics/psionic-powers.ftl index c68bb2a4968..01b50cce326 100644 --- a/Resources/Locale/en-US/psionics/psionic-powers.ftl +++ b/Resources/Locale/en-US/psionics/psionic-powers.ftl @@ -1,4 +1,5 @@ generic-power-initialization-feedback = I Awaken. +arleady-casting = I cannot channel more than one power at a time. # Dispel dispel-power-description = Dispel summoned entities such as familiars or forcewalls. @@ -48,6 +49,28 @@ psionic-regeneration-power-initialization-feedback = I look within myself, finding a wellspring of life. psionic-regeneration-power-metapsionic-feedback = {CAPITALIZE($entity)} possesses an overwhelming will to live +# Healing Word +action-name-healing-word = Healing Word +action-description-healing-word = Speak the Lesser Secret Of Life, and restore health to another. +healing-word-power-description = Speak the Lesser Secret Of Life, and restore health to another. +healing-word-power-initialization-feedback = + At the beginning of time, a word was spoken that brought life into the Spheres. + Though it taxes my mind to know it, this Secret is known to me now. + I need only speak it. +healing-word-power-metapsionic-feedback = {CAPITALIZE($entity)} bears the Lesser Secret of Life. +healing-word-begin = {CAPITALIZE($entity)} mutters a word that brings both joy and pain alike to those who hear it. + +# Revivify +action-name-revivify = Breath of Life +action-description-revivify = Speak the Greater Secret of Life, and restore another to life. +revivify-power-description = Speak the Greater Secret of Life, and restore another to life. +revivify-power-initialization-feedback = + For a moment, my soul journeys across time and space to the beginning of it all, there I hear it. + The Secret of Life in its fullness. I feel my entire existence burning out from within, merely by knowing it. + Power flows through me as a mighty river, begging to be released with a simple spoken word. +revivify-power-metapsionic-feedback = {CAPITALIZE($entity)} bears the Greater Secret of Life. +revivify-word-begin = {CAPITALIZE($entity)} enunciates a word of such divine power, that those who hear it weep from joy. + # Telegnosis telegnosis-power-description = Create a telegnostic projection to remotely observe things. telegnosis-power-initialization-feedback = diff --git a/Resources/Prototypes/Actions/psionics.yml b/Resources/Prototypes/Actions/psionics.yml index 981d53884ea..d38608a469b 100644 --- a/Resources/Prototypes/Actions/psionics.yml +++ b/Resources/Prototypes/Actions/psionics.yml @@ -146,3 +146,76 @@ icon: Interface/VerbIcons/psionic_invisibility_off.png event: !type:RemovePsionicInvisibilityOffPowerActionEvent +- type: entity + id: ActionHealingWord + name: action-name-healing-word + description: action-description-healing-word + noSpawn: true + components: + - type: EntityTargetAction + icon: { sprite : Interface/Actions/psionics.rsi, state: healing_word } + useDelay: 10 + checkCanAccess: false + range: 6 + itemIconStyle: BigAction + canTargetSelf: true + blacklist: + components: + - PsionicInsulation + - Mindbroken + event: !type:PsionicHealOtherPowerActionEvent + healingAmount: + groups: # These all get divided by the number of damage types in the group. So they're all -2.5. + Genetic: -2.5 + Toxin: -5 + Airloss: -5 + Brute: -7.5 + Burn: -10 + rotReduction: 10 + useDelay: 1 + doRevive: true + powerName: Healing Word + popupText: healing-word-begin + playSound: true + minGlimmer: 2 + maxGlimmer: 4 + glimmerObviousSoundThreshold: 100 + glimmerObviousPopupThreshold: 200 + +- type: entity + id: ActionRevivify + name: action-name-revivify + description: action-description-revivify + noSpawn: true + components: + - type: EntityTargetAction + icon: { sprite : Interface/Actions/psionics.rsi, state: revivify } + useDelay: 120 + checkCanAccess: false + range: 2 + itemIconStyle: BigAction + canTargetSelf: false + blacklist: + components: + - PsionicInsulation + - Mindbroken + event: !type:PsionicHealOtherPowerActionEvent + healingAmount: + # These all get divided by the number of damage types in the group. So they're all -15 + # Additionally, they're multiplied by the caster's Amplification, which, + # assuming this is the only power they have, the multiplier is between 2.9-3.9 + groups: + Genetic: -15 + Toxin: -30 + Airloss: -60 # Except airloss, which heals 30 per type + Brute: -45 + Burn: -60 + rotReduction: 60 + doRevive: true + powerName: Revivify + popupText: revivify-begin + playSound: true + minGlimmer: 10 # These also get multiplied by caster stats. So, + maxGlimmer: 15 # keeping in mind the ~3.5x multiplier, this spikes glimmer by as much as 60 points. + glimmerObviousSoundThreshold: 50 + glimmerObviousPopupThreshold: 100 diff --git a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml index 0781122b8ec..2aee2273a18 100644 --- a/Resources/Prototypes/Nyanotrasen/psionicPowers.yml +++ b/Resources/Prototypes/Nyanotrasen/psionicPowers.yml @@ -11,3 +11,5 @@ # PsionicInvisibilityPower: 0.15 MindSwapPower: 0.15 TelepathyPower: 1 + HealingWordPower: 0.85 + RevivifyPower: 0.1 diff --git a/Resources/Prototypes/Psionics/psionics.yml b/Resources/Prototypes/Psionics/psionics.yml index 461098eab21..b881f1f11fb 100644 --- a/Resources/Prototypes/Psionics/psionics.yml +++ b/Resources/Prototypes/Psionics/psionics.yml @@ -140,3 +140,25 @@ initializationFeedback: telepathy-power-initialization-feedback metapsionicFeedback: psionic-language-power-feedback # Reuse for telepathy, clairaudience, etc powerSlotCost: 0 + +- type: psionicPower + id: HealingWordPower + name: HealingWord + description: healing-word-power-description + actions: + - ActionHealingWord + initializationFeedback: healing-word-power-initialization-feedback + metapsionicFeedback: healing-word-power-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: RevivifyPower + name: Revivify + description: revivify-power-description + actions: + - ActionRevivify + initializationFeedback: revivify-power-initialization-feedback + metapsionicFeedback: revivify-power-feedback + amplificationModifier: 2.5 # An extremely rare and dangerous power + powerSlotCost: 2 diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml index c7395ff3e2d..d4f8bdb0671 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml @@ -5,8 +5,8 @@ playTimeTracker: JobChaplain requirements: - !type:CharacterDepartmentTimeRequirement - department: Epistemics # DeltaV - Epistemics Department replacing Science - min: 14400 #DeltaV 4 hours + department: Epistemics # Chaplain is now one of the station's "Crew-Aligned Wizards" + min: 14400 # 4 hours - !type:CharacterLogicOrRequirement requirements: - !type:CharacterSpeciesRequirement @@ -21,7 +21,7 @@ supervisors: job-supervisors-rd access: - Chapel - - Research # DeltaV - Move Chaplain into Epistemics + - Research - Maintenance special: - !type:AddComponentSpecial @@ -32,6 +32,7 @@ - type: InnatePsionicPowers powersToAdd: - TelepathyPower + - HealingWordPower - type: startingGear id: ChaplainGear @@ -40,7 +41,7 @@ back: ClothingBackpackChaplainFilled shoes: ClothingShoesColorBlack id: ChaplainPDA - ears: ClothingHeadsetScience # DeltaV - Move Chaplain into Epistemics + ears: ClothingHeadsetScience innerClothingSkirt: ClothingUniformJumpskirtChaplain satchel: ClothingBackpackSatchelChaplainFilled duffelbag: ClothingBackpackDuffelChaplainFilled diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/healing_word.png b/Resources/Textures/Interface/Actions/psionics.rsi/healing_word.png new file mode 100644 index 0000000000000000000000000000000000000000..800e2a46e6139ae17b21fb4509242b317c6445c5 GIT binary patch literal 2189 zcmV;82y*v{P)9?Ncm1Y5zzB48of z_$vGd+6uM`f}jW@me#%+k@Zd@BuJcP_I{k1bD22<_Fz`anJqrZ<%>or;CC-Ym)*`6 z+8CyfL3}aI)aS!<5&j*Yci2?D^{QU~?`t|b6G>A8Tf6X-rW-}!8R6i1QCGYz+^c0` z3E@NGaYGI$e5Lewjh_{#Yy7U*h=gXru6nxD4IFP^Eo0<_$Amq(Y)Sh)kGe@$GQSv> zIv{fhlMn&?xQSC@jVwjHQuC5obDyv3Tc98wP5wG%3~{8%h<9Anl5|ZO^grk^BUY6hGVQ4pyq-dL6nbu;W2KhW?>Z;<5ZRC?x`w@d7?u65)gMjJXf8H>^ zJath878q+2OxT#PaA736l+>sT;>L|8mc+g3LXC|kE)X>?T-3NRv9S>CqFB2iEmR{5 zH9oMZ#UDs2t@k^(=l0xrbARUNzG8Wk-0|#Luf3U?cIb_ln{7riUuCT8tBe&kfoDrV!dj@H5(Vf$0nQ}* zyk(dDt0X2k0Wkda!9n%MzpL;d`7cJ^Rds9I(X(y2k~a{zTonmzRXHDk%J#(3 z7n860Gx^=*CLq*SL1l4OLO|Q%&osXTmIa#cKX9y88OS^`f)apL0uVGwf)1Vy;``he z_p#c|Y(WWN!)Jc_s{zBOGasjj;SLhKPK>UiDu@YAz{2HuHMR7;0tpOGf*k&KgzobN zP@nvGRxM87Abb}u&Lr+*Qb=eLf)mhx&tWA=03ZOwJ+v)zcPNk`8$ZiZg-KDNhzOnr zVc7Bh7gDKi;hRzQ%U5->8L6XUobLZ&0zK;N*CkF?e3#Fh0U`4TFPw(za z5g>h-pX(&a7oZ%FKy6Q~rit@00eZiNo0d0%xd7&cA7jO&gaLl+w^5QiuPA2-&IIs# z6F7FEqEu;#In_O=0x&XwE~tnE75gy=i34C91o3=KjYgji(|3SJpDijT2w6kAhYtR( z<@3~UNv*n>43M^DI4J;uhc7uH3_(Bx$lkVx=)z~*=LVG!36Uxi@FEM~K1h&tf^?x% zpMOn5@pCS)Y=LusYXy|qsUgbOcHO=*$AJd_WD(cvN z|HedN{LF;}s{;{$Kl$o$it=(x%O^Tm=`{G*B&^^^r{hu>4gCNB3V{kx1xgZFN!(x7 z3H&aCBx1ySWABin@V?8I*~tZ9nB3s#smZhw><0T^>Q-C(7S-mi>*_-1IQ@(7xQ`2s z-Gmh($-%v+YR8iKQDgWtYZs7{l{wsy{QU*P1rJb?7?1#0fod>@ z-k>g&+j`0Eq5xY0HlV6Q+LF>kb$0r*4{3e`tSXPSI2O2oHN9i)0vH4IW$a=_0FOY`*!GQ7kWfV!1$?AHQ=TtQd)cPr-LWt08j&4D0tLos zi)^Y5Y{+mT0J(UZ>ZEx%4KjEiz$U{R$HT6&Lq(+lmEG);HlSr+Cju}4R-fCM`JvKz zzO12H^@2>1k9D3<2UiAkRe}=%GVrjkW2InpR-P{dyy#uj3bIZ(GdZO8skm%*ZMJDJ znFlpMNbqF|~qz?yXB+!#WYbyFzk^t zpr&S8c~|*+5uh66@E@B(uU*UOp;iC@01jnXNoGw=04e|g00;m8000000Mb*F P00000NkvXXu0mjf%9`?P literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/meta.json b/Resources/Textures/Interface/Actions/psionics.rsi/meta.json new file mode 100644 index 00000000000..085cc412814 --- /dev/null +++ b/Resources/Textures/Interface/Actions/psionics.rsi/meta.json @@ -0,0 +1,17 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Created by leonardo_dabepis (discord)", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "healing_word" + }, + { + "name": "revivify" + } + ] +} diff --git a/Resources/Textures/Interface/Actions/psionics.rsi/revivify.png b/Resources/Textures/Interface/Actions/psionics.rsi/revivify.png new file mode 100644 index 0000000000000000000000000000000000000000..087f40b4c98a073de7c2e0258f6128a1c3ff755b GIT binary patch literal 1977 zcmV;q2S)gbP)9?Ncm1Y5zzB48of z_$vGd+6uM`f}jW@me#%+k@Zd@BuJcP_I{k1bD22<_Fz`anJqrZ<%>or;CC-Ym)*`6 z+8CyfL3}aI)aS!<5&j*Yci2?D^{QU~?`t|b6G>A8Tf6X-rW-}!8R6i1QCGYz+^c0` z3E@NGaYGI$e5Lewjh_{#Yy7U*h=gXru6nxD4IFP^Eo0<_$Amq(Y)Sh)kGe@$GQSv> zIv{fhlMn&?xQSC@jVwjHQuC5obDyv3Tc98wP5wG%3~{8%h<9Anl5|ZO^grk^BUY6hGVQ4pyq-dL6nbu;W2KhW?>Z;<5ZRC?x`w@d7?u65)gMjJXf8h;(q6Rd$W6&-JQwJYU2(Zm%E+YdEd{xdGp?Eh@ut|hzLXk zA_5VC|1E(_!cboK`hsblaZ*p6HE!QL9m7<91M#gbi&TDM_l?uUv}p)N0kHVhl*K3O3?H&ir-jyO~R zEWD$mGbgm4XzL5FiqAbepA8*^G60>zMizeS&1FL5O-o`~2o?Zt0I}!Ioy7giuV~2g z2*2@FlO-hpI)#l}ye-_4cIh)!)Z6^7L*$kxx6tl4cGLN@=P8{@kPIP6QKect1O_mh z!UisnnU}@SwAiQqx|$dQWpcd&7gUP1L!eYxx3Ec{OAxR3_WSN~XvXaCeW=&|4#MSD zHt!ee?&_vY<~N5I_(jRMV(sAT0lK+8T)%DIT48VH!pjTV?H29sG=IT-f)@Z}kR1Z` z11e9ID$+9%(iJZYNsbPRkKc|Z?H6CiEW2l&Kc6^}5^+GKan%V3STcwienHdM15^y~ z>MO5*z^JDx(Org6a$$tZE+b;ksJL-4jelo3fwc@wHKPDBS6)N+-?B{D-uLn6wg!#| z5X>z#%n>gozB)emC8bYKn$m6G*C+rUW%Q>%sI#*}5@YQO+mh6}YX|L!Ws{%x4<2&N zbZO~Tl=XnqbnT*YksAbw1FFP~6hkm}b^9h^OTKt=z*ty)yXePm4&iw5X9U7kEAFM2 zpMTs=kW@u_^)~{52aHuNbgC z$i4TwRSa!ANShC=7eQ~s#`R*>PbN;%ZFF9~D z&u}D1xc2ZPo9%1x0zhC-fCsE?dQfDAuk`FSA|5w25(M{s_ztamV55LQR2V{R#qvB+ zfWVa@wnunI$%VmxykW5s2u1)*jQvPRfDi*10<0gib_28}uFQ~g0nP(hDZcO9pLKmK zjf-LzfRVqRnWV3e9}y7H06`WnyFQDBc(^*}26)Gda{&|Lm6rf=?H~_1U~D3lJ@pyb z7pFgeKS!^1_vE=SPJ?b6t|jWqZi6)qHKGqgK;TnNhgH{!@)D3ASa=PFI({T&<>XdA zfTi&Cb6e@XJ-xyc_?_O$?f($pxO5pEJvB_rtFvh`W?v-@4r-fhw=w_~!hq1<_cq0( zBd{-y9HzFGHH7z-)Mn2*#LLY+`#R!+*y?*8pnDd;gQJvXFL0*AMDl?s`m+~IPXA2D zKK)AM189Yg#=OL-oC)~m0XSpBgGup#(mHI=X$XjD$O&*pfOm+;fyhhSx7#m7dbCqR zKiF&Eqj5lS=SU27If5qF28acm9pXf&x#Vt}XLbGcV(p_U{Al14$LyNBM&+CIU_Zc3%002-H<9rb30qtw*HaV)_)kN{87YmAq`_FU^2yuSR=&?9M@G`>h2^MGq3CL0&Iz0u}+cJx*LS^o{zq!wmtW_p#i|a5VqmW6XHFQ$UPrq zqfDi{UK0~40NId#Lu?@szIniG-2P(tZ3MvdEQJ-!bajLh1OCep0;j?%glAho$z!P$ zHYzr9Un2l;^I||SEgp6n1K@_BGdrqdSO literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/VerbIcons/ATTRIBUTION.txt b/Resources/Textures/Interface/VerbIcons/ATTRIBUTION.txt index 0b8ae856bd8..69aa4650b32 100644 --- a/Resources/Textures/Interface/VerbIcons/ATTRIBUTION.txt +++ b/Resources/Textures/Interface/VerbIcons/ATTRIBUTION.txt @@ -7,3 +7,6 @@ https://game-icons.net/1x1/lorc/padlock.html unlock.svg by Delapouite under CC BY 3.0 https://game-icons.net/1x1/delapouite/padlock-open.html + +healing_word.png by LeonardoDaBepis under CC BY 3.0 +revivify.png by LeonardoDaBepis under CC BY 3.0 \ No newline at end of file