Volleyball Game - Artillery Variations

From Second Life Wiki
Jump to: navigation, search

Created by Kira Komarov.

ChangeLog

  • 16/11/2011 - Initial release.

Abstract

It is interesting to build in Second Life a series of classical games, such as Football (Soccer), Polo, Volleyball and generally all the ball games based on freefall. In the future, perhaps we will be able to see "real Second Life teams" play in such games and hold tournaments. It might be an extension to Second Life which would push the borders of the points of interests of residents. I suggest a concept of virtual sports which would allow residents in metaverses to extend the possibilities of the virtual world.

The physics engine is sufficiently equipped to support most of dynamics of ball games. In fact, the physics engine in Second Life, implements much more than needed to implement these games. However, the general problem is the interaction between a player (in real life), the viewer they use and what happens in the Second Life world.

This three-layer abstraction, being a matter of some abstract form of HCI, may have prevented such a thing to happen. For example, using the viewer, there is no way to "kick the ball with precision". In other words, the physics of Second Life, do allow you to hit a ball in any direction you wish, however the viewers are not well-equipped to perform that operation precisely. If you place a ball onto the ground and collide your avatar against it, the ball will indeed roll away based on the impact angle and force, however it is currently prohibitively difficult to kick a ball towards a precise direction and at a given angle by colliding your avatar against it.

Thus, the only needed modifications are interface tools and in-world fields which would make such an operation convenient to users.

Although this article implements Volleyball (out of personal preference), it would be considerably easier to implement Football (Soccer). In some ways, only the HUD would be needed and instead of two fields, just one big field would be enough.

Introduction

Every ball-game is based more or less on some variations of freefall. My Artillery script and calculations are sufficient to understand the basic concepts of physical movement as well as being generic enough to be able to derive any ball game. I can enumerate a few games that relate to Artillery:

  • Football (Soccer)
  • Polo
  • Basketball

However, in all these cases, the problem is that although we have a full-blown physics engine, we cannot really directly influence the shooting of the ball. If you have ever tried to play "Soccer" with a friend, you would have realized pretty soon that, even by running towards the ball, you still cannot induct the speed you wish into the ball. Nor can you control the angle at which you shoot the ball. These two pretty much eliminate the possibility to create any game since all the mentioned cases are heavily based on where and how you wish to send a ball.

We can abstract over these games and use the principles to implement any ball-game. The first part consists in implementing a system in order to be able to control the ball. The last part, left up to builders, consists in respecting the characteristics of the game in question and implementing the ruleset as well as possible. This article implements the game of Volleyball, or rather beach volley in the sense that it is governed by less strict rules, and subparts of it may be used to generate other derivations of Artillery in order to create other games.

The article will be written as a technical report, describing the assembly of the Volleyball system and including how to overcome difficulties. It is in that way, different from the rest of the articles since you will have to understand how it is done, instead of being copy-pasteable. This article will also refer you to the full assembly on marketplace (distributed as a freebie) which contains the full-permission result of implementing this game.

I license the game implementation under the The General Public License (GPL), which will allow you to build your own derivates and even commercialize them, provided that the parts based and inspired from my assembly are credited to myself.

Terminology

  • The McCoys are a team, represented by the color red.
  • The Hatfields are a team, represented by the color blue.
  • A named script is a script that must be named precisely as indicated in this article.
  • An unnamed script is a script that may be called whatever the builder chooses.
  • A llListen() colliding (shared) channel is a channel on which multiple scripts push data and listen for data by performing the filtering in the listen() event.
  • A llListen() non-colliding (private) channel is a channel which is filtered using the llListen() parameters themselves thus, not even triggering the listen() event at all in other scripts.

Game Mechanics

This section describes how the assembly works. For the other rules of Volleyball, feel free to consult the abundant information on the Internet.

Two teams, McCoys and Hatfields, both receive a HUD. This HUD will be used to send the ball from one field to the other, as well as passing the ball between the same team-members.

When a player holds down the left mouse button, on a point in the area of the HUD, velocity is built up. When the player releases the button, that velocity (along with other calculations) is transferred to the ball, provided that they are close enough to the ball.

The (in this assembly) black patches, help all the players to determine where the ball will land. This is an optional feature, left up to the builder to include or take out. Provided that the maximum velocity a player can transfer to a ball is capped at something sensible, this optional feature may be left out to make it even more challenging.

Players must anticipate how much velocity to build up and intercept the ball so that they send it back to the opposing field without the ball hitting the ground. The purpose of the game is to confuse, outwit and maintain enough concentration so that the ball will eventually hit the ground in the opposing team's field.

Abstractions over Volleyball

  • In volleyball, players usually have the ability to slam the ball into the opposing field. This feature has been abstracted away in this implementation. While it is technically possible (by choosing the low Artillery angle and allowing avatars to jump and intercept the ball), it has been left in order to get the basics running. It might be added later on provided I can spare the time.
  • Because people's heights vary greatly from mini-Avatars to 2m tall avatars, we can only respect the general ratios of a Volleyball field. For that, we use the following schematic:

Volleyball court specification.

The field in this assembly thus respects that schematic as far as ratios go. However, a builder / creator may choose to scale the field up and make it larger. This will not affect the scripts.

  • Either Second Life of Real Life, ball games will never be fully resistant to cheating and interference. For example, a bystander might run across the field and interfere with the game. The game restricts the HUDs range to only a few meters, thus the two teams will not be able to interfere with each other. However, again, just as in Real Life, if a player runs to the opposing field and messes with the ball, the validity of that round is, of course, void. Thus, it is generally suggested that a referee be present to mediate between the two parties.

