aurum34
17

Author
aurum34

Published
May 22nd, 2010

Read 19,784 times
31 comments have been written
15 people liked this

Creating A Turn-Based Battle System

Written by aurum34

Aurum34's own step-by-step guide to making your own simple turn-based battle system.

1. Introduction
2. Day 1
3. Day 2
4. Day 3
5. Day 4
6. Day 5
7. Day 6

Creating A Turn-Based Battle System
Introduction

This is a tutorial on how to make a simple turn-based battle system (BS). In this tutorial I will basically be creating my own BS and detail how it works, advice on how to customize things, and los of sample code for you to study and try out.

Of course, this is not the only way of going about making such a BS so if you don´t like how I've done some things or if you prefer a different technique then you´ll make me even happier! I just want to give you an idea on how things should work together so that you can go on to make your own.

When all the sample codes are brought together there should be an entire BS but remember that this is the core structure. I will not address additional things like status effects. So take it slowly, follow every step, and study the code to really learn everything you need to know.

Requirements

Since creating a BS from scratch is something that total newbies typical can't get their head around, I´ll quickly summarise the core understandings needed to achieve good custom systems with RPG Code.

  • "if" structures
  • loops
  • canvases
  • functions/methods
  • structs

With an understand of these core concepts, you're ready to start building some pretty cool things.

Thankings

What´s a Toolkit resource without a segment thanking the members of the community that helped test and correct some of my English flaws. I have to thank them or their ghosts will probably hunt me forever *shaking*

  • Dave the Hamster - He requested a BS, so I created this tutorial. Actually, I shouldn't give him credit for asking for a BS otherwise I'll have to give credit to every newbie here!
  • Hannibal - Helped me by reading this and telling me on the things I should add/change.

I'll divide the tutorial by days. So, have fun!

Creating A Turn-Based Battle System
Day One

So, you want to make your own turn-based battle system with the RPG Toolkit? Of course you do, why else would you be reading this anyway?!

Assets

The first thing you have to concentrate on is planning and preperation. 

You will have to create your own animations as you can´t use Toolkit´s due to the fact they only run one animation at a time so if you need everybody to run a single animation at the same time, like on multiple-target attacks, then the default animations can´t help you.

There are several things to plan;

  • Animations
  • Players
  • Enemies
  • Special Moves
  • Interactions

And maybe more if you want to add more features, but let's keep it simple for now.

Variable Trick

A trick I use when I want to use a single variable to hold several values is to use strings and then split them, if there are numbers inside the string I use castnum() to get the values. This can be applied as a method, for example:

debugger(add("4,85,69"))
debugger(add("452,895,653,845,675,415,182,478,352,645"))

method add(numbers)
{
  split(numbers, ",", num[]) //this will split the numbers by the commas
  local(n)=0
  local(answer)=0
  until(num[n]=="") //go through all numbers
  {
    answer=answer+castNum(num[n]) //convert the num[] string to a number and add it to the answer
    n++ //go to the next number
  }
  return answer
}[

When you run this code you´ll notice that all of the numbers are inside a string and that this means they are held in a single variable name. So no matter how many numbers we add we only need to use one variable to pass them as a parameter.

We will use this trick to make an animation.  An animation need several frames so we will use this string of values to run a sequence of frames.

anmIdl="0.05,frame1.png,frame2.png"
RunAnm(anmidl, 20, 20, 64, 64)

Method RunAnm(anm, x, y, width, height)
{
  savescreen(2)
  split(anm, ",", anm[])
  local(n)=1
  until(anm[n]=="")
  {
    restorescreenarray(2)
    setImageTransparent(anm[n], x, y, width, height, 255, 255, 255)
    delay(castNum(anm[0]))//anm[0] is speed as string so i use castnum to make it a number
    n++
  }
  restorescreenarray(2)
  n=0 //now the clean up
  until(anm[n]=="")
  {
    kill(anm[n])
    n++
  }
  kill(n)
}

You may be wondering what the 0.05 before the frames is, right? Well, that´s the speed of the animation so you can open the animation editor, make the animation there, then copy the speed and the frames to a program like this one.

Customisation

Now it´s your time to shine - look at what I did here and maybe make your own simple animation method.

Creating A Turn-Based Battle System
Day Two

What we should do now is make structures for the players and the special moves. We shouldn't combine custom variables (speed, dex, ...) with default variables (fp, dp) because that could cause confusion at a later time, so let's just make all the structures now to start getting familiarized with them.

Player Structure

You just have to put the basic animations and variables you want to use and you're done. But also leave some methods for the menu options, if your player will have the options attack, special, run, then leave 3 option methods like I did. Put all the methods all the players will need, for example, if you have a player whose options on the menu will be "attack,special,run" and anotherone with "range,summon,run", the put the methods range, attack, summon, special and run, because you'll need them, for now they'll not do anything, but it's important to put them to know what we need to do.

// i use struct to avoid accessing errors
struct Player
{

   // constructor, study classes
   method Player(n, h, m, siz, spd, str, def, tec)
  
{

      // the animations will be setted with another method
      name = n;
      hp = h;
      maxhp = h;
      mp = m;
      maxmp = m;
      size = siz;
      speed = spd;
      strike = str;
      defense = def;
      tecniques = tec;

   }

   // methods to be implemented later
   m
ethod attack()
{}
   method special() {}
   method run() {}
   method killPlayer() {}
   method PlayerMenu() {}
   method selectOption(option) {}

   // attributes
  
name;
  
hp;
  
maxhp;
  
oldhp; //this is to keep track of the changed hp
  
mp;   
  
maxmp;
   size; //width and height of the animations
   menu; //string with the names of the options.
   speed; //speed variable
   anmIdl; //frames for idl animation
   anmAtk; //attacking
   anmCast; //casting SMove
   anmBlk; //blocking
   anmHrt; //hurt
   anmFrame; //current frame of the animation.
   anmSpeed; //this will handle the animations, explanation on day 3.
   strike; //um.. self-explanatory isn't it?
   defense; //you HAVE to know what is this for
   tecniques; //I'll use a string and split it to call the tecniques
   plx;
   ply; //location of the player

}

Now that the player structure is done you have to remember to use it, everytime you add a new player to the party you have to create a new player variable, and of course, remember to create the first player.

To create the players you just call the player() method inside the structure, thatway you can give a value to some of the variables, but the animations are another story, you'll have to call every animation variable and set it by yourself, but since the player[] will be a global variable, you don't have to worry about doing it again, just do it once and you're done.

Now let's make a player from the first playerHandle[] you have.

#include "bsclass.prg"; //include the program were you saved the structure.

Global(player[playerHandle[0]]) = Player("name", gethp(playerhandle[0]), getsmp(playerhandle[0]), 32, 1, getfp(playerhandle[0]), getdp(playerhandle[0]), "");

As you can see, we basically just called the player() method inside the structure and gave a value to the parameters of the player() method. Then the information is stored inside player[playerHandle[0]] global variable, so we don't have to do this again.

But now, we only gave a value to some of the variables inside the structure, we have to gave a value to everything to be done with it, so we have to call the variables one-by-one and give them values.

#include "bsclass.prg"; //include the program were you saved the structure.

Global(player[playerHandle[0]])=Player("name", gethp(playerhandle[0]), getsmp(playerhandle[0]), 32, 1, getfp(playerhandle[0]), getdp(playerhandle[0]), "");

player[playerHandle[0]]->anmIdl="0.5,idlframe1.png,idlframe2.png";

player[playerHandle[0]]->anmAtk="0.2,Atkframe1.png,Atkframe2.png,Atkframe3.png,Atkframe4.png";

player[playerHandle[0]]->anmCast="0.8,Castframe1.png,Castframe2.png,Castframe3.png";

player[playerHandle[0]]->anmBlk="0.08,Blkframe1.png,Blkframe2.png,Blkframe3.png";

player[playerHandle[0]]->anmHrt="0.5,Hrtframe1.png,Hrtframe2.png";

Now this player is completely done because all of it's variables are setted, remember that everytime you add a new player for the first time you have to create a variable like how I did it here, make it global since otherwise you'll have lot's of bugs, and remember that if you take away the player from the party the variable will still be there, but the battle system won't use it because it goes between the members of the party.

Enemy Structure

As you can see by the player structure, the enemy structureneeds the same variables for the animations, so let's make the enemy structure, with all the variables we need.

struct Enemy
{

   // constructor
   method Enemy(n, h, m, siz, spd, str, def, tec)
  
{

      // animations will be sett differently
     
name = n; 
      hp = h;
      maxhp = h;
      mp = m;
      maxmp = m;
      size = siz;
      speed = spd;
      strike = str;
      defense = def;
      tecniques = tec;

   }

   // methods to be implemented later   
   m
ethod attack() {}
   m
ethod killEnemy() {}

   // attributes    
   name;
hp; // to kill it
   maxhp; // in case it heals itself
   mp; // in case I want to steal it!
   maxmp;
   exp; // experience it will give you
   reward; // maybe an item?
   gp; // money!!
   size; //width and height of the animations
   speed; // speed variable
   anmIdl; // frames for idl animation
   anmAtk; // attacking
   anmCast; // casting SMove
   anmBlk; // blocking
   anmHrt; // hurt
   anmFrame; // current frame of the enimation
   anmSpeed; // this will handle the animations, explanation on day 3.
   strike; 
   defense;   
   tecniques; // I'll use a string and split it to call the tecniques
   enex;
   eney; // location of the enemy

}

You have to create enemies almost the same way as players, but the advantage is that you can just make methods to create every enemy andcall that method as many times you want instead of making global variables that will consume space of memory.

If we are going to make a ghost as an enemy, we can create a method that creates the ghost with all of it's animations and variables.

Method ghost()
{

  local(ghost)=Enemy("Ghost", 200, 15, 32, 2, 20, 40, "");

  ghost->anmIdl="0.5,enemyes/ghostidl1.png,enemyes/ghostidl2.png";

  ghost->anmAtk="0.6,enemyes/ghostAtk1.png,enemyes/ghostAtk2.png,enemyes/ghostAtk3.png,enemyes/ghostAtk4.png";

  ghost->anmCast="0.05,enemyes/ghostCast1.png,enemyes/ghostCast2.png";

  ghost->anmBlk="0.4,enemyes/ghostBlk1.png,enemyes/ghostBlk2.png";

  ghost->anmHrt="0.15,enemyes/ghostHrt1.png,enemyes/ghostHrt2.png,enemyes/ghostHrt3.png";

  return ghost;

}

See? We created a method that makes a ghost by setting it's variables and animation, now we save this inside a program called "bsEnemy.prg" and we create all of the methods that creates enemies there, this will allow us to do something like

#include "bsEnemy.prg";

ene[0]=ghost();

ene[1]=ghost();

ene[2]=ghost();

Easy right? Just calling that method makes the enemy creation easier, and that's what we need, make things as easier as we can.

Special Moves Structure

Now, let's make the powers that you can call whenever you want.

struct SMove
{

   method SMove(pwr, mpc, lvl, typ, anm, w, h)
  
{

      level = lvl;
      animation = anm;
      anmWidth = w;
      anmHeight = h;
      MPcost = mpc;
      Power = pwr;
      type = typ;

    }

    method Action() {}

    level; //required level
    animation; 
    anmWidth; // width of the animation
    anmHeight; // height of the animation
    MPcost;
    type; // to be explained later
    Power; // base power, to be increased with stats

}

Not much to say here, the special moves are almost the same as players, global variables, the only difference is that you have to put the name of the SMove inside the array to make it easier to call. But you can create your own way to call them from the bs, this is just one way to go.

#include "bsClass.prg";

local(FLAnm) = "0.1,flare1.png,flare2.png,flare3.png,flare4.png,flare5.png"

Global(Tecniques["flare"]) = SMove(80, 12, 20, 2, FLAnm, 32, 32) 

Just make it that way to make it easier to call from the player structure, I'll explain more about special moves on day 5 but for now this is enough for you to start making your SMoves.

NOTE: Save all the structures in 1 prg file and the rest of the BS on another

Customization

You have to make now the player structure and the special move structure, with these done, the battlesystem  can start taking shape tomorrow. Take your time to figure out all the variables you want to put and all the commands you want to add to the structures. When you're done, take a rest and continue the tutorial, today was all about structures, so if there's something you don't understand, look for it because the rest of the BS will use these strucrures on every code, so if you have ANY doubts you should try to solve them before reading any further.

AND THAT WAS DAY 2!

Creating A Turn-Based Battle System
Day Three

This day will be focused on explaining the custom animation system that will be used for this BS.as you should know, the definition of animation is: several static images passed one after another, so to do the animations we have to just keep drawing images to the screen.of course, we have to clean the previous image, and still considerate that we have to handle lots of players and enemies, so when changing to a new frame, the rest should stay the same, to do this, I´ll use canvases.

If you´ve ever made a hp bar program you´ll know that canvases are perfect to change only one little part of the screen with a lot of speed, if you haven´t you´re about to learn it. So let´s start with a simple code using the custom animation explained on day 1.

idlAnm="0.05,frame1.png,frame2.png,frame3.png";
runAnm(idlAnm, 20, 20, 32, 32);

Method RunAnm(anm, x, y, width, height)
{

   local(c)=createCanvas(width, height);
  
split(anm, ",", anm[]);
  
local(n) = 1;

   until(anm[n]=="")
  
{

      clear(c);
     
setImageTransparent(anm[n], 0, 0, width, height, 255, 255, 255, c);
     
drawCanvas(c, x, y);

      delay(castNum(anm[0]));
      //anm[0] is speed as string so i use castnum to conver it to float

      n++;

   }

   n=0; //now the clean up

   until(anm[n]=="")
  
{

      kill(anm[n]);
     
n++;

   }

   kill(n);

}

NOTE: from now on, you have to "manually" put the animations for the enemyes, this is done by calling the variable inside of the structure that is meant to hold the animation and giving it the value.

ene[0]=Enemy("Goblin", 10, 4, 32, 1, 3, 3);

ene[0]->anmIdl="0.05,frame1.png,frame2.png"; //don´t leave spaces

ene[0]->anmAtk="0.15,atkframe1.png,atkframe2.png,atkframe3.png";//and so on with all the animations

If you understand this you can totally tell that this is what you need, let´s just use a canvas for each player and for each enemy, the canvases must be created at the beginning of the battle and eliminated at the end so let´s start making the setupBattle method to create and draw the basic layout of the battle when it starts and let´s start anotherone to kill those canvases after the battle is done.

The setupBattle method is to put a basic layout of the battle system, draw the names of the enemies, put the background, play the music, and make the canvasses for every player and enemy on the fight. Also, it has to put the first frame of the idl animation of the players and enemies so.... It´s all going to look a lot like the following

NOTE: from now on the players will be player[playerHandle[n]], kinda like playerHandle[n]. And the enemies will be ene[n].

In addition the canvases for the players will be named CPlayer[playerHandle[n]] and the canvasses for the enemies will be Cene[n]

method setupBattle(background, music)
{

   bitmap(background);
  
mediaplay(music);
  
local(n)=0 // let´s put the players;

   until(player[playerHandle[n]]=="")
  
{

      // first we save the player´s x and y location.

      player[playerHandle[n]]->plx=120+(castInt(n/2)*20);

      player[playerHandle[n]]->ply=250+(n*50)-(n*75);

      // now we create the Canvas for this player, take the
      // first frame of the idl animation, draw it to the.

      // canvas and draw the canvas ti the player´s x and y
      // location and save the moment with getTickCount.

      CPlayer[playerHandle[n]]=createCanvas(player[playerHandle[n]]->size, player[playerHandle[n]]->size);

      split(player[playerHandle[n]]->idlAnm, ",", anm[]);

      setImagetransparent(anm[1], 0, 0, player[playerHandle[n]]->size, player[playerHandle[n]]->size, 255, 255, 255, CPlayer[playerHandle[n]]);

      drawCanvas(CPlayer[playerHandle[n]], player[playerHandle[n]]->plx, player[playerHandle[n]]->ply);

      player[playerHandle[n]]->anmSpeed=getTickCount();

      player[playerHandle[n]]->anmFrame=1;

      // now we just put the name of the player on the screen
      // and step to the next player

      setImage("menuimage.png", 25+(20*n), 25+(22*n), getTextWidth(playerHandle[n])+10, getTextHeight(playerHandle[n])+10);

      pixelText(30+(20*n), 30+(22*n), player[playerHandle[n]]->name); //here is the name of the player

      n++;

   }

   n = 0;

   //from this point it´s just clean-up memory leaks
  
until(anm[n]=="") 
  
{

      kill(anm[n]);
     
n++;

   }

   n=0 ;

   // now the enemies, let´s do the same.
  
until(ene[n]=="")
  
{

      ene[n]->enex=400+(castInt(n/2)*20); // let´s save the location

      ene[n]->eney=250+(n*50)-(n*75); // to use it later

      Cene[n]=createCanvas(ene[n]->size, ene[n]->size);

      split(ene[n]->anmidl, ",", anm[]);

      setImageTransparent(anm[1], 0, 0, ene[n]->size, ene[n]->size, 255, 255, 255, Cene[n]);

      drawCanvas(Cene[n], ene[n]->enex, ene[n]->eney);

      ene[n]->anmSpeed=getTickCount();

      ene[n]->anmFrame=1;

      pixelText(340+(20*n), 30+(22*n), ene[n]->name);

      setImage("menuimage.png", 335+(20*n), 25+(22*n), getTextWidth(ene[n]->name)+10, getTextHeight(ene[n]->name)+10);

      n++; //kill the variable after use

   }

   n = 0;

   // from this point it´s just clean-up the variables
   // that should be deleted
  
until(anm[n]=="") 
  
{

      kill(anm[n]);
     
n++;

   }

   kill(n);

}

This should be the last time we change this method, so don´t miss any of the details that you want to add. Here I just put the players and enemies on the screen and their names, put anything you want.now that the battle has started and we know how animations will work, let´s make a quick method to kill all the canvases before ontinue, I´m just killing canvases for now but if you have other useless variables that you won´t need outside of battle like CanRun or anything else, kill those variables on this Method.

method endBatttle()
{

   local(n) = 0;

   until(CPlayer[playerHandle[n]]=="")
  
{

      killCanvas(CPlayer[playerHandle[p]]);

      n++;

   }

   n=0;

   until(Cene[n]=="")
  
{
     
killCanvas(Cene[n]);
     
n++;
  
}

}

And now that we have the beginning of the animation, and the end of them, we just have to make the method that will control the movement of the idl animation while we wait for a turn.

method AnimateBattle()
{

  local(n) = 0; //let´s put the players

  until(player[playerHandle[n]]=="")
 
{

     split(player[playerHandle[n]]->idlAnm, ",", anm[]);

     if(castnum(anm[0])*1000<=getTickCount()-player[playerHandle[n]]->anmSpeed)
     {

        clear(CPlayer[playerHandle[n]]);

        player[playerHandle[n]]->anmFrame++;

        if(anm[player[playerHandle[n]]->anmFrame]=="") {player[playerHandle[n]]->anmFrame=1};

        setImagetransparent(anm[player[playerHandle[n]]->anmFrame], 0, 0, player[playerHandle[n]]->size, player[playerHandle[n]]->size, 255, 255, 255, CPlayer[playerHandle[n]]);

        drawCanvas(CPlayer[playerHandle[n]], player[playerHandle[n]]->plx, player[playerHandle[n]]->ply);

        player[playerHandle[n]]->anmSpeed=getTickCount();

     }

     n++;

  }

  n=0;

  until(anm[n]=="") //from this point it´s just clean-up the variables that should be deleted

  {

    kill(anm[n]);

    n++;

  }

  n=0 ;

  until(ene[n]=="")//now the enemyes, let´s do the same.

  {

    split(ene[n]->anmidl, ",", anm[]);

    if(castnum(anm[0])*1000<=getTickCount()-ene[n]->anmSpeed) //if it´s time for the nextframe

    {

      clear(Cene[n]);

      ene[n]->anmFrame++;

      if(anm[ene[n]->anmFrame]=="") {ene[n]->anmFrame=1};

      setImageTransparent(anm[ene[n]->anmFrame], 0, 0, ene[n]->size, ene[n]->size, 255, 255, 255, Cene[n]);

      drawCanvas(Cene[n], ene[n]->enex, ene[n]->eney);

      ene[n]->anmSpeed=getTickCount();

    }

    n++;

  }

  n=0;

  until(anm[n]=="") //from this point it´s just clean-up the variables that should be deleted

  {

    kill(anm[n]);

    n++;

  }

  kill(n);

}

This method should do it, basically just takes a look at the time that has passed since the last frame of the animation was set, changes to the next frame and if the animation has ended it starts it all over. Then saves the tickCount to use it as reference for the next time the animation has to be setted.

Customization: if you´re using anotherway of doing things you should make the methods I made, one for the setup of the battle, anotherone to the end of the battle, and anotherone to continue the animation. If you´re going to use my methods, then just take a good look and study the codes until you get how exactly do they work.

Creating A Turn-Based Battle System
Day Four

This day will be a preparation for the turns.

Now that the basic BS is done, we only have to make the interactions, but to do so we have to decide whose turn is it, to do so you can do almost everything, I'll just use a temporal variable and add the speed to it until it's 100 or more, basically I'll do this:

until(temp[1]>100) //check the value of a variable
{
  temp[1]+=Player[playerHandle[0]]->speed; //add the player speed to this value
}
temp[1]-=100; //in case temp[1] is bigger than 100 we take that, so if it 105, it becomes 5.

Basically this is the idea, but I want to make a better method, one that will go through all the players and enemies, this method will check every player and enemy and will keep adding the speed value of each of them to a variable, and when the variable is 100 or more, then a method that should be inside the structure will be called and that's how the turns are made.

Method decideTurn()
{
  local(n)=0;
  until(playerHandle[n]=="") //go through all players
  {
    turn[playerHandle[n]]+=player[playerHandle[n]]->speed; //add this player's speed to this player's turn var.
    If(turn[playerHandle[n]]>99) //if the turn is 100 or more....
    {
      turn[playerHandle[n]]-=100; //we take away the leftovers
      player[playerHandle[n]]->playerMenu(); //we call this method inside the class
    }
  }
  n=0;
  until(ene[n]=="") //now we do the same with the enemies
  {
    turn[n]+=ene[n]->speed;
    If(turn[n]>99)
    {
      turn[n]-=100;
      ene[n]->Attack();
    }
  }
}

See what it does? It's a simple code that adds the speed to the turn[] variable, and when it's 100 or more it calls a method that should be inside of the structure itself that should perform the attack.

Now, it seems rather pointless to make 2 methods to go through all the players and enemies (this and the animation one) so if we can join them (and we can) it means speed savings. Let's just do it at once OK? And let's call it different.

But remember that with this method now you won't need the other 2, so don't save them.

Method Battle()
{
  local(n)=0;
  until(playerHandle[n]=="") //go through all players
  {
    split(player[playerHandle[n]]->idlAnm, ",", anm[]); //split the current animation
    If(castnum(anm[0])*1000<=getTickCount()-player[playerHandle[n]]->anmSpeed); //check if it's time for next frame
    {
 clear(CPlayer[playerHandle[n]]); //clear the canvas of this player
 player[playerHandle[n]]->anmFrame++; //change to the next frame
 If(anm[player[playerHandle[n]]->anmFrame]=="")
 {
   player[playerHandle[n]]->anmFrame=1; //restart the animation if necessary
 }
      setImagetransparent(anm[player[playerHandle[n]]->anmFrame], 0, 0, player[playerHandle[n]]->size, player[playerHandle[n]]->size, 255, 255, 255, CPlayer[playerHandle[n]]); //set the next frame
      drawCanvas(CPlayer[playerHandle[n]], player[playerHandle[n]]->plx, player[playerHandle[n]]->ply); //draw canvas
      player[playerHandle[n]]->anmSpeed=getTickCount();
    }
    turn[playerHandle[n]]+=player[playerHandle[n]]->speed; //add the speed to the turn[] var
    If(turn[playerHandle[n]]>99) //if it's this player's turn
    {
      turn[playerHandle[n]]-=100; //take 100 to say this turn has passed
      player[playerHandle[n]]->playerMenu(); //call this method to perform the turn
    }
    n++;
  }
  n=0;
  until(anm[n]=="") //from this point it's just clean-up the variables that should be deleted
  {
    kill(anm[n]);
    n++;
  }
  n=0 ;
  until(ene[n]=="")//now the enemies, let's do the same.
  {
    split(ene[n]->anmidl, ",", anm[]);
    if(castnum(anm[0])*1000<=getTickCount()-ene[n]->anmSpeed); //if it's time for the nextframe
    {
      clear(Cene[n]);
      ene[n]->anmFrame++;
      if(anm[ene[n]->anmFrame]=="") {ene[n]->anmFrame=1};
      setImageTransparent(anm[ene[n]->anmFrame], 0, 0, ene[n]->size, ene[n]->size, 255, 255, 255, Cene[n]);
      drawCanvas(Cene[n], ene[n]->enex, ene[n]->eney);
      ene[n]->anmSpeed=getTickCount();
    }
    turn[n]+=ene[n]->speed;
    If(turn[n]>99)
    {
      turn[n]-=100;
      ene[n]->Attack();
    }
    n++;
  }
  n=0;
  until(anm[n]=="") //from this point it's just clean-up the variables that should be deleted.
  {
    kill(anm[n]);
    n++;
  }
  kill(n);
}

As you can see I just joined the codes I had and now this will check if it's time to pass to the next frame of the animation and then checks to see if it's It's turn. Of course, now that we're using more variables, we have to kill them on the end battle method because we don't want those variables to have a value when we start a new battle.

Method endBatttle()
{
  local(n)=0;
  until(CPlayer[playerHandle[n]]=="")
  {
    killCanvas(CPlayer[playerHandle[p]]);
    kill(turn[playerHandle[n]]);
    n++;
  }
  n=0;
  until(Cene[n]=="")
  {
    killCanvas(Cene[n]);
    kill(turn[n]);
    n++;
  }
}

Remember that any variable you use on battle that you don't kill right away (and that's not inside the structures) you have to kill it on this method.

So now we have the idl animations running and the turns being selected, now we just make the enemies attack us and the player's option menu and the BS will be done and working.

But first we have to make another set of canvases to draw the hp of the players. This is easy, we just have to make them on the same method were we do the other canvases, kill them on that same Method too, and reresh them in the same place everything is refreshed, the battle Method.

NOTE:sorry if it bugs you the fact I'm repeating this method so many times but I have to make the changes step by step for everyone's sake (mostly mine)


NOTE:the reason why I didn't coded the hp before is that I didn't wanted to overcome you so much, but since the battle is really starting now (we'll see attacks on day 5) I though it was time to start managing hp

Method setupBattle(background, music)
{
  bitmap(background);
  mediaplay(music);
  local(n)=0; //let's put the players
  until(player[playerHandle[n]]=="")
  {
    //first we save the player's x and y location
    player[playerHandle[n]]->plx=120+(castInt(n/2)*20);
    player[playerHandle[n]]->ply=250+(n*50)-(n*75);
    //now we create the Canvas for this player, take the first frame of the idl animation, draw it to the
    //canvas and draw the canvas ti the player's x and y location and save the moment with getTickCount
    CPlayer[playerHandle[n]]=createCanvas(player[playerHandle[n]]->size, player[playerHandle[n]]->size);
    split(player[playerHandle[n]]->idlAnm, ",", anm[]);
    setImagetransparent(anm[1], 0, 0, player[playerHandle[n]]->size, player[playerHandle[n]]->size, 255, 255, 255, CPlayer[playerHandle[n]]);
    drawCanvas(CPlayer[playerHandle[n]], player[playerHandle[n]]->plx, player[playerHandle[n]]->ply);
    player[playerHandle[n]]->anmSpeed=getTickCount();
    player[playerHandle[n]]->anmFrame=1;
    //now we just put the name of the player on the screen and step to the next player
    setImage("menuimage.png", 55+(20*n), 25+(22*n), getTextWidth(playerHandle[n])+40, getTextHeight(player[playerHandle[n]]->name)+40);
    pixelText(55+(20*n), 30+(22*n), player[playerHandle[n]]->name); //here is the name of the player
    //create life Canvas
    Lplayer[playerHandle[n]]=CreateCanvas(30, getTextHeight(player[playerHandle[n]]->name)+10);
    pixelText(0, 5, player[playerHandle[n]]->hp+"/"+GetMaxHp(playerHandle[n]), Lplayer[playerHandle[n]]);
    drawCanvas(Lplayer[playerHandle[n]], 60+(20*n)+getTextWidth(player[playerHandle[n]]->name), 30+(22*n));
    player[playerHandle[n]]->oldhp=player[playerhandle[n]]->hp;
    n++;
  }
  local(nn)=0;
  until(anm[nn]=="") //from this point it's just clean-up the variables that should be deleted
  {
    kill(anm[nn]);
    nn++;
  }
  kill(nn);
  n=0;
  until(ene[n]=="")//now the enemies, let's do the same.
  {
    ene[n]->enex=400+(castInt(n/2)*20); //let'
s save the location
    ene[n]->eney=250+(n*50)-(n*75); //to use it later
    Cene[n]=createCanvas(ene[n]->size, ene[n]->size);
    split(ene[n]->anmidl, ",", anm[]);
    setImageTransparent(anm[1], 0, 0, ene[n]->size, ene[n]->size, 255, 255, 255, Cene[n]);
    drawCanvas(Cene[n], ene[n]->enex, ene[n]->eney);
    ene[n]->anmSpeed=getTickCount();
    ene[n]->anmFrame=1;
    pixelText(340+(20*n), 30+(22*n), ene[n]->name);
    setImage("menuimage.png", 335+(20*n), 25+(22*n), getTextWidth(ene[n]->name)+10, getTextHeight(ene[n]->name)+10);
    n++; //kill the variable after use
  }
  local(nn)=0;
  until(anm[nn]=="") //from this point it's just clean-up the variables that should be deleted
  {
    kill(anm[nn]);
    nn++;
  }
  kill(nn);
}

As you can see, I only did a small modification, the players now show their hp and I obtain the maxhp from the regular playerHandle[] structure.

The mp will be shown when the skills menu is activated, besides that, there's nothing to do, this method is done.

Method Battle()
{
  local(n)=0; //let's put the players
  until(player[playerHandle[n]]=="")
  {
    if(player[playerHandle[n]->hp>0) //if the player is alive
    {
      split(player[playerHandle[n]]->idlAnm, ",", anm[]);
      if(castnum(anm[0])*1000<=getTickCount()-player[playerHandle[n]]->anmSpeed)
      {
       clear(CPlayer[playerHandle[n]]);
       player[playerHandle[n]]->anmFrame++;
       if(anm[player[playerHandle[n]]->anmFrame]=="") {player[playerHandle[n]]->anmFrame=1};
       setImagetransparent(anm[player[playerHandle[n]]->anmFrame], 0, 0, player[playerHandle[n]]->size, player[playerHandle[n]]->size, 255, 255, 255, CPlayer[playerHandle[n]]);
       drawCanvas(CPlayer[playerHandle[n]], player[playerHandle[n]]->plx, player[playerHandle[n]]->ply);
       player[playerHandle[n]]->anmSpeed=getTickCount();
      }
      turn[playerHandle[n]]+=player[playerHandle[n]]->speed;
      If(turn[playerHandle[n]]>99)
      {
        turn[playerHandle[n]]-=100;
        player[playerHandle[n]]->playerMenu();
      }
      if(player[playerHandle[n]]->hp~=player[playerHandle[n]]->oldhp)
      {
        clear(Lplayer[playerHandle[n]]);
        pixelText(0, 5, player[playerHandle[n]]->hp+"/"+GetMaxHp;(playerHandle[n]), Lplayer[playerHandle[n]]);
        drawCanvas(Lplayer[playerHandle[n]], 60+(20*n)+getTextWidth(player[playerHandle[n]]->name), 30+(22*n));
        player[playerHandle[n]]->oldhp=player[playerHandle[n]]->hp;
      }
    }
    else {clear(CPlayer[playerHandle[n]]);drawCanvas(CPlayer[playerHandle[n]], player[playerHandle[n]]->plx, player[playerHandle[n]]->ply)) //if the player is death
    local(nn)=0;
    until(anm[nn]=="") //from this point it's just clean-up the variables that should be deleted
    {
      kill(anm[nn]);
      nn++;
    }
    n++;
  }

  local(n)=0;
  until(ene[n]=="")//now the enemies, let's do the same.
  {
    if(ene[n]->hp>0) //if the enemy is alive
    {
      split(ene[n]->anmidl, ",", anm[]);
      if(castnum(anm[0])*1000<=getTickCount()-ene[n]->anmSpeed) //if it's time for the nextframe
      {
       clear(Cene[n]);
       ene[n]->anmFrame++;
       if(anm[ene[n]->anmFrame]=="") {ene[n]->anmFrame=1}
       setImageTransparent(anm[ene[n]->anmFrame], 0, 0, ene[n]->size, ene[n]->size, 255, 255, 255, Cene[n]);
       drawCanvas(Cene[n], ene[n]->enex, ene[n]->eney);
       ene[n]->anmSpeed=getTickCount();
      }
      turn[n]+=ene[n]->speed;
      If(turn[n]>99)
      {
        turn[n]-=100;
        ene[n]->Attack();
      }
    }
    else {clear(Cene[n]; drawCanvas(Cene[n], ene[n]->enex, ene[n]->eney)) //if it's death
    local(nn)=0;
    until(anm[nn]=="") //from this point it's just clean-up the variables that should be deleted
    {
      kill(anm[nn]);
      nn++;
    }
    n++;
  }

  kill(n);
  kill(nn);
}

One change too, the player part check to see if the hp has changed.

And finally, to finish, the end of the day is the end battle method, to kill the life canvasses

Method endBatttle()
{
  local(n)=0;
  until(CPlayer[playerHandle[n]]=="")
  {
    killCanvas(CPlayer[playerHandle[p]]);
    killCanvas(Lplayer[playerHandle[n]]);
    kill(turn[playerHandle[n]]);
    n++
  }
  n=0;
  until(Cene[n]=="")
  {
    killCanvas(Cene[n]);
    kill(turn[n]);
    n++;
  }
}

Customization: All I did was implement a way to decide the turns and then I implemented a way to keep track of the player's hp. It's your turn!! You should find some things to draw to the life canvas besides of what i did, come on, show your talent!

END OF DAY 4

Creating A Turn-Based Battle System

DAY 5

Today will be about implementing a basic random ai to the enemies turn and how the special moves work.

Since the player turn is a little more complicated than the enemies turn, I'll make the enemies turn first.

Now that the turn is selected the program calls for the Attack() method that is inside the class, but on day 2 we only made the variables and leaved the methods blank. Let's make a simple attack method and we'll put it inside the class later.

Method Attack() //this has to be inside the enemy class
{
  local(n)=0;
  until(player[playerHandle[n]]=="") {n++}//get the amount of players
  n=random(n+1)-1; //select a random player
  until(player[playerhandle[n]]->hp>0) {n=random(n+1)-1} //check if the selected player is alive

  local(e)=random(4); //e=1 special move, else attack
  split(tecniques, ",", tec[]); //this method will be inside the class, it's not necessary ene[n]->tecniques
  if(e==1 && tec[0]~="" && mp>0) //special move.
  {
    e=0;
    until(tec[e]=="") {e++} //how many tecs can this monster use
    e=random(e+1)-1; //randomly select one
    if(tecnique[tec[e]]->MPcost<mp)

    {
         if(tecnique[tec[e]]->type<3) //type 1 and 2 means attack players
         {
           mp-=tecnique[tec[e]]->MPcost;
           tecnique[tec[e]]->Action(this, player[playerHandle[n]]); //throw tec
         }
         else //healing tec
         {
           mp-=tecnique[tec[e]]->MPcost;
           local(r)=99;
           until(ene[r]->hp>0) {r=random(10)}
           tecnique[tec[e]]->Action(this, ene[r]); //throw tec to living enemy
         }
    }
    else //miss attack
    {
      runAnm(this, anmAtk);
      runAnm(player[playerHandle[n]], player[playerHandle[n]]->anmBlk);
    }
  }
  else //regular attack
  {
    runAnm(this, anmAtk);
    player[playerHandle[n]]->hp-=((strike*3)/4)+random(strike/4); //damage formula, you should change this
    runAnm(player[playerHandle[n]], player[playerHandle[n]]->anmAtk);
  }
  if(player[playerHandle[n]]->hp<1) {player[playerHandle[n]]->killPlayer()} //check if the player is death
}

If you see the regular attack, it only selects a random player, run the animations for the attack and that's it. But the enemy will never fail so you may want to change that.

If you see the comments you can figure out what I did, but to make it simple, I'll also explain it step by step.

-Count total of players
-Select a living player
-Randomly select if use a tec or attack the player
---If a tec is selected and this enemy can do a tec and has some mp then select a random tec
------If you have the mp to throw the selected tec then check if it's an attack or healing tec.
---------If it's an attack then hurt the randomly selected player
---------If it heals then run the animation of the tec and heal you
------If you don't have the required mp to throw the tec then perform a miss attack
---Otherwise, make a simple attack that never fails (you should change this)

Now let's make the runAnm method that the attack Method is calling.

Method RunAnm(clas, anm) //the class is the player[] or ene[]
{
  local(n)=0; //first we have to look for the canvas
  if(clas->ply) //if this is a player animation
  {
    until(player[playerHandle[n]]=="")
    {
      if(player[playerHandle[n]]->plx==clas->plx && player[playerHandle[n]]->ply==clas->ply)
      {
        local(c)=CPlayer[playerHandle[n]];
      }
      n++;
    }
  }
  else //if it's not
  {
    until(ene[n]=="")
    {
      if(ene[n]->enex==clas->enex && ene[n]->eney==clas->eney)
      {
        local(c)=Cene[n];
      }
      n++;
    }
  }
  n=1;
  split(anm, ",", anm[]);
  until(anm[n]=="")
  {
    clear(c);
    setImageTransparent(anm[n], 0, 0, clas->size, clas->size, 255, 255, 255, c);
    if(clas->plx>0) {drawCanvas(c, clas->plx, clas->ply)}
    else {drawCanvas(c, clas->enex, clas->eney)}
    delay(castNum(anm[0]));//anm[0] is speed as string so I use castnum to make it a number
    n++;
  }
  split(clas->anmIdl, ",", anm[]);
  clear(c); //now let's change for the first frame of the idl animation
  setImageTransparent(anm[1], 0, 0, clas->size, clas->size, 255, 255, 255, c);
  if(clas->plx>0) {drawCanvas(c, clas->plx, clas->ply)}
  else {drawCanvas(c, clas->enex, clas->eney)}

  n=0; //now the clean up
  until(anm[n]=="")
  {
    kill(anm[n]);
    n++;
  }
  kill(n);
}

I know that the special move part may seem a little complicated but its not, first I want to say that as there should be an ene[] array before the battle begins:

ene[0]=goblin;
ene[1]=goblin;
battleNow();

There should be a tecnique[] array as well but using the name of the tecnique instead of numbers

tecnique["charge"]=SMove(5, 2, 1, 1, "0.15,charge1.png,charge2.png");
goblin=Enemy("Goblin", 20, 8, 64, 1, 5, 3, "charge,charge"); //yeah, 2 times the same smove isn't smart I know
ene[0]=goblin;
ene[1]=goblin;
bettleNow();

Now we create the tecnique "charge" and then we save it on the variable tecnique["charge"], we then make the goblin and make him able to use "charge" when we create it

It would be really annoying to have to create ALL the tecniques oon every battle, so make the tecniques global variables on your start program or a program that runs before the first battle, that will take the pressure off because you'll be able to call it whenever you want.

Now, going back to the enemy attack method, if the chosen special move has a cost greater than it's current MP, then the enemy will perform a regular miss attack.

I believe we're done with this method because I'm not going to do an ai program. A basic ai would be that you put a way for the enemy to know which player has the lowest hp and attack it, or find a way to select a specific special move, but I'll leave that to you.

Now let's go to the special moves, on a regular BS you need the special moves have a specific type (single enemy, single player, all enemies, all players) I'll just cover those basic types with the next:

1)Bolt (single enemy target)
2)Meteor (all enemy target)
3)Heal (single friendly target)
4)Cure all (all friendly target)

I'll cover these moves to help you understand, if you can understand these types of moves, you can make your own and since these types are the basics you'll basically have everything you need for the BS moves.


1)Bolt

Since when you call this method you're inside a class, the action method can use a lot of the tricks we used for the runAnm method, such as differentiate players and enemies by using the ply variable, or obtaining the canvas (although, I don't think we need it for now) let's just start.

When you make a move you have to analyze what do you want it to do, for a bolt what we want is just:

a- the caster will run it's casting animation
b- a bolt to appear were the target is
c- the target will run it's hurt animation
d- the target will lose hp
e- check if the target should die

And now that we know what we want to happen, we code it.

NOTE: remember that the following method will be inside of the SMove class

BTW:I'm still using the runAnm method we made on day 2, but only for special moves

Method Action(caster, target)
{
  runAnm(caster, caster->anmCast); //a
  savesreen();
  if(target->ply>0) {runAnm(animation, target->plx, target->ply, anmWidth, anmHeight)} //b
  else {runAnm(animation, target->enex, target->eney, anmWidth, anmHeight)}
  delay(1);
  //you may want to center this animation a little better.
  restorescren();

  runAnm(target, target->anmHrt); //c

  target->hp-=(((caster->strike+Power)*3)/4)+random((caster->strike+Power)/4); //d

  if(target->hp<1)//e
  {
    if(target->ply>0) {target->killPlayer()} //if an enemy attacked the player and it died
    else {target->killEnemy()}
  }
}

You can still customize this a lot so keep that in mind


2)Meteor

A meteor is almost the same, the only difference is that you just have to go through all the enemies or characters, to make it easier to use we'll still make this method with the same 2 parameters, this is weird but I think that it'll be easier to remember how to use and will make the coding easier.

But since we want all the target animations to be shown at the same time, we should use the animate battle method we made as a reference and implement all the hurt animations. So the planned steps would be.

A- the caster will run it's casting animation
b- a meteor will appear were every enemy is
c- the targets will run their hurt animations
d- the targets will lose hp
e- check if the targets should die

Method Action(caster, target)
{
  runAnm(caster, caster->anmCast); //a
  savesreen();
  if(target->ply>0) //attacking all players
  {
    multiAnm(animation, 3); //b
    delay(1);
    restorescren();
    multiAnm("hurt", 1); //c
    local(n)=0;
    until(player[playerHandle[n]]=="")
    {
      player[playerHandle[n]]->hp-=caster->strike+Power-player[playerHandle[n]]->defense; //d
      if(player[playerhandle[n]]->hp<1) {player[playerHandle[n]]->killPlayer()} //e
      n++;
    }
  }
  else //attacking all enemies
  {
    multiAnm(animation, 4); //b
    delay(1);
    restorescren();
    multiAnm("hurt", 2); //c
    until(ene[n]=="")
    {
      ene[n]->hp-=caster->strike+Power-ene[n]->defense; //d
      if(ene[n]->hp<1) {ene[n]->killEnemy()} //e
      n++;
    }
  }
}


