using Content.Shared.Popups; using Content.Shared.Damage; using Content.Server.DoAfter; using Content.Shared.Revenant; using Robust.Shared.Random; using Robust.Shared.Map; using Content.Shared.Tag; using Content.Shared.Maps; 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.Server.Disease; using Content.Server.Disease.Components; using Content.Shared.Item; using Content.Shared.Bed.Sleep; using System.Linq; using Content.Server.Beam; using Content.Server.Emag; using Content.Server.Humanoid; using Content.Server.Maps; using Content.Server.Revenant.Components; using Content.Server.Store.Components; 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 DiseaseSystem _disease = 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!; private void InitializeAbilities() { SubscribeLocalEvent(OnInteract); SubscribeLocalEvent(OnSoulSearchComplete); SubscribeLocalEvent(OnSoulSearchCancelled); SubscribeLocalEvent(OnHarvestComplete); SubscribeLocalEvent(OnHarvestCancelled); 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) { if (revenant.SoulSearchCancelToken != null) return; _popup.PopupEntity(Loc.GetString("revenant-soul-searching", ("target", target)), uid, uid, PopupType.Medium); revenant.SoulSearchCancelToken = new(); var searchDoAfter = new DoAfterEventArgs(uid, revenant.SoulSearchDuration, revenant.SoulSearchCancelToken.Token, target) { BreakOnUserMove = true, DistanceThreshold = 2, UserFinishedEvent = new SoulSearchDoAfterComplete(target), UserCancelledEvent = new SoulSearchDoAfterCancelled(), }; _doAfter.DoAfter(searchDoAfter); } private void OnSoulSearchComplete(EntityUid uid, RevenantComponent component, SoulSearchDoAfterComplete args) { if (!TryComp(args.Target, out var essence)) return; component.SoulSearchCancelToken = null; 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.Target)), args.Target, uid, PopupType.Medium); } private void OnSoulSearchCancelled(EntityUid uid, RevenantComponent component, SoulSearchDoAfterCancelled args) { component.SoulSearchCancelToken = null; } private void BeginHarvestDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant, EssenceComponent essence) { if (revenant.HarvestCancelToken != null) return; 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; } revenant.HarvestCancelToken = new(); var doAfter = new DoAfterEventArgs(uid, revenant.HarvestDebuffs.X, revenant.HarvestCancelToken.Token, target) { DistanceThreshold = 2, BreakOnUserMove = true, NeedHand = false, UserFinishedEvent = new HarvestDoAfterComplete(target), UserCancelledEvent = new HarvestDoAfterCancelled(), }; _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); _doAfter.DoAfter(doAfter); } private void OnHarvestComplete(EntityUid uid, RevenantComponent component, HarvestDoAfterComplete args) { component.HarvestCancelToken = null; _appearance.SetData(uid, RevenantVisuals.Harvesting, false); if (!TryComp(args.Target, out var essence)) return; _popup.PopupEntity(Loc.GetString("revenant-soul-finish-harvest", ("target", args.Target)), args.Target, PopupType.LargeCaution); essence.Harvested = true; ChangeEssenceAmount(uid, essence.EssenceAmount, component); if (TryComp(uid, out var store)) { _store.TryAddCurrency(new Dictionary() { {component.StolenEssenceCurrencyPrototype, essence.EssenceAmount} }, store); } if (!TryComp(args.Target, out var mobstate)) return; if (_mobState.IsAlive(args.Target) || _mobState.IsCritical(args.Target)) { _popup.PopupEntity(Loc.GetString("revenant-max-essence-increased"), uid, uid); component.EssenceRegenCap += component.MaxEssenceUpgradeAmount; } //KILL THEMMMM if (!_mobThresholdSystem.TryGetThresholdForState(args.Target, MobState.Dead, out var damage)) return; DamageSpecifier dspec = new(); dspec.DamageDict.Add("Poison", damage.Value); _damage.TryChangeDamage(args.Target, dspec, true, origin: uid); } private void OnHarvestCancelled(EntityUid uid, RevenantComponent component, HarvestDoAfterCancelled args) { component.HarvestCancelToken = null; _appearance.SetData(uid, RevenantVisuals.Harvesting, false); } 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(xform.WorldPosition, (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; var emo = GetEntityQuery(); foreach (var ent in _lookup.GetEntitiesInRange(uid, component.BlightRadius)) { if (emo.TryGetComponent(ent, out var comp)) _disease.TryAddDisease(ent, component.BlightDiseasePrototypeId, comp); } } 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.DoEmag(ent, ent); //it emags itself. spooky. } } }