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/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/Changelog/ChangelogAmour.yml b/Resources/Changelog/ChangelogAmour.yml index 7c883c6744..56d64efa62 100644 --- a/Resources/Changelog/ChangelogAmour.yml +++ b/Resources/Changelog/ChangelogAmour.yml @@ -171,3 +171,9 @@ type: Add id: 27 time: '2025-01-07T18:00:13.0000000+00:00' +- author: BIG_Zi_348 + changes: + - message: "Добавлен новый предмет в аплинк - подозрительный телефон." + type: Add + id: 28 + time: '2025-01-26T18:00:13.0000000+00:00' diff --git a/Resources/Locale/ru-RU/_amour/protocolcrab17.ftl b/Resources/Locale/ru-RU/_amour/protocolcrab17.ftl new file mode 100644 index 0000000000..76ed0219d6 --- /dev/null +++ b/Resources/Locale/ru-RU/_amour/protocolcrab17.ftl @@ -0,0 +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 new file mode 100644 index 0000000000..a20c90c1d2 --- /dev/null +++ b/Resources/Prototypes/_Amour/Entities/Objects/Tools/protocolcrab17.yml @@ -0,0 +1,32 @@ +- type: entity + name: suspicious phone + parent: BaseItem + id: ProtocolCRAB17 + description: So what's the next step of your master plan? Crashing this market, with no survivors! + components: + - type: Sprite + sprite: _Amour/Objects/Devices/protocolcrab17.rsi + state: icon + scale: 0.75, 0.75 + - type: EmitSoundOnPickup + sound: + path: /Audio/White/Items/handling/component_pickup.ogg + - type: EmitSoundOnDrop + sound: + path: /Audio/White/Items/handling/component_drop.ogg + - 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 5cf5badc4d..260a4a81e5 100644 --- a/Resources/Prototypes/_Honk/Catalog/uplink_catalog.yml +++ b/Resources/Prototypes/_Honk/Catalog/uplink_catalog.yml @@ -8,3 +8,14 @@ Telecrystal: 12 categories: - UplinkAllies + +- type: listing + id: UplinkProtocolCRAB17 + name: Протокол КРАБ-17 + description: Подозрительный телефон с загруженным протоколом "КРАБ-17". Привяжите свою карту и наблюдайте за падением экономики всей станции. + icon: { sprite: _Amour/Objects/Devices/protocolcrab17.rsi, state: icon } + productEntity: ProtocolCRAB17 + cost: + Telecrystal: 6 + categories: + - UplinkDisruption diff --git a/Resources/Textures/_Amour/Objects/Devices/protocolcrab17.rsi/icon.png b/Resources/Textures/_Amour/Objects/Devices/protocolcrab17.rsi/icon.png new file mode 100644 index 0000000000..27100df22e Binary files /dev/null and b/Resources/Textures/_Amour/Objects/Devices/protocolcrab17.rsi/icon.png differ diff --git a/Resources/Textures/_Amour/Objects/Devices/protocolcrab17.rsi/meta.json b/Resources/Textures/_Amour/Objects/Devices/protocolcrab17.rsi/meta.json new file mode 100644 index 0000000000..d83df8f4a6 --- /dev/null +++ b/Resources/Textures/_Amour/Objects/Devices/protocolcrab17.rsi/meta.json @@ -0,0 +1,14 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/BeeStation/BeeStation-Hornet/commit/016ff56f3b0c0884912c87002c34eedd5e82a8fe", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + } + ] +}