These animation methods are for multiple animations for or over the players and enemies.

Method multiAnm(animation, combination) //this will run several animations at once
{
  local(n)=1;
  local(nn)=0;
  local(nnn)=0;
  if(combination==1) //type=1 means all players will run this animation.
  {
    until(nnn==n*20)
    {
      n=0;
      until(player[playerHandle[n]]=="")
      {
        split(selectAnimation(player[playerHandle[n]], animation), ",", anm[]);
        if(frame[playerHandle[n]]=="") {frame[playerHandle[n]]=1)
        if(castnum(anm[0])*1000<=getTickCount()-player[playerHandle[n]]->anmSpeed && anm[frame[playerHandle[n]]]~="")
        {
          clear(CPlayer[playerHandle[n]]);
          setImagetransparent(anm[frame[playerHandle[n]]], 0, 0, player[playerHandle[n]]->size, player[playerHandle[n]]->size, 255, 255, 255, CPlayer[playerHandle[n]]);
          drawCanvas(CPlayer[playerHandle[n]], player[playerHandle[n]]->plx, player[playerHandle[n]]->ply);
          player[playerHandle[n]]->anmSpeed=getTickCount();
          frame[playerHandle[n]]++;
          if(anm[frame[playerhandle[n]]]=="") {frame[playerHandle[n]]=100; nnn+=20}; //this will help end the loop
          local(nn)=0;
          until(anm[nn]=="") //from this point it's just clean-up
          {
             kill(anm[nn]);
             nn++;
          }
        }
        n++;
      }
    }
    n=0;
    until(frame[playerHandle[n]]=="")
    {
      kill(frame[playerHandle[n]]);
      n++;
    }
  }
  if(combination==2) //type=2 means all enemies will run this animation.
  {
    until(nnn==n*20)
    {
      n=0;
      until(ene[n]=="")
      {
        split(selectAnimation(ene[n], animation), ",", anm[]);
        if(frame[n]=="") {frame[n]=1);
        if(castnum(anm[0])*1000<=getTickCount()-ene[n]->anmSpeed && anm[frame[n]]~="")
        {
          clear(Cene[n]);
          setImageTransparent(anm[frame[n]], 0, 0, ene[n]->size, ene[n]->size, 255, 255, 255, Cene[n]);
          drawCanvas(Cene[n], ene[n]->enex, ene[n]->eney);
          ene[n]->anmSpeed=getTickCount();
          frame[n]++;
          if(anm[frame[n]]=="") {frame[n]=100; nnn+=20}; //this will help end the loop
          local(nn)=0;
          until(anm[nn]=="") //from this point it's just clean-up the variables that should be deleted
          {
             kill(anm[nn]);
             nn++;
          }
        }
        n++;
      }
    }
    n=0;
    until(frame[n]=="")
    {
      kill(frame[n]);
      n++;
    }
  }
  if(combination==3) //type=3 means this animation will run over every player.
  {
    until(nnn==n*20)
    {
      n=0;
      until(player[playerHandle[n]]=="")
      {
        split(animation, ",", anm[]);
        if(frame[playerHandle[n]]=="") {frame[playerHandle[n]]=1)
        if(castnum(anm[0])*1000<=getTickCount()-anmSpeed[playerHandle[n]] && anm[frame[playerHandle[n]]]~="")
        {
          if(Canm[playerHandle[n]]=="") {Canm[playerHandle[n]]=createCanvas(player[playerhandle[n]]->size,player[playerhandle[n]]->size)}
          clear(Canm[playerHandle[n]]);
          setImagetransparent(anm[frame[playerHandle[n]]], 0, 0, player[playerHandle[n]]->size, player[playerHandle[n]]->size, 255, 255, 255, Canm[playerHandle[n]]);
          drawCanvas(Canm[playerHandle[n]], player[playerHandle[n]]->plx, player[playerHandle[n]]->ply);
          anmSpeed[playerhandle[n]]=getTickCount();
          frame[playerHandle[n]]++;
          if(anm[frame[playerHandle[n]]]=="") {frame[playerHandle[n]]=100; nnn+=20} //this will help end the loop
          local(nn)=0;
          until(anm[nn]=="") //from this point it's just clean-up
          {
            kill(anm[nn]);
            nn++;
          }
        }
        n++;
      }
    }
    n=0;
    until(frame[playerHandle[n]]=="")
    {
      killcanvas(Canm[playerhandle[n]]);
      kill(anmSpeed[playerhandle[n]]);
      kill(frame[playerHandle[n]]);
      n++;
    }
  }
  if(combination==4) //type=4 means this animation will run over every enemy.
  {
    until(nnn==n*20)
    {
      n=0;
      until(ene[n]=="")
      {
        split(animation, ",", anm[]);
        if(frame[n]=="") {frame[n]=1)
        if(castnum(anm[0])*1000<=getTickCount()-anmSpeed[n] && anm[frame[n]]~="")
        {
          if(Canm=="") {Canm=createCanvas(ene[n]->size, ene[n]->size)}
          clear(Canm[n]);
          setImageTransparent(anm[ene[n]->anmFrame], 0, 0, ene[n]->size, ene[n]->size, 255, 255, 255, Cene[n]);
          drawCanvas(Cene[n], ene[n]->enex, ene[n]->eney);
          anmSpeed[n]=getTickCount();
          frame[n]++;
          if(anm[frame[n]]=="") {frame[n]=100; nnn+=20} //this will help end the loop
          local(nn)=0;
          until(anm[nn]=="") //from this point it's just clean-up
          {
            kill(anm[nn]);
            nn++;
          }
        }
        n++;
      }
    }
    n=0;
    until(frame[n]=="")
    {
      killcanvas(Canm[n]);
      kill(anmspeed[n]);
      kill(frame[n]);
      n++;
    }
  }
}

