Pressure plates

Please take a moment to consider if this thread is worth bumping.

Recommended Posts

Posted (edited)

Hello there!Â  ðŸ˜‰
There is a linkset of several pressure plates and I need to activate some main action only when all the pressure plates are activated (stepped on). Plates react to collisions. Each plate has its own original name, if that makes sense in this case. What is the best way to do this?

Addition: when you step on each plate, it activates a small secondary action...

Thanks for any help!

Edited by Tattooshop
Share on other sites

20 minutes ago, Tattooshop said:

Hello there!Â  ðŸ˜‰
There is a linkset of several pressure plates and I need to activate some main action only when all the pressure plates are activated (stepped on). Plates react to collisions. Each plate has its own original name, if that makes sense in this case. What is the best way to do this?

Addition: there is a special secondary action associated with the activation of each specific plate.

Thanks for any help!

Â

• 1
Share on other sites

29 minutes ago, Tattooshop said:

only when all the pressure plates are activated (stepped on)

Stepped on at the same time, or all of them have been momentarily stepped on within a certain time?

• 1
Share on other sites

Posted (edited)

For a (somewhat) complicated script like this it's best to break it into parts and then figure out how to fit those parts together. Assuming all the plates are linked together and a single-script setup you will need:

1. a global function for your 'Main action'. not strictly necessary, but for things like this I think it makes the code much easier to read.
2. A way of distinguishing plates from each-other (and other things in the linkset if there are other things in the linkset) it would be easiest to have the linkset only contain pressureplates and use link-numbers as their distinguishing feature.
3. A method of storing (and reading) the combined on/off state of all the pressure plates. I personally would use a single integer as a bit-field, assuming there are 32 or fewer plates, but a list of integers works just as well. (Actually, depending on your detection method, a single "everything is pushed" variable may suffice)
4. A reliable test for if a given plate is pushed or not. if you use Colision_start and end, you may run into problems if avatars suddenly teleport off of the plate, as that sometimes does not trigger a collision_end event.

Thinking through the caveats of all those different parts, Here's how I might go about it (untested)

```integer activated = FALSE;

MAIN_ACTION()
}
default()
{
state_entry()
{
llMinEventDelay(0.2); // not strictly neccessary, but collision events go off too rapidly for my liking.
}
collision(integer n)
{
while(~--n)
}
}
{  if(!activated)
{
activated=TRUE;
MAIN_ACTION();
}
}else
{
activated=FALSE;
}
}
}```

Edit: I didn't actually test that something is on top of the pressure plate (compare the z coordinate of their positions, and make sure the x,y coords are within some bounds), or that said thing is an avatar (llGetAgentSize()).

Edit 2: script above is for the scenario in which you want all the plates to be pressed at the same time, like maybe a 'you need x number of people to open the magic portal', or a classic button-door puzzle.

Edited by Quistessa
• 1
Share on other sites

Posted (edited)

There is a quick and dirty way of doing it: using link messages to send a 1 when collision_start occurs and -1 when collision_end occurs.

Add up the 1s and -1s, when all the plates are recording a collision in progress your total will be the number of pressure plates. It will drop back to 0 when no plates are in the process of being collided with, and will be somewhere between 1 and the total number of plates when a few are being activated.

Edited by Profaitchikenz Haiku
• 1
Share on other sites

Posted (edited)
4 minutes ago, Profaitchikenz Haiku said:

It will drop back to 0 when no plates are in the process of being collided with

You did say this was quick and dirty, and that's similar to my first thought, (but slightly better) but if a clever avatar knows the trick they can double click teleport (or sit-teleport away after standing on each or any several times) to all the switches and the number will never decrease correctly. (or at least that's been my experience with volume detect objects, maybe that bug doesn't exist for regular collisions)

Edited by Quistessa
• 1
Share on other sites

47 minutes ago, Profaitchikenz Haiku said:

Stepped on at the same time, or all of them have been momentarily stepped on within a certain time?

Â

45 minutes ago, Quistessa said:

For a (somewhat) complicated script like this it's best to break it into parts and then figure out how to fit those parts together. Assuming all the plates are linked together and a single-script setup you will need:

1. a global function for your 'Main action'. not strictly necessary, but for things like this I think it makes the code much easier to read.
2. A way of distinguishing plates from each-other (and other things in the linkset if there are other things in the linkset) it would be easiest to have the linkset only contain pressureplates and use link-numbers as their distinguishing feature.
3. A method of storing (and reading) the combined on/off state of all the pressure plates. I personally would use a single integer as a bit-field, assuming there are 32 or fewer plates, but a list of integers works just as well. (Actually, depending on your detection method, a single "everything is pushed" variable may suffice)
4. A reliable test for if a given plate is pushed or not. if you use Colision_start and end, you may run into problems if avatars suddenly teleport off of the plate, as that sometimes does not trigger a collision_end event.

Thinking through the caveats of all those different parts, Here's how I might go about it (untested)

```
integer activated = FALSE;

MAIN_ACTION()
}
default()
{
state_entry()
{
llMinEventDelay(0.2); // not strictly neccessary, but collision events go off too rapidly for my liking.
}
collision(integer n)
{
while(~--n)
}
}
{  if(!activated)
{
activated=TRUE;
MAIN_ACTION();
}
}else
{
activated=FALSE;
}
}
}```

Edit: I didn't actually test that something is on top of the pressure plate (compare the z coordinate of their positions, and make sure the x,y coords are within some bounds), or that said thing is an avatar (llGetAgentSize()).

Thank you very much!

This is just how I see it.

There will beÂ 9 pressure plates, and it does not matter in what order they are pressed and for how long. If the plate is pressed once, the plate remains activated, aÂ specific sub-action associated with theÂ plate occurs, which is counted by a counter, and as soon as 9 such unique pressures occur, the main action is triggered. ( It may be needed to reset the counter until next time. )

I assume I need to use llGetLinkName in a collision event and then build a chain of IF checks for each pressure plate name and activate sub-actions, but how I can add "counter" if all plates are stepped is not clear.

I only need to add a unique steps counter somehow... or what?

Â

Share on other sites

Posted (edited)
16 hours ago, Tattooshop said:

I assume I need to use llGetLinkName in a collision event and then build a chain of IF checks for each pressure plate name and activate sub-actions, but how I can add "counter" if all plates are stepped is not clear.

This is basically what I alluded to as #3 of my first post. One way to do it is to have a global list

`list gState = [0,0,0,0,0,0,0,0,0];`

and when you step on for example switch number 7 (it is most convenient to think of your switches as labeled 0 thru 8 for a total of 9):

`gState = llListReplaceList(gState,[1],7,7);`

and then you test whether every switch has been pressed with

```if(gState==[1,1,1,1,1,1,1,1,1]){MAIN_ACTION();}
//As has been pointed out later in this thread, this test (==) is flawed.
//You will have to implement a list-equality check yourself.```

Alternatively, you can use a single integer as a bitfield; the following 3 lines are analogous to the 3 above:

```integer gState = 0;
gState = gState | 1<<7;
if(gState ==511){...} // 511 == 2^9-1```

Â

Edited by Quistessa
accidentally used a = instead of a == in one of the tests *whistles as I retroactively change it*
• 1
Share on other sites

9 hours ago, Quistessa said:

This is basically what I alluded to as #3 of my first post. One way to do it is to have a global list

```
list gState = [0,0,0,0,0,0,0,0,0];```

and when you step on for example switch number 7 (it is most convenient to think of your switches as labeled 0 thru 8 for a total of 9):

```
gState = llListReplaceList(gState,[1],7,7);```

and then you test whether every switch has been pressed with

```
if(gState==[1,1,1,1,1,1,1,1,1]){MAIN_ACTION();}```

Alternatively, you can use a single integer as a bitfield; the following 3 lines are analogous to the 3 above:

```
integer gState = 0;
gState = gState | 1<<7;
if(gState ==511){...} // 511 == 2^9-1```

Â

Many thanks! This is what I got, but for some reason the main action occurs when I press any plate. Where did I go wrong? ðŸ¤”

```default
{
state_entry()
{
llMinEventDelay(0.2);
}

collision_start(integer num)
{
list gState = [0,0,0,0,0,0,0,0,0];

if (plate == "1") // plate name check
{
// do something
llSay(0, "plate number 1 pressed");
gState = llListReplaceList(gState,[1],1,1);
}
///.........................................................
if (plate == "7")
{
llSay(0, "plate number 7 pressed");
gState = llListReplaceList(gState,[1],7,7);
}
if (plate == "8")
{
llSay(0, "plate number 8 pressed");
gState = llListReplaceList(gState,[1],8,8);
}
if (plate == "9")
{
llSay(0, "plate number 9 pressed");
gState = llListReplaceList(gState,[1],9,9);
}

if(gState == [1,1,1,1,1,1,1,1,1])
{
// do MAIN_ACTION();
llSay(0, "all plates are pressed");
}
}
}```

Â

Share on other sites

Posted (edited)

First thing:

You are initialising the list to all zeros each time a collision_start begins, but then you are only testing for one of the multiple possible collisions.Â

```collision_start(integer num)
{
list gstate = [0,0....
integer ii;
integer whichChild;

for( ii = 0; ii< num; ii++)
{
// and update the list if it's a plate
}
// now see how many of the list are still unset
if( llListFindList(gstate, [0]) == -1)
{
// there are no un-collided plates, so let's do stuff
}
}```

I'veÂ done this the opposite way round to Quistessa's proposal partly because I'm bloody-minded but also because it might be simpler for you to follow the logic, her "while(~--num)" is more elegant but perhaps harder to understand.

Â

I also have some reservations about all the collisions occurring within the same frame such that you will be able to get them in a single event, I believe that the collisions are going to be spread over several milliseconds and so several collision_start events will be experienced before the avatar colliding has met all the plates.

Edited by Profaitchikenz Haiku
• 1
Share on other sites

55 minutes ago, Tattooshop said:

Where did I go wrong? ðŸ¤”

Comparing lists is a tricky one, but from the wiki

Equality test on lists does not compare contents, only the length. Hence:

if ( [1,2,3,4] == [0,0,0,0] )

Always returns true, therefore when you try and compare your list gState against a list of the same number of elements it will always return true.Â

You either have to do an element by element comparison to determine if two lists have identical content, or you have to adopt the tactic I showed above, to look for the presence of an element within the list that implies a desired result has not been achieved, in this case the presence of any 0 means the list is not all 1.

• 2
Share on other sites

57 minutes ago, Profaitchikenz Haiku said:

Comparing lists is a tricky one, but from the wiki

Equality test on lists does not compare contents, only the length. Hence:

if ( [1,2,3,4] == [0,0,0,0] )

Always returns true, therefore when you try and compare your list gState against a list of the same number of elements it will always return true.Â

You either have to do an element by element comparison to determine if two lists have identical content, or you have to adopt the tactic I showed above, to look for the presence of an element within the list that implies a desired result has not been achieved, in this case the presence of any 0 means the list is not all 1.

Thank you so much!

I decided to try your method, but again I couldn't.

```default
{
state_entry()
{
//llMinEventDelay(0.2);
}

collision_start(integer num)
{
list gState = [0, 0, 0, 0, 0, 0, 0, 0, 0];
integer ii;
integer whichChild;

for (ii = 0; ii < num; ii++)
{
// and update the list if it's a plate
}
// now see how many of the list are still unset
if (llListFindList(gState, [0]) == -1)
{
// there are no un-collided plates, so let's do stuff
llSay(0, "all plates are pressed");
}

if (plate == "1") // plate name check
{
// do something
llSay(0, "plate number 1 pressed");
}
///-------------------------------------------------------------
if (plate == "9")
{
llSay(0, "plate number 9 pressed");
}
}
}```

Â

Share on other sites

I also ran the test for only one object and failed.

```default
{
collision_start(integer num)
{
list gState = [0];
integer ii;
integer whichChild;
for (ii = 0; ii < num; ii++)
{
}
if (llListFindList(gState, [0]) == -1)
{
llSay(0, "Woohoo!");
}
}
}```

Â

Share on other sites

I haven't written you an entire script, you have to add the code for working out if whichChild(ii) is a gate and if so, which gate, and then setting the appropriate entry in the list. Until you add that, nothing is going to happen.

• 1
Share on other sites

a basic game framework for single player link collision/touch type games with up to 32 tasks/levels goes like this, demonstrating a 9 collision task game
Â

```// globals
key player;

reset_game()
{
llSetTimerEvent(0.0); // zero the game timer
player = NULL_KEY;
// set all plates to UP here

}

default()
{
state_entry()
{
reset_game();
}

touch_start(integer num)
{
if (player == NULL_KEY)
{
player = llDetectedKey(0);
llSay(0, llGetDisplayName(player) + " has 1 minute to complete the tasks");
llSetTimerEvent(60.0);  // 60.0 = 1 minute game time
}
}

collision_start(integer num)
{
// assume root is link 1 and plates are links 2 to 10
while (~--num)
{
// check that game has started and is the player (not some rando joining in because why not)
if ((player != NULL_KEY) && (llDetectedKey(num) == player))
{  // flag task as complete

// set the plate to DOWN here

}
};

{
llSay(0, llGetDisplayName(player) + " has completed the tasks.  Touch to start new game.");

reset_game();
}
}

timer()
{
llSay(0, llGetDisplayName(player) + " has not completed the tasks in the allowed time.  Touch to start new game.");

reset_game();
}
}

/*

plate 1: link 2 - 2 = 0. 1 << 0 = 1
plate 2: link 3 - 2 = 1. 1 << 1 = 2
plate 3: link 4 - 2 = 2. 1 << 2 = 4
plate 4: link 5 - 2 = 3. 1 << 3 = 8
plate 5: link 6 - 2 = 4. 1 << 4 = 16
plate 6: link 7 - 2 = 5. 1 << 5 = 32
plate 7: link 8 - 2 = 6. 1 << 6 = 64
plate 8: link 9 - 2 = 7. 1 << 7 = 128
plate 9: link 10 - 2 = 8. 1 << 8 = 256

1+2+4+8+16+32+64+128+256 = 511
*/```

Â

• 1
Share on other sites

Looks good, but should there be code to implement not adding in the same link number more than once? (Which I assume will be a simple bitwise & to see if that power of 2 is already set)

• 2
Share on other sites

Posted (edited)
2 hours ago, Profaitchikenz Haiku said:

Looks good, but should there be code to implement not adding in the same link number more than once? (Which I assume will be a simple bitwise & to see if that power of 2 is already set)

I only glanced at the code briefly, but it seems to me if you replace Molly's

2 hours ago, Mollymews said:

with

`tasks = tasks | ( 1 << (link - 2) ) ;`

you shouldn't have to worry about multiple activations of the same task, because 1|1 == 1.

(edit) Also, if I'm not mistaken, Molly's code is missing a test that the collision isn't the root (link number ==1).Â  |-ing with 1-2==-1 might cause some problems. Actually, I'm not 100% on how LSL treats 1<< -1. It's probably 0 in which case the root-collision scenario is taken care of automagically. Neat.

Edited by Quistessa
• 1
• 2
Share on other sites

2 hours ago, Mollymews said:

a basic game framework for single player link collision/touch type games with up to 32 tasks/levels goes like this, demonstrating a 9 collision task game
Â

