How can I write a script that refers to different characters at different times?

Started by Persephone Rose, Wed 09/01/2013 21:28:05

Previous topic - Next topic

Persephone Rose

So I'm sure that I'm probably just over-thinking things, but this problem has taken me longer than usual to figure out a solution to, so I thought I'd ask for some help.  I'm not even sure how to phrase what I'm asking, but goes nothing...

I'm working on a game with a fighting system that is a combination of RPG and button-mashing.  This code is still in its early stages, and it only works for attacking an enemy, not taking damage (which I haven't tried figuring out yet).

Here's how [what I currently have] works in my head:
Whenever you press the "Z" key on the keyboard, the character throws a punch.
If he's close enough to the enemy, he'll do damage (currently based on a random(10) integer), otherwise a message pops up that says "Not close enough!").
The enemy will run away if their health drops below 50.
When the enemy's health drops below 1, their view changes and they stop moving/attacking.

What I want to do is make it so that anywhere, anytime, you can throw a punch.  And if you're close enough to ANY enemy, you can do damage to it.  So I'd like to have one script that makes this possible, and have the enemy attacks be defined on a room-by-room basis.

So, the following is the current code in my GlobalScript.asc:

(For reference, "c1984" is a transparent, pixel-sized character that follows the player around the whole game and is used to activate/deactivate various things; in this case, once you enter a room with an enemy for the first time, the enemy starts following c1984, but appears to be going after the player).

Code: AGS
  //Setting up distance between character and enemy:
    xDiff = cRoy.x - c1984.x;
    yDiff = cRoy.y - c1984.y;
    enemyDistance = xDiff*xDiff + yDiff*yDiff;
    
     
  //PUNCH:    
  if (keycode == eKeyZ){
     
    //currentEnemy = enemyHealth; //<-- My first attempt at making it possible for the punch command to be used against any enemy, rather than just one.
    //^^ This didn't work.
    if (cRoy.View != 29){
    cRoy.ChangeView(29); //<-- Throws the punch!
    
    if (cRoy.View == 29){
      if (enemyDistance < 20500){ //<-- Ensures that punching on the other side of the room won't damage the enemy.
      damageDone = Random(10);
      enemyHealth = enemyHealth - damageDone; //<-- enemyHealth is declared in GlobalScript.ash and should be set when the player first encounters an enemy.
      //This next bit's just for me while I'm testing, I plan on having a GUI display this info later:
      Display("%d damage inflicted!", damageDone);
      Display("%d enemy health left!", enemyHealth);
      } else {
        Display("Not close enough!");
      }
    }
    
        Wait(5); //<-- So we have time to enjoy the visual of Roy extending his arm and pulling it back, maybe there's a better way to do this?
        cRoy.ChangeView(1);
       
    }
   
   if (enemyHealth < 50){
     c1984.Move(588, 100, eNoBlock); //<-- So the enemy runs away when it gets weak... Is there a way to make it do this without inputing specific coordinates, which obviously can't work in every situation...?
     c1984.FaceCharacter(cRoy, eNoBlock);
   } else if (enemyHealth < 1){
     /*"cCurrentEnemy"*/.ChangeView(8); //<-- How do I do something like this?  This is what I'm really trying to figure out.
     /*"cCurrentEnemy"*/.FollowCharacter(/*"cCurrentEnemy"*/); //<-- Stay put, you're dead!
     c1984.FollowCharacter(cRoy); //<-- Since we'll need c1984 later.
   } 


Thank you in advance for your help.

monkey0506

You seem like you're off to a pretty strong start, so I'm not sure exactly what it is that you're looking for help with on this. From the title it seems like you might just be looking for how pointer semantics work in AGS. It seems like you're reasonably familiar with scripting, but if not then you can just think of a pointer as a special type of variable (just like int, float, String).

In this case you'd need a Character* (the asterisk defines this as a "Character pointer" or "pointer to Character"). So for the enemy you might have something like this:

Code: ags
Character *enemy = cEnemy1;


This would define enemy as a pointer (variable) that refers to the Character cEnemy1. You could then use "enemy" anywhere in your code that you would have previously used "cEnemy1". Then if you change the enemy later you just have to change one line of code.

As far as the enemy's health, etc. that would of course be stored in separate variables. If you're only going to have the one enemy on-screen or in a room at any time then the variables you have now should probably suffice, but if you're going to have more enemies later then you might want to read in the manual about arrays. It would probably be useful to have several variables for tracking enemy stats.

Like I said, not sure if this is what you were going for, but hopefully it's of some use.

MurrayL

Regarding making enemies run away convincingly (without hard-coded co-ordinates), there are two methods that I can see:


  • Programmatically search for a point in the rooms walkable areas which is far away from the player. You could do this by testing points in a circle (of radius maxFleeDistance) around the player object. If nothing is found, try making the circle smaller, and repeat.
  • In each room with enemies, have a pre-built array of points which are good 'running away' spots. Have the enemy check through the array when they want to run, and pick one of the points either at random, or which is far away from the player (and requires that they don't run past him, although you could code in the super-cool ability for enemies to knock down the player as they flee). You'd probably also want to add some jitter to the point when it is used, so they don't always run to exactly the same spot.


antipus

This may or may not be the easiest answer, but will allow you to have a lot of flexibility later on. First, you keep all the functions in a separate script (mine's called "Combat.asc") so you can call them from any room. Now, in the Combat.ash file, you have a definition of what a bad guy is, and all the variables that you want different bad guys to have. The .ash code should look something like this:

Code: AGS

struct badguy {

  String name;
  int characterLink;

  int health;
  int strength;
  // int anything-else-you-want-to-keep-track-of.
}


Now in the combat.asc script, in the top and outside of any functions, create an array of bad guys.

Code: AGS
badguy enemy[100];


What this does is creates a bunch of easy-to-access data that holds all sorts of information about up to 100 bad guys. Now the trick is to link this data to actual characters in the game. Create a function that you only call once, and put something like this:

Code: AGS
enemy[0].characterLink = cBobby.ID;
enemy[1].characterLink = cDave.ID;
enemy[2].characterLink = cRoger.ID;

(Obviously, you'll be filling these with names of actual characters in your game.)

What this does is it means is that each enemy in the array of 100 is now specifically linked to a character in your game. Then you can set up loops to check each of the 100 characters quickly to see if they're close enough to hit.

Code: AGS
int i = 0;
while(i < 100){  // Could be a a bug here... Only go up to the number of characters you've actually defined.

  Character *enemy = character[enemy[i].characterLink];  // Creates a dummy character for just a second using the characterLink and ID.

  if(enemy.Room == cEgo.Room){ // If the bad guy is in the room...

    // Check distance, do whatever.
  }
  i++;
}


Hope this is helpful as a different approach!
Playing Arden's Vale has been shown to improve morale, revive unconscious kittens, and reverse hair loss.

monkey0506

antipus, using an array of struct instances is a good approach, but I would amend your "int characterLink" to just use a Character* instead. That way instead of having to reference the global character array to get a Character*, you could do something like this:

Code: ags
Character *enemy = enemies[i].agsCharacter;


It just makes the code a bit clearer as to what that particular variable is for. Also, you can't have both the global array and the local pointer named the same thing. ;)

antipus

Indeed! Agree with Monkey. I actually hadn't ever thought about putting the Character in the struct (silly me), but especially on line 4 above, it shouldn't be Character *enemy, just any other name, like Character *tempDude, or whatever.

I also realize that this may seem like a lot of setup work, but it allows you to run checks against every character you want with a loop instead of calling up character names individually.

But regarding the other aspects of your script, you could have multiple bad guys around, too.

Code: AGS
int i = 0;
int closestEnemy = -1;
int maxRange = 20500;
int xDiff; int yDiff; int enemyDistance; // If not defined elsewhere.

while(i < 100){
  Character *cTempDude = character[enemy[i].characterLink]; // Or Character *cTempDude = enemy[i].agsCharacter;

  if(cTempDude.Room == cEgo.Room){  // Check to see if the character is in the room.
    xDiff = cRoy.x - cTempDude.x;
    yDiff = cRoy.y - cTempDude.y;
    enemyDistance = xDiff*xDiff + yDiff*yDiff;   // Calculates the distance between EACH enemy and Roy.

    if(enemyDistance < maxRange){   // If an enemy is close enough to hit you, they are marked as the closest
      closestEnemy = i;             // enemy and their distance is now the "range" to check the next enemy by.
      maxRange = enemyDistance;
    }
  }
  i++;
}

if(closestEnemy == -1){
  Display("Not close enough!");  // If no one was in range...
}

else{
  int i = closestEnemy;   // If there was an enemy in range, we pick him.
  Character *cTempDude = character[enemy[i].characterLink]; // Or Character *cTempDude = enemy[i].agsCharacter;
  damageDone = Random(10);
  enemy[i].health -= damageDone;
  if(enemy[i].health < 0){
    cTempDude.ChangeView(8);   // The magic of this method is that you can edit the variables (like health) and the actual character functions at the same time. You could also use cTempDude.Walk, cTempDude.Animate, cTempDude.ChangeRoom, etc. And the one script will work with any enemy character that you throw against the user!
  }
}


I hope this addresses some of the other things that you were trying to accomplish, as well.
Playing Arden's Vale has been shown to improve morale, revive unconscious kittens, and reverse hair loss.

SMF spam blocked by CleanTalk