diff --git a/Content.Client/_White/CoinDice/CoinDiceSystem.cs b/Content.Client/_White/CoinDice/CoinDiceSystem.cs
new file mode 100644
index 0000000000..a2d4279f5b
--- /dev/null
+++ b/Content.Client/_White/CoinDice/CoinDiceSystem.cs
@@ -0,0 +1,21 @@
+using Content.Shared._White.CoinDice;
+using Robust.Client.GameObjects;
+
+namespace Content.Client._White.CoinDice;
+
+public sealed class CoinDiceSystem : SharedCoinDiceSystem
+{
+ protected override void UpdateVisuals(EntityUid uid, CoinDiceComponent? die = null)
+ {
+ if (!Resolve(uid, ref die) || !TryComp(uid, out SpriteComponent? sprite))
+ return;
+
+ var state = sprite.LayerGetState(0).Name;
+ if (state == null)
+ return;
+
+ var prefix = state.Substring(0, state.IndexOf('_'));
+ sprite.LayerSetState(0, $"{prefix}_{die.CurrentValue}");
+ }
+
+}
diff --git a/Content.Server/_White/CoinDice/CoinDiceSystem.cs b/Content.Server/_White/CoinDice/CoinDiceSystem.cs
new file mode 100644
index 0000000000..7b3ce46a72
--- /dev/null
+++ b/Content.Server/_White/CoinDice/CoinDiceSystem.cs
@@ -0,0 +1,41 @@
+using Content.Shared._White.CoinDice;
+using Content.Shared.Popups;
+using JetBrains.Annotations;
+using Robust.Shared.Audio;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Random;
+
+namespace Content.Server._White.CoinDice;
+
+[UsedImplicitly]
+public sealed class CoinDiceSystem : SharedCoinDiceSystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly SharedPopupSystem _popup = default!;
+
+ public override void Roll(EntityUid uid, CoinDiceComponent? die = null)
+ {
+ if (!Resolve(uid, ref die))
+ return;
+
+ var roll = _random.Next(1, die.Sides + 1);
+ SetCurrentSide(uid, roll, die);
+ var coindiceResult = "";
+ switch (die.CurrentValue)
+ {
+ case 1:
+ coindiceResult = "орёл";
+ break;
+ case 2:
+ coindiceResult = "решка";
+ break;
+ default:
+ coindiceResult = "ребро";
+ break;
+ }
+
+ _popup.PopupEntity(Loc.GetString("coindice-component-on-roll-land", ("die", uid), ("currentSide", coindiceResult)), uid);
+ _audio.PlayPvs(die.Sound, uid);
+ }
+}
diff --git a/Content.Shared/_White/CoinDice/CoinDiceComponent.cs b/Content.Shared/_White/CoinDice/CoinDiceComponent.cs
new file mode 100644
index 0000000000..5101ae5417
--- /dev/null
+++ b/Content.Shared/_White/CoinDice/CoinDiceComponent.cs
@@ -0,0 +1,36 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+
+namespace Content.Shared._White.CoinDice;
+
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedCoinDiceSystem))]
+[AutoGenerateComponentState(true)]
+public sealed partial class CoinDiceComponent : Component
+{
+ [DataField]
+ public SoundSpecifier Sound { get; private set; } = new SoundCollectionSpecifier("Dice");
+
+ ///
+ /// Multiplier for the value of a die. Applied after the .
+ ///
+ [DataField]
+ public int Multiplier { get; private set; } = 1;
+
+ ///
+ /// Quantity that is subtracted from the value of a die. Can be used to make dice that start at "0". Applied
+ /// before the
+ ///
+ [DataField]
+ public int Offset { get; private set; } = 0;
+
+ [DataField]
+ public int Sides { get; private set; } = 20;
+
+ ///
+ /// The currently displayed value.
+ ///
+ [DataField]
+ [AutoNetworkedField]
+ public int CurrentValue { get; set; } = 20;
+
+}
diff --git a/Content.Shared/_White/CoinDice/SharedCoinDiceSystem.cs b/Content.Shared/_White/CoinDice/SharedCoinDiceSystem.cs
new file mode 100644
index 0000000000..fc563960cc
--- /dev/null
+++ b/Content.Shared/_White/CoinDice/SharedCoinDiceSystem.cs
@@ -0,0 +1,104 @@
+using Content.Shared.Examine;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Throwing;
+using Robust.Shared.Timing;
+
+namespace Content.Shared._White.CoinDice;
+
+public abstract class SharedCoinDiceSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnUseInHand);
+ SubscribeLocalEvent(OnLand);
+ SubscribeLocalEvent(OnExamined);
+ SubscribeLocalEvent(OnDiceAfterHandleState);
+ }
+
+ private void OnDiceAfterHandleState(EntityUid uid, CoinDiceComponent component, ref AfterAutoHandleStateEvent args)
+ {
+ UpdateVisuals(uid, component);
+ }
+
+ private void OnUseInHand(EntityUid uid, CoinDiceComponent component, UseInHandEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ args.Handled = true;
+ Roll(uid, component);
+ }
+
+ private void OnLand(EntityUid uid, CoinDiceComponent component, ref LandEvent args)
+ {
+ Roll(uid, component);
+ }
+
+ private void OnExamined(EntityUid uid, CoinDiceComponent dice, ExaminedEvent args)
+ {
+ //No details check, since the sprite updates to show the side.
+ using (args.PushGroup(nameof(CoinDiceComponent)))
+ {
+ args.PushMarkup(Loc.GetString("coindice-component-on-examine-message-part-1"));
+ var coindiceResult = "";
+ switch (dice.CurrentValue)
+ {
+ case 1:
+ coindiceResult = "орёл";
+ break;
+ case 2:
+ coindiceResult = "решка";
+ break;
+ default:
+ coindiceResult = "ребро";
+ break;
+ }
+ args.PushMarkup(Loc.GetString("coindice-component-on-examine-message-part-2",
+ ("currentSide", coindiceResult)));
+ }
+ }
+
+ public void SetCurrentSide(EntityUid uid, int side, CoinDiceComponent? die = null)
+ {
+ if (!Resolve(uid, ref die))
+ return;
+
+ if (side < 1 || side > die.Sides)
+ {
+ Log.Error($"Attempted to set die {ToPrettyString(uid)} to an invalid side ({side}).");
+ return;
+ }
+
+ die.CurrentValue = (side - die.Offset) * die.Multiplier;
+ Dirty(uid, die);
+ UpdateVisuals(uid, die);
+ }
+
+ public void SetCurrentValue(EntityUid uid, int value, CoinDiceComponent? die = null)
+ {
+ if (!Resolve(uid, ref die))
+ return;
+
+ if (value % die.Multiplier != 0 || value / die.Multiplier + die.Offset < 1)
+ {
+ Log.Error($"Attempted to set die {ToPrettyString(uid)} to an invalid value ({value}).");
+ return;
+ }
+
+ SetCurrentSide(uid, value / die.Multiplier + die.Offset, die);
+ }
+
+ protected virtual void UpdateVisuals(EntityUid uid, CoinDiceComponent? die = null)
+ {
+ // See client system.
+ }
+
+ public virtual void Roll(EntityUid uid, CoinDiceComponent? die = null)
+ {
+ // See the server system, client cannot predict rolling.
+ }
+
+}
diff --git a/Resources/Locale/ru-RU/dice/dice-component.ftl b/Resources/Locale/ru-RU/dice/dice-component.ftl
index 0c0ddb6e88..483f50e92b 100644
--- a/Resources/Locale/ru-RU/dice/dice-component.ftl
+++ b/Resources/Locale/ru-RU/dice/dice-component.ftl
@@ -1,3 +1,9 @@
dice-component-on-examine-message-part-1 = Кость c [color=lightgray]{ $sidesAmount }[/color] сторонами.
dice-component-on-examine-message-part-2 = Она приземлилась на [color=white]{ $currentSide }[/color].
dice-component-on-roll-land = { CAPITALIZE($die) } приземляется на { $currentSide }.
+
+# CoinDice
+
+coindice-component-on-examine-message-part-1 = Как и любая другая монетка, имеет две стороны.
+coindice-component-on-examine-message-part-2 = Сейчас на вас смотрит - [color=white]{ $currentSide }[/color].
+coindice-component-on-roll-land = { CAPITALIZE($die) } приземляется и вам выпадает - { $currentSide }.
diff --git a/Resources/Prototypes/_White/Fluff/fluff.yml b/Resources/Prototypes/_White/Fluff/fluff.yml
index fea07c33e8..297fd4a048 100644
--- a/Resources/Prototypes/_White/Fluff/fluff.yml
+++ b/Resources/Prototypes/_White/Fluff/fluff.yml
@@ -596,20 +596,23 @@
sprite: White/Fluff/Serafim547/salvage.rsi
- type: entity
- parent: BaseDice
+ parent: BaseItem
id: VulpCoin
name: Золотая монета
suffix: fluff
description: Коллекционная чеканная монета с родины вульпканин, редкая находка обладающая не столь реальной, сколько символической ценностью.
components:
- - type: Dice
+ - type: UseDelay
+ - type: CoinDice
sound:
collection: CoinDrop
sides: 2
- currentValue: 2
+ currentValue: 1
- type: Sprite
sprite: White/Fluff/Serafim547/vulp_coin.rsi
- state: coin_2
+ state: coin_1
+ - type: Item
+ size: Tiny
# Skrimex
diff --git a/Resources/Textures/White/Fluff/Serafim547/vulp_coin.rsi/coin_1.png b/Resources/Textures/White/Fluff/Serafim547/vulp_coin.rsi/coin_1.png
index 91bac2bbff..2d3ae3fb0c 100644
Binary files a/Resources/Textures/White/Fluff/Serafim547/vulp_coin.rsi/coin_1.png and b/Resources/Textures/White/Fluff/Serafim547/vulp_coin.rsi/coin_1.png differ
diff --git a/Resources/Textures/White/Fluff/Serafim547/vulp_coin.rsi/coin_2.png b/Resources/Textures/White/Fluff/Serafim547/vulp_coin.rsi/coin_2.png
index 671d686882..bb9b72909e 100644
Binary files a/Resources/Textures/White/Fluff/Serafim547/vulp_coin.rsi/coin_2.png and b/Resources/Textures/White/Fluff/Serafim547/vulp_coin.rsi/coin_2.png differ
diff --git a/Resources/Textures/White/Fluff/Serafim547/vulp_coin.rsi/meta.json b/Resources/Textures/White/Fluff/Serafim547/vulp_coin.rsi/meta.json
index e0a701d6ba..e9968cb62a 100644
--- a/Resources/Textures/White/Fluff/Serafim547/vulp_coin.rsi/meta.json
+++ b/Resources/Textures/White/Fluff/Serafim547/vulp_coin.rsi/meta.json
@@ -14,4 +14,4 @@
"name": "coin_2"
}
]
-}
+}
\ No newline at end of file