The online racing simulator
Searching in All forums
(921 results)
DarkTimes
S2 licensed
I was trying to create a project for this on Google Code, but it won't let me as there is already a InSim library on SourceForge called pyinsim, which is empty and looks discarded! Does anyone know anything about this? I checked the dates and it was register just after I posted the first version of the library. This is really annoying!

http://sourceforge.net/projects/pyinsim/

Edit: Pff now I've got to wait for this other guy to "approve" my bloody project!
DarkTimes
S2 licensed
Real race results don't take into consideration distance travelled, they are taken from your relative position the last time you completed a lap. Also measuring distance is fraught with issues (plus not how it is done in real life). There is a thread somewhere on the forum where I have discussed this at length. I'm not saying my way is the best, simplest or most efficient, but I determined it back when I was running leagues, and it gives almost 100% results, depending on what your definition of DNF in LFS is.
DarkTimes
S2 licensed
Yes, pruning results from InSim can be complicated if you want to handle DNFs. You have to decide exactly what you consider to be a DNF. When I have written such a program before, I have counted anyone who leaves (PLL) before they have received a result (RES) to be a DNF. Here is a very rough idea of the logic I have implemented.
  • Race start, create blank result for each player with DNF flag set to true
  • Add each completed lap to current result
  • If player leaves and rejoins, clear result laps
  • If player gets RES, set DNF to false
  • Race ends*, log results
This basically gives you a big array of result objects for everyone in the race. To get the actual race results you need to sort the array in a certain order.
  • Sort by DNF
  • Sort by laps completed (count of all completed laps)
  • Sort by total elapsed time (sum of all completed lap times)
In previous programs I have written this has given me a nice list of results, correctly ordered with the DNFs last. If someone leaves and rejoins, only their most recent start is logged, which I think is fair. You could switch it round so only their first result is logged, or even log all their results and sort them by time etc..

Anyway, I've probably not explained this very well, I can knock up some examples tomorrow when I'm sober if you need.

* Race ends if number of results == total number of players, if a TINY_REN is received, or if a new race starts (RST).
Last edited by DarkTimes, .
DarkTimes
S2 licensed
I'm happy to pay the license fee, it's not very much and I think the BBC makes some very good TV shows.
DarkTimes
S2 licensed
I meant I was surprised that the code I had written didn't suck, I've been sold on the plugin idea for long time. I struggled quite a lot with the way the program was structured and it took me a long time for figure out the best route to go down. The thing I'm happiest about in pyinsim 3K is that it is not a multi-threaded program - it does everything on a single-thread. I plan to merge the new socket code back into the main pyinsim branch, as being able to create single-threaded pyinsim programs is a very important change.

That said I'm still trying to figure out whether to keep developing this. It am happy with it, but with PRISM getting so much support on the forum it seems unnecessary to have two plugin systems that are so similar. I do however have a rough prototype of a .NET plugin system that uses JavaScript as the language that I'm pretty excited about (it's more awesome than it sounds ), but it would be a colossal amount of work to finish.
Last edited by DarkTimes, .
DarkTimes
S2 licensed
Quote from codehound :Well, my problem is that I didn't(and still don't) understand what it is or what I could do with it.

I've been thinking about how to answer this question for a couple of days, and I guess it does need an explanation.

Basically pyinsim 3000 is not an InSim library, it is a standalone InSim program that you extend by writing plugins.

The program itself is very basic. It manages multiple InSim hosts, keeps track of connection and players lists, plus a bunch of other things that almost every InSim program needs. It does not implement any functionality itself, instead you add functionality to it by adding plugins. Plugins are small, modular peices of code that can be easily 'plugged-in' to the main system by dropping them into the 'plugins' folder. You can write your own plugins, or use plugins that have been written by other people. This allows you to build a host system for your server just by choosing which plugins you want to add.

