And yet another Tower Defense — File Browser

AndyetanotherTowerDefensev1.0 / AndyetanotherTowerDefensev1.0 / tower / tower.mrc

tower.mrc — 109.64 KB — Download this file

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
;        ##  #  #  ##         _____________________________
;        # \/ \/ \/ #         | ugliest ascii drawing ever |
;        \_________/   ~~~~  / ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
;         |       |    |**| /
;         |  |¯|  |     \/
;         |  | |  |    /||\
;         |   ¯   |   / || \
;         |       |     /\
;         |       |    /  \
; ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
;
;  And yet another tower defense game
; 
;
;   by visionz (charlotmartine at hotmail.com)
;
;   type //tower to start, or use menu
;
;
;  Copyright : 
;
;   This game, and all its original content is free
;   to use for whatever you feel like. Rip it, modify
;   it, claim it yours, change the credits; the only
;   thing I ask is that you have fun doing it! If you
;   would like to make a donation to support me, then
;   you must be pretty stupid :)
;
;   Thank you and have fun!
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; History
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; 28 july 2009 : First public release
;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Todo list
;
; - Implement "Score" system
; - Sort popups by zorder, change active popup zorder to highest
; - Implement save/continue
; - Add "Choose difficulty" somewhere (but where?)
;
; Known bugs
;
; - When selecting with mouse, if you go outside the window too fast while 
;   selecting, it will cause a drawrect error (doesnt cause any problem thought)
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

menu menubar,status {
  And yet another Tower defense 
  .Play : tower  
  .-
  .Uninstall 
  ..Unload it yourself, lazy ass : noop $time
}

