#
# Star Dartle 2000
#

#-----------------------------------------------------------------------
# Globals and consts

global variable(1, play zone)
global variable(2, player)
global variable(3, ticks)
global variable(4, escapes)
global variable(5, waves)

define constant(30, second)

#-----------------------------------------------------------------------
# Init

plotscript, new game, begin
  init game
  init mission
  main loop

end

script, init game, begin
  play song(0)
  suspend player
  set slice visible(lookup slice(sl:maproot), false)
  variable(col)
  col := load slice collection(0)
  play zone := lookup slice(sli:play zone, col)
  rev up starfield
end

script, init mission, begin
  player := create player ship()
  put at player start(player)
  waves := create wave manager level 1()
end

#-----------------------------------------------------------------------
# Game loop

script, main loop, begin
  while(true) do(
    ticks += 1
    do controls
    do updates
    wait(1)
  )
end

script, do controls, begin
  variable(sl)
  sl := player
  if(keyval(key:up)) then(push force y(sl, -2))
  if(keyval(key:down)) then(push force y(sl, 2))
  if(keyval(key:left)) then(push force x(sl, -2))
  if(keyval(key:right)) then(push force x(sl, 2))
  do debug keys
end

script, do debug keys, begin
  if(keyval(key:e) > 1) then(
    put random enemy(create blue enemy())
  )
end

script, do updates, begin
  update starfield
  for each obj(@increment age)
  check for enemies hitting player
  check for bullets hitting enemies
  for each obj(@run update callback)
  for each obj(@apply force)
  for each obj(@apply friction)
  for each obj(@run animation callback)
  for each obj(@delete stuff)
end

script, delete stuff, sl, begin
  if(get delete me(sl)) then(
    free slice(sl)
  )
end

#-----------------------------------------------------------------------
# Player Ship

script, create player ship, begin
  variable(sl)
  sl := load walkabout sprite(1)
  make obj(sl, 4)
  set update callback(sl, @player ship update)
  set animation callback(sl, @animate tilt and afterburner)
  set attack callback(sl, @player attack by hp)
  set death callback(sl, @default player death callback)
  exit returning(sl)
end

script, player ship update, sl, begin
  run attack callback(sl)
  keep obj onscreen(sl)
end

define constant(4, max weapon level)

script, player attack by hp, sl, begin
  switch(get hp(sl)) do(
    case(0)
    case(1) player attack 1(sl)
    case(2) player attack 2(sl)
    case(3) player attack 3(sl)
    case(4) player attack 4(sl)
  )
end

script, raise player weapon level, sl, begin
  variable(hp)
  hp := get hp(sl)
  hp := small(hp + 1, max weapon level)
  set hp(sl, hp)
end

script, player attack 1, sl, begin
  # Damage output 2 per second
  if(once every(sl, second / 2)) then(
    variable(b)
    b := create weak bullet()
    put at obj(b, sl)
  )
end

script, player attack 2, sl, begin
  # Damage output 3 per second
  if(once every(sl, second / 3)) then(
    variable(b)
    b := create weak bullet()
    put at obj(b, sl)
    variable(c)
    c := get bullet counter(sl)
    c := (c + 1) ,mod, 2
    set bullet counter(sl, c)
    switch(c) do(
      case(0) push force y(b, -3)
      case(1) push force y(b, 3)
    )
  )
end

script, player attack 3, sl, begin
  # Damage output 5 per second
  if(alternate(sl, second / 2)) then(
    if(once every(sl, second / 10)) then(
      variable(b)
      b := create weak bullet()
      put at obj(b, sl)
    )
  )
end

script, player attack 4, sl, begin
  # Damage output 7 per second
  if(once every(sl, second / 7)) then(
    variable(b)
    b := create null bullet()
    put at obj(b, sl)
    variable(c)
    c := get bullet counter(sl)
    c := (c + 1) ,mod, 8
    set bullet counter(sl, c)
    move slice to(b, slice x(b) + 320, slice y(b) + 30 * (abs(c -- 4) -- 2), second)
  )
end

