///////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2017 The 8-Bit Bunch. Licensed under the Apache License, Version 1.1
// (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at <http://www.apache.org/licenses/LICENSE-1.1>.
// Unless required by applicable law or agreed to in writing, software distributed under
// the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
// ANY KIND, either express or implied. See the License for the specific language
// governing permissions and limitations under the License.
//////////////////f/////////////////////////////////////////////////////////////////////////////////

include "gamelib.plh"
include "globalDefs.plh"
include "playtype.plh"
include "gen_modules.plh"

const MAP_TOP     = 11 // lines
const MAP_LEFT    = 2  // bytes

const SCREEN_ROWS = 21
const SCREEN_COLS = 36

const SECTION_WIDTH_2D  = 22
const SECTION_HEIGHT_2D = 23

const specPtr     = $BC // must match asm definition below for pSpecials
const specScanFor = $BE // must match asm definition below for scanFor
const specGodMask = $BF // must match asm definition below for godMask

struc TMapSection
  byte bm_mapNum
  word pm_map
  byte bm_tilesetNum
  word pm_tileset
  byte bm_visible
  byte bm_needMarks
  word wm_x0   // upper-left coord of map in global space
  word wm_y0
  word wm_x1   // bottom-right coord of map (exclusive) in global space
  word wm_y1
  byte bm_sx   // clipped screen coordinate
  byte bm_sy
  byte bm_ox   // clipped offset within map to display
  byte bm_oy
  byte bm_cw   // clipped size within map to display
  byte bm_ch
end

const SECT_BUF_SIZE = MAX_MAP_SECT * TMapSection

// Exported functions go here. First a predef for each one, then a table with function pointers
// in the same order as the constants are defined in the header.
predef _automap_show(targetX, targetY)#1
word[] funcTbl = @_automap_show

word tilePtrs[40]
byte blankTile[9]

word screenX0, screenY0, screenX1, screenY1

byte nHorzSects
byte sectionBuf[SECT_BUF_SIZE]
word pLastSection
word pAllMarks
word pSpecialsBuf
word pSpecialTiles
word scanSpecials = $100 // placed at $100 so it can easily access aux mem
word specTileset
word targetX, targetY

// Pixel representation for displaying the target (shape of an "X")
//byte[] targetData = 0, $63, $36, $1C, $1C, $36, $63, 0
byte[] targetData = 0, 0, $36, $1C, $1C, $36, 0, 0

///////////////////////////////////////////////////////////////////////////////////////////////////
// Definitions used by assembly code
asm _defs
!convtab "../../include/hiBitAscii.ct"
!source "../../include/global.i"
!source "../../include/plasma.i"
!source "../../include/mem.i"
!source "../../include/fontEngine.i"
!source "../../include/marks.i"
tmp         = $2   ; length 2
pTmp        = $4   ; length 2
tilePtrs    = $6   ; length 2
pTile       = $8   ; length 2
pBlank      = $A   ; length 2
hibits      = $C   ; length 1
nTiles      = $D   ; length 1
prevBits    = $E   ; length 1
unused0F    = $F   ; length 1
linePtrs    = $10  ; length 2*8 = 16
ysav        = $34  ; length 1
; do not use $50..B5, as the current render (tile engine or raycaster) use these
pSrc        = $B8  ; length 2
pDst        = $BA  ; length 2
pSpecials   = $BC  ; length 2
scanFor     = $BE  ; length 1
godMask     = $BF  ; length 1

HB          = evalStkH-evalStkL   ; offset to get from lo byte to hi byte on eval stack

end

///////////////////////////////////////////////////////////////////////////////////////////////////
asm drawSlice(pBlank, pScreen, nTiles, tilePtrs)#0
!zone
  +asmPlasmNoRet 4
  sta tilePtrs
  sty tilePtrs+1
  lda evalStkL+1,x
  sta nTiles
  lda evalStkL+2,x
  sta pTmp
  lda evalStkH+2,x
  sta pTmp+1
  lda evalStkL+3,x
  sta pBlank
  lda evalStkH+3,x
  sta pBlank+1
  ; Grab the line pointers for all 8 lines
  ldx #0
- lda pTmp
  sta linePtrs,x
  inx
  lda pTmp+1
  sta linePtrs,x
  inx
  jsr NextScreenLine
  cpx #16
  bne -
  ; Process one tile
.tile
  ldy #0
  lda (tilePtrs),y
  sta pTile
  iny
  lda (tilePtrs),y
  sta pTile+1
  lda tilePtrs
  clc
  adc #2
  sta tilePtrs
  bcc +
  inc tilePtrs+1
