Jump to content

Looking for some ideas on how to do this more efficiently...


Donovan Michalski
 Share

You are about to reply to a thread that has been inactive for 858 days.

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

Recommended Posts

I have a side project I've been working on, and I figured I'd discuss it here and get some input from some more experienced scripters.

My main activity in SL is racing - cars, specifically.  I have a number of vehicles I've put together for this purpose, and I'm a member of a few different groups that focus on motorsports in SL.

As with any sport, game, or activity, there are varying levels of talent amongst our regular drivers...but as long as everyone does their best to be aware of what's going on around them and drive with respect for everyone else, we all end up having a good time.

Where it tends to go wrong are the moments when a faster driver is a lap or more ahead of a slower driver, but that slower driver doesn't realize it - and they fight with the faster driver on track even though it doesn't benefit their position in the race.  The result is that both drivers end up losing time overall because they're focused on each other, instead of on the track...and sometimes it even causes accidents that hurt them both even more (not to mention how it can provoke arguments after the race is over).

In order to help with this, I've been working on a system that can be added to our vehicles that gives you a warning that the driver behind you is a lap or more ahead, and that you should give them room when they try to pass you.  In real-world motorsports, this is usually signified by showing the slower car a blue flag, notifying them that they need to let the faster driver go.

As of right now, what I'm doing is attaching a "transponder" of sorts to the back of the car.  It serves a few different purposes...it contains code from an odometer script that tracks how far you've driven since the start of the race (a race official resets everyone's distance before the race begins), it contains a rearward-facing sensor to look for other avatars within a certain range, and it handles communication with the other cars on the track.

The idea is this:  when the sensor detects another driver, it will compare how far you and the other avatar have driven.  If their total distance is greater than yours by a certain amount (set by the admin, usually the length of the full course), then a blue flag will appear above your car, and that driver's name will be above the flag in floating text.  Once they have passed you, the flag disappears.

Most of it seems to be working as intended when testing with three or four cars, but during our first test in actual racing conditions with about ten drivers, results were mixed.  Sometimes the flag would show up, and sometimes it wouldn't.

While putting this together, I came up with two methods for handling the communication between cars so that the transponder script can compare the distances:

  • Direct car-to-car communication using unique llRegionSay channels generated from the UUID of each car
  • Shared communication amongst all cars on a standard channel, with each car broadcasting its data every 2 - 2.5 seconds, each car storing a list of active drivers and distances

Our test race was done using the second method.  As mentioned before, it worked some of the time.  I don't know if the failures were due to a delay in the sensor detecting people, or if the script just didn't have enough time to process everything it needs to do in that moment.  

I'm definitely no expert when it comes to scripting.  I still have a lot to learn, and this is one of those times where I feel like I should get some input from people who've been doing it longer.

(I should note that this system is primarily intended for use in dedicated motorsports regions - the few serious racing spots we have take a lot of steps to minimize the number of running scripts in the region in order to avoid performance issues.  These aren't multipurpose regions with an already heavy script count where we're just throwing in some racing for the heck of it.)

 

// Note:  There might be items here that aren't in use, as this script has been through multiple revisions.  I've tried to clean them up as much as possible.

list    detectedNames = [];
list    driverList = [];
list    distanceList = [];
list    detectedKeys = [];
list    leaderList = [];

integer adminChannel = -10240;
integer dataChannel = -10241;
integer vehicleChannel;
integer aC_handle;
integer data_handle;
integer vC_handle;

integer transpondercount = 0;

key     owner;
key     detectedAvatar;
string  detectedName;
float   detectedDistance;

string letPass;

float   totaldistance = 0.0;
float   distance = 0.0;
float   difference;
float   blueflag = 1.0;
vector  oldPos;
vector  newPos;

integer Flag;
integer FlagPole;
integer Flag2;
integer FlagPole2;
vector blue = <0, 0, 1>;
vector black = <0.078, 0.078, 0.078>;
vector white = <1, 1, 1>;
integer FlagStatus = 0;
integer PenaltyStatus = 0;

list    PrimNameList;

///////Prim name and floating text code
PrimNames()
{
        PrimNameList = [];    
        integer i;    
        for(i=1;i<=llGetNumberOfPrims();++i) 
            {
            PrimNameList += [llGetLinkName(i)];
            }
            
        Flag = llListFindList(PrimNameList, ["RaceFlag"]) + 1;
        FlagPole = llListFindList(PrimNameList, ["RaceFlagPole"]) + 1;
        Flag2 = llListFindList(PrimNameList, ["RaceFlag2"]) + 1;
        FlagPole2 = llListFindList(PrimNameList, ["RaceFlagPole2"]) + 1;
}

LinkText(integer linknum, string text, vector color, float alpha) 
{
    llSetLinkPrimitiveParamsFast(linknum, [PRIM_TEXT, text, color, alpha]);
}
///////

///////Keep track of distance travelled 
measureDistance()
{
    newPos = llGetPos();
    distance = llVecDist(oldPos, newPos) / 1000;
    totaldistance += distance;
    oldPos = newPos;
}
///////

///////Artificial throttling - slows the sending of data so it isn't constantly flooding region chat.  There's probably a better way to do this.
transponder()
{
    ++transpondercount;
    if(transpondercount == 5)
    {
        llRegionSay(dataChannel, (string)owner + "," + (string)totaldistance);
        transpondercount = 0;
    }
}
///////

///////Checks how many cars are in sensor range, then compares your distance driven to theirs
checkDetected()
{
    integer length = llGetListLength(detectedNames);  //Get the number of detected cars
    
    if(length == 0) //Nobody in sensor range
    {
        ClearFlag();  //Clear any active flag
    }
    
    if(length > 0)  //At least one car detected
    {
        integer i;
        for(i = 0; i < length; ++i) // Do the following for each car in range
        {
            string nametocheck = llList2String(detectedNames, i);   //Get the name of the driver
            integer index = llListFindList(driverList, [nametocheck]);  //Find the driver's index number
            if(index != -1)  // If the driver is in the list of drivers using the transponder...
            {
                float theirdistance = llList2Float(distanceList, index);  //Look up their total distance
                difference = theirdistance - totaldistance;  //DO THE MATHS
                
                if(difference > blueflag)  // If you're too far behind...
                {
                    integer isleader = llListFindList(leaderList, [nametocheck]);  //See if that driver is in the leader list
                    if(isleader == -1)  //If not...
                    {
                        leaderList += nametocheck;  //...put them in the list
                        letPass = llList2CSV(leaderList);  //...convert the list to a string
                        BlueFlag();  //Activate the flag
                    }
                }
            }
        }
    }
}
///////

///////Leader list cleanup - if multiple names appear in the text above the flag, but one of them is no longer in sensor range, we want to remove that name while leaving the others
clearleaderlist()
{
    integer length = llGetListLength(leaderList);  //How long is the current leader list?
    
    if(length > 0)  //If the list contains names...
    {
        integer i;
        
        for(i = 0; i < length; ++i)  //Go through each entry in the list
        {
            string nametocheck = llList2String(leaderList, i);  //Get the name
            integer index = llListFindList(detectedNames, [nametocheck]);  //See if that name is currently in sensor range
            
            if(index == -1)  //If that person isn't being picked up by the sensor anymore...
            {
                llDeleteSubList(leaderList, i, i);  //Take them out of the leader list so they don't appear in the floating text
            }
            
        }
    }
    
    if(length == 0)  //Or, if the leader list is empty
    {
        ClearFlag();  //Just get rid of the flag
    }
}
///////

///////Turn blue flag on
BlueFlag()
{
    llSetLinkPrimitiveParamsFast(Flag, [PRIM_COLOR, 1, blue, 1.0, PRIM_TEXTURE, 1, "c51a3c5b-c325-1027-1e19-1b763b0e460d", <1.0, 1.0, 0.0>, ZERO_VECTOR, PI/2 ]);
    llSetLinkPrimitiveParamsFast(FlagPole, [PRIM_COLOR, 0, white, 1.0]);
    LinkText(FlagPole, letPass, <1, 1, 1>, 1.0);
    FlagStatus = 1;
    llRegionSay(adminChannel, (string)owner + "," + (string)FlagStatus);
}
///////

///////Turn flag off
ClearFlag()
{
    llSetLinkPrimitiveParamsFast(Flag, [PRIM_COLOR, 1, blue, 0.0, PRIM_COLOR, 2, blue, 0.0]);
    llSetLinkPrimitiveParamsFast(FlagPole, [PRIM_COLOR, 0, white, 0.0]);
    LinkText(FlagPole, "", <1, 1, 1>, 1.0);
    FlagStatus = 0;
    llRegionSay(adminChannel, (string)owner + "," + (string)FlagStatus);
}

///////
default
{
    state_entry()
    {
        owner = llGetOwner();
        vehicleChannel = (integer) ( "0xF" + llGetSubString(owner,0,6 ) );
        data_handle = llListen(dataChannel, "", "", "");
        aC_handle = llListen(adminChannel,"","","");
        vC_handle = llListen(vehicleChannel,"","","");
        PrimNames();
        oldPos = llGetPos();
    }
    
    changed(integer change)
    {
        if(change & CHANGED_OWNER)
        {
            llResetScript();
        }
    }

    sensor (integer detected)
    {
        while(detected--)
        {
            detectedAvatar = llDetectedKey(detected);  //Get the unique ID for the avatar
            string name = llGetUsername(detectedAvatar);    //Get the avatar's username
            integer index = llListFindList(detectedNames, [name]);  //Find the username in the list of people in sensor range
            if(index == -1)  //If the name isn't already there
            {
                detectedNames += name;  //Add it to the list
            }  
        }
    }
    
    no_sensor()
    {
        detectedNames = [];  //If nobody is in range, clear the list of detected avatars
        leaderList = [];  //If nobody is in range, clear the list of leaders
        letPass = "";  //If nobody is in range, clear the list that would be shown in floating text
        ClearFlag();  //If nobody is in range, clear any active flag
    }
    
    listen(integer channel, string name, key id, string message)
    {
        if(channel == dataChannel)
        {
            list data = llCSV2List(message);
            key identifier = llList2Key(data, 0);
            string drivername = llGetUsername(identifier);
            string currentdist = llList2String(data, 1);
            integer index = llListFindList(driverList, [drivername]);
            
            if(index == -1)
            {
                driverList += drivername;
                distanceList += currentdist;
            }
            
            else if(index != -1)
            {
                distanceList = llListReplaceList(distanceList, [currentdist], index, index);
            }
            
        }
        
        else if(channel == adminChannel)
        {
            if(message == "start")
            {
                llSensorRepeat("", "", AGENT, 32, PI / 3, 2);
                llSetTimerEvent(0.5);
            }
            
            else if(message == "stop")
            {
                llSensorRemove();
                llSetTimerEvent(0.0);
                ClearFlag();
            }
            
            else if(message == "reset")
            {
                totaldistance = 0.0;
                oldPos = llGetPos();
                driverList = [];
                distanceList = [];
                leaderList = [];
                letPass = "";
                ClearFlag();
                
            }  
        }
        
        else if(channel == vehicleChannel)
        {
            list data = llCSV2List(message);
            string datatype = llList2String(data, 0);
            
            if(datatype == "blackflag")
            {
                llSetLinkPrimitiveParamsFast(Flag2, [PRIM_COLOR, 1, white, 1.0, PRIM_TEXTURE, 1, "c2d5e50c-7edd-08c2-49bd-b12eada045ce", <1.0, 1.0, 0.0>, ZERO_VECTOR, PI/2 ]);
                llSetLinkPrimitiveParamsFast(Flag2, [PRIM_COLOR, 2, white, 1.0, PRIM_TEXTURE, 2, "c2d5e50c-7edd-08c2-49bd-b12eada045ce", <1.0, 1.0, 0.0>, ZERO_VECTOR, PI/2 ]);
                llSetLinkPrimitiveParamsFast(FlagPole2, [PRIM_COLOR, 0, white, 1.0]);
                PenaltyStatus = 1;
                LinkText(FlagPole2, "Drive Through Penalty", <1, 1, 1>, 1.0);
            }
            
            else if(datatype == "clearblackflag")
            {
                if(PenaltyStatus == 1)
                {
                llSetLinkPrimitiveParamsFast(Flag2, [PRIM_COLOR, 1, white, 0.0, PRIM_COLOR, 2, white, 0.0]);
                llSetLinkPrimitiveParamsFast(FlagPole2, [PRIM_COLOR, 0, white, 0.0]);
                LinkText(FlagPole2, "", <1, 1, 1>, 1.0);
                PenaltyStatus = 0;
                }
                
                else
                {
                    PenaltyStatus--;
                }
            }
        }
        
    }
    
    link_message(integer sender_num, integer num, string message, key id)
    {
        if(num == 409)
        {
            if(message == "pitting")
            {
                if(PenaltyStatus > 0)
                {
                    ++PenaltyStatus;
                }
            }
        }
    }
    
    timer()
    {
        measureDistance();
        transponder();
        checkDetected();
        if(FlagStatus == 1){clearleaderlist();}  //Only run the cleanup routine if the flag is active
    }
    
}

 

Link to comment
Share on other sites

So the ultimate goal is to telegraph to the driver whether other approaching racers are already a lap or more ahead of them? If so, I can think of a simpler system that might achieve the same result without the need for much cross communication.

Keep your rearward facing sensor, but instead of scanning for avatars, scan for the cars themselves. Have the cars all be named the same, so you can keep the sensor filter tight (this is crucial because a sensor can only detect 16 results per sweep). I presume the cars somehow know what lap they are on, correct?  Store that information in the car's root prim description field, perhaps along with the name of the driver too. That way, when your car scans behind itself and detects other cars in range, it can check those car's description field and compare their lap counter to your own. If theirs is grater than yours, display the flag and driver name as before.

By storing the lap counter and driver name in the car's root description, you make it easy for other cars to query that data immediately using llGetObjectDetails, as soon as they get the car's uuid from the senor. As opposed to having to bounce between sensor and listen events to process the data fully. Being able to do everything quickly in one event and then exit makes life much easier, especially in time sensitive scenarios like a race. And using a lap counter as opposed to say, total distance driven would be preferable I think, as it would only need to be updated once each lap.

Edited by Fenix Eldritch
rewording for clarity
Link to comment
Share on other sites

55 minutes ago, Fenix Eldritch said:

So the ultimate goal is to telegraph to the driver whether other approaching racers are already a lap or more ahead of them? If so, I can think of a simpler system that might achieve the same result without the need for much cross communication.

Keep your reward facing sensor, but instead of scanning for avatars, scan for the cars themselves. Have the cars all be named the same, so you can keep the sensor filter tight (this is crucial because a sensor can only detect 16 results per sweep). I presume the cars somehow know what lap they are on, correct?  Store that information in the car's root prim description field, perhaps along with the name of the driver too. That way, when your car scans behind itself and detects other cars in range, it can check those car's description field and compare their lap counter to your own. If theirs is grater than yours, display the flag and driver name as before.

By storing the lap counter and driver name in the car's root description, you make it easy for other cars to query that data immediately using llGetObjectDetails, as soon as they get the car's uuid from the senor. As opposed to having to bounce between sensor and listen events to process the data fully. Being able to do everything quickly in one event and then exit makes life much easier, especially in time sensitive scenarios like a race. And using a lap counter as opposed to say, total distance driven would be preferable I think, as it would only need to be updated once each lap.

 

Putting the data into the description was another idea I was considering, but hadn't tried yet.  Based on what you're saying there, I see how it would be an improvement.

 

Link to comment
Share on other sites

maybe alternatively we can think about how this can be done with the race control system

when a car passes the race control points spread round the track then send the car a message from race control

the message info can include:

a) my race position (other useful info can be laps remaining in race for me)

