include "inc/cmdsys.plh"
include "inc/conio.plh"

import roguemap
  const xcentr     = 20
  const ycentr     = 12
  const scrnwidth  = 40
  const scrnheight = 22
  const maprows    = 64
  const mapcols    = 64
  const mapsize    = maprows*mapcols

  const FLOOR_TILE  = '.'
  const WALL_TILE   = '#'
  const TORCH_TILE  = '*'
  const PIT_TILE    = ' '
  const DOOR_TILE   = '+'
  const LOCKED_TILE = '%'
  const WATER1_TILE = '>'
  const WATER2_TILE = '<'
  const KEY_TILE    = ','
  const RAFT_TILE   = '@'
  const GOLD_TILE   = '$'
  const FOOD_TILE   = '&'
  const ENTER_TILE  = '-'
  const EXIT_TILE   = '='
  const OPAQUE_TILE = $01
  const VIEWED_TILE = $80
  const LIT_TILE    = $80
  const INV_TILE    = $3F
  const MAP_TILE    = $7F

  predef loadmap(level), getmaptile(xmap, ymap), setmaptile(xmap, ymap, tile)#0
  predef updtmaptile(xmap, ymap, tile)#0, lighttorches#0
  predef drawmap(xorg, yorg, viewfield, viewdir, lightdist, viewdist)
  predef drawvisentity(xofst, yofst, tile)#0, drawplayer(tile)#0
  predef updatescreen#0
end

import roguecombat
  predef fight(player, enemy)
  word entity, entities
end

const maxlight = 10
const maxview  = 19

word xdir =  0,  1,  1,  1,  0, -1, -1, -1
word ydir = -1, -1,  0,  1,  1,  1,  0, -1
byte vplayer = '^', '\\', '>', '/', 'v', '\\', '<', '/'
byte totaldarkness = 0
byte level = 0
word free_entities

//
// Power-ups
//

const TORCH_OIL  = 250
const MANA       = 50
const RUN_ENERGY = 4

struc t_pos
  byte xpos
  byte ypos
end

//
// Player
//

struc t_player
  byte[t_pos]
  byte     angle
  byte     lamp
  byte     fov
  byte     skill
  byte     health
  byte     energy
  word     oil
  byte     gold
  byte     key
  byte     raft
  byte[16] name
end

byte player = 0   // xpos
byte        = 0   // ypos
byte        = 4   // angle
byte        = 4   // lamp
byte        = 1   // fov
byte        = 15  // skill
byte        = 100 // health
byte        = 100 // energy
word        = 500 // oil
byte        = 0   // gold
byte        = 0   // key
byte        = 0   // raft
byte[16]    = "Player"

predef moveplayer(dir)#0

//
// Other monsters
//

struc t_other
  byte[t_pos]
  byte kind
  byte tileid
  byte life
  byte power
  word prev_other
  word next_other
end

//
// One line status strings
//

const statusline = 23

byte helthstr = "Hlth:"
byte enrgystr = "NRG:"
byte oilstr   = "Oil:"
byte goldstr  = "Gld:"
byte keystr   = "Key"
byte raftstr  = "Flt"

//
// Messages
//

word instr
byte namestr      = "\nGreetings Warrior,\n  what is your name"
byte prepstr      = "\nPrepare."
byte quitstr      = "Are you sure you want to quit?"
byte youmadeitstr = "You made it out alive with $"
byte youdiedstr   = "You perished inside the catacombs :-("
byte againstr     = "\n\nPlay again by typing: +rogue\n"

//
// Utility functions
//

def abs(i)
  if i < 0
    i = -i
  fin
  return i
end

//
// Sound functions
//

def ouch#0
  conio:tone(128,5)
end

def gotit#0
  conio:tone(10,8)
  conio:tone(80,2)
end

def fall#0
  byte i

  for i = 0 to 10
    conio:tone(50, i)
  next
end

def drown#0
  word i

  conio:tone(10,20)
  conio:tone(10,1)
  for i = 0 to 1000
  next
  conio:tone(10,25)
  conio:tone(10,2)
  for i = 0 to 1000
  next
  conio:tone(10,30)
  conio:tone(10,3)
end

def groan#0
  byte i

  for i = 0 to 5
    conio:tone(5, 40 + i)
  next
end

//
// Update status line
//

def status#0
  memset($07D0, $A0A0, 40)
  conio:gotoxy(0, statusline)
  puts(@helthstr)
  puti(player.health)
  conio:gotoxy(9, statusline)
  puts(@enrgystr)
  puti(player.energy)
  conio:gotoxy(17, statusline)
  puts(@oilstr)
  puti(player:oil/10)
  conio:gotoxy(25, statusline)
  puts(@goldstr)
  puti(player.gold)
  if player.raft
    conio:gotoxy(32, statusline)
    puts(@raftstr)
  fin
  if player.key
    conio:gotoxy(36, statusline)
    puts(@keystr)
  fin
end

def clearstatus#0
  memset($07D0, $A0A0, 40)
end

//
// Move player, check for obstacles
//

