mirror of
https://github.com/wega-team/ss14-wega.git
synced 2026-06-09 10:06:49 +02:00
1054 lines
40 KiB
C#
1054 lines
40 KiB
C#
using System.Linq;
|
|
using Content.Server.Administration;
|
|
using Content.Server.Blood.Cult.UI;
|
|
using Content.Server.Body.Systems;
|
|
using Content.Server.Chat.Systems;
|
|
using Content.Server.Emp;
|
|
using Content.Server.EUI;
|
|
using Content.Server.Flash;
|
|
using Content.Server.Hallucinations;
|
|
using Content.Shared.Bed.Sleep;
|
|
using Content.Shared.Blood.Cult;
|
|
using Content.Shared.Blood.Cult.Components;
|
|
using Content.Shared.Body.Components;
|
|
using Content.Shared.Card.Tarot;
|
|
using Content.Shared.Card.Tarot.Components;
|
|
using Content.Shared.Chemistry.Components;
|
|
using Content.Shared.Clothing;
|
|
using Content.Shared.Cuffs;
|
|
using Content.Shared.Cuffs.Components;
|
|
using Content.Shared.Damage;
|
|
using Content.Shared.Damage.Components;
|
|
using Content.Shared.Damage.Systems;
|
|
using Content.Shared.DoAfter;
|
|
using Content.Shared.Doors.Components;
|
|
using Content.Shared.EnergyShield;
|
|
using Content.Shared.FixedPoint;
|
|
using Content.Shared.Fluids.Components;
|
|
using Content.Shared.Humanoid;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.Interaction.Events;
|
|
using Content.Shared.Inventory;
|
|
using Content.Shared.NullRod.Components;
|
|
using Content.Shared.Popups;
|
|
using Content.Shared.Roles;
|
|
using Content.Shared.Stacks;
|
|
using Content.Shared.Standing;
|
|
using Content.Shared.StatusEffectNew;
|
|
using Content.Shared.Stunnable;
|
|
using Content.Shared.Timing;
|
|
using Content.Shared.Hands.Components;
|
|
using Content.Shared.Examine;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Shared.Audio;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Physics.Components;
|
|
using Robust.Shared.Physics.Systems;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Random;
|
|
|
|
namespace Content.Server.Blood.Cult;
|
|
|
|
public sealed partial class BloodCultSystem
|
|
{
|
|
[Dependency] private BloodstreamSystem _blood = default!;
|
|
[Dependency] private ChatSystem _chat = default!;
|
|
[Dependency] private DamageableSystem _damage = default!;
|
|
[Dependency] private EmpSystem _emp = default!;
|
|
[Dependency] private EntityLookupSystem _entityLookup = default!;
|
|
[Dependency] private EuiManager _euiMan = default!;
|
|
[Dependency] private FixtureSystem _fixtures = default!;
|
|
[Dependency] private FlashSystem _flash = default!;
|
|
[Dependency] private HallucinationsSystem _hallucinations = default!;
|
|
[Dependency] private InventorySystem _inventory = default!;
|
|
[Dependency] private ISharedPlayerManager _player = default!;
|
|
[Dependency] private LoadoutSystem _loadout = default!;
|
|
[Dependency] private QuickDialogSystem _quickDialog = default!;
|
|
[Dependency] private SharedCuffableSystem _cuff = default!;
|
|
[Dependency] private SharedPhysicsSystem _physics = default!;
|
|
[Dependency] private SharedStackSystem _stack = default!;
|
|
[Dependency] private SharedStunSystem _stun = default!;
|
|
[Dependency] private SharedTransformSystem _transform = default!;
|
|
[Dependency] private StatusEffectsSystem _statusEffect = default!;
|
|
[Dependency] private UseDelaySystem _useDelay = default!;
|
|
[Dependency] private VisibilitySystem _visibility = default!;
|
|
|
|
private static readonly SoundPathSpecifier CultSpell = new SoundPathSpecifier("/Audio/_Wega/Effects/cult_spell.ogg");
|
|
|
|
private void InitializeBloodAbilities()
|
|
{
|
|
// Blood Magic
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultBloodMagicActionEvent>(OnBloodMagic);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodMagicDoAfterEvent>(DoAfterSpellSelect);
|
|
|
|
// Abilities
|
|
SubscribeLocalEvent<BloodSpellComponent, AfterInteractEvent>(OnInteract);
|
|
SubscribeLocalEvent<BloodCultistComponent, RecallBloodDaggerEvent>(OnRecallDagger);
|
|
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultStunActionEvent>(OnStun);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultTeleportActionEvent>(OnTeleport);
|
|
SubscribeLocalEvent<BloodCultistComponent, TeleportSpellDoAfterEvent>(OnTeleportDoAfter);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultElectromagneticPulseActionEvent>(OnElectromagneticPulse);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultShadowShacklesActionEvent>(OnShadowShackles);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultTwistedConstructionActionEvent>(OnTwistedConstruction);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultSummonEquipmentActionEvent>(OnSummonEquipment);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultSummonDaggerActionEvent>(OnSummonDagger);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultHallucinationsActionEvent>(OnHallucinations);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultConcealPresenceActionEvent>(OnConcealPresence);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultBloodRitesActionEvent>(OnBloodRites);
|
|
|
|
SubscribeLocalEvent<BloodSpellComponent, UseInHandEvent>(BloodRites);
|
|
SubscribeLocalEvent<BloodSpellComponent, ExaminedEvent>(OnExamine);
|
|
SubscribeLocalEvent<BloodSpellComponent, BloodRitesSelectRitesMessage>(BloodRitesSelect);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultBloodOrbActionEvent>(OnBloodOrb);
|
|
SubscribeLocalEvent<BloodOrbComponent, UseInHandEvent>(OnBloodOrbAbsorbed);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultBloodRechargeActionEvent>(OnBloodRecharge);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultBloodSpearActionEvent>(OnBloodSpear);
|
|
SubscribeLocalEvent<BloodCultistComponent, RecallBloodSpearEvent>(OnRecallSpear);
|
|
SubscribeLocalEvent<BloodCultistComponent, BloodCultBloodBoltBarrageActionEvent>(OnBloodBoltBarrage);
|
|
}
|
|
|
|
#region Blood Magic
|
|
private void OnBloodMagic(EntityUid uid, BloodCultistComponent component, BloodCultBloodMagicActionEvent args)
|
|
{
|
|
if (_mind.TryGetMind(uid, out _, out var mind) &&
|
|
mind is { UserId: not null } && _player.TryGetSessionById(mind.UserId, out var session))
|
|
{
|
|
var menu = new BloodMagicEui(uid, this);
|
|
_euiMan.OpenEui(menu, session);
|
|
|
|
args.Handled = true;
|
|
}
|
|
}
|
|
|
|
public void AfterSpellSelect(EntityUid cultist, EntProtoId selectedSpell)
|
|
{
|
|
if (!TryComp<BloodCultistComponent>(cultist, out var cult))
|
|
return;
|
|
|
|
if (!cult.BloodMagicActive)
|
|
{
|
|
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, cultist, TimeSpan.FromSeconds(10f), new BloodMagicDoAfterEvent(selectedSpell), cultist)
|
|
{
|
|
BreakOnMove = true,
|
|
BreakOnDamage = true,
|
|
MovementThreshold = 0.01f,
|
|
NeedHand = true
|
|
});
|
|
}
|
|
else
|
|
{
|
|
var remSpell = cult.SelectedSpell;
|
|
_action.RemoveAction(cultist, remSpell);
|
|
cult.SelectedSpell = null;
|
|
cult.BloodMagicActive = false;
|
|
|
|
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, cultist, TimeSpan.FromSeconds(10f), new BloodMagicDoAfterEvent(selectedSpell), cultist)
|
|
{
|
|
BreakOnMove = true,
|
|
BreakOnDamage = true,
|
|
MovementThreshold = 0.01f,
|
|
NeedHand = true
|
|
});
|
|
}
|
|
}
|
|
|
|
private void DoAfterSpellSelect(EntityUid cultist, BloodCultistComponent component, BloodMagicDoAfterEvent args)
|
|
{
|
|
if (args.Cancelled)
|
|
return;
|
|
|
|
component.SelectedSpell = _action.AddAction(cultist, args.SelectedSpell);
|
|
|
|
ExtractBlood(cultist, -20, 10);
|
|
component.BloodMagicActive = true;
|
|
}
|
|
#endregion
|
|
|
|
#region Abilities
|
|
private void OnRecallDagger(EntityUid cultist, BloodCultistComponent component, RecallBloodDaggerEvent args)
|
|
{
|
|
if (component.RecallDaggerActionEntity is not { } dagger || !HasComp<BloodDaggerComponent>(dagger))
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-dagger-not-found"), cultist, cultist, PopupType.SmallCaution);
|
|
args.Handled = true;
|
|
return;
|
|
}
|
|
|
|
var cultistPosition = _transform.GetWorldPosition(cultist);
|
|
_transform.SetWorldPosition(dagger, cultistPosition);
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-dagger-recalled"), cultist, cultist);
|
|
_hands.TryPickupAnyHand(cultist, dagger);
|
|
_audio.PlayPvs(CultSpell, dagger);
|
|
args.Handled = true;
|
|
}
|
|
|
|
private void OnStun(EntityUid cultist, BloodCultistComponent component, BloodCultStunActionEvent args)
|
|
{
|
|
if (TrySpawnSpellInHand(cultist, "BloodCultSpellStun"))
|
|
{
|
|
args.Handled = true;
|
|
EmpoweringCheck(args.Action, component);
|
|
}
|
|
}
|
|
|
|
private void OnTeleport(EntityUid cultist, BloodCultistComponent component, BloodCultTeleportActionEvent args)
|
|
{
|
|
if (TrySpawnSpellInHand(cultist, "BloodCultSpellTeleport"))
|
|
{
|
|
args.Handled = true;
|
|
EmpoweringCheck(args.Action, component);
|
|
}
|
|
}
|
|
|
|
private void OnElectromagneticPulse(EntityUid cultist, BloodCultistComponent component, BloodCultElectromagneticPulseActionEvent args)
|
|
{
|
|
var coords = _transform.GetMapCoordinates(cultist);
|
|
|
|
var exclusions = new List<EntityUid>();
|
|
var entitiesInRange = _entityLookup.GetEntitiesInRange(coords, 5f);
|
|
foreach (var uid in entitiesInRange)
|
|
{
|
|
if (HasComp<BloodCultistComponent>(uid))
|
|
{
|
|
exclusions.Add(uid);
|
|
continue;
|
|
}
|
|
|
|
if (HasComp<BloodCultistComponent>(Transform(uid).ParentUid))
|
|
{
|
|
exclusions.Add(uid);
|
|
if (!TryComp<ContainerManagerComponent>(uid, out var containerManager))
|
|
continue;
|
|
|
|
var containers = _container.GetAllContainers(uid, containerManager)
|
|
.Where(c => c.ContainedEntities.Count > 0).ToList();
|
|
|
|
foreach (var container in containers)
|
|
{
|
|
foreach (var ent in container.ContainedEntities)
|
|
exclusions.Add(ent);
|
|
}
|
|
}
|
|
}
|
|
_emp.EmpPulseExclusions(coords, 5f, 100000f, 60f, exclusions);
|
|
|
|
args.Handled = true;
|
|
EmpoweringCheck(args.Action, component);
|
|
}
|
|
|
|
private void OnShadowShackles(EntityUid cultist, BloodCultistComponent component, BloodCultShadowShacklesActionEvent args)
|
|
{
|
|
if (TrySpawnSpellInHand(cultist, "BloodCultSpellShadowShackles"))
|
|
{
|
|
args.Handled = true;
|
|
EmpoweringCheck(args.Action, component);
|
|
}
|
|
}
|
|
|
|
private void OnTwistedConstruction(EntityUid cultist, BloodCultistComponent component, BloodCultTwistedConstructionActionEvent args)
|
|
{
|
|
if (TrySpawnSpellInHand(cultist, "BloodCultSpellTwistedConstruction"))
|
|
{
|
|
args.Handled = true;
|
|
EmpoweringCheck(args.Action, component);
|
|
}
|
|
}
|
|
|
|
private void OnSummonEquipment(EntityUid cultist, BloodCultistComponent component, BloodCultSummonEquipmentActionEvent args)
|
|
{
|
|
if (TrySpawnSpellInHand(cultist, "BloodCultSpellSummonEquipment"))
|
|
{
|
|
args.Handled = true;
|
|
EmpoweringCheck(args.Action, component);
|
|
}
|
|
}
|
|
|
|
private void OnSummonDagger(EntityUid cultist, BloodCultistComponent component, BloodCultSummonDaggerActionEvent args)
|
|
{
|
|
if (Exists(component.RecallDaggerActionEntity))
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-blood-dagger-exists"), cultist, cultist, PopupType.SmallCaution);
|
|
args.Handled = true;
|
|
return;
|
|
}
|
|
|
|
var cultistCoords = Transform(cultist).Coordinates;
|
|
EntProtoId selectedDagger = GetCurrentGod() switch
|
|
{
|
|
BloodCultGod.NarSi => "WeaponBloodDagger",
|
|
BloodCultGod.Reaper => "WeaponDeathDagger",
|
|
BloodCultGod.Kharin => "WeaponHellDagger",
|
|
_ => "WeaponBloodDagger"
|
|
};
|
|
|
|
var dagger = Spawn(selectedDagger, cultistCoords);
|
|
component.RecallDaggerActionEntity = dagger;
|
|
_hands.TryPickupAnyHand(cultist, dagger);
|
|
|
|
args.Handled = true;
|
|
EmpoweringCheck(args.Action, component);
|
|
}
|
|
|
|
private void OnHallucinations(EntityUid cultist, BloodCultistComponent component, BloodCultHallucinationsActionEvent args)
|
|
{
|
|
if (!HasComp<BloodCultistComponent>(args.Target))
|
|
_hallucinations.StartHallucinations(args.Target, "Hallucinations", TimeSpan.FromSeconds(30f), true, "MindBreaker");
|
|
|
|
args.Handled = true;
|
|
EmpoweringCheck(args.Action, component);
|
|
}
|
|
|
|
private void OnConcealPresence(EntityUid cultist, BloodCultistComponent component, BloodCultConcealPresenceActionEvent args)
|
|
{
|
|
var transform = Transform(cultist);
|
|
var runes = _entityLookup.GetEntitiesInRange<BloodRuneComponent>(transform.Coordinates, 4f);
|
|
var structures = _entityLookup.GetEntitiesInRange<BloodStructureComponent>(transform.Coordinates, 4f);
|
|
|
|
if (runes.Count > 0)
|
|
{
|
|
foreach (var rune in runes)
|
|
{
|
|
if (TryComp(rune.Owner, out BloodRuneComponent? bloodRuneComp))
|
|
{
|
|
if (TryComp(rune.Owner, out VisibilityComponent? visibilityComp))
|
|
{
|
|
var entity = new Entity<VisibilityComponent?>(rune.Owner, visibilityComp);
|
|
if (bloodRuneComp.IsActive)
|
|
_visibility.SetLayer(entity, 6);
|
|
else
|
|
_visibility.SetLayer(entity, 1);
|
|
}
|
|
else
|
|
{
|
|
var newVisibilityComp = AddComp<VisibilityComponent>(rune.Owner);
|
|
var entity = new Entity<VisibilityComponent?>(rune.Owner, newVisibilityComp);
|
|
if (bloodRuneComp.IsActive)
|
|
_visibility.SetLayer(entity, 6);
|
|
else
|
|
_visibility.SetLayer(entity, 1);
|
|
}
|
|
|
|
bloodRuneComp.IsActive = !bloodRuneComp.IsActive;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (structures.Count > 0)
|
|
{
|
|
foreach (var structure in structures)
|
|
{
|
|
if (TryComp(structure.Owner, out BloodStructureComponent? bloodStructureComp))
|
|
{
|
|
if (TryComp(structure.Owner, out VisibilityComponent? visibilityComp))
|
|
{
|
|
var entity = new Entity<VisibilityComponent?>(structure.Owner, visibilityComp);
|
|
if (bloodStructureComp.IsActive)
|
|
_visibility.SetLayer(entity, 6);
|
|
else
|
|
_visibility.SetLayer(entity, 1);
|
|
}
|
|
else
|
|
{
|
|
var newVisibilityComp = AddComp<VisibilityComponent>(structure.Owner);
|
|
var entity = new Entity<VisibilityComponent?>(structure.Owner, newVisibilityComp);
|
|
if (bloodStructureComp.IsActive)
|
|
_visibility.SetLayer(entity, 6);
|
|
else
|
|
_visibility.SetLayer(entity, 1);
|
|
}
|
|
|
|
if (HasComp<PhysicsComponent>(structure.Owner))
|
|
{
|
|
var fixture = _fixtures.GetFixtureOrNull(structure.Owner, bloodStructureComp.FixtureId);
|
|
if (fixture != null)
|
|
{
|
|
_physics.SetHard(structure.Owner, fixture, !bloodStructureComp.IsActive);
|
|
}
|
|
}
|
|
|
|
bloodStructureComp.IsActive = !bloodStructureComp.IsActive;
|
|
}
|
|
}
|
|
}
|
|
args.Handled = true;
|
|
EmpoweringCheck(args.Action, component);
|
|
}
|
|
#region Blood Rites
|
|
private void OnBloodRites(EntityUid cultist, BloodCultistComponent component, BloodCultBloodRitesActionEvent args)
|
|
{
|
|
if (TrySpawnSpellInHand(cultist, "BloodCultSpellBloodRites"))
|
|
{
|
|
args.Handled = true;
|
|
EmpoweringCheck(args.Action, component);
|
|
}
|
|
}
|
|
|
|
private void OnExamine(EntityUid uid, BloodSpellComponent spell, ExaminedEvent args)
|
|
{
|
|
if (spell.SpellType != BloodCultSpell.BloodRites)
|
|
return;
|
|
if (TryComp<BloodCultistComponent>(args.Examiner, out var cultist))
|
|
{
|
|
args.PushMarkup(Loc.GetString("blood-rites-count",
|
|
("blood", cultist.BloodCount)));
|
|
}
|
|
}
|
|
|
|
private void BloodRites(Entity<BloodSpellComponent> ent, ref UseInHandEvent args)
|
|
{
|
|
if (!HasComp<BloodCultistComponent>(args.User) || ent.Comp.SpellType != BloodCultSpell.BloodRites)
|
|
return;
|
|
|
|
args.Handled = true;
|
|
_ui.OpenUi(ent.Owner, BloodRitesUiKey.Key, args.User);
|
|
}
|
|
|
|
private void BloodRitesSelect(Entity<BloodSpellComponent> ent, ref BloodRitesSelectRitesMessage args)
|
|
{
|
|
if (!HasComp<BloodCultistComponent>(args.Actor) || ent.Comp.SpellType != BloodCultSpell.BloodRites)
|
|
return;
|
|
|
|
_action.AddAction(args.Actor, args.Rites);
|
|
QueueDel(ent);
|
|
}
|
|
|
|
private void OnBloodOrb(EntityUid cultist, BloodCultistComponent component, BloodCultBloodOrbActionEvent args)
|
|
{
|
|
if (!TryComp<ActorComponent>(cultist, out var playerActor))
|
|
return;
|
|
|
|
var playerSession = playerActor.PlayerSession;
|
|
_quickDialog.OpenDialog(playerSession, Loc.GetString("blood-orb-dialog-title"), Loc.GetString("blood-orb-dialog-prompt"),
|
|
(string input) =>
|
|
{
|
|
if (!int.TryParse(input, out var inputValue) || inputValue <= 0)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("blood-orb-invalid-input"), cultist, cultist, PopupType.Medium);
|
|
return;
|
|
}
|
|
|
|
if (inputValue > component.BloodCount)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("blood-orb-not-enough-blood"), cultist, cultist, PopupType.Medium);
|
|
}
|
|
else
|
|
{
|
|
component.BloodCount -= inputValue;
|
|
|
|
var bloodOrb = Spawn("BloodCultOrb", Transform(cultist).Coordinates);
|
|
EnsureComp<BloodOrbComponent>(bloodOrb, out var orb);
|
|
orb.Blood = inputValue;
|
|
|
|
_action.RemoveAction(cultist, args.Action!);
|
|
_popup.PopupEntity(Loc.GetString("blood-orb-success", ("amount", inputValue)), cultist, cultist, PopupType.Medium);
|
|
}
|
|
});
|
|
|
|
args.Handled = true;
|
|
}
|
|
|
|
private void OnBloodOrbAbsorbed(Entity<BloodOrbComponent> ent, ref UseInHandEvent args)
|
|
{
|
|
var cultist = args.User;
|
|
if (!TryComp<BloodCultistComponent>(cultist, out var cultistcomp)
|
|
|| !TryComp<BloodOrbComponent>(ent, out var component))
|
|
return;
|
|
|
|
cultistcomp.BloodCount += component.Blood;
|
|
_popup.PopupEntity(Loc.GetString("blood-orb-absorbed"), cultist, cultist, PopupType.Small);
|
|
QueueDel(ent);
|
|
}
|
|
|
|
private void OnBloodRecharge(EntityUid cultist, BloodCultistComponent component, BloodCultBloodRechargeActionEvent args)
|
|
{
|
|
if (component.BloodCount < 75)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-recharge-failed"), cultist, cultist, PopupType.SmallCaution);
|
|
return;
|
|
}
|
|
|
|
var target = args.Target;
|
|
if (TryComp<VeilShifterComponent>(target, out var veilShifter))
|
|
{
|
|
var totalActivations = veilShifter.ActivationsCount;
|
|
veilShifter.ActivationsCount = Math.Min(totalActivations + 4, 4);
|
|
|
|
_appearance.SetData(target, VeilShifterVisuals.Charged, veilShifter.ActivationsCount > 0);
|
|
|
|
component.BloodCount -= 75;
|
|
_audio.PlayPvs(CultSpell, target);
|
|
_action.RemoveAction(cultist, args.Action!);
|
|
}
|
|
else if (TryComp<BloodShieldActivaebleComponent>(target, out var bloodShield) && !HasComp<EnergyShieldOwnerComponent>(cultist))
|
|
{
|
|
_inventory.TryUnequip(cultist, bloodShield.CurrentSlot, force: true);
|
|
if (_inventory.TryEquip(cultist, target, bloodShield.CurrentSlot, force: true))
|
|
{
|
|
var shield = EnsureComp<EnergyShieldOwnerComponent>(cultist);
|
|
shield.ShieldEntity = Spawn("BloodCultShieldEffect", Transform(cultist).Coordinates);
|
|
_transform.SetParent(shield.ShieldEntity.Value, cultist);
|
|
|
|
_audio.PlayPvs(CultSpell, target);
|
|
}
|
|
|
|
component.BloodCount -= 75;
|
|
_action.RemoveAction(cultist, args.Action!);
|
|
}
|
|
else if (TryComp<CardTarotComponent>(target, out var tarot) && tarot.Card == CardTarot.NotEnchanted
|
|
&& component.BloodCount >= 100)
|
|
{
|
|
var allCards = Enum.GetValues<CardTarot>();
|
|
tarot.Card = (CardTarot)_random.Next(1, allCards.Length);
|
|
|
|
bool reversed = _random.Prob(0.5f);
|
|
if (reversed) tarot.CardType = CardTarotType.Reversed;
|
|
|
|
_appearance.SetData(target, CardTarotVisuals.State, tarot.Card);
|
|
_appearance.SetData(target, CardTarotVisuals.Reversed, reversed);
|
|
|
|
_meta.SetEntityName(target, Loc.GetString("tarot-card-name"));
|
|
_meta.SetEntityDescription(target, Loc.GetString("tarot-card-desc"));
|
|
|
|
component.BloodCount -= 100;
|
|
_action.RemoveAction(cultist, args.Action!);
|
|
}
|
|
}
|
|
|
|
private void OnBloodSpear(EntityUid cultist, BloodCultistComponent component, BloodCultBloodSpearActionEvent args)
|
|
{
|
|
if (component.BloodCount < 150)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-spear-failed"), cultist, cultist, PopupType.SmallCaution);
|
|
return;
|
|
}
|
|
|
|
if (component.RecallSpearActionEntity != null)
|
|
{
|
|
QueueDel(component.RecallSpearActionEntity);
|
|
component.RecallSpearActionEntity = null;
|
|
|
|
_action.RemoveAction(cultist, component.RecallSpearAction);
|
|
component.RecallSpearAction = null;
|
|
}
|
|
|
|
var spear = Spawn("BloodCultSpear", Transform(cultist).Coordinates);
|
|
component.RecallSpearActionEntity = spear;
|
|
_hands.TryPickupAnyHand(cultist, spear);
|
|
|
|
var action = _action.AddAction(cultist, BloodCultistComponent.RecallBloodSpear);
|
|
component.RecallSpearAction = action;
|
|
|
|
component.BloodCount -= 150;
|
|
_action.RemoveAction(cultist, args.Action!);
|
|
args.Handled = true;
|
|
}
|
|
|
|
private void OnRecallSpear(EntityUid cultist, BloodCultistComponent component, RecallBloodSpearEvent args)
|
|
{
|
|
if (component.RecallSpearActionEntity is not { } spear || !Exists(spear))
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("cult-spear-not-found"), cultist, cultist);
|
|
component.RecallSpearActionEntity = null;
|
|
_action.RemoveAction(cultist, component.RecallSpearAction);
|
|
component.RecallSpearAction = null;
|
|
args.Handled = true;
|
|
return;
|
|
}
|
|
|
|
var cultistPosition = _transform.GetWorldPosition(cultist);
|
|
var spearPosition = _transform.GetWorldPosition(spear);
|
|
var distance = (spearPosition - cultistPosition).Length();
|
|
if (distance > 10f)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("cult-spear-too-far"), cultist, cultist);
|
|
return;
|
|
}
|
|
|
|
_transform.SetWorldPosition(spear, cultistPosition);
|
|
_hands.TryPickupAnyHand(cultist, spear);
|
|
_popup.PopupEntity(Loc.GetString("cult-spear-recalled"), cultist, cultist);
|
|
args.Handled = true;
|
|
}
|
|
|
|
private void OnBloodBoltBarrage(EntityUid cultist, BloodCultistComponent component, BloodCultBloodBoltBarrageActionEvent args)
|
|
{
|
|
if (component.BloodCount < 200)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-bolt-barrage-failed"), cultist, cultist, PopupType.SmallCaution);
|
|
return;
|
|
}
|
|
|
|
if (TrySpawnSpellInHand(cultist, "BloodCultSpellBloodBarrage"))
|
|
{
|
|
component.BloodCount -= 200;
|
|
_action.RemoveAction(cultist, args.Action!);
|
|
args.Handled = true;
|
|
}
|
|
}
|
|
#endregion Blood Rites
|
|
#endregion Abilities
|
|
|
|
#region Other
|
|
|
|
private void OnInteract(Entity<BloodSpellComponent> entity, ref AfterInteractEvent args)
|
|
{
|
|
if (args.Handled || !args.CanReach || args.Target is not { Valid: true } target
|
|
|| !TryComp<BloodSpellComponent>(entity, out var spellComp))
|
|
return;
|
|
|
|
var user = args.User;
|
|
switch (spellComp.SpellType)
|
|
{
|
|
case BloodCultSpell.Stun:
|
|
HandleStunSpell(entity, user, target);
|
|
break;
|
|
case BloodCultSpell.Teleport:
|
|
HandleTeleportSpell(entity, user, target);
|
|
break;
|
|
case BloodCultSpell.ShadowShackles:
|
|
HandleShadowShacklesSpell(entity, user, target);
|
|
break;
|
|
case BloodCultSpell.TwistedConstruction:
|
|
HandleTwistedConstructionSpell(entity, user, target);
|
|
break;
|
|
case BloodCultSpell.SummonEquipment:
|
|
HandleSummonEquipmentSpell(entity, user, target);
|
|
break;
|
|
case BloodCultSpell.BloodRites:
|
|
HandleBloodRitesSpell(entity, user, target, ref args);
|
|
break;
|
|
default:
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-spell-failed"), user, user, PopupType.SmallCaution);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#region Spell Handlers
|
|
|
|
private void HandleStunSpell(Entity<BloodSpellComponent> spell, EntityUid user, EntityUid target)
|
|
{
|
|
if (HasComp<BloodCultistComponent>(target) || HasComp<NullRodOwnerComponent>(target))
|
|
return;
|
|
|
|
ExtractBlood(user, -10, 6);
|
|
|
|
_stun.TryKnockdown(target, TimeSpan.FromSeconds(4f));
|
|
_statusEffect.TryAddStatusEffectDuration(target, "Muted", TimeSpan.FromSeconds(10f));
|
|
_flash.Flash(target, user, spell, TimeSpan.FromSeconds(2f), 1f);
|
|
|
|
QueueDel(spell);
|
|
}
|
|
|
|
private void HandleTeleportSpell(Entity<BloodSpellComponent> spell, EntityUid user, EntityUid target)
|
|
{
|
|
if (HasComp<NullRodOwnerComponent>(target))
|
|
return;
|
|
|
|
ExtractBlood(user, -7, 5);
|
|
|
|
_doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, user, TimeSpan.FromSeconds(2f),
|
|
new TeleportSpellDoAfterEvent(), user, target, spell)
|
|
{
|
|
BreakOnMove = true,
|
|
BreakOnDamage = true,
|
|
MovementThreshold = 0.01f,
|
|
NeedHand = true
|
|
});
|
|
}
|
|
|
|
private void HandleShadowShacklesSpell(Entity<BloodSpellComponent> spell, EntityUid user, EntityUid target)
|
|
{
|
|
if (HasComp<BloodCultistComponent>(target) || HasComp<NullRodOwnerComponent>(target))
|
|
return;
|
|
|
|
if (!IsValidForCuffing(target))
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-shadow-shackles-failed"), user, user, PopupType.SmallCaution);
|
|
return;
|
|
}
|
|
|
|
TryApplyHandcuffs(spell, user, target);
|
|
}
|
|
|
|
private bool IsValidForCuffing(EntityUid target)
|
|
{
|
|
if (!_mobState.IsAlive(target))
|
|
return true;
|
|
|
|
if (HasComp<SleepingComponent>(target))
|
|
return true;
|
|
|
|
if (TryComp<StaminaComponent>(target, out var stamina) &&
|
|
stamina.StaminaDamage >= stamina.CritThreshold * 0.9f)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
private void TryApplyHandcuffs(Entity<BloodSpellComponent> spell, EntityUid user, EntityUid target)
|
|
{
|
|
if (!TryComp<CuffableComponent>(target, out var cuffable) || !cuffable.CanStillInteract)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-shadow-shackles-failed"), user, user, PopupType.SmallCaution);
|
|
return;
|
|
}
|
|
|
|
var handcuffs = Spawn("Handcuffs", Transform(target).Coordinates);
|
|
if (!TryComp<HandcuffComponent>(handcuffs, out var handcuffsComp) ||
|
|
!_cuff.TryAddNewCuffs(target, user, handcuffs, cuffable, handcuffsComp))
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-shadow-shackles-failed"), user, user, PopupType.SmallCaution);
|
|
QueueDel(handcuffs);
|
|
return;
|
|
}
|
|
|
|
_cuff.CuffUsed(handcuffsComp);
|
|
_statusEffect.TryAddStatusEffectDuration(target, "Muted", TimeSpan.FromSeconds(12f));
|
|
|
|
QueueDel(spell);
|
|
}
|
|
|
|
private void HandleTwistedConstructionSpell(Entity<BloodSpellComponent> spell, EntityUid user, EntityUid target)
|
|
{
|
|
if (HasComp<AirlockComponent>(target))
|
|
{
|
|
TransformAirlock(spell, user, target);
|
|
return;
|
|
}
|
|
|
|
if (TryComp<StackComponent>(target, out var stack))
|
|
{
|
|
TransformMaterial(spell, user, target, stack);
|
|
return;
|
|
}
|
|
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-twisted-failed"), user, user, PopupType.SmallCaution);
|
|
}
|
|
|
|
private void TransformAirlock(Entity<BloodSpellComponent> spell, EntityUid user, EntityUid airlock)
|
|
{
|
|
ExtractBlood(user, -12, 8);
|
|
|
|
string spawnProto = "AirlockBloodCult";
|
|
if (TryComp<DoorComponent>(airlock, out var door) && !door.Occludes)
|
|
spawnProto = "AirlockBloodCultGlass";
|
|
|
|
var airlockTransform = Transform(airlock).Coordinates;
|
|
QueueDel(airlock);
|
|
|
|
var ent = Spawn(spawnProto, airlockTransform);
|
|
_audio.PlayPvs(CultSpell, ent);
|
|
QueueDel(spell);
|
|
}
|
|
|
|
private void TransformMaterial(Entity<BloodSpellComponent> spell, EntityUid user, EntityUid material, StackComponent stack)
|
|
{
|
|
if (!_prototypeManager.TryIndex(stack.StackTypeId, out var stackPrototype))
|
|
return;
|
|
|
|
if (stackPrototype.ID is not ("Steel" or "Plasteel"))
|
|
return;
|
|
|
|
ExtractBlood(user, -12, 8);
|
|
var coords = Transform(material).Coordinates;
|
|
|
|
if (stackPrototype.ID == "Steel" && stack.Count >= 30)
|
|
{
|
|
TransformSteelToConstruct(material, coords, stack);
|
|
}
|
|
else if (stackPrototype.ID == "Plasteel")
|
|
{
|
|
TransformPlasteelToRuneMetal(material, coords, stack.Count);
|
|
}
|
|
|
|
_audio.PlayPvs(CultSpell, user);
|
|
QueueDel(spell);
|
|
}
|
|
|
|
private void TransformSteelToConstruct(EntityUid steelStack, EntityCoordinates coords, StackComponent stack)
|
|
{
|
|
_stack.ReduceCount(steelStack, 30);
|
|
if (stack.Count > 0)
|
|
{
|
|
Spawn("BloodCultConstruct", coords);
|
|
}
|
|
else
|
|
{
|
|
QueueDel(steelStack);
|
|
Spawn("BloodCultConstruct", coords);
|
|
}
|
|
}
|
|
|
|
private void TransformPlasteelToRuneMetal(EntityUid plasteelStack, EntityCoordinates coords, int count)
|
|
{
|
|
var runeSteel = Spawn("SheetRuneMetal1", coords);
|
|
QueueDel(plasteelStack);
|
|
|
|
if (TryComp<StackComponent>(runeSteel, out var newStack))
|
|
_stack.SetCount((runeSteel, newStack), count);
|
|
}
|
|
|
|
private void HandleSummonEquipmentSpell(Entity<BloodSpellComponent> spell, EntityUid user, EntityUid target)
|
|
{
|
|
QueueDel(spell);
|
|
|
|
var dropEvent = new DropHandItemsEvent();
|
|
RaiseLocalEvent(target, ref dropEvent);
|
|
|
|
var selectedGear = GetGodWeaponGear();
|
|
var gear = new List<ProtoId<StartingGearPrototype>> { selectedGear };
|
|
_loadout.Equip(target, gear, null);
|
|
|
|
FillMissingEquipmentSlots(target);
|
|
|
|
QueueDel(spell);
|
|
}
|
|
|
|
private ProtoId<StartingGearPrototype> GetGodWeaponGear()
|
|
{
|
|
return GetCurrentGod() switch
|
|
{
|
|
BloodCultGod.NarSi => new ProtoId<StartingGearPrototype>("BloodCultWeaponBloodGear"),
|
|
BloodCultGod.Reaper => new ProtoId<StartingGearPrototype>("BloodCultWeaponDeathGear"),
|
|
BloodCultGod.Kharin => new ProtoId<StartingGearPrototype>("BloodCultWeaponHellGear"),
|
|
_ => new ProtoId<StartingGearPrototype>("BloodCultWeaponBloodGear")
|
|
};
|
|
}
|
|
|
|
private void FillMissingEquipmentSlots(EntityUid target)
|
|
{
|
|
if (!TryComp<InventoryComponent>(target, out var targetInventory))
|
|
return;
|
|
|
|
var slotGearMap = new Dictionary<string, ProtoId<StartingGearPrototype>>
|
|
{
|
|
["outerClothing"] = new ProtoId<StartingGearPrototype>("BloodCultOuterGear"),
|
|
["jumpsuit"] = new ProtoId<StartingGearPrototype>("BloodCultJumpsuitGear"),
|
|
["back"] = new ProtoId<StartingGearPrototype>("BloodCultBackpackGear"),
|
|
["shoes"] = new ProtoId<StartingGearPrototype>("BloodCultShoesGear")
|
|
};
|
|
|
|
foreach (var (slot, gearPrototype) in slotGearMap)
|
|
{
|
|
if (!_inventory.TryGetSlotEntity(target, slot, out _, targetInventory))
|
|
{
|
|
var gear = new List<ProtoId<StartingGearPrototype>> { gearPrototype };
|
|
_loadout.Equip(target, gear, null);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void HandleBloodRitesSpell(Entity<BloodSpellComponent> spell, EntityUid user, EntityUid target, ref AfterInteractEvent args)
|
|
{
|
|
if (!TryComp<BloodCultistComponent>(user, out var cultist))
|
|
{
|
|
QueueDel(spell);
|
|
return;
|
|
}
|
|
|
|
if (!TryComp<UseDelayComponent>(spell, out var useDelay) || _useDelay.IsDelayed((spell, useDelay)))
|
|
return;
|
|
|
|
var handled = false;
|
|
if (HasComp<BloodCultistComponent>(target))
|
|
{
|
|
handled = HealCultist(cultist, target);
|
|
}
|
|
else if (HasComp<HumanoidProfileComponent>(target) && !HasComp<NullRodOwnerComponent>(target))
|
|
{
|
|
handled = StealBloodFromHumanoid(cultist, user, target);
|
|
}
|
|
else if (TryComp<PuddleComponent>(target, out _))
|
|
{
|
|
handled = AbsorbBloodFromPuddles(cultist, user);
|
|
}
|
|
else
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-blood-rites-failed"), user, user, PopupType.SmallCaution);
|
|
handled = true;
|
|
}
|
|
|
|
if (handled)
|
|
{
|
|
args.Handled = true;
|
|
_useDelay.TryResetDelay((spell, useDelay));
|
|
}
|
|
}
|
|
|
|
private bool HealCultist(BloodCultistComponent cultist, EntityUid target)
|
|
{
|
|
if (!TryComp<DamageableComponent>(target, out var damage))
|
|
return false;
|
|
|
|
var totalBlood = cultist.BloodCount;
|
|
var prioritizedDamageTypes = new[] { "Blunt", "Piercing", "Heat", "Slash", "Caustic" };
|
|
|
|
var positiveDamage = _damage.GetPositiveDamage((target, damage));
|
|
foreach (var damageType in prioritizedDamageTypes)
|
|
{
|
|
if (totalBlood <= 0)
|
|
break;
|
|
|
|
if (positiveDamage.DamageDict.TryGetValue(damageType, out var currentDamage) && currentDamage > 0)
|
|
{
|
|
var healAmount = FixedPoint2.Min(currentDamage, totalBlood);
|
|
var healSpecifier = new DamageSpecifier { DamageDict = { { damageType, -healAmount } } };
|
|
_damage.TryChangeDamage(target, healSpecifier, true);
|
|
totalBlood -= healAmount.Int();
|
|
}
|
|
}
|
|
|
|
cultist.BloodCount = totalBlood;
|
|
return true;
|
|
}
|
|
|
|
private bool StealBloodFromHumanoid(BloodCultistComponent cultist, EntityUid user, EntityUid target)
|
|
{
|
|
if (!HasComp<BloodstreamComponent>(target) || HasComp<BloodCultistComponent>(target))
|
|
return false;
|
|
|
|
if (_blood.GetBloodLevel(target) > 0.6)
|
|
{
|
|
_blood.TryModifyBloodLevel(target, -50);
|
|
cultist.BloodCount += 50;
|
|
return true;
|
|
}
|
|
|
|
_popup.PopupEntity(Loc.GetString("blood-cult-blood-rites-failed"), user, user, PopupType.SmallCaution);
|
|
return false;
|
|
}
|
|
|
|
private bool AbsorbBloodFromPuddles(BloodCultistComponent cultist, EntityUid user)
|
|
{
|
|
var puddlesInRange = _entityLookup
|
|
.GetEntitiesInRange<PuddleComponent>(Transform(user).Coordinates, 4f)
|
|
.Where(puddle => IsBloodPuddle(puddle.Owner))
|
|
.ToList();
|
|
|
|
var absorbedBlood = 0;
|
|
foreach (var bloodPuddle in puddlesInRange)
|
|
{
|
|
absorbedBlood += ExtractBloodFromPuddle(bloodPuddle.Owner);
|
|
}
|
|
|
|
cultist.BloodCount += absorbedBlood;
|
|
return true;
|
|
}
|
|
|
|
private bool IsBloodPuddle(EntityUid puddle)
|
|
{
|
|
if (!TryComp(puddle, out ContainerManagerComponent? containerManager) ||
|
|
!containerManager.Containers.TryGetValue("solution@puddle", out var container))
|
|
return false;
|
|
|
|
return container.ContainedEntities.Any(containedEntity =>
|
|
TryComp(containedEntity, out SolutionComponent? solutionComponent) &&
|
|
solutionComponent.Solution.Contents.Any(r =>
|
|
r.Reagent.Prototype == "Blood" || r.Reagent.Prototype == "CopperBlood"));
|
|
}
|
|
|
|
private int ExtractBloodFromPuddle(EntityUid puddle)
|
|
{
|
|
if (!TryComp(puddle, out ContainerManagerComponent? containerManager) ||
|
|
!containerManager.Containers.TryGetValue("solution@puddle", out var container))
|
|
return 0;
|
|
|
|
var absorbedBlood = 0;
|
|
foreach (var containedEntity in container.ContainedEntities.ToList())
|
|
{
|
|
if (!_solution.TryGetSolution(containedEntity, "solution", out var solutionComp, out var solutionData))
|
|
continue;
|
|
|
|
var bloodReagents = solutionData.Contents
|
|
.Where(r => r.Reagent.Prototype == "Blood" || r.Reagent.Prototype == "CopperBlood")
|
|
.ToList();
|
|
|
|
foreach (var reagent in bloodReagents)
|
|
{
|
|
absorbedBlood += reagent.Quantity.Int();
|
|
_solution.RemoveReagent(solutionComp.Value, reagent);
|
|
}
|
|
|
|
if (bloodReagents.Count > 0)
|
|
Spawn("BloodCultFloorGlowEffect", Transform(puddle).Coordinates);
|
|
|
|
if (_solution.TryGetSolution(containedEntity, "solution", out _, out var updatedSolution) && updatedSolution.Contents.Count == 0)
|
|
QueueDel(puddle);
|
|
}
|
|
|
|
return absorbedBlood;
|
|
}
|
|
|
|
#endregion
|
|
|
|
private void ExtractBlood(EntityUid cultist, int extractBlood, FixedPoint2 bloodDamage)
|
|
{
|
|
if (HasComp<BloodstreamComponent>(cultist) && _blood.GetBloodLevel(cultist) > 0)
|
|
_blood.TryModifyBloodLevel(cultist, extractBlood);
|
|
else
|
|
{
|
|
var damage = new DamageSpecifier { DamageDict = { { "Slash", bloodDamage } } };
|
|
_damage.TryChangeDamage(cultist, damage, true);
|
|
}
|
|
}
|
|
|
|
private void OnTeleportDoAfter(EntityUid cultist, BloodCultistComponent component, TeleportSpellDoAfterEvent args)
|
|
{
|
|
if (args.Cancelled || args.Target == null || args.Used == null)
|
|
return;
|
|
|
|
QueueDel(args.Used);
|
|
|
|
var runes = new List<EntityUid>();
|
|
var runeQuery = EntityQueryEnumerator<BloodRuneComponent>();
|
|
|
|
while (runeQuery.MoveNext(out var runeUid, out var runeComp))
|
|
{
|
|
if (runeComp.RuneType == BloodCultRune.Teleport)
|
|
runes.Add(runeUid);
|
|
}
|
|
|
|
if (runes.Count > 0)
|
|
{
|
|
var randomRune = runes[_random.Next(runes.Count)];
|
|
Spawn("BloodCultOutEffect", Transform(args.Target.Value).Coordinates);
|
|
_transform.SetCoordinates(args.Target.Value, Transform(randomRune).Coordinates);
|
|
Spawn("BloodCultInEffect", Transform(randomRune).Coordinates);
|
|
QueueDel(randomRune);
|
|
}
|
|
}
|
|
|
|
private void EmpoweringCheck(EntityUid spell, BloodCultistComponent component)
|
|
{
|
|
if (component.SelectedEmpoweringSpells.Contains(spell))
|
|
{
|
|
component.SelectedEmpoweringSpells.Remove(spell);
|
|
_action.RemoveAction(spell);
|
|
}
|
|
}
|
|
|
|
private bool TrySpawnSpellInHand(EntityUid uid, EntProtoId proto)
|
|
{
|
|
if (!TryComp<HandsComponent>(uid, out var hands))
|
|
return false;
|
|
|
|
var spell = Spawn(proto, Transform(uid).Coordinates);
|
|
var activeHand = _hands.GetActiveHand((uid, hands));
|
|
|
|
if (_hands.TryPickupAnyHand(uid, spell))
|
|
return true;
|
|
else if (activeHand != null && _hands.TryForcePickup((uid, hands), spell, activeHand))
|
|
return true;
|
|
else
|
|
{
|
|
QueueDel(spell);
|
|
return false;
|
|
}
|
|
|
|
}
|
|
#endregion
|
|
}
|