b) the car in front of me, their race position, and the time gap between the cars
c) the car behind me, their race position, and time gap between the cars

d) the time gap to the car next in front of me by race position
e) the time gap to the car next behind me by race position

from b) and c) we can deduce blue flag (car behind has a numerically lower race position than me)

from d) and e) we can know where our nearest competitors are on the track

 

when we go this way then the race control system detects the cars, the cars don't have to detect each other

add:

however we do still have the issue of detection (which can be problematic given lag)

a way to do this alternatively is to have the car request the message when it passes thru a region grid position (race control point).  With this the most reliable method is in the race controller there is a script for each car in the race communicating with race control on independent channels

Edited by Mollymews
add
Link to comment
Share on other sites

23 minutes ago, Mollymews said:

maybe alternatively we can think about how this can be done with the race control system

when a car passes the race control points spread round the track then send the car a message from race control

the message info can include:

a) my race position (other useful info can be laps remaining in race for me)

b) the car in front of me, their race position, and the time gap between the cars
c) the car behind me, their race position, and time gap between the cars

d) the time gap to the car next in front of me by race position
e) the time gap to the car next behind me by race position

from b) and c) we can deduce blue flag (car behind has a numerically lower race position than me)

from d) and e) we can know where our nearest competitors are on the track

 

when we go this way then the race control system detects the cars, the cars don't have to detect each other

