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),
|
onPanAutomap: (dx, dy) => minimap.pan(dx, dy),
|
||||||
onEscapeMenuAction: (action) => {
|
onEscapeMenuAction: (action) => {
|
||||||
if (action === 'saveAndExit') {
|
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 => {
|
const returnToLobby = (): void => {
|
||||||
inGameView = false
|
inGameView = false
|
||||||
|
flowState.sceneReady = false
|
||||||
flowState.stage = 'bnet_lobby'
|
flowState.stage = 'bnet_lobby'
|
||||||
|
flowState.lobbySubPanel = 'create'
|
||||||
|
flowState.createGameError = null
|
||||||
|
flowState.activeInput = 'gameName'
|
||||||
toolbar.setConnectionState({ phase: 'mcp', rttMs: session.pingMs })
|
toolbar.setConnectionState({ phase: 'mcp', rttMs: session.pingMs })
|
||||||
syncVisibility()
|
syncVisibility()
|
||||||
|
frontendRenderer.render(performance.now())
|
||||||
}
|
}
|
||||||
|
|
||||||
frontendRenderer.setAsyncHandlers({
|
frontendRenderer.setAsyncHandlers({
|
||||||
|
|
@ -717,6 +727,10 @@ async function bootPlayApp(): Promise<PlayAppController> {
|
||||||
flowState.stage = 'in_game'
|
flowState.stage = 'in_game'
|
||||||
toolbar.setConnectionState({ phase: 'd2gs', rttMs: snap.pingMs })
|
toolbar.setConnectionState({ phase: 'd2gs', rttMs: snap.pingMs })
|
||||||
} else if (snap.phase === 'lobby') {
|
} else if (snap.phase === 'lobby') {
|
||||||
|
if (inGameView || flowState.stage === 'in_game') {
|
||||||
|
returnToLobby()
|
||||||
|
return
|
||||||
|
}
|
||||||
toolbar.setConnectionState({ phase: 'mcp', rttMs: snap.pingMs })
|
toolbar.setConnectionState({ phase: 'mcp', rttMs: snap.pingMs })
|
||||||
} else if (snap.phase === 'connecting') {
|
} else if (snap.phase === 'connecting') {
|
||||||
toolbar.setConnectionState({ phase: 'connecting', rttMs: null })
|
toolbar.setConnectionState({ phase: 'connecting', rttMs: null })
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,7 @@ export class OnlineSession {
|
||||||
if (!this.hudManager.onEscapeMenuAction) {
|
if (!this.hudManager.onEscapeMenuAction) {
|
||||||
this.hudManager.onEscapeMenuAction = (action) => {
|
this.hudManager.onEscapeMenuAction = (action) => {
|
||||||
if (action === 'saveAndExit') {
|
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> {
|
): Promise<GameServerAdapter> {
|
||||||
return await this.lobbyOp('enterGame', 'mcp', async (flow) => {
|
return await this.lobbyOp('enterGame', 'mcp', async (flow) => {
|
||||||
this.setPhase('entering-game')
|
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 {
|
try {
|
||||||
let adapter: GameServerAdapter
|
let adapter: GameServerAdapter
|
||||||
if (request.mode === 'create') {
|
if (request.mode === 'create') {
|
||||||
|
|
@ -735,6 +743,8 @@ export class OnlineSession {
|
||||||
async leaveGame(): Promise<void> {
|
async leaveGame(): Promise<void> {
|
||||||
const flow = this.flow
|
const flow = this.flow
|
||||||
this.detachGameServerListeners()
|
this.detachGameServerListeners()
|
||||||
|
this.hudModel.closeAllPanels()
|
||||||
|
this.hudManager.closeAllPanels()
|
||||||
if (flow && flow.state === 'ingame') {
|
if (flow && flow.state === 'ingame') {
|
||||||
try {
|
try {
|
||||||
await flow.leaveToLobby()
|
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(actions).toEqual(['options', 'returnToGame', 'saveAndExit'])
|
||||||
expect(hudManager.escapeMenuOpen).toBe(false)
|
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