using Content.Shared.Popups; using Content.Shared.Damage; using Content.Shared.Revenant; using Robust.Shared.Random; using Robust.Shared.Map; using Content.Shared.Tag; using Content.Server.Storage.Components; using Content.Server.Light.Components; using Content.Server.Ghost; using Robust.Shared.Physics; using Content.Shared.Throwing; using Content.Server.Storage.EntitySystems; using Content.Shared.Interaction; using Content.Shared.Item; using Content.Shared.Bed.Sleep; using System.Linq; using System.Numerics; using Content.Server.Maps; using Content.Server.Revenant.Components; using Content.Shared.DoAfter; using Content.Shared.Emag.Systems; using Content.Shared.FixedPoint; using Content.Shared.Humanoid; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Revenant.Components; using Robust.Shared.Physics.Components; using Robust.Shared.Utility; namespace Content.Server.Revenant.EntitySystems; public sealed partial class RevenantSystem { [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly ThrowingSystem _throwing = default!; [Dependency] private readonly EntityStorageSystem _entityStorage = default!; [Dependency] private readonly EmagSystem _emag = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; [Dependency] private readonly GhostSystem _ghost = default!; [Dependency] private readonly TileSystem _tile = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; private void InitializeAbilities() { SubscribeLocalEvent(OnInteract); SubscribeLocalEvent(OnSoulSearch); SubscribeLocalEvent(OnHarvest); SubscribeLocalEvent(OnDefileAction); SubscribeLocalEvent(OnOverloadLightsAction); SubscribeLocalEvent(OnBlightAction); SubscribeLocalEvent(OnMalfunctionAction); } private void OnInteract(EntityUid uid, RevenantComponent component, InteractNoHandEvent args) { if (args.Target == args.User || args.Target == null) return; var target = args.Target.Value; if (HasComp(target)) { args.Handled = _ghost.DoGhostBooEvent(target); return; } if (!HasComp(target) || !HasComp(target) || HasComp(target)) return; args.Handled = true; if (!TryComp(target, out var essence) || !essence.SearchComplete) { EnsureComp(target); BeginSoulSearchDoAfter(uid, target, component); } else { BeginHarvestDoAfter(uid, target, component, essence); } } private void BeginSoulSearchDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant) { var searchDoAfter = new DoAfterArgs(uid, revenant.SoulSearchDuration, new SoulEvent(), uid, target: target) { BreakOnUserMove = true, BreakOnDamage = true, DistanceThreshold = 2 }; if (!_doAfter.TryStartDoAfter(searchDoAfter)) return; _popup.PopupEntity(Loc.GetString("revenant-soul-searching", ("target", target)), uid, uid, PopupType.Medium); } private void OnSoulSearch(EntityUid uid, RevenantComponent component, SoulEvent args) { if (args.Handled || args.Cancelled) return; if (!TryComp(args.Args.Target, out var essence)) return; essence.SearchComplete = true; string message; switch (essence.EssenceAmount) { case <= 45: message = "revenant-soul-yield-low"; break; case >= 90: message = "revenant-soul-yield-high"; break; default: message = "revenant-soul-yield-average"; break; } _popup.PopupEntity(Loc.GetString(message, ("target", args.Args.Target)), args.Args.Target.Value, uid, PopupType.Medium); args.Handled = true; } private void BeginHarvestDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant, EssenceComponent essence) { if (essence.Harvested) { _popup.PopupEntity(Loc.GetString("revenant-soul-harvested"), target, uid, PopupType.SmallCaution); return; } if (TryComp(target, out var mobstate) && mobstate.CurrentState == MobState.Alive && !HasComp(target)) { _popup.PopupEntity(Loc.GetString("revenant-soul-too-powerful"), target, uid); return; } var doAfter = new DoAfterArgs(uid, revenant.HarvestDebuffs.X, new HarvestEvent(), uid, target: target) { DistanceThreshold = 2, BreakOnUserMove = true, BreakOnDamage = true, RequireCanInteract = false, // stuns itself }; if (!_doAfter.TryStartDoAfter(doAfter)) return; _appearance.SetData(uid, RevenantVisuals.Harvesting, true); _popup.PopupEntity(Loc.GetString("revenant-soul-begin-harvest", ("target", target)), target, PopupType.Large); TryUseAbility(uid, revenant, 0, revenant.HarvestDebuffs); } private void OnHarvest(EntityUid uid, RevenantComponent component, HarvestEvent args) { if (args.Cancelled) { _appearance.SetData(uid, RevenantVisuals.Harvesting, false); return; } if (args.Handled || args.Args.Target == null) return; _appearance.SetData(uid, RevenantVisuals.Harvesting, false); if (!TryComp(args.Args.Target, out var essence)) return; _popup.PopupEntity(Loc.GetString("revenant-soul-finish-harvest", ("target", args.Args.Target)), args.Args.Target.Value, PopupType.LargeCaution); essence.Harvested = true; ChangeEssenceAmount(uid, essence.EssenceAmount, component); _store.TryAddCurrency(new Dictionary { {component.StolenEssenceCurrencyPrototype, essence.EssenceAmount} }, uid); if (!HasComp(args.Args.Target)) return; if (_mobState.IsAlive(args.Args.Target.Value) || _mobState.IsCritical(args.Args.Target.Value)) { _popup.PopupEntity(Loc.GetString("revenant-max-essence-increased"), uid, uid); component.EssenceRegenCap += component.MaxEssenceUpgradeAmount; } //KILL THEMMMM if (!_mobThresholdSystem.TryGetThresholdForState(args.Args.Target.Value, MobState.Dead, out var damage)) return; DamageSpecifier dspec = new(); dspec.DamageDict.Add("Cold", damage.Value); _damage.TryChangeDamage(args.Args.Target, dspec, true, origin: uid); args.Handled = true; } private void OnDefileAction(EntityUid uid, RevenantComponent component, RevenantDefileActionEvent args) { if (args.Handled) return; if (!TryUseAbility(uid, component, component.DefileCost, component.DefileDebuffs)) return; args.Handled = true; //var coords = Transform(uid).Coordinates; //var gridId = coords.GetGridUid(EntityManager); var xform = Transform(uid); if (!_mapManager.TryGetGrid(xform.GridUid, out var map)) return; var tiles = map.GetTilesIntersecting(Box2.CenteredAround(_transform.GetWorldPosition(xform), new Vector2(component.DefileRadius * 2, component.DefileRadius))).ToArray(); _random.Shuffle(tiles); for (var i = 0; i < component.DefileTilePryAmount; i++) { if (!tiles.TryGetValue(i, out var value)) continue; _tile.PryTile(value); } var lookup = _lookup.GetEntitiesInRange(uid, component.DefileRadius, LookupFlags.Approximate | LookupFlags.Static); var tags = GetEntityQuery(); var entityStorage = GetEntityQuery(); var items = GetEntityQuery(); var lights = GetEntityQuery(); foreach (var ent in lookup) { //break windows if (tags.HasComponent(ent) && _tag.HasAnyTag(ent, "Window")) { //hardcoded damage specifiers til i die. var dspec = new DamageSpecifier(); dspec.DamageDict.Add("Structural", 15); _damage.TryChangeDamage(ent, dspec, origin: uid); } if (!_random.Prob(component.DefileEffectChance)) continue; //randomly opens some lockers and such. if (entityStorage.TryGetComponent(ent, out var entstorecomp)) _entityStorage.OpenStorage(ent, entstorecomp); //chucks shit if (items.HasComponent(ent) && TryComp(ent, out var phys) && phys.BodyType != BodyType.Static) _throwing.TryThrow(ent, _random.NextAngle().ToWorldVec()); //flicker lights if (lights.HasComponent(ent)) _ghost.DoGhostBooEvent(ent); } } private void OnOverloadLightsAction(EntityUid uid, RevenantComponent component, RevenantOverloadLightsActionEvent args) { if (args.Handled) return; if (!TryUseAbility(uid, component, component.OverloadCost, component.OverloadDebuffs)) return; args.Handled = true; var xform = Transform(uid); var poweredLights = GetEntityQuery(); var mobState = GetEntityQuery(); var lookup = _lookup.GetEntitiesInRange(uid, component.OverloadRadius); //TODO: feels like this might be a sin and a half foreach (var ent in lookup) { if (!mobState.HasComponent(ent) || !_mobState.IsAlive(ent)) continue; var nearbyLights = _lookup.GetEntitiesInRange(ent, component.OverloadZapRadius) .Where(e => poweredLights.HasComponent(e) && !HasComp(e) && _interact.InRangeUnobstructed(e, uid, -1)).ToArray(); if (!nearbyLights.Any()) continue; //get the closest light var allLight = nearbyLights.OrderBy(e => Transform(e).Coordinates.TryDistance(EntityManager, xform.Coordinates, out var dist) ? component.OverloadZapRadius : dist); var comp = EnsureComp(allLight.First()); comp.Target = ent; //who they gon fire at? } } private void OnBlightAction(EntityUid uid, RevenantComponent component, RevenantBlightActionEvent args) { if (args.Handled) return; if (!TryUseAbility(uid, component, component.BlightCost, component.BlightDebuffs)) return; args.Handled = true; // TODO: When disease refactor is in. } private void OnMalfunctionAction(EntityUid uid, RevenantComponent component, RevenantMalfunctionActionEvent args) { if (args.Handled) return; if (!TryUseAbility(uid, component, component.MalfunctionCost, component.MalfunctionDebuffs)) return; args.Handled = true; foreach (var ent in _lookup.GetEntitiesInRange(uid, component.MalfunctionRadius)) { _emag.DoEmagEffect(uid, ent); //it does not emag itself. adorable. } } }