The online racing simulator
Quote from tmehlinger :Very cool, going to play with it tonight.

Any chance you pulled the InSim relay stuff in, or will I have to modify it?

You know what I forgot about InSim relay. Oh dear, I wonder what's wrong with my brain sometimes.... eh, maybe I shouldn't go there . I will add support for it before 1.6 goes final, as it will be nice to get it in.
Quote from DarkTimes :You know what... I forgot about InSim relay. Oh dear... I wonder what's wrong with my brain sometimes.... eh, maybe I shouldn't go there . I will add support for it before 1.6 goes final, as it will be nice to get it in.

Don't lose any sleep over it, I was just curious.
I noticed a couple of bugs in your relay code. Firstly you are missing the IRP_ARQ and IRP_ARP packets, also your IR_HOS and IR_ERR packets are assigned the wrong type in their constructors. Anyway, I've added InSim relay support to pyinsim 1.6 BETA, so it shouldn't matter too much anyway.
I've uploaded pyinsim 1.6.1 BETA to the beta post. Changes in this release:
  • Rewritten socket code to use byte arrays, which should yield some good performance improvements and also help with the planned port to Python 3.0
  • Added InSim Relay support, based on code supplied by tmehlinger
  • Changed OutGauge and OutSim classes to inherit from InSim.
For information here is the tmehlinger's InSim Relay example ported to pyinsim 1.6.1

"""Example 13: InSim Relay

This example demonstrates how to use InSim Relay. We connect to the relay,
request a list of hosts, select a random one, and then print out its
connection list.

"""

import pyinsim
import random

# List of hosts.
hosts = []

# Check if host is populated and does not require a spectator pass, then
# add to hosts list.
def hostList(isr, hos):
global hosts
for host in hos.Hosts:
if host.NumConns > 1 and not host.Flags & pyinsim.HOS_SPECPASS:
hosts.append(host.HName)

# Print out the error message.
def relayError(isr, err):
print 'InSim Relay error:', pyinsim.irerr(err.ErrNo)

# Print out the name and UCID of the connection.
def newConn(isr, ncn):
print 'New connection, UName %s (%d)' % (ncn.UName, ncn.UCID)

# Request for all connections to be sent.
def requestConns(isr):
isr.send(pyinsim.ISP_TINY, SubT=pyinsim.TINY_NCN, ReqI=1)

# Select a random host from the hosts list.
def selectHost(isr):
host = pyinsim.stripColours(random.choice(hosts))
print 'Selecting host:', host
isr.send(pyinsim.IRP_SEL, HName=host)

# Request connections after 2 seconds.
isr.timer(requestConns, 2)

# Create new InSimRelay object and bind events.
isr = pyinsim.InSimRelay()
isr.bind(pyinsim.IRP_HOS, hostList)
isr.bind(pyinsim.IRP_ERR, relayError)
isr.bind(pyinsim.ISP_NCN, newConn)

try:
# Initialise InSimRelay.
isr.init()

# Request host list.
isr.send(pyinsim.IRP_HLR)

# Select host after 5 seconds.
isr.timer(selectHost, 5)

except pyinsim.InSimError as err:
print 'Error:', err

I'd be really keen to hear your input on the new InSimRelay API.
Haven't had time yet to take a proper look, but here's a tiny improvement suggestion:
def stripColours(lfsStr):
"""Strip colour codes (^1, ^3 etc..) from a string.

@type lfsStr: string
@param lfsStr: The string to strip.
@rtype: string
@return: The string sans colour codes.

"""
return _COLOUR_REGEX.sub('', lfsStr)

Since _COLOUR_REGEX is a compiled regex and using its sub method is about 30% faster

€: Also I just noticed that my __slots__ implementation or rather usage is wrong, and so is yours! __slots__ only works with classes extending object.
>>> class SlotsTest:
__slots__=['a','b']


>>> st = SlotsTest()
>>> st.c = 1
>>>
>>> class SlotsTest(object):
__slots__=['a','b']


