The online racing simulator
What am I doing wrong?
I just started writing a new insim, but ran into the strangest thing when I got around to writing an MCI handler.
There were no MCI events!

I made a test script to make absolutely sure I was doing it right:

import pyinsim

n = 0

def mci_handler(insim, mci):
global n
n += 1

insim = pyinsim.insim('127.0.0.1', 29999, Flags = pyinsim.ISF_MCI, Admin='thecorrectpass', IName='insim test')

insim.bind(pyinsim.ISP_MCI, mci_handler)

pyinsim.run()

print n

Then I fired up server .6b, connected to it, and played in the XRT
Killed the server

n == 0

How the what huh?
Thank you
You also need to specify the Interval, which is the milliseconds between each MCI packet being sent.

pyinsim.insim('127.0.0.1', 29999, Flags=pyinsim.ISF_MCI, Interval=1000, Admin='thecorrectpass', IName='insim test')

Yay!


Thank you for taking the time to help me with such a trivial problem

I really enjoy using your library! Thank you for making it so complete and open to the LFS community!

I'll make sure to compare the InSim documentation with my code next time
Hi. Thanks for the library. Just to report a small bug regarding CarContact packet. In insim.py at line 1161:

pack_s = struct.Struct('3Bb6b2B2H')

should be
pack_s = struct.Struct('3Bb6b2B2h')

that's because X and Y coordinates might have negative values and 'H' is for unsigned shorts only.
I had a good idea for an alternative way to handle events which is quite neat.

def version(insim, ver):
print ver.InSimVer

def message_out(insim, mso):
print mso.Msg

insim = pyinsim.insim('127.0.0.1', 29999, ReqI=1, Admin='', IName='Test')

# Bind event handlers
insim.ISP_VER = version
insim.ISP_MSO = message_out

pyinsim.run()

As you see you just create a variable with the packet name and assign it the function you want to call when that packet is received. These member variables don't need to exist, they're resolved dynamically, which turns out to be very simple.

def raise_packet_event(self, data):
ptype = get_packet_type(data[1])
pname = get_packet_name(ptype) # Get packet name

# Get packet handler and check it is a function we can call
packet_event = getattr(self, pname)
if packet_event and hasattr(packet_event, '__call__'):
packet = PACKET_MAP[ptype](data) # Create packet
packet_event(self, packet) # Call event handler

You could also modify it to allow you to have a list of functions to call.

insim.ISP_VER = [version1, version2]

