The online racing simulator
Which is the best way to keep NCN packets but with some additional information like NCN.IsOfficer?

At the moment im using this method which i copied from SparkCruise and added some by myself.

//Global
private Dictionary<byte, IS_NCN> _connections = new Dictionary<byte, IS_NCN>();
private Dictionary<byte, IS_NPL> _players = new Dictionary<byte, IS_NPL>();

//After InSim Initialise
insim.Send(new IS_TINY { SubT = TinyType.TINY_NCN, ReqI = 255 });
insim.Send(new IS_TINY { SubT = TinyType.TINY_NPL, ReqI = 255 });

//Packets
private void onConnectionJoin(InSim insim, IS_NCN NCN)
{
if (_connections.ContainsKey(NCN.UCID)) _connections[NCN.UCID] = NCN;
else _connections.Add(NCN.UCID, NCN);
}

private void onConnectionLeave(InSim insim, IS_CNL CNL)
{
_connections.Remove(CNL.UCID);
}

private void onPlayerJoin(InSim insim, 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);
}
}

private void onPlayerLeave(InSim insim, IS_PLL pll)
{
_players.Remove(pll.PLID);
}

//Getting Connection for PLID
private IS_NCN GetConnection(byte plid)
{
IS_NPL npl;
if (_players.TryGetValue(plid, out npl))
{
return _connections[npl.UCID];
}
return null;
}

This code is working fine, but i would like to add extra info to these NCN Packets.
The most straight-forward way is to create your own connection class that you store instead. This would be a very simple one.


class Connection {
// NCN fields.
public byte UCID;
public string UName;
// Etc..

// Custom fields.
public bool IsOfficer;
}

Dictionary<byte, Connection> _connections = new Dictionary<byte, Connection>();

void OnConnectionJoin(InSim insim, IS_NCN ncn) {
// Add to dictionary...
_connections.Add(ncn.UCID, new Connection {
UCID = ncn.UCID,
UName = ncn.UName,
IsOfficer = false,
});
}

Thanks, i'll give a try on it. This was may be better as i'm not going to use all NCN fields so i can have only the ones i need.
I am quite new to Mercurial and CodePlex yet, so if you allow me, I will just post my suggestion here. Modify whatever you want in it. I know sometimes even tiny little details can be annoying (they are to me), so, just, don't hesitate to change it however you want.

I need to stop talking! CODE, FINALLY:

<?php 
using System
;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;