General Considerations for Builders

  • The assembly must be entirely linked together with the net primitive being the root. The ball is the only unlinked object that rezzes from the Volleyball dispenser. Optionally include a derez-on-SIM-edge into the Volleyball to prevent littering.
  • The whole assembly is built to communicate using channels derived from hashes of the creator, NOT the owner. This will make HUDs transferrable with permissions which will not allow teams to tamper with the inner workings of the script. This does not mean they will use me as the creator. Whoever builds this assembly will be considered, in Second Life terms, the creator.
  • Some scripts are named, meaning the name is important. This is rather meant to distinguish the teams and field components, rather than preserve licensing. The scripts build hashes based on the names of the scripts. Further on, I will show how to rename the scripts (which is indicated in case of a tournament).
  • For the build itself, I call the two opposing teams, the McCoys and the Hatfields (initially based on a discussion between Heather Lingiuan and myself). The McCoys have a red color identifier and the Hatfields have a blue color identifier. These colors may be changed to any other pair of colors and are meant to inform the players of which team they belong to.
  • The outer shell that you see in the pictures, is optional. However, it is recommended, even if set to full transparency to keep the ball within reachable bounds in case the ball rolls away.
  • Ideally the Volleyball fields (to be explained in detail later), should rest on Linden ground. My build rests on a thick primitive up at 500m. However, given a sufficient velocity, lag and gremlins, when the ball descends, if the fields do not rest on Linden ground, the ball might go through the ground primitive. Albeit annoying to the extreme, it does not invalidate the game. If the ball slides through the ground on the opposing team's field, it is still a valid score point - just another ball will have to be rezzed again.

Overview of Components

General overview of the volleyball field assembly.

  1. Volleyball fields.
  2. Volleyball fields landing prediction overlay.
  3. Targetting HUD for two different teams.
  4. Ball.
  5. Wind direction and intensity measuring device.

Description of Components

1. Volleyball fields.

The volleyball fields consists of 2 (two) subparts and the net:

  • the volleyball net, calibrated to men's volleyball rules, the distance from the top of the net to the ground being 2.43m.
  • the fields which consist of two overlays, the field itself showing the delimiters of the field in white and another, additional overlay which will mark the predicted landing position for both teams. The latter is optional, yet encouraged given SL limitations. You may remove that part if you notice that you are able to play without it.

2. Targetting HUD

The targetting HUD consists in two descriptive fields, placed from top to bottom and describes the opponent's field and the allies field. This HUD will be used to allow players to pass the ball to teammates, as well as shoot the ball into the opponent's field. It does that by mapping the HUD relative-coordinate system to the in-world fields' coordinate system and sending the ball the landing coordinates if the ball is within a certain user-specified range.

The HUD will also inform the filed overlays of the predicted landing position. This will allow players to run to that point and intercept the ball.

3. Ball

The ball will respond in-world from commands given by the HUDs and will also compute the required Artillery trajectory to hit its destination.

4. Wind direction and intensity measuring device.

I have optionally implemented a simple wind measuring device which will be displayed in-world. Keep in mind that the ball's trajectory is indeed influenced by wind and might induce an error from the trajectory which the field overlay will not compute.

Breakdown of Fields

VBFieldLayers.png

  1. The field itself with white delimiters.
  2. Meant for hinting to the player the predicted landing spot.

The field for both teams have two overlay primitives. One of them (1) is the field itself that just displays the field borders and the other (2) will be used to indicate the predicted landing spot of the volleyball. These two primitives should overlap by setting their position one over the other.

McCoys

For the McCoys, the volleyball field (1), contains a named script [K] Volley - McCoys with the following content:

 //////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
integer comChannel;
 
default
{
    state_entry() {
        list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
        integer itra;
        integer ahash;
        for(itra=0, ahash=0; itra<llStringLength(llGetScriptName()); ++itra) {
            ahash += llListFindList(alpha, (list)llToLower(llGetSubString(llGetScriptName(), itra, itra)));
        }
        comChannel = ahash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        llListen(comChannel, "", "", "@rc");
    }
    listen(integer channel, string name, key id, string message) {
        vector pos = llGetPos();
        vector scale = llGetScale();
        llRegionSay(comChannel, "@ac=" + (string)pos.x + "," + (string)pos.y + "," + (string)pos.z + "," + (string)scale.x + "," + (string)scale.y);
        llSetColor(<1,0,0>, ALL_SIDES);
        llSetTimerEvent(5);
    }
    timer() {
        llSetColor(<1,1,1>, ALL_SIDES);
        llSetTimerEvent(0);
    }
}

The field shadow overlay (2.) overlay contains the following named script [K] Volley - McCoys:

 //////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
//              _             ___                 
//     |\/|  _ /   _      _    |  _. ._ _   _ _|_ 
//     |  | (_ \_ (_) \/ _>    | (_| | (_| (/_ |_ 
//                    /                 _|        
//
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
default 
{
    state_entry() {
        list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
        integer itra;
        integer mhash;
        string mfield = llList2String(llParseString2List(llGetScriptName(), [" - "], []),1);
        for(itra=0, mhash=0; itra<llStringLength(mfield); ++itra) {
            mhash += llListFindList(alpha, (list)llToLower(llGetSubString(mfield, itra, itra)));
        }
        integer comChannel = mhash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        llListen(comChannel+1, "", "", "");
    }
    listen(integer channel, string name, key id, string message) {
        list tData = llParseString2List(message, ["="], []);
        if(llList2String(tData, 0) != "@la") return;
        list landPos=llCSV2List(llList2String(tData,1));
        vector landXY;
        landXY.x=llList2Float(landPos, 0);
        landXY.y=llList2Float(landPos, 1);
        vector pos = llGetPos();
        llOffsetTexture(landXY.x/2, landXY.y/2, ALL_SIDES);
    }
}