alias tower {

  ; force game to quit if already running
  if ($1 == -f) { td.unload }

  if ($td.initGameEngine()) {
    td.start
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc : init various parameters for the game engine
alias -l td.initGameEngine {

  ; already running?
  if (!$hget(tower)) {

    hmake tower 1000

    hadd tower ticks 0

    ; clear the cache
    drawpic -c

    ; fps handling stuff
    hadd tower fps.last $td.ticks
    hadd tower fps.fps n/a
    hadd tower fps.count 0

    ; 1 = very easy
    td.setDifficulty 1

    hadd tower score 0

    ; various options
    hadd tower show.health 1
    hadd tower show.msg 1
    hadd tower show.region 0
    hadd tower show.zone 0
    hadd tower show.tidzone 0
    hadd tower show.range 1
    hadd tower show.tlink 0
    hadd tower opt.cleartool 0
    hadd tower opt.playsounds 1

    hadd tower debug 0

    td.setZoneSize 108 129
    td.setFont "courier new"
    td.setWhite $rgb(235,250,255)

    window -Bdfkop +fnt @tower 0 0 640 640

    window -hpf @tbuf 0 0 640 640
    window -hpf @toolset 0 0 400 400

    return 1
  }

  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: unload all the stuff, close windows
alias -l td.unload {

  unset %zone.w
  unset %zone.h
  unset %td.font
  unset %td.white
  unset %td.map_path
  unset %td.timerId

  .timerSpawnCreep off
  .timerTower off

  if ($hget(tower)) {
    hfree tower
  }

  window -c @tower
  window -c @tbuf
  window -c @toolset
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: initialise zones
; parm : width, height
alias td.setZoneSize {

  set %zone.w $1
  set %zone.h $2

  ; min width/height is 20
  if (%zone.w < 20) set %zone.w 20
  if (%zone.h < 20) set %zone.h 20

  ; compute reachable zones for each tower
  var %tid = $obj.getFirst(Tid)
  while (%tid) {

    td.computeRangeSections %tid

    %tid = $obj.getNext(tid)
  }

  ; update zone for creeps
  var %cid = $obj.getFirst(cid)
  while (%cid) {
    td.updateCidZone %cid
    %cid = $obj.getNext(cid)
  }
}

alias -l td.setFont set %td.font $1-
alias -l td.setWhite set %td.white $1

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: initialise stuff to start the level
; parm : level
alias -l td.initLevel {

  td.setPause 0
  td.setGameOver 0

  hadd tower game.level $1

  td.clearBuffer

  var %i = 1, %count = 0
  var %toolset = $replace($obj.getprop(map,$td.map).toolset,$chr(32),$null)

  while ($gettok(%toolset,%i,44)) {

    var %tool = $v1
    var %file = $obj.getProp(tower,%tool).img

    if ($isfile(%file)) {
      drawpic -c @toolset $calc(%count * 48) 0 0 0 48 48 %file
    }
    else {
      drawrect -rf @toolset $rgb(32,0,0) 1 $calc(%count * 48) 0 48 48
      drawtext -r @toolset %td.white %td.font 12 $calc(%count * 48 + 2) 2 No Img
    }

    obj.new tool %tool
    obj.setProp tool %tool pos %count 

    inc %count

    inc %i
  }

  hadd tower toolset.count %count

  ; loaddebug
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: 
; parms: win x y text
alias td.drawText {

  var %font = %td.font
  var %fontsize = 12
  var %color = %td.white
  var %bold = 0
  var %underline = 0
  var %parms 

  var %func, %id, %ctrl
  var %tok
  var %x = $2, %y = $3, %text = $replace($+($4-,{end}),$chr(32),$chr(160)), %chunk
  var %i = 1, %t = $numtok(%text,123)
  var %fx = 0, %fxid
  var %offx = 0, %offy = 0

  while (%i <= %t) {

    %tok = $gettok(%text,%i,123)   
    %ctrl = $gettok(%tok,1,125)
    %func = $gettok(%ctrl,1,58)
    %id = $gettok(%ctrl,2,58)

    if (%func == f) %font = $qt($replace(%id,$chr(160),$chr(32)))
    elseif (%func == s) %fontsize = %id
    elseif (%func == rgb) %color = $td.parseColor(%id)
    elseif (%func == fx) { %fx = $abs($calc(%fx - 1)) | %fxid = %id }
    elseif (%func == b) { %bold = $abs($calc(%bold - 1)) }
    elseif (%func == u) { %underline = $abs($calc(%underline - 1)) }

    %chunk = $gettok(%tok,2,125)
    if (%chunk) {

      if (%fx) {
        if (%fxid == 1) %offx = $calc(1 - $r(0,2))
        elseif (%fxid == 2) %offy = $calc(1 - $r(0,2))
      }
      else {
        %offx = 0
        %offy = 0
      }

      if (%bold) %parms = -nor
      else %parms = -nr

      drawtext %parms $1 %color %font %fontsize $calc(%x + %offx) $calc(%y + %offy) %chunk 

      var %w = $width(%chunk,$noqt(%font),%fontsize,%bold)

      if (%underline) {
        var %h = $height(%chunk,$noqt(%font),%fontsize)
        drawline -nr $1 %color 1 $calc(%x + %offx) $calc(%y + %offy + %h) $calc(%x + %offx + %w) $calc(%y + %offy + %h)
      }
      inc %x %w
    }

    inc %i
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: r, g, b
alias -l td.parseColor {
  tokenize 44 $+($1-,$chr(44),0,$chr(44),0)
  return $rgb($1,$2,$3)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 
alias -l td.start {

  window -a @tower

  td.jumpToScene main

  .timerTower 0 0 td.loop 
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: obj_id
; desc: select the tool used for building
alias -l td.selTool {

  hadd tower tool.sel $1
  hadd tower tool.w $obj.getProp(tower,$1).width
  hadd tower tool.h $obj.getProp(tower,$1).height
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: scene
alias td.jumpToScene {

  ; when we change scene, close all popups
  var %pop = $obj.getFirst(popup)
  while (%pop) {
    td.closePopup %pop
    %pop = $obj.getNext(popup)
  }

  ; initializes the scene
  td.initScene $1

  hadd tower scene $1
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: scene
alias td.initScene {

  if ($1 == main) {
    hdel tower mdownl
    hdel tower mdown

    td.initcredits

    if ($td.showPopup(menu,Menu,190,200,265,300,0)) {

      td.addPopupButton menu newgame  40 40 160 40  $td.gfx(newgame.bmp)  Start a new game
      td.addPopupButton menu continue 40 85 160 40  $td.gfx(continue.bmp) Continue a previous game
      td.addPopupButton menu options  40 130 160 40 $td.gfx(options.bmp)  Options
      td.addPopupButton menu quit     40 175 160 40 $td.gfx(quit.bmp)     Quit 
    }
  }
  elseif ($1 == pause) {

    if ($td.showPopup(pmenu,Pause menu,190,240,265,250,0)) {

      td.addPopupButton pmenu resume  40 40 160 40  $td.gfx(resume.bmp) Resume your game
      td.addPopupButton pmenu options 40 85 160 40 $td.gfx(options.bmp) Options 
      td.addPopupButton pmenu return  40 130 160 40 $td.gfx(quit.bmp) Return to main menu
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 
alias -l td.loop {

  var %f = $ticks

  if ($td.onMouseClick(1) || $td.onMouseUClick) {
    if ($td.hookPopup) {
      hadd tower mouse.sclick 0
      hadd tower mouse.uclick 0
    }
  }

  ; render the scene
  td.render $hget(tower, scene)  

  var %popuphook = $hget(tower,hook_popup)
  if (%popuphook) {

    var %x = $calc($mouse.x - $hget(tower,hook_offx)))
    var %y = $calc($mouse.y - $hget(tower,hook_offy)))

    if (%x > 600) %x = 600
    if (%y > 600) %y = 600
    if (%x < -100) %x = -100
    if (%y < -100) %y = -100

    obj.setProp popup %popuphook x %x
    obj.setProp popup %popuphook y %y
  }

  if ($td.onMouseUClick) hdel tower clickOn 
  hadd tower mouse.sclick 0
  hadd tower mouse.uclick 0

  ; fps stuff
  hinc tower fps.count
  if ($calc($td.ticks - $hget(Tower,fps.last)) >= 2000) {
    hadd tower fps.fps $calc($hget(tower,fps.count) / 2)
    hadd tower fps.count 0
    hadd tower fps.last $td.ticks
  }

  if (!$hget(tower,pause)) {
    var %d = $calc($ticks - %f)
    if (%d <= 0) %d = 12.4
    hinc tower ticks $calc(%d + 3)
  }

  if ($hget(tower,scene) == end) td.unload
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.update {

  var %gain = $hget(tower,self.gain)
  if (%gain > 0) {

    %cash = 1

    if (%gain >= 25) {
      %cash = $round($calc(%gain * 0.04),0)
    }

    hinc tower self.cash %cash
    hdec tower self.gain %cash
  }

  ; check if there's any creeps to spawn
  if ($hget(tower, spawnCreep.n) > 0) {

    ; it is time to spawn a new creep?
    if ($calc($td.ticks - $hget(tower, spawnCreep.last)) >= $obj.getProp(map,$td.map).SpawnInterval) {
      td.spawnCid $hget(tower,spawnCreep.objId) $hget(tower,spawncreep.rid)
      hdec tower spawnCreep.n
      hadd tower spawnCreep.last $td.ticks
    }
  }

  if (!$obj.count(cid) && !$hget(tower,spawnCreep.n)) {
    var %next = $td.getTimeBeforeNextLevel()
    if (%next <= 0) {
      td.nextLevel
    }
  }

  ; update towers being build
  var %tid = $obj.getFirst(buildingTid)
  while (%tid) {
    td.updateBuildProgress %tid
    %tid = $obj.getNext(buildingTid)
  }

  ; check for towers attack
  var %tid = $obj.getFirst(readytid)
  while (%tid) {

    ; check if it's time to attack
    var %lastAttack = $calc($td.ticks - $obj.tid(%tid).lastAttack)
    if (%lastAttack >= $calc($obj.tid(%tid).speed + $obj.tid(%tid).spe_mod)) {

      if ($td.checkForTarget(%tid) > 0) {
        obj.setProp tid %tid lastAttack $td.ticks
      }
      else {
        ; if the tower didnt attack for 2 seconds, put it to sleep
        if (%lastAttack > 2000) {
          td.goToSleep %tid
        }
      }
    }

    if ($hget(tower,debug)) {
      ; draw green rect around tower to show that it is awake
      drawrect -nr @tower $rgb(0,255,0) 1 $obj.tid(%tid).screenX $obj.tid(%tid).screenY $obj.tid(%tid).screenW $obj.tid(%tid).screenH
    }

    %tid = $obj.getNext(readytid)
  }

  var %tid = $obj.getFirst(seltid)
  while (%tid) {
    if ($obj.tid(%tid).selected) {
      td.drawTowerSelection %tid
      if ($hget(tower,show.tlink)) {
        td.renderTowerLinks %tid
      }
    }
    %tid = $obj.getNext(seltid)
  }

  ; unselect tool when right-clicking
  if ($mouse.key & 16) hdel tower tool.sel

  if ($td.onMouseClick(1)) {

    ; check if we're in build zone
    if ($td.inBuildZone()) {

      var %tool = $hget(tower,tool.sel)     
      if (%tool) {
        var %tid = $td.buildTower($hget(tower,tool.sel), $toTile($mouse.x), $toTile($mouse.y))
        if (%tid) {

          td.updateTowerBuffer 

          if ($hget(tower,opt.cleartool)) {

            ; keep the tool selected if we hold ctrl while building
            if (!$and($mouse.key,2)) {
              hdel tower tool.sel
            }
          }
        }
      }
      else {

        if (!$and($mouse.key,2)) td.clearSelection

        var %id = $td.getIdByCoords(tid,$mouse.x,$mouse.y)
        if (%id) {

          if ($obj.tid(%id).selected) {
            if ($and($mouse.key,2)) { 
              td.unselectTower %id 
              td.populateComboList
            }
          }
          else {
            td.selectTower %id
            td.populateComboList
          }

          if ($obj.count(seltid) > 0) {
            td.showPopupSelTid
          }
        }
        else {
          hadd tower sel.active 1
          hadd tower sel.rect.x $mouse.x
          hadd tower sel.rect.y $mouse.y
        }
      }
    }
  }
  elseif ($td.onMouseUClick) {

    if ($hget(tower,sel.active)) {
      td.selectAllTowersInMouseRect
      hadd tower sel.active 0
    }
  }

  if ($hget(tower,sel.active)) {

    var %x1 = $td.min($hget(tower,sel.rect.x),$mouse.x), %y1 = $td.min($hget(tower,sel.rect.y),$mouse.y)
    var %x2 = $td.max($hget(tower,sel.rect.x),$mouse.x), %y2 = $td.max($hget(tower,sel.rect.y),$mouse.y)

    drawrect -nfir @tower $rgb(255,0,0) 1 %x1 %y1 $calc(%x2 - %x1) $calc(%y2 - %y1)
    drawrect -nr @tower $rgb(0,0,0) 1 %x1 %y1 $calc(%x2 - %x1) $calc(%y2 - %y1)
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.selectAllTowersInMouseRect {

  var %x1 = $td.min($hget(tower,sel.rect.x),$mouse.x)
  var %y1 = $td.min($hget(tower,sel.rect.y),$mouse.y)
  var %x2 = $td.max($hget(tower,sel.rect.x),$mouse.x)
  var %y2 = $td.max($hget(tower,sel.rect.y),$mouse.y)

  var %tid = $obj.getFirst(Tid)
  while (%tid) {

    var %tidx = $obj.tid(%tid).centerX
    var %tidy = $obj.tid(%tid).centerY

    if ($inrect(%tidx,%tidy,%x1,%y1,$calc(%x2 - %x1),$calc(%y2 - %y1))) {
      td.selectTower %tid
    }

    %tid = $obj.getNext(tid)
  }
  if ($obj.count(seltid) > 0) {
    td.populateComboList
    td.showPopupSelTid
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.min {
  if ($1 < $2) return $1
  return $2
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.max {
  if ($1 > $2) return $1
  return $2
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tid
alias -l td.renderTowerLinks {

  var %link = $td.getLink($1)
  var %x1 = $obj.tid($1).centerX
  var %y1 = $obj.tid($1).centerY

  var %tid = $obj.getFirst(%link)
  while (%tid) {

    var %x2 = $obj.tid(%tid).centerX
    var %y2 = $obj.tid(%tid).centerY

    drawline -rn @tower $rgb(0,255,255) 1 %x1 %y1 %x2 %y2

    %tid = $obj.getNext(%link)
  }
}


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: popup_id callback_type callback_alias
alias -l td.setPopupCallback {

  obj.setProp popup $1 $+(callback_,$2) $3
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tid
; desc: 
alias -l td.selectTower {

  ; if not already selected
  if (!$obj.tid($1).selected) {

    obj.new seltid $1

    obj.setProp tid $1 selected 1
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tid
; desc: 
alias -l td.unselectTower {

  ; if selected
  if ($obj.tid($1).selected) {

    obj.delete seltid $1
    obj.setProp tid $1 selected 0

    if (!$obj.count(seltid)) {
      td.closePopup seltid
    } 
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: unselect all selected towers
alias -l td.clearSelection {

  var %id = $obj.getFirst(seltid)
  while (%id) {
    obj.setProp tid %id selected 0
    %id = $obj.getNext(seltid)
  }

  obj.clear seltid
  td.closePopup seltid
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.hookPopup {

  ; unhook the popup 
  if ($hget(tower,hook_popup)) {
    if ($td.onMouseUClick) {
      hadd tower hook_popup 0
    }
  }

  var %id = $obj.getFirst(popup)
  while (%id) {

    var %x = $obj.getProp(popup,%id).x
    var %y = $obj.getProp(popup,%id).y
    var %w = $obj.getProp(popup,%id).w
    var %h = $obj.getProp(popup,%id).h

    if ($inrect($mouse.x,$mouse.y,%x,%y,%w,%h)) {

      td.handlePopupMouse %id %x %y %w %h
      return 1
    }

    %id = $obj.getNext(popup)
  }

  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.handlePopupMouse {

  var %pop = $1
  var %x = $2, %y = $3, %w = $4, %h = $5

  var %type = $+(pctrl.,%pop)
  var %ctrl = $obj.getFirst(%type)
  while (%ctrl) {

    var %rx = $calc($obj.getProp(%type,%ctrl).x + %x + 8)
    var %ry = $calc($obj.getProp(%type,%ctrl).y + %y + 25)
    var %rw = $obj.getProp(%type,%ctrl).w
    var %rh = $obj.getProp(%type,%ctrl).h

    if ($inrect($mouse.x,$mouse.y,%rx,%ry,%rw,%rh)) {
      if ($td.onMouseClick(1)) hadd tower clickOn $+(%pop,.,%ctrl)
      elseif ($td.onMouseUClick) {
        if ($hget(tower,clickOn) == $+(%pop,.,%ctrl)) {
          if (%ctrl == xclose) {
            td.closePopup %pop
            hadd tower hook_popup 0
            hdel tower clickOn 
            return
          }
          else {

            var %ctype = $obj.getProp(%type,%ctrl).type
            if (%ctype == checkbox) {
              td.switchValue %ctrl
            }
            elseif (%ctype == checklist) {

              var %i = 0
              var %itemx = $calc(%rx + 4)
              var %items = $obj.getProp(%type,%ctrl).items

              var %id = $obj.getFirst(%items)
              while (%id) {

                var %itemy = $calc(%ry + %i * 13 + 20)
                if ($inrect($mouse.x,$mouse.y,%itemx,%itemy,$calc(%w - 20),12)) {
                  obj.setProp %type %ctrl selected %id
                  %id = 0
                }
                else {
                  %id = $obj.getNext(%items)
                }
                inc %i
              }

            }
            else {
              td.popupMouseClick %pop %ctrl
            }
          }
        }
      }

      %ctrl = 0
    }
    else {
      %ctrl = $obj.getNext(%type)
    }
  }
  if ($td.onMouseUClick) hdel tower clickOn 
  if ($td.onMouseClick(1)) {
    if (!$hget(tower,clickOn)) {  
      ; in title?
      if ($inrect($mouse.x,$mouse.y,%x,%y,%w,22)) {
        hadd tower hook_popup %pop
        hadd tower hook_offx $calc($mouse.x - %x)
        hadd tower hook_offy $calc($mouse.y - %y)
      }
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.popupMouseClick {

  if ($1 == seltid) td.onMouseClickSelTid $2
  elseif ($1 == menu) td.onMouseClickMenu $2
  elseif ($1 == pmenu) td.onMouseClickPause $2
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.onMouseClickPause {

  if ($1 == resume) {
    td.setPause 0
  }
  elseif ($1 == options) {
    td.showOptionsMenu
  }
  elseif ($1 == return) {
    td.jumpToScene main
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.onMouseClickMenu {

  ; click on New game
  if ($1 == newgame) {
    if ($td.loadmap(test)) {
      td.initLevel 0
      td.jumpToScene game
    }
  }

  ; click on Continue
  if ($1 == continue) {
    echo -a continue
  }

  ; click on Options
  if ($1 == options) {
    td.showOptionsMenu
  }

  ; click on Quit
  if ($1 == quit) {
    td.jumpToScene end
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.onMouseClickSelTid {

  ; click on sell
  if ($1 == sell) {

    var %id = $obj.getFirst(seltid)
    while (%id) {
      td.sellTower %id
      %id = $obj.getNext(seltid)
    }
    hadd tower hook_popup 0
  }

  ; awake tower
  if ($1 == Awake) {
    var %id = $obj.getFirst(seltid)
    while (%id) {
      td.awakeTower %id
      %id = $obj.getnext(seltid)
    }
  }

  ; click on upgrade
  if ($1 == upgrade) {

    if ($obj.count(seltid) > 1) {
      var %combosel = $obj.getProp(pctrl.seltid,combos).selected
      if (%combosel) { 
        if ($td.upgradeSelTidToCombo(%combosel)) {
          td.clearSelection
          hadd tower hook_popup 0
        }
      }

    }
    else { 
      var %id = $obj.getFirst(seltid)
      if (%id) {
        if ($td.upgradeTid(%id)) {
          td.clearSelection
          hadd tower hook_popup 0
        }
      }
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: tid, cid
alias -l td.attackCreep {

  var %tid = $1
  var %cid = $2

  var %cx = $obj.cid(%cid).CenterX
  var %cy = $obj.cid(%cid).CenterY
  var %BeamSize = $calc($obj.tid(%tid).level + 1)

  drawline -rn @tower %td.white %BeamSize $obj.tid(%tid).centerX $obj.tid(%tid).centerY %cx %cy

  var %att_min = $obj.tid(%tid).att_min
  var %att_max = $obj.tid(%tid).att_max
  var %att = $calc($r(%att_min, %att_max) + $obj.tid(%tid).att_mod)

  var %hp_left = $td.damageCreep(%tid,%cid,%att)
  if (%hp_left > 0) {

    ; if the creep is not dead, check to see if we need to apply an effect
    var %on_hit = $obj.tid(%tid).on_hit
    if (%on_hit) {
      if ($td.addEffectOnCid(%tid,%on_hit,%cid)) {
        td.showFloatingText -fs %cx $calc(%cy - 16) 500 0 $obj.getProp(effect,%on_hit).name
      }
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tid cid att
alias  td.damageCreep {

  var %tid = $1
  var %cid = $2
  var %att = $calc($3)

  if (%att != 0) {

    var %hp = $obj.cid(%cid).hp

    dec %hp %att

    if (%hp < 0) %hp = 0

    obj.setProp cid %cid hp %hp

    if (%hp <= 0) {

      var %cx = $obj.cid(%cid).CenterX
      var %cy = $obj.cid(%cid).CenterY

      if ($td.triggerEvent(onCreepKill,%cid,%tid)) {

        var %wav = $obj.cid(%cid).sfxdie
        if (%wav) td.playSound $td.path(%wav)

        ; the creep is dead! long live the tower!
        var %gold = $td.addDiffBonus($obj.cid(%cid).gold)
        if (%gold > 0) {
          td.addGain %gold
        }

        ; update the kill count for the tower
        obj.incProp tid %tid killCount 1

        if ($istok(15 40 70 115 150 250,$obj.tid(%tid).killcount,32)) {
          td.addVeteranLevel %tid
          td.showFloatingText -f $obj.tid(%tid).screenX $obj.tid(%tid).screenY 1500 0 Level up!
        }

        td.showFloatingText -fs %cx %cy 500 0 $td.goldToText(%Gold)

        td.removeCreep %cid
      }
    }
    return %hp
  }
  return 1
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms : gold
alias -l td.addDiffBonus {

  var %per = $calc(($hget(tower,game.diff) - 2.5) / 2)
  return $ceil($calc($1 + $1 * %per))
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms : 1-6 (1 is very easy, 6 is impossible)
alias -l td.setDifficulty {

  ; very easy = 2.2
  ; easy = 2.4
  ; medium = 2.6
  ; hard = 2.8
  ; very hard = 3
  ; impossible = 3.5

  var %diff = $calc($gettok(2.2 2.4 2.6 2.8 3 3.5,$1,32))

  if (%diff == 0) %diff = 2.2

  hadd tower game.diff %diff
  hadd tower game.dlvl $1
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: tid
alias -l td.addVeteranLevel {

  obj.incProp tid $1 Level 1

  var %lvl = $obj.tid($1).level

  ; add level up bonuses
  obj.incProp tid $1 att_mod $calc(%lvl * 5)
  obj.incProp tid $1 spe_mod $calc(%lvl * -2)

  ; check if we're at the last level
  if (%lvl == 6) {

  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: tid, effectid, cid
alias -l td.addEffectOnCid {

  var %tid = $1
  var %effect = $2
  var %cid = $3

  if (!$obj.cid(%cid).on_effect) {

    var %duration = $calc($obj.getProp(effect,%effect).duration)
    if (%duration > 0) {

      obj.incProp cid %cid damage_per_second $calc($obj.getProp(effect,%effect).damage_per_second)
      obj.incProp cid %cid spe_mod $calc($obj.getProp(effect,%effect).on_hit_speed_mod)
      obj.setProp cid %cid on_effect %tid

      inc %td.timerId
      .timerCidEff $+ %td.timerid -m 1 %duration td.removeEffectOnCid %tid %effect %cid

      return 1
    }
  }
  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: tid, effectid, cid
alias -l td.removeEffectOnCid {

  var %tid = $1
  var %effect = $2
  var %cid = $3

  if ($obj.cid(%cid).exists) {

    obj.decProp cid %cid spe_mod %spe_mod $obj.getProp(effect,%effect).on_hit_speed_mod
    obj.decProp cid %cid damage_per_second $obj.getProp(effect,%effect).damage_per_second

    obj.setProp cid %cid on_effect 0
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: cid
; desc: remove the specified creep of the field
alias -l td.removeCreep {

  ; remove the creep from the region
  obj.delete $obj.cid($1).zone $1

  ; free up memory
  obj.delete cid $1

  ; if there is no more creeps, set the timer for the next level
  if (!$obj.count(cid) && !$hget(tower, spawnCreep.n)) {

    td.goToNextLevel
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.goToNextLevel {

  ; next level will start in x seconds
  hadd tower game.nextlevel $calc($td.ticks + $obj.getProp(map,$td.map).timeBetweenLevel) 

  ; at the end of the round, put all towers to sleep
  var %tid = $obj.getFirst(tid)
  while (%tid) {
    if ($obj.tid(%tid).complete) {
      td.goToSleep %tid
    }
    %tid = $obj.getNext(tid)
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: tid
; desc: Remove a tower
alias -l td.removeTid {

  ; remove links to other towers
  var %tid = $obj.getFirst(tid)
  while (%tid) {
    td.unlinkTower $1 %tid
    %tid = $obj.getNext(tid)
  }

  td.unselectTower $1

  ; update collision buffer
  td.setTileState $obj.tid($1).x $obj.tid($1).y $obj.tid($1).w $obj.tid($1).h 0

  ; remove the tower from the ready list
  obj.delete readytid $1

  ; remove the tower from the building list
  obj.delete buildingtid $1

  ; remove the tower from the sleeplist of the zones
  var %i = 1
  var %zonelist = $obj.tid($1).zonelist
  while ($gettok(%zonelist,%i,32)) {
    obj.delete $+($v1,.sleep) $1
    inc %i
  }  

  ; free memory
  obj.delete tid $1

  td.updateTowerBuffer 
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: tid
; desc: Sell the specified tower
alias -l td.sellTower {

  var %refund = $td.getRefundAmount($1)

  if (%refund > 0) {
    td.addGain %refund
  }

  td.showFloatingText -fs $calc($obj.tid($1).screenX + $obj.tid($1).screenW / 2))  $obj.tid($1).screenY 500 0 $td.goldToText(%refund)

  td.removeTid $1
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tid
alias -l td.getRefundAmount {

  var %sell = $obj.tid($1).sell
  var %value = $obj.tid($1).value

  return $int($calc(%value * %sell))
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: obj_type x y
; desc: return id of a specified object type by screen coordinates
alias -l td.getIdByCoords {

  var %id = $obj.getFirst($1)
  while (%id) {
    if ($inrect($2,$3,$obj.getProp($1,%id).ScreenX,$obj.getProp($1,%id).ScreenY,$obj.getProp($1,%id).ScreenW,$obj.getProp($1,%id).ScreenH)) {
      return %id
    }
    %id = $obj.getNext($1)
  }
  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.playSound {

  if ($1-) {
    if ($hget(tower,opt.playsounds)) {
      splay -w $1-
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: obj_id region
; desc: order a creep to walk to a specified region
alias td.WalkCreepTo {

  var %curreg = $obj.cid($1).walkTo

  var %dx = $calc($obj.getProp(region,$2).CenterX - $obj.cid($1).CenterX)
  var %dy = $calc($obj.getProp(region,$2).CenterY - $obj.cid($1).CenterY)

  obj.setProp cid $1 imgdx $calc((1 + %dx / $abs(%dx)) * $obj.cid($1).imgnb)

  if ($abs(%dy) < $obj.cid($1).h) obj.setProp cid $1 imgdy 1
  else obj.setProp cid $1 imgdy $calc(1 + %dy / $abs(%dy))

  obj.setProp cid $1 walkTo $2
  obj.setProp cid $1 lastWalk $td.ticks
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: tid
; desc: check if it needs to attack target
alias -l td.checkForTarget {

  var %tid = $1
  var %count = 0

  if ($obj.count(cid) > 0) {

    if ($obj.tid(%tid).sleeping == 0) {

      var %i = 1
      var %zonelist = $obj.tid(%tid).zonelist
      var %target = $obj.tid(%tid).maxTarget
      var %att_type = $obj.tid(%tid).target

      while ($gettok(%zonelist,%i,32)) {

        var %zone = $v1

        var %cid = $obj.getFirst(%zone)
        while (%cid) {

          var %type = $obj.cid(%cid).type

          if ($pos(%att_type,$+(:,%type,:)) > 0) {

            if ($td.isInRange(%cid,%tid)) {
              td.attackCreep %tid %cid
              inc %count
              dec %target
            }

            if (%target <= 0) {
              return %count  
            }
          }
          else {

            ; tower can't attack that kind of creep (cant attack air, or ground...), so remove it from the list, and 
            ; remove don't put it in sleeplist of any zone, since it will never be able to attack any creep for this level
            td.gotoSleep %tid 0
            return 0
          }

          %cid = $obj.getNext(%zone)
        }

        inc %i
      }  
    }
  }

  return %count
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: cid, tid
; desc: return 1 if cid is in tower range of tid
alias -l td.isInRange {

  var %range = $calc($obj.tid($2).range + $obj.tid($2).ran_mod)
  return $inellipse($obj.cid($1).centerX,$obj.cid($1).centerY, $calc($obj.tid($2).centerX - %range), $calc($obj.tid($2).centerY - %range), $calc(%range * 2), $calc(%range * 2))
}

alias -l cid.getCenterX return $calc($obj.cid($1).x + ($obj.cid($1).w / 2))
alias -l cid.getCenterY return $calc($obj.cid($1).y + ($obj.cid($1).h / 2))

alias -l tid.getCenterX return $calc($obj.tid($1).screenX + ($obj.tid($1).screenW / 2))
alias -l tid.getCenterY return $calc($obj.tid($1).screenY + ($obj.tid($1).screenH / 2))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: (0 or 1)
alias -l td.setPause {

  if ($1) {
    if (!$2) {
      hadd tower oldscene $hget(tower,scene)
      td.jumpToScene pause
    }
    td.pauseCidEffTimer pause
    hadd tower pause 1
    hadd tower blockBuilding 1
  } 
  else {
    if (!$2) {
      var %oldscene = $hget(tower,oldscene)
      if (%oldscene) {
        td.jumpToScene $hget(tower,oldscene)
      }
    }
    td.pauseCidEffTimer resume
    hadd tower pause 0
    hadd tower blockBuilding 0
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: pause or resume
alias -l td.pauseCidEffTimer {

  var %switch = -p
  if ($1 == resume) %switch = -r

  var %i = $timer(0)

  while (%i) {
    var %name = $timer(%i)
    if ($left(%name,6) == cideff) {
      .timer $+ %name %switch
    }
    dec %i
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: (0 or 1)
alias -l td.setGameOver {

  if ($1) {
    hadd tower gameOver 1
    td.setPause 1 x
    td.jumptoScene gameover
  } 
  else {
    hadd tower gameOver 0
    td.setPause 0 x
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 
alias -l td.isGameOver {
  return $hget(tower, gameOver)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: scene 
alias -l td.render {

  ; clear window
  drawrect -nrf @tower 0 1 0 0 640 640

  if ($1 == main) td.renderMain
  elseif ($1 == game) td.renderGame
  elseif ($1 == pause) td.renderPause 
  elseif ($1 == gameover) td.renderGameOver
  elseif ($1 == win) td.renderWin

  var %tooltip = $hget(tower,tooltip)
  if (%tooltip) {
    td.showTooltip %tooltip
    hdel tower tooltip
  }

  ; refresh!
  drawdot @tower
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.renderMain {

  if ($calc($ticks - $hget(Tower,mdownl)) >= 25) {
    hinc tower mdown
    hadd tower mdownl $ticks
  }

  var %i = 1, %r
  var %t = $hget(tower,mdown), %f = %t
  if (%t > 53) %t = 53
  var %cx = 466
  var %cy = 70
  var %x1 = $calc(%cx + ($cos(0) * 147))
  var %y1 = $calc(%cy + ($sin(0) * 35))

  while (%i <= %t) {

    %r = $calc(%i / 40 * (3 * $pi))

    var %x2 = $calc(%cx + $cos(%r) * (147 + %i / 2))
    var %y2 = $calc(%cy + $sin(%r) * (35 + %i))

    drawline -rn @tower %td.white 1 %x1 %y1 %x2 %y2

    %x1 = %x2
    %y1 = %y2      

    inc %i
  }

  td.renderPopups

  td.drawtext @tower 30 40 {rgb:255,255,255}{f:courier new}{s:30}And yet another {rgb:196}{s:38}Tower Defense

  var %id = $obj.getFirst(cred)
  while (%id) {
    if (%f >= $obj.getProp(cred,%id).go_at) {
      var %vel = $obj.getProp(cred,%id).vel
      inc %vel
      if (%vel > 14) %vel = 14
      var %y = $obj.getProp(cred,%id).y
      inc %y %vel
      if (%y >= $calc(640 - 25)) { 
        %y = $calc(640 - 25)
        %vel = $calc(0 - %vel / 1.1)
        if ($abs(%vel) < 1) {
          %vel = 0
          %y = $calc(640 - 25)
        }
      }

      obj.setProp cred %id vel %vel
      obj.setProp cred %id y %y
      var %r = $abs($calc(255 * $sin($calc((%f + %id) / 12))))
      var %x = $obj.getProp(cred,%id).x
      drawtext -nr @tower $rgb(255,$calc(255 - %r),$calc(255 - %r)) %td.font 22 %x %y $obj.getProp(cred,%id).val
      if ($inrect($mouse.x,$mouse.y,%x,$calc(%y - 2),14,24)) {
        obj.setProp cred %id vel -20
      }
    }
    %id = $obj.getNext(cred)
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: shows options menu
alias td.showOptionsMenu {

  if ($td.showPopup(options,Options,420,510,200,100,1)) {

    td.addPopupCheckbox options show.health 0 0 200 12 Show creeps health bar
    td.addPopupCheckbox options show.msg 0 13 200 12 Show floating messages
    td.addPopupCheckbox options show.range 0 26 200 12 Show range
    td.addPopupCheckbox options opt.cleartool 0 39 200 12 Clear tool after build
    td.addPopupCheckbox options opt.playsounds 0 52 200 12 Play sounds

    ; td.addPopupCheckbox options show.region 0 26 200 12 Show regions
    ; td.addPopupCheckbox options show.zone 0 39 200 12 Show zones
    ; td.addPopupCheckbox options show.tidzone 0 52 200 12 Show tower's zone
    ; td.addPopupCheckbox options show.tlink 0 91 200 12 Show tower's link
  }
}

alias -l td.renderWin {

  drawtext -nor @tower %td.white %td.font 48 55 100 You are teh winner!
  drawtext -nor @tower $rgb(255,180,0) webdings 256 175 240 %

  if ($inrect($mouse.x,$mouse.y,585,615,100,20)) {
    drawtext -nor @tower 255 %td.font 16 585 615 Menu
    if ($td.onmouseclick(1)) {
      td.jumptoScene main
    }
  }
  else {
    drawtext -nor @tower %td.white %td.font 16 585 615 Menu
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.switchValue hadd tower $1 $abs($calc($hget(tower,$1) - 1))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc:
alias td.initCredits {

  var %me = $replace(created.by.visionz,.,$chr(160))

  var %i = 1, %t = $len(%me)
  while (%i <= %t) {

    obj.new cred %i 
    obj.setprop cred %i val $mid(%me,%i,1)
    obj.setProp cred %i vel 0
    obj.setProp cred %i x $calc(440 + %i * 10)
    obj.setProp cred %i y -10
    obj.setProp cred %i go_at $calc(80 + %i * 4)
    inc %i
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc:
; parms: id title x y w h showclose
alias td.showPopup {

  if ($obj.exists(popup,$1)) {
    td.closePopup $1
  }

  obj.clear $+(pctrl.,$1)

  obj.new popup $1

  ; check if a previous position was saved
  var %x = $hget(tower,$+(__save.,$1,.x))
  var %y = $hget(tower,$+(__save.,$1,.y))

  if (!%x) %x = $3
  if (!%y) %y = $4

  obj.setProp popup $1 title $2
  obj.setProp popup $1 x %x
  obj.setProp popup $1 y %y
  obj.setProp popup $1 w $5
  obj.setProp popup $1 h $6

  if ($7) {
    td.addPopupButton $1 xclose $calc($5 - 26) -21 14 14 $td.gfx(closepopup.bmp) Close
  }

  return 1
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc:
alias td.closePopup {

  if ($obj.exists(popup,$1)) {

    hadd tower $+(__save.,$1,.x) $obj.getProp(popup,$1).x
    hadd tower $+(__save.,$1,.y) $obj.getProp(popup,$1).y

    obj.delete popup $1
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 
alias -l td.renderGame {

  drawcopy -nr @tbuf 0 0 640 640 @tower 0 0

  if ($hget(tower,show.region)) td.drawRegions $1
  if ($hget(tower,show.zone)) td.renderZones
  if ($hget(tower,show.tidzone)) td.renderTidZone

  td.update

  td.renderCreeps

  td.drawMenus

  td.drawMouse

  td.renderFloatingText

  td.renderConsole

  td.renderPopups
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.renderZones {

  var %topy = $td.build.y, %topx = $td.build.x
  var %x = %topx, %w = $calc(%topx + $td.Build.W), %h = $calc(%topy + $td.Build.H)

  while (%x <= %w) {

    var %y = %topy
    while (%y <= %h) {

      var %zw = %zone.w
      var %zh = %zone.h

      if ($calc(%x + %zw) > %w) %zw = $calc(%w - %x)
      if ($calc(%y + %zh) > %h) %zh = $calc(%h - %y)

      drawrect -nr @tower $rgb(128,128,128) 1 %x %y $calc(%zw + 1) $calc(%zh + 1)

      var %zone = $td.getZoneKey(%x,%y)
      var %sleeplist = $+(%zone,.sleep)

      drawtext -nr @tower $rgb(166,166,166) %td.font 12 $calc(%x + 2) $calc(%y + 2) %zone
      drawtext -npr @tower $rgb(166,166,166) %td.font 12 $calc(%x + 2) $calc(%y + 14) Sleeplist: $obj.count(%sleeplist)

      inc %y %zone.h
    }
    inc %x %zone.w
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.renderTidZone {

  var %tid = $td.getIdByCoords(tid,$mouse.x,$mouse.y)
  if (%tid) {

    var %topy = $td.build.y
    var %maxh = $td.build.h

    var %zonelist = $obj.tid(%tid).zonelist
    var %i = 1

    while ($gettok(%zonelist,%i,32)) {

      var %zone = $v1

      var %n = $abs($calc($sin($calc($ticks / 170)) * 4))
      var %x = $calc($td.build.x + $gettok(%zone,2,46) * %zone.w) 
      var %y = $calc($td.build.y + $gettok(%zone,3,46) * %zone.h) 
      var %w = %zone.w 
      var %h = %zone.h 

      if ($calc(%y + %h) > $calc(%topy + %maxh)) %h = $calc(%topy + %maxh - %y)

      drawrect -nifr @tower $rgb(0,255,255) 1 %x %y $calc(%w + 1) $calc(%h + 1)

      inc %i
    }

    drawrect -nrf @tower %td.white 1 10 5 500 25
    drawrect -nr @tower $rgb(0,0,0) 1 10 5 500 25
    drawtext -nr @tower $rgb(0,0,0) %td.font 12 14 9 > $replace(%zonelist,$chr(32),$chr(44) $+ $chr(160))
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.renderPopups {

  var %id = $obj.getLast(popup)
  while (%id) {

    td.renderPopupWindow %id

    %id = $obj.getPrev(popup)
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.renderPopupWindow {

  var %id = $1
  var %x = $obj.getProp(popup,%id).x
  var %y = $obj.getProp(popup,%id).y
  var %w = $obj.getProp(popup,%id).w
  var %h = $obj.getProp(popup,%id).h

  var %title = $obj.getProp(popup,%id).title
  if (!%title) %title = Popup

  drawrect -nrf @tower %td.white 1 %x %y %w %h
  drawrect -nr @tower $rgb(0,0,0) 1 %x %y %w %h
  drawrect -nr @tower $rgb(0,0,0) 1 %x %y %w 22

  drawtext -nor @tower $rgb(0,0,0) %td.font 16 $calc(%x + 6) $calc(%y + 3) %title

  var %callback = $obj.getProp(popup,%id).callback_render 
  if (%callback) {
    %callback $calc(%x + 8) $calc(%y + 25) $calc(%w - 14) $calc(%h - 30)
  }

  var %type = $+(pctrl.,%id)
  var %ctrl = $obj.getFirst(%type)
  while (%ctrl) {

    td.renderPopupCtrl %id $calc(%x + 8) $calc(%y + 25) %type %ctrl 

    %ctrl = $obj.getNext(%type)
  }

}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: abs_x abs_y type ctrl 
alias -l td.renderPopupCtrl {

  var %pop = $1, %ax = $2, %ay = $3, %type = $4, %ctrl = $5
  var %ct = $obj.getProp(%type,%ctrl).type
  var %x = $calc($obj.getProp(%type,%ctrl).x + %ax)
  var %y = $calc($obj.getProp(%type,%ctrl).y + %ay)
  var %w = $obj.getProp(%type,%ctrl).w
  var %h = $obj.getProp(%type,%ctrl).h
  var %clicked = 0

  if (%ct == Button) {

    if ($td.MouseDown(1)) {
      if ($inrect($mouse.x,$mouse.y,%x,%y,%w,%h)) { 
        if ($hget(tower,clickOn) == $+(%pop,.,%ctrl)) {
          %clicked = 1
        }
      }
    }

    if (%clicked) {
      drawrect -nr @tower 0 1 $calc(%x + 1) $calc(%y + 1) $calc(%w - 1) $calc(%h - 1)
      drawpic -cn @tower $calc(%x + 2) $calc(%y + 2) 0 0 $calc(%w - 2) $calc(%h - 2) $obj.getProp(%type,%ctrl).gfx
    }
    else {
      drawpic -cn @tower %x %y $obj.getProp(%type,%ctrl).gfx
    }
    drawrect -nr @tower 0 1 %x %y %w %h

    if ($inrect($mouse.x,$mouse.y,%x,%y,%w,%h)) {
      drawrect -nr @tower 0 2 %x %y %w %h
      hadd tower tooltip $obj.getProp(%type,%ctrl).tooltip
    }
  }
  elseif (%ct == Checkbox) {

    drawrect -nr @tower 0 1 %x %y 12 12

    if ($hget(tower,%ctrl)) {
      drawrect -nfr @tower 0 1 $calc(%x + 2) $calc(%y + 2) 8 8
    }

    ;  if ($inrect($mouse.x,$mouse.y,$calc(%x + 16),$calc(%y - 2),$calc(%w - 16),$calc(%h - 2))) {
    ;    drawtext -nr @tower $rgb(255,0,0) %td.font 12 $calc(%x + 16) $calc(%y - 2) $obj.getProp(%type,%ctrl).text
    ;  }
    ;  else {
    drawtext -nr @tower 0 %td.font 12 $calc(%x + 16) $calc(%y - 2) $obj.getProp(%type,%ctrl).text
    ;  }
  }
  elseif (%ct == checklist) {

    var %selid = $obj.getprop(%type,%ctrl).selected

    drawrect -nr @tower 0 1 %x %y %w 17
    drawtext -nr @tower $rgb(0,0,0) %td.font 12 $calc(%x + 4) %y $obj.getProp(%type,%ctrl).title
    drawrect -nr @tower 0 1 %x $calc(%y + 16) %w $calc(%h - 16)

    var %itemx = $calc(%x + 4)
    var %i = 0
    var %items = $obj.getProp(%type,%ctrl).items

    var %id = $obj.getFirst(%items)
    while (%id) {

      var %itemy = $calc(%y + %i * 13 + 20)

      drawrect -nr @tower 0 1 %itemx %itemy 12 12
      drawtext -nr @tower 0 %td.font 12 $calc(%itemx + 16) $calc(%itemy - 2) $obj.getProp(%items,%id).desc
      if (%id == %selid) {
        drawrect -nfr @tower 0 1 $calc(%itemx + 2) $calc(%itemy + 2) 8 8
      }

      inc %i

      %id = $obj.getNext(%items)
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.ShowPopupSelTid {

  if ($td.showPopup(seltid,Tower info,10,100,192,186,1)) {

    td.setPopupCallback seltid render td.renderTidSelection

    var %selcount = $obj.count(seltid)
    if (%selcount == 1) {

      var %tid = $obj.getFirst(seltid)
      if (%tid) {

        var %tower = $obj.tid(%tid).upgrade

        if ($obj.exists(tower,%tower)) {
          td.addPopupButton seltid Upgrade 0 120 32 32 $td.gfx(upgrade.bmp) Upgrade to $obj.getProp(tower,%tower).name for $td.goldToText($obj.getProp(tower,%tower).cost)
        }

        td.addPopupButton seltid Awake 36 120 32 32 $td.gfx(awake.bmp) Click here to awake the tower manually

        if ($obj.tid(%tid).canSell) {
          td.addPopupButton seltid Sell 146 120 32 32 $td.gfx(sell.bmp) Sell tower for $td.goldToText($td.getRefundAmount(%tid))
        }
      }
    }
    else {

      td.addPopupChecklist seltid combos 0 19 178 95 Available combos ( $+ $obj.count(combo_list) $+ )

      var %combo = $obj.getFirst(combo_list)
      while (%combo) {
        var %tower = $obj.getProp(combo,%combo).tower
        var %cost = $obj.getProp(tower,%tower).cost
        td.addPopupChecklistItem seltid combos %combo $obj.getProp(combo,%combo).name ( $+ $td.goldtoText(%cost) $+ )
        %combo = $obj.getNext(combo_list)
      }

      td.addPopupButton seltid Upgrade 0 120 32 32 $td.gfx(upgrade.bmp) Combine %selcount towers
      td.addPopupButton seltid Awake 36 120 32 32 $td.gfx(awake.bmp) Click here to awake the towers manually      
      td.addPopupButton seltid Sell 146 120 32 32 $td.gfx(sell.bmp) Sell %selcount towers
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: popup checklist item desc
alias -l td.addPopupChecklistItem {

  var %type = $+(pctrl.,$1)
  var %ctrl = $2
  var %items = $obj.getProp(%type,%ctrl).items

  obj.new %items $3
  obj.setProp %items $3 desc $4-
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.addPopupChecklist {

  var %type = $+(pctrl.,$1)
  var %items = $+($1,.,$2,.it)

  obj.clear %items

  td.addPopupCtrl $1 $2 $3 $4 $5 $6

  obj.setProp %type $2 type checklist
  obj.setProp %type $2 items %items
  obj.setProp %type $2 title $7-
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.addPopupRadio {

  var %type = $+(pctrl.,$1)

  td.addPopupCtrl $1 $2 $3 $4 $5 $6

  obj.setProp %type $2 type Checkbox
  obj.setProp %type $2 text $7-
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.addPopupCheckbox {

  var %type = $+(pctrl.,$1)

  td.addPopupCtrl $1 $2 $3 $4 $5 $6

  obj.setProp %type $2 type Checkbox
  obj.setProp %type $2 text $7-
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.addPopupButton {

  var %type = $+(pctrl.,$1)

  td.addPopupCtrl $1 $2 $3 $4 $5 $6

  obj.setProp %type $2 type Button
  obj.setProp %type $2 gfx $7
  obj.setProp %type $2 tooltip $8-
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.addPopupCtrl {

  var %type = $+(pctrl.,$1)

  obj.new %type $2 
  obj.setProp %type $2 x $3
  obj.setProp %type $2 y $4
  obj.setProp %type $2 w $5
  obj.setProp %type $2 h $6
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: x y w h
alias -l td.renderTidSelection {

  var %x = $1
  var %y = $2
  var %w = $3
  var %h = $4

  var %count = $obj.count(seltid)

  if (%count > 1) {

    var %tidw = 32
    var %tidh = 32

    drawtext -nr @tower $rgb(0,0,0) %td.font 12 %x %y %count towers selected

    if (%showTowerInfo) {
      td.showTowerInfo %showTowerInfo $calc($mouse.x + 11) $calc($mouse.y + 21)
    }
  }
  else {

    var %seltid = $obj.getFirst(seltid)
    if (%seltid) {
      var %tidw = $obj.tid(%seltid).ScreenW
      var %tidh = $obj.tid(%seltid).ScreenH
      var %name = $obj.tid(%seltid).name

      if ($hget(tower,debug)) %name = %name $+($chr(40),%seltid,$chr(41))

      if (!%name) %name = No name

      td.drawTid @tower %seltid %x %y

      drawtext -nor @tower $rgb(0,0,0) %td.font 14 $calc(%x + %tidw + 8) %y %name

      var %texty = $calc(%y + $obj.tid(%seltid).ScreenH + 2)

      drawtext -nor @tower 0 %td.font 12 $calc(%x + 63) $calc(%y + 20) Stats
      drawtext -npr @tower $rgb(0,0,0) %td.font 12 %x $calc(%texty +  0) Attack : $obj.tid(%seltid).att_text 
      drawtext -npr @tower $rgb(0,0,0) %td.font 12 %x $calc(%texty + 15) Range  : $obj.tid(%seltid).range 
      drawtext -npr @tower $rgb(0,0,0) %td.font 12 %x $calc(%texty + 30) Speed  : $obj.tid(%seltid).speed 

      var %att_mod = $obj.tid(%seltid).att_mod
      var %ran_mod = $obj.tid(%seltid).ran_mod
      var %spe_mod = $obj.tid(%seltid).spe_mod

      if (%att_mod || %ran_mod || %spe_mod) {      
        drawtext -nor @tower 0 %td.font 12 $calc(%x + 120) $calc(%y + 20) Bonus
        drawtext -nr @tower $rgb(0,0,0) %td.font 12 $calc(%x + 120) $calc(%texty +  0) $td.pad(%att_mod,5,$chr(160))
        drawtext -nr @tower $rgb(0,0,0) %td.font 12 $calc(%x + 120) $calc(%texty + 15) $td.pad(%ran_mod,5,$chr(160))
        drawtext -nr @tower $rgb(0,0,0) %td.font 12 $calc(%x + 120) $calc(%texty + 30) $td.pad(%spe_mod,5,$chr(160))
      }

      drawtext -npr @tower $rgb(0,0,0) %td.font 12 %x $calc(%texty + 45) Target : $replace($obj.tid(%seltid).target ,:,$chr(32))
      ; drawtext -npr @tower $rgb(0,0,0) %td.font 12 %x $calc(%texty + 60) Kills  : $obj.tid(%seltid).killcount
      drawtext -npr @tower $rgb(0,0,0) %td.font 12 %x $calc(%texty + 60) Level  : $obj.tid(%seltid).level ( $+ $obj.tid(%seltid).killcount kills)
    }
  }
}


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: text,n,char
alias -l td.pad return $+($str($3,$calc($2 - $len($1))),$1)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: combo
; desc: upgrade selected tid to combo
alias -l td.upgradeSelTidToCombo {

  var %combo = $1
  var %tower = $obj.getProp(combo,%combo).tower

  var %id = $obj.getFirst(seltid)
  if (%id) {

    var %oldx = $obj.tid(%id).x
    var %oldy = $obj.tid(%id).y
    var %kills = 0
    var %value = 0
    var %lowlevel = Undefined

    ; check if we have enough cash
    if ($hget(tower,self.cash) >= $obj.getProp(tower,%tower).cost) {

      while (%id) {

        inc %kills $obj.tid(%id).killcount
        inc %value $obj.tid(%id).value

        if ($obj.tid(%id).level < %lowlevel) %lowlevel = $v1

        td.removeTid %id

        %id = $obj.getNext(seltid)
      }

      %id = $td.buildTower(%tower,%oldx,%oldy)
      if (%id) {

        obj.setProp tid %id killcount %kills
        obj.incProp tid %id value %value

        if (%lowlevel != Undefined) {
          ;     var %i = 1
          ;     while (%i <= %lowlevel) {
          ;       td.addVeteranLevel %id
          ;       inc %i
          ;     }
        }

        return %id
      }
    }
  }
  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: msg
alias -l td.showTooltip {

  var %msg = $1-
  if (%msg) {

    var %maxw = 200
    var %textw = $width(%msg,$noqt(%td.font),12)
    var %chunk
    var %i = 1, %t = $wrap(%msg,$noqt(%td.font),12,%maxw,1,0)

    var %rectx = $calc($mouse.x + 11) 
    var %recty = $calc($mouse.y + 21) 
    var %rectw = $calc(%textw + 10)
    var %recth = $calc(%t * 12 + 8)

    if (%rectw > %maxw) %rectw = %maxw

    ; keep the rect on screen  
    if ($calc(%rectx + %rectw) > 640) %rectx = $calc(640 - %rectw)
    if ($calc(%recty + %recth) > 640) %recty = $calc(640 - %recth)

    var %textx = $calc(%rectx + 4)
    var %texty = $calc(%recty + 2)

    drawrect -nrf @tower %td.white 1 %rectx %recty %rectw %recth
    drawrect -nr @tower 0 1 %rectx %recty %rectw %recth

    while (%i <= %t) {
      %chunk = $wrap(%msg,$noqt(%td.font),12,%maxw,1,%i)
      if (%chunk) {
        drawtext -nr @tower $rgb(0,0,0) %td.font 12 %textx %texty %chunk
      }
      inc %texty 12
      inc %i
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: tid
alias td.drawTowerSelection {

  var %id = $1

  var %n = $abs($calc($sin($calc($ticks / 170)) * 4))
  var %x = $calc($obj.tid(%id).ScreenX - %n)
  var %y = $calc($obj.tid(%id).ScreenY - %n)
  var %w = $calc($obj.tid(%id).ScreenW + %n * 2)
  var %h = $calc($obj.tid(%id).ScreenH + %n * 2)

  drawrect -nr @tower %td.white 2 %x %y %w %h
  drawrect -nr @tower $rgb(0,0,0) 1 %x %y %w %h

  if ($hget(tower,show.range)) {
    var %range = $obj.tid(%id).range
    drawrect -rne @tower $rgb(0,0,0) 1 $calc($obj.tid(%id).centerX - %range) $calc($obj.tid(%id).centerY - %range) $calc(2 * %range) $calc(2 * %range)

    var %ran_mod = $obj.tid(%id).ran_mod
    if (%ran_mod != 0) {
      var %range = $calc(%range + %ran_mod)
      drawrect -rne @tower $rgb(255,255,255) 1 $calc($obj.tid(%id).centerX - %range) $calc($obj.tid(%id).centerY - %range) $calc(2 * %range) $calc(2 * %range)
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.renderGameOver {

  td.drawText @tower 150 70 {rgb:255,255,255}{s:64}Game {fx:2}{rgb:255}o{rgb:255,255,255}{fx:2}ver

  drawtext -nr @tower %td.white %td.font 256 115 150 \
  drawtext -nr @tower %td.white wingdings 256 215 150 L
  drawtext -nr @tower %td.white %td.font 256 375 150 /
  drawline -nr @tower %td.white 11 323 380 323 460
  drawline -nr @tower %td.white 11 234 380 323 460
  drawline -nr @tower %td.white 11 408 380 323 460
  drawline -nr @tower $rgb(255,0,0) 11 0 460 700 460

  td.drawtext @tower 440 400 {rgb:255,255,255}<<< this is you, {fx:2}hahaha{fx:2}

  ; drawText -nr @tower %td.white %td.font 24 145 500 Total points : $base($hget(tower,score),10,10,10)

  if ($inrect($mouse.x,$mouse.y,$calc(640 - 75),$calc(640 - 32),75,32)) {
    drawtext -nr @tower $rgb(255,0,0) %td.font 32 $calc(640 - 80) $calc(640 - 32) Menu
    if ($td.mousedown(1)) {
      td.jumpToScene main
    }
  }
  else {
    drawtext -nr @tower %td.white %td.font 32 $calc(640 - 80) $calc(640 - 32) Menu
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 
alias -l td.renderPause {

  td.drawtext @tower 100 100 {s:64}{rgb:255,255,255}G{fx:2}{rgb:255}a{rgb:255,255,255}{fx:2}me paused
  td.renderPopups  
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.renderCreeps {

  var %cid = $obj.getLast(cid)
  while (%cid) {

    td.renderSingleCreep %cid

    %cid = $obj.getPrev(cid)
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.renderSingleCreep {

  var %cid = $1
  var %x = $obj.cid(%cid).x
  var %y = $obj.cid(%cid).y
  var %w = $obj.cid(%cid).w
  var %h = $obj.cid(%cid).h

  var %img = $obj.cid(%cid).img
  var %pos = $obj.cid(%cid).imgpos

  var %type = $obj.cid(%cid).type
  var %speed = $calc($obj.cid(%cid).Speed + $obj.cid(%cid).spe_mod)

  var %updateframe = $calc((8 - %speed) * 40)
  if (%updateframe <= 70) %updateframe = 70

  ; it is time to update the animation ?
  if ($calc($td.ticks - $obj.cid(%cid).imglu) > %updateframe) {

    %pos = $calc((%pos + 1) % $obj.cid(%cid).imgnb)
    obj.setProp cid %cid imgpos %pos
    obj.setProp cid %cid imglu $td.ticks      
  }

  var %WalkInterval = 50
  var %lastWalk = $calc($td.ticks - $obj.cid(%cid).lastWalk)

  ; it is time to walk?
  if (%lastWalk >= %WalkInterval) {

    var %region = $obj.cid(%cid).walkTo
    if (%region) {

      ; debug
      if (!$hget(tower,stopCreep)) {

        ; compensate for laggy machine
        ; var %ratio = $calc((%lastWalk / %WalkInterval) * ($obj.cid(%cid).Speed + $obj.cid(%cid).spe_mod))

        var %ratio = %speed

        var %tmpX = $calc($obj.getProp(region,%region).CenterX - $obj.cid(%cid).centerX)
        var %tmpY = $calc($obj.getProp(region,%region).CenterY - $obj.cid(%cid).centerY)

        var %tot = $calc($abs(%tmpX) + $abs(%tmpY))
        var %fx = $calc(%x + %tmpX / %tot * %ratio)
        var %fy = $calc(%y + %tmpY / %tot * %ratio)

        ;   if (!$td.creepCollide(%fx,%fy,%cid)) {
        td.moveCreep %cid %fx %fy
        ;  }
      }

      obj.setProp cid %cid lastWalk $td.ticks
    }
  }

  if (%type == air) {
    ; drop shadow
    var %h2 = $calc(%h / 5)
    var %w2 = $calc(%w / 5)
    drawrect -nref @tower $rgb(32,32,32) 1 $calc(%x + %w2) $calc(%y + %h2) $calc(%w - %w2 * 2) $calc(%h - %h2 * 2)
    dec %y 20
  }

  var %imgdx = $calc(($obj.cid(%cid).imgdx + %pos) * %w)
  var %imgdy = $calc($obj.cid(%cid).imgdy * %h)

  ; drawpic -ctn @tower $rgb(255,0,255) %x %y $calc(%w * %pos) 0 %w %h %img
  drawpic -ctn @tower $rgb(255,0,255) %x %y %imgdx %imgdy %w %h %img

  if ($hget(tower,show.health)) {
    td.showCidHealthBar %cid %x $calc(%y - 5) %w 4
  }

  ; check on which region the creep is standing
  var %reg = $td.getRegionIdByCoords($obj.cid(%cid).CenterX, $obj.cid(%cid).centerY)
  if (%reg) {

    if ($obj.cid(%cid).onRegion != %reg) {

      obj.setProp cid %cid onRegion %reg

      ; is the creep on the region it was suppose to go?
      if (%reg == $obj.cid(%cid).walkTo) {

        if ($td.triggerEvent(onCreepReachRegion,%cid,%reg)) {

          var %next = $td.getNextRegionInPath(%reg)
          if (%next) {

            ; order the creep to move to the next region in the path
            td.WalkCreepTo %cid %next
          }
          else {

            ; there is no more region in the path, so it means that the creep has reached the exit!
            td.substLives $obj.cid(%cid).lifeCost 
            td.removeCreep %cid
          }
        }
      }
    }
  }
  else obj.setProp cid %cid onRegion
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: number_of_lives
alias -l td.substLives {

  var %lives = $1

  if (%lives > 0) {

    td.playSound $td.path($obj.getProp(map,$td.map).sfxLoseLife)

    hdec tower self.lives %lives

    if ($hget(tower,self.lives) <= 0) {

      hadd tower self.lives 0

      ; if not already game over
      if (!$td.isGameOver) {
        .timer 1 2 td.setGameOver 1
      }
    }
    else {
      td.showFloatingText -n 200 50 1000 0 Carefull, you've just lost %lives $iif(%lives > 1,lives,life) $+ !
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.getNextRegionInPath {

  return $obj.getProp(walkPath,$1).next
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: cid, x, y, w, h
alias -l td.showCidHealthBar {

  var %hp = $obj.cid($1).hp
  var %maxhp = $obj.cid($1).maxhp
  var %pbar = $calc(%hp / %maxhp * $4)

  drawrect -nrf @tower $rgb(0,255,0) 1 $2 $calc($3 - 5) %pbar $5
  drawrect -nr @tower $rgb(0,0,0) 1 $2 $calc($3 - 5) $4 $5
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.renderConsole {

  if ($hget(tower,__console.show)) {

    var %x = $calc($td.build.x + 10)
    var %y = $calc($td.build.y + $td.build.h - 20)
    var %w = $calc($td.build.w - 20)
    var %h = 20

    drawrect -nfr @tower %td.white 1 %x %y %w %h
    drawrect -nr @tower $rgb(0,0,0) 1 %x %y %w %h
    var %text = $hget(tower,__console.text)
    if ($len(%text) > 0) {
      drawtext -nr @tower $rgb(0,0,0) %td.font 12 $calc(%x + 4) $calc(%y + 2) %text
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: map_name
alias -l td.loadmap {

  var %map = $1

  set %td.map_path $shortfn($+($scriptdirmaps\,%map,\))

  var %path = $shortfn($+(%td.map_path,%map,.txt))
  var %in = 0
  var %name, %type, %n
  var %ok

  ; file exists?
  %ok = $isfile(%path)
  if (%ok) {

    obj.clear

    var %size = $file(%path).size

    ; set the current map
    hadd tower __currentmap $1

    if ($fopen(Tdmap)) .fclose tdmap

    .fopen tdmap %path

    ; read the whole file
    while (!$feof(tdmap) && %ok) {

      var %line = $fread(tdmap)
      if ($numtok(%line,32) > 0) {
        tokenize 32 %line

        ; don't consider comments line (starts with ;)
        if ($mid($1,1,1) != $chr(59)) {

          if (%in) {

            ; check for end brackets "}"
            if ($1 == $chr(125)) { 
              if (%type == event) {
                ; set the number of lines that the event contains
                obj.setProp %type %name count %n
              }
              %in = 0
            }             
            else {
              if (%type == event) {
                ; replace Tabs by spaces
                var %text = $replace($1-,$chr(9),$chr(32))
                if ($len($remove(%text,$chr(32))) > 0) {
                  obj.setProp %type %name %n %text
                  inc %n
                }
              }
              else {
                ; get all the text after the "="
                obj.setProp %type %name $1 $($gettok($3-,1,59),2)
              }
            }
          }
          else {

            %type = $1
            %name = $2
            %n = 1

            ; filter objects type, to be sure that its valid 
            var %valid_objects = :map:creep:region:tower:event:combo:effect:
            if ($pos(%valid_objects,$+(:,%type,:)) > 0) {

              %in = 1            
              obj.new %type %name

              if ($3 == : && $4 == use) {

                ; copy $5 properties in %name
                td.copyObjectProperties %type %name $5
              }
            }
            else {
              td.showError Invalid object's type : %type
              %ok = 0
            }
          }
        }
      }
    }
    .fclose tdmap
    if (%ok) {
      if ($td.isLoadedMapValid(%map)) {

        ; precompute stuff for towers
        var %tower = $obj.getFirst(tower)
        while (%tower) {
          td.initTower %tower
          %tower = $obj.getNext(Tower)
        }

        ; update collision buffer with regions
        var %name = $obj.getFirst(region)
        while (%name) {

          if (!$obj.getProp(region,%name).build) {

            var %x = $obj.getProp(region,%name).x
            var %y = $obj.getProp(region,%name).y
            var %w = $obj.getProp(region,%name).width
            var %h = $obj.getProp(region,%name).height

            obj.setProp region %name CenterX $calc($topix(%x) + $topix(%w) / 2)
            obj.setProp region %name CenterY $calc($topix(%y) + $topix(%h) / 2)

            td.setTileState %x %y %w %h 1 %name
          }
          %name = $obj.getNext(region)
        }

        ; clear, or else cash keeps stacking if you quit and start a new game
        hadd tower self.cash 0
        hadd tower self.gain 0

        td.addGain $obj.getProp(map,%map).cash
        hadd tower self.lives $obj.getProp(map,%map).lives

        ; set the walking path
        var %reglist = $remove($obj.getProp(map,%map).WalkPath,$chr(32))
        var %i = 1, %region
        while ($gettok(%reglist,%i,44)) {
          %region = $v1
          obj.new walkPath %region
          obj.setProp walkPath %region next $gettok(%reglist,$calc(%i + 1),44)
          inc %i
        }

        ; set the timer for next level
        hadd tower game.nextLevel $calc($td.ticks + $obj.getProp(map,$td.map).timeBetweenLevel) 
      }
      else {
        %ok = 0
      }
    }
  }

  if (%ok) {
    if (!$td.triggerEvent(onInit)) {

      %ok = 0
    }
  }

  return %ok
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tower
alias -l td.initTower {

  var %id = $1
  var %img = $obj.getProp(tower,%id).img
  if (%img) obj.setProp tower %id img $td.path(%img)

  ; calc size in pixel
  obj.setProp tower %id ScreenW $topix($obj.getProp(tower,%id).width)
  obj.setProp tower %id ScreenH $topix($obj.getProp(tower,%id).height)

  var %list = :

  ; check for combo possibilities
  var %com = $obj.getFirst(combo)
  while (%com) {

    var %require = $remove($obj.getProp(combo,%com).require,$chr(32))
    if ($istok(%require,%id,44)) {
      %list = $+(%list,%com,:)
    }

    %com = $obj.getNext(%com)
  }

  obj.setProp tower %id combo_list %list
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: map_id
; desc: check if the loaded map is valid
alias -l td.isLoadedMapValid {

  var %map = $1
  var %ok = 1

  ; validate creeps
  if ($obj.count(creep) > 0) {
    var %id = $obj.getFirst(creep)
    while (%id) {

      ; if there is an image, check if it exists
      var %img = $obj.getProp(creep,%id).img
      if (%img) {

        if (!$isfile($td.path(%img))) {
          td.showError Invalid creep image for $+(',%id,'.) Img: $+(',$obj.getProp(creep,%id).img,')
          %ok = 0
        }
      }
      %id = $obj.getNext(creep)
    }
  }
  else {
    td.showError You need at least 1 creep type
    %ok = 0
  }

  ; validate towers
  if ($obj.count(Tower) > 0) {
    var %id = $obj.getFirst(tower)
    while (%id) {

      var %w = $calc($obj.getProp(tower,%id).width)
      var %h = $calc($obj.getProp(tower,%id).height)

      if (%w < 1 || %w > 2 || %h < 1 || %h > 2) {
        td.showError Invalid tower size for $+(',%id,'.) Width = %w Height = %h
        %ok = 0
      }
      ; if there is an image, check if it exists
      var %img = $obj.getProp(tower,%id).img
      if (%img) {
        if (!$isfile($td.path(%img))) {
          td.showError Invalid tower image for $+(',%id,'.) Img: $+(',%img,')
          %ok = 0
        }
      }

      ; if there is an upgrade, check if it exists
      var %upgr = $obj.getProp(tower,%id).upgrade
      if (%upgr) {
        if (!$obj.exists(tower,%upgr)) {
          td.showError Invalid upgrade tower for $+(',%id,'.) Upgrade: $+(',%upgr,')
          %ok = 0
        }
      }

      var %range = $obj.getProp(tower,%id).range
      if (!%range) {
        td.showError Invalid tower range for $+(',%id,'.) Range: $(',%range,')
        %ok = 0
      }

      %id = $obj.getNext(tower)
    }
  }
  else {
    td.showError You need at least 1 tower type
    %ok = 0
  }

  ; A valid map needs at least 2 regions
  if ($obj.count(region) < 2) {
    td.showError You need at least 2 regions
    %ok = 0
  }

  ; check to see if every regions in walkpath really exists
  var %reglist = $remove($obj.getProp(map,%map).WalkPath,$chr(32))
  var %i = 1, %region
  while ($gettok(%reglist,%i,44)) {
    %region = $v1
    if (!$obj.exists(region,%region)) {
      td.showError Undefined region $+(',%region,') in WalkPath
      %ok = 0
    }
    inc %i
  }  

  var %combo = $obj.getFirst(combo)
  while (%combo) {
    var %tower = $obj.getProp(combo,%combo).tower
    if (!$obj.exists(tower,%tower)) {
      td.showError Tower $+(',%tower,') doesn't exists for combo $(',%combo,')
      %ok = 0
    }
    %combo = $obj.getNext(combo)
  }

  return %ok
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: trigger_name
; desc: trigger an event using user defined event in the map definition
alias -l td.triggerEvent {

  ; find the user defined event name
  var %name = $($+($,obj.getProp(map,$td.map).,$1),2)
  var %ret = 1
  if (%name) %ret = $td.runEvent(%name,$2-)
  return %ret
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: event_id
; desc: run an user defined event
alias -l td.runEvent {

  var %name = $1
  var %retval = 1

  if ($td.initParser(%name)) {

    var %i = 1, %comm
    var %n = $obj.getProp(event, %name).count

    tokenize 32 $2-

    while (%i <= %n) {

      %comm = $hget(tower,$+(obj.event.,%name,.,%i))
      if ($len(%comm) > 0) {

        ; do we have to execute this command?
        if ($td.evalExec(%comm,$1-)) {

          if (%comm == return) {
            ; break the execution of the script
            %i = %n
            %retval = $gettok(%comm,2,32)
          }
          else {
            $(%comm,2)
          }
        }
      }

      inc %i
    }

    td.unloadParser
  }
  return %retval
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.initParser {

  set %parser.skip 0
  set %parser.lvl 0
  set %parser.slv 0

  return 1
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.unloadParser {

  unset %parser.skip
  unset %parser.lvl
  unset %parser.slv
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: command, parameters
; desc: check is a command line has to be executed
alias -l td.evalExec {

  var %comm = $1
  var %f = $gettok(%comm,1,32)
  var %r = 0

  tokenize 32 $2-

  if (%f == if) {

    inc %parser.lvl

    if (!%parser.skip) {
      if (!$td.checkCondition(%comm, $1-)) {
        inc %parser.skip
        %parser.slv = %parser.lvl
      }
    }
  }
  elseif (%f == else) {
    if (%parser.skip > 0) {
      if (%parser.lvl <= %parser.slv) {
        dec %parser.skip
      }
    }
    else {
      inc %parser.skip 
    }
  }
  elseif (%f == endif) {
    if (%parser.lvl <= %parser.slv) {
      if (%parser.skip > 0) {
        dec %parser.skip
      }
    }
    dec %parser.lvl
  }
  else {
    if (%parser.skip == 0) {
      %r = 1
    }
  }

  return %r
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: crappy condition checker
alias -l td.checkCondition {

  var %cond = $1
  tokenize 32 $2-
  var %a = $remove(%cond,if $chr(40), $chr(41) then)
  var %a = $replace(%a,$chr(40),$+($chr(40),$chr(32)),$chr(41),$+($chr(32),$chr(41)))
  var %r = $ $+ iif( %a ,1,0)
  return $eval(%r,2)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: event_name
; desc: run an event, used to call events in maps event 
alias -l td.callEvent {

  td.runEvent $1-
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: type, dest, src
alias -l td.copyObjectProperties {

  ; copy src object's properties into destination
  var %type = $1
  var %dest = $2
  var %src = $3

  var %wild = $+(obj.,%type,.,%src,.*)
  var %i = 1, %t = $hfind(tower,%wild,0,w)
  while (%i <= %t) {

    var %key = $hfind(tower,%wild,%i,w)
    var %val = $hget(tower,%key)

    obj.setProp %type %dest $gettok(%key,4,$asc(.)) %val

    inc %i 
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: return a random object id 
alias -l obj.randomId {

  return $r(1,$calc(2^32))
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: type
alias -l obj.getNextId {

  var %id = $calc($hget(tower,$+(obj.,$1,.nextid)) + 1)
  hinc tower $+(obj.,$1,.nextid)

  return %id
}


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l obj.clear {

  if ($1) {
    hdel -w tower $+(obj.,$1,.*)
  }
  else {
    hdel -w tower $+(obj.,*)
    hdel -w tower $+(map.,*)
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: type, name
; desc: create a new object
alias -l obj.new2 {

  if (!$hget(tower,$+(obj.,$1,.,$2,.exists))) {

    hadd tower $+(obj.,$1,.,$2,.exists) 1

    if (!$hget(tower,$+(obj.,$1,.first))) {
      hadd tower $+(obj.,$1,.first) $2
    }

    var %last = $hget(tower,$+(obj.,$1,.last))
    if (%last) {
      hadd tower $+(obj.,$1,.,%last,.onext) $2
      hadd tower $+(obj.,$1,.,$2,.oprev) %last
    }

    hadd tower $+(obj.,$1,.last) $2
    hinc tower $+(obj.,$1,.count)
    return 1
  }
  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: type, name
alias -l obj.delete2 {

  var %prev = $hget(tower,$+(obj.,$1,.,$2,.oprev))
  var %next = $hget(tower,$+(obj.,$1,.,$2,.onext))

  ; if we flush first object, we need to set the new 'first object'
  if ($hget(tower,$+(obj.,$1,.first)) == $2) {
    hadd tower $+(obj.,$1,.first) %next
  }

  ; if we flush last object, we need to set the new 'last object'
  if ($hget(tower,$+(obj.,$1,.last)) == $2) {
    hadd tower $+(obj.,$1,.last) %prev
  }

  ; fix link between previous and next object 
  if (%prev) hadd tower $+(obj.,$1,.,%prev,.onext) %next
  if (%next) hadd tower $+(obj.,$1,.,%next,.oprev) %prev

  ; fix cursor for obj.getNext()
  hadd tower $+(obj.,$1,.readnext) %next

  ; flush the old object data
  hdel -w tower $+(obj.,$1,.,$2,.*)

  hdec tower $+(obj.,$1,.count) 1
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: obj_type
; desc: return the first id of the specified object type
alias obj.getFirst2 {

  var %first = $hget(tower,$+(obj.,$1,.first)) 
  hadd tower $+(obj.,$1,.readnext) $hget(tower,$+(obj.,$1,.,%first,.onext))
  return %first
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: obj_type
; desc: return the next id of the specified object type (after reading with $obj.getFirst())
alias -l obj.getNext2 {

  var %cur = $hget(tower,$+(obj.,$1,.readnext))
  if ($hget(tower,$+(obj.,$1,.last)) == %cur) {
    hdel tower $+(obj.,$1,.readnext) 
  }
  else {
    hadd tower $+(obj.,$1,.readnext) $hget(tower,$+(obj.,$1,.,%cur,.onext))
  }

  return %cur
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: type, name
; desc: create a new object
alias -l obj.new {

  if (!$hget(tower,$+(obj.,$1,.,$2,.exists))) {

    hadd tower $+(obj.,$1,.,$2,.exists) 1
    hadd tower $+(obj.,$1,.elem) $+($hget(tower,$+(obj.,$1,.elem)),:,$2)
    hinc tower $+(obj.,$1,.count)

    return 1
  }
  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: type, id
alias obj.delete {

  if ($hget(tower,$+(obj.,$1,.,$2,.exists))) {

    hadd tower $+(obj.,$1,.elem) $remtok($hget(tower,$+(obj.,$1,.elem)),$2,1,58)

    ; fix cursor for obj.getNext()
    hdec tower $+(obj.,$1,.next) 

    ; flush the old object data
    hdel -w tower $+(obj.,$1,.,$2,.*)

    hdec tower $+(obj.,$1,.count) 1  
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: obj_type
; desc: return the first id of the specified object type
alias obj.getFirst {

  hadd tower $+(obj.,$1,.next) 2
  return $gettok($hget(tower,$+(obj.,$1,.elem)),1,58)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: obj_type
; desc: return the next id of the specified object type (after reading with $obj.getFirst())
alias -l obj.getNext {

  var %next = $hget(tower,$+(obj.,$1,.next))
  hinc tower $+(obj.,$1,.next)
  return $gettok($hget(tower,$+(obj.,$1,.elem)),%next,58)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: obj_type
; desc: return the last id of the specified object type
alias obj.getLast {
  var %count = $obj.count($1)
  hadd tower $+(obj.,$1,.prev) $calc(%count - 1)
  return $gettok($hget(tower,$+(obj.,$1,.elem)),-1,58)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: obj_type
; desc: return the previous id of the specified object type (after reading with obj.getLast)
alias obj.getPrev {

  var %prev = $hget(tower,$+(obj.,$1,.prev))
  if (%prev > 0) {

    hdec tower $+(obj.,$1,.prev)
    return $gettok($hget(tower,$+(obj.,$1,.elem)),%prev,58)
  }
}

; the $calc is there for a reason : if the element doesnt exists, i want to return 0, not an empty string
alias obj.count return $calc($hget(tower,$+(obj.,$1,.count)))
alias obj.cid return $hget(tower,$+(obj.cid,.,$1,.,$prop))
alias obj.tid return $hget(tower,$+(obj.tid,.,$1,.,$prop))
alias obj.getProp return $hget(tower,$+(obj.,$1,.,$2,.,$prop))
alias obj.setProp hadd tower $+(obj.,$1,.,$2,.,$3) $4-
alias obj.exists return $hget(tower,$+(obj.,$1,.,$2,.exists))
alias obj.incProp hinc tower $+(obj.,$1,.,$2,.,$3) $4-
alias obj.decProp hdec tower $+(obj.,$1,.,$2,.,$3) $4-

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tid
alias td.upgradeTid {

  var %tower = $obj.tid($1).upgrade

  ; check if the tower can upgrade
  if ($obj.exists(tower,%tower)) {

    ; check if we have enough cash
    if ($hget(tower,self.cash) >= $obj.getProp(tower,%tower).cost) {

      var %killcount = $obj.tid($1).killcount
      var %level = $obj.tid($1).level
      var %value = $obj.tid($1).value
      var %x = $obj.tid($1).x
      var %y = $obj.tid($1).y

      td.removeTid $1
      var %tid = $td.buildTower(%tower,%x,%y)

      if (%tid) {

        ; we want to keep Level, Killcount, and value for the upgraded tower
        obj.setProp tid %tid killcount %killcount
        obj.incProp tid %tid value %value

        var %i = 1
        while (%i <= %level) {
          td.addVeteranLevel %tid
          inc %i
        }
        return %tid
      } 
    }
  }
  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: obj, x, y
; desc: build a tower at the specified tile's position
alias -l td.buildTower {

  var %x = $2
  var %y = $3
  var %w = $obj.getProp(tower,$1).width
  var %h = $obj.getProp(tower,$1).height

  if ($td.canBuildTower($1,%x,%y,%w,%h)) {

    if ($td.triggerEvent(onBuildStart,$1,$topix(%x),$topix(%y),$topix(%w),$topix(%h))) {

      var %id = $obj.getNextId(tid)

      obj.new tid %id

      ; keep the tower type
      obj.setProp tid %id obj $1

      obj.setProp tid %id name $obj.getProp(tower,$1).name
      obj.setProp tid %id desc $obj.getProp(tower,$1).desc

      ; coords in tile
      obj.setProp tid %id x %x
      obj.setProp tid %id y %y
      obj.setProp tid %id w %w
      obj.setProp tid %id h %h

      ; coords in pixel
      obj.setProp tid %id ScreenX $topix(%x)
      obj.setProp tid %id ScreenY $topix(%y)
      obj.setProp tid %id ScreenW $topix(%w)
      obj.setProp tid %id ScreenH $topix(%h)

      obj.setProp tid %id centerX $tid.getCenterX(%id)
      obj.setProp tid %id centerY $tid.getCenterY(%id)

      ; stuff for handling "building"
      obj.setProp tid %id complete 0
      obj.setProp tid %id buildstart $td.ticks

      ; minimum buildTime is 1
      if (!$obj.getProp(tower,$1).buildtime) obj.setProp tid %id buildtime 1
      else obj.setProp tid %id buildtime $obj.getProp(tower,$1).buildtime

      ; minimum maxTarget is 1
      if (!$obj.getProp(tower,$1).maxTarget) obj.setProp tid %id maxTarget 1
      else obj.setProp tid %id maxTarget $obj.getProp(tower,$1).maxTarget

      obj.setProp tid %id target $+(:,$replace($remove($obj.getProp(tower,$1).target,$chr(32)),$chr(44),:),:)

      ; image
      obj.setProp tid %id img $obj.getProp(tower,$1).img

      obj.setProp tid %id imgpos 0

      obj.setProp tid %id range $obj.getProp(tower,$1).range

      obj.setProp tid %id sell $obj.getProp(Tower,$1).sell
      obj.setProp tid %id value $obj.getProp(Tower,$1).cost

      obj.setProp tid %id upgrade $obj.getProp(tower,$1).upgrade

      ; attack
      var %att = $obj.getProp(tower,$1).attack
      var %min = $calc($gettok(%att,1,45))
      var %max = $calc($gettok(%att,2,45))

      if (%max < %min) %max = %min
      obj.setProp tid %id att_min %min
      obj.setProp tid %id att_max %max

      obj.setProp tid %id att_text $+(%min,-,%max)

      ; speed
      obj.setProp tid %id speed $calc($obj.getProp(tower,$1).speed)

      ; stats modifiers
      obj.setProp tid %id att_mod 0
      obj.setProp tid %id spe_mod 0
      obj.setProp tid %id ran_mod 0

      obj.setProp tid %id on_hit $obj.getProp(Tower,$1).on_hit

      ; at killcount 15 40 70 115 150, gain a level
      obj.setProp tid %id KillCount 0
      obj.setProp tid %id Level 0

      obj.setProp tid %id Selected 0

      obj.setProp tid %id canSell 1

      obj.setProp tid %id combo_list $obj.getProp(tower,$1).combo_list

      ; default value for "Accept modifiers" property is 1
      var %accept_mod = $obj.getProp(Tower,$1).accept_modifiers
      if ($len(%accept_mod) == 0) %accept_mod = 1
      obj.setProp tid %id accept_modifiers %accept_mod

      ; remove cash
      hdec tower self.cash $obj.getProp(tower,$1).cost

      ; update collision buffer
      td.setTileState %x %y %w %h 3

      td.computeRangeSections %id

      ; put tower in the "towers in construction" list
      obj.new buildingTid %id

      ; put tower to sleep, it will be awake once the construction is 
      ; done and when a creep will walk in a region it can attack
      td.goToSleep %id

      return %id
    }
  }

  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tid
alias -l td.goToSleep {

  var %gotoSleepList = 1
  if ($2 == 0 ) %gotoSleepList = 0

  obj.delete readytid $1

  obj.setProp tid $1 sleeping 1

  if (%gotoSleepList) {

    if ($td.isTowerValidForSleepList($1)) {

      var %i = 1
      var %zonelist = $obj.tid($1).zonelist
      while ($gettok(%zonelist,%i,32)) {

        ; add the tower in the sleeplist of the zone
        obj.new $+($v1,.sleep) $1

        inc %i
      }
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tid
alias -l td.awakeTower {

  obj.new readytid $1

  obj.setProp tid $1 sleeping 0

  obj.setProp tid $1 lastAttack $td.ticks

  var %i = 1
  var %zonelist = $obj.tid($1).zonelist
  while ($gettok(%zonelist,%i,32)) {

    ; remove the tower from the sleeplist of the zone
    obj.delete $+($v1,.sleep) $1

    inc %i
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tid
; desc: check if we need to put the tower in the sleep list
alias -l td.isTowerValidForSleepList {

  if ($obj.tid($1).att_max > 0) {
    return 1
  }

  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tid
alias -l td.computeTowerLink {

  var %x1 = $obj.tid($1).centerX
  var %y1 = $obj.tid($1).centerY
  var %range = $calc($obj.tid($1).range + $obj.tid($1).ran_mod)
  var %link = $td.getLink($1)

  var %tid = $obj.getFirst(tid)
  while (%tid) {

    if (%tid != $1) {

      var %x2 = $obj.tid(%tid).centerX
      var %y2 = $obj.tid(%tid).centerY

      var %dist = $sqrt($calc((%x1 - %x2) ^ 2 + (%y1 - %y2) ^ 2))
      if (%dist <= %range) {
        td.linkTower $1 %tid
      }

      var %range2 = $calc($obj.tid(%tid).range + $obj.tid(%tid).ran_mod)
      if (%dist <= %range2) {
        td.linkTower %tid $1
      }      
    }
    %tid = $obj.getNext(tid)
  }
}


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tid, linkToTid
; desc: tid --> linkToTid
alias -l td.linkTower {

  var %tid = $1, %linkTo = $2
  obj.new $td.getLink(%tid) %linkTo 

  ; is the tower allowed to accept modifiers?
  if ($obj.tid(%linkTo).accept_modifiers) { 

    var %tower = $obj.tid(%tid).obj

    obj.incProp tid %linkTo att_mod $calc($obj.getProp(tower,%tower).attack_mod)
    obj.incProp tid %linkTo spe_mod $calc($obj.getProp(tower,%tower).speed_mod)

    var %ran_mod = $calc($obj.getProp(tower,%tower).range_mod)  
    if (%ran_mod != 0) {

      obj.incProp tid %linkTo ran_mod %ran_mod
      td.computeRangeSections %linkTo
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tid, tid_to_unlink
; desc: unlink a tower from another
alias -l td.unlinkTower {

  ; remove first link
  var %link = $td.getLink($2)
  if ($obj.exists(%link,$1)) {
    obj.delete %link $1
  }

  ; remove second link
  var %link = $td.getLink($1)
  if ($obj.exists(%link,$2)) {

    var %tower = $obj.tid($1).obj

    ; check if tower can accept modifiers
    if ($obj.tid($2).accept_modifiers) { 

      ; if the tower we're unlinking was giving bonuses, be sure to remove them
      obj.decProp tid $2 att_mod $calc($obj.getProp(tower,%tower).attack_mod)
      obj.decProp tid $2 spe_mod $calc($obj.getProp(tower,%tower).speed_mod)

      var %ran_mod = $calc($obj.getProp(tower,%tower).range_mod)
      if (%ran_mod != 0) {
        obj.decProp tid $2 ran_mod %ran_mod
        td.computeRangeSections $2
      }
    }

    obj.delete %link $2
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.getLink {
  return $+(tlink,$1)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc:
; parms: tower, x, y, w, h
alias -l td.canBuildTower {

  ; check is building position is free
  if ($td.isPositionFree($2,$3,$4,$5)) {

    ; check if we have enough cash
    if ($hget(tower,self.cash) >= $obj.getProp(tower,$1).cost) {

      return 1
    }
    else {
      var %msg = Not enough money!
      var %x = $td.centerTextX(%msg,$mouse.x,$topix($4))
      td.showFloatingText -fs %x $calc($mouse.y - 10) 500 0 %msg
    }
  }
  else {
    var %msg = Can't build there!
    var %x = $td.centerTextX(%msg,$mouse.x,$topix($4))
    td.showFloatingText -fs %x $calc($mouse.y - 10) 500 0 %msg
  }

  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc:
; parms: tid
alias -l td.computeRangeSections {

  var %id = $1

  ; this is the section where the tower belongs
  var %cx = $obj.tid(%id).centerX
  var %cy = $obj.tid(%id).centerY

  var %bx = $td.build.x
  var %by = $td.build.y
  var %bw = $td.build.w
  var %bh = $td.build.h

  var %zone = $td.getZoneKey(%cx,%cy)

  var %zonelist = %zone
  var %range = $calc($obj.tid(%id).range + $obj.tid(%id).ran_mod)

  var %step = %zone.w
  if (%step > %zone.h) %step = %zone.h

  var %r = 0, %2pi = 2 * $pi
  while (%r < %2pi) {

    var %ok = 1
    var %len = %step
    while (%ok) {

      if (%len > %range) {
        %len = %range
        %ok = 0
      }

      var %x = $floor($calc(%cx + $cos(%r) * %len)))
      var %y = $floor($calc(%cy + $sin(%r) * %len)))

      if ($inrect(%x,%y,%bx,%by,%bw,%bh)) {

        var %zone = $td.getZoneKey(%x,%y)
        if (!$pos(%zonelist, %zone)) {
          %zonelist = %zonelist %zone
        }
      }

      inc %len %step
    }
    inc %r .4
  }

  obj.setProp tid %id zonelist %zonelist 
}

alias loaddebug {

  td.buildTower arrow1 6 7
  td.buildTower arrow1 8 7
  td.buildTower arrow1 10 7
  td.buildTower arrow1 12 7
  td.buildTower arrow1 14 7
  td.buildTower arrow1 16 7
  td.buildTower arrow1 22 7
  td.buildTower arrow1 24 7
  td.buildTower arrow1 26 7
  td.buildTower arrow1 28 7
  td.buildTower arrow1 30 7
  td.buildTower arrow1 30 9
  td.buildTower arrow1 28 11
  td.buildTower arrow1 26 13
  td.buildTower arrow1 24 15
  td.buildTower arrow1 22 15
  td.buildTower arrow1 16 19
  td.buildTower arrow1 14 21
  td.buildTower arrow1 12 23
  td.buildTower arrow1 10 25
  td.buildTower arrow1 8 25
  td.buildTower arrow1 6 27
  td.buildTower arrow1 6 29
  td.buildTower arrow1 8 29
  td.buildTower arrow1 10 29
  td.buildTower arrow1 12 29
  td.buildTower arrow1 14 29
  td.buildTower arrow1 16 29
  td.buildTower arrow1 22 29
  td.buildTower arrow1 24 29
  td.buildTower arrow1 26 29
  td.buildTower arrow1 28 29
  td.buildTower arrow1 30 29
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: x, y
; desc: return the tile state for specified tile
;         null: Nothing on the tile
;            1: Tile is in a region
;            2: not used
;            3: There is a building on the tile
alias -l td.getTileState {
  return $hget(tower,$+(map.,$1,.,$2))
}

alias -l td.showError {
  echo -a *** Error : $1-
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: x, y, w, h 
; desc: return 1 if building is possible on that tile
alias -l td.isPositionFree {
  var %x = 0
  while (%x < $3) {
    var %y = 0
    while (%y < $4) {
      if ($hget(tower,$+(map.,$calc(%x + $1),.,$calc(%y + $2)))) {
        return 0
      }
      inc %y
    }
    inc %x
  }
  return 1
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: x, y, w, h, state
; desc: set the tile state
;        State  Description
;         null: Nothing on the tile
;            1: Tile is in a region
;            2: not used
;            3: There is a building on the tile
alias -l td.setTileState {
  var %x = 0
  while (%x < $3) {
    var %y = 0
    while (%y < $4) {
      hadd tower $+(map.,$calc(%x + $1),.,$calc(%y + $2)) $5-
      inc %y
    }
    inc %x
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: tid
; desc: return percentage of constructions progression for $1 tower
alias -l td.getBuildProgress {

  var %timestart = $obj.tid($1).buildStart
  var %buildtime = $obj.tid($1).buildTime
  var %done = $calc(($td.ticks - %timestart) / %buildtime)

  if (%done > 1) %done = 1
  return %done
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: tid
; desc: update the building progress for the tower
alias -l td.updateBuildProgress {

  var %done = $td.getBuildProgress($1)
  var %oldpos = $obj.tid($1).imgpos
  var %newpos = $int($calc(%done * 5))
  var %x = $obj.tid($1).screenX
  var %y = $obj.tid($1).screenY

  ; check if tower image has to change
  if (%oldpos != %newpos) {

    obj.setProp tid $1 imgpos %newpos

    ; update image in buffer
    td.drawTid @tbuf $1 %x %y
  }

  ; construction done?
  if (%done >= 1) {

    var %obj = $obj.tid($1).obj
    var %w = $obj.tid($1).screenW
    var %h = $obj.tid($1).screenH

    if ($td.triggerEvent(onBuildFinish,%obj,%x,%y,%w,%h)) {

      obj.delete buildingTid $1
      obj.setProp tid $1 complete 1

      td.computeTowerLink $1
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: text, x, width
; desc: return the centered x value for a width
alias -l td.centerTextX {

  return $calc($2 + ($3 - $width($1,%td.font,12)) / 2)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: text, y, height
; desc: return the centered y value for an height
alias -l td.centerTextY {

  return $calc($2 + ($3 - $height($1,%td.font,12)) / 2)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: return 1 if the mouse cursor is in the building zone
alias -l td.InBuildZone {

  return $inrect($mouse.x,$mouse.y,$td.Build.X(),$td.Build.Y(),$td.Build.W(),$td.Build.H())
}

alias -l td.Build.X return 0
alias -l td.Build.Y return 32
alias -l td.Build.W return 640
alias -l td.Build.H return 512

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
alias -l td.path {
  return $shortfn($+(%td.map_path,$1))
}

alias -l td.gfx {
  return $shortfn($+($scriptdirgfx\,$1))
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: return the currently loaded map
alias -l td.map return $hget(tower,__currentmap)
alias -l td.level return $hget(tower,game.level)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: draw regions defined in map... mostly used for debug 
alias -l td.drawRegions {

  var %name = $obj.getFirst(region)
  while (%name) {

    var %x = $obj.getProp(region,%name).x
    var %y = $obj.getProp(region,%name).y
    var %w = $obj.getProp(region,%name).width
    var %h = $obj.getProp(region,%name).height

    drawrect -nr @tower $rgb(255,0,0) 1 $topix(%x) $topix(%y) $topix(%w) $topix(%w)
    drawtext -nr @tower $rgb(255,0,0) %td.font 10 $calc($topix(%x)+2) $topix(%y) $obj.getprop(region,%name).desc 
    %name = $obj.getNext(region)
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: @window
; desc: render all the floating text
alias -l td.renderFloatingText {

  var %id = $obj.getFirst(ftext)
  while (%id) {

    ; check is it's a delayed floating text
    var %s = $obj.getProp(ftext,%id).start
    if ($td.ticks >= %s) {

      var %n = $calc($td.ticks - %s)
      var %dur = $obj.getProp(ftext,%id).duration

      ; text is still visible?
      if (%n < %dur) {

        ; % completed
        var %p = $calc(%n / %dur)
        if (%p > 1) %p = 1

        var %x = $obj.getProp(ftext,%id).x

        ; small sin effect
        if ($obj.getProp(ftext,%id).sin) {
          %x = $calc(%x + $sin($calc(%p * 6)) * 5)
        }

        var %y = $obj.getProp(ftext,%id).y 
        ; move text up
        if ($obj.getProp(ftext,%id).flying) {
          %y = $calc(%y - %p * 30)
        }

        var %text = $obj.getProp(ftext,%id).text

        drawtext -nor @tower $rgb(0,0,0) %td.font 12 $calc(%x + 1) $calc(%y + 1) %text
        drawtext -nor @tower %td.white %td.font 12 %x %y %text
      }
      else {
        ; since text is not visible anymore, flush it from memory...
        obj.delete ftext %id
      }
    }
    %id = $obj.getNext(ftext)
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 
alias td.populateComboList {

  hdel tower combosel 
  obj.clear combo_list

  var %n = 0
  var %combo = $obj.getFirst(combo)
  while (%combo) {

    var %require = $remove($obj.getProp(combo,%combo).require,$chr(32))

    ; for each selected towers
    var %tid = $obj.getFirst(seltid)
    while (%tid) {

      ; check if the tower is in the combo requirement
      var %tower = $obj.tid(%tid).obj    
      if ($istok(%require,%tower,44)) {
        %require = $remtok(%require,%tower,44)
        %tid = $obj.getNext(seltid)
      }
      else {

        %require = invalid_combo
        %tid = 0
      }
    }

    if (!%require) {
      obj.new combo_list %combo
      inc %n
    }

    %combo = $obj.getNext(combo)
  }
  return %n
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: switch, x, y, duration, delay, message
; desc: create a floating text at specified position
;       switch : -f (flying)
;                -s (sin effect)
;                -n (nothing)
;                -x (ignore option 'show floating msg')
alias -l td.showFloatingText {

  if ($hget(tower,show.msg) || $pos($1,x) > 0) {

    ; generate a random id

    var %id = $obj.getNextId(ftext)

    obj.new ftext %id

    obj.setProp ftext %id x $2
    obj.setProp ftext %id y $3
    obj.setProp ftext %id duration $4
    obj.setProp ftext %id start $calc($td.ticks + $5)
    obj.setProp ftext %id text $6-

    if ($pos($1,f) > 0) obj.setProp ftext %id flying 1
    else obj.setProp ftext %id flying 0

    if ($pos($1,s) > 0) obj.setProp ftext %id sin 1
    else obj.setProp ftext %id sin 0
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: draw the mouse cursor
alias -l td.drawMouse {

  if ($td.inBuildZone()) {

    var %tool = $hget(tower,tool.sel)
    if (%tool) {

      var %toolX = $tool.x
      var %toolY = $tool.y
      var %toolW = $tool.w 
      var %toolH = $tool.h

      var %canBuild = $td.isPositionFree(%toolX,%toolY,%toolW,%toolH)
      if (%canBuild) {

        var %range = $obj.getProp(Tower,%tool).range 

        var %tmpx = $calc($align($mouse.x) + ($topix(%toolw) / 2) - %range) 
        var %tmpy = $calc($align($mouse.y) + ($topix(%toolh) / 2) - %range) 
        drawrect -rne @tower $rgb(0,0,0) 1 %tmpx %tmpy $calc(2 * %range) $calc(2 * %range)
        drawrect -nr @tower $rgb(0,0,0) 1 $align($mouse.x) $align($mouse.y) $topix(%toolW) $topix(%toolH)
      }
      else { 

        ; occupied position
        var %x = $align($mouse.x)
        var %y = $align($mouse.y)
        var %w = $topix($tool.w)
        var %h = $topix($tool.h)
        drawrect -nr @tower $rgb(255,0,0) 1 %x %y %w %h

        drawline -nr @tower $rgb(255,0,0) 1 %x %y $calc(%x + %w - 1) $calc(%y + %h - 1)
        drawline -nr @tower $rgb(255,0,0) 1 $calc(%x + %w - 1) %y %x $calc(%y + %h - 1)
      }
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: tool's coords
alias -l tool.x return $totile($align($mouse.x))
alias -l tool.y return $totile($align($mouse.y))
alias -l tool.w return $hget(tower,tool.w)
alias -l tool.h return $hget(tower,tool.h)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: redraw all the towers on the buffer
alias -l td.updateTowerBuffer {

  td.clearBuffer
  var %tid = $obj.getFirst(tid)
  while (%tid) {
    td.drawTid @tbuf %tid $obj.tid(%tid).ScreenX $obj.tid(%tid).ScreenY
    %tid = $obj.getNext(tid)
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: clear the buffer
alias -l td.clearBuffer {

  drawpic -cnr @tbuf 0 0 $td.path($obj.getProp(map,$td.map()).background)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: @window, tid, x, y
alias -l td.drawTid {

  var %img = $obj.tid($2).img

  var %x = $3
  var %y = $4
  var %w = $obj.tid($2).screenW
  var %h = $obj.tid($2).screenH

  if (%img) {
    var %pos = $obj.tid($2).imgpos
    drawpic -ctn $1 $rgb(255,0,255) %x %y $calc(48 + %w * %pos) 0 %w %h %img
  }
  else {

    drawrect -rnf $1 $rgb(0,0,0) 1 %x %y %w %h

    drawtext -nr $1 %td.white %td.font 12 $calc(%x + 5) $calc(%y + 1) No 
    drawtext -nr $1 %td.white %td.font 12 $calc(%x + 5) $calc(%y + 13) Img
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: @window, tower, x, y
alias -l td.drawTowerTool {

  var %tower = $2
  var %x = $3, %y = $4
  var %w = $obj.getProp(tower,%tower).ScreenW
  var %h = $obj.getProp(tower,%tower).ScreenH
  var %img = $obj.getProp(tower,%tower).img

  if (%img) {
    drawpic -ctn $1 $rgb(255,0,255) %x %y $calc(48 + 5 * %w) 0 %w %h %img
  }
  else {
    drawrect -rnf $1 $rgb(0,0,0) 1 %x %y %w %h
    drawtext -nr $1 %td.white %td.font 12 $calc(%x + 5) $calc(%y + 1) No 
    drawtext -nr $1 %td.white %td.font 12 $calc(%x + 5) $calc(%y + 13) Img
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
alias -l align return $calc($1 - $1 % 16)

alias -l toTile2 return $calc($align($1) / 16)
alias -l toTile return $int($calc($1 / 16))

alias -l topix return $calc($1 * 16)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 
alias -l td.drawMenus {

  var %rgb = %td.white

  var %gold = $hget(tower,self.cash)
  drawtext -nr @tower %rgb %td.font 14 75 6 $td.goldToText(%gold)

  drawtext -nr @tower %rgb %td.font 14 375 6 Next: $td.getTimeBeforeNextLevel()
  drawtext -nr @tower %rgb %td.font 14 448 6 Round: $td.level()
  drawtext -nr @tower %rgb %td.font 14 536 6 Lives: $hget(tower,self.lives)

  if ($inrect($mouse.x,$mouse.y,305,580,32,32)) {
    if ($td.onMouseClick(1)) {
      td.setPause 1
    }
  }

  drawpic -cnt @tower $rgb(255,0,255) 305 580 $td.gfx(pause.bmp)

  if ($hget(tower,debug)) {
    drawtext -nr @tower %rgb %td.font 12 150 2 fps( $+ $int($hget(tower,fps.fps)) $+ ) cid( $+ $obj.count(cid) $+ ) ftxt( $+ $obj.count(ftext) $+ ) $mouse.x $mouse.y 
    drawtext -nr @tower %rgb %td.font 12 150 15 rdy( $+ $obj.count(readytid) $+ ) bld( $+ $obj.count(buildingtid) $+ ) tmr( $+ $timer(0) $+ ) hpmod( $+ $td.getLvlHpModifier() $+ )
  }

  var %toolcount = $hget(tower,toolset.count) 
  if (%toolcount > 0) {

    drawcopy -nt @toolset $rgb(255,0,255) 0 0 $calc(%toolcount * 48) 48 @tower 20 575

    var %tool = $hget(tower,tool.sel)
    if (%tool) {

      var %pos = $obj.getProp(tool,%tool).pos
      if ($len(%pos)) {
        drawrect -nr @tower %td.white 2 $calc(20 + %pos * 48) 575 48 48
        drawrect -nr @tower $rgb(0,0,0) 1 $calc(20 + %pos * 48) 575 48 48
      }
    }

    if ($inrect($mouse.x,$mouse.y,20,575,$calc(%toolcount * 48),48)) {

      var %pos = $int($calc(($mouse.x - 20) / 48 + 1))
      var %tool = $replace($gettok($obj.getProp(map,$td.map).toolset,%pos,44),$chr(32),$null)
      td.showTowerInfo %tool 230 460

      if ($td.onMouseClick(1)) {
        td.selTool %tool
      }
    }
  }

  var %creep = $hget(tower,spawnCreep.objId)

  drawtext -nor @tower %td.white %td.font 16 375 574 Round info ( $+ $td.getTextDiffLevel $+ )
  drawtext -nr @tower %td.white %td.font 12 375 592 $obj.getProp(creep,%creep).name ( $+ $calc($obj.getProp(creep,%creep).basehp + $td.getLvlHpmodifier()) hp $+ )
  drawtext -nr @tower %td.white %td.font 12 375 606 > $obj.getProp(creep,%creep).desc
}

alias -l td.getTextDiffLevel {

  var %lvl = $hget(tower,game.dlvl)
  if (%lvl == 1) return Very easy
  elseif (%lvl == 2) return Easy
  elseif (%lvl == 3) return Medium
  elseif (%lvl == 4) return Hard
  elseif (%lvl == 5) return Very hard
  elseif (%lvl == 6) return Impossible
  else { return Undefined }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parms: tower, x, y 
alias -l td.showTowerInfo {

  var %tool = $1
  var %desc = $obj.getProp(tower,%tool).desc
  if (!%desc) %desc = No description
  var %name = $obj.getProp(tower,%tool).name $chr(91) $td.goldToText($obj.getProp(tower,%tool).cost) $chr(93)

  var %texth = $height(X,%td.font,12)

  var %maxw = 300
  var %linenum = $wrap(%desc,$noqt(%td.font),12,%maxw,1,0)

  var %rectx = $2
  var %recty = $3
  var %rectw = 300
  var %recth = $calc(%texth * (5 + %linenum) + 8)

  ; keep the rect on screen  
  if ($calc(%rectx + %rectw) > 640) %rectx = $calc(640 - %rectw)
  if ($calc(%recty + %recth) > 640) %recty = $calc(640 - %recth)

  drawrect -nrf @tower %td.white 1 %rectx %recty %rectw %recth
  drawrect -nr @tower 0 1 %rectx %recty %rectw %recth

  drawtext -ron @tower $rgb(0,0,0) %td.font 16 $calc(%rectx + 5) $calc(%recty + 2) %name
  inc %recty 4

  var %chunk
  var %texty = $calc(%recty + 16)
  var %textx = $calc(%rectx + 5)
  var %textw = $width(%desc,$noqt(%td.font),12)
  var %i = 1
  while (%i <= %linenum) {
    %chunk = $wrap(%desc,$noqt(%td.font),12,%maxw,1,%i)
    if (%chunk) {
      drawtext -nr @tower $rgb(0,0,0) %td.font 12 %textx %texty %chunk
    }
    inc %texty %texth
    inc %i
  }  

  drawtext -npr @tower 0 %td.font 12 %textx $calc(%texty + %texth * 0) Attack: $obj.getProp(tower,%tool).attack
  drawtext -npr @tower 0 %td.font 12 %textx $calc(%texty + %texth * 1) Range: $obj.getProp(tower,%tool).range
  drawtext -npr @tower 0 %td.font 12 %textx $calc(%texty + %texth * 2) Speed: $obj.getProp(tower,%tool).speed
  drawtext -npr @tower 0 %td.font 12 %textx $calc(%texty + %texth * 3) Max target: $obj.getProp(tower,%tool).maxtarget
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.getTimeBeforeNextLevel {

  if (!$obj.count(cid)) {
    var %t = $ceil($calc(($hget(tower,game.nextlevel) - $td.ticks) / 1000)) 
    if (%t < 0) %t = 0
  }
  else {
    var %t = 0
  }

  return %t
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: none
; desc: send next level
alias -l td.nextLevel {

  if ($td.cacheShortestPath()) {

    .timerCidEff* off
    set %td.timerId 0

    hinc tower game.level

    var %level = $hget(tower,game.level)
    if (%level <= $obj.getProp(map,$td.map).maxLevel) {

      var %list = $obj.getProp(map,$td.map).levels
      var %count = $numtok(%list,44)

      var %tmplevel = $calc(((%level - 1) % %count) + 1)
      var %mylevel = $gettok(%list,%tmplevel,44)

      var %num = $calc($gettok(%mylevel,1,32))
      var %creep = $remove($gettok(%mylevel,2,32),$chr(32))

      td.spawn %num %creep $remove($gettok($obj.getProp(map,$td.map).WalkPath,1,44),$chr(32))

      td.triggerEvent onNextLevel %level

      ; you dont get interest for the first level... 
      if (%level > 1) {

        ; do we get any interest?
        var %interest = $int($calc($hget(tower,self.cash) * $obj.getProp(map,$td.map).interest_rate))
        if (%interest > 0) {

          var %last_region = $remove($gettok($obj.getProp(map,$td.map).WalkPath,-1,44),$chr(32))
          if (%last_region) {

            ; show the interest message over the last region
            var %cx = $calc($obj.getProp(region,%last_region).CenterX - 30)
            var %cy = $calc($obj.getProp(region,%last_region).CenterY - 50)

            td.showFloatingText -fs %cx %cy 1500 0 Interest $td.goldToText(%interest)
          }

          td.addGain %interest
        }
      }

      td.playSound $td.path($obj.getProp(map,$td.map).sfxLevelStart)
    }
    else {

      ; you win
      td.jumpToScene win
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: cashAmount
alias -l td.addGain {

  hinc tower self.gain $1
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: nb_of_creeps obj_id region
; desc: spawn the creeps for current level
alias -l td.spawn {

  if ($obj.exists(creep,$2)) {

    hadd tower spawnCreep.n $1
    hadd tower spawnCreep.objId $2
    hadd tower spawnCreep.rid $3
  }
  else {
    td.showError Creep type $+(',$2,') doesn't exists
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: obj_id region
alias -l td.spawnCid {

  var %obj = $1

  if (%obj) {

    var %region = $2

    var %id = $obj.getNextId(cid)

    obj.new cid %id

    obj.setProp cid %id obj $1

    obj.setProp cid %id name $obj.getProp(creep,%obj).name
    obj.setProp cid %id desc $obj.getProp(creep,%obj).desc
    obj.setProp cid %id hp $obj.getProp(creep,%obj).hp

    obj.setProp cid %id gold $td.getRandIntVal($obj.getProp(creep,%obj).gold)

    obj.setProp cid %id speed $obj.getProp(creep,%obj).speed
    obj.setProp cid %id armor $obj.getProp(creep,%obj).armor

    obj.setProp cid %id spe_mod 0

    obj.setProp cid %id x 0
    obj.setProp cid %id y 0
    obj.setProp cid %id w $obj.getProp(creep,%obj).width
    obj.setProp cid %id h $obj.getProp(creep,%obj).height

    obj.setProp cid %id centerX $cid.getCenterX(%id) 
    obj.setProp cid %id centerY $cid.getCenterY(%id) 

    var %img = $obj.getProp(creep,%obj).img    

    if (%img) {
      %img = $td.path(%img)
      obj.setProp cid %id img %img
      obj.setProp cid %id imgnb $calc($pic(%img).width / $obj.cid(%id).w / 3)
    }
    else {
      obj.setProp cid %id imgnb 0
    }

    obj.setProp cid %id imglu $td.ticks
    obj.setProp cid %id imgpos 0

    var %hp = $calc($obj.getProp(creep,%obj).basehp + $td.getLvlHpModifier()) 
    obj.setProp cid %id hp %hp
    obj.setProp cid %id maxhp %hp

    obj.setProp cid %id lifeCost $obj.getProp(creep,%obj).lifeCost

    obj.setProp cid %id sfxdie $obj.getProp(creep,%obj).sfxDie

    obj.setProp cid %id type $obj.getProp(creep,%obj).type

    td.WalkCreepTo %id %region
    td.moveCreepToRegion %id %region
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.getLvlHPModifier {

  return $int($calc(($hget(tower,game.level) + 2) ^ $hget(tower,game.diff)))
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: interval values. 
; desc: Example: 4-6 will return a number between 4 and 6
alias -l td.getRandIntVal {

  tokenize 45 $1

  var %min = $1
  var %max = $2
  if (%max < %min) %max = %min

  return $r(%max,%min)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: move creep to specified region
; param: cid, region 
alias -l td.moveCreepToRegion {

  var %cid = $1
  var %region = $2

  if ($obj.getProp(region,%region).exists) {

    ; region coords
    var %rx = $topix($obj.getProp(region,%region).x)
    var %ry = $topix($obj.getProp(region,%region).y)
    var %rw = $topix($obj.getProp(region,%region).width)
    var %rh = $topix($obj.getProp(region,%region).height)

    ; creep size
    var %cw = $obj.cid(%cid).w
    var %ch = $obj.cid(%cid).h

    ; center creep in the region
    var %x = $calc(%rx + (%rw - %cw) / 2)
    var %y = $calc(%ry + (%rh - %ch) / 2)

    td.moveCreep %cid %x %y    
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; param: cid, x, y 
alias -l td.moveCreep {

  obj.setProp cid $1 x $2
  obj.setProp cid $1 y $3
  obj.setProp cid $1 centerX $calc($2 + $obj.cid($1).w / 2)
  obj.setProp cid $1 centerY $calc($3 + $obj.cid($1).h / 2)

  td.updateCidZone $1

  var %on_effect = $obj.cid($1).on_effect
  if (%on_effect) {

    var %damage_per_second = $calc($obj.cid($1).damage_per_second)
    if (%damage_per_second > 0) {

      var %lastDamage = $obj.cid($1).last_dmg
      if ($calc($td.ticks - %lastDamage) >= 1000) {
        td.damageCreep %on_effect $1 %damage_per_second
        obj.setProp cid $1 last_dmg $td.ticks
      }
    }
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.updateCidZone {

  var %oldzone = $obj.cid($1).zone
  var %newzone = $td.getZoneKey($obj.cid($1).CenterX, $obj.cid($1).CenterY)

  if (%oldzone != %newzone) {

    if (%oldzone) obj.delete %oldzone $1
    obj.new %newzone $1

    ; awake every sleeping tower in the zone
    var %sleeplist = $+(%newzone,.sleep)
    var %tid = $obj.getFirst(%sleeplist)
    while (%tid) {

      ; only awake tower if it's done building
      if ($obj.tid(%tid).complete) {
        td.awakeTower %tid
      }

      %tid = $obj.getNext(%sleeplist)
    }

    obj.setProp cid $1 zone %newzone
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.getZoneKey {

  ; $td.build.x $td.build.y

  return $+(zone.,$int($calc($1 / %zone.w)),.,$int($calc(($2 - 32) / %zone.h)))
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
alias -l td.cacheShortestPath {
  return 1
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
alias -l td.onMouseClick {

  if ($hget(tower,mouse.sclick)) {
    if ($hget(tower,mouse.key) & $1) {
      return 1
    }
  }
  return 0
}

alias -l td.onMouseUclick {

  if ($hget(tower,mouse.uclick)) {
    return 1
  }
  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
menu @tower {
  sclick {
    hadd tower mouse.sclick $td.ticks
    hadd tower mouse.key $mouse.key
  }
  rclick {
    hadd tower mouse.sclick $td.ticks
    hadd tower mouse.key $mouse.key 
  }
  uclick {
    hadd tower mouse.uclick $td.ticks
    hadd tower mouse.key $mouse.key
  }
  leave {
    hadd tower mouse.uclick $td.ticks
    hadd tower mouse.key $mouse.key
    hdel tower sel.active
  }
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.mouseDown {

  var %r = 0
  if ($mouse.key & $1) {
    if ($inrect($mouse.x,$mouse.y,0,0,640,640)) {
      %r = 1
    }
  }
  return %r
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
on *:close:@tower:{

  td.unload
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
on *:keydown:@tower:*:{

  var %scene = $hget(tower,scene)

  if (%scene == game) td.handleKeydownGame

  ; only trigger event if the console is not visible
  if (!$hget(tower,__console.show)) {
    td.triggerEvent onKeypress $keychar
  }  
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.handleKeydownGame {

  if ($keyval == 13) {

    ; is console already open?
    if ($hget(tower,__console.show)) {
      td.hideConsole
    }
    else {
      td.showConsole
    }
  }
  else {

    if ($hget(tower,__console.show)) {
      if ($keyval == 27) td.hideConsole
      else td.handleConsoleKeypress 
    }
    else {

      if ($keyval == 27 || $keyval == 32) { 
        hadd tower tool.sel 0
        td.clearSelection
      }
    }
  }
}


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; parm: gold amount
; desc: format gold to display on screen
alias -l td.goldToText return $+($int($1),$chr(36))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: return 1 if it generates a collision
; param: x, y, cid
alias -l td.creepCollide {

  var %w = $obj.cid($3).w
  var %h = $obj.cid($3).h

  var %x = $calc($1 + (%w / 2))
  var %y = $calc($2 + (%h / 2))

  if ($td.getTileState($totile(%x),$totile(%y)) == 3) return 1

  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; desc: return region where creep is standing
; param: x y
alias -l td.getRegionIdByCoords {

  var %state = $td.getTileState($totile($1),$totile($2))

  if ($int(%state) == 1) {
    return $gettok(%state,2,32)
  }
  return 0
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; open the in-game console
alias -l td.showConsole {

  hdel tower __console.text
  hadd tower __console.show 1
  hadd tower __console.cur $calc($hget(tower,__console.count) + 1)
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; hide the in-game console
alias -l td.hideConsole {

  var %text = $hget(tower,__console.text)
  if ($len(%text) > 0) {

    ; send the text to the event
    %text = $replace(%text,$chr(160),$chr(32))
    hinc tower __console.count
    hadd tower $+(__console.,$hget(tower,__console.count)) %text
    td.triggerEvent onConsoleInput %text
  }

  hdel tower __console.text
  hadd tower __console.show 0 
}

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
alias -l td.handleConsoleKeypress {

  var %text = $hget(tower,__console.text)

  ; handle backspaces
  if ($keyval == 8) {
    if ($len(%text) > 0) {
      if ($len(%text) == 1) var %text
      else %text = $mid(%text,1,$calc($len(%text) - 1))
    }
  }  ; handle keyup 
  elseif ($keyval == 38) {
    var %n = $calc($hget(tower,__console.cur) - 1)
    if (%n > 0) {
      %text = $hget(tower,$+(__console.,%n))
      hadd tower __console.cur %n
    }
  } ; handle keydown
  elseif ($keyval == 40) {
    var %n = $calc($hget(tower,__console.cur) + 1)
    if (%n <= $hget(tower,__console.count)) {
      %text = $hget(tower,$+(__console.,%n))
      hadd tower __console.cur %n
    }
    elseif ($abs($calc(%n - $hget(tower,__console.count))) == 1) {
      var %text
      hadd tower __console.cur %n
    }
  }
  else {
    if ($keychar isalnum || $keyval == 32) {
      if ($len(%text) < 55) {
        %text = $+(%text,$replace($keychar,$chr(32),$chr(160)))
      }
    }
  }
  hadd tower __console.text %text
}

alias -l td.ticks return $hget(tower,ticks)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Omg, you have reach the end of the file. ;
; Thank you for playing this game, I hope  ;
; you enjoyed it. Remember kids, this game ; 
; is rippable friendly!                    ;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;