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

Add Info Display #35

Merged
merged 5 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions scripts/Modules/mkw_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from dataclasses import dataclass
# will be removed soon
from Modules import mkw_core as core
import math

# general structure / pointer reading
def chase_pointer(base_address, offsets, data_type):
Expand Down Expand Up @@ -40,6 +41,43 @@ class vec3:
y: float = 0.0
z: float = 0.0

def norm_xyz(self):
return math.sqrt(self.x**2 + self.y**2 + self.z**2)

def norm_xz(self):
return math.sqrt(self.x**2 + self.z**2)

@dataclass
class ExactTimer:
min: int
sec: int
mil: float
vabold marked this conversation as resolved.
Show resolved Hide resolved

def __add__(self, rhs):
ret = ExactTimer(self.min, self.sec, self.mil)
ret.min += rhs.min
ret.sec += rhs.sec
ret.mil += rhs.mil
ret.normalize()
return ret

def __sub__(self, rhs):
ret = ExactTimer(self.min, self.sec, self.mil)
ret.min -= rhs.min
ret.sec -= rhs.sec
ret.mil -= rhs.mil
ret.normalize()
return ret

def normalize(self):
carry, self.mil = divmod(self.mil, 1000)
self.sec += carry
carry, self.sec = divmod(self.sec, 60)
self.min += int(carry)

def __str__(self):
return "{:02d}:{:012.9f}".format(self.min, self.sec + self.mil)

def read_vec3(ptr):
x = memory.read_f32(ptr + 0x0)
y = memory.read_f32(ptr + 0x4)
Expand Down
27 changes: 27 additions & 0 deletions scripts/Modules/mkw_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,30 @@ def get_speed(playerIdx=0):
xyz = math.sqrt(x ** 2 + y ** 2 + z ** 2)

return speed(x, y, z, xz, xyz)

"""Next 3 functions are used for exact finish display"""
def getIGT(lap, player):
if player == 0:
address = 0x800001B0
elif player == 1:
address = 0x800001F8
malleoz marked this conversation as resolved.
Show resolved Hide resolved
else:
return classes.ExactTimer(0, 0, 0)

offsets = [0xC, (player)*0x4, 0x3C, (lap-1)*0xC + 0x5]
value = chase_pointer(classes.getRaceInfoHolder(), offsets, 'u16')
min, sec = divmod(value, 0x100)
mil = memory.read_f32(address + (lap-1)*0x4) / 1000 % 1
return classes.ExactTimer(min, sec, mil)

def updateExactFinish(lap, player):
currentLapTime = getIGT(lap, player)
pastLapTime = getIGT(lap-1, player)

return currentLapTime - pastLapTime

def getUnroundedTime(lap, player):
t = classes.timer(0, 0, 0)
for i in range(lap):
t += updateExactFinish(i + 1, player)
return t
241 changes: 241 additions & 0 deletions scripts/_draw_info_display.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
from dolphin import event, gui, utils
from Modules import mkw_classes as classes, mkw_core as core
import configparser
import math
import os

def populate_default_config(file_path):
config = configparser.ConfigParser()

config['DEBUG'] = {}
config['DEBUG']['Debug'] = "False"

config['INFO DISPLAY'] = {}
config['INFO DISPLAY']["Frame Count"] = "True"
config['INFO DISPLAY']["Lap Splits"] = "False"
config['INFO DISPLAY']["Speed"] = "True"
config['INFO DISPLAY']["Internal Velocity (X, Y, Z)"] = "False"
config['INFO DISPLAY']["Internal Velocity (XYZ)"] = "False"
config['INFO DISPLAY']["External Velocity (X, Y, Z)"] = "False"
config['INFO DISPLAY']["External Velocity (XYZ)"] = "True"
config['INFO DISPLAY']["Moving Road Velocity (X, Y, Z)"] = "False"
config['INFO DISPLAY']["Moving Road Velocity (XYZ)"] = "False"
config['INFO DISPLAY']["Moving Water Velocity (X, Y, Z)"] = "False"
config['INFO DISPLAY']["Moving Water Velocity (XYZ)"] = "False"
config['INFO DISPLAY']["Charges and Boosts"] = "True"
config['INFO DISPLAY']["Checkpoints and Completion"] = "True"
config['INFO DISPLAY']["Airtime"] = "True"
config['INFO DISPLAY']["Miscellaneous"] = "False"
config['INFO DISPLAY']["Surface Properties"] = "False"
config['INFO DISPLAY']["Position"] = "False"
config['INFO DISPLAY']["Stick"] = "True"
config['INFO DISPLAY']["Text Color (ARGB)"] = "0xFFFFFFFF"
config['INFO DISPLAY']["Digits (to round to)"] = "6"

with open(file_path, 'w') as f:
config.write(f)

return config

