Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heretic fix #984

Merged
merged 17 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions Content.IntegrationTests/Tests/ADT/Heretic/RitualKnowledgeTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System.Collections.Generic;
using System.Linq;
using Content.Server.Heretic.Ritual;
using Content.Shared.Dataset;
using Content.Shared.Tag;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;

namespace Content.IntegrationTests.Tests.ADT.Heretic;

[TestFixture, TestOf(typeof(RitualKnowledgeBehavior))]
public sealed class RitualKnowledgeTests
{
[Test]
public async Task ValidateEligibleTags()
{
// As far as I can tell, there's no annotation to validate
// a dataset of tag prototype IDs, so we'll have to do it
// in a test fixture. Sad.

await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;

var entMan = server.ResolveDependency<IEntityManager>();
var protoMan = server.ResolveDependency<IPrototypeManager>();

await server.WaitAssertion(() =>
{
// Get the eligible tags prototype
var dataset = protoMan.Index<DatasetPrototype>(RitualKnowledgeBehavior.EligibleTagsDataset);

// Validate that every value is a valid tag
Assert.Multiple(() =>
{
foreach (var tagId in dataset.Values)
{
Assert.That(protoMan.TryIndex<TagPrototype>(tagId, out var tagProto), Is.True, $"\"{tagId}\" is not a valid tag prototype ID");
}
});
});

await pair.CleanReturnAsync();
}

[Test]
public async Task ValidateTagsHaveItems()
{
await using var pair = await PoolManager.GetServerClient();
var server = pair.Server;

var entMan = server.ResolveDependency<IEntityManager>();
var protoMan = server.ResolveDependency<IPrototypeManager>();
var compFactory = server.ResolveDependency<IComponentFactory>();

await server.WaitAssertion(() =>
{
// Get the eligible tags prototype
var dataset = protoMan.Index<DatasetPrototype>(RitualKnowledgeBehavior.EligibleTagsDataset).Values.ToHashSet();

// Loop through every entity prototype and assemble a used tags set
var usedTags = new HashSet<string>();

// Ensure that every tag is used by a non-abstract entity
foreach (var entProto in protoMan.EnumeratePrototypes<EntityPrototype>())
{
if (entProto.Abstract)
continue;

if (entProto.TryGetComponent<TagComponent>(out var tags, compFactory))
{
usedTags.UnionWith(tags.Tags.Select(t => t.Id));
}
}

var unusedTags = dataset.Except(usedTags).ToHashSet();
Assert.That(unusedTags, Is.Empty, $"The following ritual item tags are not used by any obtainable entity prototypes: {string.Join(", ", unusedTags)}");
});

await pair.CleanReturnAsync();
}
}
49 changes: 35 additions & 14 deletions Content.Server/ADT/Heretic/Abilities/HereticAbilitySystem.Ash.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
using Content.Server.Atmos.Components;
using Content.Shared.Atmos;
using Content.Shared.Damage;
using Content.Shared.Heretic;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs;
using Content.Shared.Damage;
using Content.Shared.Atmos;
using Content.Server.Polymorph.Systems;
using Content.Server.Temperature.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Temperature.Components;
using Content.Server.Atmos.Components;
using Content.Server.Body.Components;
using Content.Shared.Armor;
using Content.Server.Temperature.Components;
using Content.Shared.Popups;

namespace Content.Server.Heretic.Abilities;

