diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index c321c1c2aa..47b421c419 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -207,13 +207,16 @@ public sealed class NukeopsRuleSystem : GameRuleSystem if (component.WinType == WinType.OpsMajor || component.WinType == WinType.CrewMajor) return; - foreach (var (nuke, nukeTransform) in EntityQuery(true)) + var nukeQuery = AllEntityQuery(); + var centcomms = _emergency.GetCentcommMaps(); + + while (nukeQuery.MoveNext(out var nuke, out var nukeTransform)) { if (nuke.Status != NukeStatus.ARMED) continue; // UH OH - if (nukeTransform.MapID == _emergency.CentComMap) + if (centcomms.Contains(nukeTransform.MapID)) { component.WinConditions.Add(WinCondition.NukeActiveAtCentCom); SetWinType(uid, WinType.OpsMajor, component); @@ -259,10 +262,12 @@ public sealed class NukeopsRuleSystem : GameRuleSystem component.WinConditions.Add(WinCondition.SomeNukiesAlive); var diskAtCentCom = false; - foreach (var (_, transform) in EntityManager.EntityQuery()) + var diskQuery = AllEntityQuery(); + + while (diskQuery.MoveNext(out _, out var transform)) { var diskMapId = transform.MapID; - diskAtCentCom = _emergency.CentComMap == diskMapId; + diskAtCentCom = centcomms.Contains(diskMapId); // TODO: The target station should be stored, and the nuke disk should store its original station. // This is fine for now, because we can assume a single station in base SS14. diff --git a/Content.Server/Shuttles/Components/StationCentcommComponent.cs b/Content.Server/Shuttles/Components/StationCentcommComponent.cs new file mode 100644 index 0000000000..ee12a266f1 --- /dev/null +++ b/Content.Server/Shuttles/Components/StationCentcommComponent.cs @@ -0,0 +1,28 @@ +using Robust.Shared.Map; +using Robust.Shared.Utility; + +namespace Content.Server.Shuttles.Components; + +/// +/// Spawns Central Command (emergency destination) for a station. +/// +[RegisterComponent] +public sealed class StationCentcommComponent : Component +{ + /// + /// Crude shuttle offset spawning. + /// + [DataField("shuttleIndex")] + public float ShuttleIndex; + + [DataField("map")] + public ResPath Map = new("/Maps/centcomm.yml"); + + /// + /// Centcomm entity that was loaded. + /// + [DataField("entity")] + public EntityUid Entity = EntityUid.Invalid; + + public MapId MapId = MapId.Nullspace; +} diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs index 92ac8b49c0..a72419cc8e 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.Console.cs @@ -154,39 +154,39 @@ public sealed partial class EmergencyShuttleSystem { _launchedShuttles = true; - if (CentComMap != null) + var dataQuery = AllEntityQuery(); + + while (dataQuery.MoveNext(out var stationUid, out var comp)) { - var dataQuery = AllEntityQuery(); - - while (dataQuery.MoveNext(out var comp)) + if (!TryComp(comp.EmergencyShuttle, out var shuttle) || + !TryComp(stationUid, out var centcomm)) { - if (!TryComp(comp.EmergencyShuttle, out var shuttle)) - continue; - - if (Deleted(CentComGrid)) - { - // TODO: Need to get non-overlapping positions. - _shuttle.FTLTravel(comp.EmergencyShuttle.Value, shuttle, - new EntityCoordinates( - _mapManager.GetMapEntityId(CentComMap.Value), - _random.NextVector2(1000f)), _consoleAccumulator, TransitTime); - } - else - { - _shuttle.FTLTravel(comp.EmergencyShuttle.Value, shuttle, - CentComGrid.Value, _consoleAccumulator, TransitTime, true); - } + continue; } - var podQuery = AllEntityQuery(); - var podLaunchOffset = 0.5f; - - // Stagger launches coz funny - while (podQuery.MoveNext(out _, out var pod)) + if (Deleted(centcomm.Entity)) { - pod.LaunchTime = _timing.CurTime + TimeSpan.FromSeconds(podLaunchOffset); - podLaunchOffset += _random.NextFloat(0.5f, 2.5f); + // TODO: Need to get non-overlapping positions. + _shuttle.FTLTravel(comp.EmergencyShuttle.Value, shuttle, + new EntityCoordinates( + _mapManager.GetMapEntityId(centcomm.MapId), + _random.NextVector2(1000f)), _consoleAccumulator, TransitTime); } + else + { + _shuttle.FTLTravel(comp.EmergencyShuttle.Value, shuttle, + centcomm.Entity, _consoleAccumulator, TransitTime, true); + } + } + + var podQuery = AllEntityQuery(); + var podLaunchOffset = 0.5f; + + // Stagger launches coz funny + while (podQuery.MoveNext(out _, out var pod)) + { + pod.LaunchTime = _timing.CurTime + TimeSpan.FromSeconds(podLaunchOffset); + podLaunchOffset += _random.NextFloat(0.5f, 2.5f); } } @@ -194,13 +194,16 @@ public sealed partial class EmergencyShuttleSystem while (podLaunchQuery.MoveNext(out var uid, out var pod, out var shuttle)) { - if (CentComGrid == null || pod.LaunchTime == null || pod.LaunchTime < _timing.CurTime) + var stationUid = _station.GetOwningStation(uid); + + if (!TryComp(stationUid, out var centcomm) || + Deleted(centcomm.Entity) || pod.LaunchTime == null || pod.LaunchTime < _timing.CurTime) + { continue; + } // Don't dock them. If you do end up doing this then stagger launch. - _shuttle.FTLTravel(uid, shuttle, - CentComGrid.Value, hyperspaceTime: TransitTime); - + _shuttle.FTLTravel(uid, shuttle, centcomm.Entity, hyperspaceTime: TransitTime); RemCompDeferred(uid); } @@ -210,16 +213,22 @@ public sealed partial class EmergencyShuttleSystem _leftShuttles = true; _chatSystem.DispatchGlobalAnnouncement(Loc.GetString("emergency-shuttle-left", ("transitTime", $"{TransitTime:0}"))); - _roundEndCancelToken = new CancellationTokenSource(); - Timer.Spawn((int) (TransitTime * 1000) + _bufferTime.Milliseconds, () => _roundEnd.EndRound(), _roundEndCancelToken.Token); + Timer.Spawn((int) (TransitTime * 1000) + _bufferTime.Milliseconds, () => _roundEnd.EndRound(), _roundEndCancelToken?.Token ?? default); } // All the others. if (_consoleAccumulator < minTime) { + var query = AllEntityQuery(); + // Guarantees that emergency shuttle arrives first before anyone else can FTL. - if (CentComGrid != null) - _shuttle.AddFTLDestination(CentComGrid.Value, true); + while (query.MoveNext(out var comp)) + { + if (Deleted(comp.Entity)) + continue; + + _shuttle.AddFTLDestination(comp.Entity, true); + } } } @@ -371,7 +380,9 @@ public sealed partial class EmergencyShuttleSystem public bool DelayEmergencyRoundEnd() { - if (_roundEndCancelToken == null) return false; + if (_roundEndCancelToken == null) + return false; + _roundEndCancelToken?.Cancel(); _roundEndCancelToken = null; return true; diff --git a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs index 4ac05eb218..7b83ed15b1 100644 --- a/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Threading; using Content.Server.Access.Systems; using Content.Server.Administration.Logs; using Content.Server.Administration.Managers; @@ -23,7 +23,6 @@ using Robust.Shared.Map; using Robust.Shared.Player; using Robust.Shared.Random; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Server.Shuttles.Systems; @@ -54,14 +53,6 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem private ISawmill _sawmill = default!; - public MapId? CentComMap { get; private set; } - public EntityUid? CentComGrid { get; private set; } - - /// - /// Used for multiple shuttle spawn offsets. - /// - private float _shuttleIndex; - private const float ShuttleSpawnBuffer = 1f; private bool _emergencyShuttleEnabled; @@ -76,14 +67,35 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem _configManager.OnValueChanged(CCVars.EmergencyShuttleEnabled, SetEmergencyShuttleEnabled); SubscribeLocalEvent(OnRoundStart); SubscribeLocalEvent(OnStationStartup); + SubscribeLocalEvent(OnCentcommShutdown); + SubscribeLocalEvent(OnCentcommInit); SubscribeNetworkEvent(OnShuttleRequestPosition); InitializeEmergencyConsole(); } + private void OnRoundStart(RoundStartingEvent ev) + { + CleanupEmergencyConsole(); + _roundEndCancelToken?.Cancel(); + _roundEndCancelToken = new CancellationTokenSource(); + } + + private void OnCentcommShutdown(EntityUid uid, StationCentcommComponent component, ComponentShutdown args) + { + QueueDel(component.Entity); + component.Entity = EntityUid.Invalid; + + if (_mapManager.MapExists(component.MapId)) + _mapManager.DeleteMap(component.MapId); + + component.MapId = MapId.Nullspace; + } + private void SetEmergencyShuttleEnabled(bool value) { if (_emergencyShuttleEnabled == value) return; + _emergencyShuttleEnabled = value; if (value) @@ -96,6 +108,16 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem } } + private void CleanupEmergencyShuttle() + { + var query = AllEntityQuery(); + + while (query.MoveNext(out var uid, out _)) + { + RemCompDeferred(uid); + } + } + public override void Update(float frameTime) { base.Update(frameTime); @@ -146,22 +168,22 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem /// /// Calls the emergency shuttle for the station. /// - public void CallEmergencyShuttle(EntityUid? stationUid) + public void CallEmergencyShuttle(EntityUid stationUid, StationEmergencyShuttleComponent? stationShuttle = null) { - if (!TryComp(stationUid, out var stationShuttle) || + if (!Resolve(stationUid, ref stationShuttle) || !TryComp(stationShuttle.EmergencyShuttle, out var xform) || !TryComp(stationShuttle.EmergencyShuttle, out var shuttle)) { return; } - var targetGrid = _station.GetLargestGrid(Comp(stationUid.Value)); + var targetGrid = _station.GetLargestGrid(Comp(stationUid)); // UHH GOOD LUCK if (targetGrid == null) { - _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} unable to dock with station {ToPrettyString(stationUid.Value)}"); - _chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-good-luck"), playDefaultSound: false); + _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} unable to dock with station {ToPrettyString(stationUid)}"); + _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-good-luck"), playDefaultSound: false); // TODO: Need filter extensions or something don't blame me. _audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true); return; @@ -174,10 +196,10 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem if (TryComp(targetGrid.Value, out var targetXform)) { var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); - _chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false); + _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-docked", ("time", $"{_consoleAccumulator:0}"), ("direction", angle.GetDir())), playDefaultSound: false); } - _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} docked with stations"); + _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} docked with stations"); // TODO: Need filter extensions or something don't blame me. _audio.PlayGlobal("/Audio/Announcements/shuttle_dock.ogg", Filter.Broadcast(), true); } @@ -186,35 +208,33 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem if (TryComp(targetGrid.Value, out var targetXform)) { var angle = _dock.GetAngle(stationShuttle.EmergencyShuttle.Value, xform, targetGrid.Value, targetXform, xformQuery); - _chatSystem.DispatchStationAnnouncement(stationUid.Value, Loc.GetString("emergency-shuttle-nearby", ("direction", angle.GetDir())), playDefaultSound: false); + _chatSystem.DispatchStationAnnouncement(stationUid, Loc.GetString("emergency-shuttle-nearby", ("direction", angle.GetDir())), playDefaultSound: false); } - _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid.Value)} unable to find a valid docking port for {ToPrettyString(stationUid.Value)}"); + _logger.Add(LogType.EmergencyShuttle, LogImpact.High, $"Emergency shuttle {ToPrettyString(stationUid)} unable to find a valid docking port for {ToPrettyString(stationUid)}"); // TODO: Need filter extensions or something don't blame me. _audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true); } } - private void OnStationStartup(EntityUid uid, StationEmergencyShuttleComponent component, ComponentStartup args) + private void OnCentcommInit(EntityUid uid, StationCentcommComponent component, ComponentInit args) { - AddEmergencyShuttle(component); + if (!_emergencyShuttleEnabled) + return; + + // Post mapinit? fancy + if (TryComp(component.Entity, out var xform)) + { + component.MapId = xform.MapID; + return; + } + + AddCentcomm(component); } - private void OnRoundStart(RoundStartingEvent ev) + private void OnStationStartup(EntityUid uid, StationEmergencyShuttleComponent component, ComponentStartup args) { - if (CentComMap != null) - _mapManager.DeleteMap(CentComMap.Value); - - // Just in case the grid isn't on the map. - DebugTools.Assert(Deleted(CentComGrid)); - if (CentComGrid != null) - Del(CentComGrid.Value); - - CentComGrid = null; - CentComMap = null; - - CleanupEmergencyConsole(); - SetupEmergencyShuttle(); + AddEmergencyShuttle(uid, component); } /// @@ -234,14 +254,11 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem _consoleAccumulator = _configManager.GetCVar(CCVars.EmergencyShuttleDockTime); EmergencyShuttleArrived = true; - if (CentComMap != null) - _mapManager.SetMapPaused(CentComMap.Value, false); - - var query = AllEntityQuery(); + var query = AllEntityQuery(); while (query.MoveNext(out var uid, out var comp)) { - CallEmergencyShuttle(uid); + CallEmergencyShuttle(uid, comp); } _commsConsole.UpdateCommsConsoleInterface(); @@ -249,80 +266,111 @@ public sealed partial class EmergencyShuttleSystem : EntitySystem private void SetupEmergencyShuttle() { - if (!_emergencyShuttleEnabled || CentComMap != null && _mapManager.MapExists(CentComMap.Value)) return; + if (!_emergencyShuttleEnabled) + return; - CentComMap = _mapManager.CreateMap(); - _mapManager.SetMapPaused(CentComMap.Value, true); + var centcommQuery = AllEntityQuery(); - // Load CentCom - var centComPath = _configManager.GetCVar(CCVars.CentcommMap); - - if (!string.IsNullOrEmpty(centComPath)) + while (centcommQuery.MoveNext(out var centcomm)) { - var centcomm = _map.LoadGrid(CentComMap.Value, "/Maps/centcomm.yml"); - CentComGrid = centcomm; - - if (CentComGrid != null) - _shuttle.AddFTLDestination(CentComGrid.Value, false); - } - else - { - _sawmill.Info("No CentCom map found, skipping setup."); + AddCentcomm(centcomm); } - foreach (var comp in EntityQuery(true)) + var query = AllEntityQuery(); + + while (query.MoveNext(out var uid, out var comp)) { - AddEmergencyShuttle(comp); + AddEmergencyShuttle(uid, comp); } } - private void AddEmergencyShuttle(StationEmergencyShuttleComponent component) + private void AddCentcomm(StationCentcommComponent component) + { + // Check for existing centcomms and just point to that + var query = AllEntityQuery(); + + while (query.MoveNext(out var uid, out var otherComp)) + { + if (otherComp == component) + continue; + + component.MapId = otherComp.MapId; + component.ShuttleIndex = otherComp.ShuttleIndex; + return; + } + + var mapId = _mapManager.CreateMap(); + component.MapId = mapId; + + if (!string.IsNullOrEmpty(component.Map.ToString())) + { + var ent = _map.LoadGrid(mapId, component.Map.ToString()); + + if (ent != null) + { + component.Entity = ent.Value; + _shuttle.AddFTLDestination(ent.Value, false); + } + } + else + { + _sawmill.Warning("No CentComm map found, skipping setup."); + } + } + + public HashSet GetCentcommMaps() + { + var query = AllEntityQuery(); + var maps = new HashSet(Count()); + + while (query.MoveNext(out var comp)) + { + maps.Add(comp.MapId); + } + + return maps; + } + + private void AddEmergencyShuttle(EntityUid uid, StationEmergencyShuttleComponent component) { if (!_emergencyShuttleEnabled - || CentComMap == null - || component.EmergencyShuttle != null) + || component.EmergencyShuttle != null || + !TryComp(uid, out var centcomm)) { return; } // Load escape shuttle var shuttlePath = component.EmergencyShuttlePath; - var shuttle = _map.LoadGrid(CentComMap.Value, shuttlePath.ToString(), new MapLoadOptions() + var shuttle = _map.LoadGrid(centcomm.MapId, shuttlePath.ToString(), new MapLoadOptions() { // Should be far enough... right? I'm too lazy to bounds check CentCom rn. - Offset = new Vector2(500f + _shuttleIndex, 0f) + Offset = new Vector2(500f + centcomm.ShuttleIndex, 0f) }); if (shuttle == null) { - _sawmill.Error($"Unable to spawn emergency shuttle {shuttlePath} for {ToPrettyString(component.Owner)}"); + _sawmill.Error($"Unable to spawn emergency shuttle {shuttlePath} for {ToPrettyString(uid)}"); return; } - _shuttleIndex += _mapManager.GetGrid(shuttle.Value).LocalAABB.Width + ShuttleSpawnBuffer; + centcomm.ShuttleIndex += _mapManager.GetGrid(shuttle.Value).LocalAABB.Width + ShuttleSpawnBuffer; + + // Update indices for all centcomm comps pointing to same map + var query = AllEntityQuery(); + + while (query.MoveNext(out var comp)) + { + if (comp == centcomm || comp.MapId != centcomm.MapId) + continue; + + comp.ShuttleIndex = comp.ShuttleIndex; + } + component.EmergencyShuttle = shuttle; EnsureComp(shuttle.Value); } - private void CleanupEmergencyShuttle() - { - // If we get cleaned up mid roundend just end it. - if (_launchedShuttles) - { - _roundEnd.EndRound(); - } - - _shuttleIndex = 0f; - - if (CentComMap == null || !_mapManager.MapExists(CentComMap.Value)) - { - CentComMap = null; - return; - } - - _mapManager.DeleteMap(CentComMap.Value); - } - private void OnEscapeUnpaused(EntityUid uid, EscapePodComponent component, ref EntityUnpausedEvent args) { if (component.LaunchTime == null) diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs index e60d3fdf28..7b4c33fef6 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.GridFill.cs @@ -1,4 +1,5 @@ using Content.Server.Shuttles.Components; +using Content.Server.Station.Components; using Content.Shared.CCVar; namespace Content.Server.Shuttles.Systems; @@ -39,6 +40,12 @@ public sealed partial class ShuttleSystem if (config != null) { FTLDock(config, shuttleXform); + + if (TryComp(xform.GridUid, out var stationMember)) + { + _station.AddGridToStation(stationMember.Station, ent[0]); + } + valid = true; } } diff --git a/Content.Server/Shuttles/Systems/ShuttleSystem.cs b/Content.Server/Shuttles/Systems/ShuttleSystem.cs index 3238e6aa91..30f3a829c5 100644 --- a/Content.Server/Shuttles/Systems/ShuttleSystem.cs +++ b/Content.Server/Shuttles/Systems/ShuttleSystem.cs @@ -1,6 +1,7 @@ using Content.Server.Body.Systems; using Content.Server.Doors.Systems; using Content.Server.Shuttles.Components; +using Content.Server.Station.Systems; using Content.Server.Stunnable; using Content.Shared.GameTicking; using Content.Shared.Mobs.Systems; @@ -33,6 +34,7 @@ public sealed partial class ShuttleSystem : SharedShuttleSystem [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly ShuttleConsoleSystem _console = default!; + [Dependency] private readonly StationSystem _station = default!; [Dependency] private readonly StunSystem _stuns = default!; [Dependency] private readonly ThrusterSystem _thruster = default!; [Dependency] private readonly UserInterfaceSystem _uiSystem = default!; diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 7d40e21f0d..eef0e16de9 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1171,12 +1171,6 @@ namespace Content.Shared.CCVar public static readonly CVarDef EmergencyShuttleAutoCallExtensionTime = CVarDef.Create("shuttle.auto_call_extension_time", 45, CVar.SERVERONLY); - /// - /// The map to load for CentCom for the emergency shuttle to dock to. - /// - public static readonly CVarDef CentcommMap = - CVarDef.Create("shuttle.centcomm_map", "/Maps/centcomm.yml", CVar.SERVERONLY); - /* * Crew Manifests */ diff --git a/Resources/Prototypes/Entities/Stations/base.yml b/Resources/Prototypes/Entities/Stations/base.yml index 0a31b13394..da5dc6d301 100644 --- a/Resources/Prototypes/Entities/Stations/base.yml +++ b/Resources/Prototypes/Entities/Stations/base.yml @@ -31,6 +31,12 @@ components: - type: StationArrivals +- type: entity + id: BaseStationCentcomm + abstract: true + components: + - type: StationCentcomm + - type: entity id: BaseStationEvacuation abstract: true diff --git a/Resources/Prototypes/Entities/Stations/nanotrasen.yml b/Resources/Prototypes/Entities/Stations/nanotrasen.yml index 5d46bff29a..6e1e339d17 100644 --- a/Resources/Prototypes/Entities/Stations/nanotrasen.yml +++ b/Resources/Prototypes/Entities/Stations/nanotrasen.yml @@ -14,6 +14,7 @@ - BaseStationJobsSpawning - BaseStationRecords - BaseStationArrivals + - BaseStationCentcomm - BaseStationEvacuation - BaseStationAlertLevels - BaseStationExpeditions