########################################################################
# Many of the tests in this script might seem silly. That is okay.
#
# See interactivetest.hss for tests which require user input.
#
# Any time you are preparing to fix an engine bug, consider adding a
# test that demonstrates the bug FIRST before you fix the bug.

include, hstests.hss

########################################################################

define constant(1, default wait)

global variable(100, menu item script global)
global variable(101, timer global)
global variable(102, timer global will become)
global variable(103, battle script sequence)
global variable(104, menu close global)
global variable(105, embedding global)
global variable(106, export global 1)
global variable(107, export global 2)
global variable(108, expecting death)
global variable(109, died)
global variable(110, map autorun triggered)
global variable(111, eachstep triggered)
global variable(112, textbox script triggered)
global variable(113, npc script triggered)
global variable(114, testing script triggers)
global variable(115, savegame map)
global variable(116, savegame slice)
global variable(117, savegame npc)
# 200-300 used in hstests.hss
global variable(200, ticker 0)
global variable(201, ticker 1)
global variable(202, ticker 2)
global variable(203, expected ticker 0)
global variable(204, expected ticker 1)
global variable(205, tick counter slice)
global variable(206, a global)
global variable(207, another global)
global variable(5000, global 5000)

#string 0 = error messages
#string 1-9 = temporary misc
#string 10 = error message arg
#string 11 = asserts
#string 95-96 = temp misc
define constant(10, err arg string)
define constant(11, assert expression string)

########################################################################

# Set on starting map 0
plotscript, mapautorun, begin
  # Test that the map autorun script on the starting map happens before
  # the newgame script
  map autorun triggered += 1
end

plotscript, run all tests, begin
  suspend player
  seed random(4444)
  assert(map autorun triggered == 1)
  interpreter tests
  plotstr tests
  save slot tests
  slice tests
  inventory tests
  hero tests
  death tests
  trigger tests
  door tests
  npc tests
  menu tests
  textbox tests
  script trigger tests
  enemy tests
  battle tests
  timer tests
  savegame tests 1  # Saves and loads the game
end

plotscript, load game, begin
  savegame tests 2
  $0="TESTS SUCCEEDED"
  trace(0)
  gameover
end

########################################################################

script, w, ticks=default wait, begin
  wait(ticks)
  _checkpoint
end

script, crash, begin
  if(string length(10) >> 0) then(
    $0+"("
    concatenate strings(0, err arg string)
    $0+")"
  )
  show text box(1)
  trace(0)
  w(25)
  # script error(0)
  game over
end

script, assert failure, begin
  $1="ASSERT FAILURE:"
  trace(1)
  trace(assert expression string)
  crash
end

########################################################################

script, inventory tests, begin
  $0="Inventory tests"
  # This tests only the inventory and commands to manipulate it.
  # Far more item tests including some equipment/inventory stuff is in 'equipment tests'

  # Precondition: empty inventory

  assert(get inventory size == 600)
  variable(slot, lastslot)
  for (slot, 0, get inventory size -- 1) do (
    assert(item in slot(slot) == -1)
    assert(item count in slot(slot) == 0)
  )

  # Simple stuff
  get item(item:Boots, 3)
  assert(inventory(item:Boots) == 3)
  assert(item in slot(0) == item:Boots)
  assert(item count in slot(0) == 3)
  get item(item:Boots, 99)
  assert(inventory(item:Boots) == 102)
  delete item(item:Boots)
  assert(inventory(item:Boots) == 101)
  delete item(item:Boots, 101)
  assert(inventory(item:Boots) == 0)

  # test item slot commands

  assert(item in slot(0) == -1)
  assert(item count in slot(0) == 0)
  assert(item in slot(1) == -1)
  assert(item count in slot(1) == 0)

  set item in slot(1, item:Boots)  # used on empty slot, adds one item
  assert(item in slot(1) == item:Boots)
  assert(item count in slot(1) == 1)
  assert(inventory(item:Boots) == 1)
  lastslot := get inventory size -- 1
  set item in slot(lastslot, item:Boots)
  assert(item in slot(lastslot) == item:Boots)
  set item count in slot(lastslot, 99)
  assert(inventory(item:Boots) == 100)

  # check deleteitem deletes in right order
  delete item(item:Boots, 5)
  assert(inventory(item:Boots) == 95)
  assert(item count in slot(1) == 0)
  assert(item count in slot(lastslot) == 95)

  # transmog a stack of items
  set item in slot(lastslot, item:SteelSho)
  assert(item in slot(lastslot) == item:SteelSho)
  assert(item count in slot(lastslot) == 95)
  assert(inventory(item:SteelSho) == 95)
  assert(inventory(item:Boots) == 0)

  # test the two additional ways of deleting items
  set item in slot(lastslot, -1)
  assert(item count in slot(lastslot) == 0)
  set item in slot(2, item:Boots)
  set item count in slot(2, 0)
  assert(item in slot(2) == -1) 

  # leave nothing
  for (slot, 0, get inventory size -- 1) do (
    assert(item in slot(slot) == -1)
  )

  # test max stack sizes
  assert(get item maximum stack size(item:Boots) == 99)
  assert(get item maximum stack size(item:Bulky) == 3)
  get item(item:Bulky, 6)
  assert(inventory(item:Bulky) == 6)
  assert(item count in slot(0) == 3)
  assert(item count in slot(1) == 3)
  set item in slot(2, item:Boots)
  set item count in slot(2, 40)
  # changing type should cause stacks to overflow if too large
  set item in slot(2, item:Bulky)
  assert(inventory(item:Bulky) == 46)
  assert(item count in slot(2) == 3)
  delete item(item:Bulky, 46)
end

########################################################################

script, hero tests, begin
  hero party management tests
  # The party now contains heroes 0, 1, 2, 3 in that order
  hero appearance tests
  hero caterpillar tests
  hero exp and levels tests
  hero stat growth test
  hero stat tests
  equipment tests
  hero misc tests
  hero names embed tests
  # The party should continue to be 0, 1, 2, 3
end

## hero commands left to test

## spell lists
#can learn spell (hero,attack)
#forget spell (hero,attack)
#knows spell (hero,attack)
#read spell (hero,list,slot)
#teach spell (hero,attack)
#write spell (hero,list,slot,attack)

## Equipment
#set default weapon (hero,item)

## Stats
#get level MP (who, mp level slot, type)
#set level MP (who, mp level slot, new value)

## Misc
#hero base elemental resist as int (who, element)
#hero total elemental resist as int (who, element)
#set hero base elemental resist (who, element, percent)

script, hero party management tests, begin
  $0="hero party management tests"
  assert(room in active party == 3)
  add hero(hero:Helga)
  add hero(hero:Olaf)
  add hero(hero:Frumpy)
  assert(room in active party == 0)
  assert(hero by rank(0) == hero:Freki)
  assert(hero by rank(1) == hero:Helga)
  assert(hero by rank(2) == hero:Olaf)
  assert(hero by rank(3) == hero:Frumpy)
  # with a full party, hero by rank and hero by slot should returnt he same values
  assert(hero by rank(0) == hero by slot(0))
  assert(hero by rank(1) == hero by slot(1))
  assert(hero by rank(2) == hero by slot(2))
  assert(hero by rank(3) == hero by slot(3))
  # now delete the leader
  delete hero(hero:Freki)
  assert(room in active party == 1)
  assert(hero by rank(0) == 1)
  assert(hero by rank(1) == 2)
  assert(hero by rank(2) == 3)
  assert(hero by rank(3) == -1)
  assert(hero by slot(0) == -1)
  assert(hero by slot(1) == 1)
  assert(hero by slot(2) == 2)
  assert(hero by slot(3) == 3)
  # add a different leader
  add hero(hero:Styrge)
  assert(hero by slot(0) == hero:Styrge)
  # test find hero
  assert(find hero(hero:Freki)  == -1)
  assert(find hero(hero:Helga)  == 1)
  assert(find hero(hero:Olaf)   == 2)
  assert(find hero(hero:Frumpy) == 3)
  assert(find hero(hero:Styrge) == 0)
  # test rank in caterpillar
  assert(rank in caterpillar(hero:Freki)  == -1)
  assert(rank in caterpillar(hero:Helga)  == 1)
  assert(rank in caterpillar(hero:Olaf)   == 2)
  assert(rank in caterpillar(hero:Frumpy) == 3)
  assert(rank in caterpillar(hero:Styrge) == 0)
  # Add a hero to the reserve
  add hero(hero:Kitt)
  assert(room in active party == 0)
  variable(slot)
  slot := find hero(hero:Kitt)
  assert(slot >= 4)
  assert(rank in caterpillar(hero:Kitt) == -1)
  assert(hero by slot(slot) == hero:Kitt) # this command needs to work on reserve slots
  assert(hero by rank(4) == -1) # this command should not work on reserve slots
  # names
  $1="Styrge"
  get hero name(2, 0)
  assert(string compare(1, 2))
  $1="Helga"
  get hero name(2, 1)
  assert(string compare(1, 2))
  $1="Olaf"
  get hero name(2, 2)
  assert(string compare(1, 2))
  $1="Frumpy"
  get hero name(2, 3)
  assert(string compare(1, 2))
  $1="Kitt"
  get hero name(2, find hero(hero:Kitt))
  assert(string compare(1, 2))
  $1="Kittzilla"
  set hero name(1, find hero(hero:Kitt))
  get hero name(2, find hero(hero:Kitt))
  assert(string compare(1, 2))
  # Swapping
  swap out hero(hero:Helga)
  assert(hero by slot(1) == -1)
  assert(find hero(hero:Helga) >= 4)
  swap in hero(hero:Kitt)
  assert(hero by slot(1) == hero:Kitt)
  assert(find hero(hero:Kitt) >> 0 && find hero(hero:Kitt) <= 3)
  swap by position(0, 3) # swap Styrge and Frumpy
  assert(hero by slot(0) == hero:Frumpy)
  assert(hero by slot(3) == hero:Styrge)
  swap by position(3, find hero(hero:Helga)) # swap Styrge and Helga
  assert(hero by slot(3) == hero:Helga)
  assert(find hero(hero:Styrge) >= 4)
  swap by name(hero:Kitt, hero:Styrge)
  assert(hero by slot(1) == hero:Styrge)
  assert(find hero(hero:Kitt) >= 4)
  swap out hero(hero by slot(0)) # swap out frumpy
  assert(hero by slot(0) == -1)
  # restore numeric order
  add hero(hero:Freki) # Freki
  assert(hero by slot(0) == hero:Freki)
  swap by position(1, find hero(hero:Helga))
  assert(hero by slot(1) == hero:Helga)
  swap by position(2, find hero(hero:Olaf))
  assert(hero by slot(2) == hero:Olaf)
  swap by position(3, find hero(hero:Frumpy))
  assert(hero by slot(3) == hero:Frumpy)
end

script, hero appearance tests, begin
  # preconditions
  assert(hero by slot(3) == hero:Frumpy)
  # ensure we're using party slots, not hero ids or caterpillar position
  swap by position(2, 3)
  swap by position(1, 10)

  # 2 = hero:Frumpy
  assert(get hero picture(2, inside battle) == 8)
  assert(get hero picture(2, outside battle) == 9)
  assert(get hero picture(2, hero portrait) == 2)
  assert(get hero palette(2, inside battle) == -1)
  assert(get hero palette(2, outside battle) == -1)
  assert(get hero palette(2, hero portrait) == 2)

  set hero picture(2, 0, inside battle)
  set hero picture(2, 4, outside battle)
  set hero picture(2, 7, hero portrait)
  set hero palette(2, 37, inside battle)
  set hero palette(2, 1, outside battle)
  set hero palette(2, -1, hero portrait)

  show text box(17)  # test portrait by party slot
  w(3)
  advance text box

  assert(get hero picture(2, inside battle) == 0)
  assert(get hero picture(2, outside battle) == 4)
  assert(get hero picture(2, hero portrait) == 7)
  assert(get hero palette(2, inside battle) == 37)
  assert(get hero palette(2, outside battle) == 1)
  assert(get hero palette(2, hero portrait) == -1)

  reset hero picture(2, inside battle)
  reset hero picture(2, outside battle)
  reset hero picture(2, hero portrait)
  reset hero palette(2, inside battle)
  reset hero palette(2, outside battle)
  reset hero palette(2, hero portrait)

  assert(get hero picture(2, inside battle) == 8)
  assert(get hero picture(2, outside battle) == 9)
  assert(get hero picture(2, hero portrait) == 2)
  assert(get hero palette(2, inside battle) == -1)
  assert(get hero palette(2, outside battle) == -1)
  assert(get hero palette(2, hero portrait) == 2)

  # Restore state
  swap by position(2, 3)
  swap by position(1, 10)
end

script, hero names embed tests, begin
  $0="hero names embed tests"

  variable(h0, h1, h2, h3)
  # remember the old party order
  h0 := hero by slot(0)
  h1 := hero by slot(1)
  h2 := hero by slot(2)
  h3 := hero by slot(3)
  
  swap by position(0, find hero(hero:Olaf))
  swap out hero(hero by slot(1))
  swap out hero(hero by slot(2))
  swap out hero(hero by slot(3))

  $1="Olaf,,,"
  $2="Olaf,,,"
  $3="Freki,Helga,Olaf,Frumpy,Styrge"
  hero names embed helper

  swap by position(2, find hero(hero:Olaf))
  $1="Olaf,,,"
  $2=",,Olaf,"
  hero names embed helper

  swap by position(0, find hero(hero:Frumpy))
  $1="Frumpy,Olaf,,"
  $2="Frumpy,,Olaf,"
  hero names embed helper

  swap by position(1, find hero(hero:Olaf))
  swap by position(3, find hero(hero:Frumpy))
  $1="Olaf,Frumpy,,"
  $2=",Olaf,,Frumpy"
  hero names embed helper

  add hero(hero:Olaf)  # slot 1
  $4="Nolaf"
  set hero name(4, 1)
  $1="Olaf,Nolaf,Frumpy,"
  $2="Olaf,Nolaf,,Frumpy"
  hero names embed helper

  swap by position(0, find hero(hero:Frumpy))
  $1="Frumpy,Nolaf,Olaf,"
  $2="Frumpy,Nolaf,,Olaf"
  $3="Freki,Helga,Nolaf,Frumpy,Styrge"
  hero names embed helper

  show no value, w

  # restore old party order
  swap by position(0, 1)  # Nolaf to front
  delete hero(hero:Olaf)
  swap by position(0, find hero(h0))  
  swap by position(1, find hero(h1))
  swap by position(2, find hero(h2))
  swap by position(3, find hero(h3))
  show box names
  advance textbox
