The online racing simulator
Uploaded 1.0.3 to the first post.

Changes
  • Fixed critical bug which was stopping the socket from correctly being released when the InSim connection closed. As a consequence InSim.Run() should now exit properly and InSim.IsConnected should now return the correct value in all situations.
  • Added InSim Relay support. Check Example 7 in SparkExamples for a detailed example.
Edit: I also changed the Car Update example to now spectate players who speed, as it makes more sense than just printing out car speeds.

InSim Relay

Connecting to InSim relay is pretty simple and support needed no additions to the main InSim class. In this example we connect to relay, request the host list, then print out the name of each LFS host that is received.

using System;
using Spark;
using Spark.Packets;
using Spark.Helpers;

class Program
{
static void Main()
{
using (var insim = new InSim())
{
// Relay packets begin with IR_*.
insim.Bind<IR_HOS>(HostListReceived);

// Connect to the relay host.
insim.Connect("isrelay.lfs.net", 47474);

// Request for the host list to be sent.
insim.Send(new IR_HLR { ReqI = 255 });

insim.Run();
}
}

static void HostListReceived(IR_HOS hos)
{
// Loop through each host in the list.
foreach (var host in hos.Info)
{
// Print out the host name.
Console.WriteLine("Host: {0}", StringHelper.StripColors(host.HName));

// If this is the last host, print a blank line after it.
if (host.Flags.HasFlag(InfoFlags.HOS_LAST))
Console.WriteLine();
}
}
}

Uploaded 1.0.4 to the first post.

Changes
  • Fixed bug with confirmation flags in IS_FIN.
  • Fixed bug with player flags in IS_LAP.
  • Added documentation for more packets (I'm up to NPL now).
  • Added StringHelper.ValidHost(string) and StringHelper.ValidPort(string) methods, which allow for easier checking for valid host and port numbers.
  • Some tweaks to CarHelper and TrackHelper, added some new methods and made them more reliable.
  • Added bool PacketEventArgs.TryCast<T>(out T) method.
Example of the new bool PacketEventArgs.TryCast<T>(out T) method:


void _insim_PacketReceived(object sender, PacketEventArgs e)
{
IS_VER ver;
if (e.TryCast(out ver))
{
Console.WriteLine("LFS: {0} {1} InSim: {2}", ver.Product, ver.Version, ver.InSimVer);
}
}

InSimSniffer in 57 lines!

using System;
using Spark;
using Spark.Packets;

namespace SimpleSniffer
{
class Program
{
Program()
{
using (var _insim = new InSim())
{
_insim.PacketReceived += new EventHandler<PacketEventArgs>(_insim_PacketReceived);
_insim.Connect("127.0.0.1", 29999);
_insim.Send(new IS_ISI { ReqI = 255, IName = "^3Sniffer", Flags = InSimFlags.ISF_MCI, Interval = 2000 });
_insim.Run();
}
}

void _insim_PacketReceived(object sender, PacketEventArgs e)
{
SniffPacket(e.Packet);

Console.WriteLine();
}

void SniffPacket<T>(T packet)
{
var properties = packet.GetType().GetProperties();

foreach (var property in properties)
{
var value = property.GetValue(packet, null);

if (value.GetType().IsArray)
{
foreach (var item in (Array)value)
{
if (property.Name == "CompCars" || property.Name == "NodeLaps")
SniffPacket(item); // Recursion!
else
Console.WriteLine("{0}: {1}", property.Name, item);
}
}
else
{
Console.WriteLine("{0}: {1}", property.Name, value);
}
}
}

static void Main()
{
new Program();
}
}
}

Quote from DarkTimes :InSimSniffer in 57 lines!

With tools like these, it can hardly be said that InSim programming is difficult anymore. Great work DarkTimes!
Brief example of parsing MSO commands.

using Spark;
using Spark.Packets;

class Program
{
InSim _insim;

Program()
{
using (_insim = new InSim())
{
_insim.Bind<IS_MSO>(MessageReceived);
_insim.Connect("127.0.0.1", 29999);
_insim.Send(new IS_ISI { Prefix = '!', IName = "^3Spark" });
_insim.Run();
}
}

void MessageReceived(IS_MSO mso)
{
string[] args;
if (TryParseCommand(mso, out args))
{
var command = args[0].ToLower();

switch (command)
{
case "!buy":
// Process !buy command.
break;
case "!sell":
// Process !sell command.
break;
}
}
}

bool TryParseCommand(IS_MSO mso, out string[] args)
{
if (mso.UserType == UserType.MSO_PREFIX)
{
var message = mso.Msg.Substring(mso.TextStart);
args = message.Split();
return args.Length > 0;
}

args = null;
return false;
}

static void Main()
{
new Program();
}
}

#31 - PoVo
Thanks, going to try and develop my Drift insim on this
I've received a couple of questions about using Spark within a GUI application, and while I plan to release a Win32 example in the next build, the premise is very simple. A GUI app with Spark is exactly like a console app, except you don't need to call void InSim.Run(), as the program stays open regardless, however you do need to deal with cross-thread operations. As Spark is a multi-threaded library, you need to syncronize Spark with the main GUI thread in order to perform any updates. There are a bunch of ways of doing this, but I like to use delegates and lambdas for the task.

class MainForm : Form
{
void DoInvoke(Action action)
{
// Check if invoke is needed.
if (InvokeRequired)
Invoke(action); // Invoke action.
else
action(); // No invoke needed.
}

// Called when an MSO packet is received.
void MessageReceived(IS_MSO mso)
{
// Invoke Spark onto the main GUI thread.
DoInvoke(() =>
{
// Perform any updates. Try to keep them small.
_messageTextBox.Text += mso.Msg + Environment.NewLine;
});
}
}

As I said the next release will include both Win32 and WPF examples of using the library.
Thank you for the library and your examples

(I hope , you'll find some time to add outgauge support :shy
There is outgauge example. Number 5 or 6.
No there isn't, there's no OutGauge or OutSim support yet. Only InSim and InSim Relay.
using System;
using System.Collections.Generic;
using Spark;
using Spark.Helpers;
using Spark.Packets;

namespace Spark.Example4
{
/// <summary>
/// Example 4: Helpers. Connects to InSim, requests all players to be sent, the prints out
/// the time of each player that completes a lap.
/// </summary>
class Program
{
// We store the players in a dictionary with the PLID as the key.
static InSim insim;
static Dictionary<int, IS_NPL> _players = new Dictionary<int, IS_NPL>();
static Dictionary<int, IS_NCN> _conns = new Dictionary<int, IS_NCN>();

static void Main()
{
// Create new InSim object.
using (insim = new InSim())
{
// Bind handlers.
insim.Bind<IS_NCN>(NewConn);
insim.Bind<IS_CNL>(ConnLeft);
insim.Bind<IS_NPL>(NewPlayer);
insim.Bind<IS_PLL>(PlayerLeft);
insim.Bind<IS_MCI>(MultiCarInfo);

// Establish the InSim connection.
insim.Connect("62.75.188.55", 32002);

// Initialize InSim.
insim.Send(new IS_ISI { IName = "^3MP 2010 DEMO", Admin="janosik123", Prefix = '@', Flags = InSimFlags.ISF_MCI, Interval = 1000});

// Request connections.
insim.Send(new IS_TINY { SubT = TinyType.TINY_NCN, ReqI = 255});

// Request players.
insim.Send(new IS_TINY { SubT = TinyType.TINY_NPL, ReqI = 255});

//insim.Bind<IS_MCI>(MultiCarInfo);

// Prevent program from exiting.
insim.Run();
}
}

static void NewConn(IS_NCN ncn)
{
if(_conns.ContainsKey(ncn.UCID)){
_conns[ncn.UCID] = ncn;
}
else{
_conns.Add(ncn.UCID, ncn);
}
Console.WriteLine("New connection: {0} ({1}) #{2}", ncn.PName, ncn.UName, ncn.UCID);
}

static void ConnLeft(IS_CNL cnl)
{
Console.WriteLine("Connection left: {0} ({1}) #{2} Reason: {3}", getConn(cnl.UCID).PName, getConn(cnl.UCID).UName, cnl.UCID, cnl.Reason);
_conns.Remove(cnl.UCID);
}

static void NewPlayer(IS_NPL npl)
{
if (_players.ContainsKey(npl.PLID))
{
// Leaving pits, just update NPL object.
_players[npl.PLID] = npl;
}
else
{
// Add new player.
_players.Add(npl.PLID, npl);
}
Console.WriteLine("New player: {0} (#{1})", npl.PName, npl.UCID);
}

static void PlayerLeft(IS_PLL pll)
{
// Remove player.
_players.Remove(pll.PLID);
}

static void MessageOut(IS_MSO mso)
{
if(mso.Msg == "something"){

}
}

static IS_NPL getPlayer(byte PLID){
Int32 szukaj = Convert.ToInt32(PLID);
return _players[szukaj];
}

static IS_NCN getConn(byte UCID)
{
Int32 szukaj = Convert.ToInt32(UCID);
return _conns[szukaj];
}

static void spectatePlayer(byte PLID) {
Console.WriteLine("Speeding: {0}", getConn(getPlayer(PLID).UCID).UName);
if (getConn(getPlayer(PLID).UCID).UName == "misiek08")
{
insim.Send(new IS_MST { Msg = ("/spec " + getConn(getPlayer(PLID).UCID).UName) });
}
}

static void MultiCarInfo(IS_MCI mci)
{
// Loop through each car on track.
foreach (var car in mci.CompCars)
{
IS_NPL npl;
npl = getPlayer(car.PLID);
// Convert LFS speed into Mph.
var kph = MathHelper.SpeedToKph(car.Speed);
// Print nicely formatted string to console.
Console.WriteLine("Speed: {0} {1:F2}", npl.PName, kph);
if(kph > 100){
spectatePlayer(car.PLID);
}

}
}
}
}

If on server is someone it won't work. It can't find index is _players Array or _conns. I think, MCI is executed faster than NCN and NPL events.
Yes, this was an issue in some early examples, as it's possible to get MCI updates before the NCN or NLP packets have been processed. However all of the recent examples include logic to prevent this from happening.

static void MultiCarInfo(IS_MCI mci)
{
foreach (var car in mci.CompCars)
{
IS_NPL npl;
// Get the NPL packet if it exists...
if (_players.TryGetValue(car.PLID, out npl))
{
var kph = MathHelper.SpeedToKph(car.Speed);
if (kph > 80)
{
_insim.Send("/spec {0}", npl.PName);
_insim.Send("{0} ^3spectated for speeding", npl.PName);
}
}
}
}

Thank you, sir. I'm making warm-up lap system with staying at primary position and virtual lights by InSim. Is anything like this done by someone?
I don't have a Spark example of a warm-up lap, but I have written such an app before. I used this logic for it.

- Log player start position (IS_REO at race start)
- Send message to tell player to follow warmup lap rules (speed limit and no overtaking)
- Check player speed and position each update
- Check if player exceeds speed limit or player position decreases (decrease means overtaking other car)
- Spectate player and send appropriate error message
- When lead car finishes first lap send "green flag" message and stop tracking
Can I bind and unbind packet events in other packet event. My english is bad so an example:

static void PlayerLeft(IS_PLL pll)
{
// Remove player.
_players.Remove(pll.PLID);
insim.Bind<IS_REO>(AFuncOfREO);
}

And any unbind function?

EDIT: Can I set multibinds?
Yes, you can bind callbacks at any point, and you can bind multiple callbacks to the same packet. You cannot currently unbind callbacks, as I haven't written that yet. However binding callbacks from within another callback looks like a really bad idea to me, I can't think of a reason why you would want to do this.

Edit: In fact I can see a million reasons how doing this would completely **** up your program.
Ok. So I will write the reason. I'am going to make an big application for rolling start, F1 style times comparsion on screen, live tracker, takeover chatcher, point system. Enabling all callback (sometimes 6 on every packet) could kill InSim server so I'm going to make MSO handler for enabling modules of the InSim system. Unbinds should be here for the same reason. Disabling some functionality.
You only need to bind packets when your program first starts, you just bind them once and then forget about them. You should not be binding packets from the callbacks of other packets. That will cause huge problems. And it is not needed.

Anyway, the source code for Spark is available from the first post, you can look at the IPacketBinding, PacketBinding and BindingCollection classes to see how the packet binding is handled. It's very, very simple. It's just a map of function pointers. You register which packets you want to be notified about, then when that packet is received Spark loops through them and calls each function passing the packet object as a parameter.
I did multi-bind's and unbinds in PHP. Here it's only other code syntax but everything looks identical but it's your lib and my suggestion are unbinds. Thanks!

Binding some event on MSO packet is good performance idea.
Yeah, I plan to add unbinds, just haven't got around to it yet.
Now you have time to do it
I have a problem when trying to open this i am still using C# 2008 visual express edition, it comes up with version unidentified and they don't open anyone else have this problem?
Only 2010 is good working with this library.

EDIT:
DarkTimes was faster...
Okay thanks lol, my stupid old box wont let me upgrade so i will just have to wait till i get a new one to be able to see it.

FGED GREDG RDFGDR GSFDG