Merge branch 'issue-393' into main: fix waypoint menu icons parity (#393)
This commit is contained in:
commit
807585a615
|
|
@ -2081,8 +2081,12 @@ export class SceneMouseController {
|
||||||
if (this.waypointNetwork.activate(clickedWp.wp.waypointId)) {
|
if (this.waypointNetwork.activate(clickedWp.wp.waypointId)) {
|
||||||
this.status.textContent = `传送点已激活:${runtime.level}`
|
this.status.textContent = `传送点已激活:${runtime.level}`
|
||||||
}
|
}
|
||||||
if (this.hudManager && this.hudManager.leftPanel !== 'waypoint') {
|
if (this.hudManager) {
|
||||||
this.hudManager.toggleLeftPanel('waypoint')
|
this.hudManager.worldPanels.setCurrentWaypoint(clickedWp.wp.waypointId, runtime.levelId)
|
||||||
|
this.hudManager.worldPanels.selectedActTab = runtime.act
|
||||||
|
if (this.hudManager.leftPanel !== 'waypoint') {
|
||||||
|
this.hudManager.toggleLeftPanel('waypoint')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.pendingInteraction = { kind: 'waypoint', waypoint: clickedWp.wp, worldPos: clickedWp.pos }
|
this.pendingInteraction = { kind: 'waypoint', waypoint: clickedWp.wp, worldPos: clickedWp.pos }
|
||||||
|
|
@ -2281,8 +2285,12 @@ export class SceneMouseController {
|
||||||
if (this.waypointNetwork.activate(waypoint.waypointId)) {
|
if (this.waypointNetwork.activate(waypoint.waypointId)) {
|
||||||
this.status.textContent = `传送点已激活:${this.getRuntime().level}`
|
this.status.textContent = `传送点已激活:${this.getRuntime().level}`
|
||||||
}
|
}
|
||||||
if (this.hudManager && this.hudManager.leftPanel !== 'waypoint') {
|
if (this.hudManager) {
|
||||||
this.hudManager.toggleLeftPanel('waypoint')
|
this.hudManager.worldPanels.setCurrentWaypoint(waypoint.waypointId, this.getRuntime().levelId)
|
||||||
|
this.hudManager.worldPanels.selectedActTab = this.getRuntime().act
|
||||||
|
if (this.hudManager.leftPanel !== 'waypoint') {
|
||||||
|
this.hudManager.toggleLeftPanel('waypoint')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.navTarget = { x: worldPos.x, y: worldPos.y }
|
this.navTarget = { x: worldPos.x, y: worldPos.y }
|
||||||
|
|
@ -4351,6 +4359,9 @@ if (typeof window !== 'undefined') {
|
||||||
})
|
})
|
||||||
: null
|
: null
|
||||||
if (hudManager !== null) {
|
if (hudManager !== null) {
|
||||||
|
hudManager.worldPanels.setCurrentLevel(runtime.levelId ?? null)
|
||||||
|
hudManager.worldPanels.setCurrentWaypoint(runtime.waypoints[0]?.waypointId ?? null, runtime.levelId ?? null)
|
||||||
|
hudManager.worldPanels.selectedActTab = runtime.act
|
||||||
try {
|
try {
|
||||||
await hudManager.init('/ui')
|
await hudManager.init('/ui')
|
||||||
const initialHeroInfo = HERO_CLASSES[currentHeroToken] ?? HERO_CLASSES.so
|
const initialHeroInfo = HERO_CLASSES[currentHeroToken] ?? HERO_CLASSES.so
|
||||||
|
|
@ -4564,6 +4575,11 @@ if (typeof window !== 'undefined') {
|
||||||
|
|
||||||
const names = getLevelNames(next.levelId ?? next.level)
|
const names = getLevelNames(next.levelId ?? next.level)
|
||||||
hudManager?.worldPanels.showAreaBanner(names.nameZh, names.nameEn)
|
hudManager?.worldPanels.showAreaBanner(names.nameZh, names.nameEn)
|
||||||
|
if (hudManager) {
|
||||||
|
hudManager.worldPanels.setCurrentLevel(next.levelId ?? null)
|
||||||
|
hudManager.worldPanels.setCurrentWaypoint(next.waypoints[0]?.waypointId ?? null, next.levelId ?? null)
|
||||||
|
hudManager.worldPanels.selectedActTab = next.act
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -4739,8 +4755,12 @@ if (typeof window !== 'undefined') {
|
||||||
nearWaypoint = true
|
nearWaypoint = true
|
||||||
if (lastContactWaypointId !== waypoint.waypointId) {
|
if (lastContactWaypointId !== waypoint.waypointId) {
|
||||||
lastContactWaypointId = waypoint.waypointId
|
lastContactWaypointId = waypoint.waypointId
|
||||||
if (hudManager && hudManager.leftPanel !== 'waypoint') {
|
if (hudManager) {
|
||||||
hudManager.toggleLeftPanel('waypoint')
|
hudManager.worldPanels.setCurrentWaypoint(waypoint.waypointId, runtime.levelId)
|
||||||
|
hudManager.worldPanels.selectedActTab = runtime.act
|
||||||
|
if (hudManager.leftPanel !== 'waypoint') {
|
||||||
|
hudManager.toggleLeftPanel('waypoint')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (waypointNetwork.activate(waypoint.waypointId)) {
|
if (waypointNetwork.activate(waypoint.waypointId)) {
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,69 @@ export class WorldPanelsHud {
|
||||||
hoveredStashItem: { item: UiInventoryItem; x: number; y: number } | null = null
|
hoveredStashItem: { item: UiInventoryItem; x: number; y: number } | null = null
|
||||||
hoveredWaypointIdx: number | null = null
|
hoveredWaypointIdx: number | null = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The waypoint that opened this menu or where the player currently stands.
|
||||||
|
* Ground Truth (D2Client.dll 1.13c):
|
||||||
|
* - 0x6fbacdd0: Current level ID
|
||||||
|
* - 0x6fbacdd4: Current waypoint ID
|
||||||
|
*/
|
||||||
|
currentWaypointId: number | null = null
|
||||||
|
currentLevelId: number | null = null
|
||||||
|
|
||||||
|
/** Runtime waypoint unlocked overrides: waypointId -> boolean */
|
||||||
|
private waypointUnlockedOverrides = new Map<number, boolean>()
|
||||||
|
|
||||||
|
isWaypointUnlocked(wpOrId: WaypointEntry | number): boolean {
|
||||||
|
const wpId = typeof wpOrId === 'number' ? wpOrId : wpOrId.waypointId
|
||||||
|
const override = this.waypointUnlockedOverrides.get(wpId)
|
||||||
|
if (override !== undefined) return override
|
||||||
|
if (typeof wpOrId === 'object' && wpOrId !== null) return wpOrId.unlocked
|
||||||
|
for (const list of Object.values(ACT_WAYPOINTS)) {
|
||||||
|
const found = list.find(w => w.waypointId === wpId)
|
||||||
|
if (found) return found.unlocked
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
setWaypointUnlocked(waypointId: number, unlocked: boolean): void {
|
||||||
|
this.waypointUnlockedOverrides.set(waypointId, unlocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
setUnlockedWaypoints(unlockedWaypointIds: Iterable<number>): void {
|
||||||
|
this.waypointUnlockedOverrides.clear()
|
||||||
|
const set = new Set(unlockedWaypointIds)
|
||||||
|
for (const list of Object.values(ACT_WAYPOINTS)) {
|
||||||
|
for (const wp of list) {
|
||||||
|
this.waypointUnlockedOverrides.set(wp.waypointId, set.has(wp.waypointId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clearWaypointUnlockedOverrides(): void {
|
||||||
|
this.waypointUnlockedOverrides.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentWaypoint(waypointId: number | null, levelId?: number | null): void {
|
||||||
|
this.currentWaypointId = waypointId
|
||||||
|
if (levelId !== undefined) {
|
||||||
|
this.currentLevelId = levelId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setCurrentLevel(levelId: number | null): void {
|
||||||
|
this.currentLevelId = levelId
|
||||||
|
}
|
||||||
|
|
||||||
|
isCurrentWaypoint(wp: WaypointEntry): boolean {
|
||||||
|
if (this.currentWaypointId !== null && this.currentWaypointId !== undefined) {
|
||||||
|
if (wp.waypointId === this.currentWaypointId) return true
|
||||||
|
}
|
||||||
|
if (this.currentLevelId !== null && this.currentLevelId !== undefined) {
|
||||||
|
if (wp.levelId === this.currentLevelId) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether an item of (w, h) can be placed at (col, row) in the 6x8 stash grid.
|
* Check whether an item of (w, h) can be placed at (col, row) in the 6x8 stash grid.
|
||||||
* Throws RangeError if placed out of bounds. Returns all overlapping GridPlacements.
|
* Throws RangeError if placed out of bounds. Returns all overlapping GridPlacements.
|
||||||
|
|
@ -463,7 +526,10 @@ export class WorldPanelsHud {
|
||||||
const wy = oy + rowY
|
const wy = oy + rowY
|
||||||
if (logicalX >= ox + 17 && logicalX <= ox + 298 && logicalY >= wy && logicalY <= wy + 32) {
|
if (logicalX >= ox + 17 && logicalX <= ox + 298 && logicalY >= wy && logicalY <= wy + 32) {
|
||||||
const wp = list[i]!
|
const wp = list[i]!
|
||||||
if (!wp.unlocked) return true
|
if (!this.isWaypointUnlocked(wp)) return true
|
||||||
|
// Decompiled D2Client.dll 1.13c 0x6fb5a53d (cmp 0x6fbacdd0, %eax):
|
||||||
|
// Clicking current waypoint does nothing (player is already at this waypoint)
|
||||||
|
if (this.isCurrentWaypoint(wp)) return true
|
||||||
callbacks.onWaypointTeleport(wp.act, wp.slug, wp.levelId)
|
callbacks.onWaypointTeleport(wp.act, wp.slug, wp.levelId)
|
||||||
callbacks.onClose()
|
callbacks.onClose()
|
||||||
return true
|
return true
|
||||||
|
|
@ -646,14 +712,14 @@ export class WorldPanelsHud {
|
||||||
if (assets.waypointBgImg) ctx.drawImage(assets.waypointBgImg, ox, oy)
|
if (assets.waypointBgImg) ctx.drawImage(assets.waypointBgImg, ox, oy)
|
||||||
this.drawActTabs(ctx, ox, oy, font, assets.waypointTabsImg)
|
this.drawActTabs(ctx, ox, oy, font, assets.waypointTabsImg)
|
||||||
const wps = ACT_WAYPOINTS[this.selectedActTab] ?? ACT_WAYPOINTS[1]!
|
const wps = ACT_WAYPOINTS[this.selectedActTab] ?? ACT_WAYPOINTS[1]!
|
||||||
const activeIconIdx = Math.max(0, Math.min(4, this.selectedActTab - 1))
|
|
||||||
const iconSx = activeIconIdx * 30
|
|
||||||
|
|
||||||
for (let i = 0; i < wps.length; i++) {
|
for (let i = 0; i < wps.length; i++) {
|
||||||
const wp = wps[i]!
|
const wp = wps[i]!
|
||||||
const rowY = WAYPOINT_ROW_OFFSETS_Y[i] ?? (60 + i * 36)
|
const rowY = WAYPOINT_ROW_OFFSETS_Y[i] ?? (60 + i * 36)
|
||||||
const wy = oy + rowY
|
const wy = oy + rowY
|
||||||
const isHovered = this.hoveredWaypointIdx === i
|
const isHovered = this.hoveredWaypointIdx === i
|
||||||
|
const isUnlocked = this.isWaypointUnlocked(wp)
|
||||||
|
const isCurrent = isUnlocked && this.isCurrentWaypoint(wp)
|
||||||
|
|
||||||
ctx.fillStyle = isHovered ? 'rgba(42, 34, 22, 0.88)' : 'rgba(18, 16, 13, 0.85)'
|
ctx.fillStyle = isHovered ? 'rgba(42, 34, 22, 0.88)' : 'rgba(18, 16, 13, 0.85)'
|
||||||
ctx.fillRect(ox + 17, wy, 281, 31)
|
ctx.fillRect(ox + 17, wy, 281, 31)
|
||||||
|
|
@ -661,20 +727,30 @@ export class WorldPanelsHud {
|
||||||
ctx.strokeRect(ox + 17.5, wy + 0.5, 280, 30)
|
ctx.strokeRect(ox + 17.5, wy + 0.5, 280, 30)
|
||||||
|
|
||||||
// Authentic D2 waypoint icon from waygateicons.dc6 (30x30)
|
// Authentic D2 waypoint icon from waygateicons.dc6 (30x30)
|
||||||
if (assets.waypointIconsImg && assets.waypointIconsImg.complete && assets.waypointIconsImg.naturalWidth > 0) {
|
// Decompiled D2Client.dll 1.13c at 0x6fb5b2f4 - 0x6fb5b355:
|
||||||
ctx.save()
|
// - Locked waypoint: skip drawing sprite (je 0x6fb5b355)
|
||||||
if (!wp.unlocked) ctx.globalAlpha = 0.35
|
// - Current waypoint (cmp 0x6fbacdd0, %esi == 0):
|
||||||
ctx.drawImage(assets.waypointIconsImg, iconSx, 0, 30, 30, ox + 17, wy + 1, 30, 30)
|
// hovered -> Frame 1 (sx = 30)
|
||||||
ctx.restore()
|
// unhovered -> Frame 0 (sx = 0)
|
||||||
} else {
|
// - Other activated waypoint (destination):
|
||||||
// Fallback stone diamond
|
// hovered -> Frame 4 (sx = 120)
|
||||||
ctx.fillStyle = wp.unlocked ? '#4888ff' : '#4a4a4a'
|
// unhovered -> Frame 3 (sx = 90)
|
||||||
ctx.fillRect(ox + 26, wy + 9, 12, 12)
|
if (isUnlocked) {
|
||||||
|
const iconFrameIdx = isCurrent ? (isHovered ? 1 : 0) : (isHovered ? 4 : 3)
|
||||||
|
const iconSx = iconFrameIdx * 30
|
||||||
|
|
||||||
|
if (assets.waypointIconsImg && assets.waypointIconsImg.complete && assets.waypointIconsImg.naturalWidth > 0) {
|
||||||
|
ctx.drawImage(assets.waypointIconsImg, iconSx, 0, 30, 30, ox + 17, wy + 1, 30, 30)
|
||||||
|
} else {
|
||||||
|
// Fallback indicator
|
||||||
|
ctx.fillStyle = isCurrent ? (isHovered ? '#78b4ff' : '#4888ff') : (isHovered ? '#f0d898' : '#c8aa6e')
|
||||||
|
ctx.fillRect(ox + 26, wy + 9, 12, 12)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
font.drawText(ctx, `${wp.nameZh} · ${wp.nameEn}`, ox + 80, wy + 21, {
|
font.drawText(ctx, `${wp.nameZh} · ${wp.nameEn}`, ox + 80, wy + 21, {
|
||||||
font: 'fontexocet10',
|
font: 'fontexocet10',
|
||||||
color: wp.unlocked ? (isHovered ? 'white' : 'gold') : 'gray',
|
color: isUnlocked ? (isHovered ? 'white' : 'gold') : 'gray',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (kind === 'stash') {
|
} else if (kind === 'stash') {
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,8 @@ describe('Issue #378: Diablo II 1.13c Waypoint Panel Tabs & Icons', () => {
|
||||||
} as unknown as HTMLImageElement
|
} as unknown as HTMLImageElement
|
||||||
|
|
||||||
hud.selectedActTab = 2
|
hud.selectedActTab = 2
|
||||||
|
// Set current waypoint to Lut Gholein (waypointId: 9, levelId: 40)
|
||||||
|
hud.setCurrentWaypoint(9, 40)
|
||||||
|
|
||||||
hud.drawLeftDockPanel(
|
hud.drawLeftDockPanel(
|
||||||
ctx,
|
ctx,
|
||||||
|
|
@ -107,17 +109,94 @@ describe('Issue #378: Diablo II 1.13c Waypoint Panel Tabs & Icons', () => {
|
||||||
expect(tabDraws[1][1]).toBe(2 * 63) // Act 2 (active) sx = 126
|
expect(tabDraws[1][1]).toBe(2 * 63) // Act 2 (active) sx = 126
|
||||||
expect(tabDraws[2][1]).toBe(5 * 63) // Act 3 (inactive) sx = 315
|
expect(tabDraws[2][1]).toBe(5 * 63) // Act 3 (inactive) sx = 315
|
||||||
|
|
||||||
// Verify waypoint icons drawn from mockIconsImg for Act 2 (frame index 1 -> sx 30)
|
// Verify waypoint icons drawn from mockIconsImg for Act 2:
|
||||||
|
// Ground Truth D2Client.dll 1.13c:
|
||||||
|
// - Index 0 (Lut Gholein): current waypoint -> Frame 0 (sx = 0)
|
||||||
|
// - Indices 1..8: activated destination waypoints -> Frame 3 (sx = 90)
|
||||||
const iconDraws = drawImageCalls.filter((c) => c[0] === mockIconsImg)
|
const iconDraws = drawImageCalls.filter((c) => c[0] === mockIconsImg)
|
||||||
const act2WpCount = (ACT_WAYPOINTS[2] ?? []).length
|
const act2WpCount = (ACT_WAYPOINTS[2] ?? []).length
|
||||||
expect(iconDraws.length).toBe(act2WpCount)
|
expect(iconDraws.length).toBe(act2WpCount)
|
||||||
for (const call of iconDraws) {
|
expect(iconDraws[0][1]).toBe(0) // Current waypoint: Frame 0 (sx = 0)
|
||||||
expect(call[1]).toBe(30) // sx = 30 for Act 2
|
for (let i = 1; i < act2WpCount; i++) {
|
||||||
expect(call[3]).toBe(30) // sw = 30
|
expect(iconDraws[i][1]).toBe(90) // Destination waypoints: Frame 3 (sx = 90)
|
||||||
expect(call[4]).toBe(30) // sh = 30
|
expect(iconDraws[i][3]).toBe(30)
|
||||||
|
expect(iconDraws[i][4]).toBe(30)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('Issue #393: renders hovered states and omits locked waypoint icons per D2 1.13c parity', () => {
|
||||||
|
const hud = new WorldPanelsHud()
|
||||||
|
const font = new D2FontRenderer()
|
||||||
|
|
||||||
|
const mockTabsImg = {
|
||||||
|
complete: true,
|
||||||
|
naturalWidth: 630,
|
||||||
|
naturalHeight: 31,
|
||||||
|
} as unknown as HTMLImageElement
|
||||||
|
|
||||||
|
const mockIconsImg = {
|
||||||
|
complete: true,
|
||||||
|
naturalWidth: 150,
|
||||||
|
naturalHeight: 30,
|
||||||
|
} as unknown as HTMLImageElement
|
||||||
|
|
||||||
|
const assets = {
|
||||||
|
questBgImg: null,
|
||||||
|
waypointBgImg: null,
|
||||||
|
borderLeftImg: null,
|
||||||
|
stashBgImg: null,
|
||||||
|
cubeBgImg: null,
|
||||||
|
vendorBgImg: null,
|
||||||
|
buySellBtnImg: null,
|
||||||
|
waypointTabsImg: mockTabsImg,
|
||||||
|
waypointIconsImg: mockIconsImg,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. In Act 4 (3 waypoints: Pandemonium Fortress, City of the Damned, River of Flame)
|
||||||
|
hud.selectedActTab = 4
|
||||||
|
hud.setCurrentWaypoint(27, 103) // Pandemonium Fortress is current waypoint
|
||||||
|
|
||||||
|
// Hover over current waypoint (index 0)
|
||||||
|
hud.hoveredWaypointIdx = 0
|
||||||
|
let drawCalls: any[] = []
|
||||||
|
const ctx = {
|
||||||
|
drawImage: vi.fn((...args: any[]) => drawCalls.push(args)),
|
||||||
|
fillRect: vi.fn(),
|
||||||
|
strokeRect: vi.fn(),
|
||||||
|
save: vi.fn(),
|
||||||
|
restore: vi.fn(),
|
||||||
|
measureText: vi.fn(() => ({ width: 60 })),
|
||||||
|
fillText: vi.fn(),
|
||||||
|
} as unknown as CanvasRenderingContext2D
|
||||||
|
|
||||||
|
hud.drawLeftDockPanel(ctx, 'waypoint', assets, font)
|
||||||
|
let iconDraws = drawCalls.filter((c) => c[0] === mockIconsImg)
|
||||||
|
expect(iconDraws.length).toBe(3)
|
||||||
|
expect(iconDraws[0][1]).toBe(30) // Hovered current waypoint: Frame 1 (sx = 30)
|
||||||
|
expect(iconDraws[1][1]).toBe(90) // Unhovered destination waypoint: Frame 3 (sx = 90)
|
||||||
|
expect(iconDraws[2][1]).toBe(90) // Unhovered destination waypoint: Frame 3 (sx = 90)
|
||||||
|
|
||||||
|
// Hover over destination waypoint (index 1: City of the Damned)
|
||||||
|
hud.hoveredWaypointIdx = 1
|
||||||
|
drawCalls = []
|
||||||
|
hud.drawLeftDockPanel(ctx, 'waypoint', assets, font)
|
||||||
|
iconDraws = drawCalls.filter((c) => c[0] === mockIconsImg)
|
||||||
|
expect(iconDraws.length).toBe(3)
|
||||||
|
expect(iconDraws[0][1]).toBe(0) // Unhovered current waypoint: Frame 0 (sx = 0)
|
||||||
|
expect(iconDraws[1][1]).toBe(120) // Hovered destination waypoint: Frame 4 (sx = 120)
|
||||||
|
expect(iconDraws[2][1]).toBe(90) // Unhovered destination waypoint: Frame 3 (sx = 90)
|
||||||
|
|
||||||
|
// 2. Locked waypoint: locked waypoints have NO icon drawn per D2Client.dll 0x6fb5b308 (je 0x6fb5b355)
|
||||||
|
hud.hoveredWaypointIdx = null
|
||||||
|
hud.setWaypointUnlocked(29, false) // Lock River of Flame (index 2)
|
||||||
|
drawCalls = []
|
||||||
|
hud.drawLeftDockPanel(ctx, 'waypoint', assets, font)
|
||||||
|
iconDraws = drawCalls.filter((c) => c[0] === mockIconsImg)
|
||||||
|
expect(iconDraws.length).toBe(2) // Only index 0 and 1 drawn, index 2 omitted
|
||||||
|
expect(iconDraws[0][1]).toBe(0) // Frame 0
|
||||||
|
expect(iconDraws[1][1]).toBe(90) // Frame 3
|
||||||
|
})
|
||||||
|
|
||||||
it('updates hoveredWaypointIdx on mouse move and highlights hovered waypoint', () => {
|
it('updates hoveredWaypointIdx on mouse move and highlights hovered waypoint', () => {
|
||||||
const hud = new WorldPanelsHud()
|
const hud = new WorldPanelsHud()
|
||||||
hud.selectedActTab = 1
|
hud.selectedActTab = 1
|
||||||
|
|
@ -150,35 +229,41 @@ describe('Issue #378: Diablo II 1.13c Waypoint Panel Tabs & Icons', () => {
|
||||||
expect(hud.selectedActTab).toBe(3)
|
expect(hud.selectedActTab).toBe(3)
|
||||||
|
|
||||||
// 2. In Act 3:
|
// 2. In Act 3:
|
||||||
// First waypoint (Kurast Docks) is unlocked
|
// Set Kurast Docks (index 0, waypointId: 18, levelId: 75) as current waypoint
|
||||||
|
hud.setCurrentWaypoint(18, 75)
|
||||||
const wps = ACT_WAYPOINTS[3]!
|
const wps = ACT_WAYPOINTS[3]!
|
||||||
expect(wps[0]!.unlocked).toBe(true)
|
|
||||||
|
|
||||||
// Temporarily mark index 1 as locked
|
// Mark index 1 (Spider Forest, waypointId: 19) as locked via hud method
|
||||||
;(wps[1] as any).unlocked = false
|
hud.setWaypointUnlocked(19, false)
|
||||||
|
|
||||||
try {
|
// Click locked waypoint (index 1): should NOT trigger teleport or close
|
||||||
// Click locked waypoint: should NOT trigger teleport or close
|
const lockedWy = oy + 62 + 1 * 36 + 10
|
||||||
const lockedWy = oy + 62 + 1 * 36 + 10
|
const lockedClick = hud.handleLeftDockClick('waypoint', ox + 50, lockedWy, {
|
||||||
const lockedClick = hud.handleLeftDockClick('waypoint', ox + 50, lockedWy, {
|
onWaypointTeleport,
|
||||||
onWaypointTeleport,
|
onClose,
|
||||||
onClose,
|
})
|
||||||
})
|
expect(lockedClick).toBe(true)
|
||||||
expect(lockedClick).toBe(true)
|
expect(onWaypointTeleport).not.toHaveBeenCalled()
|
||||||
expect(onWaypointTeleport).not.toHaveBeenCalled()
|
expect(onClose).not.toHaveBeenCalled()
|
||||||
expect(onClose).not.toHaveBeenCalled()
|
|
||||||
|
|
||||||
// Click unlocked waypoint (index 0): should trigger teleport and close
|
// Click current waypoint (index 0, Kurast Docks): should NOT trigger teleport (player already here)
|
||||||
const unlockedWy = oy + 62 + 0 * 36 + 10
|
const currentWy = oy + 62 + 0 * 36 + 10
|
||||||
const unlockedClick = hud.handleLeftDockClick('waypoint', ox + 50, unlockedWy, {
|
const currentClick = hud.handleLeftDockClick('waypoint', ox + 50, currentWy, {
|
||||||
onWaypointTeleport,
|
onWaypointTeleport,
|
||||||
onClose,
|
onClose,
|
||||||
})
|
})
|
||||||
expect(unlockedClick).toBe(true)
|
expect(currentClick).toBe(true)
|
||||||
expect(onWaypointTeleport).toHaveBeenCalledWith(3, wps[0]!.slug, wps[0]!.levelId)
|
expect(onWaypointTeleport).not.toHaveBeenCalled()
|
||||||
expect(onClose).toHaveBeenCalled()
|
expect(onClose).not.toHaveBeenCalled()
|
||||||
} finally {
|
|
||||||
;(wps[1] as any).unlocked = true
|
// Click unlocked destination waypoint (index 2, Great Marsh): should trigger teleport and close
|
||||||
}
|
const destWy = oy + 62 + 2 * 36 + 10
|
||||||
|
const destClick = hud.handleLeftDockClick('waypoint', ox + 50, destWy, {
|
||||||
|
onWaypointTeleport,
|
||||||
|
onClose,
|
||||||
|
})
|
||||||
|
expect(destClick).toBe(true)
|
||||||
|
expect(onWaypointTeleport).toHaveBeenCalledWith(3, wps[2]!.slug, wps[2]!.levelId)
|
||||||
|
expect(onClose).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue