Turn Based Fighting System

Started by xerca, Mon 05/03/2012 13:55:01

Previous topic - Next topic

xerca

I am trying to make a turn based battle for my game. I had actually made one, which works for one enemy (also it included codes to work with at most 4 enemies in a sloppy way, but never tried that) but I wanted to change it so there could be friends fighting besides the player and actually working "more enemies". I looked at my messy pile of code which was written 4 months ago, which has absolutely no comments to help me remember. After reading it all over I remembered what did what but I couldn't improve it. It just doesn't support being helped, so I will just make a new and a better one. I just now tried making something but I couldn't do anything better. I usually solve my problems myself(in a long time) but I don't have enough time to just try and fail.

I don't really want to put the codes here because of their mess, length and foreign languagely named variables and functions. But I'll tell what it did and how it did. I'm not a genius at coding so I need some help improving my coding skill. I don't wish a readily written and working code, I just want to learn better ways of doing things.

In my old system; there was the player, which teleported to the fighting screen when he came close enough to a wandering enemy. The function which teleported them upon being close; labeled the enemy as "enemy1"(which was a global variable). In the room's fade in, the player and the enemy was placed on previously chosen coordinates and according to their "agility" stats, player or the enemy had the first turn. The player had three options: physical attack, magic and nothing. If player did nothing, nothing happened. If he used physical attack, he hopped and attacked the enemy. The damage was determined from player's strength stat, and a random number. Missing and critical hit chances were determined by player's perception and luck stats ,player's close combat skill and a random number. Those were the same for the enemy, too. If the player choosed magic, they saw an inventory and selected the magic they want and used it on the enemy. The damage was determined by the player's intelligence stat and the missing and critical chances by his perception, luck, magic skill and of course a random number. The player and the enemy had health, energy and cooldown bars. When the health is 0, you die. Energy was used for magic. And cooldown is the time you have to wait before attacking. Cooldown was determined by the agility. Whose cooldown becomes 0, has the turn and can attack. This was the feature which seperated the system from absolute turn based fights. At the end of a match, the player gained experience which is collected to level up and then each level he gained points to increase his stats and skills.

Now, what I need help with is having a friend with the player and having more possible enemies. I hate to write the code over and over again for each possibility like 1 enemy, 2 enemies, 3 enemies or 4 enemies and 2 friends, etc... I want to make a code which will work for every combination without problems but I couldn't figure how to do it. I read lots of things about things like arrays or structs, but I couldn't use them which got me to the conclusion that I couldn't actually understand those. Maybe they are just unrelated stuff I misunderstood.
I don't need much help about making things I explained, I can do them well enough. But I can't implement them to a new system with more characters.

I shall appreciate any help.

Khris

I'm going to start with arrays and structs. Once you get the syntax, it's not that hard.

Code: ags
// header

struct str_enemy {
  String Name;
  int HP;
};

import str_enemy enemy[20];

// body

str_enemy enemy[20];
export enemy;


This code defines a struct called str_enemy which is used in declarations and import lines just like you'd use "int" or "String".
The first line in the body declares an array of type str_enemy with 20 elements.
The import and export lines make the variable global, meaning it can be accessed in other scripts.

Now you can do this:
Code: ags
  enemy[0].Name = "Blob";
  enemy[0].HP = 50;
  enemy[1].Name = "Goblin";
  enemy[1].HP = 85;


On to the battle engine: to avoid duplicate code, the members of the party and the enemies should be represented by the same data structure.
What I did is use a struct with members like HP, currentHP, currentAgility, etc. and at the start of a battle load the data of the enemies and my party into that structure.
Thus the engine loop doesn't have to distinguish whether a fighter is an enemy or not until that becomes important (I simply used a bool called .isEnemy)
Pseudocode:
Code: ags
while (battle underway) {
  while (no fighter's turn) {
    advance everyone's cooldown/time bar
  }
  if (fighter is enemy) AI();
  else Process_Player_Input();
}


When I coded this, I restricted the engine to 3 party members and 5 enemies tops. It's easier to deal with fixed sizes but of course it's not very flexible.

xerca