+ dey
  lda (pTile),y
  sta hibits
  sta (pBlank),y   ; if next tile is blank, match hi bits of this tile
  iny
  ldx #0
  lda linePtrs
  lsr
  bcs .odd
.even
  lda (pTile),y
  iny
  asl
  ror prevBits
  lsr hibits
  ror
  sta (linePtrs,x)
  inc linePtrs,x
  inx
  inx
  cpx #16
  bne .even
  beq .next   ; always taken
.odd
  lda (pTile),y
  iny
  lsr prevBits
  rol
  asl
  lsr hibits
  ror
  sta (linePtrs,x)
  inc linePtrs,x
  inx
  inx
  cpx #16
  bne .odd
.next
  dec nTiles
  bne .tile
  rts
end

///////////////////////////////////////////////////////////////////////////////////////////////////
asm showCursor(color, pScreen)#0
!zone
  +asmPlasmNoRet 2
  sta pTmp
  sty pTmp+1
  lda evalStkL+1,x
  ldx #8
- ldy #0
  sta (pTmp),y
  pha
  jsr NextScreenLine
  pla
  dex
  bne -
  rts
end

///////////////////////////////////////////////////////////////////////////////////////////////////
asm scroll(srcLine, srcOff, dstLine, dstOff, width, dir, nLines)#0
!zone
  +asmPlasmNoRet           7
.param_srcLine  = evalStkL+6
.param_srcOff   = evalStkL+5
.param_dstLine  = evalStkL+4
.param_dstOff   = evalStkL+3
.param_width    = evalStkL+2
.param_dir      = evalStkL+1
.param_nLines   = evalStkL+0
.xsav           = tmp
.width          = tmp+1
  stx .xsav
  lda .param_width,x
  sta .width
.lineloop
  lda .param_srcLine,x
  jsr GetScreenLine
  ldx .xsav
  clc
  adc .param_srcOff,x
  sta pSrc
  sty pSrc+1
  lda .param_dstLine,x
  jsr GetScreenLine
  ldx .xsav
  clc
  adc .param_dstOff,x
  sta pDst
  sty pDst+1
  lda .param_dir,x
  bmi .reverse
.forward
  ldy #0
- lda (pSrc),y
  sta (pDst),y
  iny
  cpy .width
  bne -
  inc .param_srcLine,x
  inc .param_dstLine,x
.next
  dec .param_nLines,x
  bne .lineloop
  rts
.reverse
  ldy .width
  dey
- lda (pSrc),y
  sta (pDst),y
  dey
  bpl -
  dec .param_srcLine,x
  dec .param_dstLine,x
  bne .next               ; always taken
end

///////////////////////////////////////////////////////////////////////////////////////////////////
asm fillRow(mapRowData, mask, tileTrans, pSmallTiles, pSpecialTiles, blankTile, width, tilePtrs)#0
!zone
  +asmPlasmNoRet                8
.param_mapRowData    = evalStkL+7
.param_mask          = evalStkL+6
.param_tileTrans     = evalStkL+5
.param_pSmallTiles   = evalStkL+4
.param_pSpecialTiles = evalStkL+3
.param_blankTile     = evalStkL+2
.param_width         = evalStkL+1
.param_tilePtrs      = evalStkL+0
.srcOff = scanFor
.dstOff = pTile     ; temporary use since we don't need it in this routine
.special = pTile+1  ; ditto
; psudocode:
;  byte x, mapRaw, tileNum
;  for x = 0 to width-1
;    mapRaw = ^(mapRowData + x) & $1F
;    if mapRaw and (^(mapRowData + x) & $40) // automap 'seen' flag
;      tileNum = scanSpecials(x)
;      if tileNum
;        tilePtrs[x] = pSpecialTiles + ((tileNum-1) * 9) // 9 bytes per reduced tile in the tileset
;      else
;        if tileTrans
;          tileNum = ^(tileTrans + ((mapRaw - 1) << 1))
;        else
;          tileNum = mapRaw
;        fin
;        tilePtrs[x] = pSmallTiles + ((tileNum-1) * 9)  // 9 bytes per reduced tile in the tileset
;      fin
;    else
;      tilePtrs[x] = @blankTile
;    fin
;  next
  lda .param_mapRowData,x
  sta pSrc
  lda .param_mapRowData+HB,x
  sta pSrc+1
  lda .param_tilePtrs,x
  sta pDst
  lda .param_tilePtrs+HB,x
  sta pDst+1
  lda .param_tileTrans,x
  sta pTmp
  lda .param_tileTrans+HB,x
  sta pTmp+1
  lda #0
  sta .srcOff
  sta .dstOff
