remstart
   Info on type _Bot:
 
   x#: X Position
   y#: Y Position
   team: Bot's team number
   aim#: Aiming direction
   walk#: Walking direction
   score: Player score
   life: Bot's life amount left
   t: Bot's target: 0 for main target, greater than 0 for another bot's ID
   tb: If target is main target, tb is main target's ID
   rstart: Time (in milisec) when reload started.
   bullets: Number of bullets in magazine.
   shoot: Time shot started.
   mode: 0 for attacking, 1 for defending.
   offset#: Angle offset when walking and turning arround in defend mode.
   form: Formation mode - 0=none, 1=leader of formation, 2=in formation, but not the leader
   formx#: If 'form' is set to 2, then 'formx#' is bot's X position in the formation.
   formy#: If 'form' is set to 2, then 'formy#' is bot's Y position in the formation.
   leader: If 'form' is set to 2, then 'leader' is bot's formation leader.
   skill#: When a bot hits its target, his skill is growing. It is used as a factor when calculating if shot will hit or not.
 
   Info on type _Team:
 
   color: Team's color
   score: Team's total score
   x#: Team base X position
   y#: Team base Y position
   Bots#: Number of team's bots; Float value because it's needed for later calculations.
   time: Value of timer when started (if 0, then no timer)
remend
 
type _Bot
   x#=0
   y#=0
   team=1
   aim#=0
   walk#=0
   score=0
   life=100
   t=0
   tb=0
   rstart=0
   bullets=10
   shoot=0
   mode=0
   offset#=0
   form=0
   formx#=0
   formy#=0
   leader=0
   skill#=1
endtype
 
type _Team
   color=0
   score=0
   x#=0
   y#=0
   Bots#=6
   time=0
   BotCam=0
endtype
 
global win_delay=5, attacking_team=1, formation=0
global l_team=1
global cam_team=1 :`Current team for cam 1
global _cam=0 :`Current camera
global l_cam=0 :`Last camera - used for camera changing
global r_wait=2000 :`Time to wait to finish reloading (in milisec)
global top_min=4 :`Bitmap minimize factor
global _top_h,_top_w,_top_x,_top_y,_m=1
global key_min=0,key_plus=0,key_tab=0
global dim key_num(3)
global dim Bots(12) as _Bot
global dim Teams(2) as _Team
 
set display mode 800,600,16
if check display mode(1024,768,16)=1
   set display mode 1024,768,32
else
   if check display mode(800,600,16)=1
      set display mode 800,600,16
   else
      if check display mode(640,480,16)=1
         set display mode 640,480,16
      endif
   endif
endif
 
sync on : sync rate 100
backdrop on
color backdrop rgb(200,200,200)
hide mouse
 
set text font "arial"
set text size 14
set text to bold
set text transparent
 
sync : center text screen width()/2, screen height()/2,"Loading..." : sync
 
set text size 20
 
set current camera 0
autocam off
 
gosub _load
gosub _setup
 
do
   gosub _ai_calc
   gosub _check_keyboard
   gosub _print_stats
   gosub _render_fps
   gosub _render_topview
   sync
loop
 
end
 
_ai_calc:
 
   for i=1 to array count(Bots(0))
      if Bots(i).life>0
         if Bots(i).t>0 :`Target is another bot
            if Bots(Bots(i).t).team=Bots(i).team :`Check target is within the other team
               Bots(i).t=0
            else
               if Bots(Bots(i).t).life>0
                  if mode=0
                     ChaseBot(i,Bots(i).t)
                  else
                     if GetDist(Bots(Bots(i).t).x#,Bots(Bots(i).t).y#,Teams(Bots(i).tb).x#,Teams(Bots(i).tb).y#)<300
                        xm#=(Bots(Bots(i).t).x#+Teams(Bots(i).tb).x#)/2
                        ym#=(Bots(Bots(i).t).y#+Teams(Bots(i).tb).y#)/2
                        if Bots(i).x#>xm#-16 and Bots(i).x#<xm#+16 and Bots(i).y#>ym#-16 and Bots(i).y#<ym#+16
                           ChaseBot(i,Bots(i).t)
                        else
                           AdvancePos(i,xm#,ym#)
                        endif
                     else
                        ChaseBot(i,Bots(i).t)
                     endif
                  endif
               else
                  Bots(i).t=0
               endif
            endif
         else :`Target is main target
            AdvanceToMainTarget(i)
         endif
      endif
   next i
   CheckTimer()
 
   return
 