namespace 
InSimDotNet
{
    
/// <summary>
    /// Static class to help with object names.
    /// </summary>
    
public static class ObjectHelper
    
{
        
#region ObjMap
        
private static readonly Dictionary<bytestring[]> ObjMap = new Dictionary<bytestring[]>()
        {
            {
0, new string[] {"Unknown""Unknown Object"}},

            {
4, new string[] {"Chalk""Chalk Line Long"}},
            {
5, new string[] {"Chalk""Chalk Line"}},
            {
6, new string[] {"Chalk""Chalk Ahead"}},
            {
7, new string[] {"Chalk""Chalk Ahead Long"}},
            {
8, new string[] {"Chalk""Chalk Soft Left"}},
            {
9, new string[] {"Chalk""Chalk Hard Left"}},
            {
10, new string[] {"Chalk""Chalk Soft Left Long"}},
            {
11, new string[] {"Chalk""Chalk Soft Right"}},
            {
12, new string[] {"Chalk""Chalk Hard Right"}},
            {
13, new string[] {"Chalk""Chalk Soft Right Long"}},

            {
20, new string[] {"Cone""Cone Red/White"}},
            {
21, new string[] {"Cone""Cone Red"}},
            {
22, new string[] {"Cone""Cone Red Striped"}},
            {
23, new string[] {"Cone""Cone Blue Striped"}},
            {
24, new string[] {"Cone""Cone Blue"}},
            {
25, new string[] {"Cone""Cone Green Striped"}},
            {
26, new string[] {"Cone""Cone Green"}},
            {
27, new string[] {"Cone""Cone Orange"}},
            {
28, new string[] {"Cone""Cone White"}},
            {
29, new string[] {"Cone""Cone Yellow Striped"}},
            {
30, new string[] {"Cone""Cone Yellow"}},
            {
40, new string[] {"Cone""Cone Red Directional"}},
            {
41, new string[] {"Cone""Cone Blue Directional"}},
            {
42, new string[] {"Cone""Cone Green Directional"}},
            {
43, new string[] {"Cone""Cone Yellow Directional"}},

            {
48, new string[] {"Tyre""Tyre"}},
            {
49, new string[] {"Tyres""Tyre Stack of 2"}},
            {
50, new string[] {"Tyres""Tyre Stack of 3"}},
            {
51, new string[] {"Tyres""Tyre Stack of 4"}},
            {
52, new string[] {"Tyre""Big Tyre"}},
            {
53, new string[] {"Tyres""Big Tyre Stack of 2"}},
            {
54, new string[] {"Tyres""Big Tyre Stack of 3"}},
            {
55, new string[] {"Tyres""Big Tyre Stack of 4"}},

            {
64, new string[] {"Marker""Marker Curve Left"}},
            {
65, new string[] {"Marker""Marker Curve Right"}},
            {
66, new string[] {"Marker""Marker Left"}},
            {
67, new string[] {"Marker""Marker Right"}},
            {
68, new string[] {"Marker""Marker Left Hard"}},
            {
69, new string[] {"Marker""Marker Right Hard"}},
            {
70, new string[] {"Marker""Marker Left->Right"}},
            {
71, new string[] {"Marker""Marker Right->Left"}},
            {
72, new string[] {"Marker""Marker U-Turn->Right"}},
            {
73, new string[] {"Marker""Marker U-Turn->Left"}},
            {
74, new string[] {"Marker""Marker Winding Left"}},
            {
75, new string[] {"Marker""Marker Winding Right"}},
            {
76, new string[] {"Marker""Marker U-Turn Left"}},
            {
77, new string[] {"Marker""Marker U-Turn Right"}},

            {
84, new string[] {"Marker""Marker 25"}},
            {
85, new string[] {"Marker""Marker 50"}},
            {
86, new string[] {"Marker""Marker 75"}},
            {
87, new string[] {"Marker""Marker 100"}},
            {
88, new string[] {"Marker""Marker 125"}},
            {
89, new string[] {"Marker""Marker 150"}},
            {
90, new string[] {"Marker""Marker 200"}},
            {
91, new string[] {"Marker""Marker 250"}},

            {
96, new string[] {"Railing""Railing Short"}},
            {
97, new string[] {"Railing""Railing Medium"}},
            {
98, new string[] {"Railing""Railing Long"}},

            {
104, new string[] {"Barrier""Barrier Long"}},
            {
105, new string[] {"Barrier""Barrier Red"}},
            {
106, new string[] {"Barrier""Barrier White"}},

            {
112, new string[] {"Banner""Banner 1"}},
            {
113, new string[] {"Banner""Banner 2"}},

            {
120, new string[] {"Ramp""Ramp"}},
            {
121, new string[] {"Ramp""Ramp Wide"}},

            {
128, new string[] {"Speed Bump""Speed Bump Long"}},
            {
129, new string[] {"Speed Bump""Speed Bump"}},

            {
136, new string[] {"Post""Post Green"}},
            {
137, new string[] {"Post""Post Orange"}},
            {
138, new string[] {"Post""Post Red"}},
            {
139, new string[] {"Post""Post White"}},

            {
144, new string[] {"Bale""Bale"}},

            {
148, new string[] {"Railing""Railing"}},

            {
160, new string[] {"Sign""Sign Keep Left"}},
            {
161, new string[] {"Sign""Sign Keep Right"}},

            {
168, new string[] {"Sign""Sign 80 km/h"}},
            {
169, new string[] {"Sign""Sign 50 km/h"}},

            {
255, new string[] {"Marshall""Marshall Object"}}
        };
        
#endregion

        /// <summary>
        /// Determines the full name of an object or null if the Index does not exist.
        /// </summary>
        /// <param name="Index">The object's index.</param>
        /// <returns>The full name.</returns>
        
public static string GetObjName(byte Index)
        {
            
string[] obj;
            if (
ObjMap.TryGetValue(Indexout obj))
            {
                return 
obj[1];
            }

            return 
null;
        }

        
/// <summary>
        /// Determines the type of the object or returns null if the Index does not exist.
        /// </summary>
        /// <param name="Index">The object's index.</param>
        /// <returns>The type of the object.</returns>
        
public static string GetObjType(byte Index)
        {
            
string[] obj;
            if (
ObjMap.TryGetValue(Indexout obj))
            {
                return 
obj[0];
            }

            return 
null;
        }
        
        
/// <summary>
        /// Determines if the specified object exists.
        /// </summary>
        /// <param name="Index">The index of the object.</param>
        /// <returns>True if the object exists.</returns>
        
public static bool ObjExists(byte Index)
        {
            return 
ObjMap.ContainsKey(Index);
        }
    }
}
?>

I decided to use the array approach, because, imo, it gives more freedom. You know, if you need to represent the object in another way, you can just go ahead and do it. But I don't know what that means on the optimization side. Anyway, hope it works at least, because I am just about to test it, at the moment of posting this.

And last but not least: Must definitely thank T3charmy for a list of those, which I straightly copied over to C# and just modified a bit, so it's.. well.. C#, and not PHP anymore.


[E] I was stupid enough to not even try to compile the code. And it gave me errors. Did compile it now however, and it was a success.
OK, thanks!

I've added it to the project and pushed it to CodePlex (with a couple of tiny changes). Let me know if there's any problem with it!
Sorry for a possibly very lame question, but: Can you give me a very short example on how to send an AXM packet to add an object, please? Just simple static values of 10 for both X and Y coords would do, nothing more.

I tried, but I can't modify Info, so I guess I'm doing it the wrong way.

Thanks.
OK, I started looking into it and realized I'd made a bunch of mistakes with sending the AXM packet, so I fixed them and pushed version 2.0.10 out to CodePlex.

In terms of sending an AXM packet, here is an example of how to do that. This places a red cone on a the start/finish line at Blackwood GP.


<?php 
using 
(InSim insim = new InSim()) {
    
// Initialize InSim with AXM_EDIT flag.
    
insim.Initialize(new InSimSettings {
        
Host "127.0.0.1",
        
Port 29999,
        
Admin String.Empty,
        
Flags InSimFlags.ISF_AXM_EDIT,
    });

    
// Create AXM packet.
    
IS_AXM axm = new IS_AXM {
        
PMOAction AutoXActionFlags.PMO_ADD_OBJECTS
    
};

    
// Add object.
    
axm.Info.Add(new ObjectInfo {
        
Heading 128,
        
Index 20,
        
= -1115,
        
2009,
        
Zchar 24,
    });

    
// Send packet to LFS.
    
insim.Send(axm);

    
Console.ReadKey(true);
}
?>

Thanks for the example. It will be useful when i finish the transfer from LFS_External to InSim.Net.

The thing is that i have another problem now. I've been trying to bind TINY_AXC(Autocross Clear), but i didn't find a correct way.
insim.Bind<TINY_AXC>(OnAutocrossClear);

This code is not working and looking the rest pages of the topic i didn't find a solution.

Also at LFS_External i remember that each packet had a "try/catch" is it needed here too, or
insim.InSimError += new EventHandler<InSimErrorEventArgs>(onInSimError);

