The online racing simulator
Python - pyinsim - Python InSim library
(142 posts, started )
Python - pyinsim - Python InSim library
pyinsim is a InSim library for the Python programming language. It allows you to create a socket connection with the game and to send and receive packets of data. These packets can be used to control LFS, to request information and to send various commands. pyinsim provides a high-level abstraction for dealing with InSim, to save you from having to deal with the nitty-gritty of working with sockets directly.

Here are some quick links:What's New?

pyinsim 2.0.0 has a bunch of new features!
  • A new home on GitHub!
  • Improved and more efficient networking code
  • Better support for managing multiple hosts
  • Improved API
CodePlex

As mentioned, pyinsim now has an official project page on GitHub, and this is where all future development will take place. It will also be the place to go to download the library and will always have the latest release and revision of the source code. You can find our new home at:

https://github.com/alexmcbride/pyinsim

Download

You can always download the latest version of pyinsim from its GitHub homepage.

https://github.com/alexmcbride/pyinsim
Reserved
Uploaded pyinsim 2.0.0 beta 2 to CodePlex with a bug fix and a couple of small changes.
I'd just like to say:
I love you! Downloading to try it out
Cool thanks.

It should be pretty stable at the moment, but a few thing are still incomplete, mainly the documentation, also there are a couple of bugs I need to work out. If you find any bugs or issues please assign them to me using the Issue Tracker.

Cheers
OK - I take back that part about it being stable.

I've released beta 3 to CodePlex. There was a bug that was making pyinsim use 100% CPU which I've now fixed. I also fixed the issue with EVT_INIT not being raised and an issue affecting disconnecting from hosts.
It's like executable pseudocode for InSim.
Yeah, Python has a tendency to look like pseudocode.

Anyway, pushed beta 4 out to release. I rewrote the UDP timeout code, which has broken the API a little. You now specifiy a timeout when initializing the connection, so OutGauge now works like this:

import pyinsim

def outgauge_handler(og, packet):
pass # Do something with packet.

# Set timeout to 30 seconds.
pyinsim.outgauge('127.0.0.1', 30000, outgauge_handler, 30.0)

pyinsim.run()

Also fixed a small but significant bug in the tounicode() string function.
Here is a rough example of a program that tries to reconnect to InSim every 5 seconds, posted in response to this request.

import pyinsim
import threading

class Reconnector(object):
def __init__(self, address, delay):
self.delay = delay
self.address = address
self.do_init()

def do_init(self):
print 'Trying to connect'
self.insim = pyinsim.insim(self.address[0], self.address[1])
self.insim.bind(pyinsim.EVT_INIT, self.init)
self.insim.bind(pyinsim.EVT_CLOSE, self.close)
pyinsim.run()

def init(self, insim):
print 'Connected to InSim!'

def close(self, insim):
print 'Not connected, starting timer'
threading.Timer(self.delay, self.do_init).start()

if __name__ == '__main__':
Reconnector(('127.0.0.1', 29999), delay=5.0)

Pushed beta 5 to release. Only a small bug fix with using the background=True parameter of the run function. Backgound is useful when using pyinsim in a GUI app, as it makes the socket code run on a background thread so it doesn't block the message pump.

I think the next release of the library will be final.
Pushed pyinsim 2.0.0 final to release, which you can find on CodePlex.
-
(DarkTimes) DELETED by DarkTimes
I've noticed some people are still downloading the old version of the library. The old < 2.0 version has been left for archive purposes only. For all new pyinsim programs you should use the most recent stable version, which is always available on CodePlex. The only reason you would ever need to download the old version of library is if you need to work with legacy code.

To summarise: do not download the old version of pyinsim unless you have a very good reason to. In almost all cases the latest stable release is what you want.
It would be amazing if you could port your cruise example from 1.6.4 to 2.0 pretty please I made a half-a**ed attempt at doing so, and it has failed miserably so far.

Edit: Through my various edits, commenting things out, and whatever else I could think of to try to get the code to work, the issue seems to keep on returning to the player management part of the code.
OK - I've updated the cruise script example. I found a bug with the UDP connection when using InSim and pushed a fix onto the CodePlex repository. You may need to get the latest source revision if you want to use a separate port for position updates at the moment. I have removed the UDPPort setting from the cruise example until I can get round to creating a proper release.

