]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
dragon refactor, objectives and use GenericAntag (#20201)
authordeltanedas <39013340+deltanedas@users.noreply.github.com>
Sat, 30 Sep 2023 20:18:01 +0000 (21:18 +0100)
committerGitHub <noreply@github.com>
Sat, 30 Sep 2023 20:18:01 +0000 (13:18 -0700)
Co-authored-by: deltanedas <@deltanedas:kde.org>
18 files changed:
Content.Server/Dragon/DragonRiftSystem.cs [new file with mode: 0644]
Content.Server/Dragon/DragonSystem.Rule.cs [deleted file]
Content.Server/Dragon/DragonSystem.cs
Content.Server/Objectives/Components/CarpRiftsConditionComponent.cs [new file with mode: 0644]
Content.Server/Objectives/Systems/CarpRiftsConditionSystem.cs [new file with mode: 0644]
Content.Server/Roles/DragonRoleComponent.cs [new file with mode: 0644]
Content.Server/Roles/RoleSystem.cs
Resources/Locale/en-US/actions/actions/dragon.ftl
Resources/Locale/en-US/dragon/dragon.ftl [new file with mode: 0644]
Resources/Locale/en-US/dragon/rifts.ftl [new file with mode: 0644]
Resources/Locale/en-US/objectives/conditions/carp-rifts.ftl [new file with mode: 0644]
Resources/Prototypes/Entities/Mobs/Player/dragon.yml
Resources/Prototypes/Entities/Structures/Specific/dragon.yml
Resources/Prototypes/GameRules/events.yml
Resources/Prototypes/GameRules/midround.yml
Resources/Prototypes/Objectives/dragon.yml [new file with mode: 0644]
Resources/Textures/Structures/Specific/carp_rift.rsi/icon_blue.png [new file with mode: 0644]
Resources/Textures/Structures/Specific/carp_rift.rsi/meta.json

diff --git a/Content.Server/Dragon/DragonRiftSystem.cs b/Content.Server/Dragon/DragonRiftSystem.cs
new file mode 100644 (file)
index 0000000..52137f2
--- /dev/null
@@ -0,0 +1,115 @@
+using Content.Server.Chat.Systems;
+using Content.Server.NPC;
+using Content.Server.NPC.Systems;
+using Content.Server.Pinpointer;
+using Content.Shared.Damage;
+using Content.Shared.Dragon;
+using Content.Shared.Examine;
+using Content.Shared.Sprite;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Map;
+using Robust.Shared.Player;
+using Robust.Shared.Serialization.Manager;
+using System.Numerics;
+
+namespace Content.Server.Dragon;
+
+/// <summary>
+/// Handles events for rift entities and rift updating.
+/// </summary>
+public sealed class DragonRiftSystem : EntitySystem
+{
+    [Dependency] private readonly ChatSystem _chat = default!;
+    [Dependency] private readonly DragonSystem _dragon = default!;
+    [Dependency] private readonly ISerializationManager _serManager = default!;
+    [Dependency] private readonly NavMapSystem _navMap = default!;
+    [Dependency] private readonly NPCSystem _npc = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<DragonRiftComponent, ExaminedEvent>(OnExamined);
+        SubscribeLocalEvent<DragonRiftComponent, AnchorStateChangedEvent>(OnAnchorChange);
+        SubscribeLocalEvent<DragonRiftComponent, ComponentShutdown>(OnShutdown);
+    }
+
+    public override void Update(float frameTime)
+    {
+        base.Update(frameTime);
+
+        var query = EntityQueryEnumerator<DragonRiftComponent, TransformComponent>();
+        while (query.MoveNext(out var uid, out var comp, out var xform))
+        {
+            if (comp.State != DragonRiftState.Finished && comp.Accumulator >= comp.MaxAccumulator)
+            {
+                // TODO: When we get autocall you can buff if the rift finishes / 3 rifts are up
+                // for now they just keep 3 rifts up.
+
+                if (comp.Dragon != null)
+                    _dragon.RiftCharged(comp.Dragon.Value);
+
+                comp.Accumulator = comp.MaxAccumulator;
+                RemComp<DamageableComponent>(uid);
+                comp.State = DragonRiftState.Finished;
+                Dirty(uid, comp);
+            }
+            else if (comp.State != DragonRiftState.Finished)
+            {
+                comp.Accumulator += frameTime;
+            }
+
+            comp.SpawnAccumulator += frameTime;
+
+            if (comp.State < DragonRiftState.AlmostFinished && comp.Accumulator > comp.MaxAccumulator / 2f)
+            {
+                comp.State = DragonRiftState.AlmostFinished;
+                Dirty(comp);
+
+                var location = xform.LocalPosition;
+                _chat.DispatchGlobalAnnouncement(Loc.GetString("carp-rift-warning", ("location", location)), playSound: false, colorOverride: Color.Red);
+                _audio.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
+                _navMap.SetBeaconEnabled(uid, true);
+            }
+
+            if (comp.SpawnAccumulator > comp.SpawnCooldown)
+            {
+                comp.SpawnAccumulator -= comp.SpawnCooldown;
+                var ent = Spawn(comp.SpawnPrototype, xform.Coordinates);
+
+                // Update their look to match the leader.
+                if (TryComp<RandomSpriteComponent>(comp.Dragon, out var randomSprite))
+                {
+                    var spawnedSprite = EnsureComp<RandomSpriteComponent>(ent);
+                    _serManager.CopyTo(randomSprite, ref spawnedSprite, notNullableOverride: true);
+                    Dirty(ent, spawnedSprite);
+                }
+
+                if (comp.Dragon != null)
+                    _npc.SetBlackboard(ent, NPCBlackboard.FollowTarget, new EntityCoordinates(comp.Dragon.Value, Vector2.Zero));
+            }
+        }
+    }
+
+    private void OnExamined(EntityUid uid, DragonRiftComponent component, ExaminedEvent args)
+    {
+        args.PushMarkup(Loc.GetString("carp-rift-examine", ("percentage", MathF.Round(component.Accumulator / component.MaxAccumulator * 100))));
+    }
+
+    private void OnAnchorChange(EntityUid uid, DragonRiftComponent component, ref AnchorStateChangedEvent args)
+    {
+        if (!args.Anchored && component.State == DragonRiftState.Charging)
+        {
+            QueueDel(uid);
+        }
+    }
+
+    private void OnShutdown(EntityUid uid, DragonRiftComponent comp, ComponentShutdown args)
+    {
+        if (!TryComp<DragonComponent>(comp.Dragon, out var dragon) || dragon.Weakened)
+            return;
+
+        _dragon.RiftDestroyed(comp.Dragon.Value, dragon);
+    }
+}
diff --git a/Content.Server/Dragon/DragonSystem.Rule.cs b/Content.Server/Dragon/DragonSystem.Rule.cs
deleted file mode 100644 (file)
index 4a4f44e..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-using System.Linq;
-using Content.Server.GameTicking;
-using Content.Server.GameTicking.Rules.Components;
-using Content.Server.Station.Components;
-using Content.Shared.Dragon;
-using Robust.Server.GameObjects;
-using Robust.Shared.Map.Components;
-using Robust.Shared.Random;
-
-namespace Content.Server.Dragon;
-
-public sealed partial class DragonSystem
-{
-    private int RiftsMet(DragonComponent component)
-    {
-        var finished = 0;
-
-        foreach (var rift in component.Rifts)
-        {
-            if (!TryComp<DragonRiftComponent>(rift, out var drift) ||
-                drift.State != DragonRiftState.Finished)
-                continue;
-
-            finished++;
-        }
-
-        return finished;
-    }
-
-    private void OnRiftRoundEnd(RoundEndTextAppendEvent args)
-    {
-        if (EntityQuery<DragonComponent>().Count() == 0)
-            return;
-
-        args.AddLine(Loc.GetString("dragon-round-end-summary"));
-
-        var query = EntityQueryEnumerator<DragonComponent>();
-        while (query.MoveNext(out var uid, out var dragon))
-        {
-            var met = RiftsMet(dragon);
-
-            if (TryComp<ActorComponent>(uid, out var actor))
-            {
-                args.AddLine(Loc.GetString("dragon-round-end-dragon-player", ("name", uid), ("count", met), ("player", actor.PlayerSession)));
-            }
-            else
-            {
-                args.AddLine(Loc.GetString("dragon-round-end-dragon", ("name", uid), ("count", met)));
-            }
-        }
-    }
-}
index 40039be50e2373de282d2432843545f5d77b790f..ed17ba8bdc2e1d29f062e78c52643f14c5af23e7 100644 (file)
@@ -1,35 +1,33 @@
-using System.Numerics;
-using Content.Server.Chat.Systems;
-using Content.Server.GameTicking;
-using Content.Server.NPC;
-using Content.Server.NPC.Systems;
+using Content.Server.GenericAntag;
+using Content.Server.Objectives.Components;
+using Content.Server.Objectives.Systems;
 using Content.Server.Popups;
+using Content.Server.Roles;
 using Content.Shared.Actions;
-using Content.Shared.Damage;
 using Content.Shared.Dragon;
-using Content.Shared.Examine;
 using Content.Shared.Maps;
+using Content.Shared.Mind;
+using Content.Shared.Mind.Components;
 using Content.Shared.Mobs;
 using Content.Shared.Movement.Systems;
-using Content.Shared.Sprite;
 using Robust.Shared.GameStates;
 using Robust.Shared.Map;
 using Robust.Shared.Player;
-using Robust.Shared.Serialization.Manager;
 
 namespace Content.Server.Dragon;
 
 public sealed partial class DragonSystem : EntitySystem
 {
+    [Dependency] private readonly CarpRiftsConditionSystem _carpRifts = default!;
     [Dependency] private readonly IMapManager _mapManager = default!;
-    [Dependency] private readonly ISerializationManager _serManager = default!;
     [Dependency] private readonly ITileDefinitionManager _tileDef = default!;
-    [Dependency] private readonly ChatSystem _chat = default!;
-    [Dependency] private readonly SharedActionsSystem _actionsSystem = default!;
-    [Dependency] private readonly PopupSystem _popupSystem = default!;
     [Dependency] private readonly MovementSpeedModifierSystem _movement = default!;
-    [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
-    [Dependency] private readonly NPCSystem _npc = default!;
+    [Dependency] private readonly PopupSystem _popup = default!;
+    [Dependency] private readonly RoleSystem _role = default!;
+    [Dependency] private readonly SharedActionsSystem _actions = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+    private EntityQuery<CarpRiftsConditionComponent> _objQuery;
 
     /// <summary>
     /// Minimum distance between 2 rifts allowed.
@@ -47,26 +45,22 @@ public sealed partial class DragonSystem : EntitySystem
     {
         base.Initialize();
 
+        _objQuery = GetEntityQuery<CarpRiftsConditionComponent>();
+
         SubscribeLocalEvent<DragonComponent, MapInitEvent>(OnInit);
         SubscribeLocalEvent<DragonComponent, ComponentShutdown>(OnShutdown);
-        SubscribeLocalEvent<DragonComponent, DragonSpawnRiftActionEvent>(OnDragonRift);
+        SubscribeLocalEvent<DragonComponent, DragonSpawnRiftActionEvent>(OnSpawnRift);
         SubscribeLocalEvent<DragonComponent, RefreshMovementSpeedModifiersEvent>(OnDragonMove);
-
         SubscribeLocalEvent<DragonComponent, MobStateChangedEvent>(OnMobStateChanged);
-
-        SubscribeLocalEvent<DragonRiftComponent, ComponentShutdown>(OnRiftShutdown);
-        SubscribeLocalEvent<DragonRiftComponent, ComponentGetState>(OnRiftGetState);
-        SubscribeLocalEvent<DragonRiftComponent, AnchorStateChangedEvent>(OnAnchorChange);
-        SubscribeLocalEvent<DragonRiftComponent, ExaminedEvent>(OnRiftExamined);
-
-        SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRiftRoundEnd);
+        SubscribeLocalEvent<DragonComponent, GenericAntagCreatedEvent>(OnCreated);
     }
 
     public override void Update(float frameTime)
     {
         base.Update(frameTime);
 
-        foreach (var comp in EntityQuery<DragonComponent>())
+        var query = EntityQueryEnumerator<DragonComponent>();
+        while (query.MoveNext(out var uid, out var comp))
         {
             if (comp.WeakenedAccumulator > 0f)
             {
@@ -76,15 +70,13 @@ public sealed partial class DragonSystem : EntitySystem
                 if (comp.WeakenedAccumulator < 0f)
                 {
                     comp.WeakenedAccumulator = 0f;
-                    _movement.RefreshMovementSpeedModifiers(comp.Owner);
+                    _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)
@@ -103,125 +95,40 @@ public sealed partial class DragonSystem : EntitySystem
             // Delete it, naughty dragon!
             if (comp.RiftAccumulator >= comp.RiftMaxAccumulator)
             {
-                Roar(comp);
-                QueueDel(comp.Owner);
-            }
-        }
-
-        foreach (var comp in EntityQuery<DragonRiftComponent>())
-        {
-            if (comp.State != DragonRiftState.Finished && comp.Accumulator >= comp.MaxAccumulator)
-            {
-                // TODO: When we get autocall you can buff if the rift finishes / 3 rifts are up
-                // for now they just keep 3 rifts up.
-
-                comp.Accumulator = comp.MaxAccumulator;
-                RemComp<DamageableComponent>(comp.Owner);
-                comp.State = DragonRiftState.Finished;
-                Dirty(comp);
-            }
-            else if (comp.State != DragonRiftState.Finished)
-            {
-                comp.Accumulator += frameTime;
-            }
-
-            comp.SpawnAccumulator += frameTime;
-
-            if (comp.State < DragonRiftState.AlmostFinished && comp.Accumulator > comp.MaxAccumulator / 2f)
-            {
-                comp.State = DragonRiftState.AlmostFinished;
-                Dirty(comp);
-                var location = Transform(comp.Owner).LocalPosition;
-
-                _chat.DispatchGlobalAnnouncement(Loc.GetString("carp-rift-warning", ("location", location)), playSound: false, colorOverride: Color.Red);
-                _audioSystem.PlayGlobal("/Audio/Misc/notice1.ogg", Filter.Broadcast(), true);
-            }
-
-            if (comp.SpawnAccumulator > comp.SpawnCooldown)
-            {
-                comp.SpawnAccumulator -= comp.SpawnCooldown;
-                var ent = Spawn(comp.SpawnPrototype, Transform(comp.Owner).Coordinates);
-
-                // Update their look to match the leader.
-                if (TryComp<RandomSpriteComponent>(comp.Dragon, out var randomSprite))
-                {
-                    var spawnedSprite = EnsureComp<RandomSpriteComponent>(ent);
-                    _serManager.CopyTo(randomSprite, ref spawnedSprite, notNullableOverride: true);
-                    Dirty(ent, spawnedSprite);
-                }
-
-                if (comp.Dragon != null)
-                    _npc.SetBlackboard(ent, NPCBlackboard.FollowTarget, new EntityCoordinates(comp.Dragon.Value, Vector2.Zero));
+                Roar(uid, comp);
+                QueueDel(uid);
             }
         }
     }
 
-    #region Rift
-
-    private void OnRiftExamined(EntityUid uid, DragonRiftComponent component, ExaminedEvent args)
+    private void OnInit(EntityUid uid, DragonComponent component, MapInitEvent args)
     {
-        args.PushMarkup(Loc.GetString("carp-rift-examine", ("percentage", MathF.Round(component.Accumulator / component.MaxAccumulator * 100))));
+        Roar(uid, component);
+        _actions.AddAction(uid, ref component.SpawnRiftActionEntity, component.SpawnRiftAction);
     }
 
-    private void OnAnchorChange(EntityUid uid, DragonRiftComponent component, ref AnchorStateChangedEvent args)
-    {
-        if (!args.Anchored && component.State == DragonRiftState.Charging)
-        {
-            QueueDel(uid);
-        }
-    }
-
-    private void OnRiftShutdown(EntityUid uid, DragonRiftComponent component, ComponentShutdown args)
-    {
-        if (TryComp<DragonComponent>(component.Dragon, out var dragon) && !dragon.Weakened)
-        {
-            foreach (var rift in dragon.Rifts)
-            {
-                QueueDel(rift);
-            }
-
-            dragon.Rifts.Clear();
-
-            // We can't predict the rift being destroyed anyway so no point adding weakened to shared.
-            dragon.WeakenedAccumulator = dragon.WeakenedDuration;
-            _movement.RefreshMovementSpeedModifiers(component.Dragon.Value);
-            _popupSystem.PopupEntity(Loc.GetString("carp-rift-destroyed"), component.Dragon.Value, component.Dragon.Value);
-        }
-    }
-
-    private void OnRiftGetState(EntityUid uid, DragonRiftComponent component, ref ComponentGetState args)
+    private void OnShutdown(EntityUid uid, DragonComponent component, ComponentShutdown args)
     {
-        args.State = new DragonRiftComponentState()
-        {
-            State = component.State
-        };
+        DeleteRifts(uid, false, component);
     }
 
-    private void OnDragonMove(EntityUid uid, DragonComponent component, RefreshMovementSpeedModifiersEvent args)
+    private void OnSpawnRift(EntityUid uid, DragonComponent component, DragonSpawnRiftActionEvent args)
     {
         if (component.Weakened)
         {
-            args.ModifySpeed(0.5f, 0.5f);
-        }
-    }
-
-    private void OnDragonRift(EntityUid uid, DragonComponent component, DragonSpawnRiftActionEvent args)
-    {
-        if (component.Weakened)
-        {
-            _popupSystem.PopupEntity(Loc.GetString("carp-rift-weakened"), uid, uid);
+            _popup.PopupEntity(Loc.GetString("carp-rift-weakened"), uid, uid);
             return;
         }
 
         if (component.Rifts.Count >= RiftsAllowed)
         {
-            _popupSystem.PopupEntity(Loc.GetString("carp-rift-max"), uid, uid);
+            _popup.PopupEntity(Loc.GetString("carp-rift-max"), uid, uid);
             return;
         }
 
         if (component.Rifts.Count > 0 && TryComp<DragonRiftComponent>(component.Rifts[^1], out var rift) && rift.State != DragonRiftState.Finished)
         {
-            _popupSystem.PopupEntity(Loc.GetString("carp-rift-duplicate"), uid, uid);
+            _popup.PopupEntity(Loc.GetString("carp-rift-duplicate"), uid, uid);
             return;
         }
 
@@ -230,72 +137,144 @@ public sealed partial class DragonSystem : EntitySystem
         // Have to be on a grid fam
         if (!_mapManager.TryGetGrid(xform.GridUid, out var grid))
         {
-            _popupSystem.PopupEntity(Loc.GetString("carp-rift-anchor"), uid, uid);
+            _popup.PopupEntity(Loc.GetString("carp-rift-anchor"), uid, uid);
             return;
         }
 
+        // cant stack rifts near eachother
         foreach (var (_, riftXform) in EntityQuery<DragonRiftComponent, TransformComponent>(true))
         {
             if (riftXform.Coordinates.InRange(EntityManager, xform.Coordinates, RiftRange))
             {
-                _popupSystem.PopupEntity(Loc.GetString("carp-rift-proximity", ("proximity", RiftRange)), uid, uid);
+                _popup.PopupEntity(Loc.GetString("carp-rift-proximity", ("proximity", RiftRange)), uid, uid);
                 return;
             }
         }
 
+        // cant put a rift on solars
         foreach (var tile in grid.GetTilesIntersecting(new Circle(xform.WorldPosition, RiftTileRadius), false))
         {
             if (!tile.IsSpace(_tileDef))
                 continue;
 
-            _popupSystem.PopupEntity(Loc.GetString("carp-rift-space-proximity", ("proximity", RiftTileRadius)), uid, uid);
+            _popup.PopupEntity(Loc.GetString("carp-rift-space-proximity", ("proximity", RiftTileRadius)), uid, uid);
             return;
         }
 
         var carpUid = Spawn(component.RiftPrototype, xform.MapPosition);
         component.Rifts.Add(carpUid);
         Comp<DragonRiftComponent>(carpUid).Dragon = uid;
-        _audioSystem.PlayPvs("/Audio/Weapons/Guns/Gunshots/rocket_launcher.ogg", carpUid);
     }
 
-    #endregion
-
-    private void OnShutdown(EntityUid uid, DragonComponent component, ComponentShutdown args)
+    // TODO: just make this a move speed modifier component???
+    private void OnDragonMove(EntityUid uid, DragonComponent component, RefreshMovementSpeedModifiersEvent args)
     {
-        foreach (var rift in component.Rifts)
+        if (component.Weakened)
         {
-            QueueDel(rift);
+            args.ModifySpeed(0.5f, 0.5f);
         }
     }
 
     private void OnMobStateChanged(EntityUid uid, DragonComponent component, MobStateChangedEvent args)
     {
-        //Empties the stomach upon death
-        //TODO: Do this when the dragon gets butchered instead
-        if (args.NewMobState == MobState.Dead)
+        // 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);
+    }
+
+    private void OnCreated(EntityUid uid, DragonComponent comp, ref GenericAntagCreatedEvent args)
+    {
+        var mindId = args.MindId;
+        var mind = args.Mind;
+
+        _role.MindAddRole(mindId, new DragonRoleComponent(), mind);
+        _role.MindAddRole(mindId, new RoleBriefingComponent()
+        {
+            Briefing = Loc.GetString("dragon-role-briefing")
+        }, mind);
+    }
+
+    private void Roar(EntityUid uid, DragonComponent comp)
+    {
+        if (comp.SoundRoar != null)
+            _audio.Play(comp.SoundRoar, Filter.Pvs(uid, 4f, EntityManager), uid, true);
+    }
+
+    /// <summary>
+    /// Delete all rifts this dragon made.
+    /// </summary>
+    /// <param name="uid">Entity id of the dragon</param>
+    /// <param name="resetRole">If true, the role's rift count will be reset too</param>
+    /// <param name="comp">The dragon component</param>
+    public void DeleteRifts(EntityUid uid, bool resetRole, DragonComponent? comp = null)
+    {
+        if (!Resolve(uid, ref comp))
+            return;
+
+        foreach (var rift in comp.Rifts)
         {
-            if (component.SoundDeath != null)
-                _audioSystem.PlayPvs(component.SoundDeath, uid, component.SoundDeath.Params);
+            QueueDel(rift);
+        }
+
+        comp.Rifts.Clear();
 
-            foreach (var rift in component.Rifts)
+        // stop here if not trying to reset the objective's rift count
+        if (!resetRole || !TryComp<MindContainerComponent>(uid, out var mindContainer) || !mindContainer.HasMind)
+            return;
+
+        var mind = Comp<MindComponent>(mindContainer.Mind.Value);
+        foreach (var objId in mind.AllObjectives)
+        {
+            if (_objQuery.TryGetComponent(objId, out var obj))
             {
-                QueueDel(rift);
+                _carpRifts.ResetRifts(objId, obj);
+                break;
             }
-
-            component.Rifts.Clear();
         }
     }
 
-    private void Roar(DragonComponent component)
+    /// <summary>
+    /// Increment the dragon role's charged rift count.
+    /// </summary>
+    public void RiftCharged(EntityUid uid, DragonComponent? comp = null)
     {
-        if (component.SoundRoar != null)
-            _audioSystem.Play(component.SoundRoar, Filter.Pvs(component.Owner, 4f, EntityManager), component.Owner, true, component.SoundRoar.Params);
+        if (!Resolve(uid, ref comp))
+            return;
+
+        if (!TryComp<MindContainerComponent>(uid, out var mindContainer) || !mindContainer.HasMind)
+            return;
+
+        var mind = Comp<MindComponent>(mindContainer.Mind.Value);
+        foreach (var objId in mind.AllObjectives)
+        {
+            if (_objQuery.TryGetComponent(objId, out var obj))
+            {
+                _carpRifts.RiftCharged(objId, obj);
+                break;
+            }
+        }
     }
 
-    private void OnInit(EntityUid uid, DragonComponent component, MapInitEvent args)
+    /// <summary>
+    /// Do everything that needs to happen when a rift gets destroyed by the crew.
+    /// </summary>
+    public void RiftDestroyed(EntityUid uid, DragonComponent? comp = null)
     {
-        Roar(component);
-        _actionsSystem.AddAction(uid, ref component.SpawnRiftActionEntity, component.SpawnRiftAction);
+        if (!Resolve(uid, ref comp))
+            return;
+
+        // do reset the rift count since crew destroyed the rift, not deleted by the dragon dying.
+        DeleteRifts(uid, true, comp);
+
+        // 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);
     }
 }
-
diff --git a/Content.Server/Objectives/Components/CarpRiftsConditionComponent.cs b/Content.Server/Objectives/Components/CarpRiftsConditionComponent.cs
new file mode 100644 (file)
index 0000000..8e43154
--- /dev/null
@@ -0,0 +1,17 @@
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that the dragon open and fully charge a certain number of rifts.
+/// Depends on <see cref="NumberObjective"/> to function.
+/// </summary>
+[RegisterComponent, Access(typeof(CarpRiftsConditionSystem))]
+public sealed partial class CarpRiftsConditionComponent : Component
+{
+    /// <summary>
+    /// The number of rifts currently charged.
+    /// </summary>
+    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    public int RiftsCharged;
+}
diff --git a/Content.Server/Objectives/Systems/CarpRiftsConditionSystem.cs b/Content.Server/Objectives/Systems/CarpRiftsConditionSystem.cs
new file mode 100644 (file)
index 0000000..efb2d22
--- /dev/null
@@ -0,0 +1,56 @@
+using Content.Server.Objectives.Components;
+using Content.Server.Roles;
+using Content.Shared.Objectives.Components;
+
+namespace Content.Server.Objectives.Systems;
+
+public sealed class CarpRiftsConditionSystem : EntitySystem
+{
+    [Dependency] private readonly NumberObjectiveSystem _number = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<CarpRiftsConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
+    }
+
+    private void OnGetProgress(EntityUid uid, CarpRiftsConditionComponent comp, ref ObjectiveGetProgressEvent args)
+    {
+        args.Progress = GetProgress(comp, _number.GetTarget(uid));
+    }
+
+    private float GetProgress(CarpRiftsConditionComponent comp, int target)
+    {
+        // prevent divide-by-zero
+        if (target == 0)
+            return 1f;
+
+        if (comp.RiftsCharged >= target)
+            return 1f;
+
+        return (float) comp.RiftsCharged / (float) target;
+    }
+
+    /// <summary>
+    /// Increments RiftsCharged, called after a rift fully charges.
+    /// </summary>
+    public void RiftCharged(EntityUid uid, CarpRiftsConditionComponent? comp = null)
+    {
+        if (!Resolve(uid, ref comp))
+            return;
+
+        comp.RiftsCharged++;
+    }
+
+    /// <summary>
+    /// Resets RiftsCharged to 0, called after rifts get destroyed.
+    /// </summary>
+    public void ResetRifts(EntityUid uid, CarpRiftsConditionComponent? comp = null)
+    {
+        if (!Resolve(uid, ref comp))
+            return;
+
+        comp.RiftsCharged = 0;
+    }
+}
diff --git a/Content.Server/Roles/DragonRoleComponent.cs b/Content.Server/Roles/DragonRoleComponent.cs
new file mode 100644 (file)
index 0000000..77fd97a
--- /dev/null
@@ -0,0 +1,12 @@
+using Content.Server.Dragon;
+using Content.Shared.Roles;
+
+namespace Content.Server.Roles;
+
+/// <summary>
+/// Role used to keep track of space dragons for antag purposes.
+/// </summary>
+[RegisterComponent, Access(typeof(DragonSystem))]
+public sealed partial class DragonRoleComponent : AntagonistRoleComponent
+{
+}
index 4ca6c0ac8019475e0bdf6884cd3b2b317ac9a276..b04f051c6e8bcf4706698fd6bb24162029672a82 100644 (file)
@@ -9,6 +9,7 @@ public sealed class RoleSystem : SharedRoleSystem
         // TODO make roles entities
         base.Initialize();
 