As you might notice, the names are identical, yet they contain different code. That is because the code builds an identifier hash on which channels are generated. In this case, both the field and the field shadow overlay are based on the same channel. For the opponents field, the Hatfields, other channels are generated based on the script's name.

Similarly, you will see that the ball uses common identifiers to allow control from both teams.

Hatfields

Symmetrically, the Hatfield's volleyball field (1), contains the named script [K] Volley - Hatfields:

 //////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
integer comChannel;
 
default
{
    state_entry() {
        list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
        integer itra;
        integer ahash;
        for(itra=0, ahash=0; itra<llStringLength(llGetScriptName()); ++itra) {
            ahash += llListFindList(alpha, (list)llToLower(llGetSubString(llGetScriptName(), itra, itra)));
        }
        comChannel = ahash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        llListen(comChannel, "", "", "@rc");
    }
    listen(integer channel, string name, key id, string message) {
        vector pos = llGetPos();
        vector scale = llGetScale();
        llRegionSay(comChannel, "@ac=" + (string)pos.x + "," + (string)pos.y + "," + (string)pos.z + "," + (string)scale.x + "," + (string)scale.y);
        llSetColor(<0,0,1>, ALL_SIDES);
        llSetTimerEvent(5);
    }
    timer() {
        llSetColor(<1,1,1>, ALL_SIDES);
        llSetTimerEvent(0);
    }
}

And the shadow overlay (2), for the Hatfields is a named script [K] Volley - Hatfields:

 //////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
//               _                  ___                 
// |_|  _. _|_ _|_ o  _  |  _|  _    |  _. ._ _   _ _|_ 
// | | (_|  |_  |  | (/_ | (_| _>    | (_| | (_| (/_ |_ 
//                                            _|        
//
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
default 
{
    state_entry() {
        list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
        integer itra;
        integer mhash;
        string mfield = llList2String(llParseString2List(llGetScriptName(), [" - "], []),1);
        for(itra=0, mhash=0; itra<llStringLength(mfield); ++itra) {
            mhash += llListFindList(alpha, (list)llToLower(llGetSubString(mfield, itra, itra)));
        }
        integer comChannel = mhash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        llListen(comChannel+1, "", "", "");
    }
    listen(integer channel, string name, key id, string message) {
        list tData = llParseString2List(message, ["="], []);
        if(llList2String(tData, 0) != "@la") return;
        list landPos=llCSV2List(llList2String(tData,1));
        vector landXY;
        landXY.x=llList2Float(landPos, 0);
        landXY.y=llList2Float(landPos, 1);
        vector pos = llGetPos();
        llOffsetTexture(landXY.x/2, landXY.y/2, ALL_SIDES);
    }
}

Breakdown of HUDs

VBHuds.png

There are two HUDs, one for McCoys and one for Hatfields. The one depicted in the screenshot above is the McCoys HUD because the McCoys have a red identifier and that identifier is facing to the bottom of the screen. Symmetrically, the Hatfields obtain a reversed HUD with the blue box pointing downwards and indicating they are a Hatfield team member. Thus, each HUD has two linked primitives. The "upper" primitive box representing the opponent and the "lower" primitive box representing the player's own team (allies).

The HUDs are used to shoot the ball towards a certain point on the field. They do that by mapping differently aligned and rotated coordinate systems to the coordinate system of the fields. As a general rule, a player belongs to a certain team, either McCoys or Hatfields, if their field's color is the same as the color of the lower box of their HUD. In the picture above, I am wearing the McCoys HUD and standing on the McCoys field which makes me a McCoy.

Whenever the HUDs, either the McCoys HUD or the Hatfields HUD, are worn, they go through a calibration procedure which makes both fields light up in order to indicate and show the player to which team they belong to.

Using a McCoys HUD on a Hatfields field is a violation of the game and a referee should prevent that.

All HUD scripts, except for the scripts responsible for flashing the fields, contain one settable parameter:

integer MAX_VELOCITY = 50;

Which represents the settable maximum velocity that may be attained by holding down the left mouse button a certain field component. This is important, because the velocity is also dependent on the angle. Intuitively, one challenge of the game is to induce enough velocity for the ball to cross the net. However, a player may induce more than that to confuse or make it harder for the opposing team to intercept the ball once it descends on the opposing field. This parameter limits the maximum velocity because too much of it may shoot the ball so high that it might be fairly impossible for the opposing team to intercept it.

The literal value here "50" represents 50m/s and assumes that all players may accelerate the ball to a maximum velocity of 50m/s. This is a bit high and should be lowered and tweaked to your field dimension and players.

Also, since every single script in the HUD contains this parameter, you may exaggerate and (in case of teams), assign different max velocities to players (ie: John Doe has more muscle than his teammate Kelly). In case you do that, balancing as well as setting up regulations for players in a competition is entirely up to you.

Flashing HUD

In order to indicate to the player that they are currently building up velocity to shoot the ball into a certain field, both the blue and red boxes on the HUD may contain the following unnamed script [K] Velocity Buildup:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
//
//                                            _  
//       \  / _  |  _   _ o _|_      |_| | | | \ 
//        \/ (/_ | (_) (_ |  |_ \/   | | |_| |_/ 
//                              /                
//
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
integer lock=0;
 
