Tuesday, August 19, 2008

Second Life Game Scripting - Pinball #24 - Game Play

I finally made it to the point where I could add what I call "Full Circle Game Play". This is where the player is given a limited opportunity, score is kept and the game restarts when they are out of resources.

In this case I decided to place this code in the PayAndKeyboard script I use to keep track of the keyboard and will add the pay to later if people start to play.


integer balls_remain;

string curscore_name;
integer curscore;
integer scoreboard = -1;

string highscore_name = "Wood Wheels";
integer highscore = 27;

First the variables that are used and a startup high score. Since this score will be reset each time I recompile the script I've decided to edit this entry by hand when I make changes so then I can at least keep a longer term high score. I wonder who will be the first to beat my high score? I'll post their name here.

Then I added a restart game method in the PayAndKeyboard script because this will be called from two places. When they run out of balls and when they first start the game.

RestartGame()
{
llSay(0,"Game Restarting");

llSay(0,"Current High Score - "+highscore_name+" - "+(string)highscore);

balls_remain = 5;
llSay(0, "You have "+(string)balls_remain+" balls remaining");

curscore = 0;
llMessageLinked(scoreboard,MSG_SET_NUM,(string)curscore,NULL_KEY);
}

That link message was part of the original scoreboard and I'm just resetting the score to 0 for the game.

From the keyboard code, the run_time_permissions is called after we are given permission to listen to the keys.

run_time_permissions(integer perm)
{
// permissions dialog answered
if (perm & PERMISSION_TAKE_CONTROLS)
{
// we got a yes
// take up and down controls
llTakeControls(CONTROL_ROT_LEFT | CONTROL_ROT_RIGHT | CONTROL_UP | CONTROL_DOWN, TRUE, FALSE);

// remove any balls on the table
llSay(1296,"byebye");
in_play = FALSE;

avatar_attached = TRUE;

RestartGame();
}
}

In this code we first tell any ball currently on the table to go away. This is the same message sent by the back wall and the ball is listening for this.

We also call RestartGame to kick off the actual game play. We set the avatar_attached flag after the "byebye" because this has logic issues when this script is actually listening for this exact message.

There is one other place where we can restart a new game. This is where the

if ((channel== 1296) && (llToLower(message) == "byebye"))
{
if (avatar_attached)
{
balls_remain= balls_remain - 1;
llSay(0,"Oops, drain. "+(string)balls_remain+" balls remain");
if (balls_remain<=0)
{
if (curscore>highscore)
{
llSay(0,"HIGH SCORE!!!!!");
highscore = curscore;
highscore_name = curscore_name;
}
RestartGame();
}

llSay(0, "Press 'Page Up' to start another ball.");
}
else
{
llSay(0, "Press 'Page Up' to start another ball.");
}

in_play = FALSE;
}

This is the code that decrements the number of balls remaining, checks for high score and restarts the game if the number of balls is 0. This is why I reset the avatar_attached flag after sending "byebye". Note there I saved the player name curscore_name when the user first touched the table.


touch_start(integer total_number)
{
if (avatar_attached)
{
llSay(0, "Currently allows only one player at a time. Sorry.");
return;
}

....

string detected_name = llDetectedName( 0 );
curscore_name = detected_name;
}

Most touch_start samples iterate through the total number of touches. In this case it is only a one player game so I only use 0 as the player name since I'm going to ignore all others onece the avater is attached to the table and playing.

Oh, I almost forgot. I had to add one more message to the score board so it sends the current score to the root prim whenever the score changes. I then save that in the curscore. I thought about sending a message to the scoreboard to have it send the score back, but I was worried about the asynchronous nature of the communication and thought the control code would be much more complicated than simply sending it on each change.

This is the listener in the PayAndKeyboard script.

integer MSG_TOTAL = 4116; // current score from the scoreboard

link_message(integer from, integer msg_id, string str, key id)
{
if (msg_id == MSG_TOTAL)
{
//llOwnerSay("scoreboard says: "+str);
curscore = (integer) str;
}
}


This is the full link_message function in the scoreboard. The scoreboard is listening for message from the bumpers when they tell it to increment the score. And the root script sends the 0 score when the game starts (see above). Yes, that means that the PayAndKeyboard will set the curscore to 0, then call the scoreboard which will send that same score back to the root again. It's just one extra message and probably not a big deal.

Notice that this function now sends MSG_TOTAL to LINK_ROOT whenever it receives a message to change the score. I could probably have had this as a single call outside the if/else statement, but I'm always worried about some rogue message setting off the message or some future change not needing it and leaving it in that I just added the same line to both sections of the if/else.

link_message(integer from, integer msg_id, string str, key id)
{
//llOwnerSay("scoreboard received "+(string)msg_id);
if (msg_id == MSG_SET_NUM)
{
curval = (integer)str;
//llOwnerSay("setting num to "+(string)curval);
doSendNumbers();

// send the score to the root
llMessageLinked(LINK_ROOT,MSG_TOTAL,(string)curval,NULL_KEY);
}
else if (msg_id == MSG_INC_NUM)
{
curval = curval + 1;
//llOwnerSay("inc num to "+(string)curval);
doSendNumbers();

// send the score to the root
llMessageLinked(LINK_ROOT,MSG_TOTAL,(string)curval,NULL_KEY);
}
}

No comments: