This commit is contained in:
Sir Warock 2026-08-14 01:46:09 +00:00 committed by GitHub
commit 2925ec87c9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 394 additions and 6 deletions

View File

@ -1,8 +1,8 @@
using Content.Shared._DV.Psionics.Events; // DeltaV - Psionics Refactor
using Content.Shared.Body.Events;
using Content.Shared.Damage.Events;
using Content.Shared.Mobs.Events;
using Content.Shared.Mobs;
using Content.Shared.Mobs.Events;
using Content.Shared.Movement.Events;
using Content.Shared.Movement.Systems;
using Content.Shared.Rejuvenate;

View File

@ -0,0 +1,121 @@
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
using Content.Shared.FixedPoint;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
namespace Content.Shared._DV.Psionics.Components.PsionicPowers;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class AltruisticHemomancyPowerComponent : BasePsionicPowerComponent
{
public override EntProtoId ActionProtoId { get; set; } = "ActionAltruisticHemomancy";
public override string PowerName { get; set; } = "Altruistic Hemomancy";
public override int MinGlimmerChanged { get; set; } = 5;
public override int MaxGlimmerChanged { get; set; } = 20;
/// <summary>
/// The normal DoAfterDuration of healing.
/// </summary>
[DataField]
public TimeSpan DoAfterDuration = TimeSpan.FromSeconds(5);
/// <summary>
/// The modifier for the DoAfterDuration if the target is in critical state.
/// </summary>
[DataField]
public float CriticalDoAfterDurationModifier = 0.5f;
/// <summary>
/// The modifier applied to the blood cost when healing a target in critical state.
/// </summary>
[DataField]
public float CriticalHealingCostModifier = 1.25f;
/// <summary>
/// The standard blood cost of using the power. It's 1/10th of a human entire blood reserves.
/// </summary>
[DataField]
public float BloodCost = -30f;
/// <summary>
/// The minimum blood percentage someone has to have to use the power.
/// </summary>
[DataField]
public float MinBloodPercentage = 0.4f;
/// <summary>
/// The healing applied evenly to the target with each DoAfter.
/// </summary>
[DataField]
public Dictionary<ProtoId<DamageGroupPrototype>, FixedPoint2> Heal = new()
{
{ "Brute", -20 },
{ "Burn", -20 },
{ "Airloss", -20 },
{ "Toxin", -20 },
};
/// <summary>
/// The target's bleeding reduced with every healing tick.
/// </summary>
[DataField]
public float ReducedBleeding = -4f;
/// <summary>
/// How many times a target can be healed with the one ability usage.
/// </summary>
[DataField]
public int MaxHealingTicks = 3;
/// <summary>
/// The current counter of completed healing.
/// </summary>
[ViewVariables, AutoNetworkedField]
public int TickCounter = 0;
/// <summary>
/// The target's rot counter reduced with every healing tick.
/// </summary>
[DataField]
public TimeSpan ReduceRot = TimeSpan.FromSeconds(30);
/// <summary>
/// The Damage the psionic user receives everytime they heal targets.
/// </summary>
[DataField]
public DamageSpecifier DamageOnHealing = new()
{
DamageDict = new()
{
{ "Bloodloss", 10 },
},
};
/// <summary>
/// The Damage the psionic user receives everytime they heal dead targets.
/// </summary>
[DataField]
public DamageSpecifier DamageOnHealingRot = new()
{
DamageDict = new()
{
{ "Cellular", 30 },
},
};
/// <summary>
/// The modifier applied to the blood cost when healing a dead target.
/// </summary>
[DataField]
public float RotHealingCostModifier = 2f;
/// <summary>
/// The modifier for the DoAfterDuration if the target is dead.
/// </summary>
[DataField]
public float RotDoAfterDurationModifier = 2f;
}

View File

@ -0,0 +1,5 @@
using Content.Shared.Actions;
namespace Content.Shared._DV.Psionics.Events.PowerActionEvents;
public sealed partial class AltruisticHemomancyPowerActionEvent : EntityTargetActionEvent;

View File

@ -0,0 +1,7 @@
using Content.Shared.DoAfter;
using Robust.Shared.Serialization;
namespace Content.Shared._DV.Psionics.Events.PowerDoAfterEvents;
[Serializable, NetSerializable]
public sealed partial class AltruisticHemomancyDoAfterEvent : SimpleDoAfterEvent;