>>> st = SlotsTest()
>>> st.c = 1

Traceback (most recent call last):
File "<pyshell#63>", line 1, in <module>
st.c = 1
AttributeError: 'SlotsTest' object has no attribute 'c'
>>>

OK - thanks, I've changed the regexes and fixed the __slots__ issue on the packets which use them.

I have uploaded 1.6.2 BETA to the beta post, which fixes a bunch of issues with 1.6.1, which is basically that I managed to break OutGauge and OutSim. It should all be working now, hopefully.
Quote from DarkTimes :I noticed a couple of bugs in your relay code. Firstly you are missing the IRP_ARQ and IRP_ARP packets, also your IR_HOS and IR_ERR packets are assigned the wrong type in their constructors. Anyway, I've added InSim relay support to pyinsim 1.6 BETA, so it shouldn't matter too much anyway.

I probably posted an older version of my file, sorry about that. I had run into the incorrect types thing (it manifested itself in things not working at all ) and fixed it myself. Whoops. As for the missing packets, good catch... I totally missed them.

This is great stuff, keep up the good work!
Uploaded 1.6.3 to the beta post.

Changes since 1.6.2 BETA
  • Fixed bug with NodeLap unpacking
  • lookup() function now works more reliably
  • InSim.sendm() now supports sending MTC messages (see docs)
  • Improvements to parseCommand()
  • Added new cruise server example to 'examples' folder
I wanted a more complete example program for the documentation, so I wrote a (very) small cruise server. You can drive around and earn cash, which you can then spend on buying and selling cars. It has an on-screen button that shows your current cash and total distance travelled, and several various chat commands like !buy, !sell, !cars, !prices and !help. As I say it's just a simple example, but I do have to admit I had fun writing it. Now I need to write a CTRA-style example for balance. The full example can be found in the 'examples' folder in the beta download.

It requires a sub-directory called 'data\cruise' to store the user data, which is included the examples folder in the download, but you will need to create yourself if you just copy this code.

"""Example 15: Cruise

A simple cruise server. As you drive you earn cash, which can be spent on buying
and selling cars. User data is stored as pickled Python objects in the
data\cruise folder, and is retrieved and stored when players join and leave the
host. There are several chat commands such as !prices, !buy, !sell, !cars and
!help, plus an onscreen display which shows the players current cash and their
total kilometers driven.

"""

# Make sure correct version of pyinsim is imported.
VERSION = '1.6.4'
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

# Constants.
HOST = 'localhost'
PORT = 29999
PREFIX = '!'
UDPPORT = 30000
INTERVAL = 500 # Milliseconds
PROG_NAME = '^7pyCruise'
CASH_MULTIPLIER = 2 # Dollars per second
STARTING_CARS = ['UF1',]
STARTING_CASH = 1000 # Dollars
HEARTBEAT_INTERVAL = 1 # Seconds
USER_DIR = 'data\\cruise'
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}

# Global variables.
connections = {}
players = {}
insim = pyinsim.InSim()

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

