WalksOnto doesn't seem to detect walking onto a region

Started by Pogwizd, Wed 25/03/2020 23:04:18

Previous topic - Next topic

Pogwizd

Hi Guys,

I'm afraid I've hit on a wall and I need to ask you for help.

In my game, the player has to walk onto a number of regions in a specific order to solve a puzzle. However, the order changes depending on which region is walked onto first. There are four regions in total and the player can decide from which they are going to start the sequence.

In order to do that I set up four regions in my room and used four region_WalksOnto functions to detect the event of walking onto them. What's more, I've got a counter of how many regions have been walked onto in a correct order (I called it a numberOfTrues) and an array of possible paths (there are four: "1,2,3,4,1", "2,3,4,1,2", "3,4,1,2,3", "4,1,2,3,4"; the numbers represent the IDs of the regions; each path has five values, where each five stands for a whole round). This way I've been able to check if the next region the player walks onto is the next one on the given path. If the player walks on a region that is not correct and breaks the sequence, then the numberOfTrues gets reset to 1 and a new path is selected.

Now, I'm not saying it's the best way of doing it but it seemed to be working fine. At least I thought so until I stopped displaying the current value of numberOfTrues during the game. What I mean is during the development I would use either Display() or a label to show the current values of numberOfTrues or what region had been walked onto. Without displaying those values the game seems to be working randomly... Sometimes it would work fine and the character would recognise that the path has been completed and then it would not. And that happens only when the values are not displayed.

I've tested it now about 30 times... (at this point, even thought it's a game I've made, I am sick of looking at it :) ) and I'm sure it works fine with the values displayed (with the use of Display() or labels) and that it works randomly without.

I'm far from saying that this might be the case, but could it be possible that for some reason AGS may be failing to detect walking onto a region?

Here is the excerpt of my code that handles the walking onto regions plus variables used in them:

Code: ags

int tablePaths[20];
int pathSelected;
int numberOfTrues = 0;

function room_Load()
{  
   //starting from south
  tablePaths[0] = 1;
  tablePaths[1] = 2;
  tablePaths[2] = 3;
  tablePaths[3] = 4;
  tablePaths[4] = 1;
  
  //starting from east
  tablePaths[5] = 2;
  tablePaths[6] = 3;
  tablePaths[7] = 4;
  tablePaths[8] = 1;
  tablePaths[9] = 2;
  
  //starting from north
  tablePaths[10] = 3;
  tablePaths[11] = 4;
  tablePaths[12] = 1;
  tablePaths[13] = 2;
  tablePaths[14] = 3;
  
  //starting from west
  tablePaths[15] = 4;
  tablePaths[16] = 1;
  tablePaths[17] = 2;
  tablePaths[18] = 3;
  tablePaths[19] = 4;
  
}




function region1_WalksOnto()
{
  
 
  if(listHasBeenRead == true && tableWalkedAroundThreeTimes == false)
  {
    if(numberOfTrues == 0 && justEnteredKitchen == true)
    {
      pathSelected = 0;
      numberOfTrues++;
    }
    
  
    else
    {
      if(region[1].ID == tablePaths[pathSelected+numberOfTrues])
      {
        numberOfTrues++;
      }
      
      else
      {
        numberOfTrues = 1;
        pathSelected = 0;
      }
    }    
    
    if(justEnteredKitchen == true && numberOfTrues == 2)
    {
      justEnteredKitchen = false;
      
    }
    
  }
  
  

}

function region2_WalksOnto()
{
  
  
  if(listHasBeenRead == true && tableWalkedAroundThreeTimes == false)
  {
    if(numberOfTrues == 0 && justEnteredKitchen == true)
    {
      pathSelected = 5;
      numberOfTrues++;
    }
    
    
    else
    {
      if(region[2].ID == tablePaths[pathSelected+numberOfTrues])
      {
        numberOfTrues++;
      }
      
      else
      {
        numberOfTrues = 1;
        pathSelected = 5;
      }
    }
    
    if(justEnteredKitchen == true && numberOfTrues == 2)
    {
      justEnteredKitchen = false;
      
    }
    
    
  }
  
  
  
 
}