Thanks very much for the speed and quality of your response. I feel like really understood it.(read it 4 times before feeling that way but still I'm happy) As far as I understood, you created a "struct" called "str_enemy" with properties and created 20 examples of that type. So they can all have the same properties with different values. Then you gave values to their properties. I hope I got it right.

But I don't know what you meant by "body". Is it the global script(not the header)? It is like creating variables so I suppose that.

Also you created 20 "enemies" but exported only "enemy" without a number. Does it not matter?

There are also party members fighting with the player and they have properties like HP and name, too. Should they be str_enemy struct type, too or should they have a different type? Thinking there is a bool ".isEnemy", they should be "enemy" too as a type but that just doesn't sound right.

I think of making the party members fight on their own like enemies, so I think I can use the same fighting AI for them. Just having them attack the enemies instead of themselves should work.

By the way, I don't think there would be a fight including more than the player, 3 party members and 4 enemies; so that much flexibility is not necessary - I don't even think that more characters would fit into the screen.

Thanks again for your help, your answer really helped me more than I expected.

Khris

What I called body is the main script, the .asc part.

You can in theory put everything in the global script but I recommend creating additional scripts to organize things. Each of them has their own header and main part.
The order matters because a script can't access stuff declared in scripts further down. So the more general a variable or function is, the higher up in the script tree it should go.
You could for example create a script that declares the enemy struct and defines all the different types of enemies. Since it would pretty much only contain static definitions, it should be near or at the top.

A room script is the lowest in the hierarchy, meaning it can access all the (global) variables and functions. When I created my battle engine, I created a battle room and put most of the battle code in there.

The export line requires only the name, you must not state the variable's type or, in case it's an array, it's dimension. So "export enemy;" will export the entire 20 element array.

In my engine I used another struct for the party data. At the start of a fight, the relevant parts of party[0] to party[2] were copied into fighter[0] to fighter[2] and the members of fighter[3] to fighter[7] were changed according to what enemies the party had encountered.

Here's a video of my battle engine in action, in case you're interested. I originally planned to release the battle engine as a module but I haven't worked on it in months and it's pretty messy.

xerca

Wow, that looks a lot like the system I made.

I read about creating your own script for organization (and that hierarchy) in the manual before, but never tried it. I didn't know how it would work but now I see it is quite simple. It would be best to do it this time. Most of my old battle code was in a room script, too. But the functions and some variables were defined in the global script.

I liked your way of getting the fighter's data into the battle. I can make something alike.

By the way, I'm sure I heard that winning clip from the last part of the video somewhere.

Thanks, again.

Khris

Both tracks are from Chrono Cross. :)

Calin Leafshade

You should write a book, Khris

xerca

#7
Quote from: Khris on Mon 05/03/2012 18:59:05
Both tracks are from Chrono Cross. :)
I think I remembered that one from Chrono Trigger, the game which I also stole the cooldown-between-turns idea from.

EDIT: I deleted the last question due to remembering something but suddenly I had another one:

I couldn't find a proper way to randomize AI attacks. Let's say there are enemies, or just one enemy and it is its turn to attack. There may be only one oppenent(the player, which is always fighter[0]), there may be two, three or four different opponents to attack. I can theoretically write something that will check every probability like if fighter[0] and fighter[3] is available, select between two numbers and if fighter[0], [2] and [3] is available then select between 3 random numbers, etc. Or I could make it so no matter who the friend is, they are numbered in order so if there are 2 friends there won't be fighter[3] or fighter[4] regardless of who they are and that would simplify the process but I wonder if there is a way to use one general thing for every possibility.

Victor6

Assuming I read your post correctly. A really simple way around this would be to add a faction variable (1 = player, 2 = enemy, etc) for each character. then have the AI randomly pick targets until it finds on that is A. Alive, and B. belongs to the players side.

Something like

int aitarget = -1;
int roll;
while(aitarget == -1){
  roll=Random((insert max number of targets here);
    if (unithp[roll] > 0 && unitfaction[roll] == 1){
       aitarget = roll;
    }
}
** run attack function on aitarget **

the above code might be a little dodgy. It's getting late.....

xerca

Thank you very much, it works. I actually thought a way checking whether the target is dead but my idea wasn't so good because it made some possibilities more possible than others in some cases. Yours is perfect.

Khris

What I did was, like you mentioned, populate fighter[0] to [party_size-1].
It's a pity AGS doesn't support full OO yet, that would make thing like that so much easier.

xerca

I now finished the system mostly and it works well, thanks to you guys, especially Khris. But lots of time, I needed to use structs as parameters in functions or as pointers but as far as I understand, AGS simply doesn't allow you to do that. (I mean using a struct like it's Character or InventoryItem, etc) Everytime, I worked hard to find a workaround and did things in a different way but it started to get a little messy again and I just can't add some things if I don't use it like that. Is there any way I can do that? Like a module which lets you use structs as pointers? Or maybe I'm missing a simple thing?

It is not necessary but it would definitely make things a LOT easier.

Khris

Unfortunately no, no full OO yet. You have to use arrays of structs and pass the indexes. It's not as elegant but shouldn't make the code deteriorate into a huge mess either.

xerca

Quote from: Khris on Sun 11/03/2012 16:23:37
Unfortunately no, no full OO yet. You have to use arrays of structs and pass the indexes. It's not as elegant but shouldn't make the code deteriorate into a huge mess either.
Okay, then.

SMF spam blocked by CleanTalk