The online racing simulator
Searching in All forums
(921 results)
DarkTimes
S2 licensed
Windows 7 is shiny.
DarkTimes
S2 licensed
Quote from PoVo :I hope you continue developing this library actively!

As I said in my last post I've been working on Spark this week and I've made many improvements to the library. So many improvements that what was going to be version 1.1 has now changed to version 2.0.

I'm still working on the string encoding stuff though, which I know I've been going on about for ages, but it's complicated and I want to get it right. As far as I'm aware no other InSim library (besides pyinsim) correctly deals with all of the LFS codepages, so it should be cool when it's finished. Most libraries use the code from LFSLib.NET, which is old and does not handle double-byte character sets like Traditional Chinese.

The new string code is slightly slower, but fact is we're still talking in the region of factions of a millisecond, even for a worst-case scenario string (240 chars, lots of codepage changes, special chars, colours etc..). In tests last night I was able to parse a MSO packet 100,000 times in well under a second (even on my crappy old PC). While there is an efficiency overhead, I honestly don't think it will be perceptible. I was thinking of implementing a lazy string class, that only decoded strings if and when they're needed, and while I still may implement something like that in the future, from my tests I've decided I don't think it's necessary and I can't be bothered right now with the additional complexity.

On the plus side I've learned more about unicode and character sets than I can stand.

My new power-supply arrived today, plus a shiny new copy of Windows 7, so I have the joy of spending the rest of the day upgrading my development machine!
DarkTimes
S2 licensed
I've been working on this quite a bit this week, and despite occasionally being driven from my computer by the noise, I've gotten a fair amount done.
  • Library is now fully CLS compliant and conforms to Microsoft Guidelines All (except naming)
  • New simplified initialization code and support for seperate UDP connection for MCI and NLP packets
  • Complete OutSim and OutGauge support
  • Complete documentation for all packets!
  • Thousands (literally) of small tweaks and changes
I still need to finish the string encoding logic and figure out a few issues let over with that, but I'm going to leave that until after my new computer part arrives sometime this week. I also want to make a CodePlex site for the project but I've run into a few issues with the name Spark. I plan to rename the library InSim.NET for the next release, unless anyone can think of something better.
DarkTimes
S2 licensed
I have Updated the IS_BTN packet with a new Caption property. As I'm not ready to create a full release, here is some code you can copy and paste into the IS_BTN.cs file.

using System;

namespace Spark.Packets
{
/// <summary>
/// BuTton, for sending a button to InSim.
/// </summary>
public sealed class IS_BTN : IPacket, ISendable
{
/// <summary>
/// Gets the size of the packet.
/// </summary>
public byte Size { get; private set; }

/// <summary>
/// Gets the type of the packet.
/// </summary>
public PacketType Type { get; private set; }

/// <summary>
/// Gets or sets the packet request ID (returned in <see cref="IS_BTC"/> and <see cref="IS_BTT"/> packets).
/// </summary>
public byte ReqI { get; set; }

/// <summary>
/// Gets or sets the connection to display the button (0 = local / 255 = all).
/// </summary>
public byte UCID { get; set; }

/// <summary>
/// Gets or sets the unique button click ID.
/// </summary>
public byte ClickID { get; set; }

/// <summary>
/// Used internally by InSim.
/// </summary>
public byte Inst { get; set; }

/// <summary>
/// Gets or sets the button style flags.
/// </summary>
public ButtonStyles BStyle { get; set; }

/// <summary>
/// Gets or sets the max characters the user is allowed to type in.
/// </summary>
public byte TypeIn { get; set; }

/// <summary>
/// Gets or sets the distance from the left of the screen the button will be displayed (0 to 200).
/// </summary>
public byte L { get; set; }

/// <summary>
/// Gets or sets the distance from the top of the screen the button will be displayed (0 to 200).
/// </summary>
public byte T { get; set; }

/// <summary>
/// Gets or sets the width of the button (0 to 200).
/// </summary>
public byte W { get; set; }

/// <summary>
/// Gets or sets the height of the button (0 to 200).
/// </summary>
public byte H { get; set; }

/// <summary>
/// Gets or sets the text of the button.
/// </summary>
public string Text { get; set; }

/// <summary>
/// Gets or sets the caption for a type-in button.
/// </summary>
public string Caption { get; set; }

/// <summary>
/// Creates a new <see cref="IS_BTN"/> object.
/// </summary>
public IS_BTN()
{
Size = 12;
Type = PacketType.ISP_BTN;
}

/// <summary>
/// Creates a new <see cref="IS_BTN"/> object.
/// </summary>
public byte[] Pack()
{
string text = Text;

if (!String.IsNullOrEmpty(Caption))
{
text = String.Format("{0}{1}{0}{2}", Char.MinValue, Caption, text);
}

if (!String.IsNullOrEmpty(text))
{
Size = text.Length < 240 ? (byte)(12 + (text.Length + (4 - (text.Length % 4)))) : (byte)252;
}

var writer = new PacketWriter(Size);
writer.Write(Size);
writer.Write((byte)Type);
writer.Write(ReqI);
writer.Write(UCID);
writer.Write(ClickID);
writer.Write(Inst);
writer.Write((byte)BStyle);
writer.Write(TypeIn);
writer.Write(L);
writer.Write(T);
writer.Write(W);
writer.Write(H);
writer.Write(text, Size - 12);
return writer.GetData();
}
}
}