# Draw on-screen display.
def draw_osd(ncn):
insim.send(pyinsim.ISP_BTN, ReqI=1, UCID=ncn.UCID, ClickID=1,
BStyle=pyinsim.ISB_DARK, 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.vars.on_track:
ncn.vars.cash += CASH_MULTIPLIER
draw_osd(ncn)
insim.timer(heartbeat, HEARTBEAT_INTERVAL)

# 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.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)
connections[ncn.UCID] = ncn

# 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.vars.on_track = True
else:
insim.sendm('/spec %s' % ncn.UName)
insim.sendm('^3| ^7You do not own the %s' % npl.CName, ncn.UCID)

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

# Print out car prices.
def cmd_prices(ncn, args):
insim.sendm('^3| ^7Car Prices:', ncn.UCID)
for car, price in CAR_PRICES.iteritems():
insim.sendm('^3| ^7%s: $%d' % (car, price), ncn.UCID)

# Buy a new car.
def cmd_buy(ncn, args):
if args:
for arg in args:
car = arg.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 %s does not exist' % car, ncn.UCID)
elif CAR_PRICES[car] > ncn.vars.cash:
insim.sendm('^3| ^7You do not have enough cash for the %s' % car, ncn.UCID)
else:
ncn.vars.cash -= CAR_PRICES[car]
ncn.vars.cars.append(car)
insim.sendm('^3| %s ^7bought the %s' % (ncn.PName, car))
else:
insim.sendm('^3| ^7No car selected', ncn.UCID)

# Sell an owned car.
def cmd_sell(ncn, args):
if args:
for arg in args:
car = arg.upper()
if car not in CAR_PRICES:
insim.sendm('^3| ^7The %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| %s ^7sold the %s' % (ncn.PName, car))
else:
insim.sendm('^3| ^7No car selected', ncn.UCID)

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

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

CMD_LOOKUP = {'prices': cmd_prices, 'buy': cmd_buy, 'sell': cmd_sell,
'cars': cmd_cars, 'help': cmd_help,}

# Handle command message from LFS.
def message_out(insim, mso):
cmd = pyinsim.parseCmd(mso)
if cmd:
ncn = connections[mso.UCID]
cmd0 = cmd[0].lower()
if cmd0 in CMD_LOOKUP:
CMD_LOOKUP[cmd0](ncn, cmd[1])
else:
insim.sendm('^3| ^7Unknown command', ncn.UCID)

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

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

# Remind to set /cruise flag.
def race_start(insim, rst):
if not rst.Flags & pyinsim.HOSTF_CRUISE:
insim.sendm('^3| ^1WARNING: NOT IN CRUISE MODE!')
insim.sendm('^3| ^1WARNING: NOT IN CRUISE MODE!')

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

if __name__ == '__main__':
# Bind event-handlers.
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.ISP_RST, race_start)
insim.bind(pyinsim.EVT_CLOSED, closed)

try:
# Initialise InSim.
insim.init(HOST, PORT, IName=PROG_NAME, Prefix=PREFIX, UDPPort=UDPPORT,
Flags=pyinsim.ISF_MCI, Interval=INTERVAL)

# Request players/connections.
req_conns()

# Start heartbeat timer.
insim.timer(heartbeat, HEARTBEAT_INTERVAL)
except pyinsim.Error as err:
print 'InSim Error:', err

I'm currently in the process of switching the documentation over to the Sphinx documentation generator and, now I've finally managed to get it working (grrr!), the HTML it produces is really quite beautiful. The flexibility of Sphinx also means I'm now able to move a lot of the old "inline" documentation out of the module itself, meaning the docstrings are now much snappier and to the point, keeping the big long explanations in the accompanying HTML files. The only problem is that the output is so pretty it's encouraged me to write more documentation, which I'm in the slow process of doing! Oh well... Also I may need to find a web site to host it this time, so more people can see it.
Quote from DarkTimes :I'm currently in the process of switching the documentation over to the Sphinx documentation generator and, now I've finally managed to get it working (grrr!), the HTML it produces is really quite beautiful. The flexibility of Sphinx also means I'm now able to move a lot of the old "inline" documentation out of the module itself, meaning the docstrings are now much snappier and to the point, keeping the big long explanations in the accompanying HTML files. The only problem is that the output is so pretty it's encouraged me to write more documentation, which I'm in the slow process of doing! Oh well... Also I may need to find a web site to host it this time, so more people can see it.

How about ours?
Thanks Turkey, but I've taken up an offer of hosting the distribution from morpha. It makes sense since he's responsible for having written parts of it. Thanks your generous offer anyway.
Uploaded 1.6.4 beta to the beta post.