will print any errors including packets?
You cannot bind directly to a TINY_* (although I've thought about adding that before), you need to bind an IS_TINY and then check the SubT.

insim.Bind<IS_TINY>(TinyReceived);

void TinyReceived(InSim insim, IS_TINY tiny) {
if (tiny.SubT == TinyType.TINY_AXC) {
// AXC received.
}
}

Quote :Also at LFS_External i remember that each packet had a "try/catch" is it needed here too, or
insim.InSimError += new EventHandler<InSimErrorEventArgs>(onInSimError);

will print any errors including packets?

Yes, InSimError is raised when any exceptions occur on the packet received thread. You don't need to wrap everything in try/catch blocks.
What have I found so far?

2.0.10:
You can't read from AXM.Info[i], it gives you an error. I am trying to get the Index of all received objects in a function, and that didn't seem to work.
In change set 142d0e60cb66, however, it works well.

And yay, my first StackOverflow (more precisely - first time I encounter one, because I don't think it's my source :razz. This happens when I try to add an object. Here's a screenshot (I should've just used the Snipping Tool, but who to think):


Also, the function I've created to send an object follows:

<?php 
        
public static void Send1Object(short Xshort Yshort Zbyte Headingbyte Index)
        {
            try
            {
                
// Create the AXM packet
                
IS_AXM AXM = new IS_AXM PMOAction ActionFlags.PMO_ADD_OBJECTS };

                
// Create the object
                
AXM.Info.Add(new ObjectInfo() { XYZchar ZHeading HeadingIndex IndexFlags });

                
// Send the packet to LFS
                
Dizplay.iSend(AXM);
            }
            catch (
Exception E)
            {
                
ErrorRecord(E);
            }
        }
?>

It doesn't even care that I have surrounded the thing in try/catch blocks. It goes by it like a bus, passing an empty station...
Attached images
stackoverflow_dontthinkitsmycode_muaha_ha.png
Yeah, I had originally made the Info an ICollection, which does not allow indexing, which is why I changed it to an IList. The StackOverflow exception was because the IS_AXM constructor kept calling itself.

I pushed a new changeset onto CodePlex with a fix.
Awesome! Thanks a lot for the update, the example, the fix, everything!

Tested it, and it works like a charm! :lovies3d:


Also, if anyone is curious or needs to transfer coords from MCI to AXM, here is how:

AXM X coord = MCI X / 4096 | & conversion to short
AXM Y coord = MCI Y / 4096 | & conversion to short
AXM Z coord = MCI Z / 16384 | & conversion to short
AXM Heading = MCI Heading / 256 | & conversion to byte
Quote from broken :Also, if anyone is curious or needs to transfer coords from MCI to AXM, here is how:

AXM X coord = MCI X / 4096 | & conversion to short
AXM Y coord = MCI Y / 4096 | & conversion to short
AXM Z coord = MCI Z / 16384 | & conversion to short
AXM Heading = MCI Heading / 256 | & conversion to byte

I can see it now! Set a cone on each node, set a cone behind the client as he drives around ... Some silly fun can be had here!
Quote from Dygear :I can see it now! Set a cone on each node, set a cone behind the client as he drives around ... Some silly fun can be had here!

Confirmed:


<?php 
                        
if (CC.Speed 91.02 50)
                        {
                            
short X = (short)(MCI.Info[i].4096);
                            
short Y = (short)(MCI.Info[i].4096);

                            
short Z = (short)(MCI.Info[i].16384);

                            
byte Heading = (byte)(MCI.Info[i].Heading 256 192);

                            
Processor.Send1Object(XYZHeading128);
                        }
?>

Send1Object is provided above, if you need it.

T3charmy was first though, using PRISM, because it had all this working before InSim.NET. DarkTimes, we've lost this race!
Quote from broken :

<?php 
short Z 
= (short)(MCI.Info[i].16384);
?>


Should that not be;

<?php 
char Z 
= (char)(MCI.Info[i].16384);
?>

or

<?php 
byte Z 
= (byte)(MCI.Info[i].16384);
?>

