diff --git a/Content.Client/_Amour/ProtocolCRAB17/ProtocolCRAB17System.cs b/Content.Client/_Amour/ProtocolCRAB17/ProtocolCRAB17System.cs new file mode 100644 index 0000000000..d23ed4d0be --- /dev/null +++ b/Content.Client/_Amour/ProtocolCRAB17/ProtocolCRAB17System.cs @@ -0,0 +1,12 @@ +using Content.Shared._Amour.ProtocolCRAB17; + +namespace Content.Client._Amour.ProtocolCRAB17; + +public sealed class ProtocolCRAB17System : SharedProtocolCRAB17System +{ + public override void OnDoAfter(Entity ent, ref ProtocolCRAB17DoAfterEvent args) + { + + } + +} diff --git a/Content.Server/_Amour/ProtocolCRAB17/ProtocolCRAB17Rule.cs b/Content.Server/_Amour/ProtocolCRAB17/ProtocolCRAB17Rule.cs new file mode 100644 index 0000000000..1bdc88c4e1 --- /dev/null +++ b/Content.Server/_Amour/ProtocolCRAB17/ProtocolCRAB17Rule.cs @@ -0,0 +1,109 @@ +using Content.Server.Chat.Systems; +using Content.Server.StationEvents.Events; +using Content.Server.GameTicking.Components; +using Content.Shared._Amour.ProtocolCRAB17; +using Robust.Shared.Timing; +using Content.Shared._White.Economy; +using Content.Server._White.Economy; + +namespace Content.Server._Amour.ProtocolCRAB17; + +public sealed class ProtocolCRAB17Rule : StationEventSystem +{ + [Dependency] private readonly ChatSystem _chatSystem = default!; + [Dependency] private readonly IEntityManager _entities = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly BankCardSystem _bankCardSystem = default!; + + public TimeSpan? LastTimeExecuted; + + public override void Initialize() + { + base.Initialize(); + } + + protected override void Added(EntityUid uid, ProtocolCRAB17RuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) + { + base.Added(uid, component, gameRule, args); + + if (TryGetRandomStation(out var stationUid)) + { + component.TargetStation = stationUid; + } + + var query = AllEntityQuery(); + while (query.MoveNext(out _, out var comp)) + { + if (comp.BankAccountId == null) + continue; + + component.Callers.Add((int) comp.BankAccountId); + } + } + + protected override void Started(EntityUid uid, ProtocolCRAB17RuleComponent component, GameRuleComponent gameRule, + GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); + + if (component.TargetStation == null) + { + ForceEndSelf(uid, gameRule); + return; + } + + _chatSystem.DispatchStationAnnouncement((EntityUid) component.TargetStation, Loc.GetString("protocol-CRAB17-stage-1"), + colorOverride: Color.OrangeRed, sender: "Центральное Командование", announcementSound: ProtocolCRAB17RuleComponent.Announcement); + } + + protected override void Ended(EntityUid uid, ProtocolCRAB17RuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args) + { + base.Ended(uid, component, gameRule, args); + + var allMoneyStolen = 0; + + var query = AllEntityQuery(); + while (query.MoveNext(out _, out var bankCardComponent)) + { + if (bankCardComponent.AccountId == null) + continue; + + if (component.Callers.Contains((int) bankCardComponent.AccountId)) + continue; + + var money = _bankCardSystem.GetBalance((int) bankCardComponent.AccountId); + allMoneyStolen += money; + + _bankCardSystem.TryChangeBalance((int) bankCardComponent.AccountId, -money); + } + + var callersCount = component.Callers.Count; + + foreach (var caller in component.Callers) + { + _bankCardSystem.TryChangeBalance(caller, (int) Math.Floor((double) (allMoneyStolen / callersCount))); + } + + if (component.TargetStation != null) + _chatSystem.DispatchStationAnnouncement((EntityUid) component.TargetStation, Loc.GetString("protocol-CRAB17-stage-2", ("amount", allMoneyStolen)), + colorOverride: Color.OrangeRed, sender: "Центральное Командование"); + + var ertsys = _entities.System(); + ertsys.LastTimeExecuted = _timing.CurTime; + component.Callers.Clear(); + } + + public bool CanStartEvent() + { + var ertsys = _entities.System(); + + if (ertsys.LastTimeExecuted == null) + return true; + + else if (ertsys.LastTimeExecuted < _timing.CurTime - ProtocolCRAB17RuleComponent.TimeBetweenEvents) + return true; + + return false; + } + +} diff --git a/Content.Server/_Amour/ProtocolCRAB17/ProtocolCRAB17RuleComponent.cs b/Content.Server/_Amour/ProtocolCRAB17/ProtocolCRAB17RuleComponent.cs new file mode 100644 index 0000000000..5a05214f22 --- /dev/null +++ b/Content.Server/_Amour/ProtocolCRAB17/ProtocolCRAB17RuleComponent.cs @@ -0,0 +1,19 @@ +using Robust.Shared.Audio; + +namespace Content.Server._Amour.ProtocolCRAB17; + +[RegisterComponent] +public sealed partial class ProtocolCRAB17RuleComponent : Component +{ + /// + /// Minimal time between events. + /// + public static TimeSpan TimeBetweenEvents = TimeSpan.FromMinutes(20); + + public static SoundSpecifier Announcement = new SoundPathSpecifier("/Audio/_Amour/ProtocolCRAB17/Bogdanoff_is_calling.ogg"); + + [ViewVariables] + public EntityUid? TargetStation; + [ViewVariables] + public HashSet Callers = new(); +} diff --git a/Content.Server/_Amour/ProtocolCRAB17/ProtocolCRAB17System.cs b/Content.Server/_Amour/ProtocolCRAB17/ProtocolCRAB17System.cs new file mode 100644 index 0000000000..a67069094f --- /dev/null +++ b/Content.Server/_Amour/ProtocolCRAB17/ProtocolCRAB17System.cs @@ -0,0 +1,38 @@ +using Content.Shared._Amour.ProtocolCRAB17; +using Content.Server.GameTicking; +using Content.Server.Popups; + +namespace Content.Server._Amour.ProtocolCRAB17; + +public sealed class ProtocolCRAB17System : SharedProtocolCRAB17System +{ + [Dependency] private readonly GameTicker _gameTicker = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + [Dependency] private readonly ProtocolCRAB17Rule _protocolCRAB17Rule = default!; + + public override void OnDoAfter(Entity ent, ref ProtocolCRAB17DoAfterEvent args) + { + if (args.Cancelled || args.Handled) + return; + + if (_gameTicker.IsGameRuleActive("ProtocolCRAB17Event")) + { + _popupSystem.PopupEntity(Loc.GetString("protocol-CRAB17-event-running"), ent, args.User); + args.Handled = true; + return; + } + + if (!_protocolCRAB17Rule.CanStartEvent()) + { + _popupSystem.PopupEntity(Loc.GetString("protocol-CRAB17-timeout"), ent, args.User); + args.Handled = true; + return; + } + + _popupSystem.PopupEntity(Loc.GetString("protocol-CRAB17-activated"), ent, args.User); + _gameTicker.AddGameRule("ProtocolCRAB17Event"); + args.Handled = true; + return; + } + +} diff --git a/Content.Shared/_Amour/ProtocolCRAB17/ProtocolCRAB17Component.cs b/Content.Shared/_Amour/ProtocolCRAB17/ProtocolCRAB17Component.cs new file mode 100644 index 0000000000..ce76b7c6ec --- /dev/null +++ b/Content.Shared/_Amour/ProtocolCRAB17/ProtocolCRAB17Component.cs @@ -0,0 +1,31 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Serialization; + +namespace Content.Shared._Amour.ProtocolCRAB17; + +/// +/// WD +/// +[NetworkedComponent, RegisterComponent] +public sealed partial class ProtocolCRAB17Component : Component +{ + [ViewVariables] + public int? BankAccountId; + + [ViewVariables] + public bool HasBankAccountId = false; + + [DataField] + public SoundSpecifier UseSound = new SoundPathSpecifier("/Audio/_Amour/ProtocolCRAB17/Phone_Dial.ogg"); + + [DataField] + public SoundSpecifier SoundApply = new SoundPathSpecifier("/Audio/White/Machines/chime.ogg"); + +} + +[Serializable, NetSerializable] +public sealed partial class ProtocolCRAB17DoAfterEvent : SimpleDoAfterEvent +{ +} diff --git a/Content.Shared/_Amour/ProtocolCRAB17/SharedProtocolCRAB17System.cs b/Content.Shared/_Amour/ProtocolCRAB17/SharedProtocolCRAB17System.cs new file mode 100644 index 0000000000..1eb78069f0 --- /dev/null +++ b/Content.Shared/_Amour/ProtocolCRAB17/SharedProtocolCRAB17System.cs @@ -0,0 +1,71 @@ +using Content.Shared._White.Economy; +using Content.Shared.DoAfter; +using Content.Shared.Interaction; +using Content.Shared.Interaction.Events; +using Content.Shared.Popups; +using Content.Shared.Timing; +using Robust.Shared.Audio.Systems; + +namespace Content.Shared._Amour.ProtocolCRAB17; + +public abstract class SharedProtocolCRAB17System : EntitySystem +{ + [Dependency] private readonly SharedPopupSystem _popupSystem = default!; + [Dependency] private readonly UseDelaySystem _useDelay = default!; + [Dependency] private readonly SharedAudioSystem _audioSystem = default!; + [Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUseInHand); + SubscribeLocalEvent(OnDoAfter); + SubscribeLocalEvent(OnInteractUsing); + } + + private void OnInteractUsing(EntityUid uid, ProtocolCRAB17Component component, InteractUsingEvent args) + { + if (!TryComp(args.Used, out BankCardComponent? bankCard)) + return; + + if (!TryComp(uid, out UseDelayComponent? useDelay) || + !_useDelay.TryResetDelay((uid, useDelay), true)) + return; + + component.BankAccountId = bankCard.AccountId; + component.HasBankAccountId = true; + _popupSystem.PopupPredicted(Loc.GetString("protocol-CRAB17-card-accepted"), uid, args.User); + _audioSystem.PlayPredicted(component.SoundApply, uid, args.User); + } + + private void OnUseInHand(EntityUid uid, ProtocolCRAB17Component component, ref UseInHandEvent args) + { + if (args.Handled || + !TryComp(uid, out UseDelayComponent? useDelay) || + !_useDelay.TryResetDelay((uid, useDelay), true)) + return; + + if (component.HasBankAccountId == false) + { + _popupSystem.PopupClient(Loc.GetString("protocol-CRAB17-no-card"), uid, args.User); + return; + } + + var doAfterEventArgs = new DoAfterArgs(EntityManager, args.User, useDelay.Delay, new ProtocolCRAB17DoAfterEvent(), uid, target: uid) + { + BreakOnMove = true, + BreakOnDamage = true, + NeedHand = true + }; + + // TODO find a way to stop playsound on doafter cancel + _audioSystem.PlayPredicted(component.UseSound, uid, args.User); + _popupSystem.PopupClient(Loc.GetString("protocol-CRAB17-try-activate"), uid, args.User); + _doAfterSystem.TryStartDoAfter(doAfterEventArgs); + args.Handled = true; + } + + public abstract void OnDoAfter(Entity ent, ref ProtocolCRAB17DoAfterEvent args); + +} diff --git a/Resources/Audio/_Amour/ProtocolCRAB17/Bogdanoff_is_calling.ogg b/Resources/Audio/_Amour/ProtocolCRAB17/Bogdanoff_is_calling.ogg new file mode 100644 index 0000000000..89b4fa64a0 Binary files /dev/null and b/Resources/Audio/_Amour/ProtocolCRAB17/Bogdanoff_is_calling.ogg differ diff --git a/Resources/Audio/_Amour/ProtocolCRAB17/Bogdanoff_is_calling.xmp b/Resources/Audio/_Amour/ProtocolCRAB17/Bogdanoff_is_calling.xmp new file mode 100644 index 0000000000..af525ec6b4 --- /dev/null +++ b/Resources/Audio/_Amour/ProtocolCRAB17/Bogdanoff_is_calling.xmp @@ -0,0 +1,77 @@ + + + + + + + + CuePoint Markers + Cue + f44100 + + + CD Track Markers + Track + f44100 + + + Subclip Markers + InOut + f44100 + + + + 2025-01-26T18:51:20+03:00 + 2025-01-26T18:51:20+03:00 + xmp.iid:54b1ecf1-4293-e549-85f0-201b1c450278 + xmp.did:07ec6da3-7091-9044-a75c-40bd06f0df0b + xmp.did:07ec6da3-7091-9044-a75c-40bd06f0df0b + + + + saved + xmp.iid:07ec6da3-7091-9044-a75c-40bd06f0df0b + 2025-01-26T18:51:20+03:00 + Adobe Audition 24.0 (Windows) + /metadata + + + saved + xmp.iid:54b1ecf1-4293-e549-85f0-201b1c450278 + 2025-01-26T18:51:20+03:00 + Adobe Audition 24.0 (Windows) + / + + + + audio/ogg; codec="vorbis" + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Resources/Audio/_Amour/ProtocolCRAB17/Phone_Dial.ogg b/Resources/Audio/_Amour/ProtocolCRAB17/Phone_Dial.ogg new file mode 100644 index 0000000000..255b4d6a77 Binary files /dev/null and b/Resources/Audio/_Amour/ProtocolCRAB17/Phone_Dial.ogg differ diff --git a/Resources/Locale/ru-RU/_amour/protocolcrab17.ftl b/Resources/Locale/ru-RU/_amour/protocolcrab17.ftl index 7c78ed39c4..76ed0219d6 100644 --- a/Resources/Locale/ru-RU/_amour/protocolcrab17.ftl +++ b/Resources/Locale/ru-RU/_amour/protocolcrab17.ftl @@ -1,2 +1,12 @@ ent-ProtocolCRAB17 = подозрительный телефон .desc = Какой следующий шаг твоего мастер плана? Уничтожить этот рынок, не оставив выживших! + +protocol-CRAB17-no-card = Протокол не может быть запущен без привязанной банковской карты. +protocol-CRAB17-card-accepted = Карта привязана. +protocol-CRAB17-try-activate = Вы вводите комбинацию и ожидаете ответ... +protocol-CRAB17-activated = Инициация протокола КРАБ-17 завершена. Ожидайте благополучных новостей. +protocol-CRAB17-event-running = Протокол уже запущен, ваши средства в безопасности! +protocol-CRAB17-timeout = Линия занята, повторите попытку позже... + +protocol-CRAB17-stage-1 = Внимание! В централизованной системе учёта финансов обнаружена критическая ошибка. Сброс банковских данных станции неизбежен. Персоналу необходимо в кратчайшие сроки обналичить все счета, для избежания потери средств. +protocol-CRAB17-stage-2 = Системы банковского учёта сброшены до последней стабильной версии и готовы к дальнейшей работе. Оценочная суммарная потеря средств составляет: { $amount } кредитов. diff --git a/Resources/Prototypes/_Amour/Entities/Objects/Tools/protocolcrab17.yml b/Resources/Prototypes/_Amour/Entities/Objects/Tools/protocolcrab17.yml index 034438227d..a20c90c1d2 100644 --- a/Resources/Prototypes/_Amour/Entities/Objects/Tools/protocolcrab17.yml +++ b/Resources/Prototypes/_Amour/Entities/Objects/Tools/protocolcrab17.yml @@ -17,3 +17,16 @@ - type: EmitSoundOnLand sound: path: /Audio/White/Items/handling/component_drop.ogg + - type: ProtocolCRAB17 + - type: UseDelay + delay: 4 + +- type: entity + id: ProtocolCRAB17Event + parent: BaseGameRule + noSpawn: true + components: + - type: StationEvent + weight: 0 + duration: 120 + - type: ProtocolCRAB17Rule diff --git a/Resources/Prototypes/_Honk/Catalog/uplink_catalog.yml b/Resources/Prototypes/_Honk/Catalog/uplink_catalog.yml index 03e6a20fea..260a4a81e5 100644 --- a/Resources/Prototypes/_Honk/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/_Honk/Catalog/uplink_catalog.yml @@ -16,6 +16,6 @@ icon: { sprite: _Amour/Objects/Devices/protocolcrab17.rsi, state: icon } productEntity: ProtocolCRAB17 cost: - Telecrystal: 8 + Telecrystal: 6 categories: - UplinkDisruption