add vouchers display

This commit is contained in:
2025-07-12 12:37:09 +02:00
parent c54ce4bec1
commit 168c141d64
37 changed files with 336 additions and 3 deletions

View File

@@ -34,6 +34,7 @@ import {
TooltipTrigger,
} from '@/components/ui/tooltip'
import { jokers } from '@/shared/jokers'
import { vouchers } from '@/shared/vouchers'
import { useFormatter } from 'next-intl'
import Image from 'next/image'
import { useSearchParams } from 'next/navigation'
@@ -107,7 +108,12 @@ type Game = {
logOwnerFinalJokers: string[] // Log owner's final jokers
opponentFinalJokers: string[] // Opponent's final jokers
events: LogEvent[]
rerolls: number
rerolls: number // Log owner's reroll count
rerollCostTotal: number // Log owner's total reroll cost
logOwnerVouchers: string[] // Log owner's vouchers
opponentRerolls: number // Opponent's reroll count
opponentRerollCostTotal: number // Opponent's total reroll cost
opponentVouchers: string[] // Opponent's vouchers
winner: 'logOwner' | 'opponent' | null // Who won the game
pvpBlinds: PvpBlind[] // PVP blind data
currentPvpBlind: number | null // Current PVP blind number
@@ -140,6 +146,11 @@ const initGame = (id: number, startDate: Date): Game => ({
opponentFinalJokers: [],
events: [],
rerolls: 0,
rerollCostTotal: 0,
logOwnerVouchers: [],
opponentRerolls: 0,
opponentRerollCostTotal: 0,
opponentVouchers: [],
winner: null,
pvpBlinds: [],
currentPvpBlind: null,
@@ -347,6 +358,34 @@ export default function LogParser() {
}
continue
}
if (line.includes('Client got nemesisEndGameStats message')) {
if (currentGame) {
// Extract Opponent Reroll Count
const rerollCountMatch = line.match(/\(reroll_count: (\d+)\)/)
if (rerollCountMatch?.[1]) {
currentGame.opponentRerolls = Number.parseInt(
rerollCountMatch[1],
10
)
}
// Extract Opponent Reroll Cost Total
const rerollCostMatch = line.match(/\(reroll_cost_total: (\d+)\)/)
if (rerollCostMatch?.[1]) {
currentGame.opponentRerollCostTotal = Number.parseInt(
rerollCostMatch[1],
10
)
}
// Extract Opponent Vouchers
const vouchersMatch = line.match(/\(vouchers: ([^)]+)\)/)
if (vouchersMatch?.[1]) {
currentGame.opponentVouchers = vouchersMatch[1].split('-')
}
}
continue
}
if (line.includes('Client sent message: action:receiveEndGameJokers')) {
if (currentGame) {
// Mark end date if not already set (might happen slightly before 'got')
@@ -362,6 +401,31 @@ export default function LogParser() {
}
continue
}
if (line.includes('Client sent message: action:nemesisEndGameStats')) {
if (currentGame) {
// Extract Log Owner Reroll Count
const rerollCountMatch = line.match(/reroll_count:(\d+)/)
if (rerollCountMatch?.[1]) {
currentGame.rerolls = Number.parseInt(rerollCountMatch[1], 10)
}
// Extract Log Owner Reroll Cost Total
const rerollCostMatch = line.match(/reroll_cost_total:(\d+)/)
if (rerollCostMatch?.[1]) {
currentGame.rerollCostTotal = Number.parseInt(
rerollCostMatch[1],
10
)
}
// Extract Log Owner Vouchers
const vouchersMatch = line.match(/vouchers:(.+)$/)
if (vouchersMatch?.[1]) {
currentGame.logOwnerVouchers = vouchersMatch[1].split('-')
}
}
continue
}
if (lineLower.includes('startgame message')) {
if (currentGame) {
if (!currentGame.endDate) currentGame.endDate = timestamp
@@ -855,7 +919,8 @@ export default function LogParser() {
? ((currentGame.endDate instanceof Date
? currentGame.endDate.getTime()
: new Date(currentGame.endDate).getTime()) -
currentGame.startDate.getTime()) / 1000
currentGame.startDate.getTime()) /
1000
: null
games.push(currentGame)
}
@@ -1049,8 +1114,18 @@ export default function LogParser() {
: opponentLabel}
</p>
<p>
<strong>Rerolls:</strong>{' '}
<strong>{ownerLabel} Rerolls:</strong>{' '}
{game.rerolls || 'Unknown'}
{game.rerollCostTotal
? ` (Cost: ${game.rerollCostTotal})`
: ''}
</p>
<p>
<strong>{opponentLabel} Rerolls:</strong>{' '}
{game.opponentRerolls || 'Unknown'}
{game.opponentRerollCostTotal
? ` (Cost: ${game.opponentRerollCostTotal})`
: ''}
</p>
<p>
<strong>Deck:</strong> {game.deck || 'Unknown'}
@@ -1277,6 +1352,93 @@ export default function LogParser() {
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className='text-lg'>Vouchers</CardTitle>
</CardHeader>
<CardContent className='space-y-3 text-sm'>
<div>
<strong>
{ownerLabel}
{game.winner === 'logOwner' ? ' 🏆' : ''}:
</strong>
{game.logOwnerVouchers.length > 0 ? (
<ul className='mt-3 ml-4 flex list-inside gap-3'>
{game.logOwnerVouchers.map((voucher, i) => {
if (!voucher) {
return null
}
const cleanName =
vouchers[voucher]?.name ??
cleanVoucherKey(voucher)
return (
// biome-ignore lint/suspicious/noArrayIndexKey: Simple list
<li key={i} className={'list-none'}>
<div
className={
'flex flex-col items-center justify-center gap-2'
}
>
<Image
src={`/cards/${voucher}.png`}
alt={cleanName}
width={142}
height={190}
/>
<span>{cleanName}</span>
</div>
</li>
)
})}
</ul>
) : (
<p className='text-gray-500 italic'>
No data found.
</p>
)}
</div>
<div>
<strong>
{opponentLabel}
{game.winner === 'opponent' ? ' 🏆' : ''}:
</strong>
{game.opponentVouchers.length > 0 ? (
<ul className='mt-3 ml-4 flex list-inside gap-3'>
{game.opponentVouchers.map((voucher, i) => {
if (!voucher) {
return null
}
const cleanName =
vouchers[voucher]?.name ??
cleanVoucherKey(voucher)
return (
// biome-ignore lint/suspicious/noArrayIndexKey: Simple list
<li key={i} className={'list-none'}>
<div
className={
'flex flex-col items-center justify-center gap-2'
}
>
<Image
src={`/cards/${voucher}.png`}
alt={cleanName}
width={142}
height={190}
/>
<span>{cleanName}</span>
</div>
</li>
)
})}
</ul>
) : (
<p className='text-gray-500 italic'>
No data found.
</p>
)}
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className='text-lg'>Mods</CardTitle>
@@ -1637,6 +1799,18 @@ function cleanJokerKey(key: string): string {
)
}
function cleanVoucherKey(key: string): string {
if (!key) return ''
return key
.trim()
.replace(/^v_/, '') // Remove prefix v_
.replace(/_/g, ' ') // Replace underscores with spaces
.replace(
/\w\S*/g, // Capitalize each word (Title Case)
(txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
)
}
type JsonValue =
| string
| number

41
src/shared/vouchers.ts Normal file
View File

@@ -0,0 +1,41 @@
export interface VoucherInfo {
name: string
file: string
}
export const vouchers: { [key: string]: VoucherInfo } = {
v_antimatter: { name: 'Antimatter', file: 'v_antimatter.png' },
v_blank: { name: 'Blank', file: 'v_blank.png' },
v_clearance_sale: { name: 'Clearance Sale', file: 'v_clearance_sale.png' },
v_crystal_ball: { name: 'Crystal Ball', file: 'v_crystal_ball.png' },
v_directors_cut: { name: "Director's Cut", file: 'v_directors_cut.png' },
v_glow_up: { name: 'Glow Up', file: 'v_glow_up.png' },
v_grabber: { name: 'Grabber', file: 'v_grabber.png' },
v_hieroglyph: { name: 'Hieroglyph', file: 'v_hieroglyph.png' },
v_hone: { name: 'Hone', file: 'v_hone.png' },
v_illusion: { name: 'Illusion', file: 'v_illusion.png' },
v_liquidation: { name: 'Liquidation', file: 'v_liquidation.png' },
v_locked: { name: 'Locked', file: 'v_locked.png' },
v_magic_trick: { name: 'Magic Trick', file: 'v_magic_trick.png' },
v_money_tree: { name: 'Money Tree', file: 'v_money_tree.png' },
v_nacho_tong: { name: 'Nacho Tong', file: 'v_nacho_tong.png' },
v_observatory: { name: 'Observatory', file: 'v_observatory.png' },
v_omen_globe: { name: 'Omen Globe', file: 'v_omen_globe.png' },
v_overstock_norm: { name: 'Overstock', file: 'v_overstock_norm.png' },
v_overstock_plus: { name: 'Overstock Plus', file: 'v_overstock_plus.png' },
v_paint_brush: { name: 'Paint Brush', file: 'v_paint_brush.png' },
v_palette: { name: 'Palette', file: 'v_palette.png' },
v_petroglyph: { name: 'Petroglyph', file: 'v_petroglyph.png' },
v_planet_merchant: { name: 'Planet Merchant', file: 'v_planet_merchant.png' },
v_planet_tycoon: { name: 'Planet Tycoon', file: 'v_planet_tycoon.png' },
v_recyclomancy: { name: 'Recyclomancy', file: 'v_recyclomancy.png' },
v_reroll_glut: { name: 'Reroll Glut', file: 'v_reroll_glut.png' },
v_reroll_surplus: { name: 'Reroll Surplus', file: 'v_reroll_surplus.png' },
v_retcon: { name: 'Retcon', file: 'v_retcon.png' },
v_seed_money: { name: 'Seed Money', file: 'v_seed_money.png' },
v_tarot_merchant: { name: 'Tarot Merchant', file: 'v_tarot_merchant.png' },
v_tarot_tycoon: { name: 'Tarot Tycoon', file: 'v_tarot_tycoon.png' },
v_telescope: { name: 'Telescope', file: 'v_telescope.png' },
v_undiscovered: { name: 'Locked', file: 'v_undiscovered.png' },
v_wasteful: { name: 'Wasteful', file: 'v_wasteful.png' },
}