Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Info Display enhancement : Rotation, Rotation Speed, TimeDifference, Oriented Speed. #57

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
/scripts/MKW_Inputs/*.csv
scripts/Modules/test_del_me_later.py
scripts/Modules/test.bit
scripts/Modules/test_del_me_later.py
scripts/Modules/test_del_me_later.py
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be deleted prior to merge.

scripts/AGC_Data/ghost.data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consistency.

Suggested change
scripts/AGC_Data/ghost.data
/scripts/AGC_Data/ghost.data

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the purpose of this is to have a placeholder file to keep the folder, .gitkeep is the preferred name. Otherwise, messages or notes are best done in .txt or .md file bodies.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the only purpose of that text file, delete it if it's not needed

Empty file.
58 changes: 58 additions & 0 deletions scripts/Modules/TimeDifference information.txt
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid spaces in filenames. TimeDifferenceInfo.txt would be my pick.

Optionally, this can be improved via markdown.

Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
This file contain some information about the TimeDifference scripts in infodisplay.

In infodisplay.ini, you can chose to display or not each time difference calculation.

In infodisplay.ini, "timediff setting" is a setting with 4 possible value :
"player" which will use the TimeDifference (Player -> Ghost)
"ghost" which will use the TimeDifference (Ghost -> Player)
"ahead" which will use the TimeDifference (the one ahead -> the one behind)
"behind" which will use the TimeDifference (the one behind -> the one ahead)
any other value will default to "player".

In infodisplay.ini "history size" is a setting used for the TimeDifference RaceComp.
history size = 200 means the TimeDiff RaceComp can at best detect a timedifference of 200 frames or less.
It uses memory, so don't use an unecessary large number.

Some TimeDifference calculations are not symmetrical. It means this calculation gives different result
for the time difference between the ghost and the player, and between the player and the ghost.
Here's an example : For the TimeDifference Absolute (Player1 -> Player2) :
We take Player1's speed, we take the distance between both players.
And we simply define the TimeDiff as the distance divided by the speed.
Player1 and Player2 have asymmetrical roles in the calculation.
Therefore : we talk about the timedifference from Player1 to Player2 (and not the timedifference between Player1 and Player2)

This is how each one is calculated :

-TimeDifference Absolute (P1 -> P2) (Not very relevant imo)
Take S1 the speed of P1
Take D the distance between P1 and P2
Return D / S1

-TimeDifference Relative (P1 -> P2) (A bit more relevant maybe)
Take S1 the speed of P1 directed "toward" P2. (mathematically, it's a dot product)
Take D the distance between P1 and P2
Return D / S1

-TimeDifference Projected (P1 -> P2) (A good one for short distances)
Take S1 the speed of P1
Take D the distance represented here : https://blounard.s-ul.eu/iMDYhZDI.png
Return D / S1

-TimeDifference CrossPath (P1 -> P2) (Another good one for short distances)
this one is symmetrical
With the notation here : https://blounard.s-ul.eu/WYbotlks.png
Calculate t1 = TimeDifference (P1 -> C) (in this case, all 3 above timedifference formula will give the same result)
Calculate t2 = TimeDifference (P2 -> C) (--------------------------------------------------------------------------)
Return t1-t2

-TimeDifference ToFinish (P1 -> P2) (Perfectly precise when both player are going straight to the finish line at constant speed. Useless otherwise)
this one is symmetrical
Calculate t1, the time needed for P1 to cross the finish line if P1 keep going at a constant speed.
Calculate t2, the time needed for P2 to cross the finish line if P2 keep going at a constant speed.
Return t1-t2

-TimeDifference RaceComp (P1 -> P2) (Useful even for long distances. Based on RaceCompletion data. Has several flaws)
this one is symmetrical
Store in memory the racecompletion data for both players for the last few frames.
Make the player ahead go "back in time" until it's behind the other player.
How much frame you had to go "back in time" is how much frame the player was ahead.
Binary file added scripts/Modules/__pycache__/__init__.cpython-38.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
297 changes: 297 additions & 0 deletions scripts/Modules/agc_lib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
from dolphin import gui, memory
from .mkw_classes import quatf, vec3
ximk marked this conversation as resolved.
Show resolved Hide resolved
from .mkw_classes import VehiclePhysics, KartMove, RaceConfig, Timer, RaceManagerPlayer
ximk marked this conversation as resolved.
Show resolved Hide resolved
import math

class FrameData:
"""Class to represent a set of value accessible each frame in the memory"""
def __init__(self, addrlist = None, string = None , usedefault=False):
self.values = [] #List of bytearray
if string is not None:
self.read_from_string(string)

elif addrlist is not None:
if not usedefault:
for addr, size in addrlist:
self.values.append(memory.read_bytes(addr, size))
else:
for addr, size in addrlist:
self.values.append(bytearray(size))

def __str__(self):
text = ''
for array in self.values:
for byte in array:
text += str(byte)+','
if len(array)>0:
text = text[:-1]
text += ';'
if len(self.values)>0:
text = text[:-1]
return text+'\n'

def read_from_string(self, string):
values = string.split(';')
for value in values:
self.values.append(bytearray([int(s) for s in value.split(',')]))

def interpolate(self,other, selfi, otheri):
#Call only if self.value[0] represent a vec3
v1 = vec3.from_bytes(self.values[0])
v2 = vec3.from_bytes(other.values[0])
v = (v1*selfi)+(v2*otheri)
self.values[0] = v.to_bytes()

def write(self, addrlist):
for index in range(len(self.values)):
addr = addrlist[index][0]
val = self.values[index]
memory.write_bytes(addr, val)

def float_to_str(f):
ms = round((f%1)*1000)
s = math.floor(f)%60
m = math.floor(f)//60
return f"{m},{s},{ms}"

def floats_to_str(fs):
return f"{float_to_str(fs[0])};{float_to_str(fs[1])};{float_to_str(fs[2])}"

class Split:
"""Class for a lap split. Contain just a float, representing the split in s"""
def __init__(self, f):
self.val = f
def __str__(self):
return f"{self.val:.3f}"
def __add__(self,other):
return Split(max(0, self.val+other.val))

@staticmethod
def from_string(string):
return Split(float(string))

@staticmethod
def from_time_format(m,s,ms):
return Split(m*60+s+ms/1000)

@staticmethod
def from_bytes(b):
data_int = b[0]*256*256+b[1]*256+b[2]
ms = data_int%1024
data_int = data_int//1024
s = data_int%128
data_int = data_int//128
m = data_int%128
return Split(m*60+s+ms/1000)

def time_format(self):
#return m,s,ms corresponding
f = self.val
ms = round((f%1)*1000)
s = math.floor(f)%60
m = math.floor(f)//60
return m,s,ms

def bytes_format(self):
#return a bytearray of size 3 for rkg format
m,s,ms = self.time_format()
data_int = ms+s*1024+m*1024*128
b3 = data_int%256
data_int = data_int//256
b2 = data_int%256
data_int = data_int//256
b1 = data_int%256
return bytearray((b1,b2,b3))


class TimerData:
"""Class for the laps splits, both in RKG and Timer format
Cumulative convention (lap2 split is stored as lap1+lap2)"""
def __init__(self,string =None, readid=0, splits = None):
#Call with a string OR when the race is finished
if string is None:
if splits is None:
self.splits = [] #List of Split (size 3)
timerlist = [RaceManagerPlayer.lap_finish_time(readid, lap) for lap in range(3)]
for timer in timerlist:
self.splits.append(Split.from_time_format(timer.minutes(), timer.seconds(), timer.milliseconds()))
else:
self.splits = splits
else:
self.splits = []
laps = string.split(';')
for lap in laps:
self.splits.append(Split.from_string(lap))

@staticmethod
def from_sliced_rkg(rkg_metadata):
sliced_bytes = rkg_metadata.values[3]
l1 = Split.from_bytes(sliced_bytes[1:4])
l2 = Split.from_bytes(sliced_bytes[4:7])+l1
l3 = Split.from_bytes(sliced_bytes[7:10])+l2
return TimerData(splits = [l1,l2,l3])

def __str__(self):
text = 't'
for split in self.splits:
text += str(split)+";"
text = text[:-1]
return text+'\n'

def add_delay(self, delay):
s = -delay/59.94
for i in range(len(self.splits)):
self.splits[i] = Split(max(self.splits[i].val+s, 0))


def to_bytes(self):
#A lap split is 3 bytes, so there is 9 bytes total
#Non cumulative format, ready to be written in a rkg
r = bytearray()
prev = 0
for split in self.splits:
r = r + Split(split.val - prev).bytes_format()
prev = split.val
return r

def write_rkg(self):
r = rkg_addr()
memory.write_bytes(r+0x11, self.to_bytes())


def metadata_to_file(filename, readid):
#Should be called before the countdown
metadata = FrameData(get_metadata_addr(readid))
file = open(filename, 'w')
if file is None :
gui.add_osd_message("Error : could not create the data file")
else :
file.write(str(metadata))
file.close()
gui.add_osd_message(f"{filename} successfully opened")

def get_metadata(readid):
return FrameData(get_metadata_addr(readid))

def get_rkg_metadata():
return FrameData(get_rkg_metadata_addr())

def rkg_metadata_to_file(filename):
rkg_metadata = get_rkg_metadata()
file = open(filename, 'w')
if file is None :
gui.add_osd_message("Error : could not create the data file")
else :
file.write("r"+str(rkg_metadata))
file.close()
gui.add_osd_message(f"{filename} successfully opened")

def frame_to_file(filename, readid):
frame = FrameData(get_addr(readid))
file = open(filename, 'a')
if file is None :
gui.add_osd_message("Error : could not create the data file")
else :
file.write(str(frame))
file.close()

def get_framedata(readid):
return FrameData(get_addr(readid))

def timerdata_to_file(filename, rid):
timerdata = TimerData(readid = rid)
file = open(filename, 'a')
if file is None :
gui.add_osd_message("Error : could not create the data file")
else :
file.write(str(timerdata))
file.close()

def get_timerdata(rid):
return TimerData(readid = rid)

def file_to_framedatalist(filename):
datalist = []
file = open(filename, 'r')
if file is None :
gui.add_osd_message("Error : could not load the data file")
else:
timerdata = None
metadata = None
rkg_metadata = None
listlines = file.readlines()
if listlines[0][0] == 'r':
rkg_metadata = FrameData(string = listlines[0][1:])
timerdata = TimerData.from_sliced_rkg(rkg_metadata)
else:
metadata = FrameData(string = listlines[0])
if listlines[-1][0]=='t':
timerdata = TimerData(string = listlines.pop()[1:])
for i in range(1, len(listlines)):
datalist.append(FrameData(string = listlines[i]))
file.close()
gui.add_osd_message(f"Data successfully loaded from {filename}")
return metadata, datalist, timerdata, rkg_metadata


def framedatalist_to_file(filename, datalist, rid):
metadata = get_metadata(rid)
timerdata = get_timerdata(rid)
file = open(filename, 'w')
if file is None :
gui.add_osd_message("Error : could not create the data file")
else:
file.write(str(metadata))
for frame in range(max(datalist.keys())+1):
if frame in datalist.keys():
file.write(str(datalist[frame]))
else:
file.write(str(FrameData(get_addr(rid), usedefault=True)))
file.write(str(timerdata))
file.close()
ximk marked this conversation as resolved.
Show resolved Hide resolved

def framedatalist_to_file_rkg(filename, datalist):
metadata = get_rkg_metadata()
file = open(filename, 'w')
if file is None :
gui.add_osd_message("Error : could not create the data file")
else:
file.write('r'+str(metadata))
for frame in range(max(datalist.keys())+1):
if frame in datalist.keys():
file.write(str(datalist[frame]))
else:
file.write(str(FrameData(get_addr(rid), usedefault=True)))
file.close()


def get_addr(player_id):
a = VehiclePhysics.chain(player_id)
b = KartMove.chain(player_id)
return [(a+0x68, 12), #Position
(a+0xF0, 16), #Rotation
(a+0x74, 12), #EV
(a+0x14C, 12), #IV
(b+0x18, 4), #MaxEngineSpd
(b+0x20, 4), #EngineSpd
(b+0x9C, 4), #OutsideDriftAngle
(b+0x5C, 12)]#Dir

def get_metadata_addr(player_id):
a = RaceConfig.chain() + player_id*0xF0
return [(a+0x30, 8)]#CharacterID and VehicleID

def rkg_addr():
return memory.read_u32(RaceConfig.chain() + 0xC0C)

def get_rkg_metadata_addr():
r = rkg_addr()
return [(r+0x4, 3), #Skipping track ID
(r+0x8, 4), #Skipping Compression flag
(r+0xD, 1), #Skipping Input Data Length
(r+0x10, 0x78)]

def is_rkg():
s = bytearray('RKGD', 'ASCII')
r = rkg_addr()
return s == memory.read_bytes(r, 4)
Loading
Loading