[SOLVED] Variable character and extender functions

Started by Cassiebsg, Sun 29/06/2014 11:42:30

Previous topic - Next topic

Cassiebsg

Argh!
Been since yesterday trying to get this to work, but to no avail! :(

I'm trying to get some NPCs to go from one point to another, then to yet another point and then move room. And this will be true for 7 or 9 characters.
This will be running on the background, so the player can go on doing whatever he/she likes.

So I go this code in room1, which works just fine, except that it's calling the character by name and not variable (using Move because I have yet to create more than one sprite for the NPCs).

Code: ags

function repeatedly_execute_always()

  {
    if  (cJake.x>446)
      {
      oBubble.StopAnimating();
      background_animation=false;
      object[1].Visible=false;
      }
    else if (background_animation==false)
      {
      object[1].Visible=true;
      oBubble.SetView (28,  0,  1);
      oBubble.Animate(0,  10, eRepeat, eNoBlock);
      background_animation=true;
      SetTimer(1, 200);
        }
   else if (IsTimerExpired(1)==1)
    {
    cMRandy.SayBackground("... Mick ...");
    cMick.Move(516,  274, eNoBlock, eAnywhere);
    SetTimer(2, 200);
    cMick.SayBackground("1");
    }
    else if (IsTimerExpired(2)==1)
      {
        checked_in=true;
        cMick.SayBackground("2");
        cMick.Move(200,  240, eNoBlock, eWalkableAreas);
        SetTimer(1, 400);
      }
     else if ((cMick.Moving==0) & (checked_in==true))
      {
       cMRandy.SayBackground("3");
       cMick.ChangeRoom(3);
       checked_in=false;
       }    
   }


As I said, this works just fine. However, I really don't want to have to write the same for 7 or more characters and just change the names. Ideally my character name needs to be a variable, that I can either read from a txt file or from a set of strings.

so I've been trying to use extender functions, but seems like every time I put an IF in the function, it stops from reading the remaining lines. :(

Here's the code:

Code: ags

// main global script file

bool checked_in=false;

function RollCall (Character*chartobecalled)
  {
   cMRandy.SayBackground("... Mick ...");
   chartobecalled.Move(516,  274, eNoBlock, eAnywhere);
   SetTimer(2, 200);
   chartobecalled.SayBackground("1");
     
     if (IsTimerExpired(2)==1)
       {
         checked_in=true;
         chartobecalled.SayBackground("2");
         chartobecalled.Move(200,  240, eNoBlock, eWalkableAreas);
         SetTimer(1, 400);
       }
      else if ((cMick.Moving==0) & (checked_in==true))
       {
        cMRandy.SayBackground("3");
        chartobecalled.ChangeRoom(3);
        checked_in=false;
        }
  }




Code: ags

// room script file

bool background_animation = false;
bool checked_in = false;
import function RollCall (Character*chartobecalled);

function repeatedly_execute_always()

  {
    if  (cJake.x>446)
      {
      oBubble.StopAnimating();
      background_animation=false;
      object[1].Visible=false;
      }
    else if (background_animation==false)
      {
      object[1].Visible=true;
      oBubble.SetView (28,  0,  1);
      oBubble.Animate(0,  10, eRepeat, eNoBlock);
      background_animation=true;
      SetTimer(1, 200);
        }
    else if (IsTimerExpired(1)==1)
    {
      RollCall(cMick);
    }


I've been  following this thread to try and figure it out: http://www.adventuregamestudio.co.uk/forums/index.php?topic=39894.msg525594#msg525594

(And no, I have yet to try coding the name variable, but if I can't get the code to work when it's "simple", then there's no point in continuing, right?)

There are those who believe that life here began out there...

Gurok

#1
Hrm... your example is quite complicated and I couldn't really determine what everything was supposed to do, but I might have an idea as to why your if() statements aren't working.

In your first example, the basic structure of your repeatedly_execute_always is this:
Code: ags
function repeatedly_execute_always()
{
	if(cJake.x > 446)
	{
	}
	else if(background_animation == false)
	{
		SetTimer(1, 200);
	}
	else if(IsTimerExpired(1) == 1)
	{
		SetTimer(2, 200);
	}
	else if(IsTimerExpired(2) == 1)
	{
		SetTimer(1, 400);
	}
	else if((cMick.Moving == 0) && (checked_in == true))
	{
	}    
}


When you placed some of that code into the RollCall function, the structure changed so that it was:
Code: ags
function repeatedly_execute_always()
{
	if(cJake.x > 446)
	{
	}
	else if(background_animation == false)
	{
		SetTimer(1, 200);
	}
	else if(IsTimerExpired(1) == 1)
	{
		SetTimer(2, 200);
		if(IsTimerExpired(2) == 1)
		{
			SetTimer(1, 400);
		}
		else if((cMick.Moving==0) && (checked_in==true))
		{
		}
	}
}


Something was lost in the conversion to a function. Specifically, those if() statements inside your RollCall function do not recreate the else if() structure in your old repeatedly_execute_always function. That *could* be why your If statements inside the RollCall function never get reached. Right now, the second timer is only checked if the first timer has expired.

Also, just a casual observation, you're using the wrong and (&). The single & is for a bitwise and. You want && for logical and.

Perhaps try this (no guarantees!):
Code: ags
    // main global script file
     
    bool checked_in=false;
     
    function RollCall (Character*chartobecalled)
      {
         if(IsTimerExpired(1)==1)
{
       cMRandy.SayBackground("... Mick ...");
       chartobecalled.Move(516,  274, eNoBlock, eAnywhere);
       SetTimer(2, 200);
       chartobecalled.SayBackground("1");
}
         
  else       if (IsTimerExpired(2)==1)
           {
             checked_in=true;
             chartobecalled.SayBackground("2");
             chartobecalled.Move(200,  240, eNoBlock, eWalkableAreas);
             SetTimer(1, 400);
           }
          else if ((cMick.Moving==0) & (checked_in==true))
           {
            cMRandy.SayBackground("3");
            chartobecalled.ChangeRoom(3);
            checked_in=false;
            }
      }


and
Code: ags
// room script file
 
bool background_animation = false;
bool checked_in = false;
import function RollCall (Character*chartobecalled);
 
function repeatedly_execute_always()
 
  {
    if  (cJake.x>446)
      {
      oBubble.StopAnimating();
      background_animation=false;
      object[1].Visible=false;
      }
    else if (background_animation==false)
      {
      object[1].Visible=true;
      oBubble.SetView (28,  0,  1);
      oBubble.Animate(0,  10, eRepeat, eNoBlock);
      background_animation=true;
      SetTimer(1, 200);
        }
    else
      RollCall(cMick);
}


I think timers are a bad choice here. I have a feeling this won't work if you try to have multiple characters moving around at once. I suspect the timers (and global booleans!) will interfere with each other. I don't know if that's an issue for you. If it is, you might be best to have variables that exist for each character. Let's see if this fixes the if() statement problem first though.
[img]http://7d4iqnx.gif;rWRLUuw.gi

Cassiebsg

#2
Thanks Gurok, that solved it... sort off.
It's now constantly running the "else   RollCall(cMick);" (which I was trying to avoid), however that'll only be a problem if I actually want something else to happen there (which I currently don't, though might give problems when I run out of NPCs to call, and want to stop the function...). ;)

As for the timers and bools, not sure. But there will only be one NPC walking around at a time... at least in this function. And the timer was the answer I found to do a "wait" command on repeatedly_execute_always... Maybe there's a better solution I don't know of? :)

Anyway, thanks again. :)
I'll go now and try to figure out how that variable character name works, and will post again once a) I've solved the problem; or b) I'm stuck again and need help. ;)

EDIT: Well, guess it turned out to be option b! :( No matter what I try, I keep getting an error (keeps changing of course!)

The code is currently running all from room1, since I only need this to happen in this room.

Code: ags

// room script file

bool background_animation = false;
bool checked_in = false;
int i = 1;


function RollCall (Character*chartobecalled)
  {
    if (IsTimerExpired(1)==1)
    {
      
   //cMRandy.SayBackground("... %s ...", c);
   chartobecalled.Move(516,  274, eNoBlock, eAnywhere);
   chartobecalled.SayBackground("1");
   SetTimer(2, 200);
    }
    else if (IsTimerExpired(2)==1)

       {
         checked_in=true;
         chartobecalled.SayBackground("2");
         chartobecalled.Move(200,  240, eNoBlock, eWalkableAreas);
         SetTimer(1, 200);
       }
      else if ((chartobecalled.Moving==0) && (checked_in==true))
       {
        cMRandy.SayBackground("3");
     //   chartobecalled.ChangeRoom(3);
        checked_in=false;
        }
  }
  

function repeatedly_execute_always()

  {
  if  (cJake.x>446)
      {
      oBubble.StopAnimating();
      background_animation=false;
      object[1].Visible=false;
      }
  else if (background_animation==false)
      {
      object[1].Visible=true;
      oBubble.SetView (28,  0,  1);
      oBubble.Animate(0,  10, eRepeat, eNoBlock);
      background_animation=true;
      SetTimer(1, 200);
        }
  else 
    {
      while (i < Game.CharacterCount)
      {
        if (character[i].GetProperty("is_on_RollCall")==1 && character[i].Room==player.Room)
        {
        Display("Your turn %s!", character[i].Name);
       String cname = String.Format("%s", character[i].scrname);
       Display(cname);
         RollCall(cname); //this is of course the line that's causing problems!
         i++;
        }
        else 
        {
         i++;
        }
      }
    }
 }   


Currently the error read "Failed to save room room1.crm; details below
room1.asc(60): Error (line 60): Type mismatch: cannot convert 'String*' to 'Character*'
"

I feel I got as close as my scripting skills allow me. I just don't know how to make the text string to became the character name... if that is even possible...

PS - I know you guys don't like double posting, I don't like it either... but how does one keep track of edited threads? Since they don't show on the "Show unread posts since last visit."?
There are those who believe that life here began out there...

Khris

The function RollCall expects a Character pointer, but you are passing text, a String. That String happens to be the Character's script name, but to AGS, there's absolutely no relation between cEgo and "cEgo".

There's no need to mess around with .scrname though, you can just pass character[i].

Instead of:
Code: ags
       String cname = String.Format("%s", character[i].scrname);
       Display(cname);
       RollCall(cname);


Just do this:
Code: ags
       Display(character[i].scrname);
       RollCall(character[i]); //this is of course the line that's causing problems!


Cassiebsg

#4
Hey Khris!

Well, that doesn't give me an error, that's true, but the function RollCall[i/] doesn't do anything either. :/

Hey Khris!

Well, that doesn't give me an error, that's true, but the function RollCall doesn't do anything either. :/

EDIT: Ok, the function  RollCall IS working with that code! :) I just added a else to the function and made my character say a word, and that works...
Can it be that the while is breaking my timer 1?

From the Manual:
Quote
Note that this function will only return 1 once - after that, the timer is placed into an OFF state where it will always return 0 until restarted.

Why would an expired timer, return a 0 and not a 1? Makes no sense in my mind... and probably why the code doesn't work now?


EDIT 2/Jul/2014:
Well, I've been working om my code (and I should have gone to bed 2 hours ago!), and have it sorta working (I liked it better when the timers were working!).
Anyway, I finally got it running, but 1 major & 1 minor problem arose...
I removed the timers, that weren't working due to them defaulting to 0! And replaced it with an int check. Works okay, but is missing the delay (wait) time I had before. So now they just move to point a & then b. Instead going from point a, stop (wait) for a bit and then move to point b & change rooms. This is the minor problem.
The major problem, is that while the script is running (NPCs are moving) I can't access the GUIs (they're grayed out)! :( While I can live with the missing pause to talk, I can't live with this one as I want the player to be able to continue playing while that "happens in the background".

Also I'm missing the knowhow on how to get cMRAndy to say the variable character name.... :/

Anyway, here's a little video of the code in action: http://youtu.be/n4uKFNcCFFs

And here's the full code, as it currently is:
Code: ags

// room script file

bool background_animation = false;
int checked_in = 0;
int i = 1;


 function RollCall (Character*chartobecalled)
 {
    if ((chartobecalled.Moving==0) && (checked_in==0))
    {
     chartobecalled.Move(516,  274, eNoBlock, eAnywhere);
     chartobecalled.SayBackground("Here!");
     checked_in=1;
    }
   else if ((chartobecalled.Moving==0) && (checked_in==1))
     {
      chartobecalled.SayBackground("Yes.");
      chartobecalled.Move(200,  240, eNoBlock, eWalkableAreas);
      checked_in=2;
      }
    else if ((chartobecalled.Moving==0) && (checked_in==2))
      {
       chartobecalled.ChangeRoom(3);
       checked_in=0;
       }
     else if (chartobecalled.Room!=player.Room)
       {
        i++;
       }
 }
  

function repeatedly_execute_always()

  {
   if  (cJake.x>446)
    {
    oBubble.StopAnimating();
    background_animation=false;
    oBubble.Visible=false;
     }
   else if (background_animation==false)
     {
     oBubble.Visible=true;
     oBubble.SetView (28,  0,  1);
     oBubble.Animate(0,  10, eRepeat, eNoBlock);
     background_animation=true;
     }
   else if ((i < Game.CharacterCount) && (character[i].GetProperty("is_on_RollCall")==1) && (character[i].Room==player.Room))
     {
     cMRandy.SayBackground("Next");
     RollCall(character[i]);   
     }
   else    
     {
     i++;
     }  
    
  }   

There are those who believe that life here began out there...

Khris

Hi,

the timer issue is quite simple: IsTimerExpired(x) will only be true once, then false again. This is deliberate, otherwise the code inside if (IsTimerExpired(x)) would fire every game loop and one would need an additional variable or Game.DoOnceOnly() to keep that from happening.

If you run a blocking while loop, it might pause timers, yes. I haven't tested this, but it sounds plausible.
GUIs should only grey out during blocking events; double check that you used SayBackground and eNoBlock everywhere.

Coding independently walking and talking characters is one of the hardest things when it comes to creating a game world, btw.

I'm too lazy to go through your code right now, but I suspect maybe some bad if-elseif-logic could have caused problems? Your indentation is completely random, at which point you might as well not use it at all... :-D
IsTimerExpired() should always be checked on its own, directly inside rep_exe (not nested into other conditions). Unless of course you don't want to fire the delayed code immediately after the timer runs out.

I would advise you to
a) use proper indentation
b) use nesting
c) maybe consider moving longer parts into their own functions

