diff --git a/Content.Server/_White/Cult/GameRule/CultRuleSystem.cs b/Content.Server/_White/Cult/GameRule/CultRuleSystem.cs index f419ad95af..efcb2a7002 100644 --- a/Content.Server/_White/Cult/GameRule/CultRuleSystem.cs +++ b/Content.Server/_White/Cult/GameRule/CultRuleSystem.cs @@ -29,6 +29,7 @@ using Content.Shared._White.Mood; using Content.Shared.Cloning; using Content.Shared.Mind; using Content.Shared.NPC.Systems; +using Robust.Server.Containers; using Robust.Server.Player; namespace Content.Server._White.Cult.GameRule; @@ -50,6 +51,7 @@ public sealed class CultRuleSystem : GameRuleSystem [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly GulagSystem _gulag = default!; [Dependency] private readonly BloodSpearSystem _bloodSpear = default!; + [Dependency] private readonly ContainerSystem _container = default!; private const int PlayerPerCultist = 10; private int _minStartingCultists; @@ -160,22 +162,18 @@ public sealed class CultRuleSystem : GameRuleSystem private void OnCultistComponentInit(EntityUid uid, CultistComponent component, ComponentInit args) { + RaiseLocalEvent(uid, new MoodEffectEvent("CultFocused")); + var query = QueryActiveRules(); while (query.MoveNext(out _, out var cult, out _)) { - if (!TryComp(uid, out var mindComponent)) - return; - - if (!mindComponent.HasMind) - return; - cult.CurrentCultists.Add(component); var name = Name(uid); - if (TryComp(uid, out var actor) && !cult.CultistsCache.ContainsKey(name)) + if (TryComp(uid, out var actor)) { - cult.CultistsCache.Add(name, actor.PlayerSession.Name); + cult.CultistsCache.TryAdd(name, actor.PlayerSession.Name); } UpdateCultistsAppearance(cult); @@ -203,17 +201,23 @@ public sealed class CultRuleSystem : GameRuleSystem while (query.MoveNext(out _, out var cult, out _)) { cult.CurrentCultists.Remove(component); - - _bloodSpear.DetachSpearFromUser((uid, component)); - - foreach (var empower in component.SelectedEmpowers) - { - _actions.RemoveAction(uid, GetEntity(empower)); - } - - RemoveCultistAppearance(uid); - CheckRoundShouldEnd(); } + + if (!TerminatingOrDeleted(uid)) + { + RemoveAllCultistItems(uid); + RemoveCultistAppearance(uid); + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("CultFocused")); + } + + _bloodSpear.DetachSpearFromUser((uid, component)); + + foreach (var empower in component.SelectedEmpowers) + { + _actions.RemoveAction(uid, GetEntity(empower)); + } + + CheckRoundShouldEnd(); } private void OnCultistsStateChanged(EntityUid uid, CultistComponent component, MobStateChangedEvent ev) @@ -422,8 +426,6 @@ public sealed class CultRuleSystem : GameRuleSystem _factionSystem.RemoveFaction(cultist, "NanoTrasen", false); _factionSystem.AddFaction(cultist, "Cultist"); - RaiseLocalEvent(cultist, new MoodEffectEvent("CultFocused")); - if (_inventorySystem.TryGetSlotEntity(cultist, "back", out var backPack)) { foreach (var itemPrototype in rule.StartingItems) @@ -442,6 +444,20 @@ public sealed class CultRuleSystem : GameRuleSystem return true; } + private void RemoveAllCultistItems(EntityUid uid) + { + if (!_inventorySystem.TryGetContainerSlotEnumerator(uid, out var enumerator)) + return; + + while (enumerator.MoveNext(out var container)) + { + if (container.ContainedEntity != null && HasComp(container.ContainedEntity.Value)) + { + _container.Remove(container.ContainedEntity.Value, container, true, true); + } + } + } + public void TransferRole(EntityUid transferFrom, EntityUid transferTo) { if (HasComp(transferFrom)) diff --git a/Content.Server/_White/Cult/HolyWater/DeconvertCultist.cs b/Content.Server/_White/Cult/HolyWater/DeconvertCultist.cs index 4ceb407cd0..640943ae27 100644 --- a/Content.Server/_White/Cult/HolyWater/DeconvertCultist.cs +++ b/Content.Server/_White/Cult/HolyWater/DeconvertCultist.cs @@ -1,18 +1,11 @@ using System.Threading; using Content.Server._White.Cult.GameRule; -using Content.Server.Objectives.Components; using Content.Server.Popups; -using Content.Server.Roles; using Content.Server.Stunnable; using Content.Shared.Chemistry.Reagent; using Content.Shared.IdentityManagement; -using Content.Shared.Inventory; -using Content.Shared._White.Cult.Components; -using Content.Shared._White.Mood; using Content.Shared.Jittering; -using Content.Shared.Mind; using JetBrains.Annotations; -using Robust.Server.Containers; using Robust.Shared.Prototypes; using CultistComponent = Content.Shared._White.Cult.Components.CultistComponent; using Timer = Robust.Shared.Timing.Timer; @@ -60,26 +53,10 @@ public sealed partial class DeconvertCultist : ReagentEffect cultist.HolyConvertToken = null; - var inventory = entityManager.System(); - var containerSystem = entityManager.System(); - if (!inventory.TryGetContainerSlotEnumerator(uid, out var enumerator)) - return; - - while (enumerator.MoveNext(out var container)) - { - if (container.ContainedEntity != null && - entityManager.HasComponent(container.ContainedEntity.Value)) - { - containerSystem.Remove(container.ContainedEntity.Value, container, true, true); - } - } - entityManager.RemoveComponent(uid); entityManager.RemoveComponent(uid); var cultRuleSystem = entityManager.System(); cultRuleSystem.RemoveObjectiveAndRole(uid); - - entityManager.EventBus.RaiseLocalEvent(uid, new MoodRemoveEffectEvent("CultFocused")); } } diff --git a/Content.Server/_White/Cult/Items/Components/CultRobeModifierComponent.cs b/Content.Server/_White/Cult/Items/Components/CultRobeModifierComponent.cs index a10ad7ee6a..ef8bde7c0d 100644 --- a/Content.Server/_White/Cult/Items/Components/CultRobeModifierComponent.cs +++ b/Content.Server/_White/Cult/Items/Components/CultRobeModifierComponent.cs @@ -10,4 +10,7 @@ public sealed partial class CultRobeModifierComponent : Component public string DamageModifierSetId = "CultRobe"; public string? StoredDamageSetId { get; set; } + + [ViewVariables] + public bool Active; } diff --git a/Content.Server/_White/Cult/Items/Systems/CultRobeModifierSystem.cs b/Content.Server/_White/Cult/Items/Systems/CultRobeModifierSystem.cs index fc48667e93..06a240438b 100644 --- a/Content.Server/_White/Cult/Items/Systems/CultRobeModifierSystem.cs +++ b/Content.Server/_White/Cult/Items/Systems/CultRobeModifierSystem.cs @@ -29,18 +29,22 @@ public sealed class CultRobeModifierSystem : EntitySystem if (args.Slot != "outerClothing") return; + component.Active = true; + ModifySpeed(args.Equipee, component, true); ModifyDamage(args.Equipee, component, true); } private void OnUnequip(EntityUid uid, CultRobeModifierComponent component, GotUnequippedEvent args) { - if (!HasComp(args.Equipee)) + if (!component.Active) return; if (args.Slot != "outerClothing") return; + component.Active = false; + ModifySpeed(args.Equipee, component, false); ModifyDamage(args.Equipee, component, false); } diff --git a/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs b/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs index 09c658af7a..51c5a62052 100644 --- a/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs +++ b/Content.Server/_White/Wizard/Magic/WizardSpellsSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Numerics; +using Content.Server._White.Cult; using Content.Server._White.IncorporealSystem; using Content.Server._White.Wizard.Magic.Amaterasu; using Content.Server._White.Wizard.Magic.Other; @@ -24,7 +25,6 @@ using Content.Shared._White.Wizard; using Content.Shared._White.Wizard.Magic; using Content.Shared.Actions; using Content.Shared.Borer; -using Content.Shared.Changeling; using Content.Shared.Cluwne; using Content.Shared.Coordinates.Helpers; using Content.Shared.Hands.Components; @@ -36,6 +36,7 @@ using Content.Shared.Inventory.VirtualItem; using Content.Shared.Item; using Content.Shared.Magic; using Content.Shared.Maps; +using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Physics; using Content.Shared.Popups; @@ -85,6 +86,7 @@ public sealed class WizardSpellsSystem : EntitySystem { base.Initialize(); + SubscribeLocalEvent(OnTimeStop); SubscribeLocalEvent(OnMindswapSpell); SubscribeLocalEvent(OnTeleportSpell); SubscribeLocalEvent(OnInstantRecallSpell); @@ -103,6 +105,25 @@ public sealed class WizardSpellsSystem : EntitySystem SubscribeLocalEvent(OnBeforeCastSpell); } + #region Timestop + + private void OnTimeStop(StopTimeSpellEvent msg) + { + if (!CanCast(msg)) + return; + + var ent = Spawn(msg.Prototype, Transform(msg.Performer).Coordinates); + _transformSystem.AttachToGridOrMap(ent); + + var comp = EnsureComp(ent); + comp.Uid = msg.Performer; + + msg.Handled = true; + Speak(msg); + } + + #endregion + #region Mindswap private void OnMindswapSpell(MindswapSpellEvent msg) @@ -113,17 +134,12 @@ public sealed class WizardSpellsSystem : EntitySystem var target = msg.Target; var uid = msg.Performer; - if (HasComp(target) || HasComp(target) || - HasComp(target)) - { - _popupSystem.PopupEntity("Не работает на культистов, генокрадов и революционеров.", uid, uid, - PopupType.MediumCaution); + if (!TryComp(target, out MobStateComponent? mobState) || mobState.CurrentState != MobState.Alive) return; - } if (TryComp(target, out InfestedBorerComponent? borer) && borer.ControllingBrain) { - _popupSystem.PopupEntity("Им уже кто-то управляет.", uid, uid, PopupType.MediumCaution); + _popupSystem.PopupEntity(Loc.GetString("mindswap-borer-failed"), uid, uid, PopupType.MediumCaution); return; } @@ -138,7 +154,7 @@ public sealed class WizardSpellsSystem : EntitySystem if (targetHasMind) { _mindSystem.TransferTo(targetMindId, uid, mind: targetMind); - _popupSystem.PopupEntity(Loc.GetString("Ваш разум подменили!"), uid, uid, PopupType.LargeCaution); + _popupSystem.PopupEntity(Loc.GetString("mindswap-success"), uid, uid, PopupType.LargeCaution); } TransferAllMagicActions(uid, target); @@ -149,23 +165,11 @@ public sealed class WizardSpellsSystem : EntitySystem msg.Handled = true; Speak(msg); - var hasWiz = HasComp(uid); - var targetHasWiz = HasComp(target); - - if (hasWiz == targetHasWiz) - return; - - if (hasWiz) - { - RemComp(uid); - EnsureComp(target); - } - - if (targetHasWiz) - { - RemComp(target); - EnsureComp(uid); - } + SwapComponent(uid, target); + SwapComponent(uid, target); + SwapComponent(uid, target); + SwapComponent(uid, target); + SwapComponent(uid, target); } #endregion @@ -919,5 +923,26 @@ public sealed class WizardSpellsSystem : EntitySystem } } + private void SwapComponent(EntityUid uid1, EntityUid uid2) where T : Component, new() + { + var hasComp = HasComp(uid1); + var targetHasComp = HasComp(uid2); + + if (hasComp == targetHasComp) + return; + + if (hasComp) + { + EnsureComp(uid2); + RemComp(uid1); + } + + if (targetHasComp) + { + EnsureComp(uid1); + RemComp(uid2); + } + } + #endregion } diff --git a/Content.Shared/Throwing/ThrownItemSystem.cs b/Content.Shared/Throwing/ThrownItemSystem.cs index 770273fa03..61227edee2 100644 --- a/Content.Shared/Throwing/ThrownItemSystem.cs +++ b/Content.Shared/Throwing/ThrownItemSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Shared._White.Wizard.Timestop; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Gravity; @@ -78,6 +79,8 @@ namespace Content.Shared.Throwing private void OnSleep(EntityUid uid, ThrownItemComponent thrownItem, ref PhysicsSleepEvent @event) { + if (HasComp(uid)) // WD + return; StopThrow(uid, thrownItem); } diff --git a/Content.Shared/_White/Wizard/Timestop/FreezeContactsComponent.cs b/Content.Shared/_White/Wizard/Timestop/FreezeContactsComponent.cs new file mode 100644 index 0000000000..f527cea3a5 --- /dev/null +++ b/Content.Shared/_White/Wizard/Timestop/FreezeContactsComponent.cs @@ -0,0 +1,8 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._White.Wizard.Timestop; + +[RegisterComponent, NetworkedComponent] +public sealed partial class FreezeContactsComponent : Component +{ +} diff --git a/Content.Shared/_White/Wizard/Timestop/FreezeContactsSystem.cs b/Content.Shared/_White/Wizard/Timestop/FreezeContactsSystem.cs new file mode 100644 index 0000000000..6da9f64b5c --- /dev/null +++ b/Content.Shared/_White/Wizard/Timestop/FreezeContactsSystem.cs @@ -0,0 +1,183 @@ +using System.Linq; +using System.Numerics; +using Content.Shared.ActionBlocker; +using Content.Shared.Emoting; +using Content.Shared.Hands; +using Content.Shared.Interaction.Events; +using Content.Shared.Inventory.Events; +using Content.Shared.Item; +using Content.Shared.Movement.Events; +using Content.Shared.Speech; +using Content.Shared.Throwing; +using Robust.Shared.Containers; +using Robust.Shared.Physics; +using Robust.Shared.Physics.Components; +using Robust.Shared.Physics.Events; +using Robust.Shared.Physics.Systems; +using Robust.Shared.Spawners; + +namespace Content.Shared._White.Wizard.Timestop; + +public sealed class FreezeContactsSystem : EntitySystem +{ + [Dependency] private readonly SharedPhysicsSystem _physics = default!; + [Dependency] private readonly SharedContainerSystem _container = default!; + [Dependency] private readonly ActionBlockerSystem _blocker = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnEntityEnter); + SubscribeLocalEvent(OnEntityExit); + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnRemove); + SubscribeLocalEvent(OnPreventCollide); + SubscribeLocalEvent(OnGetInserted); + + SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnMoveAttempt); + SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnAttempt); + SubscribeLocalEvent(OnEquipAttempt); + SubscribeLocalEvent(OnUnequipAttempt); + } + + private void OnMoveAttempt(EntityUid uid, FrozenComponent component, UpdateCanMoveEvent args) + { + if (component.LifeStage > ComponentLifeStage.Running) + return; + + args.Cancel(); + } + + private void OnAttempt(EntityUid uid, FrozenComponent component, CancellableEntityEventArgs args) + { + args.Cancel(); + } + + private void OnEquipAttempt(EntityUid uid, FrozenComponent component, IsEquippingAttemptEvent args) + { + // is this a self-equip, or are they being stripped? + if (args.Equipee == uid) + args.Cancel(); + } + + private void OnUnequipAttempt(EntityUid uid, FrozenComponent component, IsUnequippingAttemptEvent args) + { + // is this a self-equip, or are they being stripped? + if (args.Unequipee == uid) + args.Cancel(); + } + + private void OnGetInserted(Entity ent, ref EntGotInsertedIntoContainerMessage args) + { + RemCompDeferred(ent); + } + + private void OnPreventCollide(Entity ent, ref PreventCollideEvent args) + { + if (args.OurBody.BodyType == BodyType.Dynamic && !HasComp(args.OtherEntity)) + args.Cancelled = true; + } + + private void OnRemove(Entity ent, ref ComponentRemove args) + { + var (uid, comp) = ent; + + _blocker.UpdateCanMove(uid); + + if (_container.IsEntityOrParentInContainer(uid)) + return; + + if (!TryComp(uid, out PhysicsComponent? physics)) + return; + + _physics.SetLinearVelocity(uid, comp.OldLinearVelocity, false, body: physics); + _physics.SetAngularVelocity(uid, comp.OldAngularVelocity, body: physics); + } + + private void OnInit(Entity ent, ref ComponentInit args) + { + var (uid, comp) = ent; + + _blocker.UpdateCanMove(uid); + + if (!TryComp(uid, out PhysicsComponent? physics)) + return; + + comp.OldLinearVelocity = physics.LinearVelocity; + comp.OldAngularVelocity = physics.AngularVelocity; + + _physics.SetLinearVelocity(uid, Vector2.Zero, false, body: physics); + _physics.SetAngularVelocity(uid, 0f, body: physics); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = AllEntityQuery(); + + while (query.MoveNext(out var uid, out var frozen, out var fixtures, out var physics)) + { + frozen.Lifetime -= frameTime; + + if (physics.LinearVelocity != Vector2.Zero) + _physics.SetLinearVelocity(uid, Vector2.Zero, manager: fixtures, body: physics); + + if (physics.AngularVelocity != 0f) + _physics.SetAngularVelocity(uid, 0f, manager: fixtures, body: physics); + + if (frozen.Lifetime > 0) + continue; + + RemCompDeferred(uid); + } + } + + private void OnEntityExit(Entity ent, ref EndCollideEvent args) + { + if (IsTouchingFrozenContacts(args.OtherEntity, args.OtherBody)) + return; + + RemCompDeferred(args.OtherEntity); + } + + private void OnEntityEnter(Entity ent, ref StartCollideEvent args) + { + var hadFrozen = HasComp(args.OtherEntity); + var frozen = EnsureComp(args.OtherEntity); + + if (!TryComp(ent, out TimedDespawnComponent? timedDespawn)) + return; + + frozen.Lifetime = timedDespawn.Lifetime; + + if (TryComp(args.OtherEntity, out TimedDespawnComponent? otherTimedDespawn)) + otherTimedDespawn.Lifetime += timedDespawn.Lifetime; + + if (hadFrozen) + return; + + if (!TryComp(args.OtherEntity, out ThrownItemComponent? thrownItem)) + return; + + if (thrownItem.LandTime != null) + thrownItem.LandTime = thrownItem.LandTime.Value + TimeSpan.FromSeconds(timedDespawn.Lifetime); + + if (thrownItem.ThrownTime != null) + thrownItem.ThrownTime = thrownItem.ThrownTime.Value + TimeSpan.FromSeconds(timedDespawn.Lifetime); + } + + private bool IsTouchingFrozenContacts(EntityUid uid, PhysicsComponent body) + { + return _physics.GetContactingEntities(uid, body).Any(HasComp); + } +} diff --git a/Content.Shared/_White/Wizard/Timestop/FrozenComponent.cs b/Content.Shared/_White/Wizard/Timestop/FrozenComponent.cs new file mode 100644 index 0000000000..62560be1ba --- /dev/null +++ b/Content.Shared/_White/Wizard/Timestop/FrozenComponent.cs @@ -0,0 +1,17 @@ +using System.Numerics; +using Robust.Shared.GameStates; + +namespace Content.Shared._White.Wizard.Timestop; + +[RegisterComponent, NetworkedComponent] +public sealed partial class FrozenComponent : Component +{ + [ViewVariables] + public float Lifetime = 10f; + + [ViewVariables] + public Vector2 OldLinearVelocity; + + [ViewVariables] + public float OldAngularVelocity; +} diff --git a/Content.Shared/_White/Wizard/WizardEvents.cs b/Content.Shared/_White/Wizard/WizardEvents.cs index e55ff3b241..d3263f839d 100644 --- a/Content.Shared/_White/Wizard/WizardEvents.cs +++ b/Content.Shared/_White/Wizard/WizardEvents.cs @@ -184,4 +184,13 @@ public sealed partial class MindswapSpellEvent : EntityTargetActionEvent, ISpeak public string? Speech { get; private set; } } +public sealed partial class StopTimeSpellEvent : InstantActionEvent, ISpeakSpell +{ + [DataField("prototype", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] + public string Prototype = default!; + + [DataField("speech")] + public string? Speech { get; private set; } +} + #endregion diff --git a/Resources/Audio/White/Magic/timeparadox2.ogg b/Resources/Audio/White/Magic/timeparadox2.ogg new file mode 100644 index 0000000000..3e3df99f63 Binary files /dev/null and b/Resources/Audio/White/Magic/timeparadox2.ogg differ diff --git a/Resources/Locale/ru-RU/_white/wizard/scrolls.ftl b/Resources/Locale/ru-RU/_white/wizard/scrolls.ftl index ccbdefb1e4..4a7e5c9d7e 100644 --- a/Resources/Locale/ru-RU/_white/wizard/scrolls.ftl +++ b/Resources/Locale/ru-RU/_white/wizard/scrolls.ftl @@ -17,6 +17,7 @@ scroll-component-recall = призыв scroll-component-teleport = телепортацию scroll-component-smite = кару scroll-component-mindswap = подмену сознания +scroll-component-timestop = остановку времени ent-BaseScroll = магический свиток .desc = Этот древний пергамент, ставший реликвией в арканных преданиях, хранит в себе бесчисленные мистические заклятия и забытые заклинания. @@ -52,3 +53,5 @@ ent-ScrollSmite = свиток кары .desc = { ent-BaseScroll.desc } ent-ScrollMindswap = свиток подмены сознания .desc = { ent-BaseScroll.desc } +ent-ScrollTimestop = свиток остановки времени + .desc = { ent-BaseScroll.desc } diff --git a/Resources/Locale/ru-RU/_white/wizard/spellbook.ftl b/Resources/Locale/ru-RU/_white/wizard/spellbook.ftl index 2cf05319f0..b4a0f7e1c2 100644 --- a/Resources/Locale/ru-RU/_white/wizard/spellbook.ftl +++ b/Resources/Locale/ru-RU/_white/wizard/spellbook.ftl @@ -43,6 +43,9 @@ spellbook-smite-desc = { ent-ActionSmite.desc } spellbook-mindswap-name = { ent-ActionMindswapSpell } spellbook-mindswap-desc = { ent-ActionMindswapSpell.desc } +spellbook-timestop-name = { ent-ActionTimestopSpell } +spellbook-timestop-desc = { ent-ActionTimestopSpell.desc } + spellbook-hardsuit-name = Скафандр волшебника spellbook-hardsuit-desc = Украшенный магическими драгоценными камнями скафандр, функционирующий так же, как и обычная мантия волшебника, но в то же время является пригодным для использования в космосе и бронированным. Небольшое замедление. Теперь вы можете произносить заклинания в космосе и местах с низкой температурой! Имеет функцию энергетического щита,который защищает от всех снарядов. Щит разряжается при получении урона и автоматически заряжается. diff --git a/Resources/Locale/ru-RU/_white/wizard/spells.ftl b/Resources/Locale/ru-RU/_white/wizard/spells.ftl index c54b13bb07..84183b1aa7 100644 --- a/Resources/Locale/ru-RU/_white/wizard/spells.ftl +++ b/Resources/Locale/ru-RU/_white/wizard/spells.ftl @@ -44,4 +44,7 @@ ent-ActionSmite = Кара .desc = Заряжает вашу руку мерзкой энергией, которую можно использовать для взрыва жертв. Заклинание требует, чтобы вы коснулись своей цели, поэтому вы не сможете использовать его в наручниках или будучи оглушённым. Не работает без волшебной мантии и шляпы. ent-ActionMindswapSpell = Подмена сознания - .desc = Позволяет заклинателю переключаться между телами с целью. Вы должны быть рядом с целью, в которую хотите перейти, после чего вы оба будете нокаутированы. Не работает на культистов, генокрадов и революционеров. + .desc = Позволяет заклинателю переключаться между телами с целью. Вы должны быть рядом с целью, в которую хотите перейти, после чего вы оба будете нокаутированы. + +ent-ActionTimestopSpell = Остановка времени + .desc = Останавливает время в радиусе вокруг вас. Находящиеся под воздействием существа не смогут двигаться, а пролетающие мимо снаряды будут остановлены до окончания остановки времени. Не работает без волшебной мантии и шляпы. diff --git a/Resources/Locale/ru-RU/_white/wizard/wizard.ftl b/Resources/Locale/ru-RU/_white/wizard/wizard.ftl index 84feddbcb0..5fe80c6233 100644 --- a/Resources/Locale/ru-RU/_white/wizard/wizard.ftl +++ b/Resources/Locale/ru-RU/_white/wizard/wizard.ftl @@ -36,6 +36,9 @@ ent-ClothingOuterRealWizardFancy = мантия волшебника ent-ClothingHeadHelmetWizardHelmArmored = шлем мага .desc = Странный головной убор, который наверняка принадлежит настоящему магу. Не обладает свойствами волшебной шляпы. +mindswap-success = Ваш разум подменили! +mindswap-borer-failed = Его разумом кто-то управляет. + store-currency-display-spell-point = Очки заклинаний store-category-spells-attack = Атакующие заклинания diff --git a/Resources/Prototypes/Magic/white.yml b/Resources/Prototypes/Magic/white.yml index d3a01429a5..ea81456334 100644 --- a/Resources/Prototypes/Magic/white.yml +++ b/Resources/Prototypes/Magic/white.yml @@ -330,6 +330,7 @@ components: - MindContainer - ActionContainer + - MobState canTargetSelf: false checkCanInteract: false useDelay: 60 @@ -339,3 +340,24 @@ state: mindswap event: !type:MindswapSpellEvent speech: "GIN'YU CAPAN!" + +- type: entity + id: ActionTimestopSpell + name: Stop time + noSpawn: true + components: + - type: Magic + requiresClothes: true + - type: InstantAction + alwaysPlaySound: false + sound: !type:SoundPathSpecifier + path: /Audio/White/Magic/timeparadox2.ogg + useDelay: 50 + itemIconStyle: BigAction + checkCanInteract: false + icon: + sprite: Objects/Magic/magicactions.rsi + state: time + event: !type:StopTimeSpellEvent + prototype: Timestop + speech: "TOKI YO TOMARE!" diff --git a/Resources/Prototypes/_White/Entities/Effects/timestop.yml b/Resources/Prototypes/_White/Entities/Effects/timestop.yml new file mode 100644 index 0000000000..83093521d5 --- /dev/null +++ b/Resources/Prototypes/_White/Entities/Effects/timestop.yml @@ -0,0 +1,31 @@ +- type: entity + id: Timestop + name: "chronofield" + description: "ZA WARUDO" + components: + - type: Sprite + drawdepth: FloorObjects + sprite: White/Effects/timestop.rsi + state: icon + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeCircle + radius: 2.5 + density: 0 + hard: false + mask: + - None + layer: + - Impassable + - HighImpassable + - MidImpassable + - LowImpassable + - Opaque + - BulletImpassable + - type: Physics + bodyType: Static + - type: TimedDespawn + lifetime: 10 + - type: FreezeContacts diff --git a/Resources/Prototypes/_White/Objects/Scrolls/scrolls.yml b/Resources/Prototypes/_White/Objects/Scrolls/scrolls.yml index 1af1de715e..9919612b0a 100644 --- a/Resources/Prototypes/_White/Objects/Scrolls/scrolls.yml +++ b/Resources/Prototypes/_White/Objects/Scrolls/scrolls.yml @@ -160,3 +160,12 @@ - type: Scroll actionId: ActionMindswapSpell learnPopup: scroll-component-mindswap + +- type: entity + id: ScrollTimestop + parent: BaseScroll + name: "Stop time scroll" + components: + - type: Scroll + actionId: ActionTimestopSpell + learnPopup: scroll-component-timestop diff --git a/Resources/Prototypes/_White/Wizard/spellbook_catalog.yml b/Resources/Prototypes/_White/Wizard/spellbook_catalog.yml index 97fdecd3ea..492aa26f6d 100644 --- a/Resources/Prototypes/_White/Wizard/spellbook_catalog.yml +++ b/Resources/Prototypes/_White/Wizard/spellbook_catalog.yml @@ -30,6 +30,22 @@ - !type:ListingLimitedStockCondition stock: 1 +- type: listing + id: SpellBookTimestop + name: spellbook-timestop-name + description: spellbook-timestop-desc + productEntity: ScrollTimestop + icon: + sprite: Objects/Magic/magicactions.rsi + state: time + cost: + SpellPoint: 2 + categories: + - DefenceSpells + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + - type: listing id: SpellBookKnock name: spellbook-knock-name diff --git a/Resources/Textures/Objects/Magic/magicactions.rsi/meta.json b/Resources/Textures/Objects/Magic/magicactions.rsi/meta.json index 248ab268d2..5886bfc706 100644 --- a/Resources/Textures/Objects/Magic/magicactions.rsi/meta.json +++ b/Resources/Textures/Objects/Magic/magicactions.rsi/meta.json @@ -60,6 +60,9 @@ }, { "name": "mindswap" + }, + { + "name": "time" } ] } diff --git a/Resources/Textures/Objects/Magic/magicactions.rsi/time.png b/Resources/Textures/Objects/Magic/magicactions.rsi/time.png new file mode 100644 index 0000000000..be66fe4cf4 Binary files /dev/null and b/Resources/Textures/Objects/Magic/magicactions.rsi/time.png differ diff --git a/Resources/Textures/White/Effects/timestop.rsi/icon.png b/Resources/Textures/White/Effects/timestop.rsi/icon.png new file mode 100644 index 0000000000..0aff799465 Binary files /dev/null and b/Resources/Textures/White/Effects/timestop.rsi/icon.png differ diff --git a/Resources/Textures/White/Effects/timestop.rsi/meta.json b/Resources/Textures/White/Effects/timestop.rsi/meta.json new file mode 100644 index 0000000000..b4fc8b7197 --- /dev/null +++ b/Resources/Textures/White/Effects/timestop.rsi/meta.json @@ -0,0 +1,37 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/pull/49264/commits/d0dffe7ca643db2624424fdcebf45863f85c0448", + "size": { + "x": 160, + "y": 160 + }, + "states": [ + { + "name": "icon", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +}