turn based battle system help

Started by Anshinin, Tue 10/03/2015 01:47:53

Previous topic - Next topic

Anshinin

I'm confused though, how could I get a button to run a script like this if putting it inside of it would make a weird nested function?

Khris

#21
You need to understand how functions work.

Let's define one:
Code: ags
function HelloPlayer() {
  player.FaceLocation(player.x, player.y + 1, eBlock); // face down
  player.Say("Hello");
}


We have now effectively added another command to the game, and we can use it in other functions.

Code: ags
function cEgo_LookAt() {
  Display("It's the character you control in this game.");
  HelloPlayer(); // call our custom function
}


Make sure you're absolutely clear what's happening here, otherwise there's no point in moving on. If the player clicks the player character with the look cursor, the game will call cEgo_LookAt(), which will Display() some text, then call the custom function, HelloPlayer(). Which in turn will make the player character face down and say something.

At no point is anything nested here; we have functions in the script, and we have function calls inside those functions.


The other important thing is that the function HelloPlayer() is actually "visible" from inside cEgo_LookAt(). This can be achieved by a) placing HelloPlayer() in the same script as cEgo_LookAt(), but further up, or b) placing it in a script further up the script tree, and importing the function in a header that ends up as part of the script that contains cEgoLookAt().

In English, if you have a few custom functions you need in all rooms, declare them at the top of the global script and import them in the global header.
If you have a lot of them, move their declarations and import lines to the main part / header of a new script.


As for your battle system, link a function to the button's on click event, so you'll get this:
Code: ags
function btn_Attack(GUIControl *control, MouseButton button)  // edit, added button param
{

}

Inside this function you call your battle system's custom attack function. Only the function call goes in there, not the actual function.

Edit: added button param to example click handler (which should not be pasted)

Snarky

Yeah, what Khris said.