.loop
  ldy .srcOff
  lda (pSrc),y      ; get raw data
  ora godMask       ; mask in show-all mode
  asl
  bpl .blank        ; skip unless $40 bit is set denoting this space has been seen
  lsr
  pha
  lda pSpecials+1
  pha
  lda pSpecials
  pha
  jsr $100          ; scan for automap specials override
  sta .special
  tay
  pla
  sta pSpecials
  pla
  sta pSpecials+1
  pla
  dex               ; index pSpecialTiles instead of pSmallTiles
  cpy #0
  bne .notrans      ; ... if it's a special tile
  inx               ; otherwise, normal processing (no special override)
  and .param_mask,x ; mask to get just tile number
  beq .blank
  tay
  lda pTmp+1
  beq .notrans
  dey
  tya
  asl
  tay
  lda (pTmp),y
  tay
.notrans
  dey               ; tileNum - 1
  lda #0
  sta tmp+1
  sty tmp
  tya               ; multiply by 9 bytes per reduced tile in the tileset
  asl
  rol tmp+1
  asl
  rol tmp+1
  asl
  rol tmp+1
  adc tmp
  bcc +
  inc tmp+1
+ clc               ; X may have been adjusted to address pSpecialTiles instead
  adc .param_pSmallTiles,x
  pha
  lda tmp+1
  adc .param_pSmallTiles+HB,x
  ldy .special
  beq +
  inx               ; back to using normal pSmallTiles
+ bcc .store        ; always taken
.blank
  lda .param_blankTile,x
  pha
  lda .param_blankTile+HB,x
.store
  ldy .dstOff
  iny
  sta (pDst),y
  dey
  pla
  sta (pDst),y
  iny
  iny
  sty .dstOff
  inc .srcOff
  dec .param_width,x
  bne .loop
  rts
end

///////////////////////////////////////////////////////////////////////////////////////////////////
asm writeMarks()#0
!zone
  +asmPlasmNoRet 0
  sei
  sta setAuxZP
  bit setLcRW+lcBank1
  jsr writeMarks
  bit setLcRW+lcBank2
  sta clrAuxZP
  cli
  rts
end

///////////////////////////////////////////////////////////////////////////////////////////////////
asm mergeMarks(allMarks, mapNum, mapData, width, stride, height)#0
!zone {
  +asmPlasmNoRet           6
.param_allMarks = evalStkL+5
.param_mapNum   = evalStkL+4
.param_mapData  = evalStkL+3
.param_width    = evalStkL+2
.param_stride   = evalStkL+1
.param_height   = evalStkL+0
.byteCt = pTmp
.bitCt  = pTmp+1

  ; First, set up a routine in zero page to read a byte from aux mem
.jsr
  jsr fixedRTS
  stx ysav
  tsx
  dex
  lda $100,x
  clc
  adc #.rdaux-.jsr-2
  sta pTmp
  lda $101,x
  adc #0
  sta pTmp+1
  ldy #12
- lda (pTmp),y
  sta $10,y
  dey
  bpl -
  bmi .go   ; always taken - skip over the routine we just copied

.rdaux
  sei     ; prevent interrupts while in aux mem
  sta setAuxRd
  lda (pSrc),y
  iny
  sta clrAuxRd
  cli
  rts

.go
  ldx ysav
  lda .param_allMarks,x
  sta pSrc
  lda .param_allMarks+HB,x
  sta pSrc+1
.scan
  ldy #0
  jsr $10             ; read map num
  cmp .param_mapNum,x
  beq .found
  cmp #0
  bne +
  +prChr 'M'
  brk                 ; not found!!?
+ jsr $10             ; read size
  clc
  adc pSrc
  sta pSrc
  bcc .scan
  inc pSrc+1
  bne .scan           ; always taken

.found
  lda .param_mapData,x
  sta pDst
  lda .param_mapData+HB,x
  sta pDst+1
  iny                 ; skip over size byte
  ; Process a row of data
.row
  lda .param_width,x
  sta .byteCt
  lda #0
  sta .bitCt
  ; Process one byte (column) in the row
.column
  dec .bitCt
  bpl +
  jsr $10
  sta tmp
  lda #7
  sta .bitCt
+ lsr tmp
  bcc .nextcol
  ldx #0
  lda (pDst,x)
  ora #$40
  sta (pDst,x)
.nextcol
  inc pDst
  bne +
  inc pDst+1
+ dec .byteCt
  bne .column
.nextrow
  ldx ysav
  lda .param_stride,x
  sec
  sbc .param_width,x
  clc
  adc pDst
  sta pDst
  bcc +
  inc pDst+1
+ dec .param_height,x
  bne .row
  rts
} ; end of zone
end