def moveplayer(dir)#0
  byte xmove, ymove

  xmove = player.xpos + dir * xdir[player.angle]
  ymove = player.ypos + dir * ydir[player.angle]
  when getmaptile(xmove, ymove) & MAP_TILE
    is PIT_TILE
      fall
      player.energy = 0
      player.health = 0
      break
    is WATER1_TILE
    is WATER2_TILE
      if not player.raft
        drown
        player.energy = 0
        player.health = 0
        break
      fin
    is FLOOR_TILE
    is TORCH_TILE
    is KEY_TILE
    is RAFT_TILE
    is GOLD_TILE
    is FOOD_TILE
      if player.energy < 10
        player.fov = 0
      fin
      if player.energy
        player.energy = player.energy - 1
      fin
      player.xpos = xmove
      player.ypos = ymove
      break
    otherwise
      ouch
  wend
end

//
// Find other entities in map and put in database
//

def findentities#0
  word newother
  byte xmap, ymap, what

  for ymap = 1 to maprows - 2
    for xmap = 2 to mapcols - 2
      what = 0
      when getmaptile(xmap, ymap) & MAP_TILE
        is ENTER_TILE
          player.xpos = xmap
          player.ypos = ymap
          if getmaptile(xmap, ymap - 1) & MAP_TILE == FLOOR_TILE
            player.angle = 0
          else
            player.angle = 4
          fin
          break
    is 'R' // Rogue
      what = what + 1
    is 'Z' // Zombie
      what = what + 1
        is 'O' // Ogre
      what = what + 1
        is 'T' // Thief
      newother         = heapalloc(t_other)
      newother->xpos   = xmap
      newother->ypos   = ymap
      newother->kind   = what
      newother->tileid = ^(entity[what] + 1) // First character of name string
      newother->power  = ^(entity[what] + ^entity[what] + 1)
      newother->life   = 100
      //
      // Insert into head of entities list
      //
      newother=>prev_other = 0
      newother=>next_other = entities
      if entities
        entities=>prev_other = newother
      fin
      entities = newother
      //
      // Clear entity from map, replace with floor
      //
      setmaptile(xmap, ymap, FLOOR_TILE)
      wend
    next
    if not (ymap & 7)
      putc('.')
    fin
  next
end

//
// Draw other entities on map if visible
//

def drawentities#0
  word other, xofst, yofst

  other = entities
  while other
    xofst = other->xpos - player.xpos
    yofst = other->ypos - player.ypos
    if abs(xofst) < 20 and abs(yofst) < 12
      drawvisentity(xofst, yofst, other->tileid)
    fin
    other = other=>next_other
  loop
end

//
// Move entitiess
//

def lineofsight(x1, y1, x2, y2)
  word sx, sy, dx, dy, dx2, dy2, dd2, err

  dx = x2 - x1
  if dx < 0
    dx = -dx
    sx = -1
  else
    sx = 1
  fin
  dy = y2 - y1
  if dy < 0
    dy = -dy
    sy = -1
  else
    sy = 1
  fin
  if dx > maxview or dy > maxview
    return FALSE
  fin
  dx2 = dx << 1
  dy2 = dy << 1
  if dx > dy
    dd2 = dx2 - dy2
    err = dx  - dy2
    while x1 <> x2
      if err < 0
        y1 = y1 + sy
        err = err + dd2
      else
        err = err - dy2
      fin
      x1 = x1 + sx
      if getmaptile(x1, y1) & OPAQUE_TILE
        return FALSE
      fin
    loop
  else
    dd2 = dy2 - dx2
    err = dy  - dx2
    while y1 <> y2
      if err < 0
        x1 = x1 + sx
        err = err + dd2
      else
        err = err - dx2
      fin
      y1 = y1 + sy
      if getmaptile(x1, y1) & OPAQUE_TILE
        return FALSE
      fin
    loop
  fin
  return TRUE
end

def moveentities(playerisvis)#0
  byte xmove, ymove
  word other

  other = entities
  while other
    if playerisvis
      if lineofsight(player.xpos, player.ypos, other->xpos, other->ypos)
        xmove = other->xpos
        ymove = other->ypos
        if player.xpos > other->xpos
          xmove = other->xpos + 1
        elsif player.xpos < other->xpos
          xmove = other->xpos - 1
        fin
        if player.ypos > other->ypos
          ymove = other->ypos + 1
        elsif player.ypos < other->ypos
          ymove = other->ypos - 1
        fin
        when getmaptile(xmove, ymove) & MAP_TILE
          is FLOOR_TILE
          is TORCH_TILE
          is KEY_TILE
          is GOLD_TILE
          is FOOD_TILE
            other->xpos = xmove
            other->ypos = ymove
            groan
        wend
      fin
    fin
    if other->xpos == player.xpos and other->ypos == player.ypos
      if fight(@player, other)
        //
        // Player trying to run away
        //
        if player.energy > RUN_ENERGY
          moveplayer(1)
        fin
        moveplayer(1)
      fin
      return
    fin
    other = other=>next_other
  loop
end

//
// Draw entire map view
//

def drawview#0
  totaldarkness = drawmap(player.xpos, player.ypos, player.fov, player.angle, player.lamp, maxview)
  if not totaldarkness
    drawentities
    drawplayer(vplayer[player.angle])
  fin
  updatescreen
end

//
// Read player input and do something
//

def waitkey
  word delay

  delay = 200
  while not conio:keypressed()
    delay--
    if delay == 0
      drawview
      delay = 200
    fin
  loop
  return toupper(conio:getkey())
end

def play
  byte xt, yt

  if player.health == 0
    return FALSE
  fin
  conio:gotoxy(xcentr, ycentr)
  when waitkey
    is 'I'
      if totaldarkness
        player.angle = conio:rnd() & 7
      else
        player.angle = 0
      fin
      moveplayer(1)
      break
    is 'J'
      if totaldarkness
        player.angle = conio:rnd() & 7
      else
        player.angle = 6
      fin
      moveplayer(1)
      break
    is 'K'
      if totaldarkness
        player.angle = conio:rnd() & 7
      else
        player.angle = 2
      fin
      moveplayer(1)
      break
    is 'M'
      if totaldarkness
        player.angle = conio:rnd() & 7
      else
        player.angle = 4
      fin
      moveplayer(1)
      break
    is 'Q'
      if player.energy > RUN_ENERGY
        player.energy = player.energy - RUN_ENERGY
        moveplayer(1)
      fin
    is 'W'
    is $0B // Up Arrow
      moveplayer(1)
      break
    is 'S'
    is $0A // Down Arrow
      moveplayer(-1)
      break
    is 'A'
    is $08 // Left Arrow
      player.angle = (player.angle - 1) & 7
      break
    is 'D'
    is $15 // Right Arrow
      player.angle = (player.angle + 1) & 7
      break
    is ' '
      when getmaptile(player.xpos + xdir[player.angle], player.ypos + ydir[player.angle]) & MAP_TILE
        is LOCKED_TILE
          if not player.key
            ouch
            break
          fin
        is DOOR_TILE
          updtmaptile(player.xpos + xdir[player.angle], player.ypos + ydir[player.angle], FLOOR_TILE)
      break
    is ENTER_TILE
      break
    is EXIT_TILE
      return FALSE
      wend
      break
    is $0D // Return
      when getmaptile(player.xpos, player.ypos) & MAP_TILE
        is KEY_TILE
          player.key = 1
          updtmaptile(player.xpos, player.ypos, FLOOR_TILE)
          gotit
          break
        is RAFT_TILE
          player.raft = 1
          updtmaptile(player.xpos, player.ypos, FLOOR_TILE)
          gotit
          break
        is GOLD_TILE
          player.gold = player.gold + 1
          updtmaptile(player.xpos, player.ypos, FLOOR_TILE)
          gotit
          break
        is TORCH_TILE
          if player.oil < 1000
            player:oil = player:oil + TORCH_OIL
            if player:oil > 1000
              player:oil = 1000
            fin
            setmaptile(player.xpos, player.ypos, FLOOR_TILE)
            for yt = player.ypos - 1 to player.ypos + 1
              for xt = player.xpos - 1 to player.xpos + 1
                setmaptile(xt, yt, getmaptile(xt, yt) & MAP_TILE)
              next
            next
            gotit
          fin
          break
        is FOOD_TILE
          if player.health < 100 or player.energy < 100
            player.health = player.health + MANA/2
            if player.health > 100
              player.health = 100
            fin
            player.energy = player.energy + MANA
            if player.energy > 100
              player.energy = 100
            fin
            updtmaptile(player.xpos, player.ypos, FLOOR_TILE)
            player.fov = 1
            gotit
          fin
          break
      wend
      break
    is 'O'
      player.lamp = 0
      break
    is '<'
    is ','
      if player.lamp > 0
        player.lamp = player.lamp - 1
      fin
      break
    is '>'
    is '.'
      if player.lamp < maxlight - 1
        player.lamp = player.lamp + 1
      fin
      break
    is 'X'
      clearstatus
      conio:gotoxy(0, statusline)
      puts(@quitstr)
      if toupper(conio:getkey()) == 'Y'
        player.health = 0
    return FALSE
      fin
  wend
  if player.energy and player.health < 100
    player.health = player.health + 1
  fin
  if player:oil > player.lamp
    player:oil = player:oil - player.lamp
  else
    player:oil  = 0
    player.lamp = 0
  fin
  return player.health
end

//
// Main loop
//

puts(@namestr)
instr = gets($BA)
if ^instr > 15
  ^instr = 15
fin
memcpy(@player.name, instr, ^instr + 1)
conio:echo(ECHO_OFF)
while loadmap(level)
  puts(@prepstr)
  free_entities = heapmark()
  findentities
  lighttorches
  repeat
    moveentities(player.lamp or getmaptile(player.xpos, player.ypos) & LIT_TILE))
    drawview
    status
  until not play
  heaprelease(free_entities)
  entities = 0
  player.key = 0
  level = level + 1
  clearstatus
  conio:gotoxy(0, statusline)
  if player.health == 0
    break
  fin
loop
conio:echo(ECHO_ON)
if player.health > 0
  puts(@youmadeitstr)
  puti(player.gold)
else
  puts(@youdiedstr)
fin
puts(@againstr)
done