script, create weak bullet, begin
  variable(sl)
  sl := create rect(6, 1)
  set rect border(sl, border:none)
  set rect bg col(sl, 15)
  make bullet(sl, 8)
  set update callback(sl, @weak bullet update)
  exit returning(sl)
end

script, weak bullet update, sl, begin
  set force x(sl, 8)
  remove if offscreen(sl)
end

script, create null bullet, begin
  variable(sl)
  sl := create container(6, 1)
  add walkabout pic(sl, 13) 
  make bullet(sl, 8)
  set update callback(sl, @null bullet update)
  set animation callback(sl, @null bullet animate)
  exit returning(sl)
end

script, null bullet animate, sl, begin
  variable(pic)
  pic := get pic(sl)
  loop frame(pic, 0, 7, 0)
end

script, null bullet update, sl, begin
  remove if offscreen(sl)
end

#-----------------------------------------------------------------------
# Damage

script, bullet collides with enemy, b, e, begin
  run collision callback(b, e)
end

script, enemy collides with player, e, p, begin
  run collision callback(e, p)
end

script, default bullet collide, b, e, begin
  inflict damage(e, get damage(b))
  set delete me(b, true)
end

script, default enemy collide, e, p, begin
  inflict damage(p, get damage(e))
end

script, inflict damage, sl, dmg, begin
  variable(hp)
  hp := get hp(sl)
  hp -= dmg
  set hp(sl, hp)
  if(hp <= 0) then(
    object dies(sl)
  )
end

script, object dies, obj, begin
  run death callback(obj)
end

script, default enemy death callback, e, begin
  put at obj(create explosion(), e)
  if(random percent(20)) then(
    put at obj(create powerup(), e)
  )
  set delete me(e, true)
end

script, default player death callback, p, begin
  put at obj(create explosion(), p)
  set delete me(p, true)
  game over
end

#-----------------------------------------------------------------------
# Power-Ups

script, create powerup, begin
  variable(sl)
  sl := create container(10, 10)
  make obj(sl)
  add walkabout pic(sl, 14)
  add text overlay(sl, $0="")
  set update callback(sl, @powerup update)
  set animation callback(sl, @powerup animate)
  exit returning(sl)
end

script, powerup update, sl, begin
  set force x(sl, -1)
  if(circle collide(player, sl)) then(
    raise player weapon level(player)
    set delete me(sl, true)
  )
  remove if offscreen(sl)
end

script, powerup animate, sl, begin
  variable(pic)
  pic := get pic(sl)
  loop frame(pic, 0, 7, second / 5)
  if(once every(sl, second / 3)) then(
    variable(txt, c)
    $0=" WEAPON+ + +"
    txt := get text overlay(sl)
    c := get counter(sl)
    $1=" "
    replace char(1, 1, ascii from string(0, c + 1))
    set slice text(txt, 1)
    c := (c + 1) ,mod, string length(0)
    set counter(sl, c)
  )
end

#-----------------------------------------------------------------------
# Enemies

script, create walkabout enemy, sprite, update callback, max speed, begin
  variable(sl)
  sl := load walkabout sprite(sprite)
  make enemy(sl, max speed)
  set update callback(sl, update callback)
  exit returning(sl)
end

script, create small enemy enemy, sprite, update callback, max speed, begin
  variable(sl)
  sl := load small enemy sprite(sprite)
  make enemy(sl, max speed)
  set update callback(sl, update callback)
  exit returning(sl)
end

script, create blue enemy, begin
  variable(sl)
  sl := create walkabout enemy(2, @mindless enemy update, 2)
  exit returning(sl)
end

script, mindless enemy update, sl, begin
  push force x(sl, get max force(sl) * -1)
  keep onscreen vertically(sl)
  check for escape(sl)
end

script, create green enemy, begin
  variable(sl)
  sl := create walkabout enemy(4, @serpentine enemy update, 2)
  exit returning(sl)
end

script, serpentine enemy update, sl, begin
  if(just once(sl)) then(
    set age(sl, second / 4)
  )
  if(alternate(sl, second / 2)) then(
    push force y(sl, -2, 4)
  )else(
    push force y(sl, 2, 4)
  )
  push force x(sl, get max force(sl) * -1)
  keep onscreen vertically(sl)
  check for escape(sl)
