- {reel.map((slot, i) => (
-
+
+
+
+
+
+ {reels.map((reel, i) => (
+
+
+
+ ))}
+
+
+
+
+ act('payout')} disabled={balance <= 0}>
+ Refund
+
+ }
+ >
+
+ {formatMoney(balance)} cr
+
+
+
+
+
+ act('spin')}
+ disabled={spinning || balance < cost}
+ >
+ Spin!
+
+
+
+
+
+
+ );
+};
+
+const getBannerPages = () => [
+ BannerTitle,
+ BannerOnlyFewCreds,
+ BannerPrizeMoney,
+ BannerTitle,
+ BannerJackpot,
+ BannerStats,
+];
+
+const BANNER_TEXTS = [
+ 'SPIN! SPIN!',
+ 'WARMED UP!',
+ 'HOT SLOTS',
+ 'SPIN & WIN!',
+ 'BELIEVE IT!',
+ 'ZERO 2 HERO!',
+ 'JUST ONE MORE',
+ 'SPIN OF FATE',
+ 'BONUS TIME!',
+ 'BORN TO SPIN!',
+ 'NICE SPIN!',
+ 'NO SPIN NO WIN',
+ 'BET & FORGET',
+ 'HONK 4 LUCK',
+ 'DEBT 4 LIFE',
+ 'SPINGULARITY',
+ 'SPIN CITY',
+ 'BURN & EARN',
+ 'JACKPOT SOON!',
+ 'WIN THE DAY!',
+ 'FORTUNE CALLS',
+ 'INSTANT GOLD!',
+ 'DREAM BIGGER!',
+ 'WINNERS ONLY!',
+ 'SPIN IS LIFE',
+ 'BIG ONE SOON!',
+ 'LUCKY SPIN!',
+ 'CASH OUT? NO!',
+];
+
+const WINNING_TEXTS = [
+ null,
+ 'FREE SPINS!',
+ 'PRIZE!',
+ 'BIG PRIZE!',
+ 'JACKPOT!!!',
+];
+
+const Banner = () => {
+ const { data } = useBackend
();
+ const [page, setPage] = useState(0);
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setPage((page) => (page + 1) % getBannerPages().length);
+ }, 5000);
+ return () => clearInterval(interval);
+ }, []);
+
+ const winningText = WINNING_TEXTS[data.winning];
+ if (winningText) {
+ return (
+
+ );
+ }
+
+ const Component = getBannerPages()[page];
+
+ return (
+
+ );
+};
+
+type BannerTitleProps = {
+ text?: string;
+};
+
+const BannerTitle = (props: BannerTitleProps) => {
+ const { data } = useBackend();
+ const defaultText = useRef(pickRandom(BANNER_TEXTS));
+ let text = props.text;
+ if (!text) {
+ if (data.balance <= 0) {
+ text = 'INSERT COIN';
+ } else if (data.balance <= 5) {
+ text = 'ONE LAST SPIN';
+ } else {
+ text = defaultText.current;
+ }
+ }
+ const letters = text.split('');
+ return (
+
+ {letters.map((letter, i) => (
+ {letter}
))}
);
};
-const SlotsTile = (props: SlotsTileProps) => {
+const BannerOnlyFewCreds = () => {
+ const { data } = useBackend();
+ const variant = useRef(pickRandom([0, 1]));
+
+ if (variant.current === 1) {
+ return (
+
+ For only{' '}
+
+ {data.cost}
+ {' '}
+ credit{pluralS(data.cost)}!
+
+
+ You can fix all your problems!
+
+
+ );
+ }
+
return (
-
-
+
+ Only{' '}
+
+ {data.cost}
+ {' '}
+ credit{pluralS(data.cost)} for a chance
+
+ to win big !
);
};
-export const SlotMachine = (props) => {
- const { act, data } = useBackend
();
- // icons: The list of possible icons, including colour and name
- // backendState: the current state of the slots according to the backend
- const {
- plays,
- jackpots,
- money,
- cost,
- state,
- balance,
- jackpot,
- working: rolling,
- paymode,
- } = data;
+const BannerPrizeMoney = () => {
+ const { data } = useBackend();
+ return (
+
+ Available prize money:
+
+
+ {data.money} credit{pluralS(data.money)}
+
+
+ );
+};
+const BannerJackpot = () => {
+ const { data } = useBackend();
return (
-
-
-
-
- Only {cost} credit{pluralS(cost)} for a chance to win big!
-
-
- Available prize money:{' '}
-
- {money} credit{pluralS(money)}
- {' '}
-
- {paymode === 1 && (
-
- Current jackpot:{' '}
-
- {money + jackpot} credit{pluralS(money + jackpot)}!
-
-
- )}
-
- So far people have spun{' '}
-
- {plays} time{pluralS(plays)},
- {' '}
- and won{' '}
-
- {jackpots} jackpot{pluralS(jackpots)}!
-
-
-
-
-
+ Current jackpot:
+
+
+ {data.money + data.jackpot} credit{pluralS(data.money + data.jackpot)}!
+
+
+ );
+};
+
+const BannerStats = () => {
+ const { data } = useBackend();
+ return (
+
+
+ So far people have spun{' '}
+
+ {data.plays} time{pluralS(data.plays)}
+
+
+
+ and won{' '}
+
+ {data.jackpots} jackpot{pluralS(data.jackpots)}!
+
+
+ );
+};
+
+const ICON_STRIP_LENGTH = 30;
+
+type IconStripProps = {
+ icons: string[];
+ iconsNeeded: string[];
+ spinning?: boolean;
+};
+
+const IconStrip = (props: IconStripProps) => {
+ const { icons, iconsNeeded, spinning } = props;
+
+ const [drawnIcons, setDrawnIcons] = useState([
+ ...pickRandomMany(icons, ICON_STRIP_LENGTH - 3),
+ ...iconsNeeded,
+ ]);
+
+ useEffect(() => {
+ if (spinning) {
+ setDrawnIcons((drawnIcons) => [
+ ...drawnIcons.slice(-3),
+ ...pickRandomMany(icons, ICON_STRIP_LENGTH - 6),
+ ...iconsNeeded,
+ ]);
+ } else {
+ setDrawnIcons([
+ ...pickRandomMany(icons, ICON_STRIP_LENGTH - 3),
+ ...iconsNeeded,
+ ]);
+ }
+ }, [spinning]);
+
+ return (
+
+ {drawnIcons.map((icon, i) => (
+
- {state.map((reel, i) => {
- return ;
- })}
-
-
- act('spin')}
- disabled={rolling || balance < cost}
- >
- Spin!
-
-
- Balance: {balance}
-
- act('payout')} disabled={!(balance > 0)}>
- Refund balance
-
-
-
-
+ />
+ ))}
+
);
};
diff --git a/tgui/packages/tgui/styles/interfaces/SlotMachine.scss b/tgui/packages/tgui/styles/interfaces/SlotMachine.scss
new file mode 100644
index 000000000000..82599f53d22c
--- /dev/null
+++ b/tgui/packages/tgui/styles/interfaces/SlotMachine.scss
@@ -0,0 +1,181 @@
+.SlotMachine__Banner {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 60px;
+ font-size: 18px;
+ text-align: center;
+ text-shadow:
+ 2px 2px 6px #000,
+ 0 2px #000,
+ 0 -2px #000,
+ 2px 0 #000,
+ -2px 0 #000;
+
+ background: linear-gradient(-45deg, #d16949, #ce3771, #229dca, #189175);
+ background-size: 400% 400%;
+ animation: SlotMachine__GradientMove 10s ease infinite;
+}
+
+@keyframes SlotMachine__GradientMove {
+ 0% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+ 100% {
+ background-position: 0% 50%;
+ }
+}
+
+.SlotMachine__Banner--winning {
+ animation: SlotMachine__Winning 0.4s step-end infinite !important;
+}
+
+@keyframes SlotMachine__Winning {
+ 0% {
+ color: #ff0;
+ background: #f00;
+ }
+ 50% {
+ color: #f00;
+ background: #ff0;
+ }
+ 100% {
+ color: #f00;
+ background: #ff0;
+ }
+}
+
+.SlotMachine__BannerTitle {
+ font-size: 26px;
+ font-weight: bold;
+ font-family: 'Courier New', Courier, monospace;
+ white-space: pre;
+}
+
+.SlotMachine__BannerTitle span {
+ animation:
+ SlotMachine__Depth1 1.2s ease-in-out alternate infinite,
+ SlotMachine__Depth2 0.8s ease-in-out alternate infinite;
+ animation-composition: replace, add;
+ position: relative;
+ display: inline-block;
+ padding: 0 0.075em;
+ @for $i from 1 through 14 {
+ &:nth-child(#{$i}) {
+ animation-delay: $i * -0.2s;
+ }
+ }
+}
+
+@keyframes SlotMachine__Depth1 {
+ 0% {
+ transform: translatex(-0.2em) translatey(-0.1em);
+ }
+ 100% {
+ transform: translatex(0.2em) translatey(0.1em);
+ }
+}
+
+@keyframes SlotMachine__Depth2 {
+ 0% {
+ transform: translatex(0.1em) translatey(-0.1em);
+ }
+ 100% {
+ transform: translatex(-0.1em) translatey(0.1em);
+ }
+}
+
+.SlotMachine__Reels {
+ display: flex;
+ margin: 0 -3px;
+}
+
+.SlotMachine__Reel {
+ position: relative;
+ flex: 1;
+ margin: 0 3px;
+ overflow: hidden;
+ height: 180px;
+ background: linear-gradient(
+ to bottom,
+ #888 0%,
+ #ccc 15%,
+ #eee 40%,
+ #ddd 60%,
+ #aaa 85%,
+ #666 100%
+ );
+}
+
+.SlotMachine__Reel::after {
+ position: absolute;
+ top: 58px;
+ left: 0;
+ bottom: 58px;
+ right: 0;
+ display: block;
+ content: '';
+ border-top: 2px solid black;
+ border-bottom: 2px solid black;
+}
+
+.SlotMachine__Reel:nth-child(1) .SlotMachine__IconStrip {
+ animation-delay: 0s;
+ animation-duration: 2.8s;
+}
+
+.SlotMachine__Reel:nth-child(2) .SlotMachine__IconStrip {
+ animation-delay: 0.1s;
+ animation-duration: 3s;
+}
+
+.SlotMachine__Reel:nth-child(3) .SlotMachine__IconStrip {
+ animation-delay: 0.2s;
+ animation-duration: 3.2s;
+}
+
+.SlotMachine__Reel:nth-child(4) .SlotMachine__IconStrip {
+ animation-delay: 0.3s;
+ animation-duration: 3.4s;
+}
+
+.SlotMachine__Reel:nth-child(5) .SlotMachine__IconStrip {
+ animation-delay: 0.4s;
+ animation-duration: 3.6s;
+}
+
+.SlotMachine__IconStrip {
+ transform: translateY(calc(-60px * 27));
+ // animation-duration: 3.5s;
+ text-align: center;
+ // animation: scroll-up 2s linear infinite;
+ // animation-play-state: paused;
+ // transform: translateY(0px);
+ text-shadow:
+ 2px 2px 6px #000,
+ 0 2px #000,
+ 0 -2px #000,
+ 2px 0 #000,
+ -2px 0 #000;
+}
+
+.SlotMachine__IconStrip--spinning {
+ transform: translateY(0);
+ animation-name: SlotMachine__Spin;
+ animation-iteration-count: 1;
+ animation-fill-mode: forwards;
+ animation-timing-function: cubic-bezier(1, 1.26, 0.88, 0.96);
+}
+
+@keyframes SlotMachine__Spin {
+ 0% {
+ transform: translateY(0);
+ }
+ 100% {
+ transform: translateY(calc(-60px * 27));
+ }
+}
diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss
index 67289ecb7476..63381f34492e 100644
--- a/tgui/packages/tgui/styles/main.scss
+++ b/tgui/packages/tgui/styles/main.scss
@@ -40,6 +40,7 @@
@include meta.load-css('./interfaces/RequestManager.scss');
@include meta.load-css('./interfaces/Roulette.scss');
@include meta.load-css('./interfaces/Safe.scss');
+@include meta.load-css('./interfaces/SlotMachine.scss');
@include meta.load-css('./interfaces/Techweb.scss');
@include meta.load-css('./interfaces/Trophycase.scss');
@include meta.load-css('./interfaces/Uplink.scss');
diff --git a/tools/UpdatePaths/Scripts/89706_dankpocket.txt b/tools/UpdatePaths/Scripts/89706_dankpocket.txt
new file mode 100644
index 000000000000..8f663909e727
--- /dev/null
+++ b/tools/UpdatePaths/Scripts/89706_dankpocket.txt
@@ -0,0 +1,2 @@
+# dankpockets are now a subtype of donkpockets
+/obj/item/food/dankpocket : /obj/item/food/donkpocket/dank{@OLD}