Anyway here is the cruise script updated for pyinsim 2.0. I also made a couple of tweaks to the way commands work to make them simpler. You will need to create a subdirectory called 'users' in the script folder, which is where it stores user info.

# Make sure correct version of pyinsim is imported.
VERSION = '2.0.0'
try:
import pyinsim
if not pyinsim.version(VERSION):
raise ImportError
except ImportError:
print 'You must install pyinsim %s or better' % VERSION
import sys
sys.exit(1)

# Dependencies.
import cPickle
import os
import threading

# Constants.
HOST = '127.0.0.1'
PORT = 29999
ADMIN = ''
PREFIX = '!'
UDPPORT = 30000
INTERVAL = 1000 # Milliseconds
PROG_NAME = 'PyCruise'
CASH_MULTIPLIER = 2 # Dollars per second
STARTING_CARS = ['UF1',]
STARTING_CASH = 10000 # Dollars
HEARTBEAT_INTERVAL = 1 # Seconds
USER_DIR = 'users'
HOST_ID = 0
SPEED_DEADZONE = 10
CAR_PRICES = {'XFG': 4500, 'XRG': 6000, 'FBM': 150000, 'XRT': 14000, 'RB4': 12000,
'FXO': 12000, 'LX4': 15000, 'LX6': 25000, 'MRT': 30000,'UF1': 3000,
'RAC': 30000, 'FZ5': 38000, 'XFR': 50000, 'UFR': 45000, 'FOX': 150000,
'FO8': 165000, 'BF1': 350000, 'FXR': 120000, 'XRR': 120000, 'FZR': 130000}
WELCOME_MSG = '''Welcome to %s
For help type ^3!help
Website: www.pyinsim.codeplex.com
Good luck and behave yourself!''' % PROG_NAME
ADMIN_USERNAMES = ('DarkTimes',) # Set list of admins LFSWorld usernames.

# Global variables.
connections = {}
players = {}

# Class to store user info.
class UserVars:
def __init__(self):
self.dist = 0
self.cash = STARTING_CASH
self.cars = STARTING_CARS

# Draw on-screen display.
def draw_osd(insim, ncn):
insim.send(pyinsim.ISP_BTN, ReqI=1, UCID=ncn.UCID, ClickID=1,
BStyle=pyinsim.ISB_DARK | 3, T=4, L=85, W=30, H=6,
Text='Cash: $%d | Distance: %.2f Km' % (ncn.vars.cash, ncn.vars.dist))

# Called every second to update cash and OSD.
def heartbeat(insim):
for ncn in connections.values():
if ncn.UCID != HOST_ID:
if ncn.speed > SPEED_DEADZONE:
ncn.vars.cash += CASH_MULTIPLIER
draw_osd(insim, ncn)
if insim.connected:
threading.Timer(HEARTBEAT_INTERVAL, heartbeat, [insim]).start()

# Load user info from file.
def load_user_vars(uname):
path = os.path.join(USER_DIR, uname)
if os.path.exists(path):
try:
with open(path, 'r') as f:
return cPickle.load(f)
except IOError as err:
print 'Load Error:', err
return UserVars() # Default

# Save user info to file.
def save_user_vars(uname, vars):
path = os.path.join(USER_DIR, uname)
try:
with open(path, 'w') as f:
cPickle.dump(vars, f)
except IOError as err:
print 'Save Error:', err

# Request all connections and players to be sent.
def req_conns(insim):
insim.send(pyinsim.ISP_TINY, ReqI=1, SubT=pyinsim.TINY_NCN)
insim.send(pyinsim.ISP_TINY, ReqI=1, SubT=pyinsim.TINY_NPL)


# New connection joined host
def new_conn(insim, ncn):
if ncn.UCID != HOST_ID:
ncn.vars = load_user_vars(ncn.UName)
ncn.speed = 0.0
ncn.last_pos = [0,0,0]
ncn.current_car = None
connections[ncn.UCID] = ncn
if ncn.UCID != HOST_ID:
for line in WELCOME_MSG.splitlines():
insim.sendm('^3| ^7%s' % line, ucid=ncn.UCID)