However, there is another issue as well, which is how to get the loop in FightBattle() to wait for the input from the button and do the corresponding attack. There are a couple of ways to go about that, but off the top of my head, I think the easiest is to implement something like the PlayerPicksTarget() function I used in my example. (Instead of picking which enemy to target, you're picking which attack to use, but the logic is otherwise the same.)

This function should display the button(s) you want and then block until the player clicks on one, something like this:

Code: ags
Button* selectedAttackButton;

Button* PlayerPicksAttack()
{
  selectedAttackButton = null;
  gAttackPanel.Visible = true; // <-- the GUI with your buttons to choose an attack
  while(selectedAttackButton == null) 
    Wait(1);
  gAttackPanel.Visible = false;
  return selectedAttackButton;
}


Now the on_click() function for each button can just be:

Code: ags
function btnAttack_onClick(GUIControl* theControl)
{
  selectedAttackButton = theControl;
}


(If you have the battle system in a script file separate from the global script, which I would highly recommend, you either need to export and import selectedAttackButton, or provide and import a method to set it.)

If, for whatever reason, you can't block (i.e. use Wait()) inside the PlayerPicksAttack() function, it gets a little more complicated. You'll have to break up the FightBattle() loop and add some code in repeatedly_execute() to check whether the player has picked an attack yet, and if so run the attack and the enemy's response, determine if the battle is over, etc.

Remember we warned you that this whole thing was going to be pretty complex!

Anshinin

I really appreciate all the advice for this, and I'm totally doing my best to figure this out and learn from this. I understand it'll be complex but I also want to make a quality game.

I tried putting that in and it gave me this error.

Code: ags
GlobalScript.asc(729): Error (line 729): Type mismatch: cannot convert 'GUIControl*' to 'Button*'

Khris

I remembered it wrong, but you shouldn't paste button handler functions into the global script anyway, because then they won't be linked to the button, and clicking the button won't work, and you'll get back here and post that you pasted the code but the button won't work.

Just go to the button's event pane (select the button, click the thunderbolt icon), then click on the [...] button next to OnClick, and AGS will create and link the function, exactly like with all other events (which is what I told you to do two posts ago).

Anshinin

I had already done that, the only thing I did was place "selectedAttackButton = control;" into the function for my button. I didn't copy the whole function over since the name is different anyway.

ChamberOfFear

Quote from: Anshinin on Wed 17/02/2016 00:09:27
I had already done that, the only thing I did was place "selectedAttackButton = control;" into the function for my button. I didn't copy the whole function over since the name is different anyway.

Did you try this?
Code: ags
selectedAttackButton = control.AsButton;

Anshinin

That worked perfectly! Since you said this pick attack is essentially the substitution for Picking the Target, what would I replace this with?

Code: ags
int target = PlayerPicksAttack();


Since this is a button technically and not a int

Snarky

Well, as the function currently returns a Button*, that's the return type you need to use:

Code: ags
  Button* btnAttack = PlayerPicksAttack();


At some point you'll presumably want to use this value to branch into different logic to calculate the effect of the attack:

Code: ags
  if(btnAttack == btnAttackFire)
  {
    // Run a fire attack
  }
  else if(btnAttack == btnAttackMagic)
  {
    // Run a magic attack
  }
  // ...


Alternatively, instead of using the raw button value directly, you could have PlayerPicksAttack() convert it to some more semantically meaningful type, e.g. an enum or a custom struct storing more information. That would be the way to go if there isn't a direct, consistent mapping between the button and the attack, or if there are more parameters to the attack.

Anshinin

Which function would I put that under?

Snarky

Well, that's somewhat up to you, but logically it needs to go between when the player picks an attack (or other action), and when the game actually carries out that attack. So it could be inside AttackEnemy() (which would then need to take arguments both for the enemy target and for the type of attack), or it could be inside the battle loop in FightBattle(). If all the attacks/actions you can do are essentially similar (e.g. hit/kick/stab), I would put it in AttackEnemy(), but if there are some that are very different (e.g. drink a healing potion, or freeze time or something) it probably belongs more logically outside it, and you'd have other functions in addition to AttackEnemy() to handle those other scenarios.

We're getting to the core logic of your battle system here, where you really have to make the decisions depending on what you want it to be like.

Anshinin

#31
Okay that makes sense, however I'd like to make it so you can equip better weapons as the game proceeds so it wouldn't just be one button with the attack coded in. How could I code it where it detects an item being equipped and factors in that's value for power from my weapons struct to make the damage dealt?

EDIT: Also, would I put the functions for the buttons under the battle script or in the globalscript? Since when it's in the globalscript it's not detecting "selectedAttackButton = control.AsButton;"

Snarky

#32
Well, I'm not 100% clear on all the details of your system, but you'll probably want to have a variable either for the weapon that is currently equipped, or for the weapon that was selected for the attack. In a different language this would typically be a pointer to a Weapon object, but because AGS doesn't allow that, the best you can do (at least the best I can do: maybe monkey can work around it somehow) is an index into the weapons array.

Then the actual attack calculation needs to use that index to look up the relevant weapons stats, and use those value along with any other contributing factors to determine hit/miss and damage.

You may want similar variables for e.g. armor that is equipped (perhaps even separate variables for armor, shield, helmet, etc.). In a fancy system where enemies also can have different armor and weapons, you could make all of this part of the Enemy/Fighter struct, but if the player is the only character that ever needs to keep track of it, that's a bit of an overkill.

The button event handlers need to go in the Global Script: that's an AGS limitation. (However, if they're all going to be the same you don't need multiple copies of them. You can link all to a single function.) Like I mentioned in that earlier post:

Quote(If you have the battle system in a script file separate from the global script, which I would highly recommend, you either need to export and import selectedAttackButton, or provide and import a method to set it.)

Importing functions and variables (and exporting the latter) is a basic thing you need to know in writing AGS script, so best wrap your head around it.

You can export selectedAttackButton from the battle script and add an import of it to the battle header (which means it will be imported to all scripts below it in the list, so it can be accessed from any of them). However, as a matter of principle it's best not to expose too much of the inner workings of how something like this works (for example, if you want to rewrite it to work slightly differently, you want to keep the changes isolated), so it's probably better to define a function that represents the semantic action rather than the implementation detail of "set this variable", like:

Code: ags
void SelectAttack(Button* button)
{
  selectedAttackButton = button;
}


Add an import to the battle header, and now you can use this variation in the button click handler:

Code: ags
function btnAttack_onClick(GUIControl* theControl)
{
  SelectAttack(theControl.AsButton);    // thx ChamberOfFear for fix
}

Anshinin

I'm thinking I would make a struct for weapons in the game, and then have an array set and perhaps one of the values for it could be "equipped" and if it's a 0 it wouldnt be equipped and 1 then it would and display the button if it's equipped? I think logically I could code that.

Also I tried doing that and it's giving me this error.

Code: ags
BattleScript.asc(10): Error (line 10): Type of identifier differs from original declaration


in reference

Code: ags
Button* selectedAttackButton;


In the header

Code: ags
import void selectedAttackButton();

Snarky

Quote from: Anshinin on Sun 21/02/2016 22:28:48
I'm thinking I would make a struct for weapons in the game, and then have an array set

Yes. If you go back to the beginning of the thread, you'll see that this is what Vincent and Ghost recommended.

Quoteand perhaps one of the values for it could be "equipped" and if it's a 0 it wouldnt be equipped and 1 then it would and display the button if it's equipped?

Mmm... Maybe. But that would imply that you could equip an unlimited number of weapons at a time. How would you decide which one was actually used when you attack? And it would make things a little more complicated, because every time you need to do something with your equipped weapon, you'd need to loop through the array. Also, if you just need a flag for something that is true or false, you should declare it as a bool and actually set it to true or false, not 0 and 1.

If you can only equip a couple of weapons at a time, I would just have a couple of variables, e.g. int primaryWeapon, secondaryWeapon, and set them to the indexes of those weapons in the weapons[] array. If your "equipped" weapons work more like an inventory, I might make another array of equippedWeapons[] that again references the indexes in the weapons[] array, but in that case it's a bit less clear.

QuoteAlso I tried doing that and it's giving me this error.

Code: ags
BattleScript.asc(10): Error (line 10): Type of identifier differs from original declaration


in reference

Code: ags
Button* selectedAttackButton;


In the header

Code: ags
import void selectedAttackButton();


I'm not sure what you're trying to do here. You've declared selectedAttackButton as a Button pointer, but then you try to import it as a function. That doesn't work, obviously. You have to choose one or the other. What I recommended was to use a function to access it, but that function can't have the same name as the variable. Notice SelectAttack() vs. selectedAttackButton.

Khris

I'm terribly sorry, but:
Quote from: Khris on Tue 16/02/2016 16:20:01
You need to understand how functions work.
[...]
Make sure you're absolutely clear what's happening here, otherwise there's no point in moving on.

[...] if you have a few custom functions you need in all rooms, declare them at the top of the global script and import them in the global header.
If you have a lot of them, move their declarations and import lines to the main part / header of a new script.

Anshinin

I apologize if I seem like I'm wasting you guys time, I'm reading documentation on the scripting language alongside this and trying to learn it the best I can. I think I'll take a bit of time to review over this thread so far and try to understand everything 100% before I move on because I realize my questions aren't improving my understanding and I need to tackle this at a more fundamental level.

I still will keep up with this thread I just don't want to waste your time with simple questions. So basically I won't move on until I'm absolutely clear about what's happening here :P

SMF spam blocked by CleanTalk