end

script, create red enemy, begin
  variable(sl)
  sl := create walkabout enemy(3, @diagonal enemy update, 2)
  exit returning(sl)
end

script, diagonal enemy update, sl, begin
  if(just once(sl)) then(
    variable(dest y, screen h)
    screen h := slice height(play zone)
    if(slice y(sl) < screen h / 2) then(
      dest y := screen h
    )else(
      dest y := 0
    )
    move slice to(sl, -40, dest y, second * 4)
  )
  keep onscreen vertically(sl)
  check for escape(sl)
end

script, create small asteroid enemy, begin
  variable(sl)
  sl := create small enemy enemy(0, @asteroid enemy update, 1)
  set hp(sl, 5)
  exit returning(sl)
end

script, asteroid enemy update, sl, begin
  push force x(sl, get max force(sl) * -1)
  keep onscreen vertically(sl)
  check for escape(sl)
end


#-----------------------------------------------------------------------
# Effects

script, create explosion, begin
  variable(sl)
  sl := load walkabout sprite(11)
  make obj(sl, 0)
  set animation callback(sl, @explosion animate)
  exit returning(sl)
end

script, explosion animate, sl, begin
  if(frame to last(sl, 0, 7)) then(
    set delete me(sl, true)
  )
end

#-----------------------------------------------------------------------
# Waves

script, create wave manager level 1, begin
  variable(sl)
  sl := create container(0,0)
  make obj(sl, 0)
  set update callback(sl, @wave manager level 1 update)
  exit returning(sl)
)

script, wave manager level 1 update, sl, begin
  if(just once(sl)) then(
    create spaced quad wave(@create small asteroid enemy)
  )
  if(once every(sl, second * 5)) then(
    variable(count)
    count := increment counter(sl)
    switch(count) do(
      case(1)
        create random quad wave(@create blue enemy)
      case(2)
        create random quad wave(@create red enemy)
      case(3)
        create center quad wave(@create green enemy)
      case(4)
        create spaced quad wave(@create blue enemy)
      case(5)
        create spaced tri wave(@create green enemy)
      case(else)
        set counter(sl, 0)
    )
  )
end

script, create random quad wave, ammo, begin
  variable(sl)
  sl := create container(0,0)
  make obj(sl, 0)
  set update callback(sl, @random quad wave update)
  set ammo creator(sl, ammo)
  exit returning(sl)
end

script, random quad wave update, sl, begin
  if(just once(sl)) then(
    put random enemy(sl)
  )
  if(once every(sl, second * 2 / 3)) then(
    put at obj(run ammo creator(sl), sl)
    if(increment counter(sl) >= 4) then(
      set delete me(sl, true)
    )
  )
end

script, create center quad wave, ammo, begin
  variable(sl)
  sl := create container(0,0)
  make obj(sl, 0)
  set update callback(sl, @center quad wave update)
  set ammo creator(sl, ammo)
  exit returning(sl)
end

script, center quad wave update, sl, begin
  if(just once(sl)) then(
    put center enemy(sl)
  )
  if(once every(sl, second * 2 / 3)) then(
    put at obj(run ammo creator(sl), sl)
    if(increment counter(sl) >= 4) then(
      set delete me(sl, true)
    )
  )
end

script, create spaced tri wave, ammo, begin
  variable(sl)
  sl := create container(0,0)
  make obj(sl, 0)
  set counter(sl, 3)
  set update callback(sl, @spaced n wave update)
  set ammo creator(sl, ammo)
  exit returning(sl)
end

script, create spaced quad wave, ammo, begin
  variable(sl)
  sl := create container(0,0)
  make obj(sl, 0)
  set counter(sl, 4)
  set update callback(sl, @spaced n wave update)
  set ammo creator(sl, ammo)
  exit returning(sl)
end

script, spaced n wave update, sl, begin
  if(just once(sl)) then(
    variable(i, c)
    c := get counter(sl)
    for(i, 0, c -- 1) do(
      put spaced enemy(run ammo creator(sl), i, c)
    )
  )
  set delete me(sl, true)