View File

@ -0,0 +1,162 @@
using Content.Shared._DV.Psionics.Components.PsionicPowers;
using Content.Shared._DV.Psionics.Events.PowerActionEvents;
using Content.Shared._DV.Psionics.Events.PowerDoAfterEvents;
using Content.Shared.Atmos.Rotting;
using Content.Shared.Body.Systems;
using Content.Shared.Damage.Components;
using Content.Shared.Damage.Systems;
using Content.Shared.DoAfter;
using Content.Shared.IdentityManagement;
using Content.Shared.Mobs.Systems;
using Content.Shared.Popups;
namespace Content.Shared._DV.Psionics.Systems.PsionicPowers;
public sealed class AltruisticHemomancyPowerSystem : BasePsionicPowerSystem<AltruisticHemomancyPowerComponent, AltruisticHemomancyPowerActionEvent>
{
[Dependency] private readonly SharedBloodstreamSystem _bloodstream = default!;
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly SharedRottingSystem _rotting = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AltruisticHemomancyPowerComponent, AltruisticHemomancyDoAfterEvent>(OnDoAfter);
}
protected override void OnPowerInit(Entity<AltruisticHemomancyPowerComponent> power, ref MapInitEvent args)
{
base.OnPowerInit(power, ref args);
Popup.PopupEntity(Loc.GetString("psionic-power-altruistic-hemomancy-init"), power.Owner, power.Owner, PopupType.LargeCaution);
}
protected override void OnPowerUsed(Entity<AltruisticHemomancyPowerComponent> psionic, ref AltruisticHemomancyPowerActionEvent args)
{
if (!TryComp<DamageableComponent>(args.Target, out var damageable)
|| !Psionic.CanBeTargeted(args.Target, hasAggressor: args.Performer))
return;
var damage = _damageable.GetPositiveDamage((args.Target, damageable));
if (!damage.AnyPositive())
{
Popup.PopupClient(Loc.GetString("psionic-power-altruistic-hemomancy-healthy"), args.Performer, args.Performer);
return;
}
var doAfterDuration = psionic.Comp.DoAfterDuration;
if (_mobState.IsCritical(args.Target))
doAfterDuration *= psionic.Comp.CriticalDoAfterDurationModifier;
else if (_mobState.IsDead(args.Target))
doAfterDuration *= psionic.Comp.RotDoAfterDurationModifier;
var doAfterArgs = new DoAfterArgs(EntityManager,
args.Performer,
doAfterDuration,
new AltruisticHemomancyDoAfterEvent(),
psionic,
args.Target)
{
BreakOnMove = true,
BreakOnWeightlessMove = false,
};
if (!DoAfter.TryStartDoAfter(doAfterArgs, out var id))
return;
psionic.Comp.SaveDoAfterId(id.Value);
Dirty(psionic);
var messageSelf = Loc.GetString("psionic-power-altruistic-hemomancy-start-self", ("target", Identity.Entity(args.Target, EntityManager)));
var messageOthers = Loc.GetString("psionic-power-altruistic-hemomancy-start-others",
("user", Identity.Entity(args.Performer, EntityManager)),
("target", Identity.Entity(args.Target, EntityManager)));
Popup.PopupPredicted(messageSelf, messageOthers, args.Performer, args.Performer, PopupType.MediumCaution);
AfterPowerUsed(psionic, args.Performer);
}
private void OnDoAfter(Entity<AltruisticHemomancyPowerComponent> psionic, ref AltruisticHemomancyDoAfterEvent args)
{
if (args.Target is not {} target
|| args.Cancelled
|| args.Handled
|| !Psionic.CanBeTargeted(target, hasAggressor: args.User)
|| !TryComp<DamageableComponent>(target, out var damageable)
|| !_damageable.GetPositiveDamage((target, damageable)).AnyPositive())
return;
args.Handled = true;
// TODO: Fix this when Upstream fixes the prediction issue with this.
// I cannot build in a check for sufficient bloodlevel as of now. GetBloodLevel() causes missprediction issues.
//if (_bloodstream.GetBloodLevel(args.User) < psionic.Comp.MinBloodPercentage)
// {
// Popup.PopupClient(Loc.GetString("psionic-power-altruistic-hemomancy-insufficient-blood"), args.User, args.User, PopupType.SmallCaution);
// return;
// }
if (_mobState.IsDead(target))
HealRot(psionic, args.User, target);
else
HealDamage(psionic, args.User, target, damageable);
psionic.Comp.TickCounter++;
if (psionic.Comp.TickCounter < psionic.Comp.MaxHealingTicks && _damageable.GetPositiveDamage((target, damageable)).AnyPositive())
{
args.Args.Delay = psionic.Comp.DoAfterDuration;
if (_mobState.IsCritical(target))
args.Args.Delay *= psionic.Comp.CriticalDoAfterDurationModifier;
else if (_mobState.IsDead(target))
args.Args.Delay *= psionic.Comp.RotDoAfterDurationModifier;
args.Repeat = true;
Dirty(psionic);
return;
}
psionic.Comp.TickCounter = 0;
psionic.Comp.RemoveSavedDoAfterId();
Dirty(psionic);
var messageSelf = Loc.GetString("psionic-power-altruistic-hemomancy-end-self");
var messageOthers = Loc.GetString("psionic-power-altruistic-hemomancy-end-others",
("user", Identity.Entity(args.User, EntityManager)));
Popup.PopupPredicted(messageSelf, messageOthers, args.User, args.User, PopupType.SmallCaution);
}
private void HealDamage(Entity<AltruisticHemomancyPowerComponent> psionic, EntityUid user, EntityUid target, DamageableComponent damageable)
{
var bloodPercentageCost = psionic.Comp.BloodCost;
if (_mobState.IsCritical(target))
bloodPercentageCost *= psionic.Comp.CriticalHealingCostModifier;
if (!_bloodstream.TryModifyBloodLevel(user, bloodPercentageCost))
return;
// Damage the user for every heal.
_damageable.TryChangeDamage(user, psionic.Comp.DamageOnHealing, interruptsDoAfters: false, origin: psionic);
_bloodstream.TryModifyBleedAmount(target, psionic.Comp.ReducedBleeding);
foreach (var groupHeal in psionic.Comp.Heal)
{
_damageable.HealEvenly((target, damageable), groupHeal.Value, groupHeal.Key, user);
}
}
private void HealRot(Entity<AltruisticHemomancyPowerComponent> psionic, EntityUid user, EntityUid target)
{
if (!_bloodstream.TryModifyBloodLevel(user, psionic.Comp.BloodCost * psionic.Comp.RotHealingCostModifier))
return;
Popup.PopupClient(Loc.GetString("psionic-power-altruistic-hemomancy-rot-damage"), user, user, PopupType.SmallCaution);
_rotting.ReduceAccumulator(target, psionic.Comp.ReduceRot);
_damageable.TryChangeDamage(user, psionic.Comp.DamageOnHealingRot, true, false, user);
}
}

