diff --git a/.gitignore b/.gitignore index 2c0ddba..2950980 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,25 @@ -/scripts/MKW_Inputs/*.csv +scripts/MKW_Inputs/**/*.csv +scripts/MKW_Inputs/**/*.rkg +scripts/AGC_Data/**/*.agc +scripts/external/**/*.ini +scripts/Settings/**/*.ini +scripts/FrameDumps/**/*.txt +scripts/FrameDumps/**/*.rawtxt +scripts/FrameDumps/**/*.ini +scripts/FrameDumps/**/*.mp4 +scripts/FrameDumps/**/*.mp3 +scripts/FrameDumps/**/*.wav +scripts/FrameDumps/*.png + +scripts/Data Dump/** +/scripts/Ghost/**/*.rkg +/scripts/Ghost/**/*.csv +**/__pycache__ +**/*test*.py + + +scripts/RMC/test.py +scripts/RMC/test codecallbacl.py + +/scripts/Input_optimizer +scripts/RMC/test tp lap.py diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..17da246 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "python.analysis.extraPaths": [ + "./scripts/python-stubs" + ], + "editor.detectIndentation": false +} \ No newline at end of file diff --git a/scripts/AGC_Data/AGC files goes here.txt b/scripts/AGC_Data/AGC files goes here.txt new file mode 100644 index 0000000..e69de29 diff --git a/scripts/FrameDumps/Extra/author_display_util.py b/scripts/FrameDumps/Extra/author_display_util.py new file mode 100644 index 0000000..ba9b238 --- /dev/null +++ b/scripts/FrameDumps/Extra/author_display_util.py @@ -0,0 +1,93 @@ +import moviepy +import numpy as np +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +import sys +import os +import time +import subprocess +import math + +from Extra import common + +def make_list_author(c, main_folder): + #return a dict so that result[frame] contain an ordered list of author that contributed on that frame + #also return a list of all author + result = {} + raw_author_list = [] + author_file = os.path.join(main_folder, c.get('author_list_filename')) + if os.path.isfile(author_file): + with open(author_file, 'r', encoding='utf-8') as f: + entry_list = f.read().split('\n') + for entry in entry_list: + temp = entry.split(':') + author_name = temp[0] + raw_author_list.append(author_name) + frames_entry = temp[1].split(',') + for token in frames_entry: + temp = token.split('-') + for frame in range(int(temp[0]), int(temp[-1])+1): + if not frame in result.keys(): + result[frame] = [] + result[frame].append(author_name) + return raw_author_list, result + + +def add_author_display(image, frame_dict, config, main_folder, raw_author_list, author_dict): + ad_config = config['Author display'] + state, state_counter = int(frame_dict['state']), int(frame_dict['state_counter']) + author_display_layer = Image.new('RGBA', image.size, (0,0,0,0)) + ID = ImageDraw.Draw(author_display_layer) + font_folder = os.path.join(main_folder, 'Fonts') + top_left_text = ad_config.get('top_left').split(',') + top_left = round(float(top_left_text[0])*image.width), round(float(top_left_text[1])*image.height) + font_size = ad_config.getint('font_size') + font = ImageFont.truetype(os.path.join(font_folder, ad_config.get('font')), font_size) + active_text_color = common.get_color(ad_config.get('active_text_color')) + inactive_text_color = common.get_color(ad_config.get('inactive_text_color')) + outline_width = ad_config.getint('outline_width') + active_outline_color = common.get_color(ad_config.get('active_outline_color')) + inactive_outline_color = common.get_color(ad_config.get('inactive_outline_color')) + curframe = int(frame_dict['frame_of_input']) + spacing = 4 + current_h = top_left[1] + + for author_name in raw_author_list: + if curframe in author_dict.keys() and author_name in author_dict[curframe]: + ID.text( (top_left[0], current_h), author_name, fill = active_text_color, font = font, stroke_width = outline_width, stroke_fill = active_outline_color) + else: + ID.text( (top_left[0], current_h), author_name, fill = inactive_text_color , font = font, stroke_width = outline_width, stroke_fill = inactive_outline_color) + current_h += spacing + font_size + + + if ad_config.getboolean('fade_animation'): + author_display_layer = common.fade_image_manually(author_display_layer, frame_dict, ad_config) + + # TODO + # if you change the delay to 0f (encoder line 97&98) the anim starts 1f early + # once shad's GUI is done, this can be parameterized easily. It's hard to notice so I didn't bother. + + if ad_config.getboolean('fly_animation'): + + if state == 1 and state_counter <= 10: + return None + + offset = common.fly_in(frame_dict, image.height) + if state == 4 and 192 < state_counter <= 201: + image.alpha_composite(author_display_layer, (round(0 - image.width*offset), 0)) + return None + + ITEM_POSITION = 381/1440 + AUTHOR_DISPLAY_POSITION = (1 - float(top_left_text[1])) + height_correction = round((AUTHOR_DISPLAY_POSITION - ITEM_POSITION)*image.height) + + if offset is not None: + if ad_config.get('fly_in_direction') == 'bottom': + image.alpha_composite(author_display_layer, (0, image.height - top_left[1] - height_correction - offset)) + else: + image.alpha_composite(author_display_layer, (0, offset - round(ITEM_POSITION*image.height))) + else: + image.alpha_composite(author_display_layer, (0,0)) + else: + image.alpha_composite(author_display_layer, (0,0)) diff --git a/scripts/FrameDumps/Extra/common.py b/scripts/FrameDumps/Extra/common.py new file mode 100644 index 0000000..d1afa97 --- /dev/null +++ b/scripts/FrameDumps/Extra/common.py @@ -0,0 +1,147 @@ +import moviepy +import numpy as np +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +from PIL import ImageFilter +import sys +import os +import time +import subprocess +import math + +def get_color(color_text): + n = int(color_text, 16) + l = [] + for _ in range(4): + l.append(n%256) + n//=256 + l.reverse() + return tuple(l) + +def color_white_part(image, color): + + if image.mode != 'RGBA': + image = image.convert('RGBA') #needed, crashes otherwise if image doesnt have transparency like analog_5_3_part1 + + color = tuple(color[:3]) + pixels = image.load() + for y in range(image.height): + for x in range(image.width): + r, _, _, a = pixels[x, y] + + if r == 255: + pixels[x, y] = (color[0], color[1], color[2], a) + else: + brightness = r / 255 + new_r = int(color[0] * brightness) + new_g = int(color[1] * brightness) + new_b = int(color[2] * brightness) + pixels[x, y] = (new_r, new_g, new_b, a) + + return image + +def get_filter_list(config_text): + raw_list = config_text.split(',') + filters = [] + for raw_text in raw_list: + if raw_text in ['BLUR', 'blur', 'Blur']: + filters.append(ImageFilter.BLUR) + elif raw_text in ['CONTOUR', 'contour', 'Countour']: + filters.append(ImageFilter.CONTOUR) + elif raw_text in ['DETAIL', 'detail', 'Detail']: + filters.append(ImageFilter.DETAIL) + elif raw_text in ['EDGE_ENHANCE', 'Edge_enhance', 'edge_enhance', 'Edge_Enhance']: + filters.append(ImageFilter.EDGE_ENHANCE) + elif raw_text in ['EDGE_ENHANCE_MORE', 'Edge_enhance_more', 'edge_enhance_more', 'Edge_Enhance_More', 'Edge_enhance_More']: + filters.append(ImageFilter.EDGE_ENHANCE_MORE) + elif raw_text in ['EMBOSS', 'Emboss', 'emboss']: + filters.append(ImageFilter.EMBOSS) + elif raw_text in ['FIND_EDGES', 'Find_edges', 'find_edges', 'Find_Edges']: + filters.append(ImageFilter.FIND_EDGES) + elif raw_text in ['SHARPEN', 'Sharpen', 'sharpen']: + filters.append(ImageFilter.SHARPEN) + elif raw_text in ['SMOOTH', 'Smooth', 'smooth']: + filters.append(ImageFilter.SMOOTH) + elif raw_text in ['SMOOTH_MORE', 'Smooth_more', 'smooth_more', 'Smooth_More']: + filters.append(ImageFilter.SMOOTH_MORE) + return filters + +def get_resampler(resample_filter): + if resample_filter in ['nearest', 'Nearest', 'NEAREST']: + return Image.Resampling.NEAREST + elif resample_filter in ['bicubic', 'Bicubic', 'BICUBIC']: + return Image.Resampling.BICUBIC + elif resample_filter in ['bilinear', 'Bilinear', 'BILINEAR']: + return Image.Resampling.BILINEAR + elif resample_filter in ['box', 'Box', 'BOX']: + return Image.Resampling.BOX + elif resample_filter in ['hamming', 'Hamming', 'HAMMING']: + return Image.Resampling.HAMMING + else: + return Image.Resampling.LANCZOS + + +def is_layer_active(frame_dict, config): + #Return a boolean if the layer should be visible on screen + fade_in_len = config.getint('fade_in_duration', 20) + fade_out_len = config.getint('fade_out_duration', 120) + start = config.getint('start_frame', 0) + try: + end = config.getint('end_frame') + except: + end = None + state, state_counter = int(frame_dict['state']), int(frame_dict['state_counter']) + curframe = int(frame_dict['frame_of_input']) + + if state < 1 or curframe <= start: + return False + if end and curframe >= end + fade_out_len: + return False + if end is None and state >= 4 and state_counter >= fade_out_len: + return False + return True + +def fade_image_manually(image, frame_dict, config): + fade_in_len = max(1,config.getint('fade_in_duration', 20)) + fade_out_len = max(1,config.getint('fade_out_duration', 120)) + start = config.getint('start_frame', 0) + try: + end = config.getint('end_frame') + except: + end = None + state, state_counter = int(frame_dict['state']), int(frame_dict['state_counter']) + curframe = int(frame_dict['frame_of_input']) + + if state >= 1 and start <= curframe < fade_in_len + start : + #fade in + alpha = (curframe - start)/fade_in_len + r,g,b,a = image.split() + a = a.point(lambda x:x*alpha) + image = Image.merge("RGBA", (r,g,b,a)) + if end is None and state >= 4 and state_counter < fade_out_len: + #fade out on stage 4 + alpha = 1 - state_counter/fade_out_len + r,g,b,a = image.split() + a = a.point(lambda x:x*alpha) + image = Image.merge("RGBA", (r,g,b,a)) + if end and end < curframe <= fade_out_len + end : + #fade out on frame > end + alpha = 1 - (curframe - end)/fade_out_len + r,g,b,a = image.split() + a = a.point(lambda x:x*alpha) + image = Image.merge("RGBA", (r,g,b,a)) + return image + +def fly_in(frame_dict, height): + state, state_counter = int(frame_dict['state']), int(frame_dict['state_counter']) + + if state == 1 and 10 < state_counter < 21: + DISPLACEMENT_RATIOS = [0.0757, 0.1549, 0.225, 0.4333, 0.382, 0.3402, 0.308, 0.284, 0.269, 0.265] + idx = state_counter - 11 + return round(DISPLACEMENT_RATIOS[idx]*height) + if state == 4 and 192 < state_counter < 202: + FLY_OUT_RATE = 1/10 + idx = state_counter - 192 + return FLY_OUT_RATE * idx + return None diff --git a/scripts/FrameDumps/Extra/config_util.py b/scripts/FrameDumps/Extra/config_util.py new file mode 100644 index 0000000..6c26ec2 --- /dev/null +++ b/scripts/FrameDumps/Extra/config_util.py @@ -0,0 +1,341 @@ +import moviepy +import numpy as np +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +import sys +import os +import time +import subprocess +import math + +import configparser + + +def create_config(filename): + config = configparser.ConfigParser(allow_no_value=True) + config.optionxform = str + config.add_section('README') + config.set('README', 'Visit : https://docs.google.com/document/d/e/2PACX-1vTXoEveB_1MZ3WizOpEWvZ-oyJMgg-3pRLGiNu-5vo853BMcrr8RY69REcTsheurI9qS2kfqrx1BZkT/pub\n\n' ) + + config.add_section('Encoding options') + config.set('Encoding options', '\n#Valid options are "normal", "discord", "youtube"') + ''' + config.set('Encoding options', '#"normal" is no particular encoding settings, for people who want to do more video editing afterward') + config.set('Encoding options', '#"discord" is for size based encoding, allowing for a specific filesize output') + config.set('Encoding options', '#"youtube" is for directly uploading on YT. uses encode settings from https://gist.github.com/wuziq/b86f8551902fa1d477980a8125970877') + ''' + config.set('Encoding options', '#You can also set an integer, to only render the .png of the corresponding frame (useful for trying different display options quickly)') + config.set('Encoding options', 'encode_style', 'discord')# + config.set('Encoding options', '\n#Video output size in mb. only used with discord encoding style') + config.set('Encoding options', 'output_file_size', '9.5')#in MB + config.set('Encoding options', '\n#range from 0 to 51. Only used with normal encoding style') + config.set('Encoding options', 'crf_value', '0')# + config.set('Encoding options', '\n#Choice "ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow"') + config.set('Encoding options', '#it affect the size of the output file, and the performances. it also affect quality when using discord encoding style') + config.set('Encoding options', 'preset', 'slow')# + config.set('Encoding options', '\n#Threads amount to use for ffmpeg') + config.set('Encoding options', 'threads', '4')# + config.set('Encoding options', '\n#Height of the output video in pixel') + config.set('Encoding options', 'resize_resolution', '1080')# + config.set('Encoding options', '\n#Choice "stretch", "crop", "fill", "none"') + config.set('Encoding options', 'resize_style', 'none') #stretch, crop, fill, none + config.set('Encoding options', '\n#Choice "nearest", "box", "bilinear", "hamming", "bicubic", "lanczos"') + config.set('Encoding options', '#visit https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-filters') + config.set('Encoding options', 'scaling_option', 'lanczos') #https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-filters + config.set('Encoding options', '\n#filename for the output file. .mp4 extension required') + config.set('Encoding options', 'output_filename', 'output.mp4')# + config.set('Encoding options', '\n#time in seconds for the video fade in') + config.set('Encoding options', 'video_fade_in', '0')# + config.set('Encoding options', '\n#time in seconds for the video fade out') + config.set('Encoding options', 'video_fade_out', '0')# + config.set('Encoding options', '\n#Choice "blur", "contour", "detail", "edge_enhance", "edge_enhance_more", "emboss", "find_edges", "sharpen", "smooth", "smooth_more"') + config.set('Encoding options', '#You can use several effects by separating them with a ",". DO NOT USE SPACES') + config.set('Encoding options', 'special_effects', 'none') #https://pillow.readthedocs.io/en/stable/reference/ImageFilter.html#module-PIL.ImageFilter + + config.set('Encoding options', '\n\n##############################################################################\n') + + config.add_section('Audio options') + config.set('Audio options', '\n#Choice "dsp", "dtk", "none"') + config.set('Audio options', 'audiodump_target', 'none') #dsp, dtk, none + config.set('Audio options', '\n#multiplicator on the in game volume') + config.set('Audio options', 'audiodump_volume', '1.0')# + config.set('Audio options', '\n#Must be a file in the same folder as this config file') + config.set('Audio options', 'bgm_filename', '')# + config.set('Audio options', '\n#multiplicator on the bgm volume') + config.set('Audio options', 'bgm_volume', '1')# + config.set('Audio options', '\n#Time in second the BGM should start at in the output video. You can use negative values to skip the beginning of the BGM') + config.set('Audio options', 'bgm_offset', '0')# + config.set('Audio options', '\n#time in second for the audio fade in') + config.set('Audio options', 'fade_in', '0')# + config.set('Audio options', '\n#time in second for the audio fade out') + config.set('Audio options', 'fade_out', '0')# + + config.set('Audio options', '\n\n##############################################################################\n') + + config.add_section('Infodisplay') + config.set('Infodisplay', '\n#draw the infodisplay') + config.set('Infodisplay', 'show_infodisplay', 'True')# + config.set('Infodisplay', '\n#Font filename. You must put the font in the Fonts folder.') + config.set('Infodisplay', 'font', 'MKW_Font')# + config.set('Infodisplay', '\n#font size in pixel on the final output resolution for fonts other than the mkw font.') + config.set('Infodisplay', 'font_size', '48')# + config.set('Infodisplay', '\n#Scaling factor for MKW Font if used') + config.set('Infodisplay', 'mkw_font_scaling', '3')# + config.set('Infodisplay', '\n#vertical spacing in pixel between lines') + config.set('Infodisplay', 'spacing', '4')# + config.set('Infodisplay', '\n#frame OF INPUT this section will appear on') + config.set('Infodisplay', 'start_frame', '0')# + config.set('Infodisplay', '\n#frame OF INPUT this section will disappear on. Leave empty for race end') + config.set('Infodisplay', 'end_frame', '' )# + config.set('Infodisplay', '\n#put a fade in and fade out animation to the infodisplay') + config.set('Infodisplay', 'fade_animation', 'True')# + config.set('Infodisplay', '\n#duration of the fade in animation in the infodisplay') + config.set('Infodisplay', 'fade_in_duration', '20')# + config.set('Infodisplay', '\n#duration of the fade out animation in the infodisplay') + config.set('Infodisplay', 'fade_out_duration', '120')# + config.set('Infodisplay', '\n#put a fly in and fly out animation to the infodisplay') + config.set('Infodisplay', 'fly_animation', 'False')# + config.set('Infodisplay', '\n#Choose to have the Info Display fly in from the top or bottom. (when fly_animation is enabled)') + config.set('Infodisplay', 'fly_in_direction', 'bottom')# + config.set('Infodisplay', '\n#Anchor for infodisplay text. 0,0 is top left, 1,1 is bottom right, 0.5,0.5 is middle of the screen') + config.set('Infodisplay', 'anchor', '0.2,0.1')# + config.set('Infodisplay', '\n#Choice "left", "middle", "right"') + config.set('Infodisplay', 'anchor_style', 'middle')# + config.set('Infodisplay', '\n# True for having the value, then the text, False for having the text, then the value') + config.set('Infodisplay', 'invert_text', 'True')# + config.set('Infodisplay', '\n#size of the outline of the font in pixel') + config.set('Infodisplay', 'outline_width', '3')# + config.set('Infodisplay', '\n#color of the outline of the font') + config.set('Infodisplay', 'outline_color', '000000FF')# + + config.set('Infodisplay', '\n#reimplements pretty speedometer using fade/fly in animations. set it to "xyz", "xz","iv"; or "off" to disable it.') + config.set('Infodisplay', 'pretty_speedometer_type', 'off')#Might be deleted later, since we can have several infodisplay, which gives more options + config.set('Infodisplay', 'pretty_speedometer_color', 'F2E622FF')# + + config.set('Infodisplay', '\n#parameters for the XYZ speed (delta position)') + config.set('Infodisplay', 'show_speed_xyz', 'True') + config.set('Infodisplay', 'text_speed_xyz', '. Speed') + config.set('Infodisplay', 'color_speed_xyz', 'FF0000FF') + + config.set('Infodisplay', '\n#parameters for the XZ speed (delta position)') + config.set('Infodisplay', 'show_speed_xz', 'False') + config.set('Infodisplay', 'text_speed_xz', '. Speed XZ') + config.set('Infodisplay', 'color_speed_xz', 'FF0000FF') + + config.set('Infodisplay', '\n#parameters for the Y speed (delta position)') + config.set('Infodisplay', 'show_speed_y', 'False') + config.set('Infodisplay', 'text_speed_y', '. Speed Y') + config.set('Infodisplay', 'color_speed_y', 'FF0000FF') + + config.set('Infodisplay', '\n#parameters for the XYZ internal velocity') + config.set('Infodisplay', 'show_iv_xyz', 'True') + config.set('Infodisplay', 'text_iv_xyz', '. IV') + config.set('Infodisplay', 'color_iv_xyz', '00FF00FF') + + config.set('Infodisplay', '\n#parameters for the XZ internal velocity') + config.set('Infodisplay', 'show_iv_xz', 'False') + config.set('Infodisplay', 'text_iv_xz', '. IV XZ') + config.set('Infodisplay', 'color_iv_xz', '00FF00FF') + + config.set('Infodisplay', '\n#parameters for the Y internal velocity') + config.set('Infodisplay', 'show_iv_y', 'False') + config.set('Infodisplay', 'text_iv_y', '. IV Y') + config.set('Infodisplay', 'color_iv_y', '00FF00FF') + + config.set('Infodisplay', '\n#parameters for the XYZ external velocity') + config.set('Infodisplay', 'show_ev_xyz', 'True') + config.set('Infodisplay', 'text_ev_xyz', '. EV') + config.set('Infodisplay', 'color_ev_xyz', '0000FFFF') + + config.set('Infodisplay', '\n#parameters for the XZ external velocity') + config.set('Infodisplay', 'show_ev_xz', 'False') + config.set('Infodisplay', 'text_ev_xz', '. EV XZ ') + config.set('Infodisplay', 'color_ev_xz', '0000FFFF') + + config.set('Infodisplay', '\n#parameters for the Y external velocity') + config.set('Infodisplay', 'show_ev_y', 'False') + config.set('Infodisplay', 'text_ev_y', '. EV Y') + config.set('Infodisplay', 'color_ev_y', '0000FFFF') + + config.set('Infodisplay', '\n#display custom text using the mkw font. the anchors x axis will be the center of the text, the y axis will be the top') + config.set('Infodisplay', 'enable_custom_text', 'False') + + config.set('Infodisplay', '\n#to add multiple texts, copy these lines below and increment the number at the end of the parameters') + config.set('Infodisplay', 'custom_text_anchor_1', '0.2.01') + config.set('Infodisplay', 'custom_text_scaling_1', '2.5') + config.set('Infodisplay', 'custom_text_1', 'your text here') + config.set('Infodisplay', 'custom_text_color_1', 'F2E622FF') + + config.set('Infodisplay', '\n\n##############################################################################\n') + + config.add_section('Speed display') + config.set('Speed display', '\n#draw the speed display') + config.set('Speed display', 'show_speed_display', 'True') + config.set('Speed display', '\n#frame OF INPUT this section will appear on') + config.set('Speed display', 'start_frame', '0')# + config.set('Speed display', '\n#frame OF INPUT this section will disappear on. Leave empty for race end') + config.set('Speed display', 'end_frame', '')# + config.set('Speed display', '\n#put a fade in and fade out animation to the Speed display') + config.set('Speed display', 'fade_animation', 'True')# + config.set('Speed display', '\n#duration of the fade in animation in the Speed display') + config.set('Speed display', 'fade_in_duration', '20')# + config.set('Speed display', '\n#duration of the fade out animation in the Speed display') + config.set('Speed display', 'fade_out_duration', '120')# + config.set('Speed display', '\n#put a fly in and fly out animation to the speed display') + config.set('Speed display', 'fly_animation', 'False')# + config.set('Speed display', '\n#Choose to have the Speed Display fly in from the top or bottom. (when fly_animation is enabled)') + config.set('Speed display', 'fly_in_direction', 'bottom')# + config.set('Speed display', '\n#Top left anchor for speed display text. 0,0 is top left, 1,1 is bottom right, 0.5,0.5 is middle of the screen') + config.set('Speed display', 'top_left', '0.7, 0.5')# + config.set('Speed display', '\n#Activating this will make the circle rotate with your facing yaw, so it always face up') + config.set('Speed display', 'rotate_with_yaw', 'True')# + config.set('Speed display', '\n#Radius of the circle in pixel on the final output resolution') + config.set('Speed display', 'circle_radius', '200')# + config.set('Speed display', '\n#Color of the interior of the circle') + config.set('Speed display', 'circle_color', 'FFFFFF80')# + config.set('Speed display', '\n#color of the border of the circle') + config.set('Speed display', 'circle_outline_color', '000000FF')# + config.set('Speed display', '\n#size of the border of the circle') + config.set('Speed display', 'circle_outline_width', '4')# + config.set('Speed display', '\n#draw the XZ axis when rotating with yaw. Draw the sideways and forward axis when not rotating with yaw') + config.set('Speed display', 'draw_axis', 'True')# + config.set('Speed display', '\n#color of the axis') + config.set('Speed display', 'axis_color', '000000FF')# + config.set('Speed display', '\n#width of the axis in pixel on the final output resolution') + config.set('Speed display', 'axis_width', '2')# + config.set('Speed display', '\n#Draw a pieslice corresponding to the facing yaw') + config.set('Speed display', 'draw_pieslice', 'False')# + config.set('Speed display', '\n#color of that pieslice') + config.set('Speed display', 'pieslice_color', 'FFFF00FF') + config.set('Speed display', '\n#width of the arrows') + config.set('Speed display', 'arrow_width', '6')# + config.set('Speed display', '\n#size of the border of the arrows') + config.set('Speed display', 'arrow_outline_width', '1')# + config.set('Speed display', '\n#color of the border of the arrows') + config.set('Speed display', 'arrow_outline_color', '000000FF')# + config.set('Speed display', '\n#maximum speed corresponding to a full arrow. this parameter also scale the length of the arrow') + config.set('Speed display', 'cap_speed', '125')# + config.set('Speed display', '\n#arrow corresponding to speed (delta position)') + config.set('Speed display', 'show_speed', 'True') + config.set('Speed display', 'color_speed', 'FF0000FF') + config.set('Speed display', '\n#arrow corresponding to internal velocity') + config.set('Speed display', 'show_iv', 'True') + config.set('Speed display', 'color_iv', '00FF00FF') + config.set('Speed display', '\n#arrow corresponding to external velocity') + config.set('Speed display', 'show_ev', 'True') + config.set('Speed display', 'color_ev', '0000FFFF') + + config.set('Speed display', '\n\n##############################################################################\n') + + config.add_section('Input display') + config.set('Input display', '\n#draw the input display') + config.set('Input display', 'show_input_display', 'True') + config.set('Input display', '\n#frame OF INPUT this section will appear on') + config.set('Input display', 'start_frame', '0')# + config.set('Input display', '\n#frame OF INPUT this section will disappear on. Leave empty for race end') + config.set('Input display', 'end_frame', '')# + config.set('Input display', '\n#put a fade in and fade out animation to the Input display') + config.set('Input display', 'fade_animation', 'True')# + config.set('Input display', '\n#duration of the fade in animation in the Input display') + config.set('Input display', 'fade_in_duration', '20')# + config.set('Input display', '\n#duration of the fade out animation in the Input display') + config.set('Input display', 'fade_out_duration', '120')# + config.set('Input display', '\n#put a fly in and fly out animation to the input display') + config.set('Input display', 'fly_animation', 'False')# + config.set('Input display', '\n#Choose to have the Input Display fly in from the top or bottom. (when fly_animation is enabled)') + config.set('Input display', 'fly_in_direction', 'bottom')# + config.set('Input display', '\n#Top left anchor for input display text. 0,0 is top left, 1,1 is bottom right, 0.5,0.5 is middle of the screen') + config.set('Input display', 'top_left', '0.03,0.7')# + config.set('Input display', '\n#width options equivalent to pyrkg and other input display tools. image quality has also been improved') + config.set('Input display', '#all pairs of widths 3-7 and outline widths 2-4 are possible, except (7,4)') + config.set('Input display', 'width', '3')# + config.set('Input display', 'outline_width', '3')# + config.set('Input display', '\n#coloring options for the input display parts. alpha channel has no effect but must use RGBA format') + config.set('Input display', 'color_shoulder_left', 'FFFFFFFF') + config.set('Input display', 'color_shoulder_right', 'FFFFFFFF') + config.set('Input display', 'color_dpad', 'FFFFFFFF') + config.set('Input display', 'color_analog', 'FFFFFFFF') + config.set('Input display', 'color_a_button', 'FFFFFFFF') + config.set('Input display', 'color_stick_text', 'FFFFFFFF') + config.set('Input display', '\n#multiplier on the size of input display. Default size is 250x400 in pixel on the output resolution') + config.set('Input display', 'scaling', '1')# + config.set('Input display', '\n#Choice "nearest", "box", "bilinear", "hamming", "bicubic", "lanczos"') + config.set('Input display', '#visit #https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-filters') + config.set('Input display', 'scaling_option', 'lanczos') #https://pillow.readthedocs.io/en/stable/handbook/concepts.html#concept-filters + config.set('Input display', '\n#draw the bounding box. The file itself can be modified in the Input_display folder') + config.set('Input display', 'draw_box', 'True')# + config.set('Input display', '\n#Draw the +7,-7 text corresponding to the stick input') + config.set('Input display', 'draw_stick_text', 'True')# + config.set('Input display', '\n#size of stick text. the default size is 36') + config.set('Input display', 'stick_text_size', '36')# + config.set('Input display', '\n#Choice "blur", "contour", "detail", "edge_enhance", "edge_enhance_more", "emboss", "find_edges", "sharpen", "smooth", "smooth_more"') + config.set('Input display', '#You can use several effects by separating them with a ",". DO NOT USE SPACES') + config.set('Input display', 'special_effects', 'none')# + config.set('Input display', '\n\n##############################################################################\n') + + config.add_section('Author display') + config.set('Author display', '\n#draw the author display') + config.set('Author display', 'show_author_display', 'False') + config.set('Author display', '\n#frame OF INPUT this section will appear on') + config.set('Author display', 'start_frame', '0')# + config.set('Author display', '\n#frame OF INPUT this section will disappear on. Leave empty for race end') + config.set('Author display', 'end_frame', '')# + config.set('Author display', '\n#put a fade in and fade out animation to the Author display') + config.set('Author display', 'fade_animation', 'True')# + config.set('Author display', '\n#duration of the fade in animation in the Author display') + config.set('Author display', 'fade_in_duration', '20')# + config.set('Author display', '\n#duration of the fade out animation in the Author display') + config.set('Author display', 'fade_out_duration', '120')# + config.set('Author display', '\n#put a fly in and fly out animation to the author display') + config.set('Author display', 'fly_animation', 'False')# + config.set('Author display', '\n#Choose to have the Author Display fly in from the top or bottom. (when fly_animation is enabled)') + config.set('Author display', 'fly_in_direction', 'top')# + config.set('Author display', '\n#Top left anchor for author display text. 0,0 is top left, 1,1 is bottom right, 0.5,0.5 is middle of the screen') + config.set('Author display', 'top_left', '0.1,0.4')# + config.set('Author display', '\n#Must be a file in the same folder as this config file. Mandatory for the author display to work') + config.set('Author display', 'author_list_filename', 'authors.txt')# + config.set('Author display', '\n#Font filename. You must put the font in the Font folder.') + config.set('Author display', 'font', 'FOT-Rodin Pro EB.otf')# + config.set('Author display', '\n#font size in pixel on the final output resolution') + config.set('Author display', 'font_size', '48')# + config.set('Author display', '\n#color used for the text when the author has input on this frame') + config.set('Author display', 'active_text_color', 'FFFFFFFF')# + config.set('Author display', '\n#color used for the text when the author does not have input on this frame') + config.set('Author display', 'inactive_text_color', 'FFFFFF55')# + config.set('Author display', '\n#outline width for the font used') + config.set('Author display', 'outline_width', '3')# + config.set('Author display', '\n#color of the outline when the author has input on this frame') + config.set('Author display', 'active_outline_color', '000000FF')# + config.set('Author display', '\n#color of the outline when the author has input on this frame') + config.set('Author display', 'inactive_outline_color', '00000055')# + config.set('Author display', '\n\n##############################################################################\n') + + config.add_section('Extra display') + config.set('Extra display', '\n#this is a debug feature, ignore it\n') + config.set('Extra display', '\n#draw the Extra display') + config.set('Extra display', 'show_extra_display', 'True')# + config.set('Extra display', '\n#Font filename. You must put the font in the Fonts folder.') + config.set('Extra display', 'font', 'CONSOLA.TTF')# + config.set('Extra display', '\n#font size in pixel on the final output resolution for fonts other than the mkw font.') + config.set('Extra display', 'font_size', '48')# + config.set('Extra display', '\n#Scaling factor for MKW Font if used') + config.set('Extra display', 'mkw_font_scaling', '3')# + config.set('Extra display', '\n#vertical spacing in pixel between lines') + config.set('Extra display', 'spacing', '4')# + config.set('Extra display', '\n#Anchor for Extra display text. 0,0 is top left, 1,1 is bottom right, 0.5,0.5 is middle of the screen') + config.set('Extra display', 'anchor', '0.05,0.05')# + config.set('Extra display', '\n#Choice "left", "middle", "right"') + config.set('Extra display', 'anchor_style', 'middle')# + config.set('Extra display', '\n#size of the outline of the font in pixel') + config.set('Extra display', 'outline_width', '3')# + config.set('Extra display', '\n#color of the outline of the font') + config.set('Extra display', 'outline_color', '000000FF')# + + with open(filename, 'w') as f: + config.write(f) + +def get_config(config_filename): + config = configparser.ConfigParser() + config.read(config_filename) + return config diff --git a/scripts/FrameDumps/Extra/extradisplay_util.py b/scripts/FrameDumps/Extra/extradisplay_util.py new file mode 100644 index 0000000..2c5c1b2 --- /dev/null +++ b/scripts/FrameDumps/Extra/extradisplay_util.py @@ -0,0 +1,96 @@ +import moviepy +import numpy as np +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +import sys +import os +import time +import subprocess +import math + +from Extra import common + +#credit to https://github.com/nkfr26/mkwii-text-generator-tkinter +width_offset_table = {"A" : -8, "B" :-16, "C" :-21, "D" :-16, + "E" :-16, "F" :-21, "G" :-18, "H" :-16, + "I" :-14, "J" :-16, "K" :-15, "L" :-14, + "M" :-17, "N" :-16, "O" :-13, "P" :-15, + "Q" :-12, "R" :-12, "S" :-16, "T" :-15, + "U" :-16, "V" :-12, "W" :-22, "X" :-22, + "Y" :-24, "Z" :-12, "+" :-32, "-" :-32, + "/" :-28, ":" : -1, "." : -1, "0" : -1, + "1" : -1, "2" : -1, "3" : -1, "4" : -1, + "5" : -1, "6" : -1, "7" : -1, "8" : -1, + "9" : -1, "&" :-32} + + + +def add_mkw_text(line_layer, x, text, mkw_font_dict, color, mkw_scaling): + for letter in text: + if letter in mkw_font_dict.keys(): + cur_letter = mkw_font_dict[letter].copy() + channels = list(cur_letter.split()) + for i in range(4): + channels[i] = channels[i].point(lambda x : round(x*color[i]/255)) + cur_letter = Image.merge("RGBA", tuple(channels)) + line_layer.alpha_composite(cur_letter, (x,0)) + x += cur_letter.size[0] + round(width_offset_table[letter]*4*mkw_scaling) + elif letter == '>' : + x += round(10*mkw_scaling) + elif letter == '<': + x -= round(10*mkw_scaling) + else: + x += round(268*mkw_scaling) + round(4*mkw_scaling*-1) #Make spaces the exact same width as numbers + return x + + +def add_mkw_font_line(id_layer, text, color, mkw_font_dict, anchor, mkw_scaling, anchor_style): + w,h = anchor + line_layer = Image.new('RGBA', (id_layer.size[0], round(mkw_scaling*336)+1), (0,0,0,0)) + + + left_x = 0 + middle_x = add_mkw_text(line_layer, left_x, text[0], mkw_font_dict, color, mkw_scaling) + right_x = add_mkw_text(line_layer, middle_x, text[1], mkw_font_dict, color, mkw_scaling) + + if anchor_style == 'left': + offset = left_x + elif anchor_style == 'middle': + offset = middle_x + else: + offset = right_x + w -= offset + + id_layer.alpha_composite(line_layer, (w,h)) + + +def add_extradisplay(image, extra_text, ex_config, font_folder, mkw_font_dict): + + infodisplay_layer = Image.new('RGBA', image.size, (0,0,0,0)) + ID = ImageDraw.Draw(infodisplay_layer) + mkw_scaling = eval(ex_config.get('mkw_font_scaling'))/12 + top_left_text = ex_config.get('anchor').split(',') + top_left = round(float(top_left_text[0])*image.width), round(float(top_left_text[1])*image.height) + current_h = top_left[1] + spacing = ex_config.getint('spacing') + font_size = ex_config.getint('font_size') + font_filename = os.path.join(font_folder, ex_config.get('font')) + if os.path.isfile(font_filename): + font = ImageFont.truetype(font_filename, font_size) + else: + font = None + outline_width = ex_config.getint('outline_width') + outline_color = common.get_color(ex_config.get('outline_color')) + color = common.get_color(ex_config.get('color', 'FFFF00FF')) + + for text in extra_text.split('\n'): + if font is None: + add_mkw_font_line(infodisplay_layer, text, color, mkw_font_dict, (top_left[0], current_h), mkw_scaling, 'left') + current_h += round(336*mkw_scaling) + spacing + else: + ID.text( (top_left[0], current_h), text, fill = color, font = font, stroke_width = outline_width, stroke_fill = outline_color) + current_h += spacing + font_size + + image.alpha_composite(infodisplay_layer, (0,0)) + diff --git a/scripts/FrameDumps/Extra/infodisplay_util.py b/scripts/FrameDumps/Extra/infodisplay_util.py new file mode 100644 index 0000000..dd6d1a4 --- /dev/null +++ b/scripts/FrameDumps/Extra/infodisplay_util.py @@ -0,0 +1,220 @@ +import moviepy +import numpy as np +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +import sys +import os +import time +import subprocess +import math + +from Extra import common + +#credit to https://github.com/nkfr26/mkwii-text-generator-tkinter +width_offset_table = {"A" : -8, "B" :-16, "C" :-21, "D" :-16, + "E" :-16, "F" :-21, "G" :-18, "H" :-16, + "I" :-14, "J" :-16, "K" :-15, "L" :-14, + "M" :-17, "N" :-16, "O" :-13, "P" :-15, + "Q" :-12, "R" :-12, "S" :-16, "T" :-15, + "U" :-16, "V" :-12, "W" :-22, "X" :-22, + "Y" :-24, "Z" :-12, "+" :-32, "-" :-32, + "/" :-28, ":" : -1, "." : -1, "0" : -1, + "1" : -1, "2" : -1, "3" : -1, "4" : -1, + "5" : -1, "6" : -1, "7" : -1, "8" : -1, + "9" : -1, "&" :-32} + + +def make_text_key(d, c, font, key): + text_key = 'text'+key[4:] + + if key == 'show_speed_xyz': + val = (float(d['spd_x']) ** 2 + float(d['spd_y']) ** 2 + float(d['spd_z']) ** 2)**0.5 + elif key == 'show_speed_xz': + val = (float(d['spd_x']) ** 2 + float(d['spd_z']) ** 2)**0.5 + elif key == 'show_speed_y': + val = float(d['spd_y']) + elif key == 'show_iv_xyz': + val = (float(d['iv_x']) ** 2 + float(d['iv_y']) ** 2 + float(d['iv_z']) ** 2)**0.5 + elif key == 'show_iv_xz': + val = (float(d['iv_x']) ** 2 + float(d['iv_z']) ** 2)**0.5 + elif key == 'show_iv_y': + val = float(d['iv_y']) + elif key == 'show_ev_xyz': + val = (float(d['ev_x']) ** 2 + float(d['ev_y']) ** 2 + float(d['ev_z']) ** 2)**0.5 + elif key == 'show_ev_xz': + val = (float(d['ev_x']) ** 2 + float(d['ev_z']) ** 2)**0.5 + elif key == 'show_ev_y': + val = float(d['ev_y']) + elif key == 'show_frame_count': + val = float(d['frame_of_input']) + + prefix_text = c[text_key] + if prefix_text[0] in ['.', '"']: + prefix_text = prefix_text[1:] + if prefix_text[-1] in ['.', '"']: + prefix_text = prefix_text[:-1] + return f'{prefix_text}',f'{val:.2f}' + + +def add_mkw_text(line_layer, x, text, mkw_font_dict, color, mkw_scaling): + for letter in text: + if letter in mkw_font_dict.keys(): + cur_letter = mkw_font_dict[letter].copy() + channels = list(cur_letter.split()) + for i in range(4): + channels[i] = channels[i].point(lambda x : round(x*color[i]/255)) + cur_letter = Image.merge("RGBA", tuple(channels)) + line_layer.alpha_composite(cur_letter, (x,0)) + x += cur_letter.size[0] + round(width_offset_table[letter]*4*mkw_scaling) + elif letter == '>' : + x += round(10*mkw_scaling) + elif letter == '<': + x -= round(10*mkw_scaling) + else: + x += round(268*mkw_scaling) + round(4*mkw_scaling*-1) #Make spaces the exact same width as numbers + return x + + +def add_mkw_font_line(id_layer, prefix, value, color, mkw_font_dict, anchor, mkw_scaling, anchor_style, invert_design): + w,h = anchor + if value == None or prefix == None: + custom_text_layer = Image.new('RGBA', (id_layer.size[0], round(mkw_scaling*336)+1), (0,0,0,0)) + x = add_mkw_text(custom_text_layer, 0, prefix or value, mkw_font_dict, color, mkw_scaling) + if anchor_style == 'right': + id_layer.alpha_composite(custom_text_layer, (w-x,h)) # for pretty speedometer + else: + id_layer.alpha_composite(custom_text_layer, (w-x//2,h)) # for custom text + return None + + line_layer = Image.new('RGBA', (id_layer.size[0], round(mkw_scaling*336)+1), (0,0,0,0)) + + if invert_design and not prefix == '<>': + text = value,prefix + else: + text = prefix,value + + left_x = 0 + middle_x = add_mkw_text(line_layer, left_x, text[0], mkw_font_dict, color, mkw_scaling) + right_x = add_mkw_text(line_layer, middle_x, text[1], mkw_font_dict, color, mkw_scaling) + + if prefix == '<>': + id_layer.alpha_composite(line_layer, (w-(middle_x+right_x)//2,h)) + return None + + if anchor_style == 'left': + offset = left_x + elif anchor_style == 'middle': + offset = middle_x + else: + offset = right_x + w -= offset + + id_layer.alpha_composite(line_layer, (w,h)) + + +def add_infodisplay(image, id_dict, id_config, font_folder, mkw_font_dict): + state, state_counter = int(id_dict['state']), int(id_dict['state_counter']) + infodisplay_layer = Image.new('RGBA', image.size, (0,0,0,0)) + ID = ImageDraw.Draw(infodisplay_layer) + mkw_scaling = eval(id_config.get('mkw_font_scaling'))/12 + top_left_text = id_config.get('anchor').split(',') + top_left = round(float(top_left_text[0])*image.width), round(float(top_left_text[1])*image.height) + current_h = top_left[1] + spacing = id_config.getint('spacing') + font_size = id_config.getint('font_size') + font_filename = os.path.join(font_folder, id_config.get('font')) + if os.path.isfile(font_filename): + font = ImageFont.truetype(font_filename, font_size) + else: + font = None + outline_width = id_config.getint('outline_width') + outline_color = common.get_color(id_config.get('outline_color')) + anchor_style = id_config.get('anchor_style') + invert = id_config.getboolean('invert_text') + + + if id_config.get('pretty_speedometer_type') in ('xyz', 'xz', 'iv'): + custom_color = common.get_color(id_config.get('pretty_speedometer_color')) + if id_config.get('pretty_speedometer_type') == 'xyz': + val = (float(id_dict['spd_x']) ** 2 + float(id_dict['spd_y']) ** 2 + float(id_dict['spd_z']) ** 2)**0.5 + elif id_config.get('pretty_speedometer_type') == 'xz': + val = (float(id_dict['spd_x']) ** 2 + float(id_dict['spd_z']) ** 2)**0.5 + else: + val = (float(id_dict['iv_x']) ** 2 + float(id_dict['iv_y']) ** 2 + float(id_dict['iv_z']) ** 2)**0.5 + value_text = f'{val:.2f}' + add_mkw_font_line(infodisplay_layer, None, value_text.upper(), custom_color, mkw_font_dict[0.2375], (round(0.932*image.width), round(0.862*image.height)), 0.2375, 'right', False) + + + if id_config.getboolean('enable_custom_text'): + i = 1 + while f'custom_text_{i}' in id_config: + custom_text_anchor = id_config.get(f'custom_text_anchor_{i}').split(',') + custom_anchor = round(float(custom_text_anchor[0])*image.width), round(float(custom_text_anchor[1])*image.height) + custom_scaling = eval(id_config.get(f'custom_text_scaling_{i}'))/12 + custom_text = id_config.get(f'custom_text_{i}') + custom_color = common.get_color(id_config.get(f'custom_text_color_{i}')) + add_mkw_font_line(infodisplay_layer, custom_text.upper(), None, custom_color, mkw_font_dict[custom_scaling], (custom_anchor[0], custom_anchor[1]), custom_scaling, 'centered', False) + i += 1 + + + for key in id_config.keys(): + if len(key) > 4 and key[:4] == 'show' and key != 'show_infodisplay' and id_config.getboolean(key): + prefix_text,value_text = make_text_key(id_dict, id_config, font, key) + color_text = id_config.get('color'+key[4:]) + color = common.get_color(color_text) + + if font is None: + add_mkw_font_line(infodisplay_layer, prefix_text.upper(), value_text.upper(), color, mkw_font_dict[mkw_scaling], (top_left[0], current_h), mkw_scaling, anchor_style, invert) + current_h += round(336*mkw_scaling) + spacing + else: + if invert: + text = value_text + prefix_text + if anchor_style == 'left': + offset = 0 + elif anchor_style == 'middle': + offset = -ID.textlength(value_text, font, font_size = font_size) + else: + offset = -ID.textlength(text, font, font_size = font_size) + else: + text = prefix_text + value_text + if anchor_style == 'left': + offset = 0 + elif anchor_style == 'middle': + offset = -ID.textlength(prefix_text, font, font_size = font_size) + else: + offset = -ID.textlength(text, font, font_size = font_size) + + ID.text( (top_left[0]+offset, current_h), text, fill = color, font = font, stroke_width = outline_width, stroke_fill = outline_color) + current_h += spacing + font_size + + + if id_config.getboolean('fade_animation'): + infodisplay_layer = common.fade_image_manually(infodisplay_layer, id_dict, id_config) + + + if id_config.getboolean('fly_animation'): + + if state == 1 and state_counter <= 10: + return None + + offset = common.fly_in(id_dict, image.height) + + if state == 4 and 192 < state_counter < 202: + image.alpha_composite(infodisplay_layer, (round(0 - image.width*offset), 0)) + return None + + ITEM_POSITION = 381/1440 + INFODISPLAY_POSITION = (1 - float(top_left_text[1])) + height_correction = round((INFODISPLAY_POSITION - ITEM_POSITION)*image.height) + + if offset is not None: + if id_config.get('fly_in_direction') == 'bottom': + image.alpha_composite(infodisplay_layer, (0, image.height - top_left[1] - height_correction - offset)) + else: + image.alpha_composite(infodisplay_layer, (0, offset - round(ITEM_POSITION*image.height))) + else: + image.alpha_composite(infodisplay_layer, (0,0)) + else: + image.alpha_composite(infodisplay_layer, (0,0)) + diff --git a/scripts/FrameDumps/Extra/input_display_util.py b/scripts/FrameDumps/Extra/input_display_util.py new file mode 100644 index 0000000..ccf6b61 --- /dev/null +++ b/scripts/FrameDumps/Extra/input_display_util.py @@ -0,0 +1,142 @@ +import moviepy +import numpy as np +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +from PIL import ImageFilter +import sys +import os +import time +import subprocess +import math +from Extra import common + +def make_input_display(raw_input_text, config_id, font, recolored_images, w, ow): + args = raw_input_text.split(',') + A = int(args[0]) + B = int(args[1]) + L = int(args[2]) + X = int(args[5]) + Y = int(args[6]) + T = int(args[7]) + + color_stick_text_tmp = common.get_color(config_id.get('color_stick_text')) + color_stick_text = tuple(color_stick_text_tmp[:3]) + + if config_id.getboolean('draw_box'): + output = recolored_images['background'].copy() + else: + output = Image.new('RGBA', (400,250), (255, 0, 0, 0)) + + if A == 0: + recolored_key = f'button_{w}_{ow}|color_a_button' + output.alpha_composite(recolored_images[recolored_key], (267,114)) + elif A ==1 : + recolored_key = f'button_filled_{w}_{ow}|color_a_button' + output.alpha_composite(recolored_images[recolored_key], (267,114)) + + if B == 0: + recolored_key = f'shoulder_{w}_{ow}|color_shoulder_right' + output.alpha_composite(recolored_images[recolored_key], (236, 46)) + elif B ==1 : + recolored_key = f'shoulder_filled_{w}_{ow}|color_shoulder_right' + output.alpha_composite(recolored_images[recolored_key], (236,46)) + + if L == 0: + recolored_key = f'shoulder_{w}_{ow}|color_shoulder_left' + output.alpha_composite(recolored_images[recolored_key], (56,46)) + elif L ==1 : + recolored_key = f'shoulder_filled_{w}_{ow}|color_shoulder_left' + output.alpha_composite(recolored_images[recolored_key], (56,46)) + + recolored_key = f'dpad_fill_{T}' + output.alpha_composite(recolored_images[recolored_key], (57,107)) + + recolored_key = f'dpad_{w}_{ow}|color_dpad' + output.alpha_composite(recolored_images[recolored_key], (47,97)) + + + recolored_key = f'analog_base_{w}_{ow}|color_analog' + output.alpha_composite(recolored_images[recolored_key], (152,94)) + + recolored_key = f'analog_outer_{w}_{ow}|color_analog' + output.alpha_composite(recolored_images[recolored_key], (155 + X * 3,97 - Y * 3)) + + if config_id.getboolean('draw_box'): + recolored_key = f'analog_bg_part1_{w}_{ow}|color_analog' + output.paste(recolored_images[recolored_key], (155 + 34 + X * 3,97 + 20 - Y * 3)) + recolored_key = f'analog_bg_part2_{w}_{ow}|color_analog' + output.paste(recolored_images[recolored_key], (155 + 27 + X * 3,97 + 25 - Y * 3)) + recolored_key = f'analog_bg_part3_{w}_{ow}|color_analog' + output.paste(recolored_images[recolored_key], (155 + 21 + X * 3,97 + 31 - Y * 3)) + recolored_key = f'analog_bg_part4_{w}_{ow}|color_analog' + output.paste(recolored_images[recolored_key], (155 + 27 + X * 3,97 + 64 - Y * 3)) + recolored_key = f'analog_bg_part5_{w}_{ow}|color_analog' + output.paste(recolored_images[recolored_key], (155 + 34 + X * 3,97 + 70 - Y * 3)) + + else: + recolored_key = f'analog_part1_{w}_{ow}|color_analog' + output.paste(recolored_images[recolored_key], (155 + 34 + X * 3,97 + 20 - Y * 3)) + recolored_key = f'analog_part2_{w}_{ow}|color_analog' + output.paste(recolored_images[recolored_key], (155 + 27 + X * 3,97 + 25 - Y * 3)) + recolored_key = f'analog_part3_{w}_{ow}|color_analog' + output.paste(recolored_images[recolored_key], (155 + 21 + X * 3,97 + 31 - Y * 3)) + recolored_key = f'analog_part4_{w}_{ow}|color_analog' + output.paste(recolored_images[recolored_key], (155 + 27 + X * 3,97 + 64 - Y * 3)) + recolored_key = f'analog_part5_{w}_{ow}|color_analog' + output.paste(recolored_images[recolored_key], (155 + 34 + X * 3,97 + 70 - Y * 3)) + + scaling = config_id.getfloat('scaling') + if config_id.getboolean('draw_stick_text'): + stick_text_size = config_id.getint('stick_text_size') + ID = ImageDraw.Draw(output) + text = f'({"+" if X>0 else (" " if X==0 else '')}{X},{"+" if Y>0 else (" " if Y==0 else '')}{Y})' + ID.text((132 + round(36 - stick_text_size)*scaling, 198 + (36 - stick_text_size)//scaling), text, font = font, fill = color_stick_text, stroke_width = 3 if ow >= 3 else 2, stroke_fill = (0,0,0)) + + if scaling != 1.0: + resample_filter = common.get_resampler(config_id.get('scaling_option')) + output = output.resize((round(400*scaling), round(250*scaling)), resample_filter) + + for filtre in common.get_filter_list(config_id.get('special_effects')): + output = output.filter(filtre) + + return output + +def add_input_display(image, frame_dict, config, font_folder, recolored_images, w, ow): + state, state_counter = int(frame_dict['state']), int(frame_dict['state_counter']) + stick_text_size = config['Input display'].getint('stick_text_size') + font = ImageFont.truetype(os.path.join(font_folder, 'CONSOLA.TTF'), stick_text_size) + input_display = make_input_display(frame_dict['input'], config['Input display'], font, recolored_images, w, ow) + top_left_text = config['Input display'].get('top_left').split(',') + top_left = round(float(top_left_text[0])*image.width), round(float(top_left_text[1])*image.height) + + + if config['Input display'].getboolean('fade_animation'): + input_display = common.fade_image_manually(input_display, frame_dict, config['Input display']) + + + if config['Input display'].getboolean('fly_animation'): + + if state == 1 and state_counter <= 10: + return None + + offset = common.fly_in(frame_dict, image.height) + + if state == 4 and 192 < state_counter < 202: + image.alpha_composite(input_display, (round(top_left[0] - image.width*offset), top_left[1])) + return None + + ITEM_POSITION = 381/1440 + INPUT_DISPLAY_POSITION = (1 - float(top_left_text[1])) + height_correction = round((INPUT_DISPLAY_POSITION - ITEM_POSITION)*image.height) + + if offset is not None: + if config['Input display'].get('fly_in_direction') == 'bottom': + image.alpha_composite(input_display, (top_left[0], image.height - height_correction - offset)) + else: + image.alpha_composite(input_display, (top_left[0], top_left[1] - round(ITEM_POSITION*image.height) + offset)) + else: + image.alpha_composite(input_display, top_left) + else: + image.alpha_composite(input_display, top_left) + diff --git a/scripts/FrameDumps/Extra/speed_display_util.py b/scripts/FrameDumps/Extra/speed_display_util.py new file mode 100644 index 0000000..7f27e13 --- /dev/null +++ b/scripts/FrameDumps/Extra/speed_display_util.py @@ -0,0 +1,156 @@ +import moviepy +import numpy as np +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +import sys +import os +import time +import subprocess +import math + +from Extra import common + +def get_color(color_text): + n = int(color_text, 16) + l = [] + for _ in range(4): + l.append(n%256) + n//=256 + l.reverse() + return tuple(l) + +def rotate_point(point, angle, center): + angle *= -math.pi/180 + x,y = point + x0, y0 = center + x -= x0 + y -= y0 + x,y = x*math.cos(angle) - y*math.sin(angle), x*math.sin(angle) + y*math.cos(angle) + x += x0 + y += y0 + return int(x),int(y) + +def add_arrow(im, startpoint, vector, color, width, outline_color, outline_width, radius): + arrow_image = Image.new('RGBA', (radius*2+1, radius*2+1), (255, 0, 0, 0)) + ID = ImageDraw.Draw(arrow_image) + endpoint = (startpoint[0]+vector[0], startpoint[1]+vector[1]) + t = 0.85 + angle = 8 + mid_point = (startpoint[0]+vector[0]*t, startpoint[1]+vector[1]*t) + left_point = rotate_point(mid_point, angle, startpoint) + right_point = rotate_point(mid_point, -angle, startpoint) + xy = [startpoint, endpoint] + lyr = [left_point, endpoint, right_point] + ID.line(xy, outline_color, outline_width+width, 'curve') + ID.line(xy, color, width, 'curve') + ID.line(lyr, outline_color, outline_width+width, 'curve') + ID.line(lyr, color, width, 'curve') + + im.alpha_composite(arrow_image, (0,0)) + + +def normalize_speed(vec, cap, ratio): + magn = (vec[0]**2+vec[1]**2)**0.5 + if magn > cap: + return vec[0]*ratio*cap/magn, vec[1]*ratio*cap/magn + else: + return vec[0]*ratio, vec[1]*ratio + +def add_speed_display(im, d, config): + c = config['Speed display'] + state, state_counter = int(d['state']), int(d['state_counter']) + radius = c.getint('circle_radius') + circle_color = get_color(c.get('circle_color')) + circle_width = c.getint('circle_outline_width') + cirlce_border_color = get_color(c.get('circle_outline_color')) + center = (radius, radius) + yaw = float(d['yaw'])+180 + speed_display_image = Image.new('RGBA', (radius*2+1, radius*2+1), (255, 0, 0, 0)) + + #draw circle + ID = ImageDraw.Draw(speed_display_image) + ID.circle(center, 0.95*radius, fill = circle_color, outline = cirlce_border_color, width = circle_width) + + #draw axis + if c.getboolean('draw_axis'): + axis_color = get_color(c.get('axis_color')) + axis_width = c.getint('axis_width') + if c.getboolean('rotate_with_yaw'): + ID.line([rotate_point((0, radius), yaw, center), rotate_point((radius*2+1, radius), yaw, center)], axis_color, axis_width) + ID.line([rotate_point((radius, 0), yaw, center), rotate_point((radius, radius*2+1), yaw, center)], axis_color, axis_width) + else: + ID.line([center, rotate_point((radius, 0), -yaw, center)], axis_color, axis_width) + ID.line([center, rotate_point((0, radius), -yaw, center)], axis_color, axis_width) + ID.line([center, rotate_point((radius*2+1, radius), -yaw, center)], axis_color, axis_width) + + #draw pieslice + if c.getboolean('draw_pieslice'): + angle = 10 + pieslice_color = get_color(c.get('pieslice_color')) + if c.getboolean('rotate_with_yaw'): + ID.pieslice([(0.2*radius, 0.2*radius), (1.8*radius, 1.8*radius)], 270-angle, 270+angle, pieslice_color) + else: + ID.pieslice([(0.2*radius, 0.2*radius), (1.8*radius, 1.8*radius)], 270-angle+yaw, 270+angle+yaw, pieslice_color) + + #Get speed vectors + speed_activated = [] + for key in c.keys(): + if len(key) > 5 and key[:4] == 'show' and key != 'show_speed_display' and c.getboolean(key): + speed_activated.append(key[5:]) + + correspondance_dict = {"speed" : (float(d['spd_x']), float(d['spd_z'])), + "iv" : (float(d['iv_x']), float(d['iv_z'])), + "ev" : (float(d['ev_x']), float(d['ev_z']))} + + #Normalize to screen pixel size + top_speed = c.getint('cap_speed') + ratio = 0.95*radius/top_speed + speed_vec_activated = [normalize_speed(correspondance_dict[txt], top_speed, ratio) for txt in speed_activated] + if c.getboolean('rotate_with_yaw'): + for i in range(len(speed_vec_activated)): + speed_vec_activated[i] = rotate_point(speed_vec_activated[i], yaw, (0,0)) + + #draw arrows + arrow_width = c.getint('arrow_width') + arrow_outline_width = c.getint('arrow_outline_width') + arrow_outline_color = get_color(c.get('arrow_outline_color')) + for i in range(len(speed_vec_activated)): + color_key = 'color_'+speed_activated[i] + color = get_color(c.get(color_key)) + spd_vec = speed_vec_activated[i] + add_arrow(speed_display_image, center, spd_vec, color, arrow_width, arrow_outline_color, arrow_outline_width, radius) + + top_left_text = c.get('top_left').split(',') + top_left = round(float(top_left_text[0])*im.width), round(float(top_left_text[1])*im.height) + + + if c.getboolean('fade_animation'): + speed_display_image = common.fade_image_manually(speed_display_image, d, c) + + + if c.getboolean('fly_animation'): + + if state == 1 and state_counter <= 10: + return None + + offset = common.fly_in(d, im.height) + + if state == 4 and 192 < state_counter < 202: + im.alpha_composite(speed_display_image, (round(top_left[0] - im.width*offset), top_left[1])) + return None + + ITEM_POSITION = 381/1440 + SPEED_DISPLAY_POSITION = (1 - float(top_left_text[1])) + height_correction = round((SPEED_DISPLAY_POSITION - ITEM_POSITION)*im.height) + + if offset is not None: + if c.get('fly_in_direction') == 'bottom': + im.alpha_composite(speed_display_image, (top_left[0], im.height - height_correction - offset)) + else: + im.alpha_composite(speed_display_image, (top_left[0], top_left[1] - round(ITEM_POSITION*im.height) + offset)) + else: + im.alpha_composite(speed_display_image, top_left) + else: + im.alpha_composite(speed_display_image, top_left) + diff --git a/scripts/FrameDumps/Fonts/CONSOLA.TTF b/scripts/FrameDumps/Fonts/CONSOLA.TTF new file mode 100644 index 0000000..556d2fd Binary files /dev/null and b/scripts/FrameDumps/Fonts/CONSOLA.TTF differ diff --git a/scripts/FrameDumps/Fonts/FOT-Rodin Pro EB.otf b/scripts/FrameDumps/Fonts/FOT-Rodin Pro EB.otf new file mode 100644 index 0000000..c3f5bde Binary files /dev/null and b/scripts/FrameDumps/Fonts/FOT-Rodin Pro EB.otf differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/+.png b/scripts/FrameDumps/Fonts/MKW_Font/+.png new file mode 100644 index 0000000..6e31e77 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/+.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/-.png b/scripts/FrameDumps/Fonts/MKW_Font/-.png new file mode 100644 index 0000000..aea97de Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/-.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/0.png b/scripts/FrameDumps/Fonts/MKW_Font/0.png new file mode 100644 index 0000000..e26f641 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/0.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/1.png b/scripts/FrameDumps/Fonts/MKW_Font/1.png new file mode 100644 index 0000000..a77835c Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/1.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/2.png b/scripts/FrameDumps/Fonts/MKW_Font/2.png new file mode 100644 index 0000000..2ca405a Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/2.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/3.png b/scripts/FrameDumps/Fonts/MKW_Font/3.png new file mode 100644 index 0000000..1604f0a Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/3.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/4.png b/scripts/FrameDumps/Fonts/MKW_Font/4.png new file mode 100644 index 0000000..ffc73c4 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/4.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/5.png b/scripts/FrameDumps/Fonts/MKW_Font/5.png new file mode 100644 index 0000000..13fd5d9 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/5.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/6.png b/scripts/FrameDumps/Fonts/MKW_Font/6.png new file mode 100644 index 0000000..f92ab95 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/6.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/7.png b/scripts/FrameDumps/Fonts/MKW_Font/7.png new file mode 100644 index 0000000..d6b12ed Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/7.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/8.png b/scripts/FrameDumps/Fonts/MKW_Font/8.png new file mode 100644 index 0000000..9e7ca53 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/8.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/9.png b/scripts/FrameDumps/Fonts/MKW_Font/9.png new file mode 100644 index 0000000..5249ba1 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/9.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/A.png b/scripts/FrameDumps/Fonts/MKW_Font/A.png new file mode 100644 index 0000000..da22cbf Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/A.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/B.png b/scripts/FrameDumps/Fonts/MKW_Font/B.png new file mode 100644 index 0000000..98c5cef Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/B.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/C.png b/scripts/FrameDumps/Fonts/MKW_Font/C.png new file mode 100644 index 0000000..130d8cc Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/C.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/COLON.png b/scripts/FrameDumps/Fonts/MKW_Font/COLON.png new file mode 100644 index 0000000..ef3ef1f Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/COLON.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/D.png b/scripts/FrameDumps/Fonts/MKW_Font/D.png new file mode 100644 index 0000000..aca1bd1 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/D.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/E.png b/scripts/FrameDumps/Fonts/MKW_Font/E.png new file mode 100644 index 0000000..996ae7c Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/E.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/F.png b/scripts/FrameDumps/Fonts/MKW_Font/F.png new file mode 100644 index 0000000..5531483 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/F.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/G.png b/scripts/FrameDumps/Fonts/MKW_Font/G.png new file mode 100644 index 0000000..73c5789 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/G.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/H.png b/scripts/FrameDumps/Fonts/MKW_Font/H.png new file mode 100644 index 0000000..d4a62d2 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/H.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/I.png b/scripts/FrameDumps/Fonts/MKW_Font/I.png new file mode 100644 index 0000000..7d7dacd Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/I.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/J.png b/scripts/FrameDumps/Fonts/MKW_Font/J.png new file mode 100644 index 0000000..5d272cb Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/J.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/K.png b/scripts/FrameDumps/Fonts/MKW_Font/K.png new file mode 100644 index 0000000..cbc75fc Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/K.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/L.png b/scripts/FrameDumps/Fonts/MKW_Font/L.png new file mode 100644 index 0000000..329162c Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/L.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/M.png b/scripts/FrameDumps/Fonts/MKW_Font/M.png new file mode 100644 index 0000000..6822244 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/M.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/N.png b/scripts/FrameDumps/Fonts/MKW_Font/N.png new file mode 100644 index 0000000..d4e0df5 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/N.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/O.png b/scripts/FrameDumps/Fonts/MKW_Font/O.png new file mode 100644 index 0000000..fefbf6b Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/O.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/P.png b/scripts/FrameDumps/Fonts/MKW_Font/P.png new file mode 100644 index 0000000..92172a8 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/P.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/PERIOD.png b/scripts/FrameDumps/Fonts/MKW_Font/PERIOD.png new file mode 100644 index 0000000..d3bb53e Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/PERIOD.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/Q.png b/scripts/FrameDumps/Fonts/MKW_Font/Q.png new file mode 100644 index 0000000..13164a4 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/Q.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/R.png b/scripts/FrameDumps/Fonts/MKW_Font/R.png new file mode 100644 index 0000000..3b5cf3c Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/R.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/S.png b/scripts/FrameDumps/Fonts/MKW_Font/S.png new file mode 100644 index 0000000..597fac1 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/S.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/SLASH.png b/scripts/FrameDumps/Fonts/MKW_Font/SLASH.png new file mode 100644 index 0000000..1dd1e3d Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/SLASH.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/SLASH_.png b/scripts/FrameDumps/Fonts/MKW_Font/SLASH_.png new file mode 100644 index 0000000..a4d00a1 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/SLASH_.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/SLASH_smaller.png b/scripts/FrameDumps/Fonts/MKW_Font/SLASH_smaller.png new file mode 100644 index 0000000..9c8747c Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/SLASH_smaller.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/T.png b/scripts/FrameDumps/Fonts/MKW_Font/T.png new file mode 100644 index 0000000..237c0ac Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/T.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/U.png b/scripts/FrameDumps/Fonts/MKW_Font/U.png new file mode 100644 index 0000000..dd4d3ab Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/U.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/V.png b/scripts/FrameDumps/Fonts/MKW_Font/V.png new file mode 100644 index 0000000..7fa9b7c Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/V.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/W.png b/scripts/FrameDumps/Fonts/MKW_Font/W.png new file mode 100644 index 0000000..ce8f706 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/W.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/X.png b/scripts/FrameDumps/Fonts/MKW_Font/X.png new file mode 100644 index 0000000..1445631 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/X.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/Y.png b/scripts/FrameDumps/Fonts/MKW_Font/Y.png new file mode 100644 index 0000000..152b3bb Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/Y.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/Z.png b/scripts/FrameDumps/Fonts/MKW_Font/Z.png new file mode 100644 index 0000000..7b885d6 Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/Z.png differ diff --git a/scripts/FrameDumps/Fonts/MKW_Font/backup font upscaled x4.7z b/scripts/FrameDumps/Fonts/MKW_Font/backup font upscaled x4.7z new file mode 100644 index 0000000..2d017ba Binary files /dev/null and b/scripts/FrameDumps/Fonts/MKW_Font/backup font upscaled x4.7z differ diff --git a/scripts/FrameDumps/Fonts/SMG2-Font.OTF b/scripts/FrameDumps/Fonts/SMG2-Font.OTF new file mode 100644 index 0000000..3a00db0 Binary files /dev/null and b/scripts/FrameDumps/Fonts/SMG2-Font.OTF differ diff --git a/scripts/FrameDumps/Input_display/analog_base_3_2.png b/scripts/FrameDumps/Input_display/analog_base_3_2.png new file mode 100644 index 0000000..d8af482 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_3_3.png b/scripts/FrameDumps/Input_display/analog_base_3_3.png new file mode 100644 index 0000000..c2979d4 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_3_4.png b/scripts/FrameDumps/Input_display/analog_base_3_4.png new file mode 100644 index 0000000..d432f0c Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_4_2.png b/scripts/FrameDumps/Input_display/analog_base_4_2.png new file mode 100644 index 0000000..386f31f Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_4_3.png b/scripts/FrameDumps/Input_display/analog_base_4_3.png new file mode 100644 index 0000000..1f092b8 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_4_4.png b/scripts/FrameDumps/Input_display/analog_base_4_4.png new file mode 100644 index 0000000..cf38b20 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_5_2.png b/scripts/FrameDumps/Input_display/analog_base_5_2.png new file mode 100644 index 0000000..264de48 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_5_3.png b/scripts/FrameDumps/Input_display/analog_base_5_3.png new file mode 100644 index 0000000..675ee1b Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_5_4.png b/scripts/FrameDumps/Input_display/analog_base_5_4.png new file mode 100644 index 0000000..9c2590d Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_6_2.png b/scripts/FrameDumps/Input_display/analog_base_6_2.png new file mode 100644 index 0000000..0c6e30b Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_6_3.png b/scripts/FrameDumps/Input_display/analog_base_6_3.png new file mode 100644 index 0000000..decc1c8 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_6_4.png b/scripts/FrameDumps/Input_display/analog_base_6_4.png new file mode 100644 index 0000000..4970d19 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_7_2.png b/scripts/FrameDumps/Input_display/analog_base_7_2.png new file mode 100644 index 0000000..9df9d00 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_base_7_3.png b/scripts/FrameDumps/Input_display/analog_base_7_3.png new file mode 100644 index 0000000..6ecc70a Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_base_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_3_2.png b/scripts/FrameDumps/Input_display/analog_bg_part1_3_2.png new file mode 100644 index 0000000..c40c8cd Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_3_3.png b/scripts/FrameDumps/Input_display/analog_bg_part1_3_3.png new file mode 100644 index 0000000..b431530 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_3_4.png b/scripts/FrameDumps/Input_display/analog_bg_part1_3_4.png new file mode 100644 index 0000000..561401c Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_4_2.png b/scripts/FrameDumps/Input_display/analog_bg_part1_4_2.png new file mode 100644 index 0000000..d715d13 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_4_3.png b/scripts/FrameDumps/Input_display/analog_bg_part1_4_3.png new file mode 100644 index 0000000..d57c043 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_4_4.png b/scripts/FrameDumps/Input_display/analog_bg_part1_4_4.png new file mode 100644 index 0000000..d57c043 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_5_2.png b/scripts/FrameDumps/Input_display/analog_bg_part1_5_2.png new file mode 100644 index 0000000..f490c99 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_5_3.png b/scripts/FrameDumps/Input_display/analog_bg_part1_5_3.png new file mode 100644 index 0000000..0b51e7a Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_5_4.png b/scripts/FrameDumps/Input_display/analog_bg_part1_5_4.png new file mode 100644 index 0000000..0b51e7a Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_6_2.png b/scripts/FrameDumps/Input_display/analog_bg_part1_6_2.png new file mode 100644 index 0000000..0e3c316 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_6_3.png b/scripts/FrameDumps/Input_display/analog_bg_part1_6_3.png new file mode 100644 index 0000000..0e3c316 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_6_4.png b/scripts/FrameDumps/Input_display/analog_bg_part1_6_4.png new file mode 100644 index 0000000..0e3c316 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_7_2.png b/scripts/FrameDumps/Input_display/analog_bg_part1_7_2.png new file mode 100644 index 0000000..f893e36 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part1_7_3.png b/scripts/FrameDumps/Input_display/analog_bg_part1_7_3.png new file mode 100644 index 0000000..f893e36 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part1_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_3_2.png b/scripts/FrameDumps/Input_display/analog_bg_part2_3_2.png new file mode 100644 index 0000000..3e1db1f Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_3_3.png b/scripts/FrameDumps/Input_display/analog_bg_part2_3_3.png new file mode 100644 index 0000000..266193b Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_3_4.png b/scripts/FrameDumps/Input_display/analog_bg_part2_3_4.png new file mode 100644 index 0000000..311eea2 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_4_2.png b/scripts/FrameDumps/Input_display/analog_bg_part2_4_2.png new file mode 100644 index 0000000..32587a0 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_4_3.png b/scripts/FrameDumps/Input_display/analog_bg_part2_4_3.png new file mode 100644 index 0000000..1658160 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_4_4.png b/scripts/FrameDumps/Input_display/analog_bg_part2_4_4.png new file mode 100644 index 0000000..c687dba Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_5_2.png b/scripts/FrameDumps/Input_display/analog_bg_part2_5_2.png new file mode 100644 index 0000000..b37f69d Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_5_3.png b/scripts/FrameDumps/Input_display/analog_bg_part2_5_3.png new file mode 100644 index 0000000..eb58f0b Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_5_4.png b/scripts/FrameDumps/Input_display/analog_bg_part2_5_4.png new file mode 100644 index 0000000..f9be293 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_6_2.png b/scripts/FrameDumps/Input_display/analog_bg_part2_6_2.png new file mode 100644 index 0000000..60cd530 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_6_3.png b/scripts/FrameDumps/Input_display/analog_bg_part2_6_3.png new file mode 100644 index 0000000..43763ec Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_6_4.png b/scripts/FrameDumps/Input_display/analog_bg_part2_6_4.png new file mode 100644 index 0000000..aef69a6 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_7_2.png b/scripts/FrameDumps/Input_display/analog_bg_part2_7_2.png new file mode 100644 index 0000000..4125f78 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part2_7_3.png b/scripts/FrameDumps/Input_display/analog_bg_part2_7_3.png new file mode 100644 index 0000000..be66ebb Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part2_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_3_2.png b/scripts/FrameDumps/Input_display/analog_bg_part3_3_2.png new file mode 100644 index 0000000..71476ea Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_3_3.png b/scripts/FrameDumps/Input_display/analog_bg_part3_3_3.png new file mode 100644 index 0000000..bcd0384 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_3_4.png b/scripts/FrameDumps/Input_display/analog_bg_part3_3_4.png new file mode 100644 index 0000000..6ec0a3f Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_4_2.png b/scripts/FrameDumps/Input_display/analog_bg_part3_4_2.png new file mode 100644 index 0000000..2aff7ff Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_4_3.png b/scripts/FrameDumps/Input_display/analog_bg_part3_4_3.png new file mode 100644 index 0000000..c54ebc6 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_4_4.png b/scripts/FrameDumps/Input_display/analog_bg_part3_4_4.png new file mode 100644 index 0000000..d890334 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_5_2.png b/scripts/FrameDumps/Input_display/analog_bg_part3_5_2.png new file mode 100644 index 0000000..d51dcf5 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_5_3.png b/scripts/FrameDumps/Input_display/analog_bg_part3_5_3.png new file mode 100644 index 0000000..a3a7a28 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_5_4.png b/scripts/FrameDumps/Input_display/analog_bg_part3_5_4.png new file mode 100644 index 0000000..1210d39 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_6_2.png b/scripts/FrameDumps/Input_display/analog_bg_part3_6_2.png new file mode 100644 index 0000000..ce0d50c Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_6_3.png b/scripts/FrameDumps/Input_display/analog_bg_part3_6_3.png new file mode 100644 index 0000000..fb2a3f2 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_6_4.png b/scripts/FrameDumps/Input_display/analog_bg_part3_6_4.png new file mode 100644 index 0000000..c9b7469 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_7_2.png b/scripts/FrameDumps/Input_display/analog_bg_part3_7_2.png new file mode 100644 index 0000000..225d364 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part3_7_3.png b/scripts/FrameDumps/Input_display/analog_bg_part3_7_3.png new file mode 100644 index 0000000..ddea846 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part3_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_3_2.png b/scripts/FrameDumps/Input_display/analog_bg_part4_3_2.png new file mode 100644 index 0000000..3bdb682 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_3_3.png b/scripts/FrameDumps/Input_display/analog_bg_part4_3_3.png new file mode 100644 index 0000000..5c83cf7 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_3_4.png b/scripts/FrameDumps/Input_display/analog_bg_part4_3_4.png new file mode 100644 index 0000000..3d26c90 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_4_2.png b/scripts/FrameDumps/Input_display/analog_bg_part4_4_2.png new file mode 100644 index 0000000..8975ded Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_4_3.png b/scripts/FrameDumps/Input_display/analog_bg_part4_4_3.png new file mode 100644 index 0000000..d3af557 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_4_4.png b/scripts/FrameDumps/Input_display/analog_bg_part4_4_4.png new file mode 100644 index 0000000..7570183 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_5_2.png b/scripts/FrameDumps/Input_display/analog_bg_part4_5_2.png new file mode 100644 index 0000000..30fd119 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_5_3.png b/scripts/FrameDumps/Input_display/analog_bg_part4_5_3.png new file mode 100644 index 0000000..96a5aaf Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_5_4.png b/scripts/FrameDumps/Input_display/analog_bg_part4_5_4.png new file mode 100644 index 0000000..684e415 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_6_2.png b/scripts/FrameDumps/Input_display/analog_bg_part4_6_2.png new file mode 100644 index 0000000..4dd1079 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_6_3.png b/scripts/FrameDumps/Input_display/analog_bg_part4_6_3.png new file mode 100644 index 0000000..09c4d36 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_6_4.png b/scripts/FrameDumps/Input_display/analog_bg_part4_6_4.png new file mode 100644 index 0000000..1c30b5b Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_7_2.png b/scripts/FrameDumps/Input_display/analog_bg_part4_7_2.png new file mode 100644 index 0000000..43f5e2c Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part4_7_3.png b/scripts/FrameDumps/Input_display/analog_bg_part4_7_3.png new file mode 100644 index 0000000..0288bfa Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part4_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_3_2.png b/scripts/FrameDumps/Input_display/analog_bg_part5_3_2.png new file mode 100644 index 0000000..0a6ebec Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_3_3.png b/scripts/FrameDumps/Input_display/analog_bg_part5_3_3.png new file mode 100644 index 0000000..2dff5f6 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_3_4.png b/scripts/FrameDumps/Input_display/analog_bg_part5_3_4.png new file mode 100644 index 0000000..90b0c28 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_4_2.png b/scripts/FrameDumps/Input_display/analog_bg_part5_4_2.png new file mode 100644 index 0000000..24fb90e Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_4_3.png b/scripts/FrameDumps/Input_display/analog_bg_part5_4_3.png new file mode 100644 index 0000000..8aec737 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_4_4.png b/scripts/FrameDumps/Input_display/analog_bg_part5_4_4.png new file mode 100644 index 0000000..8aec737 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_5_2.png b/scripts/FrameDumps/Input_display/analog_bg_part5_5_2.png new file mode 100644 index 0000000..9d79f11 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_5_3.png b/scripts/FrameDumps/Input_display/analog_bg_part5_5_3.png new file mode 100644 index 0000000..934b6ee Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_5_4.png b/scripts/FrameDumps/Input_display/analog_bg_part5_5_4.png new file mode 100644 index 0000000..934b6ee Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_6_2.png b/scripts/FrameDumps/Input_display/analog_bg_part5_6_2.png new file mode 100644 index 0000000..6428412 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_6_3.png b/scripts/FrameDumps/Input_display/analog_bg_part5_6_3.png new file mode 100644 index 0000000..6428412 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_6_4.png b/scripts/FrameDumps/Input_display/analog_bg_part5_6_4.png new file mode 100644 index 0000000..6428412 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_7_2.png b/scripts/FrameDumps/Input_display/analog_bg_part5_7_2.png new file mode 100644 index 0000000..553bd59 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_bg_part5_7_3.png b/scripts/FrameDumps/Input_display/analog_bg_part5_7_3.png new file mode 100644 index 0000000..553bd59 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_bg_part5_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_3_2.png b/scripts/FrameDumps/Input_display/analog_outer_3_2.png new file mode 100644 index 0000000..b927db1 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_3_3.png b/scripts/FrameDumps/Input_display/analog_outer_3_3.png new file mode 100644 index 0000000..fb74c6c Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_3_4.png b/scripts/FrameDumps/Input_display/analog_outer_3_4.png new file mode 100644 index 0000000..0046f76 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_4_2.png b/scripts/FrameDumps/Input_display/analog_outer_4_2.png new file mode 100644 index 0000000..460d7b9 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_4_3.png b/scripts/FrameDumps/Input_display/analog_outer_4_3.png new file mode 100644 index 0000000..f5ce423 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_4_4.png b/scripts/FrameDumps/Input_display/analog_outer_4_4.png new file mode 100644 index 0000000..0a14423 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_5_2.png b/scripts/FrameDumps/Input_display/analog_outer_5_2.png new file mode 100644 index 0000000..8a85b8e Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_5_3.png b/scripts/FrameDumps/Input_display/analog_outer_5_3.png new file mode 100644 index 0000000..cb7ad95 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_5_4.png b/scripts/FrameDumps/Input_display/analog_outer_5_4.png new file mode 100644 index 0000000..8ed6727 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_6_2.png b/scripts/FrameDumps/Input_display/analog_outer_6_2.png new file mode 100644 index 0000000..f996ae9 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_6_3.png b/scripts/FrameDumps/Input_display/analog_outer_6_3.png new file mode 100644 index 0000000..6d635f9 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_6_4.png b/scripts/FrameDumps/Input_display/analog_outer_6_4.png new file mode 100644 index 0000000..8787d78 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_7_2.png b/scripts/FrameDumps/Input_display/analog_outer_7_2.png new file mode 100644 index 0000000..29523e8 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_outer_7_3.png b/scripts/FrameDumps/Input_display/analog_outer_7_3.png new file mode 100644 index 0000000..04378cc Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_outer_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_3_2.png b/scripts/FrameDumps/Input_display/analog_part1_3_2.png new file mode 100644 index 0000000..a804cff Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_3_3.png b/scripts/FrameDumps/Input_display/analog_part1_3_3.png new file mode 100644 index 0000000..e06724d Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_3_4.png b/scripts/FrameDumps/Input_display/analog_part1_3_4.png new file mode 100644 index 0000000..077e986 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_4_2.png b/scripts/FrameDumps/Input_display/analog_part1_4_2.png new file mode 100644 index 0000000..e7e3006 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_4_3.png b/scripts/FrameDumps/Input_display/analog_part1_4_3.png new file mode 100644 index 0000000..6e54869 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_4_4.png b/scripts/FrameDumps/Input_display/analog_part1_4_4.png new file mode 100644 index 0000000..6e54869 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_5_2.png b/scripts/FrameDumps/Input_display/analog_part1_5_2.png new file mode 100644 index 0000000..831fc8b Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_5_3.png b/scripts/FrameDumps/Input_display/analog_part1_5_3.png new file mode 100644 index 0000000..d267ff1 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_5_4.png b/scripts/FrameDumps/Input_display/analog_part1_5_4.png new file mode 100644 index 0000000..d267ff1 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_6_2.png b/scripts/FrameDumps/Input_display/analog_part1_6_2.png new file mode 100644 index 0000000..3414326 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_6_3.png b/scripts/FrameDumps/Input_display/analog_part1_6_3.png new file mode 100644 index 0000000..3414326 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_6_4.png b/scripts/FrameDumps/Input_display/analog_part1_6_4.png new file mode 100644 index 0000000..3414326 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_7_2.png b/scripts/FrameDumps/Input_display/analog_part1_7_2.png new file mode 100644 index 0000000..387d5b5 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part1_7_3.png b/scripts/FrameDumps/Input_display/analog_part1_7_3.png new file mode 100644 index 0000000..387d5b5 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part1_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_3_2.png b/scripts/FrameDumps/Input_display/analog_part2_3_2.png new file mode 100644 index 0000000..96b7cc8 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_3_3.png b/scripts/FrameDumps/Input_display/analog_part2_3_3.png new file mode 100644 index 0000000..2cc1590 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_3_4.png b/scripts/FrameDumps/Input_display/analog_part2_3_4.png new file mode 100644 index 0000000..a82bb08 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_4_2.png b/scripts/FrameDumps/Input_display/analog_part2_4_2.png new file mode 100644 index 0000000..e7ba201 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_4_3.png b/scripts/FrameDumps/Input_display/analog_part2_4_3.png new file mode 100644 index 0000000..ce1f2ef Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_4_4.png b/scripts/FrameDumps/Input_display/analog_part2_4_4.png new file mode 100644 index 0000000..ba5c3b2 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_5_2.png b/scripts/FrameDumps/Input_display/analog_part2_5_2.png new file mode 100644 index 0000000..1f7f92f Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_5_3.png b/scripts/FrameDumps/Input_display/analog_part2_5_3.png new file mode 100644 index 0000000..e7b9915 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_5_4.png b/scripts/FrameDumps/Input_display/analog_part2_5_4.png new file mode 100644 index 0000000..09a53a5 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_6_2.png b/scripts/FrameDumps/Input_display/analog_part2_6_2.png new file mode 100644 index 0000000..f687f80 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_6_3.png b/scripts/FrameDumps/Input_display/analog_part2_6_3.png new file mode 100644 index 0000000..6766005 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_6_4.png b/scripts/FrameDumps/Input_display/analog_part2_6_4.png new file mode 100644 index 0000000..422effd Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_7_2.png b/scripts/FrameDumps/Input_display/analog_part2_7_2.png new file mode 100644 index 0000000..bf661c9 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part2_7_3.png b/scripts/FrameDumps/Input_display/analog_part2_7_3.png new file mode 100644 index 0000000..808ab02 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part2_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_3_2.png b/scripts/FrameDumps/Input_display/analog_part3_3_2.png new file mode 100644 index 0000000..f909cec Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_3_3.png b/scripts/FrameDumps/Input_display/analog_part3_3_3.png new file mode 100644 index 0000000..c33101c Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_3_4.png b/scripts/FrameDumps/Input_display/analog_part3_3_4.png new file mode 100644 index 0000000..20eba53 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_4_2.png b/scripts/FrameDumps/Input_display/analog_part3_4_2.png new file mode 100644 index 0000000..d9cabb3 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_4_3.png b/scripts/FrameDumps/Input_display/analog_part3_4_3.png new file mode 100644 index 0000000..fd2bb0f Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_4_4.png b/scripts/FrameDumps/Input_display/analog_part3_4_4.png new file mode 100644 index 0000000..ce8308a Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_5_2.png b/scripts/FrameDumps/Input_display/analog_part3_5_2.png new file mode 100644 index 0000000..0ca882c Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_5_3.png b/scripts/FrameDumps/Input_display/analog_part3_5_3.png new file mode 100644 index 0000000..7b078a7 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_5_4.png b/scripts/FrameDumps/Input_display/analog_part3_5_4.png new file mode 100644 index 0000000..8fb4225 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_6_2.png b/scripts/FrameDumps/Input_display/analog_part3_6_2.png new file mode 100644 index 0000000..870d12b Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_6_3.png b/scripts/FrameDumps/Input_display/analog_part3_6_3.png new file mode 100644 index 0000000..a6d8db1 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_6_4.png b/scripts/FrameDumps/Input_display/analog_part3_6_4.png new file mode 100644 index 0000000..98a5f04 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_7_2.png b/scripts/FrameDumps/Input_display/analog_part3_7_2.png new file mode 100644 index 0000000..7eab2ad Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part3_7_3.png b/scripts/FrameDumps/Input_display/analog_part3_7_3.png new file mode 100644 index 0000000..06802c4 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part3_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_3_2.png b/scripts/FrameDumps/Input_display/analog_part4_3_2.png new file mode 100644 index 0000000..9122ac0 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_3_3.png b/scripts/FrameDumps/Input_display/analog_part4_3_3.png new file mode 100644 index 0000000..9cec05c Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_3_4.png b/scripts/FrameDumps/Input_display/analog_part4_3_4.png new file mode 100644 index 0000000..377a391 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_4_2.png b/scripts/FrameDumps/Input_display/analog_part4_4_2.png new file mode 100644 index 0000000..fd8020f Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_4_3.png b/scripts/FrameDumps/Input_display/analog_part4_4_3.png new file mode 100644 index 0000000..f7243c7 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_4_4.png b/scripts/FrameDumps/Input_display/analog_part4_4_4.png new file mode 100644 index 0000000..fa6fe31 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_5_2.png b/scripts/FrameDumps/Input_display/analog_part4_5_2.png new file mode 100644 index 0000000..c5880f1 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_5_3.png b/scripts/FrameDumps/Input_display/analog_part4_5_3.png new file mode 100644 index 0000000..67f89cb Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_5_4.png b/scripts/FrameDumps/Input_display/analog_part4_5_4.png new file mode 100644 index 0000000..161df32 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_6_2.png b/scripts/FrameDumps/Input_display/analog_part4_6_2.png new file mode 100644 index 0000000..53a5ab9 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_6_3.png b/scripts/FrameDumps/Input_display/analog_part4_6_3.png new file mode 100644 index 0000000..01b193d Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_6_4.png b/scripts/FrameDumps/Input_display/analog_part4_6_4.png new file mode 100644 index 0000000..8da0547 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_7_2.png b/scripts/FrameDumps/Input_display/analog_part4_7_2.png new file mode 100644 index 0000000..99a51c5 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part4_7_3.png b/scripts/FrameDumps/Input_display/analog_part4_7_3.png new file mode 100644 index 0000000..fc7c20d Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part4_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_3_2.png b/scripts/FrameDumps/Input_display/analog_part5_3_2.png new file mode 100644 index 0000000..ddb9efe Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_3_3.png b/scripts/FrameDumps/Input_display/analog_part5_3_3.png new file mode 100644 index 0000000..0114be5 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_3_4.png b/scripts/FrameDumps/Input_display/analog_part5_3_4.png new file mode 100644 index 0000000..303cc03 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_4_2.png b/scripts/FrameDumps/Input_display/analog_part5_4_2.png new file mode 100644 index 0000000..2695f7c Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_4_3.png b/scripts/FrameDumps/Input_display/analog_part5_4_3.png new file mode 100644 index 0000000..43a4de4 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_4_4.png b/scripts/FrameDumps/Input_display/analog_part5_4_4.png new file mode 100644 index 0000000..43a4de4 Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_5_2.png b/scripts/FrameDumps/Input_display/analog_part5_5_2.png new file mode 100644 index 0000000..35a647c Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_5_3.png b/scripts/FrameDumps/Input_display/analog_part5_5_3.png new file mode 100644 index 0000000..a1f678c Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_5_4.png b/scripts/FrameDumps/Input_display/analog_part5_5_4.png new file mode 100644 index 0000000..a1f678c Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_6_2.png b/scripts/FrameDumps/Input_display/analog_part5_6_2.png new file mode 100644 index 0000000..8621cfc Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_6_3.png b/scripts/FrameDumps/Input_display/analog_part5_6_3.png new file mode 100644 index 0000000..8621cfc Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_6_4.png b/scripts/FrameDumps/Input_display/analog_part5_6_4.png new file mode 100644 index 0000000..8621cfc Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_7_2.png b/scripts/FrameDumps/Input_display/analog_part5_7_2.png new file mode 100644 index 0000000..e7bd69e Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/analog_part5_7_3.png b/scripts/FrameDumps/Input_display/analog_part5_7_3.png new file mode 100644 index 0000000..e7bd69e Binary files /dev/null and b/scripts/FrameDumps/Input_display/analog_part5_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/background.png b/scripts/FrameDumps/Input_display/background.png new file mode 100644 index 0000000..66669b5 Binary files /dev/null and b/scripts/FrameDumps/Input_display/background.png differ diff --git a/scripts/FrameDumps/Input_display/button_3_2.png b/scripts/FrameDumps/Input_display/button_3_2.png new file mode 100644 index 0000000..e618781 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/button_3_3.png b/scripts/FrameDumps/Input_display/button_3_3.png new file mode 100644 index 0000000..f59ffa4 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/button_3_4.png b/scripts/FrameDumps/Input_display/button_3_4.png new file mode 100644 index 0000000..8ae52c8 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/button_4_2.png b/scripts/FrameDumps/Input_display/button_4_2.png new file mode 100644 index 0000000..9f649e1 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/button_4_3.png b/scripts/FrameDumps/Input_display/button_4_3.png new file mode 100644 index 0000000..3347cc2 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/button_4_4.png b/scripts/FrameDumps/Input_display/button_4_4.png new file mode 100644 index 0000000..69dd670 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/button_5_2.png b/scripts/FrameDumps/Input_display/button_5_2.png new file mode 100644 index 0000000..6a9b52c Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/button_5_3.png b/scripts/FrameDumps/Input_display/button_5_3.png new file mode 100644 index 0000000..ac2f974 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/button_5_4.png b/scripts/FrameDumps/Input_display/button_5_4.png new file mode 100644 index 0000000..69c70a1 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/button_6_2.png b/scripts/FrameDumps/Input_display/button_6_2.png new file mode 100644 index 0000000..20d2da1 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/button_6_3.png b/scripts/FrameDumps/Input_display/button_6_3.png new file mode 100644 index 0000000..c4144a1 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/button_6_4.png b/scripts/FrameDumps/Input_display/button_6_4.png new file mode 100644 index 0000000..60937b1 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/button_7_2.png b/scripts/FrameDumps/Input_display/button_7_2.png new file mode 100644 index 0000000..a6d7358 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/button_7_3.png b/scripts/FrameDumps/Input_display/button_7_3.png new file mode 100644 index 0000000..1387d34 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_3_2.png b/scripts/FrameDumps/Input_display/button_filled_3_2.png new file mode 100644 index 0000000..9941476 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_3_3.png b/scripts/FrameDumps/Input_display/button_filled_3_3.png new file mode 100644 index 0000000..3b937cb Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_3_4.png b/scripts/FrameDumps/Input_display/button_filled_3_4.png new file mode 100644 index 0000000..6936f39 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_4_2.png b/scripts/FrameDumps/Input_display/button_filled_4_2.png new file mode 100644 index 0000000..09b7837 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_4_3.png b/scripts/FrameDumps/Input_display/button_filled_4_3.png new file mode 100644 index 0000000..d8c3e68 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_4_4.png b/scripts/FrameDumps/Input_display/button_filled_4_4.png new file mode 100644 index 0000000..235d61c Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_5_2.png b/scripts/FrameDumps/Input_display/button_filled_5_2.png new file mode 100644 index 0000000..8fa72cd Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_5_3.png b/scripts/FrameDumps/Input_display/button_filled_5_3.png new file mode 100644 index 0000000..cc95b3e Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_5_4.png b/scripts/FrameDumps/Input_display/button_filled_5_4.png new file mode 100644 index 0000000..f482d38 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_6_2.png b/scripts/FrameDumps/Input_display/button_filled_6_2.png new file mode 100644 index 0000000..2fc0bb0 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_6_3.png b/scripts/FrameDumps/Input_display/button_filled_6_3.png new file mode 100644 index 0000000..b28c03b Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_6_4.png b/scripts/FrameDumps/Input_display/button_filled_6_4.png new file mode 100644 index 0000000..eb03ad0 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_7_2.png b/scripts/FrameDumps/Input_display/button_filled_7_2.png new file mode 100644 index 0000000..8d9faba Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/button_filled_7_3.png b/scripts/FrameDumps/Input_display/button_filled_7_3.png new file mode 100644 index 0000000..1294996 Binary files /dev/null and b/scripts/FrameDumps/Input_display/button_filled_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_3_2.png b/scripts/FrameDumps/Input_display/dpad_3_2.png new file mode 100644 index 0000000..104a5a5 Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_3_3.png b/scripts/FrameDumps/Input_display/dpad_3_3.png new file mode 100644 index 0000000..c40c967 Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_3_4.png b/scripts/FrameDumps/Input_display/dpad_3_4.png new file mode 100644 index 0000000..95e2d5a Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_4_2.png b/scripts/FrameDumps/Input_display/dpad_4_2.png new file mode 100644 index 0000000..72a0569 Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_4_3.png b/scripts/FrameDumps/Input_display/dpad_4_3.png new file mode 100644 index 0000000..c6272bb Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_4_4.png b/scripts/FrameDumps/Input_display/dpad_4_4.png new file mode 100644 index 0000000..3e7c640 Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_5_2.png b/scripts/FrameDumps/Input_display/dpad_5_2.png new file mode 100644 index 0000000..a5a62e9 Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_5_3.png b/scripts/FrameDumps/Input_display/dpad_5_3.png new file mode 100644 index 0000000..fe5d31e Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_5_4.png b/scripts/FrameDumps/Input_display/dpad_5_4.png new file mode 100644 index 0000000..3c4c629 Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_6_2.png b/scripts/FrameDumps/Input_display/dpad_6_2.png new file mode 100644 index 0000000..830d2fc Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_6_3.png b/scripts/FrameDumps/Input_display/dpad_6_3.png new file mode 100644 index 0000000..ad71820 Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_6_4.png b/scripts/FrameDumps/Input_display/dpad_6_4.png new file mode 100644 index 0000000..bb2981f Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_7_2.png b/scripts/FrameDumps/Input_display/dpad_7_2.png new file mode 100644 index 0000000..9f58a5d Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_7_3.png b/scripts/FrameDumps/Input_display/dpad_7_3.png new file mode 100644 index 0000000..78260f7 Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_fill_0.png b/scripts/FrameDumps/Input_display/dpad_fill_0.png new file mode 100644 index 0000000..aa774af Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_fill_0.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_fill_1.png b/scripts/FrameDumps/Input_display/dpad_fill_1.png new file mode 100644 index 0000000..7e109b7 Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_fill_1.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_fill_2.png b/scripts/FrameDumps/Input_display/dpad_fill_2.png new file mode 100644 index 0000000..2b7738f Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_fill_2.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_fill_3.png b/scripts/FrameDumps/Input_display/dpad_fill_3.png new file mode 100644 index 0000000..8efae37 Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_fill_3.png differ diff --git a/scripts/FrameDumps/Input_display/dpad_fill_4.png b/scripts/FrameDumps/Input_display/dpad_fill_4.png new file mode 100644 index 0000000..eb089d0 Binary files /dev/null and b/scripts/FrameDumps/Input_display/dpad_fill_4.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_3_2.png b/scripts/FrameDumps/Input_display/shoulder_3_2.png new file mode 100644 index 0000000..32b20bc Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_3_3.png b/scripts/FrameDumps/Input_display/shoulder_3_3.png new file mode 100644 index 0000000..71616ed Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_3_4.png b/scripts/FrameDumps/Input_display/shoulder_3_4.png new file mode 100644 index 0000000..4a5950d Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_4_2.png b/scripts/FrameDumps/Input_display/shoulder_4_2.png new file mode 100644 index 0000000..d0a2061 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_4_3.png b/scripts/FrameDumps/Input_display/shoulder_4_3.png new file mode 100644 index 0000000..2095ba7 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_4_4.png b/scripts/FrameDumps/Input_display/shoulder_4_4.png new file mode 100644 index 0000000..6b2a5a9 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_5_2.png b/scripts/FrameDumps/Input_display/shoulder_5_2.png new file mode 100644 index 0000000..9bfcf7c Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_5_3.png b/scripts/FrameDumps/Input_display/shoulder_5_3.png new file mode 100644 index 0000000..74fbc7f Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_5_4.png b/scripts/FrameDumps/Input_display/shoulder_5_4.png new file mode 100644 index 0000000..4ffb27d Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_6_2.png b/scripts/FrameDumps/Input_display/shoulder_6_2.png new file mode 100644 index 0000000..8ced5b6 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_6_3.png b/scripts/FrameDumps/Input_display/shoulder_6_3.png new file mode 100644 index 0000000..7971f20 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_6_4.png b/scripts/FrameDumps/Input_display/shoulder_6_4.png new file mode 100644 index 0000000..ab8e1e6 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_7_2.png b/scripts/FrameDumps/Input_display/shoulder_7_2.png new file mode 100644 index 0000000..cc6ea9e Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_7_3.png b/scripts/FrameDumps/Input_display/shoulder_7_3.png new file mode 100644 index 0000000..5d0debb Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_3_2.png b/scripts/FrameDumps/Input_display/shoulder_filled_3_2.png new file mode 100644 index 0000000..cc177f1 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_3_2.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_3_3.png b/scripts/FrameDumps/Input_display/shoulder_filled_3_3.png new file mode 100644 index 0000000..9042ce6 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_3_3.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_3_4.png b/scripts/FrameDumps/Input_display/shoulder_filled_3_4.png new file mode 100644 index 0000000..a0cf6cb Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_3_4.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_4_2.png b/scripts/FrameDumps/Input_display/shoulder_filled_4_2.png new file mode 100644 index 0000000..53df565 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_4_2.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_4_3.png b/scripts/FrameDumps/Input_display/shoulder_filled_4_3.png new file mode 100644 index 0000000..1a3febf Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_4_3.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_4_4.png b/scripts/FrameDumps/Input_display/shoulder_filled_4_4.png new file mode 100644 index 0000000..f748f94 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_4_4.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_5_2.png b/scripts/FrameDumps/Input_display/shoulder_filled_5_2.png new file mode 100644 index 0000000..3049793 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_5_2.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_5_3.png b/scripts/FrameDumps/Input_display/shoulder_filled_5_3.png new file mode 100644 index 0000000..5a7a457 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_5_3.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_5_4.png b/scripts/FrameDumps/Input_display/shoulder_filled_5_4.png new file mode 100644 index 0000000..d442ad7 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_5_4.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_6_2.png b/scripts/FrameDumps/Input_display/shoulder_filled_6_2.png new file mode 100644 index 0000000..d7973f4 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_6_2.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_6_3.png b/scripts/FrameDumps/Input_display/shoulder_filled_6_3.png new file mode 100644 index 0000000..5007770 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_6_3.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_6_4.png b/scripts/FrameDumps/Input_display/shoulder_filled_6_4.png new file mode 100644 index 0000000..107d78d Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_6_4.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_7_2.png b/scripts/FrameDumps/Input_display/shoulder_filled_7_2.png new file mode 100644 index 0000000..f21feb5 Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_7_2.png differ diff --git a/scripts/FrameDumps/Input_display/shoulder_filled_7_3.png b/scripts/FrameDumps/Input_display/shoulder_filled_7_3.png new file mode 100644 index 0000000..bd8353a Binary files /dev/null and b/scripts/FrameDumps/Input_display/shoulder_filled_7_3.png differ diff --git a/scripts/FrameDumps/Input_display/tutorial/composition_normal.png b/scripts/FrameDumps/Input_display/tutorial/composition_normal.png new file mode 100644 index 0000000..5338900 Binary files /dev/null and b/scripts/FrameDumps/Input_display/tutorial/composition_normal.png differ diff --git a/scripts/FrameDumps/Input_display/tutorial/composition_shoulder_aligned.png b/scripts/FrameDumps/Input_display/tutorial/composition_shoulder_aligned.png new file mode 100644 index 0000000..0ab88b5 Binary files /dev/null and b/scripts/FrameDumps/Input_display/tutorial/composition_shoulder_aligned.png differ diff --git a/scripts/FrameDumps/delete_older_dumps.py b/scripts/FrameDumps/delete_older_dumps.py new file mode 100644 index 0000000..740a57a --- /dev/null +++ b/scripts/FrameDumps/delete_older_dumps.py @@ -0,0 +1,30 @@ +import sys +import os + +''' +This script simply delete all audio (.wav only) and +video (.avi only) your Dump/Frames and Dump/Audio folder +Don't run this script before encoding ! +''' + + +print('Are you sure you want to delete all your dumps ?') +answer = input('yes/no\n') +if answer in ['yes', 'y', 'ye', 'YES', 'Y', 'YE']: + with open('dump_info.txt', 'r') as f: + dump_folder = f.readline().strip('\n') + frame_dump_path = os.path.join(dump_folder, 'Frames') + audio_dump_path = os.path.join(dump_folder, 'Audio') + for filename in os.listdir(frame_dump_path): + if filename[-4:] == '.avi': + print(f'Now deleting {filename}') + os.remove(os.path.join(frame_dump_path, filename)) + + for filename in os.listdir(audio_dump_path): + if filename[-4:] == '.wav': + print(f'Now deleting {filename}') + os.remove(os.path.join(audio_dump_path, filename)) + + print('All dumps have been deleted.') + +input("Press Enter to exit") diff --git a/scripts/FrameDumps/encoder.py b/scripts/FrameDumps/encoder.py new file mode 100644 index 0000000..93c2398 --- /dev/null +++ b/scripts/FrameDumps/encoder.py @@ -0,0 +1,370 @@ +import moviepy +import numpy as np +from PIL import Image +from PIL import ImageDraw +from PIL import ImageFont +from PIL import ImageFilter +import sys +import os +import time +import subprocess +import math + +from Extra import config_util +from Extra import input_display_util +from Extra import infodisplay_util +from Extra import speed_display_util +from Extra import author_display_util +from Extra import extradisplay_util +from Extra import common + +time.sleep(1) #waits a bit to make sure the framedump metadata is written + +current_folder = os.path.dirname(sys.argv[0]) +extra_display_folder = os.path.dirname(sys.argv[0]) + + +#Initializing MKW Font images +mkw_font_folder = os.path.join(current_folder, 'Fonts', 'MKW_Font') +MKW_FONT_IMG = {} +for filename in os.listdir(mkw_font_folder): + if filename[-4:] == '.png': + letter = filename[:-4] + if letter == 'SLASH_': + letter = '/' + if letter == 'SLASH': + letter = '&' + elif letter == 'COLON': + letter = ':' + elif letter == 'PERIOD': + letter = '.' + MKW_FONT_IMG[letter] = Image.open(os.path.join(mkw_font_folder, filename)) + + +def make_dict(filetext): + d = {} + for line in filetext.split('\n'): + temp = line.split(':') + if len(temp) == 2: + d[temp[0]] = temp[1] + return d + + +def transform_image(image, i=-1): + #Resample algorithm + resample_filter = config['Encoding options'].get('scaling_option') + resampler = common.get_resampler(resample_filter) + resize_style = config['Encoding options'].get('resize_style') + target_height = config['Encoding options'].getint('resize_resolution') + target_width = round(target_height*16/9) + target_resolution = (target_width, target_height) + + #Convert from numpy array to PIL Image + image = Image.fromarray(image) + + #Resize the image + if resize_style in ['stretch', 'Stretch', 'STRETCH']: + image = image.resize(target_resolution, resampler) + if resize_style in ['crop', 'Crop', 'CROP']: + dump_width, dump_height = image.size + if dump_width*9 >= dump_height*16: + ratio = target_height/dump_height + image = image.resize((round(dump_width*ratio), round(dump_height*ratio)), resampler) + image = image.crop(((image.width-target_width)//2, 0, target_width+(image.width-target_width)//2, target_height)) + else: + ratio = target_width/dump_width + image = image.resize((round(dump_width*ratio), round(dump_height*ratio)), resampler) + image = image.crop(((0, (image.height-target_height)//2, target_width, target_height+image.height-target_height)//2)) + if resize_style in ['fill', 'Fill', 'FILL']: + dump_width, dump_height = image.size + if dump_width*9 >= dump_height*16: + ratio = target_width/dump_width + image = image.resize((round(dump_width*ratio), round(dump_height*ratio)), resampler) + black_background = Image.new('RGB', target_resolution, (0,0,0)) + black_background.paste(image, (0, (target_height-image.height)//2)) + image = black_background + else: + ratio = target_height/dump_height + image = image.resize((round(dump_width*ratio), round(dump_height*ratio)), resampler) + black_background = Image.new('RGB', target_resolution, (0,0,0)) + black_background.paste(image, ((target_width-image.width)//2), 0) + image = black_background + + + image = image.convert("RGBA") + font_folder = os.path.join(current_folder, 'Fonts') + + if os.path.isfile(os.path.join(extra_display_folder, 'RAM_data', f'{i}.txt')): + with open(os.path.join(extra_display_folder, 'RAM_data', f'{i}.txt'), 'r') as f: + t = f.read() + if len(t)>1: + frame_dict = make_dict(t) + + if config['Input display'].getboolean('show_input_display') and common.is_layer_active(frame_dict, config['Input display']) : + input_display_util.add_input_display(image, frame_dict, config, font_folder, recolored_images, w, ow) + if config['Speed display'].getboolean('show_speed_display') and common.is_layer_active(frame_dict, config['Speed display']) : + speed_display_util.add_speed_display(image, frame_dict, config) + if config['Author display'].getboolean('show_author_display') and common.is_layer_active(frame_dict, config['Author display']) : + author_display_util.add_author_display(image, frame_dict, config, current_folder, raw_author_list, author_dict) + for infodisplay_name in infodisplay_layers: + if common.is_layer_active(frame_dict, config[infodisplay_name]): + infodisplay_util.add_infodisplay(image, frame_dict, config[infodisplay_name], font_folder, scaled_fonts_dict) + + for section in extra_display_layers: + if config[section].getboolean('show_extra_display'): + file_ext = config[section].get("file_extension") + filename = f'{i}.{file_ext}' + if os.path.isfile(os.path.join(extra_display_folder, 'RAM_data', filename)): + with open(os.path.join(extra_display_folder, 'RAM_data', filename), 'r') as f: + text = f.read() + extradisplay_util.add_extradisplay(image, text, config[section], font_folder, MKW_FONT_IMG) + + filters = common.get_filter_list(config['Encoding options'].get('special_effects')) + for filtre in filters: + image = image.filter(filtre) + return np.array(image.convert("RGB")) + +def transform_video(get_frame, t): + image = get_frame(t) + i = round(t*59.94) -1 + print('') + return transform_image(image, i) + +def main(): + extra_display_folder = os.path.dirname(sys.argv[0]) + current_folder = os.path.dirname(sys.argv[0]) + + if len(sys.argv) > 1: + config_filename = sys.argv[1] + else: + config_filename = os.path.join(extra_display_folder, 'config.ini') + if not os.path.isfile(config_filename): + config_util.create_config(config_filename) + + + global config + config = config_util.get_config(config_filename) + + + global w + global ow + w = config['Input display'].getint('width') + ow = config['Input display'].getint('outline_width') + bg = config['Input display'].getboolean('draw_box') + #Initializing input display images + input_display_folder = os.path.join(current_folder, 'Input_display') + INPUT_DISPLAY_IMG = {} + + for filename in os.listdir(input_display_folder): + is_w_ow = (filename[-7:] == f'{w}_{ow}.png') + aligns_with_bg = ('bg' in filename) == bg + if is_w_ow and (not 'part' in filename) or is_w_ow and aligns_with_bg or filename == 'background.png' or filename[:9] == 'dpad_fill': + INPUT_DISPLAY_IMG[filename[:-4]] = Image.open(os.path.join(current_folder, 'Input_display', filename)) + + + color_dict = {'color_shoulder_left': common.get_color(config['Input display'].get('color_shoulder_left')), + 'color_shoulder_right': common.get_color(config['Input display'].get('color_shoulder_right')), + 'color_dpad': common.get_color(config['Input display'].get('color_dpad')), + 'color_analog': common.get_color(config['Input display'].get('color_analog')), + 'color_a_button': common.get_color(config['Input display'].get('color_a_button')), + 'color_stick_text': common.get_color(config['Input display'].get('color_stick_text')),} + + base_keys = { + 'shoulder': ['color_shoulder_left', 'color_shoulder_right'], 'shoulder_filled': ['color_shoulder_left', + 'color_shoulder_right'], 'dpad': ['color_dpad'], 'analog_outer': ['color_analog'], + 'analog_base': ['color_analog'], 'button': ['color_a_button'], 'button_filled': ['color_a_button']} + + analog_keys = {'analog_part1': ['color_analog'], 'analog_part2': ['color_analog'], + 'analog_part3': ['color_analog'], 'analog_part4': ['color_analog'], 'analog_part5': ['color_analog']} + + analog_bg_keys = {'analog_bg_part1': ['color_analog'], 'analog_bg_part2': ['color_analog'], + 'analog_bg_part3': ['color_analog'], 'analog_bg_part4': ['color_analog'], 'analog_bg_part5': ['color_analog']} + + if bg: + base_keys.update(analog_bg_keys) + else: + base_keys.update(analog_keys) + + global recolored_images + recolored_images = {} + + for base_key, colors in base_keys.items(): + img_key = f"{base_key}_{w}_{ow}" + base_img = INPUT_DISPLAY_IMG[img_key] + for color_name in colors: + color = color_dict[color_name] + recolored_key = f"{img_key}|{color_name}" + recolored_images[recolored_key] = common.color_white_part(base_img.copy(), color) + + recolored_images['background'] = INPUT_DISPLAY_IMG['background'] + recolored_images['dpad_fill_0'] = INPUT_DISPLAY_IMG['dpad_fill_0'] + for x in range(1,5): + recolored_images[f'dpad_fill_{x}'] = common.color_white_part(INPUT_DISPLAY_IMG[f'dpad_fill_{x}'].copy(), color_dict['color_dpad']) + + + global raw_author_list, author_dict + raw_author_list, author_dict = author_display_util.make_list_author(config['Author display'], extra_display_folder) + + #Parse config to see all needed infodisplay layers + global infodisplay_layers + infodisplay_layers = [] + for section in config.sections(): + if len(section) >= len('Infodisplay') and section[:len('Infodisplay')] == 'Infodisplay': + if config[section].getboolean("show_infodisplay"): + infodisplay_layers.append(section) + + global extra_display_layers + extra_display_layers = [] + for section in config.sections(): + if len(section) >= len('Extra display') and section[:len('Extra display')] == 'Extra display': + if config[section].getboolean("show_extra_display"): + extra_display_layers.append(section) + + i = 1 + scaling_set = [] + scaling_set.append(0.2375) # 2.85 / 12, this is for the pretty speedometer. + while f'custom_text_{i}' in config['Infodisplay']: + scaling_set.append(eval(config['Infodisplay'].get(f'custom_text_scaling_{i}'))/12) + i += 1 + for id_name in infodisplay_layers: + scaling_set.append(eval(config[id_name].get('mkw_font_scaling'))/12) + + global scaled_fonts_dict + scaled_fonts_dict = {} + for scale in scaling_set: + tmp_dict = {} + for key in MKW_FONT_IMG.keys(): + size = MKW_FONT_IMG[key].size + w2,h2 = (round(size[0]*scale), round(size[1]*scale)) + tmp_dict[key] = MKW_FONT_IMG[key].resize((w2,h2) , Image.Resampling.LANCZOS) + scaled_fonts_dict[scale] = tmp_dict + + #Getting filenames + with open(os.path.join(extra_display_folder, 'dump_info.txt'), 'r') as f: + ignore_list = f.read().split('\n') + dump_folder = ignore_list[0] + framedump_folder = os.path.join(dump_folder, 'Frames') + audiodump_folder = os.path.join(dump_folder, 'Audio') + + framedump_filenames = [] + for filename in os.listdir(framedump_folder): + filepath = os.path.join(framedump_folder, filename) + if not filepath in ignore_list: + framedump_filenames.append(filepath) + + + framedump_clip = [moviepy.VideoFileClip(filename) for filename in framedump_filenames] + #Concatening directly the clip from framedump_clip to source was sometimes duplicating frames... + #This is why I do this workaround, which expects very few frames from all clip except the last one. + list_frame_beginning = [] + for i in range(len(framedump_clip) -1): + for cur_frame in framedump_clip[i].iter_frames(): + list_frame_beginning.append(cur_frame) + if len(framedump_clip) > 1 : + beg_clip = moviepy.ImageSequenceClip(list_frame_beginning, fps = 59.94) + source = moviepy.concatenate_videoclips([beg_clip, framedump_clip[-1]]) + else: + source = framedump_clip[-1] + + #Add audio dump + audio_dumper = config['Audio options'].get('audiodump_target') + audio_source = [] + audiodump_filename = None + + if audio_dumper in ['dsp', 'DSP', 'Dsp']: + for filename in os.listdir(audiodump_folder): + filepath = os.path.join(audiodump_folder, filename) + if not filepath in ignore_list and filepath[-len('_dspdump.wav'):] == '_dspdump.wav': + audiodump_filename = filepath + + elif audio_dumper in ['dtk', 'DTK', 'Dtk']: + for filename in os.listdir(audiodump_folder): + filepath = os.path.join(audiodump_folder, filename) + if not filepath in ignore_list and filepath[-len('_dtkdump.wav'):] == '_dtkdump.wav': + audiodump_filename = filepath + + if audiodump_filename: + audio_source.append(moviepy.AudioFileClip(audiodump_filename)) + volume = config['Audio options'].getfloat('audiodump_volume') + audio_source[-1] = audio_source[-1].with_effects([moviepy.afx.MultiplyVolume(volume)]) + + #Add bgm + bgm_filename = config['Audio options'].get('bgm_filename') + if os.path.isfile(os.path.join(extra_display_folder, bgm_filename)): + bgm = moviepy.AudioFileClip(bgm_filename) + offset = config['Audio options'].getint('bgm_offset') + if offset <= 0: + audio_source.append(bgm[-offset:-offset+source.duration]) + else: + audio_source.append((moviepy.audio.AudioClip.AudioClip(lambda t: [0], offset, bgm) + bgm)[:source.duration]) + volume = config['Audio options'].getfloat('bgm_volume') + + audio_source[-1] = audio_source[-1].with_effects([moviepy.afx.MultiplyVolume(volume)]) + if audio_source: + fade_in = config['Audio options'].getfloat('fade_in') + fade_out = config['Audio options'].getfloat('fade_out') + source.audio = moviepy.CompositeAudioClip(audio_source).with_effects([moviepy.afx.AudioFadeOut(fade_out), + moviepy.afx.AudioFadeIn(fade_in)]) + + #Fade effect + fade_in = config['Encoding options'].getfloat('video_fade_in') + fade_out = config['Encoding options'].getfloat('video_fade_out') + + #Encode + t = time.time() + encode_style = config['Encoding options'].get('encode_style') + output_filename = os.path.join(extra_display_folder, config['Encoding options'].get('output_filename')) + no_vid = False + if encode_style in ['Normal', 'normal', 'NORMAL', 'n', 'N', 'norm',]: + output_video = source.transform(transform_video, apply_to="mask").with_effects([moviepy.vfx.FadeOut(fade_out), + moviepy.vfx.FadeIn(fade_in)]) + output_video.write_videofile(output_filename, + codec = 'libx264', + preset = config['Encoding options'].get('preset'), + threads = config['Encoding options'].getint('threads'), + ffmpeg_params = ['-crf', config['Encoding options'].get('crf_value')]) + elif encode_style in ['Discord', 'DISCORD', 'discord', 'd', 'D', 'disc']: + output_video = source.transform(transform_video, apply_to="mask").with_effects([moviepy.vfx.FadeOut(fade_out), + moviepy.vfx.FadeIn(fade_in)]) + video_length = output_video.duration + target_bitrate = str(int(0.90*8000000*config['Encoding options'].getfloat('output_file_size')/video_length)) + output_video.write_videofile(output_filename, + codec = 'libx264', + preset = config['Encoding options'].get('preset'), + threads = config['Encoding options'].getint('threads'), + bitrate = target_bitrate, + audio_bitrate = str(int(target_bitrate)//100), + ffmpeg_params = ['-movflags', 'faststart'] + ) + elif encode_style in ['Youtube', 'youtube', 'YT', 'Yt', 'yt', 'YOUTUBE', 'y', 'Y']: #https://gist.github.com/wuziq/b86f8551902fa1d477980a8125970877 + output_video = source.transform(transform_video, apply_to="mask").with_effects([moviepy.vfx.FadeOut(fade_out), + moviepy.vfx.FadeIn(fade_in)]) + output_video.write_videofile(output_filename, + codec = 'libx264', + preset = config['Encoding options'].get('preset'), + threads = config['Encoding options'].getint('threads'), + ffmpeg_params = ['-movflags', 'faststart', + '-profile:v' ,'high', + '-bf', '2', + '-g', '30', + '-coder' ,'1', + '-crf' ,'16', + '-pix_fmt', 'yuv420p', + '-c:a', 'aac', '-profile:a', 'aac_low', + '-b:a' ,'384k']) + + else: + no_vid = True + counter = 0 + for frame in source.iter_frames(): + counter += 1 + if counter == int(encode_style): + im = Image.fromarray(transform_image(frame, counter)) + im.show() + im.save(f'{counter}.png') + if not no_vid : + print('time : ', time.time() -t) + print('frames expected :', -1+len(os.listdir(os.path.join(current_folder, 'RAM_data')))) + print('frames dumped :', round(output_video.fps * output_video.duration)) + input('Press ENTER to exit') +main() diff --git a/scripts/Ghost/AutoSave/autosave ghost here.txt b/scripts/Ghost/AutoSave/autosave ghost here.txt new file mode 100644 index 0000000..e69de29 diff --git a/scripts/Ghost/Decode RKG CSV only.py b/scripts/Ghost/Decode RKG CSV only.py new file mode 100644 index 0000000..11045fb --- /dev/null +++ b/scripts/Ghost/Decode RKG CSV only.py @@ -0,0 +1,28 @@ +import sys +import os +from rkg_lib import decode_RKG + + +if __name__ == "__main__": + if len(sys.argv) < 2: + for filename in os.listdir(): + if filename[-4:] == '.rkg': + with open(filename, "rb") as f: + raw_data = f.read() + csv_filename = filename[:-4]+'.csv' + metadata_filename = filename[:-4]+'.txt' + mii_filename = filename[:-4]+'.mii' + metadata, inputList, mii_data = decode_RKG(raw_data) + + inputList.write_to_file(csv_filename) + else: + with open(sys.argv[1], "rb") as f: + raw_data = f.read() + csv_filename = filename[:-4]+'.csv' + metadata_filename = filename[:-4]+'.txt' + mii_filename = filename[:-4]+'.mii' + metadata, inputList, mii_data = decode_RKG(raw_data) + + inputList.write_to_file(csv_filename) + + diff --git a/scripts/Ghost/Decode RKG Full.py b/scripts/Ghost/Decode RKG Full.py new file mode 100644 index 0000000..f643dcb --- /dev/null +++ b/scripts/Ghost/Decode RKG Full.py @@ -0,0 +1,48 @@ +import sys +import os +from rkg_lib import decode_RKG + + +if __name__ == "__main__": + if len(sys.argv) < 2: + for filename in os.listdir(): + if filename[-4:] == '.rkg': + with open(filename, "rb") as f: + raw_data = f.read() + csv_filename = filename[:-4]+'.csv' + metadata_filename = filename[:-4]+'.txt' + mii_filename = filename[:-4]+'.mii' + + try: + metadata, inputList, mii_data = decode_RKG(raw_data) + inputList.write_to_file(csv_filename) + with open(metadata_filename, 'w') as f: + f.write(str(metadata)) + with open(mii_filename, 'wb') as f: + f.write(mii_data) + #print('decoding of '+filename+' succeded') + except Exception as e: + print('decoding of '+filename+' failed') + print(e) + a = 0/0 + else: + filename = sys.argv[1] + if filename[-4:] == '.rkg': + with open(filename, "rb") as f: + raw_data = f.read() + csv_filename = filename[:-4]+'.csv' + metadata_filename = filename[:-4]+'.txt' + mii_filename = filename[:-4]+'.mii' + + try: + metadata, inputList, mii_data = decode_RKG(raw_data) + inputList.write_to_file(csv_filename) + with open(metadata_filename, 'w') as f: + f.write(str(metadata)) + with open(mii_filename, 'wb') as f: + f.write(mii_data) + #print('decoding of '+filename+' succeded') + except Exception as e: + print('decoding of '+filename+' failed') + print(e) + a = 0/0 diff --git a/scripts/Ghost/Encode CSV to RKG.py b/scripts/Ghost/Encode CSV to RKG.py new file mode 100644 index 0000000..1983af8 --- /dev/null +++ b/scripts/Ghost/Encode CSV to RKG.py @@ -0,0 +1,56 @@ +import sys +import os +from rkg_lib import decode_RKG, FrameSequence, RKGMetaData, encode_RKG + + +if __name__ == "__main__": + if len(sys.argv) < 2: + for filename in os.listdir(): + if filename[-4:] == '.csv': + rkg_filename = filename[:-4]+'pyencode'+'.rkg' + metadata_filename = filename[:-4]+'.txt' + mii_filename = filename[:-4]+'.mii' + + inputList = FrameSequence(filename) + + with open(metadata_filename, 'r') as f: + if f is None: + metadata = RKGMetaData(None, True) + else: + metadata = RKGMetaData.from_string(f.read()) + + with open(mii_filename, 'rb') as f: + if f is None: + mii_data = f.read()[:0x4A] + else: + mii_data = bytearray(0x4A) + rkg_data = encode_RKG(metadata, inputList, mii_data) + + with open(rkg_filename, 'wb') as f: + f.write(rkg_data) + else: + filename = sys.argv[1] + if filename[-4:] == '.csv': + rkg_filename = filename[:-4]+'pyencode'+'.rkg' + metadata_filename = filename[:-4]+'.txt' + mii_filename = filename[:-4]+'.mii' + + inputList = FrameSequence(filename) + + with open(metadata_filename, 'r') as f: + if f is None: + metadata = RKGMetaData(None, True) + else: + metadata = RKGMetaData.from_string(f.read()) + + with open(mii_filename, 'rb') as f: + if f is None: + mii_data = f.read()[:0x4A] + else: + mii_data = bytearray(0x4A) + rkg_data = encode_RKG(metadata, inputList, mii_data) + + with open(rkg_filename, 'wb') as f: + f.write(rkg_data) + + diff --git a/scripts/Ghost/rkg_lib.py b/scripts/Ghost/rkg_lib.py new file mode 100644 index 0000000..7c89b53 --- /dev/null +++ b/scripts/Ghost/rkg_lib.py @@ -0,0 +1,815 @@ +import configparser +from math import floor +import os +import zlib +from typing import List, Optional +import csv + +class Frame: + """ + A class representing an input combination on a frame. + + Attributes: + accel (bool): Whether or not we press 'A' on that frame. + brake (bool): Whether or not we press 'B' on that frame. + item (bool): Whether or not we press 'L' on that frame. + stick_x (int): Horizontal stick input, ranging from -7 to +7. + stick_y (int): Vertical stick input, ranging from -7 to +7. + dpad_up (bool): Whether or not we press 'Up' on that frame. + dpad_down (bool): Whether or not we press 'Down' on that frame. + dpad_left (bool): Whether or not we press 'Left' on that frame. + dpad_right (bool): Whether or not we press 'Right' on that frame. + valid (bool): Whether or not the Frame is valid. + iter_idx (int): Tracks current iteration across the inputs + """ + accel: bool + brake: bool + item: bool + drift: bool + brakedrift: bool + + stick_x: int + stick_y: int + + dpad_up: bool + dpad_down: bool + dpad_left: bool + dpad_right: bool + + valid: bool + + iter_idx: int + + def __init__(self, raw: List): + """ + Initializes a Frame object given a CSV line. + + The structure of the list is as follows: + * raw[0] (str) - A + * raw[1] (str) - B/R + * raw[2] (str) - L + * raw[5] (str) - Horizontal stick + * raw[6] (str) - Vertical stick + * raw[7] (str) - Dpad + * raw[3] (str) - Drift "ghost" button + * raw[4] (str) - BrakeDrift "ghost" button + Args: + raw (List): CSV line to be read + """ + self.valid = True + + self.accel = self.read_button(raw[0]) + self.brake = self.read_button(raw[1]) + self.item = self.read_button(raw[2]) + self.drift = self.read_button(raw[3]) + self.brakedrift = self.read_button(raw[4]) + self.stick_x = self.read_stick(raw[5]) + self.stick_y = self.read_stick(raw[6]) + self.read_dpad(raw[7]) + + + def __iter__(self): + self.iter_idx = 0 + return self + + def __next__(self): + # If we update to Python 3.10, replace with switch statement + self.iter_idx += 1 + if (self.iter_idx == 1): + return int(self.accel) + if (self.iter_idx == 2): + return int(self.brake) + if (self.iter_idx == 3): + return int(self.item) + if (self.iter_idx == 4): + return int(self.drift) + if (self.iter_idx == 5): + return int(self.brakedrift) + if (self.iter_idx == 6): + return self.stick_x + if (self.iter_idx == 7): + return self.stick_y + if (self.iter_idx == 8): + return self.dpad_raw() + + raise StopIteration + + + + @staticmethod + def default(): + return Frame([0,0,0,0,0,0,0,0]) + + def __str__(self): + return ','.join(map(str, self)) + + + def read_button(self, button: str) -> bool: + """ + Parses the button input into a boolean. Sets `self.valid` to False if invalid. + """ + try: + val = int(button) + except ValueError: + self.valid = False + return False + + if val < 0 or val > 1: + self.valid = False + + return bool(val) + + def read_stick(self, stick: str) -> int: + """ + Parses the stick input into an int. Sets `self.valid` to False if invalid. + """ + try: + val = int(stick) + except ValueError: + self.valid = False + return 0 + + if val < -7 or val > 7: + self.valid = False + + return val + + def read_dpad(self, dpad: str) -> None: + """ + Sets dpad members based on dpad input. Sets `self.valid` to False if invalid. + """ + try: + val = int(dpad) + except ValueError: + self.valid = False + return + + if val < 0 or val > 4: + self.valid = False + + self.dpad_up = val == 1 + self.dpad_down = val == 2 + self.dpad_left = val == 3 + self.dpad_right = val == 4 + + def dpad_raw(self) -> int: + """ + Converts dpad values back into its raw form, for writing to the csv + """ + if self.dpad_up: + return 1 + if self.dpad_down: + return 2 + if self.dpad_left: + return 3 + if self.dpad_right: + return 4 + return 0 + +def compressInputList(rawInputList): + """A function that convert Raw Input List (List of Frames) to + Compressed Input List (List of 7-int-list, TTK .csv format)""" + compressedInputList = [] + prevInputRaw = Frame(["0"]*8) + prevInputCompressed = [0,0,0,0,0,0,-1] + for rawInput in rawInputList: + compressedInput = [int(rawInput.accel), + 0, + int(rawInput.item), + rawInput.stick_x, + rawInput.stick_y, + rawInput.dpad_raw(), + -1] + if not rawInput.brake: + compressedInput[1] = 0 + elif rawInput.brakedrift: + compressedInput[1] = 3 + elif not prevInputRaw.brake: + compressedInput[1] = 1 + elif rawInput.drift and not prevInputRaw.drift: + compressedInput[1] = 3-prevInputCompressed[1] + else: + compressedInput[1] = prevInputCompressed[1] + + if rawInput.accel and rawInput.brake and (not rawInput.drift): + if prevInputRaw.accel and prevInputRaw.brake and prevInputRaw.drift: + compressedInput[6] = 0 + elif not prevInputRaw.brake: + compressedInput[6] = 0 + + if ((not rawInput.accel) or (not rawInput.brake)) and rawInput.drift: + compressedInput[6] = 1 + + prevInputRaw = rawInput + prevInputCompressed = compressedInput + compressedInputList.append(compressedInput) + return compressedInputList + +def decompressInputList(compressedInputList): + """A function that convert Compressed Input List (List of 7-int-list, TTK .csv format) to + Raw Input List (List of Frames)""" + prevInputRaw = Frame(["0"]*8) + prevInputCompressed = [0,0,0,0,0,0,-1] + rawInputList = [] + for compressedInput in compressedInputList: + accel = compressedInput[0] + brake = int(compressedInput[1]>0) + item = compressedInput[2] + X = compressedInput[3] + Y = compressedInput[4] + dpad = compressedInput[5] + brakedrift = int(compressedInput[1]==3) + + if accel + brake < 2: + drift = 0 + elif prevInputRaw.drift: + drift = 1 + elif prevInputCompressed[1] == compressedInput[1]: + drift = 0 + else: + drift = 1 + + if compressedInput[6] != -1: + drift = compressedInput[6] + + rawInput = Frame(list(map(str, (accel, brake, item, drift, brakedrift, X, Y, dpad)))) + prevInputRaw = rawInput + prevInputCompressed = compressedInput + rawInputList.append(rawInput) + return rawInputList + + + +class FrameSequence: + """ + A class representing a sequence of inputs, indexed by frames. + + Attributes: + frames (list): The sequence of frames. + filename (str): The name of the CSV file initializing the frame sequence. + """ + frames: list + filename: str + iter_idx: int + + def __init__(self, filename: Optional[str]=None): + self.frames = [] + self.filename = filename + + if self.filename: + self.read_from_file() + + def __len__(self): + return len(self.frames) + + def __getitem__(self, i): + if (i < len(self.frames)): + return self.frames[i] + return None + + def __iter__(self): + self.iter_idx = -1 + return self + + def __next__(self): + self.iter_idx += 1 + if (self.iter_idx < len(self.frames)): + return self.frames[self.iter_idx] + raise StopIteration + + def read_from_list(self, inputs: List) -> None: + """ + Constructs the frames list by using a list instead of a csv + + Args: + input (List): The raw input data we want to store + Returns: None + """ + for input in inputs: + frame = self.process(input) + if not frame: + pass + self.frames.append(frame) + + def read_from_list_of_frames(self, inputs: List) -> None: + """ + Constructs the frames list by using a list of Frames instead of a csv + + Args: + input (List): The raw input data we want to store + Returns: None + """ + for frame in inputs: + self.frames.append(frame) + + def read_from_file(self) -> None: + """ + Loads the CSV into a new frame sequence. Ideally called on savestate load. + + Args: None + Returns: None + """ + self.frames.clear() + try: + with open(self.filename, 'r') as f: + reader = csv.reader(f) + compressedInputList = [] + for row in reader: + compressedInputList.append(list(map(int,row))) + for frame in decompressInputList(compressedInputList): + self.frames.append(frame) + except IOError: + return + + def write_to_file(self, filename: str) -> bool: + """ + Writes the frame sequence to a csv + + Args: + filename (str): The path to the file we wish to write to + Returns: + A boolean indicating whether the write was successful + """ + try: + with open(filename, 'w', newline='') as f: + writer = csv.writer(f, delimiter=',') + writer.writerows(compressInputList(self.frames)) + except IOError: + return False + return True + + def process(self, raw_frame: List) -> Optional[Frame]: + """ + Processes a raw frame into an instance of + the Frame class. Ideally used internally. + + Args: + raw_frame (List): Line from the CSV to process. + + Returns: + A new Frame object initialized with the raw frame, + or None if the frame is invalid. + """ + assert len(raw_frame) == 8 + if len(raw_frame) != 8: + return None + + frame = Frame(raw_frame) + if not frame.valid: + return None + + return frame + + +def extract_bits(data, start_bit, length): + byte_index = start_bit // 8 + bit_offset = start_bit % 8 + bits = 0 + + for i in range(length): + bit_position = bit_offset + i + bits <<= 1 + bits |= (data[byte_index + (bit_position // 8)] >> (7 - (bit_position % 8))) & 1 + + return bits + + + + + + +def crc16(i): + '''Argument : bytearray + Return : 16bit int''' + #https://stackoverflow.com/questions/25239423/crc-ccitt-16-bit-python-manual-calculation + def _update_crc(crc, c): + def _initial(c): + POLYNOMIAL = 0x1021 + PRESET = 0 + crc = 0 + c = c << 8 + for j in range(8): + if (crc ^ c) & 0x8000: + crc = (crc << 1) ^ POLYNOMIAL + else: + crc = crc << 1 + c = c << 1 + return crc + POLYNOMIAL = 0x1021 + PRESET = 0 + _tab = [ _initial(i) for i in range(256) ] + cc = 0xff & c + tmp = (crc >> 8) ^ cc + crc = (crc << 8) ^ _tab[tmp & 0xff] + crc = crc & 0xffff + return crc + POLYNOMIAL = 0x1021 + PRESET = 0 + crc = PRESET + for c in i: + crc = _update_crc(crc, c) + return crc + + +def convertTimer(bits: int): + '''Convert a 24 bits int representing a Timer (m,s,ms), to a float in seconds''' + ms = bits & 1023 + bits >>= 10 + s = bits & 127 + bits >>= 7 + m = bits & 127 + return m*60+s+ms/1000 + +def convertTimerBack(split: float): + '''Convert a float in seconds, to a 24 bits int representing a Timer (m,s,ms)''' + m = floor(split/60) + s = floor(split%60) + ms = floor(split*1000)%1000 + return ms + (s<<10) + (m <<17) + +def add_bits(bit_list, number, size): + ''' Add to a bit list another number, with a bit size''' + for i in range(size): + bit_list.append((number >> (size - i -1)) & 1) + +def bits_to_bytearray(bit_list): + ''' Convert an array of bits to an array of bytes (grouping bits by 8)''' + b = bytearray() + byte = 0 + for i in range(len(bit_list)): + byte = (byte << 1) + bit_list[i] + if i%8 == 7: + b.append(byte) + byte = 0 + if byte != 0: + b.append(byte) + return b + +class RKGMetaData: + def __init__(self, rkg_data, useDefault = False): + if not useDefault: + self.finish_time = convertTimer(extract_bits(rkg_data, 4*8+0, 24)) + self.track_id = extract_bits(rkg_data, 7*8+0, 6) + self.character_id = extract_bits(rkg_data, 8*8+6, 6) + self.vehicle_id = extract_bits(rkg_data, 8*8+0, 6) + self.year = extract_bits(rkg_data, 9*8+4, 7) + self.month = extract_bits(rkg_data, 0xA*8+3, 4) + self.day = extract_bits(rkg_data, 0xA*8+7, 5) + self.controller_id = extract_bits(rkg_data, 0xB*8+4, 4) + self.compressed_flag = extract_bits(rkg_data, 0xC*8+4, 1) + self.ghost_type = extract_bits(rkg_data, 0xC*8+7, 7) + self.drift_id = extract_bits(rkg_data, 0xD*8+6, 1) + self.input_data_length = extract_bits(rkg_data, 0xE*8+0, 16) + self.lap_count = extract_bits(rkg_data, 0x10*8+0, 8) + self.lap1_split = convertTimer(extract_bits(rkg_data, 0x11*8+0, 24)) + self.lap2_split = convertTimer(extract_bits(rkg_data, 0x14*8+0, 24)) + self.lap3_split = convertTimer(extract_bits(rkg_data, 0x17*8+0, 24)) + self.country_code = extract_bits(rkg_data, 0x34*8+0, 8) + self.state_code = extract_bits(rkg_data, 0x35*8+0, 8) + self.location_code = extract_bits(rkg_data, 0x36*8+0, 16) + else: + self.finish_time = 100 + self.track_id = 0 + self.character_id = 0 + self.vehicle_id = 0 + self.year = 25 + self.month = 1 + self.day = 1 + self.controller_id = 3 + self.compressed_flag = 0 + self.ghost_type = 38 + self.drift_id = 0 + self.input_data_length = 908 + self.lap_count = 3 + self.lap1_split = 0 + self.lap2_split = 5999.999 + self.lap3_split = 5999.999 + self.country_code = 255 + self.state_code = 255 + self.location_code = 65535 + + def __iter__(self): + return iter([self.finish_time, + self.track_id, + self.character_id, + self.vehicle_id, + self.year, + self.month, + self.day, + self.controller_id, + self.compressed_flag, + self.ghost_type, + self.drift_id, + self.input_data_length, + self.lap_count, + self.lap1_split, + self.lap2_split, + self.lap3_split, + self.country_code, + self.state_code, + self.location_code]) + + def __str__(self): + res = "" + res += "finish_time = " + str(self.finish_time)+"\n" + res += "track_id = " + str(self.track_id)+"\n" + res += "character_id = " + str(self.character_id)+"\n" + res += "vehicle_id = " + str(self.vehicle_id)+"\n" + res += "year = " + str(self.year)+"\n" + res += "month = " + str(self.month)+"\n" + res += "day = " + str(self.day)+"\n" + res += "controller_id = " + str(self.controller_id)+"\n" + res += "compressed_flag = " + str(self.compressed_flag)+"\n" + res += "ghost_type = " + str(self.ghost_type)+"\n" + res += "drift_id = " + str(self.drift_id)+"\n" + res += "input_data_length = " + str(self.input_data_length)+"\n" + res += "lap_count = " + str(self.lap_count)+"\n" + res += "lap1_split = " + str(self.lap1_split)+"\n" + res += "lap2_split = " + str(self.lap2_split)+"\n" + res += "lap3_split = " + str(self.lap3_split)+"\n" + res += "country_code = " + str(self.country_code)+"\n" + res += "state_code = " + str(self.state_code)+"\n" + res += "location_code = " + str(self.location_code) + return res + + @staticmethod + def from_string(string): + lines = string.split("\n") + self = RKGMetaData(None, True) + self.finish_time = float(lines[0].split('=')[1]) + self.track_id = int(lines[1].split('=')[1]) + self.character_id = int(lines[2].split('=')[1]) + self.vehicle_id = int(lines[3].split('=')[1]) + self.year = int(lines[4].split('=')[1]) + self.month = int(lines[5].split('=')[1]) + self.day = int(lines[6].split('=')[1]) + self.controller_id = int(lines[7].split('=')[1]) + self.compressed_flag = int(lines[8].split('=')[1]) + self.ghost_type = int(lines[9].split('=')[1]) + self.drift_id = int(lines[10].split('=')[1]) + self.input_data_length = int(lines[11].split('=')[1]) + self.lap_count = int(lines[12].split('=')[1]) + self.lap1_split = float(lines[13].split('=')[1]) + self.lap2_split = float(lines[14].split('=')[1]) + self.lap3_split = float(lines[15].split('=')[1]) + self.country_code = int(lines[16].split('=')[1]) + self.state_code = int(lines[17].split('=')[1]) + self.location_code = int(lines[18].split('=')[1]) + return self + + def to_bytes(self): + bit_list = [] + add_bits(bit_list, convertTimerBack(self.finish_time), 24) + add_bits(bit_list, self.track_id, 6) + add_bits(bit_list, 0, 2) + add_bits(bit_list, self.vehicle_id, 6) + add_bits(bit_list, self.character_id, 6) + add_bits(bit_list, self.year, 7) + add_bits(bit_list, self.month, 4) + add_bits(bit_list, self.day, 5) + add_bits(bit_list, self.controller_id, 4) + add_bits(bit_list, 0, 4) + add_bits(bit_list, self.compressed_flag, 1) + add_bits(bit_list, 0, 2) + add_bits(bit_list, self.ghost_type, 7) + add_bits(bit_list, self.drift_id, 1) + add_bits(bit_list, 0, 1) + add_bits(bit_list, self.input_data_length, 8*2) + add_bits(bit_list, self.lap_count, 8*1) + add_bits(bit_list, convertTimerBack(self.lap1_split), 24) + add_bits(bit_list, convertTimerBack(self.lap2_split), 24) + add_bits(bit_list, convertTimerBack(self.lap3_split), 24) + add_bits(bit_list, 0, 2*3*8) + add_bits(bit_list, 0, 8*0x14) + add_bits(bit_list, self.country_code, 8) + add_bits(bit_list, self.state_code, 8) + add_bits(bit_list, self.location_code, 8*2) + add_bits(bit_list, 0, 8*4) + + res = bytearray(b'RKGD') + bits_to_bytearray(bit_list) + return res + + +def decompress_ghost_input(ghost_src): + if len(ghost_src) > 0x8F and ghost_src[0x8C:0x90] == b'Yaz1': + uncompressed_size = (ghost_src[0x90] << 24) + (ghost_src[0x91] << 16) + (ghost_src[0x92] << 8) + ghost_src[0x93] + return decode_Yaz1(ghost_src, 0x9c, uncompressed_size) + else: + return list(ghost_src[0x88:]) + +def decode_Yaz1(src, offset, uncompressed_size): + src_pos = offset + valid_bit_count = 0 + try: + curr_code_byte = src[offset + src_pos] + except: + curr_code_byte = src[src_pos] + + dst = [] + + while len(dst) < uncompressed_size: + if valid_bit_count == 0: + curr_code_byte = src[src_pos] + src_pos += 1 + valid_bit_count = 8 + + if (curr_code_byte & 0x80) != 0: + dst.append(src[src_pos]) + src_pos += 1 + else: + byte1 = src[src_pos] + byte2 = src[src_pos + 1] + src_pos += 2 + dist = ((byte1 & 0xF) << 8) | byte2 + copy_source = len(dst) - (dist + 1) + num_bytes = byte1 >> 4 + if num_bytes == 0: + num_bytes = src[src_pos] + 0x12 + src_pos += 1 + else: + num_bytes += 2 + + for _ in range(num_bytes): + dst.append(dst[copy_source]) + copy_source += 1 + + curr_code_byte <<= 1 + valid_bit_count -= 1 + + return dst + + +def decode_rkg_inputs(rkg_data): + raw_data = decompress_ghost_input(rkg_data) + button_inputs = [] + analog_inputs = [] + trick_inputs = [] + cur_byte = 8 + nr_button_inputs = (raw_data[0] << 8) | raw_data[1] + nr_analog_inputs = (raw_data[2] << 8) | raw_data[3] + nr_trick_inputs = (raw_data[4] << 8) | raw_data[5] + + for _ in range(nr_button_inputs): + if cur_byte + 1 < len(raw_data): + inputs = raw_data[cur_byte] + frames = raw_data[cur_byte + 1] + else: + inputs = 0 + frames = 0 + accelerator = inputs & 0x1 + drift = (inputs & 0x2) >> 1 + item = (inputs & 0x4) >> 2 + pseudoAB = (inputs & 0x8) >> 3 + breakdrift = (inputs & 0x10) >> 4 + button_inputs += [(accelerator, drift, item, pseudoAB, breakdrift)] * frames + cur_byte += 2 + + for _ in range(nr_analog_inputs): + if cur_byte + 1 < len(raw_data): + inputs = raw_data[cur_byte] + frames = raw_data[cur_byte + 1] + else: + inputs = 0 + frames = 0 + horizontal = ((inputs >> 4) & 0xF) - 7 + vertical = (inputs & 0xF) - 7 + analog_inputs += [(horizontal, vertical)] * frames + cur_byte += 2 + + for _ in range(nr_trick_inputs): + if cur_byte + 1 < len(raw_data): + inputs = raw_data[cur_byte] + frames = raw_data[cur_byte + 1] + else: + inputs = 0 + frames = 0 + trick = (inputs & 0x70) >> 4 + extra_frames = (inputs & 0x0F) << 8 + trick_inputs += [trick] * (frames + extra_frames) + cur_byte += 2 + inputList = [] + for i in range(len(button_inputs)): + if i >= len(analog_inputs): + analog_inputs.append((0,0)) + if i >= len(trick_inputs): + trick_inputs.append(0) + inputList.append(Frame([button_inputs[i][0], + button_inputs[i][1], + button_inputs[i][2], + button_inputs[i][3], + button_inputs[i][4], + analog_inputs[i][0], + analog_inputs[i][1], + trick_inputs[i]])) + res = FrameSequence() + res.read_from_list_of_frames(inputList) + return res + + +def encodeFaceButton(aButton, bButton, lButton, pabButton, bdButton): + return aButton * 0x1 + bButton * 0x2 + lButton * 0x4 + pabButton * 0x8 + bdButton * 0x10 + +def encodeDirectionInput(horizontalInput, verticalInput): + return ((horizontalInput+7) << 4) + verticalInput+7 + +def encodeTrickInput(trickInput): + return trickInput << 4 + +def encodeRKGInput(inputList : 'FrameSequence'): + data = bytearray() + dataIndex = 0 + iL = inputList.frames + fbBytes, diBytes, tiBytes = 0, 0, 0 + + #Encode face buttons inputs + prevInput = encodeFaceButton(iL[0].accel, iL[0].brake, iL[0].item, iL[0].drift, iL[0].brakedrift) + amountCurrentFrames = 0x0 + for ipt in iL: + currentInput = encodeFaceButton(ipt.accel, ipt.brake, ipt.item, ipt.drift, ipt.brakedrift) + if prevInput != currentInput or amountCurrentFrames >= 0xFF: + data.append(prevInput) + data.append(amountCurrentFrames) + prevInput = currentInput + amountCurrentFrames = 0x1 + fbBytes = fbBytes + 1 + else: + amountCurrentFrames = amountCurrentFrames + 1 + data.append(prevInput) + data.append(amountCurrentFrames) + fbBytes = fbBytes + 1 + + #Encode joystick inputs + prevInput = encodeDirectionInput(iL[0].stick_x, iL[0].stick_y) + amountCurrentFrames = 0x0 + for ipt in iL: + currentInput = encodeDirectionInput(ipt.stick_x, ipt.stick_y) + if prevInput != currentInput or amountCurrentFrames >= 0xFF: + data.append(prevInput) + data.append(amountCurrentFrames) + prevInput = currentInput + amountCurrentFrames = 0x1 + diBytes = diBytes + 1 + else: + amountCurrentFrames = amountCurrentFrames + 1 + data.append(prevInput) + data.append(amountCurrentFrames) + diBytes = diBytes + 1 + + #Encode trick inputs + prevInput = encodeTrickInput(iL[0].dpad_raw()) + amountCurrentFrames = 0x0 + for ipt in iL: + currentInput = encodeTrickInput(ipt.dpad_raw()) + if prevInput != currentInput or amountCurrentFrames >= 0xFFF: + data.append(prevInput + (amountCurrentFrames >> 8)) + data.append(amountCurrentFrames % 0x100) + prevInput = currentInput + amountCurrentFrames = 0x1 + tiBytes = tiBytes + 1 + else: + amountCurrentFrames = amountCurrentFrames + 1 + + data.append(prevInput + (amountCurrentFrames >> 8)) + data.append(amountCurrentFrames % 0x100) + tiBytes = tiBytes + 1 + + return data, fbBytes, diBytes, tiBytes + + +def decode_RKG(raw_data : bytearray): + ''' Decode RKG data to 3 objetcs : + - RKGMetaData + - FrameSequence + - Mii Raw data (bytearray, size 0x4A)''' + if len(raw_data) < 0x88: + print("decode_RKG can't decode raw_data : raw_data too small") + else: + metadata = RKGMetaData(raw_data) + inputList = decode_rkg_inputs(raw_data) + mii_data = raw_data[0x3C:0x86] + return metadata, inputList, mii_data + + +def encode_RKG(metadata : 'RKGMetaData', inputList : 'FrameSequence', mii_data : bytearray): + ''' Encode to a RKG raw data (bytearray) ''' + inputData, fbBytes, diBytes, tiBytes = encodeRKGInput(inputList) + dataIndex = (fbBytes + diBytes + tiBytes) * 0x2 + metadata.input_data_length = dataIndex + 0x8 + metadata.compressed_flag = 0 + crc16_int = crc16(mii_data) + + metadata_bytes = metadata.to_bytes() + crc16_data = bytearray([crc16_int >> 8, crc16_int & 0xFF]) + inputHeader = bytearray([fbBytes >> 8, fbBytes & 0xFF, diBytes >> 8, diBytes & 0xFF, tiBytes >> 8, tiBytes & 0xFF, 0, 0]) + + rkg_data = metadata_bytes + mii_data + crc16_data + inputHeader + inputData + crc32 = zlib.crc32(rkg_data) + arg1 = floor(crc32 / 0x1000000) + arg2 = floor((crc32 & 0x00FF0000) / 0x10000) + arg3 = floor((crc32 & 0x0000FF00) / 0x100) + arg4 = floor(crc32 % 0x100) + + + return rkg_data + bytearray([arg1, arg2, arg3, arg4]) + + diff --git a/scripts/Install_Pycore_Packages.bat b/scripts/Install_Pycore_Packages.bat new file mode 100644 index 0000000..5a3ddcf --- /dev/null +++ b/scripts/Install_Pycore_Packages.bat @@ -0,0 +1,8 @@ +python -m pip install --upgrade pip +pip install numpy +pip install matplotlib +pip install pillow +pip install pygame +pip install moviepy +python verify_package.py +pause \ No newline at end of file diff --git a/scripts/MKW_Inputs/Backups/backup here.txt b/scripts/MKW_Inputs/Backups/backup here.txt new file mode 100644 index 0000000..e69de29 diff --git a/scripts/MKW_Inputs/Startslides/flame_left.csv b/scripts/MKW_Inputs/Startslides/flame_left.csv deleted file mode 100644 index 1bbef69..0000000 --- a/scripts/MKW_Inputs/Startslides/flame_left.csv +++ /dev/null @@ -1,240 +0,0 @@ -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,1 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-6,0,0 -1,0,0,-1,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-5,0,0 -1,0,0,-3,0,0 -1,0,0,0,0,0 -1,0,0,-1,0,0 -1,0,0,-1,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-1,0,0 -1,0,0,0,0,0 -1,0,0,-1,0,0 -1,0,0,-5,0,0 -1,0,0,-3,0,0 -1,0,0,-3,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,2,0,1 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-4,0,0 -1,0,0,-6,-1,0 -1,0,0,0,-1,0 -1,0,0,-1,0,0 -1,0,0,-6,-1,0 -1,0,0,-6,-1,0 -1,0,0,-1,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,7,0,1 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -0,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,-1,0,0 -1,0,0,-1,0,0 -1,0,0,-1,0,0 -1,0,0,-1,0,0 -1,0,0,-1,0,0 -1,0,0,2,0,1 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 diff --git a/scripts/MKW_Inputs/Startslides/flame_right.csv b/scripts/MKW_Inputs/Startslides/flame_right.csv deleted file mode 100644 index 9a38d5f..0000000 --- a/scripts/MKW_Inputs/Startslides/flame_right.csv +++ /dev/null @@ -1,240 +0,0 @@ -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,1 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,2,0,0 -1,0,0,2,0,0 -1,0,0,6,0,0 -1,0,0,1,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,5,0,0 -1,0,0,3,0,0 -1,0,0,0,0,0 -1,0,0,1,0,0 -1,0,0,1,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,1,0,0 -1,0,0,0,0,0 -1,0,0,1,0,0 -1,0,0,5,0,0 -1,0,0,3,0,0 -1,0,0,3,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-2,0,1 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,4,0,0 -1,0,0,6,-1,0 -1,0,0,0,-1,0 -1,0,0,1,0,0 -1,0,0,6,-1,0 -1,0,0,6,-1,0 -1,0,0,1,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,-7,0,1 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -0,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,1,0,0 -1,0,0,1,0,0 -1,0,0,1,0,0 -1,0,0,1,0,0 -1,0,0,1,0,0 -1,0,0,-2,0,1 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-2,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 diff --git a/scripts/MKW_Inputs/Startslides/spear_left.csv b/scripts/MKW_Inputs/Startslides/spear_left.csv deleted file mode 100644 index dc40c20..0000000 --- a/scripts/MKW_Inputs/Startslides/spear_left.csv +++ /dev/null @@ -1,240 +0,0 @@ -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,-7,0,1 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,0,0,0 -1,0,0,-7,-2,0 -1,0,0,-7,-2,0 -1,0,0,-7,-2,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-7,7,0 -0,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,-7,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,-7,0,1 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,7,0,1 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,0,0,0 -0,0,0,-7,7,0 -0,0,0,-7,7,0 -0,0,0,-7,7,0 -0,0,0,-7,7,0 -0,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,0,1 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,0,0,1 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,-7,-1,0 -1,0,0,-7,-1,0 -1,0,0,-7,-1,0 -1,0,0,-7,-1,0 -1,0,0,-7,-1,0 -1,0,0,-7,-1,0 -1,0,0,-7,-1,0 -1,0,0,-7,-1,0 -1,0,0,-7,-1,0 -1,0,0,-7,-1,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 diff --git a/scripts/MKW_Inputs/Startslides/spear_right.csv b/scripts/MKW_Inputs/Startslides/spear_right.csv deleted file mode 100644 index 05865d4..0000000 --- a/scripts/MKW_Inputs/Startslides/spear_right.csv +++ /dev/null @@ -1,240 +0,0 @@ -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,7,0,1 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,0,0,0 -1,0,0,7,-2,0 -1,0,0,7,-2,0 -1,0,0,7,-2,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,7,7,0 -0,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,7,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,0,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,7,0,1 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,-7,0,1 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,0,0,0 -0,0,0,7,7,0 -0,0,0,7,7,0 -0,0,0,7,7,0 -0,0,0,7,7,0 -0,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,7,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,7,0 -1,0,0,-7,0,1 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,0,0,1 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,7,-1,0 -1,0,0,7,-1,0 -1,0,0,7,-1,0 -1,0,0,7,-1,0 -1,0,0,7,-1,0 -1,0,0,7,-1,0 -1,0,0,7,-1,0 -1,0,0,7,-1,0 -1,0,0,7,-1,0 -1,0,0,7,-1,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,7,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 diff --git a/scripts/MKW_Inputs/Startslides/star_left.csv b/scripts/MKW_Inputs/Startslides/star_left.csv deleted file mode 100644 index b549b9d..0000000 --- a/scripts/MKW_Inputs/Startslides/star_left.csv +++ /dev/null @@ -1,240 +0,0 @@ -0,0,0,7,0,0 -0,0,0,7,0,0 -0,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,-6,0,1 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,1 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-4,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-5,0,0 -1,0,0,-5,0,0 -1,0,0,-4,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-3,0,0 -1,0,0,-4,0,0 -1,0,0,-3,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-1,0,0 -1,0,0,-4,0,0 -1,0,0,-3,0,0 -1,0,0,-1,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,1 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,7,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,-6,7,0 -1,0,0,-6,7,0 -1,0,0,-6,7,0 -1,0,0,-6,7,0 -1,0,0,-6,7,0 diff --git a/scripts/MKW_Inputs/Startslides/star_right.csv b/scripts/MKW_Inputs/Startslides/star_right.csv deleted file mode 100644 index c262361..0000000 --- a/scripts/MKW_Inputs/Startslides/star_right.csv +++ /dev/null @@ -1,240 +0,0 @@ -0,0,0,-7,0,0 -0,0,0,-7,0,0 -0,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,6,0,1 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,1 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,4,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,5,0,0 -1,0,0,5,0,0 -1,0,0,4,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,3,0,0 -1,0,0,4,0,0 -1,0,0,3,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,1,0,0 -1,0,0,4,0,0 -1,0,0,3,0,0 -1,0,0,1,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -0,0,0,0,0,0 -0,0,0,0,0,0 -0,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,1 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,-7,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,0,0 -1,0,0,0,0,0 -1,0,0,0,0,0 -1,0,0,6,7,0 -1,0,0,6,7,0 -1,0,0,6,7,0 -1,0,0,6,7,0 -1,0,0,6,7,0 diff --git a/scripts/MKW_Inputs/ttk files go here.txt b/scripts/MKW_Inputs/ttk files go here.txt new file mode 100644 index 0000000..e69de29 diff --git a/scripts/Modules/Info EVA usage.txt b/scripts/Modules/Info EVA usage.txt new file mode 100644 index 0000000..0b97c9b --- /dev/null +++ b/scripts/Modules/Info EVA usage.txt @@ -0,0 +1,33 @@ +https://mariokartwii.com/showthread.php?tid=1954 + +Range : 0x80000100 to 0x80001800 + + +0x800002CE thru 0x800002D3 FanCy Speedometer [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1492 +0x800002CE thru 0x800002D3 FanCy HUD [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1496 +0x800002E0 - 0x80000300 : PyCore Exact finish time +0x800005FD Rapid Fire (GCN) [mdmwii] https://mariokartwii.com/showthread.php?tid=263 +0x80000A20 thru 0x80000A23 Steal-Mii [Vega] https://mariokartwii.com/showthread.php?tid=1218 +0x80000DC0 thru 0x80000DCF FanCy Speedometer [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1492 +0x80000DC0 thru 0x80000DCF FanCy HUD [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1496 +0x80000FA0 thru 0x800012FF FanCy Speedometer [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1492 +0x80000FA0 thru 0x800012FF FanCy HUD [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1496 +0x80000FC8 thru 0x80000FCF Luck Wheelie Bot [Vega] https://mariokartwii.com/showthread.php?tid=1861 +0x80000FCC thru 0x80000FCF Rapid Fire/Hop [Vega] https://mariokartwii.com/showthread.php?tid=1862 +0x80001100 thru 0x80001197 Vehicle Stats Modifier [JoshuaMK] https://mariokartwii.com/showthread.php?tid=1334 +0x800014B0 thru 0x80001503 Draw Text To Screen [SwareJonge] https://mariokartwii.com/showthread.php?tid=1714 +0x800014B0 thru 0x8000152F Graphical Speedometer [SwareJonge] https://mariokartwii.com/showthread.php?tid=1715 +0x8000152C thru 0x8000152F Mii Cloner [mdmwii] https://mariokartwii.com/showthread.php?tid=274 +0x80001534 thru 0x80001537 Future Fly (Wii Chuck) [mdmwii] https://mariokartwii.com/showthread.php?tid=1078 +0x80001534 thru 0x80001537 Future Fly (GCN) [mdmwii] https://mariokartwii.com/showthread.php?...6&pid=1923 +0x80001550 thru 0x80001553 MarioBOT [mdmwii] https://mariokartwii.com/showthread.php?tid=54 +0x80001570 thru 0x8000157B 1st Person Camera View [JoshuaMK, mdmwii] https://mariokartwii.com/showthread.php?tid=1331 +0x80001570 thru 0x8000157F 1st Person Camera View [mdmwii] https://mariokartwii.com/showthread.php?tid=597 +0x80001600 thru 0x8000161B Future Fly (Wii Chuck) [mdmwii] https://mariokartwii.com/showthread.php?tid=1078 +0x80001600 thru 0x8000161F Future Fly (GCN) [mdmwii] https://mariokartwii.com/showthread.php?...6&pid=1923 +0x80001620 thru 0x80001627 DWC_Authdata NAND File Modifier [Vega] https://mariokartwii.com/showthread.php?tid=1075 +0x80001648 thru 0x8000164B Ultimate Region ID Cycler In Between Races [Vega] https://mariokartwii.com/showthread.php?tid=1070 +0x80001660 thru 0x80001663 Speed-O-Meter; TTs Only [mdmwii] https://mariokartwii.com/showthread.php?tid=1040 +0x80001660 thru 0x80001663 Graphical Speed/MTC/Air/Boost Meter [Vega] https://mariokartwii.com/showthread.php?tid=1112 +0x80001660 thru 0x80001663 Graphical Speed-O-Meter [Vega] https://mariokartwii.com/showthread.php?tid=1111 +0x80001660 thru 0x80001689 Graphical SpeedBar [Vega] https://mariokartwii.com/showthread.php?tid=1113 \ No newline at end of file diff --git a/scripts/Modules/TimeDifference information.txt b/scripts/Modules/TimeDifference information.txt new file mode 100644 index 0000000..7b66c9b --- /dev/null +++ b/scripts/Modules/TimeDifference information.txt @@ -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. \ No newline at end of file diff --git a/scripts/Modules/__init__.py b/scripts/Modules/__init__.py index e69de29..92b50fc 100644 --- a/scripts/Modules/__init__.py +++ b/scripts/Modules/__init__.py @@ -0,0 +1,20 @@ +''' +from Modules import mkw_classes +#from Modules import marco_utils +from Modules import settings_utils +from Modules import input_display + +#from Modules import mkw_translation +from Modules import mkw_utils +from Modules import framesequence + +from Modules import ttk_config +from Modules import rkg_lib + +from Modules import ttk_lib + +from Modules import agc_lib +from Modules import bruteforcer_lib +from Modules import startslide_utils +from Modules import infodisplay_utils +''' diff --git a/scripts/Modules/agc_lib.py b/scripts/Modules/agc_lib.py new file mode 100644 index 0000000..ee277b4 --- /dev/null +++ b/scripts/Modules/agc_lib.py @@ -0,0 +1,290 @@ +from dolphin import gui, memory, utils +from .mkw_classes import quatf, vec3 +from .framesequence import Frame +from .mkw_utils import chase_pointer, fps_const +from .ttk_lib import write_player_inputs, write_ghost_inputs +from .mkw_classes import VehiclePhysics, KartMove, RaceConfig, Timer, RaceManagerPlayer, RaceConfigPlayer, RaceConfigScenario, Controller, InputMgr +import math + +class AGCFrameData: + """Class to represent a set of value accessible each frame in the memory + """ + Position : 'vec3' + Rotation : 'quatf' + IV : 'vec3' + EV : 'vec3' + MaxIV : float + CurIV : float + ODA : float #Outside Drift Angle + Dir : 'vec3' + Dive : float + Input : 'Frame' + + def __init__(self, usedefault=False, read_slot = 0): + if not usedefault: + self.Position = VehiclePhysics.position(read_slot) + self.Rotation = VehiclePhysics.main_rotation(read_slot) + self.IV = VehiclePhysics.internal_velocity(read_slot) + self.EV = VehiclePhysics.external_velocity(read_slot) + self.MaxIV = KartMove.soft_speed_limit(read_slot) + self.CurIV = KartMove.speed(read_slot) + self.ODA = KartMove.outside_drift_angle(read_slot) + self.Dir = KartMove.dir(read_slot) + self.Dive = KartMove.diving_rotation(read_slot) + self.Input = Frame.from_current_frame(read_slot) + else: + self.Position = vec3(0,0,0) + self.Rotation = quatf(0,0,0,0) + self.IV = vec3(0,0,0) + self.EV = vec3(0,0,0) + self.MaxIV = 0 + self.CurIV = 0 + self.ODA = 0 + self.Dir = vec3(0,0,0) + self.Dive = 0 + self.Input = Frame.default() + + def __iter__(self): + return iter([self.Position, + self.Rotation, + self.IV, + self.EV, + self.MaxIV, + self.CurIV, + self.ODA, + self.Dir, + self.Dive, + self.Input]) + + def __str__(self): + return ';'.join(map(str, self)) + + @staticmethod + def read_from_string(string): + res = AGCFrameData(usedefault = True) + temp = string.split(';') + assert len(temp) == 10 + + res.Position = vec3.from_string(temp[0]) + res.Rotation = quatf.from_string(temp[1]) + res.IV = vec3.from_string(temp[2]) + res.EV = vec3.from_string(temp[3]) + res.MaxIV = float(temp[4]) + res.CurIV = float(temp[5]) + res.ODA = float(temp[6]) + res.Dir = vec3.from_string(temp[7]) + res.Dive = float(temp[8]) + res.Input = Frame(temp[9].split((','))) + + return res + + def interpolate(self,other, selfi, otheri): + #BE CAREFUL, THIS FUNCTION MODIFY SELF ! (be sure to call on a copy before) + v1 = self.Position + v2 = other.Position + v = (v1*selfi)+(v2*otheri) + self.Position = v + + def load(self, write_slot, input_only = False): + kma = KartMove.chain(write_slot) + vpa = VehiclePhysics.chain(write_slot) + + if not input_only: + self.Position.write(vpa + 0x68) + self.Rotation.write(vpa + 0xF0) + self.IV.write(vpa + 0x14C) + self.EV.write(vpa + 0x74) + memory.write_f32(kma + 0x18, self.MaxIV) + memory.write_f32(kma + 0x20, self.CurIV) + memory.write_f32(kma + 0x9C, self.ODA) + self.Dir.write(kma + 0x5C) + memory.write_f32(kma + 0xF4, self.Dive) + else: + if write_slot == 0 : + write_player_inputs(self.Input) + else: + write_ghost_inputs(self.Input, write_slot) + + + +class AGCMetaData: + """Class for the metadata of a ghost. + Contain Character ID, Vehicle ID, Drift ID, and all 3 lap splits""" + + def __init__(self, useDefault = False, read_id = 0): + if not useDefault: + rcp = RaceConfigPlayer(RaceConfigScenario(RaceConfig.race_scenario()).player(read_id)) + gc_controller = Controller(InputMgr().gc_controller(read_id)) + self.charaID = rcp.character_id().value + self.vehicleID = rcp.vehicle_id().value + self.driftID = int(gc_controller.drift_is_auto()) #ASSUME YOU USE A GCN CONTROLLER ! TODO : HANDLE ALL CONTROLLER + self.timer_data = [Split.from_timer(RaceManagerPlayer.lap_finish_time(read_id, lap)) for lap in range(3)] + else: + self.charaID = 0 + self.vehicleID = 0 + self.driftID = 0 + self.timer_data = [Split(0),Split(0),Split(0)] + + def __str__(self): + return ";".join(map(str, self)) + + def __iter__(self): + return iter([self.charaID, self.vehicleID, self.driftID]+self.timer_data) + + @staticmethod + def read_from_string(string): + res = AGCMetaData(useDefault = True) + temp = string.split(";") + + assert len(temp) >= 6 + + res.charaID = int(temp[0]) + res.vehicleID = int(temp[1]) + res.driftID = int(temp[2]) + res.timer_data = [Split.from_string(temp[i]) for i in range(3, len(temp))] + + return res + + @staticmethod + def read_from_RKGMetadata(rkg_metadata): + res = AGCMetaData(useDefault = True) + res.charaID = rkg_metadata.character_id + res.vehicleID = rkg_metadata.vehicle_id + res.driftID = rkg_metadata.drift_id + res.timer_data = [Split(rkg_metadata.lap1_split), + Split(rkg_metadata.lap2_split + rkg_metadata.lap1_split), + Split(rkg_metadata.lap3_split + rkg_metadata.lap2_split + rkg_metadata.lap1_split)] + + return res + + def load(self, write_slot): + addr_player = RaceConfigScenario(RaceConfig.race_scenario()).player(write_slot) + if write_slot == 0: + addr_controller = InputMgr.gc_controller(0) + else: + addr_controller = InputMgr.ghost_controller(write_slot) + + memory.write_u32(addr_player + 0x8, self.vehicleID) + memory.write_u32(addr_player + 0xC, self.charaID) + memory.write_u8(addr_controller + 0x51, self.driftID) + + def delay_timer(self, delay): + delay /= fps_const + region = utils.get_game_id() + try: + address = {"RMCE01": 0x809BFDC0, "RMCP01": 0x809C4680, + "RMCJ01": 0x809C36E0, "RMCK01": 0x809B2CC0} + timer_addr = chase_pointer(address[region], [0x1D0, 0x16C, 0xC], 'u32') + 0x2B8 + except KeyError: + raise RegionError + + + for lap in range(3): + lap_time = self.timer_data[lap].val + if lap > 0: + lap_time -= self.timer_data[lap-1].val #timer_data is cumulative format so you have to substract each cumulative laps + lap_time = max(0, lap_time - delay) + m, s, ms = Split(lap_time).time_format() + memory.write_u16(timer_addr + lap*0xC + 0x4, m) + memory.write_u8(timer_addr + lap*0xC + 0x6, s) + memory.write_u16(timer_addr + lap*0xC + 0x8, ms) + + +class Split: + """Class for a lap split. Contain just a float, representing the split in seconds""" + 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_timer(timer_inst): + return Split(timer_inst.minutes()*60+timer_inst.seconds()+timer_inst.milliseconds()/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) + + @staticmethod + def from_rkg(rkg_addr,lap): + timer_addr = rkg_addr+0x11+0x3*(lap-1) + b = memory.read_bytes(timer_addr, 3) + return Split.from_bytes(b) + + + 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)) + + def time_format_bytes(self): + #return a bytearray of size 6, for the timer format. + #2 first bytes are m, then s on 1 byte, then 00, then ms on 2 bytes + m,s,ms = self.time_format() + return bytearray([m//256, m%256, s%256, 0, ms//256, ms%256]) + + + + +def file_to_framedatalist(filename): + datalist = [] + file = open(filename, 'r') + if file is None : + gui.add_osd_message("Error : could not load "+filename) + return None + else: + listlines = file.readlines() + metadata = AGCMetaData.read_from_string(listlines[0]) + for i in range(1, len(listlines)): + datalist.append(AGCFrameData.read_from_string(listlines[i])) + file.close() + gui.add_osd_message(f"Data successfully loaded from {filename}") + return metadata, datalist + + +def framedatalist_to_file(filename, datalist, metadata): + file = open(filename, 'w') + if file is None : + gui.add_osd_message("Error : could not create "+filename) + else: + file.write(str(metadata)+'\n') + for frame in range(max(datalist.keys())+1): + if frame in datalist.keys(): + file.write(str(datalist[frame])+'\n') + else: + file.write(str(AGCFrameData(usedefault=True))+'\n') + file.close() + + + + diff --git a/scripts/Modules/bruteforcer_lib.py b/scripts/Modules/bruteforcer_lib.py new file mode 100644 index 0000000..009f003 --- /dev/null +++ b/scripts/Modules/bruteforcer_lib.py @@ -0,0 +1,330 @@ +from dolphin import controller, event, savestate + +from Modules import ttk_lib +from Modules.framesequence import FrameSequence, Frame +from Modules import mkw_utils as mkw_utils +from Modules.mkw_classes import RaceManagerPlayer, VehiclePhysics +import random +import os + +def save(name): + b = savestate.save_to_bytes() + f = open(name, 'wb') + f.write(b) + f.close() + +def load(name): + f = open(name, 'rb') + b = f.read() + f.close() + savestate.load_from_bytes(b) + +def prevframe(frame, frequency): + return frame-1 - (frame-2)%frequency + + +class Input: + def __init__(self, A, B=None, L=None, H=None, V=None, D=None): + if B is None: + if type(A) == int: + r = A + self.A = r%2 == 1 + r= r//2 + self.B = r%2 == 1 + r= r//2 + self.L = r%2 == 1 + r= r//2 + self.H = r%15 + r= r//15 + self.V = r%15 + r= r//15 + self.D = r%5 + else : + raise TypeError("When calling Input(i) with 1 argument, i must be int") + else : + self.A = A + self.B = B + self.L = L + self.H = H + self.V = V + self.D = D + + def __int__(self): + r = 0 + r+= int(self.A) + r+= int(self.B)*2 + r+= int(self.L)*4 + r+= int(self.H)*8 + r+= int(self.V)*120 + r+= int(self.D)*1800 + return r + + def __str__(self): + return f"{str(self.A)}, {str(self.B)}, {str(self.L)}, {str(self.H)}, {str(self.V)}, {str(self.D)}" + + +class InputIterable: + def __init__(self, iterable, rule=None): + if rule is None: + rule = lambda x : True + self.rule = rule + try: + inp = next(iterable) + while not rule(inp): + inp = next(iterable) + self.val = inp + self.iterator = iterable + except StopIteration: + self.val = None + self.iterator = iterable + print("Tried to Init a InputIterable with iterator not letting any rule(value)") + + def __next__(self): + self.val = next(self.iterator) + while not self.rule(self.val): + self.val = next(self.iterator) + + + + + +class InputList: + """Class for List of InputRuled iterator + Accessing with an index not in the list will create a new InputRuled iterator + InputList[frame] should be an Input + InputList.inputlist[frame] is an InputIterable + ruleset must be a function : int -> rule + rule type is a function : Input -> bool + iterset must be a function : int -> itergen + itergen type is a function : list(Input) -> Iter(Input) """ + def __init__(self, ruleset, iterset): + self.inputlist = {} + self.ruleset = ruleset + self.iterset = iterset + + def __getitem__(self, index): + if not index in list(self.inputlist.keys()): + if index>0: + iterable = self.iterset(index)([self[index-1]]) + self.inputlist[index] = InputIterable(iterable, self.ruleset(index)) + else: + iterable = self.iterset(index)([]) + self.inputlist[index] = InputIterable(iterable, self.ruleset(index)) + return self.inputlist[index].val + + def __str__(self): + return str([int(self[i]) for i in range(10)]) + + def update(self,frame): + """Update the list. + Return the frame of last modification""" + if frame<0: + raise ValueError('Tried to update a InputList with from a negative frame') + self[frame] + self[frame+1] + self[frame+2] + del self.inputlist[frame+1] + del self.inputlist[frame+2] + try: + next(self.inputlist[frame]) + return frame + except StopIteration: + del self.inputlist[frame] + return self.update(frame-1) + +def first_input_ruled(rule): + for i in range(9000): + if rule(i): + return Input(i) + +def last_input_ruled(rule): + for i in range(8999, -1, -1): + if rule(i): + return Input(i) + + +def simple_order_iterator(l): + for i in range(9000): + yield Input(i) + +def last_input_iterator(l): + j = 0 + if len(l)>0: + j = int(l[0]) + for i in range(9000): + yield Input((i+j)%9000) + +def _123rule(inp): + return int(inp)<4 + +def basic_rule(inp): + #Rule for 3 possible inputs : Gi straight, turn left, turn right + return inp.A and (not inp.B) and (not inp.L) and (inp.H in [0,7,14]) and (inp.V==7) and (inp.D==0) +def forward_rule(inp): + #Rule for 1 possible input : Press A. + return inp.A and (not inp.B) and (not inp.L) and (inp.H ==7) and (inp.V==7) and (inp.D==0) + +forward = Input(True, False, False, 7, 7, 0) +ruleset123 = lambda x : _123rule + +itersetconst = lambda x : simple_order_iterator + +big = InputList(ruleset123, itersetconst) + +def run_input(inp): + gc_input = {} + #trick input + gc_input['Left'] = inp.D==3 + gc_input['Right'] = inp.D==4 + gc_input['Up'] = inp.D==1 + gc_input['Down'] = inp.D==2 + + #button input + gc_input['A'] = inp.A + gc_input['B'] = inp.B + gc_input['L'] = inp.L + + #stick input + match = {0 : 59, + 1 : 68, + 2 : 77, + 3 : 86, + 4 : 95, + 5 : 104, + 6 : 112, + 7 : 128, + 8 : 152, + 9 : 161, + 10 : 170, + 11 : 179, + 12 : 188, + 13 : 197, + 14 : 205} + gc_input['StickX'] = match[inp.H] + gc_input['StickY'] = match[inp.V] + + #Everything else, irrelevant + gc_input['Z'] = False + gc_input['R'] = False + gc_input['X'] = False + gc_input['Y'] = False + gc_input['Start'] = False + gc_input['CStickX'] = 0 + gc_input['CStickY'] = 0 + gc_input['TriggerLeft'] = 0 + gc_input['TriggerRight'] = 0 + gc_input['AnalogA'] = 0 + gc_input['AnalogB'] = 0 + gc_input['Connected'] = True + + controller.set_gc_buttons(0, gc_input) + +def makeFrame(inp): + f = Frame([str(int(i)) for i in [inp.A, inp.B, inp.L, inp.H-7, inp.V-7, inp.D]]) + return f + +def run_input2(inp): + f = makeFrame(inp) + ttk_lib.write_player_inputs(f) + + +class Randomizer_Raw_Stick: + ''' Class for making random modification to a FrameSequence + Only modify 1 stick input at a time + + direction : either horizontal or vertical + frame is the frame of input you want to randomize + probability is the probability of a random modification actually happenning when calling the random method + modif_range is the max amplitude of the modification. + example with 2, you can only change a +4 stick input to {+2, +3, +4, +5, +6}''' + def __init__(self, direction, frame, probability = 0.1, modif_range = 15): + self.direction = direction + self.proba = probability + self.range = modif_range + self.frame = frame + + def random(self, inputList : FrameSequence): + '''THIS FUNCTION MODIFIES inputList''' + if self.frame < len(inputList.frames): + if random.random() < self.proba: + baseFrame = inputList.frames[self.frame] + if self.direction in ["X", "x", "horizontal"]: + baseFrame.stick_x = random.randint(max(-7, baseFrame.stick_x - self.range), min(7, baseFrame.stick_x + self.range)) + elif self.direction in ["Y", "y", "vertical"]: + baseFrame.stick_y = random.randint(max(-7, baseFrame.stick_y - self.range), min(7, baseFrame.stick_y + self.range)) + else: + raise IndexError(f'Index {self.direction} is not supported yet') + +class Randomizer_Alternate_Stick: + ''' Class for making random modification to a FrameSequence + Modify 2 consecutive inputs with +d/-d + Example : +5, -3 -> +4, -2. + + direction : either horizontal or vertical + frame is the frame of input you want to randomize + probability is the probability of a random modification actually happenning when calling the random method + modif_range is the max amplitude of the modification. + example with 2, you can only change a +4 stick input to {+2, +3, +4, +5, +6} + ''' + + def __init__(self, direction, frame, probability = 0.1, modif_range = 15): + self.direction = direction + self.proba = probability + self.range = modif_range + self.frame = frame + + def random(self, inputList : FrameSequence): + '''THIS FUNCTION MODIFIES inputList''' + if self.frame + 1 < len(inputList.frames): + if random.random() < self.proba: + baseFrame1 = inputList.frames[self.frame] + baseFrame2 = inputList.frames[self.frame+1] + shift = random.randint(1, self.range) + sign = random.randint(0,1)*2 -1 #either -1 or 1 + if self.direction in ["X", "x", "horizontal"]: + if sign == 1: + max1 = 7 - baseFrame1.stick_x + max2 = 7 + baseFrame2.stick_x + else: + max1 = 7 + baseFrame1.stick_x + max2 = 7 - baseFrame2.stick_x + max_all = min(max1, max2, shift) + baseFrame1.stick_x = baseFrame1.stick_x + sign * max_all + baseFrame2.stick_x = baseFrame2.stick_x - sign * max_all + elif self.direction in ["Y", "y", "vertical"]: + if sign == 1: + max1 = 7 - baseFrame1.stick_y + max2 = 7 + baseFrame2.stick_y + else: + max1 = 7 + baseFrame1.stick_y + max2 = 7 - baseFrame2.stick_y + max_all = min(max1, max2, shift) + baseFrame1.stick_y = baseFrame1.stick_y + sign * max_all + baseFrame2.stick_y = baseFrame2.stick_y - sign * max_all + else: + raise IndexError(f'Index {self.direction} is not supported yet') + + +def score_racecomp(frame_limit): + def res(): + frame = mkw_utils.frame_of_input() + if frame < frame_limit : + return None + return RaceManagerPlayer(0).race_completion() + return res + +def score_XZ_EV(frame_limit): + def res(): + frame = mkw_utils.frame_of_input() + if frame < frame_limit : + return None + return VehiclePhysics(0).external_velocity().length_xz() + return res + +def score_Z_position(frame_limit, negative=False): + def res(): + frame = mkw_utils.frame_of_input() + if frame < frame_limit : + return None + return VehiclePhysics(0).position().z * -1 if negative else 1 + return res diff --git a/scripts/Modules/default.mii b/scripts/Modules/default.mii new file mode 100644 index 0000000..55ae733 Binary files /dev/null and b/scripts/Modules/default.mii differ diff --git a/scripts/Modules/framesequence.py b/scripts/Modules/framesequence.py index 99c48fc..53faac1 100644 --- a/scripts/Modules/framesequence.py +++ b/scripts/Modules/framesequence.py @@ -1,4 +1,9 @@ +from dolphin import gui + +from pathlib import Path from typing import List, Optional +from .mkw_classes import KartInput +from .mkw_classes import RaceManagerPlayer, RaceInputState import csv @@ -22,6 +27,8 @@ class Frame: accel: bool brake: bool item: bool + drift: bool + brakedrift: bool stick_x: int stick_y: int @@ -43,9 +50,11 @@ def __init__(self, raw: List): * raw[0] (str) - A * raw[1] (str) - B/R * raw[2] (str) - L - * raw[3] (str) - Horizontal stick - * raw[4] (str) - Vertical stick - * raw[5] (str) - Dpad + * raw[3] (str) - Drift "ghost" button + * raw[4] (str) - BrakeDrift "ghost" button + * raw[5] (str) - Horizontal stick + * raw[6] (str) - Vertical stick + * raw[7] (str) - Dpad Args: raw (List): CSV line to be read @@ -55,9 +64,12 @@ def __init__(self, raw: List): self.accel = self.read_button(raw[0]) self.brake = self.read_button(raw[1]) self.item = self.read_button(raw[2]) - self.stick_x = self.read_stick(raw[3]) - self.stick_y = self.read_stick(raw[4]) - self.read_dpad(raw[5]) + self.drift = self.read_button(raw[3]) + self.brakedrift = self.read_button(raw[4]) + self.stick_x = self.read_stick(raw[5]) + self.stick_y = self.read_stick(raw[6]) + self.read_dpad(raw[7]) + def __iter__(self): self.iter_idx = 0 @@ -73,13 +85,46 @@ def __next__(self): if (self.iter_idx == 3): return int(self.item) if (self.iter_idx == 4): - return self.stick_x + return int(self.drift) if (self.iter_idx == 5): - return self.stick_y + return int(self.brakedrift) if (self.iter_idx == 6): + return self.stick_x + if (self.iter_idx == 7): + return self.stick_y + if (self.iter_idx == 8): return self.dpad_raw() + raise StopIteration + @staticmethod + def from_current_frame(slot): + race_mgr_player = RaceManagerPlayer(slot) + kart_input = KartInput(addr=race_mgr_player.kart_input()) + current_input_state = RaceInputState(addr=kart_input.current_input_state()) + ablr = current_input_state.buttons().value + dpad = current_input_state.trick() + xstick = current_input_state.raw_stick_x() - 7 + ystick = current_input_state.raw_stick_y() - 7 + aButton = ablr & 1 + bButton = (ablr & 2) >> 1 + lButton = (ablr & 4) >> 2 + dButton = (ablr & 8) >> 3 + bdButton = (ablr & 16) >> 4 + + return Frame([aButton, bButton, lButton, dButton, bdButton, xstick, ystick, dpad]) + + + @staticmethod + def default(): + return Frame([0,0,0,0,0,0,0,0]) + + def __str__(self): + return ','.join(map(str, self)) + + def copy(self): + return Frame(str(self).split(',')) + def read_button(self, button: str) -> bool: """ Parses the button input into a boolean. Sets `self.valid` to False if invalid. @@ -142,7 +187,86 @@ def dpad_raw(self) -> int: return 4 return 0 +def compressInputList(rawInputList): + """A function that convert Raw Input List (List of Frames) to + Compressed Input List (List of 7-int-list, TTK .csv format)""" + compressedInputList = [] + prevInputRaw = Frame(["0"]*8) + prevInputCompressed = [0,0,0,0,0,0,-1] + for rawInput in rawInputList: + compressedInput = [int(rawInput.accel), + 0, + int(rawInput.item), + rawInput.stick_x, + rawInput.stick_y, + rawInput.dpad_raw(), + -1] + if not rawInput.brake: + compressedInput[1] = 0 + elif rawInput.brakedrift: + compressedInput[1] = 3 + elif not prevInputRaw.brake: + compressedInput[1] = 1 + elif rawInput.drift and not prevInputRaw.drift: + compressedInput[1] = 3-prevInputCompressed[1] + else: + compressedInput[1] = prevInputCompressed[1] + + if rawInput.accel and rawInput.brake and (not rawInput.drift): + if prevInputRaw.accel and prevInputRaw.brake and prevInputRaw.drift: + compressedInput[6] = 0 + elif not prevInputRaw.brake: + compressedInput[6] = 0 + + if ((not rawInput.accel) or (not rawInput.brake)) and rawInput.drift: + compressedInput[6] = 1 + + prevInputRaw = rawInput + prevInputCompressed = compressedInput + compressedInputList.append(compressedInput) + return compressedInputList +def decompressInputList(compressedInputList): + """A function that convert Compressed Input List (List of 7-int-list, TTK .csv format) to + Raw Input List (List of Frames)""" + prevInputRaw = Frame(["0"]*8) + prevInputCompressed = [0,0,0,0,0,0,-1] + rawInputList = [] + line = 1 + for compressedInput in compressedInputList: + accel = compressedInput[0] + brake = int(compressedInput[1]>0) + item = compressedInput[2] + X = compressedInput[3] + Y = compressedInput[4] + dpad = compressedInput[5] + brakedrift = int(compressedInput[1]==3) + + if accel + brake < 2: + drift = 0 + elif prevInputRaw.drift: + drift = 1 + elif prevInputCompressed[1] == compressedInput[1]: + drift = 0 + else: + drift = 1 + + if compressedInput[6] != -1: + drift = compressedInput[6] + + rawInput = Frame(list(map(str, (accel, brake, item, drift, brakedrift, X, Y, dpad)))) + if not rawInput.valid: + print(f'Error in the csv file at line {line}') + gui.add_osd_message(f'Error in the csv file at line {line}') + return rawInputList + prevInputRaw = rawInput + prevInputCompressed = compressedInput + rawInputList.append(rawInput) + line += 1 + return rawInputList + + + class FrameSequence: """ A class representing a sequence of inputs, indexed by frames. @@ -179,6 +303,12 @@ def __next__(self): if (self.iter_idx < len(self.frames)): return self.frames[self.iter_idx] raise StopIteration + + def copy(self) -> 'FrameSequence': + res = FrameSequence() + for frame in self.frames: + res.frames.append(frame.copy()) + return res def read_from_list(self, inputs: List) -> None: """ @@ -193,6 +323,17 @@ def read_from_list(self, inputs: List) -> None: if not frame: pass self.frames.append(frame) + + def read_from_list_of_frames(self, inputs: List) -> None: + """ + Constructs the frames list by using a list of Frames instead of a csv + + Args: + input (List): The raw input data we want to store + Returns: None + """ + for frame in inputs: + self.frames.append(frame) def read_from_file(self) -> None: """ @@ -204,14 +345,23 @@ def read_from_file(self) -> None: self.frames.clear() try: with open(self.filename, 'r') as f: - reader = csv.reader(f) - for row in reader: - frame = self.process(row) - if not frame: - # TODO: Handle error - pass - - self.frames.append(frame) + compressedInputList = [] + line = 1 + for row in f.readlines(): + args = row.strip('\n').split(',') + if len(args) not in [6, 7]: + print(f'Error in the csv file at line {line}') + gui.add_osd_message(f'Error in the csv file at line {line}') + return + try: + compressedInputList.append(list(map(int,args))) + except ValueError: + print(f'Error in the csv file at line {line}') + gui.add_osd_message(f'Error in the csv file at line {line}') + return + line += 1 + for frame in decompressInputList(compressedInputList): + self.frames.append(frame) except IOError: return @@ -225,9 +375,11 @@ def write_to_file(self, filename: str) -> bool: A boolean indicating whether the write was successful """ try: + output_file = Path(filename) + output_file.parent.mkdir(exist_ok=True, parents=True) with open(filename, 'w', newline='') as f: writer = csv.writer(f, delimiter=',') - writer.writerows(self.frames) + writer.writerows(compressInputList(self.frames)) except IOError: return False return True @@ -244,7 +396,8 @@ def process(self, raw_frame: List) -> Optional[Frame]: A new Frame object initialized with the raw frame, or None if the frame is invalid. """ - if len(raw_frame) != 6: + assert len(raw_frame) == 8 + if len(raw_frame) != 8: return None frame = Frame(raw_frame) diff --git a/scripts/Modules/infodisplay_utils.py b/scripts/Modules/infodisplay_utils.py new file mode 100644 index 0000000..8c4bceb --- /dev/null +++ b/scripts/Modules/infodisplay_utils.py @@ -0,0 +1,602 @@ +import configparser +import math +import os +from datetime import datetime + +from Modules.mkw_classes.common import SurfaceProperties, eulerAngle, quatf, vec3 +from Modules.mkw_utils import History +from dolphin import gui, utils +from Modules import settings_utils as setting +from Modules import mkw_utils as mkw_utils +from Modules import ttk_lib as ttk_lib +from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState, TimerManager +from Modules.mkw_classes import RaceConfig, RaceConfigScenario, RaceConfigSettings, RaceConfigPlayer, RaceConfigPlayerType +from Modules.mkw_classes import KartObject, KartMove, KartSettings, KartBody +from Modules.mkw_classes import VehicleDynamics, VehiclePhysics, KartBoost, KartJump, KartSub +from Modules.mkw_classes import KartState, KartCollide, KartInput, RaceInputState, KartObjectManager + + +def make_line_text_speed(left_text_prefix, left_text_suffix, size, speed, digits): + """Function to generate a line of text + It has "left_text" as a str on the left, + enough spaces to make the text on the left exactly size length + then it has ":" followed by the speed, finished with a \n. + Param: str left_text + int size + float speed + Return str text""" + return left_text_prefix+" "*(size - len(left_text_prefix+left_text_suffix))+left_text_suffix + f"{speed:.{digits}f}\n" + +def make_text_speed(speed, speedname, player, boolspd, boolspdoriented, boolspdxyz, digits): + """Function to generate the text for a certain speed + Parameters : vec3 speed : the speed to generate the text for. + str speedname : the string to write before each line + int player : ID of the player (used for oriented speed, 0 if player) + bool boolspd : True if we draw the (X, Y, Z) speed + bool boolspdoriented : True if we draw (Forward, Sideway, Y) + bool boolspdxyz : True if we draw (XZ, XYZ) + Return str text ready to be displayed""" + text = "" + facing_yaw = mkw_utils.get_facing_angle(player).yaw + offset_size = 13 + if boolspd and boolspdoriented : + text += make_line_text_speed(speedname,"X: ", offset_size, speed.x, digits) + text += make_line_text_speed(speedname,"Y: ", offset_size, speed.y, digits) + text += make_line_text_speed(speedname,"Z: ", offset_size, speed.z, digits) + text += make_line_text_speed(speedname,"Forward: ", offset_size, speed.forward(facing_yaw), digits) + text += make_line_text_speed(speedname,"Sideway: ", offset_size, speed.sideway(facing_yaw), digits) + elif boolspd : + text += make_line_text_speed(speedname,"X: ", offset_size, speed.x, digits) + text += make_line_text_speed(speedname,"Y: ", offset_size, speed.y, digits) + text += make_line_text_speed(speedname,"Z: ", offset_size, speed.z, digits) + elif boolspdoriented : + text += make_line_text_speed(speedname,"Forward: ", offset_size, speed.forward(facing_yaw), digits) + text += make_line_text_speed(speedname,"Sideway: ", offset_size, speed.sideway(facing_yaw), digits) + text += make_line_text_speed(speedname,"Y: ", offset_size, speed.y, digits) + if boolspdxyz : + text += make_line_text_speed(speedname,"XZ: ", offset_size, speed.length_xz(), digits) + text += make_line_text_speed(speedname,"XYZ: ", offset_size, speed.length(), digits) + return text + +def make_text_speeddiff(playerSpeed, ghostSpeed, prefix_text, digits): + xyz = playerSpeed.length() - ghostSpeed.length() + xz = playerSpeed.length_xz() - ghostSpeed.length_xz() + y = playerSpeed.y - ghostSpeed.y + return f"{prefix_text} XYZ: {xyz:.{digits}f} XZ: {xz:.{digits}f} Y: {y:.{digits}f}" + +def make_text_rotdiff(rotdiff, prefix_text, digits): + return f"{prefix_text}: {rotdiff:.{digits}f}" + + +def make_text_timediff(timediff, prefix_text, prefix_size, timesize, digits): + timediffms = timediff/59.94 + ms = f"{timediffms:.{digits}f}" + frame = f"{timediff:.{digits}f}" + ms += " "*(timesize - len(ms)) + ms = ms[:timesize] + frame = frame[:timesize] + if timediff == 0: + extra = " (Tied)" + elif timediff > 0: + extra = " (Behind)" + else: + extra = " (Ahead)" + return prefix_text+":"+" "*(prefix_size - len(prefix_text))+ms+"| "+frame+extra+"\n" + + +def make_text_rotation(rot, rotspd, prefix_text, prefix_size, rotsize, digits): + rot_text = f"{rot:.{digits}f}" + rotspd_text = f"{rotspd:.{digits}f}" + rot_text += " "*(rotsize - len(rot_text)) + rot_text = rot_text[:rotsize] + rotspd_text = rotspd_text[:rotsize] + return prefix_text+":"+" "*(prefix_size - len(prefix_text))+rot_text+"| "+rotspd_text+"\n" + +def create_infodisplay(c, RaceComp_History, Angle_History): + text = "" + + race_mgr_player = RaceManagerPlayer() + race_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario()) + race_config_player = RaceConfigPlayer(addr=race_scenario.player()) + race_settings = RaceConfigSettings(race_scenario.settings()) + kart_object = KartObject() + kart_state = KartState(addr=kart_object.kart_state()) + kart_move = KartMove(addr=kart_object.kart_move()) + kart_body = KartBody(addr=kart_object.kart_body()) + vehicle_dynamics = VehicleDynamics(addr=kart_body.vehicle_dynamics()) + vehicle_physics = VehiclePhysics(addr=vehicle_dynamics.vehicle_physics()) + + if c.debug : + the_quat = VehiclePhysics.main_rotation(0) + the_vec = VehiclePhysics.external_velocity(0) + + up = kart_move.up() + floor_quat = quatf.from_vectors(vec3(0,1,0), up) + value = the_quat @ the_vec + value2 = the_quat.conjugate() @ the_vec + angles = eulerAngle.from_quaternion((the_quat*floor_quat).normalize()) + rpi = mkw_utils.get_relative_angles(eulerAngle.from_quaternion(the_quat), up) + #angles = eulerAngle.from_quaternion(floor_quat*the_quat) + text += f"""Debug : +{value.x:.2f}, {value.y:.2f}, {value.z:.2f} +{value2.x:.2f}, {value2.y:.2f}, {value2.z:.2f} +{rpi:.4f}, {angles.yaw:.4f}, {angles.roll:.4f}\n""" + + newline = False + if c.frame_count: + newline = True + text += f"Frame: {mkw_utils.frame_of_input()}\n" + + if c.rkg_buffer_size and race_config_player.type() == RaceConfigPlayerType.REAL_LOCAL: + newline = True + value = 100*ttk_lib.get_full_rkg_size(ttk_lib.PlayerType.PLAYER)/0x13b0 + text += f"RKG Buffer : {value:2.3f}%\n" + + if newline: + text += '\n' + newline = False + if c.lap_splits: + for lap in range(1, math.floor(race_mgr_player.race_completion_max())): + exact_finish = mkw_utils.read_exact_finish(lap) + text += "Lap {}: {}".format(lap, exact_finish- (mkw_utils.read_exact_finish(lap-1) if (lap>1) else 0)) + needed_diff = mkw_utils.calculate_extra_finish_data(exact_finish) + text += f" ({needed_diff[1]} / +{needed_diff[0]})" + text += "\n" + if RaceManager.state().value > 2: + lap = math.floor(race_mgr_player.race_completion_max())-1 + exact_finish = mkw_utils.read_exact_finish(lap) + text += f"Total: {exact_finish}" + needed_diff = mkw_utils.calculate_extra_finish_data(exact_finish) + text += f" ({needed_diff[1]} / +{needed_diff[0]})" + text += "\n" + text += "\n" + if c.speed: + speed = mkw_utils.delta_position(playerIdx=0) + engine_speed = kart_move.speed() + cap = kart_move.soft_speed_limit() + text += make_text_speed(speed, "", 0, False, c.speed_oriented, c.speed, c.digits) + text += f" Engine: {engine_speed:.{c.digits}f} / {cap:.{c.digits}f}\n" + text += "\n" + + if (c.iv or c.iv_xyz or c.iv_oriented): + iv = vehicle_physics.internal_velocity() + text += make_text_speed(iv, "IV ", 0, c.iv, c.iv_oriented, c.iv_xyz, c.digits) + text += "\n" + + if (c.ev or c.ev_xyz or c.ev_oriented): + ev = vehicle_physics.external_velocity() + text += make_text_speed(ev, "EV ", 0, c.ev, c.ev_oriented, c.ev_xyz, c.digits) + text += "\n" + + if (c.mrv or c.mrv_xyz or c.mrv_oriented): + mrv = vehicle_physics.moving_road_velocity() + text += make_text_speed(mrv, "MRV ", 0, c.mrv, c.mrv_oriented, c.mrv_xyz, c.digits) + text += "\n" + + if (c.mwv or c.mwv_xyz or c.mwv_oriented): + mwv = vehicle_physics.moving_water_velocity() + text += make_text_speed(mwv, "MWV ", 0, c.mwv, c.mwv_oriented, c.mwv_xyz, c.digits) + text += "\n" + + if c.charges or c.misc: + kart_settings = KartSettings(addr=kart_object.kart_settings()) + + if c.charges: + kart_boost = KartBoost(addr=kart_move.kart_boost()) + + mt = kart_move.mt_charge() + smt = kart_move.smt_charge() + ssmt = kart_move.ssmt_charge() + mt_boost = kart_move.mt_boost_timer() + trick_boost = kart_boost.trick_and_zipper_timer() + shroom_boost = kart_move.mushroom_timer() + if kart_settings.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 = race_mgr_player.lap_completion() + race_comp = race_mgr_player.race_completion() + cp = race_mgr_player.checkpoint_id() + kcp = race_mgr_player.max_kcp() + rp = race_mgr_player.respawn() + text += f" Lap%: {lap_comp:.{c.digits}f}\n" + text += f"Race%: {race_comp:.{c.digits}f}\n" + text += f"CP: {cp} | KCP: {kcp} | RP: {rp}\n\n" + + if c.air: + airtime = kart_move.airtime() + text += f"Airtime: {airtime}\n\n" + + if c.misc or c.surfaces: + kart_collide = KartCollide(addr=kart_object.kart_collide()) + + if c.misc: + kart_jump = KartJump(addr=kart_move.kart_jump()) + trick_cd = kart_jump.cooldown() + hwg_timer = kart_state.hwg_timer() + gcf = kart_collide.glitchy_corner() + oob_timer = kart_collide.solid_oob_timer() + respawn_timer = kart_collide.time_before_respawn() + offroad_inv = kart_move.offroad_invincibility() + if kart_move.is_bike: + text += f"Wheelie Length: {kart_move.wheelie_frames()}\n" + text += f"Wheelie CD: {kart_move.wheelie_cooldown()} | " + text += f"Trick CD: {trick_cd}\n" + text += f"HWG: {hwg_timer} | GCF: {gcf:.{c.digits}f}\n" + text += f"Respawn: {respawn_timer} | OOB: {oob_timer}\n" + text += f"Offroad: {offroad_inv}\n\n" + + if c.surfaces: + surface_properties = kart_collide.surface_properties() + is_offroad = (surface_properties.value & SurfaceProperties.OFFROAD) > 0 + is_trickable = (surface_properties.value & SurfaceProperties.TRICKABLE) > 0 + kcl_speed_mod = kart_move.kcl_speed_factor() + text += f" Offroad: {is_offroad}\n" + text += f"Trickable: {is_trickable}\n" + text += f"KCL Speed Modifier: {kcl_speed_mod * 100:.{c.digits}f}%\n\n" + + if c.position: + pos = vehicle_physics.position() + text += f"X Pos: {pos.x}\n" + text += f"Y Pos: {pos.y}\n" + text += f"Z Pos: {pos.z}\n\n" + + if c.rotation : + fac = mkw_utils.get_facing_angle(0) + mov = mkw_utils.get_moving_angle(0) + if len(Angle_History) > 1: + prevfac = Angle_History[1]['facing'] + prevmov = Angle_History[1]['moving'] + else: + prevfac = mkw_utils.get_facing_angle(0) + prevmov = mkw_utils.get_moving_angle(0) + facdiff = fac - prevfac + movdiff = mov - prevmov + prefix_size = 10 + rotsize = c.digits+4 + text += " "*(prefix_size+1)+"Rotation"+" "*(rotsize - 8)+"| Speed\n" + text += make_text_rotation(fac.pitch, facdiff.pitch, "Pitch", prefix_size, rotsize, c.digits) + text += make_text_rotation(fac.yaw, facdiff.yaw, "Yaw", prefix_size, rotsize, c.digits) + text += make_text_rotation(mov.yaw, movdiff.yaw, "Moving Y", prefix_size, rotsize, c.digits) + text += make_text_rotation(fac.roll, facdiff.roll, "Roll", prefix_size, rotsize, c.digits) + text += "\n" + if (c.dpg or c.dpg_xyz or c.dpg_oriented) and not mkw_utils.is_single_player() : + dpg = VehiclePhysics.position(1) - VehiclePhysics.position(0) + text += make_text_speed(dpg, "Dist PG ", 0, c.dpg, c.dpg_oriented, c.dpg_xyz, c.digits) + text += "\n" + + newline = False + if (c.vd_spd and not mkw_utils.is_single_player()): + newline = True + text += make_text_speeddiff(mkw_utils.delta_position(playerIdx=0), mkw_utils.delta_position(playerIdx=1), 'Spd Diff', c.digits) + text += "\n" + + if (c.vd_iv and not mkw_utils.is_single_player()): + newline = True + text += make_text_speeddiff(VehiclePhysics.internal_velocity(0), VehiclePhysics.internal_velocity(1), ' IV Diff', c.digits) + text += "\n" + + if (c.vd_ev and not mkw_utils.is_single_player()): + newline = True + text += make_text_speeddiff(VehiclePhysics.external_velocity(0), VehiclePhysics.external_velocity(1), ' EV Diff', c.digits) + text += "\n" + + if (not mkw_utils.is_single_player()) and (c.rd_pitch or c.rd_yaw or c.rd_roll or c.rd_movy): + facdiff = mkw_utils.get_facing_angle(0) - mkw_utils.get_facing_angle(1) + movdiff = mkw_utils.get_moving_angle(0) - mkw_utils.get_moving_angle(1) + + if (c.rd_pitch and not mkw_utils.is_single_player()): + newline = True + text += make_text_rotdiff(facdiff.pitch, "Pitch diff", c.digits) + text += "\n" + + if (c.rd_yaw and not mkw_utils.is_single_player()): + newline = True + text += make_text_rotdiff(facdiff.yaw, " Yaw diff", c.digits) + text += "\n" + + if (c.rd_movy and not mkw_utils.is_single_player()): + newline = True + text += make_text_rotdiff(movdiff.yaw, " MovY diff", c.digits) + text += "\n" + + if (c.rd_roll and not mkw_utils.is_single_player()): + newline = True + text += make_text_rotdiff(facdiff.roll, " Roll diff", c.digits) + text += "\n" + + if newline: + text += "\n" + newline = False + + if c.td and not mkw_utils.is_single_player(): + size = 10 + timesize = c.digits+4 + p1, p2 = mkw_utils.get_timediff_settings(c.td_set) + s = 1 if 1-p1 else -1 + text += "TimeDiff:"+" "*(timesize+size-16)+"Seconds | Frames\n" + if c.td_absolute: + absolute = mkw_utils.get_time_difference_absolute(p1,p2) + text += make_text_timediff(absolute, "Absolute", size, timesize, c.digits) + if c.td_relative: + relative = s*mkw_utils.get_time_difference_relative(p1,p2) + text += make_text_timediff(relative, "Relative", size, timesize, c.digits) + if c.td_projected: + projected = s*mkw_utils.get_time_difference_projected(p1,p2) + text += make_text_timediff(projected, "Projected", size, timesize, c.digits) + if c.td_crosspath: + crosspath = s*mkw_utils.get_time_difference_crosspath(p1,p2) + text += make_text_timediff(crosspath, "CrossPath", size, timesize, c.digits) + if c.td_tofinish: + tofinish = s*mkw_utils.get_time_difference_tofinish(p1,p2) + text += make_text_timediff(tofinish, "ToFinish", size, timesize, c.digits) + if c.td_racecomp: + racecomp = mkw_utils.get_time_difference_racecompletion(RaceComp_History) + text += make_text_timediff(racecomp, "RaceComp", size, timesize, c.digits) + text += "\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: + kart_input = KartInput(addr=race_mgr_player.kart_input()) + current_input_state = RaceInputState(addr=kart_input.current_input_state()) + + stick_x = current_input_state.raw_stick_x() - 7 + stick_y = current_input_state.raw_stick_y() - 7 + text += f"X: {stick_x} | Y: {stick_y}\n\n" + + return text + + + +def make_text_speed_fr(speed, speedname, player, boolspd, boolspdoriented, boolspdxyz, digits): + """french version of make_text_speed""" + text = "" + facing_yaw = mkw_utils.get_facing_angle(player).yaw + offset_size = 13 + if boolspd and boolspdoriented : + text += make_line_text_speed(speedname,"X: ", offset_size, speed.x, digits) + text += make_line_text_speed(speedname,"Y: ", offset_size, speed.y, digits) + text += make_line_text_speed(speedname,"Z: ", offset_size, speed.z, digits) + text += make_line_text_speed(speedname,"Frontale: ", offset_size, speed.forward(facing_yaw), digits) + text += make_line_text_speed(speedname,"Latérale: ", offset_size, speed.sideway(facing_yaw), digits) + elif boolspd : + text += make_line_text_speed(speedname,"X: ", offset_size, speed.x, digits) + text += make_line_text_speed(speedname,"Y: ", offset_size, speed.y, digits) + text += make_line_text_speed(speedname,"Z: ", offset_size, speed.z, digits) + elif boolspdoriented : + text += make_line_text_speed(speedname,"Frontale: ", offset_size, speed.forward(facing_yaw), digits) + text += make_line_text_speed(speedname,"Latérale: ", offset_size, speed.sideway(facing_yaw), digits) + text += make_line_text_speed(speedname,"Y: ", offset_size, speed.y, digits) + if boolspdxyz : + text += make_line_text_speed(speedname,"XZ: ", offset_size, speed.length_xz(), digits) + text += make_line_text_speed(speedname,"XYZ: ", offset_size, speed.length(), digits) + return text + + +def create_infodisplay_fr(c, RaceComp_History, Angle_History): + text = "" + + race_mgr_player = RaceManagerPlayer() + race_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario()) + race_settings = RaceConfigSettings(race_scenario.settings()) + kart_object = KartObject() + kart_state = KartState(addr=kart_object.kart_state()) + kart_move = KartMove(addr=kart_object.kart_move()) + kart_body = KartBody(addr=kart_object.kart_body()) + vehicle_dynamics = VehicleDynamics(addr=kart_body.vehicle_dynamics()) + vehicle_physics = VehiclePhysics(addr=vehicle_dynamics.vehicle_physics()) + + if c.debug : + value = mkw_utils.delta_position(0) - VehiclePhysics.speed(0) + text += f"Débogage : {value.length()}\n" + + if c.frame_count: + text += f"Image: {mkw_utils.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(race_mgr_player.race_completion_max()) + lap_count = race_settings.lap_count() + + if player_max_lap >= 2 and lap_count > 1: + for lap in range(1, player_max_lap): + text += "Tour {}: {}\n".format(lap, mkw_utils.update_exact_finish(lap, 0)) + + if player_max_lap > lap_count: + text += "Dernier: {}\n".format(mkw_utils.get_unrounded_time(lap_count, 0)) + text += "\n" + + if c.speed: + speed = mkw_utils.delta_position(playerIdx=0) + engine_speed = kart_move.speed() + cap = kart_move.soft_speed_limit() + text += make_text_speed_fr(speed, "", 0, False, c.speed_oriented, c.speed, c.digits) + text += f" Moteur: {round(engine_speed, c.digits)} / {round(cap, c.digits)}\n" + text += "\n" + + if (c.iv or c.iv_xyz or c.iv_oriented): + iv = vehicle_physics.internal_velocity() + text += make_text_speed_fr(iv, "VI ", 0, c.iv, c.iv_oriented, c.iv_xyz, c.digits) + text += "\n" + + if (c.ev or c.ev_xyz or c.ev_oriented): + ev = vehicle_physics.external_velocity() + text += make_text_speed_fr(ev, "VE ", 0, c.ev, c.ev_oriented, c.ev_xyz, c.digits) + text += "\n" + + if (c.mrv or c.mrv_xyz or c.mrv_oriented): + mrv = vehicle_physics.moving_road_velocity() + text += make_text_speed_fr(mrv, "ROUTE ", 0, c.mrv, c.mrv_oriented, c.mrv_xyz, c.digits) + text += "\n" + + if (c.mwv or c.mwv_xyz or c.mwv_oriented): + mwv = vehicle_physics.moving_water_velocity() + text += make_text_speed_fr(mwv, "EAU ", 0, c.mwv, c.mwv_oriented, c.mwv_xyz, c.digits) + text += "\n" + if c.charges or c.misc: + kart_settings = KartSettings(addr=kart_object.kart_settings()) + + if c.charges: + kart_boost = KartBoost(addr=kart_move.kart_boost()) + + mt = kart_move.mt_charge() + smt = kart_move.smt_charge() + ssmt = kart_move.ssmt_charge() + mt_boost = kart_move.mt_boost_timer() + trick_boost = kart_boost.trick_and_zipper_timer() + shroom_boost = kart_move.mushroom_timer() + if kart_settings.is_bike(): + text += f"Jauge MT: {mt} | Jauge SPMT: {ssmt}\n" + else: + text += f"Jauge MT: {mt} ({smt}) | Jauge SPMT: {ssmt}\n" + + text += f"MT: {mt_boost} | Figure: {trick_boost} | Champi: {shroom_boost}\n\n" + + if c.cps: + lap_comp = race_mgr_player.lap_completion() + race_comp = race_mgr_player.race_completion() + cp = race_mgr_player.checkpoint_id() + kcp = race_mgr_player.max_kcp() + rp = race_mgr_player.respawn() + text += f" Tour%: {round(lap_comp,c.digits)}\n" + text += f"Course%: {round(race_comp,c.digits)}\n" + text += f"PS: {cp} | PSC: {kcp} | PR: {rp}\n\n" + + if c.air: + airtime = kart_move.airtime() + text += f"Temps Aérien: {airtime}\n\n" + + if c.misc or c.surfaces: + kart_collide = KartCollide(addr=kart_object.kart_collide()) + + if c.misc: + kart_jump = KartJump(addr=kart_move.kart_jump()) + trick_cd = kart_jump.cooldown() + hwg_timer = kart_state.hwg_timer() + oob_timer = kart_collide.solid_oob_timer() + respawn_timer = kart_collide.time_before_respawn() + offroad_inv = kart_move.offroad_invincibility() + if kart_move.is_bike: + text += f"Durée Roue Arrière: {kart_move.wheelie_frames()}\n" + text += f"Refroidissement Roue Arrière: {kart_move.wheelie_cooldown()} | " + text += f"Refroidissement Figure: {trick_cd}\n" + text += f"BMH: {hwg_timer} | HL: {oob_timer}\n" + text += f"Réinvocation: {respawn_timer}\n" + text += f"Hors-piste: {offroad_inv}\n\n" + + if c.surfaces: + surface_properties = kart_collide.surface_properties() + is_offroad = (surface_properties.value & SurfaceProperties.OFFROAD) > 0 + is_trickable = (surface_properties.value & SurfaceProperties.TRICKABLE) > 0 + kcl_speed_mod = kart_move.kcl_speed_factor() + text += f" Hors-Piste: {is_offroad}\n" + text += f"Figure possible: {is_trickable}\n" + text += f"KCL modificateur vitesse: {round(kcl_speed_mod * 100, c.digits)}%\n\n" + + if c.position: + pos = vehicle_physics.position() + text += f"X Pos: {pos.x}\n" + text += f"Y Pos: {pos.y}\n" + text += f"Z Pos: {pos.z}\n\n" + + if c.rotation : + fac = mkw_utils.get_facing_angle(0) + mov = mkw_utils.get_moving_angle(0) + if len(Angle_History) > 1: + prevfac = Angle_History[1]['facing'] + prevmov = Angle_History[1]['moving'] + else: + prevfac = mkw_utils.get_facing_angle(0) + prevmov = mkw_utils.get_moving_angle(0) + facdiff = fac - prevfac + movdiff = mov - prevmov + prefix_size = 10 + rotsize = c.digits+4 + text += " "*(prefix_size+1)+"Rotation"+" "*(rotsize - 8)+"| Vitesse\n" + text += make_text_rotation(fac.pitch, facdiff.pitch, "Tangage", prefix_size, rotsize, c.digits) + text += make_text_rotation(fac.yaw, facdiff.yaw, "Lacet", prefix_size, rotsize, c.digits) + text += make_text_rotation(mov.yaw, movdiff.yaw, "Dép. Y", prefix_size, rotsize, c.digits) + text += make_text_rotation(fac.roll, facdiff.roll, "Roulis", prefix_size, rotsize, c.digits) + text += "\n" + if (c.dpg or c.dpg_xyz or c.dpg_oriented) and not mkw_utils.is_single_player() : + dpg = VehiclePhysics.position(1) - VehiclePhysics.position(0) + text += make_text_speed(dpg, "Dist JF ", 0, c.dpg, c.dpg_oriented, c.dpg_xyz, c.digits) + text += "\n" + if c.td and not mkw_utils.is_single_player(): + size = 10 + timesize = c.digits+4 + p1, p2 = mkw_utils.get_timediff_settings(c.td_set) + s = 1 if 1-p1 else -1 + text += "Temps d'écart:"+" "*(timesize+size-16)+"Secondes | Images\n" + if c.td_absolute: + absolute = mkw_utils.get_time_difference_absolute(p1,p2) + text += make_text_timediff(absolute, "Absolu", size, timesize, c.digits) + if c.td_relative: + relative = s*mkw_utils.get_time_difference_relative(p1,p2) + text += make_text_timediff(relative, "Relatif", size, timesize, c.digits) + if c.td_projected: + projected = s*mkw_utils.get_time_difference_projected(p1,p2) + text += make_text_timediff(projected, "Projeté", size, timesize, c.digits) + if c.td_crosspath: + crosspath = s*mkw_utils.get_time_difference_crosspath(p1,p2) + text += make_text_timediff(crosspath, "Croisé", size, timesize, c.digits) + if c.td_tofinish: + tofinish = s*mkw_utils.get_time_difference_tofinish(p1,p2) + text += make_text_timediff(tofinish, "VersFin", size, timesize, c.digits) + if c.td_racecomp: + racecomp = mkw_utils.get_time_difference_racecompletion(RaceComp_History) + text += make_text_timediff(racecomp, "Complétion", size, timesize, c.digits) + text += "\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: + kart_input = KartInput(addr=race_mgr_player.kart_input()) + current_input_state = RaceInputState(addr=kart_input.current_input_state()) + + stick_x = current_input_state.raw_stick_x() - 7 + stick_y = current_input_state.raw_stick_y() - 7 + text += f"X: {stick_x} | Y: {stick_y}\n\n" + + return text + +def draw_infodisplay(c, RaceComp_History, Angle_History): + gui.draw_text((10, 10), c.color, create_infodisplay(c, RaceComp_History, Angle_History)) + + +def get_font_size(): + font_size = 14 + script_path = utils.get_script_dir() + config_filename = os.path.join(script_path, '..', '..', 'Config', 'Dolphin.ini') + with open(config_filename, 'r') as f: + settings = f.readlines() + for text in settings: + temp = text.split('=') + if temp[0][:13] == 'ImguiFontSize': + font_size = int(temp[1]) + return font_size +def draw_infodisplay_fr(c, RaceComp_History, Angle_History): + text = create_infodisplay_fr(c, RaceComp_History, Angle_History) + lines = text.split('\n') + j,k = len(lines)//3, 2*len(lines)//3 + font_size = get_font_size() + t1 = '\n'.join(lines[:j]) + t2 = '\n'.join(lines[j:k]) + t3 = '\n'.join(lines[k:]) + gui.draw_text((10, 10), int('0xFF0055A4', 16), t1) + gui.draw_text((10, 10+font_size*j), int('0xFFFFFFFF', 16), t2) + gui.draw_text((10, 10+font_size*k), int('0xFFEF4135', 16), t3) + + +def special(): + return (datetime.now().day == 1) and (datetime.now().month == 4) diff --git a/scripts/Modules/macro_utils.py b/scripts/Modules/macro_utils.py new file mode 100644 index 0000000..3f0c181 --- /dev/null +++ b/scripts/Modules/macro_utils.py @@ -0,0 +1,95 @@ +from typing import TypedDict + + +class GCInputs(TypedDict, total=False): + Left: bool + Right: bool + Down: bool + Up: bool + Z: bool + R: bool + L: bool + A: bool + B: bool + X: bool + Y: bool + Start: bool + StickX: int + StickY: int + CStickX: int + CStickY: int + TriggerLeft: int + TriggerRight: int + AnalogA: int + AnalogB: int + Connected: bool + + +class DolphinGCController: + def __init__(self, controller, port=0): + self._controller = controller + self._port = port + self._user_inputs = controller.get_gc_buttons(port) + + def current_inputs(self) -> GCInputs: + return self._controller.get_gc_buttons(self._port) + + def user_inputs(self) -> GCInputs: + return self._user_inputs + + def set_inputs(self, new_inputs: GCInputs): + current_inputs = self._controller.get_gc_buttons(self._port) + self._controller.set_gc_buttons(self._port, {**current_inputs, **new_inputs}) + + +GC_STICK_RANGES = { + 7: (205, 255), + 6: (197, 204), + 5: (188, 196), + 4: (179, 187), + 3: (170, 178), + 2: (161, 169), + 1: (152, 160), + 0: (113, 151), + -1: (105, 112), + -2: (96, 104), + -3: (87, 95), + -4: (78, 86), + -5: (69, 77), + -6: (60, 68), + -7: (0, 59), +} + + +def to_raw_gc_stick(mkw_stick: int): + try: + return GC_STICK_RANGES[mkw_stick][0] + except KeyError: + raise IndexError(f"Input ({mkw_stick}) outside of expected range (-7 to 7)") + +def to_mkwii_gc_stick(raw_stick: int): + for val, (start, end) in GC_STICK_RANGES.items(): + if start <= raw_stick <= end: + return val + raise IndexError(f"Input ({raw_stick}) outside of expected range (0 to 255)") + + +def convert_stick_inputs(inputs: GCInputs, mkwii_to_raw=False): + for mkey in ("StickX", "StickY", "CStickX", "CStickY"): + if mkey in inputs: + if mkwii_to_raw: + inputs[mkey] = to_raw_gc_stick(inputs[mkey]) + else: + inputs[mkey] = to_mkwii_gc_stick(inputs[mkey]) + return inputs + + +class MKWiiGCController(DolphinGCController): + def current_inputs(self) -> GCInputs: + return convert_stick_inputs(super().current_inputs()) + + def user_inputs(self) -> GCInputs: + return convert_stick_inputs(super().current_inputs()) + + def set_inputs(self, new_inputs: GCInputs): + super().set_inputs(convert_stick_inputs(new_inputs, mkwii_to_raw=True)) \ No newline at end of file diff --git a/scripts/Modules/mbp_utils.py b/scripts/Modules/mbp_utils.py new file mode 100644 index 0000000..d9e1c3e --- /dev/null +++ b/scripts/Modules/mbp_utils.py @@ -0,0 +1,171 @@ +from dolphin import utils, debug + + +PAL_EV_ADDR = { + 0x8057db60 : "Hop Reset", + 0x8057db68 : "Hop Reset", + 0x8057db70 : "Hop Reset", + 0x8057dba4 : "Hop Gain", + 0x8057dbb0 : "Hop Gain", + 0x8057dbbc : "Hop Gain", + 0x805880f4 : 'Lean effect', #https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartMove.cc#L2323 + 0x80588104 : 'Lean effect', + 0x80588114 : 'Lean effect', + 0x80596be4 : 'Slowfall', # Stage 1 typically + 0x80596bec : 'Slowfall', + 0x80596bf4 : 'Slowfall', + 0x805b52fc : 'Mult 0.998', # https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartDynamics.cc#L104 + 0x805b5304 : 'Mult 0.998', + 0x805b5308 : 'Mult 0.998', + 0x805aec6c : 'EV to IV', # https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartDynamics.cc#L115 + 0x805aec78 : 'EV to IV', + 0x805aec84 : 'EV to IV', + 0x805b4b90 : 'Respawn', + 0x805b4b94 : 'Respawn', + 0x805b4b98 : 'Respawn', + 0x805b5288 : '"Forces"', # https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartDynamics.cc#L98 + 0x805b528c : '"Forces"', # Forces comes from gravity, but also this : https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartMove.cc#L1529 + 0x805b5290 : '"Forces"', + 0x805b76f4 : "FloorCol Y 4", + 0x805b772c : "FloorCol Y 1", + 0x805b7734 : "FloorCol X", + 0x805b7738 : "FloorCol Z", + 0x805b7754 : "FloorCol Y 2", #seems to reset to 0 + 0x805b52a4 : "FloorCol Y 3", #seems to reset to 0 https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartDynamics.cc#L101 + 0x805b7de8 : 'Wheel EV decay', #https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartCollide.cc#L744 ? + 0x805b7df4 : 'Wheel EV decay', + 0x805b7e00 : 'Wheel EV decay', + 0x805b4d2c : 'Canon Entry', + 0x805b4d30 : 'Canon Entry', + 0x805b4d34 : 'Canon Entry', + 0x80585290 : 'Canon Duration', + 0x80585298 : 'Canon Duration', + 0x805852a0 : 'Canon Duration', + 0x8057ff2c : "Slow Ramp", + 0x8057ff34 : "Slow Ramp", + 0x8057ff3c : "Slow Ramp", + 0x80568a00 : 'Tumble launch', + 0x80568a0c : 'Tumble launch', #typically colliding a chainchomp + 0x80568a18 : 'Tumble launch', + 0x80568dac : 'Tumble land', + 0x80568db4 : 'Tumble land', + 0x80568dbc : 'Tumble land', + 0x805690e8 : 'Flip launch', + 0x805690f0 : 'Flip launch', #typically a cataquack setting your EV to (0,60,0) + 0x805690f8 : 'Flip launch', + 0x80569170 : 'Some Object', + 0x80569180 : 'Some Object', + 0x80569190 : 'Some Object', + 0x80569558 : 'Flip mid air', + 0x80569560 : 'Flip mid air', + 0x80569568 : 'Flip mid air', + 0x805694cc : 'Flip landing', + 0x805694d4 : 'Flip landing', + 0x805694dc : 'Flip landing', + 0x80569a78 : 'Squish', + 0x80569a80 : 'Squish', + 0x80569a88 : 'Squish', + } + +NTSCU_EV_ADDR = { + 0x805772fc : "Hop Reset", + 0x80577304 : "Hop Reset", + 0x8057730c : "Hop Reset", + 0x80577340 : "Hop Gain", + 0x8057734c : "Hop Gain", + 0x80577358 : "Hop Gain", + 0x805818d0 : 'Lean effect', + 0x805818e0 : 'Lean effect', + 0x805818f0 : 'Lean effect', + 0x805aa3d4 : 'Mult 0.998', + 0x805aa3dc : 'Mult 0.998', + 0x805aa3e0 : 'Mult 0.998', + 0x805a3d44 : 'EV to IV', + 0x805a3d50 : 'EV to IV', + 0x805a3d5c : 'EV to IV', + 0x805aa360 : 'Gravity', + 0x805aa364 : 'Gravity', + 0x805aa368 : 'Gravity', + 0x805ac80c : "SuperGrind?", + 0x805ac810 : "SuperGrind?", + 0x805acec0 : 'Wheel EV decay', + 0x805acecc : 'Wheel EV decay', + 0x805aced8 : 'Wheel EV decay', + } + +PAL_POS_ADDR = { + 0x805b4b84 : "Respawn 1", #TP to 0 + 0x805b4b88 : "Respawn 1", + 0x805b4b8c : "Respawn 1", + 0x80590254 : "Respawn 2", #TP to Respawn + 0x80590258 : "Respawn 2", + 0x8059025c : "Respawn 2", + 0x80579be0 : "Respawn 3", #slowfall before gravity + 0x80579be8 : "Respawn 3", + 0x80579bf0 : "Respawn 3", + 0x805b5628 : "Speed", + 0x805b5634 : "Speed", + 0x805b5640 : "Speed", + 0x805b6d88 : "Collision 1", #Walls/Floor typically + 0x805b6d8c : "Collision 1", + 0x805b6d90 : "Collision 1", + 0x80597098 : "Collision 2", #Corner ? + 0x805970a4 : "Collision 2", + 0x805970b0 : "Collision 2", + 0x80597490 : "Vehicle compensation", + 0x805974a0 : "Vehicle compensation", + 0x805974b0 : "Vehicle compensation", + 0x80596e2c : "Collision Object", #tree in rPB for example, or wigglers, or pipes + 0x80596e3c : "Collision Object", + 0x80596e4c : "Collision Object", + 0x80585238 : "Canon", + 0x80585240 : "Canon", + 0x80585248 : "Canon", + 0x80586698 : "Reject Road?", #reject road ??? + 0x805866a0 : "Reject Road?", + 0x805866a8 : "Reject Road?", + } + + +PAL_IV_ADDR = { + 0x8057bc44 : "Acceleration", #When holding A or B + 0x8057c00c : "Soft Cap", #Include walls + 0x8057bffc : "Soft Cap backward", #When you exceed the soft cap with negative speed + 0x8057af04 : "Deceleration", #When not holding A or B(multiply by 0.98 ?) + 0x8057af78 : "Turn Decel", #When turning with handling + 0x8057affc : "Air Decel", #When in the air for 5+ frames (multiply by 0.999 ?) + 0x8057ac2c : "EV to IV", #https://github.com/vabold/Kinoko/blob/main/source/game/kart/KartMove.cc#L1198 + 0x8057b0e4 : "Slope", #when you get speed from being on a slope. Only works below 30 IV ? + 0x8057bc88 : "SSMT", #Multiply by 0.8 ? + 0x8057bcac : "0 IV lock", #When you hold B, you'll stay at 0IV for a few frame before reversing + 0x8057ac48 : "Backward IV decel", #If your is IV under -20, it add 0.5 to your IV (seemingly) + 0x80578554 : "Canon Reset", #Reset IV to 0 when entering the canon + 0x805850c8 : "Canon 2", # Set IV to cap every frame + + } + +def union_dict(*args): + res = {} + for d in args: + for k in d.keys(): + res[k] = d[k] + return res + +def get_addr_dict(): + region = utils.get_game_id() + if region == 'RMCE01': + return NTSCU_EV_ADDR + elif region == 'RMCP01': + return union_dict(PAL_EV_ADDR, PAL_POS_ADDR, PAL_IV_ADDR) + else: + return {} + + +def make_mbp(addr): + mbp_dict = { "At" : addr, + "BreakOnRead" : False, + "BreakOnWrite" : True, + "LogOnHit" : True, + "BreakOnHit" : False} + debug.set_memory_breakpoint(mbp_dict) + diff --git a/scripts/Modules/mkw_classes/__init__.py b/scripts/Modules/mkw_classes/__init__.py index cae5be1..507f099 100644 --- a/scripts/Modules/mkw_classes/__init__.py +++ b/scripts/Modules/mkw_classes/__init__.py @@ -8,7 +8,7 @@ # noqa: F401 from .common import RegionError -from .common import vec2, vec3, mat34, quatf +from .common import vec2, vec3, mat34, quatf, eulerAngle from .common import ExactTimer from .common import CupId, CourseId, VehicleId, CharacterId, WheelCount, VehicleType from .common import SpecialFloor, TrickType, SurfaceProperties, RaceConfigPlayerType @@ -56,4 +56,4 @@ from .race_manager import RaceManager, RaceState from .time_manager import TimerManager from .timer import Timer -from .race_manager_player import RaceManagerPlayer \ No newline at end of file +from .race_manager_player import RaceManagerPlayer diff --git a/scripts/Modules/mkw_classes/common.py b/scripts/Modules/mkw_classes/common.py index 1df46d7..e424200 100644 --- a/scripts/Modules/mkw_classes/common.py +++ b/scripts/Modules/mkw_classes/common.py @@ -23,8 +23,8 @@ def __sub__(self, other): @staticmethod def read(ptr) -> "vec2": - bytes = memory.read_bytes(ptr, 0x8) - return vec2(*struct.unpack('>' + 'f'*2, bytes)) + bts = memory.read_bytes(ptr, 0x8) + return vec2(*struct.unpack('>' + 'f'*2, bts)) @dataclass class vec3: @@ -38,16 +38,73 @@ def __add__(self, other): def __sub__(self, other): return vec3(self.x - other.x, self.y - other.y, self.z - other.z) + def __neg__(self): + return vec3(-self.x, -self.y, -self.z) + + def __mul__(self, other): + """ vec3 * vec3 -> float (dot product) + vec3 * float -> vec3 (scalar multiplication)""" + if type(other) == vec3: + return self.x * other.x + self.y * other.y + self.z * other.z + else: + return vec3(self.x * other, self.y * other, self.z * other) + + __rmul__ = __mul__ + + def __matmul__(self, other): + """ vec3 @ vec3 -> vec3 (cross product) + vec3 @ float -> vec3 (scalar multiplication)""" + if type(other) == vec3: + x = self.y*other.z - self.z*other.y + y = self.z*other.x - self.x*other.z + z = self.x*other.y - self.y*other.x + return vec3(x,y,z) + else: + return vec3(self.x * other, self.y * other, self.z * other) + def length(self) -> float: return math.sqrt(self.x**2 + self.y**2 + self.z**2) def length_xz(self) -> float: return math.sqrt(self.x**2 + self.z**2) + def forward(self, facing_yaw) -> float: + speed_yaw = -180/math.pi * math.atan2(self.x, self.z) + diff_angle_rad = (facing_yaw - speed_yaw)*math.pi/180 + return math.sqrt(self.x**2 + self.z**2)*math.cos(diff_angle_rad) + + def sideway(self, facing_yaw) -> float: + speed_yaw = -180/math.pi * math.atan2(self.x, self.z) + diff_angle_rad = (facing_yaw - speed_yaw)*math.pi/180 + return math.sqrt(self.x**2 + self.z**2)*math.sin(diff_angle_rad) + @staticmethod def read(ptr) -> "vec3": - bytes = memory.read_bytes(ptr, 0xC) - return vec3(*struct.unpack('>' + 'f'*3, bytes)) + bts = memory.read_bytes(ptr, 0xC) + return vec3(*struct.unpack('>' + 'f'*3, bts)) + + def write(self, addr): + memory.write_bytes(addr, self.to_bytes()) + + @staticmethod + def from_bytes(bts) -> "vec3": + return vec3(*struct.unpack('>' + 'f'*3, bts)) + + def to_bytes(self) -> bytearray: + return bytearray(struct.pack('>fff', self.x, self.y, self.z)) + + def __str__(self): + return str(self.x)+','+str(self.y)+','+str(self.z) + + @staticmethod + def from_string(string) -> "vec3": + temp = string.split(',') + assert len(temp) == 3 + return vec3(float(temp[0]), float(temp[1]), float(temp[2])) + + + + @dataclass class mat34: @@ -66,8 +123,8 @@ class mat34: @staticmethod def read(ptr) -> "mat34": - bytes = memory.read_bytes(ptr, 0x30) - return mat34(*struct.unpack('>' + 'f'*12, bytes)) + bts = memory.read_bytes(ptr, 0x30) + return mat34(*struct.unpack('>' + 'f'*12, bts)) @dataclass class quatf: @@ -76,10 +133,149 @@ class quatf: z: float = 0.0 w: float = 0.0 + def vec(self) -> "vec3": + ''' Return the vec3 part of a quaternion''' + return vec3(self.x, self.y, self.z) + + def __abs__(self): + ''' The norm / absolute value of the quaternion ''' + return math.sqrt(self.x*self.x + self.y*self.y + self.z+self.z + self.w*self.w) + + def normalize(self): + ''' Return a normalize version of self. Do not modify self ''' + try: + return self * (1/abs(self)) + except: + return quatf(0,0,0,0) + + def __mul__(self, other) -> "quatf": + ''' quatf * quatf -> quatf (quaternion multiplication) + quatf * vec3 -> quatf (quaternion multiplication with w = 0 for the vec3) + quatf * float/int -> quatf (scalar mutliplication)''' + if type(other) == vec3: + q2 = quatf(other.x, other.y, other.z, 0) + elif type(other) == quatf: + q2 = other + elif type(other) == int or type(other) == float: + return quatf(self.x*other, self.y*other, self.z*other, self.w*other) + else: + raise TypeError('expected vec3 or quatf') + q1 = self + w = q1.w*q2.w - q1.x*q2.x - q1.y*q2.y - q1.z*q2.z + x = q1.w*q2.x + q1.x*q2.w + q1.y*q2.z - q1.z*q2.y + y = q1.w*q2.y - q1.x*q2.z + q1.y*q2.w + q1.z*q2.x + z = q1.w*q2.z + q1.x*q2.y - q1.y*q2.x + q1.z*q2.w + return quatf(x,y,z,w) + + def conjugate(self): + return quatf(-self.x, -self.y, -self.z, self.w) + + def __matmul__(self, other): + ''' quatf @ vec3 -> vec3 (vector rotation by a quaternion)''' + if not type(other) == vec3: + raise TypeError('expected vec3') + conj = self.conjugate() + res = self * other + x = res.y * conj.z + res.x * conj.w + res.w * conj.x - res.z * conj.y + y = res.z * conj.x + res.y * conj.w + res.w * conj.y - res.x * conj.z + z = res.x * conj.y + res.z * conj.w + res.w * conj.z - res.y * conj.x + return vec3(x, y, z) + @staticmethod def read(ptr) -> "quatf": - bytes = memory.read_bytes(ptr, 0x10) - return quatf(*struct.unpack('>' + 'f'*4, bytes)) + bts = memory.read_bytes(ptr, 0x10) + return quatf(*struct.unpack('>' + 'f'*4, bts)) + + def __str__(self): + return str(self.x)+','+str(self.y)+','+str(self.z)+','+str(self.w) + + def write(self, addr): + memory.write_bytes(addr, self.to_bytes()) + + @staticmethod + def from_string(string) -> "quatf": + temp = string.split(',') + assert len(temp) == 4 + return quatf(float(temp[0]), float(temp[1]), float(temp[2]), float(temp[3])) + + @staticmethod + def from_bytes(bts) -> "quatf": + return quatf(*struct.unpack('>' + 'f'*4, bts)) + + def to_bytes(self) -> bytearray: + return bytearray(struct.pack('>ffff', self.x, self.y, self.z, self.w)) + + @staticmethod + def from_angles(angles): + #arg : angles : eulerAngle + cr = math.cos(angles.pitch * 0.5 / (180/math.pi)) + sr = math.sin(angles.pitch * 0.5 / (180/math.pi)) + cp = math.cos(angles.yaw * 0.5 / (180/math.pi)) + sp = math.sin(angles.yaw * 0.5 / (180/math.pi)) + cy = math.cos(angles.roll * 0.5 / (180/math.pi)) + sy = math.sin(angles.roll * 0.5 / (180/math.pi)) + + return quatf(cr * sp * sy + sr * cp * cy, + cr * sp * cy + sr * cp * sy, + - cr * cp * sy + sr * sp * cy, + - cr * cp * cy + sr * sp * sy) + + @staticmethod + def from_vectors(vect1, vect2): + #args : 2 vec3, get the rotation from vect1 to vect2 + cross = vect1 @ vect2 + w = vect1.length() * vect2.length() + vect1 * vect2 + return quatf(cross.x, cross.y, cross.z, w).normalize() + + +def angle_degree_format(angle): + return ((angle+180)%360) - 180 + +class eulerAngle: + """A class for Euler Angles. + Angles in degrees, between -180 and 180""" + def __init__(self, pitch=0, yaw=0, roll=0): + self.pitch = angle_degree_format(pitch) + self.yaw = angle_degree_format(yaw) + self.roll = angle_degree_format(roll) + + def __add__(self, other): + pitch = self.pitch + other.pitch + yaw = self.yaw + other.yaw + roll = self.roll + other.roll + return eulerAngle(pitch, yaw, roll) + + def __sub__(self, other): + pitch = self.pitch - other.pitch + yaw = self.yaw - other.yaw + roll = self.roll - other.roll + return eulerAngle(pitch, yaw, roll) + + def __mul__(self, other): + ''' angle * number -> angle ''' + pitch = self.pitch * other + yaw = self.yaw * other + roll = self.roll * other + return eulerAngle(pitch, yaw, roll) + + @staticmethod + def from_quaternion(q : quatf): + x1, x2 = 2*q.x*q.w-2*q.y*q.z, 1-2*q.x*q.x-2*q.z*q.z + y1, y2 = 2*q.y*q.w-2*q.x*q.z, 1-2*q.y*q.y-2*q.z*q.z + z = 2*q.x*q.y + 2*q.z*q.w + roll = 180/math.pi * math.asin(z) if abs(z) <= 1 else 90*z/abs(z) + pitch = -180/math.pi * math.atan2(x1, x2) + yaw = -180/math.pi * math.atan2(y1, y2) + return eulerAngle(pitch, yaw, roll) + + def get_unit_vec3(self): + """ Return a vec3 of size 1, which point + the same direction as self """ + y = math.sin(self.pitch*math.pi /180) + xz = math.cos(self.pitch*math.pi /180) + z = xz * math.cos(self.yaw*math.pi /180) + x = - xz * math.sin(self.yaw*math.pi /180) + return vec3(x,y,z) @dataclass class ExactTimer: diff --git a/scripts/Modules/mkw_classes/kart_collide.py b/scripts/Modules/mkw_classes/kart_collide.py index 73e8fe2..575ff08 100644 --- a/scripts/Modules/mkw_classes/kart_collide.py +++ b/scripts/Modules/mkw_classes/kart_collide.py @@ -64,4 +64,14 @@ def solid_oob_timer(player_idx=0) -> int: def inst_solid_oob_timer(self) -> int: solid_oob_timer_ref = self.addr + 0x4A - return memory.read_u16(solid_oob_timer_ref) \ No newline at end of file + return memory.read_u16(solid_oob_timer_ref) + + @staticmethod + def glitchy_corner(player_idx=0) -> int: + kart_collide_ref = KartCollide.chain(player_idx) + glitchy_corner_ref = kart_collide_ref + 0x68 + return memory.read_f32(glitchy_corner_ref) + + def inst_glitchy_corner(self) -> int: + glitchy_corner_ref = self.addr + 0x68 + return memory.read_f32(glitchy_corner_ref) \ No newline at end of file diff --git a/scripts/Modules/mkw_classes/race_manager.py b/scripts/Modules/mkw_classes/race_manager.py index 61a0ef4..507782e 100644 --- a/scripts/Modules/mkw_classes/race_manager.py +++ b/scripts/Modules/mkw_classes/race_manager.py @@ -7,7 +7,7 @@ class RaceState(Enum): INTRO_CAMERA = 0 # Course preview COUNTDOWN = 1 # including starting pan RACE = 2 - FINISHED_RACE = 3 + FINISHED_RACE = 4 class RaceManager: def __init__(self): @@ -262,4 +262,4 @@ def disable_lower_respawns() -> bool: def inst_disable_lower_respawns(self) -> bool: """Delfino Plaza?""" disable_lower_respawns_ref = self.addr + 0x48 - return memory.read_u8(disable_lower_respawns_ref) > 0 \ No newline at end of file + return memory.read_u8(disable_lower_respawns_ref) > 0 diff --git a/scripts/Modules/mkw_classes/vehicle_physics.py b/scripts/Modules/mkw_classes/vehicle_physics.py index bbc05f0..ed6f250 100644 --- a/scripts/Modules/mkw_classes/vehicle_physics.py +++ b/scripts/Modules/mkw_classes/vehicle_physics.py @@ -59,11 +59,11 @@ def inst_inverse_inertia_tensor(self) -> mat34: def rotation_speed(player_idx=0) -> float: vehicle_physics_ref = VehiclePhysics.chain(player_idx) rotation_speed_ref = vehicle_physics_ref + 0x64 - return mat34.read(rotation_speed_ref) + return memory.read_f32(rotation_speed_ref) def inst_rotation_speed(self) -> float: rotation_speed_ref = self.addr + 0x64 - return mat34.read(rotation_speed_ref) + return memory.read_f32(rotation_speed_ref) @staticmethod def position(player_idx=0) -> vec3: diff --git a/scripts/Modules/mkw_utils.py b/scripts/Modules/mkw_utils.py index d2d2dfd..c2676be 100644 --- a/scripts/Modules/mkw_utils.py +++ b/scripts/Modules/mkw_utils.py @@ -1,7 +1,10 @@ -from dolphin import memory, utils +from dolphin import memory, utils, event +from collections import deque -from .mkw_classes import mat34, quatf, vec3, ExactTimer -from .mkw_classes import VehicleDynamics, VehiclePhysics, RaceManagerPlayer +from .mkw_classes import mat34, quatf, vec3, ExactTimer, eulerAngle +from .mkw_classes import VehicleDynamics, VehiclePhysics, RaceManagerPlayer, KartObjectManager, RaceManager, RaceState + +import math # These are helper functions that don't quite fit in common.py # This file also contains getter functions for a few global variables. @@ -9,6 +12,47 @@ # NOTE (xi): wait for get_game_id() to be put in dolphin.memory before clearing # these commented-out lines: +fps_const = 59.94 #Constant for fps in mkw (59.94fps) + + +class History: + ''' Class for storing each frame some data + get_data_dict must be a dict of functions that return the desired data + the functions must be able to take 0 arguments (Maybe if needed i'll implement an argument dict, for the argument of the corresponding functions + + Example : {'position' : VehiclePhysics.position, + 'rotation' : VehiclePhysics.main_rotation} + data is a list containing dict of data corresponding + data[0] contain the latest added frame''' + + def __init__(self, get_data_dict, max_size): + self.max_size = max_size + self.get_data = get_data_dict + self.data = deque() + + def update(self): + if len(self.data) >= self.max_size: + self.data.pop() + cur_frame_dict = {} + for key in self.get_data.keys(): + cur_frame_dict[key] = self.get_data[key]() + self.data.appendleft(cur_frame_dict) + + def __getitem__(self, index): + return self.data[index] + + def __len__(self): + return len(self.data) + + def clear(self): + self.data.clear() + + def __bool__(self): + return bool(self.data) + + def __iter__(self): + return iter(self.data) + def chase_pointer(base_address, offsets, data_type): """This is a helper function to allow multiple ptr dereferences in quick succession. base_address is dereferenced first, and then @@ -42,12 +86,44 @@ def frame_of_input(): "RMCJ01": 0x809C2920, "RMCK01": 0x809B1F00} return memory.read_u32(address[id]) +def extended_race_state(): + ''' Return an extended RaceState. + -1 <-> Not in a race + 0 <-> Intro Camera + 1 <-> Countdown + 2 <-> In the race + 3 <-> Race over (waiting for other to finish) + 4 <-> Race over (everyone is finished) + 5 <-> Race over and ghost saved''' + if KartObjectManager.player_count()==0: + return -1 + else: + race_mgr = RaceManager() + state = race_mgr.state().value + if state < 4: + return state + else: + try: + region = utils.get_game_id() + address = {"RMCE01": 0x809B8F88, "RMCP01": 0x809BD748, + "RMCJ01": 0x809BC7A8, "RMCK01": 0x809ABD88} + rkg_addr = chase_pointer(address[region], [0x18], 'u32') + except KeyError: + raise RegionError + if memory.read_u32(rkg_addr) == 0x524b4744 : + return 5 + else: + return 4 + def delta_position(playerIdx=0): dynamics_ref = VehicleDynamics(playerIdx) physics_ref = VehiclePhysics(addr=dynamics_ref.vehicle_physics()) return physics_ref.position() - dynamics_ref.position() +def is_single_player(): + return KartObjectManager().player_count() == 1 + # Next 3 functions are used for exact finish display def get_igt(lap, player): @@ -80,6 +156,346 @@ def get_unrounded_time(lap, player): t += update_exact_finish(i + 1, player) return t -# TODO: Rotation display helper functions +def calculate_exact_finish(positions, lap): + ''' + Calculate the exact finish time, assuming this + function is called the 1st frame you cross the line. + Store it to EVA at 0x800002E0 + lap*0x4 as a 4bytes float. + ''' + if len(positions) < 3: + return 0 + prevPos = positions[2]['pos'] + pos = positions[1]['pos'] + fl1, fl2 = get_finish_line_coordinate() + t = time_to_cross(prevPos, pos-prevPos, fl1, fl2) + if not (0<= t <= 1): + #Error detected. We write a default value instead + address = 0x800002E0 + lap*0x4 + print('Error in Exact Finish Time') + memory.write_f32(address, 999.999999) + else: + exact_finish = (frame_of_input()+t-241)/fps_const + address = 0x800002E0 + lap*0x4 + memory.write_f32(address, exact_finish) + +def read_exact_finish(lap): + #Read exact finish from EVA, stored with above function + address = 0x800002E0 + lap*0x4 + return memory.read_f32(address) + + +def calculate_extra_finish_data(exact_finish): + ''' + This function returns how much exact finish time + you need to gain a rounded millisecond, and + you need to lose a rounded millisecond + Return in MICROSECONDS + ''' + t = (exact_finish*fps_const)%1 + frame_count = math.floor(exact_finish*fps_const) + + frame_rounded_ms = math.floor(frame_count/fps_const*1000) + subframe_rounded_ms = math.ceil(t/fps_const*1000) + + if subframe_rounded_ms < 17: + exact_ahead = subframe_rounded_ms/1000*fps_const - t #time in frames needed for gaining 1ms + else: + exact_ahead = 1-t - (math.floor((frame_count+1)/fps_const*1000) - frame_rounded_ms - 17)/1000*fps_const + + if subframe_rounded_ms > 1 : + exact_behind = (subframe_rounded_ms-1)/1000*fps_const - t #time in frames needed for losing 1ms + else: + exact_behind = -t - (math.floor((frame_count-1)/fps_const*1000) - frame_rounded_ms + 17)/1000*fps_const + + return (round(exact_ahead/fps_const*1000000), round(exact_behind/fps_const*1000000)) + +#Rotation display helper functions +def quaternion_to_euler_angle(q): + """Param : quatf + Return : eulerAngle """ + x1, x2 = 2*q.x*q.w-2*q.y*q.z, 1-2*q.x*q.x-2*q.z*q.z + y1, y2 = 2*q.y*q.w-2*q.x*q.z, 1-2*q.y*q.y-2*q.z*q.z + z = 2*q.x*q.y + 2*q.z*q.w + roll = 180/math.pi * math.asin(z) + pitch = -180/math.pi * math.atan2(x1, x2) + yaw = -180/math.pi * math.atan2(y1, y2) + return eulerAngle(pitch, yaw, roll) + +def get_facing_angle(player = 0): + """Param : int player_id + Return : eulerAngle , correspond to facing angles""" + quaternion = VehiclePhysics(player).main_rotation() + return quaternion_to_euler_angle(quaternion) + +def speed_to_euler_angle(speed): + """Param : vec3 speed + Return : eulerAngle""" + s = speed + pitch = 180/math.pi * math.atan2(s.z, s.y) #unsure and unused + yaw = -180/math.pi * math.atan2(s.x, s.z) + roll = -180/math.pi * math.atan2(s.y, s.x)#unsure and unused + return eulerAngle(pitch, yaw, roll) + +def get_moving_angle(player): + """Param : int player_id + Return : eulerAngle , correspond to moving angles""" + speed = delta_position(player) + return speed_to_euler_angle(speed) + +def get_unit_vectors_from_angles(angles): + sp, cp = math.sin(angles.pitch/180*math.pi) , math.cos(angles.pitch/180*math.pi) + sy, cy = math.sin(angles.yaw/180*math.pi) , math.cos(angles.yaw/180*math.pi) + sr, cr = math.sin(angles.roll/180*math.pi) , math.cos(angles.roll/180*math.pi) + + #Vec pointing "forward" + y1 = sp + x1 = - cp * sy + z1 = cp * cy + + #Vec pointing "upward" + z2 = - cy * sp * cr - sy * sr #not sure if it's ++, +-, -+ or -- + y2 = cp * cr + x2 = sy * sp * cr - cy * sr + + return vec3(x1,y1,z1), vec3(x2,y2,z2) + +def get_relative_angles(absolute_angles, up_vector): + r_y = up_vector + r_x = r_y @ vec3(0,0,1) + r_z = r_x @ r_y + + forward_vec, upward_vec = get_unit_vectors_from_angles(absolute_angles) + + pitch = math.asin(forward_vec*r_y) + + return pitch*180/math.pi + + +#The time difference functions. +""" +time_difference_[name](P1, S1, P2, S2) is a function that takes as arguments +P1,S1 : Player1's Position and Speed vec3. +P2,S2 : Player2's Position and Speed vec3 +Return the time it would take for Player1 to catch Player2 (not always symmetric) + +get_time_difference_[name](Player1, Player2) takes as arguments +Player1 : Player1 ID +Player2 : Player2 ID +Return the time it would take for Player1 to catch Player2 (not always symmetric) +It's the function called in draw_infodisplay.py +""" + +def get_physics(player1, player2): + """Take the Player1 and Player2 ID's, return their + P1, S1, P2, S2 data""" + P1, S1 = VehiclePhysics(player1).position(), delta_position(player1) + P2, S2 = VehiclePhysics(player2).position(), delta_position(player2) + return P1,S1,P2,S2 + + +def get_distance_ghost_vec(): + """Give the distance (vec3) between the player and the ghost + Player to ghost vec""" + player_position = VehiclePhysics(0).position() + ghost_position = VehiclePhysics(1).position() + return (ghost_position - player_position) + +def get_distance_ghost(): + """Give the distance(float) between the player and the ghost""" + return get_distance_ghost_vec().length() + +def time_difference_absolute(P1, P2, S1, S2): + s = S1.length() + if s != 0: + return (P2-P1).length() / s + return float('inf') + +def get_time_difference_absolute(player1, player2): + """Time difference "Absolute" (simple and bad) + Simply takes the distance player-ghost, and divide it by raw speed (always positive)""" + P1, S1, P2, S2 = get_physics(player1, player2) + return time_difference_absolute(P1, P2, S1, S2) + +def time_difference_relative(P1, P2, S1, S2): + L = (P2 - P1).length() + if L == 0: + return 0 + s = S1*(P2-P1)/L + if s == 0: + return float('inf') + return (P2-P1).length() / s + +def get_time_difference_relative(player1, player2): + """Time difference "Relative" + Take distance player-ghost. Divide it by the player's speed "toward" the ghost (dot product)""" + P1, S1, P2, S2 = get_physics(player1, player2) + return time_difference_relative(P1, P2, S1, S2) + +def time_difference_projected(P1, P2, S1, S2): + s = S1.length() + if s == 0: + return float('inf') + return (P2-P1)*S1/(s**2) + +def get_time_difference_projected(player1, player2): + """ Time difference "Projected" + Take the distance between the player and the plane oriented by the player speed, covering the ghost. + Then divide it by the player raw speed + This is the 2D version because no numpy""" + P1, S1, P2, S2 = get_physics(player1, player2) + return time_difference_projected(P1, P2, S1, S2) + + +def time_to_cross(A, S, B, C): + """If A is going at a constant speed S, how many frame will it take + to cross the vertical plan containing B and C + Param : A, S, B, C : (vec3),(vec3),(vec3),(vec3) + Return t (float) """ + N = (B-C)@vec3(0,1,0) #normal vector to the plan containing B,C + ns = N*S + if ns != 0: + return N*(B-A)/ns + return float('inf') + + +def time_difference_crosspath(P1, P2, S1, S2): + t1 = time_to_cross(P1, S1, P2, P2+S2) + t2 = time_to_cross(P2, S2, P1, P1+S1) + return t1-t2 + + +def get_time_difference_crosspath(player1, player2): + """Time difference "CrossPath" + Take both XZ trajectories of the player and the ghost + Calculate how much time it takes them to reach the crosspoint. (2D only) + Return the difference.""" + P1, S1, P2, S2 = get_physics(player1, player2) + return time_difference_crosspath(P1, P2, S1, S2) + + + +def get_finish_line_coordinate(): + """pointA is the position of the left side of the finish line (vec3) + point B ---------------------right------------------------------ + both have 0 as their Y coordinate.""" + game_id = utils.get_game_id() + address = {"RMCE01": 0x809B8F28, "RMCP01": 0x809BD6E8, + "RMCJ01": 0x809BC748, "RMCK01": 0x809ABD28} + kmp_ref = chase_pointer(address[game_id], [0x4, 0x0], 'u32') + offset = memory.read_u32(kmp_ref+0x24) + pointA = vec3(memory.read_f32(kmp_ref+0x4C+offset+0x8+0x0), 0, memory.read_f32(kmp_ref+0x4C+offset+0x8+0x4)) + pointB = vec3(memory.read_f32(kmp_ref+0x4C+offset+0x8+0x8), 0, memory.read_f32(kmp_ref+0x4C+offset+0x8+0xC)) + return pointA, pointB + + +def expected_time_left(player_id=0): + """return the expected time left for the + player before it crosses the finish line""" + prevPos = VehicleDynamics.position(player_id) + pos = VehiclePhysics.position(player_id) + fl1, fl2 = get_finish_line_coordinate() + return time_to_cross(pos, pos-prevPos, fl1, fl2) + +def time_difference_tofinish(P1, P2, S1, S2): + A,B = get_finish_line_coordinate() + t1 = time_to_cross(P1, S1, A, B) + t2 = time_to_cross(P2, S2, A, B) + return t1-t2 + +def get_time_difference_tofinish(player1, player2): + """Assume player and ghost are not accelerated. + Calculate the time to the finish line for both, and takes the difference.""" + P1, S1, P2, S2 = get_physics(player1, player2) + return time_difference_tofinish(P1, P2, S1, S2) + + +def find_index(value, value_list): + """Find the index i so value_list[i]>=value>value_list[i+1] + We suppose value_list[i+1] < value_list[i] + and value_list[0]>= value>=value_list[-1]""" + n = len(value_list) + if n == 1 : + return 0 + h = n//2 + if value <= value_list[h]: + return h+find_index(value, value_list[h:]) + return find_index(value, value_list[:h]) + +def get_time_difference_racecompletion(history): + """Use RaceCompletionData History to calculate the frame difference + The function assume that RaceCompletion is increasing every frames""" + if history: + curframe = history[0] + lastframe = history[-1] + inf = float('inf') + if curframe['prc'] >= curframe['grc']: + if curframe['grc'] > lastframe['prc']: + l = [dic['prc'] for dic in history] + i = find_index(curframe['grc'], l) + t = i + (curframe['grc'] - l[i])/ (l[i+1] - l[i]) + return -t + return -inf + else: + if curframe['prc'] > lastframe['grc']: + l =[dic['grc'] for dic in history] + i = find_index(curframe['prc'], l) + t = i + (curframe['prc'] - l[i])/ (l[i+1] - l[i]) + return t + return inf + else: + return float('inf') + + +def get_timediff_settings(string): + if string == 'player': + return 0, 1 + if string == 'ghost': + return 1, 0 + pp, sp, pg, sg = get_physics(0,1) + player_is_ahead = int(sp*(pg-pp)>0) + if string == 'ahead': + return 1-player_is_ahead, player_is_ahead + if string == 'behind': + return player_is_ahead, 1-player_is_ahead + else: + print('TimeDiff setting value not recognized. Default to "player"') + return 0, 1 + +def player_teleport(player_id = 0, + x = None, y = None, z = None, + pitch = None, yaw = None, roll = None): + '''Function to teleport the player in player_id to the + corresponding position and rotation. + Use None for parameter you don't wanna change''' + + addr = VehiclePhysics.chain(player_id) + position = VehiclePhysics.position(player_id) + quaternion = VehiclePhysics.main_rotation(player_id) + angles = eulerAngle.from_quaternion(quaternion) + + if not x is None: + position.x = x + if not y is None: + position.y = y + if not z is None: + position.z = z + if not pitch is None: + angles.pitch = pitch + if not yaw is None: + angles.yaw = yaw + if not roll is None: + angles.roll = roll + + quaternion = quatf.from_angles(angles) + position.write(addr + 0x68) + quaternion.write(addr + 0xF0) + +def add_angular_ev(player_id = 0, angle = 90, magnitude = 100, y = 0): + addr = VehiclePhysics.chain(player_id) + yaw = get_facing_angle(player_id).yaw + z = math.cos((-yaw+angle)*math.pi/180) * magnitude + x = math.sin((-yaw+angle)*math.pi/180) * magnitude + ev = vec3(x,y,z) + ev.write(addr + 0x74) -# TODO: Time difference display helper functions \ No newline at end of file + diff --git a/scripts/Modules/rkg_lib.py b/scripts/Modules/rkg_lib.py new file mode 100644 index 0000000..68f8c60 --- /dev/null +++ b/scripts/Modules/rkg_lib.py @@ -0,0 +1,518 @@ +import configparser +from dolphin import gui, memory, utils +from math import floor +import os +import zlib +from .mkw_classes import RaceConfig, RaceConfigScenario, RaceConfigSettings +from .mkw_classes import RaceConfigPlayer, PlayerInput, KartInput, Controller +from .framesequence import Frame, FrameSequence +from .mkw_utils import chase_pointer +from .src import decompress, compress + +def extract_bits(data, start_bit, length): + byte_index = start_bit // 8 + bit_offset = start_bit % 8 + bits = 0 + + for i in range(length): + bit_position = bit_offset + i + bits <<= 1 + bits |= (data[byte_index + (bit_position // 8)] >> (7 - (bit_position % 8))) & 1 + + return bits + +def crc16(i): + '''Argument : bytearray + Return : 16bit int''' + #https://stackoverflow.com/questions/25239423/crc-ccitt-16-bit-python-manual-calculation + def _update_crc(crc, c): + def _initial(c): + POLYNOMIAL = 0x1021 + PRESET = 0 + crc = 0 + c = c << 8 + for j in range(8): + if (crc ^ c) & 0x8000: + crc = (crc << 1) ^ POLYNOMIAL + else: + crc = crc << 1 + c = c << 1 + return crc + POLYNOMIAL = 0x1021 + PRESET = 0 + _tab = [ _initial(i) for i in range(256) ] + cc = 0xff & c + tmp = (crc >> 8) ^ cc + crc = (crc << 8) ^ _tab[tmp & 0xff] + crc = crc & 0xffff + return crc + POLYNOMIAL = 0x1021 + PRESET = 0 + crc = PRESET + for c in i: + crc = _update_crc(crc, c) + return crc + + +def convertTimer(bits: int): + '''Convert a 24 bits int representing a Timer (m,s,ms), to a float in seconds''' + ms = bits & 1023 + bits >>= 10 + s = bits & 127 + bits >>= 7 + m = bits & 127 + return m*60+s+ms/1000 + +def convertTimerBack(split: float): + '''Convert a float in seconds, to a 24 bits int representing a Timer (m,s,ms)''' + m = floor(split/60) + s = floor(split%60) + ms = floor(split*1000)%1000 + return ms + (s<<10) + (m <<17) + +def add_bits(bit_list, number, size): + ''' Add to a bit list another number, with a bit size''' + for i in range(size): + bit_list.append((number >> (size - i -1)) & 1) + +def bits_to_bytearray(bit_list): + ''' Convert an array of bits to an array of bytes (grouping bits by 8)''' + b = bytearray() + byte = 0 + for i in range(len(bit_list)): + byte = (byte << 1) + bit_list[i] + if i%8 == 7: + b.append(byte) + byte = 0 + if byte != 0: + b.append(byte) + return b + +class RKGMetaData: + def __init__(self, rkg_data, useDefault = False): + if not useDefault: + self.finish_time = convertTimer(extract_bits(rkg_data, 4*8+0, 24)) + self.track_id = extract_bits(rkg_data, 7*8+0, 6) + self.character_id = extract_bits(rkg_data, 8*8+6, 6) + self.vehicle_id = extract_bits(rkg_data, 8*8+0, 6) + self.year = extract_bits(rkg_data, 9*8+4, 7) + self.month = extract_bits(rkg_data, 0xA*8+3, 4) + self.day = extract_bits(rkg_data, 0xA*8+7, 5) + self.controller_id = extract_bits(rkg_data, 0xB*8+4, 4) + self.compressed_flag = extract_bits(rkg_data, 0xC*8+4, 1) + self.ghost_type = extract_bits(rkg_data, 0xC*8+7, 7) + self.drift_id = extract_bits(rkg_data, 0xD*8+6, 1) + self.input_data_length = extract_bits(rkg_data, 0xE*8+0, 16) + self.lap_count = extract_bits(rkg_data, 0x10*8+0, 8) + self.lap1_split = convertTimer(extract_bits(rkg_data, 0x11*8+0, 24)) + self.lap2_split = convertTimer(extract_bits(rkg_data, 0x14*8+0, 24)) + self.lap3_split = convertTimer(extract_bits(rkg_data, 0x17*8+0, 24)) + self.country_code = extract_bits(rkg_data, 0x34*8+0, 8) + self.state_code = extract_bits(rkg_data, 0x35*8+0, 8) + self.location_code = extract_bits(rkg_data, 0x36*8+0, 16) + else: + self.finish_time = 100 + self.track_id = 0 + self.character_id = 0 + self.vehicle_id = 0 + self.year = 25 + self.month = 1 + self.day = 1 + self.controller_id = 3 + self.compressed_flag = 1 + self.ghost_type = 38 + self.drift_id = 0 + self.input_data_length = 908 + self.lap_count = 3 + self.lap1_split = 0 + self.lap2_split = 5999.999 + self.lap3_split = 5999.999 + self.country_code = 255 + self.state_code = 255 + self.location_code = 65535 + + def __iter__(self): + return iter([self.finish_time, + self.track_id, + self.character_id, + self.vehicle_id, + self.year, + self.month, + self.day, + self.controller_id, + self.compressed_flag, + self.ghost_type, + self.drift_id, + self.input_data_length, + self.lap_count, + self.lap1_split, + self.lap2_split, + self.lap3_split, + self.country_code, + self.state_code, + self.location_code]) + + def __str__(self): + res = "" + res += "finish_time = " + str(self.finish_time)+"\n" + res += "track_id = " + str(self.track_id)+"\n" + res += "character_id = " + str(self.character_id)+"\n" + res += "vehicle_id = " + str(self.vehicle_id)+"\n" + res += "year = " + str(self.year)+"\n" + res += "month = " + str(self.month)+"\n" + res += "day = " + str(self.day)+"\n" + res += "controller_id = " + str(self.controller_id)+"\n" + res += "compressed_flag = " + str(self.compressed_flag)+"\n" + res += "ghost_type = " + str(self.ghost_type)+"\n" + res += "drift_id = " + str(self.drift_id)+"\n" + res += "input_data_length = " + str(self.input_data_length)+"\n" + res += "lap_count = " + str(self.lap_count)+"\n" + res += "lap1_split = " + str(self.lap1_split)+"\n" + res += "lap2_split = " + str(self.lap2_split)+"\n" + res += "lap3_split = " + str(self.lap3_split)+"\n" + res += "country_code = " + str(self.country_code)+"\n" + res += "state_code = " + str(self.state_code)+"\n" + res += "location_code = " + str(self.location_code) + return res + + @staticmethod + def from_string(string): + lines = string.split("\n") + self = RKGMetaData(None, True) + self.finish_time = float(lines[0].split('=')[1]) + self.track_id = int(lines[1].split('=')[1]) + self.character_id = int(lines[2].split('=')[1]) + self.vehicle_id = int(lines[3].split('=')[1]) + self.year = int(lines[4].split('=')[1]) + self.month = int(lines[5].split('=')[1]) + self.day = int(lines[6].split('=')[1]) + self.controller_id = int(lines[7].split('=')[1]) + self.compressed_flag = int(lines[8].split('=')[1]) + self.ghost_type = int(lines[9].split('=')[1]) + self.drift_id = int(lines[10].split('=')[1]) + self.input_data_length = int(lines[11].split('=')[1]) + self.lap_count = int(lines[12].split('=')[1]) + self.lap1_split = float(lines[13].split('=')[1]) + self.lap2_split = float(lines[14].split('=')[1]) + self.lap3_split = float(lines[15].split('=')[1]) + self.country_code = int(lines[16].split('=')[1]) + self.state_code = int(lines[17].split('=')[1]) + self.location_code = int(lines[18].split('=')[1]) + return self + + def to_bytes(self): + bit_list = [] + add_bits(bit_list, convertTimerBack(self.finish_time), 24) + add_bits(bit_list, self.track_id, 6) + add_bits(bit_list, 0, 2) + add_bits(bit_list, self.vehicle_id, 6) + add_bits(bit_list, self.character_id, 6) + add_bits(bit_list, self.year, 7) + add_bits(bit_list, self.month, 4) + add_bits(bit_list, self.day, 5) + add_bits(bit_list, self.controller_id, 4) + add_bits(bit_list, 0, 4) + add_bits(bit_list, self.compressed_flag, 1) + add_bits(bit_list, 0, 2) + add_bits(bit_list, self.ghost_type, 7) + add_bits(bit_list, self.drift_id, 1) + add_bits(bit_list, 0, 1) + add_bits(bit_list, self.input_data_length, 8*2) + add_bits(bit_list, self.lap_count, 8*1) + add_bits(bit_list, convertTimerBack(self.lap1_split), 24) + add_bits(bit_list, convertTimerBack(self.lap2_split), 24) + add_bits(bit_list, convertTimerBack(self.lap3_split), 24) + add_bits(bit_list, 0, 2*3*8) + add_bits(bit_list, 0, 8*0x14) + add_bits(bit_list, self.country_code, 8) + add_bits(bit_list, self.state_code, 8) + add_bits(bit_list, self.location_code, 8*2) + add_bits(bit_list, 0, 8*4) + + return bytearray(b'RKGD') + bits_to_bytearray(bit_list) + + @staticmethod + def from_current_race(): + metadata = RKGMetaData(None, True) + race_config_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario()) + race_config_settings = RaceConfigSettings(addr=race_config_scenario.settings()) + race_config_player = RaceConfigPlayer(addr=race_config_scenario.player()) + player_input = PlayerInput(player_idx=0) + kart_input = KartInput(player_input.kart_input()) + controller = Controller(addr=kart_input.race_controller()) + + metadata.track_id = race_config_settings.course_id().value + metadata.vehicle_id = race_config_player.vehicle_id().value + metadata.character_id = race_config_player.character_id().value + metadata.drift_id = int(controller.drift_is_auto()) + + return metadata + + +def decompress_ghost_input(ghost_src): + if len(ghost_src) > 0x8F and ghost_src[0x8C:0x90] == b'Yaz1': + uncompressed_size = (ghost_src[0x90] << 24) + (ghost_src[0x91] << 16) + (ghost_src[0x92] << 8) + ghost_src[0x93] + return decode_Yaz1(ghost_src, 0x9c, uncompressed_size) + else: + return list(ghost_src[0x88:]) + +def decode_Yaz1(src, offset, uncompressed_size): + src_pos = offset + valid_bit_count = 0 + try: + curr_code_byte = src[offset + src_pos] + except: + curr_code_byte = src[src_pos] + + dst = [] + + while len(dst) < uncompressed_size: + if valid_bit_count == 0: + curr_code_byte = src[src_pos] + src_pos += 1 + valid_bit_count = 8 + + if (curr_code_byte & 0x80) != 0: + dst.append(src[src_pos]) + src_pos += 1 + else: + byte1 = src[src_pos] + byte2 = src[src_pos + 1] + src_pos += 2 + dist = ((byte1 & 0xF) << 8) | byte2 + copy_source = len(dst) - (dist + 1) + num_bytes = byte1 >> 4 + if num_bytes == 0: + num_bytes = src[src_pos] + 0x12 + src_pos += 1 + else: + num_bytes += 2 + + for _ in range(num_bytes): + dst.append(dst[copy_source]) + copy_source += 1 + + curr_code_byte <<= 1 + valid_bit_count -= 1 + + return dst + + +def decode_rkg_inputs(rkg_data): + raw_data = decompress_ghost_input(rkg_data) + button_inputs = [] + analog_inputs = [] + trick_inputs = [] + cur_byte = 8 + nr_button_inputs = (raw_data[0] << 8) | raw_data[1] + nr_analog_inputs = (raw_data[2] << 8) | raw_data[3] + nr_trick_inputs = (raw_data[4] << 8) | raw_data[5] + + for _ in range(nr_button_inputs): + if cur_byte + 1 < len(raw_data): + inputs = raw_data[cur_byte] + frames = raw_data[cur_byte + 1] + else: + inputs = 0 + frames = 0 + accelerator = inputs & 0x1 + drift = (inputs & 0x2) >> 1 + item = (inputs & 0x4) >> 2 + pseudoAB = (inputs & 0x8) >> 3 + breakdrift = (inputs & 0x10) >> 4 + button_inputs += [(accelerator, drift, item, pseudoAB, breakdrift)] * frames + cur_byte += 2 + + for _ in range(nr_analog_inputs): + if cur_byte + 1 < len(raw_data): + inputs = raw_data[cur_byte] + frames = raw_data[cur_byte + 1] + else: + inputs = 0 + frames = 0 + horizontal = ((inputs >> 4) & 0xF) - 7 + vertical = (inputs & 0xF) - 7 + analog_inputs += [(horizontal, vertical)] * frames + cur_byte += 2 + + for _ in range(nr_trick_inputs): + if cur_byte + 1 < len(raw_data): + inputs = raw_data[cur_byte] + frames = raw_data[cur_byte + 1] + else: + inputs = 0 + frames = 0 + trick = (inputs & 0x70) >> 4 + extra_frames = (inputs & 0x0F) << 8 + trick_inputs += [trick] * (frames + extra_frames) + cur_byte += 2 + inputList = [] + for i in range(len(button_inputs)): + if i >= len(analog_inputs): + analog_inputs.append((0,0)) + if i >= len(trick_inputs): + trick_inputs.append(0) + inputList.append(Frame([button_inputs[i][0], + button_inputs[i][1], + button_inputs[i][2], + button_inputs[i][3], + button_inputs[i][4], + analog_inputs[i][0], + analog_inputs[i][1], + trick_inputs[i]])) + res = FrameSequence() + res.read_from_list_of_frames(inputList) + return res + + +def encodeFaceButton(aButton, bButton, lButton, pabButton, bdButton): + return aButton * 0x1 + bButton * 0x2 + lButton * 0x4 + pabButton * 0x8 + bdButton * 0x10 + +def encodeDirectionInput(horizontalInput, verticalInput): + return ((horizontalInput+7) << 4) + verticalInput+7 + +def encodeTrickInput(trickInput): + return trickInput << 4 + +def encodeRKGFaceButtonInput(inputList : 'FrameSequence'): + data = bytearray() + iL = inputList.frames + fbBytes = 0 + if len(iL) == 0 : + return data, fbBytes + + prevInput = encodeFaceButton(iL[0].accel, iL[0].brake, iL[0].item, iL[0].drift, iL[0].brakedrift) #Not sure if it should be 0 instead + amountCurrentFrames = 0x0 + for ipt in iL: + currentInput = encodeFaceButton(ipt.accel, ipt.brake, ipt.item, ipt.drift, ipt.brakedrift) + if prevInput != currentInput or amountCurrentFrames >= 0xFF: + data.append(prevInput) + data.append(amountCurrentFrames) + prevInput = currentInput + amountCurrentFrames = 0x1 + fbBytes = fbBytes + 1 + else: + amountCurrentFrames = amountCurrentFrames + 1 + data.append(prevInput) + data.append(amountCurrentFrames) + fbBytes = fbBytes + 1 + return data, fbBytes + +def encodeRKGDirectionInput(inputList : 'FrameSequence'): + data = bytearray() + iL = inputList.frames + diBytes = 0 + if len(iL) == 0 : + return data, diBytes + + prevInput = encodeDirectionInput(iL[0].stick_x, iL[0].stick_y) + amountCurrentFrames = 0x0 + for ipt in iL: + currentInput = encodeDirectionInput(ipt.stick_x, ipt.stick_y) + if prevInput != currentInput or amountCurrentFrames >= 0xFF: + data.append(prevInput) + data.append(amountCurrentFrames) + prevInput = currentInput + amountCurrentFrames = 0x1 + diBytes = diBytes + 1 + else: + amountCurrentFrames = amountCurrentFrames + 1 + data.append(prevInput) + data.append(amountCurrentFrames) + diBytes = diBytes + 1 + return data, diBytes + +def encodeRKGTrickInput(inputList : 'FrameSequence'): + data = bytearray() + iL = inputList.frames + tiBytes = 0 + if len(iL) == 0 : + return data, tiBytes + + prevInput = encodeTrickInput(iL[0].dpad_raw()) + amountCurrentFrames = 0x0 + for ipt in iL: + currentInput = encodeTrickInput(ipt.dpad_raw()) + if prevInput != currentInput or amountCurrentFrames >= 0xFFF: + data.append(prevInput + (amountCurrentFrames >> 8)) + data.append(amountCurrentFrames % 0x100) + prevInput = currentInput + amountCurrentFrames = 0x1 + tiBytes = tiBytes + 1 + else: + amountCurrentFrames = amountCurrentFrames + 1 + + data.append(prevInput + (amountCurrentFrames >> 8)) + data.append(amountCurrentFrames % 0x100) + tiBytes = tiBytes + 1 + return data, tiBytes + +def encodeRKGInput(inputList : 'FrameSequence'): + #Encode face buttons inputs + fbData, fbBytes = encodeRKGFaceButtonInput(inputList) + + #Encode joystick inputs + diData, diBytes = encodeRKGDirectionInput(inputList) + + #Encode trick inputs + tiData, tiBytes = encodeRKGTrickInput(inputList) + + return fbData+diData+tiData, fbBytes, diBytes, tiBytes + + +def decode_RKG(raw_data : bytearray): + ''' Decode RKG data to 3 objetcs : + - RKGMetaData + - FrameSequence + - Mii Raw data (bytearray, size 0x4A)''' + if len(raw_data) < 0x88: + print("decode_RKG can't decode raw_data : raw_data too small") + else: + metadata = RKGMetaData(raw_data) + inputList = decode_rkg_inputs(raw_data) + mii_data = raw_data[0x3C:0x86] + return metadata, inputList, mii_data + + +def encode_RKG(metadata : 'RKGMetaData', inputList : 'FrameSequence', mii_data : bytearray): + ''' Encode to a RKG raw data (bytearray) ''' + inputData, fbBytes, diBytes, tiBytes = encodeRKGInput(inputList) + dataIndex = (fbBytes + diBytes + tiBytes) * 0x2 + metadata.input_data_length = dataIndex + 0x8 + crc16_int = crc16(mii_data) + + metadata_bytes = metadata.to_bytes() + crc16_data = bytearray([crc16_int >> 8, crc16_int & 0xFF]) + inputHeader = bytearray([fbBytes >> 8, fbBytes & 0xFF, diBytes >> 8, diBytes & 0xFF, tiBytes >> 8, tiBytes & 0xFF, 0, 0]) + + if metadata.compressed_flag: + compressed_input_data = compress(inputHeader + inputData) + rkg_data = metadata_bytes + mii_data + crc16_data + len(compressed_input_data).to_bytes(4, 'big') + compressed_input_data + else: + rkg_data = metadata_bytes + mii_data + crc16_data + inputHeader + inputData + crc32 = zlib.crc32(rkg_data) + arg1 = floor(crc32 / 0x1000000) + arg2 = floor((crc32 & 0x00FF0000) / 0x10000) + arg3 = floor((crc32 & 0x0000FF00) / 0x100) + arg4 = floor(crc32 % 0x100) + + return rkg_data + bytearray([arg1, arg2, arg3, arg4]) + + +def get_RKG_data_memory(): + ''' Return (is_available, rkg_data) : (bool, bytearray) + rkg_data is the uncompressed rkg created by the game when the race ends + It's only available after the text "A ghost has been created" is displayed. + is_available is a boolean saying if the text has appeared yet''' + region = utils.get_game_id() + try: + address = {"RMCE01": 0x809B8F88, "RMCP01": 0x809BD748, + "RMCJ01": 0x809BC7A8, "RMCK01": 0x809ABD88} + rkg_addr = chase_pointer(address[region], [0x18], 'u32') + except KeyError: + raise RegionError + if not memory.read_u32(rkg_addr) == 0x524b4744 : + return False, None + else: + return True, memory.read_bytes(rkg_addr, 0x2800) + diff --git a/scripts/Modules/settings_utils.py b/scripts/Modules/settings_utils.py new file mode 100644 index 0000000..4d311e4 --- /dev/null +++ b/scripts/Modules/settings_utils.py @@ -0,0 +1,249 @@ +from dolphin import utils +import configparser +import os + + +################# INFO DISPLAY CONFIG ######################## +class InfoDisplayConfigInstance(): + def __init__(self, config : configparser.ConfigParser): + self.debug = config['DEBUG'].getboolean('Debug') + self.frame_count = config['INFO DISPLAY'].getboolean('Frame Count') + self.rkg_buffer_size = config['INFO DISPLAY'].getboolean('RKG Buffer Size') + self.lap_splits = config['INFO DISPLAY'].getboolean('Lap Splits') + self.speed = config['INFO DISPLAY'].getboolean('Speed') + self.speed_oriented = config['INFO DISPLAY'].getboolean('Oriented Speed') + self.iv = config['INFO DISPLAY'].getboolean('Internal Velocity (X, Y, Z)') + self.iv_oriented = config['INFO DISPLAY'].getboolean('Oriented Internal Velocity') + self.iv_xyz = config['INFO DISPLAY'].getboolean('Internal Velocity (XYZ)') + self.ev = config['INFO DISPLAY'].getboolean('External Velocity (X, Y, Z)') + self.ev_oriented = config['INFO DISPLAY'].getboolean('Oriented External Velocity') + self.ev_xyz = config['INFO DISPLAY'].getboolean('External Velocity (XYZ)') + self.mrv = config['INFO DISPLAY'].getboolean('Moving Road Velocity (X, Y, Z)') + self.mrv_oriented = config['INFO DISPLAY'].getboolean('Oriented Moving Road Velocity') + 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_oriented = config['INFO DISPLAY'].getboolean('Oriented Moving Water Velocity') + 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.rotation = config['INFO DISPLAY'].getboolean('Rotation') + self.dpg = config['INFO DISPLAY'].getboolean('Distance Player-Ghost (X, Y, Z)') + self.dpg_oriented = config['INFO DISPLAY'].getboolean('Oriented Distance Player-Ghost') + self.dpg_xyz = config['INFO DISPLAY'].getboolean('Distance Player-Ghost (XYZ)') + self.vd_spd = config['INFO DISPLAY'].getboolean('Player-Ghost Speed diff') + self.vd_iv = config['INFO DISPLAY'].getboolean('Player-Ghost IV diff') + self.vd_ev = config['INFO DISPLAY'].getboolean('Player-Ghost EV diff') + self.rd_pitch = config['INFO DISPLAY'].getboolean('Player-Ghost Pitch diff') + self.rd_yaw = config['INFO DISPLAY'].getboolean('Player-Ghost Facing Yaw diff') + self.rd_movy = config['INFO DISPLAY'].getboolean('Player-Ghost Moving Yaw diff') + self.rd_roll = config['INFO DISPLAY'].getboolean('Player-Ghost Roll diff') + self.td_absolute = config['INFO DISPLAY'].getboolean('TimeDiff Absolute') + self.td_relative = config['INFO DISPLAY'].getboolean('TimeDiff Relative') + self.td_projected = config['INFO DISPLAY'].getboolean('TimeDiff Projected') + self.td_crosspath = config['INFO DISPLAY'].getboolean('TimeDiff CrossPath') + self.td_tofinish = config['INFO DISPLAY'].getboolean('TimeDiff ToFinish') + self.td_racecomp = config['INFO DISPLAY'].getboolean('TimeDiff RaceComp') + self.td_set = config['INFO DISPLAY']['TimeDiff Setting'] + self.td = self.td_absolute or self.td_relative or self.td_projected or self.td_crosspath or self.td_tofinish or self.td_racecomp + 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)')) + self.history_size = config['INFO DISPLAY'].getint('History Size') + + def write_to_file(self): + config = configparser.ConfigParser() + config['DEBUG'] = {} + config['DEBUG']['Debug'] = str(self.debug) + + config['INFO DISPLAY'] = {} + config['INFO DISPLAY']["Frame Count"] = str(self.frame_count) + config['INFO DISPLAY']["RKG Buffer Size"] = str(self.rkg_buffer_size) + config['INFO DISPLAY']["Lap Splits"] = str(self.lap_splits) + config['INFO DISPLAY']["Speed"] = str(self.speed) + config['INFO DISPLAY']["Oriented Speed"] = str(self.speed_oriented) + config['INFO DISPLAY']["Internal Velocity (X, Y, Z)"] = str(self.iv) + config['INFO DISPLAY']["Oriented Internal Velocity"] = str(self.iv_oriented) + config['INFO DISPLAY']["Internal Velocity (XYZ)"] = str(self.iv_xyz) + config['INFO DISPLAY']["External Velocity (X, Y, Z)"] = str(self.ev) + config['INFO DISPLAY']["Oriented External Velocity"] = str(self.ev_oriented) + config['INFO DISPLAY']["External Velocity (XYZ)"] = str(self.ev_xyz) + config['INFO DISPLAY']["Moving Road Velocity (X, Y, Z)"] = str(self.mrv) + config['INFO DISPLAY']["Oriented Moving Road Velocity"] = str(self.mrv_oriented) + config['INFO DISPLAY']["Moving Road Velocity (XYZ)"] = str(self.mrv_xyz) + config['INFO DISPLAY']["Moving Water Velocity (X, Y, Z)"] = str(self.mwv) + config['INFO DISPLAY']["Oriented Moving Water Velocity"] = str(self.mwv_oriented) + config['INFO DISPLAY']["Moving Water Velocity (XYZ)"] = str(self.mwv_xyz) + config['INFO DISPLAY']["Charges and Boosts"] = str(self.charges) + config['INFO DISPLAY']["Checkpoints and Completion"] = str(self.cps) + config['INFO DISPLAY']["Airtime"] = str(self.air) + config['INFO DISPLAY']["Miscellaneous"] = str(self.misc) + config['INFO DISPLAY']["Surface Properties"] = str(self.surfaces) + config['INFO DISPLAY']["Position"] = str(self.position) + config['INFO DISPLAY']["Rotation"] = str(self.rotation) + config['INFO DISPLAY']["Distance Player-Ghost (X, Y, Z)"] = str(self.dpg) + config['INFO DISPLAY']["Oriented Distance Player-Ghost"] = str(self.dpg_oriented) + config['INFO DISPLAY']["Distance Player-Ghost (XYZ)"] = str(self.dpg_xyz) + config['INFO DISPLAY']['Player-Ghost Speed diff'] = str(self.vd_spd) + config['INFO DISPLAY']['Player-Ghost IV diff'] = str(self.vd_iv) + config['INFO DISPLAY']['Player-Ghost EV diff'] = str(self.vd_ev) + config['INFO DISPLAY']['Player-Ghost Pitch diff'] = str(self.rd_pitch) + config['INFO DISPLAY']['Player-Ghost Facing Yaw diff'] = str(self.rd_yaw) + config['INFO DISPLAY']['Player-Ghost Moving Yaw diff'] = str(self.rd_movy) + config['INFO DISPLAY']['Player-Ghost Roll diff'] = str(self.rd_roll) + config['INFO DISPLAY']["TimeDiff Absolute"] = str(self.td_absolute) + config['INFO DISPLAY']["TimeDiff Relative"] = str(self.td_relative) + config['INFO DISPLAY']["TimeDiff Projected"] = str(self.td_projected) + config['INFO DISPLAY']["TimeDiff CrossPath"] = str(self.td_crosspath) + config['INFO DISPLAY']["TimeDiff ToFinish"] = str(self.td_tofinish) + config['INFO DISPLAY']["TimeDiff RaceComp"] = str(self.td_racecomp) + config['INFO DISPLAY']["TimeDiff Setting"] = self.td_set + config['INFO DISPLAY']["Stick"] = str(self.stick) + config['INFO DISPLAY']["Text Color (ARGB)"] = str(self.color) + config['INFO DISPLAY']["Digits (to round to)"] = str(self.digits) + config['INFO DISPLAY']["History Size"] = str(self.history_size) + + file_path = os.path.join(utils.get_script_dir(), 'Settings', 'Infodisplay.ini') + with open(file_path, 'w') as f: + config.write(f) + +def populate_default_config_infodisplay(file_path): + config = configparser.ConfigParser() + + config['DEBUG'] = {} + config['DEBUG']['Debug'] = "False" + + config['INFO DISPLAY'] = {} + config['INFO DISPLAY']["Frame Count"] = "True" + config['INFO DISPLAY']["RKG Buffer Size"] = "False" + config['INFO DISPLAY']["Lap Splits"] = "False" + config['INFO DISPLAY']["Speed"] = "True" + config['INFO DISPLAY']["Oriented Speed"] = "False" + config['INFO DISPLAY']["Internal Velocity (X, Y, Z)"] = "False" + config['INFO DISPLAY']["Oriented Internal Velocity"] = "False" + config['INFO DISPLAY']["Internal Velocity (XYZ)"] = "False" + config['INFO DISPLAY']["External Velocity (X, Y, Z)"] = "False" + config['INFO DISPLAY']["Oriented External Velocity"] = "False" + config['INFO DISPLAY']["External Velocity (XYZ)"] = "True" + config['INFO DISPLAY']["Moving Road Velocity (X, Y, Z)"] = "False" + config['INFO DISPLAY']["Oriented Moving Road Velocity"] = "False" + config['INFO DISPLAY']["Moving Road Velocity (XYZ)"] = "False" + config['INFO DISPLAY']["Moving Water Velocity (X, Y, Z)"] = "False" + config['INFO DISPLAY']["Oriented Moving Water Velocity"] = "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']["Rotation"] = "True" + config['INFO DISPLAY']["Distance Player-Ghost (X, Y, Z)"] = "False" + config['INFO DISPLAY']["Oriented Distance Player-Ghost"] = "False" + config['INFO DISPLAY']["Distance Player-Ghost (XYZ)"] = "False" + config['INFO DISPLAY']['Player-Ghost Speed diff'] = "False" + config['INFO DISPLAY']['Player-Ghost IV diff'] = "False" + config['INFO DISPLAY']['Player-Ghost EV diff'] = "False" + config['INFO DISPLAY']['Player-Ghost Pitch diff'] = "False" + config['INFO DISPLAY']['Player-Ghost Facing Yaw diff'] = "False" + config['INFO DISPLAY']['Player-Ghost Moving Yaw diff'] = "False" + config['INFO DISPLAY']['Player-Ghost Roll diff'] = "False" + config['INFO DISPLAY']["TimeDiff Absolute"] = "False" + config['INFO DISPLAY']["TimeDiff Relative"] = "False" + config['INFO DISPLAY']["TimeDiff Projected"] = "True" + config['INFO DISPLAY']["TimeDiff CrossPath"] = "False" + config['INFO DISPLAY']["TimeDiff ToFinish"] = "True" + config['INFO DISPLAY']["TimeDiff RaceComp"] = "True" + config['INFO DISPLAY']["TimeDiff Setting"] = "behind" + config['INFO DISPLAY']["Stick"] = "True" + config['INFO DISPLAY']["Text Color (ARGB)"] = "0xFFFFFFFF" + config['INFO DISPLAY']["Digits (to round to)"] = "6" + config['INFO DISPLAY']["History Size"] = "200" + + + with open(file_path, 'w') as f: + config.write(f) + + return config + +def get_infodisplay_config(): + config = configparser.ConfigParser() + file_path = os.path.join(utils.get_script_dir(), 'Settings', 'Infodisplay.ini') + config.read(file_path) + if not config.sections(): + config = populate_default_config_infodisplay(file_path) + return InfoDisplayConfigInstance(config) + + +################## AGC CONFIG ######################## +class AGCConfigInstance(): + def __init__(self, config : configparser.ConfigParser): + self.useFrames = config['DELAY'].getboolean('Delay unit in frame') + self.ghost_delay = eval(config['DELAY'].get('Ghost delay')) + self.player_delay = eval(config['DELAY'].get('Player delay')) + self.ghost_path = config['PATH'].get('Ghost .agc file path') + self.player_path = config['PATH'].get('Player .agc file path') + +def populate_default_config_agc(file_path): + config = configparser.ConfigParser() + + config['DELAY'] = {} + config['DELAY']['Delay unit in frame'] = "True" + config['DELAY']['Ghost delay'] = "0" + config['DELAY']['Player delay'] = "0" + + config['PATH'] = {} + config['PATH']['Ghost .agc file path'] = "AGC_Data/ghost_data.agc" + config['PATH']['Player .agc file path'] = "AGC_Data/player_data.agc" + + with open(file_path, 'w') as f: + config.write(f) + + return config + +def get_agc_config(): + config = configparser.ConfigParser() + file_path = os.path.join(utils.get_script_dir(), 'Settings', 'AGC.ini') + config.read(file_path) + if not config.sections(): + config = populate_default_config_agc(file_path) + return AGCConfigInstance(config) + + +################## TTK CONFIG ######################## +class TTKConfigInstance(): + def __init__(self, config : configparser.ConfigParser): + #self.ttk_path = config['PATH'].get('TTK folder path') + self.player_filename = config['PATH'].get('Player filename') + self.ghost_filename = config['PATH'].get('Ghost filename') + self.track_suffix = config['PATH'].getboolean('Use track suffix') + self.ttk_backup = config['BACKUP'].getint('Backup Amount') + +def populate_default_config_ttk(file_path): + config = configparser.ConfigParser() + + config['PATH'] = {} + #config['PATH']['TTK folder path'] = "MKW_Inputs/" + config['PATH']['Player filename'] = "TTK_Player_Inputs" + config['PATH']['Ghost filename'] = "TTK_Ghost_Inputs" + config['PATH']['Use track suffix'] = 'True' + + config['BACKUP'] = {} + config['BACKUP']['Backup Amount'] = '5' + + with open(file_path, 'w') as f: + config.write(f) + + return config + +def get_ttk_config(): + config = configparser.ConfigParser() + file_path = os.path.join(utils.get_script_dir(), 'Settings', 'TTK.ini') + config.read(file_path) + if not config.sections(): + config = populate_default_config_ttk(file_path) + return TTKConfigInstance(config) + diff --git a/scripts/Modules/src/__init__.py b/scripts/Modules/src/__init__.py new file mode 100644 index 0000000..e36beed --- /dev/null +++ b/scripts/Modules/src/__init__.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# libyaz0 +# Version 0.5 +# Copyright © 2017-2018 MasterVermilli0n / AboodXD + +# This file is part of libyaz0. + +# libyaz0 is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# libyaz0 is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +################################################################ +################################################################ + +try: + from . import yaz0_pyd as yaz0 + +except: + try: + from . import yaz0_so as yaz0 + + except: + try: + import pyximport + pyximport.install() + + from . import yaz0_cy as yaz0 + + except: + from . import yaz0 + + +def IsYazCompressed(data): + return data[:4] in [b'Yaz0', b'Yaz1'] + + +def decompress(data): + isYaz = IsYazCompressed(data) + + if isYaz: + return yaz0.DecompressYaz(bytes(data)) + + else: + raise ValueError("Not Yaz0 compressed!") + + +def compress(data, alignment=0, level=1): + compressed_data = yaz0.CompressYaz(bytes(data), level) + + result = bytearray(b'Yaz1') + result += len(data).to_bytes(4, "big") + result += alignment.to_bytes(4, "big") + result += b'\0\0\0\0' + result += compressed_data + + return result + + +def guessFileExt(data): + if data[0:4] == b"FRES": + return ".bfres" + + elif data[0:4] == b"FFNT": + return ".bffnt" + + elif data[0:4] == b"BNTX": + return ".bntx" + + elif data[0:4] == b"BNSH": + return ".bnsh" + + elif data[0:4] == b"FLAN": + return ".bflan" + + elif data[0:4] == b"FLYT": + return ".bflyt" + + elif data[0:4] == b"Gfx2": + return ".gtx" + + elif data[0:4] == b"SARC": + return ".sarc" + + elif data[0:4] == b"Yaz0": + return ".szs" + + elif data[-0x28:-0x24] == b"FLIM": + return ".bflim" + + else: # Couldn't guess the file extension + return ".bin" + + +__all__ = [IsYazCompressed, decompress, compress, guessFileExt] diff --git a/scripts/Modules/src/yaz0.py b/scripts/Modules/src/yaz0.py new file mode 100644 index 0000000..191acc4 --- /dev/null +++ b/scripts/Modules/src/yaz0.py @@ -0,0 +1,162 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# libyaz0 +# Version 0.5 +# Copyright © 2017-2018 MasterVermilli0n / AboodXD + +# This file is part of libyaz0. + +# libyaz0 is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# libyaz0 is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +################################################################ +################################################################ + +import struct + + +def DecompressYaz(src): + src_end = len(src) + + dest_end = struct.unpack(">I", src[4:8])[0] + dest = bytearray(dest_end) + + code = src[16] + + src_pos = 17 + dest_pos = 0 + + while src_pos < src_end and dest_pos < dest_end: + for _ in range(8): + if src_pos >= src_end or dest_pos >= dest_end: + break + + if code & 0x80: + dest[dest_pos] = src[src_pos]; dest_pos += 1; src_pos += 1 + + else: + b1 = src[src_pos]; src_pos += 1 + b2 = src[src_pos]; src_pos += 1 + + copy_src = dest_pos - ((b1 & 0x0f) << 8 | b2) - 1 + + n = b1 >> 4 + if not n: + n = src[src_pos] + 0x12; src_pos += 1 + + else: + n += 2 + + for _ in range(n): + dest[dest_pos] = dest[copy_src]; dest_pos += 1; copy_src += 1 + + code <<= 1 + + else: + if src_pos >= src_end or dest_pos >= dest_end: + break + + code = src[src_pos]; src_pos += 1 + + return bytes(dest) + + +def compressionSearch(src, pos, max_len, search_range, src_end): + found_len = 1 + found = 0 + + if pos + 2 < src_end: + search = pos - search_range + if search < 0: + search = 0 + + cmp_end = pos + max_len + if cmp_end > src_end: + cmp_end = src_end + + c1 = src[pos:pos + 1] + while search < pos: + search = src.find(c1, search, pos) + if search == -1: + break + + cmp1 = search + 1 + cmp2 = pos + 1 + + while cmp2 < cmp_end and src[cmp1] == src[cmp2]: + cmp1 += 1; cmp2 += 1 + + len_ = cmp2 - pos + + if found_len < len_: + found_len = len_ + found = search + if found_len == max_len: + break + + search += 1 + + return found, found_len + + +def CompressYaz(src, level): + if not level: + search_range = 0 + + elif level < 9: + search_range = 0x10e0 * level // 9 - 0x0e0 + + else: + search_range = 0x1000 + + pos = 0 + src_end = len(src) + + dest = bytearray() + code_byte_pos = 0 + + max_len = 0x111 + + while pos < src_end: + code_byte_pos = len(dest) + dest.append(0) + + for i in range(8): + if pos >= src_end: + break + + found_len = 1 + + if search_range: + found, found_len = compressionSearch(src, pos, max_len, search_range, src_end) + + if found_len > 2: + delta = pos - found - 1 + + if found_len < 0x12: + dest.append(delta >> 8 | (found_len - 2) << 4) + dest.append(delta & 0xFF) + + else: + dest.append(delta >> 8) + dest.append(delta & 0xFF) + dest.append((found_len - 0x12) & 0xFF) + + pos += found_len + + else: + dest[code_byte_pos] |= 1 << (7 - i) + dest.append(src[pos]); pos += 1 + + return bytes(dest) diff --git a/scripts/Modules/src/yaz0_cy.pyx b/scripts/Modules/src/yaz0_cy.pyx new file mode 100644 index 0000000..c4ef964 --- /dev/null +++ b/scripts/Modules/src/yaz0_cy.pyx @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# libyaz0 +# Version 0.5 +# Copyright © 2017-2018 MasterVermilli0n / AboodXD + +# This file is part of libyaz0. + +# libyaz0 is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# libyaz0 is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +################################################################ +################################################################ + +from cpython cimport array +from cython cimport view +from libc.stdlib cimport malloc, free +from libc.string cimport memchr + + +ctypedef unsigned char u8 +ctypedef char s8 +ctypedef unsigned int u32 + + +cpdef bytes DecompressYaz(bytes src_): + cdef: + array.array dataArr = array.array('B', src_) + u8 *src = dataArr.data.as_uchars + u8 *src_end = src + len(src_) + + u32 dest_len = (src[4] << 24 | src[5] << 16 | src[6] << 8 | src[7]) + u8 *dest = malloc(dest_len) + u8 *dest_pos = dest + u8 *dest_end = dest + dest_len + + u8 code = src[16] + + u8 b1, b2 + u8 *copy_src + int n + + src += 17 + + try: + while src < src_end and dest < dest_end: + for _ in range(8): + if src >= src_end or dest >= dest_end: + break + + if code & 0x80: + dest[0] = src[0]; dest += 1; src += 1 + + else: + b1 = src[0]; src += 1 + b2 = src[0]; src += 1 + + copy_src = dest - ((b1 & 0x0f) << 8 | b2) - 1 + + n = b1 >> 4 + if not n: + n = src[0] + 0x12; src += 1 + + else: + n += 2 + + for _ in range(n): + dest[0] = copy_src[0]; dest += 1; copy_src += 1 + + code <<= 1 + + else: + if src >= src_end or dest >= dest_end: + break + + code = src[0]; src += 1 + + return bytes(dest_pos) + + finally: + free(dest_pos) + + +cdef (u8 *, u32) compressionSearch(u8 *src, u8 *src_pos, int max_len, u32 range_, u8 *src_end): + cdef: + u32 found_len = 1 + + u8 *found + u8 *search + u8 *cmp_end + u8 c1 + u8 *cmp1 + u8 *cmp2 + int len_ + + if src + 2 < src_end: + search = src - range_ + if search < src_pos: + search = src_pos + + cmp_end = src + max_len + if cmp_end > src_end: + cmp_end = src_end + + c1 = src[0] + while search < src: + search = memchr(search, c1, src - search) + if not search: + break + + cmp1 = search + 1 + cmp2 = src + 1 + + while cmp2 < cmp_end and cmp1[0] == cmp2[0]: + cmp1 += 1; cmp2 += 1 + + len_ = cmp2 - src + + if found_len < len_: + found_len = len_ + found = search + if found_len == max_len: + break + + search += 1 + + return found, found_len + + +cpdef bytes CompressYaz(bytes src_, u8 opt_compr): + cdef u32 range_ + + if not opt_compr: + range_ = 0 + + elif opt_compr < 9: + range_ = 0x10e0 * opt_compr / 9 - 0x0e0 + + else: + range_ = 0x1000 + + cdef: + array.array dataArr = array.array('B', src_) + u8 *src = dataArr.data.as_uchars + u8 *src_pos = src + u8 *src_end = src + len(src_) + + u8 *dest = malloc(len(src_) + (len(src_) + 8) // 8) + u8 *dest_pos = dest + u8 *code_byte = dest + + int max_len = 0x111 + + int i + u32 found_len, delta + u8 *found + + try: + while src < src_end: + code_byte = dest + dest[0] = 0; dest += 1 + + for i in range(8): + if src >= src_end: + break + + found_len = 1 + + if range_: + found, found_len = compressionSearch(src, src_pos, max_len, range_, src_end) + + if found_len > 2: + delta = src - found - 1 + + if found_len < 0x12: + dest[0] = delta >> 8 | (found_len - 2) << 4; dest += 1 + dest[0] = delta & 0xFF; dest += 1 + + else: + dest[0] = delta >> 8; dest += 1 + dest[0] = delta & 0xFF; dest += 1 + dest[0] = (found_len - 0x12) & 0xFF; dest += 1 + + src += found_len + + else: + code_byte[0] |= 1 << (7 - i) + dest[0] = src[0]; dest += 1; src += 1 + + return bytes(dest_pos) + + finally: + free(dest_pos) diff --git a/scripts/Modules/src/yaz0_pyd.pyd b/scripts/Modules/src/yaz0_pyd.pyd new file mode 100644 index 0000000..5a320b2 Binary files /dev/null and b/scripts/Modules/src/yaz0_pyd.pyd differ diff --git a/scripts/Modules/startslide_utils.py b/scripts/Modules/startslide_utils.py new file mode 100644 index 0000000..077ca21 --- /dev/null +++ b/scripts/Modules/startslide_utils.py @@ -0,0 +1,70 @@ +from enum import Enum +from dolphin import event, gui, utils, memory # type: ignore +from Modules import ttk_lib +from Modules.mkw_utils import frame_of_input +from Modules import mkw_translations as translate +from Modules.mkw_classes import RaceManager, RaceState +from Modules.framesequence import FrameSequence +import os + +flame_slide_bikes = ("Flame Runner"), +mach_slide_bikes = ("Mach Bike", "Sugarscoot", "Zip Zip") +spear_slide_bikes = ("Jet Bubble", "Phantom", "Spear", "Sneakster") +wario_slide_bikes = ("Wario Bike") +wiggle_slide_bikes = ("Bit Bike", "Bullet Bike", "Dolphin Dasher", "Magikruiser", + "Quacker", "Shooting Star", "Standard Bike L", "Standard Bike M", + "Standard Bike S") + +class Direction(Enum): + RIGHT = "right" + LEFT = "left" + +def check_vehicle(vehicle: str): + global direction + path = utils.get_script_dir() + + if vehicle in flame_slide_bikes: + return os.path.join(path, "Startslides", f"flame_left.csv") + + elif vehicle in spear_slide_bikes: + return os.path.join(path, "Startslides", f"spear_left.csv") + + elif vehicle in mach_slide_bikes: + return os.path.join(path, "Startslides", f"mach_left.csv") + + elif vehicle in wario_slide_bikes: + return os.path.join(path, "Startslides", f"wario_left.csv") + + elif vehicle in wiggle_slide_bikes: + return os.path.join(path, "Startslides", f"wiggle_left.csv") + + else: # Karts fall here. We take any slides, just for the startboost + return os.path.join(path, "Startslides", f"spear_left.csv") + + +def on_state_load(is_slot, slot): + global player_inputs + if memory.is_memory_accessible(): + player_inputs = FrameSequence(check_vehicle(translate.vehicle_id())) + player_inputs.read_from_file() + +def on_frame_advance(): + frame = frame_of_input() + stage = RaceManager.state() + + player_input = player_inputs[frame] + if (player_input and stage.value == RaceState.COUNTDOWN.value): + ttk_lib.write_player_inputs(player_input, mirror = True if direction == Direction.RIGHT else False) + + +def execute_startslide(direction_: Direction): + global direction + direction = direction_ + + global player_inputs + player_inputs = FrameSequence(check_vehicle(translate.vehicle_id())) + + event.on_savestateload(on_state_load) + event.on_frameadvance(on_frame_advance) + + gui.add_osd_message(f"Startslide: {len(player_inputs) > 0}") diff --git a/scripts/Modules/ttk_config.py b/scripts/Modules/ttk_config.py index f5c70c6..a5ed9ab 100644 --- a/scripts/Modules/ttk_config.py +++ b/scripts/Modules/ttk_config.py @@ -1,4 +1,5 @@ from . import mkw_translations +from Modules import settings_utils as setting # when running undo_state_backup.py, creates up to backup_amount # backup files for your race inputs while you are TASing @@ -7,11 +8,17 @@ # csvs that player and ghost inputs will be written to must be tucked # inside a function because we want to support the course changing def text_file_path(path_name: str) -> str: - course = mkw_translations.course_slot_abbreviation() + ttk_settings = setting.get_ttk_config() + + if ttk_settings.track_suffix: + course = '_' + mkw_translations.course_slot_abbreviation() + else: + course = '' + text_file_paths = { - "Player": "MKW_Inputs/MKW_Player_Inputs_" + course + ".csv", - "Ghost": "MKW_Inputs/MKW_Ghost_Inputs_" + course + ".csv", - "Backup": "MKW_Inputs/Backups/" + course + "_" + "backup##.csv" + "Player": "MKW_Inputs/" + ttk_settings.player_filename + course + ".csv", + "Ghost": "MKW_Inputs/" + ttk_settings.ghost_filename + course + ".csv", + "Backup": "MKW_Inputs/Backups/" "ttk_backup##" + course +".csv" } return text_file_paths[path_name] diff --git a/scripts/Modules/ttk_lib.py b/scripts/Modules/ttk_lib.py index bd9540a..9e329ff 100644 --- a/scripts/Modules/ttk_lib.py +++ b/scripts/Modules/ttk_lib.py @@ -6,10 +6,13 @@ from typing import Tuple, List, Optional import zlib -from .framesequence import FrameSequence +from .framesequence import FrameSequence, Frame +from .rkg_lib import get_RKG_data_memory, decode_rkg_inputs, encodeRKGInput +from .rkg_lib import encodeRKGDirectionInput, encodeRKGTrickInput, encodeRKGFaceButtonInput +from .mkw_utils import chase_pointer from . import ttk_config -from .mkw_classes import RaceManager, RaceState +from .mkw_classes import RaceManager, RaceState, RaceManagerPlayer from .mkw_classes import GhostController, GhostButtonsStream from .mkw_classes import InputMgr, GhostWriter, PlayerInput, KartInput, Controller from .mkw_classes import RaceConfig, RaceConfigScenario, RaceConfigSettings @@ -28,8 +31,10 @@ def decode_face_button(input): A = input % 0x2 B = (input >> 1) % 0x2 L = (input >> 2) % 0x2 + D = (input >> 3) % 0x2 #drift button + BD = (input >> 4) % 0x2 #breakdrift button - return [A, B, L] + return [A, B, L, D, BD] def decode_direction_input(input): X = input >> 4 @@ -51,7 +56,40 @@ def encode_direction_input(X, Y): def encode_trick_input(input): return input * 0x10 - + +def get_rkg_size(player_type: PlayerType, input_type: ControllerInputType) -> int: + + # Determine memory region to access + if (player_type == PlayerType.PLAYER): + # This assumes player is index 0 + # For now let's assert this is a player + race_config = RaceConfig() + race_scenario = RaceConfigScenario(addr=race_config.race_scenario()) + race_config_player = RaceConfigPlayer(addr=race_scenario.player()) + assert(race_config_player.type() == RaceConfigPlayerType.REAL_LOCAL) + + ghost_writer = GhostWriter(addr=PlayerInput.ghost_writer()) + stream_addr = ghost_writer.button_stream(input_type.value) + button_stream = GhostButtonsStream(addr=stream_addr) + return button_stream.sequence_count() + else: + # TODO: Ghost is index=1 if you are racing a ghost, but if you watch a replay + # for example, the ghost is index 0. We want to support this scenario, so + # we'll need to determine how to set controller_idx appropriately. + ghost_addr = InputMgr.ghost_controller(1) + ghost_controller = GhostController(addr=ghost_addr) + stream_addr = ghost_controller.buttons_stream(input_type.value) + ghost_button_stream = GhostButtonsStream(addr=stream_addr) + return ghost_button_stream.sequence_count() + + +def get_full_rkg_size(player_type: PlayerType) -> int: + fb_size = get_rkg_size(player_type, ControllerInputType.FACE) + di_size = get_rkg_size(player_type, ControllerInputType.DI) + ti_size = get_rkg_size(player_type, ControllerInputType.TRICK) + return fb_size + di_size + ti_size + + # Reads binary data in-memory for the specified section def read_raw_rkg_data(player_type: PlayerType, input_type: ControllerInputType) -> list: ret_list = [] @@ -92,7 +130,7 @@ def read_raw_rkg_data(player_type: PlayerType, input_type: ControllerInputType) cur_addr += 0x2 data_tuple = memory.read_u16(cur_addr) - if (data_tuple == 0x0000 or cur_addr >= end_addr): + if (cur_addr >= end_addr): break return ret_list @@ -131,32 +169,40 @@ def decode_rkg_data(data: list, input_type: ControllerInputType) -> List[List[in # Transform raw RKG data into a FrameSequence def read_full_decoded_rkg_data(player_type: PlayerType) -> Optional[FrameSequence]: - # First make sure we're actually in a race, otherwise we need to bail out race_state = RaceManager.state() - if (race_state.value < RaceState.COUNTDOWN.value): - gui.add_osd_message("Not in race!") - return None - elif (race_state.value == RaceState.FINISHED_RACE.value): - gui.add_osd_message("Race is over!") - return None - - # Read each of the input types - face_data = read_raw_rkg_data(player_type, ControllerInputType.FACE) - di_data = read_raw_rkg_data(player_type, ControllerInputType.DI) - trick_data = read_raw_rkg_data(player_type, ControllerInputType.TRICK) - if not face_data or not di_data or not trick_data: - return None - - # Expand into a list where each index is a frame - face_data = decode_rkg_data(face_data, ControllerInputType.FACE) - di_data = decode_rkg_data(di_data, ControllerInputType.DI) - trick_data = decode_rkg_data(trick_data, ControllerInputType.TRICK) - - # Now transform into a framesequence - sequence_list = [face_data[x] + di_data[x] + [trick_data[x]] for x in range(len(face_data))] - sequence = FrameSequence() - sequence.read_from_list(sequence_list) - return sequence + if player_type == PlayerType.PLAYER and race_state.value == RaceState.FINISHED_RACE.value : + has_saved, rkg_data = get_RKG_data_memory() + if has_saved : + return decode_rkg_inputs(rkg_data) + else: + gui.add_osd_message("Wait for Ghost data saved message!") + return None + else: + # First make sure we're actually in a race, otherwise we need to bail out + if (race_state.value < RaceState.COUNTDOWN.value): + gui.add_osd_message("Not in race!") + return None + elif (race_state.value == RaceState.FINISHED_RACE.value): + gui.add_osd_message("Race is over!") + return None + + # Read each of the input types + face_data = read_raw_rkg_data(player_type, ControllerInputType.FACE) + di_data = read_raw_rkg_data(player_type, ControllerInputType.DI) + trick_data = read_raw_rkg_data(player_type, ControllerInputType.TRICK) + if not face_data or not di_data or not trick_data: + return None + + # Expand into a list where each index is a frame + face_data = decode_rkg_data(face_data, ControllerInputType.FACE) + di_data = decode_rkg_data(di_data, ControllerInputType.DI) + trick_data = decode_rkg_data(trick_data, ControllerInputType.TRICK) + + # Now transform into a framesequence + sequence_list = [face_data[x] + di_data[x] + [trick_data[x]] for x in range(len(face_data))] + sequence = FrameSequence() + sequence.read_from_list(sequence_list) + return sequence @dataclass class RKGTuple: @@ -221,7 +267,26 @@ def encode_rkg_data(input_list: FrameSequence) -> Tuple[List[int], List[int]]: all_tuples = face_tuples+di_tuples+trick_tuples tuple_lengths = [len(x) for x in (face_tuples, di_tuples, trick_tuples)] return all_tuples, tuple_lengths - + + +def setPlayerRKGBuffer(input_list: FrameSequence): + race_config = RaceConfig() + race_scenario = RaceConfigScenario(addr=race_config.race_scenario()) + race_config_player = RaceConfigPlayer(addr=race_scenario.player()) + assert(race_config_player.type() == RaceConfigPlayerType.REAL_LOCAL) + ghost_writer = GhostWriter(addr=PlayerInput.ghost_writer()) + + for input_type in ControllerInputType: + input_data, input_len = [encodeRKGFaceButtonInput, encodeRKGDirectionInput, encodeRKGTrickInput][input_type.value](input_list) + stream_addr = ghost_writer.button_stream(input_type.value) + button_stream = GhostButtonsStream(addr=stream_addr) + address = button_stream.buffer() + while len(input_data) bytearray: tuples, lengths = encode_rkg_data(input_data) @@ -272,6 +337,35 @@ def createRKGFile(input_data: FrameSequence, track_id: int, except ValueError: gui.add_osd_message("Attempted to parse byte > 0xFF! Aborting RKG write.") return bytearray() + + +def write_inputs_to_current_ghost_rkg(inputList): + '''function used for ttk activate ghost hard''' + data, fbBytes, diBytes, tiBytes = encodeRKGInput(inputList) + controller_address = KartInput(RaceManagerPlayer(1).kart_input()).race_controller() + address_fb = chase_pointer(controller_address + 0x94, [0x4], 'u32') + address_di = chase_pointer(controller_address + 0x98, [0x4], 'u32') + address_ti = chase_pointer(controller_address + 0x9C, [0x4], 'u32') + + address_fb_ptr = memory.read_u32(controller_address + 0x94) + 0x4 + address_di_ptr = memory.read_u32(controller_address + 0x98) + 0x4 + address_ti_ptr = memory.read_u32(controller_address + 0x9C) + 0x4 + + address_fb_len = memory.read_u32(controller_address + 0x94) + 0xC + address_di_len = memory.read_u32(controller_address + 0x98) + 0xC + address_ti_len = memory.read_u32(controller_address + 0x9C) + 0xC + while len(data) < 0x2774: + data.append(0x0) + + memory.write_bytes(address_fb, data[:0x2774]) + + memory.write_u32(address_di_ptr, address_fb + 2*fbBytes) + memory.write_u32(address_fb_len, 2*fbBytes) + + memory.write_u32(address_ti_ptr, address_fb + 2*(fbBytes+diBytes)) + memory.write_u32(address_di_len, 2*diBytes) + + memory.write_u32(address_ti_len, 2*tiBytes) # This is a tiny helper function that prevents slight repetition in filepath lookups def write_to_csv(inputs: FrameSequence, player_type: PlayerType) -> None: @@ -405,24 +499,29 @@ def controller_patch() -> None: memory.write_u32(patch_ptr + 0x50, memcpy_branch) memory.invalidate_icache(patch_ptr, 0x54) -def write_ghost_inputs(inputs: FrameSequence) -> None: +def write_ghost_inputs(inputs: Frame, ghost_id = 1) -> None: controller_patch() # TODO: This assumes the ghost is index 1, which is only true when racing a ghost - controller = Controller(addr=InputMgr.ghost_controller(1)) + controller = Controller(addr=InputMgr.ghost_controller(ghost_id)) set_buttons(inputs, controller) -def write_player_inputs(inputs: FrameSequence) -> None: +def write_player_inputs(inputs: Frame, mirror = False) -> None: controller_patch() kart_input = KartInput(addr=PlayerInput.kart_input()) controller = Controller(addr=kart_input.race_controller()) + if mirror: + inputs.stick_x = - inputs.stick_x + inputs.stick_y = - inputs.stick_y set_buttons(inputs, controller) + if mirror: + inputs.stick_x = - inputs.stick_x + inputs.stick_y = - inputs.stick_y def set_buttons(inputs, controller : Controller): """This writes button data to addresses with implicit padding in structs. This must be called only after controller_patch()""" addr = controller.addr - memory.write_u8(addr + 0x4d, inputs.accel + (inputs.brake << 1) + - (inputs.item << 2) | ((inputs.accel & inputs.brake) << 3) + 0x80) + memory.write_u8(addr + 0x4d, inputs.accel + (inputs.brake << 1) + (inputs.item << 2) + (inputs.drift << 3) + (inputs.brakedrift << 4) + 0x80) memory.write_u8(addr + 0x4e, inputs.stick_x + 7) memory.write_u8(addr + 0x4f, inputs.stick_y + 7) memory.write_u8(addr + 0x52, inputs.dpad_raw()) diff --git a/scripts/RMC/Adv. Ghost Comp/AGC_Copy_Inputs.py b/scripts/RMC/Adv. Ghost Comp/AGC_Copy_Inputs.py new file mode 100644 index 0000000..f2b1fbd --- /dev/null +++ b/scripts/RMC/Adv. Ghost Comp/AGC_Copy_Inputs.py @@ -0,0 +1,47 @@ +from dolphin import event, gui, utils +from Modules import agc_lib as lib +from Modules import settings_utils as setting +from Modules.mkw_classes import RaceManager, RaceState +from Modules import mkw_utils as mkw_utils +import os +from math import floor + +def main(): + global c + c = setting.get_agc_config() + + global filename + filename = os.path.join(utils.get_script_dir(), c.ghost_path) + + global framedatalist + metadata, framedatalist = lib.file_to_framedatalist(filename) + + global frame + frame = mkw_utils.frame_of_input() + +if __name__ == '__main__': + main() + + +@event.on_frameadvance +def on_frame_advance(): + global framedatalist + global c + global frame + + if not (frame == mkw_utils.frame_of_input() or frame == mkw_utils.frame_of_input()-1): + c = setting.get_agc_config() + + delay = c.ghost_delay + if not c.useFrames: + delay *= mkw_utils.fps_const + + racestate = RaceManager().state().value + frame = mkw_utils.frame_of_input() + delayed_frame = floor(delay)+frame + + + if racestate >= RaceState.COUNTDOWN.value : + if 0 < delayed_frame+1 < len(framedatalist): + framedatalist[delayed_frame+1].load(0, True) + diff --git a/scripts/RMC/Adv. Ghost Comp/AGC_Instant_Replay.py b/scripts/RMC/Adv. Ghost Comp/AGC_Instant_Replay.py new file mode 100644 index 0000000..ec86c9d --- /dev/null +++ b/scripts/RMC/Adv. Ghost Comp/AGC_Instant_Replay.py @@ -0,0 +1,47 @@ +from dolphin import event, gui, utils +from Modules import agc_lib as lib +from Modules import settings_utils as setting +from Modules.mkw_classes import RaceManager, RaceState +from Modules import mkw_utils as mkw_utils +import os +from math import floor + +def main(): + + global loaddatalist + loaddatalist = {} + + global storedatalist + storedatalist = {} + + global frame + frame = mkw_utils.frame_of_input() + +if __name__ == '__main__': + main() + + +@event.on_frameadvance +def on_frame_advance(): + global frame + + if not (frame == mkw_utils.frame_of_input() or frame == mkw_utils.frame_of_input()-1): + c = setting.get_agc_config() + loaddatalist.clear() + for key in storedatalist.keys(): + loaddatalist[key] = storedatalist[key] + storedatalist.clear() + + + racestate = RaceManager().state().value + frame = mkw_utils.frame_of_input() + + + if racestate >= RaceState.COUNTDOWN.value : + if frame in loaddatalist.keys(): + loaddatalist[frame].load(1) + + storedatalist[frame] = lib.AGCFrameData() + + + diff --git a/scripts/RMC/Adv. Ghost Comp/AGC_Load_Ghost.py b/scripts/RMC/Adv. Ghost Comp/AGC_Load_Ghost.py new file mode 100644 index 0000000..293a3a5 --- /dev/null +++ b/scripts/RMC/Adv. Ghost Comp/AGC_Load_Ghost.py @@ -0,0 +1,60 @@ +from dolphin import event, gui, utils +from Modules import agc_lib as lib +from Modules import settings_utils as setting +from Modules.mkw_classes import RaceManager, RaceState +from Modules import mkw_utils as mkw_utils +import os +from math import floor + +def main(): + global c + c = setting.get_agc_config() + + global filename + filename = os.path.join(utils.get_script_dir(), c.ghost_path) + + global framedatalist + global metadata + metadata, framedatalist = lib.file_to_framedatalist(filename) + + global frame + frame = mkw_utils.frame_of_input() + +if __name__ == '__main__': + main() + + +@event.on_frameadvance +def on_frame_advance(): + global metadata + global framedatalist + global c + global frame + + if not (frame == mkw_utils.frame_of_input() or frame == mkw_utils.frame_of_input()-1): + c = setting.get_agc_config() + + delay = c.ghost_delay + if not c.useFrames: + delay *= mkw_utils.fps_const + + racestate = RaceManager().state().value + frame = mkw_utils.frame_of_input() + delayed_frame = floor(delay)+frame + decimal_delay = delay - floor(delay) + + metadata.load(1) #Force the ghost's combo and drift type + + if not mkw_utils.is_single_player(): + if racestate >= RaceState.COUNTDOWN.value : + + metadata.delay_timer(delay) + + if 0 < delayed_frame+1 < len(framedatalist): + f1 = lib.AGCFrameData.read_from_string(str(framedatalist[delayed_frame])) #Makes a copy so you can modify f1 without affecting the framedatalist + f2 = framedatalist[delayed_frame+1] + f1.interpolate(f2, 1-decimal_delay, decimal_delay) + f1.load(1) + f2.load(1, True) + + diff --git a/scripts/RMC/Adv. Ghost Comp/AGC_Load_Player.py b/scripts/RMC/Adv. Ghost Comp/AGC_Load_Player.py new file mode 100644 index 0000000..6ba9bb7 --- /dev/null +++ b/scripts/RMC/Adv. Ghost Comp/AGC_Load_Player.py @@ -0,0 +1,59 @@ +from dolphin import event, gui, utils +from Modules import agc_lib as lib +from Modules import settings_utils as setting +from Modules.mkw_classes import RaceManager, RaceState +from Modules import mkw_utils as mkw_utils +import os +from math import floor + +def main(): + global c + c = setting.get_agc_config() + + global filename + filename = os.path.join(utils.get_script_dir(), c.player_path) + + global framedatalist + global metadata + metadata, framedatalist = lib.file_to_framedatalist(filename) + + global frame + frame = mkw_utils.frame_of_input() + + +@event.on_savestateload +def reload_config(_,__): + global c + c = setting.get_agc_config() + +if __name__ == '__main__': + main() + + +@event.on_frameadvance +def on_frame_advance(): + global metadata + global framedatalist + global c + global frame + + delay = c.player_delay + if not c.useFrames: + delay *= mkw_utils.fps_const + + racestate = RaceManager().state().value + frame = mkw_utils.frame_of_input() + delayed_frame = floor(delay)+frame + decimal_delay = delay - floor(delay) + + + if racestate >= RaceState.COUNTDOWN.value : + + if 0 < delayed_frame+1 < len(framedatalist): + f1 = lib.AGCFrameData.read_from_string(str(framedatalist[delayed_frame])) #Makes a copy so you can modify f1 without affecting the framedatalist + f2 = framedatalist[delayed_frame+1] + f1.interpolate(f2, 1-decimal_delay, decimal_delay) + f1.load(0) + f2.load(0, True) + + diff --git a/scripts/RMC/Adv. Ghost Comp/AGC_Save_Ghost.py b/scripts/RMC/Adv. Ghost Comp/AGC_Save_Ghost.py new file mode 100644 index 0000000..91a5879 --- /dev/null +++ b/scripts/RMC/Adv. Ghost Comp/AGC_Save_Ghost.py @@ -0,0 +1,64 @@ +from dolphin import event, gui, utils +import configparser +from Modules import agc_lib as lib +from Modules import settings_utils as setting +from Modules.mkw_classes import RaceManager, RaceState +from Modules import mkw_utils as mkw_utils +import os + + +def main(): + global c + c = setting.get_agc_config() + + global filename + filename = os.path.join(utils.get_script_dir(), c.ghost_path) + + global framedatalist + framedatalist = {} + + global frame + frame = mkw_utils.frame_of_input() + + global end + end = False + + global metadata + metadata = lib.AGCMetaData(read_id = 0) + +@event.on_scriptend +def save(id_): + if utils.get_script_id() == id_: + + lib.framedatalist_to_file(filename, framedatalist, metadata) + print("saved data") + + +@event.on_frameadvance +def on_frame_advance(): + global framedatalist + global end + global frame + global c + global metadata + metadata = lib.AGCMetaData(read_id = 0) + + if not (frame == mkw_utils.frame_of_input() or frame == mkw_utils.frame_of_input()-1): + c = setting.get_agc_config() + frame = mkw_utils.frame_of_input() + + racestate = RaceManager().state().value + + if (not end) and RaceState.RACE.value >= racestate >= RaceState.COUNTDOWN.value: + framedatalist[frame] = lib.AGCFrameData() + + if (not end) and racestate == RaceState.FINISHED_RACE.value: + end = True + utils.cancel_script(utils.get_script_name()) + + + + + +if __name__ == '__main__': + main() diff --git a/scripts/RMC/External Display/MKDS_minimap.py b/scripts/RMC/External Display/MKDS_minimap.py new file mode 100644 index 0000000..576f824 --- /dev/null +++ b/scripts/RMC/External Display/MKDS_minimap.py @@ -0,0 +1,122 @@ +from dolphin import event, gui, utils, memory +import configparser +import struct +from external import external_utils as ex +from Modules import agc_lib as lib +from Modules import settings_utils as setting +from Modules.mkw_classes import RaceManager, RaceState, VehiclePhysics, vec3 +from Modules import mkw_utils as mkw_utils +import os + + +def history_to_bytes(p): + ''' The encode will be 4 bytes for player pos_x, followed by 4 bytes for player pos_z + then 4 bytes for ghost pos_x, followed by 4 bytes for ghost pos_z, + repeat for all frames + first bytes correspond to recent frame, last bytes to old frames''' + res = bytearray() + for frame_data in p: + res = res + bytearray(struct.pack("f", frame_data['player'].x)) + res = res + bytearray(struct.pack("f", frame_data['player'].z)) + res = res + bytearray(struct.pack("f", frame_data['ghost'].x)) + res = res + bytearray(struct.pack("f", frame_data['ghost'].z)) + return bytes(res) + +def get_all_checkpoints(): + ''' The encode will be 4 bytes for left point_x, followed by 4 bytes for left point_z + then 4 bytes for right_point_x, followed by 4 bytes for right point_z, + repeat for all cp + first bytes correspond to first cp, last bytes to last cp (i think)''' + game_id = utils.get_game_id() + address = {"RMCE01": 0x809B8F28, "RMCP01": 0x809BD6E8, + "RMCJ01": 0x809BC748, "RMCK01": 0x809ABD28} + kmp_ref = mkw_utils.chase_pointer(address[game_id], [0x4, 0x0], 'u32') + ckpt_offset = memory.read_u32(kmp_ref+0x24) + ckph_offset = memory.read_u32(kmp_ref+0x28) + left_list = [] + right_list = [] + offset = ckpt_offset + res = bytearray() + id_list = bytearray() + while offset < ckph_offset: + res = res + bytearray(struct.pack("f", memory.read_f32(kmp_ref+0x4C+offset+0x8+0x0))) + res = res + bytearray(struct.pack("f", memory.read_f32(kmp_ref+0x4C+offset+0x8+0x4))) + res = res + bytearray(struct.pack("f", memory.read_f32(kmp_ref+0x4C+offset+0x8+0x8))) + res = res + bytearray(struct.pack("f", memory.read_f32(kmp_ref+0x4C+offset+0x8+0xC))) + id_list = id_list + bytearray(struct.pack("b", memory.read_s8(kmp_ref+0x4C+offset+0x8+0x11))) + offset += 0x14 + return bytes(res[:256*16]), bytes(id_list[:256]) + + + +def main(): + global end + end = False + + HISTORY_SIZE = 240 + + def player_pos(): + try: + return VehiclePhysics.position(0) + except: + return vec3(0,0,0) + + def ghost_pos(): + try: + return VehiclePhysics.position(1) + except: + return vec3(0,0,0) + + global position_history + position_history = mkw_utils.History({'player':player_pos, 'ghost':ghost_pos}, + HISTORY_SIZE) + + global frame + frame = mkw_utils.frame_of_input() + + global pos_writer + pos_writer = ex.SharedMemoryWriter('mkds minimap', HISTORY_SIZE*16) + + global cp_writer + cp_writer = ex.SharedMemoryWriter('mkds minimap checkpoints', 256*16) + + global cp_id_writer + cp_id_writer = ex.SharedMemoryWriter('mkds minimap checkpoints_id', 256) + + global yaw_writer + yaw_writer = ex.SharedMemoryWriter('mkds minimap yaw', 4) + + ex.start_external_script(os.path.join(utils.get_script_dir(), 'external', 'mkds_minimap_window.py')) + + +if __name__ == '__main__': + main() +@event.on_savestateload +def clear_history(*_): + position_history.clear() + +@event.on_frameadvance +def on_frame_advance(): + global position_history + global frame + global pos_writer + global end + + if (not end) and frame != mkw_utils.frame_of_input(): + position_history.update() + cp, cp_id = get_all_checkpoints() + try: + pos_writer.write(history_to_bytes(position_history)) + cp_writer.write(cp) + cp_id_writer.write(cp_id) + yaw_writer.write(struct.pack('f', mkw_utils.get_facing_angle(0).yaw)) + + except: + end = True + print('mkds minimap closed') + + frame = mkw_utils.frame_of_input() + + + + diff --git a/scripts/RMC/External Display/TAS_Input.py b/scripts/RMC/External Display/TAS_Input.py new file mode 100644 index 0000000..16acfeb --- /dev/null +++ b/scripts/RMC/External Display/TAS_Input.py @@ -0,0 +1,48 @@ +from dolphin import event, gui, utils +from external import external_utils as ex +from Modules import ttk_lib as lib +from Modules.framesequence import Frame +from Modules import mkw_utils as mkw_utils +import os + +def run_input(_ = None): + global position_history + global pos_writer + global end + + inp = input_reader.read_text().split(',') + if len(inp) == 8 and inp[3] == '-1': + #this logic is incorrect, most of the time + inp[3] = int(inp[0])*int(inp[1]) + if len(inp) == 8: + lib.write_player_inputs(Frame(inp)) + +def run_input_loop(_ = None): + while True: + run_input() + time.sleep(1) + +def main(): + global end + end = False + + global input_writer + input_writer = ex.SharedMemoryWriter('mkw tas input', 30) + + global input_reader + input_reader = ex.SharedMemoryReader('mkw tas input') + + + ex.start_external_script(os.path.join(utils.get_script_dir(), 'external', 'TAS_input_window.py')) + + +if __name__ == '__main__': + main() + + + +@event.on_framebegin +def on_frame_begin(): + run_input() + + diff --git a/scripts/RMC/External Display/TTK_GUI.py b/scripts/RMC/External Display/TTK_GUI.py new file mode 100644 index 0000000..748721b --- /dev/null +++ b/scripts/RMC/External Display/TTK_GUI.py @@ -0,0 +1,280 @@ +from dolphin import event, utils, memory # type: ignore +import os +import struct +import time +from external import external_utils as ex +from Modules import ttk_lib, rkg_lib +from Modules.framesequence import FrameSequence +from Modules.mkw_translations import course_slot_abbreviation +from Modules.mkw_utils import frame_of_input +from Modules.mkw_classes import RaceManager, RaceState + +PLAYER_STR = ["player", "ghost"] + +# Button functions + +def game_to_working_csv(source: ttk_lib.PlayerType, target: ttk_lib.PlayerType): + input_sequence = ttk_lib.read_full_decoded_rkg_data(source) + if not (input_sequence is None or len(input_sequence) == 0): + ttk_lib.write_to_csv(input_sequence, target) + +def save_player_to_player_csv(): + game_to_working_csv(ttk_lib.PlayerType.PLAYER, ttk_lib.PlayerType.PLAYER) + +def save_ghost_to_player_csv(): + game_to_working_csv(ttk_lib.PlayerType.GHOST, ttk_lib.PlayerType.PLAYER) + reload_inputs() + +def save_player_to_ghost_csv(): + game_to_working_csv(ttk_lib.PlayerType.PLAYER, ttk_lib.PlayerType.GHOST) + reload_inputs() + +def save_ghost_to_ghost_csv(): + game_to_working_csv(ttk_lib.PlayerType.GHOST, ttk_lib.PlayerType.GHOST) + reload_inputs() + + +def working_csv_to_rkg(source: ttk_lib.PlayerType): + input_sequence = ttk_lib.get_input_sequence_from_csv(source) + if input_sequence is None or len(input_sequence) == 0: + return + + filetype = [('RKG files', '*.rkg'), ('All files', '*')] + scriptDir = utils.get_script_dir() + ghostDir = os.path.join(utils.get_script_dir(), 'Ghost') + with open(os.path.join(scriptDir, 'Modules', "default.mii"), 'rb') as f: + mii_data = f.read()[:0x4A] + + file_path = ex.save_dialog_box(scriptDir, filetype, ghostDir, 'Save RKG') + if not file_path: + return + with open(file_path, "wb") as f: + f.write(rkg_lib.encode_RKG(rkg_lib.RKGMetaData.from_current_race(), input_sequence, mii_data)) + + +def save_player_csv_to_rkg(): + working_csv_to_rkg(ttk_lib.PlayerType.PLAYER) + +def save_ghost_csv_to_rkg(): + working_csv_to_rkg(ttk_lib.PlayerType.GHOST) + + +def rkg_to_working_csv(target: ttk_lib.PlayerType): + filetype = [('RKG files', '*.rkg'), ('All files', '*')] + scriptDir = utils.get_script_dir() + ghostDir = os.path.join(utils.get_script_dir(), 'Ghost') + file_path = ex.open_dialog_box(scriptDir, filetype, ghostDir, 'Open RKG') + if not file_path: + return + + with open(file_path, 'rb') as f: + rkg_data = rkg_lib.decode_RKG(f.read()) + input_sequence = None if rkg_data is None else rkg_data[1] + if input_sequence is None or len(input_sequence) == 0: + return + ttk_lib.write_to_csv(input_sequence, target) + + +def save_rkg_to_player_csv(): + rkg_to_working_csv(ttk_lib.PlayerType.PLAYER) + reload_inputs() + +def save_rkg_to_ghost_csv(): + rkg_to_working_csv(ttk_lib.PlayerType.GHOST) + reload_inputs() + +def open_player_editor(): + ex.open_file(player_inputs.filename) + +def open_ghost_editor(): + ex.open_file(ghost_inputs.filename) + +def csv_to_working_csv(target: ttk_lib.PlayerType): + filetype = [('CSV files', '*.csv'), ('All files', '*')] + scriptDir = utils.get_script_dir() + ttkDir = os.path.join(utils.get_script_dir(), 'MKW_Inputs') + file_path = ex.open_dialog_box(scriptDir, filetype, ttkDir, 'Open CSV') + if not file_path: + return + + input_sequence = FrameSequence(file_path) + input_sequence.read_from_file() + if input_sequence is None or len(input_sequence) == 0: + return + print(len(input_sequence)) + ttk_lib.write_to_csv(input_sequence, target) + +def save_csv_to_player(): + csv_to_working_csv(ttk_lib.PlayerType.PLAYER) + reload_inputs() + +def save_csv_to_ghost(): + csv_to_working_csv(ttk_lib.PlayerType.GHOST) + reload_inputs() + +# This constant determines the function that gets called by each button. +# The position of each function should match the corresponding button text in +# the other BUTTON_LAYOUT in `external/ttk_gui_window.py` +BUTTON_LAYOUT = [ + [ + [save_player_to_player_csv, save_ghost_to_player_csv], + [save_player_csv_to_rkg, save_rkg_to_player_csv], + [open_player_editor, save_csv_to_player] + ], + [ + [save_player_to_ghost_csv, save_ghost_to_ghost_csv], + [save_ghost_csv_to_rkg, save_rkg_to_ghost_csv], + [open_ghost_editor, save_csv_to_ghost], + ], +] + +def main(): + # If a button function is executed immediately while emulation is not paused, it can cause + # pycore to freeze. To avoid this, button events are stored in this queue. If emulation + # is not paused, the event will be processed in `on_frame_advance` which won't freeze. If + # emulation is paused, the event will be processed in `on_timertick`. + global button_queue + button_queue = [] + + global prev_track + prev_track = course_slot_abbreviation() + + global g_activate_ghost_hard + g_activate_ghost_hard = False + + global shm_activate, shm_player_csv, shm_ghost_csv + shm_activate = ex.SharedMemoryBlock.create(name="ttk_gui_activate", buffer_size=3) + shm_player_csv = ex.SharedMemoryWriter(name="ttk_gui_player_csv", buffer_size=256) + shm_ghost_csv = ex.SharedMemoryWriter(name="ttk_gui_ghost_csv", buffer_size=256) + + global player_inputs, ghost_inputs + player_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER) + ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST) + shm_player_csv.write_text(player_inputs.filename) + shm_ghost_csv.write_text(ghost_inputs.filename) + + ex.SharedMemoryBlock.create(name="ttk_gui_buttons", buffer_size=4) + global shm_buttons + shm_buttons = ex.SharedMemoryBlock.connect(name="ttk_gui_buttons") + + ex.SharedMemoryBlock.create(name="ttk_gui_window_closed", buffer_size=2) + global shm_close + shm_close = ex.SharedMemoryBlock.connect(name="ttk_gui_window_closed") + + window_script_path = os.path.join(utils.get_script_dir(), "external", "ttk_gui_window.py") + ex.start_external_script(window_script_path) + + + +# Function that watches for button events +# This function should be called continuously: +# either `on_timertick` when the game is paused +# or `on_frameadvance` when the game isn't paused +def listen_for_buttons(): + # If window sent a button event, add it to queue + if 'shm_buttons' in globals(): + button_event = shm_buttons.read()[:4] + else: + button_event = [False]*4 + + if button_event[0]: + button_queue.append(button_event) + shm_buttons.clear() + + if button_queue: + execute_button_function() + + if 'shm_close' in globals(): + close_event = shm_close.read_text() + if close_event == "1": + utils.cancel_script(utils.get_script_name()) + shm_close.write_text('0') + +# Helper that pops the oldest button event in the queue and executes it +def execute_button_function(): + button_event = button_queue.pop(0) + section_index, row_index, col_index = struct.unpack('>BBB', button_event[1:4]) + try: + BUTTON_LAYOUT[section_index][row_index][col_index]() + except Exception as e: + print(f"Error executing button function: {e}") + + +# Helper that reads state of activate checkboxes +def get_activation_state(): + return struct.unpack('>???', shm_activate.read()) + + +def reload_inputs(): + global player_inputs + global ghost_inputs + + player_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER) + player_inputs.read_from_file() + shm_player_csv.write_text(player_inputs.filename) + + ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST) + ghost_inputs.read_from_file() + shm_ghost_csv.write_text(ghost_inputs.filename) + + """if g_activate_ghost_hard: + ttk_lib.write_inputs_to_current_ghost_rkg(ghost_inputs)""" + + +@event.on_timertick +def on_timer_tick(): + #Only do stuff when the game is paused to avoid crashes + + """global g_activate_ghost_hard + try: + g_activate_ghost_hard + except NameError: + g_activate_ghost_hard = False""" + + if utils.is_paused() and memory.is_memory_accessible(): + """if not g_activate_ghost_hard: + if get_activation_state()[2]: + ttk_lib.write_inputs_to_current_ghost_rkg(ghost_inputs) + g_activate_ghost_hard = get_activation_state()[2]""" + listen_for_buttons() + + +@event.on_savestateload +def on_state_load(is_slot, slot): + if memory.is_memory_accessible(): + reload_inputs() + + +@event.on_framebegin +def on_frame_begin(): + listen_for_buttons() + + global g_activate_ghost_hard + activate_player, activate_ghost_soft, activate_ghost_hard = get_activation_state() + """if not g_activate_ghost_hard: + if get_activation_state()[2]: + ttk_lib.write_inputs_to_current_ghost_rkg(ghost_inputs)""" + g_activate_ghost_hard = activate_ghost_hard + + global prev_track + track = course_slot_abbreviation() + if track != prev_track: + reload_inputs() + prev_track = track + + if RaceManager.state() not in (RaceState.COUNTDOWN, RaceState.RACE): + return + + frame = frame_of_input() + + if activate_player and player_inputs[frame]: + ttk_lib.write_player_inputs(player_inputs[frame]) + + if activate_ghost_soft and ghost_inputs[frame]: + ttk_lib.write_ghost_inputs(ghost_inputs[frame]) + + if activate_ghost_hard and ghost_inputs[frame]: + ttk_lib.write_inputs_to_current_ghost_rkg(ghost_inputs) + + +main() diff --git a/scripts/RMC/External Display/external_info_display.py b/scripts/RMC/External Display/external_info_display.py new file mode 100644 index 0000000..4f84078 --- /dev/null +++ b/scripts/RMC/External Display/external_info_display.py @@ -0,0 +1,111 @@ +""" +Writes infodisplay text to a shared memory buffer than can be accessed by external applications +""" +import os +from dolphin import event, utils, memory # type: ignore +from external import external_utils +from Modules import settings_utils as setting +from Modules import mkw_utils as mkw_utils +from Modules.infodisplay_utils import create_infodisplay +from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState, KartObjectManager, VehiclePhysics + +from Modules.mkw_classes.common import SurfaceProperties, eulerAngle +from Modules.mkw_utils import History + +ROUND_DIGITS = 6 +TEXT_COLOR = 0xFFFFFFFF + +LAST_FRAME = {} + + +@event.on_frameadvance +def on_frame_advance(): + global Frame_of_input + global Angle_History + global RaceComp_History + global c + global special_event + global maxLap + + + race_mgr = RaceManager() + newframe = Frame_of_input != mkw_utils.frame_of_input() + draw = race_mgr.state().value >= RaceState.COUNTDOWN.value + if newframe and draw: + Frame_of_input = mkw_utils.frame_of_input() + try: + Angle_History.update() + RaceComp_History.update() + Pos_History.update() + if maxLap == int(RaceManagerPlayer.race_completion_max(0))-1: + mkw_utils.calculate_exact_finish(Pos_History, maxLap) + maxLap = int(RaceManagerPlayer.race_completion_max(0)) + except AssertionError: + pass + + global shm_writer + + if newframe: + Frame_of_input = mkw_utils.frame_of_input() + + if draw: + shm_writer.write_text(create_infodisplay(c, RaceComp_History, Angle_History)) + else: + RaceComp_History.clear() + Angle_History.clear() + Pos_History.clear() + + +@event.on_savestateload +def on_state_load(fromSlot: bool, slot: int): + global c + c = setting.get_infodisplay_config() + if memory.is_memory_accessible() and mkw_utils.extended_race_state() >= 0: + shm_writer.write_text(create_infodisplay(c, RaceComp_History, Angle_History)) + + +def main(): + global c + c = setting.get_infodisplay_config() + + global Frame_of_input + Frame_of_input = 0 + + def prc(): + if KartObjectManager.player_count() > 0: + return RaceManagerPlayer(0).race_completion() + return 0 + def grc(): + if KartObjectManager.player_count() > 1: + return RaceManagerPlayer(1).race_completion() + return 0 + def fa(): + return mkw_utils.get_facing_angle(0) + def ma(): + return mkw_utils.get_moving_angle(0) + def pos_(): + if KartObjectManager.player_count() > 0: + return VehiclePhysics.position(0) + return None + + global RaceComp_History + RaceComp_History = History({'prc':prc, 'grc':grc}, c.history_size) + + global Angle_History + Angle_History = History({'facing' : fa, 'moving' : ma}, 2) + + global Pos_History + Pos_History = History({'pos' : pos_}, 3) + + global maxLap + maxLap = None + + global shm_writer + shm_writer = external_utils.SharedMemoryWriter(name='infodisplay', buffer_size=4096) + + window_script_path = os.path.join(utils.get_script_dir(), "external", "info_display_window.py") + external_utils.start_external_script(window_script_path) + + +if __name__ == '__main__': + main() diff --git a/scripts/RMC/External Display/live_graphing.py b/scripts/RMC/External Display/live_graphing.py new file mode 100644 index 0000000..a05e5b0 --- /dev/null +++ b/scripts/RMC/External Display/live_graphing.py @@ -0,0 +1,41 @@ +import os +import struct +from dolphin import event, utils # type: ignore +from Modules import mkw_classes as mkw +from Modules import mkw_utils +from external import external_utils + + +def get_frame_data(): + frame = mkw_utils.frame_of_input() + vehicle_physics = mkw.VehiclePhysics() + ev = vehicle_physics.external_velocity() + return struct.pack('>Iff', frame, ev.length(), ev.length_xz()) + + +@event.on_frameadvance +def on_frame_advance(): + global shm_writer + global in_race + + if mkw.RaceManager().state().value >= mkw.RaceState.COUNTDOWN.value: + in_race = True + shm_writer.write(get_frame_data()) + elif in_race: + in_race = False + shm_writer.write(b'\x00') + + +def main(): + global in_race + in_race = False + + global shm_writer + shm_writer = external_utils.SharedMemoryWriter(name='graphdata', buffer_size=32) + + window_script_path = os.path.join(utils.get_script_dir(), "external", "live_graphing_window.py") + external_utils.start_external_script(window_script_path) + + +if __name__ == '__main__': + main() diff --git a/scripts/RMC/Extra - Debug/Auto_Input_Optimizer.py b/scripts/RMC/Extra - Debug/Auto_Input_Optimizer.py new file mode 100644 index 0000000..f0bfe5a --- /dev/null +++ b/scripts/RMC/Extra - Debug/Auto_Input_Optimizer.py @@ -0,0 +1,123 @@ +''' +This script can automatically modify inputs, with random input generation. +It is used to optimize some part of a TAS. +-You have to MANUALLY tell the script which function it need to maximize + by modifying the function 'calculate_score'. + Example : maximize the RaceCompletion of the player by frame 4242. + some example are coded in bruteforcer_lib.py +-You have to MANUALLY tell the script which random modification it can do + on your inputs. This is done with a list of "Randomizer" instance (class from bruteforcer_lib.py) + Typically those instances can do 1 random modification to a FrameSequence at a time. + the global variable randlist, definied in main(), is a list of all those Randomizer instance you + want to apply on each try. + +Currently, this script ALWAYS save an improvement, and NEVER saves a slower attempt. +this might change in the future, with a monte carlo approach. + +THIS SCRIPT USES SAVESTATE SLOT 4 +''' +from dolphin import event, savestate, utils +from Modules import ttk_lib +from Modules import mkw_utils as mkw_utils +from Modules import mkw_classes as mkw +from Modules.framesequence import FrameSequence, Frame +from Modules.bruteforcer_lib import score_racecomp, score_XZ_EV +from Modules import bruteforcer_lib as lib +import random +import os + + + + +def calculate_score(): + ''' This function has to return a score corresponding to what you want to optimize + from the current attempt. + If you want the current attempt to keep going, YOU HAVE TO RETURN NONE + This function is called on every newframe, so you can store data with + some mkw_utils.History for more complex data to calculate score for example''' + + frame = mkw_utils.frame_of_input() + if frame < 1700 : + return None + if mkw_utils.get_facing_angle(0).yaw%360 > 180.90: + return -float('inf') + return -mkw.VehiclePhysics(0).position().z + +def main(): + + global randlist + randlist = [] + + for frame in range(1407, 1633): + randlist.append(lib.Randomizer_Raw_Stick("Y", frame, 1/300, 5)) + randlist.append(lib.Randomizer_Raw_Stick("X", frame, 1/600, 5)) + randlist.append(lib.Randomizer_Alternate_Stick("Y", frame, 1/100, 2)) + randlist.append(lib.Randomizer_Alternate_Stick("X", frame, 1/300, 2)) + + + for frame in range(1450,1490): + randlist.append(lib.Randomizer_Alternate_Stick("Y", frame, 1/50, 2)) + randlist.append(lib.Randomizer_Raw_Stick("Y", frame, 1/200, 15)) + + for frame in range(1570,1615): + randlist.append(lib.Randomizer_Alternate_Stick("Y", frame, 1/50, 2)) + randlist.append(lib.Randomizer_Raw_Stick("Y", frame, 1/200, 15)) + + global highscore + highscore = None + + savestate.load_from_slot(4) + + global save + save = savestate.save_to_bytes() + + global cur_csv + cur_csv = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER) + + global best_csv + best_csv = cur_csv + + global attempt_counter + attempt_counter = 0 + + +@event.on_frameadvance +def on_frame_advance(): + global best_csv + global cur_csv + global attempt_counter + global highscore + + if True: + frame = mkw_utils.frame_of_input() + + if True: + score = calculate_score() + if not (score is None): + if (highscore is None) or (score > highscore): + attempt_counter = 0 + highscore = score + best_csv = cur_csv + savefilename = os.path.join(utils.get_script_dir(), "Input_optimizer", f"{highscore}.csv") + if best_csv.write_to_file(savefilename): + print(f"saved to {savefilename}") + else: + print('error with saving') + cur_csv = best_csv.copy() + for rand in randlist: + rand.random(cur_csv) + attempt_counter+= 1 + print(f"random attempt since last improvement : {attempt_counter}") + savestate.load_from_bytes(save) + + +@event.on_framebegin +def on_frame_begin(): + frame = mkw_utils.frame_of_input() + + player_input = cur_csv[frame] + if player_input: + ttk_lib.write_player_inputs(player_input) + +if __name__ == '__main__': + main() diff --git a/scripts/RMC/Extra - Debug/Auto_Teleport.py b/scripts/RMC/Extra - Debug/Auto_Teleport.py new file mode 100644 index 0000000..f8dac23 --- /dev/null +++ b/scripts/RMC/Extra - Debug/Auto_Teleport.py @@ -0,0 +1,114 @@ +''' +Start the script on the savestate frame ! +To use this script : You have to edit : SAVEFILENAME, TELEPORT_FRAME, LIST_OF_PARAM_EDITOR_FUNCTION, +LIST_OF_PARAM_ITERATOR, filter_attempt. +SAVEFILENAME : filename for the file you will save results in +TELEPORT_FRAME : frame you want the memory edits to happen (usually a teleportation) +LIST_OF_PARAM_EDITOR_FUNCTION : List of functions that will perform a memory edit when you get to TELEPORT_FRAME + Those functions MUST take only 1 argument, which is generally value of the memory edit desired. + Example : lambda x : mkw_utils.player_teleport(0, x=x) is a function that take an argument x, and teleport to the corresponding x position + You can get more fancy, and use this to modify other parameters, like the external velocity +LIST_OF_PARAM_ITERATOR : List of iterators. Must be the same length as LIST_OF_PARAM_EDITOR_FUNCTION. + LIST_OF_PARAM_ITERATOR[i] must be an iterator of valid argument for the function LIST_OF_PARAM_EDITOR_FUNCTION[i] + The script will iterate on all combinaison of possible valid arguments given by those iterators. + The total amount of iteration of the script is the product of all iterators in LIST_OF_PARAM_ITERATOR +filter_attempt : Read the comment of the function to edit it. +''' +from dolphin import event, savestate +from Modules.mkw_classes import VehiclePhysics +from Modules import mkw_utils as mkw_utils +from itertools import product + +SAVEFILENAME = 'collect_data.csv' + +TELEPORT_FRAME = 1858 + +LIST_OF_PARAM_EDITOR_FUNCTION = [lambda x : mkw_utils.player_teleport(0, x=x), #X position teleport function + lambda y : mkw_utils.player_teleport(0, y=y), #Z position teleport function + lambda z: mkw_utils.player_teleport(0, z=z)]#Yaw teleport function + +def make_range(middle, radius, step): + return iter(range(middle-radius, middle+radius, step)) + +LIST_OF_PARAM_ITERATOR = [make_range(14012, 20, 2), + make_range(22482, 20, 2), + make_range(51857, 20, 2)] + +def filter_attempt(): + ''' This function has to return the string corresponding to the data you want to save + from the current attempt. Generally, you want atleast cur_param, and eventually more data + You can return an empty string if you don't want to save anything from the current attempt + If you want the current attempt to keep going, YOU HAVE TO RETURN NONE + This function is called on every newframe, so you can store data with some mkw_utils.History for more complex data to save for example''' + + frame = mkw_utils.frame_of_input() + if frame < 1900 : + return None + if VehiclePhysics.position(0).x > 15000: + return str(cur_param)[1:-1]+'\n' + else: + return '' + +def main(): + global save + save = savestate.save_to_bytes() + + global savestring + savestring = '' + + global params_iterator + params_iterator = product(*LIST_OF_PARAM_ITERATOR) + + global cur_param + cur_param = next(params_iterator) + + global end + end = False + + global iteration + iteration = 1 + + global frame + frame = mkw_utils.frame_of_input() + +if __name__ == '__main__': + main() + + +@event.on_frameadvance +def on_frame_advance(): + global frame + global params_iterator + global save + global savestring + global cur_param + global end + global iteration + + if not end: + + newframe = mkw_utils.frame_of_input() != frame + frame = mkw_utils.frame_of_input() + + if newframe: + save_str = filter_attempt() + if not (save_str is None): + savestring += save_str + with open(SAVEFILENAME, 'w') as f: + f.write(savestring) + try: + cur_param = next(params_iterator) + print('iteration_count', iteration) + iteration += 1 + savestate.load_from_bytes(save) + except StopIteration: + end = True + + if frame == TELEPORT_FRAME: + print('current param', str(cur_param)) + for i in range(len(LIST_OF_PARAM_EDITOR_FUNCTION)): + f = LIST_OF_PARAM_EDITOR_FUNCTION[i] + arg = cur_param[i] + f(arg) + + diff --git a/scripts/RMC/Extra - Debug/Auto_Teleport_Connected_Component.py b/scripts/RMC/Extra - Debug/Auto_Teleport_Connected_Component.py new file mode 100644 index 0000000..2e6cdfa --- /dev/null +++ b/scripts/RMC/Extra - Debug/Auto_Teleport_Connected_Component.py @@ -0,0 +1,137 @@ +''' +Start the script on the savestate frame ! +To use this script : You have to edit : SAVEFILENAME, TELEPORT_FRAME, LIST_OF_PARAM_EDITOR_FUNCTION, +LIST_OF_PARAM_ITERATOR, filter_attempt. +SAVEFILENAME : filename for the file you will save results in +TELEPORT_FRAME : frame you want the memory edits to happen (usually a teleportation) +LIST_OF_PARAM_EDITOR_FUNCTION : List of functions that will perform a memory edit when you get to TELEPORT_FRAME + Those functions MUST take only 1 argument, which is generally value of the memory edit desired. + Example : lambda x : mkw_utils.player_teleport(0, x=x) is a function that take an argument x, and teleport to the corresponding x position + You can get more fancy, and use this to modify other parameters, like the external velocity +BASE_LIST : The first arguments of the functions in LIST_OF_PARAM_EDITOR_FUNCTION that will be tried +STEP_LIST : The steps you will take when looking for nearby arguments for LIST_OF_PARAM_EDITOR_FUNCTION +MAX_LIST : Put a maximum value for each argument of LIST_OF_PARAM_EDITOR_FUNCTION + You can use None for no max value +MIN_LIST : same but minimum value +filter_attempt : Read the comment of the function to edit it. +''' +from dolphin import event, savestate, utils +from Modules.mkw_classes import VehiclePhysics +from Modules import mkw_utils as mkw_utils +from itertools import product +from collections import deque + +SAVEFILENAME = 'collect_data.csv' + +TELEPORT_FRAME = 1858 + +LIST_OF_PARAM_EDITOR_FUNCTION = [lambda x : mkw_utils.player_teleport(0, x=x), #X position teleport function + lambda y : mkw_utils.player_teleport(0, y=y), #Z position teleport function + lambda z: mkw_utils.player_teleport(0, z=z)]#Yaw teleport function + + +BASE_LIST = [14012, 22600, 51857] + +STEP_LIST = [1, 0, 1] + +MAX_LIST = [None, None, None] + +MIN_LIST = [None, None, None] + +def filter_attempt(): + ''' This function has to return the string corresponding to the data you want to save + from the current attempt. Generally, you want atleast cur_param, and eventually more data + You can return an empty string if you don't want to save anything from the current attempt + If you want the current attempt to keep going, YOU HAVE TO RETURN NONE + This function is called on every newframe, so you can store data with some mkw_utils.History for more complex data to save for example''' + + frame = mkw_utils.frame_of_input() + if frame < 1900 : + return None + if VehiclePhysics.position(0).x > 15000: + return str(tuple([cur_coordinate[i] * STEP_LIST[i] + BASE_LIST[i] for i in range(DEPTH)]))[1:-1]+'\n' + else: + return '' + +def main(): + global DEPTH + DEPTH = len(BASE_LIST) + + global save + save = savestate.save_to_bytes() + + global savestring + savestring = '' + + global waiting_list + waiting_list = deque() + waiting_list.append(tuple([0]*DEPTH)) + + global visited_dict + visited_dict = {} + visited_dict[tuple([0]*DEPTH)] = True + + global end + end = False + + global cur_coordinate + cur_coordinate = tuple([0]*DEPTH) + + global iteration + iteration = 1 + + global frame + frame = mkw_utils.frame_of_input() + +if __name__ == '__main__': + main() + + +@event.on_frameadvance +def on_frame_advance(): + global frame + global params_iterator + global save + global savestring + global cur_coordinate + global end + global iteration + global waiting_list + global visited_dict + + if not end: + + newframe = mkw_utils.frame_of_input() != frame + frame = mkw_utils.frame_of_input() + + if newframe: + save_str = filter_attempt() + if not (save_str is None): + savestring += save_str + with open(SAVEFILENAME, 'w') as f: + f.write(savestring) + if save_str != '': + for change in product((-1,0,1), repeat = DEPTH): + new_coordinate = tuple([cur_coordinate[i] + change[i]*int(STEP_LIST[i] != 0) for i in range(DEPTH)]) + if not (new_coordinate in visited_dict.keys()): + #todo : check if new_coordinate doesn't go outside min and max + visited_dict[new_coordinate] = True + waiting_list.append(new_coordinate) + print('new coordinate added') + if waiting_list: + cur_coordinate = waiting_list.popleft() + print('wait list size:', len(waiting_list)) + savestate.load_from_bytes(save) + else: + end = True + utils.toggle_play() + + + if newframe and frame == TELEPORT_FRAME: + print('current param', str(cur_coordinate)) + for i in range(DEPTH): + f = LIST_OF_PARAM_EDITOR_FUNCTION[i] + arg = cur_coordinate[i] * STEP_LIST[i] + BASE_LIST[i] + f(arg) + + diff --git a/scripts/RMC/Extra - Debug/Bruteforce.py b/scripts/RMC/Extra - Debug/Bruteforce.py new file mode 100644 index 0000000..68ea0d3 --- /dev/null +++ b/scripts/RMC/Extra - Debug/Bruteforce.py @@ -0,0 +1,67 @@ +from dolphin import controller, event +from Modules import bruteforcer_lib as bfl +from Modules import mkw_utils as utils +from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState + +def main(): + def ruleset(x): + if x<250: + return bfl.forward_rule + return bfl.basic_rule + + iterset = lambda x : bfl.last_input_iterator + + global IptList + IptList = bfl.InputList(ruleset, iterset) + + global ss_frequency + ss_frequency = 60 + + global distance_key + distance_key = 0 + + global delay_input + delay_input = 1 + + + +if __name__ == '__main__': + main() + +def savename(frame): + return str(frame)+'.rawsav' + + +@event.on_frameadvance +def on_frame_advance(): + frame = utils.frame_of_input() + if RaceManager().state().value >= RaceState.COUNTDOWN.value and not utils.is_single_player(): + if utils.get_distance_ghost() <= distance_key : + if frame%ss_frequency == 1: + bfl.save(savename(frame)) + bfl.run_input(IptList[frame]) + else: + modif_frame = IptList.update(frame-delay_input) + frame_to_load = bfl.prevframe(modif_frame, ss_frequency) + print(f"desync at {frame}, modifying input at {modif_frame}, loading {frame_to_load}") + bfl.run_input(IptList[frame_to_load]) + bfl.load(savename(frame_to_load)) + #bfl.run_input2(bfl.forward) + + + + + + + + + + + + + + + + + + diff --git a/scripts/RMC/Extra - Debug/EV_burst.py b/scripts/RMC/Extra - Debug/EV_burst.py new file mode 100644 index 0000000..3dcbdca --- /dev/null +++ b/scripts/RMC/Extra - Debug/EV_burst.py @@ -0,0 +1,34 @@ +from dolphin import event, savestate, utils +import os +from pathlib import Path +from Modules import mkw_utils as mkw_utils +from Modules import framesequence as framesequence +from Modules import ttk_lib as ttk_lib +from Modules.mkw_translations import vehicle_id +from Modules import mkw_classes as mkw + +import time + +def main(): + #savestate.load_from_slot(2) + + mkw_utils.add_angular_ev(0, 0, 10, 0) + global end + end = False + + + + +@event.on_frameadvance +def frameadvance(): + global end + global g_save_file_name + + if (not end) and mkw_utils.extended_race_state() > 0: + utils.cancel_script(utils.get_script_name()) + end = True + + + +if __name__ == '__main__': + main() diff --git a/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_0.998_decay.py b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_0.998_decay.py new file mode 100644 index 0000000..b73c5c9 --- /dev/null +++ b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_0.998_decay.py @@ -0,0 +1,48 @@ +from dolphin import memory, utils, event + +def main(): + global region + region = utils.get_game_id() + + global og_values + og_values = {} + + global address_list + address_list = {"RMCE01": (0x805aa3d4, 0x805aa3dc, 0x805aa3e0)} + + try: + for address in address_list[region]: + og_values[address] = memory.read_u32(address) + memory.write_u32(address, 0x60000000) + memory.invalidate_icache(address, 0x4) + except KeyError: + raise RegionError + +@event.on_scriptend +def revert(id_): + if utils.get_script_id() == id_: + if memory.is_memory_accessible(): + for address in og_values.keys(): + memory.write_u32(address, og_values[address]) + memory.invalidate_icache(address, 0x4) + else: + print('Memory not accessible.') + print("Couldn't reverse the effect of the script") + + +@event.on_savestateload +def reload(*_): + global og_values + region = utils.get_game_id() + if memory.is_memory_accessible(): + try: + for address in address_list[region]: + og_values[address] = memory.read_u32(address) + memory.write_u32(address, 0x60000000) + memory.invalidate_icache(address, 0x4) + except KeyError: + raise RegionError + +if __name__ == '__main__': + main() + diff --git a/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_SG.py b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_SG.py new file mode 100644 index 0000000..cfcb035 --- /dev/null +++ b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_SG.py @@ -0,0 +1,48 @@ +from dolphin import memory, utils, event + +def main(): + global region + region = utils.get_game_id() + + global og_values + og_values = {} + + global address_list + address_list = {"RMCE01": (0x805ac80c, 0x805ac810)} + + try: + for address in address_list[region]: + og_values[address] = memory.read_u32(address) + memory.write_u32(address, 0x60000000) + memory.invalidate_icache(address, 0x4) + except KeyError: + raise RegionError + +@event.on_scriptend +def revert(id_): + if utils.get_script_id() == id_: + if memory.is_memory_accessible(): + for address in og_values.keys(): + memory.write_u32(address, og_values[address]) + memory.invalidate_icache(address, 0x4) + else: + print('Memory not accessible.') + print("Couldn't reverse the effect of the script") + + +@event.on_savestateload +def reload(*_): + global og_values + region = utils.get_game_id() + if memory.is_memory_accessible(): + try: + for address in address_list[region]: + og_values[address] = memory.read_u32(address) + memory.write_u32(address, 0x60000000) + memory.invalidate_icache(address, 0x4) + except KeyError: + raise RegionError + +if __name__ == '__main__': + main() + diff --git a/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_gravity.py b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_gravity.py new file mode 100644 index 0000000..db97539 --- /dev/null +++ b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_gravity.py @@ -0,0 +1,48 @@ +from dolphin import memory, utils, event + +def main(): + global region + region = utils.get_game_id() + + global og_values + og_values = {} + + global address_list + address_list = {"RMCE01": (0x805aa360, 0x805aa364, 0x805aa368)} + + try: + for address in address_list[region]: + og_values[address] = memory.read_u32(address) + memory.write_u32(address, 0x60000000) + memory.invalidate_icache(address, 0x4) + except KeyError: + raise RegionError + +@event.on_scriptend +def revert(id_): + if utils.get_script_id() == id_: + if memory.is_memory_accessible(): + for address in og_values.keys(): + memory.write_u32(address, og_values[address]) + memory.invalidate_icache(address, 0x4) + else: + print('Memory not accessible.') + print("Couldn't reverse the effect of the script") + + +@event.on_savestateload +def reload(*_): + global og_values + region = utils.get_game_id() + if memory.is_memory_accessible(): + try: + for address in address_list[region]: + og_values[address] = memory.read_u32(address) + memory.write_u32(address, 0x60000000) + memory.invalidate_icache(address, 0x4) + except KeyError: + raise RegionError + +if __name__ == '__main__': + main() + diff --git a/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_ground_decay.py b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_ground_decay.py new file mode 100644 index 0000000..7c2aed0 --- /dev/null +++ b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_ground_decay.py @@ -0,0 +1,44 @@ +from dolphin import memory, utils, event + +def main(): + region = utils.get_game_id() + global og_values + og_values = {} + try: + address_list = {"RMCE01": (0x805acec0, 0x805acecc, 0x805aced8)} + for address in address_list[region]: + og_values[address] = memory.read_u32(address) + memory.write_u32(address, 0x60000000) + memory.invalidate_icache(address, 0x4) + except KeyError: + raise RegionError + +@event.on_scriptend +def revert(id_): + if utils.get_script_id() == id_: + if memory.is_memory_accessible(): + for address in og_values.keys(): + memory.write_u32(address, og_values[address]) + memory.invalidate_icache(address, 0x4) + else: + print('Memory not accessible.') + print("Couldn't reverse the effect of the script") + + +@event.on_savestateload +def reload(*_): + global og_values + region = utils.get_game_id() + if memory.is_memory_accessible(): + try: + address_list = {"RMCE01": (0x805acec0, 0x805acecc, 0x805aced8)} + for address in address_list[region]: + og_values[address] = memory.read_u32(address) + memory.write_u32(address, 0x60000000) + memory.invalidate_icache(address, 0x4) + except KeyError: + raise RegionError + +if __name__ == '__main__': + main() + diff --git a/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_lean_effect.py b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_lean_effect.py new file mode 100644 index 0000000..04ac3ae --- /dev/null +++ b/scripts/RMC/Extra - Debug/Gecko code disabler/skip_EV_lean_effect.py @@ -0,0 +1,48 @@ +from dolphin import memory, utils, event + +def main(): + global region + region = utils.get_game_id() + + global og_values + og_values = {} + + global address_list + address_list = {"RMCE01": (0x805818d0, 0x805818e0, 0x805818f0)} + + try: + for address in address_list[region]: + og_values[address] = memory.read_u32(address) + memory.write_u32(address, 0x60000000) + memory.invalidate_icache(address, 0x4) + except KeyError: + raise RegionError + +@event.on_scriptend +def revert(id_): + if utils.get_script_id() == id_: + if memory.is_memory_accessible(): + for address in og_values.keys(): + memory.write_u32(address, og_values[address]) + memory.invalidate_icache(address, 0x4) + else: + print('Memory not accessible.') + print("Couldn't reverse the effect of the script") + + +@event.on_savestateload +def reload(*_): + global og_values + region = utils.get_game_id() + if memory.is_memory_accessible(): + try: + for address in address_list[region]: + og_values[address] = memory.read_u32(address) + memory.write_u32(address, 0x60000000) + memory.invalidate_icache(address, 0x4) + except KeyError: + raise RegionError + +if __name__ == '__main__': + main() + diff --git a/scripts/RMC/Extra - Debug/pointer_chase_backward.py b/scripts/RMC/Extra - Debug/pointer_chase_backward.py new file mode 100644 index 0000000..70314b1 --- /dev/null +++ b/scripts/RMC/Extra - Debug/pointer_chase_backward.py @@ -0,0 +1,171 @@ +from dolphin import memory +import time +from Modules import agc_lib as lib + + + +# Knuth-Morris-Pratt string matching +# David Eppstein, UC Irvine, 1 Mar 2002 + +def KnuthMorrisPratt(text, pattern): + + '''Yields all starting positions of copies of the pattern in the text. +Calling conventions are similar to string.find, but its arguments can be +lists or iterators, not just strings, it returns all matches, not just +the first one, and it does not need the whole text in memory at once. +Whenever it yields, it will have read the text exactly up to and including +the match that caused the yield.''' + + # allow indexing into pattern and protect against change during yield + pattern = list(pattern) + + # build table of shift amounts + shifts = [1] * (len(pattern) + 1) + shift = 1 + for pos in range(len(pattern)): + while shift <= pos and pattern[pos] != pattern[pos-shift]: + shift += shifts[pos-shift] + shifts[pos+1] = shift + + # do the actual search + startPos = 0 + matchLen = 0 + for c in text: + while matchLen == len(pattern) or \ + matchLen >= 0 and pattern[matchLen] != c: + startPos += shifts[matchLen] + matchLen -= shifts[matchLen] + matchLen += 1 + if matchLen == len(pattern): + yield startPos + + +def list_of_possible_list(l): + """Takes a list of list as an argmument. + Return a list containing each possible list you can form from those list + Ex : [ [a, b] + [a] + [c, e] ] + Return : [ [a,a,c] , [a,a,e], [b,a,c] etc]""" + res = [] + if l : + tmp = list_of_possible_list(l[1:]) + for val in l[0]: + for list_ in tmp: + res.append([val]+list_) + else: + return [[]] + + + +def scan_memory_array(addrStart, addrEnd, byte_array): + t1 = time.time() + memory_read = memory.read_bytes(addrStart, addrEnd-addrStart) + t2 = time.time() + res = [addrStart+offset for offset in KnuthMorrisPratt(memory_read, byte_array)] + t3 = time.time() + print(t2-t1, t3-t2) + return res + +def find_address_in_memory(addrStart, addrEnd, addrToFind, offsetmax): + res = [] + for addr in range(addrStart, addrEnd, 4): + if addrToFind - offsetmax <= memory.read_u32(addr) <= addrToFind: + res.append(addr) + return res + +def find_address_in_memory_fast(inv_graph, addr, offsetmax): + res = [] + for curaddr in range(addr, addr-offsetmax, -4): + if curaddr in inv_graph.keys(): + #res = res+inv_graph[curaddr] + res.append(min(inv_graph[curaddr])) + return res + +def is_addr(addr): + return (0x80000000 <= addr <= 0x9FFFFFFF) + +def create_graph(): + """Return a list where if addr is an address containing a pointer, list[addr] is the address pointed""" + graph = {} + for addr in range(0x80000000, 0x9FFFFFFF, 4): + val = memory.read_u32(addr) + if is_addr(val): + graph[addr] = val + return graph + +def invert_graph(graph): + """Return inv_graph so if graph[addr] is an address, inv_graph[graph[addr]] contain addr.""" + inv_graph = {} + for addr in graph.keys(): + if graph[addr] not in inv_graph.keys(): + inv_graph[graph[addr]] = [] + inv_graph[graph[addr]].append(addr) + return inv_graph + +def graphtofile(filename, graph, address): + """takes a graph (dic type), an address, and store to file""" + f = open(filename, 'w') + f.write(str(address)+"\n") + for addr in graph.keys(): + f.write(str(addr)+','+str(graph[addr])+'\n') + f.close() + + +def chase_backward(graph, inv_graph, startAddr, offsetmax, depth_max): + """Return a list of ptr chain, starting from anywhere, leading to startAddr""" + prev_addr_list = find_address_in_memory_fast(inv_graph, startAddr, offsetmax) + res = [[startAddr]] + if depth_max == 0: + 1+1 + else: + for prev_addr in prev_addr_list: + prev_res = chase_backward(graph, inv_graph, prev_addr, offsetmax, depth_max -1) + for ptr_chain in prev_res: + res.append(ptr_chain + [startAddr - graph[prev_addr]]) + return res + + +def chase_backward_multi_graph(graph_list, inv_graph_list, startAddr_list, offsetmax, depth_max): + res = [] + if depth_max: + for offset in range(0, offsetmax, 4): + b = True + for i in range(len(graph_list)): + b = b and startAddr_list[i]-offset in inv_graph_list[i].keys() + if b: + addr_list_list = [] + for i in range(len(graph_list)): + addr_list_list.append(inv_graph_list[i][startAddr_list[i]-offset]) + possible_prev_addr_list = list_of_possible_list(addr_list_list) + for prev_addr_list in possible_prev_addr_list: + prev_res = chase_backward_multi_graph(graph_list, inv_graph_list, prev_addr_list, offsetmax, depth_max-1) + for ptr_chain in prev_res: + res.append(ptr_chain + [offset]) + return res + + +def main(): + + s = lib.Split.from_rkg(lib.rkg_addr(), 1) + l = scan_memory_array(0x80F00000, 0x81FFFFFF, s.time_format_bytes()) + print(len(l)) + print(hex(l[0])) + t1 = time.time() + g = create_graph() + t2 = time.time() + i = invert_graph(g) + t3 = time.time() + print('create: ', t2-t1) + print('invert: ', t3-t2) + if l: + graphtofile('3.graph', g, l[0]) + + + +if __name__ == '__main__': + main() + + + + diff --git a/scripts/RMC/Freefly.py b/scripts/RMC/Freefly.py new file mode 100644 index 0000000..0478f62 --- /dev/null +++ b/scripts/RMC/Freefly.py @@ -0,0 +1,74 @@ +from dolphin import event, memory +from Modules import mkw_utils +from Modules.framesequence import Frame +from Modules.mkw_classes import vec3, VehiclePhysics, eulerAngle +import math + + +def main(): + global position + position = VehiclePhysics.position(0) + + global speed + speed = 80 + + global angles + angles = mkw_utils.get_facing_angle(0) + + global prevInput + prevInput = Frame([0,0,0,0,0,0,0,0]) + + global frame + frame = mkw_utils.frame_of_input() + + global yawAcc + yawAcc = eulerAngle(0,2,0) + + global pitchAcc + pitchAcc = eulerAngle(2,0,0) + + +@event.on_savestateload +def on_loadstate(*_): + if memory.is_memory_accessible(): + global position + position = VehiclePhysics.position(0) + + global speed + speed = 80 + + global angles + angles = mkw_utils.get_facing_angle(0) + +@event.on_frameadvance +def on_frame_advance(): + global frame + global angles + global position + global prevInput + global speed + + newframe = frame != mkw_utils.frame_of_input() + frame = mkw_utils.frame_of_input() + + if newframe : + curInput = Frame.from_current_frame(0) + + angles += yawAcc * (curInput.stick_x / 7) + angles -= pitchAcc * (curInput.stick_y / 7) + + if curInput.item and not prevInput.item : + speed *= 1.1 + if curInput.brake and not prevInput.brake: + speed /= 1.1 + + if curInput.accel: + position += angles.get_unit_vec3() * speed + + prevInput = curInput + + mkw_utils.player_teleport(0, position.x, position.y, position.z, + angles.pitch, angles.yaw, 0) + +if __name__ == '__main__': + main() diff --git a/scripts/RMC/Macros/optimize_EV.py b/scripts/RMC/Macros/optimize_EV.py new file mode 100644 index 0000000..7e68fff --- /dev/null +++ b/scripts/RMC/Macros/optimize_EV.py @@ -0,0 +1,38 @@ +""" +Usage: When holding left or right, neutral inputs will be added to optimize EV building via +leaning. Holding a hard direction (+-7) will turn as tight as possible without +sacrificing EV, while holding less than 7 will automatically turn as little as possible. +Can be used for neutral gliding, supersliding, or any other lean-based EV tech. +Enable this with the RFH script to perform supergrinding. +""" +from dolphin import controller, event # type: ignore +from Modules import mkw_classes as mkw +from Modules.macro_utils import MKWiiGCController + +def clamp(x: int, l: int, u: int): + return l if x < l else u if x > u else x + +@event.on_frameadvance +def on_frame_advance(): + if mkw.RaceManager().state() not in (mkw.RaceState.COUNTDOWN, mkw.RaceState.RACE): + return + if not mkw.KartSettings.is_bike(): + return + + ctrl = MKWiiGCController(controller) + user_inputs = ctrl.user_inputs() + kart_move = mkw.KartMove() + + neutral_input = 1 + + # Soft drift + if abs(user_inputs['StickX']) < 7: + ctrl.set_inputs({ + "StickX": clamp(user_inputs['StickX'], -2, 2), + }) + neutral_input = -1 + + if abs(kart_move.lean_rot()) + kart_move.lean_rot_increase() > kart_move.lean_rot_cap(): + ctrl.set_inputs({ + "StickX": neutral_input * clamp(user_inputs['StickX'], -1, 1), + }) diff --git a/scripts/RMC/Macros/rapid_fire_hop.py b/scripts/RMC/Macros/rapid_fire_hop.py new file mode 100644 index 0000000..4b855c3 --- /dev/null +++ b/scripts/RMC/Macros/rapid_fire_hop.py @@ -0,0 +1,21 @@ +""" +Usage: Hold B to rapid fire hop. Enable the `optimize_EV` script to supergrind. +""" +from dolphin import controller, event # type: ignore +from Modules import mkw_classes as mkw +from Modules.macro_utils import MKWiiGCController + + +@event.on_frameadvance +def on_frame_advance(): + if mkw.RaceManager().state() != mkw.RaceState.RACE: + return + + ctrl = MKWiiGCController(controller) + user_inputs = ctrl.user_inputs() + + if user_inputs["B"]: + hop_pressed = mkw.KartState.bitfield() & 0x80 > 0 + ctrl.set_inputs({ + "B": not hop_pressed, + }) diff --git a/scripts/RMC/Macros/semi_auto_chains.py b/scripts/RMC/Macros/semi_auto_chains.py new file mode 100644 index 0000000..9f3fa45 --- /dev/null +++ b/scripts/RMC/Macros/semi_auto_chains.py @@ -0,0 +1,19 @@ +""" +Usage: Hold UP while in a wheelie to perform perfect chain wheelies automatically. +""" +from dolphin import controller, event # type: ignore +from Modules import mkw_classes as mkw +from Modules.macro_utils import MKWiiGCController + +@event.on_frameadvance +def main(): + if mkw.RaceManager().state() != mkw.RaceState.RACE: + return + + ctrl = MKWiiGCController(controller) + user_inputs = ctrl.user_inputs() + + if user_inputs["Up"] and mkw.KartMove.wheelie_frames() == 180: + ctrl.set_inputs({ + "Up": False + }) diff --git a/scripts/RMC/Macros/soft_drift_assist.py b/scripts/RMC/Macros/soft_drift_assist.py new file mode 100644 index 0000000..1cd8f08 --- /dev/null +++ b/scripts/RMC/Macros/soft_drift_assist.py @@ -0,0 +1,23 @@ +""" +Usage: Hold a nonzero vertical stick input to clamp any horizontal stick inputs to optimal +soft drift values. Useful for soft drifting without turning off Full Input Range. +""" +from dolphin import controller, event # type: ignore +from Modules import mkw_classes as mkw +from Modules.macro_utils import MKWiiGCController + +def clamp(x: int, l: int, u: int): + return l if x < l else u if x > u else x + +@event.on_frameadvance +def on_frame_advance(): + if mkw.RaceManager().state() != mkw.RaceState.RACE: + return + + ctrl = MKWiiGCController(controller) + user_inputs = ctrl.user_inputs() + + if user_inputs["StickY"] != 0: + ctrl.set_inputs({ + "StickX": clamp(user_inputs["StickX"], -3, 3) + }) diff --git a/scripts/RMC/Macros/start_slide_left.py b/scripts/RMC/Macros/start_slide_left.py new file mode 100644 index 0000000..6c264c1 --- /dev/null +++ b/scripts/RMC/Macros/start_slide_left.py @@ -0,0 +1,4 @@ +from Modules.startslide_utils import Direction, execute_startslide + +if __name__ == '__main__': + execute_startslide(Direction.LEFT) \ No newline at end of file diff --git a/scripts/RMC/Macros/start_slide_right.py b/scripts/RMC/Macros/start_slide_right.py new file mode 100644 index 0000000..682131c --- /dev/null +++ b/scripts/RMC/Macros/start_slide_right.py @@ -0,0 +1,4 @@ +from Modules.startslide_utils import Direction, execute_startslide + +if __name__ == '__main__': + execute_startslide(Direction.RIGHT) \ No newline at end of file diff --git a/scripts/RMC/Macros/superhop.py b/scripts/RMC/Macros/superhop.py new file mode 100644 index 0000000..c6aa2fe --- /dev/null +++ b/scripts/RMC/Macros/superhop.py @@ -0,0 +1,151 @@ +""" +Usage: Hold B and commit to a drift to start superhopping in that direction. +To counterhop, hold the stick in the opposite direction of the superhopping. +To wheelie between hops, hold the UP button. Holding R will start a normal +drift even if B is held. Vertical stick inputs will not be overwritten. +""" +from dolphin import controller, event, gui # type: ignore +import dataclasses +from Modules import mkw_classes as mkw +from Modules.macro_utils import MKWiiGCController + +DEBUG_DISPLAY = False + +@dataclasses.dataclass +class State: + stage: int = -1 + direction: int = 0 + hold_drift: bool = False + + def __repr__(self): + return str(self.__dict__) + + def copy(self): + return State(**dataclasses.asdict(self)) + + +@event.on_savestatesave +def on_save_state(is_slot: bool, slot: int): + if is_slot: + savestates[slot] = state.copy() + +@event.on_savestateload +def on_load_state(is_slot: bool, slot: int): + global state + if is_slot: + state = savestates[slot].copy() + + +@event.on_frameadvance +def on_frame_advance(): + global state + + race_mgr = mkw.RaceManager() + if race_mgr.state().value != mkw.RaceState.RACE.value: + return + if not mkw.KartSettings.is_bike(): + return + + ctrl = MKWiiGCController(controller) + user_inputs = ctrl.user_inputs() + + kart_object = mkw.KartObject() + kart_move = mkw.KartMove(addr=kart_object.kart_move()) + + kart_collide = mkw.KartCollide(addr=kart_object.kart_collide()) + floor_collision = kart_collide.surface_properties().value & 0x1000 > 0 + + if DEBUG_DISPLAY: + gui.draw_text((10, gui.get_display_size()[1] - 20), 0xFFFFFFFF, state.__repr__()) + + + # Stop superhopping if R is held or B is released + if user_inputs['R'] or not user_inputs['B']: + state.stage = -1 + state.direction = 0 + return + + # Start superhopping on first drift commit + if state.stage == -1 and kart_move.hop_stick_x() != 0: + state.direction = kart_move.hop_stick_x() + state.hold_drift = True + state.stage = 0 + + # Default superhopping state + if state.stage == 0: + # If vehicle is on ground, start hop sequence + if floor_collision: + state.stage = 1 + # Otherwise, vehicle is in air so apply spindrift inputs + else: + neutral_glide = abs(kart_move.lean_rot()) + kart_move.lean_rot_increase() > kart_move.lean_rot_cap() + ctrl.set_inputs({ + "A": True, + "B": True, + "StickX": 0 if neutral_glide else (7 * state.direction), + "Up": False, + }) + + # Hop sequence, Frame 1 + if state.stage == 1: + # If next hop is drift hop, hold drift for one more frame + if state.hold_drift: + ctrl.set_inputs({ + "A": True, + "B": True, + "StickX": 7 * state.direction, + "Up": False, + }) + # If next hop isn't drift hop, skip + else: + state.stage = 2 + # Frame 2: Release drift + if state.stage == 2: + ctrl.set_inputs({ + "A": True, + "B": False, + "StickX": 2 * state.direction, + "Up": user_inputs["Up"], + }) + # Frame 3: Hop + if state.stage == 3: + ctrl.set_inputs({ + "A": True, + "B": True, + "StickX": 2 * state.direction, + "Up": False, + }) + # Frame 4: Commit drift + if state.stage == 4: + counterhop = user_inputs["StickX"] * state.direction > 0 + ctrl.set_inputs({ + "A": True, + "B": True, + "StickX": (2 * state.direction) if counterhop else (-1 * state.direction), + "Up": False, + }) + # Frame 5: Spindrift + if state.stage == 5: + ctrl.set_inputs({ + "A": True, + "B": True, + "StickX": 7 * state.direction, + "Up": False, + }) + state.hold_drift = not state.hold_drift + state.stage = 0 + + if state.stage > 0: + state.stage += 1 + + +def main(): + global state + state = State() + + global savestates + savestates = [state.copy()] * 10 + + +if __name__ == '__main__': + main() diff --git a/scripts/RMC/Macros/wheelie_turn.py b/scripts/RMC/Macros/wheelie_turn.py new file mode 100644 index 0000000..b02cba7 --- /dev/null +++ b/scripts/RMC/Macros/wheelie_turn.py @@ -0,0 +1,50 @@ +""" +Usage: Hold left or right while in a wheelie to perform optimal wheelie turning. Currently +does not account for boosts. +""" +from dolphin import controller, event # type: ignore +import math +from Modules import mkw_classes as mkw +from Modules.macro_utils import MKWiiGCController + +def clamp(x: int, l: int, u: int): + return l if x < l else u if x > u else x + +@event.on_frameadvance +def on_frame_advance(): + if mkw.RaceManager().state() != mkw.RaceState.RACE: + return + if not mkw.KartSettings.is_bike(): + return + + ctrl = MKWiiGCController(controller) + user_inputs = ctrl.user_inputs() + + player_stats = mkw.PlayerStats() + kart_move = mkw.KartMove() + + current_speed = kart_move.speed() + top_speed = player_stats.base_speed() * 1.15 + turn_speed = player_stats.handling_speed_multiplier() + A3 = player_stats.standard_accel_as(3) + + formula = math.ceil(((1 - ((top_speed - A3) / top_speed)) / (1 - turn_speed)) * 7) + + is_in_wheelie = mkw.KartState.bitfield(field_idx=0) & 0x20000000 > 0 + + if is_in_wheelie: + stick_val = 0 + if top_speed < current_speed * (turn_speed + (1 - turn_speed)) + A3: + if formula == 1: + stick_val = 1 + elif formula == 2: + stick_val = 2 + else: + if formula == 1: + pass + elif formula == 2: + stick_val = 1 + + ctrl.set_inputs({ + "StickX": clamp(user_inputs["StickX"], -stick_val, stick_val) + }) diff --git a/scripts/RMC/Misc. Scripts - Macros/semi_auto_chains.py b/scripts/RMC/Misc. Scripts - Macros/semi_auto_chains.py deleted file mode 100644 index 5355e4b..0000000 --- a/scripts/RMC/Misc. Scripts - Macros/semi_auto_chains.py +++ /dev/null @@ -1,14 +0,0 @@ -from dolphin import controller, event - -from Modules.mkw_classes import KartMove, RaceManager, RaceState - -@event.on_frameadvance -def main(): - race_mgr = RaceManager() - if race_mgr.state().value >= RaceState.COUNTDOWN.value: - pressing_up = controller.get_gc_buttons(0)["Up"] - if pressing_up and KartMove.wheelie_frames() == 180: - controller.set_gc_buttons( - 0, {"A": True, - "Up": False, - "StickX": controller.get_gc_buttons(0)["StickX"]}) diff --git a/scripts/RMC/Misc. Scripts - Macros/start_slide_left.py b/scripts/RMC/Misc. Scripts - Macros/start_slide_left.py deleted file mode 100644 index 1cb2784..0000000 --- a/scripts/RMC/Misc. Scripts - Macros/start_slide_left.py +++ /dev/null @@ -1,53 +0,0 @@ -from dolphin import event, gui, utils -from Modules import ttk_lib -from Modules.mkw_utils import frame_of_input -from Modules import mkw_translations as translate -from Modules.mkw_classes import RaceManager, RaceState, KartSettings -from Modules.framesequence import FrameSequence -import os - -flame_slide_bikes = ("Flame Runner", "Mach Bike", "Sugarscoot", "Zip Zip") -spear_slide_bikes = ("Jet Bubble", "Phantom", "Spear", "Sneakster", "Wario Bike") -star_slide_bikes = ("Bit Bike", "Bullet Bike", "Dolphin Dasher", "Magikruiser", - "Quacker", "Shooting Star", "Standard Bike L", "Standard Bike M", - "Standard Bike S") - -@event.on_savestateload -def on_state_load(is_slot, slot): - player_inputs.read_from_file() - -@event.on_frameadvance -def on_frame_advance(): - frame = frame_of_input() - stage = RaceManager.state() - - player_input = player_inputs[frame] - if (player_input and stage.value == RaceState.COUNTDOWN.value): - ttk_lib.write_player_inputs(player_input) - -def main() -> None: - global player_inputs - player_inputs = FrameSequence(check_vehicle(translate.vehicle_id())) - - gui.add_osd_message("Startslide: {} ".format(len(player_inputs) > 0)) - -# Ensures the right slide for the currently selected bike is being loaded, -# even through savestates and vehicle swaps. -def check_vehicle(vehicle): - - # Returns True if the player is using a bike. - if KartSettings.is_bike(): - - path = utils.get_script_dir() - - if vehicle in flame_slide_bikes: - return os.path.join(path, "MKW_Inputs", "Startslides", "flame_left.csv") - - elif vehicle in spear_slide_bikes: - return os.path.join(path, "MKW_Inputs", "Startslides", "spear_left.csv") - - elif vehicle in star_slide_bikes: - return os.path.join(path, "MKW_Inputs", "Startslides", "star_left.csv") - -if __name__ == '__main__': - main() diff --git a/scripts/RMC/Misc. Scripts - Macros/start_slide_right.py b/scripts/RMC/Misc. Scripts - Macros/start_slide_right.py deleted file mode 100644 index f6a2033..0000000 --- a/scripts/RMC/Misc. Scripts - Macros/start_slide_right.py +++ /dev/null @@ -1,53 +0,0 @@ -from dolphin import event, gui, utils -from Modules import ttk_lib -from Modules.mkw_utils import frame_of_input -from Modules import mkw_translations as translate -from Modules.mkw_classes import RaceManager, RaceState, KartSettings -from Modules.framesequence import FrameSequence -import os - -flame_slide_bikes = ("Flame Runner", "Mach Bike", "Sugarscoot", "Zip Zip") -spear_slide_bikes = ("Jet Bubble", "Phantom", "Spear", "Sneakster", "Wario Bike") -star_slide_bikes = ("Bit Bike", "Bullet Bike", "Dolphin Dasher", "Magikruiser", - "Quacker", "Shooting Star", "Standard Bike L", "Standard Bike M", - "Standard Bike S") - -@event.on_savestateload -def on_state_load(is_slot, slot): - player_inputs.read_from_file() - -@event.on_frameadvance -def on_frame_advance(): - frame = frame_of_input() - stage = RaceManager.state() - - player_input = player_inputs[frame] - if (player_input and stage.value == RaceState.COUNTDOWN.value): - ttk_lib.write_player_inputs(player_input) - -def main() -> None: - global player_inputs - player_inputs = FrameSequence(check_vehicle(translate.vehicle_id())) - - gui.add_osd_message("Startslide: {} ".format(len(player_inputs) > 0)) - -# Ensures the right slide for the currently selected bike is being loaded, -# even through savestates and vehicle swaps. -def check_vehicle(vehicle): - - # Returns True if the player is using a bike. - if KartSettings.is_bike(): - - path = utils.get_script_dir() - - if vehicle in flame_slide_bikes: - return os.path.join(path, "MKW_Inputs", "Startslides", "flame_right.csv") - - elif vehicle in spear_slide_bikes: - return os.path.join(path, "MKW_Inputs", "Startslides", "spear_right.csv") - - elif vehicle in star_slide_bikes: - return os.path.join(path, "MKW_Inputs", "Startslides", "star_right.csv") - -if __name__ == '__main__': - main() diff --git a/scripts/RMC/Misc. Scripts - Macros/wheelie_turn_left.py b/scripts/RMC/Misc. Scripts - Macros/wheelie_turn_left.py deleted file mode 100644 index 9f4cdbb..0000000 --- a/scripts/RMC/Misc. Scripts - Macros/wheelie_turn_left.py +++ /dev/null @@ -1,41 +0,0 @@ -from dolphin import controller, event -import math - -from Modules.mkw_classes import PlayerStats, KartMove - -@event.on_frameadvance -def on_frame_advance(): - player_stats = PlayerStats() - kart_move = KartMove() - - speed = player_stats.base_speed() - current_speed = kart_move.speed() - top_speed = speed * 1.15 - - turn_speed = player_stats.handling_speed_multiplier() - - A3 = player_stats.standard_accel_as(3) - - wheelie_frames = kart_move.wheelie_frames() - - formula = math.ceil(((1 - ((top_speed - A3) / top_speed)) / (1 - turn_speed)) * 7) - - if (wheelie_frames != 181): - stick_val = 128 - if top_speed < current_speed * (turn_speed + (1 - turn_speed)) + A3: - if formula == 1: - stick_val = 112 - elif formula == 2: - stick_val = 104 - else: - if formula == 1: - pass - elif formula == 2: - stick_val = 112 - controller.set_gc_buttons( - 0, {"A": True, "StickX": stick_val}) - else: - controller.set_gc_buttons( - 0, {"A": True, - "Up": controller.get_gc_buttons(0)["Up"], - "StickX": controller.get_gc_buttons(0)["StickX"]}) diff --git a/scripts/RMC/Misc. Scripts - Macros/wheelie_turn_right.py b/scripts/RMC/Misc. Scripts - Macros/wheelie_turn_right.py deleted file mode 100644 index 0e048bc..0000000 --- a/scripts/RMC/Misc. Scripts - Macros/wheelie_turn_right.py +++ /dev/null @@ -1,41 +0,0 @@ -from dolphin import controller, event -import math - -from Modules.mkw_classes import PlayerStats, KartMove - -@event.on_frameadvance -def on_frame_advance(): - player_stats = PlayerStats() - kart_move = KartMove() - - speed = player_stats.base_speed() - current_speed = kart_move.speed() - top_speed = speed * 1.15 - - turn_speed = player_stats.handling_speed_multiplier() - - A3 = player_stats.standard_accel_as(3) - - wheelie_frames = kart_move.wheelie_frames() - - formula = math.ceil(((1 - ((top_speed - A3) / top_speed)) / (1 - turn_speed)) * 7) - - if (wheelie_frames != 181): - stick_val = 128 - if top_speed < current_speed * (turn_speed + (1 - turn_speed)) + A3: - if formula == 1: - stick_val = 152 - elif formula == 2: - stick_val = 167 - else: - if formula == 1: - pass - elif formula == 2: - stick_val = 152 - controller.set_gc_buttons( - 0, {"A": True, "StickX": stick_val}) - else: - controller.set_gc_buttons( - 0, {"A": True, - "Up": controller.get_gc_buttons(0)["Up"], - "StickX": controller.get_gc_buttons(0)["StickX"]}) diff --git a/scripts/RMC/RKG_Loader.py b/scripts/RMC/RKG_Loader.py new file mode 100644 index 0000000..a415e9b --- /dev/null +++ b/scripts/RMC/RKG_Loader.py @@ -0,0 +1,73 @@ +from dolphin import gui, utils, event, memory +from Modules import ttk_lib +from Modules import agc_lib +from Modules import mkw_utils +from Modules.mkw_classes import RaceManager, RaceState +from external import external_utils as ex +from Modules.rkg_lib import decode_RKG +import os +import sys + + +""" +save_player_to_player_csv + +This script takes the player's inputs and writes them to the player csv +""" + +def main() -> None: + gui.add_osd_message("Script started") + global agc_metadata + global input_sequence + global end + + #Prompt the user to select a .rkg file + filetype = [('RKG files', '*.rkg'), ('All files', '*')] + scriptDir = utils.get_script_dir() + ghostDir = os.path.join(scriptDir, 'Ghost') + + file_path = ex.open_dialog_box(scriptDir, filetype, '', 'Open a RKG File') + + with open(file_path, 'rb') as f: + metadata, input_sequence, mii_data = decode_RKG(f.read()) + + agc_metadata = agc_lib.AGCMetaData.read_from_RKGMetadata(metadata) + + if input_sequence: + end = False + if not mkw_utils.is_single_player(): + if RaceManager().state().value > 0: + ttk_lib.write_inputs_to_current_ghost_rkg(input_sequence) + agc_metadata.delay_timer(0) + else: + end = True + +if __name__ == '__main__': + main() + +@event.on_savestateload +def on_state_load(is_slot, slot): + if memory.is_memory_accessible() and not mkw_utils.is_single_player(): + ttk_lib.write_inputs_to_current_ghost_rkg(input_sequence) + agc_metadata.delay_timer(0) + + +@event.on_frameadvance +def on_frame_advance(): + global agc_metadata + global input_sequence + global end + + if not end: + racestate = RaceManager().state().value + + agc_metadata.load(1) + + if not mkw_utils.is_single_player(): + if racestate == RaceState.COUNTDOWN.value : + ttk_lib.write_inputs_to_current_ghost_rkg(input_sequence) + agc_metadata.delay_timer(0) + + + + diff --git a/scripts/RMC/TTK - Activate/tas_toolkit.py b/scripts/RMC/TTK - Activate/tas_toolkit.py index 73b7202..b744c88 100644 --- a/scripts/RMC/TTK - Activate/tas_toolkit.py +++ b/scripts/RMC/TTK - Activate/tas_toolkit.py @@ -1,4 +1,4 @@ -from dolphin import event, gui +from dolphin import event, gui, memory from Modules import ttk_lib from Modules.mkw_utils import frame_of_input from Modules.framesequence import FrameSequence @@ -16,12 +16,18 @@ @event.on_savestateload def on_state_load(is_slot, slot): + global player_inputs + global ghost_inputs + if memory.is_memory_accessible(): + player_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER) + ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST) player_inputs.read_from_file() ghost_inputs.read_from_file() -@event.on_frameadvance -def on_frame_advance(): +@event.on_framebegin +def on_frame_begin(): global player_inputs, ghost_inputs + frame = frame_of_input() state = RaceManager.state().value inputs_ready = state in (RaceState.COUNTDOWN.value, RaceState.RACE.value) @@ -39,6 +45,7 @@ def main() -> None: global player_inputs, ghost_inputs player_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER) ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST) + gui.add_osd_message( "TTK | Player: {} | Ghost: {}".format( diff --git a/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only.py b/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only.py index 57f00e3..bf6d5a3 100644 --- a/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only.py +++ b/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only.py @@ -1,4 +1,4 @@ -from dolphin import event, gui +from dolphin import event, gui, memory from Modules import ttk_lib from Modules.mkw_utils import frame_of_input from Modules.framesequence import FrameSequence @@ -15,11 +15,15 @@ @event.on_savestateload def on_state_load(is_slot, slot): + global ghost_inputs + if memory.is_memory_accessible(): + ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST) ghost_inputs.read_from_file() -@event.on_frameadvance -def on_frame_advance(): +@event.on_framebegin +def on_frame_begin(): global ghost_inputs + frame = frame_of_input() state = RaceManager.state().value inputs_ready = state in (RaceState.COUNTDOWN.value, RaceState.RACE.value) @@ -32,6 +36,7 @@ def main() -> None: # Load both the player and ghost input sequences global ghost_inputs ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST) + gui.add_osd_message( "TTK | Player: {} | Ghost: {}".format( diff --git a/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only_hard.py b/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only_hard.py new file mode 100644 index 0000000..142eecb --- /dev/null +++ b/scripts/RMC/TTK - Activate/tas_toolkit_ghost_only_hard.py @@ -0,0 +1,48 @@ +from dolphin import event, gui, memory +from Modules import ttk_lib +from Modules.mkw_utils import frame_of_input +from Modules.framesequence import FrameSequence +from Modules.mkw_classes import RaceManager, RaceState + + +""" +tas_toolkit_ghost_only + +This script reads inputs from the ghost csv files, and applies it live in-game +The inputs are reloaded on every state load +""" + +@event.on_savestateload +def on_state_load(is_slot, slot): + global ghost_inputs + if memory.is_memory_accessible(): + ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST) + ghost_inputs.read_from_file() + +@event.on_framebegin +def on_frame_begin(): + global ghost_inputs + + frame = frame_of_input() + state = RaceManager.state().value + inputs_ready = state in (RaceState.COUNTDOWN.value, RaceState.RACE.value, RaceState.INTRO_CAMERA.value) + + if inputs_ready: + ttk_lib.write_inputs_to_current_ghost_rkg(ghost_inputs) + +def main() -> None: + # Load both the player and ghost input sequences + global ghost_inputs + ghost_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST) + ghost_inputs.read_from_file() + + + gui.add_osd_message( + "TTK | Player: {} | Ghost: {}".format( + False, len(ghost_inputs) > 0 + ) + ) + + +if __name__ == '__main__': + main() diff --git a/scripts/RMC/TTK - Activate/tas_toolkit_player_only.py b/scripts/RMC/TTK - Activate/tas_toolkit_player_only.py index ff480ac..c493574 100644 --- a/scripts/RMC/TTK - Activate/tas_toolkit_player_only.py +++ b/scripts/RMC/TTK - Activate/tas_toolkit_player_only.py @@ -1,4 +1,4 @@ -from dolphin import event, gui +from dolphin import event, gui, memory from Modules import ttk_lib from Modules.mkw_utils import frame_of_input from Modules.framesequence import FrameSequence @@ -15,11 +15,17 @@ @event.on_savestateload def on_state_load(is_slot, slot): + global player_inputs + if memory.is_memory_accessible(): + player_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER) player_inputs.read_from_file() -@event.on_frameadvance -def on_frame_advance(): + +@event.on_framebegin +def on_frame_begin(): global player_inputs + global frame + frame = frame_of_input() state = RaceManager.state().value inputs_ready = state in (RaceState.COUNTDOWN.value, RaceState.RACE.value) @@ -32,6 +38,7 @@ def main() -> None: # Load both the player and ghost input sequences global player_inputs player_inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER) + gui.add_osd_message( "TTK | Player: {} | Ghost: {}".format( diff --git a/scripts/RMC/TTK - Saving/Erase_Player_Buffer_RKG.py b/scripts/RMC/TTK - Saving/Erase_Player_Buffer_RKG.py new file mode 100644 index 0000000..c582989 --- /dev/null +++ b/scripts/RMC/TTK - Saving/Erase_Player_Buffer_RKG.py @@ -0,0 +1,13 @@ +from Modules import ttk_lib +from Modules.mkw_utils import frame_of_input +from Modules.framesequence import FrameSequence, Frame + + + +def main() -> None: + nulFrameSequence = FrameSequence() + nulFrameSequence.read_from_list_of_frames([Frame.default() for _ in range(frame_of_input())]) + ttk_lib.setPlayerRKGBuffer(nulFrameSequence) + +if __name__ == '__main__': + main() diff --git a/scripts/RMC/TTK - Saving/save_GUI.py b/scripts/RMC/TTK - Saving/save_GUI.py new file mode 100644 index 0000000..0e12892 --- /dev/null +++ b/scripts/RMC/TTK - Saving/save_GUI.py @@ -0,0 +1,70 @@ +from dolphin import gui, utils, memory, event +from Modules import ttk_lib +from external import external_utils as ex +from Modules.rkg_lib import decode_RKG, encode_RKG, RKGMetaData +from Modules.framesequence import FrameSequence +import os +import sys +import time + + + +def main() -> None: + gui.add_osd_message("Script started") + + scriptDir = utils.get_script_dir() + scriptname = os.path.join(scriptDir, 'external', 'TTK_Save_GUI_window.py') + + std = ex.run_external_script(scriptname) + args = std.split('\n')[0].split('|') + + if len(args) > 1: + if args[0] == 'Player Inputs': + inputs = ttk_lib.read_full_decoded_rkg_data(ttk_lib.PlayerType.PLAYER) + elif args[0] == 'Ghost Inputs': + inputs = ttk_lib.read_full_decoded_rkg_data(ttk_lib.PlayerType.GHOST) + elif args[0] == 'CSV Player': + inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.PLAYER) + inputs.read_from_file() + elif args[0] == 'CSV Ghost': + inputs = ttk_lib.get_input_sequence_from_csv(ttk_lib.PlayerType.GHOST) + inputs.read_from_file() + else: + if args[0][-4:] == ".rkg": + with open(args[0], "rb") as f: + inputs = decode_RKG(f.read())[1] + elif args[0][-4:] == ".csv": + inputs = FrameSequence(args[0]) + inputs.read_from_file() + else: + raise ValueError('Invalid source file') + else: + raise ValueError('Invalid source file') + + with open(os.path.join(scriptDir, 'Modules', "default.mii"), 'rb') as f: + mii_data = f.read()[:0x4A] + + for i in range(1, len(args)): + arg = args[i] + if arg == 'csv_player': + ttk_lib.write_to_csv(inputs, ttk_lib.PlayerType.PLAYER) + elif arg == 'csv_ghost': + ttk_lib.write_to_csv(inputs, ttk_lib.PlayerType.GHOST) + elif arg[-4:] == ".rkg": + with open(arg, "wb") as f: + f.write(encode_RKG(RKGMetaData.from_current_race(), inputs, mii_data)) + elif arg[-4:] == ".csv": + inputs.write_to_file(arg) + + + +if __name__ == '__main__': + main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/TTK - Saving/save_ghost_to_both_csv.py b/scripts/RMC/TTK - Saving/save_ghost_to_both_csv.py index d25f0ba..35f9d16 100644 --- a/scripts/RMC/TTK - Saving/save_ghost_to_both_csv.py +++ b/scripts/RMC/TTK - Saving/save_ghost_to_both_csv.py @@ -1,5 +1,6 @@ -from dolphin import gui +from dolphin import gui, utils, event from Modules import ttk_lib +import time """ save_ghost_to_both_csv @@ -20,5 +21,15 @@ def main() -> None: ttk_lib.write_to_csv(input_sequence, ttk_lib.PlayerType.GHOST) ttk_lib.write_to_csv(input_sequence, ttk_lib.PlayerType.PLAYER) + + if __name__ == '__main__': main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/TTK - Saving/save_ghost_to_ghost_csv.py b/scripts/RMC/TTK - Saving/save_ghost_to_ghost_csv.py index 1593222..df5686d 100644 --- a/scripts/RMC/TTK - Saving/save_ghost_to_ghost_csv.py +++ b/scripts/RMC/TTK - Saving/save_ghost_to_ghost_csv.py @@ -1,5 +1,6 @@ -from dolphin import gui +from dolphin import gui, utils, event from Modules import ttk_lib +import time """ save_ghost_to_ghost_csv @@ -21,3 +22,11 @@ def main() -> None: if __name__ == '__main__': main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/TTK - Saving/save_ghost_to_player_csv.py b/scripts/RMC/TTK - Saving/save_ghost_to_player_csv.py index 52b73f0..27c2cbe 100644 --- a/scripts/RMC/TTK - Saving/save_ghost_to_player_csv.py +++ b/scripts/RMC/TTK - Saving/save_ghost_to_player_csv.py @@ -1,5 +1,6 @@ -from dolphin import gui +from dolphin import gui, utils, event from Modules import ttk_lib +import time """ save_ghost_to_player_csv @@ -21,3 +22,11 @@ def main() -> None: if __name__ == '__main__': main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/TTK - Saving/save_ghost_to_rkg.py b/scripts/RMC/TTK - Saving/save_ghost_to_rkg.py index 6ecb275..be7ef4f 100644 --- a/scripts/RMC/TTK - Saving/save_ghost_to_rkg.py +++ b/scripts/RMC/TTK - Saving/save_ghost_to_rkg.py @@ -1,5 +1,6 @@ -from dolphin import gui +from dolphin import gui, utils, event from Modules import ttk_lib +import time """ save_ghost_to_rkg @@ -22,3 +23,11 @@ def main() -> None: if __name__ == '__main__': main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/TTK - Saving/save_player_to_both_csv.py b/scripts/RMC/TTK - Saving/save_player_to_both_csv.py index 5b972eb..980fbfd 100644 --- a/scripts/RMC/TTK - Saving/save_player_to_both_csv.py +++ b/scripts/RMC/TTK - Saving/save_player_to_both_csv.py @@ -1,5 +1,6 @@ -from dolphin import gui +from dolphin import gui, event, utils from Modules import ttk_lib +import time """ save_player_to_both_csv @@ -22,3 +23,11 @@ def main() -> None: if __name__ == '__main__': main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/TTK - Saving/save_player_to_file_csv.py b/scripts/RMC/TTK - Saving/save_player_to_file_csv.py new file mode 100644 index 0000000..c9af753 --- /dev/null +++ b/scripts/RMC/TTK - Saving/save_player_to_file_csv.py @@ -0,0 +1,42 @@ +from dolphin import gui, utils, event +from Modules import ttk_lib +from external import external_utils as ex +import os +import time + +""" +save_player_to_player_csv + +This script takes the player's inputs and writes them to the player csv +""" + +def main() -> None: + gui.add_osd_message("Script started") + + # Convert internal RKG to input list + input_sequence = ttk_lib.read_full_decoded_rkg_data(ttk_lib.PlayerType.PLAYER) + + if (input_sequence is None or len(input_sequence) == 0): + gui.add_osd_message("No inputs read!") + return + + filetype = [('CSV files', '*.csv'), ('All files', '*')] + scriptDir = utils.get_script_dir() + ttk_dir = os.path.join(scriptDir, 'MKW_Inputs') + write_file = ex.save_dialog_box(scriptDir, filetype, ttk_dir, 'Save as CSV File') + + if write_file: + input_sequence.write_to_file(write_file) + else: + gui.add_osd_message("Save Aborted") + +if __name__ == '__main__': + main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/TTK - Saving/save_player_to_ghost_csv.py b/scripts/RMC/TTK - Saving/save_player_to_ghost_csv.py index 57bbbba..c78f103 100644 --- a/scripts/RMC/TTK - Saving/save_player_to_ghost_csv.py +++ b/scripts/RMC/TTK - Saving/save_player_to_ghost_csv.py @@ -1,5 +1,6 @@ -from dolphin import gui +from dolphin import gui, utils, event from Modules import ttk_lib +import time """ save_player_to_ghost_csv @@ -21,3 +22,11 @@ def main() -> None: if __name__ == '__main__': main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/TTK - Saving/save_player_to_player_csv.py b/scripts/RMC/TTK - Saving/save_player_to_player_csv.py index 45cb644..79c60a0 100644 --- a/scripts/RMC/TTK - Saving/save_player_to_player_csv.py +++ b/scripts/RMC/TTK - Saving/save_player_to_player_csv.py @@ -1,5 +1,6 @@ -from dolphin import gui +from dolphin import gui, event, utils from Modules import ttk_lib +import time """ save_player_to_player_csv @@ -21,3 +22,11 @@ def main() -> None: if __name__ == '__main__': main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/TTK - Saving/save_player_to_rkg.py b/scripts/RMC/TTK - Saving/save_player_to_rkg.py index f385869..e83d111 100644 --- a/scripts/RMC/TTK - Saving/save_player_to_rkg.py +++ b/scripts/RMC/TTK - Saving/save_player_to_rkg.py @@ -1,5 +1,6 @@ -from dolphin import gui +from dolphin import gui, event, utils from Modules import ttk_lib +import time """ save_player_to_rkg @@ -22,3 +23,11 @@ def main() -> None: if __name__ == '__main__': main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/TTK - Saving/save_rkg_from_ghost_csv.py b/scripts/RMC/TTK - Saving/save_rkg_from_ghost_csv.py index 559a9ac..091aaf2 100644 --- a/scripts/RMC/TTK - Saving/save_rkg_from_ghost_csv.py +++ b/scripts/RMC/TTK - Saving/save_rkg_from_ghost_csv.py @@ -1,5 +1,6 @@ -from dolphin import gui +from dolphin import gui, event, utils from Modules import ttk_lib +import time """ save_rkg_from_ghost_csv @@ -21,3 +22,11 @@ def main() -> None: if __name__ == '__main__': main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/TTK - Saving/save_rkg_from_player_csv.py b/scripts/RMC/TTK - Saving/save_rkg_from_player_csv.py index 44ff7f3..1a732b8 100644 --- a/scripts/RMC/TTK - Saving/save_rkg_from_player_csv.py +++ b/scripts/RMC/TTK - Saving/save_rkg_from_player_csv.py @@ -1,5 +1,6 @@ -from dolphin import gui +from dolphin import gui, event, utils from Modules import ttk_lib +import time """ save_rkg_from_player_csv @@ -21,3 +22,11 @@ def main() -> None: if __name__ == '__main__': main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/TTK - Saving/save_rkg_to_player_csv.py b/scripts/RMC/TTK - Saving/save_rkg_to_player_csv.py new file mode 100644 index 0000000..9d7d2fc --- /dev/null +++ b/scripts/RMC/TTK - Saving/save_rkg_to_player_csv.py @@ -0,0 +1,48 @@ +from dolphin import gui, utils, event +from Modules import ttk_lib +from external import external_utils as ex +from Modules.rkg_lib import decode_RKG +import os +import sys +import time + + +""" +save_player_to_player_csv + +This script takes the player's inputs and writes them to the player csv +""" + +def main() -> None: + gui.add_osd_message("Script started") + + #Prompt the user to select a .rkg file + filetype = [('RKG files', '*.rkg'), ('All files', '*')] + scriptDir = utils.get_script_dir() + ghostDir = os.path.join(scriptDir, 'Ghost') + + file_path = ex.open_dialog_box(scriptDir, filetype, ghostDir, 'Open a RKG File') + + if not file_path: + gui.add_osd_message("No file selected!") + return + + with open(file_path, 'rb') as f: + input_sequence = decode_RKG(f.read())[1] + + if (input_sequence is None or len(input_sequence) == 0): + gui.add_osd_message("No inputs read!") + return + + ttk_lib.write_to_csv(input_sequence, ttk_lib.PlayerType.PLAYER) + +if __name__ == '__main__': + main() + global script_end_time + script_end_time = time.time() + + +@event.on_timertick +def cancel(): + if script_end_time and (time.time() - script_end_time > 0.2): + utils.cancel_script(utils.get_script_name()) diff --git a/scripts/RMC/_RKG_AutoSave.py b/scripts/RMC/_RKG_AutoSave.py new file mode 100644 index 0000000..8ea7ced --- /dev/null +++ b/scripts/RMC/_RKG_AutoSave.py @@ -0,0 +1,54 @@ +from dolphin import event, gui, utils +import configparser +import math +import os +from binascii import hexlify + +from Modules.mkw_classes.common import SurfaceProperties, eulerAngle +from Modules.mkw_utils import History + +from Modules import settings_utils as setting +from Modules import mkw_utils as mkw_utils +from Modules import mkw_translations as mkw_translations +from Modules.rkg_lib import get_RKG_data_memory, decode_RKG +from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState, TimerManager +from Modules.mkw_classes import RaceConfig, RaceConfigScenario, RaceConfigSettings +from Modules.mkw_classes import KartObject, KartMove, KartSettings, KartBody +from Modules.mkw_classes import VehicleDynamics, VehiclePhysics, KartBoost, KartJump +from Modules.mkw_classes import KartState, KartCollide, KartInput, RaceInputState + + + + +def main(): + global state + state = RaceManager.state().value + +if __name__ == '__main__': + main() + + +@event.on_frameadvance +def on_frame_advance(): + global state + + if RaceManager.state().value == 4: + if state != 5 : + is_saved, rkg_data = get_RKG_data_memory() + + if is_saved: + state = 5 + course_id = RaceConfigSettings(RaceConfigScenario(RaceConfig.race_scenario()).settings()).course_id() + metadata, inputList, mii_data = decode_RKG(rkg_data) + crc_string = str(hexlify(rkg_data[-4:]))[2:-2] + ft_string = f'_{metadata.finish_time:.3f}s_' + filename = os.path.join(utils.get_script_dir(), 'Ghost', 'AutoSave', mkw_translations.course_slot_abbreviation() +ft_string+crc_string+'.rkg') + with open(filename, 'wb') as f: + f.write(rkg_data) + gui.add_osd_message("Ghost saved to "+filename) + else: + state = 4 + else: + state = RaceManager.state().value + + diff --git a/scripts/RMC/_TTK_Backup.py b/scripts/RMC/_TTK_Backup.py new file mode 100644 index 0000000..7f6a548 --- /dev/null +++ b/scripts/RMC/_TTK_Backup.py @@ -0,0 +1,28 @@ +from dolphin import event, gui, utils, memory + +from Modules import settings_utils as setting +from Modules import ttk_lib as ttk_lib +from Modules import mkw_translations as mkw_translations +from Modules.mkw_classes import RaceManager, RaceState + +def main(): + global backup_count + backup_count = 0 + +if __name__ == '__main__': + main() + + +@event.on_beforesavestateload +def do_backup(_, __): + global backup_count + + + if memory.is_memory_accessible() and RaceManager.state().value in [1,2]: + config = setting.get_ttk_config() + backup_count += 1 + backup_count %= config.ttk_backup + + inputs = ttk_lib.read_full_decoded_rkg_data(ttk_lib.PlayerType.PLAYER) + ttk_lib.write_to_backup_csv(inputs, backup_count) + diff --git a/scripts/RMC/_draw_info_display.py b/scripts/RMC/_draw_info_display.py index b460731..12631b4 100644 --- a/scripts/RMC/_draw_info_display.py +++ b/scripts/RMC/_draw_info_display.py @@ -1,274 +1,120 @@ -from dolphin import event, gui, utils +from dolphin import event, gui, utils, memory import configparser import math import os -from Modules.mkw_classes.common import SurfaceProperties +from Modules.mkw_classes.common import SurfaceProperties, eulerAngle +from Modules.mkw_utils import History -import Modules.mkw_utils as mkw_utils -from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState -from Modules.mkw_classes import RaceConfig, RaceConfigScenario, RaceConfigSettings -from Modules.mkw_classes import KartObject, KartMove, KartSettings, KartBody -from Modules.mkw_classes import VehicleDynamics, VehiclePhysics, KartBoost, KartJump -from Modules.mkw_classes import KartState, KartCollide, KartInput, RaceInputState +from external.external_utils import run_external_script +from Modules import settings_utils as setting +from Modules import mkw_utils as mkw_utils +from Modules.infodisplay_utils import draw_infodisplay, create_infodisplay +from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState, KartObjectManager, VehiclePhysics -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 +@event.on_savestateload +def on_state_load(fromSlot: bool, slot: int): + global c + c = setting.get_infodisplay_config() + + RaceComp_History.clear() + Angle_History.clear() + Pos_History.clear() + + global maxLap + maxLap = 0 + + global text + if memory.is_memory_accessible() and mkw_utils.extended_race_state() >= 0: + Angle_History.update() + RaceComp_History.update() + maxLap = int(RaceManagerPlayer.race_completion_max(0)) + text = create_infodisplay(c, RaceComp_History, Angle_History) + else: + text = '' + -def create_infodisplay(): - text = "" - if c.debug: - # test values here - text += f"{utils.get_game_id()}\n\n" +@event.on_framepresent +def on_present(): + gui.draw_text((10, 10), c.color, text) - if c.frame_count: - text += f"Frame: {mkw_utils.frame_of_input()}\n\n" - race_mgr_player = RaceManagerPlayer() - race_scenario = RaceConfigScenario(addr=RaceConfig.race_scenario()) - race_settings = RaceConfigSettings(race_scenario.settings()) - kart_object = KartObject() - kart_state = KartState(addr=kart_object.kart_state()) - kart_move = KartMove(addr=kart_object.kart_move()) - kart_body = KartBody(addr=kart_object.kart_body()) - vehicle_dynamics = VehicleDynamics(addr=kart_body.vehicle_dynamics()) - vehicle_physics = VehiclePhysics(addr=vehicle_dynamics.vehicle_physics()) - - - 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(race_mgr_player.race_completion_max()) - lap_count = race_settings.lap_count() - - if player_max_lap >= 2 and lap_count > 1: - for lap in range(1, player_max_lap): - text += "Lap {}: {}\n".format(lap, mkw_utils.update_exact_finish(lap, 0)) - - if player_max_lap > lap_count: - text += "Final: {}\n".format(mkw_utils.get_unrounded_time(lap_count, 0)) - text += "\n" - - if c.speed: - speed = mkw_utils.delta_position(playerIdx=0) - engine_speed = kart_move.speed() - cap = kart_move.soft_speed_limit() - text += f" XZ: {round(speed.length_xz(), c.digits)}\n" - text += f" XYZ: {round(speed.length(), 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 = vehicle_physics.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.length_xz(),c.digits)}\n" - text += f" IV XYZ: {round(iv.length(),c.digits)}\n\n" - - if (c.ev or c.ev_xyz): - ev = vehicle_physics.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.length_xz(),c.digits)}\n" - text += f" EV XYZ: {round(ev.length(),c.digits)}\n\n" - - if (c.mrv or c.mrv_xyz): - mrv = vehicle_physics.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" +def main(): + global c + c = setting.get_infodisplay_config() + + global Frame_of_input + Frame_of_input = 0 + + def prc(): + if KartObjectManager.player_count() > 0: + return RaceManagerPlayer(0).race_completion() + return 0 + def grc(): + if KartObjectManager.player_count() > 1: + return RaceManagerPlayer(1).race_completion() + return 0 + def fa(): + return mkw_utils.get_facing_angle(0) + def ma(): + return mkw_utils.get_moving_angle(0) + def pos_(): + if KartObjectManager.player_count() > 0: + return VehiclePhysics.position(0) + return None - if c.mrv_xyz: - text += f" MRV XZ: {round(mrv.length_xz(),c.digits)}\n" - text += f" MRV XYZ: {round(mrv.length(),c.digits)}\n\n" - - if (c.mwv or c.mwv_xyz): - mwv = vehicle_physics.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.length_xz(),c.digits)}\n" - text += f" MWV XYZ: {round(mwv.length(),c.digits)}\n\n" - - if c.charges or c.misc: - kart_settings = KartSettings(addr=kart_object.kart_settings()) - - if c.charges: - kart_boost = KartBoost(addr=kart_move.kart_boost()) - - mt = kart_move.mt_charge() - smt = kart_move.smt_charge() - ssmt = kart_move.ssmt_charge() - mt_boost = kart_move.mt_boost_timer() - trick_boost = kart_boost.trick_and_zipper_timer() - shroom_boost = kart_move.mushroom_timer() - if kart_settings.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 = race_mgr_player.lap_completion() - race_comp = race_mgr_player.race_completion() - cp = race_mgr_player.checkpoint_id() - kcp = race_mgr_player.max_kcp() - rp = race_mgr_player.respawn() - 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" + global RaceComp_History + RaceComp_History = History({'prc':prc, 'grc':grc}, c.history_size) - if c.air: - airtime = kart_move.airtime() - text += f"Airtime: {airtime}\n\n" + global Angle_History + Angle_History = History({'facing' : fa, 'moving' : ma}, 2) - if c.misc or c.surfaces: - kart_collide = KartCollide(addr=kart_object.kart_collide()) + global Pos_History + Pos_History = History({'pos' : pos_}, 3) - if c.misc: - kart_jump = KartJump(addr=kart_move.kart_jump()) - trick_cd = kart_jump.cooldown() - hwg_timer = kart_state.hwg_timer() - oob_timer = kart_collide.solid_oob_timer() - respawn_timer = kart_collide.time_before_respawn() - offroad_inv = kart_move.offroad_invincibility() - if kart_move.is_bike: - text += f"Wheelie Length: {kart_move.wheelie_frames()}\n" - text += f"Wheelie CD: {kart_move.wheelie_cooldown()} | " - text += f"Trick CD: {trick_cd}\n" - text += f"HWG: {hwg_timer} | OOB: {oob_timer}\n" - text += f"Respawn: {respawn_timer}\n" - text += f"Offroad: {offroad_inv}\n\n" + global maxLap + maxLap = None - if c.surfaces: - surface_properties = kart_collide.surface_properties() - is_offroad = (surface_properties.value & SurfaceProperties.OFFROAD) > 0 - is_trickable = (surface_properties.value & SurfaceProperties.TRICKABLE) > 0 - kcl_speed_mod = kart_move.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" + global text + text = '' - if c.position: - pos = vehicle_physics.position() - 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: - kart_input = KartInput(addr=race_mgr_player.kart_input()) - current_input_state = RaceInputState(addr=kart_input.current_input_state()) - - stick_x = current_input_state.raw_stick_x() - 7 - stick_y = current_input_state.raw_stick_y() - 7 - text += f"X: {stick_x} | Y: {stick_y}\n\n" - - return text +if __name__ == '__main__': + main() -@event.on_savestateload -def on_state_load(fromSlot: bool, slot: int): - race_mgr = RaceManager() - if race_mgr.state().value >= RaceState.COUNTDOWN.value: - gui.draw_text((10, 10), c.color, create_infodisplay()) - @event.on_frameadvance def on_frame_advance(): + global Frame_of_input + global Angle_History + global RaceComp_History + global c + global maxLap + global text + race_mgr = RaceManager() - if race_mgr.state().value >= RaceState.COUNTDOWN.value: - gui.draw_text((10, 10), c.color, create_infodisplay()) + newframe = Frame_of_input != mkw_utils.frame_of_input() + draw = mkw_utils.extended_race_state() >= 0 + if newframe and draw: + Frame_of_input = mkw_utils.frame_of_input() + Angle_History.update() + RaceComp_History.update() + Pos_History.update() + if maxLap == int(RaceManagerPlayer.race_completion_max(0))-1: + mkw_utils.calculate_exact_finish(Pos_History, maxLap) + maxLap = int(RaceManagerPlayer.race_completion_max(0)) + + if draw: + text = create_infodisplay(c, RaceComp_History, Angle_History) + else: + RaceComp_History.clear() + Angle_History.clear() + Pos_History.clear() + text = '' + diff --git a/scripts/RMC/_draw_input_display.py b/scripts/RMC/_draw_input_display.py deleted file mode 100644 index 513d82f..0000000 --- a/scripts/RMC/_draw_input_display.py +++ /dev/null @@ -1,68 +0,0 @@ -from dolphin import gui, event -from Modules import input_display as display - -from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState -from Modules.mkw_classes import KartInput, RaceInputState, ButtonActions - -stick_dict = {-7: 0, -6: 60, -5: 70, -4: 80, -3: 90, -2: 100, -1: 110, - 0: 128, 1: 155, 2: 165, 3: 175, 4: 185, 5: 195, 6: 200, 7: 255} - - -@event.on_frameadvance -def on_frame_advance(): - race_mgr = RaceManager() - if race_mgr.state().value >= RaceState.COUNTDOWN.value: - - # TODO: use masks instead of the values for buttons - race_mgr_player_addr = race_mgr.race_manager_player() - race_mgr_player = RaceManagerPlayer(addr=race_mgr_player_addr) - kart_input = KartInput(addr=race_mgr_player.kart_input()) - current_input_state = RaceInputState(addr=kart_input.current_input_state()) - ablr = current_input_state.buttons() - dpad = current_input_state.trick() - xstick = current_input_state.raw_stick_x() - 7 - ystick = current_input_state.raw_stick_y() - 7 - - # A Button - if ablr.value & ButtonActions.A: - func = display.fill_pressed_button - else: - func = display.create_unpressed_button - func([330, gui.get_display_size()[1] - 95], 35, 0xFFFFFFFF) - - # L Button - if ablr.value & ButtonActions.L: - func = display.fill_pressed_bumper - else: - func = display.create_unpressed_bumper - func([30, gui.get_display_size()[1] - 200], 100, 50, 0xFFFFFFFF) - - # R Button - if ablr.value & ButtonActions.B: - func = display.fill_pressed_bumper - else: - func = display.create_unpressed_bumper - func([280, gui.get_display_size()[1] - 200], 100, 50, 0xFFFFFFFF) - - # D-Pad - # TODO: fix the module so that -35 does not have to be used here - display.create_dpad( - [30, gui.get_display_size()[1] - 32.5], 30, -35, 0xFFFFFFFF) - - direction = None - if dpad == 1: - direction = ["Up"] - elif dpad == 2: - direction = ["Down"] - elif dpad == 3: - direction = ["Left"] - elif dpad == 4: - direction = ["Right"] - - if direction: - display.fill_dpad([30, gui.get_display_size()[1] - 32.5], - 30, -35, 0xFFFFFFFF, direction) - - # Control Stick - display.create_control_stick([210, gui.get_display_size()[1] - 100], 50, 30, 50, - stick_dict.get(xstick, 0), stick_dict.get(ystick, 0), 0xFFFFFFFF) diff --git a/scripts/RMC/alt_info_display.py b/scripts/RMC/alt_info_display.py new file mode 100644 index 0000000..85ef4dc --- /dev/null +++ b/scripts/RMC/alt_info_display.py @@ -0,0 +1,196 @@ +""" +EPIK95 +An alternative infodisplay that I made for personal use. The text that gets displayed is created in one large +formatted string, which makes it very easy to modify or add to the display directly instead of using a config file. +Setting EXTERNAL_MODE to True will render the display in a separate window (requires Python to be installed). +Setting GHOST_DISPLAY to True will render a second copy of the display with values for the ghost. +If "Remove UI Delay" is enabled in Dolphin's graphics settings, set NO_DELAY to True to prevent flickering. +""" +from dolphin import event, gui, utils, memory # type: ignore +import os +from Modules import mkw_classes as mkw, mkw_utils +from external import external_utils as ex + +EXTERNAL_MODE = False +NO_DELAY = True +GHOST_DISPLAY = False + +ROUND_DIGITS = 6 +TEXT_COLOR = 0xFFFFFFFF + +def round_(x, digits=ROUND_DIGITS): + return round(x or 0, digits) + +def round_str(x, digits=ROUND_DIGITS): + rounded = round_(x, digits) + return f"{rounded:.{digits}f}" + +def delta(current, previous): + if previous is None: + return "+?" + res = round_(current - previous, ROUND_DIGITS) + return ("+" if res > 0 else "") + round_str(res) + + +def create_infodisplay(player_idx: int = 0): + # Instantiate classes + race_mgr_player = mkw.RaceManagerPlayer(player_idx) + race_scenario = mkw.RaceConfigScenario(addr=mkw.RaceConfig.race_scenario()) + + if player_idx >= race_scenario.player_count(): + return "[invalid player index]" + + race_settings = mkw.RaceConfigSettings(race_scenario.settings()) + kart_object = mkw.KartObject(player_idx) + kart_move = mkw.KartMove(addr=kart_object.kart_move()) + kart_boost = mkw.KartBoost(addr=kart_move.kart_boost()) + kart_jump = mkw.KartJump(addr=kart_move.kart_jump()) + kart_state = mkw.KartState(addr=kart_object.kart_state()) + kart_collide = mkw.KartCollide(addr=kart_object.kart_collide()) + kart_body = mkw.KartBody(addr=kart_object.kart_body()) + vehicle_dynamics = mkw.VehicleDynamics(addr=kart_body.vehicle_dynamics()) + vehicle_physics = mkw.VehiclePhysics(addr=vehicle_dynamics.vehicle_physics()) + + pos = vehicle_physics.position() + v = mkw_utils.delta_position(player_idx) + ev = vehicle_physics.external_velocity() + facing_angle = mkw_utils.get_facing_angle(player=0) + + is_bike = mkw.KartSettings.is_bike(player_idx) + surface_properties = kart_collide.surface_properties().value + + # Create infodisplay text + text = ( + +f""" +Frame: {mkw_utils.frame_of_input()} + +Position + X: {round_str(pos.x)} + Y: {round_str(pos.y)} + Z: {round_str(pos.z)} + +Velocity + Engine: {round_str(kart_move.speed())} / {round_str(kart_move.soft_speed_limit(), 2)} + XYZ: {round_str(v.length())} + XZ: {round_str(v.length_xz())} + Y: {round_str(v.y)} + +External Velocity + XYZ: {round_str(ev.length())} ({delta(ev.length(), prev_values[player_idx].get("ev_xyz"))}) + XZ: {round_str(ev.length_xz())} ({delta(ev.length_xz(), prev_values[player_idx].get("ev_xz"))}) + Y: {round_str(ev.y)} ({delta(ev.y, prev_values[player_idx].get("ev_y"))}) + +Lean Rotation + Angle: {is_bike and round_(kart_move.lean_rot())} + Rate: {is_bike and round_(kart_move.lean_rot_increase())} + Cap: {is_bike and round_(kart_move.lean_rot_cap())} + +Boosts + MT: {kart_move.mt_charge()} / 270 -> {kart_move.mt_boost_timer()} + SSMT: {kart_move.ssmt_charge()} / 75 -> {kart_boost.all_mt_timer() - kart_move.mt_boost_timer()} + Mushroom: {kart_move.mushroom_timer()} | Trick: {kart_boost.trick_and_zipper_timer()} + Auto Drift: {kart_move.auto_drift_start_frame_counter()} / 12 + +Checkpoints + Lap%: {round_str(race_mgr_player.lap_completion())} + Race%: {round_str(race_mgr_player.race_completion())} + CP: {race_mgr_player.checkpoint_id()} | KCP: {race_mgr_player.max_kcp()} | RP: {race_mgr_player.respawn()} + +Airtime: {kart_move.airtime()} +Wallhug: {(surface_properties & mkw.SurfaceProperties.WALL) > 0} +Barrel Roll: {kart_state.bitfield(field_idx=3) & 0x10 > 0} + +Surface properties: {surface_properties} +""") + + #? Other infodisplay sections that can be copy/pasted in if needed + _unused = ( + +""" +HWG Timer: {kart_state.hwg_timer()} +Glitchy corner: {round_(kart_collide.glitchy_corner())} + +Cooldowns + Wheelie: {kart_move.wheelie_cooldown()} | Trick: {kart_jump.cooldown()} + +Offroad: {(surface_properties & mkw.SurfaceProperties.OFFROAD) > 0} +OOB Timer: {kart_collide.solid_oob_timer()} + + Forward: {round_str(v.forward(facing_angle.yaw))} + Sideways: {round_str(v.sideway(facing_angle.yaw))} + +Boost Panel: {kart_boost.mushroom_and_boost_panel_timer() - kart_move.mushroom_timer()} + +Airtime: {(kart_move.airtime() + 1) if (surface_properties & 0x1000) == 0 else 0} +""") + + # Store any values that will be needed on the next frame + prev_values[player_idx].update({ + "ev_xyz": ev.length(), + "ev_xz": ev.length_xz(), + "ev_y": ev.y, + }) + + return text.strip() + + +def update_infodisplay(): + global text_0, text_1 + + if EXTERNAL_MODE: + shm_writer.write_text(create_infodisplay()) + else: + text_0 = create_infodisplay(player_idx=0) + if GHOST_DISPLAY: + text_1 = create_infodisplay(player_idx=1) + + +@event.on_framepresent +def on_present(): + if not EXTERNAL_MODE: + gui.draw_text((10, 10), TEXT_COLOR, text_0) + if GHOST_DISPLAY: + gui.draw_text((260, 10), TEXT_COLOR, text_1) + + +@event.on_frameadvance +def on_frame_advance(): + global current_frame + if current_frame != (mkw_utils.frame_of_input() - 1): + for p in prev_values: + p.clear() + current_frame = mkw_utils.frame_of_input() + + if mkw_utils.extended_race_state() >= 0: + update_infodisplay() + + +@event.on_savestateload +def on_state_load(fromSlot: bool, slot: int): + if memory.is_memory_accessible() and mkw_utils.extended_race_state() >= 0: + update_infodisplay() + +def main(): + global current_frame + current_frame = 0 + + global prev_values + prev_values = [ {}, {} ] + + global text_0 #text for player id 0 + text_0 = '' + + global text_1 #text for player id 1 + text_1 = '' + + if EXTERNAL_MODE: + global shm_writer + shm_writer = ex.SharedMemoryWriter(name='infodisplay', buffer_size=4096) + + window_script_path = os.path.join(utils.get_script_dir(), "external", "info_display_window.py") + ex.start_external_script(window_script_path) + + +if __name__ == '__main__': + main() diff --git a/scripts/RMC/draw_Speed_Display.py b/scripts/RMC/draw_Speed_Display.py new file mode 100644 index 0000000..4971475 --- /dev/null +++ b/scripts/RMC/draw_Speed_Display.py @@ -0,0 +1,35 @@ +from dolphin import gui, event +from Modules import input_display as display +import math + +from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState +from Modules.mkw_classes import VehiclePhysics +from Modules import mkw_utils as mkw_utils + + +@event.on_frameadvance +def on_frame_advance(): + race_mgr = RaceManager() + if race_mgr.state().value >= RaceState.COUNTDOWN.value: + + # TODO: use masks instead of the values for buttons + race_mgr_player_addr = race_mgr.race_manager_player() + race_mgr_player = RaceManagerPlayer(addr=race_mgr_player_addr) + + yaw = mkw_utils.get_facing_angle(0).yaw + 90 + IV = VehiclePhysics.internal_velocity(0) + EV = VehiclePhysics.external_velocity(0) + + size = gui.get_display_size() + center = (128, size[1]-128) + + + # Yaw + gui.draw_line(center, (center[0]+math.cos(yaw/180*math.pi)*30, center[1]+math.sin(yaw/180*math.pi)*30), 0xAAFF0000, 2) + + # IV + gui.draw_line(center, (center[0]+IV.x, center[1]+IV.z), 0xAA00FF00, 1) + + # EV + gui.draw_line(center, (center[0]+EV.x, center[1]+EV.z), 0xAA0000FF, 1) + diff --git a/scripts/RMC/draw_input_display.py b/scripts/RMC/draw_input_display.py new file mode 100644 index 0000000..1cb7624 --- /dev/null +++ b/scripts/RMC/draw_input_display.py @@ -0,0 +1,99 @@ +from dolphin import gui, event, memory # type: ignore +from Modules import input_display as display + +from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState +from Modules.mkw_classes import KartInput, RaceInputState, ButtonActions + +stick_dict = {-7: 0, -6: 60, -5: 70, -4: 80, -3: 90, -2: 100, -1: 110, + 0: 128, 1: 155, 2: 165, 3: 175, 4: 185, 5: 195, 6: 200, 7: 255} + +LEFT_OFFSET = 250 + +def update_input(): + global is_init + race_mgr = RaceManager() + if race_mgr.state().value >= RaceState.COUNTDOWN.value: + global ablr + global dpad + global xstick + global ystick + race_mgr_player_addr = race_mgr.race_manager_player() + race_mgr_player = RaceManagerPlayer(addr=race_mgr_player_addr) + kart_input = KartInput(addr=race_mgr_player.kart_input()) + current_input_state = RaceInputState(addr=kart_input.current_input_state()) + ablr = current_input_state.buttons() + dpad = current_input_state.trick() + xstick = current_input_state.raw_stick_x() - 7 + ystick = current_input_state.raw_stick_y() - 7 + is_init = True + +def main(): + global is_init + is_init = False + update_input() + +def draw(): + + # A Button + if ablr.value & ButtonActions.A: + func = display.fill_pressed_button + else: + func = display.create_unpressed_button + func([LEFT_OFFSET + 330, gui.get_display_size()[1] - 95], 35, 0xFFFFFFFF) + + # L Button + if ablr.value & ButtonActions.L: + func = display.fill_pressed_bumper + else: + func = display.create_unpressed_bumper + func([LEFT_OFFSET + 30, gui.get_display_size()[1] - 200], 100, 50, 0xFFFFFFFF) + + # R Button + if ablr.value & ButtonActions.B: + func = display.fill_pressed_bumper + else: + func = display.create_unpressed_bumper + func([LEFT_OFFSET + 280, gui.get_display_size()[1] - 200], 100, 50, 0xFFFFFFFF) + + # D-Pad + # TODO: fix the module so that -35 does not have to be used here + display.create_dpad( + [LEFT_OFFSET + 30, gui.get_display_size()[1] - 32.5], 30, -35, 0xFFFFFFFF) + + direction = None + if dpad == 1: + direction = ["Up"] + elif dpad == 2: + direction = ["Down"] + elif dpad == 3: + direction = ["Left"] + elif dpad == 4: + direction = ["Right"] + + if direction: + display.fill_dpad([LEFT_OFFSET + 30, gui.get_display_size()[1] - 32.5], + 30, -35, 0xFFFFFFFF, direction) + + # Control Stick + display.create_control_stick([LEFT_OFFSET + 210, gui.get_display_size()[1] - 100], 50, 30, 50, + stick_dict.get(xstick, 0), stick_dict.get(ystick, 0), 0xFFFFFFFF) + + gui.draw_text((LEFT_OFFSET + 195, gui.get_display_size()[1] - 40), 0xFFFFFFFF, f"{xstick}, {ystick}") + + +@event.on_frameadvance +def on_frame_advance(): + update_input() + + +@event.on_savestateload +def on_state_load(fromSlot: bool, slot: int): + if memory.is_memory_accessible(): + update_input() + +@event.on_framepresent +def on_present(): + if is_init: + draw() + +main() diff --git a/scripts/RMC/framedump.py b/scripts/RMC/framedump.py new file mode 100644 index 0000000..cfefd7a --- /dev/null +++ b/scripts/RMC/framedump.py @@ -0,0 +1,122 @@ +from dolphin import event, savestate, utils +from Modules import mkw_utils as mkw_utils +from Modules.agc_lib import AGCMetaData +from Modules.framesequence import Frame +from external import external_utils as ex +from Modules.mkw_classes import RaceManager, RaceManagerPlayer, RaceState, KartObjectManager, VehiclePhysics, vec3 + +from pathlib import Path +import os +import datetime +import time + +def main(): + + global folder_path + folder_path = os.path.join(utils.get_script_dir(), 'FrameDumps') + #Create folders if needed + Path(os.path.join(folder_path, 'RAM_data', 'nul.nul')).parent.mkdir(exist_ok=True, parents=True) + for filename in os.listdir(os.path.join(folder_path, 'RAM_data')): + os.remove(os.path.join(folder_path, 'RAM_data', filename)) + + #Create a list of every file in dumps + frame_dump_path = os.path.join(utils.get_dump_dir(), 'Frames') + audio_dump_path = os.path.join(utils.get_dump_dir(), 'Audio') + + global ignore_file_list + ignore_file_list = [] + for filename in os.listdir(frame_dump_path): + ignore_file_list.append(os.path.join(frame_dump_path, filename)+'\n') + for filename in os.listdir(audio_dump_path): + ignore_file_list.append(os.path.join(audio_dump_path, filename)+'\n') + + if not (utils.is_framedumping() or utils.is_audiodumping()): + utils.start_framedump() + utils.start_audiodump() + else: + print("Don't start this script when already dumping") + utils.cancel_script(utils.get_script_name()) + + global frame_counter + frame_counter = 0 #Count the frame of dumps, different from frame of input + + global framedump_prefix + framedump_prefix = None + + global state_counter, state + state_counter = 0 + state = 0 + +@event.on_scriptend +def scriptend(id_): + + print(framedump_prefix) + if utils.get_script_id() == id_: + if (utils.is_framedumping() and utils.is_audiodumping()): + utils.stop_framedump() + utils.stop_audiodump() + if utils.is_paused(): + utils.toggle_play() + ex_script_path = os.path.join(folder_path, 'encoder.py') + ex_info_path = os.path.join(folder_path, 'dump_info.txt') + with open(ex_info_path, 'w') as f: + f.writelines([utils.get_dump_dir()+'\n'] + ignore_file_list) + ex.start_external_script(ex_script_path, False, False) + +def frame_text(): + text = '' + text += f'frame_of_input:{mkw_utils.frame_of_input()}\n' + text += f'yaw:{mkw_utils.get_facing_angle(0).yaw}\n' + text += f'spd_x:{mkw_utils.delta_position(0).x}\n' + text += f'spd_y:{mkw_utils.delta_position(0).y}\n' + text += f'spd_z:{mkw_utils.delta_position(0).z}\n' + text += f'iv_x:{VehiclePhysics.internal_velocity(0).x}\n' + text += f'iv_y:{VehiclePhysics.internal_velocity(0).y}\n' + text += f'iv_z:{VehiclePhysics.internal_velocity(0).z}\n' + text += f'ev_x:{VehiclePhysics.external_velocity(0).x}\n' + text += f'ev_y:{VehiclePhysics.external_velocity(0).y}\n' + text += f'ev_z:{VehiclePhysics.external_velocity(0).z}\n' + text += f'mvr_x:{VehiclePhysics.moving_road_velocity(0).x}\n' + text += f'mvr_y:{VehiclePhysics.moving_road_velocity(0).y}\n' + text += f'mvr_z:{VehiclePhysics.moving_road_velocity(0).z}\n' + text += f'mvw_x:{VehiclePhysics.moving_water_velocity(0).x}\n' + text += f'mvw_y:{VehiclePhysics.moving_water_velocity(0).y}\n' + text += f'mvw_z:{VehiclePhysics.moving_water_velocity(0).z}\n' + text += f'cp:{RaceManagerPlayer(0).checkpoint_id()}\n' + text += f'kcp:{RaceManagerPlayer(0).max_kcp()}\n' + text += f'rp:{RaceManagerPlayer(0).respawn()}\n' + text += f'racecomp:{RaceManagerPlayer(0).race_completion()}\n' + text += f'lapcomp:{RaceManagerPlayer(0).lap_completion()}\n' + text += f'input:{Frame.from_current_frame(0)}\n' + text += f'state:{state}\n' + text += f'state_counter:{state_counter}\n' + return text + +@event.on_frameadvance +def on_frame_advance(): + global frame_counter + + global framedump_prefix + + global state_counter, state + + if state == mkw_utils.extended_race_state(): + state_counter += 1 + else: + state = mkw_utils.extended_race_state() + state_counter = 0 + + if frame_counter == 0: + c = datetime.datetime.now() + framedump_prefix = f"{utils.get_game_id()}_{c.year}-{c.month:02d}-{c.day:02d}_{c.hour:02d}-{c.minute:02d}-{c.second:02d}" + if mkw_utils.extended_race_state() in [1,2,3,4]: + with open(os.path.join(folder_path, 'RAM_data', f'{frame_counter}.txt'), 'w') as f: + f.write(frame_text()) + else: + with open(os.path.join(folder_path, 'RAM_data', f'{frame_counter}.txt'), 'w') as f: + f.write('') + + frame_counter += 1 + +if __name__ == '__main__': + main() diff --git a/scripts/Settings/Setting files goes here.txt b/scripts/Settings/Setting files goes here.txt new file mode 100644 index 0000000..e69de29 diff --git a/scripts/Startslides/flame_left.csv b/scripts/Startslides/flame_left.csv new file mode 100644 index 0000000..4381d3c --- /dev/null +++ b/scripts/Startslides/flame_left.csv @@ -0,0 +1,395 @@ +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,1,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,5,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,7,0,1,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 diff --git a/scripts/Startslides/mach_left.csv b/scripts/Startslides/mach_left.csv new file mode 100644 index 0000000..87eba05 --- /dev/null +++ b/scripts/Startslides/mach_left.csv @@ -0,0 +1,401 @@ +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +0,0,0,0,0,1,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-2,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,-3,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,7,0,1,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 diff --git a/scripts/Startslides/spear_left.csv b/scripts/Startslides/spear_left.csv new file mode 100644 index 0000000..350adf3 --- /dev/null +++ b/scripts/Startslides/spear_left.csv @@ -0,0 +1,461 @@ +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +0,0,0,0,0,1,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,1,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 diff --git a/scripts/Startslides/wario_left.csv b/scripts/Startslides/wario_left.csv new file mode 100644 index 0000000..34ca424 --- /dev/null +++ b/scripts/Startslides/wario_left.csv @@ -0,0 +1,492 @@ +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,1,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,1,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 diff --git a/scripts/Startslides/wiggle_left.csv b/scripts/Startslides/wiggle_left.csv new file mode 100644 index 0000000..390fd37 --- /dev/null +++ b/scripts/Startslides/wiggle_left.csv @@ -0,0 +1,407 @@ +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +0,0,0,0,0,0,-1 +0,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +0,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,1,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,-7,0,0,-1 +1,0,0,3,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,1,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,7,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 +1,0,0,0,0,0,-1 diff --git a/scripts/Update_Packages_+_Scripts.bat b/scripts/Update_Packages_+_Scripts.bat new file mode 100644 index 0000000..ee594c8 --- /dev/null +++ b/scripts/Update_Packages_+_Scripts.bat @@ -0,0 +1,4 @@ +python -m pip install --upgrade pip +pip install requests +python update_scripts.py +./Install_Pycore_Packages.bat \ No newline at end of file diff --git a/scripts/external/TAS_input_window.py b/scripts/external/TAS_input_window.py new file mode 100644 index 0000000..57319df --- /dev/null +++ b/scripts/external/TAS_input_window.py @@ -0,0 +1,101 @@ +from tkinter import * +from tkinter import ttk +from math import floor +import external_utils as ex + +input_writer = ex.SharedMemoryWriter('mkw tas input', 30, False) + +def convert_drift(t): + if t == 'Auto': + return '-1' + else: + return t +def convert_trick(t): + if t == 'Up': + return '1' + if t == 'Down': + return '2' + if t == 'Left': + return '3' + if t == 'Right': + return '4' + return '0' + +def write_state(_ = None): + a = ','.join([str(int(Avar.get())), + str(int(Bvar.get())), + str(int(Lvar.get())), + convert_drift(drift_button.get()), + str(int(BDvar.get())), + str(floor(float(x_stick.get()))), + str(floor(float(y_stick.get()))), + convert_trick(trick_button.get())]) + input_writer.write_text(a) + +root = Tk() +root.title('TAS Input') +root.wm_attributes('-topmost', 1) #Always on top + +content = ttk.Frame(root, padding = 10) + +Avar = BooleanVar(value=False) +Bvar = BooleanVar(value=False) +Lvar = BooleanVar(value=False) +Xvar = StringVar() +Yvar = StringVar() +Tvar = StringVar() +Dvar = StringVar() +BDvar = BooleanVar(value=False) + +a_button = ttk.Checkbutton(content, text="A", variable=Avar, onvalue=True, command=write_state) +b_button = ttk.Checkbutton(content, text="B", variable=Bvar, onvalue=True, command=write_state) +l_button = ttk.Checkbutton(content, text="ITEM", variable=Lvar, onvalue=True, command=write_state) +bd_button = ttk.Checkbutton(content, text="BRAKEDRIFT", variable=BDvar, onvalue=True, command=write_state) + +x_label = ttk.Label(content, text='X = 0') +def update_x_label(x): + write_state() + x_label['text'] = 'X = '+str(floor(float(x))) +x_stick = ttk.Scale(content, orient=HORIZONTAL, length=150, from_=-6.5, to=7.5, variable=Xvar, command=update_x_label) + + +y_label = ttk.Label(content, text='Y = 0') +def update_y_label(x): + write_state() + y_label['text'] = 'Y = '+str(floor(float(x))) +y_stick = ttk.Scale(content, orient=HORIZONTAL, length=150, from_=-6.5, to=7.5, variable=Yvar, command=update_y_label) + + +trick_button = ttk.Combobox(content, textvariable=Tvar, values = ('None', 'Up', 'Left', 'Right', 'Down'), state = 'readonly') +trick_button.bind('<>', write_state) +trick_label = ttk.Label(content, text='Trick :') + + +drift_button = ttk.Combobox(content, textvariable=Dvar, values = ('Auto', '0', '1'), state = 'readonly') +drift_button.bind('<>', write_state) +drift_label = ttk.Label(content, text='Drift :') +drift_disclaimer = ttk.Label(content, text='Use Auto if unsure') + +drift_button.set('Auto') +trick_button.set('None') +x_stick.set(0.5) +y_stick.set(0.5) + +content.grid(column=0, row=0) +a_button.grid(column=0, row=3, sticky='w') +b_button.grid(column=0, row=4, sticky='w') +l_button.grid(column=0, row=5, sticky='w') +x_stick.grid(column=1, row=6, columnspan=2) +x_label.grid(column=0, row=6, sticky='w') +y_label.grid(column=0, row=10, sticky='w') +y_stick.grid(column=1, row=10, columnspan=2) +trick_button.grid(column=1, row=11, sticky='w') +trick_label.grid(column=0, row=11, sticky='w') +drift_button.grid(column=1, row=12, sticky='w') +drift_label.grid(column=0, row=12, sticky='w') +drift_disclaimer.grid(column=2, row=12, sticky='e') +bd_button.grid(column=0, row=13, sticky='w') + +root.mainloop() + +input_writer.close() diff --git a/scripts/external/TTK_Save_GUI_window.py b/scripts/external/TTK_Save_GUI_window.py new file mode 100644 index 0000000..ff1e06a --- /dev/null +++ b/scripts/external/TTK_Save_GUI_window.py @@ -0,0 +1,135 @@ +from tkinter import * +from tkinter import ttk, filedialog +from math import floor +import external_utils as ex +from random import randint +from datetime import datetime + + +global save_button_row +global misscount +save_button_row = 5 +misscount = 0 + +af_text = ['How did you get there ?', 'what.', 'Huh WTF ?', 'Keep trying', 'Almost!', 'Bad QM', 'Not this time', 'Cmon already', "It's just 20%", 'You can do it!', "You can't do it!", 'Just a little more', 'Is it really that hard to click on a button ?', 'Even my grandma can do it', 'How many messages are there ?', 'I wonder if there is something special at the end...', "(Spoiler : there isn't", ")*", "forgot the bracket, oops", 'Unless there is actually something ...', "It's starting to get very long", "You had about 1.5% chance to get that far", "There is no end btw, you just need luck", "You should get it in average every 6 tries", "Okay I'll tell you something", "Soon", "In like just a few messages", "It's a cool hidden feature", "Nothing incredible, but still cool to know", "Fact 1 : press S on the connect4 ending screen to access settings", "Fact 2 : You can play whenever you want the connect4 game !", 'Fact 2 (part 2) : Just launch "special.py" in Scripts/external', 'This is the end of the messages', 'Good luck if you are still trying to save your TTK inputs lol'] + +def write_state(_ = None): + if source_combobox.get(): + std_text = source_combobox.get() + if std_text == 'File': + std_text = source_file_text.get(1.0, END).split('\n')[0] + if csv_player_var.get(): + std_text += '|' + 'csv_player' + if csv_ghost_var.get(): + std_text += '|' + 'csv_ghost' + if dest_file_var.get(): + std_text += '|' + dest_file_text.get(1.0, END).split('\n')[0] + print(std_text, end = '') + root.destroy() + else: + status_label['text'] = 'Select a source to save' +def tp(_=None): + global save_button_row + global misscount + d = datetime.now() + a = randint(0,5) + if a == save_button_row or d.day != 1 or d.month != 4: + misscount = 0 + write_state() + else: + save_button_row = a + misscount += 1 + if misscount < len(af_text): + status_label['text'] = af_text[misscount] + save_button.grid(column=1, row=a) + + + +root = Tk() +root.title('TTK Save') +root.wm_attributes('-topmost', 1) #Always on top + +content = ttk.Frame(root, padding = 10) + +dialog_frame = ttk.Frame(content, padding = 5) +dialog_frame.grid(column = 0, row = 0, rowspan = 6) + + + +csv_pick_var = StringVar() +rkg_pick_var = StringVar() + + +save_label = ttk.Label(dialog_frame, text = 'Save ') +save_label.grid(column = 0, row = 0, rowspan = 4) + + +source_var = StringVar() +source_file_text = Text(dialog_frame, height = 4, width = 20, state = 'disabled') +source_file_text.grid(column=1, row=5, sticky='w') +source_combobox = ttk.Combobox(dialog_frame, textvariable=source_var, values = ('Player Inputs', 'Ghost Inputs', 'CSV Player', 'CSV Ghost', 'File'), state = 'readonly') +def source_ask_open_file(_ = None): + if source_combobox.get() == 'File': + filename = filedialog.askopenfilename(parent = dialog_frame, + title = 'Open an input file', + filetypes = [('RKG Files', '*.rkg'), ('CSV Files', '*.csv'), ('All Files', '*')]) + if filename == '': + source_combobox.set('Player Inputs') + else: + source_file_text['state'] = 'normal' + source_file_text.delete(1.0, END) + source_file_text.insert(END, filename) + source_file_text['state'] = 'disabled' + else: + source_file_text['state'] = 'normal' + source_file_text.delete(1.0, END) + source_file_text['state'] = 'disabled' + +source_combobox.bind('<>', source_ask_open_file) +source_combobox.grid(column=1, row=0, rowspan = 4) + +to_label = ttk.Label(dialog_frame, text = ' to ') +to_label.grid(column = 2, row = 0, rowspan = 4) + +csv_player_var = BooleanVar(value=False) +csv_player_button = ttk.Checkbutton(dialog_frame, text="CSV Player", variable=csv_player_var, onvalue=True) +csv_player_button.grid(column=4, row=0, sticky='w') + +csv_ghost_var = BooleanVar(value=False) +csv_ghost_button = ttk.Checkbutton(dialog_frame, text="CSV Ghost", variable=csv_ghost_var, onvalue=True) +csv_ghost_button.grid(column=4, row=1, sticky='w') + +dest_file_var = BooleanVar(value=False) +dest_file_text = Text(dialog_frame, height = 4, width = 20, state = 'disabled') +dest_file_text.grid(column=3, row=5, columnspan=2) +def dest_ask_save_file(_=None): + if dest_file_var.get(): + filename = filedialog.asksaveasfilename(parent = dialog_frame, defaultextension = '.rkg', + title = 'Save to an input file', + filetypes = [('RKG Files', '*.rkg'), ('CSV Files', '*.csv'), ('All Files', '*')]) + if filename == '': + dest_file_var.set(False) + else: + dest_file_text['state'] = 'normal' + dest_file_text.delete(1.0, END) + dest_file_text.insert(END, filename) + dest_file_text['state'] = 'disabled' + else: + dest_file_text['state'] = 'normal' + dest_file_text.delete(1.0, END) + dest_file_text['state'] = 'disabled' + +dest_file_button = ttk.Checkbutton(dialog_frame, text="File", variable=dest_file_var, onvalue=True, command=dest_ask_save_file) +dest_file_button.grid(column=4, row=2, sticky='w') + +save_button = ttk.Button(content, text='Save', command=tp) +save_button.grid(column=2, row=save_button_row ) + +status_label = ttk.Label(content, text='') +status_label.grid(column=0, row=7 ) + + +content.grid(column=0, row=0) + +root.mainloop() + diff --git a/scripts/external/external_utils.py b/scripts/external/external_utils.py new file mode 100644 index 0000000..a957e00 --- /dev/null +++ b/scripts/external/external_utils.py @@ -0,0 +1,188 @@ +from multiprocessing import shared_memory +import atexit +import subprocess +import os +import configparser +import platform + + +def start_external_script(path: str, force_exit = True, create_no_window = True, *args): + ''' Start an external script using the user's own python env + path (str) : path to the filename of the script to start + force_exit (bool) : Can force the external script to stop when the calling script ends. + create_no_window (bool) : Can prevent the external script from creating a new window. + *agrs : extra args to give to the external script. (access with sys.argv)''' + if create_no_window: + process = subprocess.Popen(["python", path, *args], creationflags=subprocess.CREATE_NO_WINDOW) + else: + process = subprocess.Popen(["python", path, *args]) + if force_exit: + atexit.register(process.terminate) + + +def run_external_script(path: str, *args): + output = subprocess.check_output(["python", path, *args], text=True, creationflags=subprocess.CREATE_NO_WINDOW) + return output + +#Open a file with the default application +def open_file(path: str): + if platform.system() == 'Darwin': # macOS + subprocess.call(('open', path)) + elif platform.system() == 'Windows': # Windows + os.startfile(path, 'edit') + else: # linux variants + subprocess.call(('xdg-open', path)) + +class SharedMemoryWriter: + def __init__(self, name: str, buffer_size: int, create=True): + self._shm = shared_memory.SharedMemory(create=create, name=name, size=buffer_size) + atexit.register(self.close) + + def write(self, bytes_: bytes): + if len(bytes_) > len(self._shm.buf): + raise ValueError("Data is too large for shared memory buffer.") + self._shm.buf[:len(self._shm.buf)] = bytes_ + b'\x00' * (len(self._shm.buf) - len(bytes_)) + + def write_text(self, text: str): + bytes_ = text.encode('utf-8') + self.write(bytes_) + + def close(self): + self._shm.close() + self._shm.unlink() + + +class SharedMemoryReader: + def __init__(self, name: str): + self._shm = shared_memory.SharedMemory(name=name) + atexit.register(self.close) + + def read(self): + return bytes(self._shm.buf) + + def read_text(self): + return self.read().rstrip(b'\x00').decode('utf-8') + + def close(self): + self._shm.close() + + def close_with_writer(self): + self._shm.close() + self._shm.unlink() + + +class SharedMemoryBlock: + """ Allows both reading and writing to a shared memory block """ + def __init__(self, _shm: shared_memory.SharedMemory): + self._shm = _shm + + @staticmethod + def create(name: str, buffer_size: int): + """ Create new shared memory block """ + shm = SharedMemoryBlock(shared_memory.SharedMemory(create=True, name=name, size=buffer_size)) + atexit.register(shm.destroy) + return shm + + @staticmethod + def connect(name: str): + """ Connect to existing shared memory block """ + shm = SharedMemoryBlock(shared_memory.SharedMemory(name=name)) + atexit.register(shm.disconnect) + return shm + + def clear(self): + self._shm.buf[:len(self._shm.buf)] = b'\x00' * len(self._shm.buf) + + def read(self): + return bytes(self._shm.buf) + + def read_text(self): + return self.read().rstrip(b'\x00').decode('utf-8') + + def write(self, bytes: bytes): + if len(bytes) > len(self._shm.buf): + raise ValueError("Data is too large for shared memory buffer.") + self.clear() + self._shm.buf[:len(bytes)] = bytes + + def write_text(self, text: str): + bytes = text.encode('utf-8') + self.write(bytes) + + def disconnect(self): + self._shm.close() + + def destroy(self): + self.disconnect() + self._shm.unlink() + + +def open_dialog_box(scriptDir, filetypes = [('All files', '*')], initialdir = '', title = '', multiple = False): + ''' Shortcut function to prompt a openfile dialog box + type_list : list of types allowed for the open dialog box + format : ('{File dormat description}', '*.{file extension}') + exemple : ('RKG files', '*.rkg') ''' + + #Todo : maybe having the text as a subprocess check_output argument instead of SharedMemory, but idk how to implement it + script_path = os.path.join(scriptDir, "external", 'open_file_dialog_box.py') + type_writer = SharedMemoryWriter('open_file_dialog', 1024) + str_list = [] + for type_ in filetypes: + str_list.append(','.join(type_)) + type_writer.write_text(';'.join(str_list) + '|' + initialdir + '|' + title + '|' + str(multiple)) + filename = subprocess.check_output(["python", script_path], text=True, creationflags=subprocess.CREATE_NO_WINDOW) + type_writer.close() + return filename + +def save_dialog_box(scriptDir, filetypes = [('All files', '*')], initialdir = '', title = '', defaultextension = ''): + ''' Shortcut function to prompt a savefile dialog box + type_list : list of types allowed for the open dialog box + format : ('{File dormat description}', '*.{file extension}') + exemple : ('RKG files', '*.rkg') ''' + + #Todo : maybe having the text as a subprocess check_output argument instead of SharedMemory, but idk how to implement it + script_path = os.path.join(scriptDir, "external", 'save_file_dialog_box.py') + type_writer = SharedMemoryWriter('save_file_dialog', 1024) + str_list = [] + for type_ in filetypes: + str_list.append(','.join(type_)) + type_writer.write_text(';'.join(str_list) + '|' + initialdir + '|' + title + '|' + defaultextension) + filename = subprocess.check_output(["python", script_path], text=True, creationflags=subprocess.CREATE_NO_WINDOW) + type_writer.close() + return filename + +#The 2 following functions are used to save and restore +#various tkinter settings +def save_external_setting(config_file, setting_name, setting_value): + ''' Param : config_file : str, filename of the savefile + setting_name : str, unique name of the setting (used as a key in the config file) + setting_value : str, value of the setting''' + + #create an empty file if it doesn't exist + if not os.path.exists(config_file): + with open(config_file, 'w') as f: + pass + + config = configparser.ConfigParser() + config.read(config_file) + if not config.sections(): + config["SETTINGS"] = {} + config["SETTINGS"][setting_name] = setting_value + + with open(config_file, 'w') as f: + config.write(f) + +def load_external_setting(config_file, setting_name): + ''' Param : config_file : str, filename of the savefile + setting_name : str, unique name of the setting (used as a key in the config file)''' + + if not os.path.exists(config_file): + return None + config = configparser.ConfigParser() + config.read(config_file) + if not config.sections(): + return None + if config["SETTINGS"].get(setting_name): + return config["SETTINGS"].get(setting_name) + return None + diff --git a/scripts/external/info_display_window.py b/scripts/external/info_display_window.py new file mode 100644 index 0000000..a7367d6 --- /dev/null +++ b/scripts/external/info_display_window.py @@ -0,0 +1,54 @@ +import tkinter +import external_utils +import time + +# Fix blurry text +from ctypes import windll +windll.shcore.SetProcessDpiAwareness(1) + + +def main(): + try: + shm_reader = external_utils.SharedMemoryReader(name='infodisplay') + print("Connected to shared data") + except FileNotFoundError: + raise FileNotFoundError("Shared memory buffer not found. Make sure the `external_info_display` script is enabled.") + + window = tkinter.Tk() + window.title('Infodisplay') + window.config(bg="black") + window.geometry('330x600') + + display_text = tkinter.StringVar() + + label = tkinter.Label( + textvariable=display_text, + anchor='nw', + font=('Courier', '9'), + justify='left', + width=250, + fg='white', + bg='black', + ) + label.pack(padx=5, pady=5) + + try: + while True: + new_text = shm_reader.read_text() + if new_text and new_text != display_text.get(): + display_text.set(new_text) + + window.update_idletasks() + window.update() + time.sleep(0.01) + + except KeyboardInterrupt: + print("Reader stopped.") + + finally: + shm_reader.close() + print("Disconnected from shared data") + + +if __name__ == '__main__': + main() diff --git a/scripts/external/live_graphing_window.py b/scripts/external/live_graphing_window.py new file mode 100644 index 0000000..13140da --- /dev/null +++ b/scripts/external/live_graphing_window.py @@ -0,0 +1,106 @@ +import matplotlib +matplotlib.use('TkAgg') # Use TkAgg backend for compatibility + +import matplotlib.pyplot as plt +import struct +import external_utils + +# Connect to shared memory block +shm_reader = external_utils.SharedMemoryReader(name="graphdata") +current_frame = 0 +max_height = 0 + +# Initialize data storage +x_data = [] +y_data_xyz = [] +y_data_xz = [] + +MAX_FRAMES = 200 +MIN_HEIGHT = 75 + +# Create a figure and axis +fig, ax = plt.subplots(figsize=(5, 4)) +xyz_line, = ax.plot([], [], label="XYZ") +# xyz_line.set_color('orange') +xz_line, = ax.plot([], [], label="XZ") +# xz_line.set_color('lightblue') + + + +# Configure plot +ax.set_xlim(0, MAX_FRAMES) +ax.set_ylim(0, MIN_HEIGHT) # Fixed y-axis range for sine wave +ax.set_xlabel('Frame') +ax.set_ylabel('Units') +ax.set_title('External Velocity') +ax.legend() +ax.grid(True) + +# Enable interactive mode and show the window +plt.ion() +plt.show(block=False) + +# Draw and create the background after showing the window +fig.canvas.draw() +background = fig.canvas.copy_from_bbox(ax.bbox) + +# Event handler to exit when window is closed +running = True +def on_close(event): + global running + running = False +fig.canvas.mpl_connect('close_event', on_close) + +try: + while running: + # Read new data point + new_data = shm_reader.read() + frame, ev_xyz, ev_xz = struct.unpack('>Iff', new_data[:12]) + + if frame == current_frame: + fig.canvas.flush_events() + continue + elif frame < current_frame: + try: + cutoff = x_data.index(frame) + x_data = x_data[:cutoff] + y_data_xyz = y_data_xyz[:cutoff] + y_data_xz = y_data_xz[:cutoff] + except ValueError: + x_data.clear() + y_data_xyz.clear() + y_data_xz.clear() + + current_frame = frame + max_height = max(max_height, ev_xyz) + + # Append new data + x_data.append(frame) + y_data_xyz.append(ev_xyz) + y_data_xz.append(ev_xz) + + # Avoid memory overflow + if len(x_data) > MAX_FRAMES: + x_data.pop(0) + y_data_xyz.pop(0) + y_data_xz.pop(0) + + # Update the line data + xyz_line.set_xdata(x_data) + xyz_line.set_ydata(y_data_xyz) + xz_line.set_xdata(x_data) + xz_line.set_ydata(y_data_xz) + + # Automatically adjust axes to fit data + ax.set_xlim(max(0, frame - MAX_FRAMES), max(frame, MAX_FRAMES)) + ax.set_ylim(0, max(max_height + 10, MIN_HEIGHT)) + + # Restore the background, draw the line, and blit + fig.canvas.restore_region(background) + ax.draw_artist(xyz_line) + ax.draw_artist(xz_line) + fig.canvas.blit(ax.bbox) + fig.canvas.flush_events() + +except KeyboardInterrupt: + print("Real-time plotting stopped.") diff --git a/scripts/external/mkds_minimap_window.py b/scripts/external/mkds_minimap_window.py new file mode 100644 index 0000000..fb7cf9e --- /dev/null +++ b/scripts/external/mkds_minimap_window.py @@ -0,0 +1,173 @@ +import struct +import external_utils as ex +import pygame +import math + +WINDOW_SIZE = 480 +SCALE = 1*WINDOW_SIZE/480*100/120/30 +FONT_SIZE = 15 + +def bytes_to_poslist(b): + p_list = [] + g_list = [] + for i in range(0,len(b),16): + if i+15 < len(b): + struct.unpack('f', b[i:i+4]) + p_list.append((struct.unpack('f', b[i:i+4])[0], struct.unpack('f', b[i+4:i+8])[0])) + g_list.append((struct.unpack('f', b[i+8:i+12])[0], struct.unpack('f', b[i+12:i+16])[0])) + if len(p_list) == 0: + p_list.append((0,0)) + if len(g_list) == 0: + g_list.append((0,0)) + return p_list, g_list + +def bytes_to_idlist(b): + res = [] + for i in range(len(b)): + res.append(struct.unpack('b', b[i:i+1])[0]) + return res + + +def format_position(position, base_position, scaling, yaw = 180): + ''' translate and scale and rotate the position + from MKW coordinate to pygame screen coordinate + the base position is mapped to the middle of the screen ''' + yaw *= -math.pi/180 + x,y = position + x0, y0 = base_position + x -= x0 + y -= y0 + x*= SCALE * scaling + y*= SCALE * scaling + x,y = -x*math.cos(yaw) + y*math.sin(yaw), -x*math.sin(yaw) - y*math.cos(yaw) + x += screen.get_width()/2 + y += screen.get_height()/2 + return x,y + +def is_inbound(coordinate): + x,y = coordinate + return 0<=x<=screen.get_width() and 0<=y<=screen.get_height() + +memory_reader = ex.SharedMemoryReader('mkds minimap') +cp_reader = ex.SharedMemoryReader('mkds minimap checkpoints') +cp_id_reader = ex.SharedMemoryReader('mkds minimap checkpoints_id') +yaw_reader = ex.SharedMemoryReader('mkds minimap yaw') +pygame.init() +screen = pygame.display.set_mode((480, 480), pygame.RESIZABLE) +clock = pygame.time.Clock() +running = True +dt = 0 +scale_mult = 1 +draw_line = True +draw_circle = True +draw_help = True +align_with_yaw = True +help_font = pygame.font.SysFont('Courier New', FONT_SIZE) +pygame.key.set_repeat(300, 50) + + +while running: + # poll for events + # pygame.QUIT event means the user clicked X to close your window + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + if event.type == pygame.KEYDOWN and event.key == pygame.K_h: + draw_help = not draw_help + if event.type == pygame.KEYDOWN and event.key == pygame.K_b: + scale_mult *= 1.1 + if event.type == pygame.KEYDOWN and event.key == pygame.K_n: + scale_mult /= 1.1 + if event.type == pygame.KEYDOWN and event.key == pygame.K_j: + draw_line = not draw_line + if event.type == pygame.KEYDOWN and event.key == pygame.K_k: + draw_circle = not draw_circle + if event.type == pygame.KEYDOWN and event.key == pygame.K_l: + align_with_yaw = not align_with_yaw + + + player_positions, ghost_positions = bytes_to_poslist(memory_reader.read()) + cp_left, cp_right = bytes_to_poslist(cp_reader.read()) + id_list = bytes_to_idlist(cp_id_reader.read()) + base_position = player_positions[0] + yaw = struct.unpack('f', yaw_reader.read()[:4])[0] + if not align_with_yaw: + yaw = 180 + #hacky way to get rid of some unwanted data + for i in range(len(cp_left)-1, -1, -1): + if (cp_right[i][0] == 0 or cp_left[i][0] == 0): + cp_right.pop(i) + cp_left.pop(i) + id_list.pop(i) + + # fill the screen with a color to wipe away anything from last frame + screen.fill("white") + + #Draw help + if draw_help: + screen.blit(help_font.render('Help - Keybinds', False, (0, 255, 0)), (0,0)) + screen.blit(help_font.render('Toggle help : h', False, (0, 255, 0)), (0,FONT_SIZE)) + screen.blit(help_font.render('Zoom in : b', False, (0, 255, 0)), (0,FONT_SIZE*2)) + screen.blit(help_font.render('Zoom out : n', False, (0, 255, 0)), (0,FONT_SIZE*3)) + screen.blit(help_font.render('Draw path dots : k', False, (0, 255, 0)), (0,FONT_SIZE*4)) + screen.blit(help_font.render('Draw path lines : j', False, (0, 255, 0)), (0,FONT_SIZE*5)) + screen.blit(help_font.render('Rotate with facing yaw : l', False, (0, 255, 0)), (0,FONT_SIZE*6)) + #Draw player red circle + player_pos = format_position(player_positions[0], base_position, scale_mult, yaw) + pygame.draw.circle(screen, "red", player_pos, 4) + + #Draw ghost blue circle + ghost_pos = format_position(ghost_positions[0], base_position, scale_mult, yaw) + pygame.draw.circle(screen, "blue", ghost_pos, 4) + + #Draw Player Path + for i in range(0, len(player_positions) -1): + if not (player_positions[i+1][0] == 0): + p1 = format_position(player_positions[i], base_position, scale_mult, yaw) + p2 = format_position(player_positions[i+1], base_position, scale_mult, yaw) + if draw_line: + pygame.draw.line(screen, "red", p1, p2) + if draw_circle: + pygame.draw.circle(screen, "red", p1, 2) + #Draw Ghost Path + for i in range(len(ghost_positions) -1): + if not (ghost_positions[i+1][0] == 0): + p1 = format_position(ghost_positions[i], base_position, scale_mult, yaw) + p2 = format_position(ghost_positions[i+1], base_position, scale_mult, yaw) + if draw_line: + pygame.draw.line(screen, "blue", p1, p2) + if draw_circle: + pygame.draw.circle(screen, "blue", p1, 2) + #Draw CP box + for i in range(len(cp_left)): + if id_list[i] == -1: + color = 'black' + elif id_list[i] == 0: + color = 'purple' + elif id_list[i] > 0: + color = 'red' + p1 = format_position(cp_left[i], base_position, scale_mult, yaw) + p2 = format_position(cp_left[(i+1)%len(cp_left)], base_position, scale_mult, yaw) + pygame.draw.line(screen, color, p1, p2) #left side of the box + p1 = format_position(cp_right[i], base_position, scale_mult, yaw) + p2 = format_position(cp_right[(i+1)%len(cp_left)], base_position, scale_mult, yaw) + pygame.draw.line(screen, color, p1, p2) #right side of the box + p1 = format_position(cp_left[i], base_position, scale_mult, yaw) + p2 = format_position(cp_right[i], base_position, scale_mult, yaw) + pygame.draw.line(screen, color, p1, p2) #cp line + + + + # flip() the display to put your work on screen + pygame.display.flip() + + # limits FPS to 60 + # dt is delta time in seconds since last frame, used for framerate- + # independent physics. + dt = clock.tick(60) / 1000 + +pygame.quit() +memory_reader.close_with_writer() +cp_reader.close_with_writer() +cp_id_reader.close_with_writer() +yaw_reader.close_with_writer() diff --git a/scripts/external/open_file_dialog_box.py b/scripts/external/open_file_dialog_box.py new file mode 100644 index 0000000..23f9d31 --- /dev/null +++ b/scripts/external/open_file_dialog_box.py @@ -0,0 +1,37 @@ +import struct +import external_utils +import tkinter as tk +from tkinter import filedialog + +root = tk.Tk() +root.wm_attributes('-topmost', 1) +root.withdraw() + +type_reader = external_utils.SharedMemoryReader('open_file_dialog') +reader_text = type_reader.read_text() +type_reader.close() + +''' Example of types list format : + "RKG files,*.rkg;CSV files,*.csv;All files,*" + ";" separate all entries, and "," separate the name from the extension''' + +temp = reader_text.split('|') +file_types = temp[0] +initialDir = temp[1] +title = temp[2] +multiple = temp[3] == 'True' + +types_list = file_types.split(';') +for i in range(len(types_list)): + type_tuple = types_list[i].split(',') + types_list[i] = (type_tuple[0], type_tuple[1]) + + +file_path = filedialog.askopenfilename(title = title, + filetypes = types_list, + initialdir = initialDir, + multiple = multiple) + + +print(file_path, end='') + diff --git a/scripts/external/save_file_dialog_box.py b/scripts/external/save_file_dialog_box.py new file mode 100644 index 0000000..b9e1264 --- /dev/null +++ b/scripts/external/save_file_dialog_box.py @@ -0,0 +1,37 @@ +import struct +import external_utils +import tkinter as tk +from tkinter import filedialog + +root = tk.Tk() +root.wm_attributes('-topmost', 1) +root.withdraw() + +type_reader = external_utils.SharedMemoryReader('save_file_dialog') +reader_text = type_reader.read_text() +type_reader.close() + +''' Example of types list format : + "RKG files,*.rkg;CSV files,*.csv;All files,*" + ";" separate all entries, and "," separate the name from the extension''' + +temp = reader_text.split('|') +file_types = temp[0] +initialDir = temp[1] +title = temp[2] +defaultextension = temp[3] + +types_list = file_types.split(';') +for i in range(len(types_list)): + type_tuple = types_list[i].split(',') + types_list[i] = (type_tuple[0], type_tuple[1]) + + +file_path = filedialog.asksaveasfilename(title = title, + filetypes = types_list, + initialdir = initialDir, + defaultextension = defaultextension) + + +print(file_path, end='') + diff --git a/scripts/external/ttk_gui_window.py b/scripts/external/ttk_gui_window.py new file mode 100644 index 0000000..2d2dc89 --- /dev/null +++ b/scripts/external/ttk_gui_window.py @@ -0,0 +1,133 @@ +import tkinter as tk +from tkinter import ttk # lol +import os +import sys +import struct +import external_utils as ex +from idlelib.tooltip import Hovertip + +# This constant determines how the buttons are arranged. +# Ex: BUTTON_LAYOUT[section_index][row_index][column_index] +BUTTON_LAYOUT = [ + [ + ["Load from Player", "Load from Ghost"], + ["Save to RKG", "Load from RKG"], + ["Open in Editor", "Load from CSV"], + ], + [ + ["Load from Player", "Load from Ghost"], + ["Save to RKG", "Load from RKG"], + ["Open in Editor", "Load from CSV"], + ], +] + +TOOLTIP_LAYOUT = [ + [ + ["Load the inputs from the Player.\nSave the inputs to the Player CSV file.", "Load the inputs from the Ghost.\nSave the inputs to the Player CSV file."], + ["Save the Player CSV file to a RKG file.\nTakes metadata from the current Player's state", "Load the inputs from a RKG file.\nSave the inputs to the Player CSV file."], + ["Open the Player CSV file with default Editor", "Load the inputs from a CSV file.\nSave the inputs to the Player CSV file."], + ], + [ + ["Load the inputs from the Player.\nSave the inputs to the Ghost CSV file.", "Load the inputs from the Ghost.\nSave the inputs to the Ghost CSV file."], + ["Save the Ghost CSV file to a RKG file.\nTakes metadata from the current Ghost's state", "Load the inputs from a RKG file.\nSave the inputs to the Ghost CSV file."], + ["Open the Ghost CSV file with default Editor", "Load the inputs from a CSV file.\nSave the inputs to the Ghost CSV file."], + ], +] + +ACTIVATE_CHECKBOX_LAYOUT = [ ["Activate"], + ["Activate Soft", "Activate Hard"] ] +def main(): + try: + shm_activate = ex.SharedMemoryBlock.connect(name="ttk_gui_activate") + shm_buttons = ex.SharedMemoryBlock.connect(name="ttk_gui_buttons") + shm_player_csv = ex.SharedMemoryReader(name="ttk_gui_player_csv") + shm_ghost_csv = ex.SharedMemoryReader(name="ttk_gui_ghost_csv") + shm_close_event = ex.SharedMemoryBlock.connect(name="ttk_gui_window_closed") + except FileNotFoundError as e: + raise FileNotFoundError(f"Shared memory buffer '{e.filename}' not found. Make sure the `TTK_GUI` script is enabled.") + + window = tk.Tk() + window.title("TAS Toolkit GUI") + + dir_path = os.path.dirname(sys.argv[0]) + setting_filename = os.path.join(dir_path, "tkinter.ini") + #Load geometry + geometry = ex.load_external_setting(setting_filename, 'ttk_gui_geometry') + if geometry is None: + geometry = '500x250' + window.geometry(geometry) + # window.attributes('-topmost',True) + + #Save geometry on exit + def on_closing(): + ex.save_external_setting(setting_filename, 'ttk_gui_geometry', str(window.winfo_geometry())) + window.destroy() + shm_close_event.write_text("1") + window.protocol("WM_DELETE_WINDOW", on_closing) + + root_frame = ttk.Frame(window) + root_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) + + # Checkboxes state + activate_state = [tk.BooleanVar(), tk.BooleanVar(), tk.BooleanVar()] + def on_checkbox_change(): + shm_activate.write(struct.pack('>???', *[var.get() for var in activate_state])) + + # File name display state + player_csv = tk.StringVar(value=f"File : {os.path.basename(shm_player_csv.read_text())}") + ghost_csv = tk.StringVar(value=f"File : {os.path.basename(shm_ghost_csv.read_text())}") + + # Construct page layout + for section_index, section_title in enumerate(["Player Inputs", "Ghost Inputs"]): + section_frame = ttk.LabelFrame(root_frame, text=section_title) + section_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5) + + section_activate_button_frame = ttk.Frame(section_frame) + section_activate_button_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=3, pady=3) + + for activate_button_index in range(len(ACTIVATE_CHECKBOX_LAYOUT[section_index])): + button_text = ACTIVATE_CHECKBOX_LAYOUT[section_index][activate_button_index] + activate_state_index = activate_button_index + sum( [ len(ACTIVATE_CHECKBOX_LAYOUT[i]) for i in range(section_index) ]) + + ttk.Checkbutton(section_activate_button_frame, text=button_text, variable=activate_state[activate_state_index], command=on_checkbox_change) \ + .pack(expand = True, pady=1) + + ttk.Label(section_frame, textvariable=[player_csv, ghost_csv][section_index]) \ + .pack(pady=5) + + for row_index, row in enumerate(BUTTON_LAYOUT[section_index]): + btn_row_frame = ttk.Frame(section_frame) + btn_row_frame.pack(pady=5) + + for col_index, btn_text in enumerate(row): + button_data = struct.pack('>?BBB', True, section_index, row_index, col_index) + def on_click(data=button_data): + shm_buttons.write(data) + button_inst = ttk.Button(btn_row_frame, text=btn_text, command=on_click, width=15) + button_inst.pack(side=tk.LEFT, padx=5) + tooltip_text = TOOLTIP_LAYOUT[section_index][row_index][col_index] + tooltip_inst = Hovertip(button_inst, tooltip_text, hover_delay=1200) + + # Function that runs repeatedly while window is open + def loop_actions(): + new_text = shm_player_csv.read_text() + if new_text: + player_csv.set(f"File : {os.path.basename(new_text)}") + + new_text = shm_ghost_csv.read_text() + if new_text: + ghost_csv.set(f"File : {os.path.basename(new_text)}") + + window.after(ms=10, func=loop_actions) + + shm_close_event.write_text("0") + + window.after(ms=0, func=loop_actions) + window.mainloop() + + #This part of the code is only accessed when the window has been closed + + + +if __name__ == '__main__': + main() diff --git a/scripts/python-stubs/dolphin.pyi b/scripts/python-stubs/dolphin.pyi new file mode 100644 index 0000000..0591323 --- /dev/null +++ b/scripts/python-stubs/dolphin.pyi @@ -0,0 +1,33 @@ +""" +Aggregator module of all dolphin-provided modules. +It lets people import the dolphin-provided modules in a more +intuitive way. For example, people can then do this: + from dolphin import event, memory +instead of: + import dolphin_event as event + import dolphin_memory as memory + +Valid: + import dolphin + from dolphin import * + from dolphin import event + import dolphin_event as event +Invalid: + import dolphin.event + from dolphin.event import ... +""" # noqa: D400,D415 # Tries to add a . on an import example +import dolphin_event as event +import dolphin_memory as memory +import dolphin_gui as gui +import dolphin_savestate as savestate +import dolphin_controller as controller +import dolphin_utils as utils + +__all__ = [ + "event", + "memory", + "gui", + "savestate", + "controller", + "utils", +] diff --git a/scripts/python-stubs/dolphin_controller.pyi b/scripts/python-stubs/dolphin_controller.pyi new file mode 100644 index 0000000..0c2f48a --- /dev/null +++ b/scripts/python-stubs/dolphin_controller.pyi @@ -0,0 +1,133 @@ +""" +Module for programmatic inputs. + +Currently, only for GameCube, Wiimote, Nunchuck buttons and Wii IR (pointing). +No acceleration or other extensions data yet. +""" +from typing import TypedDict, type_check_only + + +@type_check_only +class GCInputs(TypedDict): + Left: bool + Right: bool + Down: bool + Up: bool + Z: bool + R: bool + L: bool + A: bool + B: bool + X: bool + Y: bool + Start: bool + StickX: int # 0-255, 128 is neutral + StickY: int # 0-255, 128 is neutral + CStickX: int # 0-255, 128 is neutral + CStickY: int # 0-255, 128 is neutral + TriggerLeft: int # 0-255 + TriggerRight: int # 0-255 + AnalogA: int # 0-255 + AnalogB: int # 0-255 + Connected: bool + + +@type_check_only +class WiiInputs(TypedDict): + Left: bool + Right: bool + Down: bool + Up: bool + Plus: bool + Minus: bool + One: bool + Two: bool + A: bool + B: bool + Home: bool + + +class NunchuckInputs(TypedDict): + C: bool + Z: bool + StickX: int # 0-255, 128 is neutral + StickY: int # 0-255, 128 is neutral + + +def get_gc_buttons(controller_id: int, /) -> GCInputs: + """ + Retrieves the current input map for the given GameCube controller. + + :param controller_id: 0-based index of the controller + :return: dictionary describing the current input map + """ + + +def set_gc_buttons(controller_id: int, inputs: GCInputs, /) -> None: + """ + Sets the current input map for the given GameCube controller. + The override will hold for the current frame. + + :param controller_id: 0-based index of the controller + :param inputs: dictionary describing the input map + """ + + +def get_wii_buttons(controller_id: int, /) -> WiiInputs: + """ + Retrieves the current input map for the given Wii controller. + + :param controller_id: 0-based index of the controller + :return: dictionary describing the current input map + """ + + +def set_wii_buttons(controller_id: int, inputs: WiiInputs, /) -> None: + """ + Sets the current input map for the given Wii controller. + The override will hold for the current frame. + + :param controller_id: 0-based index of the controller + :param inputs: dictionary describing the input map + """ + + +def set_wii_ircamera_transform( + controller_id: int, x: float, y: float, + z: float = -2, pitch: float = 0, yaw: float = 0, roll: float = 0, /, +) -> None: + """ + Places the simulated IR camera at the specified location + with the specified rotation relative to the sensor bar. + For example, to move 2 meters away from the sensor, + 15 centimeters to the right and 5 centimeters down, use: + `set_wii_ircamera_transform(controller_id, 0.15, -0.05, -2)`. + + :param controller_id: 0-based index of the controller + :param x: x-position of the simulated IR camera in meters + :param y: y-position of the simulated IR camera in meters + :param z: z-position of the simulated IR camera in meters. + Default is -2, meaning 2 meters from the simulated sensor bar. + :param pitch: pitch of the simulated IR camera in radians. + :param yaw: yaw of the simulated IR camera in radians. + :param roll: roll of the simulated IR camera in radians. + """ + + +def get_nunchuck_buttons(controller_id: int, /) -> NunchuckInputs: + """ + Retrieves the current input map for the given Nunchuck extension. + + :param controller_id: 0-based index of the controller + :return: dictionary describing the current input map + """ + + +def set_nunchuck_buttons(controller_id: int, inputs: NunchuckInputs, /) -> None: + """ + Sets the current input map for the given Nunchuck extension. + The override will hold for the current frame. + + :param controller_id: 0-based index of the controller + :param inputs: dictionary describing the input map + """ diff --git a/scripts/python-stubs/dolphin_event.pyi b/scripts/python-stubs/dolphin_event.pyi new file mode 100644 index 0000000..ffbaa5c --- /dev/null +++ b/scripts/python-stubs/dolphin_event.pyi @@ -0,0 +1,224 @@ +""" +Module for awaiting or registering callbacks on all events emitted by Dolphin. + +The odd-looking Protocol classes are just a lot of syntax to essentially describe +the callback's signature. See https://www.python.org/dev/peps/pep-0544/#callback-protocols +""" +from collections.abc import Callable +from typing import Protocol, type_check_only + + +def on_frameadvance(callback: Callable[[], None] | None) -> None: + """Registers a callback to be called every time the game has rendered a new frame.""" + + +async def frameadvance() -> None: + """Awaitable event that completes once the game has rendered a new frame.""" + + +def on_framebegin(callback: Callable[[], None] | None) -> None: + """Registers a callback to be called every time the game checks for controller inputs. + Typically happen twice per frame, before and after frameadvance. + Should be used for performing inputs on a specific frame """ + + +async def framebegin() -> None: + """Awaitable event that completes once the game checks for controller inputs.""" + + + +@type_check_only +class _CodebreakpointCallback(Protocol): + def __call__(self, addr: int) -> None: + """ + Example callback stub for on_codebreakpoint. + + :param addr: address that was accessed + """ + + +def on_codebreakpoint(callback: _CodebreakpointCallback | None) -> None: + """ + Registers a callback to be called every time a previously added code breakpoint is hit. + + :param callback: + :return: + """ + + +async def codebreakpoint() -> tuple[int,]: + """Awaitable event that completes once a previously added code breakpoint is hit.""" + + +@type_check_only +class _MemorybreakpointCallback(Protocol): + def __call__(self, is_write: bool, addr: int, value: int) -> None: + """ + Example callback stub for on_memorybreakpoint. + + :param is_write: true if a value was written, false if it was read + :param addr: address that was accessed + :param value: new value at the given address + """ + + +def on_memorybreakpoint(callback: _MemorybreakpointCallback | None) -> None: + """ + Registers a callback to be called every time a previously added memory breakpoint is hit. + + :param callback: + :return: + """ + + +async def memorybreakpoint() -> tuple[bool, int, int]: + """Awaitable event that completes once a previously added memory breakpoint is hit.""" + + +@type_check_only +class _SaveStateCallback(Protocol): + def __call__(self, is_slot: bool, slot: int, /) -> None: + """ + Example callback stub for on_savestatesave and/or on_savestateload. + + :param is_slot: true if save/load was with a savestate slot, \ + false if save/load was from a file + :param slot: the slot the save/load occurred to/from. \ + Should be disregarded if is_slot is false + """ + + +def on_savestatesave(callback: _SaveStateCallback | None) -> None: + """ + Registers a callback to be called every time a savestate is saved. + + :param callback: + :return: + """ + + +async def savestatesave() -> tuple[bool, int]: + """Awaitable event that completes once a savestate is saved.""" + + +def on_savestateload(callback: _SaveStateCallback | None) -> None: + """ + Registers a callback to be called every time a savestate has been loaded. + + :param callback: + :return: + """ + + +async def savestateload() -> tuple[bool, int]: + """Awaitable event that completes once a savestate has been loaded.""" + + + +def on_beforesavestateload(callback: _SaveStateCallback | None) -> None: + """ + Registers a callback to be called before a savestate is been loaded. + + :param callback: + :return: + """ + + +async def beforesavestateload() -> tuple[bool, int]: + """Awaitable event that completes before a savestate is been loaded.""" + + + +@type_check_only +class _BoolCallback(Protocol): + def __call__(self, boolean: bool) -> None: + """ + Example callback stub for on_focuschange. + + :param boolean: correspond to the has_focus state \ + or other boolean depending on the context + """ + +def on_unpause(callback: Callable[[], None] | None) -> None: + """ + Registers a callback to be called on emulation unpause. + + :param callback: + :return: + """ + + +async def unpause() -> None: + """Awaitable event that completes on emulation unpause.""" + + + +def on_focuschange(callback: _BoolCallback | None) -> None: + """ + Registers a callback to be called on render window focus change. + + :param callback: + :return: + """ + + +async def focuschange() -> bool: + """Awaitable event that completes on render window focus change.""" + +@type_check_only +class _GeometryCallback(Protocol): + def __call__(self, x: int, y:int, width:int, height:int) -> None: + """ + Example callback stub for rendergeometrychange. + + :param x: x coordinate of the window + :param y: y coordinate of the window + :param width: width of the window + :param height: height of the window + """ + +def on_rendergeometrychange(callback: _GeometryCallback | None) -> None: + """ + Registers a callback to be called on render window geometry change. + + :param callback: + :return: + """ + + +async def rendergeometrychange() -> tuple[int, int, int, int]: + """Awaitable event that completes on render window geometry change.""" + + +def on_timertick(callback: Callable[[], None] | None) -> None: + """Registers a callback to be called 60 times per second""" + + +async def frameadvance() -> None: + """Awaitable event that completes 60 times per second""" + + +@type_check_only +class _ScriptEndCallback(Protocol): + def __call__(self, id_: int) -> None: + """ + Example callback stub for rendergeometrychange. + + :param id_: id of the script ending (correspond with utils.get_script_id()) + """ + +def on_scriptend(callback: _ScriptEndCallback | None) -> None: + """ + Registers a callback to be called when any script ends. + + :param callback: + :return: + """ + + +async def scriptend() -> int: + """Awaitable event that completes when any script ends.""" + + +def system_reset() -> None: + """Resets the emulation.""" diff --git a/scripts/python-stubs/dolphin_gui.pyi b/scripts/python-stubs/dolphin_gui.pyi new file mode 100644 index 0000000..100e689 --- /dev/null +++ b/scripts/python-stubs/dolphin_gui.pyi @@ -0,0 +1,118 @@ +""" +Module for doing stuff with the graphical user interface. + +All colors are in ARGB. +All positions are (x, y) with (0, 0) being top left. X is the horizontal axis. +""" + +from typing_extensions import TypeAlias + +Position: TypeAlias = tuple[float, float] + + +def add_osd_message(message: str, duration_ms: int = 2000, color: int = 0xFFFFFF30) -> None: + """ + Adds a new message to the on-screen-display. + + :param message: message to agg + :param duration_ms: how long the message should be visible for, in milliseconds + :param color: color of the message text as an int + """ + + +def clear_osd_messages() -> None: + """Clear all on-screen-display messages.""" + + +def get_display_size() -> tuple[float, float]: + """:return: The current display size in pixels.""" + +def get_font_size() -> int: + """:return: The current font size in pixels.""" + + +def draw_line(a: Position, b: Position, color: int, thickness: float = 1) -> None: + """Draws a line from a to b.""" + + +def draw_rect( + a: Position, + b: Position, + color: int, + rounding: float = 0, + thickness: float = 1, +) -> None: + """Draws a hollow rectangle from a (upper left) to b (lower right).""" + + +def draw_rect_filled(a: Position, b: Position, color: int, rounding: float = 0) -> None: + """Draws a filled rectangle from a (upper left) to b (lower right).""" + + +def draw_quad( + a: Position, + b: Position, + c: Position, + d: Position, + color: int, + thickness: float = 1, +) -> None: + """ + Draws a hollow quad through the points a, b, c and d. + Points should be defined in clockwise order. + """ + + +def draw_quad_filled(a: Position, b: Position, c: Position, d: Position, color: int) -> None: + """Draws a filled quad through the points a, b, c and d.""" + + +def draw_triangle(a: Position, b: Position, c: Position, color: int, thickness: float = 1) -> None: + """Draws a hollow triangle through the points a, b and c.""" + + +def draw_triangle_filled(a: Position, b: Position, c: Position, color: int) -> None: + """Draws a filled triangle through the points a, b and c.""" + + +def draw_circle( + center: Position, + radius: float, + color: int, + num_segments: int | None = None, + thickness: float = 1, +) -> None: + """ + Draws a hollow circle with the given center point and radius. + If num_segments is set to None (default), a sensible default is used. + """ + + +def draw_circle_filled( + center: Position, radius: float, color: int, + num_segments: int | None = None, +) -> None: + """ + Draws a filled circle with the given center point and radius. + If num_segments is set to None (default), a sensible default is used. + """ + + +def draw_text(pos: Position, color: int, text: str) -> None: + """Draws text at a fixed position.""" + + +def draw_polyline( + points: list[Position], + color: int, + closed: bool = False, + thickness: float = 1, +) -> None: + """Draws a line through a list of points.""" + + +def draw_convex_poly_filled(points: list[Position], color: int) -> None: + """ + Draws a convex polygon through a list of points. + Points should be defined in clockwise order. + """ diff --git a/scripts/python-stubs/dolphin_memory.pyi b/scripts/python-stubs/dolphin_memory.pyi new file mode 100644 index 0000000..def2b82 --- /dev/null +++ b/scripts/python-stubs/dolphin_memory.pyi @@ -0,0 +1,231 @@ +"""Module for interacting with the emulated machine's memory.""" + + +def read_u8(addr: int, /) -> int: + """ + Reads 1 byte as an unsigned integer. + + :param addr: memory address to read from + :return: value as integer + """ + + +def read_u16(addr: int, /) -> int: + """ + Reads 2 bytes as an unsigned integer. + + :param addr: memory address to read from + :return: value as integer + """ + + +def read_u32(addr: int, /) -> int: + """ + Reads 4 bytes as an unsigned integer. + + :param addr: memory address to read from + :return: value as integer + """ + + +def read_u64(addr: int, /) -> int: + """ + Reads 8 bytes as an unsigned integer. + + :param addr: memory address to read from + :return: value as integer + """ + + +def read_s8(addr: int, /) -> int: + """ + Reads 1 byte as a signed integer. + + :param addr: memory address to read from + :return: value as integer + """ + + +def read_s16(addr: int, /) -> int: + """ + Reads 2 bytes as a signed integer. + + :param addr: memory address to read from + :return: value as integer + """ + + +def read_s32(addr: int, /) -> int: + """ + Reads 4 bytes as a signed integer. + + :param addr: memory address to read from + :return: value as integer + """ + + +def read_s64(addr: int, /) -> int: + """ + Reads 8 bytes as a signed integer. + + :param addr: memory address to read from + :return: value as integer + """ + + +def read_f32(addr: int, /) -> float: + """ + Reads 4 bytes as a floating point number. + + :param addr: memory address to read from + :return: value as floating point number + """ + + +def read_f64(addr: int, /) -> float: + """ + Reads 8 bytes as a floating point number. + + :param addr: memory address to read from + :return: value as floating point number + """ + + +def read_bytes(addr: int, size: int, /) -> bytearray: + """ + Reads size bytes and outputs a bytearray of length size. + + :param addr: memory address to start reading from + :param size: number of bytes to read + :return: bytearray containing the read bytes + """ + + +def invalidate_icache(addr: int, size: int, /) -> None: + """ + Invalidates JIT cached code between the address and address + size, \ + forcing the JIT to refetch instructions instead of executing from its cache. + + :param addr: memory address to start invalidation at + :param size: size of the cache as integer + """ + + +def write_u8(addr: int, value: int, /) -> None: + """ + Writes an unsigned integer to 1 byte. + Overflowing values are truncated. + + :param addr: memory address to read from + :param value: value as integer + """ + + +def write_u16(addr: int, value: int, /) -> None: + """ + Writes an unsigned integer to 2 bytes. + Overflowing values are truncated. + + :param addr: memory address to read from + :param value: value as integer + """ + + +def write_u32(addr: int, value: int, /) -> None: + """ + Writes an unsigned integer to 4 bytes. + Overflowing values are truncated. + + :param addr: memory address to read from + :param value: value as integer + """ + + +def write_u64(addr: int, value: int, /) -> None: + """ + Writes an unsigned integer to 8 bytes. + Overflowing values are truncated. + + :param addr: memory address to read from + :param value: value as integer + """ + + +def write_s8(addr: int, value: int, /) -> None: + """ + Writes a signed integer to 1 byte. + Overflowing values are truncated. + + :param addr: memory address to read from + :param value: value as integer + """ + + +def write_s16(addr: int, value: int, /) -> None: + """ + Writes a signed integer to 2 bytes. + Overflowing values are truncated. + + :param addr: memory address to read from + :param value: value as integer + """ + + +def write_s32(addr: int, value: int, /) -> None: + """ + Writes a signed integer to 4 bytes. + Overflowing values are truncated. + + :param addr: memory address to read from + :param value: value as integer + """ + + +def write_s64(addr: int, value: int, /) -> None: + """ + Writes a signed integer to 8 bytes. + Overflowing values are truncated. + + :param addr: memory address to read from + :param value: value as integer + """ + + +def write_f32(addr: int, value: float, /) -> None: + """ + Writes a floating point number to 4 bytes. + Overflowing values are truncated. + + :param addr: memory address to read from + :param value: value as floating point number + """ + + +def write_f64(addr: int, value: float, /) -> None: + """ + Writes a floating point number to 8 bytes. + Overflowing values are truncated. + + :param addr: memory address to read from + :param value: value as floating point number + """ + + +def write_bytes(addr: int, bytes: bytearray, /) -> None: + """ + Writes each byte from the provided bytearray, + starting from addr. + + :param addr: memory address to start writing to + :param bytes: bytearray of bytes to write + """ + +def is_memory_accessible() -> bool: + """ + Return a boolean value corresponding to + the state of the memory. + True means the memory is accessible, + False means the memory isn't accessible. + Trying to read/write the memory while it's not accessible + may result in Dolphin crashing + """ \ No newline at end of file diff --git a/scripts/python-stubs/dolphin_savestate.pyi b/scripts/python-stubs/dolphin_savestate.pyi new file mode 100644 index 0000000..205604c --- /dev/null +++ b/scripts/python-stubs/dolphin_savestate.pyi @@ -0,0 +1,31 @@ +"""Module for creating and loading savestates.""" + + +def save_to_slot(slot: int, /) -> None: + """ + Saves a savestate to the given slot. + The slot number must be between 0 and 99. + """ + + +def save_to_file(filename: str, /) -> None: + """Saves a savestate to the given file.""" + + +def save_to_bytes() -> bytes: + """Saves a savestate and returns it as bytes.""" + + +def load_from_slot(slot: int, /) -> None: + """ + Loads a savestate from the given slot. + The slot number must be between 0 and 99. + """ + + +def load_from_file(filename: str, /) -> None: + """Loads a savestate from the given file.""" + + +def load_from_bytes(state_bytes: bytes, /) -> None: + """Loads a savestate from the given bytes.""" diff --git a/scripts/python-stubs/dolphin_utils.pyi b/scripts/python-stubs/dolphin_utils.pyi new file mode 100644 index 0000000..fbd5a8a --- /dev/null +++ b/scripts/python-stubs/dolphin_utils.pyi @@ -0,0 +1,100 @@ +"""Module for various utilities.""" + + +def get_script_dir() -> str: + """ + Returns the path to the Scripts directory, \ + which is found in the Load folder inside of the user directory by default. + + :return: value as string + """ + + +def open_file() -> str: + """ + Prompts the user to open a file. + + :return: value as string + """ + + +def start_framedump() -> None: + """Starts a framedump.""" + + +def stop_framedump() -> None: + """Stops a framedump.""" + + +def is_framedumping() -> bool: + """ + Checks if a framedump is occuring. + + :return: value as bool + """ + + +def start_audiodump() -> None: + """Starts an audiodump.""" + + +def stop_audiodump() -> None: + """Stops an audiodump.""" + + +def is_audiodumping() -> bool: + """ + Checks if an audiodump is occuring. + + :return: value as bool + """ + + +def save_screenshot(filename: str | None = None) -> None: + """Saves a screenshot of the running game.""" + + +def toggle_play() -> None: + """Plays/Pauses the current game.""" + + +def is_paused() -> bool: + """ + Checks if the emulation is currently paused. + + :return: value as bool + """ + + +def renderer_has_focus() -> bool: + """ + Checks if the render window has focus. + + :return: value as bool + """ + + +def renderer_geometry() -> (int, int, int, int): + """ + Get the geometry value of the rendering QtWidget + + :return: (x, y, width, height) as (int, int, int, int) + """ + +def cancel_script(scriptPath : str) -> None: + """Cancel the script with the corresponding path + The script cancel is delayed to when the host is free. + It means it won't cancel immediately, but it will cancel after + the current python script main() is over.""" + +def activate_script(scriptPath : str) -> None: + """Activate the script with the corresponding path + The script activate is delayed to when the host is free. + It means it won't activate immediately, but it will activate after + the current python script main() is over.""" + +def get_script_name() -> str: + """Return the filepath of the current script""" + +def get_script_id() -> int: + """Return the id of the current script""" \ No newline at end of file diff --git a/scripts/update_scripts.py b/scripts/update_scripts.py new file mode 100644 index 0000000..042bcf6 --- /dev/null +++ b/scripts/update_scripts.py @@ -0,0 +1,20 @@ +import requests, zipfile, io, os, shutil + +print('Downloading new scripts') +url = 'https://github.com/Blounard/mkw-scripts/archive/refs/heads/main.zip' +r = requests.get(url) +print('Extracting scripts') +z = zipfile.ZipFile(io.BytesIO(r.content)) +z.extractall("script_update_temp") + +source_folder = r'script_update_temp\mkw-scripts-main\scripts' + +print('Replacing scripts') +for entry in os.scandir(source_folder): + source_name = os.path.join(source_folder, entry.name) + if os.path.isfile(source_name): + shutil.copy(source_name, entry.name) + else: + shutil.copytree(source_name, entry.name, dirs_exist_ok=True) + +shutil.rmtree("script_update_temp") diff --git a/scripts/verify_package.py b/scripts/verify_package.py new file mode 100644 index 0000000..fe0c8b0 --- /dev/null +++ b/scripts/verify_package.py @@ -0,0 +1,10 @@ +import numpy +import matplotlib +import pygame +import tkinter +import os +from PIL import Image +import moviepy + +print('\n\n\nPackages installed !') +os.system("pause")