public sealed partial class HereticAbilitySystem : EntitySystem
{
[Dependency] private readonly IEntityManager _entMan = default!;
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;

private void SubscribeAsh()
{
SubscribeLocalEvent<HereticComponent, EventHereticAshenShift>(OnJaunt);
Expand All @@ -29,20 +32,38 @@ private void SubscribeAsh()

private void OnJaunt(Entity<HereticComponent> ent, ref EventHereticAshenShift args)
{
if (TryUseAbility(ent, args) && TryDoJaunt(ent))
var damage = args.Damage;
if (damage != null && ent.Comp.CurrentPath == "Ash")
damage *= float.Lerp(1f, 0.6f, ent.Comp.PathStage * 0.1f);

// If ent will hit their crit threshold, we don't let them jaunt and give them a popup saying so.
if (damage != null && _entMan.TryGetComponent<DamageableComponent>(ent, out var damageableComp) && _entMan.TryGetComponent<MobThresholdsComponent>(ent, out var thresholdsComp) && _mobThresholdSystem.TryGetThresholdForState(ent, MobState.Critical, out var critThreshold, thresholdsComp))
{
if (damageableComp.Damage.GetTotal() + damage.GetTotal() >= critThreshold)
{
_popup.PopupEntity(Loc.GetString("heretic-ability-fail-lowhealth", ("damage", damage.GetTotal())), ent, PopupType.LargeCaution);
return;
}
}

if (TryUseAbility(ent, args) && TryDoJaunt(ent, damage))
args.Handled = true;
}
private void OnJauntGhoul(Entity<GhoulComponent> ent, ref EventHereticAshenShift args)
{
if (TryUseAbility(ent, args) && TryDoJaunt(ent))
if (TryUseAbility(ent, args) && TryDoJaunt(ent, null))
args.Handled = true;
}
private bool TryDoJaunt(EntityUid ent)
private bool TryDoJaunt(EntityUid ent, DamageSpecifier? damage)
{
Spawn("PolymorphAshJauntAnimation", Transform(ent).Coordinates);
var urist = _poly.PolymorphEntity(ent, "AshJaunt");
if (urist == null)
return false;

if (damage != null)
_dmg.TryChangeDamage(ent, damage, true, false);

return true;
}

Expand All @@ -65,7 +86,7 @@ private void OnVolcano(Entity<HereticComponent> ent, ref EventHereticVolcanoBlas
if (!_splitball.Spawn(ent, ignoredTargets))
return;

if (ent.Comp.Ascended) // will only work on ash path
if (ent.Comp is { Ascended: true, CurrentPath: "Ash" }) // will only work on ash path
_flammable.AdjustFireStacks(ent, 20f, ignite: true);

args.Handled = true;
Expand All @@ -75,7 +96,7 @@ private void OnNWRebirth(Entity<HereticComponent> ent, ref EventHereticNightwatc
if (!TryUseAbility(ent, args))
return;

var power = ent.Comp.CurrentPath == "Ash" ? ent.Comp.PathStage : 2.5f;
var power = ent.Comp.CurrentPath == "Ash" ? ent.Comp.PathStage : 4f;
var lookup = _lookup.GetEntitiesInRange(ent, power);

foreach (var look in lookup)
Expand All @@ -98,7 +119,7 @@ private void OnNWRebirth(Entity<HereticComponent> ent, ref EventHereticNightwatc
_dmg.TryChangeDamage(ent, dmgspec, true, false, dmgc);
}

if (!flam.OnFire)
if (flam.OnFire)
_flammable.AdjustFireStacks(look, power, flam, true);

if (TryComp<MobStateComponent>(look, out var mobstat))
Expand Down Expand Up @@ -156,4 +177,4 @@ private void OnAscensionAsh(Entity<HereticComponent> ent, ref HereticAscensionAs
// this does NOT protect you against lasers and whatnot. for now. when i figure out THIS STUPID FUCKING LIMB SYSTEM!!!
// regards.
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
using Content.Shared.Damage.Components;
using Content.Shared.Heretic;
using Content.Shared.Slippery;
using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.CombatMode.Pacification;

namespace Content.Server.Heretic.Abilities;

public sealed partial class HereticAbilitySystem : EntitySystem
{
private void SubscribeBlade()
{
SubscribeLocalEvent<HereticComponent, HereticCuttingEdgeEvent>(OnCuttingEdge);
SubscribeLocalEvent<HereticComponent, ShotAttemptedEvent>(OnShootAttempt);

SubscribeLocalEvent<HereticComponent, HereticDanceOfTheBrandEvent>(OnDanceOfTheBrand);
SubscribeLocalEvent<HereticComponent, EventHereticRealignment>(OnRealignment);
SubscribeLocalEvent<HereticComponent, HereticChampionStanceEvent>(OnChampionStance);
Expand All @@ -18,10 +23,25 @@ private void SubscribeBlade()
SubscribeLocalEvent<HereticComponent, HereticAscensionBladeEvent>(OnAscensionBlade);
}

private void OnCuttingEdge(Entity<HereticComponent> ent, ref HereticCuttingEdgeEvent args)
{
ent.Comp.CanShootGuns = false;
}

private void OnShootAttempt(Entity<HereticComponent> ent, ref ShotAttemptedEvent args)
{
if (ent.Comp.CanShootGuns == false)
{
_popup.PopupEntity(Loc.GetString("heretic-cant-shoot", ("entity", args.Used)), ent, ent);
args.Cancel();
}
}

private void OnDanceOfTheBrand(Entity<HereticComponent> ent, ref HereticDanceOfTheBrandEvent args)
{
EnsureComp<RiposteeComponent>(ent);
}

private void OnRealignment(Entity<HereticComponent> ent, ref EventHereticRealignment args)
{
if (!TryUseAbility(ent, args))
Expand All @@ -44,7 +64,7 @@ private void OnRealignment(Entity<HereticComponent> ent, ref EventHereticRealign
Dirty(ent, stam);
}

_statusEffect.TryAddStatusEffect(ent, "Pacified", TimeSpan.FromSeconds(10f), true);
_statusEffect.TryAddStatusEffect<PacifiedComponent>(ent, "Pacified", TimeSpan.FromSeconds(10f), true);

args.Handled = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public sealed partial class MansusGraspSystem : EntitySystem
[Dependency] private readonly DamageableSystem _damage = default!;
[Dependency] private readonly TemperatureSystem _temperature = default!;
[Dependency] private readonly HandsSystem _hands = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;

public override void Initialize()
{
Expand All @@ -69,6 +70,7 @@ private void OnAfterInteract(Entity<MansusGraspComponent> ent, ref AfterInteract
if (!TryComp<HereticComponent>(args.User, out var hereticComp))
{
QueueDel(ent);
args.Handled = true;
return;
}

Expand Down Expand Up @@ -101,6 +103,7 @@ private void OnAfterInteract(Entity<MansusGraspComponent> ent, ref AfterInteract

hereticComp.MansusGraspActive = false;
QueueDel(ent);
args.Handled = true;
}

private void OnAfterInteract(Entity<TagComponent> ent, ref AfterInteractEvent args)
Expand All @@ -125,6 +128,7 @@ private void OnAfterInteract(Entity<TagComponent> ent, ref AfterInteractEvent ar

// spawn our rune
var rune = Spawn("HereticRuneRitualDrawAnimation", args.ClickLocation);
_transform.AttachToGridOrMap(rune);
var dargs = new DoAfterArgs(EntityManager, args.User, 14f, new DrawRitualRuneDoAfterEvent(rune, args.ClickLocation), args.User)
{
BreakOnDamage = true,
Expand All @@ -140,7 +144,7 @@ private void OnRitualRuneDoAfter(Entity<HereticComponent> ent, ref DrawRitualRun
QueueDel(ev.RitualRune);

if (!ev.Cancelled)
Spawn("HereticRuneRitual", ev.Coords);
_transform.AttachToGridOrMap(Spawn("HereticRuneRitual", ev.Coords));
}

public void ApplyGraspEffect(EntityUid performer, EntityUid target, string path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ public override void Update(float frameTime)
{
base.Update(frameTime);

while (EntityQueryEnumerator<AristocratComponent>().MoveNext(out var uid, out var aristocrat))
var query = EntityQueryEnumerator<AristocratComponent>();
while (query.MoveNext(out var uid, out var aristocrat))
{
if (!uid.IsValid())
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ public sealed partial class RitualKnowledgeBehavior : RitualCustomBehavior
private EntityLookupSystem _lookup = default!;
private HereticSystem _heretic = default!;

[ValidatePrototypeId<DatasetPrototype>]
public const string EligibleTagsDataset = "EligibleTags";

// this is basically a ripoff from hereticritualsystem
public override bool Execute(RitualData args, out string? outstr)
{
Expand All @@ -33,7 +36,7 @@ public override bool Execute(RitualData args, out string? outstr)
// generate new set of tags
if (requiredTags.Count == 0)
for (int i = 0; i < 4; i++)
requiredTags.Add(_rand.Pick(_prot.Index<DatasetPrototype>("EligibleTags").Values), 1);
requiredTags.Add(_rand.Pick(_prot.Index<DatasetPrototype>(EligibleTagsDataset).Values), 1);

var lookup = _lookup.GetEntitiesInRange(args.Platform, .75f);
var missingList = new List<string>();
Expand Down
2 changes: 2 additions & 0 deletions Content.Shared/ADT/Heretic/Components/HereticComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,6 @@ public sealed partial class HereticComponent : Component
/// Requires wearing focus, codex cicatrix, hood or anything else that allows him to do so.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)] public bool CanCastSpells = false;

[ViewVariables(VVAccess.ReadWrite)] public bool CanShootGuns = true;
}
8 changes: 7 additions & 1 deletion Content.Shared/ADT/Heretic/Heretic.Abilites.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Content.Shared.Actions;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.Inventory;
using Robust.Shared.GameStates;
Expand Down Expand Up @@ -85,7 +86,11 @@ public sealed partial class EventHereticLivingHeart : InstantActionEvent { } //
public sealed partial class EventHereticMansusLink : EntityTargetActionEvent { }

// ash
public sealed partial class EventHereticAshenShift : InstantActionEvent { }
public sealed partial class EventHereticAshenShift : InstantActionEvent
{
[DataField]
public DamageSpecifier? Damage;
}
public sealed partial class EventHereticVolcanoBlast : InstantActionEvent { }
public sealed partial class EventHereticNightwatcherRebirth : InstantActionEvent { }
public sealed partial class EventHereticFlames : InstantActionEvent { }
Expand All @@ -101,6 +106,7 @@ public sealed partial class HereticVoidBlinkEvent : WorldTargetActionEvent { }
public sealed partial class HereticVoidPullEvent : InstantActionEvent { }

// blade (+ upgrades)
[Serializable, NetSerializable, DataDefinition] public sealed partial class HereticCuttingEdgeEvent : EntityEventArgs { }
[Serializable, NetSerializable, DataDefinition] public sealed partial class HereticDanceOfTheBrandEvent : EntityEventArgs { }
public sealed partial class EventHereticRealignment : InstantActionEvent { }
[Serializable, NetSerializable, DataDefinition] public sealed partial class HereticChampionStanceEvent : EntityEventArgs { }
Expand Down
3 changes: 3 additions & 0 deletions Resources/Locale/ru-RU/ADT/Heretic/abilities/heretic.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,6 @@ heretic-speech-blind = E'E'S
heretic-speech-emp = E'P
heretic-speech-shapeshift = SH'PE
heretic-speech-link = PI'RC' TH' M'ND

heretic-cant-shoot = Я не могу использовать {$entity} из-за моей священной приверженности пути клинка.
heretic-ability-fail-lowhealth = Это заклинание наносит {$damage} урона, оно введёт вас в критическое состояние, если его использовать!
13 changes: 2 additions & 11 deletions Resources/Prototypes/ADT/Datasets/tags.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
- type: dataset
id: EligibleTags
values:
- BreathMask
- ClownEmergencyOxygenTank
- EmergencyMedipen
- EmergencyNitrogenTank
- EmergencyOxygenTank
- ExtendedEmergencyOxygenTank
- MedicalPatch
- SecurityBreathMask
- SpaceMedipen
- AirAlarm
- AirAlarmElectronics
- Airlock
- AirSensor
- Ambrosia
- ApprisalTool
- AppraisalTool
- Arrow
- ArtifactFragment
- Banana
Expand Down Expand Up @@ -122,4 +113,4 @@
- Trash
- Wirecutter
- Wrench
- ADTTeaPack
- ADTTeaPack
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,4 @@
Piercing: 15
Structural: 100
soundHit:
path: /Audio/Weapons/Guns/Hits/bullet_hit.ogg
path: /Audio/Weapons/Guns/Hits/bullet_hit.ogg
Loading
Loading