As an example, instead of
Code: ags
    if ((chartobecalled.Moving==0) && (checked_in==0))
    {
     chartobecalled.Move(516,  274, eNoBlock, eAnywhere);
     chartobecalled.SayBackground("Here!");
     checked_in=1;
    }
   else if ((chartobecalled.Moving==0) && (checked_in==1))
     {
      chartobecalled.SayBackground("Yes.");
      chartobecalled.Move(200,  240, eNoBlock, eWalkableAreas);
      checked_in=2;
      }


you use:
Code: ags
    if (chartobecalled.Moving == 0) {
      // char to be called reached goal
      if (checked_in == 0) {
        chartobecalled.Move(516, 274, eNoBlock, eAnywhere);
        chartobecalled.SayBackground("Here!");
        checked_in=1;
      }
      else if (checked_in == 1) {
        chartobecalled.SayBackground("Yes.");
        chartobecalled.Move(200,  240, eNoBlock, eWalkableAreas);
        checked_in=2;
      }
      else if ... {
        ...
      }
    }

Clean code is half the debugging.

Cassiebsg

#6
Hey Khris.

Sorry about the indenting, but around midnat, I got tired of adjusting it every time I tried something new (like adding a new if/else if/else line of code, and then removing it again. Don't know why, but the tab key creates spaces instead of tab, and the undo button doesn't work as soon as I run the code to test it, making the undo button useless IMHO... so I guess I just got tired of writing the same line of code, copy/pasting, adding and deleting spaces... Just wanted to get it working.
Think I have a solution to create a pause when the 2 NPCs are talking, I'll just have to create a small animation that lasts a few seconds. ;) And once they have they're walking cycle done, they won't move so fast (it's funny to see a "blue cup" walking)... LOL

And as far as I can see and AGS tells me, have no blocking scripts in this code, since it's running on rep_exec_always (which is the reason I can't use wait)! I tried putting it on rep_exec but it seems to run the code only once.
Or maybe I need to put it in rep_exe and create a while loop for the entire "function?" Would that work?

Not sure what you mean with "nesting"... :/

EIDT 6/7/2014: Problem solved with Gurok's help.
There are those who believe that life here began out there...

SMF spam blocked by CleanTalk