```
// globals
key player;

reset_game()
{
llSetTimerEvent(0.0); // zero the game timer
player = NULL_KEY;
// set all plates to UP here

}

default()
{
state_entry()
{
reset_game();
}

touch_start(integer num)
{
if (player == NULL_KEY)
{
player = llDetectedKey(0);
llSay(0, llGetDisplayName(player) + " has 1 minute to complete the tasks");
llSetTimerEvent(60.0);  // 60.0 = 1 minute game time
}
}

collision_start(integer num)
{
// assume root is link 1 and plates are links 2 to 10
while (~--num)
{
// check that game has started and is the player (not some rando joining in because why not)
if ((player != NULL_KEY) && (llDetectedKey(num) == player))
{  // flag task as complete

// set the plate to DOWN here

}
};

{
llSay(0, llGetDisplayName(player) + " has completed the tasks.  Touch to start new game.");

reset_game();
}
}

timer()
{
llSay(0, llGetDisplayName(player) + " has not completed the tasks in the allowed time.  Touch to start new game.");

reset_game();
}
}

/*

plate 1: link 2 - 2 = 0. 1 << 0 = 1
plate 2: link 3 - 2 = 1. 1 << 1 = 2
plate 3: link 4 - 2 = 2. 1 << 2 = 4
plate 4: link 5 - 2 = 3. 1 << 3 = 8
plate 5: link 6 - 2 = 4. 1 << 4 = 16
plate 6: link 7 - 2 = 5. 1 << 5 = 32
plate 7: link 8 - 2 = 6. 1 << 6 = 64
plate 8: link 9 - 2 = 7. 1 << 7 = 128
plate 9: link 10 - 2 = 8. 1 << 8 = 256

1+2+4+8+16+32+64+128+256 = 511
*/```

Â

Â

2 hours ago, Profaitchikenz Haiku said:

Looks good, but should there be code to implement not adding in the same link number more than once? (Which I assume will be a simple bitwise & to see if that power of 2 is already set)

Â

16 minutes ago, Quistessa said:

I only glanced at the code briefly, but it seems to me if you replace Molly's

with

```

you shouldn't have to worry about multiple activations of the same task, because 1|1 == 1.

(edit) Also, if I'm not mistaken, Molly's code is missing a test that the collision isn't the root (link number ==1).Â  |-ing with 1-2==-1 might cause some problems. Actually, I'm not 100% on how LSL treats 1<< -1. It's probably 0 in which case the root-collision scenario is taken care of automagically. Neat.

Thanks everyone for your great suggestions!

Sorry for deviating from the ways you suggested, but how to implement this option and is it possible:

each plate transmits a signal for the glow of a symbol / prim, there are 9 such symbols, respectively, but is it possible to simply count the number of glowing primitives on each collision, and if there are 9, then the main condition is fulfilled?

Â

Share on other sites

Posted (edited)
1 minute ago, Tattooshop said:

Oops sorry

Â

Â

Edited by Tattooshop
• 1
Share on other sites

9 minutes ago, Tattooshop said:

each plate transmits a signal for the glow of a symbol / prim, there are 9 such symbols, respectively, but is it possible to simply count the number of glowing primitives on each collision, and if there are 9, then the main condition is fulfilled?

Yes, you /could/ do it that way, for each prim use llGetLinkPrimitiveParams() to check the glow, and if it's > 0 increment your accumulator variable. But that's not really any different logically speaking from the examples above.

• 1
Share on other sites

I'm still a little perplexed as to how an avatar is going to collide with 9 child-prims in a short enough space of time to generate 9 collision_start events that can be read in aÂ  single instance of the collisionÂ event. As soon as the first collision occurs an event will be queued, then another and another, and so on, but they are most likely to be either single collisions or just airs, remember we're talking milliseconds here. The use of llMinEventDelay(0.2) is going to further space out the time between subsequent collisions being queued.

I could understand it better if it worked like this:

no collision events have occurred, no children are glowing.

A collision occurs, detected as being from one of the plates (possibly more than one)

The list entries for the relevant plates are set to 1 and the plates in question set glowing. A test is made to see if all 9 plates have been hit.

A timer is started, let's say 5 seconds.

Further collision events are raised and when found to be from plates, the list entries are amended, the plates set to glow, the test foor all plates hit made, and the timer reset again to say 5 seconds

If all 9 plates are found to have been touched, the timer is cleared, the action takes place, and then everything is reset to the basic condition.

If on the other hand the timer event occurs, then not enough plates were touched and everything is cleared ready for the next round.

I cannot see that it is going to be possible for an avatar to touch all child plates quickly enough for all 9 collision events to v detected in the single (first) event resutling from the first collision.

• 1
Share on other sites

5 minutes ago, Profaitchikenz Haiku said:

I'm still a little perplexed as to how an avatar is going to collide with 9 child-prims in a short enough space of time to generate 9 collision_start events that can be read in aÂ  single instance of the collisionÂ event.

Going back to the original topic post, There are two reasonable interpretations for what Tatooshop wanted:

• Possibility A) when a specific number of avatars (9) are all standing on the pressure plates, a thing happens. This is basically what I implemented in my first post.
• Possibility B) when an avatar presses a plate it turns 'on' (and stays on indefinitely). When all the plates are on a thing happens and all the plates turn 'off'.

after further discussion it seems pretty clear (to me) that Possibility B is what Tatooshop is trying to do.

The main reason Tatooshop is having trouble and causing confusion, is (I think) because they don't have a good enough understanding of what a global variable is or how it can be used to record the current combined state of all the plates.

• 1
Share on other sites

Posted (edited)
1 hour ago, Quistessa said:

after further discussion it seems pretty clear (to me) that Possibility B is what Tatooshop is trying to do.

The main reason Tatooshop is having trouble and causing confusion, is (I think) because they don't have a good enough understanding of what a global variable is or how it can be used to record the current combined state of all the plates.

Agreed, and agreed.

So the list recording which plates have been touched needsÂ to be a global

llMinEventDelay is serving no purpose and may even be counter-productive

Collision events are to be expected to occur either singly or perhaps two at a time

There needs to be a finite time fromÂ the first plate collision within which the remaining plates need to be collided with, if exceeded then reset everything and start again

Looking back up though, with what you and I have provided, it's all there, it just needs re-arranging.

ETA

Just thinking, there is scope for this idea to become a general-purpose puzzle routine: for example instead of simply activatingÂ all plates to get them on for the action to occur, you might have to activateÂ them individually in a specific order, or else they are set to glow individually and you have to activate the one that has just lit up.

Or you could make a hop-scotch game with it....

Edited by Profaitchikenz Haiku
• 1
Share on other sites

Posted (edited)
10 hours ago, Tattooshop said:

Many thanks! This is what I got, but for some reason the main action occurs when I press any plate. Where did I go wrong? ðŸ¤”

Try this as a starting point, it's very simplistic and requires you to add in all the remaining plates, sorry about the tabbing but Geany doesn't work nicely with the forum code

And it hasn't been tested inworld so expect syntax errors

Â

```list gState = [0,0,0,0,0,0,0,0,0];