function region3_WalksOnto()
{
  
  
  
  if(listHasBeenRead == true && tableWalkedAroundThreeTimes == false)
  {
    if(numberOfTrues == 0 && justEnteredKitchen == true)
    {
      pathSelected = 10;
      numberOfTrues++;
    }
    
    
    
    else
    {
      if(region[3].ID == tablePaths[pathSelected+numberOfTrues])
      {
        numberOfTrues++;
      }
      
      else
      {
        numberOfTrues = 1;
        pathSelected = 10;
      }
    } 
    
    if(justEnteredKitchen == true && numberOfTrues == 2)
    {
      justEnteredKitchen = false;
    }
    
    
  }
    
}

function region4_WalksOnto()
{

  
  if(listHasBeenRead == true && tableWalkedAroundThreeTimes == false)
  {
    if(numberOfTrues == 0 && justEnteredKitchen == true)
    {
      pathSelected = 15;
      numberOfTrues++;
    }
    
    
    else
    {
      if(region[4].ID == tablePaths[pathSelected+numberOfTrues])
      {
        numberOfTrues++;
      }
      
      else
      {
        numberOfTrues = 1;
        pathSelected = 15;
      }
    }
    
    if(justEnteredKitchen == true && numberOfTrues == 2)
    {
      justEnteredKitchen = false;
    }
  
  }
}


P.S.
I know the code inside WalkOnto functions could be put into a separate function but I got bogged down with this problem and haven't got time to do it.

I'm using AGS 3.4.3.

I would appreciate any suggestions as I feel defeated by AGS :)

TheManInBoots

#1
I assume people are hesitant to reply to your thread because it is a lot to read through.
I do not have the time to find out where your scripting problem is, your post is too long.
Maybe you have to take into account that the player can walk onto the same region TWICE, and that messes up the process? (you could avoid that by adding a variable that keeps track if the player walks twice on the same region)
Tbh I actually got carried away by the problem and I had some fun thinking about it, and I will show you how I would script the entire thing you wrote, but in no more than 24 lines and in only one single function :P:

The finished result is in the end, I explained it step by step, in case you wanted to understand what I wrote:
(Note: my variable 'step' corresponds to your 'numberoftrues' and my variable 'plan' corresponds to your 'pathselected')

So first I would create the regions as the first 4 regions of the room, so that their ID numbers correspond (so that their IDs are 1,2,3,4).

Then I would have one int variable "step" that describes on which step you're on
(when step==1 you stepped on the first random region, when step==2 you are on the second random region etc.)

After that I would create  a variable "plan" that describes which number plan you follow.
Because we have 4 different plans:
Plan 1:1,2,3,4,1
Plan 2:2,3,4,1,2
Plan 3:3,4,1,2,3
Plan 4:4,1,2,3,4

So when the player walks on the first region, the variable "plan" is set accordingly
(e.g. player walks on region1 => plan=1;
player walks on region2 => plan=2;)

So to turn it into script, in the region walks onto function you'd write:

Code: ags
function region1_WalksOnto()
{
function region1_WalksOnto()
{
step  ;//step variable grows by one when walking onto the region, so if it was the first step, now step==1
if(step==1)plan=region[1].ID;//so if this is the first step, the plan number becomes the region number (if region==1, plan=1, if region==2, then plan=2 etc.)
}
}



You will use the same pattern for all four region functions.
So whenever the player walks onto a region, step grows by 1, and we now we are on the next step.
So now we have to figure out what happens when it's NOT step one but step 2-5 when the player walks onto region 1.
If we are doing the second step==2, than the plan has to b plan 4, when standing on region 1:
Plan 4:4,1,2,3,4
If it's not plan 4, then the pattern is broken, and the whole thing restarts, by simply resetting "step" and "plan" and setting both variables to 0.

So we add to the function
Code: ags

function region1_WalksOnto()
{
step  ;
if(step==1)plan=region[1].ID;

if(step==2&&plan!=4)
{step=0;
plan=0;}
}
}


And then we do that for the rest of the steps, giving us this function:
Spoiler

Code: ags