end

script, hero names embed helper, begin
  show string(2)
  $4="${C0},${C1},${C2},${C3}"
  expand string(4)
  $5="${P0},${P1},${P2},${P3}"
  expand string(5)
  $6="${H0},${H1},${H2},${H3},${H4}"
  expand string(6)
  assert(string compare(1, 4))
  assert(string compare(2, 5))
  assert(string compare(3, 6))
  show box names
end

script, show box names, begin
  show text box(9)
  w(10)
  show text box(10)
  w(10)
  show text box(11)
  w(10)
end

## Party management
#lock hero (who)
#unlock hero (who)

script, hero caterpillar tests, begin
  variable(i)
  $0="hero caterpillar tests"

  $err arg string="leader, cater=ON"
  do hero cater tests(me)
  
  suspend caterpillar
  for(i, 0, 3) do(
    $err arg string="hero "
    append number(err arg string, i)
    $err arg string=", cater=OFF"
    do hero cater tests(i)
  )
  resume caterpillar
  $err arg string=""

end

script, do hero cater tests, who, begin
  variable(d)
  walk hero to x (who, 8), w
  wait for hero(who)
  assert(hero x(who) == 8)
  walk hero to y(who, 10), w
  wait for hero(who)
  assert(hero y(who) == 10)
  camera follows hero(who)
  walk hero to x(who, 9), w
  wait for hero(who)
  assert(hero x(who) == 9)
  walk hero to y(who, 8), w
  wait for hero(who)
  assert(hero y(who) == 8)
  camera follows hero(0)
  walk hero to x (who, 6), w
  wait for hero(who)
  assert(hero x(who) == 6)
  set hero position(who, 7, 7), w
  assert(hero x(who) == 7)
  assert(hero y(who) == 7)
  walk hero(who, south, 2)
  wait for hero(who), w
  assert(hero y(who) == 9)
  assert(hero direction(who) == south)
  walk hero(who, west, 2)
  wait for hero(who), w
  assert(hero x(who) == 5)
  assert(hero direction(who) == west)
  walk hero(who, north, 1)
  wait for hero(who), w
  assert(hero y(who) == 8)
  assert(hero direction(who) == north)
  walk hero(who, east, 1)
  wait for hero(who), w
  assert(hero x(who) == 6)
  assert(hero direction(who) == east)
  # spin
  for(d, north, west) do(
    set hero direction(who, d), w
    assert(hero direction(who) == d)
  )
  # moonwalk
  for(d, north, west) do(
    walk hero(who, ((d + 2) ,mod, 4))
    set hero direction(who, d)
    wait for hero(who), w
    assert(hero direction(who) == d)
  )
  # walk into wall
  walk hero(who, north, 3) # should only make it 2 tiles
  wait for hero(who)
  assert(hero Y(who) == 6)
  # check walls
  expect hero walls(who, true, false, false, false)
  walk hero(who, west, 1)
  wait for hero(who), w
  walk hero(who, north, 1)
  wait for hero(who), w
  expect hero walls(who, false, true, false, false)
  walk hero(who, north, 2)
  wait for hero(who), w
  walk hero(who, east, 1)
  wait for hero(who), w
  expect hero walls(who, false, false, true, false)
  walk hero(who, east, 5)
  wait for hero(who), w
  expect hero walls(who, true, true, false, false)
  # speed test time!
  set hero speed(who, 2)
  assert(get hero speed(who) == 2)
  walk hero(who, south, 1)
  w(9), assert(hero is walking(who))
  w(1), assert(hero is walking(who) == false)
  set hero speed(who, 4)
  assert(get hero speed(who) == 4)
  walk hero(who, south, 1)
  w(4), assert(hero is walking(who))
  w(1), assert(hero is walking(who) == false)
  set hero speed(who, 5)
  assert(get hero speed(who) == 5)
  walk hero(who, south, 1)
  w(3), assert(hero is walking(who))
  w(1), assert(hero is walking(who) == false)
  set hero speed(who, 10)
  assert(get hero speed(who) == 10)
  walk hero(who, south, 1)
  w(1), assert(hero is walking(who))
  w(1), assert(hero is walking(who) == false)
  set hero speed(who, 20)
  assert(get hero speed(who) == 20)
  walk hero(who, south, 1)
  w(1), assert(hero is walking(who) == false)
  walk hero(who, west, 1), w
  walk hero(who, north, 2), w(2)
  assert(hero x(who) == 10)
  assert(hero y(who) == 6)
  set hero speed(who)
  assert(get hero speed(who) == 4)
  walk hero(who, west, 3)
  wait for hero(who)
  #FIXME: should probably test misaligned walking too
  # frame
  variable(fr)
  for(fr, 1, 8) do(
    set hero frame(who, (fr ,mod, 2))
    assert(hero frame(who) == (fr ,mod, 2))
    w(fr)
  )
  #pixel pos
  variable(px, py)
  px := hero pixel x(who)
  assert(px == 140)
  py := hero pixel y(who)
  assert(py == 120)
  variable(ix, iy)
  for(iy, 0, 5) do(
    for(ix, 0, 5) do(
      put hero(who, 105 + ix, 105 + iy)
      assert(hero pixel x(who) == 105 + ix)
      assert(hero pixel y(who) == 105 + iy)
      w
    )
  )
  put hero(who, px, py), w
end

script, expect hero walls, who, wn, we, ws, ww, begin
  assert(check hero wall(who, north) == wn)
  assert(check hero wall(who, east) == we)
  assert(check hero wall(who, south) == ws)
  assert(check hero wall(who, west) == ww)
end

## caterpillar
#suspend hero walls
#resume hero walls
#set hero frame (who, frame)
#set hero speed (who, speed)
#set hero z (who, z)

script, hero exp and levels tests, begin
  variable(i)
  $0="hero exp and levels tests"
  assert(experience to level(1) == 30)
  assert(experience to level(2) == 71)
  assert(experience to level(3) == 125)
  assert(experience to level(4) == 195)
  assert(experience to level(5) == 284)
  assert(experience to level(6) == 396)
  assert(experience to level(7) == 535)
  assert(experience to level(8) == 707)
  assert(experience to level(9) == 918)
  assert(experience to level(10) == 1176)
  assert(experience to level(11) == 1491)
  assert(experience to level(12) == 1874)
  assert(experience to level(13) == 2339)
  assert(experience to level(14) == 2902)
  assert(experience to level(15) == 3583)
  assert(experience to level(99) == 50183838)
  assert(get hero level(0) == 0)
  # level setting
  for(i, 1, 99) do(
    set hero level(0, i)
    assert(get hero level(0) == i)
    assert(total experience(0) == experience to level(i))
  )
  # level-based spell learning/forgetting
  set hero level(0, 9)
  assert(knows spell (0, 2) == false) # Freki should not have L.10 Wolf at level 9
  set hero level(0, 10)
  assert(knows spell (0, 2) == true) # Freki should learn L.10 Wolf at level 10
  set hero level(0, 0, false)
  assert(get hero level(0) == 0)
  assert(hero levelled(0) == -10)
  assert(knows spell (0, 2) == true) # Freki should not forget L.10 Wolf because of how we deleveled
  update level up learning(0, true)
  assert(knows spell (0, 2) == false) # Now Freki should forget L.10 Wolf.
  # experience
  assert(experience to next level(0) == 30)
  give experience(0, 5)
  assert(hero levelled(0) == 0)
  assert(experience to next level(0) == 25)
  give experience(0, 25)
  assert(get hero level(0) == 1)
  assert(hero levelled(0) == 1)
  assert(experience to next level(0) == 41)
  give experience(0, -1) # de-level should not work with this command, but experience will go down
  assert(get hero level(0) == 1)
  assert(hero levelled(0) == 0)
  assert(experience to next level(0) == 42)
  give experience(0, 42)
  assert(get hero level(0) == 2)
  assert(hero levelled(0) == 1)
  set hero level(0, 9)
  give experience(0, experience to next level(0))
  assert(get hero level(0) == 10)
  assert(hero levelled(0) == 1)
  assert(spells learned(0, get count) == 1)
  assert(spells learned(0, 0) == 2)
  assert(spells learnt(0, 0) == 1) #deprecated, but why not test it?
  give experience(0, 1) # should not cause levelling
  assert(hero levelled == 0)
  assert(spells learned(0, get count) == 0)
  # split experience
  set hero level(0, 0, true)
  give experience(party, 30)
  assert(hero levelled(0) == 0)
  assert(total experience(0) == 8)
  assert(total experience(1) == 8)
  assert(total experience(2) == 8)
  assert(total experience(3) == 8)
  give experience(party, 88)
  assert(get hero level(0) == 1)
  assert(get hero level(1) == 1)
  assert(get hero level(2) == 1)
  assert(get hero level(3) == 1)
  assert(total experience(find hero(hero:Kitt)) == 0) # outside of the party
  # experience for dead heroes
  set hero level(0, 0, true)
  set hero level(1, 0, true)
  set hero level(2, 0, true)
  set hero level(3, 0, true)
  set hero stat(2, 0, 0) # kill Olaf
  set dead heroes gain experience (false)
  give experience(party, 120)
  assert(total experience(0) == 40)
  assert(total experience(1) == 40)
  assert(total experience(2) == 0)
  assert(total experience(3) == 40)
  set hero level(0, 0, true)
  set hero level(1, 0, true)
  set hero level(2, 0, true)
  set hero level(3, 0, true)
  set dead heroes gain experience (true)
  give experience(party, 120)
  assert(total experience(0) == 30)
  assert(total experience(1) == 30)
  assert(total experience(2) == 30)
  assert(total experience(3) == 30)
  set hero level(0, 0, true)
  set hero level(1, 0, true)
  set hero level(2, 0, true)
  set hero level(3, 0, true)
  set hero stat(2, 0, get hero stat(2, 0, maximum stat)) # revive Olaf
  # level caps
  set hero level(0, 1)
  assert(get level cap == 99)
  set level cap(2)
  assert(get level cap == 2)
  give experience(0, 41)
  assert(get hero level(0) == 2)
  assert(total experience(0) == 71)
  assert(experience to next level(0) == 54) # being at the level cap does not alter the exp to next level
  give experience(0, 10000000)
  assert(hero levelled(0) == 0)
  assert(get hero level(0) == 2)
  set level cap(99)
  # reset
  set hero level(0, 0, true)
  set hero level(1, 0, true)
  set hero level(2, 0, true)
  set hero level(3, 0, true)
end

script, hero stat growth test, begin
  $0="hero stat growth test"
  show string(0), w
  # This tests the crappy default stat growth curve, which we will hopefully replace someday
  set hero level(0 ,0)
  assert(get hero stat(0, 0, maximum stat) == 10)
  set hero level(0 ,1)
  assert(get hero stat(0, 0, maximum stat) == 13)
  set hero level(0 ,2)
  assert(get hero stat(0, 0, maximum stat) == 16)
#  set hero level(0 ,3)
#  assert(get hero stat(0, 0, maximum stat) == 19)
#  set hero level(0 ,4)
#  assert(get hero stat(0, 0, maximum stat) == 23)
#  set hero level(0 ,5)
#  assert(get hero stat(0, 0, maximum stat) == 26)
#  set hero level(0 ,6)
#  assert(get hero stat(0, 0, maximum stat) == 30)
#  set hero level(0 ,7)
#  assert(get hero stat(0, 0, maximum stat) == 34)
#  set hero level(0 ,8)
#  assert(get hero stat(0, 0, maximum stat) == 38)
#  set hero level(0 ,9)
#  assert(get hero stat(0, 0, maximum stat) == 42)
  set hero level(0 ,10)
  assert(get hero stat(0, 0, maximum stat) == 46)

  assert(get level mp(0, 0) == 5)
  assert(get level mp(0, 1) == 3)
  assert(get level mp(0, 2) == 2)
  assert(get level mp(0, 3) == 1)
  assert(get level mp(0, 4) == 0)
  assert(get level mp(0, 5) == 0)
  assert(get level mp(0, 6) == 0)
  assert(get level mp(0, 7) == 0)

#  set hero level(0 ,11)
#  assert(get hero stat(0, 0, maximum stat) == 50)
#  set hero level(0 ,12)
#  assert(get hero stat(0, 0, maximum stat) == 55)
#  set hero level(0 ,13)
#  assert(get hero stat(0, 0, maximum stat) == 60)
#  set hero level(0 ,14)
#  assert(get hero stat(0, 0, maximum stat) == 64)
#  set hero level(0 ,15)
#  assert(get hero stat(0, 0, maximum stat) == 69)
#  set hero level(0 ,16)
#  assert(get hero stat(0, 0, maximum stat) == 75)
#  set hero level(0 ,17)
#  assert(get hero stat(0, 0, maximum stat) == 80)
#  set hero level(0 ,18)
#  assert(get hero stat(0, 0, maximum stat) == 85)
#  set hero level(0 ,19)
#  assert(get hero stat(0, 0, maximum stat) == 91)
  set hero level(0 ,20)
  assert(get hero stat(0, 0, maximum stat) == 96)
#  set hero level(0 ,21)
#  assert(get hero stat(0, 0, maximum stat) == 102)
#  set hero level(0 ,22)
#  assert(get hero stat(0, 0, maximum stat) == 108)
#  set hero level(0 ,23)
#  assert(get hero stat(0, 0, maximum stat) == 114)
#  set hero level(0 ,24)
#  assert(get hero stat(0, 0, maximum stat) == 121)
#  set hero level(0 ,25)
#  assert(get hero stat(0, 0, maximum stat) == 127)
#  set hero level(0 ,26)
#  assert(get hero stat(0, 0, maximum stat) == 134)
#  set hero level(0 ,27)
#  assert(get hero stat(0, 0, maximum stat) == 140)
#  set hero level(0 ,28)
#  assert(get hero stat(0, 0, maximum stat) == 147)
#  set hero level(0 ,29)
#  assert(get hero stat(0, 0, maximum stat) == 154)
  set hero level(0 ,30)
  assert(get hero stat(0, 0, maximum stat) == 161)
#  set hero level(0 ,31)
#  assert(get hero stat(0, 0, maximum stat) == 168)
#  set hero level(0 ,32)
#  assert(get hero stat(0, 0, maximum stat) == 176)
#  set hero level(0 ,33)
#  assert(get hero stat(0, 0, maximum stat) == 183)
#  set hero level(0 ,34)
#  assert(get hero stat(0, 0, maximum stat) == 191)
#  set hero level(0 ,35)
#  assert(get hero stat(0, 0, maximum stat) == 199)
#  set hero level(0 ,36)
#  assert(get hero stat(0, 0, maximum stat) == 207)
#  set hero level(0 ,37)
#  assert(get hero stat(0, 0, maximum stat) == 215)
#  set hero level(0 ,38)
#  assert(get hero stat(0, 0, maximum stat) == 223)
#  set hero level(0 ,39)
#  assert(get hero stat(0, 0, maximum stat) == 232)
  set hero level(0 ,40)
  assert(get hero stat(0, 0, maximum stat) == 240)