I like the idea, but this approach requires building a new race control system rather than using one of the existing systems on the market - most multi-driver races are held using Les White's lap gate system, while competitions based on setting fastest laptimes use one of the old SD timing systems (the original creator no longer offers those, but sold some full perm copies before shutting down her own store).

I actually have a different system I got from someone over on Opensim that I've converted for use here in SL, making some pretty big revisions to get it functioning like we need it to for the types of races we run.  It's far from a finished product though, and it has only just recently started being used successfully in actual racing events.

Adding control points around the track can be done - I've put together similar objects in the past that communicate various bits of data to the cars as needed.  As with what I'm doing now, they work fine when there are only a few drivers...with more drivers on the track, though, it's hard to say how well it would go.  I think a lot would depend on how much data is being thrown around at any given moment.

The other aspect is that I'm hoping to keep it simple - right now it's just a single prim with one script that attaches to the rear of the car, and of course the flag(s) that are positioned above the car.  If all you need is the blue flag functionality, it can be attached to any car built with any script system, and it doesn't rely on a specific race gate/timer system.  I have some black flag/penalty code built into this script as sort of a secondary function (so a race administrator can issue a penalty to a driver who isn't following the rules), but that's just an extra I threw in there.

Edited by Donovan Michalski
Link to comment
Share on other sites