Changes
  • Fixed bug with OSD in cruise.py example
  • Fixed bug with version() not correctly detecting higher pyinsim versions
  • Lots of small optimisations to the packet receive code and to unpacking individual packets
  • Changed ISP_ALL to EVT_ALL
  • Changed ISP_OUTSIM and ISP_OUTGAUGE to OUT_OUTSIM and OUT_OUTGAUGE
  • Renamed many helper functions, as part of the great API cleanup
  • Renamed InSimError to Error and InSimVersion to VersionError
  • Cleaned up all packets, removing unessesary methods and parameters
  • Improvements to InSim.sendb(), so it works more reliably
Note: This beta release does not include the documentation, as it's currently being reworked.

Note: All the packets have been tweaked, so there may be some attribute errors.
I've picked up my cruise script project again thanks to DarkTimes's base script with 1.6.4 beta, and I'm loving it.
I've been doing some changes and additions, and so far so good. Except one thing.
I've tried making an !give code to send other users money. It's not very advanced and I know it will let you send money to yourself. but what I need to get done first is it actually giving money. Here is what I have:

#Change cash level
def cmd_give(ncn, args):
if args:
for arg in args:
ncn = arg.split(':')[-0]
amt = arg.split(':')[-1]
#insim.sendm('^3| ^7You are trying to send %s $%s' % (user, amt))
ncn.vars.cash += amt
insim.sendm('cash changed')

The commented out line was for testing. I was trying to get it to say you are sending whatever user a certain amount. However that failed. It would print out 2 messages, saying "You are trying to send user $user" and "You are trying to send 1 $1".
Even though I figured I would have issues, I tried to keep going. Basically, I got as far as that. Any ideas? Here is what python shell tells me:

>>> Traceback (most recent call last):
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2570, in __recvThread
[self.__recv(sock) for sock in socks]
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2591, in __recv
[self.__onPacketData(pdata) for pdata in self.__buffer]
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2608, in __onPacketData
if isp: self.event(ptype, packet)
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2475, in event
[callback(self, *args) for callback in self.__callbacks[evt]]
File "C:\Users\Jonathan\Pyinsim 164 beta\examples\cruise.py", line 267, in message_out
CMD_LOOKUP[cmd0](ncn, cmd[1])
File "C:\Users\Jonathan\Pyinsim 164 beta\examples\cruise.py", line 231, in cmd_give
ncn.vars.cash += amt
AttributeError: 'str' object has no attribute 'vars'

The commands are passed by pyinsim already split, so if you typed the command '!give DarkTimes 500' in LFS, the args which are passed into cmd_give() should be a list like this:

['DarkTimes', '500']

The !give part of the command was process earlier in the script.

I wrote and tested this and it seems to work. I had to add an extra function to find a user by their UName, because it cleaned up some of the flow logic in the function.

# Get a conn from their UName
def ncn_from_uname(uname):
ncn = filter(lambda n: n.UName == uname, connections.values())
if ncn:
return ncn[0]
return None

def cmd_give(ncn, args):
# Check args are correct length.
if len(args) != 2:
insim.sendm('^3| Invalid give command', ncn.UCID)
return

# Get the uname and ncn for the user we're giving this to.
other_uname = args[0]
other_ncn = ncn_from_uname(other_uname)
if not other_ncn:
insim.sendm('^3| The user %s ^3does not exist' % other_uname, ncn.UCID)
return

# Check the amount set is a number.
if not args[1].isdigit():
insim.sendm('^3| The amount %s is not a number' % args[1], ncn.UCID)
return

# Award the amount and confirm.
amount = int(args[1])
other_ncn.vars.cash += amount
insim.sendm('^3| You have been given $%d by %s' % (amount, ncn.PName), other_ncn.UCID)
insim.sendm('^3| You have given %s^3 the amount of $%d' % (other_ncn.PName, amount), ncn.UCID)

I hope that helps.
Thanks DarkTimes!
Edit:
Can I get some help with an !location or !position code

I have tried to make a code to print out the users coordinates (doesnt have to be a certain user, just the user who types it), but can't seem to get it.