#  set hero level(0 ,41)
#  assert(get hero stat(0, 0, maximum stat) == 249)
#  set hero level(0 ,42)
#  assert(get hero stat(0, 0, maximum stat) == 258)
#  set hero level(0 ,43)
#  assert(get hero stat(0, 0, maximum stat) == 267)
#  set hero level(0 ,44)
#  assert(get hero stat(0, 0, maximum stat) == 276)
#  set hero level(0 ,45)
#  assert(get hero stat(0, 0, maximum stat) == 285)
#  set hero level(0 ,46)
#  assert(get hero stat(0, 0, maximum stat) == 295)
#  set hero level(0 ,47)
#  assert(get hero stat(0, 0, maximum stat) == 304)
#  set hero level(0 ,48)
#  assert(get hero stat(0, 0, maximum stat) == 314)
#  set hero level(0 ,49)
#  assert(get hero stat(0, 0, maximum stat) == 324)
  set hero level(0 ,50)
  assert(get hero stat(0, 0, maximum stat) == 334)
#  set hero level(0 ,51)
#  assert(get hero stat(0, 0, maximum stat) == 344)
#  set hero level(0 ,52)
#  assert(get hero stat(0, 0, maximum stat) == 354)
#  set hero level(0 ,53)
#  assert(get hero stat(0, 0, maximum stat) == 365)
#  set hero level(0 ,54)
#  assert(get hero stat(0, 0, maximum stat) == 375)
#  set hero level(0 ,55)
#  assert(get hero stat(0, 0, maximum stat) == 386)
#  set hero level(0 ,56)
#  assert(get hero stat(0, 0, maximum stat) == 397)
#  set hero level(0 ,57)
#  assert(get hero stat(0, 0, maximum stat) == 408)
#  set hero level(0 ,58)
#  assert(get hero stat(0, 0, maximum stat) == 419)
#  set hero level(0 ,59)
#  assert(get hero stat(0, 0, maximum stat) == 430)
  set hero level(0 ,60)
  assert(get hero stat(0, 0, maximum stat) == 442)
#  set hero level(0 ,61)
#  assert(get hero stat(0, 0, maximum stat) == 453)
#  set hero level(0 ,62)
#  assert(get hero stat(0, 0, maximum stat) == 465)
#  set hero level(0 ,63)
#  assert(get hero stat(0, 0, maximum stat) == 477)
#  set hero level(0 ,64)
#  assert(get hero stat(0, 0, maximum stat) == 489)
#  set hero level(0 ,65)
#  assert(get hero stat(0, 0, maximum stat) == 501)
#  set hero level(0 ,66)
#  assert(get hero stat(0, 0, maximum stat) == 513)
#  set hero level(0 ,67)
#  assert(get hero stat(0, 0, maximum stat) == 526)
#  set hero level(0 ,68)
#  assert(get hero stat(0, 0, maximum stat) == 538)
#  set hero level(0 ,69)
#  assert(get hero stat(0, 0, maximum stat) == 551)
  set hero level(0 ,70)
  assert(get hero stat(0, 0, maximum stat) == 564)
#  set hero level(0 ,71)
#  assert(get hero stat(0, 0, maximum stat) == 577)
#  set hero level(0 ,72)
#  assert(get hero stat(0, 0, maximum stat) == 590)
#  set hero level(0 ,73)
#  assert(get hero stat(0, 0, maximum stat) == 604)
#  set hero level(0 ,74)
#  assert(get hero stat(0, 0, maximum stat) == 617)
#  set hero level(0 ,75)
#  assert(get hero stat(0, 0, maximum stat) == 631)
#  set hero level(0 ,76)
#  assert(get hero stat(0, 0, maximum stat) == 644)
#  set hero level(0 ,77)
#  assert(get hero stat(0, 0, maximum stat) == 658)
#  set hero level(0 ,78)
#  assert(get hero stat(0, 0, maximum stat) == 672)
#  set hero level(0 ,79)
#  assert(get hero stat(0, 0, maximum stat) == 686)
  set hero level(0 ,80)
  assert(get hero stat(0, 0, maximum stat) == 701)
#  set hero level(0 ,81)
#  assert(get hero stat(0, 0, maximum stat) == 715)
#  set hero level(0 ,82)
#  assert(get hero stat(0, 0, maximum stat) == 730)
#  set hero level(0 ,83)
#  assert(get hero stat(0, 0, maximum stat) == 745)
#  set hero level(0 ,84)
#  assert(get hero stat(0, 0, maximum stat) == 759)
#  set hero level(0 ,85)
#  assert(get hero stat(0, 0, maximum stat) == 774)
#  set hero level(0 ,86)
#  assert(get hero stat(0, 0, maximum stat) == 790)
#  set hero level(0 ,87)
#  assert(get hero stat(0, 0, maximum stat) == 805)
#  set hero level(0 ,88)
#  assert(get hero stat(0, 0, maximum stat) == 820)
#  set hero level(0 ,89)
#  assert(get hero stat(0, 0, maximum stat) == 836)
  set hero level(0 ,90)
  assert(get hero stat(0, 0, maximum stat) == 852)
#  set hero level(0 ,91)
#  assert(get hero stat(0, 0, maximum stat) == 868)
#  set hero level(0 ,92)
#  assert(get hero stat(0, 0, maximum stat) == 884)
#  set hero level(0 ,93)
#  assert(get hero stat(0, 0, maximum stat) == 900)
#  set hero level(0 ,94)
#  assert(get hero stat(0, 0, maximum stat) == 916)
#  set hero level(0 ,95)
#  assert(get hero stat(0, 0, maximum stat) == 933)
#  set hero level(0 ,96)
#  assert(get hero stat(0, 0, maximum stat) == 949)
#  set hero level(0 ,97)
#  assert(get hero stat(0, 0, maximum stat) == 966)
  set hero level(0 ,98)
  assert(get hero stat(0, 0, maximum stat) == 983)
  set hero level(0 ,99)
  assert(get hero stat(0, 0, maximum stat) == 1000)
  set hero level(0, 0, true)
  show no value, w
end

script, restore hp and mp, begin
  variable(i)
  for (i, 0, 40) do (
    if (hero by slot(i) <> -1) then (
      set hero stat(i, stat:hp, get hero stat(i, stat:hp, maximum stat), current stat)
      set hero stat(i, stat:mp, get hero stat(i, stat:mp, maximum stat), current stat)
    )
  )
end

script, hero stat tests, begin
  $0="hero stat tests"
  # test hero stat commands, stat capping, equip stat bonuses, current vs. max quirks, negative stats, level mp

  # check initial condition
  assert(hero by slot(1) == hero:Helga)
  assert(get hero level(1) == 0)
  assert(get hero stat(1, stat:hp, current stat) == 10)
  assert(get hero stat(1, stat:mp, current stat) == 20)
  assert(get hero stat(1, stat:atk, current stat) == 10)
  assert(get hero stat(1, stat:spd, current stat) == 30)
  assert(get hero stat(1, stat:hp, base stat) == 10)
  assert(get hero stat(1, stat:mp, base stat) == 20)
  assert(get hero stat(1, stat:atk, base stat) == 9)  # default weapon has +1 atk
  assert(get hero stat(1, stat:spd, base stat) == 30)

  # test stat caps
  assert(get hero stat cap(stat:hp) == 0)
  assert(get hero stat cap(stat:mp) == 0)
  assert(get hero stat cap(stat:atk) == 100)  #in-editor
  assert(get hero stat cap(stat:spd) == 0)
  # set capped hero stat
  set capped hero stat(1, stat:atk, 110, current stat)
  assert(get hero stat(1, stat:atk, current stat) == 100)
  assert(get hero stat(1, stat:atk, base stat) == 9)
  set capped hero stat(1, stat:atk, 120, maximum stat)
  assert(get hero stat(1, stat:atk, maximum stat) == 100)
  assert(get hero stat(1, stat:atk, base stat) == 99)
  set capped hero stat(1, stat:hp, 999999, current stat)
  assert(get hero stat(1, stat:hp, current stat) == 999999)
  set hero stat(1, stat:hp, 10, current stat)  #back to original

  # set hero stat should not be affected by caps
  set hero stat(1, stat:atk, 110, current stat)
  assert(get hero stat(1, stat:atk, current stat) == 110)

  # modify caps  
  set hero stat cap(stat:hp, 5)
  set hero stat cap(stat:mp, 12)
  assert(get hero stat cap(stat:hp) == 5)
  assert(get hero stat(1, stat:hp, current stat) == 5)
  assert(get hero stat(1, stat:hp, maximum stat) == 5)
  assert(get hero stat(1, stat:hp, base stat) == 10)
  assert(get hero stat(1, stat:mp, current stat) == 12)
  assert(get hero stat(1, stat:mp, maximum stat) == 12)
  assert(get hero stat(1, stat:mp, base stat) == 20)

  # set base stat
  set hero stat(1, stat:atk, 20, base stat)
  assert(get hero stat(1, stat:atk, maximum stat) == 21)  #+1 from weapon
  set hero stat(1, stat:atk, 200, base stat)
  assert(get hero stat(1, stat:atk, base stat) == 200)
  assert(get hero stat(1, stat:atk, maximum stat) == 100)  #capped
  set hero stat(1, stat:atk, 10, current stat)  #back to original
  set hero stat(1, stat:atk, 10, maximum stat)  #original max stat but not base stat!
  assert(get hero stat(1, stat:atk, base stat) == 110)
  set hero stat(1, stat:atk, 9, base stat)  #back to original max and base
  assert(get hero stat(1, stat:atk, maximum stat) == 10)

  set hero stat cap(stat:atk, 0)
  set hero stat cap(stat:spd, 10)
  assert(get hero stat cap(stat:atk) == 0)
  assert(get hero stat cap(stat:spd) == 10)
  assert(get hero stat(1, stat:atk, current stat) == 10)
  assert(get hero stat(1, stat:atk, maximum stat) == 10)
  assert(get hero stat(1, stat:spd, current stat) == 10)
  assert(get hero stat(1, stat:spd, maximum stat) == 10)
  set hero stat cap(stat:hp, 0)
  set hero stat cap(stat:mp, 0)
  set hero stat cap(stat:spd, 0)

  # test original stats restored
  assert(get hero stat(1, stat:hp, maximum stat) == 10)
  assert(get hero stat(1, stat:hp, base stat) == 10)
  assert(get hero stat(1, stat:mp, maximum stat) == 20)
  assert(get hero stat(1, stat:mp, base stat) == 20)
  assert(get hero stat(1, stat:atk, current stat) == 10)
  assert(get hero stat(1, stat:atk, maximum stat) == 10)
  assert(get hero stat(1, stat:atk, base stat) == 9)
  assert(get hero stat(1, stat:spd, current stat) == 30)
  assert(get hero stat(1, stat:spd, maximum stat) == 30)
  assert(get hero stat(1, stat:spd, base stat) == 30)
  # but not current values for hp, mp
  assert(get hero stat(1, stat:hp, current stat) == 5)
  assert(get hero stat(1, stat:mp, current stat) == 12)
  # ...fix that up
  restore hp and mp

  # Simple test of hero level mp
  # The hero is level 0. Other levels are tested in "hero stat growth test"
  assert(get level mp(1, 0, current stat) == 1)
  assert(get level mp(1, 0, maximum stat) == 1)
  variable(level)
  for(level, 1, 7) do (
    assert(get level mp(1, level, current stat) == 0)
    assert(get level mp(1, level, maximum stat) == 0)
  )
  set level mp(1, 7, 99)
  assert(get level mp(1, 7) == 99)
  set level mp(1, 7, 0)

end

script, equipment tests, begin
  $0="equipment tests"
  # This is a continuation of 'hero stat tests', see that for preconditions
  # additional precondition: empty inventory, as well as:
  assert(find hero(hero:Freki) == 0)
  assert(find hero(hero:Helga) == 1)

  # test equipment commands
  get item(item:Boots)
  assert(inventory(item:Boots) == 1)
  assert(equip where(1, item:Boots) == slot:legs)  # equippable by all heroes
  assert(check equipment(1, slot:legs) == -1)
  assert(check equipment(1, slot:weapon) == item:DefltWep)  # default weapon  (not properly tested)
  assert(get default weapon(1) == item:DefltWep)

  force equip(1, slot:legs, item:Boots)
  # stat bonuses: spd+2 hp+3
  assert(check equipment(1, slot:legs) == item:Boots)
  assert(inventory(item:Boots) == 0)
  # test stat bonuses
  assert(get hero stat(1, stat:hp, current stat) == 10)
  assert(get hero stat(1, stat:hp, maximum stat) == 13)  # only affects max values of hp, mp
  assert(get hero stat(1, stat:hp, base stat) == 10)
  assert(get hero stat(1, stat:spd, current stat) == 32)
  assert(get hero stat(1, stat:spd, maximum stat) == 32) # resets current value to max for other stats
  assert(get hero stat(1, stat:spd, base stat) == 30)

  assert(inventory(item:SteelSho) == 0)
  assert(equip where(0, item:SteelSho) == slot:legs)  #equippable only by Freki
  assert(equip where(1, item:SteelSho) == false)

  # Note that force equip can be used to equip an item not in the inventory,
  # in that case you get a free copy.
  # Force equip also allows equipping in wrong slot
  force equip(1, slot:weapon, item:SteelSho)
  # stat bonuses: atk+4  (default weapon has atk+1)
  assert(check equipment(1, slot:weapon) == item:SteelSho)
  assert(check equipment(1, slot:legs) == item:Boots)
  assert(inventory(item:DefltWep) == 0)  # shouldn't gain a default weapon
  assert(get hero stat(1, stat:atk, current stat) == 13)
  assert(get hero stat(1, stat:atk, maximum stat) == 13)

  # test unequipping
  unequip(1, slot:legs)
  assert(inventory(item:Boots) == 1)
  assert(check equipment(1, slot:legs) == -1)

  set hero stat(1, stat:hp, 29, current stat)  # check effects of unequipping... even on stats
  set hero stat(1, stat:spd, 29, current stat) # which the equipment doesn't have bonuses on

  unequip(1, slot:weapon)
  assert(inventory(item:SteelSho) == 1)
  assert(check equipment(1, slot:weapon) == item:DefltWep)

  assert(get hero stat(1, stat:hp, current stat) == 10)  # current values of hp,mp should be capped to max
  assert(get hero stat(1, stat:hp, maximum stat) == 10)
  assert(get hero stat(1, stat:spd, current stat) == 30)
  assert(get hero stat(1, stat:spd, maximum stat) == 30) # resets current value to max for other stats

  # TODO: test equip elemental resists

  # test for Bug 743 - Unequipping items with stat bonuses that exceed stat limits is broken
  assert(get hero stat(1, stat:hp, current stat) == 10)  # preconditions (max same)
  assert(get hero stat(1, stat:spd, current stat) == 30)
  set hero stat cap(stat:hp, 5)
  set hero stat cap(stat:spd, 5)
  force equip(1, slot:legs, item:Boots)
  assert(get hero stat(1, stat:hp, current stat) == 5)  # cap hp, mp to the cap
  assert(get hero stat(1, stat:hp, maximum stat) == 5)
  assert(get hero stat(1, stat:spd, current stat) == 5) # resets current value to max for other stats
  assert(get hero stat(1, stat:spd, maximum stat) == 5)
  unequip(1, slot:legs)
  assert(get hero stat(1, stat:hp, current stat) == 5)
  assert(get hero stat(1, stat:hp, maximum stat) == 5)
  assert(get hero stat(1, stat:spd, current stat) == 5)
  assert(get hero stat(1, stat:spd, maximum stat) == 5)
  set hero stat cap(stat:hp, 0)
  set hero stat cap(stat:spd, 0)
  assert(get hero stat(1, stat:hp, current stat) == 5)  # only affects max values of hp, mp
  assert(get hero stat(1, stat:hp, maximum stat) == 10)
  assert(get hero stat(1, stat:spd, current stat) == 30)  # resets current value to max for other stats
  assert(get hero stat(1, stat:spd, maximum stat) == 30)
  # ...fix that up
  restore hp and mp
  assert(get hero stat(1, stat:hp, current stat) == 10)

  # cleanup
  delete item(item:SteelSho)
  delete item(item:Boots)
