using Content.Server.Fluids.EntitySystems; using Content.Server.Objectives.Components; using Content.Server.Objectives.Systems; using Content.Server.Popups; using Content.Shared.Actions; using Content.Shared.Chemistry.Components; using Content.Shared.Dragon; using Content.Shared.Gibbing; using Content.Shared.Maps; using Content.Shared.Mind; using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.Movement.Systems; using Content.Shared.NPC.Systems; using Content.Shared.Zombies; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Map.Components; namespace Content.Server.Dragon; public sealed partial class DragonSystem : EntitySystem { [Dependency] private CarpRiftsConditionSystem _carpRifts = default!; [Dependency] private SharedMindSystem _mind = default!; [Dependency] private MovementSpeedModifierSystem _movement = default!; [Dependency] private NpcFactionSystem _faction = default!; [Dependency] private PopupSystem _popup = default!; [Dependency] private SharedActionsSystem _actions = default!; [Dependency] private SharedAudioSystem _audio = default!; [Dependency] private SharedTransformSystem _transform = default!; [Dependency] private SharedMapSystem _map = default!; [Dependency] private MobStateSystem _mobState = default!; [Dependency] private TurfSystem _turf = default!; [Dependency] private GibbingSystem _gibbing = default!; [Dependency] private SmokeSystem _smoke = default!; [Dependency] private EntityQuery _carpRiftsConditionQuery = default!; /// /// Minimum distance between 2 rifts allowed. /// private const int RiftRange = 15; /// /// Radius of tiles /// private const int RiftTileRadius = 2; private const int RiftsAllowed = 3; public override void Update(float frameTime) { base.Update(frameTime); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp)) { if (comp.WeakenedAccumulator > 0f) { comp.WeakenedAccumulator -= frameTime; // No longer weakened. if (comp.WeakenedAccumulator < 0f) { comp.WeakenedAccumulator = 0f; _movement.RefreshMovementSpeedModifiers(uid); } } // At max rifts if (comp.Rifts.Count >= RiftsAllowed) continue; // If there's an active rift don't accumulate. if (comp.Rifts.Count > 0) { var lastRift = comp.Rifts[^1]; if (TryComp(lastRift, out var rift) && rift.State != DragonRiftState.Finished) { comp.RiftAccumulator = 0f; continue; } } if (!_mobState.IsDead(uid)) comp.RiftAccumulator += frameTime; // Gib it, naughty dragon! if (comp.RiftAccumulator >= comp.RiftMaxAccumulator) { Roar(uid, comp, Transform(uid).Coordinates); var smoke = Spawn(comp.SmokePrototype, Transform(uid).Coordinates); if (TryComp(smoke, out var smokeComp)) _smoke.StartSmoke(smoke, comp.SmokeSolution, smokeComp.Duration, smokeComp.SpreadAmount, smokeComp); _gibbing.Gib(uid); } } } [SubscribeLocalEvent] private void OnInit(EntityUid uid, DragonComponent component, MapInitEvent args) { Roar(uid, component); _actions.AddAction(uid, ref component.SpawnRiftActionEntity, component.SpawnRiftAction); } [SubscribeLocalEvent] private void OnShutdown(EntityUid uid, DragonComponent component, ComponentShutdown args) { DeleteRifts(uid, false, component); } [SubscribeLocalEvent] private void OnSpawnRift(EntityUid uid, DragonComponent component, DragonSpawnRiftActionEvent args) { if (component.Weakened) { _popup.PopupEntity(Loc.GetString("carp-rift-weakened"), uid, uid); return; } if (component.Rifts.Count >= RiftsAllowed) { _popup.PopupEntity(Loc.GetString("carp-rift-max"), uid, uid); return; } if (component.Rifts.Count > 0 && TryComp(component.Rifts[^1], out var rift) && rift.State != DragonRiftState.Finished) { _popup.PopupEntity(Loc.GetString("carp-rift-duplicate"), uid, uid); return; } var xform = Transform(uid); // Have to be on a grid fam if (!TryComp(xform.GridUid, out var grid)) { _popup.PopupEntity(Loc.GetString("carp-rift-anchor"), uid, uid); return; } // cant stack rifts near eachother foreach (var (_, riftXform) in EntityQuery(true)) { if (_transform.InRange(riftXform.Coordinates, xform.Coordinates, RiftRange)) { _popup.PopupEntity(Loc.GetString("carp-rift-proximity", ("proximity", RiftRange)), uid, uid); return; } } var position = _transform.GetWorldPosition(xform); // cant put a rift on solars foreach (var tile in _map.GetTilesIntersecting(xform.GridUid.Value, grid, new Circle(position, RiftTileRadius), false)) { if (!_turf.IsSpace(tile)) continue; _popup.PopupEntity(Loc.GetString("carp-rift-space-proximity", ("proximity", RiftTileRadius)), uid, uid); return; } var carpUid = Spawn(component.RiftPrototype, new MapCoordinates(position, xform.MapID), rotation: Transform(xform.GridUid.Value).LocalRotation); component.Rifts.Add(carpUid); Comp(carpUid).Dragon = uid; } // TODO: just make this a move speed modifier component??? [SubscribeLocalEvent] private void OnDragonMove(EntityUid uid, DragonComponent component, RefreshMovementSpeedModifiersEvent args) { if (component.Weakened) { args.ModifySpeed(0.5f, 0.5f); } } [SubscribeLocalEvent] private void OnMobStateChanged(EntityUid uid, DragonComponent component, MobStateChangedEvent args) { // Deletes all rifts after dying if (args.NewMobState != MobState.Dead) return; if (component.SoundDeath != null) _audio.PlayPvs(component.SoundDeath, uid); // objective is explicitly not reset so that it will show how many you got before dying in round end text DeleteRifts(uid, false, component); } [SubscribeLocalEvent] private void OnZombified(Entity ent, ref EntityZombifiedEvent args) { // prevent carp attacking zombie dragon _faction.AddFaction(ent.Owner, ent.Comp.Faction); } private void Roar(EntityUid uid, DragonComponent comp, EntityCoordinates? coords = null) { if (comp.SoundRoar != null) { if (coords != null) _audio.PlayPvs(comp.SoundRoar, coords.Value); else _audio.PlayPvs(comp.SoundRoar, uid); } } /// /// Delete all rifts this dragon made. /// /// Entity id of the dragon /// If true, the role's rift count will be reset too /// The dragon component public void DeleteRifts(EntityUid uid, bool resetRole, DragonComponent? comp = null) { if (!Resolve(uid, ref comp)) return; foreach (var rift in comp.Rifts) { QueueDel(rift); } comp.Rifts.Clear(); // stop here if not trying to reset the objective's rift count if (!resetRole || !_mind.TryGetMind(uid, out _, out var mind)) return; foreach (var objId in mind.Objectives) { if (_carpRiftsConditionQuery.TryGetComponent(objId, out var obj)) { _carpRifts.ResetRifts(objId, obj); break; } } } /// /// Increment the dragon role's charged rift count. /// public void RiftCharged(EntityUid uid, DragonComponent? comp = null) { if (!Resolve(uid, ref comp)) return; if (!_mind.TryGetMind(uid, out _, out var mind)) return; foreach (var objId in mind.Objectives) { if (_carpRiftsConditionQuery.TryGetComponent(objId, out var obj)) { _carpRifts.RiftCharged(objId, obj); break; } } } /// /// Do everything that needs to happen when a rift gets destroyed by the crew. /// public void RiftDestroyed(EntityUid uid, DragonComponent? comp = null) { if (!Resolve(uid, ref comp)) return; // We can't predict the rift being destroyed anyway so no point adding weakened to shared. comp.WeakenedAccumulator = comp.WeakenedDuration; _movement.RefreshMovementSpeedModifiers(uid); _popup.PopupEntity(Loc.GetString("carp-rift-destroyed"), uid, uid); } }