class ConfigInstance():
def __init__(self, config : configparser.ConfigParser):
self.debug = config['DEBUG'].getboolean('Debug')
self.frame_count = config['INFO DISPLAY'].getboolean('Frame Count')
self.lap_splits = config['INFO DISPLAY'].getboolean('Lap Splits')
self.speed = config['INFO DISPLAY'].getboolean('Speed')
self.iv = config['INFO DISPLAY'].getboolean('Internal Velocity (X, Y, Z)')
self.iv_xyz = config['INFO DISPLAY'].getboolean('Internal Velocity (XYZ)')
self.ev = config['INFO DISPLAY'].getboolean('External Velocity (X, Y, Z)')
self.ev_xyz = config['INFO DISPLAY'].getboolean('External Velocity (XYZ)')
self.mrv = config['INFO DISPLAY'].getboolean('Moving Road Velocity (X, Y, Z)')
self.mrv_xyz = config['INFO DISPLAY'].getboolean('Moving Road Velocity (XYZ)')
self.mwv = config['INFO DISPLAY'].getboolean('Moving Water Velocity (X, Y, Z)')
self.mwv_xyz = config['INFO DISPLAY'].getboolean('Moving Water Velocity (XYZ)')
self.charges = config['INFO DISPLAY'].getboolean('Charges and Boosts')
self.cps = config['INFO DISPLAY'].getboolean('Checkpoints and Completion')
self.air = config['INFO DISPLAY'].getboolean('Airtime')
self.misc = config['INFO DISPLAY'].getboolean('Miscellaneous')
self.surfaces = config['INFO DISPLAY'].getboolean('Surface Properties')
self.position = config['INFO DISPLAY'].getboolean('Position')
self.stick = config['INFO DISPLAY'].getboolean('Stick')
self.color = int(config['INFO DISPLAY']['Text Color (ARGB)'], 16)
self.digits = min(7, config['INFO DISPLAY'].getint('Digits (to round to)'))

def main():
config = configparser.ConfigParser()

file_path = os.path.join(utils.get_script_dir(), 'Modules', 'infodisplay.ini')
config.read(file_path)

if not config.sections():
config = populate_default_config(file_path)

global c
c = ConfigInstance(config)

if __name__ == '__main__':
main()

# draw information to the screen

def create_infodisplay():
text = ""

if c.debug:
# test values here
malleoz marked this conversation as resolved.
Show resolved Hide resolved
text += f"{utils.get_game_id()}\n\n"

if c.frame_count:
text += f"Frame: {core.get_frame_of_input()}\n\n"

if c.lap_splits:
# The actual max lap address does not update when crossing the finish line
# for the final time to finish the race. However, for whatever reason,
# race completion does. We use the "max" version to prevent lap times
# from disappearing when crossing the line backwards.
player_max_lap = math.floor(
classes.RaceInfoPlayer.race_completion_max())
lap_count = classes.RaceDataSettings.lap_count()

if player_max_lap >= 2 and lap_count > 1:
for lap in range(1, player_max_lap):
text += "Lap {}: {}\n".format(lap, core.updateExactFinish(lap, 0))

if player_max_lap > lap_count:
text += "Final: {}\n".format(core.getUnroundedTime(lap_count, 0))
text += "\n"

if c.speed:
speed = core.get_speed()
engine_speed = classes.KartMove.speed()
cap = classes.KartMove.soft_speed_limit()
text += f" XZ: {round(speed.xz, c.digits)}\n"
text += f" XYZ: {round(speed.xyz, c.digits)}\n"
text += f" Y: {round(speed.y, c.digits)}\n"
text += f" Engine: {round(engine_speed, c.digits)} / {round(cap, c.digits)}"
text += "\n\n"

if (c.iv or c.iv_xyz):
iv = classes.VehiclePhysics.internal_velocity()

if c.iv:
text += f" IV X: {round(iv.x,c.digits)}\n"
text += f" IV Y: {round(iv.y,c.digits)}\n"
text += f" IV Z: {round(iv.z,c.digits)}\n\n"

if c.iv_xyz:
text += f" IV XZ: {round(iv.norm_xz(),c.digits)}\n"
text += f" IV XYZ: {round(iv.norm_xyz(),c.digits)}\n\n"

if (c.ev or c.ev_xyz):
ev = classes.VehiclePhysics.external_velocity()

if c.ev:
text += f" EV X: {round(ev.x,c.digits)}\n"
text += f" EV Y: {round(ev.y,c.digits)}\n"
text += f" EV Z: {round(ev.z,c.digits)}\n\n"

if c.ev_xyz:
text += f" EV XZ: {round(ev.norm_xz(),c.digits)}\n"
text += f" EV XYZ: {round(ev.norm_xyz(),c.digits)}\n\n"