end

script, hero misc tests, begin
  $0="hero misc tests"
  show string(0), w
  variable(n)
  assert(hero by slot(0) == hero:Freki)
  assert(get hero hand x(0, hand:attack A) == 22)
  assert(get hero hand y(0, hand:attack A) == 11)
  assert(get hero hand x(0, hand:attack B) == 2)
  assert(get hero hand y(0, hand:attack B) == 17)
  set hero hand x(0, hand:attack A, 50)
  set hero hand y(0, hand:attack A, -25)
  set hero hand x(0, hand:attack B, -10)
  set hero hand y(0, hand:attack B, 1)
  assert(get hero hand x(0, hand:attack A) == 50)
  assert(get hero hand y(0, hand:attack A) == -25)
  assert(get hero hand x(0, hand:attack B) == -10)
  assert(get hero hand y(0, hand:attack B) == 1)
  set hero hand x(0, hand:attack A, get default hero hand x(0, hand:attack A))
  set hero hand y(0, hand:attack A, get default hero hand y(0, hand:attack A))
  set hero hand x(0, hand:attack B, get default hero hand x(0, hand:attack B))
  set hero hand y(0, hand:attack B, get default hero hand y(0, hand:attack B))
  assert(get hero hand x(0, hand:attack A) == 22)
  assert(get hero hand y(0, hand:attack A) == 11)
  assert(get hero hand x(0, hand:attack B) == 2)
  assert(get hero hand y(0, hand:attack B) == 17)
  
end

########################################################################

script, npc tests, begin
  npc movement tests(1)
  npc movement tests(NPC reference(1))
  npc id tests(NPC reference(1))
  npc tag and onetime tests
  use npc script tests
end

script, npc movement tests, n, begin
  $0="npc movement tests"
  $err arg string="n=", append number(err arg string, n)
  show string(0)
  
  # assert starting pos
  assert(npc X(n) == 3)
  assert(npc Y(n) == 5)
  # walk in a circle, testing speed
  set npc speed(n, 4)
  walk npc(n, south, 1), w
  wait for npc(n)
  assert(npc Y(n) == 6)
  set NPC speed(n, 5)
  walk npc(n, east, 1), w
  wait for npc(n)
  assert(npc X(n) == 4)
  set NPC speed(n, 10)
  walk npc(n, north, 1), w
  wait for npc(n)
  assert(npc Y(n) == 5)
  set NPC speed(n, 2)
  walk npc(n, west, 1), w
  wait for npc(n)
  assert(npc X(n) == 3)
  set npc speed(n, 4)
  
  set npc position(n, 3, 5)
  
  show no value
  $err arg string=""
end

script, npc id tests, n, begin
  set npc direction(n, south), w
  assert(get npc id(n) == 1)
  change npc id(n, 2), w(4)
  assert(get npc id(n) == 2)
  change npc id(n, 1), w
end

script, npc tag and onetime tests, begin
  $0="npc onetime tests"
  variable(hx, hy, n)
  # Moving the hero in this script is a little silly,
  # but ensures that the onetime NPCs are on-screen
  # ...which they probably would have been anyway
  hx := hero x(me)
  hy := hero y(me)
  walk hero to y(me, 12)
  wait for hero(me), w
  walk hero to x(me, 4)
  wait for hero(me), w
  
  assert(check onetime(3) == false)
  assert(check onetime(4) == false)
  use npc(3), w
  assert(check onetime(3) == true)
  assert(check onetime(4) == false)
  use npc(4), w
  assert(check onetime(3) == true)
  assert(check onetime(4) == true)
  w
  n := npc reference(4)
  assert(n == false)
  set onetime(4, false), w
  n := npc reference(4)
  assert(n <> false)
  
  assert(check tag(10000) == false)
  w
  n := npc reference(5)
  assert(n <> false)
  set tag(10000, true)
  w
  n := npc reference(5)
  assert(n == false)
  assert(check tag(10000) == true)
  
  walk hero to x(me, hx)
  wait for hero(me), w
  walk hero to y(me, hy)
  wait for hero(me), w
end

plotscript, another npc script, begin
  assert(ticknumber == 2)
end

script, use npc script tests, begin
  $0="use npc script tests"
  # Tests the implicit wait behaviour of usenpc

  tick counter slice := create container
  set slice velocity x(tick counter slice, 1, 1000)
  assert(ticknumber == 0)

  create npc(8)  # Does nothing
  use npc(8)
  assert(ticknumber == 1)
  delete npc(8)

  # Both this script and anothernpcscript should wait
  create npc(7)  # Calls anothernpcscript
  use npc(7)
  assert(ticknumber == 2)
  delete npc(7)

  free slice(tick counter slice)
end

########################################################################

script, death tests, begin
  $0="death tests"
  # See also "test harm tile death" below

  assert (get death script == 0)
  set death script(@on death script)
  assert (get death script == @on death script)

  assert(died == 0)
  expecting death := false

  # Setting hero stats to 0 should NOT trigger death
  variable (i)
  for (i, 0, 3) do (
    set hero stat(i, stat:hp, 0)
  )
  wait
  assert(died == 0)

  # Death by oob attack
  set hero stat(0, stat:hp, 10)
  # Only hero 0 alive
  expecting death := true
  map cure(atk:suicide, 0)
  assert(get hero stat(0, stat:hp) == 0)
  # Death script should not be triggered until next tick
  assert(died == 0)
  wait(1)
  assert(died == 1)

  # Re-trigger even if already dead
  map cure(atk:suicide, 0)
  assert(died == 1)
  wait(1)
  assert(died == 2)

  # Death shouldn't be triggered if one hero has 0 max hp
  variable(old maxhp)
  oldmaxhp := get hero stat(1, stat:hp, maximum stat)
  set hero stat(1, stat:hp, 0, maximum stat)
  map cure(atk:suicide, 0)
  wait(1)
  assert(died == 2)
  set hero stat(1, stat:hp, old maxhp, maximum stat)

  # Death shouldn't be triggered if heroes are healed before the next tick
  map cure(atk:suicide, 0)
  set hero stat(0, stat:hp, 1)
  wait(1)
  assert(died == 2)

  #FIXME: test death by battle

  expecting death := false
  died := 0
  restore hp and mp
end

script, on death script, begin
  $9="on death script"
  trace(9)
  if (expecting death) then (
    died += 1
    exit script
  )
  crash
end

script, test harm tile death, start hp, begin
  # Called from "harm tile tests"

  expecting death := true
  set harm tile damage(100)
  walk hero(me, right, 1)
  wait for hero(me)
  # The step finishes, causing death, then it will be processed the same tick, spawning the script
  assert(died == 1)
  assert hurt pattern(7, start hp, start hp, start hp, start hp, start hp)
  expecting death := false
  died := 0
end

########################################################################

script, trigger tests, begin
  harm tile tests
end

script, assert hurt pattern, test no, start hp, h0, h1, h2, h3, begin
  # h# is true if hero # should be hurt
  $0="assert hurt pattern, test no. "
  append number(0, test no)
  assert(get hero stat(0, stat:hp) == start hp -- h0)
  assert(get hero stat(1, stat:hp) == start hp -- h1)
  assert(get hero stat(2, stat:hp) == start hp -- h2)
  assert(get hero stat(3, stat:hp) == start hp -- h3)
  set hero stat(0, stat:hp, start hp)
  set hero stat(1, stat:hp, start hp)
  set hero stat(2, stat:hp, start hp)
  set hero stat(3, stat:hp, start hp)
end

script, harm tile tests, begin
  $0="harm tile tests"

  # Test harmtile damage
  walk hero to x(me, 7)
  walk hero to y(me, 6)
  wait for hero(me)

  variable(start hp)
  start hp := get hero stat(0, stat:hp, current stat)
  # hero is now at tile 7,6, caterpillar is enabled and not suspended, and all heroes have 'start hp' hp

  write map block(6, 6, 140, 1)
  write pass block(6, 6, harmtile)

  # Test damage to individual heroes, caterpillar enabled
  # also testing crossing over harmtile without stopping
  walk hero(me, left, 1)
  wait for hero(me), _checkpoint   # check for flash
  assert hurt pattern(0, start hp, true, false, false, false)
  walk hero(me, left, 2)
  wait for hero(me), _checkpoint
  assert hurt pattern(1, start hp, false, true, true, false)

  # Test damage to individual heroes, caterpillar suspended
  suspend caterpillar
  walk hero(1, right, 1)
  walk hero(2, down, 1)
  walk hero(3, left, 1)
  wait for hero(3)
  assert hurt pattern(2, start hp, false, true, false, true)

  # Test damage to whole party, caterpillar disabled
  set caterpillar mode(off)
  walk hero(me, right, 3)
  wait for hero(me)
  # "Harm tiles harm non-caterpillar party" is off
  assert hurt pattern(3, start hp, true, false, false, false)
  # Turn on "Harm tiles harm non-caterpillar party"
  write general(177, read general(177), or, 2^12)
  walk hero(me, left, 2)
  wait for hero(me)
  assert hurt pattern(4, start hp, true, true, true, true)
  # Check this makes no difference
  resume caterpillar
  walk hero(me, right, 2)
  wait for hero(me)
  assert hurt pattern(5, start hp, true, true, true, true)

  set harm tile damage(2)
  walk hero(me, left, 2)
  wait for hero(me)
  assert hurt pattern(6, start hp, 2, 2, 2, 2)

  test harm tile death(start hp)
  
  write map block(6, 6, 0, 1)
  write pass block(6, 6, 0)

  set caterpillar mode(on)
  set harm tile damage(1)  # restore
  # Caterpiller on and resumed, all heroes have 'start hp' hp
end

########################################################################

