Skip to content
This repository was archived by the owner on Jul 24, 2022. It is now read-only.

Commit f0ee1aa

Browse files
committed
replays working
1 parent 5a2d826 commit f0ee1aa

27 files changed

+171
-43
lines changed

README.md

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
## Formula 1 TV Leaderboard App for Assetto Corsa
22

3-
App for our assetto corsa Formula 1 championship
3+
App for our assetto corsa Formula 1 championship.
44

5-
Can be tweaked for other championships using the "teams.ini" file
5+
Can be tweaked for other championships using the "teams.ini" file.
6+
7+
During the race it saves a file that can be read when replaying the race to be able to load the whole UI again.
68

79
## STILL A WORK IN PROGRESS!!
810

@@ -11,11 +13,11 @@ Can be tweaked for other championships using the "teams.ini" file
1113

1214
Each line contains the following information about each driver in order separated by a colon "`:`"
1315

14-
- Team Name
15-
- Name of the image file inside teams/ with the team color (without file extension)
16-
- Driver Number
17-
- Driver Name
16+
- Team Name
17+
- Name of the image files inside `teams/` and `cars/` with the team color (without file extension)
18+
- Name of the image files inside `numbers` (without file extension)
19+
- Driver Name
1820

1921

2022
### Screenshots
21-
![im1](/screenshots/screen1.png)
23+
![im1](/screenshots/screen1.png)

apps/python/F12020Leaderboard/DriverWidget.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ def show(self, id, pos, starting_position, tyres, pit_stops):
199199
ac.setVisible(self.teamNameLabel, 1)
200200
ac.setVisible(self.numberLabel, 1)
201201
except KeyError:
202-
ac.console("%s:Name Missing in teams.txt %s" % (FC.APP_NAME, name))
202+
ac.log("%s:Name Missing in teams.txt %s" % (FC.APP_NAME, name))
203203

204204
if self.extended != DriverWidget.extended or self.id != id:
205205
self.extended = DriverWidget.extended
@@ -239,5 +239,4 @@ def show(self, id, pos, starting_position, tyres, pit_stops):
239239

240240
@staticmethod
241241
def toogle_extended(*args):
242-
ac.console("HERE")
243242
DriverWidget.extended = not DriverWidget.extended

apps/python/F12020Leaderboard/F12020Leaderboard.py

+158-30
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import sys
22
import os
33
import time
4+
import bisect
45

56
import ac
67
import acsys
@@ -18,19 +19,24 @@
1819
from LeaderboardRow import LeaderboardRow
1920
from FastestLapBanner import FastestLapBanner
2021

22+
23+
MAX_LAP_TIME = 999999999
24+
2125
# TIMERS
2226
timer0, timer1, timer2 = 0, 0, 0
27+
2328

2429
# VARIABLES
2530
totalDrivers = 0
2631
drivers = None
27-
fastest_lap = 99999999
32+
fastest_lap = MAX_LAP_TIME
2833

2934
race_started = False
3035
replay_started = False
3136

3237
# REPLAY FILE
3338
replay_file = None
39+
replay_data = None
3440

3541
# WINDOWS
3642
leaderboardWindow = None
@@ -54,6 +60,8 @@ def __init__(self, id, n_splits):
5460
self.pit_time = 0
5561
self.tyre = ""
5662
self.timeDiff = 0
63+
self.out = False
64+
self.current_lap = 0
5765