default
{
    link_message(integer sender, integer num, string str, key id)
    {
        if(lock) return;
        lock=1;
        llSetColor(<1,1,1>, ALL_SIDES);
        llSetTimerEvent(0.01);
    }
    timer() {
        llSetColor(<0,0,1>, ALL_SIDES);
        llSetTimerEvent(0);
        lock=0;
    }
}

Hatfields Component of McCoys HUD

In the McCoys HUD (the one in the picture above), the (1.) Hatfields HUD Component, contains the named script [K] Volley - Hatfields which is responsible for communicating the predicted landing target to the ball and the field overlays, as well as the optional (yet, recommended) unnamed script [K] Velocity Buildup which flashes the HUD indicating buildup.

 //////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
//                 _                      _  
//        |\/|  _ /   _      _   |_| | | | \ 
//        |  | (_ \_ (_) \/ _>   | | |_| |_/ 
//                                                                        
//////////////////////////////////////////////////////////
//                    CONFIGURATION                     //
//////////////////////////////////////////////////////////
 
// Set this to the maximum possible velocity a player 
// can induce into the volleyball.
 
integer MAX_VELOCITY = 50;
 
//////////////////////////////////////////////////////////
//                  END CONFIGURATION                   //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
float Ox;
float Oy;
float Oz;
float Sx;
float Sy;
 
integer velocity=2;
integer comHandle;
integer comChannel;
 
init() {
    llSetColor(<0,0,0>, ALL_SIDES);
    list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
    integer itra;
    integer ahash;
    for(itra=0, ahash=0; itra<llStringLength(llGetScriptName()); ++itra) {
        ahash += llListFindList(alpha, (list)llToLower(llGetSubString(llGetScriptName(), itra, itra)));
    }
    comChannel = ahash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
    comHandle = llListen(comChannel, "", "", "");
    llSetTimerEvent(2+llGetRegionTimeDilation());
}
 
default
{
    on_rez(integer start_param) {
        init();
    }
 
    attach(key id) {
        init();
    }
 
    state_entry() {
        init();
    }
 
    timer() {
        llRegionSay(comChannel, "@rc");
    }
 
    listen(integer channel, string name, key id, string message) {
        list calibData = llParseString2List(message, ["="], []);
        if(llList2String(calibData,0)!="@ac") return;
        llSetTimerEvent(0);
        llListenRemove(comHandle);
        calibData = llCSV2List(llList2String(calibData,1));
        Ox = llList2Float(calibData, 0);
        Oy = llList2Float(calibData, 1);
        Oz = llList2Float(calibData, 2);
        Sx = llList2Float(calibData, 3);
        Sy = llList2Float(calibData, 4);
        list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
        integer itra;
        integer mhash;
        string mfield = llList2String(llParseString2List(llGetScriptName(), [" - "], []),1);
        for(itra=0, mhash=0; itra<llStringLength(mfield); ++itra) {
            mhash += llListFindList(alpha, (list)llToLower(llGetSubString(mfield, itra, itra)));
        }
        comChannel = mhash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        llSetColor(<0,0,1>, ALL_SIDES);
    }
 
    touch(integer num_detected)
    {
        if(velocity<MAX_VELOCITY) ++velocity;
        llMessageLinked(LINK_THIS, 1, "", "");       
    }
 
    touch_end(integer num_detected) {
        vector tp = llDetectedTouchST(0);
        float dx = (Ox+(tp.x-0.5)*200/100*Sx/-2);
        float dy = (Oy+(tp.y-0.5)*200/100*Sy/-2);
        llRegionSay(comChannel+1, "@la=" + (string)((tp.y-0.5)*2)+ "," + (string)((tp.x-0.5)*-2));
        llWhisper(comChannel, (string)dx + "," + (string)dy + "," + (string)Oz + "," + (string)velocity);
        velocity=0;
    }
 
}

McCoys Component of McCoys HUD

Similarly, the lower box (4. in the picture above) on the McCoys HUD, contains the following named script [K] Volley - McCoys:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////                                   
//                 _                      _  
//        |\/|  _ /   _      _   |_| | | | \ 
//        |  | (_ \_ (_) \/ _>   | | |_| |_/ 
//                       /                   
//////////////////////////////////////////////////////////
//                    CONFIGURATION                     //
//////////////////////////////////////////////////////////
 
// Set this to the maximum possible velocity a player 
// can induce into the volleyball.
 
integer MAX_VELOCITY = 50;
 
//////////////////////////////////////////////////////////
//                  END CONFIGURATION                   //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
float Ox;
float Oy;
float Oz;
float Sx;
float Sy;
 
integer velocity=2;
integer comHandle;
integer comChannel;
 
init() {
    llSetColor(<0,0,0>, ALL_SIDES);
    list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
    integer itra;
    integer ahash;
    for(itra=0, ahash=0; itra<llStringLength(llGetScriptName()); ++itra) {
        ahash += llListFindList(alpha, (list)llToLower(llGetSubString(llGetScriptName(), itra, itra)));
    }
    comChannel = ahash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
    comHandle = llListen(comChannel, "", "", "");
    llSetTimerEvent(2+llGetRegionTimeDilation());
}
 