This is why we need remote, synchronous r/w access w/ optional validation to dedicated memory address space like in actual games or programs.

Using chat for critically timed checks is abhorrent/inefficient and allows race conditions galore.

The closest thing we have is Experience KVP r/w, even though it is asynchronous, but still offers single frame, single threaded, data validation r/w handling.

We need an in-region, remote access, synchronous alternative.

Edited by Lucia Nightfire
  • Like 1
Link to comment
Share on other sites

1 hour ago, Donovan Michalski said:

The other aspect is that I'm hoping to keep it simple - right now it's just a single prim with one script that attaches to the rear of the car, and of course the flag(s) that are positioned above the car.  If all you need is the blue flag functionality, it can be attached to any car built with any script system, and it doesn't rely on a specific race gate/timer system.

a alternative to detection is for the blue script (in all cars) to listen on a dedicated blue flag channel. The blue flag script enabled-cars periodically broadcast their status on the channel.  The blue flag script maintaining a list of all cars in the race for the duration of the race, then calculating relative status for itself

it can be done so that the cars broadcast their own status at different times to spread the load.  Car1 (some time lapse) Car2 (time lapse) Car3 ... etc. The blue script timer (which will fire the broadcast) starting at different times for each car, then at some constant time (for all cars) thereafter

 

 