script, menu tests, begin
  $0="menu tests"
  variable(i, m, mi, main, m1)
  m := create menu, w
  if(get menu id(m) <> -1) then($0="get menu id should have reported -1 for script-generated menu", crash)
  
  mi := add menu item(m), w
  $1="Puppies"
  set menu item caption(mi, 1), w
  mi := add menu item(m), w
  $1="Kittens"
  set menu item caption(mi, 1), w
  mi := add menu item(m), w
  $1="Walruses"
  set menu item caption(mi, 1), w
  mi := add menu item(m), w
  $1="Octopus"
  set menu item caption(mi, 1), w
  mi := add menu item(m), w
  $1="Plip"
  set menu item caption(mi, 1), w

  main := open menu, w
  bring menu forward(bottom menu), w

  for(i, align:left, align:right) do(
    set menu anchor x(m, i), w
    if(get menu anchor x(m) <> i) then($0="menu anchor x", crash)
    set menu anchor y(m, i), w
    if(get menu anchor y(m) <> i) then($0="menu anchor y", crash)
  )

  $1="Puppies"
  mi := find menu item caption(m, 1)
  if(mi == 0) then($0="failed to find first menu item by caption", crash)
  get menu item caption(mi, 2)
  if(not(string compare(1, 2))) then($0="found menu item that was not puppies", crash)

  $1="Walruses"
  mi := find menu item caption(m, 1)
  get menu item caption(mi, 2)
  if(not(string compare(1, 2))) then($0="found menu item that was not walruses", crash)
  
  mi := find menu item caption(m, 1, mi) #search for Walruses starting after Walruses
  if(mi <> 0) then($0="find menu item walrus should have failed", crash)

  mi := add menu item(m)
  $1="Kittens"
  set menu item caption(mi, 1), w

  variable(kitten1, kitten2, walruses)
  #search for Kitten from top
  $1="Kittens"
  kitten1 := find menu item caption(m, 1)
  #search for Kitten starting after Walruses
  $2="Walruses"
  walruses := find menu item caption(m, 2) 
  kitten2 := find menu item caption(m, 1, walruses) 
  if(kitten1 == 0) then($0="failed to find first kitten", crash)
  if(kitten2 == 0) then($0="failed to find second kitten", crash)
  get menu item caption(kitten1, 2)
  if(not(string compare(1, 2))) then($0="found menu item that was not kitten(1)", crash)
  get menu item caption(kitten2, 2)
  if(not(string compare(1, 2))) then($0="found menu item that was not kitten(2)", crash)
  if(kitten1 == kitten2) then($0="found the same kitten twice", crash)

  mi := add menu item(m), w
  delete menu item(mi), w
  $1="Kittens"
  mi := find menu item caption(m, 1)
  delete menu item(mi), w
  mi := first menu item(m)
  delete menu item(mi), w

  if(find menu ID(1) <> 0) then($0="found unopened menu", crash)
  open menu(1),w
  m1 := find menu ID(1)
  if(get menu ID(m1) <> 1) then($0="get menu ID mismatch vs find menu", crash)
  if(m1 == 0) then($0="failed to find opened menu", crash)  
  close menu(m1),w

  for(i, -3, 12) do(
    set menu border(main, i), w
  )
  if(get menu border(main) <> 12) then($0="readback of menu border thickness failed", crash)
  for(i, 0, 10) do(
    set menu boxstyle(main, i)
  )
  if(get menu boxstyle(main) <> 10) then($0="readback of menu boxstyle failed", crash)

  for(i, 0, 15) do(
    set menu max chars(main, i), w
    if(get menu max chars(main) <> i) then($0="readback of menu max chars failed", crash)
  )
  set menu max chars(main, 0), w
  for(i, 10, 30) do(
    set menu min chars(main, i), w
    if(get menu min chars(main) <> i) then($0="readback of menu min chars failed", crash)
  )
  set menu min chars(main, 0), w

  for(i, -50, 50, 10) do(
    set menu offset x(main, i), w
    if(get menu offset x(main) <> i) then($0="readback of menu offset x failed", crash)
    set menu offset y(main, i), w
    if(get menu offset y(main) <> i) then($0="readback of menu offset y failed", crash)
  )
  set menu offset x(main, 0)
  set menu offset y(main, 0)
  for(i, align:left, align:right) do(
    set menu anchor x(main, i), w
    if(get menu anchor x(main) <> i) then($0="readback of menu anchor x failed", crash)
    set menu anchor y(main, i), w
    if(get menu anchor y(main) <> i) then($0="readback of menu anchor y failed", crash)
  )
  set menu anchor x(main, 0), w
  set menu anchor y(main, 0), w

  for(i, align:left, align:right) do(
    set menu text align(main, i), w
    if(get menu text align(main) <> i) then($0="readback of menu text align failed", crash)
  )
  set menu text align(main, 0), w

  set menu bit(main, 0, true), w  #Transparent box
  if(get menu bit(main, 0) <> true) then($0="failed to set menu bit", crash)
  set menu bit(main, 0, false), w #Opaque box
  if(get menu bit(main, 0) <> false) then($0="failed to unset menu bit", crash)
  variable(save rows)
  save rows := get menu max rows(main)
  set menu max rows(main, 3), w
  set menu bit(main, 1, true), w  #never show scrollbar
  set menu bit(main, 1, false), w #show scrollbar
  set menu max rows(main, save rows)
  set menu bit(main, 4, true), w  #no box
  set menu bit(main, 4, false), w #show box
  set menu bit(main, 8, true), w  #Advance text box when menu closes
  show text box(6), w
  if(not(menu is open(main))) then($0="why isn't the main menu open?", crash)
  close menu(top menu), w
  close menu(main), w
  if(current text box <> -1) then($0="menu bit failed to close textbox", crash)

  if(menu is open(main)) then($0="why is the main menu still open?", crash)

  variable(antelope, buffalo, catbus, duiker)
  m := create menu, w
  antelope := add menu item(m), $0="Antelope", set menu item caption(antelope, 0), w
  buffalo  := add menu item(m), $0="Buffalo",  set menu item caption(buffalo, 0), w
  catbus   := add menu item(m), $0="Catbus",   set menu item caption(catbus, 0), w
  duiker   := add menu item(m), $0="Duiker",   set menu item caption(duiker, 0), w

  # Hide when disabled 
  set menu item bit(catbus, 0, true), w
  if(get menu item bit(catbus, 0) <> true) then($0="menu item bit readback failed", crash)
  # Disable now
  set menu item type(catbus, menutype:label)
  set menu item subtype(catbus, 1), w
  # Don't hide
  set menu item bit(catbus, 0, false), w
  # Re-enable
  set menu item subtype(catbus, 0), w

  set menu item settag(catbus, 3)
  if(get menu item settag(catbus) <> 3) then($0="menu item settag readback failed", crash)
  if(check tag(3)) then($0="tag 3 shouldn't be on yet (menu)", crash)
  use menu item(catbus), w
  if(not(check tag(3))) then($0="tag 3 shouldn't be on now (menu)", crash)
  set menu item settag(catbus, 0)
  set menu item togtag(catbus, 3)
  use menu item(catbus), w
  if(check tag(3)) then($0="tag 3 should be toggled off (menu)", crash)
  use menu item(catbus), w
  if(check tag(3)==OFF) then($0="tag 3 should be toggled on (menu)", crash)
  use menu item(catbus), w
  if(check tag(3)) then($0="tag 3 should be toggled off again (menu)", crash)
  set menu item togtag(catbus, 0)

  for(i, 1, 2) do(
    set menu item tag(catbus, -3, i), w
    if(get menu item tag(catbus, i) <> -3) then($0="menu item tag readback failed", crash)
    set tag(3, ON), w
    set menu item tag(catbus, 3, i), w
    set tag(3, OFF), w
    set menu item tag(catbus, 0, i), w
  )

  $0="menu item tag req", show string(0)
  for(i, 1, 2) do(
    set tag(3, OFF)
    if(get menu item tag(catbus, i) <> 0) then($0="menu item tag req should be zero:", append number(0, i), crash)
    set menu item tag(catbus, 3, i), w
    if(get menu item tag(catbus, i) <> 3) then($0="menu item tag req readback fail:", append number(0, i), crash)
    set tag(3, ON), w
    set menu item tag(catbus, -3, i), w
    set tag(3, OFF), w
    set menu item tag(catbus, 0, i), w
  )
  show no value
  
  for(i, 0, 2) do(
    set menu item extra(buffalo, i, (i + 1) * 10)
    if(get menu item extra(buffalo, i) <> (i + 1) * 10) then($0="menu item extra readback failed", crash)
  )
  
  set menu item type(catbus, menutype: special)
  $0="" # use default caption
  set menu item caption(catbus, 0), w
  for (i, 0, 13) do(
    set menu item subtype(catbus, i), w
  )
  $0="Catbus, not Save"
  set menu item caption(catbus, 0), w
  $0=""
  set menu item caption(catbus, 0), w
  set menu item type(catbus, menutype:menu), w
  for(i, 0, 2) do(
    set menu item subtype(catbus, i), w
    if(get menu item subtype(catbus) <> i) then($0="menu item subtype readback failed", crash)
  )
  $0="Catbus"
  set menu item caption(catbus, 0), w
  
  # open another menu
  set menu item type(catbus, menutype:menu)
  set menu item subtype(catbus, 2)
  use menu item(catbus), w
  if(get menu id(top menu) <> 2) then($0="Failed to open a menu from a menu", crash)
  close menu(top menu), w

  # open a text box
  set menu item type(catbus, menutype:textbox)
  set menu item subtype(catbus, 7)
  use menu item(catbus), w
  if(current textbox <> 7) then($0="Failed to open a text box from a menu", crash)
  advance text box, w

  if(get menu item type(catbus) <> menutype:textbox) then($0="menu item type readback failed", crash)
  set menu item type(catbus, menutype:label)

  # run a script from a menu
  set menu item type(catbus, menutype:script)
  set menu item subtype(catbus, @on menu item use)
  use menu item(catbus), w
  if(menu item script global <> 99) then($0="Failed to run a script from a menu", crash)

  variable(oldcol)
  oldcol := get menu textcolor(m)
  for(i, 0, 10) do(
    set menu textcolor(m, i * 13), w
    if(get menu textcolor(m) <> i * 13) then($0="menu textcolor readback failed", crash)
  )
  set menu textcolor(m, oldcol)

  # iterate all menu items
  variable(count)
  count := 0
  mi := first menu item(m)
  while(mi) do(
    count += 1
    mi := next menu item(mi)
  )
  if(count <> 4) then($0="iterated wrong number of menu items (all)")
  # now hide catbus
  set menu item bit(catbus, menu item bit:hide when disabled, true)
  set menu item tag(catbus, 3)
  set tag(3, OFF)
  # iterate all except hidden
  count := 0
  mi := first menu item(m)
  while(mi) do(
    count += 1
    mi := next menu item(mi, true)
  )
  if(count <> 3) then($0="iterated wrong number of menu items (vis only)")
  # iterate all including hidden
  count := 0
  mi := first menu item(m)
  while(mi) do(
    count += 1
    mi := next menu item(mi, false)
  )
  if(count <> 4) then($0="iterated wrong number of menu items (vis only)")


  # close when selected
  set menu item bit(antelope, 1, true)
  use menu item(antelope), w
  if(top menu == m) then($0="Even Toed Ungulate menu failed to close", crash)

  menu close global := 0
  m := create menu, w
  mi := add menu item(m)
  $1="close me"
  set menu item caption(mi, 1), w
  set menu item bit(mi, menu item bit:close menu when selected, true)
  set menu on close script(m, @on close menu test)
  if(get menu on close script(m) <> @on close menu test) then($0="menu close script readback failed", crash)
  use menu item(mi), w
  if(menu close global <> 99) then($0="menu close script didn't run", crash)

  menu scroll tests
end

script, on menu item use, begin
  menu item script global := 99
end

script, on close menu test, begin
  menu close global := 99
end


script, scroll through menu, menu, numitems, begin
  variable (i, j, mi)

  # test scrolling
  for (i, 1, 3, 2) do (
    set menu max rows(menu, i)
    # forwards
    mi := first menu item(menu)
    while (mi) do (
      select menu item(mi), w
      mi := next menu item(mi)
    )
    # backwards
    mi := menu item by slot(menu, numitems -- 1)
    while (mi) do (
      select menu item(mi), w
      mi := previous menu item(mi)
    )
    # randomly
    for (j, 0, 4) do (
      select menu item(menu item by slot(menu, random(0, numitems -- 1))), w
    )
  )
  set menu max rows(menu, 0)
end

script, menu scroll tests, begin
  $0="menu scroll tests"

  variable (main, mi, next, i, numitems)
  main := open menu, w
  set menu bit(main, 1, false), w #show scrollbar
  allow minimap(true), w #make sure there are no hidden items
  allow save anywhere(true), w

  # test menu item iteration
  mi := first menu item(main)
  while (mi) do (
    get menuitemcaption(mi, err arg string)
    #trace(err arg string)
    appendnumber(err arg string, numitems)
    assert (menu item slot(mi) == numitems)
    assert (menu item by slot(main, numitems, true) == mi)
    assert (menu item by slot(main, numitems, false) == mi)
    assert (menu item true slot(mi) == numitems)
    assert (menu item by true slot(main, numitems) == mi)
    next := next menu item(mi, false)
    if (next) then (
      assert (mi == previous menu item(next))
    )
    set menu item extra(mi, 0, numitems)
    mi := next
    numitems += 1
  )
  $err arg string=""

  # menu item iteration edge cases
  assert (previous menu item(first menu item(main)) == 0)
  assert (next menu item(menu item by slot(main, numitems -- 1)) == 0)
  assert (menu item by slot(main, numitems, true) == 0)
  assert (menu item by slot(main, numitems, false) == 0)
  assert (menu item by slot(main, -1) == 0)
  assert (menu item by true slot(main, -1) == 0)
  assert (menu item by true slot(main, numitems) == 0)

  # test "select menu item"
  mi := first menu item(main)
  while (mi) do (
    select menu item(mi), w
    assert (selected menu item(main) == mi)
    mi := next menu item(mi)
  )

  # test scrolling
  scroll through menu(main, numitems)

  # check hidden items sorted to end:
  # 1. hide three items
  set tag(3, OFF)
  variable(hidden1, hidden2, hidden3)
  hidden3 := menu item by slot(main, 4)
  set menu item bit(hidden3, menu item bit: hide when disabled, true)
  set menu item tag(hidden3, 3, 1)
  hidden1 := menu item by slot(main, 0)
  set menu item bit(hidden1, menu item bit: hide when disabled, true)
  set menu item tag(hidden1, 3, 1)
  hidden2 := menu item by slot(main, 3)
  set menu item bit(hidden2, menu item bit: hide when disabled, true)
  set menu item tag(hidden2, 3, 1)
  w

  # 2. add new item
  mi := add menu item(main)
  set menu item extra(mi, 0, numitems)
  numitems += 1
  $1="a new item!"
  set menu item caption(mi, 1), w

  # 3. check visible items in correct order
  variable (temp, extra, mi2)
  temp := -1
  for (i, 0, num items -- 3 -- 1) do (
    mi := menu item by slot(main, i, true)
    mi2 := menu item by slot(main, i, false)
    assert (mi == mi2)
    extra := get menu item extra(mi2, 0)
    if (extra <= temp) then ($0="menu items not in increasing order", crash)
    temp := extra
  )

  # 4. check hidden items at end in correct order
  if (menu item by slot(main, numitems -- 3, true) <> 0) then ($0="menuitembyslot returned hidden item", crash)
  if (menu item by slot(main, numitems -- 3, false) <> hidden1) then ($0="hidden items out of order", crash)
  if (menu item by slot(main, numitems -- 2, false) <> hidden2) then ($0="hidden items out of order", crash)
  if (menu item by slot(main, numitems -- 1, false) <> hidden3) then ($0="hidden items out of order", crash)
  assert (menu item by slot(main, numitems, false) == 0)

  # 5. check "true order" hasn't changed
  for (i, 0, num items -- 1) do (
    mi := menu item by true slot(main, i)
    assert (menu item true slot(mi) == i)
    assert (get menu item extra(mi, 0) == i)
  )

  # test scrolling again
  scroll through menu(main, numitems -- 3)

  # check unhidden menu items sorted back correctly
  set tag(3, ON), w
  for (i, 0, numitems -- 1) do (
    mi := menu item by slot(main, i, false)
    assert (mi == menu item by slot(main, i, true))
    assert (get menu item extra(mi, 0) == i)
  )

  close menu(main)
end

#next menu(menu handle)
#open menu (ID, allow duplicate)
#parent menu(menu item handle)
#previous menu(menu handle)
#swap menu items(handle1, handle2)
#wait for menu (menu handle)

########################################################################