if (c.mrv or c.mrv_xyz):
mrv = classes.VehiclePhysics.moving_road_velocity()

if c.mrv:
text += f" MRV X: {round(mrv.x,c.digits)}\n"
text += f" MRV Y: {round(mrv.y,c.digits)}\n"
text += f" MRV Z: {round(mrv.z,c.digits)}\n\n"

if c.mrv_xyz:
text += f" MRV XZ: {round(mrv.norm_xz(),c.digits)}\n"
text += f" MRV XYZ: {round(mrv.norm_xyz(),c.digits)}\n\n"

if (c.mwv or c.mwv_xyz):
mwv = classes.VehiclePhysics.moving_water_velocity()

if c.mwv:
text += f" MWV X: {round(mwv.x,c.digits)}\n"
text += f" MWV Y: {round(mwv.y,c.digits)}\n"
text += f" MWV Z: {round(mwv.z,c.digits)}\n\n"

if c.mwv_xyz:
text += f" MWV XZ: {round(mwv.norm_xz(),c.digits)}\n"
text += f" MWV XYZ: {round(mwv.norm_xyz(),c.digits)}\n\n"

if c.charges:
mt = classes.KartMove.mt_charge()
smt = classes.KartMove.smt_charge()
ssmt = classes.KartMove.ssmt_charge()
mt_boost = classes.KartMove.mt_boost_timer()
trick_boost = classes.KartMove.trick_timer()
shroom_boost = classes.KartMove.mushroom_timer()
if bool(classes.KartParam.is_bike()):
text += f"MT Charge: {mt} | SSMT Charge: {ssmt}\n"
else:
text += f"MT Charge: {mt} ({smt}) | SSMT Charge: {ssmt}\n"

text += f"MT: {mt_boost} | Trick: {trick_boost} | Mushroom: {shroom_boost}\n\n"

if c.cps:
lap_comp = classes.RaceInfoPlayer.lap_completion()
race_comp = classes.RaceInfoPlayer.race_completion()
cp = classes.RaceInfoPlayer.checkpoint_id()
kcp = classes.RaceInfoPlayer.max_kcp()
rp = classes.RaceInfoPlayer.respawn_point()
text += f" Lap%: {round(lap_comp,c.digits)}\n"
text += f"Race%: {round(race_comp,c.digits)}\n"
text += f"CP: {cp} | KCP: {kcp} | RP: {rp}\n\n"

if c.air:
airtime = classes.KartMove.airtime()
text += f"Airtime: {airtime}\n\n"

if c.misc:
wheelie_frames = classes.KartMove.wheelie_frames()
wheelie_cd = classes.KartMove.wheelie_cooldown()
trick_cd = classes.KartJump.cooldown()
oob_timer = classes.KartCollide.solid_oob_timer()
if classes.KartParam.is_bike() == 1:
malleoz marked this conversation as resolved.
Show resolved Hide resolved
text += f"Wheelie Length: {wheelie_frames}\n"
text += f"Wheelie CD: {wheelie_cd} | Trick CD: {trick_cd}\n"
else:
text += f"Trick CD: {trick_cd}\n"
text += f"OOB: {oob_timer}\n\n"

if c.surfaces:
is_offroad = classes.KartCollide.surface_properties().offroad > 0
is_trickable = classes.KartCollide.surface_properties().trickable > 0
kcl_speed_mod = classes.KartMove.kcl_speed_factor()
text += f" Offroad: {is_offroad}\n"
text += f"Trickable: {is_trickable}\n"
text += f"KCL Speed Modifier: {round(kcl_speed_mod * 100, c.digits)}%\n\n"

if c.position:
pos = classes.VehiclePhysics.pos()
text += f"X Pos: {pos.x}\n"
text += f"Y Pos: {pos.y}\n"
text += f"Z Pos: {pos.z}\n\n"

# TODO: figure out why classes.RaceInfoPlayer.stick_x() and
# classes.RaceInfoPlayer.stick_y() do not update
# (using these as placeholders until further notice)
if c.stick:
stick_x = core.chase_pointer(
classes.getRaceInfoHolder(), [0xC, 0x0, 0x48, 0x38], 'u8') - 7
stick_y = core.chase_pointer(
classes.getRaceInfoHolder(), [0xC, 0x0, 0x48, 0x39], 'u8') - 7
text += f"X: {stick_x} | Y: {stick_y}\n\n"

return text


@event.on_savestateload
def on_state_load(fromSlot: bool, slot: int):
if classes.RaceInfo.stage() >= 1:
gui.draw_text((10, 10), c.color, create_infodisplay())

@event.on_frameadvance
def on_frame_advance():
if classes.RaceInfo.stage() >= 1:
gui.draw_text((10, 10), c.color, create_infodisplay())
Empty file removed scripts/infodisplay.py
Empty file.