App Modding
App Modding
CODING
Software mods
650
CHANGING GENERIC AC SETTINGS:
• assetto_corsa.ini
• audio_engine.ini
• camera_drivable.ini
• camera_free.ini
• camera_onboard_free.ini
• camera_start_mode.ini
• camera_track.ini
• chase_cam.ini
• chat.ini
• chat_app.ini
• colorCurves.ini
• damage_displayer.ini
• driver_performances.ini
• engine_smoke.ini
• fades.ini
• fanatec.ini
• ghost_car.ini
• graphics.ini
• hdr.ini
• ig_config.ini
• inireaderdocuments.ini
• lighting.ini
• map.ini
• messages.ini
• mouse_hider.ini
• name_displayer.ini
• options.ini
• physics.ini
• pitstop.ini
• proximity_indicator.ini
• random_camera.ini
• replay_steer_driver.ini
• scene_dimmer.ini
• session_info.ini
• skidmarks.ini
• sparks.ini (sparks_backup.ini)
• telemetry_presets.ini
• temp_blister.ini
• track_skins.ini
• tyre_pieces_grass.ini
• tyre_smoke.ini
• tyre_smoke_grass.ini
• tyres_app.ini
• vr.ini
• weather.ini
OH YEEEEAH NOW YOU’RE STARTING TO UNDERSTAND WHAT’S GOING ON HERE
There are some settings to give the tire smoke a more natural color than the default one. You'll need to simply edit your tyre_smoke.ini, which can be found
under %root%\assettocorsa\system\cfg:
[SETTINGS]
EMISSIVE_BLEND=0.5
VELOCITY_BASE=0,0,0
VELOCITY_RANDOM=1,1,1
SIZE_BASE=1,1
SIZE_RANDOM=0.02,0.02
COLOR_BASE=0.70,0.72,0.8 ; RGB (red, green, blue) colour intensity.
COLOR_RANDOM=0.1,0.1,0.1
DRAG_BASE=0.1
DRAG_RANDOM=0.0
651
GRAVITY_BASE=0.1
GRAVITY_RANDOM=0.01
OPACITY_BASE=0.15
OPACITY_RANDOM=0.2
OPACITY_VELOCITY_BASE=-0.1
OPACITY_VELOCITY_RANDOM=0.005
SIZE_VELOCITY_BASE=0.3,0.3
SIZE_VELOCITY_RANDOM=0.1,0.1
VELOCITY_ROTATION_AXIS_RANDOM=0
FREQUENCY_HZ=30
[TRIGGERS]
SLIP_LEVEL=0.8
The most important lines are COLOR_BASE and COLOR_RANDOM. They allow you to create coloured smoke effects too (Fig.).
Fig. – Setting the red value to 10 in COLOR_BASE can get you this result. You can do pretty cool stuff. Imagine neon sci-fi cars in a futuristic environment.
You can use tyre_smoke_grass.ini in the same path to alter the dust effect when going offroad. It’s identical to tyre_smoke.ini, only the [TRIGGERS]
section is missing.
Keep in mind that these config files are for the vanilla effects, and if you enable CSP’s Particle FX, they will/may be overridden.
How to achieve this with CSP then?
Fig. – (left) Vanilla damage display. (right) Alternative Real Display Damage (https://www.racedepartment.com/downloads/alternative-real-display-damage-v1-0.26934) and Minimal Damage
Icon (https://www.assettocorsa.net/forum/index.php?threads/updated-minimal-damage-icon-ac-1-0-rc-and-up.12726).
It is only required to make the textures in white. AC will work by itself the colours, from cream to red (it’s hardcoded).
You can also make the original textures smaller; the original is a bit intrusive. A mod that does this is the smaller damage display / icon mod:
https://www.racedepartment.com/downloads/smaller-damage-display-icon.3858
A big question has always been: how can the display be moved on the screen? There are no settings in the vanilla launcher, but Kunos made it possible to
move the damage icon wherever you want with the damage_displayer.ini in the %root%\assettocorsa\system\cfg folder. Here is the script:
[MAIN]
TIME=8 ; Amount of time the display will stay active after the car takes damage. [s]
652
MAX_DAMAGE_KMH=60
POSITION_X=-1
DISTANCE_FROM_CENTER_Y=26
PRINT_VALUES=0 ; Boolean. Enable or disable damage values debug on screen.
To put the damage display completely to the left side of the monitor change the POSITION_X value to 0. To place it on the right side you have to test:
raise the value until its where you want it.
The DISTANCE_FROM_CENTER_Y value moves it up/down, and a negative number means up from the middle of the screen. Put a positive value there
and it will go downwards.
This file is exactly what the Content Manager launcher modifies in the menu of Fig..
Fig. – The Damage displayer tab under the Assetto Corsa settings menu of CM.
What CM doesn’t give you access to however is the PRINT_VALUES line, useful for debugging the damage levels of the car, which are showed on screen
whenever damage is taken (Fig.). When you are crash testing your mods, I would suggest to increase the TIME value so that the numbers on screen do not
disappear (set it to 3000, so it lasts 50 minutes).
Fig. – The car hit a wall on the front bumper, and the numbers show up accordingly on the left upper corner of the screen.
there are ways of "tricking" the server into going into weird values.
- set the server to increase in 1% track grip increments, have a session carry-over that results in a decimal, and boom! more than 100% track grip
- set temps to the "max" and then use the variation value and it has a chance of going higher
- set the server with a negative track grip change, and it can go below 0% grip
whether the game recognises any values "outside of bounds" I don't know
you can also configure cars with negative restrictor and ballast, and it will increase the performance of the cars (or make them fly)
but you can only do these with the acserver. you can't do them in single player
653
CHAPTER 1: Getting started with Assetto Corsa application development (WIP)
Starting with Assetto Corsa application development is not hard, but it is harder than it needs to be. The common wisdom seems to be to read the sample
applications that come with Assetto Corsa (in /apps/python/, there is a Chat and gMeter application), or to read the code behind some of the applications
being shared on the forums.
This certainly works, and is how I learned, but it takes more guess-and-test than should be necessary. For example, early on in reading the forum I heard of
something called py_log.txt, but it wasn't clear where to find it.
In this document, I pull back the curtain and explicitly show the basics. I assume you already know some Python. Other than a small note about Python's
treatment of global variables, I do not cover any Python.
I'm going to develop below an application called appName. You should, of course, name your application something more descriptive.
Preliminaries
Locations of interest
There are two folders you should be aware of:
• The Assetto Corsa installation directory. This is most likely to be found in your Steam directory. On Windows, this look like C:\Program Files
(x86)\Steam\steamapps\common\assettocorsa. You may have chosen to install AC elsewhere, and I trust you can find where that is.
• The Assetto Corsa documents directory. On Windows, this looks like C:\User\My Documents\Assetto Corsa\, where Useris replaced with your Windows account name.
• In the installation directory, the interesting subfolder is /apps/. This is where the application is placed.
• In the Documents directory, the subfolder of interest is /logs/. In this directory you will find both log.txt and py_log.txt.
log.txt is where AC logs everything about the execution of AC itself. Sometime this will contain relevant information about your application that AC logs
automatically.
py_log.txt is where AC places strings explicitly requested to be logged by running applications.
Basic workflow
The workflow of testing an application isn't the best. It can be slow going when you're making a lot of changes, especially if you make syntax or logical
mistakes. Try to be careful and ensure the code you're attempting to run is correct. You might want to look into something like pylint so you can find errors
in the code without having to run Assetto Corsa.
If something is wrong with your code, and error message might show up automatically in the in-game console or in py_log.txt. It will always show up in
log.txt. The best way to find an error in log.txt is by search for your application name, in this case appName, and reading the surrounding output.
I call an on-track event a session. I usually test in practise mode around a short track like silverstone-international, but it shouldn't matter what you choose.
In either case - an error or a desired change - you don't have to exit Assetto Corsa. Instead, you can alt-tab out, change the code, and then start a new
session. This is faster than continuously starting and exiting the main Assetto Corsa application. It's still a bit of a drag having to exit and restart sessions, so
as I noted before try to be careful that at least the syntax of your code is correct before testing it. Otherwise, you wait around while starting a session only to
find out your application has failed to load.
It's a habit of mine to always check the console at the start of a session. If something in my application is wrong, there is likely to be a message in the
console about what went wrong. It's also often the case that the message in the console contains the line number in my application where the error was
found. If this is not the case, the error might instead be in py_log.txt orlog.txt. Again, you have a good change of finding a specific line number there, or at
worst a helpful error message.
import sys
import ac
import acsys
654
The most basic applications only takes a few lines of code. The AC plugin architecture will automatically execute certain functions in which it expects to find
your code. To begin an application, you must define a function as follows:
def acMain(ac_version):
...
The code for your application will go in place of the ellipses. For now, we'll insert the bare minimum code:
def acMain(ac_version):
appWindow = ac.newApp("appName")
ac.setSize(appWindow, 200, 200)
return "appName"
Actually, the bare minimum is probably just the return statement, but that application is not at all interesting.
If you run Assetto Corsa and start a session, you will find in the application sidebar an entry named appName. If you activate this, you will see a very basic
widget consisting of a 200x200 application window with the name appName at the top. Here is what you should see in the application sidebar:
The function ac.console writes to the Assetto Corsa console. To bring up the console, hit the Home key on your keyboard. Hit the key again to dismiss the
console.
The way you might use these functions is quite similar, so think of it this way:
• Use ac.log when you want the text to persist after the session has ended. This is helpful if you want to debug the application through lots of printed statements.
• Use ac.console when you might want to read the output during the session. By bringing up the in-game console you can immediately view the messages. Yes, you can
alt-tab out and view the py_log.txt while the session is still running, but it's not nearly as pleasant.
These functions are both good targets to dump information that you're not quite sure about. Use them to figure out exactly that a piece of code is doing.
def acMain(ac_version):
appWindow = ac.newApp("appName")
ac.setSize(appWindow, 200, 200)
Start a new session, and check that you see the text Hello, Assetto Corsa console! in the console when you hit the Homekey on your keyboard. Additionally,
ensure that Hello, Assetto Corsa application world! has shown up in the filepy_log.txt.
Unsurprisingly, both should be the case. There were no tricks here. One important thing to note is that your application does not have exclusive usage of
either the console or python log file. Other applications you have installed might also be sending text to the console or python log. You can either disable all
other application, or prefix all message with a unique string, e.g.*** Message from appName: so that you can quickly find the output from your application.
655
Remember, your application windows is a 200x200 widget. Some of this space is taken up by the header, where the appNamelabel automatically appears.
This is why I set the label at position 30 vertically. I set it at 3 horizontally to offset it slightly from the border.
The function acMain has setup our application window. To do something with it, we must use an additional functionacUpdate.
One important thing to note is that we're going to need to access the label l_lapcount from within acUpdate if we want to place dynamic information into it.
So far, the label has been a variable local to acMain. Since we're not the one callingacUpdate, we can't pass the label along to it as a parameter. Instead, we
must make l_lapcount a global variable. To do so, define it outside of acMain. Then, within acMain we must inform the function that l_lapcount is a global
variable. If we forget to do so, we'll create a local variable l_lapcount within acMain which will shadow the global variable, and any changes we make within
acMain will not be visible outside of it. Most importantly, if we forget to do this the actual label we placed in the application window in acUpdate would not
be available from acUpdate.
We'll also add a global variable lapcount which only needs to be accessible within acUpdate. The code should look like so:
l_lapcount=0
lapcount=0
def acMain(ac_version):
global l_lapcount
appWindow = ac.newApp("appName")
ac.setSize(appWindow, 200, 200)
def acUpdate(deltaT):
global l_lapcount, lapcount
laps = ac.getCarState(0, acsys.CS.LapCount)
if laps > lapcount:
lapcount = laps
ac.setText(l_lapcount, "Laps: {}".format(lapcount))
acUpdate takes a parameter that is ???(Guess: ?milli?seconds since it was called last).
Note that within acUpdate we make a call ac.getCarState(0, acsys.CS.LapCount). This might look confusing at first since I never explained anything about it,
but it's just another function made available through our import of ac and acsys. I don't want to duplicate the official documentation, so please look at the
resources section at the end of this guide for a link to the official documentation. Eventually, you should read it so that you know what has been made
available for application development by the Assetto Corsa developers, but it's not important at the moment. You can continue on with this guide.
Now, after completing a lap your application window will look like this:
656
and so on as you complete laps.
You could, of course, also log this information to the console or the python log:
ac.log("{} laps completed".format(lapcount))
ac.console("{} laps completed".format(lapcount))
You'll want to add within acShutdown any code that should be completed before your application exits. For example, if there are outstanding database
modifications, you want to make sure you commit them and safely close the connection to the database.
You can also register callbacks for certain events. Two that I am aware of are ac.addOnAppActivatedListener and ac.addOnAppDismissedListener. For
instance, you might define a function on_activation and register it by calling
ac.addOnAppActivatedListener(appWindow, on_activation)
It seems that acUpdate is always called, even when the application is dismissed. If this is not desirable, the idiom would be to check a flag within acUpdate
that is set in the callback registered with ac.addOnAppActivatedListener, so that you only run code when the application is activated.
Exercise: Add an additional label to the application window and fill it with the current fuel in the tank. Update the value dynamically throughout the session
with a period shorter than once-per-lap.
If you can complete this, you have understood everything I tried to communicate by writing this document.
Pro tip: for a better workflow with Assetto Corsa Python apps, you should install the following library for normal auto-complete for the “ac“ module in the
Python IDE: https://github.com/rikby/ac-stubs
This will solve the problem shown in Fig. below.
FAQ
Some of the basic questions and answers when dealing with apps. Let’s try to help coders.
QUESTION [2]: I've copied the app in the respective folder, but it's not showing in-game.
ANSWER [2]: Any app you install, must be first activated: go to Options > General, and check the box next to the app. The procedure is similar in CM.
QUESTION [3]: I want to activate the app in Options, but it doesn't show in the list.
ANSWER [3]: Check first the instructions (usually in the README.txt if present); you probably didn't copy the app in the right path.
658
CHAPTER 2: AC Python documentation
We’re decrypting info from the enemy. Everything is going as planned sir. Jokes aside, the documentation was in a pretty rough state when I
got my hands on it, many things aren’t clear. Also, the grammar doesn’t help, I know. But for now, enjoy.
import ac
The following functions become available when you import the “ac” module inside your python document. You can find the identifiers in the acsys.py file
under %root%\apps\python\system.
ac.getCarState(<CAR_IDENTIFIER>, <INFO_IDENTIFIER> , /*OPTIONAL*/<OPTIONAL_IDENTIFIER>)
This is the most important function. It returns the <INFO_IDENTIFIER> type of information associated to car <CAR_IDENTIFIER>. The optional identifier can be omitted, it is
used for special info where they require a specific tire, as described in the following section. The <OPTIONAL_IDENTIFIER> and it can be one of the following values:
On the left a synthetic description is given using blue color:
FL, Front Left
FR, Front Right
RL, Rear Left
RR, Rear Right
Using the following <INFO_IDENTIFIER>s ac.getCarState returns a 3D vector (with x,y,z components):
AccG, Gravity acceleration on the vehicle’s GC x,y,z = [0, …]
LocalAngularVelocity, Get the angular velocity of the car, using the car as origin x,y,z = [0, …]
LocalVelocity, Get the velocity using the car as origin x,y,x = [0, …]
SpeedTotal, Get all the speed representation x= km/h, y = mph, z = m/s
Velocity, Current velocity vector x,y,z = [0, …]
WheelAngularSpeed, Current Wheel angular speed x,y,z = [0, …]
WorldPosition, Current Car Coordinates on map x,y,z = [0,...]
Using the following <INFO_IDENTIFIER>s ac.getCarState returns a 4D vector (with w,x,y,z components):
CamberRad, The camber angle in Radiants for each tyre
CamberDeg, The camber angle in Degree for each tyre
SlipAngle, Slip angle of the chosen tire in degrees. [0, 360]
SlipRatio, Slip Ration of the tyres x,y,z,w = [0,1]
Mz, Self-Aligning Torque x,y,z,w = [0, …]
Load, Current load on each tyre x,y,z,w = [0,...]
TyreRadius, Radius of any Tyre x,y,z,w = [0,...]
NdSlip,
TyreSlip,
DY, Lateral friction coefficient for each tyre
CurrentTyresCoreTemp, Current core temperature of the tyres °C x,y,z,w = [0,...]
ThermalState, Current temperature of the tyres °C x,y,z,w = [0,...]
DynamicPressure, Current pressure of the tyres psi x,y,z,w =[0, …]
TyreLoadedRadius, Radius of the tyre under load x,y,z,w =[0, …]
SuspensionTravel, Suspension vertical travel x,y,z,w =[0, …]
TyreDirtyLevel, Quantity of dirt on the tyres x,y,z,w =[0, 10)
Using the following <INFO_IDENTIFIER>s combined with the <OPTIONAL_IDENTIFIER> ac.getCarState returns a 3D vector (with x,y,z components) related to the
<OPTIONAL_IDENTIFIER> wheel:
TyreContactNormal, Normal vector to tyre’s contact point (z)
TyreContactPoint, Tyre contact point with the tarmac
TyreHeadingVector, Tyre Heading Vector (x)
TyreRightVector, Tyre Right Vector (y)
659
Using the following <INFO_IDENTIFIER>s combined with the <OPTIONAL_IDENTIFIER> ac.getCarState returns a scalar vector (with x,y,z components) related to the
<OPTIONAL_IDENTIFIER> index O:
Aero, o=0 drag Coefficient
Aero, o=1 lift Coefficient front
Aero, o=2 lift Coefficient rear
GENERAL INFO:
ac.getDriverName(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car returns as a string the driver’s name of the <CAR_ID> car. This function returns the car name on success, 1 otherwise.
ac.getTrackName(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car returns as a string the track’s name where <CAR_ID> is running. This function returns the track’s name on success, 1
otherwise.
ac.getTrackConfiguration(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car returns as a string the track’s configuration where <CAR_ID> is running. This function returns the track’s name on
success, 1 otherwise.
ac.getCarName(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car returns as a string the car’s name of the <CAR_ID> car. This function returns the car name on success, 1 otherwise.
ac.getLastSplits(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car returns as a Python List the car’s last splits of the <CAR_ID> car. This function returns the Python list with the splits on
success, 1 otherwise.
ac.isCarInPitline(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car. This function returns 1 if the car is currently in the Pitline. (-lane?)
ac.isCarInPit(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car. This function returns 1 if the car is currently in the Pitbox.
ac.isConnected(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car. This function returns 1 if the car is currently connected.
ac.getCarBallast(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car. This function returns the ballast value of the car.
660
ac.getCarMinHeight(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car. This function returns the minimum height of the car.
ac.getServerName()
This function returns the name of the joined server.
ac.getServerIP()
This function returns the IP of the joined server.
ac.getServerHttpPort()
This function returns the Http port of the joined server.
ac.getServerSlotsCount()
This function returns the total number of occupied slots in the server.
ac.getCarsCount()
This function returns the session’s max number of cars.
ac.getCarLeaderboardPosition(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car. This function returns the position of the car on the Leaderboard.
ac.getCarRealTimeLeaderboardPosition(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car. This function returns the position of the car on realtime.
ac.getCarFFB()
This function returns the FFB Gain value for the player current car.
ac.setCarFFB(<VALUE>)
<VALUE>is the value that will be added to the current FFB gain. This function sets the FFB Gain value for the player current car.
CAMERA MANAGEMENT
ac.setCameraMode(<INFO_IDENTIFIER>)
<INFO_IDENTIFIER> can be (Camera Mode): Cockpit, Car, Drivable, Track, Helicopter, OnBoardFree, Free, Random, ImageGeneratorCamera, Start
ac.getCameraMode()
This function returns a <INFO_IDENTIFIER> above
ac.isCameraOnBoard(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car. This function returns 1 on success, 1 otherwise.
ac.setCameraCar(<CAMERA_ID>,<CAR_ID>)
<CAMERA_ID> must be the F6 camera index. <CAR_ID> must be the car ID, 0 for the player’s car. This function returns 1 on success, 1 otherwise.
ac.getCameraCarCount(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car. This function returns the number of F6 cameras, 1 otherwise.
ac.focusCar(<CAR_ID>)
<CAR_ID> must be the car ID, 0 for the player’s car. This method get switch the actor to the selected car. This function returns 1 on success, 1 otherwise.
ac.getFocusedCar()
This method get the selected car index.
DEBUG
ac.log(<VALUE>)
<VALUE> must be a string. Use ac.log if you want to send some text to the AC log.txt file. The function returns 1 on success.
ac.console(<VALUE>)
<VALUE> must be a string. Use ac.console to send a string to the AC console. The function returns 1 on success.
661
ac.setIconPosition(<CONTROL_IDENTIFIER>,<X>,<Y>)
<X>,<Y> must be a floating point numbers, <CONTROL_IDENTIFIER> must be a form. Use ac.setPosition to set the new icon’s position instead of the default one. The
function returns 1 on success, 1 otherwise
ac.setTitlePosition(<CONTROL_IDENTIFIER>,<X>,<Y>)
<X>,<Y> must be a floating point numbers, <CONTROL_IDENTIFIER> must be a form. Use ac.setPosition to set the new title’s position inside the app. The function returns 1
on success, 1 otherwise
ac.getPosition(<CONTROL_IDENTIFIER>)
<CONTROL_IDENTIFIER> is the identifier of a control. Use ac.getPosition to get the control's position in the parent window, this function returns a python tuple width,height.
The function returns the position as a tuple x,y on success, 1 otherwise.
ac.setText(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a string, <CONTROL_IDENTIFIER> is the control that we want to set the text to. Set the text of the control specified by <CONTROL_IDENTIFIER>, with the
<VALUE> text passed as an argument. The function returns 1 on success, 1 otherwise
ac.getText(<CONTROL_IDENTIFIER>)
<CONTROL_IDENTIFIER> is the control that we want to get the text from. Use ac.getText to get the control's text. This function returns the coordinates x,y of the control on
success, 1 otherwise.
ac.setBackgroundOpacity(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a floating point value between 0 and 1. Use ac.setBackgroundOpacity to change the alpha channel of the desired control. The function returns 1 on
success, 1 otherwise.
ac.drawBackground(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be 0 or 1. Use ac.drawBackground to set the background visible (1)(DEFAULT) or transparent (0). The function returns 1 on success, 1 otherwise.
ac.drawBorder(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be 0 or 1. Use ac.drawBorder to draw the border of the desired control (1) (DEFAULT) or not (0). The function returns 1 on success, 1 otherwise.
ac.setBackgroundTexture(<CONTROL_IDENTIFIER>, <PATH>)
<PATH> starts from Assetto Corsa root folder, <CONTROL_IDENTIFIER> must be a control identifier. Use ac.setBackgroundTexture to draw a specified texture stored in the
path specified by <PATH> as background image for the control specified by <CONTROL_IDENTIFIER>. The function returns 1 on success, 1 otherwise.
ac.setFontAlignment(<CONTROL_IDENTIFIER>, <ALIGNMENT>)
<ALIGNMENT> is one of the following strings: “left“, “right” or “center”. Use ac.setFontAlignment to set the font alignment of the control text as specified by the
<ALIGNMENT> string. The function returns 1 on success, 1 otherwise.
ac.setBackgroundColor(<CONTROL_IDENTIFIER>, <R>,<G>,<B>)
<PATH> starts from Assetto Corsa root folder. Use ac.setBackgroundColor to set the background color of the window as specified by the R,G,B values. The function returns 1
on success, 1 otherwise
ac.setVisible(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be 0 or 1. It is possible to hide the object using the function ac.setVisible with VALUE set to 1. The function returns 1 on success, 1 otherwise.
ac.addOnAppActivatedListener(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a function name defined inside the Python script, <CONTROL_IDENTIFIER> must be an app. This method set the <VALUE> function as callback function
for the event of app selection on the task bar. The function returns 1 on success, 1 otherwise.
ac.addOnAppDismissedListener(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a function name defined inside the Python script, CONTROL_IDENTIFIER> must be an app. This method set the <VALUE> function as callback function for
the event of app deselection on the task bar. The function returns 1 on success, 1 otherwise.
ac.addRenderCallback(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a function name defined inside the Python script. This method set the <VALUE> function as callback function for the finished rendering event. The function
returns 1 on success, 1 otherwise
ac.setFontColor(<CONTROL_IDENTIFIER>,<R>,<G>,<B>,<A>)
<CONTROL_IDENTIFIER> must be a Controlidentifier, <R>,<G>,<B>,<A> are the color value scaled from 0 to 1. This function returns 1 on success, 1 otherwise
ac.setFontSize(<CONTROL_IDENTIFIER>, <VALUE>)
This method set <VALUE> as new new size of the control’s font. The function returns 1 on success, 1 otherwise.
ac.initFont(0, <FONTNAME>, <ITALIC>, <BOLD>)
This method loads the font in memory (stored as font+italic+bold). Should be used at the initialization of the application. The font needs to be saved in the “content/fonts”
folder.
<FONTNAME> must be the name of the base font (real name, not filename, so it can be “Arial”) <ITALIC> must be a 0 for nonitalic font, 1 for italic font (instance will be “Arial
Italic”) <BOLD> must be a 0 for nonbold font, 1 for bold font (instance will be “Arial Bold” or “Arial Italic Bold” is also italic).
The function returns 1 on success, 1 otherwise
ac.setCustomFont(<CONTROL_IDENTIFIER>, <FONTNAME>, <ITALIC>, <BOLD>)
This method create or replace the font of a control. Should not be done without calling “iniFont”. The font needs to be saved in the “content/fonts” folder.
<CONTROL_IDENTIFIER> is the control owner of the font <FONTNAME> must be the name of the base font (real name, not filename, so it can be “Arial”) <ITALIC> must be a
0 for nonitalic font, 1 for italic font (instance will be “Arial Italic”) <BOLD> must be a 0 for nonbold font, 1 for bold font (instance will be “Arial Bold” or “Arial Italic Bold” is also
italic)
The function returns 1 on success, 1 otherwise
662
Button:
ac.addButton(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a string, <CONTROL_IDENTIFIER> must be a form. The function adds a Button to the window specified by <CONTROL_IDENTIFIER>. The function returns
the Button ID on success, 1 otherwise
ac.addOnClickedListener(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a function name defined in this file. It is possible to associate the button with an event to trigger when it is clicked using this function. The function returns
1 on success, 1 otherwise
Graph:
ac.addGraph(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a string. The function adds a Graph to the window specified in <CONTROL_IDENTIFIER>. The function returns the Graph ID on success, 1 otherwise.
ac.addSerieToGraph(<CONTROL_IDENTIFIER>, <R>,<G>,<B>)
<R>,<G>,<B> must be floating point numbers between 0 and 1. To plot some data it is necessary to add a Serie. A serie is a succession of points to plot on the graph.
When adding a serie you must specify the color of the serie as argument. The function returns 1 on success, 1 otherwise
ac.addValueToGraph(<CONTROL_IDENTIFIER,<SERIE_INDEX>,<VALUE>)
<SERIE_INDEX> is the Serie ID in the graph that where <VALUE> will be added. The function returns 1 on success, 1 otherwise.
ac.setRange(<CONTROL_IDENTIFIER>,<MIN_VALUE>,<MAX_VALUE>,<MAX_POINTS>)
<MIN_VALUE>,<MAX_VALUE>,<MAX_POINTS> must be floating point numbers. In order to plot the data inside the Graph it is necessary to specify the amplitude of the
ordinates and the maximum number of points to store in memory. The function returns 1 on success, 1 otherwise.
Spinner:
ac.addSpinner(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a string. It is possible to add a Spinner using the function. The function returns the Spinner ID on success, 1 otherwise.
ac.setRange(<CONTROL_IDENTIFIER>, <MIN_VALUE>,<MAX_VALUE>)
<MIN_VALUE>,<MAX_VALUE> must be floating point numbers. It is possible to set the min and max values of the Control:
The function returns 1 on success, 1 otherwise
ac.setValue(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be floating point number. This function set the "value" parameter of the specific Control if this is an available parameter. This function affects controls like
Spinner, Progress Bar or Check Box. The function returns 1 on success, 1 otherwise.
ac.getValue(<CONTROL_IDENTIFIER>)
<VALUE> must be floating point number. This function returns the "value" parameter of the specific Control if this is an available parameter. The function returns the value on
success, 1 otherwise.
ac.setStep(<CONTROL_IDENTIFIER>,<VALUE)
<CONTROL_IDENTIFIER> must be a Spinner ID <VALUE> must be floating point number. Set the value added or subtracted when the + or button is pressed in a Spinner
controller. The function returns 1 on success, 1 otherwise.
ac.addOnValueChangeListener(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a function name defined inside the Python script. It is now possible to associate the spinner with an event to trigger when one of the two buttons is
pressed. The function returns 1 on success, 1 otherwise.
Progress Bar:
ac.addProgressBar(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a string. It is possible to add a Progress Bar using the function. The function returns the Progress Bar ID on success, 1 otherwise.
Input Text:
ac.addTextInput(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a string. It is possible to add an Input Text Field using the function. The function returns the Input Text ID on success, 1 otherwise.
ac.setFocus(<CONTROL_IDENTIFIER>, <FOCUS>)
<CONTROL_IDENTIFIER> must be an Input Text, <FOCUS> must be 0 or 1. If FOCUS is 1, this function sets the Input Text as first responder. The function returns 1 on
success, 1 otherwise
ac.addOnValidateListener(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a function name defined inside the Python script. It is possible to associate the <CONTROL_IDENTIFIER> with an event to trigger when the enter key is
pressed. The function returns 1 on success, 1 otherwise.
List Box:
ac.addListBox(<CONTROL_IDENTIFIER>,<NAME>)
<CONTROL_IDENTIFIER> must be a window identifier. This method adds a List Box to the window specified by CONTROL_IDENTIFIER. The function returns the ListBox ID
on success, 1 otherwise.
ac.addItem(<CONTROL_IDENTIFIER>,<NAME>)
<CONTROL_IDENTIFIER> must be a ListBox identifier. This method adds a List Box item to the List Box specified. The item's label is specified by the Name string.
This function returns the ListBox Item ID on success, 1 otherwise.
663
ac.removeItem(<CONTROL_IDENTIFIER>,<ID>)
<CONTROL_IDENTIFIER> must be a ListBox identifier. This method removes from the List Box the item with ID as identifier. This function returns the size of the List Box on
success, 1 otherwise.
ac.getItemCount(<CONTROL_IDENTIFIER>)
<CONTROL_IDENTIFIER> must be a ListBox identifier. This function returns the size of the List Box on success, 1 otherwise.
ac.setItemNumberPerPage(<CONTROL_IDENTIFIER>,<NUMBER>)
<CONTROL_IDENTIFIER> must be a ListBox identifier, <NUMBER> is the number of element to be displayed desired for each page. This function sets the number of
elements displayed for each page in a List Box. This function returns 1 on success, 1 otherwise
ac.highlightListBoxItem(<CONTROL_IDENTIFIER>,<ID>)
<CONTROL_IDENTIFIER> must be a ListBox identifier, <ID> is the element to be selected. This function sets the list box item with <ID> as identifier as selected. This function
returns 1 on success, 1 otherwise
ac.addOnListBoxSelectionListener(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a function name defined inside the Python script. Control identifier must be a List Box controller otherwise the function does nothing and returns 0.
This method set the <VALUE> function as callback function for the event of item SELECTION inside a ListBox. The callback function receives as input parameters the Item's
NAME and its ID (his position inside the listbox). The function returns 1 on success, 1 otherwise
ac.addOnListBoxDeselectionListener(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a function name defined inside the Python script. Control identifier must be a List Box controller otherwise the function does nothing and returns 0.
This method set the <VALUE> function as callback function for the event of item DESELECTION inside a ListBox. The callback function receives as input parameters the Item's
NAME and its ID (his position inside the listbox). The function returns 1 on success, 1 otherwise
ac.setAllowDeselection(<CONTROL_IDENTIFIER>,<ALLOW_DESELECTION>)
<CONTROL_IDENTIFIER> must be a ListBox identifier, <ALLOW_DESELECTION> must be 0 or 1. Passing true as a parameter, when the user clicks on a selected item the
item is deselected. In this way there could be 0 or 1 selected item at a given time. If also ac.setAllowMultiSelection is set as true there can be more than 1 selected items at a
given time. This function returns 1 on success, -1 otherwise
ac.setAllowMultiSelection(<CONTROL_IDENTIFIER>,<ALLOW_MULTI_SELECTION>)
<CONTROL_IDENTIFIER> must be a ListBox identifier, <ALLOW_MULTI_SELECTION> must be 0 or 1. Passing true as a parameter, when the user clicks on a different item
the item is added to the selected item list. In this way there could me more than one selected items at a given time. This function returns 1 on success, 1 otherwise.
ac.getSelectedItems(<CONTROL_IDENTIFIER>)
<CONTROL_IDENTIFIER> must be a ListBox identifier. This method returns the list of the selected items at a given time. This function returns a Python list of Long on
success, 1 otherwise.
Check Box:
ac.addCheckBox(<CONTROL_IDENTIFIER>,<VALUE>)
<CONTROL_IDENTIFIER> must be a form ,<VALUE> must be the form’s name. This function adds a checkbox to the current form passed as <CONTROL_IDENTIFIER>.
The function returns the checkbox created on success, 1 otherwise.
ac.addOnCheckBoxChanged(<CONTROL_IDENTIFIER>, <VALUE>)
<VALUE> must be a function name defined inside the Python script. Control identifier must be a Check Box controller otherwise the function does nothing and returns 0.
This method set the <VALUE> function as callback function for the event of checkbox SELECTION or DESELECTION inside a ListBox. The callback function receives as input
parameters the CheckBox's NAME and its value, 1 if selected, 1 otherwise. The function returns 1 on success, 1 otherwise.
Text Box:
ac.addTextBox(<CONTROL_IDENTIFIER>,<NAME>)
<CONTROL_IDENTIFIER> form identifier, <NAME> text box name. This method adds a text box (scrollable if the text is longer than the textbox, to the current form.
THIS CONTROL IS NOT CURRENTLY WORKING YET, so no set text has been exposed
664
ac.glColor4f(<R>,<G>,<B>,<A>)
<R>,<G>,<B> rgb coordinates scaled from 0 to 1, <A> alpha component from 0 to 1, all the values must be a floating point numbers set the current rendering color to
<R>,<G>,<B> color, with <A> as transparency. This function returns 1 on success, 1 otherwise.
ac.glQuad(<X>,<Y>,<WIDTH>,<HEIGHT>)
<X>,<Y>,<WIDTH>,<HEIGHT> must be a floating point numbers draw a quad quickly without using glBegin, … , glEnd This function returns 1 on success, 1 otherwise
ac.glQuadTextured(<X>,<Y>,<WIDTH>,<HEIGHT>,<TEXTURE_ID>)
<X>,<Y>,<WIDTH>,<HEIGHT> must be a floating point numbers, <TEXTURE_ID> is the id of the texture previously loaded. draw a quad quickly without using glBegin, … ,
glEnd This function returns 1 on success, 1 otherwise
ac.getCarRestrictor
ac.getCarTyreCompound
ac.getSize
ac.setAppTitle
ac.setFont
ac.shutdown
665
AC Shared Memory Documentation (another piece of the puzzle is here ;-D)
SPageFileStatic
The following members are initialized when the instance starts and never changes until the instance is closed.
wchar_t smVersion[15] ; Version of the Shared Memory structure
wchar_t acVersion[15] ; Version of Assetto Corsa
int numberOfSessions = 0 ; Number of sessions in this instance
int numCars = 0 ; Max number of possible cars on track
wchar_t carModel[33] ; Name of the player’s car
wchar_t track[33] ; Name of the track
wchar_t playerName[33] ; Name of the player
wchar_t playerSurname[33] ; Surname of the player
wchar_t playerNick[33] ; Nickname of the player
int sectorCount = 0 ; Number of track sectors
float maxTorque = 0 ; Max torque value of the player’s car
float maxPower = 0 ; Max power value of the player’s car
int maxRpm = 0 ; Max rpm value of the player’s car
float maxFuel = 0 ; Max fuel value of the player’s car
float suspensionMaxTravel[4] ; Max travel distance of each tyre [Front Left, Front Right, Rear Left, Rear Right]
float tyreRadius[4] ; Radius of each tyre [Front Left, Front Right, Rear Left, Rear Right]
float maxTurboBoost = 0 ; Max turbo boost value of the player’s car
float deprecated_1 ; Do not use it
float deprecated_2 ; Do not use it
int penaltiesEnabled = 0 ; Cut penalties enabled: 1 (true) or 0 (false)
float aidFuelRate = 0 ; Fuel consumption rate: 0 (no cons), 1 (normal), 2 (double cons) etc.
float aidTireRate = 0 ; Tire wear rate: 0 (no wear), 1 (normal), 2 (double wear) etc.
float aidMechanicalDamage = 0 ; Damage rate: 0 (no damage) to 1 (normal)
int aidAllowTyreBlankets = 0 ; Player starts with hot (optimal temp) tyres: 1 (true) or 0 (false)
float aidStability = 0 ; Stability aid: 0 (no aid) to 1 (full aid)
int aidAutoClutch = 0 ; If player’s car has the “auto clutch” feature enabled : 0 or 1
int aidAutoBlip = 0 ; If player’s car has the “auto blip” feature enabled : 0 or 1
int hasDRS = 0 ; If player’s car has the “DRS” system: 0 or 1
int hasERS = 0 ; If player’s car has the “ERS” system: 0 or 1
int hasKERS = 0 ; If player’s car has the “KERS” system: 0 or 1
float kersMaxJ = 0 ; Max KERS Joule value of the player’s car
int engineBrakeSettingsCount = 0 ; Count of possible engine brake settings of the player’s car
int ersPowerControllerCount = 0 ; Count of the possible power controllers of the player’s car
float trackSPlineLength = 0 ; Length of the spline of the selected track
wchar_t trackConfiguration[33] ; Name of the track’s layout (only multi-layout tracks)
float ersMaxJ = 0 ; Max ERS Joule value of the player’s car
int isTimedRace = 0 ; 1 if the race is a timed one
int hasExtraLap = 0 ; 1 if the timed race is set with an extra lap
wchar_t carSkin[33] ; Name of the used skin
int reversedGridPositions ; How many positions are going to be swapped in the second race
int PitWindowStart ; Pit window is open on Lap/Minute
int PitWindowEnd ; Pit window is closed on Lap/Minute
SPageFilePhysics
The following members change at each graphic step. They all refer to the player’s car.
int packetId = 0 ; Index of the shared memory’s current step
float gas = 0 ; Value of gas pedal: 0 to 1 (fully pressed)
float brake = 0 ; Value of brake pedal: 0 to 1 (fully pressed)
float fuel = 0 ; Liters of fuel in the car
int gear = 0 ; Selected gear (0 is reverse, 1 is neutral, 2 is first gear)
int rpms = 0 ; Value of rpm
float steerAngle = 0 ; Angle of steer
float speedKmh = 0 ; Speed in Km/h
float velocity[3] ; Velocity for each axis (world related) [x, y, z]
float accG[3] ; G-force for each axis (local related) [x, y, z]
float wheelSlip[4] ; Spin speed of each tyre [Front Left, Front Right, Rear Left, Rear Right]
float wheelLoad[4] ; Load on each tyre (in N) [Front Left, Front Right, Rear Left, Rear Right]
float wheelsPressure[4] ; Pressure of each tyre [Front Left, Front Right, Rear Left, Rear Right]
float wheelAngularSpeed[4] ; Angular speed of each tyre [Front Left, Front Right, Rear Left, Rear Right]
float tyreWear[4] ; Current wear of each tyre [Front Left, Front Right, Rear Left, Rear Right]
float tyreDirtyLevel[4] ; Dirt level on each tyre [Front Left, Front Right, Rear Left, Rear Right]
float tyreCoreTemperature[4] ; Core temperature of each tyre [Front Left, Front Right, Rear Left, Rear Right]
float camberRAD[4] ; Camber of each tyre in Radian [Front Left, Front Right, Rear Left, Rear Right]
float suspensionTravel[4] ; Suspension travel for each tyre [Front Left, Front Right, Rear Left, Rear Right]
float drs = 0 ; If DRS is present and enabled: 0 (false) or 1 (true)
float tc = 0 ; Slip ratio limit for the traction control (if enabled)
float heading = 0 ; Heading of the car on world coordinates
float pitch = 0 ; Pitch of the car on world coordinates
float roll = 0 ; Roll of the car on world coordinates
float cgHeight ; Height of Center of Gravity
float carDamage[5] ; Level of damage for each car section (only first 4 are valid)
int numberOfTyresOut = 0 ; How many tyres are allowed to stay out of the track to not receive a penalty
int pitLimiterOn = 0 ; If pit limiter is enabled: 0 (false) or 1 (true)
float abs = 0 ; Slip ratio limit for the ABS (if enabled)
float kersCharge = 0 ; KERS/ERS battery charge: 0 to 1
float kersInput = 0 ; KERS/ERS input to engine: 0 to 1
int autoShifterOn = 0 ; If auto shifter is enabled: 0 (false) or 1 (true)
float rideHeight[2] ; Right heights: front and rear
float turboBoost = 0 ; Turbo boost
float ballast = 0 ; Kilograms of ballast added to the car (only in multiplayer)
float airDensity = 0 ; Air density
float airTemp = 0 ; Ambient temperature
float roadTemp = 0 ; Road temperature
float localAngularVel[3] ; Angular velocity of the car [x, y, z]
float finalFF = 0 ; Current Force Feedback value;
float performanceMeter = 0 ; Performance meter compared to the best lap
int engineBrake = 0 ; Engine brake setting
int ersRecoveryLevel = 0 ; ERS recovery level
666
int ersPowerLevel = 0 ; ERS selected power controller
int ersHeatCharging = 0 ; ERS changing: 0 (Motor) or 1 (Battery)
int ersIsCharging = 0 ; If ERS battery is recharging: 0 (false) or 1 (true)
float kersCurrentKJ = 0 ; KERS/ERS KiloJoule spent during the lap
int drsAvailable = 0 ; If DRS is available (DRS zone): 0 (false) or 1 (true)
int drsEnabled = 0 ; If DRS is enabled: 0 (false) or 1 (true)
float brakeTemp[4] ; Brake temp for each tire [Front Left, Front Right, Rear Left, Rear Right]
float clutch = 0 ; Value of clutch pedal: 0 to 1 (fully pressed)
float tyreTempI[4] ; Inner temperature of each tyre [Front Left, Front Right, Rear Left, Rear Right]
float tyreTempM[4] ; Middle temperature of each tyre [FL, FR, RL, RR]
float tyreTempO[4] ; Outer temperature of each tyre [FL, FR, RL, RR]
int isAIControlled ; AI controlled car: 0 (human) or 1 (AI)
float tyreContactPoint[4][3] ; Vector for contact point of each tyre [FL, FR, RL, RR][x, y, z]
float tyreContactNormal[4][3] ; Vector for contact normal of each tyre [FL, FR, RL, RR][x, y, z]
float tyreContactHeading[4][3] ; Vector for contact heading of each tyre [FL, FR, RL, RR][x, y, z]
float brakeBias ; Brake bias from 0 (rear) to 1 (front)
float localVelocity[3] ; Vector for local velocity
struct SPageFileGraphic
The following members change at each graphical step. They all refer to the player’s car.
int packetId = 0 ; Index of the shared memory’s current step
AC_STATUS status = AC_OFF ; Status of the instance: AC_OFF 0; AC_REPLAY 1; AC_LIVE 2; AC_PAUSE 3
AC_SESSION_TYPEsession =AC_PRACTICE ; Session type: AC_UNKNOWN -1; AC_PRACTICE 0¸AC_QUALIFY 1; AC_RACE 2; AC_HOTLAP 3;
AC_TIME_ATTACK 4; AC_DRIFT 5; AC_DRAG 6
wchar_t currentTime[15] ; Current lap time
wchar_t lastTime[15] ; Last lap time
wchar_t bestTime[15] ; Best lap time
wchar_t split[15] ; Time in sector
int completedLaps = 0 ; Number of completed laps by the player
int position = 0 ; Current player position (standings)
int iCurrentTime = 0 ; Current lap time
int iLastTime = 0 ; Last lap time
int iBestTime = 0 ; Best lap time
float sessionTimeLeft = 0 ; Time left until session is closed
float distanceTraveled = 0 ; Distance traveled during the instance
int isInPit = 0 ; If player’s car is stopped in the pit: 0 (false) or 1 (true)
int currentSectorIndex = 0 ; Current sector index
int lastSectorTime = 0 ; Last sector time
int numberOfLaps = 0 ; Number of laps needed to close the session
wchar_t tyreCompound[33] ; Current tyre compound
float replayTimeMultiplier = 0 ; Replay multiplier
float normalizedCarPosition = 0 ; Car position on the track’s spline
float carCoordinates[3] ; Car position on world coordinates [x, y, z]
float penaltyTime = 0 ; Time of penalty
AC_FLAG_TYPEflag =AC_NO_FLAG ; Type of flag being shown: AC_NO_FLAG 0; AC_BLUE_FLAG 1; AC_YELLOW_FLAG 2; AC_BLACK_FLAG
3; AC_WHITE_FLAG 4; AC_CHECKERED_FLAG 5; AC_PENALTY_FLAG 6
int idealLineOn = 0 ; If ideal line is enabled: 0 (false) or 1 (true)
int isInPitLane = 0 ; If player’s car is in the pitlane: 0 (false) or 1 (true)
float surfaceGrip = 0 ; Current grip of the track’s surface
int mandatoryPitDone = 0 ; Set to 1 if the player has done the mandatory pit
float windSpeed = 0 ; Speed of the wind on the current session
float windDirection = 0 ; Direction of the wind (0-359 degrees) on the current session
physics : acpmf_physics
graphics : acpmf_graphics
static : acpmf_static
667
AC UDP Remote Telemetry Documentation
AC supports remote telemetry via UDP (User Datagram Protocol) socket. It is possible to make your application read real time telemetry data from AC
following this document.
All the code samples follow the C++ syntax and can be made with Visual Studio Editor. The PC running Assetto Corsa will be referred as the ACServer, and
the Remote Telemetry Server will be referred to as RTS. The data types are the following:
• int are 32-bit little endian integers
• float are 32-bit floating point numbers
• bool are 8-bit boolean values
where:
• intidentifier: not used in the current Remote Telemetry version by AC. In future versions it will identify the platform type of the client. This will be used to adjust a
specific behaviour for each platform:
eIPhoneDevice =0
eIPadDevice =1
eAndroidPhone =2
eAndroidTablet =3
• intversion: not used in the current Remote Telemetry version by AC. In future version this field will identify the AC Remote Telemetry version that the device expects
to speak with.
• intoperationId: this is the type of operation required by the client.
In summary, for the first handshaking phase your application will need to send the following structured data to ACServer:
structhandshaker;
handshaker.identifier = 1 ;
handshaker.version = 1 ;
handshaker.operationId= 0 ;
where:
• charcarName[50]: is the name of the car that the player is driving on the AC Server
• chardriverName[50]: is the name of the driver running on the AC Server
• intidentifier: for now is just 4242, this code will identify different status, as “NOT AVAILABLE” for connection
• intversion: for now is set to 1, this will identify the version running on the AC Server
• chartrackName[50]: is the name of the track on the AC Server
• chartrackConfig[50]: is the track configuration
Your application will need to parse this structured data in order to get the information. This step is necessary to understand which driver we’re connecting to.
668
Now, operationId must be one of the following options: SUBSCRIBE_UPDATE = 1 or SUBSCRIBE_SPOT = 2 (see paragraph 1.1).
After this phase the Client is added as a listener to AC Remote Telemetry listeners.
• charidentifier: is set to char “ a ” , it is used to understand that the structured data is the data that the client app wants
• int size: the size of the structured data in Bytes.
If the client subscribed himself with SUBSCRIBE_SPOT identifier, it will receive the following structured data whenever a spot event is triggered (for example for the end of a
lap).
Differently from SUBSCRIBE_UPDATE, this event will interest all the cars in the AC session:
structRTLap
{
intcarIdentifierNumber;
intlap;
chardriverName[50];
charcarName[50];
inttime;
};
Your application will need to parse locally the structured data sent by ACServer.
669
ENDING
Let’s go back to a simpler time.
The truth? None of us are experienced enough at modding Assetto Corsa. That being said, it's not rocket science.
Modding is just like any other hobby, only you are trying to finish something. You can have passions like karate, mountain biking, scale models, beer drinking
or bowling just for fun without any goals and you never need to finish anything. There is no end product. In modding you want to create something. Starting
things is easy. Finishing things is difficult.
Like with all hobbies, as time goes by, you can lose interest. Maybe you get into a situation where it is less fun and more work and you just don't feel like
doing it anymore. Or you continue "later", that becomes “never”. Your life schedule changes and you have less time and/or energy to keep learning complex
software. Maybe you just found something else that is more fun. I don't really see anything mysterious about it. How many times did you start something and
never finished it?
I think a huge part is about problem solving. The documentation and the tools are never perfect and with some games they are downright appalling. To
conclude something, you need to learn how specific game wants things and how its features work, what different parameters mean and what works, what is
deprecated or disabled and the different ways to get a result.
That’s why this book details what’s important. All the info you need is at your fingertips.
But even with good documentation and tools, there are steps along the way where complexity ramps up. I think they are:
• Problems that just seem like they can’t be solved. Maybe the car won't load into the game, some feature just doesn't work and you can’t find any information about it. You
keep trying and get more and more desperate and negative as you go. Eventually it feels like waste of time. It is not necessarily a problem that stops all work but it puts a
big barrier on your willingness to keep going. In the end it is not fun anymore.
• When you figure out something you need to do, and notice it is long hours of tedious work, you may just lose interest. This could be things like splitting your mesh into
different materials and UV maps. Doing this first time is difficult because you don't really know how to do it. You really need to get through the process at least once from
beginning to end so you know how it works and comes together. This can be a huge problem if you want to work so that you are 100% finished with one thing before
moving to the next thing. Especially if you are not experienced you need to go back and forth constantly, sometimes doing some things multiple times to get it right.
• Having to learn new software. If you want to make a car you need your 3D modeler and some kind of texture painting software. You need to learn how different DDS files
work and their settings. You have some smaller tools you may use for normal mapping, font creation. If you make a track from Lidar you need to learn to use software that
handles the point cloud data. Even inside the 3D software there are many different sections that are almost like separate programs. For physics you need to find some kind
of suspension program and learn to use spreadsheet program.
• If you are beginner at everything you need lots of time and will power to learn all of that from scratch. You need to learn how to search for textures and references. Good
quality reference takes time to find. A 400x320 pixel blurry picture of a car going around a corner is not reference. And every time you need to find one image of something
you need to have the persistence of mind to go to internet and find just that image and then get back to work instead of going to relax on forums writing long posts or
watching cat videos on YouTube.
I don't think any car or track mod project gets abandoned because the scaling is wrong or pivots are wrong. Any person who has that low level of modding
pain tolerance never had the passion to keep working on it. It is the smaller and bigger things that from outside are almost invisible.
You want a deeper truth? Watching video tutorials on the internet will not make you learn anything. In an attempt to try and show an “idiot” a method to
solve a problem under ten minutes, they take the shortest possible route to give a solution, with shortcuts that sometimes do not turn out to be such, and
they do not give a reason for what they do, but remain at the level of a parrot who only knows how to repeat the same procedure over and over again. While
this may actually work for an ”idiot” as long as he doesn’t forget it, the one who wants to evolve to produce something useful and satisfying for himself and
others will always fall behind. It would take hours to prepare a comprehensive and non-redundant speech, but even if it were redundant, a speech that can
teach. Revealing how much and how one can go wrong is more difficult.
Having the desire is not enough. Life is hard. So, experiment away, expect it to go wrong loads of times, and eventually you will know what works for you
the way you do it. This will make you far more powerful as a modder.
I am truly, deeply happy, because I can say that I made the very first manual for AC. Being the first, it’s also the best out there! And I am the last one to
laugh. Those few who participated to the story of this project will know what I mean.
You’ll probably still have an army of questions. You’re not alone, there are many others. But I can’t answer now.
This is Farewell.
671