5866
def get_split_id(self, spline):
5967
return int(spline//(1/self.n_splits))
@@ -139,6 +147,7 @@ def acUpdate(deltaT):
139147
global race_started, replay_started
140148

141149
global replay_file
150+
global replay_data
142151

143152
# Widgets
144153
global leaderboardWindow, driverWidget
@@ -168,7 +177,9 @@ def acUpdate(deltaT):
168177

169178
# ============================
170179
# SERVER LAP
171-
lc = max((ac.getCarState(i, acsys.CS.LapCount) for i in range(totalDrivers))) + 1
180+
for i in range(totalDrivers):
181+
drivers[i].current_lap = ac.getCarState(i, acsys.CS.LapCount)
182+
lc = max((drivers[i].current_lap for i in range(totalDrivers))) + 1
172183
if lc >= info.graphics.numberOfLaps:
173184
ac.setText(lapCountTimerLabel, "FINAL LAP")
174185
ac.setFontColor(lapCountTimerLabel, 1,0,0,1)
@@ -183,22 +194,22 @@ def acUpdate(deltaT):
183194
driver_ahead, driver = dPosition[i-1], dPosition[i]
184195
timeDiff = driver.split_times[driver.current_split - 1] - driver_ahead.split_times[driver.current_split - 1]
185196
if timeDiff < 0: continue # ignore these times, happens on overtakes
197+
if driver.position > totalDrivers: continue # might try to update before it is possible
186198
driver.timeDiff = timeDiff
187-
if driver.position > totalDrivers: continue # might try to update befor it is possible
188199
leaderboard[driver.position].update_time("+" + time_to_string(timeDiff*1000))
189200
leaderboard[0].update_time("Interval") # Force it
190201

191202
# ============================
192203
# MARK FASTEST LAP
193204
if lc > FC.FASTEST_LAP_STARTING_LAP:
194-
fastest_driver = None
195205
for d in drivers:
196206
lap = ac.getCarState(d.id, acsys.CS.BestLap)
197207
if lap != 0 and lap < fastest_lap:
198208
fastest_lap = lap
199209
fastest_lap_banner.show(lap, ac.getDriverName(d.id))
200-
fastest_driver = d
201210
LeaderboardRow.FASTEST_LAP_ID = d.id;
211+
if replay_file:
212+
write_fastest_lap(replay_file, info.graphics.completedLaps, info.graphics.iCurrentTime, d, fastest_lap)
202213
for row in leaderboard:
203214
row.mark_fastest_lap()
204215

@@ -216,14 +227,17 @@ def acUpdate(deltaT):
216227

217228
# ============================
218229
# CHANGE CAR FOCUS AND DRIVER WIDGET
219-
id = ac.getFocusedCar()
220-
if drivers[id].position <= totalDrivers: # in case it wasnt updated yet
221-
driverWidget.show(id, drivers[id].position, drivers[id].starting_position, drivers[id].tyre, drivers[id].pits)
230+
if race_started:
231+
id = ac.getFocusedCar()
232+
if drivers[id].position <= totalDrivers: # in case it wasnt updated yet
233+
driverWidget.show(id, drivers[id].position, drivers[id].starting_position, drivers[id].tyre, drivers[id].pits)
234+
else:
235+
driverWidget.hide()
222236

223237
# ========================================================
224238
# SAVE DRIVER STATUS IN A FILE TO LOAD ON REPLAY
225239
if replay_file:
226-
write_driver_info(replay_file, info.graphics.iCurrentTime, drivers)
240+
write_driver_info(replay_file, info.graphics.completedLaps, info.graphics.iCurrentTime, drivers)
227241
# ========================================================
228242

229243
# 3 times per second
@@ -234,16 +248,20 @@ def acUpdate(deltaT):
234248
for d in drivers:
235249
d.starting_position = ac.getCarLeaderboardPosition(d.id)
236250
d.tyre = ac.getCarTyreCompound(d.id)
237-
replay_file = setup_replay_file(drivers) # save starting info on the drivers
251+
replay_file = setup_replay_file(drivers, info.graphics.numberOfLaps) # save starting info on the drivers
238252

239253
# ============================
240254
# POSITION UPDATE
241255
for i in range(totalDrivers):
242256
pos = ac.getCarRealTimeLeaderboardPosition(i)
243-
if ac.isConnected(i) == 0: # mark unconnected drivers
257+
connected = ac.isConnected(i)
258+
if connected == 0 and not drivers[i].out: # mark unconnected drivers
244259
leaderboard[pos].mark_out()
245-
else:
260+
drivers[i].out = True
261+
elif connected == 1 and drivers[i].out:
246262
leaderboard[pos].mark_in()
263+
drivers[i].out = False
264+
if connected == 1:
247265
leaderboard[pos].update_name(i)
248266

249267
# OVERTAKE
@@ -303,20 +321,86 @@ def acUpdate(deltaT):
303321
ac.setBackgroundOpacity(driverWidget.window, 0)
304322
ac.setBackgroundOpacity(fastest_lap_banner.window, 0)
305323

306-
id = ac.getFocusedCar()
307-
drivers[id].position = 1
308-
driverWidget.show(id, drivers[id].position, drivers[id].starting_position, drivers[id].tyre, drivers[id].pits)
324+
# ============================
325+
# SERVER LAP
326+
if replay_data:
327+
lc = max((drivers[i].current_lap for i in range(totalDrivers))) + 1
328+
if lc >= replay_data['nLaps']:
329+
ac.setText(lapCountTimerLabel, "FINAL LAP")
330+
ac.setFontColor(lapCountTimerLabel, 1,0,0,1)
331+
else:
332+
ac.setText(lapCountTimerLabel, "%d / %d" % (lc, replay_data['nLaps']))
333+
ac.setFontColor(lapCountTimerLabel, 0.86, 0.86, 0.86, 1)
334+
335+
# ============================
336+
# PITS MARKER
337+
for row in leaderboard:
338+
if ac.isCarInPitline(row.driverId) == 1:
339+
row.mark_enter_pits()
340+
else:
341+
row.mark_left_pits()
342+
343+
# ============================
344+
# DRIVER WIDGET UPDATE
345+
if replay_started:
346+
id = ac.getFocusedCar()
347+
driverWidget.show(id, drivers[id].position, drivers[id].starting_position, drivers[id].tyre, drivers[id].pits)
348+
else:
349+
driverWidget.hide()
350+
351+
# ============================
352+
# UPDATE TIMES
353+
if replay_data:
354+
for row in leaderboard:
355+
row.update_time("+" + time_to_string(drivers[row.driverId].timeDiff*1000))
356+
if row.row == 0:
357+
row.update_time("Interval") # Force it
309358

310359
if timer1 > 0.3:
311360
if not replay_started:
312361
if info.graphics.iCurrentTime > 0:
313-
replay_file = open_replay_file_read()
362+
replay_data = load_replay_file(drivers)
314363
replay_started = True
315-
for d in drivers:
316-
d.starting_position = ac.getCarLeaderboardPosition(d.id)
317-
d.tyre = ac.getCarTyreCompound(d.id)
318364

319-
365+
# ============================
366+
# FASTEST LAP BANNER TIMER
367+
if fastest_lap_banner.timer > 0:
368+
fastest_lap_banner.timer -= timer1
369+
fastest_lap_banner.hide()
370+
371+
# ============================
372+
# GET DATA FOR THIS UPDATE
373+
if replay_data:
374+
new_positions = lookup_data(info.graphics.completedLaps, info.graphics.iCurrentTime, replay_data, drivers)
375+
376+
# ============================
377+
# POSITION UPDATE
378+
for i in range(totalDrivers):
379+
pos = new_positions[i]
380+
if drivers[i].out: # mark unconnected drivers
381+
leaderboard[pos].mark_out()
382+
else:
383+
leaderboard[pos].mark_in()
384+
leaderboard[pos].update_name(i)
385+
386+
# OVERTAKE
387+
if pos != drivers[i].position: # there was an overtake
388+
drivers[i].timer = FC.OVERTAKE_POSITION_LABEL_TIMER # set timer
389+
if pos < drivers[i].position:
390+
leaderboard[pos].mark_green_position()
391+
elif pos > drivers[i].position:
392+
leaderboard[pos].mark_red_position()
393+
elif drivers[i].timer <= 0:
394+
leaderboard[pos].mark_white_position()
395+
else:
396+
drivers[i].timer -= timer1
397+
drivers[i].position = pos
398+
# END OVERTAKE
399+
400+
timer1 = 0
401+
402+
# END UPDATE
403+
320404
def acShutdown():
321405
global replay_file
322406
if replay_file:
@@ -325,23 +409,67 @@ def acShutdown():
325409
def reset_variables():
326410
pass
327411

328-
def write_driver_info(replay_file, time, drivers):
329-
data = "%d " % time
412+
def write_driver_info(replay_file, laps, time, drivers):
413+
data = "U %d %d " % (laps, time)
330414
for d in drivers:
331-
data += "[%d;%f;%s;%d] " % (d.position, d.timeDiff, d.tyre, d.pits)
415+
data += "%d;%f;%s;%d;%d;%d " % (d.position, d.timeDiff, d.tyre, d.pits, int(d.out), d.current_lap)
332416
replay_file.write(data+'\n')
333-
pass
334417

335-
def setup_replay_file(drivers):
418+
def write_fastest_lap(replay_file, laps, time, driver, fastest_lap):
419+
replay_file.write("FL %d;%d %d;%f\n" % (laps, time, driver.id, fastest_lap))
420+
421+
def setup_replay_file(drivers, nLaps):
336422
replay_file = open(FC.REPLAY_DIR + "replayFile.txt", "w")
337-
data = "START: "
423+
data = "START %d %d " % (len(drivers), nLaps)
338424
for d in drivers:
339-
data += "[%d;%s] " % (d.starting_position, d.tyre)
425+
data += "%d;%s " % (d.starting_position, d.tyre)
340426
replay_file.write(data+'\n')
341427
return replay_file
342428

343-
def load_replay_file():
429+
def load_replay_file(drivers):
344430
try:
345-
return open(FC.REPLAY_DIR + "replayFile.txt", "r")
431+
with open(FC.REPLAY_DIR + "replayFile.txt", "r") as rf:
432+
data = {}
433+
line = next(rf).split()
434+
if line[0] != "START":
435+
ac.log("Replay file doesnt start with 'START' tag.")
436+
return
437+
totalDrivers = int(line[1])
438+
data['totalDrivers'] = totalDrivers
439+
data['nLaps'] = int(line[2])
440+
for i in range(3, 3+totalDrivers):
441+
line[i] = line[i].split(';')
442+
drivers[i-3].starting_position = int(line[i][0])
443+
drivers[i-3].tyre = line[i][1]
444+
for line in rf:
445+
line = line.split()
446+
if line[0] == "U": # normal update
447+
update = [float(line[2])]
448+
for i in range(3, 3+totalDrivers):
449+
line[i] = line[i].split(';')
450+
update.append([int(line[i][0]), float(line[i][1]), line[i][2], int(line[i][3]), bool(int(line[i][4])), int(line[i][5])])
451+
if int(line[1]) not in data:
452+
data[int(line[1])] = []
453+
data[int(line[1])].append(update)
454+
elif line[0] == "FL": # TODO fastest lap
455+
pass
456+
else:
457+
ac.log("Replay file has wrong tag '%s'." % line[0])
458+
return
459+
return data
460+
346461
except FileNotFoundError:
347-
ac.log("Replay File not found.")
462+
ac.log("Replay File not found.")
463+
464+
def lookup_data(lap, time, replay_data, drivers):
465+
it = bisect.bisect_left(replay_data[lap], [time])
466+
if it > len(replay_data[lap]): return
467+
data = replay_data[lap][it]
468+
for i in range(len(drivers)):
469+
drivers[i].timeDiff = data[i+1][1]
470+
drivers[i].tyre = data[i+1][2]
471+
drivers[i].pits = data[i+1][3]
472+
drivers[i].out = data[i+1][4]
473+
drivers[i].current_lap = data[i+1][5]
474+
return [data[i+1][0] for i in range(len(drivers))] # return new positions
475+

apps/python/F12020Leaderboard/LeaderboardRow.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def __init__(self, leaderboardWindow, row):
4040
try:
4141
ac.setBackgroundTexture(self.teamLabel, FC.TEAM_COLORS[self.driverName]);
4242
except KeyError:
43-
ac.console("%s:Name Missing in teams.txt %s" % (FC.APP_NAME, self.driverName))
43+
ac.log("%s:Name Missing in teams.txt %s" % (FC.APP_NAME, self.driverName))
4444

4545
self.infoLabel = ac.addLabel(leaderboardWindow, "Interval")
4646
ac.setPosition(self.infoLabel, 250, py)
@@ -72,7 +72,7 @@ def update_name(self, id):
7272
try:
7373
ac.setBackgroundTexture(self.teamLabel, FC.TEAM_COLORS[self.driverName])
7474
except KeyError:
75-
ac.console("%s:Name Missing in teams.txt %s" % (FC.APP_NAME, self.driverName))
75+
ac.log("%s:Name Missing in teams.txt %s" % (FC.APP_NAME, self.driverName))
7676

7777
def update_time(self, time):
7878
if self.out or self.pit: return # no need to update
@@ -105,7 +105,6 @@ def mark_in(self):
105105
def mark_out(self):
106106
if self.out: return
107107
self.out = True
108-
ac.console("OUT %d %s" % (self.row, self.driverName))
109108
ac.setVisible(self.positionLabel, 0)
110109
ac.setPosition(self.teamLabel, self.px + 12, self.py + 2)
111110
ac.setPosition(self.nameLabel, self.px + 30, self.py)
-307 KB
Loading
15.9 KB
Loading
37.4 KB
Loading
18.9 KB
Loading
1.88 KB
Loading
12.1 KB
Loading
19.4 KB
Loading
15.2 KB
Loading
47.6 KB
Loading
17.8 KB
Loading
10.1 KB
Loading
Binary file not shown.
27.8 KB
Loading
17.8 KB
Loading
-407 KB
Loading
Binary file not shown.
28.3 KB
Loading
27.7 KB
Loading
13.1 KB
Loading
22.4 KB
Loading
Loading
5.62 KB
Loading

apps/python/F12020Leaderboard/utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def get_image_size(fname):
1313
if imghdr.what(fname) == 'png':
1414
check = struct.unpack('>i', head[4:8])[0]
1515
if check != 0x0d0a1a0a:
16-
ac.console("%s: Error getting image size %s" % (FC.APP_NAME, fname))
16+
ac.log("%s: Error getting image size %s" % (FC.APP_NAME, fname))
1717
return
1818
width, height = struct.unpack('>ii', head[16:24])
1919
return width, height

0 commit comments

Comments
 (0)