script, textbox tests, begin
  $0="textbox tests"
  show text box(2), w
  advance text box, w
  show text box(3), w
  advance text box, w
  if(current textbox <> 4) then(crash)
  advance text box, w
  show text box(3), w
  advance text box, w
  if(current textbox <> 5) then(crash)
  advance text box, w
  if(current textbox <> -1) then(crash)
  w
  show text box(12), w, advance text box, w
  show text box(13), w, advance text box, w

  variable (oldval)
  oldval := read general(178)  # genBits2
  assert((oldval, and, 2^2) == 2^2)  # "showtextbox happens immediately" is on

  # check showtextbox isn't delayed
  show text box(14)
  assert(checktag(tag:textbox 14 happened) == true)
  # no wait required before advance
  advance textbox
  wait
  assert(current textbox == -1)
  settag(tag:textbox 14 happened, false)

  # check both showtextboxes work
  show text box(14)
  show text box(15)
  assert(checktag(tag:textbox 14 happened) == true)
  assert(checktag(tag:textbox 15 happened) == true)
  advance textbox
  settag(tag:textbox 14 happened, false)
  settag(tag:textbox 15 happened, false)

  write general(178, oldval -- 2^2) # "showtextbox happens immediately" OFF

  # Check currenttextbox still updates immediately
  show text box(2)
  assert(current textbox == 2)
  # wait required before advance
  wait
  advance text box
  assert(current textbox == -1)

  # check showtextbox is delayed
  show text box(14)
  assert(checktag(tag:textbox 14 happened) == false)
  wait
  assert(checktag(tag:textbox 14 happened) == true)
  advance textbox
  settag(tag:textbox 14 happened, false)

  # check only last showtextbox works
  show text box(14)
  show text box(15)
  wait
  assert(checktag(tag:textbox 14 happened) == false)
  assert(checktag(tag:textbox 15 happened) == true)
  advance textbox

  write general(178, oldval)
end

########################################################################

script, battle tests, begin
  # there are some other battle tests in with the timer tests
  $0="battle tests"
  show value(random(1, 999999999)), w
  force random battle(1)
  show value(random(1, 999999999)), w # this should remain deterministic after battle
  show no value, w
  # attack forces run
  set tag(tag:Victory, true)
  assert(fight formation(1) == false), w  # sets victory tag
  assert(check tag(tag:Victory) == false)
  # attack forces victory
  assert(fight formation(2) == true), w
  assert(check tag(tag:Victory) == true)
  # attack forces exit
  assert(fight formation(3) == false), w
  # test tag check: battle should not trigger
  force random battle(2)
  assert(last formation <> 4)
  w(5)
  fight formation(5)
  w(5)
  # Test attacks setting tags.
  set tag(4, false)
  set tag(5, false)
  fight formation(6)
  w(5)
  # The first time should NOT set tag 5 because tag 4 was not set
  assert(not(check tag(5))), w
  set tag(4, true)
  fight formation(6)
  w(5)
  # The second time SHOULD set tag 5 because tag 4 WAS set
  assert(check tag(5)), w
  set tag(6, false)
  fight formation(7)
  w(5)
  # The third one should always turn the tag on because the tag check is 0.
  assert(check tag(6)), w
end

# Formation set should be 1-3
script, force random battle, formation set = 1, begin
  variable(s, x, y, d)
  if (formation set == 0) then (formation set := 1)  # If run with runscriptbyid
  # save hero state
  s := get hero speed(me)
  x := hero x(me)
  y := hero y(me)
  d := hero direction(me)
  # get the hero ready for forcing the battle
  set hero speed(0, 20)
  # Formations 1-3 are at x = 14-16
  set hero position(0, 13 + formation set, 9), w
  # force the battle
  walk hero(0, north, 1), w
  # restore hero state
  set hero speed(me, s)
  set hero position(me, x, y)
  set hero direction(me, d)
  w
end

script, force non random battle, begin
  w
  fight formation(0)
  w(2)
end

plotscript, after battle, begin
  $0="After battle script:"
  battle script sequence += 1
  append number(0, battle script sequence)
  trace(0)
  show string at(0, 0, 0)
  w
  hide string(0)
end

plotscript, instead of battle, form, begin
  $0="Instead of battle script("
  append number(0, form)
  $0+")"
  trace(0)
  w
  fight formation(form)
  $0="  finish instead of battle"
  trace(0)
end

########################################################################

script, enemy tests, begin
  $0="enemy tests"

  assert(enemy elemental resist as int(enemy:Data Read Test, 2) == 0)
  assert(enemy elemental resist as int(enemy:Data Read Test, 15) == -5)

  # Name
  get enemy name(enemy:Data Read Test, 1)
  $2 = "Data read test"
  assert(string compare(1, 2))
  $2 = "Data read test2"
  set enemy name(enemy:Data Read Test, 2)
  get enemy name(enemy:Data Read Test, 1)
  assert(string compare(1, 2))

  # Stats
  assert(get enemy stat(enemy:Data Read Test, stat:HP) == 13)
  set enemy stat(enemy:Data Read Test, stat:Hits, 6)
  assert(get enemy stat(enemy:Data Read Test, stat:Hits) == 6)

  # Other
  set enemy appearance(enemy:Data Read Test, Enemy:PictureSize, EnemySize:large)
  assert(get enemy appearance(enemy:Data Read Test, Enemy:PictureSize) == EnemySize:large)
  assert(get enemy appearance(enemy:Data Read Test, Enemy:Palette) == -1)
  assert(read enemy data(enemy:Data Read Test, Enemy:Item) == 1)
  assert(read enemy data(enemy:Data Read Test, Enemy:ItemPercent) == 50)
  write enemy data(enemy:Data Read Test, Enemy:RareItem, 2)
  assert(read enemy data(enemy:Data Read Test, Enemy:RareItem) == 2)
end

########################################################################

script, timer tests, begin
  variable(i)
  
  allocate timers(32)
  set timer(31, 10, 1, @timer test 1, 1)
  show string at(1, 10, 10)
  w, w, w, w
  if(read timer(31) <> 6) then($0="read timer failed", crash)
  w(6), w

  timer global := 100
  set timer(31, 0, 1, @looping timer test, 1)
  w(11)
  
  $err arg string="random"
  test timer and battle interactions(@force random battle)
  $err arg string="fight formation"
  test timer and battle interactions(@force non random battle)
  $err arg string=""

  
  $0="timer interactions with after battle", trace(0)
  teleport to map(1, 10, 8), w # after battle
  battle script sequence := 0
  test timer and battle interactions(@force random battle)
  if(battle script sequence <> 3) then($0="(ab) afterbattle should have happened 3 times", crash)

  $0="timer interactions with instead of battle", trace(0)
  teleport to map(2, 10, 8), w # instead of battle
  battle script sequence := 0
  test timer and battle interactions(@force random battle)
  if(battle script sequence <> 0) then($0="(ib) afterbattle should have happened 0 times", crash)

  $0="timer interactions with after battle + instead of battle", trace(0)
  teleport to map(3, 10, 8), w # after battle + instead of battle
  battle script sequence := 0
  test timer and battle interactions(@force random battle)
  if(battle script sequence <> 3) then($0="(ab+ib) afterbattle should have happened 3 times", crash)

  teleport to map(0, 10, 8), w

  hide string(1)
end

script, test timer and battle interactions, trigger battle, begin
  #regular timer
  timer global := 0
  timer global will become := 1000
  set timer(31, 10, 1, @battle timer test, -1, 0)
  run script by id(trigger battle)
  if(read timer(31) <= 0) then($0="timer should still be running", crash)
  if(timer global <> 0) then($0="timer global should still be zero", crash)
  w(10)
  if(read timer(31) >> 0) then($0="timer should be done by now", crash)
  if(timer global <> timer global will become) then($0="timer global didn't change", crash)
  
  #timerflag:battle
  timer global := 0
  timer global will become := 2000
  set timer(31, 10, 1, @battle timer test, 1, timerflag:battle)
  run script by id(trigger battle)
  if(read timer(31) >> 0) then($0="(bat) timer should be done by now", crash)
  if(timer global <> timer global will become) then($0="(bat) timer global didn't change", crash)

  #timerflag:battle ,or, timerflag:critical
  timer global := 0
  timer global will become := 3000
  set timer(31, 10, 1, @battle timer test, 1, (timerflag:battle,or,timerflag:critical))
  set timer(30, 20, 1, @battle timer test, 0, (timerflag:battle))
  run script by id(trigger battle)
  if(read timer(31) >> 0) then($0="(bat+crit) timer should be done by now", crash)
  if(timer global <> timer global will become) then($0="(bat+crit) timer global didn't change", crash)
  timer global will become := 4000
  if(read timer(30) <= 0) then($0="(bat+crit) second timer should still be running", crash)
  w(12)
  if(read timer(30) >> 0) then($0="(bat+crit) second timer should be done by now", crash)
  if(timer global <> timer global will become) then($0="(bat+crit) timer global didn't change on second timer", crash)
  timer global := 0
  w
end

script, timer test 1, begin
  show value(12345)
  w
end

script, looping timer test, begin
  if(timer global <= 0) then(timer global := 0, exit script)
  set timer(31, 0, 1, @looping timer test, 1)
  timer global -= 10
  show value(timer global)
  _checkpoint
end

script, battle timer test, begin
  timer global := timer global will become
  $0="battle timer test:"
  append number(0, timer global)
  trace(0)
  show string(0), w
end

########################################################################

script, door tests, begin
  $0="Door tests"

  # preconditions
  assert(herox(me) == 6)
  assert(heroy(me) == 6)

  # querying doors
  assert(get door x(0) == 8)
  assert(get door y(0) == 5)
  assert(door at spot(8, 5) == 0)
  assert(door at spot(0, 0) == -1)  # lots of doors at 0,0 with exists=NO
  assert(door at spot(1, 1) == 1)   # door 1 has no enabled doorlinks
  assert(get door destination id(0) == 1)
  assert(get door destination map(0) == 4)
  assert(door exists(0))
  assert(get door destination id(1) == -1)
  assert(get door destination map(1) == -1)
  assert(door exists(1))
  assert(door exists(3) == false)

  # using doors
  walk hero to x(0, 8)
  wait for hero
  walk hero to y(0, 5)
  wait for hero
  assert(current map == map:door test map)
  assert(hero x(0) == 4)
  assert(hero y(0) == 4)
  walk hero(0, south, 1)
  wait for hero
  use door(1)  # shouldn't work, disabled by tag
  assert(hero x(0) == 4)  # not triggered
  walk hero(0, north, 1)
  wait for hero
  assert(hero x(0) == 4)  # not triggered
  set tag(tag:door req tag, on)
  wait
  assert(hero x(0) == 4)  # not triggered by enabling a door underneath you
  suspend doors
  walk hero(0, south, 1)
  wait for hero
  walk hero(0, north, 1)
  wait for hero
  assert(hero x(0) == 4)  # not triggered
  resume doors
  wait
  assert(hero x(0) == 4)  # door underneath hero not triggered
  walk hero(0, south, 1)
  wait for hero
  walk hero(0, north, 1)
  wait for hero
  assert(hero x(0) == 11)  # triggered
  assert(hero y(0) == 4)
  use door(0)  # door underneath hero
  assert(current map == map:test town)
  walk hero to y(0, 6)
  wait for hero
  walk hero to x(0, 6)
  wait for hero
end

########################################################################

# Not defined as a plotscript on purpose
script, each-step script, x, y, dir, begin
  $0="each-step script", trace
  assert(x == hero x)
  assert(y == hero y)
  assert(dir == hero direction)

  # First to run
  assert(npc script triggered == 0)
  assert(map autorun triggered == 0)
  eachstep triggered += 1
end

plotscript, npc triggered script, arg, npc, begin
  $0="npc triggered script", trace
  assert(arg == -1)
  assert(npc == npc reference(6))

  # Second to run
  assert(eachstep triggered == 1)
  assert(map autorun triggered == 0)
  npc script triggered += 1

  # Should happen immediately (also tested in textbox tests)
  # and trigger two scripts
  advance textbox

  # Fifth to run
  assert(current textbox == -1)
  assert(textbox script triggered == 1)
  assert(map autorun triggered == 1)
  npc script triggered += 1
end

plotscript, textbox advance script, begin
  $0="textbox advance script", trace
  assert(current map == 4)
  assert(hero direction == left)

  # These should be the correct position on the new map
  assert(hero x == 11)
  assert(hero y == 4)

  # Third to run
  assert(current textbox == -1)
  assert(npc script triggered == 1)
  assert(eachstep triggered == 1)
  assert(map autorun triggered == 0)
  textbox script triggered += 1
end

# Set on map 4
plotscript, map 4 autorun, arg, begin
  if (testing script triggers == false) then (exit script)
  $0="map 4 autorun", trace

  assert(arg == 42)
  assert(current map == 4)

  # Fourth to run
  assert(textbox script triggered == 1)
  map autorun triggered += 1   # also set in "map autorun"
end


script, concurrent script trigger tests, begin
  $0="concurrent script trigger tests"

  # Test multiple triggers happening the same tick, and that they all happen in the right order.
  # This tests both scripts triggered inside and outside the interpreter

  # Take a step, triggering each-step script and stepping on an NPC, which runs a script,
  # and opens a textbox, which is advanced, running a script and activating a door which
  # goes to a map with an autorun script

  # Preconditions
  assert(current map == 0 && hero x == 6 && hero y == 6)

  map autorun triggered := 0
  set eachstep script (@each-step script)
  testing script triggers := true

  tick counter slice := create container
  set slice velocity x(tick counter slice, 1, 1000)
  assert(ticknumber == 0)
  assert(get hero speed(me) == 4)

  create npc(6, hero x -- 1, hero y)
  walk hero(me, left, 1)
  wait for hero

  #Events happen in this order, and scripts should run in reverse
  #order of triggering:
  #  (step completes)
  #  (npc triggered, opens textbox)
  #  npc script triggered
  #  each step triggered
  # (interpreter entered)
  #  (npc script calls advancetextbox)
  #  (textbox loaded)
  #  (preparemap called)
  #  autorun triggered
  #  after textbox script triggered
  # (this script resumes, waitfornpc finishing)

  #So scripts run in order (all in the same tick):
  # | each step
  # | npc
  # | | autorun
  # | | textbox
  # | npc finishes
  # this script

  # Sixth to run
  assert(npc script triggered == 2)
  assert(textbox script triggered == 1)
  assert(map autorun triggered == 1)
  assert(ticknumber == 5)

  # reset
  testing script triggers := false
  free slice(tick counter slice)
  teleport to map (0, 6, 6)
  assert(current map == 0)
  # Incremented by the other map autorun script
  assert(map autorun triggered == 2)
end

########################################################################

script, another eachstep script, begin
  eachstep triggered += 1
  wait for hero
end

script, yet another eachstep script, begin
  eachstep triggered += 1
  subscript, waiting, begin
    wait for hero
  end
  waiting
end