function region1_WalksOnto()
{
step  ;
if(step==1)plan=region[1].ID;

if(step==2&&plan!=4)
{step=0;
plan=0;}
if(step==3&&plan!=3)
{step=0;
plan=0;}
if(step==4&&plan!=2)
{step=0;
plan=0;}
}

[close]

If step==5 then you want to trigger the 'Succesevent' if it's the correct plan, and else reset the entire pattern.
When correctly following the pattern, the last region is the same as the first, so if the player started on region 1 and started plan 1, now he has to be standing on region 1 again, so we add below the rest:
Code: ags

if(step==5&&plan==region[1].ID)
{player.Say("I can't believe it, I did it!");}
else if(step==5)
{step=0;
plan=0;}


Giving us the full function:
Spoiler

Code: ags

function region1_WalksOnto()
{
step  ;
if(step==1)plan=region[1].ID;

if(step==2&&plan!=4)
{step=0;
plan=0;}
if(step==3&&plan!=3)
{step=0;
plan=0;}
if(step==4&&plan!=2)
{step=0;
plan=0;}
if(step==5&&plan==region[1].ID)
{player.Say("I can't believe it, I did it!");}
else if(step==5)
{step=0;
plan=0;}
}

[close]

And then we need to do that for every other function us well, and adjust the other numbers as well.

Now, how can you write all that in only one single function?
Let's check out the exact numbers for all functions:

For region one, we said, if step==2, then the plan must be plan 4, if step==3, then plan must be plan 3, if step==4, then plan must be plan 2.

For clarity, here I write it as a diagram:
Region 1:
2-4
3-3
4-2

And those are the diagrams for the other regions:
Region 2:
2-1
3-4
4-3

Region 3:
2-2
3-1
4-4

Region 4:
2-3
3-2
4-1

So if you watch those diagrams exactly, you can notice that there is a certain pattern. The right row has the 1,2,3,4 in reverse, while rotating down one digit for each region and beginning at 1 again after 4.

So we can write that in a function. The variable 'requiredplan' describes the plan that each step needs for each region for it to work:
And if you look at the diagrams and look how to calculate it, you get this:

Code: ags
requiredplan=region[].ID 1-step;//I wrote region[].ID PLUS 1-step
if(requiredplan<1)requiredplan=4-requiredplan;


So notice that I added the if(requiredplan<1) or if(requiredplan<=0) function.
That is so that the rotation of the numbers begins back at 4.

So now you can simply write

Code: ags

int requiredplan;
requiredplan=region[1].ID 1-step;//I wrote region[1].ID PLUS 1-step
if(requiredplan<1)requiredplan=4-requiredplan;

if(plan!=requiredplan)
{step=0;
plan=0;}

And that works for all steps, so you don't need to write a separate if condition for steps 2,3 and 4 anymore.

So checkpoint!, the entire function now would look like this:
Spoiler

Code: ags

int step;
int plan;
function region1_WalksOnto()
{
step  ;
if(step==1)plan=region[1].ID;

int requiredplan;
requiredplan=region[1].ID 1-step;
if(requiredplan<1)requiredplan=4-requiredplan;

if(plan!=requiredplan)
{step=0;
plan=0;}

if(step==5&&plan==region[1].ID)
{player.Say("I can't believe it, I did it!");}
else if(step==5)
{step=0;
plan=0;}
}

[close]

But now, you still have a function for each region. How to you bring all the 4 region function into 1 single function?
By simply letting the function recognize on which region the player character stands, and then run the same function for all 4 regions.
In order to get the current region ID, you need a Region pointer.
You write:

Code: ags
Region*CurrentRegion;
CurrentRegion=Region.GetAtRoomXY(cJim.x, cJim.y);
int RegionID=CurrentRegion.ID;


So then you implement that in the initial function:
Code: ags

function region1_WalksOnto()
{
step  ;
if(step==1)plan=RegionID;
}


And then the entire, FINAL function would look like this:
Code: ags

int step;
int plan;
function AllFourRegions_WalksOnto()
{
step  ;
Region*CurrentRegion;
CurrentRegion=Region.GetAtRoomXY(cJim.x, cJim.y);
int RegionID=CurrentRegion.ID;

if(step==1)plan=RegionID;

int requiredplan;
requiredplan=RegionID 1-step;//I wrote RegionID PLUS 1-step
if(requiredplan<1)requiredplan=4-requiredplan;

if(plan!=requiredplan)
{step=0;
plan=0;}

if(step==5&&plan==RegionID)
{player.Say("I can't believe it, I did it!");}
else if(step==5)
{step=0;
plan=0;}
}


