Compare commits
10 Commits
c9cb28bed9
...
f7d10a9b4f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7d10a9b4f | ||
|
|
f6749f3146 | ||
|
|
6e2247d6f3 | ||
|
|
969d9f5028 | ||
|
|
1a4789af4c | ||
|
|
3d3cb7a45e | ||
|
|
0c8d2273ea | ||
|
|
5b21c560d6 | ||
|
|
bd7d42de55 | ||
|
|
90fd231fbd |
@@ -6,6 +6,7 @@ import { socket } from '@/socket';
|
||||
|
||||
import Settings from '@/components/Settings';
|
||||
import Card from '@/components/Card';
|
||||
import Notes from '@/components/Notes';
|
||||
import NotFound from '@/components/NotFound';
|
||||
import { cardMap, layout } from '@/constants/tarokka';
|
||||
|
||||
@@ -105,6 +106,7 @@ export default function GamePage() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Notes gameData={gameData} show={cards.every(({ flipped }) => flipped)} />
|
||||
</main>
|
||||
) : null;
|
||||
}
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import type { Metadata } from 'next';
|
||||
import { Geist, Geist_Mono } from 'next/font/google';
|
||||
import { Pirata_One, Eagle_Lake, Cinzel_Decorative } from 'next/font/google';
|
||||
import './globals.css';
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: '--font-geist-sans',
|
||||
const pirataOne = Pirata_One({
|
||||
variable: '--font-pirata',
|
||||
subsets: ['latin'],
|
||||
weight: '400',
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
variable: '--font-geist-mono',
|
||||
const eagleLake = Eagle_Lake({
|
||||
variable: '--font-eagle-lake',
|
||||
subsets: ['latin'],
|
||||
weight: '400',
|
||||
});
|
||||
|
||||
const cinzel = Cinzel_Decorative({
|
||||
variable: '--font-cinzel',
|
||||
subsets: ['latin'],
|
||||
weight: '400',
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -23,8 +31,11 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>{children}</body>
|
||||
<html
|
||||
lang="en"
|
||||
className={`${pirataOne.variable} ${eagleLake.variable} ${cinzel.variable} antialiased`}
|
||||
>
|
||||
<body className={`${eagleLake.className} antialiased`}>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export default function Home() {
|
||||
<main className="min-h-screen flex items-center justify-center bg-[url('/img/table3.png')] bg-cover bg-center">
|
||||
<button
|
||||
onClick={handleCreateGame}
|
||||
className="bg-gray-800 hover:bg-gray-700 text-white text-lg px-6 py-3 rounded-xl shadow transition cursor-pointer"
|
||||
className="bg-slate-800 hover:bg-slate-700 text-yellow-400 hover:text-yellow-300 text-lg px-6 py-3 rounded-lg shadow transition-all duration-250 cursor-pointer"
|
||||
>
|
||||
Create New Game
|
||||
</button>
|
||||
|
||||
@@ -33,8 +33,8 @@ export default function Card({ dm, card, position, settings, flipAction }: CardP
|
||||
<>
|
||||
{text.map((t, i) => (
|
||||
<div key={i}>
|
||||
<p>{t}</p>
|
||||
{i < text.length - 1 && <hr className="my-2 border-gray-300" />}
|
||||
<p className="text-yellow-400">{t}</p>
|
||||
{i < text.length - 1 && <hr className="my-2 border-yellow-400" />}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
@@ -54,14 +54,14 @@ export default function Card({ dm, card, position, settings, flipAction }: CardP
|
||||
<img
|
||||
src={getURL(cardBack as TarokkaGameCard, settings)}
|
||||
alt="Card Back"
|
||||
className="rounded-lg border border-gray-600"
|
||||
className="rounded-lg border border-yellow-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="absolute group inset-0 backface-hidden rotate-y-180">
|
||||
<img
|
||||
src={getURL(card, settings)}
|
||||
alt={aria}
|
||||
className="rounded-lg border border-gray-600 "
|
||||
className="rounded-lg border border-yellow-500 "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,11 +6,18 @@ import { Copy as CopyIcon, Check as CheckIcon } from 'lucide-react';
|
||||
import ToolTip from '@/components/ToolTip';
|
||||
|
||||
type CopyButtonProps = {
|
||||
title: string;
|
||||
title?: string;
|
||||
copy: string;
|
||||
tooltip?: string | string[];
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function CopyButton({ title, copy }: CopyButtonProps) {
|
||||
export default function CopyButton({
|
||||
title,
|
||||
copy,
|
||||
tooltip = ['Copy', 'Copied'],
|
||||
className,
|
||||
}: CopyButtonProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async () => {
|
||||
@@ -23,21 +30,24 @@ export default function CopyButton({ title, copy }: CopyButtonProps) {
|
||||
}
|
||||
};
|
||||
|
||||
const ttContent = (
|
||||
<span className="text-yellow-300">
|
||||
{Array.isArray(tooltip) && tooltip.length > 1 ? (copied ? tooltip[1] : tooltip[0]) : tooltip}
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<ToolTip content={copy}>
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="w-full py-1 px-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg flex flex-col items-start gap-1 shadow transition-all cursor-pointer"
|
||||
>
|
||||
<button onClick={handleCopy} className={`cursor-pointer ${className}`}>
|
||||
<ToolTip content={ttContent} className="w-full font-yellow-400">
|
||||
<div className="flex items-center gap-2 w-full text-sm font-medium">
|
||||
{`Copy ${title}`}
|
||||
{title}
|
||||
{copied ? (
|
||||
<CheckIcon className="ml-auto" size={16} />
|
||||
) : (
|
||||
<CopyIcon className="ml-auto" size={16} />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</ToolTip>
|
||||
</ToolTip>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
78
components/Notes.tsx
Normal file
78
components/Notes.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
'use client';
|
||||
|
||||
import { useMemo, useState } from 'react';
|
||||
import { ScrollText } from 'lucide-react';
|
||||
|
||||
import CopyButton from '@/components/CopyButton';
|
||||
import Scrim from '@/components/Scrim';
|
||||
import getCardInfo from '@/tools/getCardInfo';
|
||||
import { cardMap, layout } from '@/constants/tarokka';
|
||||
|
||||
import { GameUpdate } from '@/types';
|
||||
|
||||
type NotesProps = {
|
||||
gameData: GameUpdate;
|
||||
show: boolean;
|
||||
};
|
||||
|
||||
export default function Notes({ gameData: { dmID, cards, settings }, show }: NotesProps) {
|
||||
const isDM = !!dmID;
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const notes: (string[] | undefined)[] = useMemo(
|
||||
() =>
|
||||
Array.from({ length: 9 })
|
||||
.map((_cell: unknown, index: number) => cards[cardMap[index]])
|
||||
.map((card, index) =>
|
||||
card ? getCardInfo(card, layout[cardMap[index]], isDM, settings) : null,
|
||||
)
|
||||
.map(
|
||||
(_cell: unknown, index: number, cards) =>
|
||||
cards[Object.keys(cardMap).find((key) => cardMap[key] === index) || 0],
|
||||
)
|
||||
.filter((truthy) => truthy),
|
||||
[cards, isDM, settings],
|
||||
);
|
||||
|
||||
const showNotes = show && open && (isDM || settings.notes);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed bottom-4 right-4 z-25 transition-all duration-250 ${show ? 'pointer-events-auto opacity-100' : 'pointer-events-none opacity-0'}`}
|
||||
>
|
||||
<button
|
||||
className={`text-yellow-400 hover:text-yellow-300 p-2 transition-all duration-250 cursor-pointer ${showNotes ? 'pointer-events-none opacity-0' : 'pointer-events-auto opacity-100'}`}
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
>
|
||||
<ScrollText className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
<Scrim
|
||||
clickAction={() => setOpen((prev) => !prev)}
|
||||
className={`transition-all duration-250 ${showNotes ? 'pointer-events-auto opacity-100' : 'pointer-events-none opacity-0'}`}
|
||||
>
|
||||
<div
|
||||
className={`fixed bottom-4 right-4 transition-all duration-250 bg-slate-800 border border-yellow-400 rounded-lg space-y-2 ${showNotes ? 'w-[33vw] h-[67vh]' : 'w-0 h-0'}`}
|
||||
>
|
||||
<CopyButton
|
||||
copy={notes.map((note) => note!.join('\n')).join('\n\n')}
|
||||
className="text-yellow-400 hover:drop-shadow-[0_0_1px_#ffd700] absolute top-2 right-2 p-2 transition-all duration-250 bg-black/20 hover:bg-black/40 rounded-full cursor-pointer"
|
||||
/>
|
||||
<div className="text-yellow-400 h-full overflow-scroll p-6 transition-all delay-200 duration-50 ${showNotes ? 'opacity-100' : 'opacity-0'}">
|
||||
{notes.map((note, index) => (
|
||||
<div key={index}>
|
||||
<div className="flex flex-col gap-2">
|
||||
{note!.map((blurb, index) => (
|
||||
<p key={index}>{blurb}</p>
|
||||
))}
|
||||
</div>
|
||||
{index < notes.length - 1 && <hr className="my-3 border-yellow-400" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Scrim>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
26
components/Scrim.tsx
Normal file
26
components/Scrim.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
'use client';
|
||||
|
||||
type ScrimProps = {
|
||||
children: React.ReactNode;
|
||||
clickAction: (event: React.MouseEvent<HTMLDivElement>) => void;
|
||||
show?: boolean;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function Scrim({ children, clickAction, show = true, className = '' }: ScrimProps) {
|
||||
const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
if (event.target === event.currentTarget) {
|
||||
clickAction(event);
|
||||
}
|
||||
};
|
||||
if (!show) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={handleClick}
|
||||
className={`fixed inset-0 bg-black/20 backdrop-blur-sm z-40 ${className}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,23 +1,28 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Settings as Gear, X } from 'lucide-react';
|
||||
import { Settings as Gear } from 'lucide-react';
|
||||
import { Cinzel_Decorative } from 'next/font/google';
|
||||
|
||||
import CopyButton from '@/components/CopyButton';
|
||||
import Scrim from '@/components/Scrim';
|
||||
import Switch from '@/components/Switch';
|
||||
import { CardStyle, GameUpdate } from '@/types';
|
||||
|
||||
type PermissionTogglePanelProps = {
|
||||
const cinzel = Cinzel_Decorative({
|
||||
variable: '--font-cinzel',
|
||||
subsets: ['latin'],
|
||||
weight: '400',
|
||||
});
|
||||
|
||||
type SettingsProps = {
|
||||
gameData: GameUpdate;
|
||||
changeAction: (updatedSettings: GameUpdate) => void;
|
||||
};
|
||||
|
||||
const cardStyleOptions: CardStyle[] = ['standard', 'color', 'grayscale'];
|
||||
|
||||
export default function PermissionTogglePanel({
|
||||
gameData,
|
||||
changeAction,
|
||||
}: PermissionTogglePanelProps) {
|
||||
export default function Settings({ gameData, changeAction }: SettingsProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const togglePermission = (key: string) => {
|
||||
@@ -40,66 +45,83 @@ export default function PermissionTogglePanel({
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed top-4 right-4 z-50">
|
||||
{!open && (
|
||||
<button
|
||||
className="p-2 text-gray-100 hover:text-gray-300 cursor-pointer"
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
>
|
||||
<Gear className="w-5 h-5" />
|
||||
</button>
|
||||
)}
|
||||
const Links = () => (
|
||||
<>
|
||||
<CopyButton
|
||||
title="Copy DM link"
|
||||
copy={`${location.origin}/${gameData.dmID}`}
|
||||
tooltip={`${location.origin}/${gameData.dmID}`}
|
||||
className="flex flex-row content-between w-full py-1 px-2 transition-all duration-250 bg-slate-700 hover:bg-slate-600 hover:text-yellow-300 rounded-lg shadow"
|
||||
/>
|
||||
<CopyButton
|
||||
title="Copy Spectator link"
|
||||
copy={`${location.origin}/${gameData.spectatorID}`}
|
||||
tooltip={`${location.origin}/${gameData.spectatorID}`}
|
||||
className="flex flex-row content-between w-full py-1 px-2 transition-all duration-250 bg-slate-700 hover:bg-slate-600 hover:text-yellow-300 rounded-lg shadow"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
{open && (
|
||||
<div className="relative text-gray-100 bg-gray-800 shadow-lg rounded-lg border border-gray-500 p-6 space-y-2">
|
||||
<button
|
||||
className="absolute top-1 right-1 p-1 hover:text-gray-300 cursor-pointer"
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
<CopyButton title="DM link" copy={`${location.origin}/${gameData.dmID}`} />
|
||||
<CopyButton title="Spectator link" copy={`${location.origin}/${gameData.spectatorID}`} />
|
||||
{Object.entries(gameData.settings)
|
||||
.filter(([_key, value]) => typeof value === 'boolean')
|
||||
.map(([key, value]) => (
|
||||
<Switch
|
||||
key={key}
|
||||
label={key}
|
||||
value={value}
|
||||
toggleAction={() => togglePermission(key)}
|
||||
/>
|
||||
))}
|
||||
<fieldset className="flex flex-col">
|
||||
<div className="text-xs text-gray-400 mb-1">Card style:</div>
|
||||
<div className="inline-flex overflow-hidden rounded-md w-full">
|
||||
{cardStyleOptions.map((option, index) => (
|
||||
<label
|
||||
key={option}
|
||||
className={`cursor-pointer px-4 py-2 text-sm font-medium transition
|
||||
${gameData.settings.cardStyle === option ? 'bg-gray-500 text-white' : 'bg-gray-800 text-gray-300 hover:bg-gray-700'}
|
||||
const Permissions = () => (
|
||||
<>
|
||||
{Object.entries(gameData.settings)
|
||||
.filter(([_key, value]) => typeof value === 'boolean')
|
||||
.map(([key, value]) => (
|
||||
<Switch key={key} label={key} value={value} toggleAction={() => togglePermission(key)} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
const CardStyle = () => (
|
||||
<fieldset className="flex flex-col">
|
||||
<div className="text-xs mb-1">Card style:</div>
|
||||
<div className="inline-flex overflow-hidden rounded-md w-full">
|
||||
{cardStyleOptions.map((option, index) => (
|
||||
<label
|
||||
key={option}
|
||||
className={`cursor-pointer px-4 py-2 text-sm font-medium transition
|
||||
${gameData.settings.cardStyle === option ? 'bg-slate-700 text-yellow-300 font-extrabold' : 'bg-slate-800 hover:bg-slate-700'}
|
||||
${index === 0 ? 'rounded-l-md' : ''}
|
||||
${index === cardStyleOptions.length - 1 ? 'rounded-r-md' : ''}
|
||||
${index !== 0 && 'border-l border-gray-600'}
|
||||
border border-gray-600
|
||||
border border-yellow-500 hover:text-yellow-300
|
||||
`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="cardStyle"
|
||||
value={option}
|
||||
checked={gameData.settings.cardStyle === option}
|
||||
onChange={() => tuneRadio(option)}
|
||||
className="sr-only"
|
||||
/>
|
||||
{option}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
name="cardStyle"
|
||||
value={option}
|
||||
checked={gameData.settings.cardStyle === option}
|
||||
onChange={() => tuneRadio(option)}
|
||||
className="sr-only"
|
||||
/>
|
||||
{option}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</fieldset>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`fixed top-4 right-4 z-25 ${cinzel.className}`}>
|
||||
<Scrim
|
||||
clickAction={() => setOpen((prev) => !prev)}
|
||||
className={`transition-all duration-250 ${open ? 'pointer-events-auto opacity-100' : 'pointer-events-none opacity-0'}`}
|
||||
>
|
||||
<div
|
||||
className={`fixed top-4 right-4 flex flex-col items-center justify-center bg-slate-800 text-yellow-400 rounded-lg border border-yellow-400 p-6 space-y-2 transition-all duration-250 ${open ? 'opacity-100 w-[350px] h-[300px]' : 'opacity-0 w-0 h-0'}`}
|
||||
>
|
||||
<Links />
|
||||
<Permissions />
|
||||
<CardStyle />
|
||||
</div>
|
||||
)}
|
||||
</Scrim>
|
||||
<button
|
||||
className={`p-2 transition-all duration-250 text-yellow-400 hover:text-yellow-300 cursor-pointer ${open ? 'pointer-events-none opacity-0' : 'pointer-events-auto opacity-100'}`}
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
>
|
||||
<Gear className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,20 +6,20 @@ export interface SwitchProps {
|
||||
|
||||
export default function Switch({ label, value, toggleAction }: SwitchProps) {
|
||||
return (
|
||||
<label className="flex items-center justify-between w-full gap-2 cursor-pointer">
|
||||
<label className="flex items-center justify-between w-full gap-2 cursor-pointer text-yellow-400 hover:text-yellow-300">
|
||||
<span className="text-sm capitalize">{label}</span>
|
||||
|
||||
<div className="relative inline-block w-8 h-4 align-middle select-none transition duration-200 ease-in">
|
||||
<input type="checkbox" checked={value} onChange={toggleAction} className="sr-only" />
|
||||
<div
|
||||
className={`block w-8 h-4 rounded-full transition ${
|
||||
value ? 'bg-gray-500' : 'bg-gray-600'
|
||||
value ? 'bg-slate-500' : 'bg-slate-600'
|
||||
}`}
|
||||
/>
|
||||
<div
|
||||
className={`absolute top-[2px] left-[2px] w-3 h-3 rounded-full transition-transform duration-200 ease-out transform
|
||||
${value ? 'translate-x-4 scale-110 shadow-[0_0_2px_2px_rgba(255,255,255,0.4)]' : 'scale-95'}
|
||||
${value ? 'bg-gray-100' : 'bg-gray-400'}`}
|
||||
className={`absolute top-[2px] left-[2px] w-3 h-3 rounded-full transition-all duration-250 ease-out transform
|
||||
${value ? 'translate-x-4 scale-110' : 'scale-95'}
|
||||
${value ? 'bg-yellow-400' : 'bg-yellow-500'}`}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
@@ -9,6 +9,7 @@ type TooltipProps = {
|
||||
offsetX?: number;
|
||||
offsetY?: number;
|
||||
edgeBuffer?: number;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export default function Tooltip({
|
||||
@@ -19,6 +20,7 @@ export default function Tooltip({
|
||||
offsetX = 20,
|
||||
offsetY = 20,
|
||||
edgeBuffer = 10,
|
||||
className,
|
||||
}: TooltipProps) {
|
||||
const ttRef = useRef<HTMLDivElement | null>(null);
|
||||
const [show, setShow] = useState(false);
|
||||
@@ -67,12 +69,13 @@ export default function Tooltip({
|
||||
onMouseMove={handleMouseMove}
|
||||
onTouchStart={handleTouchStart}
|
||||
onTouchEnd={handleTouchEnd}
|
||||
className={className}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div
|
||||
ref={ttRef}
|
||||
className={`fixed max-w-[35vh] pointer-events-none z-50 text-xs bg-black text-white rounded-xl border border-gray-300 px-2 py-1 transition-opacity duration-250 ${content && show ? 'opacity-100' : 'opacity-0'}`}
|
||||
className={`fixed max-w-[35vh] pointer-events-none z-50 text-xs bg-[#1e293b] rounded-lg border border-yellow-500 px-2 py-1 transition-opacity duration-250 ${content && show ? 'opacity-100' : 'opacity-0'}`}
|
||||
style={{
|
||||
top: `${pos.y + offsetY}px`,
|
||||
left: `${pos.x + offsetX}px`,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"dev": "nodemon",
|
||||
"build": "next build && tsc --project tsconfig.server.json",
|
||||
"start": "cross-env NODE_ENV=production TS_NODE_BASEURL=./build node -r tsconfig-paths/register build/server.js",
|
||||
"deploy": "docker buildx build --platform linux/amd64 -t nasty.mcmorgans:5000/tarokka --push ."
|
||||
"deploy": "docker buildx build --platform linux/amd64 -t 192.168.0.2:5000/tarokka --push ."
|
||||
},
|
||||
"dependencies": {
|
||||
"cross-env": "^7.0.3",
|
||||
|
||||
@@ -19,15 +19,15 @@ export default function getTooltip(
|
||||
if (isHighCard(card)) {
|
||||
// High deck ally
|
||||
if (position.id === 'ally') {
|
||||
if (dm) text.push(`Ally: ${card.prophecy.allies[0].ally}`);
|
||||
if (dm) text.push(card.prophecy.allies[0].dmText);
|
||||
if (dm || settings.prophecy) text.push(card.prophecy.allies[0].playerText);
|
||||
if (dm) text.push(card.prophecy.allies[0].dmText);
|
||||
if (dm) text.push(`Ally: ${card.prophecy.allies[0].ally}`);
|
||||
}
|
||||
|
||||
// High deck Strahd
|
||||
if (position.id === 'strahd') {
|
||||
if (dm) text.push(card.prophecy.strahd.dmText);
|
||||
if (dm || settings.prophecy) text.push(card.prophecy.strahd.playerText);
|
||||
if (dm) text.push(card.prophecy.strahd.dmText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user