script, script double trigger tests, begin
  $0="script trigger tests"

  # precondition
  assert(hero x == 6 && hero y == 6)

  # Test double triggering of scripts
  set eachstep script(@another eachstep script)

  variable (oldval)
  oldval := read general(101)  # genBits
  assert((oldval, and, 2^10) == 0)  # "permit double triggering scripts" is off

  eachstep triggered := 0
  walk hero(me, left, 3)
  wait for hero
  # eachstep should only trigger once
  assert(eachstep triggered == 1)

  write general(101, oldval + 2^10)  # turn on "permit double triggering scripts"

  eachstep triggered := 0
  walk hero(me, right, 3)
  wait for hero
  assert(eachstep triggered == 3)

  set eachstep script(@yet another eachstep script)

  eachstep triggered := 0
  walk hero(me, left, 3)
  wait for hero
  assert(eachstep triggered == 3)

  write general(101, oldval)  # turn off "permit double triggering scripts"

  eachstep triggered := 0
  walk hero(me, right, 3)
  wait for hero
  # eachstep should should trigger three times because the "waiting"
  # subscript foils the double trigger check
  assert(eachstep triggered == 3)

  set eachstep script(0)
  assert(hero x == 6 && hero y == 6)
end

script, script trigger tests, begin
  concurrent script trigger tests
  script double trigger tests
end

########################################################################

script, slice tests, begin
  $0="Slice tests"

  # Extremely unfinished!

  slice collection tests
  slice find tests
  slice tree tests

  # Test rect transparency
  variable (sl)
  sl := create rect(100, 100), w
  assert(get rect trans(sl) == trans:solid)
  assert(get rect fuzziness(sl) == 100)
  set rect trans(sl, trans:hollow), w
  assert(get rect trans(sl) == trans:hollow)
  assert(get rect fuzziness(sl) == 0)
  set rect fuzziness(sl, 25), w
  assert(get rect trans(sl) == trans:fuzzy)
  assert(get rect fuzziness(sl) == 25)
  set rect fuzziness(sl, 0), w
  assert(get rect trans(sl) == trans:hollow)
  assert(get rect fuzziness(sl) == 0)
  set rect fuzziness(sl, 100), w
  assert(get rect trans(sl) == trans:solid)
  assert(get rect fuzziness(sl) == 100)
  set rect trans(sl, trans:fuzzy), w
  assert(get rect trans(sl) == trans:fuzzy)
  assert(get rect fuzziness(sl) == 50)
  free slice(sl)

  # Text slices
  $2="fee Fi\n \n foe"
  sl := create text
  set slice text(sl, 3)
  get slice text(2, sl)
  assert(string compare(2, 3))
  $2=""
  $3=""
  free slice(sl)
end

########################################################################

# A lot more could be added to this
script, slice collection tests, begin
  $0="slice collection tests"

  variable(sl, sl2)

  # This is a slice collection with the root slice edited
  sl := load slice collection(0)
  w
  assert(sl)
  assert(parent slice(sl) == sprite layer)  # default parent
  assert(slice is rect(sl) == true)
  assert(is filling parent(sl) == false)
  assert(slice x(sl) == -10)
  assert(slice y(sl) == -20)
  assert(slice width(sl) == 100)
  assert(slice height(sl) == 50)
  assert(get slice visible(sl) == true)
  assert(get slice extra(sl, 0) == 10)
  assert(get slice extra(sl, 1) == 20)
  assert(get slice extra(sl, 2) == 30)
  assert(get top padding(sl) == 1)
  assert(get right padding(sl) == 2)
  assert(get bottom padding(sl) == 3)
  assert(get left padding(sl) == 4)
  assert(get slice clipping(sl) == true)
  assert(get slice lookup(sl) == sli:my root slice)
  assert(get rect fuzziness(sl) == 48)
  assert(get rect fg col(sl) == 101)
  assert(get rect bg col(sl) == 71)
  assert(get rect style(sl) == -1)
  assert(get horiz align(sl) == edge:right)
  assert(get vert align(sl) == edge:middle)
  assert(get horiz anchor(sl) == edge:right)
  assert(get vert anchor(sl) == edge:top)
  assert(slice screen x(sl) == 310)   # position of the anchor point
  assert(slice screen y(sl) == 80)

  sl2 := lookup slice(sli:some lookup)
  assert(sl2)
  assert(parent slice(sl2) == sl)
  assert(slice is sprite(sl2) == true)
  assert(get sprite type(sl2) == spritetype:walkabout)
  assert(get spriteset number(sl2) == 6)
  assert(get sprite palette(sl2) == 1)
  assert(get sprite frame(sl2) == 4)
  assert(get sprite trans(sl2) == false)
  assert(sprite is horiz flipped(sl2) == true)
  assert(sprite is vert flipped(sl2) == false)
  assert(sprite is dissolving(sl2) == false)
  assert(slice width(sl2) == 20)
  assert(slice height(sl2) == 20)
  assert(slice x(sl2) == 10)
  assert(slice y(sl2) == -4)
  assert(slice screen x(sl2) == 224)
  assert(slice screen y(sl2) == 77)

  free slice(sl)

  # A simpler collection
  sl := load slice collection(1)
  assert(sl)
  assert(get slice visible(sl) == false) # edited
  assert(is filling parent(sl) == true)  # the default
  free slice(sl)
end

########################################################################

# Unfinished tests of findcollidingslice and sliceatpixel
script, slice find tests, begin
  $0="Slice find tests"

  variable(parent, sl1, sl2, sl3, sl4, sl5, temp)

  parent := create select
  # f
  put slice (parent, 100, 200)

  sl1 := create container(0, 0)
  set parent(sl1, parent)
  assert(slice at pixel(parent, 0, 0) == 0)  # can't be hit
  assert(slice at pixel(parent, 0, 0, get count) == 0)

  sl2 := create container(10, 10)
  set parent(sl2, parent)

  sl3 := create container(1, 1)
  set parent(sl3, parent)
  put slice(sl3, 4, 5)

  sl4 := create container(1, 1)
  set parent(sl4, sl3)

  wait(1)  # update select visibility

  ### Testing slice at pixel
  # Unfinished: no testing of slices with negative width/height.

  # Only sl1 should be visible...
  assert(get slice visible(sl1) == true)
  assert(get slice visible(sl2) == false)
  assert(get slice visible(sl3) == false)
  assert(get slice visible(sl4) == true)

  assert(slice at pixel(parent, 100, 200) == sl2)  # topleft corner
  assert(slice at pixel(parent, 105, 205) == sl2) # inside
  assert(slice at pixel(parent, 100, 210) == 0)  # on the edge of sl2
  assert(slice at pixel(parent, 110, 200) == 0)  # on the edge of sl2
  assert(slice at pixel(parent, 100, 200, get count) == 1)

  # sliceatpixel and findcollidingslice must return slices from bottommost to topmost
  assert(slice at pixel(parent, 104, 205, get count) == 3)
  assert(slice at pixel(parent, 104, 205, 0) == sl2)
  assert(slice at pixel(parent, 104, 205, 1) == sl3)
  assert(slice at pixel(parent, 104, 205, 2) == sl4)
  assert(slice at pixel(parent, 104, 205, 3) == 0)

  # now without descending:
  assert(slice at pixel(parent, 104, 205, get count, false) == 2)
  assert(slice at pixel(parent, 104, 205, 0, false) == sl2)
  assert(slice at pixel(parent, 104, 205, 1, false) == sl3)
  assert(slice at pixel(parent, 104, 205, 2, false) == 0)

  # test visibleonly
  assert(slice at pixel(parent, 104, 205, get count, true, true) == 0)
  # visibility of the 'parent' slice doesn't affect anything (sl3 is not visible)
  assert(slice at pixel(sl3, 104, 205, get count, true, true) == 1)
  # change visible slice to sl3
  set select slice index(parent, 2)
  wait(1)  # update select visibility
  assert(get slice visible(sl3) == true)
  # now sl3 and sl4 should be hit
  assert(slice at pixel(parent, 104, 205, get count, true, true) == 2)
  assert(slice at pixel(parent, 104, 205, 0, true, true) == sl3)
  assert(slice at pixel(parent, 104, 205, 1, true, true) == sl4)

  # ##Testing find colliding slice
  # Unfinished: don't yet check all the cases of an AABB collision test.
  # (Easier to test manually with collisiontest.rpg)
  # Also, no testing of slices with negative width/height.

  sl5 := load walkabout sprite(0)
  assert(slice height(sl5) == 20)
  put slice(sl5, 100, 190)

  # sliceatpixel and findcollidingslice must return slices from bottommost to topmost
  #tracevalue(parent, sl1, sl2, sl3, sl4, sl5)

  assert(find colliding slice(parent, sl5, get count) == 3)
  # Don't find sl1 which is zero size (FIXME: which I think probably isn't correct, since "slice contains" would say yes)
  #assert(find colliding slice(parent, sl5, 0) == sl1)
  assert(find colliding slice(parent, sl5, 0) == sl2)
  assert(find colliding slice(parent, sl5, 1) == sl3)
  assert(find colliding slice(parent, sl5, 2) == sl4)
  assert(find colliding slice(parent, sl5, 3) == 0)

  # Test non-overlap of adjacent slices
  set parent(sl5, parent)
  put slice(sl5, 0, 5 -- 20)  # positioned so sl3 on bottom edge
  # Create another couple slices adjacent to sl5 on the top and left
  temp := create container(1, 1)
  set parent(temp, parent)
  put slice(temp, -1, 0)  # 1 left of sl5
  temp := create container(1, 1)
  set parent(temp, parent)
  put slice(temp, 0, 5 -- 20 -- 1)  # 1 up from sl5
  assert(find colliding slice(parent, sl5, 0) == sl2)  # doesn't find sl1, zero size
  assert(find colliding slice(parent, sl5, 1) == 0)  # should not find sl3, nor sl5, nor the temp slices

  # Now overlap by one
  put slice(sl5, 0, 5 -- 20 + 1)  # now sl3 overlaps
  assert(find colliding slice(parent, sl5, 0) == sl2)   # doesn't find sl1, zero size
  assert(find colliding slice(parent, sl5, 1) == sl3)
  assert(find colliding slice(parent, sl5, 2) == sl4)
  assert(find colliding slice(parent, sl5, 3) == 0)  # should not find sl5

  # try using a 0x0 slice to search for overlaps - should find nothing.
  # (FIXME: This is probably not correct; as an infinitesimal point it overlaps sl2 and sl5.
  assert(find colliding slice(parent, sl1, 0) == 0)

  # Test no descending
  # Don't find sl1 which is zero size (FIXME: which I think probably isn't correct, since "slice contains" would say yes)
  assert(find colliding slice(parent, sl5, 0, false) == sl2)
  assert(find colliding slice(parent, sl5, 1, false) == sl3)
  assert(find colliding slice(parent, sl5, 2, false) == 0)  # shouldn't find sl4
  assert(find colliding slice(parent, sl5, get count, false) == 2)

  # Test visible only (only sl3 is visible)
  assert(find colliding slice(parent, sl5, get count, true, true) == 2)
  assert(find colliding slice(parent, sl5, 0, true, true) == sl3)
  assert(find colliding slice(parent, sl5, 1, true, true) == sl4)
  assert(find colliding slice(parent, sl5, 2, true, true) == 0)

  # Do two 0x0 slices at the same point overlap? (they shouldn't)
  free slice children(parent)
  temp := create container(0, 0)
  set parent(temp, parent)
  temp := create container(0, 0)
  set parent(temp, parent)
  assert(find colliding slice(parent, temp, get count) == 0)

  free slice(parent)
end

########################################################################

# Return index of a child of the root slice.
script, root child index, lookup code, begin
  variable(sl)
  clear string(err arg string)
  append number(err arg string, lookup code)
  sl := lookup slice(lookup code)
  assert(sl)
  assert(parent slice(sl) == lookup slice(sl:root))
  return(slice child indeX(sl))
end

# Unfinished, only inspects children of root.
script, slice tree tests, begin
  $0="Slice tree tests"

  assert(sprite layer == lookup slice(sl:script layer))

  # Ensure the major slice layers appear in a fixed order.
  # Probably OK for new slices to get added between these (anyone making assumptions about the n-th
  # child here may be too much pain to support)

  variable (scr, map, textbox, backdrop, strings)

  map := root child index(sl:map root)
  backdrop := root child index(sl:backdrop)
  scr := root child index(sl:script layer)
  textbox := root child index(sl:textbox layer)
  strings := root child index(sl:string layer)

  assert(map < backdrop)
  assert(backdrop < scr)
  assert(scr < textbox)
  assert(textbox < strings)
end

########################################################################

script, save slot tests, begin
  $0="save slot tests"
  # This includes tests for commands which operate on save slots,
  # but doesn't test load saves itself; that's in "savegame tests"

  delete save(1)
  assert(save slot used(1) == false)

  # import/export globals

  export global 1 := -123456789
  export globals(1, @export global 1, @export global 1)
  export global 1 := -2
  assert(import globals(1, @export global 1) == -123456789)
  assert(export global 1 == -2)
  import globals(1, @export global 1, @export global 2)
  assert(export global 1 == -123456789)
  assert(export global 2 == 0)
  export global 2 := -100
  export globals(1, @export global 2, @export global 2)
  save in slot(2)
  export global 1 := 0
  export global 2 := 0
  export globals(1, @export global 1, @export global 2)
  export global 1 := 100
  export global 2 := 100
  import globals(1, @export global 1, @export global 2)
  assert(export global 1 == 0)
  assert(export global 2 == 0)

  # exporting globals should not cause a slot to appear as used
  assert(save slot used(1) == false)
  assert(save slot used(2) == true)

  # testing saveinslot earlier
  import globals(2, @export global 1, @export global 2)
  assert(export global 1 == -123456789)
  assert(export global 2 == -100)

  # different form
  export global 1 := -3
  assert(import globals(2, @export global 1, @export global 1) == 0)
  assert(export global 1 == -123456789)

  # loading from nonexistent save... it's not actually specified
  # what's meant to happen, but this is reasonable
  import globals(3, @export global 1, @export global 2)
  assert(export global 1 == 0)
  assert(export global 2 == 0)

  # delete save is meant to preserve stored global variables  (FAILS!!)
  delete save(1)
  delete save(2)
  assert(save slot used(1) == false)
  assert(save slot used(2) == false)
  export global 1 := -1
  export global 2 := -2
  #import globals(1, @export global 1, @export global 2)
  #assert(export global 1 == 0)
  #assert(export global 2 == 0)
  #import globals(2, @export global 1, @export global 2)
  #assert(export global 1 == -123456789)
  #assert(export global 2 == -100)

end

########################################################################