default
{
    on_rez(integer start_param) {
        init();
    }
 
    attach(key id) {
        init();
    }
 
    state_entry() {
        init();
    }
 
    timer() {
        llRegionSay(comChannel, "@rc");
    }
 
    listen(integer channel, string name, key id, string message) {
        list calibData = llParseString2List(message, ["="], []);
        if(llList2String(calibData,0)!="@ac") return;
        llSetTimerEvent(0);
        llListenRemove(comHandle);
        calibData = llCSV2List(llList2String(calibData,1));
        Ox = llList2Float(calibData, 0);
        Oy = llList2Float(calibData, 1);
        Oz = llList2Float(calibData, 2);
        Sx = llList2Float(calibData, 3);
        Sy = llList2Float(calibData, 4);
        list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
        integer itra;
        integer mhash;
        string mfield = llList2String(llParseString2List(llGetScriptName(), [" - "], []),1);
        for(itra=0, mhash=0; itra<llStringLength(mfield); ++itra) {
            mhash += llListFindList(alpha, (list)llToLower(llGetSubString(mfield, itra, itra)));
        }
        comChannel = mhash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        llSetColor(<1,0,0>, ALL_SIDES);
    }
 
    touch(integer num_detected)
    {
        if(velocity<MAX_VELOCITY) ++velocity;
        llMessageLinked(LINK_THIS, 1, "", "");       
    }
 
    touch_end(integer num_detected) {
        vector tp = llDetectedTouchST(0);
        float dx = (Ox+(tp.x-0.5)*200/100*Sx/-2);
        float dy = (Oy+(tp.y-0.5)*200/100*Sy/-2);
        llRegionSay(comChannel+1, "@la=" + (string)((tp.y-0.5)*2)+ "," + (string)((tp.x-0.5)*-2));
        llWhisper(comChannel, (string)dx + "," + (string)dy + "," + (string)Oz + "," + (string)velocity);
        velocity=0;
    }
 
}



VBHudsSym.png

Symmetrically, we have the scripts belonging to the Hatfields HUD. The Hatfields HUD will also, optionally, contain the generic unnamed script [K] Velocity Buildup which may be placed in both boxes on the HUD.

McCoys Component of Hatfields HUD

The McCoys component (red box on the HUD), contains the named script [K] Volley - McCoys:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////                                   
//               _                           _  
//     |_|  _. _|_ _|_ o  _  |  _|  _   |_| | | | \ 
//     | | (_|  |_  |  | (/_ | (_| _>   | | |_| |_/ 
//                                              
//////////////////////////////////////////////////////////
//                    CONFIGURATION                     //
//////////////////////////////////////////////////////////
 
// Set this to the maximum possible velocity a player 
// can induce into the volleyball.
 
integer MAX_VELOCITY = 50;
 
//////////////////////////////////////////////////////////
//                  END CONFIGURATION                   //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
float Ox;
float Oy;
float Oz;
float Sx;
float Sy;
 
integer velocity=2;
integer comHandle;
integer comChannel;
 
init() {
    llSetColor(<0,0,0>, ALL_SIDES);
    list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
    integer itra;
    integer ahash;
    for(itra=0, ahash=0; itra<llStringLength(llGetScriptName()); ++itra) {
        ahash += llListFindList(alpha, (list)llToLower(llGetSubString(llGetScriptName(), itra, itra)));
    }
    comChannel = ahash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
    comHandle = llListen(comChannel, "", "", "");
    llSetTimerEvent(2+llGetRegionTimeDilation());
}
 
default
{
    on_rez(integer start_param) {
        init();
    }
 
    attach(key id) {
        init();
    }
 
    state_entry() {
        init();
    }
 
    timer() {
        llRegionSay(comChannel, "@rc");
    }
 
    listen(integer channel, string name, key id, string message) {
        list calibData = llParseString2List(message, ["="], []);
        if(llList2String(calibData,0)!="@ac") return;
        llSetTimerEvent(0);
        llListenRemove(comHandle);
        calibData = llCSV2List(llList2String(calibData,1));
        Ox = llList2Float(calibData, 0);
        Oy = llList2Float(calibData, 1);
        Oz = llList2Float(calibData, 2);
        Sx = llList2Float(calibData, 3);
        Sy = llList2Float(calibData, 4);
        list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
        integer itra;
        integer mhash;
        string mfield = llList2String(llParseString2List(llGetScriptName(), [" - "], []),1);
        for(itra=0, mhash=0; itra<llStringLength(mfield); ++itra) {
            mhash += llListFindList(alpha, (list)llToLower(llGetSubString(mfield, itra, itra)));
        }
        comChannel = mhash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        llSetColor(<1,0,0>, ALL_SIDES);
    }
 
    touch(integer num_detected)
    {
        if(velocity<MAX_VELOCITY) ++velocity;
        llMessageLinked(LINK_THIS, 1, "", "");       
    }
 
    touch_end(integer num_detected) {
        vector tp = llDetectedTouchST(0);
        float dx = (Ox+(tp.x-0.5)*200/100*Sx/2);
        float dy = (Oy+(tp.y-0.5)*200/100*Sy/2);
        llRegionSay(comChannel+1, "@la=" + (string)((tp.y-0.5)*-2)+ "," + (string)((tp.x-0.5)*2));
        llWhisper(comChannel, (string)dx + "," + (string)dy + "," + (string)Oz + "," + (string)velocity);
        velocity=0;
    }
 
}

Hatfields Component of Hatfields HUD

The lower blue box on the Hatfields HUD, contains the named script [K] Volley - Hatfields:

 //////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
//               _                           _  
//     |_|  _. _|_ _|_ o  _  |  _|  _   |_| | | | \ 
//     | | (_|  |_  |  | (/_ | (_| _>   | | |_| |_/ 
//                                              
//////////////////////////////////////////////////////////
//                    CONFIGURATION                     //
//////////////////////////////////////////////////////////
 
// Set this to the maximum possible velocity a player 
// can induce into the volleyball.
 