+        SubscribeAntagEvents<DragonRoleComponent>();
         SubscribeAntagEvents<InitialInfectedRoleComponent>();
         SubscribeAntagEvents<NinjaRoleComponent>();
         SubscribeAntagEvents<NukeopsRoleComponent>();
index a9b887142d689c860d65fc3d2ad2eeb21bee1998..b2b9b6d3af9bd7c0ddafcf70ceed6e6988fde549 100644 (file)
@@ -3,19 +3,3 @@ devour-action-popup-message-fail-target-not-valid = That doesn't look particular
 devour-action-popup-message-fail-target-alive = You can't consume creatures that are alive!
 
 dragon-spawn-action-popup-message-fail-no-eggs = You don't have the stamina to do that!
-
-# Rifts
-carp-rift-warning = A rift is causing an unnaturally large energy flux at {$location}. Stop it at all costs!
-carp-rift-duplicate = Cannot have 2 charging rifts at the same time!
-carp-rift-examine = It is [color=yellow]{$percentage}%[/color] charged!
-carp-rift-max = You have reached your maximum amount of rifts
-carp-rift-anchor = Rifts require a stable surface to spawn.
-carp-rift-proximity = Too close to a nearby rift! Need to be at least {$proximity}m away.
-carp-rift-space-proximity = Too close to space! Need to be at least {$proximity}m away.
-carp-rift-weakened = You are unable to summon more rifts in your weakened state.
-carp-rift-destroyed = A rift has been destroyed! You are now weakened temporarily.
-
-# Round end
-dragon-round-end-summary = The dragons were:
-dragon-round-end-dragon = {$name} with {$count} rifts
-dragon-round-end-dragon-player = {$name} ({$player}) with {$count} rifts
diff --git a/Resources/Locale/en-US/dragon/dragon.ftl b/Resources/Locale/en-US/dragon/dragon.ftl
new file mode 100644 (file)
index 0000000..11e8a58
--- /dev/null
@@ -0,0 +1,5 @@
+dragon-round-end-agent-name = dragon
+
+objective-issuer-dragon = [color=#7567b6]Space Dragon[/color]
+
+dragon-role-briefing = Summon 3 carp rifts and take over this quadrant!
diff --git a/Resources/Locale/en-US/dragon/rifts.ftl b/Resources/Locale/en-US/dragon/rifts.ftl
new file mode 100644 (file)
index 0000000..5ad061a
--- /dev/null
@@ -0,0 +1,9 @@
+carp-rift-warning = A rift is causing an unnaturally large energy flux at {$location}. Stop it at all costs!
+carp-rift-duplicate = Cannot have 2 charging rifts at the same time!
+carp-rift-examine = It is [color=yellow]{$percentage}%[/color] charged!
+carp-rift-max = You have reached your maximum amount of rifts
+carp-rift-anchor = Rifts require a stable surface to spawn.
+carp-rift-proximity = Too close to a nearby rift! Need to be at least {$proximity}m away.
+carp-rift-space-proximity = Too close to space! Need to be at least {$proximity}m away.
+carp-rift-weakened = You are unable to summon more rifts in your weakened state.
+carp-rift-destroyed = A rift has been destroyed! You are now weakened temporarily.
diff --git a/Resources/Locale/en-US/objectives/conditions/carp-rifts.ftl b/Resources/Locale/en-US/objectives/conditions/carp-rifts.ftl
new file mode 100644 (file)
index 0000000..6010473
--- /dev/null
@@ -0,0 +1,2 @@
+objective-carp-rifts-title = Open {$count} carp rifts
+objective-carp-rifts-description = Use the rift action to open {$count} rifts and ensure they do not get destroyed. If you don't open a rift after 5 minutes, you get killed.
index 398d101021739a002141d9a3ecfbb8dbfea1089d..368d1846925e8dc7e6ca66d2c2311c82d3147bb4 100644 (file)
     spawnsLeft: 2
     spawnsProto: MobCarpDragon
     spawnRiftAction: ActionSpawnRift
+  - type: GenericAntag
+    rule: Dragon
   - type: GuideHelp
     guides:
     - MinorAntagonists
index 9c9b09c7c711bf63e4945139e441fe82cc5d9973..8a7f13d1a45de250043de34a095d76728a3fed7d 100644 (file)
@@ -5,35 +5,43 @@
   placement:
     mode: SnapgridCenter
   components:
-    - type: DragonRift
-    - type: Transform
-      anchored: true
-    - type: Physics
-      bodyType: Static
-      canCollide: false
-    - type: Fixtures
-    - type: Sprite
-      layers:
-        - sprite: Structures/Specific/carp_rift.rsi
-          state: icon
-          color: "#569fff"
-          shader: unshaded
-    - type: InteractionOutline
-    - type: Clickable
-    - type: PointLight
-      enabled: true
-      color: "#366db5"
-      radius: 10.0
-      energy: 5.0
-      netsync: false
-    - type: Damageable
-      damageContainer: Inorganic
-      damageModifierSet: Metallic
-    - type: Destructible
-      thresholds:
-        - trigger:
-            !type:DamageTrigger
-            damage: 300
-          behaviors:
-            - !type:DoActsBehavior
-              acts: [ "Destruction" ]
+  - type: DragonRift
+  - type: Transform
+    anchored: true
+  - type: Physics
+    bodyType: Static
+    canCollide: false
+  - type: Fixtures
+  - type: Sprite
+    layers:
+    - sprite: Structures/Specific/carp_rift.rsi
+      state: icon
+      color: "#569fff"
+      shader: unshaded
+  - type: InteractionOutline
+  - type: Clickable
+  - type: PointLight
+    enabled: true
+    color: "#366db5"
+    radius: 10.0
+    energy: 5.0
+    netsync: false
+  - type: NavMapBeacon
+    color: "#ff0000"
+    text: carp rift
+    # only show after making the announcement at 50%
+    enabled: false
+  - type: Damageable
+    damageContainer: Inorganic
+    damageModifierSet: Metallic
+  - type: Destructible
+    thresholds:
+    - trigger:
+        !type:DamageTrigger
+        damage: 300
+      behaviors:
+      - !type:DoActsBehavior
+        acts: [ "Destruction" ]
+  - type: EmitSoundOnSpawn
+    sound:
+      path: /Audio/Weapons/Guns/Gunshots/rocket_launcher.ogg
index 23732b97627190b7c904e1f0174df98cba268c23..927aea0973fb6f282fbdad57b0291b3c4990b555 100644 (file)
 
 - type: entity
   parent: BaseGameRule
-  id: Dragon
+  id: DragonSpawn
   noSpawn: true
   components:
   - type: StationEvent
     weight: 5
     duration: 1
     earliestStart: 45
+    reoccurrenceDelay: 60
     minimumPlayers: 20
   - type: RandomSpawnRule
     prototype: SpawnPointGhostDragon
index 1927cde53c37fc68977ba1c04220ebddedbc71b9..d7dff95b51e0212be64fa96a910e573c8d89a8b8 100644 (file)
     - NinjaSurviveObjective
   - type: NinjaRule
     threats: NinjaThreats
+
+# stores configuration for dragon
+- type: entity
+  noSpawn: true
+  parent: BaseGameRule
+  id: Dragon
+  components:
+  - type: GenericAntagRule
+    agentName: dragon-round-end-agent-name
+    objectives:
+    - CarpRiftsObjective
+    - DragonSurviveObjective
diff --git a/Resources/Prototypes/Objectives/dragon.yml b/Resources/Prototypes/Objectives/dragon.yml
new file mode 100644 (file)
index 0000000..2cf7eb2
--- /dev/null
@@ -0,0 +1,42 @@
+- type: entity
+  abstract: true
+  parent: BaseObjective
+  id: BaseDragonObjective
+  components:
+  - type: Objective
+    # difficulty isn't used at all since objective are fixed
+    difficulty: 1.5
+    issuer: dragon
+  - type: RoleRequirement
+    roles:
+      components:
+      - DragonRole
+
+- type: entity
+  noSpawn: true
+  parent: BaseDragonObjective
+  id: CarpRiftsObjective
+  components:
+  - type: Objective
+    icon:
+      sprite: Structures/Specific/carp_rift.rsi
+      state: icon_blue
+  - type: NumberObjective
+    # dragon can only spawn 3 rifts so keep objective the same
+    min: 3
+    max: 3
+    title: objective-carp-rifts-title
+    description: objective-carp-rifts-description
+  - type: CarpRiftsCondition
+
+- type: entity
+  noSpawn: true
+  parent: [BaseDragonObjective, BaseSurviveObjective]
+  id: DragonSurviveObjective
+  name: Survive
+  description: You have to stay alive to maintain control.
+  components:
+  - type: Objective
+    icon:
+      sprite: Mobs/Aliens/Carps/dragon.rsi
+      state: alive
diff --git a/Resources/Textures/Structures/Specific/carp_rift.rsi/icon_blue.png b/Resources/Textures/Structures/Specific/carp_rift.rsi/icon_blue.png
new file mode 100644 (file)
index 0000000..5d55d70
Binary files /dev/null and b/Resources/Textures/Structures/Specific/carp_rift.rsi/icon_blue.png differ
index 2567d69886b811e2b6579915f5f78fcb918f4949..76699055fb278c8d9663d4013528ceeb40c7a74b 100644 (file)
@@ -1,7 +1,7 @@
 {
   "version": 1,
   "license": "CC-BY-SA-3.0",
-  "copyright": "https://github.com/tgstation/tgstation/blob/19da0cee1869bad0186d54d6bcd8a55ed30b9db6/icons/obj/carp_rift.dmi",
+  "copyright": "Taken from tgstations at https://github.com/tgstation/tgstation/blob/19da0cee1869bad0186d54d6bcd8a55ed30b9db6/icons/obj/carp_rift.dmi. icon_blue recolored by deltanedas (github)",
   "size": {
     "x": 32,
     "y": 32
           0.2
         ]
       ]
+    },
+    {
+      "name": "icon_blue",
+      "delays": [
+        [
+          0.2,
+          0.2,
+          0.2
+        ]
+      ]
     }
   ]
-}
\ No newline at end of file
+}