I may well have a go at implementing this in the main library, as I really do like the simplified syntax it allows.
I was using a similar idea from AMX Mod and Source Mod with regards to InSim packets. As each InSim packet is an event, making the methods of a class handle these events via defined methods I found to be interesting. Where you could use the method clientConnect for when the IS_NCN packet is sent or clientDisconnect when the IS_CNL packet is sent. Dynamic arguments are not really an option for me in PHP so I would have to give them the packet (and then what's the point of having a high level of abstraction in the first place.) or manually map the method's arguments myself and have to remember to change them when the version number changes, and new functions or the packet structure changes. Ideally, only editing the packet's module should be enough to update everything, but PHP does not allow me to do that. At least Python has that covered for you.
I wrote a small example program to show how to create a InSim button app using a Finite-State Machine. I got the inspiration when I was writing a simple game and saw how effective a FSM is for this kind of thing.

In this example each connection on the host is given their own ScreenManager, that manages what screen that player is currently being shown. Each screen handles displaying and deleting all its own buttons, and also all its own internal logic.

This example allows you to flip backwards and forwards between a welcome screen and an options screen.

Well anyway, it's pretty heavily commented.

import pyinsim

# Class to manage what screen is currently being shown to a
# player. Each connection on the host has their own copy of
# the ScreenManager.
class ScreenManager(object):
def __init__(self, insim, ncn):
self.insim = insim # Copy of the InSim connection.
self.ncn = ncn # Copy of the players connection packet.
self.current_screen = None # The currently loaded screen for this player.

# Transition this player to the next screen.
def transition(self, screen):
# First tell the current screen (if any) to unload its buttons.
if self.current_screen:
self.current_screen.unload_buttons()

# Set the screen the player is currently viewing.
self.current_screen = screen

# A few helpful variables to have in the screen.
self.current_screen.screen_manager = self
self.current_screen.insim = self.insim
self.current_screen.ncn = self.ncn

# Tell the new screen to send its buttons to LFS.
self.current_screen.load_buttons()

def button_click(self, btc):
# Pass on the button click event to the current screen.
self.current_screen.button_click(btc)

# TODO: Implement more packet handlers. Should define handlers
# for any packets the screens may need to handle, just like
# button_click event.

# Abstract class used to define a screen.
class Screen(object):
def __init__(self):
self.screen_manager = None
self.insim = None
self.ncn = None

# Called when the screen is being shown, used to send
# all buttons to be displayed on the screen.
def load_buttons(self):
pass

# Called when the screen is being hidden, used to delete
# any buttons that were sent.
def unload_buttons(self):
pass

# Called whenever a BTC packet is received. Can extend this class
# to handle any other packet events the derived screens might need.
def button_click(self, btc):
pass

# Example welcome screen, shown to players when they first connect
# to the host.
class WelcomeScreen(Screen):
def __init__(self):
Screen.__init__(self)

def load_buttons(self):
# Send buttons when screen first loaded.
self.insim.send(pyinsim.ISP_BTN,
ReqI=1,
UCID=self.ncn.UCID,
ClickID=1,
BStyle=pyinsim.ISB_DARK,
L=20, T=20, W=40, H=10,
Text='Welcome Screen')
self.insim.send(pyinsim.ISP_BTN,
ReqI=1,
UCID=self.ncn.UCID,
ClickID=2,
BStyle=pyinsim.ISB_LIGHT|pyinsim.ISB_CLICK,
L=20, T=32, W=40, H=10,
Text='Go to options screen')

def unload_buttons(self):
# When screen unloaded delete any buttons sent.
self.insim.send(pyinsim.ISP_BFN,
SubT=pyinsim.BFN_DEL_BTN,
UCID=self.ncn.UCID,
ClickID=1)
self.insim.send(pyinsim.ISP_BFN,
SubT=pyinsim.BFN_DEL_BTN,
UCID=self.ncn.UCID,
ClickID=2)

def button_click(self, btc):
# Handle button click event.
if btc.ClickID == 2:
# Tell the screen manager to transition to the OptionsScreen.
self.screen_manager.transition(OptionsScreen())

# Example options screen, same as WelcomeScreen really.
class OptionsScreen(Screen):
def __init__(self):
Screen.__init__(self)

def load_buttons(self):
self.insim.send(pyinsim.ISP_BTN,
ReqI=1,
UCID=self.ncn.UCID,
ClickID=1,
BStyle=pyinsim.ISB_DARK,
L=20, T=20, W=40, H=10,
Text='Options Screen')
self.insim.send(pyinsim.ISP_BTN,
ReqI=1,
UCID=self.ncn.UCID,
ClickID=2,
BStyle=pyinsim.ISB_LIGHT|pyinsim.ISB_CLICK,
L=20, T=32, W=40, H=10,
Text='Go to welcome')

def unload_buttons(self):
self.insim.send(pyinsim.ISP_BFN,
SubT=pyinsim.BFN_DEL_BTN,
UCID=self.ncn.UCID,
ClickID=1)
self.insim.send(pyinsim.ISP_BFN,
SubT=pyinsim.BFN_DEL_BTN,
UCID=self.ncn.UCID,
ClickID=2)

def button_click(self, btc):
if btc.ClickID == 2:
# Tell ScreenManager for this player to transition
# back to the WelcomeScreen.
self.screen_manager.transition(WelcomeScreen())

class InSimApp(object):
def __init__(self):
# Dict to store the ScreenManager for each player.
self.screen_managers = {}

# Create new InSim connection.
self.insim = pyinsim.insim('127.0.0.2', 29999, Admin='', IName='InSimFSM')

# Bind packet events.
self.insim.bind(pyinsim.ISP_ISM, self.insim_multi)
self.insim.bind(pyinsim.ISP_NCN, self.new_connection)
self.insim.bind(pyinsim.ISP_CNL, self.connection_leave)
self.insim.bind(pyinsim.ISP_BTC, self.button_click)

# Request multiplayer packet on connect.
self.insim.send(pyinsim.ISP_TINY, ReqI=1, SubT=pyinsim.TINY_ISM)

def insim_multi(self, insim, ism):
# When connected to host request connections list.
insim.send(pyinsim.ISP_TINY, ReqI=1, SubT=pyinsim.TINY_NCN)

def new_connection(self, insim, ncn):
# Create new ScreenManager for this connection and add it
# to the screen_managers dictionary.
screen_manager = ScreenManager(insim, ncn)
self.screen_managers[ncn.UCID] = screen_manager

# Tell ScreenManager to display the welcome screen for this player.
screen_manager.transition(WelcomeScreen())

def connection_leave(self, insim, cnl):
# When a connection leaves delete their screen manager.
del self.screen_managers[cnl.UCID]

def button_click(self, insim, btc):
# When packet received pass it on to the connections ScreenManager, which will
# then pass it on to the currently viewed screen.
self.screen_managers[btc.UCID].button_click(btc)


if __name__ == '__main__':
# Create new InSim app.
InSimApp()

# Start all InSim connections and poll for packet events.
pyinsim.run()

You can download the full program (without comments) below.
Attached files
InSimFSM.zip - 100.4 KB - 840 views
Very useful thanks!
pyinsim 3.0
I've been working on pyinsim 3.0.0 recently, which is a new version of pyinsim designed to support Python 3.0. I decided to take this opportunity to fix a bunch of issues I had with the previous version of library, and also add some new features.

I'm not sure how/if I'm going to integrate the new version with the existing CodePlex repository, so in the mean time I created a new Mercurial repository on my BitBucket account. You can view, clone or download the development version of pyinsim 3.0.0 here:

https://bitbucket.org/alexmcbride/pyinsim

The biggest change to pyinsim, aside from some changes to the API, is that all strings are now unicode by default. pyinsim now automatically converts LFS encoded strings to unicode and back again when sending or receiving packets. In Python 3.0 all strings are unicode, and while I messed around with using the new bytes type to represent LFS encoded strings, this created a lot of issues and made programming pyinsim apps much more complex.

One of the primary goals of pyinsim is to be as simple to use as possible, so I took the decision to just make all strings unicode and do the encoding/decoding on the fly, (hopefully) without you noticing. Obviously this means that packing and unpacking packets is slightly slower than before, as each character in each string needs to be converted to the correct type, but in my tests any additional latency was barely noticeable.

Right now you can actually switch between unicode and ASCII strings (and even reimplement the string encoding/decoding stuff) by giving pyinsim a new StrFactory object. There are two default StrFactory objects in pyinsim, AsciiStrFactory and UniStrFactory, and you can change between them like this:
import pyinsim

# Set the string factory to ASCII.
pyinsim.str_factory = pyinsim.AsciiStrFactory()

# Set the string factory to Unicode.
pyinsim.str_factory = pyinsim.UniStrFactory()

# TODO: Write normal pyinsim code here.

By default the library uses the UniStrFactory() to handle strings, and this is intended to be the 'proper' way to handle strings from now on. You shouldn't need to think about all this, as I said it should work without you needing to worry about it. I have a few ideas for how to speed up the string conversions in the future, but right now I'm going with the simple way of doing things. All this StrFactory stuff is kinda interesting, but I don't know if I'll keep it around in the final version. I reminded of the quote about how any choice you give the consumer of your API is a choice you were unwilling to make yourself.

I have also made the API in general a little more verbose, as finally I think that a combination of succinctness and verbosity is the best balance. Too much of the former makes the library hard to understand, too much of the later makes it hard to program against. In pyinsim 2.0 I tried the make the API as succinct as possible, but in retrospect that was a mistake, and a lot of the functions ended up with confusing names. I have now changed how you init the InSim system, and also in how you bind events, as well as some other stuff in order to correct this.

Instead of explaining every change here is a quick example of some new pyinsim 3.0.0 code. This example is of a simple 'sniffer' style app, that dumps the contents of each packet received to the console (very useful for debugging).

#!/usr/bin/env python
#coding=utf-8

# Import pyinsim package.
import pyinsim

# Function called whenever a packet is received.
def packet(insim, pack):
# Dump packet contents to output.
print(vars(pack))

# Initialize InSim.
insim = pyinsim.insim_init('127.0.0.1', 29999, Admin='')

# Bind function callback for all packet events.
insim.bind_event(pyinsim.EVT_PACKET, packet)

# Send MST packet to LFS.
insim.send(pyinsim.ISP_MST, Msg='Hello, InSim!')

# Run the pyinsim event loop.
pyinsim.run()

As you can see the insim_init function is new, as well as the bind_event function (their related functions (e.g. outsim_init, bind_event etc..) have been renamed as well). Hopefully it should all be self-explanatory really, and you should look at the dev source code (mainly core.py and helper.py) to see the changes in full. I will of course try to document every change before the final version, but currently everything is still in flux. The library works much as it did before, it's just that some of the function and class names are slightly different. Better. Better?

There are countless improvements to the code overall, most of the library has been rewritten, and I've figured out better ways to handle lots of stuff such as the write/read socket code. Even though this new version of pyinsim does more (such as decoding unicode strings on the fly!), it is much more efficient in other areas. That being said, every change is also an opportunity for a new bug to creep in, so I anticipate lots of issues and errors to find.

Anyway, if you're an experienced pyinsim programmer, feel free to give it a try and come back to me with your comments and suggestions.

Thanks!
Been working a little more on pyinsim 3.0.0 and I added non-threaded timers to the library, which is something I'd been meaning to add for a while. These are timers that get executed from the main packet handling loop, so you don't need to worry about any threading or synchronisation issues. This is a big help if you're trying to keep your program limited to a single thread.

Creating a timer is very simple.

import pyinsim

# Function called when the timer elapses.
def timer_elapsed(insim):
print('elapsed!')

insim = pyinsim.insim_init('127.0.0.1', 29999, Admin='')

# Set a timer to elapse once after 10 seconds.
insim.timer(10.0, timer_elapsed, repeat=False)

pyinsim.run()

Setting repeat to False causes the timer to elapse once and then dispose of itself, setting repeat to True causes the timer to keep elapsing until the connection is closed.

I use the select() timeout to handle the timers internally, which means that the timers will not start executing until after the call to pyinsim.run() has been made. It also meant that if you set a timer while the select function was already blocking, the timer wouldn't start executing until after the next packet had been received (and the select call unblocked). To get around this I made it so if you add a timer while select is blocking it will ping LFS (TINY_PING) to get some data sent back and make select return. It's pretty hacky but it seems to work. I can't find a way to force select to unblock, so until I find a neater way to handle things it will have to do.
Hi. First thank you for all your efforts, Pyinsim is a great tool. Regarding pyinsim 3.0 and the automatic encoding. I often work with playernames that have latin characters like "ñ", "á", "é", etc. so I wonder if this automatic encode will affect me in any way.

The timer is a cool feature as well. Currently I use a class that inherits Thread to create several objects than run processes as daemon threads. I found that I needed to be able to run once or repeat the process, pause it, kill it, pass arguments to the referred function (as a list), etc.

It's nice to see that pyinsim development is active. I'll do some testings on this new version. Cheers.
Quote from jason_lfs :Hi. First thank you for all your efforts, Pyinsim is a great tool. Regarding pyinsim 3.0 and the automatic encoding. I often work with playernames that have latin characters like "ñ", "á", "é", etc. so I wonder if this automatic encode will affect me in any way.

It shouldn't affect you. All it means is that instead of strings containing mixed character encodings (separated by ^L, ^G etc..), all strings are now just unicode. In fact it should make handling strings in pyinsim simpler.

Quote :The timer is a cool feature as well. Currently I use a class that inherits Thread to create several objects than run processes as daemon threads. I found that I needed to be able to run once or repeat the process, pause it, kill it, pass arguments to the referred function (as a list), etc.

You can set the timers to run once or to repeat continuously, you can also stop timers, and pass them arguments. There is no way to pause a timer currently, I'll see about adding one.

You can set a timer to repeat by setting Repeat=True parameter. The timer function returns a timer object that you can use to stop the timer later. To stop a timer you need to call the timer_stop function with the timer obj as a parameter. This is something I will probably change in the future, as it's all a bit clunky.

# Store obj reference returned from method.
t = insim.timer(10, elapse_callback, repeat=True)

# Tell pyinsim to stop the timer.
pyinsim.timer_stop(t)

You can also pass arguments using the args parameter when creating a timer. These args get passed to the callback function when the timer elapses, along with a reference to the InSim object that the timer was set on.

def timer_elapsed(insim, arg1, arg2, arg3):
pass

# Set args to be passed to timer callback.
insim.timer(10.0, timer_elapsed, repeat=False, args=[arg1, arg2, arg3])

Right now the timers are a work in progress, as are most things in the library, so I will probably need to work on them some more before all the various uses are covered. I can already think of several problems with the way they are currently implemented that I will need to address.
I started thinking about the timers and decided that I was handling them the wrong way. I realised the simplest thing to do was to just make them behave exactly like the existing Python threading.Timer class, except obviously with a different underlying implementation. I've changed it so that you now create timers exactly like you used to, except instead of creating a threading.Timer object, you create a pyinsim.Timer.

import pyinsim

# Function called when timer elapses.
def timer_elapsed():
print('Timer elapsed')

insim = pyinsim.insim_init('127.0.0.1', 29999, Admin='')

# Create a timer object set to elapse every 10 seconds.
timer = pyinsim.Timer(10.0, timer_elapsed, repeat=True)

# Start the timer.
timer.start()

pyinsim.run()

As you can see you just create a new instance of the pyinsim.Timer class and call start. You can also stop the timer by calling the pyinsim.Timer.stop() method. The Timer constructor allows you to set *args and **kwargs that get passed to the callback function as paramaters, just like the normal threading.Timer, so you can do stuff like this:


def timer_elapsed(arg1, arg2):
pass

timer = pyinsim.Timer(10.0, timer_elapsed, True, arg1='hello', arg2='world')
timer.start()

etc..

This seems like a much more sensible way to handle the timers than the way I was doing it before.

I've pushed these new changes onto the BitBucket repository.
I like the changes so far. pyinsim is easier to use than ever

Has anyone tried adding autocross objects? I just got wall of text that ended with this:
[COLOR="Red"]Traceback (most recent call last):
File "C:\Python32\lib\asyncore.py", line 83, in read
obj.handle_read_event()
File "C:\Python32\lib\asyncore.py", line 444, in handle_read_event
self.handle_read()
File "C:\Python32\lib\site-packages\pyinsim\core.py", line 312, in handle_read
self.dispatch_to.handle_read(self, data)
File "C:\Python32\lib\site-packages\pyinsim\core.py", line 480, in handle_read
self.raise_packet_event(packet_type, data)
File "C:\Python32\lib\site-packages\pyinsim\core.py", line 401, in raise_packet_event
[c(self, packet) for c in callbacks]
File "C:\Python32\lib\site-packages\pyinsim\core.py", line 401, in <listcomp>
[c(self, packet) for c in callbacks]
File "C:\Users\Kevin\projects\Live For Speed\testing\place_point.py", line 72, in Multi_car_info
Place_cone(h.X, h.Y, h.Z, h.Heading, players[h.PLID].UCID)
File "C:\Users\Kevin\projects\Live For Speed\testing\place_point.py", line 66, in Place_cone
insim.send_packet(pyinsim.IS_AXM(UCID = UCID, PMOAction=1, Info=[oi]))
File "C:\Python32\lib\site-packages\pyinsim\core.py", line 436, in send_packet
self.tcp_socket.send(packet.pack())
File "C:\Python32\lib\site-packages\pyinsim\insim.py", line 1254, in pack
return self.s.pack(self.Size + (self.NumO * 8), self.Type, self.ReqI, self.NumO, self.UCID, self.PMOAction, self.PMOFlags) + b''.join([info.pack() for info in self.Info])
File "C:\Python32\lib\site-packages\pyinsim\insim.py", line 1254, in <listcomp>
return self.s.pack(self.Size + (self.NumO * 8), self.Type, self.ReqI, self.NumO, self.UCID, self.PMOAction, self.PMOFlags) + b''.join([info.pack() for info in self.Info])
File "C:\Python32\lib\site-packages\pyinsim\insim.py", line 1230, in pack
return self.s.pack(self.X, self.Y, self.Zchar, self.Flags, self.Index, self.Heading)
struct.error: short format requires SHRT_MIN <= number <= SHRT_MAX[/COLOR]

Here's what I did to get that:
def Place_cone(x, y, z, heading, UCID):
oi = pyinsim.ObjectInfo(x, y, 4, 0, 20, 0)
insim.send_packet(pyinsim.IS_AXM(UCID = UCID, PMOAction=1, Info=[oi]))
print('sent an axm')

def Multi_car_info(insim, MCI):
if MCI.ReqI == 17:
h = MCI.Info[0]
Place_cone(h.X, h.Y, h.Z, h.Heading, players[h.PLID].UCID)
print('sent packet')
else:
print('mci')

OK - I hadn't thought about this before, but after some investigation I've figured it out. The problem is that you are copying the CompCar.X and CompCar.Y variables straight into the ObjectInfo.X and ObjectInfo.Y variables, but cars and objects in LFS represent their coordinates in different ways.

If you look in InSim.txt you see that the X and Y coords in CompCar are stored as 32bit ints and you need to divide them by 65536 to convert them into metres:

int X; // X map (65536 = 1 metre)
int Y; // Y map (65536 = 1 metre)

Whereas if you look at LYT.txt you see that the X and Y coords of ObjectInfos are stored as 16bit shorts which need to be divided by 16 to convert them into metres:

1 short 0 X : position (1 metre = 16)
1 short 2 Y : position (1 metre = 16)

So basically you need to first convert the CompCar X and Y coords to metres by dividing by 65536, then convert the metres to object coords by multiplying by 16.

In python code that would look like this:

# Convert car coords to metres
carXMetres = car.X / 65536
carYMetres = car.Y / 65536

# Convert metres to object coords
obj.X = carXMetres * 16
obj.Y = carYMetres * 16

# LFS expects the coords to be ints.
obj.X = int(obj.X)
obj.Y = int(obj.Y)

To incorporate them into your code example you could do something like this. Bare in mind that I've not tested this, but I think it should be correct.

def Place_cone(x, y, z, heading, UCID):
# Convert LFS coords to metres then convert the metres to
# object coords.
x = int((x / 65536) * 16)
y = int((y / 65536) * 16)

oi = pyinsim.ObjectInfo(x, y, 4, 0, 20, 0)
insim.send_packet(pyinsim.IS_AXM(UCID = UCID, PMOAction=1, Info=[oi]))
print('sent an axm')

def Multi_car_info(insim, MCI):
if MCI.ReqI == 17:
h = MCI.Info[0]
Place_cone(h.X, h.Y, h.Z, h.Heading, players[h.PLID].UCID)
print('sent packet')
else:
print('mci')

Hopefully that should work! Also, I hope that makes sense!

Edit: Note - you may also need to convert the heading, look at lyt.txt and insim.txt to see how to do that.
Do I have to update python too to get the new version working?

Now my python is saying that:
Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
Quote from martzz :Do I have to update python too to get the new version working?

Now my python is saying that:
Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56)
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.