_check_keyboard:
 
   l_team=cam_team
   l_cam=_cam
 
   if keystate(15)=1 :`Tab key
      if key_tab=0
         attacking_team=3-attacking_team :`Swap teams
         Teams(1).time=0
         Teams(2).time=0
         gosub _setup
      endif
      key_tab=1
   else
      key_tab=0
   endif
 
   if keystate(74)=1 :`Numpad minus key
      if top_min<8 and key_min=0
         inc top_min
         gosub _set_bitmap_pos
      endif
      key_min=1
   else
      key_min=0
   endif
 
   if keystate(78)=1 :`Numpad plus key
      if top_min>1 and key_plus=0
         dec top_min
         gosub _set_bitmap_pos
      endif
      key_plus=1
   else
      key_plus=0
   endif
 
   if keystate(11)=1 :`0 key
      if l_cam<>0 and key_num(1)=0
         l_cam=_cam
         _cam=0
      endif
      key_num(1)=1
   else
      key_num(1)=0
   endif
   if keystate(2)=1 :`1 key
      if key_num(2)=0
         if l_cam<>1
            l_cam=_cam
            _cam=1
         endif
         cam_team=1
         if l_team=cam_team
            inc Teams(1).BotCam
            if Teams(1).BotCam>int(Teams(1).Bots#) then dec Teams(1).BotCam,int(Teams(1).Bots#)
         endif
      endif
      key_num(2)=1
   else
      key_num(2)=0
   endif
   if keystate(3)=1 :`2 key
      if key_num(3)=0
         if l_cam<>1
            l_cam=_cam
            _cam=1
         endif
         cam_team=2
         if l_team=cam_team
            inc Teams(2).BotCam
            if Teams(2).BotCam>int(Teams(2).Bots#) then dec Teams(2).BotCam,int(Teams(2).Bots#)
         endif
      endif
      key_num(3)=1
   else
      key_num(3)=0
   endif
 
   return
 
_print_stats:
 
   `Print FPS rate
   ink rgb(0,0,128),0
   text 10,10,"FPS: "+str$(screen fps())
 
   for i=1 to array count(Teams(0))
      ink Teams(i).color,0
      text 10,screen height()-20-(array count(Teams(0))-i)*15,"Team "+str$(i)+": "+str$(Teams(i).score)
   next i
   ink rgb(0,0,128),0
   for i=1 to array count(Bots(0))
      if timer()<Bots(i).rstart+r_wait
         r_str$="  [ Reloading ]"
      else
         r_str$=""
      endif
      text 10,50+i*15, "Bot "+str$(i)
      text 70,50+i*15, "Life: "+str$(Bots(i).life)
      text 150,50+i*15, "Target: "+str$(Bots(i).t)
      text 230,50+i*15, r_str$
   next i
 
   return
_render_topview:
 
   for i=1 to array count(Teams(0))
      ink Teams(i).color,Teams(i).color
      box _top_x+(int(Teams(i).x#)-16)/top_min,_top_y+(int(Teams(i).y#)-16)/top_min,_top_x+(int(Teams(i).x#)+16)/top_min,_top_y+(int(Teams(i).y#)+16)/top_min
   next i
 
   for i=1 to array count(Bots(0))
      if Bots(i).life>0
         ink Teams(Bots(i).team).color,Teams(Bots(i).team).color
         circle _top_x+(int(Bots(i).x#))/top_min,_top_y+(int(Bots(i).y#))/top_min,16/top_min
         line _top_x+(int(Bots(i).x#))/top_min,_top_y+(int(Bots(i).y#))/top_min,_top_x+(int(Bots(i).x#+cos(Bots(i).aim#)*16))/top_min,_top_y+(int(Bots(i).y#+sin(Bots(i).aim#)*16))/top_min
      endif
   next i
 
   return
 
_render_fps:
   for i=1 to array count(Bots(0))
      if Bots(i).life>0
         position object i,Bots(i).x#,16,Bots(i).y#
         if object visible(i)=0 then show object i
      else
         if object visible(i)=1 then hide object i
      endif
   next i
 
   for i=1 to array count(Teams(0))
      j=array count(Bots(0))+i
      position object j,Teams(i).x#,16,Teams(i).y#
   next i
 
   if _cam=1
      CamFollowBot()
   else
      if _cam<>l_cam
         ResetCam()
      endif
   endif
   return
 
_setup:
   ResetCam()
   randomize timer()
   Teams(1).color=rgb(0,0,255)
   Teams(1).x#=(screen width())-128
   Teams(1).y#=rnd((screen height())-128*2)+128
   Teams(1).Bots#=6
   Teams(1).BotCam=1
   Teams(2).color=rgb(255,0,0)
   Teams(2).x#=128
   Teams(2).y#=(screen height())-Teams(1).y#
   Teams(2).Bots#=6
   Teams(2).BotCam=7
   cteam=1
   j#=1
   a#=1
 
   for i=1 to array count(Teams(0))
      j=array count(Bots(0))+i
      CheckDelObj(j)
 
      make object cube j,32
      set object cull j,0
      color object j,Teams(i).color
      position object j,Teams(i).x#,16,Teams(i).y#
   next i
 
   for i=1 to array count(Bots(0))
      if j#>Teams(cteam).Bots#
         inc cteam
         j#=1
      endif
      a#=j#/(Teams(cteam).Bots#)*360.0
      Bots(i).team=cteam
      Bots(i).x#=Teams(cteam).x#+cos(a#)*64
      Bots(i).y#=Teams(cteam).y#+sin(a#)*64
      Bots(i).t=0
 
      Bots(i).mode=(cteam=3-attacking_team)
      Bots(i).tb=3-attacking_team
      Bots(i).life=100
      if cteam=attacking_team
         if int(j#)=1 then Bots(i).form=1 else Bots(i).form=2
      endif
 
      CheckDelObj(i)
 
      make object sphere i,32
      set object cull i,0
      color object i,Teams(Bots(i).team).color
      position object i,Bots(i).x#,16,Bots(i).y#
      inc j#
   next i
 
   randomize timer()
   formation=rnd(1)
   SwitchLeader(1)
 
   return
 
_load:
   gosub _set_bitmap_pos
   create bitmap 1,10,10
   set current bitmap 1
   ink 0,0
   box 1,1,5,5
   box 6,6,10,10
   ink rgb(255,255,255),0
   box 6,1,10,5
   box 1,6,5,10
   get image 1,1,1,10,10
   set current bitmap 0
   make matrix _m,screen width(),screen height(),10,10
   prepare matrix texture _m,1,1,1
   delete bitmap 1
   sync
   return
 
_set_bitmap_pos:
 
   _top_w=screen width()/top_min
   _top_h=screen height()/top_min
   _top_x=0
   _top_y=screen height()-_top_h
 
   return
 
function ResetCam()
   scr#=sqrt( (screen width()/2)^2 + (screen height()/2)^2 )
   offsetxz=100
   offsety=80
   position camera -1*offsetxz,offsety,-1*offsetxz
   xzdist#=sqrt(2*offsetxz^2)
   yrotate camera 0,45
   xrotate camera 0,-1*int(atan( -1*offsety/(scr#+xzdist#) ))
endfunction
 
function CamFollowBot()
__retry_bot:
   b=Teams(cam_team).BotCam
   if Bots(b).life<=0
      inc Teams(cam_team).BotCam
      if Teams(cam_team).BotCam>int(Teams(cam_team).Bots#) then dec Teams(cam_team).BotCam,int(Teams(cam_team).Bots#)
      goto __retry_bot
   endif
   position camera 0,Bots(b).x#+cos(Bots(b).aim#-180)*100,16,Bots(b).y#+sin(Bots(b).aim#-180)*100
   rotate camera 0,0,wrapvalue(90-Bots(b).aim#),0
endfunction
 
function CheckDelObj(id)
   if object exist(id)=1 then delete object id
endfunction
 
function GetAngle(x1#,y1#,x2#,y2#)
   a#=90-atanfull(x2#-x1#,y2#-y1#)
endfunction a#
 
function GetDist(x1#,y1#,x2#,y2#)
   d#=sqrt((x1#-x2#)^2+(y1#-y2#)^2)
endfunction d#
 
function AdvancePos(n,x#,y#)
   Bots(n).walk#=GetAngle(Bots(n).x#,Bots(n).y#,x#,y#)
   Bots(n).aim#=GetAngle(Bots(n).x#,Bots(n).y#,x#,y#)
   Advance(n,2)
   TryToShoot(n,Bots(n).t)
endfunction
 
function Advance(n,d#)
   lx#=Bots(n).x#
   ly#=Bots(n).y#
   Bots(n).x#=Bots(n).x#+cos(Bots(n).walk#)*d#
   Bots(n).y#=Bots(n).y#+sin(Bots(n).walk#)*d#
   BotCollision(n)
   `BoxCollision(n)
   WallCollision(n)
endfunction
 
function BotCollision(n)
   for i=1 to array count(Bots(0))
      if i<>n and Bots(i).life>0 and GetDist(Bots(n).x#,Bots(n).y#,Bots(i).x#,Bots(i).y#)<32 :`Collision found
         a#=GetAngle(Bots(i).x#,Bots(i).y#,Bots(n).x#,Bots(n).y#)
         d#=32-GetDist(Bots(n).x#,Bots(n).y#,Bots(i).x#,Bots(i).y#)
         Bots(n).x#=Bots(n).x#+cos(a#)*d#
         Bots(n).y#=Bots(n).y#+sin(a#)*d#
      endif
   next i
endfunction
 
function BoxCollision(n)
   x#=Bots(n).x#
   y#=Bots(n).y#
   for i=1 to array count(Teams(0))
      tx#=Teams(i).x#
      ty#=Teams(i).y#
      if GetDist(tx#,ty#,x#,y#)<(16/cos(GetAngle(tx#,ty#,x#,y#)))+16 :`Collision between circle and box
         if x#<tx#+16+16 :`Right side of box
            d#=tx#-x#+16+16
            a#=180.0
         endif
         if x#>tx#-16-16 :`Left side
            d#=x#-tx#+16+16
            a#=0.0
         endif
         if y#<ty#+16+16 :`Bottom side
            d#=ty#-y#+16+16
            a#=270.0
         endif
         if y#>ty#-16-16 :`Top side
            d#=y#-ty#+16+16
            a#=90.0
         endif
         Bots(n).x#=Bots(n).x#+cos(a#)*d#
         Bots(n).y#=Bots(n).y#+sin(a#)*d#
      endif
   next i
endfunction
 
function WallCollision(n)
   col=0
   x#=Bots(n).x#
   y#=Bots(n).y#
   if x#<16 :`Collision with left wall
      d#=x#-16
      a#=180.0
      col=1
   endif
   if x#>(screen width())-16 :`Right wall
      d#=(screen width())-x#-16
      a#=0.0
      col=1
   endif
   if y#<16 :`Top wall
      d#=y#-16
      a#=270.0
      col=1
   endif
   if y#>(screen height())-16 :`Bottom wall
      d#=(screen height())-y#-16
      a#=90.0
      col=1
   endif
   if col=1
      Bots(n).x#=Bots(n).x#+cos(a#)*d#
      Bots(n).y#=Bots(n).y#+sin(a#)*d#
   endif
endfunction
 
function Shoot(n,t)
   if timer()<Bots(n).shoot+500 then exitfunction
   l=8
   if Bots(n).mode=1 then l=10 :`If in defend mode, set bullets to be more powerful
   randomize timer()
   if rnd(int(GetDist(Bots(n).x#,Bots(n).y#,Bots(t).x#,Bots(t).y#)/40/Bots(n).skill#))=0 :`Closer than 40 will always hit
      dec Bots(t).life,l
      inc Bots(n).skill#,0.08
   endif
   dec Bots(n).bullets
   Bots(n).shoot=timer()
   if Bots(t).life<0 then Bots(t).life=0
endfunction
 
function TryToShoot(n,t)
   if Bots(n).bullets>0
      if timer()>Bots(n).rstart+r_wait
         Shoot(n,t)
      endif
   else
      Bots(n).rstart=timer()
      Bots(n).bullets=10
   endif
endfunction
 
function LookForTarget(n)
   t=FindNearestEnemy(n,250)
   if t>0 then Bots(n).t=t
endfunction
 
function FindNearestEnemy(n,range#)
   mindist#=0
   t=0
   for i=1 to array count(Bots(0))
      if Bots(n).team<>Bots(i).team and Bots(i).life>0
         a#=GetAngle(Bots(n).x#,Bots(n).y#,Bots(i).x#,Bots(i).y#)
         dist#=GetDist(Bots(n).x#,Bots(n).y#,Bots(i).x#,Bots(i).y#)
         if (a#>Bots(n).aim#-45 and a#<Bots(n).aim#+45) :`Check if bot is in FOV
            if (dist#<mindist# or mindist#=0) and dist#<range#
               mindist#=dist#
               t=i
            endif
         endif
      endif
   next i
endfunction t
 
function AimTo(n,x#,y#)
   Bots(n).aim#=GetAngle(Bots(n).x#,Bots(n).y#,x#,y#)
   Bots(n).walk#=Bots(n).aim#
endfunction
 
function ChaseBot(n,t)
   AimTo(n,Bots(t).x#,Bots(t).y#)
   dist#=GetDist(Bots(n).x#,Bots(n).y#,Bots(t).x#,Bots(t).y#)
   if dist#<70
      Bots(n).walk#=wrapvalue(Bots(n).walk#)
      Advance(n,2)
      TryToShoot(n,t)
   else
      if dist#>80
         Advance(n,2)
         TryToShoot(n,t)
      else
         Bots(n).walk#=wrapvalue(Bots(n).walk#+90)
         Advance(n,2)
         Bots(n).walk#=wrapvalue(Bots(n).walk#-90)
         TryToShoot(n,t)
      endif
   endif
endfunction
 
function AdvanceToMainTarget(n)
   if Bots(n).mode=0
      AdvanceAndAttack(n)
   else
      AdvanceAndDefend(n)
   endif
endfunction
 
function AdvanceAndAttack(n)
   t=Bots(n).tb
   AimTo(n,Teams(t).x#,Teams(t).y#)
   dist#=GetDist(Bots(n).x#,Bots(n).y#,Teams(t).x#,Teams(t).y#)
   if dist#<16 :`Bot touches target
      if Teams(t).time+win_delay*1000<timer() then Teams(t).time=timer()
   else
      adv#=2
      if Bots(n).form=2
         if Bots(Bots(n).leader).life<40
            SwitchLeader(Bots(i).team)
         else
            dist#=GetDist(Bots(n).x#,Bots(n).y#,Bots(n).formx#,Bots(n).formy#)
            if dist#>10 then AimTo(n,Bots(n).formx#,Bots(n).formy#)
         endif
      endif
      if Bots(n).form=1
         bots$=""
         for i=(n+1) to (n+int(Teams(Bots(n).team).Bots#)-1)
            bots$=bots$+chr$(i)
         next i
         if FindNearestEnemy(n,320)>0 :`Can see enemy
            LineFormation(n,bots$)
         else
            if formation=1
               CircleFormation(n,bots$)
            else
               VFormation(n,bots$)
            endif
         endif
         if BotsInFormation(n,bots$)=0 then adv#=1.0
      endif
      Advance(n,adv#)
      LookForTarget(n)
   endif
endfunction
 
function AdvanceAndDefend(n)
   t=Bots(n).tb
   tx#=Teams(t).x#
   ty#=Teams(t).y#
   dist#=GetDist(Bots(n).x#,Bots(n).y#,tx#,ty#)
   AimTo(n,tx#,ty#)
   Bots(n).aim#=wrapvalue(Bots(n).aim#+180)
   if dist#<120
      if dist#<110
         Bots(n).walk#=Bots(n).aim#
      else
         Bots(n).walk#=wrapvalue(Bots(n).walk#+90)
      endif
   endif
   Advance(n,2)
   Bots(n).aim#=wrapvalue(Bots(n).aim#+Bots(n).offset#)
   inc Bots(n).offset#,10
   LookForTarget(n)
endfunction
 
function CheckTimer()
   dim t(array count(Teams(0)))
   for i=1 to array count(Teams(0))
      t(i)=1
      if Teams(i).time>0
         center text screen width()/2,20,"Time left: "+str$(win_delay-int((timer()-Teams(i).time)/1000))
         t_timeleft=timer()
         if Teams(i).time+win_delay*1000+200>=timer()
            WinGame(3-i)
         endif
      endif
   next i
   j=0
   cteam=1
   for i=1 to array count(Bots(0))
      inc j
      if j>int(Teams(cteam).Bots#)
         j=1
         inc cteam
      endif
      t(cteam)=(t(cteam) and (Bots(i).life<=0))
   next i
   for i=1 to array count(Teams(0))
      Teams(i).time=0
      if t(i)=1
         WinGame(3-i)
      endif
   next i
endfunction
 
function WinGame(n)
   Teams(n).time=0
   inc Teams(n).score
   attacking_team=3-attacking_team :`Swap teams
   gosub _setup
endfunction
 
function VFormation(leader,bots$)
   Bots(leader).form=1
   for i=1 to len(bots$)
      n=asc(left$(mid$(bots$,i),1))
      Bots(n).form=2
      Bots(n).leader=leader
      if i mod 2=0
         a#=135
      else
         a#=225
      endif
      a#=a#+Bots(leader).walk#
      Bots(n).formx#=Bots(leader).x#+cos(a#)*64*((i+(i mod 2))/2)
      Bots(n).formy#=Bots(leader).y#+sin(a#)*64*((i+(i mod 2))/2)
      Bots(n).aim#=Bots(leader).aim#
   next i
   if (len(bots$) mod 2)=1
      n=asc(right$(bots$,1))
      a#=180+Bots(leader).walk#
      Bots(n).formx#=Bots(leader).x#+cos(a#)*64
      Bots(n).formy#=Bots(leader).y#+sin(a#)*64
   endif
endfunction
 
function CircleFormation(leader,bots$)
   Bots(leader).form=1
   i#=0
   for i=1 to len(bots$)
      inc i#
      n=asc(left$(mid$(bots$,i),1))
      Bots(n).form=2
      Bots(n).leader=leader
      a#=i#/len(bots$)*360.0
      Bots(n).formx#=Bots(leader).x#+cos(a#)*64
      Bots(n).formy#=Bots(leader).y#+sin(a#)*64
      Bots(n).aim#=a#
   next i
endfunction
 
function LineFormation(leader,bots$)
   Bots(leader).form=1
   for i=1 to len(bots$)
      n=asc(left$(mid$(bots$,i),1))
      Bots(n).form=2
      Bots(n).leader=leader
      if i mod 2=0
         a#=-1
      else
         a#=1
      endif
      a#=a#*90+Bots(leader).walk#
      Bots(n).formx#=Bots(leader).x#+cos(a#)*64*((i+(i mod 2))/2)
      Bots(n).formy#=Bots(leader).y#+sin(a#)*64*((i+(i mod 2))/2)
      Bots(n).aim#=Bots(leader).aim#
   next i
endfunction
 
function BotsInFormation(leader,bots$)
   for i=1 to len(bots$)
      n=asc(left$(mid$(bots$,i),1))
      dist#=GetDist(Bots(n).x#,Bots(n).y#,Bots(n).formx#,Bots(n).formy#)
      if dist#>17 then exitfunction 0
   next i
endfunction 1
 
function SwitchLeader(t)
   max=0
   for i=1 to array count(Bots(0))
      if Bots(i).team=t and Bots(i).life>=40 and Bots(i).life>max
         max=Bots(i).life
         l=i
      endif
   next i
   Bots(l).form=1
   Bots(l).leader=0
   for i=1 to array count(Bots(0))
      if Bots(i).team=t and Bots(i).life>=40 and i<>l
         Bots(i).form=2
         Bots(i).leader=l
      endif
   next i
endfunction