View File

@ -0,0 +1,24 @@
using Content.Shared._DV.Psionics.Components;
using Content.Shared.EntityTable;
using Content.Shared.Roles;
using Robust.Shared.Prototypes;
namespace Content.Shared._DV.Roles;
public sealed partial class ModifyPsionicPowerTableSpecial : JobSpecial
{
/// <summary>
/// The Prototype ID of the table containing the available psionic powers to roll.
/// </summary>
[DataField(required: true)]
public ProtoId<EntityTablePrototype> PsionicPowerTableId;
public override void AfterEquip(EntityUid mob)
{
var entityManager = IoCManager.Resolve<IEntityManager>();
if (!entityManager.TryGetComponent(mob, out PotentialPsionicComponent? psionic))
return;
psionic.PsionicPowerTableId = PsionicPowerTableId;
}
}

View File

@ -26,6 +26,16 @@ psionic-equipped-shielded-in-doafter = The insulative gear broke your concentrat
psionic-dispelled = Someone dispelled your psionic concentration!
## Specific Psionic messages
# Altruistic Hemomancy
psionic-power-altruistic-hemomancy-init = Your blood.. feels.. ALIVE!
psionic-power-altruistic-hemomancy-healthy = Your blood responds.. with boredom.
psionic-power-altruistic-hemomancy-insufficient-blood = Your blood grows thin.. you lose your focus..
psionic-power-altruistic-hemomancy-rot-damage = Your blood twists and turns in agony as it vanquishes the rot..
psionic-power-altruistic-hemomancy-start-self = Your blood beckons to your command, nurturing {THE($target)}'s body!
psionic-power-altruistic-hemomancy-start-others = {CAPITALIZE(THE($user))}'s hands tear open, commanding blood into {THE($target)}'s wounds!
psionic-power-altruistic-hemomancy-end-self = Your blood turns numb and sluggish, returning to your embrace.
psionic-power-altruistic-hemomancy-end-others = {CAPITALIZE(THE($user))}'s blood retracts, closing their torn hands!
# Metapsionic Pulse
psionic-power-metapsionic-success = You detect psychic presence there.
psionic-power-metapsionic-failure = You don't detect any psychic presence there.