Now you can use that same universal function for every region.
Notice I changed the name of the function to "AllFourRegions_WalksOnto()"
Because what you can do now, in the room editor, for every function, before clicking on the ellipse "..." button for the WalksOnto function in the Eventspanel, you change that name to "AllFourRegions_WalksOnto". And this way every single region is going to be directed directly to that function, and you don't have to write every function separately for every region anymore.


P.S.: If you already have other regions in the room and do not want to delete them to make the four regions IDs 1,2,3,4, you can simply add a substraction to the formula so that it still works. As long as the region ID's are in a continuous row.

Retro Wolf

It can sometimes take a while for people to reply to a thread because people live in different parts of the world and could be in bed. Also the original post is nowhere near "too long".

Laura Hunt

Just a random thought: if everything works properly when you use Display() and doesn't work when you don't, could you try inserting a Wait(1) at the same point where you were using the Display function and see if it works now?

(Disclaimer: I'm still a newbie so this might not make sense, but at least it's quick and easy to try :-D)

TheManInBoots

#4
Quote from: Retro Wolf on Thu 26/03/2020 08:53:14
It can sometimes take a while for people to reply to a thread because people live in different parts of the world and could be in bed. Also the original post is nowhere near "too long".
Well yeah, that too.

At least for me personally it takes a while to work myself into a long script like the one Pogwizd showed and then find the bug. Especially if it's not an obvious bug one might have to recreate it and test it out. That's what I meant when I said it might be too long and not all people might have the time to do it.

Retro Wolf


Snarky

Quote from: Pogwizd on Wed 25/03/2020 23:04:18
I'm far from saying that this might be the case, but could it be possible that for some reason AGS may be failing to detect walking onto a region?

Without absolutely ruling anything out, that seems unlikely. And it is particularly unlikely that failing to detect walking onto a region would depend on whether you show the outcome on a label. (As Laura suggests, putting in a Display() call, on the other hand, could theoretically make a difference.)

At a glance, I don't see anything in your code that jumps out as obviously wrong. However, you haven't included the logic that actually detects when numberOfTrues reaches 5 (i.e., the part that appears to be glitching), so it's hard to say. In any case, the code does seem unnecessarily complex, which makes it harder to reason about its behavior and find bugs. So it's a question of to what extent you're prepared to try a different way of doing it.

If it were me, I would start by noticing that all four "different" paths are actually the same, just cycled around (basically, you always have to do one "full loop", but you can start anywhere). If that's a fixed property of the puzzle, it makes things a lot easier, because it doesn't matter "which path" you're on to determine if this was the right step, it only matters which region you came from and which one you're entering. (If, on the other hand, the correct next step at any point depends on where you started from, then you do need to define the paths separately.)

So I'd keep the previous region stored in a variable (updated each time after you enter a new one), and just do a check like:

Code: ags
int previousRegion=0;
int numberOfTrues = 0;

function region1_WalksOnto()
{
  if(previousRegion == 4) // We're on the right path
    numberOfTrues++;
  else // We've taken a wrong turn (or this is the start), start again
    numberOfTrues = 1;

  if(numberOfTrues==5)
  {
    // TODO: We've completed a cycle, do something!
  }

  previousRegion = 1;
}