integer MAX_VELOCITY = 50;
 
//////////////////////////////////////////////////////////
//                  END CONFIGURATION                   //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
float Ox;
float Oy;
float Oz;
float Sx;
float Sy;
 
integer velocity=2;
integer comHandle;
integer comChannel;
 
init() {
    llSetColor(<0,0,0>, ALL_SIDES);
    list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
    integer itra;
    integer ahash;
    for(itra=0, ahash=0; itra<llStringLength(llGetScriptName()); ++itra) {
        ahash += llListFindList(alpha, (list)llToLower(llGetSubString(llGetScriptName(), itra, itra)));
    }
    comChannel = ahash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
    comHandle = llListen(comChannel, "", "", "");
    llSetTimerEvent(2+llGetRegionTimeDilation());
}
 
default
{
    on_rez(integer start_param) {
        init();
    }
 
    attach(key id) {
        init();
    }
 
    state_entry() {
        init();
    }
 
    timer() {
        llRegionSay(comChannel, "@rc");
    }
 
    listen(integer channel, string name, key id, string message) {
        list calibData = llParseString2List(message, ["="], []);
        if(llList2String(calibData,0)!="@ac") return;
        llSetTimerEvent(0);
        llListenRemove(comHandle);
        calibData = llCSV2List(llList2String(calibData,1));
        Ox = llList2Float(calibData, 0);
        Oy = llList2Float(calibData, 1);
        Oz = llList2Float(calibData, 2);
        Sx = llList2Float(calibData, 3);
        Sy = llList2Float(calibData, 4);
        list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
        integer itra;
        integer mhash;
        string mfield = llList2String(llParseString2List(llGetScriptName(), [" - "], []),1);
        for(itra=0, mhash=0; itra<llStringLength(mfield); ++itra) {
            mhash += llListFindList(alpha, (list)llToLower(llGetSubString(mfield, itra, itra)));
        }
        comChannel = mhash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        llSetColor(<0,0,1>, ALL_SIDES);
    }
 
    touch(integer num_detected)
    {
        if(velocity<MAX_VELOCITY) ++velocity;
        llMessageLinked(LINK_THIS, 1, "", "");       
    }
 
    touch_end(integer num_detected) {
        vector tp = llDetectedTouchST(0);
        float dx = (Ox+(tp.x-0.5)*200/100*Sx/2);
        float dy = (Oy+(tp.y-0.5)*200/100*Sy/2);
        llRegionSay(comChannel+1, "@la=" + (string)((tp.y-0.5)*-2)+ "," + (string)((tp.x-0.5)*2));
        llWhisper(comChannel, (string)dx + "," + (string)dy + "," + (string)Oz + "," + (string)velocity);
        velocity=0;
    }
 
}

Breakdown of the Ball

VBBall.png

The ball is the mediator between the two teams and responsible for the trajectory calculations based on the coordinates that the HUDs send. The configuration section of the ball contains the parameter:

float HIT_DISTANCE = 2.0;

which is a settable parameter describing how far away an avatar must be in order to be able to hit the ball using the HUDs.

The volley ball, in the event that the volley ball field is in a public area, should be set to "Allow anybody to move.", as indicated in the picture above since it will allow both teams to drag it around and reposition it. However, in case of a tournament, this option should be turned off and preferably the referee should rez the ball.

The ball contains the named script McCoys vs Hatfields:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
//                     _          
//                    |_)  _. | | 
//                    |_) (_| | | 
//             
//
//////////////////////////////////////////////////////////
//                    CONFIGURATION                     //
//////////////////////////////////////////////////////////
 
// This represents the distance from the ball that an 
// avatar may hit the ball. Keep in mind, this is 
// measured roughly from the avatar's groin. Be generous.
 
// This is also related to response time, a ball that 
// is hurling towards you may be hard to hit and you 
// would also have to take into account the region 
// dilation time which is implicit in this value and 
// not calculated on the fly.
float HIT_DISTANCE = 2.0;
 
//////////////////////////////////////////////////////////
//                  END CONFIGURATION                   //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
string bidM;
string bidH;
integer comChannelM;
integer comChannelH;
 
vector origin;
vector target;
float V0;
float dΔ;
float valSin;
list cDest;
 
default {
 
    state_entry() {
        bidM = llList2String(llParseString2List(llGetScriptName(), [" vs "], []), 0);
        bidH = llList2String(llParseString2List(llGetScriptName(), [" vs "], []), 1);
        list alpha = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"];
        integer itra;
        integer mhash;
        for(itra=0, mhash=0; itra<llStringLength(bidM); ++itra) {
            mhash += llListFindList(alpha, (list)llToLower(llGetSubString(bidM, itra, itra)));
        }
        integer hhash;
        for(itra=0, hhash=0; itra<llStringLength(bidH); ++itra) {
            hhash += llListFindList(alpha, (list)llToLower(llGetSubString(bidH, itra, itra)));
        }
        comChannelM = mhash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        comChannelH = hhash + ((integer)("0x"+llGetSubString((string)llGetCreator(),-8,-1)) & 0x3FFFFFFF) ^ 0xBFFFFFFF;
        llListen(comChannelM, "", "", "");
        llListen(comChannelH, "", "", "");
    }
 
    listen(integer channel, string name, key id, string message) {
        if(llVecDist(llList2Vector(llGetObjectDetails(id, [OBJECT_POS]), 0), origin=llGetPos()) > HIT_DISTANCE) return;
        cDest = llCSV2List(message);
        target.x = llList2Float(cDest, 0);
        target.y = llList2Float(cDest, 1);
        target.z = llList2Float(cDest, 2);
        V0=llList2Float(cDest,3);
        dΔ=llVecDist(<target.x,target.y,0>,<origin.x,origin.y,0>);
        valSin = 9.81*/llPow(V0, 2);
        if(valSin < -1 || valSin > 1) valSin = 9.81/llPow(V0, 2);
        if(valSin < -1 || valSin > 1) return;
        llSetVelocity(llVecNorm(<1,0,0>*llRotBetween(<1,0,0>,llVecNorm(<target.x-origin.x,target.y-origin.y, dΔ*llTan((90-RAD_TO_DEG*llAsin(valSin)/2) * DEG_TO_RAD) + llFabs(target.z-origin.z)>)))*V0, FALSE);
    }
}