Yes, the new version of pyinsim currently only supports Python 3.0. I don't know how it works on Linux, on Windows you can install Python 3.0 and Python 2.7 side-by-side. I will eventually back-port the new version to Python 2.7, but I don't know when exactly.

On Windows just install Python 3.0 and then rename 'c:\python32\python.exe' to 'c:\python32\python3.exe', then you can choose between Python versions by typing either 'python' or 'python3' at the command line.
Thanks for the help before. Turns out I had made another dumb mistake or two.

I really like the new version of pyinsim, so thanks for putting it up. :banana:
Is it just me, or is there no IS_NONE class in insim.py?

It'd be nifty as LFS sends a NONE packet when users update their controls. Actually, only when a player is in the player list and updates their controls.

Do you guys think it'd be a good idea to suggest a 'player changed controls' packet in the improvement suggestions forum?
Hi. This is a small script I did to parse PTH files on Python. It's based on Dygear's PHP implementation (http://www.lfsforum.net/showth ... t=71343&highlight=pth).


<?php 
import struct

def parse_pth
(file):
    print 
"Parsing ",file
    f
=open(file,"rb")
    
    
f.seek(6)
    
version,rev=struct.unpack('bb',f.read(2))
    
f.seek(8)
    
total_nodes,finish_line=struct.unpack('ll',f.read(8))
    print 
"Version: ",version" Revision: ",rev"Total nodes: ",total_nodes" Finish line:"finish_line
    nodes
=[]

    for 
i in range(0,total_nodes):
        
f.seek(16+40*i)
        
bn=f.read(40)
        
nodes.append({"center"struct.unpack('lll',bn[0:12]),"direction"struct.unpack('fff',bn[12:24]),"limit":struct.unpack('ff',bn[24:32]),"road"struct.unpack('ff',bn[32:40])})
    
    
f.close()
    return (
nodes,total_nodes,finish_line,version,rev)

#Example Usage
(nodes,total_nodes,finish_line,version,rev)=parse_pth("./pth/BL1.pth")

for 
id,node in enumerate(nodes):
    print 
"Node:",id", center (x,y,z):"node["center"], ", direction (x,y,z):",node["direction"], ", limits: ",node["limit"], ", road limit: ",node["road"

?>

Quote from Mountaindewzilla :Is it just me, or is there no IS_NONE class in insim.py?

It'd be nifty as LFS sends a NONE packet when users update their controls. Actually, only when a player is in the player list and updates their controls.

Do you guys think it'd be a good idea to suggest a 'player changed controls' packet in the improvement suggestions forum?

InSim does not have an IS_NONE packet. In fact, I'm struggling to even conceive of how such a packet would look. What is it exactly that LFS sends when the user updates their controls?
The TINY_NONE packet is a keep alive packet. What he is looking for is the ISP_PFL packet, for player flags.
Quote from Dygear :What he is looking for is the ISP_PFL packet, for player flags.

Ah, OK. Obviously pyinsim supports IS_PFL.

def player_flags(insim, packet):
pass

insim.bind(ISP_PFL, player_flags)

Quote from DarkTimes :InSim does not have an IS_NONE packet. In fact, I'm struggling to even conceive of how such a packet would look. What is it exactly that LFS sends when the user updates their controls?

Pic related.

Thanks for the tip, guys.
Attached images
heh.png
Quote from Mountaindewzilla :Pic related.

Thanks for the tip, guys.

Ah, that's a bug. If you look carefully you'll notice that the Size is listed a zero as well. The Size, Type and ReqI of the IS_PFL packet was not being set, so it was displaying the default values. The default value for Type is ISP_NONE.

I've fixed the error and pushed InSimSniffer 1.3.1 onto CodePlex.

FGED GREDG RDFGDR GSFDG