The online racing simulator
Helpful functions/tips
1
(28 posts, started )
Helpful functions/tips
I just thought... Such thread might help a lot of people, and all the people might show their way of coding, which will improve the imagination of others, and show them that a lot more stuff than they thought can be done.
ps: I consider myself one of the "others".

Tips:
1: To be better for everyone, if you have downloaded a free source project from somewhere - include it in here, and say if the code either matters for it or not.
2: Include the language it is coded on (C#, php, C++, VB, etc...).
3: Give some more information on what you are using it for and whatever more you want to include about the specific tip/function.
4: Say if it is either tested or not yet.
5: If you just want to make post with improvement of someone's code, why not post it? If they like it, you might have just helped them.
6: If it has been tested, say if it is finished, or you are still not very happy of the result, after all someone might help you.

Sooo... I will start with a few helpful functions:

Language: C#
Works with: LFS_External from the forums.
Result: Replaces clsConnection to clsPlayer and oposite
Level: Very easy.
Number functions in the code strip: 2
State: Tested and working like a charm. :P

static public clsPlayer convertToPlayer(clsConnection C)
{
foreach (clsPlayer P in Form1.Players)
{
if (P.PlayerID == C.PLID)
{
return P;
}
}
return null;
}

static public clsConnection convertToConnection(clsPlayer P)
{
foreach (clsConnection C in Form1.Connections)
{
if (C.PLID == P.PlayerID)
{
return C;
}
}
return null;
}

----------------------------------

Language: C#
Works with: Will work with every program.
Source used: LFS_External from the forums.
Result: Removes all lfs color codes in a string.
Level: TOO easy.
Number functions in the code strip: 1
State: Tested and works perfectly.

static public string removeColorCodes(string coloredText)
{
coloredText = coloredText.Replace("^1", "").Replace("^2", "").Replace("^3", "").Replace("^4", "").Replace("^5", "").Replace("^6", "").Replace("^7", "").Replace("^8", "").Replace("^9", "");
return coloredText;
}

----------------------------------

Language: C#
Works with: Will work with every program.
Source used: LFS_External from the forums.
Result: Reports errors to external text file.
Level: Not too easy.
Number functions in the code strip: 1
State: Tested and works perfectly.
Requirements: needs file "errorFile.txt" in folder "\errors\" which has to be located on the same location as your project. Also needs a string[] with additional information, which, of course can be null.
Request: It has a disabled code, which I couldn't get to work good, so if you have any solutions for it, you can tell me.

static public void buildErrorReportOnCrash(Exception E, string reportSituation, string errorDescr, string[] additionalInfo, string Username)
{
string errorInfo = @"errors";
string userInfo = @"users";
bool MEFCF = false;
bool SEFCF = false;
bool MEFWF = false;
bool SEFWF = false;
try
{
if (File.Exists(userInfo + "\\" + Username + ".txt") == true)
{
File.Copy(Application.ExecutablePath.Remove(Application.ExecutablePath.LastIndexOf('\\')) + "\\users\\" + Username + ".txt", Application.ExecutablePath.Remove(Application.ExecutablePath.LastIndexOf('\\')) + "\\users\\" + "BACKUP (" + Username + ")" + Convert.ToString(System.DateTime.Now.Hour) + ":" + Convert.ToString(System.DateTime.Now.Minute) + ":" + Convert.ToString(System.DateTime.Now.Second) + " " + Convert.ToString(System.DateTime.Now.Day) + "." + Convert.ToString(System.DateTime.Now.Month) + "." + Convert.ToString(System.DateTime.Now.Year));
}
}
catch
{
reportSituation += "-UBF";
}
string errorCode = Convert.ToString(System.DateTime.Now.Hour).PadLeft(2, '0');
errorCode += Convert.ToString(System.DateTime.Now.Minute).PadLeft(2, '0');
errorCode += Convert.ToString(System.DateTime.Now.Second).PadLeft(2, '0');
errorCode += Convert.ToString(System.DateTime.Now.Day).PadLeft(2, '0');
errorCode += Convert.ToString(System.DateTime.Now.Month).PadLeft(2, '0');
errorCode += Convert.ToString(System.DateTime.Now.Year) + "-" + errorDescr;

try
{
if (File.Exists(errorInfo + "\\errorFile.txt") == false)
{
File.Create(errorInfo + "\\errorFile.txt");
StreamWriter NewFile = new StreamWriter(errorInfo + "\\errorFile.txt");
NewFile.WriteLine("File created on: " + System.DateTime.Now.ToString());
NewFile.WriteLine("");
NewFile.Flush();
NewFile.Close();
}
}
catch
{
MEFCF = true;
}
try
{
/*if (File.Exists(errorInfo + "\\" + errorCode + ".txt") == false)
{
File.Create(errorInfo + "\\" + errorCode + ".txt");
StreamWriter NewFile = new StreamWriter(errorInfo + "\\" + errorCode + ".txt");
NewFile.WriteLine("File created on: " + System.DateTime.Now.ToString());
NewFile.WriteLine("");
NewFile.Flush();
NewFile.Close();
}*/
}
catch
{
SEFCF = true;
}

try
{
string mainErrorFile = "Failed to read file!";
try
{
StreamReader ErRe1 = new StreamReader(errorInfo + "\\errorFile.txt");
mainErrorFile = ErRe1.ReadToEnd();
ErRe1.Dispose();
ErRe1.Close();
}
catch { }

//Thread.Sleep(250);
StreamWriter ErWr1 = new StreamWriter(errorInfo + "\\errorFile.txt");
try
{
ErWr1.WriteLine(mainErrorFile);
}
catch { }
ErWr1.WriteLine("------------------------------------------------------------" + errorCode + "------------------------------------------------------------");
ErWr1.WriteLine("Exception: " + E.ToString());
ErWr1.WriteLine("------------------------------------------------------------");
try
{
ErWr1.WriteLine("Inner: " + E.InnerException.ToString());
}
catch
{
ErWr1.WriteLine("Inner: Inner exception not available");
}
try
{
ErWr1.WriteLine("Inner: " + E.InnerException.InnerException.ToString());
}
catch
{
ErWr1.WriteLine("Inner: Inner exception(x2) not available");
}
try
{
ErWr1.WriteLine("Inner: " + E.InnerException.InnerException.InnerException.ToString());
}
catch
{
ErWr1.WriteLine("Inner: Inner exception(x3) not available");
}
ErWr1.WriteLine("------------------------------------------------------------");
ErWr1.WriteLine("");
ErWr1.WriteLine("Report: " + reportSituation);
ErWr1.WriteLine("");
ErWr1.WriteLine("------------------------------------------------------------");
for (int tempint1 = 0; tempint1 < 15; tempint1++)
{
try
{
ErWr1.WriteLine("Add. Info(" + tempint1 + "): " + additionalInfo[tempint1]);
}
catch
{
ErWr1.WriteLine("Add. Info(" + tempint1 + "): Additional info n." + tempint1 + " does not contain any information.");
}
}
ErWr1.WriteLine("------------------------------------------------------------");
ErWr1.WriteLine("");
ErWr1.WriteLine("User: " + Username);
ErWr1.WriteLine("");
ErWr1.WriteLine("------------------------------------------------------------");
ErWr1.WriteLine("");
ErWr1.WriteLine("");
ErWr1.Flush();
ErWr1.Close();
}
catch
{
MEFWF = true;
}

try
{
/*string subErrorFile = "Failed to read file!";
try
{
StreamReader ErRe2 = new StreamReader(errorInfo + "\\" + errorCode + ".txt");
subErrorFile = ErRe2.ReadToEnd();
ErRe2.Dispose();
ErRe2.Close();
}
catch { }

///Thread.Sleep(250);
StreamWriter ErWr2 = new StreamWriter(errorInfo + "\\" + errorCode + ".txt");
try
{
ErWr2.WriteLine(subErrorFile);
}
catch { }
ErWr2.WriteLine("------------------------------------------------------------" + errorCode + "------------------------------------------------------------");
ErWr2.WriteLine("Exception: " + E.ToString());
ErWr2.WriteLine("------------------------------------------------------------");
try
{
ErWr2.WriteLine("Inner: " + E.InnerException.ToString());
}
catch
{
ErWr2.WriteLine("Inner: Inner exception not available");
}
try
{
ErWr2.WriteLine("Inner: " + E.InnerException.InnerException.ToString());
}
catch
{
ErWr2.WriteLine("Inner: Inner exception(x2) not available");
}
try
{
ErWr2.WriteLine("Inner: " + E.InnerException.InnerException.InnerException.ToString());
}
catch
{
ErWr2.WriteLine("Inner: Inner exception(x3) not available");
}
ErWr2.WriteLine("");
ErWr2.WriteLine("------------------------------------------------------------");
ErWr2.WriteLine("");
ErWr2.WriteLine("Report: " + reportSituation);
ErWr2.WriteLine("");
ErWr2.WriteLine("------------------------------------------------------------");
ErWr2.WriteLine("");
ErWr2.WriteLine("User: " + Username);
ErWr2.WriteLine("");
ErWr2.WriteLine("------------------------------------------------------------");
ErWr2.WriteLine("");
ErWr2.WriteLine("");
//ErWr.Flush();
ErWr2.Close();*/
}
catch
{
SEFWF = true;
}

if (MEFCF == true && SEFCF == true)
{
reportSituation += "-EFCF";
}
else if (MEFCF == true && SEFCF == false)
{
reportSituation += "-MEFCF";
}
else if (MEFCF == false && SEFCF == true)
{
reportSituation += "-SEFCF";
}

if (MEFWF == true && SEFWF == true)
{
reportSituation += "-EFWF";
}
else if (MEFWF == true && SEFWF == false)
{
reportSituation += "-MEFWF";
}
else if (MEFWF == false && SEFWF == true)
{
reportSituation += "-SEFWF";
}

Form1.InSim.Send_MST_Message("/msg ^3» ^1An error has occured. Please report to forums.");
Form1.InSim.Send_MST_Message("/msg ^3» ^1Report: ^8" + reportSituation);
Form1.InSim.Send_MST_Message("/msg ^3» ^1E. code: ^8" + errorCode);
Form1.InSim.Send_MST_Message("/msg ^3» ^1Occured with user: ^3" + Username);
}

Example of how to call this function:
Note: This is code from my insim, catching errors in the MSO thread.
try {}
catch (Exception EX)
{
string[] addInf = ("MSO.Msg: " + MSO.Msg + "|" + "MSO.UserType: " + MSO.UserType).Split('|');
additionalFunction.buildErrorReportOnCrash(EX, "MSO-TWR-UNN", "MSO(UNN)", addInf, Connections[GetConnIdx(MSO.UCID)].Username);
}

Tip: Use shortened codes which you have written somewhere to be recognised.
In this case the error code says:
MSO - MSO thread
TWR - Too Wide Range (of the exception)
UNN - Unknown (can't guess what exactly went wrong since the whole MSO thread is "surrounded" with try {}
MSO(UNN) will be added after the error code, so there is bigger chance if 2 errors occur in 1 second, to be recognised.
----------------------------------

Language: C#
Works with: Will work with every program.
Source used: LFS_External from the forums.
Result: Gets absolute(or more like - lowest) distance between 2 points in given _closed_ figure.
Level: Probably easy..
Number functions in the code strip: 1
State: Tested and works perfectly.
Requirements: No requirements
What I use this for mainly: To detect the distance between 2 players in nodes.
I call it like: the_class_its_located_in.absDifference(node of player1, node of player2, total count of nodes on track)

static public float absDifference(int numb1, int numb2, int bound)
{
int numH = numb1;
int numL = numb2;
if (numb1 > numb2)
{
numH = numb1;
numL = numb2;
}
else if (numb1 < numb2)
{
numH = numb2;
numL = numb1;
}
else if (numb1 == numb2)
{
return 0;
}

if (numL < bound && numH < bound)
{
return ((bound - numH) + numL);
}
else
{
return 3333333333;
//Returns error if something is messed up.
}
}

----------------------------------

Language: C#
Works with: Will work with every program.
Source used: LFS_External from the forums.
Result: Gets the time between the date you specify and today. Can be made to work with 2 different dates pretty easy too.
Level: Probably easy..
Number functions in the code strip: 1
State: Tested and works perfectly.
Requirements: No requirements

static public TimeSpan timeSinceThen(DateTime sentTime)
{
DateTime nowTime = System.DateTime.Now;
TimeSpan pastTime = nowTime.Subtract(sentTime);
return pastTime;
}

----------------------------------

Now something I need help with. I guess that the solution is pretty easy, but I didn't have much time for this function anyway...

Language: C#
Works with: Will work with every program.
Source used: LFS_External from the forums.
Result: Get days in the month.
Level: Probably easy..
Number functions in the code strip: 1
State: Not working - problems with February.

static public int daysInMonth(int Month, int Year)
{
switch (Month)
{
case 1: return 31;
case 2:
int visYearF = Year / 4;
float visYearI = Year / 4;
if (float.Parse(visYearF.ToString()) == visYearI)
{
return 29;
}
else
{
return 28;
}
case 3: return 31;
case 4: return 30;
case 5: return 31;
case 6: return 30;
case 7: return 31;
case 8: return 31;
case 9: return 30;
case 10: return 31;
case 11: return 30;
case 12: return 31;
}
return 33;
}

----------------------------------

Right after I finished writing the post and was about to click the Submit button - it reminded me of buttons! So there is another thing that could make your code more readable:

Language: C#
Works with: Will work with LFS_External from the forums.
Result: Display a sequence of buttons, and delete sequences.
Level: Probably easy..
Number functions in the code strip: 2
State: Working better than expected.
Note: Don't display too many buttons at once, or you may make your guest lose connection to the server..

static public void createSequence(string sequenceName, byte UCID, byte RequestID, clsConnection C)
{
switch (sequenceName.ToLower())
{
#region Test
case "test":
Form1.InSim.Send_BTN_CreateButton("test!", LFS_External.InSim.Flags.ButtonStyles.ISB_CLICK | LFS_External.InSim.Flags.ButtonStyles.ISB_LIGHT, 4, 4, 50, 50, 105, UCID, 40, false);
Form1.InSim.Send_BTN_CreateButton("", LFS_External.InSim.Flags.ButtonStyles.ISB_DARK, 100, 100, 50, 50, 101, UCID, 40, false);
Form1.InSim.Send_BTN_CreateButton("", LFS_External.InSim.Flags.ButtonStyles.ISB_LIGHT, 98, 24, 51, 125, 102, UCID, 40, false);
Form1.InSim.Send_BTN_CreateButton("test!", LFS_External.InSim.Flags.ButtonStyles.ISB_C2, 4, 76, 50, 50, 103, UCID, 40, false);
Form1.InSim.Send_BTN_CreateButton("test!", "Enter message", LFS_External.InSim.Flags.ButtonStyles.ISB_CLICK | LFS_External.InSim.Flags.ButtonStyles.ISB_LIGHT, 5, 7, 144, 118, 104, 104, UCID, 40, false);
break;
#endregion
}
}

static public void deleteSequence(string sequenceName, byte UCID)
{
byte startID = 0;
byte endID = 0;
switch (sequenceName.ToLower())
{
#region Test
case "test":
startID = 100;
endID = 155;
break;
#endregion
}

#region Proccess of deletion
if (startID < endID && endID > 1)
{
byte tempByte1 = startID;
while (tempByte1 <= endID)
{
Form1.InSim.Send_BFN_DeleteButton(LFS_External.Enums.BtnFunc.BFN_DEL_BTN, tempByte1, UCID);
tempByte1 += 1;
}
}
#endregion
}

There is a UCID - Used each time you display a button.
There is also clsConnection option - If you want to display buttons containing information about one player - with the UCID of another. UCID and clsConnection, of course - CAN be of the same person.

BTW: I never got to know what the RequestID of a button is used for... Proves what a lame coder I am lol! But people learn as long as they live, they say.

----------------------------------

That was some hot code from my program's kitchen. Now let us see something from yours.

::: I know that my post is a little bit confusing and I'm sorry. It's because I didn't take my time to structure the post and make the explanations sound more clear. So once again: I'm sorry if you don't understand something. You can always ask tho.

All the examples I gave you are simple and all you need is basic knowledge of coding to make them work.
Excellent post Broken,

I`m sure it will be related to for new coders etc etc..

Mick
Quote from broken :Now something I need help with. I guess that the solution is pretty easy, but I didn't have much time for this function anyway...

Language: C#
Works with: Will work with every program.
Source used: LFS_External from the forums.
Result: Get days in the month.
Level: Probably easy..
Number functions in the code strip: 1
State: Not working - problems with February.

This is built into .NET already, part of DateTime

[COLOR=blue]public[/COLOR] [COLOR=blue]static[/COLOR] [COLOR=blue]int[/COLOR] DaysInMonth(
[COLOR=blue]int[/COLOR] year,
[COLOR=blue]int[/COLOR] month
)


int DaysInFeb2009 = DateTime.DaysInMonth(2009, 2); // Returns 28, as 2009 is not a leap year
int DaysInFeb2009 = DateTime.DaysInMonth(2008, 2); // Returns 29, as 2008 was a leap year

Quote from broken :BTW: I never got to know what the RequestID of a button is used for... Proves what a lame coder I am lol! But people learn as long as they live, they say.

RequestID is sent in IS_BTC. It's usually used if you have two buttons with the same ClickID that are sent in different "areas" (so they don't show at the same time, because they can't), so that when the BTC is sent, you can see which "area" it was sent from. That's a terrible explaination I know, but I'm terrible at explaining things in general

For example, if you have a menu system, with different tabs. Each tab has 5 buttons, each with the same ClickID. But they all have different RequestIDs. So when one button is clicked, you can look at the RequestID in IS_BTC and see which tab is was sent from, to decide what to do with it. Any better?

Nice post though, a lot of useful information Might be worth adding it to the wiki, in a tutorial section, with a bit of code cleanup (no offence intended, but some areas are a bit messy to understand)?
Quote from dougie-lampkin :This is built into .NET already, part of DateTime

[COLOR=blue]public[/COLOR] [COLOR=blue]static[/COLOR] [COLOR=blue]int[/COLOR] DaysInMonth(
[COLOR=blue]int[/COLOR] year,
[COLOR=blue]int[/COLOR] month
)


int DaysInFeb2009 = DateTime.DaysInMonth(2009, 2); // Returns 28, as 2009 is not a leap year
int DaysInFeb2009 = DateTime.DaysInMonth(2008, 2); // Returns 29, as 2008 was a leap year

RequestID is sent in IS_BTC. It's usually used if you have two buttons with the same ClickID that are sent in different "areas" (so they don't show at the same time, because they can't), so that when the BTC is sent, you can see which "area" it was sent from. That's a terrible explaination I know, but I'm terrible at explaining things in general

For example, if you have a menu system, with different tabs. Each tab has 5 buttons, each with the same ClickID. But they all have different RequestIDs. So when one button is clicked, you can look at the RequestID in IS_BTC and see which tab is was sent from, to decide what to do with it. Any better?

Nice post though, a lot of useful information Might be worth adding it to the wiki, in a tutorial section, with a bit of code cleanup (no offence intended, but some areas are a bit messy to understand)?

The RequestID explanation was just perfect. Thanks!
-About february... I don't want to specify each year separately, but to make it work smart. :P
-EDIT: Yeah, see, I'm stupid.... I understood what you mean now tho.
And as for the messy code and explanations: I know. I didn't do much cleanup myself. Lazy a** I am. Wrote so much stuff and didn't spend a little time on cleaning it up.
Quote from broken :The RequestID explanation was just perfect. Thanks!

Glad someone understands it, it makes 0 sense to me now

Quote from broken :-About february... I don't want to specify each year separately, but to make it work smart. :P

Just use "DateTime.Now.Year()" as the year part of it, it has it's own special algorithm to work out if it was a leap year If you're interested, this is exactly what it does:

public static int DaysInMonth(int year, int month)
{
if ((month < 1) || (month > 12))
{
throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
}
int[] numArray = IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;
return (numArray[month] - numArray[month - 1]);
}

Quote from broken :And as for the messy code and explanations: I know. I didn't do much cleanup myself. Lazy a** I am. Wrote so much stuff and didn't spend a little time on cleaning it up.

Heh, I know exactly what you mean. Just look at my spaghetti code in the open source cruise app :rolleyes:
Quote from dougie-lampkin :Glad someone understands it, it makes 0 sense to me now

From the 1st explanation I got an overall idea of what it does. Which after a little thinking I was probably going to understand.
But the 2nd explanation just saved this hard part - thinking.

Quote from dougie-lampkin :Just use "DateTime.Now.Year()" as the year part of it, it has it's own special algorithm to work out if it was a leap year If you're interested, this is exactly what it does:

public static int DaysInMonth(int year, int month)
{
if ((month < 1) || (month > 12))
{
throw new ArgumentOutOfRangeException("month", Environment.GetResourceString("ArgumentOutOfRange_Month"));
}
int[] numArray = IsLeapYear(year) ? DaysToMonth366 : DaysToMonth365;
return (numArray[month] - numArray[month - 1]);
}


I didn't really get it but as long as it works, I wouldn't care about the code. That's how I do.

Quote from dougie-lampkin :Heh, I know exactly what you mean. Just look at my spaghetti code in the open source cruise app :rolleyes:

I actually learned how to code on that application. Believe me it can be more stuffed up than you can imagine, so it's just perfect the way it is now.... If you could just see it when I started modifying it... It was suuuch a big mess. Right now I'm recoding almost everything. The damage was baad.

And you said it yourself: If people don't have knowledge in coding they shall not try it out. Actually I take that more like "If you don't have any basic knowledge in C#, then don't whine if you stuff it up.", cause imo that's what they should do(or in this case - what they don't have to do).

Oh and on topic: I remember reading somewhere: (about C#) Have only one try and catch code in a single thread. I really find sense in this, as the more try and catch codes you do when they are "in each other"(if you know what I mean) the less information you'll get, I think.. This will be because when you do try {} you actually prevent the previous try {} code from detecting this error. Now wasn't that confusing or what?
#7 - amp88
Interesting idea. Some of the examples already posted (and hopefully will be posted by others) could also be included in the LFS Programming section of LFS Manual.

Here's my contribution. All examples are written in Java.

1. Storing InSim packets in a buffer and handling periodically

When you connect your InSim application to LFS it's going to start receiving responses from LFS. You can either handle those responses immediately (which is almost always a bad idea and can cause problems!) or you can store those responses in a buffer then handle all the responses in the buffer periodically. The method using a buffer is really the way to go. It allows you better control over your application and it also safeguards you from synchronisation issues which could come into play if you handle packets immediately when you receive them. So, for the buffer approach you need to consider a few things:
  • What are you going to use as a buffer: I use an ArrayList in my applications (a List implementation that's backed by an array) because it's quick, it's flexible (it expands to hold however many objects you require) and it's simple to use.
  • What mechanism are you going to use to determine when to handle the packets: As with most things in programming there are at least a couple of ways to do things. What I've started doing is using a Timer which will fire periodically to trigger handling the responses. With the Timer in Java you specify a delay period (see next point) and an action to perform when the delay period is is completed. The action to perform is calling the method to handle the responses that you've collected during the delay period.
  • How often are you going to handle the responses from LFS: This will depend on a number of things including what your application does (if you're doing something like an OutGauge client then you'll want a reasonably short delay so you can keep the display updated but if you're just writing a simple race tracking application you could cope with handling packets quite infrequently) and how long it takes to handle the packets (if your delay is 10ms but the code to handle the responses takes, on average, 15ms to run then obviously you need to think about that).
Here's a simple example that shows creating the buffer to hold the packets temporarily, creating the Timer to empty the buffer and handle the responses and actually handling each of the responses in the buffer. In the JInSim implementation whatever class you use as your implementation (implementing the InSimListener interface) must inherit the packetReceived method. This method is called whenever a packet is received from LFS and JInSim conveniently bundles it into an object for the user. The eagle eyed may spot that I'm effectively using a PriorityQueue in the following example (I sort the packets in the list by priority before handling them). This is simply because some packets are more important than others (e.g. a new player joining the server is normally more important than a flag response letting you know someone caused a yellow flag). I'll put the code for the method that returns the priority for packets below (the line "Helper.getInSimPacketPriority(o1.getClass())").

package insimtesting;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;

import javax.swing.Timer;

import net.sf.jinsim.response.InSimListener;
import net.sf.jinsim.response.InSimResponse;

public class InSimTesterBuffer implements InSimListener {
// A global buffer to hold responses until they can be handled
private ArrayList<InSimResponse> globalBuffer = new ArrayList<InSimResponse>(50);

// A Timer which will fire periodically to handle the response in the buffer
private Timer bufferTimer;

public InSimTesterBuffer() {
bufferTimer = new Timer(500, new ActionListener() {
public void actionPerformed(ActionEvent e) {
handleResponses();
}
});

bufferTimer.start();
}

public synchronized void packetReceived(InSimResponse resp) {
// When a new packet is received add it to the global buffer and attach the time at which it was received.
globalBuffer.add(resp);
}

// Handle the responses that are currently in the global buffer. This is called from the buffer timer which fires periodically
private synchronized void handleResponses() {
if(globalBuffer.size() > 0) {
InSimResponse[] temporaryBuffer = globalBuffer.toArray(new InSimResponse[globalBuffer.size()]);

globalBuffer.clear();

// Sort the packets by priority
Arrays.sort(temporaryBuffer, new Comparator<InSimResponse>() {
public int compare(InSimResponse o1, InSimResponse o2) {
Integer o1Priority = Helper.getInSimPacketPriority(o1.getClass());
Integer o2Priority = Helper.getInSimPacketPriority(o2.getClass());

if(o1Priority == null && o2Priority == null) {
return 0;
} else if(o1Priority == null) {
return 1;
} else if(o2Priority == null) {
return -1;
} else {
return o1Priority.compareTo(o2Priority);
}
}
});

// Handle each of the responses in the temporary buffer
for(InSimResponse responseAndTime : temporaryBuffer) {
handleResponse(responseAndTime);
}
}
}

// Handle a given response
private synchronized void handleResponse(InSimResponse response) {
switch(response.getPacketType()) {
case NEW_CONNECTION: {
// New connection joined the server
break;
} case NEW_PLAYER: {
// New player joined the session
break;
} case CONNECTION_LEFT: {
// Connection left the server
break;
} case PLAYER_LEAVE: {
// Player left the session
break;
} case LAP: {
// Laptime received
break;
} case SPLIT: {
// Split time received
break;
} default: {
System.out.println(response.getClass().getSimpleName());
}
}
}
}

2. Handling connection and player information

I like to use a Map to store connection and player information. For anyone who's not familiar with the concept of a Map abstract data type here's a bit of background. A map stores information in the form of a Key/Value pair. What that really means is when you add data to the map you store the actual data (the value) along with a small piece of information that's unique to that value (the key). In the case of connections the value would be the connection information (username, nickname, isAdmin etc) and the key would be the unique connectionID. Then when you want to retrieve the connection information from the Map all you do is give it the key and it will return the connection information associated with that connectionID. There are a number of reasons to use a Map to hold connection/player information, including the fact that Maps are generally pretty quick (if you store information in a list then on average you'll need to look at half the list before you find the information you're actually looking for) and they make the code cleaner than looking through an array.

Here's some Java code showing how to create the connection information Map, how to add a connection to it, how to remove a connection from it and how to retrieve the connection information when necessary. This code won't compile because you don't have the ConnectionInfo class, but it's only an example of what a map is and how to use it. It's important to note that in the getConnectionForID function it's possible to return a null value. This would happen if there was no connection currently held for the given connectionID. Callers to this method would need to keep that in mind otherwise you could have a NullPointerException.

public class ConnectionMapExample {
// A map containing all of the connections currently on the LFS server, key is the unique connectionID
private HashMap<Byte, ConnectionInfo> currentConnections = new HashMap<Byte, ConnectionInfo>(50);

// Add the given connection information to the current connections map
public void addConnection(ConnectionInfo newConnection) {
currentConnections.put(newConnection.getConnectionId(), newConnection);
}

// Remove the given connectionID from the current connections map
public void removeConnection(byte connectionID) {
if(currentConnections.containsKey(connectionID)) {
currentConnections.remove(connectionID);
}
}

// Get the connection information for the connection attached to the given connectionID
public ConnectionInfo getConnectionForID(byte connectionID) {
if(currentConnections.containsKey(connectionID)) {
return currentConnections.get(connectionID);
} else {
return null;
}
}
}

3. Static helper functions

Formatting time strings

When LFS sends data about times (split times, laptimes, pit stop times etc) it sends the time in the form of a number of milliseconds. Obviously it's no good to print out to the user that the split time is 13809 milliseconds. The following method returns a string that displays the laptime in a readable form. The JInSim library already includes a method similar to this and I wouldn't be surprised if all the popular libraries did too, but if not here's something you could use.

public static String getTimeString(int time) {
StringBuilder timeString = new StringBuilder("");

int hours = time/3600000;
int minutes = (time%3600000) / 60000;
int seconds = (time%60000) / 1000;
int thousandths = time%1000;

if(hours > 0) {
timeString.append(hours+":");
}

if(minutes > 0) {
timeString.append(minutes+":");
}

if(seconds < 10) {
if(minutes > 0) {
timeString.append("0"+seconds+".");
} else {
timeString.append(seconds+".");
}
} else {
timeString.append(seconds+".");
}

if(thousandths < 10) {
if(thousandths == 0) {
timeString.append("00");
} else {
timeString.append("00"+thousandths);
}
} else if(thousandths < 100) {
if(thousandths % 10 == 0) {
timeString.append("0"+(thousandths/10));
} else {
timeString.append("0"+thousandths);
}
} else {
if(thousandths % 10 == 0) {
timeString.append(thousandths/10);
} else {
timeString.append(thousandths);
}
}

return timeString.toString();
}

Getting InSim packet priorities

As I mentioned in example 1 (storing and handling responses periodically) I use a priority system where I handle certain packets before others. The way I do this is by storing an integer for each of the classes I care about. The lower the integer the more important the packet is. The classes that you care about will obviously depend on what your application actually does, but here's what I'm using for the qualifying ticker application:

inSimPacketPriorities.put(ConnectionCloseResponse.class, 0);
inSimPacketPriorities.put(NewConnectionResponse.class, 1);
inSimPacketPriorities.put(ConnectionLeaveResponse.class, 1);
inSimPacketPriorities.put(NewPlayerResponse.class, 2);
inSimPacketPriorities.put(PlayerLeavingResponse.class, 2);
inSimPacketPriorities.put(RaceStartResponse.class, 3);
inSimPacketPriorities.put(CameraPositionResponse.class, 4);
inSimPacketPriorities.put(StateResponse.class, 5);
inSimPacketPriorities.put(NodeLapInfoResponse.class, 6);
inSimPacketPriorities.put(MultiCarInfoResponse.class, 7);

So a new connection is more important than a new player. A race start response is more important than a state response etc. Any classes which don't have a specific priority will just be handled at the end of those which do have a specific priority. I'm using a Map implementation to store these priorities again, which is the same idea as with the player/connection information. The actual method for getting the priority just returns the value from the map for the given class:

public static Integer getInSimPacketPriority(Class<?> inSimClass) {
return inSimPacketPriorities.get(inSimClass);
}

Note that the return type for this method is Integer not the primitive int. The null value will be returned from this method for packets where I don't actually care what they are.

Removing formatting codes from player names

The player names in LFS contain formatting codes (e.g. colour codes). The following is a method to strip out the formatting codes and just return the plain string:

public static String getPlainPlayerName(String rawPlayerName) {
if(playerNameCache.containsKey(rawPlayerName)) {
return playerNameCache.get(rawPlayerName);
} else {
// If the cache is too big trim it back down
if(playerNameCache.size() > 100) {
playerNameCache.clear();
}

String nameToReturn = rawPlayerName;

if(nameToReturn != null) {
// Look out for the vertical bar (pipe) before colour codes. ^v is the pipe
if(nameToReturn.contains("^v")) {
nameToReturn = nameToReturn.replaceAll("\\^v", "|");
}

while(nameToReturn.contains("^")) {
nameToReturn = nameToReturn.substring(0, nameToReturn.indexOf("^"))+
nameToReturn.substring(nameToReturn.indexOf("^")+2);
}

playerNameCache.put(rawPlayerName, nameToReturn);

return nameToReturn;
} else {
return "";
}
}
}

Getting a nice multicoloured label for a player name

If you're presenting a player name to the user in an application it can be quite nice to keep in LFS colour formatting codes. If you take a look at the attached screenshot in this post, for example, you can see that the player names on the left hand side have the same colour format as the names do in LFS. The following helper method takes the player name (including formatting codes!) and returns a horizontal Box with JLabels that make up the player name in pretty colours:

public static Box getColourfulPlayerNameBox(String rawPlayerName) {
if(playerNameBoxCache.containsKey(rawPlayerName)) {
return playerNameBoxCache.get(rawPlayerName);
} else {
// Clear out the cache if it's too big
if(playerNameBoxCache.size() > 75) {
playerNameBoxCache.clear();
}

Box playerNameBox = Box.createHorizontalBox();

// Split the raw text into parts based on colour
String[] parts = rawPlayerName.split("\\^");

for(String part : parts) {
Color thisPartForegroundColour = QualiTickerPrefs.LFSTEXTDEFAULTCOLOR;

if(part.length() > 0 && Character.isDigit(part.charAt(0))) {
String colourCode = "^"+part.charAt(0);
part = part.substring(1);

if(colourCode.equals(Colors.BLACK)) {
thisPartForegroundColour = QualiTickerPrefs.LFSTEXTBLACKCOLOR;
} else if(colourCode.equals(Colors.WHITE)) {
thisPartForegroundColour = QualiTickerPrefs.LFSTEXTWHITECOLOR;
} else if(colourCode.equals(Colors.BLUE)) {
thisPartForegroundColour = QualiTickerPrefs.LFSTEXTBLUECOLOR;
} else if(colourCode.equals(Colors.GREEN)) {
thisPartForegroundColour = QualiTickerPrefs.LFSTEXTGREENCOLOR;
} else if(colourCode.equals(Colors.LIGHT_BLUE)) {
thisPartForegroundColour = QualiTickerPrefs.LFSTEXTLIGHTBLUECOLOR;
} else if(colourCode.equals(Colors.RED)) {
thisPartForegroundColour = QualiTickerPrefs.LFSTEXTREDCOLOR;
} else if(colourCode.equals(Colors.VIOLET)) {
thisPartForegroundColour = QualiTickerPrefs.LFSTEXTVIOLETCOLOR;
} else if(colourCode.equals(Colors.YELLOW)) {
thisPartForegroundColour = QualiTickerPrefs.LFSTEXTYELLOWCOLOR;
} else if(colourCode.equals("^9")) {
// The ^9 string is used to indicate a change back to
// the default colour from another colour
}
} else if(part.length() > 0 && part.charAt(0) == 'v') {
// The string ^v is used to signify the vertical bar (pipe) in the name
part = part.replaceAll("v", "|");

// If there's a label before use the same colour as that label
if(playerNameBox.getComponentCount() > 0) {
JLabel lastLabel = (JLabel) playerNameBox.getComponent(playerNameBox.getComponentCount()-1);

thisPartForegroundColour = lastLabel.getForeground();
}
}

JLabel label = new JLabel(part);
label.setForeground(thisPartForegroundColour);
label.setOpaque(true);
label.setBackground(LFSQualifyingTickerGUI.BACKGROUND_COLOUR);

playerNameBox.add(label);
}

playerNameBox.add(Box.createHorizontalGlue());

// Add to cache
playerNameBoxCache.put(rawPlayerName, playerNameBox);

return playerNameBox;
}
}

Converting raw speed to 'normal' units

The speed values in LFS (e.g. in CompCar/MCI packets) is held in a strange unit. A value of 32768 is equal to 100 m/s. The following methods take the strange unit and a number of decimals to round the answer to and return the speed in more familiar units:

To MPH:

public static double getSpeedInMilesPerHour(short rawSpeed, int decimalPlaces) {
// 32768 = 100 metres per second
BigDecimal mphBD = new BigDecimal((rawSpeed / 32768.0) * 223.693629);

return mphBD.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP).doubleValue();
}

To KPH:

public static double getSpeedInKilometresPerHour(short rawSpeed, int decimalPlaces) {
// 32768 = 100 metres per second
BigDecimal kphBD = new BigDecimal((rawSpeed / 32768.0) * 360);

return kphBD.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP).doubleValue();
}

Quote from broken :Language: C#
Works with: Will work with every program.
Source used: LFS_External from the forums.
Result: Removes all lfs color codes in a string.
Level: TOO easy.
Number functions in the code strip: 1
State: Tested and works perfectly.

static public string removeColorCodes(string coloredText)
{
coloredText = coloredText.Replace("^1", "").Replace("^2", "").Replace("^3", "").Replace("^4", "").Replace("^5", "").Replace("^6", "").Replace("^7", "").Replace("^8", "").Replace("^9", "");
return coloredText;
}


I use a short regular expression for this, which I think is a little neater.

string StripColours(string str)
{
return new Regex(@"\^[0-9]").Replace(str, string.Empty);
}

Quote from broken :Language: C#
Works with: Will work with every program.
Source used: LFS_External from the forums.
Result: Reports errors to external text file.
Level: Not too easy.
Number functions in the code strip: 1
State: Tested and works perfectly.
Requirements: needs file "errorFile.txt" in folder "\errors\" which has to be located on the same location as your project. Also needs a string[] with additional information, which, of course can be null.
Request: It has a disabled code, which I couldn't get to work good, so if you have any solutions for it, you can tell me.

I use a different method, which I place in the Program.cs file. This is some "fire-and-forget" code which will catch any unhanded exceptions from your program and write the contents to a text file. With this method you don't need to clutter you code with log statements, as it just catches everything you don't deal with yourself automatically.

using System;
using System.Windows.Forms;
using System.IO;
using System.Threading;
using System.Diagnostics;
using System.Reflection;

namespace MyApplication
{
static class Program
{
static readonly string LogPath = Path.Combine(Application.StartupPath, "Log.txt");

[STAThread]
static void Main(string[] args)
{
#if !DEBUG
AppDomain.CurrentDomain.UnhandledException +=
new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
Application.ThreadException +=
new ThreadExceptionEventHandler(Application_ThreadException);
#endif

using (MainForm mainForm = new MainForm(args))
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(mainForm);
}
}

static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
ManageUnhandledException(e.Exception);
}

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
ManageUnhandledException((Exception)e.ExceptionObject);
}

static void ManageUnhandledException(Exception e)
{
try
{
using (StreamWriter writer = new StreamWriter(LogPath))
{
writer.WriteLine("Name: " + Assembly.GetExecutingAssembly().GetName().Name);
writer.WriteLine("Version: " + Assembly.GetExecutingAssembly().GetName().Version.ToString());
writer.WriteLine("Date: " + DateTime.Now.ToString());
writer.WriteLine("OS: " + Environment.OSVersion.VersionString);
writer.WriteLine("Culture: " + Thread.CurrentThread.CurrentUICulture);
writer.WriteLine("Message: " + e.Message);
writer.WriteLine("Source: " + e.Source);
writer.WriteLine("Target: " + e.TargetSite);
writer.WriteLine("Stack Trace: " + e.StackTrace);
writer.WriteLine("Inner Exception: " + e.InnerException);
}

MessageBox.Show("Debug information has been written to a log file", "Unhandled Exception");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Critical Error!");
}
finally
{
Process.GetCurrentProcess().Kill();
}
}
}
}

Some more for C# .NET.

Convert a unix timestamp to a .NET DateTime.

DateTime UnixTimeToDateTime(double timestamp)
{
return new DateTime(1970, 1, 1).AddSeconds(timestamp).ToLocalTime();
}

Convert milliseconds to a formatted lap time string.

string TimeString(long ms)
{
int h = (int)Math.Floor((double)ms / 3600000);
int m = (int)Math.Floor((double)ms / 1000 / 60) % 60;
int s = (int)Math.Floor((double)ms / 1000) % 60;
int t = (int)ms % 1000;

if (h > 0)
{
return string.Format("{0}:{1:00}:{2:00}.{3:000}", h, m, s, t);
}

return string.Format("{0}:{1:00}.{2:000}", m, s, t);
}

Convert milliseconds into a .NET TimeSpan.

TimeSpan MillisecondsToTimeSpan(int milliseconds)
{
return TimeSpan.FromMilliseconds(milliseconds);
}

Convert LFS speed (MCI etc..) to Mps, Mph or Kph.

double SpeedToMps(long speed)
{
return speed / 327.68;
}

double SpeedToMph(long speed)
{
return speed / 146.486;
}

double SpeedToKph(long speed)
{
return speed / 91.02;
}

Get the distance between two points.

public struct Vector3
{
public int X { get; set; }
public int Y { get; set; }
public int Z { get; set; }

public Vector3(int x, int y, int z)
{
X = x;
Y = y;
Z = z;
}
}

double Distance(Vector3 a, Vector3 b)
{
return Math.Sqrt(((b.X - a.X) * (b.X - a.X)) +
((b.Y - a.Y) * (b.Y - a.Y)) +
((b.Z - a.Z) * (b.Z - a.Z)));
}

Quote from amp88 :The eagle eyed may spot that I'm effectively using a PriorityQueue in the following example (I sort the packets in the list by priority before handling them). This is simply because some packets are more important than others (e.g. a new player joining the server is normally more important than a flag response letting you know someone caused a yellow flag). I'll put the code for the method that returns the priority for packets below (the line "Helper.getInSimPacketPriority(o1.getClass())").

I don't get the idea of changing the priority of the packets. They are sent in the order which the events occur in the game. If you switch that around you could get into weird situations where you are processing a yellow flag for a driver who has already spectated. I can imagine there may be some situations where this might be desirable, but if I were building an InSim library, for instance, then reordering the packets sounds like a pretty bad idea to me.

Also in terms of the TCP buffer, if you are handling the packets on a separate thread, or asynchronously, I'm not sure I get the idea of using an internal timer. If I wanted to do socket blocking operations on the main process thread, then yes I would (have to) use a timer, but otherwise I would just want to get the packets out there as quickly as possible. Surely by using your own timing you are A. recreating functionality already handled by the socket and B. adding unnecessary latency.

Anyway, sorry, I'm not trying to be disparaging, just airing a few thought I had after reading that paragraph. That said as well, I don't know Java, so maybe I shouldn't even be commenting.
@DarkTimes: You really amazed me... The code you provided seems a lot smarter and in the same time shorter, even tho I haven't tested it yet, but I will definetly steal some of it.

[ Edit: ]
Ok, after my application was pissing me off from quite a while, because it was almost always staying in processes after I had closed it, I decided to do a little search on google. And so I did. I just wrote "C# process kill" and the first article I found was just what I needed. So... If you have any similar problem(this is mainly a problem in LFS_External), there is a source that could help you:

IMPORTANT -> NAMESPACE NEEDED : using System.Diagnostics;




static public bool proccessFindAndKill(string name)
{
//here we're going to get a list of all running processes on
//the computer
foreach (Process clsProcess in Process.GetProcesses())
{
//now we're going to see if any of the running processes
//match the currently running processes by using the StartsWith Method,
//this prevents us from incluing the .EXE for the process we're looking for.
//. Be sure to not
//add the .exe to the name you provide, i.e: NOTEPAD,
//not NOTEPAD.EXE or false is always returned even if
//notepad is running
if (clsProcess.ProcessName.StartsWith(name))
{
//since we found the proccess we now need to use the
//Kill Method to kill the process. Remember, if you have
//the process running more than once, say IE open 4
//times the loop thr way it is now will close all 4,
//if you want it to just close the first one it finds
//then add a return; after the Kill
try
{
clsProcess.Kill();
}
catch { }
//process killed, return true
return true;
}
}
//process not found, return false
return false;
}

I don't find any sense in explaining how to use it, because everyone with less than basic knowledge in C# can figure it out. On top of that there are already provided detailed enough explanations on how to use it.

Source: http://www.dreamincode.net/code/snippet1543.htm
#12 - PoVo
Thanks Broken, love the C# process killer.
Has anyone written code that determines who is ahead given two drivers' current node positions? For example, if driver 1 is at node 234 and driver 2 is at node 235 then driver 2 is ahead. If driver 1 is at node 234 and driver 2 is at node 236 but node 235 is the finish line node then driver 1 is ahead (if you know that both drivers have completed the same number of laps). Been trying to write my own but I'm struggling with the complexity of the finish line node not necessarily being 0.
Either get the node index of the finish line from the .pth files or via IS_RST
struct IS_RST // Race STart
{
(...)
word NumNodes; // total number of nodes in the path
word Finish; // node index - finish line
word Split1; // node index - split 1
word Split2; // node index - split 2
word Split3; // node index - split 3
};

But you don't actually need that if everyone is going the right way.
Upon receiving NLP or MCI, sort by Node, then by Lap and it should be the correct order. (obviously that really depends on the implementation / language uses)
Using the PTH data, you could actually determine who's ahead within the same node, but that'd be a bit more complex.
Quote from morpha :But you don't actually need that if everyone is going the right way.
Upon receiving NLP or MCI, sort by Node, then by Lap and it should be the correct order. (obviously that really depends on the implementation / language uses)

I can't use that for MoE/IGTC/GTAL etc because it doesn't take into account disconnections and it doesn't have a notion of teams (e.g. driver 1 from team 1 times out after completing 25 laps, driver 2 from team 1 joins to take over and the race ordering would put driver 2 at the back of the field, discounting the laps driver 1 did).

The problem is that if the finish line isn't 0 there will be a 0 node somewhere around the track. So, for example, you could have the finish line being node 235 and the total number of nodes on the track being 300. So, consider having 3 drivers in the race on the same lap. Driver 1 is at node 233, driver 2 is at 297 and driver 3 is at 3. What's the order of those drivers? Driver 1 is in the lead (because he's just about to complete the lap), driver 3 is in 2nd position (because he's passed the point on the track where it goes from node 300 (the maximum node) through to node 3 and driver 2 is in 3rd position because he's still in the portion of the track between the finish line (node 235) and the point where the nodes wrap back around (299 - 300 - 1). I was just wondering if someone's already figured this out programmatically.
Whether it accounts for timeouts, takeovers, etc. is up to your implementation. If a player is joining in as replacement for a timed-out or disconnected contender, you'd have to manipulate their Lap count to reflect the combined lap count of both drivers before applying the sorting algorithm.
This could be implemented in several ways, one I can think of right now is having an array, dictionary or whatever it is in your language of choice holding addLaps. This would be indexed by UName and a player's lapcount would be added to it upon receiving a PLL. Your MCI/NLP handler would then simply add the addLaps to the replacement car's lap count. How it knows who replaced whom is up to you, but I'm happy to help if you provide more information

Incorrect sorting if finishLineNode is non-zero is a good point though, in that case simply subtract the finish line node index from all nodes >= or > (depending on when LFS changes the Lap in NodeLap/CompCars)it.

Example: Finish line is at 280, sorting by Node then Lap would result in the following order (assuming everyone is on the same lap):
P1Node = 295 (basically just entered this lap "14 nodes ago", not the real leader)
P2Node = 283 (another one who's really at the back)
P3Node = 279 (real leader, about to cross the finish line)
P4Node = 201

Apply the subtract method and it looks like this:
P3Node = 279 (real leader, about to cross the finish line)
P4Node = 201
P1Node = 15 (former false leader)
P2Node = 3 (another one who's really at the back)

Quote from morpha :Whether it accounts for timeouts, takeovers, etc. is up to your implementation. If a player is joining in as replacement for a timed-out or disconnected contender, you'd have to manipulate their Lap count to reflect the combined lap count of both drivers before applying the sorting algorithm.
This could be implemented in several ways, one I can think of right now is having an array, dictionary or whatever it is in your language of choice holding addLaps. This would be indexed by UName and a player's lapcount would be added to it upon receiving a PLL. Your MCI/NLP handler would then simply add the addLaps to the replacement car's lap count. How it knows who replaced whom is up to you, but I'm happy to help if you provide more information

It's OK, I have a handle on that.

Quote from morpha :Incorrect sorting if finishLineNode is non-zero is a good point though, in that case simply subtract the finish line node index from all nodes >= or > (depending on when LFS changes the Lap in NodeLap/CompCars)it.

Example: Finish line is at 280, sorting by Node then Lap would result in the following order (assuming everyone is on the same lap):
P1Node = 295 (basically just entered this lap "14 nodes ago", not the real leader)
P2Node = 283 (another one who's really at the back)
P3Node = 279 (real leader, about to cross the finish line)
P4Node = 201

Apply the subtract method and it looks like this:
P3Node = 279 (real leader, about to cross the finish line)
P4Node = 201
P1Node = 15 (former false leader)
P2Node = 3 (another one who's really at the back)


I don't think that's going to work though. Take a look at the attached example.

Using your suggestion the corrected values would be:

Player 1: 22 (257-235)
Player 2: 20
Player 3: 230

So the players would be ranked as player 3 in 1st (correct), player 1 in 2nd (incorrect) and player 2 in 3rd (incorrect).
Attached images
nodeexample.png
Right, haven't given it much thought, obviously not enough anyway. Subtract maxnodes instead and see how that goes
if playernode > finishnode: distance = finishnode - playernode + maxnode.
if playernode <= finishnode: distance = finishnode - playernode.

Distance is then each players distance to finishnode. Closest = leader.
Quote from nikka :if playernode > finishnode: distance = finishnode - playernode + maxnode.
if playernode <= finishnode: distance = finishnode - playernode.

Distance is then each players distance to finishnode. Closest = leader.

Ah, yes! That looks more like it. Thanks very much
Quote from broken :Ok, after my application was pissing me off from quite a while, because it was almost always staying in processes after I had closed it, I decided to do a little search on google. And so I did. I just wrote "C# process kill" and the first article I found was just what I needed. So... If you have any similar problem(this is mainly a problem in LFS_External), there is a source that could help you:

I had similar problem. I have noticed that app is staying in processes when the inSim isn't closed on exit. If you have copied code form LFS_External sample you have to check if Form1_FormClosing event is declared in events. You can also debug it with LFS chat messages, it shows every time you connect and disconnect.

I hope it's a helpful tip, especially for beginners.
My top tip for the moment is if you are writing a WPF app with .NET 4.0 you can use the command...

TextOptions.TextFormattingMode="Display"

... to make the fonts not look blurry. Also if you wanna sort a list of IS_RES results with LINQ you can do this...

var ordered = from res in results
orderby res.LapsDone descending, res.TTime
select res;

foreach (IS_RES res in ordered)
{
// Do something...
}

Quote from Makao :I had similar problem. I have noticed that app is staying in processes when the inSim isn't closed on exit. If you have copied code form LFS_External sample you have to check if Form1_FormClosing event is declared in events. You can also debug it with LFS chat messages, it shows every time you connect and disconnect.

I hope it's a helpful tip, especially for beginners.

Well, I'm trying to learn C++ now and am not really that interested in C# anymore, but thanks nevertheless.

Btw: Process.getCurrentProcess().Kill(); was the latest solution for me(it might be wrong, I just wrote it by heart ... but it is something similar.. getCurrentProcess maybe doesn't have brackets... Or maybe that was Kill.. Not sure).
It's...

Process.GetCurrentProcess().Kill();

Although you could also do...

Environment.Exit();

Ah, I almost got it.


Thanks.
1

Helpful functions/tips
(28 posts, started )
FGED GREDG RDFGDR GSFDG