end

#-----------------------------------------------------------------------
# Putting Ships

script, put at player start, sl, begin
  set slice x(sl, slice width(sl) * -1)
  set slice y(sl, slice height(play zone) / 2)
  set force x(sl, 10)
end

script, put at obj, sl, obj, begin
  set slice edge x(sl, edge:center, slice edge x(obj, edge:center))
  set slice edge y(sl, edge:center, slice edge y(obj, edge:center))
end

script, put random enemy, sl, begin
  set slice edge x(sl, edge:left, slice width(play zone))
  variable(h)
  h := slice height(sl) / 2
  set slice y(sl, random(h, slice height(play zone) -- h))
end

script, put center enemy, sl, begin
  set slice edge x(sl, edge:left, slice width(play zone))
  set slice edge y(sl, edge:center, slice height(play zone) / 2)
end

script, put spaced enemy, sl, i, total, begin
  set slice edge x(sl, edge:left, slice width(play zone))
  variable(n)
  n := slice height(play zone) / (total + 1)
  set slice edge y(sl, edge:center, n + n * i)
end

#-----------------------------------------------------------------------
# Iteration

script, for each obj, callback, begin
  variable(sl, next)
  sl := first child(play zone)
  while(sl) do(
    next := next sibling(sl)
    if(get slice lookup(sl) == sli:obj) then(
      run script by id(callback, sl)
    )
    sl := next
  )
end

script, check for bullets hitting enemies, begin
  variable(b, next b)
  b := first child(play zone)
  while(b) do(
    next b := next sibling(b)
    if(get slice lookup(b) == sli:obj) then(
      if(get is bullet(b)) then(
        variable(e, next e)
        e := first child(play zone)
        while(e) do(
          next e := next sibling(e)
          if(get slice lookup(e) == sli:obj) then(
            if(get is enemy(e)) then(
              if(slice collide(b, e)) then(
                bullet collides with enemy(b, e)
              )
            )
          )
          e := next e
        )
      )
    )
    b := next b
  )
end

script, check for enemies hitting player, begin
  variable(e, next e)
  e := first child(play zone)
  while(e) do(
    next e := next sibling(e)
    if(get slice lookup(e) == sli:obj) then(
      if(get is enemy(e)) then(
        if(circle collide(player, e)) then(
          enemy collides with player(e, player)
        )
      )
    )
    e := next e
  )
end

#-----------------------------------------------------------------------
# Starfield

script, rev up starfield, begin
  variable(i)
  for(i, 0, slice width(play zone) / 2) do(
    update starfield
  )
end

script, update starfield, begin
  variable(star zone)
  star zone := lookup slice(sli:star zone, play zone)
  variable(star, next star)
  star := first child(star zone)
  while(star) do(
    next star := next sibling(star)
    set slice x(star, slice x(star) -- 1)
    set rect bg col(star, 16 + random(0, 6) * 16)
    if(slice x(star) < 0) then(free slice(star))
    star := next star
  )
  if(random percent(10)) then(
    star := create rect(1, 1)
    set slice lookup(star, sli:star)
    set parent(star, star zone)
    set slice x(star, slice width(star zone))
    set slice y(star, random(0, slice height(star zone)))
    set rect border(star, border: none)
  )
end

#-----------------------------------------------------------------------
# Force and friction

script, push force x, sl, amount, max=0, begin
  variable(v)
  v := get force x(sl)
  v += amount
  if(max == 0) then(
    max := get max force(sl)
  )
  if(v > max) then(v := max)
  if(v < max * -1) then(v := max * -1)
  set force x(sl, v)
end

script, push force y, sl, amount, max=0, begin
  variable(v)
  v := get force y(sl)
  v += amount
  if(max == 0) then(
    max := get max force(sl)
  )
  if(v > max) then(v := max)
  if(v < max * -1) then(v := max * -1)
  set force y(sl, v)
end

script, apply force, sl, begin
  set slice x(sl, slice x(sl) + get force x(sl))
  set slice y(sl, slice y(sl) + get force y(sl))
