Support named ordinary cards in Advanced Voice
This commit is contained in:
1 parent
b00d720e40
commit
a244aeb5f8
20 files changed
+514
-41
No files matched your search
@@ -7,7 +7,7 @@ import {
|
||||
isStoneBurner,
|
||||
isMirrorWeapon,
|
||||
isDefenseCard,
|
||||
VOICE_KINDS,
|
||||
voiceChoices,
|
||||
} from '@/game/battle-cards';
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -150,15 +150,15 @@ export function BattlePreparation({
|
||||
<p className="muted">Both combatants continue in attacker order before Prescience or sealed plans. Only an unallied holder may spend the card.</p>
|
||||
{offer.blocked && <p className="notice">{offer.blocked}</p>}
|
||||
{offer.canUse && <>
|
||||
<label htmlFor="borrowed-voice-kind">Card type
|
||||
<label htmlFor="borrowed-voice-kind">Card or type
|
||||
<select id="borrowed-voice-kind" value={kind} onChange={e => setKind(e.target.value)}>
|
||||
{VOICE_KINDS.filter(k => offer.choices.some(choice => choice.kind === k))
|
||||
{voiceChoices(game.advanced).filter(k => offer.choices.some(choice => choice.kind === k))
|
||||
.map(k => <option key={k} value={k}>{battleCardLabel(k)}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label className="decision-checkbox">
|
||||
<input type="checkbox" checked={must} onChange={e => setMust(e.target.checked)} />
|
||||
Require this type (unchecked: forbid)
|
||||
Require this card or type (unchecked: forbid)
|
||||
</label>
|
||||
<p className="notice">Provisional policy: Karama may cancel the Voice. The Nexus card stays spent.</p>
|
||||
<Button className="game-action" disabled={busy || !!offer.blocked || !selected}
|
||||
@@ -185,13 +185,13 @@ export function BattlePreparation({
|
||||
</p>
|
||||
{preparation.blocked && <p className="notice">{preparation.blocked}</p>}
|
||||
<label htmlFor="voice-kind">
|
||||
Card type
|
||||
Card or type
|
||||
<select
|
||||
id="voice-kind"
|
||||
value={kind}
|
||||
onChange={(e) => setKind(e.target.value)}
|
||||
>
|
||||
{VOICE_KINDS.filter((name) => name !== 'mirrorWeapon' || !mirrorWeaponModeBlock(game)).map((k) => (
|
||||
{voiceChoices(game.advanced).filter((name) => name !== 'mirrorWeapon' || !mirrorWeaponModeBlock(game)).map((k) => (
|
||||
<option key={k} value={k}>
|
||||
{battleCardLabel(k)}
|
||||
</option>
|
||||
@@ -204,7 +204,7 @@ export function BattlePreparation({
|
||||
checked={must}
|
||||
onChange={(e) => setMust(e.target.checked)}
|
||||
/>
|
||||
Require this type (unchecked: forbid)
|
||||
Require this card or type (unchecked: forbid)
|
||||
</label>
|
||||
<Button
|
||||
className="game-action"
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { GameView } from '@/game/engine';
|
||||
import type { MixedActionExpression } from '@/game/mixed-action-question';
|
||||
import { parseBattleActionClaim } from '@/game/mixed-battle-question';
|
||||
import { battlePowerOf, nativeBattlePowerOpportunity, type BattlePowerClaim } from '@/game/battle-power-question';
|
||||
import { battleCardLabel, VOICE_KINDS } from '@/game/battle-cards';
|
||||
import { battleCardLabel, voiceChoices } from '@/game/battle-cards';
|
||||
import { nativeStormDialOpportunity } from '@/game/storm-dial-question';
|
||||
import { karamaBattleCancelOpportunity, WINDOW_CARD_EFFECTS, windowCardOpportunity, type WindowCardEffect } from '@/game/window-card-play';
|
||||
import { parseTurnActionLeaf, TURN_ACTION_MAX_DEPTH, TURN_ACTION_MAX_LEAVES, type TurnActionLeaf } from '@/game/turn-action-promises';
|
||||
@@ -328,13 +328,13 @@ const config: MixedActionConfig<TurnActionLeaf> = {
|
||||
</select>
|
||||
</> : value.claim.kind === 'voiceCommand' ? <>
|
||||
<label htmlFor={`${id}-power-card-type`}>Voice card type</label>
|
||||
<select id={`${id}-power-card-type`} value={VOICE_KINDS.includes(value.claim.cardType) ? value.claim.cardType : ''}
|
||||
disabled={disabled} aria-invalid={!VOICE_KINDS.includes(value.claim.cardType)} onChange={event => {
|
||||
if (value.claim.kind === 'voiceCommand' && (event.currentTarget.value === '' || VOICE_KINDS.includes(event.currentTarget.value)))
|
||||
<select id={`${id}-power-card-type`} value={voiceChoices(game.advanced).includes(value.claim.cardType) ? value.claim.cardType : ''}
|
||||
disabled={disabled} aria-invalid={!voiceChoices(game.advanced).includes(value.claim.cardType)} onChange={event => {
|
||||
if (value.claim.kind === 'voiceCommand' && (event.currentTarget.value === '' || voiceChoices(game.advanced).includes(event.currentTarget.value)))
|
||||
onChange({ ...value, claim: { ...value.claim, cardType: event.currentTarget.value } });
|
||||
}}>
|
||||
<option value="">Choose a native Voice card type</option>
|
||||
{VOICE_KINDS.map(kind => <option key={kind} value={kind}>{battleCardLabel(kind)}</option>)}
|
||||
{voiceChoices(game.advanced).map(kind => <option key={kind} value={kind}>{battleCardLabel(kind)}</option>)}
|
||||
</select>
|
||||
<label htmlFor={`${id}-power-must`}>Exact Voice command</label>
|
||||
<select id={`${id}-power-must`} value={value.claim.must ? 'must' : 'cannot'} disabled={disabled}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ordinaryCardAvailability } from '@/game/card-availability';
|
||||
import { gholaForcePreparations } from '@/game/ghola-force-preparation';
|
||||
import { richeseCardDefinition } from '@/game/richese-cards';
|
||||
import { nativeBattlePowerOpportunity, type NativeBattlePower } from '@/game/battle-power-question';
|
||||
import { battleCardLabel, VOICE_KINDS } from '@/game/battle-cards';
|
||||
import { battleCardLabel, voiceChoices } from '@/game/battle-cards';
|
||||
import { nativeStormDialOpportunity } from '@/game/storm-dial-question';
|
||||
import { karamaBattleCancelOpportunity, WINDOW_CARD_EFFECTS, windowCardNativeEffect, windowCardOpportunity, type WindowCardEffect } from '@/game/window-card-play';
|
||||
import type { TurnActionLeaf } from '@/game/turn-action-promises';
|
||||
@@ -409,7 +409,7 @@ export function TurnActionGuideButton(props: TurnActionProps) {
|
||||
JSON.stringify(publicBattleContext(current)) === battleSignature;
|
||||
const powerPacket = Object.keys(action).every(key => ['type', 'turnActionPromiseEvent', 'turnActionPromisePhase',
|
||||
...(action.type === 'voice' ? ['kind', 'must'] : action.type === 'prescience' ? ['field'] : [])].includes(key)) &&
|
||||
(action.type === 'voice' ? typeof action.kind === 'string' && VOICE_KINDS.includes(action.kind) && typeof action.must === 'boolean'
|
||||
(action.type === 'voice' ? typeof action.kind === 'string' && voiceChoices(current.advanced).includes(action.kind) && typeof action.must === 'boolean'
|
||||
: action.type === 'prescience' ? ['leader', 'weapon', 'defense', 'dial'].includes(String(action.field))
|
||||
: action.type === 'declineBattlePower');
|
||||
const ownedPower = currentBattle && !!power && !!powerSource && powerSource.ready && powerSource.owner === actor &&
|
||||
|
||||
Reference in new issue
Block a user