# Connection left host.
def conn_left(insim, cnl):
if cnl.UCID != HOST_ID:
ncn = connections[cnl.UCID]
save_user_vars(ncn.UName, ncn.vars)
del connections[cnl.UCID]

# Player tries to join track.
def new_ply(insim, npl):
players[npl.PLID] = npl
ncn = connections[npl.UCID]
if npl.CName in ncn.vars.cars:
ncn.current_car = npl.CName
else:
insim.sendm('/spec %s' % ncn.UName)
insim.sendm('^3| ^7You do not own the %s' % npl.CName, ncn.UCID)
insim.sendm('^3| ^7Type ^3!cars ^7to see which cars you own', ncn.UCID)

# Player leaves track.
def ply_left(insim, pll):
npl = players[pll.PLID]
ncn = connections[npl.UCID]
ncn.vars.current_car = None
del players[pll.PLID]


# Print out car prices ordered by price.
def cmd_prices(insim, ncn, args):
cars = CAR_PRICES.items()
cars.sort(key=lambda c: c[1])
insim.sendm('^3| ^7Car Prices:', ncn.UCID)
for car, price in cars:
insim.sendm('^3| ^7%s: $%d' % (car, price), ncn.UCID)

# Buy a new car.
def cmd_buy(insim, ncn, args):
if args:
car = args[0].upper()
if car in ncn.vars.cars:
insim.sendm('^3| ^7You already own the %s' % car, ncn.UCID)
elif car not in CAR_PRICES:
insim.sendm('^3| ^7The car %s does not exist' % car, ncn.UCID)
elif CAR_PRICES[car] > ncn.vars.cash:
insim.sendm('^3| ^7You need $%d more cash to afford the %s' % (CAR_PRICES[car] - ncn.vars.cash, car), ncn.UCID)
else:
ncn.vars.cash -= CAR_PRICES[car]
ncn.vars.cars.append(car)
insim.sendm('^3| ^7You bought the %s for $%d' % (car, CAR_PRICES[car]), ncn.UCID)
insim.sendm('^3| ^8%s ^7bought the %s!' % (ncn.PName, car))
else:
insim.sendm('^3| ^7Usage: !buy <car>', ncn.UCID)

# Sell an owned car.
def cmd_sell(insim, ncn, args):
if args:
car = args[0].upper()
if car not in CAR_PRICES:
insim.sendm('^3| ^7The car %s does not exist' % car, ncn.UCID)
elif car not in ncn.vars.cars:
insim.sendm('^3| ^7You do not own the %s' % car, ncn.UCID)
else:
ncn.vars.cash += CAR_PRICES[car]
ncn.vars.cars.remove(car)
insim.sendm('^3| ^7You have sold the %s for $%d' % (car, CAR_PRICES[car]), ncn.UCID)
insim.sendm('^3| ^8%s ^7sold the %s' % (ncn.PName, car))
if car == ncn.current_car:
insim.sendm('/spec %s' % ncn.UName)
insim.sendm('^3| ^7You no longer own the %s' % car, ncn.UCID)
else:
insim.sendm('^3| ^7Usage: !sell <car>', ncn.UCID)

# Print list of owned cars ordered by price.
def cmd_cars(insim, ncn, args):
cars = [(c, CAR_PRICES[c]) for c in ncn.vars.cars]
cars.sort(key=lambda c: c[1])
insim.sendm('^3| ^7Currently Owned Cars:', ncn.UCID)
for car, price in cars:
insim.sendm('^3| ^7%s: $%d' %(car, price), ncn.UCID)

def cmd_location(insim, ncn, args):
if ncn.current_car:
x = pyinsim.length(ncn.last_pos[0])
y = pyinsim.length(ncn.last_pos[1])
z = pyinsim.length(ncn.last_pos[2])
insim.sendm('^3| ^7X: %d Y: %d Z: %d' % (x, y, z), ncn.UCID)
else:
insim.sendm('^3| ^7You are not currently in a car', ncn.UCID)