end

script, apply friction, sl, begin
  variable(v)
  v := get force x(sl)
  v -= sign(v)
  set force x(sl, v)
  v := get force y(sl)
  v -= sign(v)
  set force y(sl, v)
end

script, keep obj onscreen, sl, begin
  if(slice edge x(sl, edge:left) < 0) then(
    set force x(sl, large(get force x(sl), 0))
  )
  if(slice edge x(sl, edge:right) > slice width(play zone)) then(
    set force x(sl, small(get force x(sl), 0))
  )
  if(slice edge y(sl, edge:top) < 0) then(
    set force y(sl, large(get force y(sl), 0))
  )
  if(slice edge y(sl, edge:bottom) > slice height(play zone)) then(
    set force y(sl, small(get force y(sl), 0))
  )
end

script, keep onscreen vertically, sl, begin
  if(slice edge y(sl, edge:top) < 0) then(
    set force y(sl, large(get force y(sl), 0))
  )
  if(slice edge y(sl, edge:bottom) > slice height(play zone)) then(
    set force y(sl, small(get force y(sl), 0))
  )
end

script, check for escape, sl, begin
  if(slice edge x(sl, edge:right) < 0) then(
    escapes += 1
    show string(string sprintf(0, $1="Escapes:%d", escapes))
    set delete me(sl, true)
  )
end

script, remove if offscreen, sl, begin
  if(not(slice collide(sl, play zone))) then(
    set delete me(sl, true)
  )
end

#-----------------------------------------------------------------------
# Animation

script, loop frame, sl, first, last, tickmod=1, begin
  variable(fr)
  if(tickmod > 0 && (ticks ,mod, tickmod) <> 0) then(exit script)
  fr := get sprite frame(sl)
  fr += 1
  if(fr < first) then(fr := first)
  if(fr > last) then(fr := first)
  set sprite frame(sl, fr)
end

script, frame to last, sl, first, last, tickmod=1, begin
  # Returns true if the frame has reached the last
  if(tickmod > 0 && ticks ,mod, tickmod <> 0) then(exit script)
  variable(fr)
  fr := get sprite frame(sl)
  fr += 1
  if(fr < first) then(fr := first)
  if(fr > last) then(fr := last)
  set sprite frame(sl, fr)
  exit returning(fr == last)
end

script, animate tilt and afterburner, sl, begin
  animate tilt(sl)
  animate afterburner(sl)
end

script, animate tilt, sl, begin
  variable(fy)
  fy := get force y(sl)
  variable(frame)
  frame := 0
  if(fy > 0) then(frame := 1)
  if(fy < 0) then(frame := 2)
  set sprite frame(sl, frame)
end

script, animate afterburner, sl, begin
  variable(fx, a, max)
  fx := get force x(sl)
  a := lookup slice(sli:afterburn, sl)
  if(a) then(
    if(fx <= 0) then(
      free slice(a)
      a := 0
    )
  )else(
    if(fx > 0) then(
      a := create afterburn(sl)
    )
  )
  if(a) then(
    max := get max force(sl)
    if(fx > max / 2) then(
      loop frame(a, 2, 5)
    )else(
      loop frame(a, 0, 1)
    )
  )
end

script, create afterburn, sl, begin
  variable(a)
  a := load walkabout sprite(9)
  set slice lookup(a, sli:afterburn)
  center slice(a)
  set parent(a, sl)
  set slice x(a, slice width(sl) * -1)
end

script, add walkabout pic, sl, pic id, begin
  variable(pic)
  pic := load walkabout sprite(pic id)
  set parent(pic, sl)
  set slice lookup(pic, sli:pic)
  center slice(pic)
end

script, get pic, sl, begin
  exit returning(lookup slice(sli:pic, sl))
end

script, add text overlay, sl, str id, begin
  variable(txt)
  txt := create text
  set slice text(txt, str id)
  set outline(txt, true)
  set parent(txt, sl)
  set slice lookup(txt, sli:text)
  center slice(txt)
end

script, get text overlay, sl, begin
  exit returning(lookup slice(sli:text, sl))
end