Avoid Littering

Teri Tatham suggested that you might end up littering the SIM with balls. In order to void that, I suggested adding a simple script to the ball that would continuously reschedule a timer to remove the ball after the last collision:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
default {
  collision_end(integer num) {
    llSetTimerEvent(3600);
  }
  timer() {
    llDie();
  }
}

This script, when added to the ball will schedule derezzing the ball after 3600 seconds (1 hour). You may change this to any value. The way this works is that every time the ball hits something, it will reschedule the deletion. In this case, if the ball has not collided with anything (whether avatar or primitives), it is safe to assume that the ball is out of the game and may be removed from the land. 1 hour should be more than enough, but a safe bet would also be 5 minutes.

Breakdown of Miscellaneous Components

VBMiscComponents.png

The wind indicators show the players, at all times the direction of the wind (component 1), as well as the windspeed by rotating a light-emitting cylinder (2). The other helper components (3, 4 and 5) allow you to deploy the assembly so it may be used by the public.

Wind direction

The wind direction component (1) primitive contains the following unnamed script [K] Wind Direction:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
default
{
    state_entry() {
        llSetTimerEvent(0.5);
    }
    timer() {
        vector wind = llWind(ZERO_VECTOR);
        llLookAt(llGetPos() * llRotBetween(<1,0,0>, wind), 0.5, 0.2);
    }
}

Wind speed

The wind speed component (2) contains the following unnamed script [K] Wind Speed:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
default
{
    state_entry() {
        llSetTimerEvent(0.5);
    }
    timer() {
        llTargetOmega(<0,0,1>, llVecMag(llWind(ZERO_VECTOR)), 0.5);
    }
}

Volleyball Dispenser

The volleyball dispenser (3), contains the following unnamed script [K] Volley Ball Dispenser:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
default
{
    state_entry()
    {
        llSetText("Touch to dispense a volleyball.", <1, 1, 1>, 1.0);
    }
 
    touch_start(integer total_number)
    {
        if(llGetInventoryName(INVENTORY_OBJECT,0) == "") return;
 
        llRezObject(llGetInventoryName(INVENTORY_OBJECT,0), llGetPos() + <0, 0, 1>, <0,0,2>, ZERO_ROTATION, 0);
    }
}

Hatfields HUD Dispenser

The Hatfields HUD dispensers (4) contain the same unnamed script [K] Hatfields HUD Dispenser:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
default
{
    state_entry()
    {
        llSetText("Hatfields, touch for HUD.", <1, 1, 1>, 1.0);
    }
 
    touch_start(integer total_number)
    {
        if(llGetInventoryName(INVENTORY_OBJECT,0) == "") return;
 
        llGiveInventory(llDetectedKey(0), llGetInventoryName(INVENTORY_OBJECT,0));
    }
}

McCoys HUD Dispenser

The McCoys HUD dispenser (5), contains the following unnamed script [K] McCoys HUD Dispenser:

//////////////////////////////////////////////////////////
// [K] Kira Komarov - 2011, License: GPLv3              //
// Please see: http://www.gnu.org/licenses/gpl.html     //
// for legal details, rights of fair usage and          //
// the disclaimer and warranty conditions.              //
//////////////////////////////////////////////////////////
 
//////////////////////////////////////////////////////////
//                     INTERNALS                        //
//////////////////////////////////////////////////////////
 
default
{
    state_entry()
    {
        llSetText("McCoys, touch for HUD.", <1, 1, 1>, 1.0);
    }
 
    touch_start(integer total_number)
    {
        if(llGetInventoryName(INVENTORY_OBJECT,0) == "") return;
 
        llGiveInventory(llDetectedKey(0), llGetInventoryName(INVENTORY_OBJECT,0));
    }
}

HUD-to-Field Calibration

VBCalibrationGood.png

In order to leave the scripts as they are, you will need to calibrate the field to the HUD axis. The whole assembly should have a world rotation of 0. If facing the McCoys field from the Hatfields field, an easy way to check, is that the right of the McCoys component of the Hatfields HUD will be in-line and parallel with the X axis of the field. Rotation can be taken into account but, I simply haven't bothered with it yet.

For contrast, the following picture (taken approximatively from the same perspective and angle) shows a bad calibration:

VBCalibrationBad.png

Tournament Considerations