Last edited by DarkTimes, .
DarkTimes
S2 licensed
OK, I forgot about that sorry. Scawen is saying that you deliminate the text from the caption with a null character, which is the character '\0'. So what you would do is:

Text = "\0DEFG\0ABC";

Or if you wanted to do it in a more C# way:

Text = Char.MinValue + "DEFG" + Char.MinValue + "ABC";

Or more readable C# way:

Text = String.Format("{0}DEFG{0}ABC", Char.MinValue);

Last edited by DarkTimes, .
DarkTimes
S2 licensed
What exactly do you mean by 'title'? You set the text on the TypeIn box the same way you set the text on a normal button, with the Text property on the IS_BTN packet.

Anyway, I had written a small GUI example I was meaning to post, you can download it below. Nothing to complex, it just prints the contents of each MSO packet to a TextBox.
DarkTimes
S2 licensed
Updated: uploaded 1.0.10 with two bug fixes

I've uploaded version 1.0.9 to the first post, which has a few changes.
  • Fixed bug in TCP send code, that could cause some weird issues in certain cases
  • Added overridden Bind() method (see below)
  • Added overriden Send() method for sending messages (see below)
  • Obsoleted several old methods which will one day be removed
  • Changed the internal socket used in TcpSocket and UdpSocket to be protected
  • General code improvements
  • Fixed bug with NullReferenceException when calling InSim.Dispose()
  • Fixed bug with 'Spark.TcpSocket not connected' InSim error when calling InSim.Disconnect()
I would like to spend more time updating this, adding the long overdue string encoding stuff and many other fixes, but at the moment I'm hampered by a fault with my computer that makes it sound like a grumpy hornet, and am unable to program for long periods without going insane from the noise. A new part should arrive during the week, and if it doesn't I fear I may have to set myself on fire.

Anyway... you can now bind packets like this:

insim.Bind<IS_NCN>(NewConnection);

void NewConnection([b]InSim insim[/b], IS_NCN ncn)
{
// Pass.
}

With this binding method Spark will pass a reference to the InSim instance that raised the packet event, so you can easily access the specific host a packet was sent from. This is useful, even required, when using a single program instance to manage multiple hosts.

In addition the two old Send(string message) and Send(string message, int ucid, int plid) have been replaced with a single method that uses C# 4.0 optional parameters. This method has the prototype Send(string message, int ucid=0, int plid=0). You can use it like so:

// Send MST or MSX
insim.Send("Hello, InSim!");

// Send MTC to connection
insim.Send("Hello, you!", ucid: 0);

// Or send MTC to player
insim.Send("Hello, you!", plid: 0);

As I'm trying to maintain backwards compatibility I have left the old methods intact, but have marked them with obsolete attributes that will give you a friendly compiler warning when you use them. I have also obsoleted a few other small things that I plan one day to remove.