Link to comment
Share on other sites

  • 2 weeks later...
On 2/14/2022 at 11:48 AM, Fenix Eldritch said:

So the ultimate goal is to telegraph to the driver whether other approaching racers are already a lap or more ahead of them? If so, I can think of a simpler system that might achieve the same result without the need for much cross communication.

Keep your rearward facing sensor, but instead of scanning for avatars, scan for the cars themselves. Have the cars all be named the same, so you can keep the sensor filter tight (this is crucial because a sensor can only detect 16 results per sweep). I presume the cars somehow know what lap they are on, correct?  Store that information in the car's root prim description field, perhaps along with the name of the driver too. That way, when your car scans behind itself and detects other cars in range, it can check those car's description field and compare their lap counter to your own. If theirs is grater than yours, display the flag and driver name as before.

By storing the lap counter and driver name in the car's root description, you make it easy for other cars to query that data immediately using llGetObjectDetails, as soon as they get the car's uuid from the senor. As opposed to having to bounce between sensor and listen events to process the data fully. Being able to do everything quickly in one event and then exit makes life much easier, especially in time sensitive scenarios like a race. And using a lap counter as opposed to say, total distance driven would be preferable I think, as it would only need to be updated once each lap.

Just as a follow-up, this is the route I ultimately went with.

I have three triggers placed along the track that use llVolumeDetect - when a car passes through them, they send a message to the car via llRegionSay.  

The car, upon hearing the message, increments a counter that is stored in the vehicle description field.

The rearward-facing sensor scans for that number, and compares it to your own.  If the difference is greater than a certain amount, it displays the flag.

It worked great in the test races we did a couple of nights ago, and also helped with my goal of remaining independent of both the race gate/timer system and the vehicle script system.

Link to comment
Share on other sites

You are about to reply to a thread that has been inactive for 858 days.

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

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
 Share

×
×
  • Create New...