View File

@ -17,8 +17,11 @@
- type: BibleUser #Lets them heal with bibles
- type: Condemned
soulOwnedNotDevil: true # This just means they won't suffer the effects of being condemned, while still not being able to sell their soul. - Goobstation
- !type:ModifyPsionicChanceSpecial # DeltaV - Chaplains are much more likely to be psionic.
# Begin DeltaV Additions
- !type:ModifyPsionicChanceSpecial # Chaplains are much more likely to be psionic.
jobBonusChance: 0.15 # Additional 15% chance.
- !type:ModifyPsionicPowerTableSpecial # Chaplains get a different power table.
psionicPowerTableId: ChaplainPowerTable
- type: startingGear
id: ChaplainGear

View File

@ -213,3 +213,19 @@
useDelay: 60
- type: InstantAction
event: !type:PsychokineticScreamPowerActionEvent
- type: entity
parent: BasePsionicAction
id: ActionAltruisticHemomancy
name: Coagulate
description: Command your blood to heal another alive being, exchanging your own crimson essence. Command it unto the dead to revert their rot, but it'll cost ya..
components:
- type: Action
icon:
sprite: _DV/Interface/Actions/actions_psionics_small.rsi
state: altruistic_hemomancy
useDelay: 45
- type: TargetAction
range: 1
- type: EntityTargetAction
event: !type:AltruisticHemomancyPowerActionEvent

View File

@ -1,4 +1,4 @@
# Table
# Tables
- type: entityTable
id: PsionicPowerTable
table: !type:GroupSelector
@ -13,10 +13,41 @@
weight: 1
- id: TelegnosisPowerEntity
weight: 1
- id: MassSleepPowerEntity
weight: 0.3
- id: MetapsionicPulsePowerEntity
weight: 0.5
- id: AltruisticHemomancyPowerEntity
weight: 0.3
- id: MassSleepPowerEntity
weight: 0.3
- id: NoosphericZapPowerEntity
weight: 0.3
- id: MindSwapPowerEntity
weight: 0.15
#- id: PsionicEruptionPowerEntity
# weight: 0.1
- type: entityTable
id: ChaplainPowerTable # Sadly, we need to copy the entire thing just to change one value.
table: !type:GroupSelector
children:
- id: AltruisticHemomancyPowerEntity
weight: 3 # Ten times the chance to gain this healing power as a chaplain.
- id: DispelPowerEntity
weight: 1
- id: PrecognitionPowerEntity
weight: 1
- id: PsionicRegenerationPowerEntity
weight: 1
- id: PsychokineticScreamPowerEntity
weight: 1
- id: TelegnosisPowerEntity
weight: 1
- id: MetapsionicPulsePowerEntity
weight: 0.5
- id: FracturedFormPowerEntity
weight: 0.3
- id: MassSleepPowerEntity
weight: 0.3
- id: NoosphericZapPowerEntity
weight: 0.3
- id: MindSwapPowerEntity
@ -84,3 +115,9 @@
categories: [ HideSpawnMenu ]
components:
- type: TelegnosisPower
- type: entity
id: AltruisticHemomancyPowerEntity
categories: [ HideSpawnMenu ]
components:
- type: AltruisticHemomancyPower

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 B

View File

@ -1,7 +1,7 @@
{
"version": 1,
"license": "CC-BY-SA-4.0",
"copyright": "Fractured Form sprited by TehFlaminTaco on Github for DeltaV.",
"copyright": "Fractured Form sprited by TehFlaminTaco on Github for DeltaV, Altruistic Hemomancy sprited by @Stixcking on Github.",
"size": {
"x": 32,
"y": 32
@ -9,6 +9,9 @@
"states": [
{
"name": "fractured_form"
},
{
"name": "altruistic_hemomancy"
}
]
}