default
{
state_entry()
{
llSetTimerEvent(0.0);
}

collision_start(integer num)
{
integer ii;
string plate;
if( llListFindList(gState, [1]) < 0) // we've only just begun
{
llSetTimerEvent(30.0);
}

for( ii = 0; ii < num; ii++)
{
if (plate == "1") // plate name check
{
// do something
llSay(0, "plate number 1 pressed");
gState = llListReplaceList(gState,[1],1,1);
}
// else if( plate == "2")
//add in all the other plates here in a similar manner

}

if(llListFindList(gState, [0]) < 0)
{
// do MAIN_ACTION();
llSetTimerEvent(0.0);
llSay(0, "all plates are pressed");
}
}
timer()
{
llSetTimerEvent(0.0);
llSay(0, "Bad luck, move faster next time");
// and reset the script to have another go
}
}```

Â

Edited by Profaitchikenz Haiku
• 1
• 1
Share on other sites

Posted (edited)

Something for you to bear in mind: as given above the code won't work because when you detect plate 1 being collided with, you replace gState(1)'s value to show is set.

But list elements start from 0, and since you don't have a plate named "0" you will never change the first element of gState from 0 to 1.

therefore, if you have 9 plates named 1 to 9, and nine elements in the list, the element that you must change from 0 to 1 for plate "n" is actually n - 1, so for plate 1 you do llListReplaceList(gState, [1], 0, 0)Â

and for plate 2

llListReplaceList(gState, [1], 1, 1)

Your naming convention does allow you to do this:

```integer index = (string) plate; // so plate "2" generates integer 2

gState = llListReplaceList(gState, [1], index -1, index - 1); // replaces the correct list element 0 to 8
// corresponding to plate "1" to "9"```

But I'd suggest you leave that for later and for now just manually add the numbers in, remembering to subract 1 when accessing the list locations

Â

Edited by Profaitchikenz Haiku
• 1
Share on other sites

Please take a moment to consider if this thread is worth bumping.