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 +}