(I'm leaving out all the stuff about listHasBeenRead and tableWalkedAroundThreeTimes and justEnteredKitchen, just to check that this part is working.)

Now you could do the same for all the regions, checking which region you came from and thus deciding whether this was a valid step, but given that the logic is always the same, I'd pull it out as a function:

Code: ags
int previousRegion=0;
int numberOfTrues = 0;

void regionChange(int newRegion)
{
  if(newRegion == (previousRegion%4)+1) // We're on the right path
    numberOfTrues++;
  else // We've taken a wrong turn (or this is the start), start again
    numberOfTrues = 1;

  if(numberOfTrues==5)
  {
    // TODO: We've completed a cycle, do something!
  }

  previousRegion = newRegion;
}

function region1_WalksOnto()
{
  regionChange(1);
}

function region2_WalksOnto()
{
  regionChange(2);
}

function region3_WalksOnto()
{
  regionChange(3);
}

function region4_WalksOnto()
{
  regionChange(4);
}


Spoiler
This uses the modulo operator, %, in line 6. If you take n % 4, it makes the number n "wrap around" from 0â€"3: 4 becomes 0 (4%4 == 0), 5 becomes 1, etc. In this case it only affects n==4, ensuring that going from 4 to 1 is detected as a valid step.
[close]

You also have to take care that there's no other way to exit these regions that isn't covered, because if so the previousRegion variable won't be properly updated. If it's possible to step out onto some outside region, you would need to handle that as well, probably by setting both previousRegion and numberOfTrues to 0.

Pogwizd

Personally, I don’t think that my first message was too long, as I believe it’s better to write the whole situation up, so anyone willing to help can get the full picture. Also, I did not expect anyone to get back to me straight away, nor I expected people to leave whatever they were up to just to have a look at my post. I thought it’s voluntary 😉.
Anyway, no matter.

TheManInTheBoots, thanks for your code.

Laura Hunt, thanks for the suggestion, I will give it a go in the evening. If it doesn’t work I won’t judge you because I’m a rookie myself 😉

Snarky, thank you very much for your suggestions. I will give it a proper look in the evening and come back with the results 😊. I'm ready to rewrite the whole thing if necessary because making it work as intended is the priority for me.

PS.

The logic that handles detecting the whole round looks as follows:

Code: ags

Function room_RepExec()
{
    if(numberOfTrues == 5)
    {
        tableCounter++;

        if(tableCounter ==1)
        {
            cCharacter.Say(“One lap done”);
        }
    
        if(tableCounter ==2)
        {
            cCharacter.Say(“Getting there”);
        }
    
        if(tableCounter ==3)
        {
            cCharacter.Say(“Done”); 
        }
    
        numberOfTrues = 1;
    }  
}


Although, now when I'm looking at it I'm guessing there was no point in putting it into room_RepExec().

TheManInBoots

Quote from: Pogwizd on Thu 26/03/2020 15:21:54
Personally, I don’t think that my first message was too long
Oh yes, I don't want to be misunderstood. I don't think your post was too long either, just too long for me to run a proper bug test through it.
That's obviously totally fine if you add a lot of details to it.
Good luck with solving it

Pogwizd

Ugh... I've just spent hours watching carefully what was going on in my game. Turns out that the walkable area covering one of the regions was a bit wider than the region itself. So, when I was moving my character a little bit too close to the bottom edge of the region I wasn't triggering the WalkOnto() function.

I've not tested it out yet because I'm about to fall asleep but that I'm pretty sure that's what has been tripping me up (but I might be wrong again ;) ).

Sorry guys, it was just my stupidity ;)

Thank you all for quickly getting back to me your suggestions.

Khris

TheManInBoots, please post code that's more readable. At least indent properly.

TheManInBoots

#11
Pogwizd,

if the problem finally was something else other than the walkable area, feel free to share.

Btw., possibly two great strategies for debugging a complicated script are Narrowing Down and Interactive templates.

In your example Narrowing down would mean for example that you focus first on making the pattern work with only two regions.
Get the pattern recognized with two patterns (so 1-2-1 and 2-1-2).
Once everything works you add a third region and make it work, and so on. Step by step.
You could also remove variables like "listHasBeenRead" or "TableHasWalkedAroundThreeTimes", I think you called it, to begin with.

And only once it all works, you add the variables back.
This way you know much better where the problem lies, and you can focus on a small part of the script.

With interactive templates I mean for example next to the labels that you created to track the variables behavior, you add buttons that represent the region.
So whenever you press the button, the correspondent region function is triggered, and you can exactly track the behavior and see how the region functions and variables respond with the labels. You're more in control and way more focused.

Pogwizd

TheManInBoots,
thanks for your message. Sorting out the walkable area did the trick, though, and everything works fine now.

SMF spam blocked by CleanTalk