Thanks!
We already store the users last XYZ coords in the user_vars, as list called last_pos, so basically all you would need to do is something like this:

def cmd_location(ncn, args):
if ncn.vars.on_track:
# Get XYZ and convert to meters
last_pos = ncn.vars.last_pos
x = pyinsim.meters(last_pos[0])
y = pyinsim.meters(last_pos[1])
z = pyinsim.meters(last_pos[2])

# Send message
insim.sendm('^3| Location: %d %d %d' % (x, y, z), ncn.UCID)
else:
insim.sendm('^3| You are not on track', ncn.UCID)

Note: that code is untested.
Tried it, doesn't seem to work.

>>> Traceback (most recent call last):
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2570, in __recvThread
[self.__recv(sock) for sock in socks]
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2591, in __recv
[self.__onPacketData(pdata) for pdata in self.__buffer]
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2608, in __onPacketData
if isp: self.event(ptype, packet)
File "C:\Python264\lib\site-packages\pyinsim\pyinsim.py", line 2475, in event
[callback(self, *args) for callback in self.__callbacks[evt]]
File "C:\Users\Jonathan\Pyinsim 164 beta\examples\cruise.py", line 407, in message_out
CMD_LOOKUP[cmd0](ncn, cmd[1])
File "C:\Users\Jonathan\Pyinsim 164 beta\examples\cruise.py", line 174, in cmd_location
x = pyinsim.meters(last_pos[0])
AttributeError: 'module' object has no attribute 'meters'

I misspelled the word 'metres'.

Try:

def cmd_location(ncn, args):
if ncn.vars.on_track:
# Get XYZ and convert to meters
last_pos = ncn.vars.last_pos
x = pyinsim.metres(last_pos[0])
y = pyinsim.metres(last_pos[1])
z = pyinsim.metres(last_pos[2])

# Send message
insim.sendm('^3| Location: %d %d %d' % (x, y, z), ncn.UCID)
else:
insim.sendm('^3| You are not on track', ncn.UCID)

Quote from DarkTimes :I misspelled the word 'metres'.

Try:

def cmd_location(ncn, args):
if ncn.vars.on_track:
# Get XYZ and convert to meters
last_pos = ncn.vars.last_pos
x = pyinsim.metres(last_pos[0])
y = pyinsim.metres(last_pos[1])
z = pyinsim.metres(last_pos[2])

# Send message
insim.sendm('^3| Location: %d %d %d' % (x, y, z), ncn.UCID)
else:
insim.sendm('^3| You are not on track', ncn.UCID)


Will try, thanks! Totally missed that myself :P
-
(DarkTimes) DELETED by DarkTimes
I've not been working on this for several months, as I had to cut back on my day-to-day coding due to carpel-tunnel syndrome. However I'm fully recovered and am able to return to spending many hours a day coding (albeit with a much improved posture and an ergonomic keyboard). This means I hope to finish up on this beta and finally get it out there. Sorry for the six month delay.
Quote from DarkTimes :I had to cut back on my day-to-day coding due to carpel-tunnel syndrome.

I know how that feels! I'm only 21 too, so this sucks massively.
-
(DarkTimes) DELETED by DarkTimes
-
(DarkTimes) DELETED by DarkTimes
Quote from Dygear :I know how that feels! I'm only 21 too, so this sucks massively.

Hah, I wish I was 21.

Actully I don't, being 21 was rubbish.
Just a quickie: [post=1452414]I found out[/post] that the charsets I used in strmanip are incorrect, so here's a fixed (but untested!) version.
Attached files
_strmanip.zip - 85.8 KB - 281 views
Quote from DarkTimes :I wanted a more complete example program for the documentation, so I wrote a (very) small cruise server. [...] As I say it's just a simple example, but I do have to admit I had fun writing it.

Your a sick man, DarkTimes! So, where is the CTRA system?

FGED GREDG RDFGDR GSFDG