For instance, if you were building a lapper type server, there may be several plugins you could use. One for tracking lap times, another for storing laps in a database, another for awarding points to drivers, another for dealing with commands etc.. This way you can pick and choose which parts of the system you want to use, and can easily turn features on and off by adding and removing plugins. Ideally, each plugin would only do one small thing. You can think of them as small independent blocks you can combine in different ways to build exactly the program that you want. For instance if you didn't want your lapper server to award points to drivers, you could just turn that plugin off. Or if you wanted it to store driver points in a different way, you could switch that plugin out for a different one, or replace it with your own.

pyinsim 3k provides the infrastructure to support this. Ideally it should be usable by people without any programming skill, all they would need to know is how to add plugins to the system and configure them. Of course having the knowledge to write and edit Python code would be a benefit.

I need to write a proper post explaining how the plugins work, but I hoped people would figure that out by looking at the examples. Still, a more detailed explanation of how they work and why they work the way they do is required. Also it is just a rough, unfinished prototype. That said it is stable and most of the code is pretty good (which is why I released it, I was surprised by how much it didn't suck).
Last edited by DarkTimes, .
DarkTimes
S2 licensed
What are the error messages you are getting?
DarkTimes
S2 licensed
If you want it as an .exe then it might just be easier to write it using .NET. You can make exe files from Python programs, but it's a pain the arse and usually not worth the hassel.

In terms of the other things, sure.
DarkTimes
S2 licensed
Quote from JustForFunRacing :Hmm.... would it possible to code something like that with Python/pyinsim:

http://www.lfsforum.net/showthread.php?t=70428

Here is a rough plugin I've written for pyinsim3000 that logs lap and split times to a text file. You need to drop it into the plugins directory and add 'plugins.lap_csv' to the PLUGINS variable in the config.py file.

It creates a new CSV file for each session (practice, qualifying, race etc..) and stores the CSV in the format:

username, nickname, track, car, lap_num, sp1, sp2, sp3, time

Obviously I've written it quickly, so it might have one or two bugs. You can stop and start the logging with the commands !start and !stop.

# lap_csv.py

import plugin
import datetime
import os

CURRENT_DIR = os.path.dirname(__file__)

# uname, pname, track, car, lap_num, sp1, sp2, sp3, time

class LapInfo(object):
def __init__(self):
self.splits = [0,0,0]
self.last_split = 0

class LapCSV(plugin.Plugin):
def __init__(self):
plugin.Plugin.__init__(self)
self.bind(plugin.ISP_RST, self.race_start)
self.bind(plugin.ISP_TINY, self.tiny)
self.bind(plugin.ISP_SPX, self.split)
self.bind(plugin.ISP_LAP, self.lap)
self.bind(plugin.ISP_MSO, self.message_out)

def connected(self, host):
host.send(plugin.ISP_TINY, ReqI=255, SubT=plugin.TINY_RST)

def disconnected(self, host):
if hasattr(host, 'logging') and host.logging:
host.file.close()
host.logging = False

def create_name(self, track):
now = datetime.datetime.now()
return '%s_%s.csv' % (track, now.strftime('%d_%m_%Y_%H_%M_%S'))

def race_start(self, host, rst):
if hasattr(host, 'logging') and host.logging:
host.file.close()
host.logging = False
name = self.create_name(rst.Track)
path = os.path.join(CURRENT_DIR, name)
host.file = open(path, 'w')
host.logging = True

def tiny(self, host, tiny):
if tiny.SubT == plugin.TINY_REN:
if hasattr(host, 'logging') and host.logging:
host.file.close()
host.logging = False

def split(self, host, spx):
npl = host.players[spx.PLID]
if not hasattr(npl, 'lap_info'):
npl.lap_info = LapInfo()
npl.lap_info.splits[spx.Split-1] = (spx.STime - npl.lap_info.last_split)
npl.lap_info.last_split = spx.STime

def lap(self, host, lap):
if hasattr(host, 'logging') and host.logging:
npl = host.players[lap.PLID]
if not hasattr(npl, 'lap_info'):
return
ncn = host.conns[npl.UCID]
sp1 = npl.lap_info.splits[0]
sp2 = npl.lap_info.splits[1]
sp3 = lap.LTime - npl.lap_info.last_split
host.file.write('%s,%s,%s,%s,%d,%s,%s,%s,%s\n' % (ncn.UName,
ncn.PName,
host.state.Track,
npl.CName,
lap.LapsDone,
plugin.timestr(sp1),
plugin.timestr(sp2),
plugin.timestr(sp3),
plugin.timestr(lap.LTime),))
npl.lap_info = LapInfo()

def message_out(self, host, mso):
if mso.UserType == plugin.MSO_PREFIX:
args = mso.Msg[mso.TextStart:].split()
if args:
cmd = args[0].lower()
if cmd == '!start':
if hasattr(host, 'logging'):
host.logging = True
host.sendm('^3| ^7Started logging', mso.UCID)
elif cmd == '!stop':
if hasattr(host, 'logging'):
host.logging = False
host.sendm('^3| ^7Stopped logging', mso.UCID)

Last edited by DarkTimes, .
DarkTimes
S2 licensed
Yeah, it's easy to log lap and split times, in fact I remember writing this exact program for someone a couple of years ago. The only issue is that LFS does not provide any way to figure out what setup someone is using, as that's considered cheating by the game. I can write a simple plugin that logs lap and splits for you though. Just tell me the format that the CSV file should use.
DarkTimes
S2 licensed
De La Rosa replaced for the rest of the season by Heidfeld.

http://news.bbc.co.uk/sport1/h ... t/formula_one/8994638.stm
DarkTimes
S2 licensed
Great result for Alonso, although I would like to have seen Button win. Just a shame that Hamilton took himself out on the first corner, would have been nice to see him mess it up at the front. A solid drive from Vettel and Webber is now leading the championship.
Last edited by DarkTimes, .
DarkTimes
S2 licensed
Isn't that really the lollipop mans fault?
DarkTimes
S2 licensed
Someone being put into an ambulance in the pitlane. Also Martin Brundle trending on Twitter.

Edit: HRT mechanic, Yamamoto ran him over.
DarkTimes
S2 licensed
Yeah I should have said "using Python as a".
DarkTimes
S2 licensed
Uploaded 1.1.1 to the first post.

Changes
  • Now detects location of LFS.exe automatically
I realised that as LFS now uses a proper installer I can grab the location of LFS.exe from the registry instead of requiring the user to configure it. That said, you can still set the location manually if you want/need to.

Note: I have depreciated the old version of this program, I will only support .NET 4.0 from now on.
Last edited by DarkTimes, .
DarkTimes
S2 licensed
OK, only one download in the past three days, I guess using a scripting language to control hosts isn't a popular idea.
Last edited by DarkTimes, .
DarkTimes
S2 licensed
Yeah, Python is a very elegant language. That's one of the reasons I like it so much. Plus, it's just awesome.

Anyway, trying to think up ideas for plugins, I wrote a little swear-filter. It checks each message for a swear-word and kicks the player after giving three warning. You might need to make the actual swear list more realistic.

# !/usr/bin/env python
# swear_filter.py

import plugin
import re

# List of swearwords, in lower-case.
SWEAR_WORDS = ['boobs', 'bum', 'fart', 'knockers', 'poo']
MAX_WARNINGS = 3 # 0 for no warning.
SPLIT_REGEX = re.compile('\W+')

def get_swearwords(msg):
"""Return a list of swearwords in the message."""
words = SPLIT_REGEX.split(msg.lower())
return filter(lambda w: w in SWEAR_WORDS, words)

def contains_swearword(msg):
"""Return true if a message contains a swearword."""
words = SPLIT_REGEX.split(msg.lower())
for word in words:
if word in SWEAR_WORDS:
return True
return False

class SwearFilter(plugin.Plugin):
"""Plugin to handle a swear-filter."""
def __init__(self):
plugin.Plugin.__init__(self)
self.warnings = {} # Store warnings in a dict with the uname as the key.
self.bind(plugin.ISP_MSO, self.message_out)
self.bind(plugin.ISP_CNL, self.connection_left)

def message_out(self, host, mso):
"""Handle MSO message packet."""
if mso.UserType == plugin.MSO_USER:
msg = mso.Msg[mso.TextStart:]
if contains_swearword(msg):
ncn = host.conns[mso.UCID]
self.update_warnings(ncn)
self.check_warnings(host, ncn)

def connection_left(self, host, cnl):
"""Handle CNL packet, delete any warnings which exist for the user."""
ncn = host.conns(cnl.UCID)
if ncn.UName in self.warnings:
del self.warnings[ncn.UName]

def update_warnings(self, ncn):
"""Increment a user's warnings."""
if ncn.UName in self.warnings:
self.warnings[ncn.UName] += 1
else:
self.warnings[ncn.UName] = 1 # Add new warning to dict.

def check_warnings(self, host, ncn):
"""Check if the user has exceeded their warning quota, otherwise send warning messge."""
if self.warnings[ncn.UName] >= MAX_WARNINGS:
host.sendm('^7| %s ^3kicked for swearing' % ncn.PName)
host.sendm('/kick %s' % ncn.UName)
else:
warnings = self.warnings[ncn.UName]
host.sendm('^7| ^3Swear warning (%d of %d)' % (warnings, MAX_WARNINGS), ncn.UCID)

Drop it into a module called 'swear_filter.py' and add a reference to 'plugins.swear_filter' to the PLUGINS config variable.
Last edited by DarkTimes, .
DarkTimes
S2 licensed
Inspired by PRISM I decided to see if I could use pyinsim to create a plugin hosting system. I've been hacking away at it for a few days and it seems to be working quite well, although it's just a prototype. I ended up having to rewrite a bunch of stuff in pyinsim, especially the socket code, so that it nicely supported multiple hosts and also properly integrated with the plugin system.

Here is a brief list of features:
  • Modular plugin system
  • Simple per-plugin data persistence
  • Inter-plugin messaging system
  • Support for multiple hosts
  • Host state management (players, connections etc..)
Creating a plugin

You create a new plugin by creating a new module in the plugins directory. Inside this module you add you plugin classes, which must inherit from the plugin.Plugin base class. Here is an example of the simplest plugin you could create, that simply sends a welcome message to each host that connects.

# hello.py

import plugin

class Hello(plugin.Plugin):
def __init__(self):
plugin.Plugin.__init__(self)

def connected(self, host):
host.send(plugin.ISP_MST, Msg='Hello, %s' % host.name)

The Plugin base class contains a few methods you can override to catch specific events.
  • initialized - The plugin system is initialized, the plugin_map and data members are populated.
  • uninitialized - The plugin system is shutting down and the plugin data is about to be saved to disk.
  • connected - A new host has connected and added to the host_map.
  • disconnected - A host has been lost and removed from the host_map.
The Plugin also contains a bunch of other methods and properties.
  • plugin_map - A dictionary of currently loaded plugins keyed by name
  • host_map - A dictionary of currently connected hosts keyed by name
  • data - A user object you can set that will be available the next time the plugin starts (you can store anything that's serializable, it's a dict by default)
  • bind(), unbind() and isbound() - Packet event binding methods
  • subscribe(), unsubscribe() and notify() - Messaging system for communicating between plugins
  • find_plugin() - Find another plugin by name
You can bind packet events inside a plugin, just like you could with normal pyinsim. Here is a simple example that spectates any driver that goes above 80 Kph.

# speeding.py

import plugin

SPEED_LIMIT = 80 # Kph

class Speeding(plugin.Plugin):
def __init__(self):
plugin.Plugin.__init__(self)
self.bind(plugin.ISP_MCI, self.car_update)

def car_update(self, host, mci):
for car in mci.Info:
if plugin.kph(car.Speed) > SPEED_LIMIT:
npl = host.players[car.PLID]
host.sendm('/spec %s' % npl.PName)
host.sendm('%s ^7spectated for speeding!' % npl.PName)
host.sendm('Do not go above %d Kph!' % SPEED_LIMIT, ucid=npl.UCID)

The host argument represents the host that the packet came from. It has a few methods and properties you can call.
  • conns - The current connection list dictionary
  • players - The current player list dictionary
  • state - The current host state (last STA received)
  • name - The host name (as defined in config.py)
  • hostaddr - A tuple containing the (ip, port) of the host
  • send() - Send a packet to the host
  • sendm() - Send a message to the host (MTC, MST or MSX)
Configuring and starting the app

The configuration can be found in config.py file, which is basically just a python file where you can set various global variables. In here you can configure which hosts you want to connect to, and also which plugin modules get loaded. It's pretty self-explanatory when you see it.

Once you have created some plugins and configured the hosts, you can start the app by double-clicking the 'pyinsim.bat' file in the directory, or with the command 'python app.py'.

As I say it's just a prototype that I hacked together in a couple of days. It'll probably have a few bugs in it, and I'm not 100% happy with the plugin API, so that might change if I continue to work on it, but all in all it seems to be working quite nicely.

Edit: Note you cannot use a ReqI of 255 in your plugins, that's reserved by the system. I may change it to some other number. Not sure.
Last edited by DarkTimes, .
DarkTimes
S2 licensed
Quote from Victor :(ps, I think noone has any comments because it's still not clear what it's supposed todo)

Yeah, I didn't understand what the commands were for or how they worked, so I wasn't able to comment.
DarkTimes
S2 licensed
Quote from Flame CZE :It still doesn't work. I saw youur post yesterday or so and there was a piece of code to get the UCID's from players connecting, maybe it could work then.

I did post a reply which was incorrect, so I deleted it. I forgot that setting the UCID to 255 sent the button to everyone on the host.

The code works fine for me as it is. If I copy and paste your code into a new project, add a reference to Spark, start LFS and hit run, I can see the button on the menu screen. I'm not sure what problem you're having.
DarkTimes
S2 licensed
Quote from Dygear :Ok, as long as your gentle .

Yeah, I agree on that point. But I can't do aliased or name arguments to give args out of order. The only thing I could do is hand the constructor a string and let it tokenized that (and to be frank, that's not great.) I've been thinking about this for quite some time. Still no closer to an answer.