///////////////////////////////////////////////////////////////////////////////////////////////////
asm _scanSpecials_start()#0
; Advance pSpecials looking for 'scanFor'
!zone {
  ; This gets copied to and run from $100 so it can easily access aux mem
  sta setAuxRd
.loop
  ldy #0
  lda (pSpecials),y
  iny
  cmp scanFor
  beq +
  eor #$FF          ; if end of table...
  beq .ret          ; ...return zero (and don't go past end of table)
  lda (pSpecials),y
  tay
+ tya               ; at this point, y=1 is match, 2+ is no-match skipping
  sec               ; yes, add one extra
  adc pSpecials
  sta pSpecials
  bcc +
  inc pSpecials+1
+ dey               ; if len was 1, it was match; >=2 no match, loop again
  bne .loop
  lda (pSpecials),y ; y=0: match, get the value inside
.ret
  sta clrAuxRd
  rts
}
end
asm _scanSpecials_end()#0
  nop ; dummy
end

///////////////////////////////////////////////////////////////////////////////////////////////////
// Swap 8x7 pixel area from the target graphic buffer to the screen.
asm swapTarget(x, y, swapBuf)#0
!zone {
  +asmPlasmNoRet 3
  sta pSrc          ; swapBuf
  sty pSrc+1
  lda evalStkL+1,x
  sta tmp+1         ; y
  lda evalStkL+2,x
  sta tmp           ; x
  lda #8            ; lineCount
.nextLine
  pha
  lda tmp+1
  jsr GetScreenLine
  ldy tmp
  ldx #0
  lda (pSrc,x)      ; swap buffer byte...
  pha
  lda (pTmp),y      ; ...with screen byte
  sta (pSrc,x)
  pla
  sta (pTmp),y
  inc pSrc          ; next buffer position
  bne +
  inc pSrc+1
+ inc tmp+1         ; advance to next line
  pla
  sec
  sbc #1            ; decrement count
  bne .nextLine     ; loop until done
  rts
}
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def loadMarks()#0
  word len

  // Load the specials table since partition zero is probably still open
  auxMmgr(START_LOAD, 1) // partition 1 is where the table lives
  pSpecialsBuf = auxMmgr(QUEUE_LOAD, CODE_GEN_MAP_SPECIALS<<8 | RES_TYPE_CODE)

  // While we're at it, load the automap special tiles.
  specTileset = mmgr(QUEUE_LOAD, 2<<8 | RES_TYPE_SM_TILESET) // always tileset 2 (just after global tiles)
  mmgr(FINISH_LOAD, 0)

  // Call the automark write routine. This has not only the effect of flushing the marks
  // to disk, but also leaving them loaded for us at $4000 (main).
  writeMarks

  // Copy the marks to aux, where they'll be safe from memory manager operations
  len = *$4000
  pAllMarks = auxMmgr(REQUEST_MEMORY, len)
  memcpy($4002, pAllMarks, len, 1)

  // Figure out where the small versions of the special tiles reside
  pSpecialTiles = specTileset + 3
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def displayRow3D(pScreen, mapRowData, tileTrans, pSmallTiles, pSpecialTiles, width)#0
  fillRow(mapRowData, $1F, tileTrans, pSmallTiles, pSpecialTiles, @blankTile, width, @tilePtrs)
  drawSlice(@blankTile, pScreen, width, @tilePtrs)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def displayRow2D(pScreen, mapRowData, pSmallTiles, pSpecialTiles, width)#0
  fillRow(mapRowData, $3F, NULL, pSmallTiles, pSpecialTiles, @blankTile, width, @tilePtrs)
  drawSlice(@blankTile, pScreen, width, @tilePtrs)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def make3DSection(pSection)#0
  // Record the map data (it's already loaded for 3D display)
  pSection->bm_mapNum = mapNum
  pSection=>pm_map = pCurMap
  pSection->bm_needMarks = TRUE

  // Record the tile set number
  pSection->bm_tilesetNum = ^(pSection=>pm_map + 3)
  pSection=>pm_tileset = NULL

  // Record the coordinates
  pSection=>wm_x0 = 0
  pSection=>wm_y0 = 0
  pSection=>wm_x1 = totalMapWidth
  pSection=>wm_y1 = totalMapHeight
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def make2DSection(pSection, sh, sv)#0
  // Record the map data (the top-left segment is already loaded)
  pSection->bm_mapNum = mapNum + (sv*nHorzSects) + sh
  pSection=>pm_map = (pSection->bm_mapNum == mapNum) ?? pCurMap :: NULL
  pSection->bm_needMarks = TRUE

  // Tileset will be calculated later
  pSection->bm_tilesetNum = 0
  pSection=>pm_tileset = NULL

  // Record the coordinates
  pSection=>wm_x0 = sh * SECTION_WIDTH_2D
  pSection=>wm_y0 = sv * SECTION_HEIGHT_2D
  pSection=>wm_x1 = pSection=>wm_x0 + SECTION_WIDTH_2D
  pSection=>wm_y1 = pSection=>wm_y0 + SECTION_HEIGHT_2D
end

///////////////////////////////////////////////////////////////////////////////////////////////////
//def printSection(pSection)#0
//  printf1("Sect %d: ", (pSection - @sectionBuf) / TMapSection)
//  printf2("%d/%d - ", pSection=>wm_x0, pSection=>wm_y0)
//  printf2("%d/%d; ", pSection=>wm_x1, pSection=>wm_y1)
//  printf1("vis=%d ", pSection->bm_visible)
//  if pSection->bm_visible
//    printf2("scrn=%d/%d ", pSection->bm_sx, pSection->bm_sy)
//    printf2("off=%d/%d ", pSection->bm_ox, pSection->bm_oy)
//    printf2("clip=%dw/%dh", pSection->bm_cw, pSection->bm_ch)
//  fin
//  puts(".\n")
//end

///////////////////////////////////////////////////////////////////////////////////////////////////
def prepSections(extraLeft, extraTop)#0
  word pSection
  word x0, y0, x1, y1
  byte anyQueued

  anyQueued = FALSE
  mmgr(START_LOAD, curMapPartition)
  pSection = @sectionBuf
  while pSection <> pLastSection
    x0 = max(screenX0, pSection=>wm_x0)
    y0 = max(screenY0, pSection=>wm_y0)
    x1 = min(screenX1, pSection=>wm_x1)
    y1 = min(screenY1, pSection=>wm_y1)
    if x0 < x1 and y0 < y1
      pSection->bm_visible = TRUE
      pSection->bm_sx = x0 - screenX0 + extraLeft
      pSection->bm_sy = y0 - screenY0 + extraTop
      pSection->bm_ox = x0 - pSection=>wm_x0
      pSection->bm_oy = y0 - pSection=>wm_y0
      pSection->bm_cw = x1 - x0
      pSection->bm_ch = y1 - y0
      if !pSection=>pm_map
        // Note, this only happens in 2D mode (3D has only one map, which always stays loaded)
        pSection=>pm_map = mmgr(QUEUE_LOAD, (pSection->bm_mapNum)<<8 | RES_TYPE_2D_MAP)
        anyQueued = TRUE
      fin
    else
      pSection->bm_visible = FALSE
    fin
    pSection = pSection + TMapSection
  loop

  // Make sure all maps are loaded before we attempt to figure out their tilesets
  if anyQueued
    mmgr(FINISH_LOAD, 0)
    mmgr(START_LOAD, curMapPartition)
  fin

  // Load all the tilesets
  pSection = @sectionBuf
  while pSection <> pLastSection
    if pSection->bm_visible and !pSection=>pm_tileset
      if !pSection->bm_tilesetNum
        // This part only happens in 2D mode (a 3D section have tileset filled in at construction)
        pSection->bm_tilesetNum = ^(pSection=>pm_map + 4)
      fin
      pSection=>pm_tileset = mmgr(QUEUE_LOAD, (pSection->bm_tilesetNum)<<8 | RES_TYPE_SM_TILESET)
    fin
    pSection = pSection + TMapSection
  loop

  mmgr(FINISH_LOAD, 0)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def freeSections#0
  word pSection
  pSection = @sectionBuf
  while pSection <> pLastSection
    if pSection=>pm_map and pSection=>pm_map <> pCurMap
      mmgr(FREE_MEMORY, pSection=>pm_map)
      pSection=>pm_map = NULL
      pSection->bm_needMarks = TRUE
    fin
    if pSection=>pm_tileset
      mmgr(FREE_MEMORY, pSection=>pm_tileset)
      pSection=>pm_tileset = NULL
    fin
    pSection = pSection + TMapSection
  loop
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def displaySection3D(pSection, pSmallTiles)#0
  word tileTrans, mapData, rowData, specBlk
  byte nTextures, y, rowSize, line, lx

  // Extract significant pointers from the map blob
  rowSize = ^(pSection=>pm_map)
  tileTrans = pSection=>pm_map + 4
  nTextures = 0
  while ^(tileTrans + (nTextures<<1))
    nTextures++
  loop
  mapData = tileTrans + (nTextures*3) + 2  // *2 for tilenum+texnum, *1 for texture flags, 2 for zero-terms

  // Merge automap marks into the data if not done yet
  rowData = mapData + rowSize + 1  // skip sentinel row and column that 3D maps have
  if pSection->bm_needMarks
    //         allMarks   mapNum                     mapData  width      stride   height
    mergeMarks(pAllMarks, pSection->bm_mapNum | $80, rowData, rowSize-2, rowSize, ^(pSection=>pm_map+1)-2)
    pSection->bm_needMarks = FALSE
  fin

  rowData = rowData + (pSection->bm_oy * rowSize) + pSection->bm_ox
  // The << 3 below is because each row is 8 screen lines
  line = (pSection->bm_sy << 3) + MAP_TOP
  lx = MAP_LEFT + pSection->bm_sx

  // Find the map data in the specials buf
  *specPtr = pSpecialsBuf
  ^specScanFor = pSection->bm_mapNum | $80
  scanSpecials()#0
  specBlk = *specPtr

  // Display each visible row
  for y = 1 to pSection->bm_ch  // offset is 1 to skip over sentinel row
    *specPtr = specBlk
    ^specScanFor = y + pSection->bm_oy - 1
    scanSpecials()#0
    displayRow3D(getScreenLine(line) + lx, rowData, tileTrans, pSmallTiles, pSpecialTiles, pSection->bm_cw)
    rowData = rowData + rowSize
    line = line + 8
  next
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def displaySection2D(pSection, pSmallTiles)#0
  word rowData, specBlk
  byte y, line, lx

  // Merge automap marks into the data if not done yet
  rowData = pSection=>pm_map + 6  // header = 6 bytes
  if pSection->bm_needMarks
    //         allMarks   mapNum               mapData  width             stride            height
    mergeMarks(pAllMarks, pSection->bm_mapNum, rowData, SECTION_WIDTH_2D, SECTION_WIDTH_2D, SECTION_HEIGHT_2D)
    pSection->bm_needMarks = FALSE
  fin

  rowData = rowData + (pSection->bm_oy * SECTION_WIDTH_2D) + pSection->bm_ox
  // The << 3 below is because each row is 8 screen lines
  line = (pSection->bm_sy << 3) + MAP_TOP
  lx = MAP_LEFT + pSection->bm_sx

  // Find the map data in the specials buf
  *specPtr = pSpecialsBuf
  ^specScanFor = pSection->bm_mapNum
  scanSpecials()#0
  specBlk = *specPtr

  // Display each visible row
  for y = 1 to pSection->bm_ch
    *specPtr = specBlk
    ^specScanFor = y + pSection->bm_oy - 1
    scanSpecials()#0
    displayRow2D(getScreenLine(line) + lx, rowData, pSmallTiles, pSpecialTiles, pSection->bm_cw)
    rowData = rowData + SECTION_WIDTH_2D
    line = line + 8
  next
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def displaySections#0
  word pSection, pSmallTiles, x
  pSection = @sectionBuf

  // Set up routine that scans the specials table in aux mem (routine lives in low stack page)
  memcpy(@_scanSpecials_start, scanSpecials, @_scanSpecials_end - @_scanSpecials_start, 0)

  while pSection <> pLastSection
    //printSection(pSection)
    if pSection->bm_visible
      // Figure out where the small tiles reside (at the end of the full size tiles)
      pSmallTiles = pSection=>pm_tileset + 3

      // And draw (different routines to handle differing map data layouts)
      if mapIs3D
        displaySection3D(pSection, pSmallTiles)
      else
        displaySection2D(pSection, pSmallTiles)
      fin
    fin
    pSection = pSection + TMapSection
  loop

  // Free memory when done
  freeSections
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def scrollUp(dist)#0
  byte nLines
  nLines = dist << 3

  screenY0 = screenY0 + (SCREEN_ROWS-dist)
  prepSections(0, SCREEN_ROWS-dist)

  //     srcLine         srcOff    dstLine  dstOff    width        dir  nLines
  scroll(MAP_TOP+nLines, MAP_LEFT, MAP_TOP, MAP_LEFT, SCREEN_COLS, 1,   (SCREEN_ROWS<<3)-nLines)

  //        top,                             bottom,                   left,       right
  setWindow(MAP_TOP+(SCREEN_ROWS<<3)-nLines, MAP_TOP+(SCREEN_ROWS<<3), MAP_LEFT*7, (MAP_LEFT+SCREEN_COLS)*7)
  clearWindow

  displaySections
  screenY0 = screenY0 - (SCREEN_ROWS-dist)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def scrollDown(dist)#0
  byte bottom, nLines
  bottom = MAP_TOP + (SCREEN_ROWS<<3) - 1
  nLines = dist << 3

  screenY1 = screenY1 - (SCREEN_ROWS-dist)
  prepSections(0, 0)

  //     srcLine        srcOff    dstLine  dstOff    width        dir  nLines
  scroll(bottom-nLines, MAP_LEFT, bottom,  MAP_LEFT, SCREEN_COLS, -1,  (SCREEN_ROWS<<3)-nLines)

  //        top,     bottom,         left,       right
  setWindow(MAP_TOP, MAP_TOP+nLines, MAP_LEFT*7, (MAP_LEFT+SCREEN_COLS)*7)
  clearWindow

  displaySections
  screenY1 = screenY1 + (SCREEN_ROWS-dist)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def scrollLeft(dist)#0
  screenX0 = screenX0 + (SCREEN_COLS-dist)
  prepSections(SCREEN_COLS-dist, 0)

  //     srcLine  srcOff         dstLine  dstOff    width             dir   nLines
  scroll(MAP_TOP, MAP_LEFT+dist, MAP_TOP, MAP_LEFT, SCREEN_COLS-dist, 1,    SCREEN_ROWS<<3)

  //        top,     bottom,                   left,                          right
  setWindow(MAP_TOP, MAP_TOP+(SCREEN_ROWS<<3), (MAP_LEFT+SCREEN_COLS-dist)*7, (MAP_LEFT+SCREEN_COLS)*7)
  clearWindow

  displaySections
  screenX0 = screenX0 - (SCREEN_COLS-dist)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def scrollRight(dist)#0
  word bottom
  bottom = MAP_TOP + (SCREEN_ROWS<<3) - 1

  screenX1 = screenX1 - (SCREEN_COLS-dist)
  prepSections(0, 0)

  //     srcLine  srcOff    dstLine dstOff         width             dir   nLines
  scroll(bottom,  MAP_LEFT, bottom, MAP_LEFT+dist, SCREEN_COLS-dist, -1,   SCREEN_ROWS<<3)

  //        top,     bottom,                   left,       right
  setWindow(MAP_TOP, MAP_TOP+(SCREEN_ROWS<<3), MAP_LEFT*7, (MAP_LEFT+dist)*7)
  clearWindow

  displaySections
  screenX1 = screenX1 + (SCREEN_COLS-dist)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def setup3D#0
  make3DSection(@sectionBuf)
  pLastSection = @sectionBuf + TMapSection
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def setup2D#0
  word pSection
  byte nVertSects, sh, sv
  nHorzSects = (totalMapWidth + SECTION_WIDTH_2D - 1) / SECTION_WIDTH_2D
  nVertSects = (totalMapHeight + SECTION_HEIGHT_2D - 1) / SECTION_HEIGHT_2D
  pLastSection = @sectionBuf + ((nVertSects * nHorzSects) * TMapSection)
  sh = 0
  sv = 0
  pSection = @sectionBuf
  while pSection <> pLastSection
    make2DSection(pSection, sh, sv)
    sh++
    if sh == nHorzSects
      sh = 0
      sv++
    fin
    pSection = pSection + TMapSection
  loop
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def centerScreen#0
  word x, y
  getPos(@x, @y)
  screenX0 = (x - (SCREEN_COLS>>1)) & ~1 // force to be even
  screenY0 = y - (SCREEN_ROWS>>1)
  screenX1 = screenX0 + SCREEN_COLS
  screenY1 = screenY0 + SCREEN_ROWS
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def showTarget(targetX, targetY)#0
  word hx, hy, cx, cy, dx, dy, x, y
  byte color, xbit, xbyte

  // If the target is within the screen area, our work is easy
  if targetX >= screenX0 and targetX < screenX1 and targetY >= screenY0 and targetY < screenY1
    x = targetX - screenX0
    y = targetY - screenY0
  else

    // Target is outside the screen area. Use line calculations to show the direction by clipping it.
    hx = SCREEN_COLS >> 1 // half width
    hy = SCREEN_ROWS >> 1 // half height

    cx = (screenX0 + screenX1)>>1  // center coordinate
    cy = (screenY0 + screenY1)>>1

    dx = targetX - cx
    dy = targetY - cy

    // for a line through the origin 0,0:
    //     m = dy/dx
    //     y = mx
    // so: y = dy*x / dx

    if dy < 0
      // Intersect with top
      y = -hy
      // -hy = dy*x / dx
      // so: -hy*dx = dy*x
      x = (-hy * dx) / dy
    elsif dy > 0
      // Intersect with bottom
      y = hy
      // hy = dy*x / dx
      // so: hy*dx = dy*x
      x = (hy*dx) / dy
    elsif dx < 0
      y = 0
      x = -hx
    else
      y = 0
      x = hx-1
    fin

    if x < -hx
      // Intersect with left
      x = -hx
      y = (dy*-hx) / dx
    elsif x > hx-1
      // Intersect with right
      x = hx-1
      y = (dy*(hx-1)) / dx
    fin

    // Finally adjust for the origin at the center of the screen
    x = x + hx
    y = y + hy
  fin

  swapTarget(x + MAP_LEFT, (y<<3) + MAP_TOP, @targetData)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def cursorWait(targetX, targetY)#1
  word playerX, playerY, i, j
  byte phase, color, targetOn
  getPos(@playerX, @playerY)
  phase = 0
  targetOn = 0
  i = 100
  while ^kbd < 128

    // Blink the target on and off at a slower rate
    i++
    if i > 3 and targetX <> 9999
      if playerX == targetX and playerY == targetY and !targetOn
        showCursor(0, getScreenLine(((playerY-screenY0)<<3)+MAP_TOP) + (playerX-screenX0+MAP_LEFT))
      fin
      targetOn = !targetOn
      showTarget(targetX, targetY)
      i = 0
    fin

    // Determine color for the player marker
    when phase
      is 0; color = $AA; break
      is 1; color = $D5; break
      is 2; color = $AA; break
      is 3; color = $D5; break
      is 4; color = $FF; break
    wend
    if playerX >= screenX0 and playerX < screenX1 and playerY >= screenY0 and playerY < screenY1
      if playerX <> targetX or playerY <> targetY or !targetOn
        showCursor(color, getScreenLine(((playerY-screenY0)<<3)+MAP_TOP) + (playerX-screenX0+MAP_LEFT))
      fin
    fin
    j = 0
    while j < 200 and ^kbd < 128
      j++
    loop
    phase = (phase+1) % 5
  loop

  // Erase target if it's still showing
  if targetX <> 9999 and targetOn; showTarget(targetX, targetY); fin

  i = recordKey
  return charToUpper(i)
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def showTitle#0
  setWindow(2, 12, 98, 182) // Top, Bottom, Left, Right
  clearWindow
  rawDisplayStr("^V001")
  centerStr(global=>s_mapName, 84)
  setBigWindow
end

///////////////////////////////////////////////////////////////////////////////////////////////////
def _automap_show(targetX, targetY)#1
  byte key

  // Clear out the blank tile buffer
  memset(@blankTile, 0, 9)

  // Setup phase
  if mapIs3D; setup3D; else setup2D; fin
  setBigWindow
  ^EMUSIG_AUTOMAP
  clearWindow
  showTitle

  centerScreen
  loadMarks
  prepSections(0,0)
  ^specGodMask = 0
  displaySections

  // Now allow the player to scroll the map
  repeat
    key = cursorWait(targetX, targetY)
    when key
      is 'W'; is 'I'; is 11
        if screenY1-8 > 0
          screenY0 = screenY0 - 8   // north
          screenY1 = screenY1 - 8
          scrollDown(8)
        fin
        break
      is 'D'; is 'L'; is 21
        if screenX0+8 < totalMapWidth-1
          screenX0 = screenX0 + 8   // east
          screenX1 = screenX1 + 8
          scrollLeft(8)
        fin
        break
      is 'S'; is 'K'; is 10
        if screenY0+8 < totalMapHeight-1
          screenY0 = screenY0 + 8   // south
          screenY1 = screenY1 + 8
          scrollUp(8)
        fin
        break
      is 'A'; is 'J'; is 8
        if screenX1-8 > 0
          screenX0 = screenX0 - 8   // west
          screenX1 = screenX1 - 8
          scrollRight(8)
        fin
        break
      is '*'
        if global->b_godmode
          ^specGodMask = ^specGodMask ^ $40 // toggle display-all
          prepSections(0,0)
          displaySections
        fin
        break
    wend
  until key == 27 or key == 'Q' or key == 'M' // esc or Q or - to exit

  // Free up stuff we allocated or loaded
  auxMmgr(FREE_MEMORY, pAllMarks)
  auxMmgr(FREE_MEMORY, pSpecialsBuf)
  mmgr(FREE_MEMORY, specTileset)

  // Ensure the global tileset is reloaded (otherwise tile eng will try to load it from wrong partition)
  mmgr(START_LOAD, 1)
  pGlobalTileset = mmgr(QUEUE_LOAD, 1<<8 | RES_TYPE_TILESET)
  mmgr(FINISH_LOAD, 0)

  // All done.
  return 0
end

///////////////////////////////////////////////////////////////////////////////////////////////////
// Boilerplate module initialization code
return @funcTbl
done
