This thread has been left for archive purposes only. Please note: this version of pyinsim is no longer supported and most of the code in this thread will not work with this or later versions! The only reason to download this is if you need to work with legacy code. Otherwise you should download the latest stable release, which you can download [url=http://pyinsim.codeplex.com/releases/[/url]!
Very nice work DarkTimes - lots of whitespace lovers with abnormal tab key fingers will be very happy The API seems straight forward, and complements that provided by LFS_External nicely.
Any objections if I move this into the libraries subforum, or are you not convinced that this is "finished"?
I considered putting it in there, but really it's not finished yet, so I don't think it's ready for that.
Hehe, awesome.
I would be very grateful for any input on how the actual library has been put together, how the packets are assembled etc.. I spent a lot of time going back and forwards between different designs, but I think the method I ended up with does work quite well. There's a fair amount of spaghetti code inside the packet class that needs sorting.
At the moment it sort of stands both as 'half-finished' and as a 'proof-of-concept'. It needs a lot of work to clean everything up.
I have some development tools I can post too, such as a set of functions which will parse InSim.txt to create the various packet definition lists etc..
Anyway... noob that I am I somehow managed to mess up the tab spacing in the version I posted (really smart with Python, of all languages lol), so I've uploaded a new version with the correct tab spacing.
Looks good from here, I had a quick scan down the source, and I can't say I'd have done it much differently. Your enumerations may benefit from being done on one line for each section, say:
ENUM_1, ENUM_2, ENUM_3 = 0, 1, 2
And you should probably document your def's like this:
def bla(): """This is a document comment, with three double quotes"""
Just a couple of thoughts, I'll see if I get some time to look over it more fully later on
Yes, I need to do the docstrings. I've always been crap at documentation, and at the moment whole classes can be rewritten still (I re-wrote the InSim class earlier for instance), so it seems silly to document stuff that I'm not sure I'm going to keep.
The enums and the packet defs could be on one line, but I find it easier to read the way I've done it. Also I wrote a script that parses the data out of InSim.txt so I didn't have to write each enum and packet myself, and that was the easiest way to output the data.
I've updated it to finish the NLP packets. I renamed _MciPacket to _CarTrackPacket, as it now handles both MCI and NLP. It's not very neat, the whole MCI/NLP packet feels like a (big) bit of a hack really, but I wanted to add NLP as it was the only major thing missing for InSim, apart from one camera packet I've not spent time working out yet. Aside from that all packets, enums and flags should be done.
Tomorrow I will spend some time refactoring and cleaning up the packet code, as it is just very messy at the moment. Do they have an obfuscated Python contest?
I've been testing a few things and I can confirm that Pyinsim runs unmodified on IronPython for the .NET Framework. I've been messing around with getting it running on the Silverlight 2 beta with IronPython, and while it fundamentally works, the socket restrictions on the beta have just made it too hard to do anything useful without a herculean effort (you need to use the web server as a proxy and bounce the packets off it. It's not very fun). From what I've read I'm confident it will work with Silverlight 2 final, albeit with some serious modifications to the socket code, so hopefully I should be able to report more progress on Pyinsim for Sliverlight in the future.
Here is an example program which enforces rolling starts on a server. You can set the number of formation laps before the race, and if during that time a player breaks the speed-limit, or overtakes another car, they will be spectated. It also lets you configure the messages when are sent and other stuff like that.
"""Full example program. Enforces rolling starts on a host. If a player speeds or over-takes another car, then are spectated and sent a message."""
import pyinsim import time import threading import sys
# InSim Config. HOST = '127.0.0.1' PORT = 29999 ADMIN = 'pass' # Your admin password here.
# Constants. MCI_INTERVAL = 100 FORMATION_LAPS = 1 SPEED_LIMIT = 80 # Kph START_MSG = "^1Rolling start - Keep below %d Kph and don't overtake!" % SPEED_LIMIT GREEN_MSG = '^2Green flag! GO GO GO!' SPEEDING_MSG = '%s ^3spectated for speeding!' OVERTAKE_MSG = '%s ^3spectated for overtaking!' MSG_TIMEOUT = 10 # How many seconds messages stay on the screen.
# Player class. class Player: """Small class to handle player.""" def __init__(self, npl): self.pName = npl['PName'] # Player name. self.plid = npl['PLID'] self.position = 0 # Position in race.
def spectatePlayer(pName): """Spectate a player.""" sendMessage('/spec %s' % pName)
def resetPositions(): """Reset all player positions to zero.""" for player in players.values(): player.position = 0
def getPlayer(plid): """Get player from players dict.""" return players[plid]
def playerExists(plid): """Get if player exists in players dict.""" return plid in players
def requestPlayers(insim): """Request for all players to be sent.""" insim.send(pyinsim.ISP_TINY, SubT=pyinsim.TINY_NPL, ReqI=1)
# Events. def playerJoined(insim, npl): """Create new player object and add to players dict.""" players[npl['PLID']] = Player(npl)
def playerLeft(insim, pll): """ Remove player object from players dict.""" del players[pll['PLID']] # When a player leaves, any drivers behind them will be specced for # over-taking, so we need to reset all positions. resetPositions()
def raceStarted(insim, rst): """Race started. Request all players to be sent and begin formation lap.""" global onFormationLap
requestPlayers(insim) if rst['RaceLaps'] > 0: sendRcmAll(START_MSG, MSG_TIMEOUT) onFormationLap = True else: # A qual or practice session, no formation lap. onFormationLap = False
def carUpdate(insim, mci): """Car position update.""" global onFormationLap
for car in mci['CompCars']: if playerExists(car['PLID']) and onFormationLap: player = getPlayer(car['PLID'])
if car['Lap'] > FORMATION_LAPS: # Formation lap has ended. onFormationLap = False sendRcmAll(GREEN_MSG, MSG_TIMEOUT) elif pyinsim.speedToKph(car['Speed']) > SPEED_LIMIT: # Player is speeding. spectatePlayer(player.pName) sendMessage(SPEEDING_MSG % player.pName) elif player.position == 0: # Player has no position, set it. player.position = car['Position'] elif car['Position'] < player.position: # Player has over-taken another car. spectatePlayer(player.pName) sendMessage(OVERTAKE_MSG % player.pName)
def connectionLost(insim): """Event called when connection to InSim is lost.""" print 'InSim Error: The connection to InSim lost'
def threadError(insim, err): """Event called when there is an error on the internal receive thread.""" print 'InSim Error:', err
Pardon my ignorance - I'm not totally clued up on current InSim version details, etc. but where's the IS_REN packet in your source? I couldn't find it.
I have uploaded a new source-distribution to the first post, of version 0.1.3. There are a couple of quite subtle but important changes.
# The documentation has been cleaned up a lot!
# The Packet class has been completely rewritten and the code is much, much simpler. Packets now inherit directly from UserDict, meaning they now have all the same methods and properties as Python dictionaries. This means you can now iterate over packets like you can a normal dictionary.
In addition to this, the Packet class now contains a new constructor parameter called values, which allows you to specify a default dictionary for the packet. Basically that means you can create packets like this:
# A new method named SendP() has been added to the InSim class, which allows you to send a packet without having to call its Pack() method first.
insim.sendP(isi)
# Instead of the old...
insim.sendB(isi.pack())
Anyway, you can download the source-dist in the first post. These features were added to support a new Pyinsim module which I'm planning to release soon, which will make writing InSim apps easier than it currently is, but I felt these changes were useful enough on their own to warrant a separate release.
This is a really basic example, it will only work in a single-player game and if you're the only car, but it demonstrates the principle.
"""Example 3: Update a button in the local LFS client with your current speed in KPH."""
import pyinsim
# Constant for the speed button ClickID ID_SPEED_BUTTON = 1
# Helper for placing speed button on the game-screen. def sendSpeedButton(insim, speed=0): insim.send(pyinsim.ISP_BTN, ReqI=1, UCID=0, ClickID=ID_SPEED_BUTTON, BStyle=pyinsim.ISB_DARK, T=175, L=5, W=25, H=8, Text='%.3d Kph' % speed)
# Event called whenever a car is updated. def carUpdate(insim, mci): # Loop through each car. for car in mci['CompCars']: # Use pyinsim's built-in function to convert speed to KPH. speed = pyinsim.speedToKph(car['Speed']) # Update speed button. sendSpeedButton(insim, speed)
insim = pyinsim.InSim()
# Bind car update event-handler. insim.bind(pyinsim.ISP_MCI, carUpdate)
# Connect to LFS and initailise InSim. try: insim.connect('127.0.0.1', 29999)
# We set the ISF_MCI flag to tell LFS to send IS_MCI car position updates, and also the # Interval of how many milliseconds to wait between updates. insim.send(pyinsim.ISP_ISI, Admin='pass', IName='pyinsim', Flags=pyinsim.ISF_LOCAL | pyinsim.ISF_MCI, Interval=100)
I get that error too if the program closes right away, closing the network connection, before the ISI packet has been received by LFS. You need to force the program to stay running so that Pyinsim can keep the network connection open. That's what this code is suppose to do:
insim.run()
So first of all make sure you have that code somewhere before the end of your app. If you do, them I might need more info to solve the problem.
Please make sure you have 0.1.3 from the first post, which I uploaded last night. The example I posted is for that version. If I try it with the old 0.1.2 then I get exactly the same error you post. Please make sure you've updated.
Edit: The actual error is that SendP doesn't exist, as that wasn't added until 0.1.3. Because I'm just catching all exceptions, it's catching that one too, and printing the connection failed message. If you remove the Except exception catching block you'll see the real error message.