diff --git a/Content.Server/Dragon/DragonRiftSystem.cs b/Content.Server/Dragon/DragonRiftSystem.cs
new file mode 100644
index 0000000000..52137f2ee6
--- /dev/null
+++ b/Content.Server/Dragon/DragonRiftSystem.cs
@@ -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;
+
+///
+/// Handles events for rift entities and rift updating.
+///
+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(OnExamined);
+ SubscribeLocalEvent(OnAnchorChange);
+ SubscribeLocalEvent(OnShutdown);
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ var query = EntityQueryEnumerator();
+ 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(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(comp.Dragon, out var randomSprite))
+ {
+ var spawnedSprite = EnsureComp(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(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
index 4a4f44e143..0000000000
--- a/Content.Server/Dragon/DragonSystem.Rule.cs
+++ /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(rift, out var drift) ||
- drift.State != DragonRiftState.Finished)
- continue;
-
- finished++;
- }
-
- return finished;
- }
-
- private void OnRiftRoundEnd(RoundEndTextAppendEvent args)
- {
- if (EntityQuery().Count() == 0)
- return;
-
- args.AddLine(Loc.GetString("dragon-round-end-summary"));
-
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out var dragon))
- {
- var met = RiftsMet(dragon);
-
- if (TryComp(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)));
- }
- }
- }
-}
diff --git a/Content.Server/Dragon/DragonSystem.cs b/Content.Server/Dragon/DragonSystem.cs
index 40039be50e..ed17ba8bdc 100644
--- a/Content.Server/Dragon/DragonSystem.cs
+++ b/Content.Server/Dragon/DragonSystem.cs
@@ -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 _objQuery;
///
/// Minimum distance between 2 rifts allowed.
@@ -47,26 +45,22 @@ public sealed partial class DragonSystem : EntitySystem
{
base.Initialize();
+ _objQuery = GetEntityQuery();
+
SubscribeLocalEvent(OnInit);
SubscribeLocalEvent(OnShutdown);
- SubscribeLocalEvent(OnDragonRift);
+ SubscribeLocalEvent(OnSpawnRift);
SubscribeLocalEvent(OnDragonMove);
-
SubscribeLocalEvent(OnMobStateChanged);
-
- SubscribeLocalEvent(OnRiftShutdown);
- SubscribeLocalEvent(OnRiftGetState);
- SubscribeLocalEvent(OnAnchorChange);
- SubscribeLocalEvent(OnRiftExamined);
-
- SubscribeLocalEvent(OnRiftRoundEnd);
+ SubscribeLocalEvent(OnCreated);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
- foreach (var comp in EntityQuery())
+ var query = EntityQueryEnumerator();
+ 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())
- {
- 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(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(comp.Dragon, out var randomSprite))
- {
- var spawnedSprite = EnsureComp(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)
+ private void OnShutdown(EntityUid uid, DragonComponent component, ComponentShutdown args)
{
- if (!args.Anchored && component.State == DragonRiftState.Charging)
- {
- QueueDel(uid);
- }
+ DeleteRifts(uid, false, component);
}
- private void OnRiftShutdown(EntityUid uid, DragonRiftComponent component, ComponentShutdown args)
- {
- if (TryComp(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)
- {
- args.State = new DragonRiftComponentState()
- {
- State = component.State
- };
- }
-
- 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(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(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(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()
{
- if (component.SoundDeath != null)
- _audioSystem.PlayPvs(component.SoundDeath, uid, component.SoundDeath.Params);
+ Briefing = Loc.GetString("dragon-role-briefing")
+ }, mind);
+ }
- foreach (var rift in component.Rifts)
+ private void Roar(EntityUid uid, DragonComponent comp)
+ {
+ if (comp.SoundRoar != null)
+ _audio.Play(comp.SoundRoar, Filter.Pvs(uid, 4f, EntityManager), uid, true);
+ }
+
+ ///
+ /// 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 || !TryComp(uid, out var mindContainer) || !mindContainer.HasMind)
+ return;
+
+ var mind = Comp(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)
+ ///
+ /// Increment the dragon role's charged rift count.
+ ///
+ 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(uid, out var mindContainer) || !mindContainer.HasMind)
+ return;
+
+ var mind = Comp(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)
+ ///
+ /// Do everything that needs to happen when a rift gets destroyed by the crew.
+ ///
+ 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
index 0000000000..8e43154e37
--- /dev/null
+++ b/Content.Server/Objectives/Components/CarpRiftsConditionComponent.cs
@@ -0,0 +1,17 @@
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+///
+/// Requires that the dragon open and fully charge a certain number of rifts.
+/// Depends on to function.
+///
+[RegisterComponent, Access(typeof(CarpRiftsConditionSystem))]
+public sealed partial class CarpRiftsConditionComponent : Component
+{
+ ///
+ /// The number of rifts currently charged.
+ ///
+ [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
index 0000000000..efb2d228d6
--- /dev/null
+++ b/Content.Server/Objectives/Systems/CarpRiftsConditionSystem.cs
@@ -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(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;
+ }
+
+ ///
+ /// Increments RiftsCharged, called after a rift fully charges.
+ ///
+ public void RiftCharged(EntityUid uid, CarpRiftsConditionComponent? comp = null)
+ {
+ if (!Resolve(uid, ref comp))
+ return;
+
+ comp.RiftsCharged++;
+ }
+
+ ///
+ /// Resets RiftsCharged to 0, called after rifts get destroyed.
+ ///
+ 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
index 0000000000..77fd97acb2
--- /dev/null
+++ b/Content.Server/Roles/DragonRoleComponent.cs
@@ -0,0 +1,12 @@
+using Content.Server.Dragon;
+using Content.Shared.Roles;
+
+namespace Content.Server.Roles;
+
+///
+/// Role used to keep track of space dragons for antag purposes.
+///
+[RegisterComponent, Access(typeof(DragonSystem))]
+public sealed partial class DragonRoleComponent : AntagonistRoleComponent
+{
+}
diff --git a/Content.Server/Roles/RoleSystem.cs b/Content.Server/Roles/RoleSystem.cs
index 4ca6c0ac80..b04f051c6e 100644
--- a/Content.Server/Roles/RoleSystem.cs
+++ b/Content.Server/Roles/RoleSystem.cs
@@ -9,6 +9,7 @@ public sealed class RoleSystem : SharedRoleSystem
// TODO make roles entities
base.Initialize();
+ SubscribeAntagEvents();
SubscribeAntagEvents();
SubscribeAntagEvents();
SubscribeAntagEvents();
diff --git a/Resources/Locale/en-US/actions/actions/dragon.ftl b/Resources/Locale/en-US/actions/actions/dragon.ftl
index a9b887142d..b2b9b6d3af 100644
--- a/Resources/Locale/en-US/actions/actions/dragon.ftl
+++ b/Resources/Locale/en-US/actions/actions/dragon.ftl
@@ -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
index 0000000000..11e8a58620
--- /dev/null
+++ b/Resources/Locale/en-US/dragon/dragon.ftl
@@ -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
index 0000000000..5ad061abf9
--- /dev/null
+++ b/Resources/Locale/en-US/dragon/rifts.ftl
@@ -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
index 0000000000..6010473c55
--- /dev/null
+++ b/Resources/Locale/en-US/objectives/conditions/carp-rifts.ftl
@@ -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.
diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml
index 398d101021..368d184692 100644
--- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml
+++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml
@@ -133,6 +133,8 @@
spawnsLeft: 2
spawnsProto: MobCarpDragon
spawnRiftAction: ActionSpawnRift
+ - type: GenericAntag
+ rule: Dragon
- type: GuideHelp
guides:
- MinorAntagonists
diff --git a/Resources/Prototypes/Entities/Structures/Specific/dragon.yml b/Resources/Prototypes/Entities/Structures/Specific/dragon.yml
index 9c9b09c7c7..8a7f13d1a4 100644
--- a/Resources/Prototypes/Entities/Structures/Specific/dragon.yml
+++ b/Resources/Prototypes/Entities/Structures/Specific/dragon.yml
@@ -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
diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml
index 23732b9762..927aea0973 100644
--- a/Resources/Prototypes/GameRules/events.yml
+++ b/Resources/Prototypes/GameRules/events.yml
@@ -68,13 +68,14 @@
- 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
diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml
index 1927cde53c..d7dff95b51 100644
--- a/Resources/Prototypes/GameRules/midround.yml
+++ b/Resources/Prototypes/GameRules/midround.yml
@@ -15,3 +15,15 @@
- 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
index 0000000000..2cf7eb292f
--- /dev/null
+++ b/Resources/Prototypes/Objectives/dragon.yml
@@ -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
index 0000000000..5d55d70b48
Binary files /dev/null and b/Resources/Textures/Structures/Specific/carp_rift.rsi/icon_blue.png differ
diff --git a/Resources/Textures/Structures/Specific/carp_rift.rsi/meta.json b/Resources/Textures/Structures/Specific/carp_rift.rsi/meta.json
index 2567d69886..76699055fb 100644
--- a/Resources/Textures/Structures/Specific/carp_rift.rsi/meta.json
+++ b/Resources/Textures/Structures/Specific/carp_rift.rsi/meta.json
@@ -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
@@ -16,6 +16,16 @@
0.2
]
]
+ },
+ {
+ "name": "icon_blue",
+ "delays": [
+ [
+ 0.2,
+ 0.2,
+ 0.2
+ ]
+ ]
}
]
-}
\ No newline at end of file
+}