Method selectAnimation(clas, animation) //this checks for the animations inside the enemy and player class
{
  if(animation=="idle") {return clas->anmIdl}
  elseif(animation=="attack") {return clas->anmAtk}
  elseif(animation=="cast") {return clas->anmCast}
  elseif(animation=="block") {return clas->anmBlk}
  elseif(animation=="hurt") {return clas->anmHrt}
  else {debugger("the animation "+animation+" doesn't exist on class")}
}

I think that that last method is a little hard to understand so you should know you don't really have to get it, just know how to use it.

When you call multianm you have to put the animation and the combination.

Combination=1 all players will run the animation from their classes //animations inside the class
Combination=2 same as above but all enemies
Combination=3 the animation will be runned above all players
Combination=4 same as above but with enemies.

When combinations are 1 or 2 the selectAnimation() method is called, this is to check strings, for example multianm("hurt", 1) will make all the player run their hurt animation because I put elseif(anm=="hurt") on the selectanimation() method, so to add more animations to the class you should modify the selectanimation() method.

Actually that's the only thing I think that could need modification so... Good for you.

When you call a special move animation you don't have to do anything but to use type to say if it's for the players or enemies (3 or 4).

And if you want to put a single animation that runs over all players or enemies you just have to call the regular runanm() and play with the width and height.

You should make your own damage formula for point d of the action() method but I think everything else should work fine, although it's your call how much you want to change.


3)Heal

As you should know, heal will add hp so that's what the action method should do, but I'll make it heal a value using a VERY simple formula that you should change

a- the caster will run it's cast animation
b- run the special move animation
c- the target will run it's idl animation (you can change this)
d- the target will gain life
e- check if the hp is greater than the maxhp and fix it

Method Action(caster, target)
{
  runAnm(caster, caster->anmCast); //a
  savesreen();
  if(target->ply>0) //healing a player
  {
    runAnm(animation, target->plx, target->ply, target->size, target->size); //b
    delay(1);
    restorescren();
    runAnm(target, target->anmIdl); //c
    target->hp+=caster->strike+Power; //d
    if(target->hp>target->maxhp) {target->hp=target>maxhp} //e
  }
  else //healing an enemy
  {
    runAnm(animation, target->enex, target->eney, target->size, target->size); //b
    delay(1);
    restorescren();
    runAnm(target, target->anmIdl); //c
    target->hp+=caster->strike+Power; //d
    if(target->hp>target->maxhp) {target->hp=target>maxhp} //e
  }
}

This is easy to understand so I wont explain it, just look it for a minute if you don't get it.


4)Cure all

This is a combination between heal and meteor, not a lot left to explain.

a- the caster will run it's cast animation
b- run the special move animation over every target
c- the targets will run their idl animation (you can change this)
d- the targets will gain life
e- check if the hp is greater than the maxhp and fix it

Method Action(caster, target)
{
  runAnm(caster, caster->anmCast); //a
  savesreen();
  if(target->ply>0) //healing all players
  {
    multiAnm(animation, 3); //b
    delay(1);
    restorescren();
    multiAnm("idle", 1); //c
    local(n)=0;
    until(player[playerHandle[n]]=="")
    {
      player[playerHandle[n]]->hp+=caster->strike+Power; //d
      if(player[playerhandle[n]]->hp>player[playerhandle[n]]->maxhp) {player[playerHandle[n]]->hp=player[playerHandle[n]]->maxhp} //e
      n++;
    }
  }
  else //healing all enemies
  {
    multiAnm(animation, 4); //b
    delay(1);
    restorescren();
    multiAnm("idle", 2); //c
    until(ene[n]=="")
    {
      ene[n]->hp+=caster->strike+Power-ene[n]->defense; //d
      if(ene[n]->hpmaxhp) {ene[n]->hp=ene[n]->maxhp} //e
      n++;
    }
  }
}

Not much has changed, just the fact that I add hp instead of taking it, compare this with the meteor.

Now, it would be weird to have all these action methods inside the same class right? So what are the options? You can make 4 different classes one for each methods.... Or you can put all the methods together and use the variable type inside the SMove class to know which method to use when we call the special move.

Method Action(caster, target) //this has to be inside the SMove class
{
  if(type==1) //attack single
  {
  runAnm(caster, caster->anmCast); //a
  savesreen();
  if(target->ply>0) {runAnm(animation, target->plx, target->ply, anmWidth, anmHeight)} //b
  else {runAnm(animation, target->enex, target->eney, anmWidth, anmHeight)}
  delay(1);
  //you may want to center this animation a little better.
  restorescren();

  runAnm(target, target->anmHrt); //c

  target->hp-=(((caster->strike+Power)*3)/4)+random((caster->strike+Power)/4); //d

  if(target->hp<1)//e
  {
    if(target->ply>0) {target->killPlayer()} //if an enemy attacked the player and it died
    else {target->killEnemy()}
  }
  }

  elseif(type==2) //attack all
  {
  runAnm(caster, caster->anmCast); //a
  savesreen();
  if(target->ply>0) //attacking all players
  {
    multiAnm(animation, 3); //b
    delay(1);
    restorescren();
    multiAnm("hurt", 1); //c
    local(n)=0;
    until(player[playerHandle[n]]=="")
    {
      player[playerHandle[n]]->hp-=caster->strike+Power-player[playerHandle[n]]->defense; //d
      if(player[playerhandle[n]]->hp<1) {player[playerHandle[n]]->killPlayer()} //e
      n++;
    }
  }
  else //attacking all enemies
  {
    multiAnm(animation, 4); //b
    delay(1);
    restorescren();
    multiAnm("hurt", 2); //c
    until(ene[n]=="")
    {
      ene[n]->hp-=caster->strike+Power-ene[n]->defense; //d
      if(ene[n]->hp<1) {ene[n]->killEnemy()}; //e
      n++
    }
  }
  }
  elseif(type==3) //heal single
  {
  runAnm(caster, caster->anmCast); //a
  savesreen();
  if(target->ply>0) //attacking all players
  {
    runAnm(animation, target->plx, target->ply, target->size, target->size); //b
    delay(1);
    restorescren();
    runAnm(target, target->anmIdl); //c
    target->hp+=caster->strike+Power; //d
    if(target->hp>target->maxhp) {target->hp=target>maxhp} //e
  }
  else //attacking all enemies
  {
    runAnm(animation, target->enex, target->eney, target->size, target->size); //b
    delay(1);
    restorescren();
    runAnm(target, target->anmIdl); //c
    target->hp+=caster->strike+Power;//d
    if(target->hp>target->maxhp) {target->hp=target>maxhp}; //e
  }
  }
  if(type==4) //heal all
  {
  runAnm(caster, caster->anmCast); //a
  savesreen();
  if(target->ply>0) //healing all players
  {
    multiAnm(animation, 3); //b
    delay(1);
    restorescren();
    multiAnm("idle", 1); //c
    local(n)=0;
    until(player[playerHandle[n]]=="")
    {
      player[playerHandle[n]]->hp+=caster->strike+Power; //d
      if(player[playerhandle[n]]->hp>player[playerhandle[n]]->maxhp) {player[playerHandle[n]]->hp=player[playerHandle[n]]->maxhp} //e
      n++;
    }
  }
  else //healing all enemies
  {
    multiAnm(animation, 4); //b
    delay(1);
    restorescren();
    multiAnm("idle", 2); //c
    until(ene[n]=="")
    {
      ene[n]->hp+=caster->strike+Power-ene[n]->defense; //d
      if(ene[n]->hpmaxhp) {ene[n]->hp=ene[n]->maxhp}; //e
      n++;
    }
  }
  }
}