Please let me know if you have any problems.
Last edited by DarkTimes, . Reason : Updated to 1.0.10
DarkTimes
S2 licensed
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.
DarkTimes
S2 licensed
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.
Last edited by DarkTimes, .
DarkTimes
S2 licensed
Yes, you should add a new binding to InitializeInSim and add a new packet-handler.

void InitializeInSim()
{
insim.Bind<IS_CPR>(ConnectionRename);
}

void ConnectionRename(IS_CPR cpr)
{
// Do whatever...
}

DarkTimes
S2 licensed
Quote from skywatcher122 :hey darktimes i tried to use your spark cruise code but my problem is loading the database when i close and re-open it the database doesn't even load its like reset to zero. somehow im finding out the Close Save Users.

How are you exiting the program? If you run it in debug mode and close it by just ending the debug session, the database won't be saved. Also when you rebuild the app it will overwrite the database, but you can change that by right-clicking on SparkCruise.sdf in the Solution Explorer, selecting Properties and setting the Copy to Output Directory option to Copy if newer.

I might rewrite the cruise example, as I've learned a crap-bunch about the Entity Framework since I originally wrote it.
DarkTimes
S2 licensed
Quote from broken :And, since my InSim knowledge is not very wide, may I have my moment of dullness, please: Does setting a MCI interval mean that the connection will be kept alive, or do I have to send a packet from the app's side too? So far, from my experience, this is enough, but I'm not sure if it is the right way of doing it.

Spark keeps the connection alive automatically. The interval specifies the number of milliseconds between MCI or NLP packets, so an interval of 500 would be one packet every half second. If you're not using MCI/NLP packets you can set the interval to zero.
Last edited by DarkTimes, .
DarkTimes
S2 licensed
Did you hook up an event to InSimError to catch any background exceptions? You might need to post your source code, or at least some code that reproduces the problem.
DarkTimes
S2 licensed
I've updated the example which should fix it now.
DarkTimes
S2 licensed
Quote from PoVo :Anyway of running this in a GUI application? I tried but "_insim.Run();" freezes the application.

If I remove "_insim.Run();", the InSim app connects, then automatically disconnects BUT runs.

Yes, it works perfectly in a GUI app (that's kinda what it's designed for). You should not call the InSim.Run() method though, as it says in the method intellisense you should only use it in a Console application (and even then it's optional).

Exactly why it is closing I don't know, but I would imagine that an exception is being thrown on the background thread, likely a cross-thread exception. You should hook up an event to InSimError and see what, if anything, is being thrown. Otherwise you may need to debug it.

In fact I addressed the InSim.Run() and cross-thread issues before.

http://www.lfsforum.net/showthread.php?p=1450110#post1450110
Last edited by DarkTimes, .
DarkTimes
S2 licensed
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)

DarkTimes
S2 licensed
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()

DarkTimes
S2 licensed
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'

Last edited by DarkTimes, .
DarkTimes
S2 licensed
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'

Last edited by DarkTimes, . Reason : Added !save command
DarkTimes
S2 licensed
Quote from DevilDare :whatever it is in that bottle.

It's called champagne.
DarkTimes
S2 licensed
Whoever wrote that code didn't understand what finally blocks were for, or try/catch blocks for that matter.
DarkTimes
S2 licensed
SQL:

SELECT * FROM results
ORDER BY lapsDone DESC, overallTime

C#

results.OrderByDescending(r => r.LapsDone).ThenBy(r => r.OverallTime);

DarkTimes
S2 licensed
The devs charge for content, that's how they make money, so it would hurt them if people could add their own for free.

* Plus Flight Sim uses a completely different business model from LFS.
Last edited by DarkTimes, .
DarkTimes
S2 licensed
OK it might not be the same language, they may just have the same name (they do look similar at a cursory glance).

Incidentally you are confusing a language with its library. When I talk about a language I'm talking about its syntax and grammar, that has nothing to do with what functions have been implemented for it.

language != library
DarkTimes
S2 licensed
I think it's GLScript.
FGED GREDG RDFGDR GSFDG