?
-
(DarkTimes) DELETED by DarkTimes : Ignore me I didn't see it was Z
It should be:

sbyte z = (sbyte)(MCI.Info[i].Z / 16384);

But really short is fine. It's this whole CLS compliance thing in .NET, bad to have a public property that's a signed byte, so I made it a short and it gets converted internally.
Ah, ok, got ya!
Pushed a new changeset onto CodePlex with a few fixes. I found a pretty big bug in the EncodingHelper class that was causing weird issues when sending packets with strings (especially if the string was at the end of the packet).

I also added an IEnumerable to the constructor of IS_AXM (and also IS_REO) which allows you to pass a collection of ObjectInfo objects (or bytes in REO's case) when you create the packet. This makes sending one a little neater (I think).


<?php 
using 
(InSim insim = new InSim()) {
    
insim.Initialize(new InSimSettings {
        
Host "127.0.0.1",
        
Port 29999,
        
Admin String.Empty,
        
Flags InSimFlags.ISF_AXM_EDIT,
    });

    
// Create collection of objects.
    
ObjectInfo[] objects = { 
        new 
ObjectInfo {
            
Heading 128,
            
Index 20,
            
= -1115,
            
2009,
            
Zchar 24,
        },
        
// Add more objects etc..
    
};

    
// Send AXM passing objects to constructor.
    
insim.Send(new IS_AXM(objects) {
        
PMOAction ActionFlags.PMO_ADD_OBJECTS,
    });
}
?>

Hopefully it's all working OK.
I was using IS_PLC to set the player cars and I realized that if you were storing the list of cars as an array of strings (or in a database table etc..), then you needed a way to convert that list into a CarFlags enum so it could be sent in the IS_PLC packet.

I had a snoop around the .NET Framework looking for something simple and I came up with this.


<?php 
CarFlags GetCarsFlags
(IEnumerable<stringcars) {
    
CarFlags flags 0;
    foreach (
string car in cars) {
        
flags |= (CarFlags)Enum.Parse(typeof(CarFlags), cartrue);
    }
    return 
flags;
}

// Use it like this.
CarFlags c GetCarsFlags(new string[] { "UF1""XFG""FBM" });

// Or like...
insim.Send(new IS_PLC {
    
UCID ucid,
    
Cars GetCarFlags(new string[] { "UF1""XFG""FBM" } ),
});
?>

This piece of code is returning me rear tyres and then front tyres. Is it me misunderstanding something or is it really inverted?

static void PitStop(InSim insim, IS_PIT pit)
{
Console.WriteLine(pit.Tyres.FrontLeft);
Console.WriteLine(pit.Tyres.FrontRight);
Console.WriteLine(pit.Tyres.RearLeft);
Console.WriteLine(pit.Tyres.RearRight);
}

I've noticed a small bug which may be from my code, but i am not sure.
Sometimes if you are spectator and then join, you get MCI error. After debugging i noticed that MCI had 3 cars but Players collection had 2. This means that MCI is called before NPL so that Players collection is not updated with the new player but MCI is searching for it.

Is there any way to fix it?
Might be more of a latency thing then any thing else. Is the InSim client on the same computer as the LFS host? Are they connected via the lookback IP? If both cases are true, then I would say that you might be right, otherwise it's almost sure to be a latency issue.
Quote from Dygear :Might be more of a latency thing then any thing else. Is the InSim client on the same computer as the LFS host? Are they connected via the lookback IP? If both cases are true, then I would say that you might be right, otherwise it's almost sure to be a latency issue.

I was testing insim locally so yes, both insim and server were on the same computer(mine) and connecting locally(127.0.0.1).
Quote from DarkKostas :I was testing insim locally so yes, both insim and server were on the same computer(mine) and connecting locally(127.0.0.1).

I never noticed this problem, but I'll see if I can reproduce it.
Maybe packet are not queued

FGED GREDG RDFGDR GSFDG