Now, this is just a way to do it, be creative and maybe add more types or a variable to control the percentage of life you're healing or more damage formulas or anything you want, just try not to stick with the things on this tut, go further.


Customization: This was a long day, but all I did was the basic methods for the enemy turn and the animations, try now and do the same, remember what I did and it may help you, this is probably the hardest day so far and it's OK if it takes you a while to understand how to keep ahead. if you're using these methods you just have to change the parts you want, like the damage formulas or the names of the animations to be used, just fool around until you like what you have, remember that when you finish this day you're done with almost all the battle system so be patient and keep optimistic.

END OF DAY 5

Creating A Turn-Based Battle System

DAY 6

Now that we have the BS running and everyone casting special moves and stuff, we only have to make the player turn and we'll be done with the BS and the tutorial. I made a variable inside the payerclass called "menu", with this variable we can make a string with the options, kinda like the special moves inside the enemy class.

So a menu variable could be "attack,magic,run" and you can use a method like the selectAnimation to call the right method to use when the option is selected.

For example, if you make menu="attack,skill,combo,run" then you can split it and draw them. Then call the method comparing the string, like if(var[0]=="attack") {attack()} assuming that this is after the menu variable is divided into the var[] array, and we selected the option 0, which means var[0] which is "attack", so we call the method attack.

Method PlayerMenu() //this is the method called when it's the player's turn, has to be inside the player class
{
  local(n)=0;
  split(menu, ",", option[]);
  until(option[n]=="") //how many choices are?
  {
    n++;
  }
  setImage("playerMenu.png", plx+size, ply, 100, (n*getFontSize())+20); //background from were the choices will be
  cmap=createCursorMap();
  n=0;
  until(option[n]=="") //draw choices
  {
    pixelText(plx+size+8, ply+(n*getFontSize())+5, option[n]);
    cursorMapAdd(plx+size+8, ply+(n*getFontSize())+5, cmap);
    n++;
  }
  local(choice)=cursorMapRun(cmap);
  killCursorMap(cmap);
  selectOption(option[choice]);
  n=0;
  until(option[n]=="") //clean-up
  {
    kill(option[n]);
    n++;
  }
  kill(choice);
}

Method selectOption(n) // this has to be inside the player class.
{
  if(n=="attack") {attack()}
  elseif(n=="magic") {magic()}
  elseif(n=="run") {run()}
}

as you can see, this is just a code to draw the options menu to one side of the player, then use cursormap to select an option, and call the method corresponding to the option selected (add more to the selectOption() method).

If a method called magic() is inside the player class but the player "menu" variable doesn't say magic (ex:"attack,run") then that player won't use magic, so this is a good way of doing things to make one class with all the methods and use it to make all the players with different abilities.

Now we should make the other methods right? Attack, magic and run right?

WRONG!!!!!

Since a method like magic() or skills() or tecniques() requires we do a list I wont make them because that's up to you how to make a list system. But I'll tell you that you only have to make a list with all the tecniques the payer can use (these are on the technique variable inside of the class) and then call the action() method inside the technique class (kindda like what we did with the enemy yesterday).

So the only methods to do now is just the attack and run, but the attack it's almost the same, you have to select an enemy using cursormaps and run the animations of the player and target and then take the hp (kinda like what we did yesterday inside the special move attack single method so go back to it).

So the only thing you haven't seen is a run() method, which is EXTREMELY easy.

Method run() //this has to be inside the player class
{
  local(r)=random(50-speed); //the faster the player, the easiest it is to get away.
  If(r==1)
  {
    r=0;
    until(ene[n]=="")
    {
      ene[n]->hp=0;//once all enemies hp's are 0 the battle will end(see startBattle() method)
      n++;
    }
  }
  else
  {
     //here a "you can't run" message
  }
}

When all the enemies are death the battle will end automatically, but if we don't call the killEnemy method inside the enemy class, then the players won't get any experience or any item as a reward.

Of course, we still have to finish those methods, kill enemy and killplayer, so let's do them and then we can finish the BS today.

Method killEnemy() //this has to be inside the penemy class
{
  hp=0;
  local(n)=0;
  until(player[playerHandle[n]]=="")
  {
    giveexp(playerHandle[n], exp);
    n++;
  }
  n=random(10);
  if(n==1 && len(reward)>0) {giveItem(reward)}
  givegp(gp);
}

As you can see this is something really easy to understand, and I don't erase the enemy from the screen because being his hp lower than 0 the battle() method erase it automatically.

Method killPlayer() //inside the player class
{
  hp=0;
}

Again, the battle method erases the player from the screen when his hp is lower than 1 so this is all that really needs to be done here, but you can run some animation and put text on the screen if you want.

Now that everything is setted, and the classes work fine, and the methods are done, we just have to start a real battle by putting the methods together.

Method StartBattle()
{
  local(doneBattle)=0;
  setupBattle();
  until(doneBattle>0)
  {
    Battle();
    local(n)=0;
    until(playerHandle[n]=="")
    {
      if(player[playerhandle[n]]->hp==0) {doneBattle+=20}
      n++;
    }
    if(doneBattle==n*20) {doneBattle=1} //all players death
    else {doneBattle=0} //keep fighting
    n=0;
    until(ene[n]=="" || doneBattle==1)
    {
      if(ene[n]->hp==0) {doneBattle+=20};
      n++;
    }
    if(doneBattle==n*20) {doneBattle=2} //all enemies killed
    elseif(doneBattle~=1) {doneBattle=0} //keep fighting
  }
  endBattle();
  if(doneBattle==2) {debugger("you won!") } //you won
  elseif(doneBattle==1) {debugger("defeated"); run("gameover.prg")} //you lost
}

So, as you see, you only have to change the last part of this code to draw nice looking messages for the results of you won! And defeated, I'll leave that to you, also, you should make a method to put the hp of the killed ones to 1 in case you win with someone death.

Finally, let's make a battle, assuming you created an enemies and special moves.

ene[0]=warlord();
ene[1]=warlord();
ene[0]->tecnique="heal,meteor,bolt,cure all";
ene[1]->tecnique="flare,fireball,crash,earthquake";
StartBattle();

This is the final result of all the work, looks like too little for so much work right? But that's the idea, make a battle system that is easy to use so that you don't have to go through all of that effort again, just minor looks when you want to add something and that's it.

Customization: today was not completely done, create all the methods you have to do for the player class, keep it simple and you won't get confused, take a look at the enemy and try to use you common sense to figure things out, I wouldn't leave this to you if I didn't knew how easy it is so don't worry and take your time to code, and then just pur everything together, make all the special moves and enemies and start getting creative to come up with a fancy way of selecting them while the player walks around.

DAY 6...DONE!

CONGRATULATIONS!!!!! YOU FINISHED THIS TUTORIAL, NOW YOU SHOULD HAVE A PRETTY GOOD IDEA ON HOW TO MAKE A BATTLE SYSTEM, JUST REMEMBER THAT THIS IS ONLY MY WAY OF DOING THINGS, NOW THAT YOU SEE THIS, YOU CAN USE THE IDEAS YOU FIND GOOD AND APPLY YOUR OWN CHANGES, MORE GRAPHICS, MORE MESSAGES ON THE ATTACK METHODS, ANYTHING YOU WANT CAN BE DONE SO JUST THINK AND CODE!!!.

BTW: I've attached the codes fixed and with the layout they should have by now.

website by phil carty © 2011
additional icons by matty andrews and dana brett harris