def cmd_save(insim, ncn, args):
if ncn.UName in ADMIN_USERNAMES:
[save_user_vars(n.UName, n.vars) for n in connections.values() if n.UCID]
insim.sendm('^3| ^7The users have been saved', ncn.UCID)
else:
insim.sendm('^3| ^7You are not an admin', ncn.UCID)

# Print usage info.
def cmd_help(insim, ncn, args):
insim.sendm('^3| ^7Usage Info:', ncn.UCID)
insim.sendm('^3| !help ^7- Show this message', ncn.UCID)
insim.sendm('^3| !prices ^7- View car prices', ncn.UCID)
insim.sendm('^3| !cars ^7- See what cars you currently own', ncn.UCID)
insim.sendm('^3| !buy <car> ^7- Buy a new car', ncn.UCID)
insim.sendm('^3| !sell <car> ^7- Sell an owned car', ncn.UCID)
insim.sendm('^3| !location ^7- See your coordinates')

CMD_LOOKUP = {'!prices': cmd_prices, '!buy': cmd_buy, '!sell': cmd_sell,
'!cars': cmd_cars, '!help': cmd_help, '!loc': cmd_location,
'!save': cmd_save}

# Handle command message from LFS.
def message_out(insim, mso):
if mso.UserType == pyinsim.MSO_PREFIX:
args = mso.Msg[mso.TextStart:].split()
if args:
cmd = args[0].lower()
if cmd in CMD_LOOKUP:
ncn = connections[mso.UCID]
CMD_LOOKUP[cmd](insim, ncn, args[1:])
else:
insim.sendm('^3| ^7Unknown command \'^3%s^7\'' % cmd, mso.UCID)

# Calculate distance travelled.
def add_distance(ncn, car):
curr_pos = (car.X, car.Y, car.Z)
if ncn.last_pos and car.Speed > SPEED_DEADZONE:
dist = pyinsim.dist(ncn.last_pos, curr_pos)
ncn.vars.dist += pyinsim.length(dist) / 1000 # Km
ncn.last_pos = curr_pos

# Player MCI updates.
def car_info(insim, mci):
for car in mci.Info:
npl = players.get(car.PLID)
if npl:
ncn = connections[npl.UCID]
ncn.speed = car.Speed
add_distance(ncn, car)

# Save user vars if connection is lost.
def closed(insim):
for ncn in connections.values():
if ncn.UCID != HOST_ID:
save_user_vars(ncn.UName, ncn)

def init(insim):
print '%s is running!' % PROG_NAME
insim.sendm('/canreset no')
insim.sendm('/cruise yes')
insim.sendm('/laps 0')

if __name__ == '__main__':
print '%s' % PROG_NAME
print ''.rjust(len(PROG_NAME), '-')
print
print 'Starting cruise server...'

# Initialize InSim and bind events.
insim = pyinsim.insim(HOST, PORT, IName='^7%s' % PROG_NAME, Prefix=PREFIX,
Flags=pyinsim.ISF_MCI, Interval=INTERVAL, Admin=ADMIN, UDPPort=UDPPORT)
insim.bind(pyinsim.ISP_NCN, new_conn)
insim.bind(pyinsim.ISP_CNL, conn_left)
insim.bind(pyinsim.ISP_NPL, new_ply)
insim.bind(pyinsim.ISP_PLL, ply_left)
insim.bind(pyinsim.ISP_MSO, message_out)
insim.bind(pyinsim.ISP_MCI, car_info)
insim.bind(pyinsim.EVT_INIT, init)
insim.bind(pyinsim.EVT_CLOSE, closed)

# Request players and connections.
req_conns(insim)

# Start heartbeat timer.
threading.Timer(HEARTBEAT_INTERVAL, heartbeat, [insim]).start()

# Run pyinsim!
pyinsim.run()

print 'Server exiting as no hosts connected'

DarkTimes, I love you! <3 Thank you so much!

Edit: Getting an error when trying to run it. Python is saying that __file__ isnt defined in the line:
CURRENT_DIR = os.path.dirname(__file__)