#-----------------------------------------------------------------------
# Metadata

# Primary meta slice sli:meta
# x              = force x
# y              = force y
# height         = max force
# width          = animation callback
# top padding    = update callback
# right padding  = age
# bottom padding = counter
# left padding   = ammo creator
# extra 0        = metadata bits
# extra 1        =
# extra 2        =
#
# Secondary meta slice (first child)
# x              = attack callback
# y              = bullet counter
# height         = collision callback
# width          = death callback
# top padding    = damage
# right padding  = hp
# bottom padding = 
# left padding   = 
# extra 0        = 
# extra 1        =
# extra 2        =


script, make enemy, sl, maxforce=2, begin
  make obj(sl, maxforce)
  set is enemy(sl, true)
  if(slice is sprite(sl)) then(
    horiz flip sprite(sl, true)
  )
  set collision callback(sl, @default enemy collide)
  set death callback(sl, @default enemy death callback)
  exit returning(sl)
end

script, make bullet, sl, maxforce=2, begin
  make obj(sl, maxforce)
  set is bullet(sl, true)
  set collision callback(sl, @default bullet collide)
  exit returning(sl)
end

script, make obj, sl, maxforce=2, begin
  add meta(sl)
  set slice lookup(sl, sli:obj)
  set parent(sl, play zone)
  set horiz anchor(sl, edge:center)
  set vert anchor(sl, edge:center)
  set max force(sl, maxforce)
  set damage(sl, 1)
  set hp(sl, 1)
end

script, add meta, sl, begin
  variable(m)
  m := create container(0, 0)
  set slice lookup(m, sli:meta)
  set parent(m, sl)
  set slice visible(m, false)
  variable(m2)
  m2 := create container(0, 0)
  set parent(m2, m)
end

script, set force x, sl, v, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  set slice x(m, v)
end

script, get force x, sl, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  exit returning(slice x(m))
end

script, set force y, sl, v, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  set slice y(m, v)
end

script, get force y, sl, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  exit returning(slice y(m))
end

script, set max force, sl, v, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  set slice height(m, v)
end

script, get max force, sl, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  exit returning(slice height(m))
end

script, set animation callback, sl, callback, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  set slice width(m, callback)
end

script, run animation callback, sl, begin
  variable(m, cb)
  m := lookup slice(sli:meta, sl)
  cb := slice width(m)
  if(cb) then(
    run script by id(cb, sl)
  )
end

script, set update callback, sl, callback, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  set top padding(m, callback)
end

script, run update callback, sl, begin
  variable(m, cb)
  m := lookup slice(sli:meta, sl)
  cb := get top padding(m)
  if(cb) then(
    run script by id(cb, sl)
  )
end

script, set age, sl, v, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  set right padding(m, v)
end

script, get age, sl, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  exit returning(get right padding(m))
end

script, set counter, sl, v, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  set bottom padding(m, v)
end

script, get counter, sl, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  exit returning(get bottom padding(m))
end

script, set ammo creator, sl, v, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  set left padding(m, v)
end

script, run ammo creator, sl, begin
  variable(m, cb)
  m := lookup slice(sli:meta, sl)
  cb := get left padding(m)
  if(cb) then(
    exit returning(run script by id(cb, sl))
  )
  exit returning(0)
end

script, set attack callback, sl, v, begin
  variable(m)
  m := first child(lookup slice(sli:meta, sl))
  set slice x(m, v)
end

script, run attack callback, sl, begin
  variable(m, cb)
  m := first child(lookup slice(sli:meta, sl))
  cb := slice x(m)
  if(cb) then(
    exit returning(run script by id(cb, sl))
  )
  exit returning(0)
end

script, set bullet counter, sl, v, begin
  variable(m)
  m := first child(lookup slice(sli:meta, sl))
  set slice y(m, v)
end

script, get bullet counter, sl, begin
  variable(m)
  m := first child(lookup slice(sli:meta, sl))
  exit returning(slice y(m))
end

script, set collision callback, sl, v, begin
  variable(m)
  m := first child(lookup slice(sli:meta, sl))
  set slice height(m, v)
end

