Skip to content

Commit 5bf3974

Browse files
authored
Botc (#400)
* Updated Modal and Token menu * Added some text formatting
1 parent 68cf644 commit 5bf3974

5 files changed

Lines changed: 106 additions & 46 deletions

File tree

src/app/botc/blood-on-the-clocktower.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import { IconUser } from '@tabler/icons-react';
3+
import { IconScript, IconUser } from '@tabler/icons-react';
44
import Button from 'components/input/button';
55
import Select from 'components/input/select';
66
import Modal from 'components/modal';
@@ -18,6 +18,7 @@ import {
1818
import NightOrder from './night-order';
1919
import Grimoire from './grimoire';
2020
import { shuffle, zip } from 'utils/array';
21+
import ParamsTextInput from 'components/input/params-text-input';
2122

2223
export const metadata: Metadata = {
2324
title: 'Blood on the Clocktower',
@@ -73,7 +74,8 @@ const BloodOnTheClocktowerElement = ({
7374

7475
return (
7576
<div className='flex max-w-3xl flex-col gap-2'>
76-
<h2>Bleck on the Corpstower</h2>
77+
<h2 className='hidden lg:block'>Bleck on the Corpstower</h2>
78+
<h3 className='lg:hidden'>Bleck on the Corpstower</h3>
7779
<details open={detailsStartOpen} className='border p-2 shadow-md'>
7880
<summary className='select-none'>Setup</summary>
7981

@@ -88,6 +90,13 @@ const BloodOnTheClocktowerElement = ({
8890
}}
8991
value={gameState.editionId}
9092
/>
93+
{gameState.editionId === 'custom' && (
94+
<ParamsTextInput
95+
label='Script URL'
96+
icon={<IconScript />}
97+
paramName='scriptUrl'
98+
/>
99+
)}
91100
<div className='h-2' />
92101
{edition && (
93102
<div className='flex gap-4'>
@@ -147,7 +156,9 @@ const BloodOnTheClocktowerElement = ({
147156
<summary className='select-none'>Grimoire</summary>
148157
<Grimoire
149158
players={gameState.players}
150-
characters={gameState.characters}
159+
setPlayers={(players) => {
160+
setGameState({ ...gameState, players });
161+
}}
151162
/>
152163
</details>
153164
{edition && gameState.players.length > 0 && (

src/app/botc/character-token.tsx

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,73 @@
11
import Modal from 'components/modal';
22
import {
3+
BOTCPlayer,
34
CharacterId,
45
CHARACTERS,
56
FIRST_NIGHT_TEXT,
67
getImagePathFromId,
78
OTHER_NIGHTS_TEXT,
89
} from './characters';
10+
import { cn } from 'utils/class-names';
11+
import Button from 'components/input/button';
12+
import { useState } from 'react';
913

1014
interface CharacterTokenProps {
1115
playerName?: string;
1216
characterId?: CharacterId;
17+
dead?: boolean;
18+
players: BOTCPlayer[];
19+
playerIndex: number;
20+
setPlayers: (newPlayers: BOTCPlayer[]) => void;
1321
}
1422

1523
const FIRST_NIGHT_CHARACTERS = new Set(FIRST_NIGHT_TEXT.map(([id, _]) => id));
1624
const OTHER_NIGHT_CHARACTERS = new Set(OTHER_NIGHTS_TEXT.map(([id, _]) => id));
1725

18-
const CharacterToken = ({ playerName, characterId }: CharacterTokenProps) => {
26+
const CharacterToken = ({
27+
playerName,
28+
characterId,
29+
dead = false,
30+
players,
31+
playerIndex,
32+
setPlayers,
33+
}: CharacterTokenProps) => {
1934
const character = characterId ? CHARACTERS[characterId] : undefined;
2035
const hasLeftLeaf = characterId && FIRST_NIGHT_CHARACTERS.has(characterId);
2136
const hasRightLeaf = characterId && OTHER_NIGHT_CHARACTERS.has(characterId);
37+
const [modalOpen, setModalOpen] = useState(false);
38+
39+
const killOrRevivePlayer = () => {
40+
const newPlayers = players.slice();
41+
const player = newPlayers[playerIndex];
42+
if (!player) throw new Error('Invalid playerIndex in CharacterToken');
43+
player.isAlive = dead;
44+
setModalOpen(false);
45+
setPlayers(newPlayers);
46+
};
2247

2348
return (
2449
<Modal
50+
title={
51+
playerName
52+
? playerName + (character ? `, the ${character.name}` : '')
53+
: character
54+
? character.name
55+
: 'Token'
56+
}
57+
open={modalOpen}
58+
onFocus={() => {
59+
setModalOpen(true);
60+
}}
61+
onBlur={() => {
62+
setModalOpen(false);
63+
}}
2564
target={
26-
<div className='relative mt-1 h-32 w-32 rounded-full bg-[repeat] bg-[url(/botc/token-noise.webp)] bg-auto text-center shadow-md'>
65+
<div
66+
className={cn(
67+
'relative mt-1 h-32 w-32 rounded-full bg-[repeat] bg-[url(/botc/token-noise.webp)] bg-auto text-center shadow-md',
68+
dead && 'grayscale',
69+
)}
70+
>
2771
<h5 className='absolute -top-1 left-1/2 -translate-x-1/2 rounded bg-red-600 px-2 text-white'>
2872
{playerName}
2973
</h5>
@@ -69,7 +113,9 @@ const CharacterToken = ({ playerName, characterId }: CharacterTokenProps) => {
69113
</div>
70114
}
71115
>
72-
TEST
116+
<Button onClick={killOrRevivePlayer}>
117+
{dead ? 'Revive' : 'Kill'} player
118+
</Button>
73119
</Modal>
74120
);
75121
};

src/app/botc/grimoire.tsx

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,23 @@
11
import CharacterToken from './character-token';
2-
import { BOTCPlayer, CharacterId } from './characters';
2+
import { BOTCPlayer } from './characters';
33

44
interface GrimoireProps {
55
players: BOTCPlayer[];
6-
characters: CharacterId[];
6+
setPlayers: (newPlayers: BOTCPlayer[]) => void;
77
}
88

9-
const playerNames = [
10-
'Hannes',
11-
'Hannes2',
12-
'Bartolomeus',
13-
'Kyoto',
14-
'Jörgen',
15-
'Pratkvarn',
16-
'Pelle',
17-
'Lars',
18-
];
19-
20-
const Grimoire = ({ characters }: GrimoireProps) => {
21-
const fakePlayers = characters.map((characterId, i) => ({
22-
characterId,
23-
name: i < playerNames.length ? (playerNames[i] as string) : undefined,
24-
}));
9+
const Grimoire = ({ players, setPlayers }: GrimoireProps) => {
2510
return (
2611
<div className='grid grid-cols-2 gap-4 lg:grid-cols-5'>
27-
{fakePlayers.map((player) => (
12+
{players.map((player, i) => (
2813
<CharacterToken
2914
key={(player.name ?? '') + player.characterId}
3015
playerName={player.name}
3116
characterId={player.characterId}
17+
players={players}
18+
playerIndex={i}
19+
setPlayers={setPlayers}
20+
dead={!player.isAlive}
3221
/>
3322
))}
3423
</div>

src/app/botc/night-order.tsx

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ const NightOrder = ({ players, allCharacters }: NightOrderProps) => {
3535

3636
const gameCharacters = players.map((p) => p.characterId);
3737
const deadCharacters = players
38-
.filter((p) => p.isAlive)
38+
.filter((p) => !p.isAlive)
3939
.map((p) => p.characterId);
4040
let nightOrder = showFirstNight
4141
? allNightOrders.firstNight
@@ -48,9 +48,30 @@ const NightOrder = ({ players, allCharacters }: NightOrderProps) => {
4848
}
4949

5050
return (
51-
<div>
51+
<div className='flex flex-col'>
52+
<div className='flex flex-col gap-4 p-2 pt-3 lg:flex-row'>
53+
<Switch
54+
label='Show dead characters'
55+
value={showDeadCharacters}
56+
onChange={() => {
57+
setShowDeadCharacters(!showDeadCharacters);
58+
}}
59+
/>
60+
<Switch
61+
label='Show characters not in play'
62+
value={showCharactersNotInPlay}
63+
onChange={() => {
64+
if (!showCharactersNotInPlay) {
65+
setShowCharactersNotInPlay(true);
66+
setShowDeadCharacters(true);
67+
} else {
68+
setShowCharactersNotInPlay(false);
69+
}
70+
}}
71+
/>
72+
</div>
5273
<Select
53-
label='Show'
74+
label='Show nights'
5475
options={[
5576
{ value: 'first', label: 'First night' },
5677
{ value: 'other', label: 'Following nights' },
@@ -74,28 +95,12 @@ const NightOrder = ({ players, allCharacters }: NightOrderProps) => {
7495
)}
7596
{nightOrder.map(([id, text]) => (
7697
<NightOrderEntry
77-
muted={!gameCharacters.includes(id)}
98+
muted={deadCharacters.includes(id) || !gameCharacters.includes(id)}
7899
key={`${id}night${showFirstNight ? 'FirstNight' : 'OtherNights'}`}
79100
characterId={id}
80101
text={text}
81102
/>
82103
))}
83-
<div className='flex flex-col gap-4 p-2 pt-3 lg:flex-row'>
84-
<Switch
85-
label='Show dead characters'
86-
value={showDeadCharacters}
87-
onChange={() => {
88-
setShowDeadCharacters(!showDeadCharacters);
89-
}}
90-
/>
91-
<Switch
92-
label='Show characters not in play'
93-
value={showCharactersNotInPlay}
94-
onChange={() => {
95-
setShowCharactersNotInPlay(!showCharactersNotInPlay);
96-
}}
97-
/>
98-
</div>
99104
</div>
100105
);
101106
};

src/components/modal/index.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import { IconX } from '@tabler/icons-react';
44
import Divider from 'components/divider';
55
import ActionIcon from 'components/input/action-icon';
6-
import { ReactNode, useState } from 'react';
6+
import { ReactNode, useEffect, useState } from 'react';
77
import { cn } from 'utils/class-names';
88

99
interface ModalProps {
10+
open?: boolean;
1011
target?: ReactNode;
1112
children: ReactNode;
1213
title?: ReactNode;
@@ -18,6 +19,7 @@ interface ModalProps {
1819
}
1920

2021
const Modal = ({
22+
open: openProp,
2123
target,
2224
children,
2325
title,
@@ -38,6 +40,13 @@ const Modal = ({
3840
setOpenValue(value);
3941
};
4042

43+
useEffect(() => {
44+
if (openProp === undefined) {
45+
return;
46+
}
47+
setOpen(openProp);
48+
}, [openProp]);
49+
4150
const handleClick = () => {
4251
if (!open) {
4352
setOpen(true);

0 commit comments

Comments
 (0)