I googled a little bit, and then ended up changing it to os.__file__, but that isn't what I wanted :P It works, but its a half-a**ed repair job :P
How are you executing the script? The __file__ constant won't be available unless you are importing the script as a module (either with the import statement of running it with 'python cruise.py' etc..).

The CURRENT_DIR is just used to get the absolute path of the USER_DIR on disk, so you can delete CURRENT_DIR and set the USER_DIR manually.

# Relative path
USER_DIR = 'users'

# Absolute path
USER_DIR = 'x:\\absolute\\path\\to\\users'

Hi ho,

In the other thread you gave this button example:

Quote from DarkTimes :

import pyinsim

insim = pyinsim.insim('127.0.0.1', 29999, Admin='', IName='Example')

insim.send(pyinsim.ISP_BTN,
ReqI=255,
ClickID=1,
[b]BStyle=pyinsim.ISB_CLICK | pyinsim.ISB_LIGHT | 5[/b],
T=60,
L=10,
W=40,
H=10,
Text='Button Example')

pyinsim.run()


How do you plonk the ISF_LOCAL flag in it?
ISF_LOCAL is part of the Flags attributes in the ISI packet, which you set when initializing the InSim connection.

import pyinsim

insim = pyinsim.insim('127.0.0.1', 29999, Admin='',
IName='Example', [b]Flags=pyinsim.ISF_LOCAL[/b])

insim.send(pyinsim.ISP_BTN,
ReqI=255,
ClickID=1,
BStyle=pyinsim.ISB_CLICK | pyinsim.ISB_LIGHT | 5,
T=60,
L=10,
W=40,
H=10,
Text='Button Example')

pyinsim.run()

-
(Dygear) DELETED by Dygear : Must Read WHOLE thread before posting.

I'm getting an error with the cruise example you so graciously updated
Traceback (most recent call last):
File "C:\Python27\lib\asyncore.py", line 79, in read
obj.handle_read_event()
File "C:\Python27\lib\asyncore.py", line 435, in handle_read_event
self.handle_read()
File "C:\Python27\lib\site-packages\pyinsim\core.py", line 255, in handle_read
self._dispatch_to._handle_tcp(self._recv_buff[:size])
File "C:\Python27\lib\site-packages\pyinsim\core.py", line 462, in _handle_tcp
[callback(self, packet) for callback in bound]
File "C:/Users/Jonathan/Desktop/Pyinsim New/examples/default cruise.py", line 189, in message_out
insim.sendm('^3| ^7Unknown command', ncn.UCID)
UnboundLocalError: local variable 'ncn' referenced before assignment
Server exiting as no hosts connected

Any time I try to use an ! code, that's what Python tells me. That's from the just the converted script, no modifications.


EDIT: I've moved
ncn = connections[mso.UCID]

from
if cmd in CMD_LOOKUP:

to after
args = mso.Msg[mso.TextStart:].split()

and now I'm just getting unknown command every time, no matter if the code exists or not.
Sorry, I think the mistake I made is on this line

insim.sendm('^3| ^7Unknown command', ncn.UCID)

which should be

insim.sendm('^3| ^7Unknown command', [b]mso[/b].UCID)

Edit: disregard that stuff.



still hasn't solved the "Unknown command" problem.
I've updated the example which should fix it now.
Nice release! I started programming with Python and i like it. But i have some questions about it. How i can handle CompCar for all players (more than 8)?
Each MCI packet can only hold a maximum of eight cars. If there are more than eight in a race then multiple MCI packets are sent. For instance if there are ten players online, then two packets will be sent, the first with eight CompCars and the second with two. You can check the Info flags on the CompCar to see which is the first and last car across the packets.

def car_info(insim, mci):
for car in mci.Info:
if car.Info & pyinsim.CCI_FIRST:
print 'first car'
elif car.Info & pyinsim.CCI_LAST:
print 'last car'

Incidentally this is nothing to do with pyinsim, it's the way that InSim works.
I pushed version 2.0.1 out to release, which contains a single small bug fix to the UDPPort attribute when initializing InSim.

As always you can find the latest release on CodePlex.

Python - pyinsim - Python InSim library
(142 posts, started )
FGED GREDG RDFGDR GSFDG