fix(client): exit Escape menu Save and Exit Game to Battle.net Lobby instead of login
This commit is contained in:
parent
c9ddd1d2e3
commit
33b1b0afce
|
|
@ -222,7 +222,12 @@ async function bootPlayApp(): Promise<PlayAppController> {
|
|||
onPanAutomap: (dx, dy) => minimap.pan(dx, dy),
|
||||
onEscapeMenuAction: (action) => {
|
||||
if (action === 'saveAndExit') {
|
||||
void sessionRef?.disconnect('Saved and exited the game; Battle.net session closed')
|
||||
if (!chatInputEl.hidden) {
|
||||
chatInputEl.value = ''
|
||||
chatInputEl.hidden = true
|
||||
chatInputEl.blur()
|
||||
}
|
||||
void sessionRef?.leaveGame()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
|
@ -442,9 +447,14 @@ async function bootPlayApp(): Promise<PlayAppController> {
|
|||
|
||||
const returnToLobby = (): void => {
|
||||
inGameView = false
|
||||
flowState.sceneReady = false
|
||||
flowState.stage = 'bnet_lobby'
|
||||
flowState.lobbySubPanel = 'create'
|
||||
flowState.createGameError = null
|
||||
flowState.activeInput = 'gameName'
|
||||
toolbar.setConnectionState({ phase: 'mcp', rttMs: session.pingMs })
|
||||
syncVisibility()
|
||||
frontendRenderer.render(performance.now())
|
||||
}
|
||||
|
||||
frontendRenderer.setAsyncHandlers({
|
||||
|
|
@ -717,6 +727,10 @@ async function bootPlayApp(): Promise<PlayAppController> {
|
|||
flowState.stage = 'in_game'
|
||||
toolbar.setConnectionState({ phase: 'd2gs', rttMs: snap.pingMs })
|
||||
} else if (snap.phase === 'lobby') {
|
||||
if (inGameView || flowState.stage === 'in_game') {
|
||||
returnToLobby()
|
||||
return
|
||||
}
|
||||
toolbar.setConnectionState({ phase: 'mcp', rttMs: snap.pingMs })
|
||||
} else if (snap.phase === 'connecting') {
|
||||
toolbar.setConnectionState({ phase: 'connecting', rttMs: null })
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ export class OnlineSession {
|
|||
if (!this.hudManager.onEscapeMenuAction) {
|
||||
this.hudManager.onEscapeMenuAction = (action) => {
|
||||
if (action === 'saveAndExit') {
|
||||
void this.disconnect('Saved and exited the game; Battle.net session closed')
|
||||
void this.leaveGame()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -664,6 +664,14 @@ export class OnlineSession {
|
|||
): Promise<GameServerAdapter> {
|
||||
return await this.lobbyOp('enterGame', 'mcp', async (flow) => {
|
||||
this.setPhase('entering-game')
|
||||
const savedName = this.world.self.name
|
||||
const savedClass = this.world.self.charClass
|
||||
this.world.reset()
|
||||
this.world.self.name = savedName
|
||||
this.world.self.charClass = savedClass
|
||||
this.predictor.setPosition(0, 0)
|
||||
this.interpolator.clear()
|
||||
this.hudModel.syncFromWorld()
|
||||
try {
|
||||
let adapter: GameServerAdapter
|
||||
if (request.mode === 'create') {
|
||||
|
|
@ -735,6 +743,8 @@ export class OnlineSession {
|
|||
async leaveGame(): Promise<void> {
|
||||
const flow = this.flow
|
||||
this.detachGameServerListeners()
|
||||
this.hudModel.closeAllPanels()
|
||||
this.hudManager.closeAllPanels()
|
||||
if (flow && flow.state === 'ingame') {
|
||||
try {
|
||||
await flow.leaveToLobby()
|
||||
|
|
|
|||
|
|
@ -282,4 +282,79 @@ describe('Diablo II v1.13c In-Game Escape Menu (D2Client.dll gmenu.cpp 0x6fb13b0
|
|||
expect(actions).toEqual(['options', 'returnToGame', 'saveAndExit'])
|
||||
expect(hudManager.escapeMenuOpen).toBe(false)
|
||||
})
|
||||
|
||||
it('Save and Exit Game leaves D2GS to Battle.net Lobby (phase="lobby") without tearing down BNCS/MCP session', async () => {
|
||||
const { OnlineSession } = await import('../../src/client/session/online-session.ts')
|
||||
let flowState: 'idle' | 'realm_ready' | 'ingame' | 'closed' = 'idle'
|
||||
let leaveToLobbyCount = 0
|
||||
let closeCount = 0
|
||||
|
||||
const session = new OnlineSession({
|
||||
flowFactory: () =>
|
||||
({
|
||||
get state() {
|
||||
return flowState
|
||||
},
|
||||
get sessionLost() {
|
||||
return null
|
||||
},
|
||||
onState: () => () => {},
|
||||
onSessionLost: () => () => {},
|
||||
login: async () => {},
|
||||
listRealms: async () => [{ index: 0, title: 'D2CS', description: 'Realm' }],
|
||||
enterRealm: async () => [
|
||||
{
|
||||
name: 'HeroSorc',
|
||||
charClass: 1,
|
||||
level: 1,
|
||||
flags: 0x20,
|
||||
act: 1,
|
||||
expiresAt: 0,
|
||||
rawAppearance: new Uint8Array(33),
|
||||
},
|
||||
],
|
||||
selectCharacter: async () => {},
|
||||
createGame: async () => {},
|
||||
joinGame: async () => {
|
||||
flowState = 'ingame'
|
||||
return {
|
||||
state: 'ingame',
|
||||
stats: { packetsIn: 0, packetsOut: 0, unknownPackets: 0, opaquePackets: 0, protocolErrors: 0, rttMs: 10 },
|
||||
send: () => {},
|
||||
leave: () => {},
|
||||
onEvent: () => () => {},
|
||||
onClose: () => () => {},
|
||||
} as any
|
||||
},
|
||||
leaveToLobby: async () => {
|
||||
leaveToLobbyCount += 1
|
||||
flowState = 'realm_ready'
|
||||
},
|
||||
close: async () => {
|
||||
closeCount += 1
|
||||
flowState = 'closed'
|
||||
},
|
||||
}) as any,
|
||||
})
|
||||
|
||||
const sessionLostEvents: unknown[] = []
|
||||
session.onSessionLost((ev) => sessionLostEvents.push(ev))
|
||||
|
||||
await session.login({ username: 'acc', password: 'pw' })
|
||||
await session.selectCharacter('HeroSorc')
|
||||
await session.enterGame({ mode: 'create', spec: { name: 'g1', difficulty: 0 } })
|
||||
expect(session.phase).toBe('ingame')
|
||||
|
||||
// Open Escape menu and activate row 1 (Save and Exit Game)
|
||||
session.hudModel.togglePanel('escapeMenu')
|
||||
expect(session.hudManager.escapeMenuOpen).toBe(true)
|
||||
session.hudManager.activateEscapeMenuIndex(1)
|
||||
await new Promise((r) => setTimeout(r, 0))
|
||||
|
||||
expect(session.hudManager.escapeMenuOpen).toBe(false)
|
||||
expect(leaveToLobbyCount).toBe(1)
|
||||
expect(closeCount).toBe(0)
|
||||
expect(sessionLostEvents).toHaveLength(0)
|
||||
expect(session.phase).toBe('lobby')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue