using Content.Shared.Popups; using Content.Shared.Damage; using Content.Shared.MobState.Components; using Content.Server.DoAfter; using Content.Shared.Revenant; using Robust.Shared.Random; using Robust.Shared.Player; 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 Content.Shared.MobState; using Content.Server.Explosion.EntitySystems; using System.Linq; using Content.Server.Emag; using Content.Shared.CharacterAppearance.Components; using Robust.Shared.Utility; namespace Content.Server.Revenant.EntitySystems; public sealed partial class RevenantSystem : EntitySystem { [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 ExplosionSystem _explosion = default!; [Dependency] private readonly EmagSystem _emag = default!; [Dependency] private readonly SharedAppearanceSystem _appearance = default!; [Dependency] private readonly GhostSystem _ghost = default!; private void InitializeAbilities() { SubscribeLocalEvent(OnInteract); SubscribeLocalEvent(OnSoulSearchComplete); SubscribeLocalEvent(OnHarvestComplete); SubscribeLocalEvent(OnHarvestCancelled); SubscribeLocalEvent(OnDefileAction); SubscribeLocalEvent(OnOverloadLightsAction); SubscribeLocalEvent(OnBlightAction); SubscribeLocalEvent(OnMalfunctionAction); } private void OnInteract(EntityUid uid, RevenantComponent component, InteractNoHandEvent args) { var target = args.Target; if (target == args.User) return; if (HasComp(target)) { args.Handled = _ghost.DoGhostBooEvent(target); return; } if (!HasComp(target) || !HasComp(target) || HasComp(target)) return; if (!_interact.InRangeUnobstructed(uid, 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) { _popup.PopupEntity(Loc.GetString("revenant-soul-searching", ("target", target)), uid, Filter.Entities(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), }; _doAfter.DoAfter(searchDoAfter); } private void OnSoulSearchComplete(EntityUid uid, RevenantComponent component, SoulSearchDoAfterComplete args) { if (!TryComp(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.Target)), args.Target, Filter.Entities(uid), PopupType.Medium); } private void BeginHarvestDoAfter(EntityUid uid, EntityUid target, RevenantComponent revenant, EssenceComponent essence) { if (essence.Harvested) { _popup.PopupEntity(Loc.GetString("revenant-soul-harvested"), target, Filter.Entities(uid), PopupType.SmallCaution); return; } if (TryComp(target, out var mobstate) && mobstate.CurrentState == DamageState.Alive && !HasComp(target)) { _popup.PopupEntity(Loc.GetString("revenant-soul-too-powerful"), target, Filter.Entities(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, Filter.Pvs(target), PopupType.Large); TryUseAbility(uid, revenant, 0, revenant.HarvestDebuffs); _doAfter.DoAfter(doAfter); } private void OnHarvestComplete(EntityUid uid, RevenantComponent component, HarvestDoAfterComplete args) { _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, Filter.Pvs(args.Target), PopupType.LargeCaution); essence.Harvested = true; ChangeEssenceAmount(uid, essence.EssenceAmount, component); component.StolenEssence += essence.EssenceAmount; 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, Filter.Entities(uid)); component.EssenceRegenCap += component.MaxEssenceUpgradeAmount; } //KILL THEMMMM var damage = _mobState.GetEarliestDeadState(mobstate, 0)?.threshold; if (damage == null) return; DamageSpecifier dspec = new(); dspec.DamageDict.Add("Cellular", damage.Value); _damage.TryChangeDamage(args.Target, dspec, true); } private void OnHarvestCancelled(EntityUid uid, RevenantComponent component, HarvestDoAfterCancelled args) { _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; value.PryTile(); } var lookup = _lookup.GetEntitiesInRange(uid, component.DefileRadius, LookupFlags.Approximate | LookupFlags.Anchored); 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); } 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 poweredLights = GetEntityQuery(); var lookup = _lookup.GetEntitiesInRange(uid, component.OverloadRadius); foreach (var ent in lookup) { if (!poweredLights.HasComponent(ent)) continue; var ev = new GhostBooEvent(); //light go flicker RaiseLocalEvent(ent, ev); if (_random.Prob(component.OverloadBreakChance)) { //values _explosion.QueueExplosion(ent, "RevenantElectric", 15, 3, 5, canCreateVacuum: false); } } } 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.TryInfect(comp, component.BlightDiseasePrototypeId); } 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. } }