For this specific assembly, the following considerations should be respected in case of a tournament.

  • A neutral referee (or several) should be used to arbitrate between the teams and supervise the game.
  • Spectators should be further than the preset HIT_DISTANCE in the ball script from all borders of the playing field.
  • Script terrorism should not be required towards the spectators. Instead, one or several warmup rounds should be played until both teams agree, along with the referee, that the game is playable. This should be done exhaustively until all three parties concur.
  • The ball should not have "Allow anybody to move." enabled.
  • The ball should be rezzed by the lead referee and the referee alone should be able to move the ball via drag. This will make the teams, as well as spectators, not able to touch the ball. The referee will thus place and replace the ball whenever it goes out of bounds or a point is scored.
  • The HUDs should be handed out by the referee as well instead of being dispensed at will.
  • The dispensers should be entirely removed.
  • Flight should be restricted for the players leaving only jumps. The spectators may of course fly, provided they do not interfere with the game.
  • Animation overrides should be allowed. In fact, setting yourself in permanent run-mode is preferable. Volleyball is not a game relying on your ability to run fast. In fact, the field can be traversed quickly in Volleyball. It is rather a game of estimating where the ball will land and how hard to hit it so that it lands where you want it to in the opponent's field as well as a game of endurance.
  • [Optional] Additionally, the string "Hatfields" should be replaced in all scripts (including script names) to some string representing one team and the string "McCoys" should be replaced in all scripts (including script names) to some string representing the other team.

All other rules, derived from Volleyball rules apply.

Public Considerations

In the event that this assembly will be provided to the public, the creator / builder of the assembly should be one and the same for all primitives contained in this assembly. The ball placed in the Volleyball dispenser should be set to "Allow anybody to move." so that both teams may reposition the ball themselves. This will, of course, allow the teams to cheat. Besides, I am sure that in such cases the whole other team will make very vigilant referees to prevent the other team from cheating. Might have to turn off land damage to prevent bloody noses - or leave it on, it's only a paper moon.

Further work

  • The textures may of course be changed. However, when designing and using textures for your football field, please keep in mind to help the players. That is, the colors and the textures you use should not filter out useful information. For example, making the net non-transparent will not help. Making the ball the color of the field will also not help, especially it will not help the referee. At least a minimal set of information should be kept obvious to the team players and the referee.
  • Since the whole assembly is entirely linked, you may drop a resizer in it and scale it up or down. The HUDs, ball and fields will adapt and synchronize provided that the rotation calibration (see above) is respected.
  • It would be of course, very helpful if you could place the field in its own parcel because that way you will be able to use Second Life tools to enforce rules. For example, although the referee arbitrates the whole game and should have the best ability to see the game, it is not necessary for the referee to be able to cross into the playing fields. Furthermore, you can also keep the audience from running into the field by using the land access list.
  • While building, I also wanted to include some automatic score-keeping. However, for random reasons, the collision() series of events are not good enough for that. The referee should probably be the one to keep the score anyways - perhaps with an extra, separate component.
  • Some nice animations could be added to the HUD to imitate hitting the ball.
  • Ball-slamming (ie: jumping up and hitting the ball downwards into the opponent's field) can be implemented by choosing the "low" angle in the Artillery calculations. It is not currently implemented but it may be a feature for later versions.

Variations on Volleyball

One possible variation of this game would be to include the HUD controls implicitly in mouselook mode and abstract away from the calculated trajectories. That is, when clicking in mouselook, a force would be applied to the ball provided it is in front of the crosshair. I don't like it that way (personal choice, I don't consider it very reliable) so that is why I went with the one explained in this article.

Obtaining the whole Assembly

  1. Imprudence-compatible export / import folder (scripts will have to be added manually based on this article): Download Imprudence-compatible XML file and textures. In order to import, use the Imprudence viewer and select File->Import Object with Textures... and select the KVolley.xml from the zip-folder you just downloaded. The volleyball shell is a primitive larger than 10m. You will have to import over land that supports primitives over 10m, for example mesh land (most of them these days). You are free to use and alter my textures except reusing the Wizardry and Steamworks [WaS] (an image representing a wizard crafting a gear) logo.
  1. Marketplace (from my shop, might be unreliable since I have no land holdings nor in-world display shop and haul my 1-primitive shop around with me - how much for a 1 primitive 0.010x0.010x0.010 land parcel?).

Feel free to mirror these files, provided the GNU/GPL licensing is respected.

A Bit of Formalism

The ball is similar to the Artillery cannon. However, the ball does not rez an object but rather calculates the trajectory and applies the velocity to itself. Since one cannot always hit a target (for example, when one doesn't have the necessary velocity), the difference between the Artillery calculations and the script in the ball is that first, the angle required to hit the spot chosen by the player is calculated. If that is not possible, then the script calculates if the velocity is enough to lift the ball off the ground. If that is also not possible, then the ball is left as it is. Enumerating those events:

  1. The ball calculates the trajectory to the chosen point on the field using the "high" angle. If it is possible, the ball shoots towards that point. If it is not possible,
  2. the ball calculates the angle created by the velocity and shoots most probably into the net. If that is not possible,
  3. the ball stays where it is.

The HUD coordinates for both fields, are given by relative offsets ranging in the domain from [0,0] to [1,1]. The HUD, via synchronization, also knows the dimensions and position of the fields. The HUD sends the offset where the player clicked to the respective field, in order to update the target where the ball will land. Based on the dimensions of the field, the offset at which the player clicked, so that the HUD calculates the landing coordinates.

When a player clicks a position on field and holds down the mouse button, the script in the HUD component starts to linearly build up velocity. When the player releases the button, if the ball is within the HIT_DISTANCE of the player, then all the data above, including the built-up velocity is transferred to the ball which proceeds with pretty much the same calculations made in the Artillery article.

The scripts use up to 4 (four), llListen() filtered (non-colliding) channels and 2 (two) colliding channels. During gameplay, 2 non-colliding channels and 2 colliding channels are used, while the other 2 sleep.

Thanks

  1. I wish to thank the University of New Orleans since this project was built over their sandbox.