script, plotstr tests, begin
  # This tests only the display of plotstrings.
  # Pure string manipulation stuff is tested in "string tests"
  $0="plotstr tests"

  assert(string is visible(0) == false)
  position string(0, 50, 50)
  assert(string is visible(0) == false)

  # There are no commands to return the current colour and style
  # of a string (and I don't see any need to add them), so that can only be
  # tested by screenshot.
  $5 = "This isn't empty"
  show string at(5, 110, 100)
  assert(string is visible(5))
  assert(string x(5) == 110)
  assert(string y(5) == 100)
  # Should default to 'string:outline' style and uiText colour
  w
  string color(5, 72, 15) # green, white
  w
  # Test return to default colour
  string color(5)
  w
  position string(5, 10, 20)
  assert(string x(5) == 10)
  assert(string y(5) == 20)
  w
  center string at(5, 160, 0)
  assert(string x(5) == 160 -- 4 * 16)
  assert(string y(5) == 0)

  $6 = "This is flat yellow-on-purple"
  show string at(6, 100, 110)
  assert(string is visible(6) == true)
  string style(6, string:flat)
  string color(6, 14, 194)  # yellow, purple
  w
  string color(6)
  $6 = "Now it's tranparent flat white"
  w

  show string(6)
  w
  show no value

  # Can't test colour, style

  # Test clear
  clear string(6)
  assert(string length(6) == 0)
  # Shouldn't affect visibility
  assert(string is visible(6) == true)
  $6 = "XYZ"

  # Test hide
  hide string(6)
  assert(string is visible(6) == false)
  w

  # Clean up
  hide string(5)
  clear string(5)
  clear string(6)
  w
end

########################################################################

script, savegame tests 1, begin
  $0="saving/loading tests 1"
  # Partially split into savegame tests 2

  # Preconditions
  assert(current map == 0)
  delete save(1)
  assert(save slot used(1) == false)

  savegame map := current map
  savegame slice := create rect(15, 20)
  put slice(savegame slice, 40, 40)
  savegame npc := createnpc(0, 5, 5)

  # Saving of string state
  # There are no commands to return the current colour and style
  # of a string (and I don't see any need to add them), so that can only be
  # tested by screenshot.
  $5 = "This is a green string"
  show string at(5, 100, 100)
  string color(5, 72)
  # Should default to 'string:outline' style
  $6 = "This is flat yellow-on-purple"
  show string at(6, 100, 110)
  string style(6, string:flat)
  string color(6, 14, 194)  # yellow, purple
  $7="This should be plain style"
  show string at(7, 100, 120)
  w

  save in slot(1)
  assert(save slot used(1) == true)

  # Change state a bit to check it's actually reset and loaded
  hide string(5)
  $5 = ""
  $6 = "Now it's plain"
  string color(6)
  $7 = "Now it's yellow-on-purple"
  string style(7, string:flat)
  string color(7, 14, 194)  # yellow, purple
  show string at(8, 0, 0)
  teleport to map(1)
  savegame map := -1
  free slice(savegame slice)
  w

  load from slot(1)

  $0="Loadgame didn't happen!"
  crash
end

# This is called from the loadgame plotscript
script, savegame tests 2, begin
  $0="saving/loading tests 2"
  trace(0)

  # Check globals, map number, slices loaded (game is set to save slices)
  assert(savegame map == current map)
  assert(slicewidth(savegame slice) == 15)

  # Check NPCs were NOT saved
  assert(get npc id(savegame npc) == -1)  # doesn't exist

  # Check strings were loaded
  assert(string is visible(5))
  assert(string is visible(6))
  assert(string is visible(8) == false)
  $8 = "This is a green string"
  assert(string compare(5, 8))
  assert(string x(6) == 100)
  assert(string y(6) == 110)
  # Can't test colour, style
  w

  # Clean up
  hide string(5)
  hide string(6)
  hide string(7)
  free slice(savegame slice)
end

########################################################################

########################################################################
#### COMMANDS THAT STILL NEED TESTS
#### (Note: interactive commands should be in interactivetest.hss)
#add enemy to formation (formation, enemy id, x, y, slot)
#allow minimap (setting)
#allow save anywhere (setting)
#alter NPC (who,NPCstat,value)
#value,and,value
#animation start tile (tile number, layer)
#append ascii (ID, char)
#append number (ID, number)
#ascii from string (ID, position)
#autosave
#break
#camera follows hero (who)
#camera follows NPC (who)
#camera pixel X
#camera pixel Y
#cancel map name display
#center slice (handle)
#change tileset (tileset, layer)
#check NPC wall (who, direction)
#check parentage (handle, parent handle)
#check tag (tag)
#child count (handle)
#clamp slice (handle1, handle2)
#clone sprite (handle)
#concatenate strings (dest, source)
#continue
#copy string (dest, source)
#create container (width, height)
#create ellipse (width, height, border color, fill color)
#create grid (width, height, rows, columns)
#create NPC (ID,X,Y,direction)
#create rect (width, height, style)
#create text
#current display tile (tile number, layer)
#current map
#current stat
#days of play
#decrement (variable,amount)
#delete char (ID, position)
#delete enemy from formation (formation, slot)
#delete map state (whichdata)
#delete save (slot)
#destroy NPC (reference)
#dismount vehicle
#number / number
#draw NPCs above heroes (setting)
#number == number
#exit returning(value)
#exit script
#expand string(ID)
#number ^ power
#extended scancodes enabled
#extract color(color, component)
#fade screen in
#fade screen out (red,green,blue)
#fight formation (number)
#fill parent (handle, true_or_false)
#find enemy in formation (formation, enemy id, copy number)
#first child(handle)
#first container child(handle)
#first rect child(handle)
#first sprite child(handle)
#focus camera (x,y,speed)
#for(counter,start,finish,step) do(commands)
#formation probability (formation set, formation)
#formation set frequency (formation set)
#formation slot enemy (formation, slot)
#formation slot x (formation, slot)
#formation slot y (formation, slot)
#free slice (handle)
#free slice children (handle)
#free sprite (handle)
#game over
#get ambient music
#get attack name
#get color(index)
#get damage cap
#get each step script
#get ellipse border col (handle)
#get ellipse fill col (handle)
#get foot offset
#get formation background (formation, background)
#get formation song (formation)
#get global string (ID, global)
#get grid columns (handle)
#get grid rows (handle)
#get hero slice (who)
#get instead of battle script
#get item name (ID, item)
#get load script
#get map edge mode
#get map name (ID, map)
#get map tileset
#get money (amount)
#get NPC ignores walls (who)
#get NPC moves (who)
#get NPC obstructs (who)
#get NPC slice (who)
#get NPC usable (who)
#get on keypress script
#get outline(handle)
#get rect border (handle)
#get slice velocity x (handle)
#get slice velocity y (handle)
#get song name (ID, song)
#get sort order (handle)
#get text bg(handle)
#get text color(handle)
#get tile animation offset (animation pattern, layer)
#get victory music
#get wrap(handle)
#get zone extra (zone id, extra num)
#get zone name (string id, zone id)
#globals to string(ID, starting global, length)
#number >> number
#number >= number
#greyscale palette (first, last)
#grid is shown (handle)
#hide battle health meter (state)
#hide battle ready meter (state)
#horiz flip sprite (handle, flip)
#hours of play
#increment (variable,amount)
#inside battle
#is filling parent (handle)
#last child(handle)
#last formation
#last save slot
#layer tileset (layer)
#leader
#number << number
#number <= number
#load attack sprite (num, palette)
#load backdrop sprite (num)
#load border sprite (num, palette)
#load from slot (slot)
#load hero sprite (num, palette)
#load large enemy sprite (num, palette)
#load map state (whichdata, customid)
#load medium enemy sprite (num, palette)
#load palette (palette number)
#load portrait sprite (num, palette)
#load small enemy sprite (num, palette)
#load tileset (tileset, layer)
#load walkabout sprite (num, palette)
#load weapon sprite (num, palette)
#value && value
#value || value
#value ^^ value
#lookup slice (lookup code, start slice)
#lose money (amount)
#map cure (attack, target, attacker)
#map height (map)
#map width (map)
#milliseconds
#minutes of play
#number,mod,number
#move slice above (handle, above what handle)
#move slice below (handle, below what handle)
#move slice by (handle, relative x, relative y, ticks)
#move slice to (handle, x, y, ticks)
#number * number
#next container sibling(handle)
#next rect sibling(handle)
#next sibling(handle)
#next sprite sibling(handle)
#not (value)
#number <> number
#NPC at pixel (x, y, number)
#NPC at spot (x, y, number)
#NPC copy count (ID)
#NPC direction (who)
#NPC extra (who, which)
#NPC frame (who)
#NPC is walking (who)
#NPC pixel X (who)
#NPC pixel Y (who)
#NPC reference (ID, copy)
#NPC X (who)
#NPC Y (who)
#number from string (ID, default)
#value,or,value
#order menu
#outside battle
#outside battle cure
#overhead tile
#pan camera (direction,distance,pixelstep)
#party money
#pay money (amount)
#place sprite
#previous sibling(handle)
#put camera (x,y)
#put npc (who,x,y)
#put slice (handle, X, Y)
#put slice screen (handle, x, y)
#put sprite (handle, x, y)
#random (lownumber, highnumber)
#random formation (formation set)
#read attack name (ID, attack)
#read color (index, element)
#read map block (x,y,layer)
#read NPC (who,NPCstat)
#read pass block (x,y)
#read timer (id)
#read zone (zone id, x, y)
#realign slice (handle, horiz align, vert align, horiz anchor, vert anchor)
#replace attack sprite (handle, num, palette)
#replace backdrop sprite (handle, num)
#replace border sprite (handle, num, palette)
#replace char (ID, position, char)
#replace hero sprite (handle, num, palette)
#replace large enemy sprite (handle, num, palette)
#replace medium enemy sprite (handle, num, palette)
#replace portrait sprite (handle, num, palette)
#replace small enemy sprite (handle, num, palette)
#replace walkabout sprite (handle, num, palette)
#replace weapon sprite (handle, num, palette)
#reset game
#reset map state (whichdata)
#reset palette
#resume map music
#resume NPCs
#resume NPC walls
#resume obstruction
#resume overlay
#resume random enemies
#resume random enemys
#resume timers
#RGB(red, green, blue)
#run script by ID (id, argument1, argument2, argument3...)
#save in slot (slot)
#save map state (whichdata, customid)
#save menu (reallysave)
#search string (ID1, ID2, start)
#seconds of play
#seed random (new seed)
#set battle wait mode (state)
#set bottom padding (handle, pixels)
#set color(index, value)
#set damage cap (cap)
#set days of play (days)
#set debug keys disable (state)
#set each step script (id)
#set ellipse border col (handle, color)
#set ellipse fill col (handle, color)
#set foot offset (offset)
#set formation background (formation, background, animation frames, animation ticks)
#set formation song (formation, song)
#set grid columns (handle, columns)
#set grid rows (handle, rows)
#set harm tile damage (amount)
#set harm tile flash (color)
#set horiz align (handle, edge)
#set horiz anchor (handle, edge)
#set hours of play (hours)
#set inn no revive mode (state)
#set instead of battle script (id)
#set inventory size (new size)
#set left padding (handle, pixels)
#set load script (id)
#set map edge mode (mode, default tile)
#set minutes of play (min)
#set money (amount)
#set no HP level up restore (state)
#set no MP level up restore (state)
#set NPC direction (who, direction)
#set NPC extra (who, which, value)
#set NPC frame (who, frame)
#set NPC ignores walls (who, value)
#set NPC moves (who, value)
#set NPC obstructs (who, value)
#set NPC position (who, X, Y)
#set NPC speed (who, speed)
#set NPC usable (who, value)
#set on keypress script (id)
#set outline(handle, outline)
#set padding (handle, pixels)
#set parent (handle, parent handle)
#set rect bgcol (handle, color)
#set rect border (handle, border)
#set rect fgcol (handle, color)
#set rect style (handle, style)
#set right padding (handle, pixels)
#set seconds of play (sec)
#set slice clipping (handle, clip)
#set slice edge x (handle, edge, value)
#set slice edge y (handle, edge, value)
#set slice extra (handle, extra, value)
#set slice height (handle, height)
#set slice lookup (handle, code)
#set slice screen x (handle, x)
#set slice screen y (handle, y)
#set slice velocity (handle, horiz pixels per tick, vert pixels per tick, ticks)
#set slice velocity x (handle, pixels per tick, ticks)
#set slice velocity y (handle, pixels per tick, ticks)
#set slice visible (handle, vis)
#set slice width (handle, width)
#set slice x (handle, X)
#set slice y (handle, Y)
#set sort order (handle, order)
#set sprite frame (handle, num)
#set sprite palette (handle, num)
#set sprite trans (handle, drawtransparent)
#set sprite visible
#set tag (tag,value)
#set text bg(handle, color)
#set text color(handle, color)
#set tile animation offset (animation pattern, offset, layer)
#set timer (id, count, speed, trigger, string, flags)
#set top padding (handle, pixels)
#variable := value
#set vert align (handle, edge)
#set vert anchor (handle, edge)
#set wrap(handle, wrap)
#set zone extra (zone id, extra num, value)
#show backdrop (number)
#show grid (handle, shown)
#show map
#show no value
#show value (number)
#sign (number)
#slice child (handle, number)
#slice collide (handle1, handle2)
#slice collide point (handle, x, y)
#slice contains (handle1, handle2)
#slice edge x (handle, edge)
#slice edge y (handle, edge)
#slice is container (handle)
#slice is ellipse (handle)
#slice is grid (handle)
#slice is moving (handle)
#slice is text (handle)
#slice is valid (id)
#slice to back (handle)
#slice to front (handle)
#sort children (handle, wipe)
#sprite frame count (handle)
#sqrt (number)
#stop slice (handle)
#stop timer (id)
#string to globals (ID, starting global, length)
#number -- number
#suspend catapillar
#suspend map music
#suspend NPCs
#suspend NPC walls
#suspend obstruction
#suspend overlay
#suspend random enemies
#suspend random enemys
#suspend timers
#system day
#system hour
#system minute
#system month
#system second
#system year
#teleport to map (map, x, y)
#trace value (expression, ...)
#trim string (ID, start, length)
#tweak palette (red, green, blue, first, last)
#update palette
#use item (item)
#use item in slot(slot)
#vert flip sprite (handle, flip)
#wait for all
#wait for camera
#wait for NPC (who)
#wait for slice (handle)
#walk NPC (who, direction, distance)
#walk NPC to X (who, X)
#walk NPC to Y (who, Y)
#write color (index, element, value)
#write map block (x,y,value,layer)
#write pass block (x,y,value)
#write zone (zone id, x, y, value)
#value,xor,value
#Y sort children (handle)
#zone at spot (x, y, count)
#zone number of tiles (zone id)