OK - I didn't realise PHP didn't support those sorts of optional parameters, the way that Python and C# 4.0 do. Seems rather strange to me that it doesn't.
DarkTimes
S2 licensed
I had a bit of a mess around and I had some comments. I know it's early in development and it's not finished, but here they are anyway.

- The packet initialization code is a little clunky. It would be nice if you could set packet properties in the constructor with optional parameters, so you could do something like this:

$this->sendPacket(new IS_MTC(Msg='Hello, world!', UCID=0));

- I cannot see any way to tell which host sent a specific packet or specify which host to send a packet to.

- The ISI prefix doesn't work, I had to change it to...

isi->Prefix = ord('!');

But aside from that it's looking good. As I say I know it's early in development, but thought I'd mention them anyway.

Good work so far though!
Last edited by DarkTimes, .
DarkTimes
S2 licensed
The button works for me, although it's quite hard to see on some screens as it's set to have a T of only 4, which sometimes causes it to get hidden by other screen elements. You need to set the T to a value > than 30 in order for it to be within the recommended visible area. Try setting it to a T of 40 or something, you can see it fine.
DarkTimes
S2 licensed
I guess PHP makes function naming extra confusing, as seemingly even the guys that wrote the standard library couldn't agree on a consistent naming convention. I find it confusing that function names, and even the order of parameters, can be totally different from one function to the next.
Last edited by DarkTimes, .
FGED GREDG RDFGDR GSFDG