changes
This commit is contained in:
@@ -12,8 +12,10 @@ import {
|
||||
type DualScreenCombatState,
|
||||
} from '../dualScreen'
|
||||
import {
|
||||
loadPvpRoguelikeCheckpoint,
|
||||
randomCpuDifficulty,
|
||||
recordCpuPvpLeaderboard,
|
||||
recordPvpRoguelikeCheckpoint,
|
||||
type CpuDifficulty,
|
||||
type PvpContentType,
|
||||
} from '../pvpRoguelike'
|
||||
@@ -29,6 +31,7 @@ type BossMechanic =
|
||||
|
||||
type PvpEncounter = DungeonEncounter & {
|
||||
bossMechanics?: BossMechanic[]
|
||||
sourceEncounterId?: number
|
||||
}
|
||||
|
||||
type SlotKey = '1' | '2' | '3' | '4' | '5'
|
||||
@@ -261,6 +264,7 @@ function buildEncounterSegment(pool: DungeonEncounter[], stage: number, kind: Pv
|
||||
const isBoss = index === 2
|
||||
return {
|
||||
...encounter,
|
||||
sourceEncounterId: encounter.id,
|
||||
id: 910000 + stage * 10 + index,
|
||||
sequence: (stage - 1) * 3 + index + 1,
|
||||
isBoss,
|
||||
@@ -381,6 +385,10 @@ export function PvPRoguelikeScreen({
|
||||
() => buildOpponentDebuffChoices(starterSpells, abilityLabelMode),
|
||||
[abilityLabelMode, starterSpells],
|
||||
)
|
||||
const [checkpointStage, setCheckpointStage] = useState(() =>
|
||||
loadPvpRoguelikeCheckpoint(profile.character.id, contentType),
|
||||
)
|
||||
const [startStage, setStartStage] = useState(checkpointStage)
|
||||
const maxResource = gameClass.maxResource
|
||||
const partyTemplate = useMemo(
|
||||
() => (contentType === 'raid' ? RAID_PARTY : INITIAL_PARTY).map((member) => ({
|
||||
@@ -397,8 +405,8 @@ export function PvPRoguelikeScreen({
|
||||
[contentType],
|
||||
)
|
||||
const [status, setStatus] = useState<'queueing' | 'playing' | 'upgrade-choice' | 'won' | 'lost'>('queueing')
|
||||
const [stage, setStage] = useState(1)
|
||||
const [encounters, setEncounters] = useState<PvpEncounter[]>(() => buildEncounterSegment(encounterPool, 1, contentType))
|
||||
const [stage, setStage] = useState(startStage)
|
||||
const [encounters, setEncounters] = useState<PvpEncounter[]>(() => buildEncounterSegment(encounterPool, startStage, contentType))
|
||||
const [encounterIndex, setEncounterIndex] = useState(0)
|
||||
const [playerSide, setPlayerSide] = useState<SideState>(() => starterSide(partyTemplate, maxResource))
|
||||
const [cpuSide, setCpuSide] = useState<SideState>(() => starterSide(cpuPartyTemplate, maxResource))
|
||||
@@ -464,9 +472,16 @@ export function PvPRoguelikeScreen({
|
||||
}, 900)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const loadedCheckpoint = loadPvpRoguelikeCheckpoint(profile.character.id, contentType)
|
||||
setCheckpointStage(loadedCheckpoint)
|
||||
setStartStage(loadedCheckpoint)
|
||||
}, [contentType, profile.character.id])
|
||||
|
||||
const awardBossReward = useCallback((encounterIndexValue: number) => {
|
||||
if (bossRewardClaimedRef.current.has(encounterIndexValue)) return
|
||||
bossRewardClaimedRef.current.add(encounterIndexValue)
|
||||
const rewardEncounter = encounters[encounterIndexValue]
|
||||
completeRoguelike(
|
||||
rewardDungeon.id,
|
||||
rewardDifficulty.id,
|
||||
@@ -476,18 +491,26 @@ export function PvPRoguelikeScreen({
|
||||
{
|
||||
bossesCleared: 1,
|
||||
experienceMode: 'pvp-boss-quarter-level',
|
||||
lootSourceEncounterId: rewardEncounter?.sourceEncounterId,
|
||||
roguelikeStage: stage,
|
||||
},
|
||||
)
|
||||
.then((result) => {
|
||||
setReward(result)
|
||||
onProfileUpdated(result.profile)
|
||||
if (result.bonusItem) {
|
||||
addLog(
|
||||
`${result.bonusItem.name} x${result.bonusItem.quantity} awarded.`,
|
||||
'loot',
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch((reason: unknown) => {
|
||||
setRewardError(
|
||||
reason instanceof Error ? reason.message : 'Unable to award roguelike experience.',
|
||||
)
|
||||
})
|
||||
}, [elapsedTicks, onProfileUpdated, rewardDifficulty.id, rewardDungeon.id])
|
||||
}, [addLog, elapsedTicks, encounters, onProfileUpdated, rewardDifficulty.id, rewardDungeon.id, stage])
|
||||
|
||||
const finishRoguelikeRun = useCallback(() => {
|
||||
if (rewardClaimedRef.current) return
|
||||
@@ -510,7 +533,7 @@ export function PvPRoguelikeScreen({
|
||||
}, [opponentDebuffChoicesCatalog, selfBuffChoicesCatalog])
|
||||
|
||||
useEffect(() => {
|
||||
const firstSegment = buildEncounterSegment(encounterPool, 1, contentType)
|
||||
const firstSegment = buildEncounterSegment(encounterPool, startStage, contentType)
|
||||
const firstEncounter = firstSegment[0]
|
||||
const basePlayer = starterSide(partyTemplate, maxResource)
|
||||
const baseCpu = starterSide(cpuPartyTemplate, maxResource)
|
||||
@@ -523,7 +546,7 @@ export function PvPRoguelikeScreen({
|
||||
bossRewardClaimedRef.current = new Set()
|
||||
setEncounters(firstSegment)
|
||||
setEncounterIndex(0)
|
||||
setStage(1)
|
||||
setStage(startStage)
|
||||
setElapsedTicks(0)
|
||||
setStatus('queueing')
|
||||
setPlayerSide(basePlayer)
|
||||
@@ -546,26 +569,26 @@ export function PvPRoguelikeScreen({
|
||||
cpuDefeatedRef.current = false
|
||||
if (gameMode === 'offline') {
|
||||
const randomCpu = randomCpuDifficulty()
|
||||
setQueueMessage(`Offline mode. CPU ${randomCpu} enters immediately.`)
|
||||
setQueueMessage(`Offline mode. CPU ${randomCpu} enters at stage ${startStage}.`)
|
||||
setCpuDifficulty(randomCpu)
|
||||
setLog([{ id: 1, text: `Offline mode. CPU ${randomCpu} enters immediately.`, tone: 'system' }])
|
||||
setLog([{ id: 1, text: `Offline mode. CPU ${randomCpu} enters at stage ${startStage}.`, tone: 'system' }])
|
||||
const timer = window.setTimeout(() => {
|
||||
setStatus('playing')
|
||||
addLog(`Round 1 begins against CPU ${randomCpu}.`, 'system')
|
||||
addLog(`Stage ${startStage} begins against CPU ${randomCpu}.`, 'system')
|
||||
}, 500)
|
||||
return () => window.clearTimeout(timer)
|
||||
}
|
||||
setQueueMessage('Searching queue. No player found yet.')
|
||||
setLog([{ id: 1, text: 'Searching queue. No player found yet.', tone: 'system' }])
|
||||
setQueueMessage(`Searching queue. Stage ${startStage} start ready.`)
|
||||
setLog([{ id: 1, text: `Searching queue. Stage ${startStage} start ready.`, tone: 'system' }])
|
||||
const timer = window.setTimeout(() => {
|
||||
const randomCpu = randomCpuDifficulty()
|
||||
setCpuDifficulty(randomCpu)
|
||||
setQueueMessage(`No queued player found. CPU ${randomCpu} steps in.`)
|
||||
setStatus('playing')
|
||||
addLog(`No queued player found. CPU ${randomCpu} steps in.`, 'system')
|
||||
addLog(`No queued player found. CPU ${randomCpu} steps in at stage ${startStage}.`, 'system')
|
||||
}, 1400)
|
||||
return () => window.clearTimeout(timer)
|
||||
}, [addLog, contentType, cpuPartyTemplate, encounterPool, gameMode, maxResource, partyTemplate])
|
||||
}, [addLog, contentType, cpuPartyTemplate, encounterPool, gameMode, maxResource, partyTemplate, startStage])
|
||||
|
||||
const applySpell = useCallback((
|
||||
current: SideState,
|
||||
@@ -841,7 +864,18 @@ export function PvPRoguelikeScreen({
|
||||
if (nextPlayer.enemyHealth <= 0 && playerClearedEncounterRef.current !== encounterIndex) {
|
||||
playerClearedEncounterRef.current = encounterIndex
|
||||
setEncountersCleared((value) => value + 1)
|
||||
if (encounter.isBoss) awardBossReward(encounterIndex)
|
||||
if (encounter.isBoss) {
|
||||
awardBossReward(encounterIndex)
|
||||
const nextCheckpoint = recordPvpRoguelikeCheckpoint(
|
||||
profile.character.id,
|
||||
contentType,
|
||||
stage,
|
||||
)
|
||||
if (nextCheckpoint > checkpointStage) {
|
||||
setCheckpointStage(nextCheckpoint)
|
||||
addLog(`Stage ${nextCheckpoint} checkpoint unlocked.`, 'loot')
|
||||
}
|
||||
}
|
||||
}
|
||||
playerRef.current = nextPlayer
|
||||
cpuRef.current = nextCpu
|
||||
@@ -866,7 +900,7 @@ export function PvPRoguelikeScreen({
|
||||
}
|
||||
}, TICK_MS)
|
||||
return () => window.clearInterval(timer)
|
||||
}, [addLog, advanceSide, awardBossReward, beginUpgradePhase, cpuDifficulty, cpuTakeTurn, encounter, encounterIndex, encountersCleared, finishRoguelikeRun, paused, status])
|
||||
}, [addLog, advanceSide, awardBossReward, beginUpgradePhase, checkpointStage, contentType, cpuDifficulty, cpuTakeTurn, encounter, encounterIndex, encountersCleared, finishRoguelikeRun, paused, profile.character.id, stage, status])
|
||||
|
||||
useEffect(() => {
|
||||
if ((status !== 'won' && status !== 'lost') || recordedRunRef.current || !cpuDifficulty) return
|
||||
@@ -1334,6 +1368,13 @@ export function PvPRoguelikeScreen({
|
||||
Ability Unlocked: {ability.name}
|
||||
</p>
|
||||
))}
|
||||
{reward.bonusItem && (
|
||||
<p className="ability-unlock">
|
||||
<span>{reward.bonusItem.glyph}</span>
|
||||
{reward.bonusItem.name} x{reward.bonusItem.quantity}
|
||||
{reward.bonusItem.duplicate ? ` (owned x${reward.bonusItem.quantityAfter})` : ''}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user