script, run collision callback, sl, other, begin
  variable(m, cb)
  m := first child(lookup slice(sli:meta, sl))
  cb := slice height(m)
  if(cb) then(
    exit returning(run script by id(cb, sl, other))
  )
  exit returning(0)
end

script, set death callback, sl, v, begin
  variable(m)
  m := first child(lookup slice(sli:meta, sl))
  set slice width(m, v)
end

script, run death callback, sl, begin
  variable(m, cb)
  m := first child(lookup slice(sli:meta, sl))
  cb := slice width(m)
  if(cb) then(
    exit returning(run script by id(cb, sl))
  )
  exit returning(0)
end

script, set damage, sl, v, begin
  variable(m)
  m := first child(lookup slice(sli:meta, sl))
  set top padding(m, v)
end

script, get damage, sl, begin
  variable(m)
  m := first child(lookup slice(sli:meta, sl))
  exit returning(get top padding(m))
end

script, set hp, sl, v, begin
  variable(m)
  m := first child(lookup slice(sli:meta, sl))
  set right padding(m, v)
end

script, get hp, sl, begin
  variable(m)
  m := first child(lookup slice(sli:meta, sl))
  exit returning(get right padding(m))
end


#-----------------------------------------------------------------------
# Metadata util

script, increment age, sl, begin
  variable(m)
  m := lookup slice(sli:meta, sl)
  set right padding(m, get right padding(m) + 1)
end

script, increment counter, sl, begin
  variable(m, v)
  m := lookup slice(sli:meta, sl)
  v := get bottom padding(m) + 1
  set bottom padding(m, v)
  exit returning(v)
end

script, once every, sl, ticknum, begin
  exit returning(get age(sl) ,mod, ticknum == 0)
end

script, alternate, sl, ticknum, begin
  exit returning(get age(sl) / ticknum ,mod, 2)
end

script, just once, sl, begin
  if(not(get just once(sl))) then(
    set just once(sl, true)
    exit returning(true)
  )
  exit returning(false)
end

#-----------------------------------------------------------------------
# Metadata bits

script, get exbit, sl, bit, begin
  variable(m, n)
  m := lookup slice(sli:meta, sl)
  n := get slice extra(m, extra 0)
  if(n,and,2^bit) then(exit returning(true))
  exit returning(false)
end

script, set exbit, sl, bit, value=1, begin
  variable(m, n)
  m := lookup slice(sli:meta, sl)
  n := get slice extra(m, extra 0)
  if(value)
    then(n := n, or, 2^bit)
    else(n := n, and, (-1, xor, 2^bit))
  set slice extra(m, extra 0, n)
end

script, get delete me, sl ( exit returning(get exbit(sl, 1)) )
script, set delete me, sl, value ( set exbit(sl, 1, value) )
script, get is enemy, sl ( exit returning(get exbit(sl, 2)) )
script, set is enemy, sl, value ( set exbit(sl, 2, value) )
script, get is bullet, sl ( exit returning(get exbit(sl, 3)) )
script, set is bullet, sl, value ( set exbit(sl, 3, value) )
script, get just once, sl ( exit returning(get exbit(sl, 4)) )
script, set just once, sl, value ( set exbit(sl, 4, value) )  

#-----------------------------------------------------------------------
# Util

script, random percent, p, begin
  exit returning(random(1, 100) <= p)
end

script, small, v1, v2, begin
  if(v1 >> v2) then(exit returning(v2))
  exit returning(v1)
end

script, large, v1, v2, begin
  if(v1 << v2) then(exit returning(v2))
  exit returning(v1)
end

script, circle collide, s1, s2, begin
  if(slice collide(s1, s2)) then(
    variable(r1, r2)
    r1 := slice width(s1) / 2
    r2 := slice width(s2) / 2
    variable(a, b, c)
    a := slice edge x(s1, edge:center) -- slice edge x(s2, edge:center)
    b := slice edge y(s1, edge:center) -- slice edge y(s2, edge:center)
    c := r1 + r2
    if(a^2 + b^2 < c^2) then(
      exit returning(true)
    )
  )
  exit returning(false)
end