def load_m64(filename: str) -> Tuple[TasMetadata, Dict[Variable, object]]: with open(filename, 'rb') as f: f.seek(0x10) rerecords = struct.unpack('>H', f.read(2))[0] f.seek(0xE4) crc = f.read(4) game_version = dict_inverse(CRC_CODES).get(crc) or 'us' # TODO: Fall back to country code? f.seek(0x222) authors = f.read(222).partition(b'\x00')[0].decode('utf-8') f.seek(0x300) description = f.read(256).partition(b'\x00')[0].decode('utf-8') metadata = TasMetadata( game_version, os.path.split(filename)[1], authors, description, rerecords, ) edits: Dict[Variable, object] = {} f.seek(0x400) frame = 0 while True: try: buttons = struct.unpack('>H', f.read(2))[0] stick_x = struct.unpack('=b', f.read(1))[0] stick_y = struct.unpack('=b', f.read(1))[0] except struct.error: break for variable, flag in INPUT_BUTTON_FLAGS.items(): if buttons & flag: edits[variable.with_frame(frame)] = True if stick_x != 0: edits[Variable('input-stick-x').with_frame(frame)] = stick_x if stick_y != 0: edits[Variable('input-stick-y').with_frame(frame)] = stick_y prev_buttons = buttons prev_stick_x = stick_x prev_stick_y = stick_y frame += 1 return (metadata, edits)
def get_mario_pos(model: Model) -> Vec3f: return ( dcast( float, model.get( Variable('mario-pos-x').with_frame(model.selected_frame))), dcast( float, model.get( Variable('mario-pos-y').with_frame(model.selected_frame))), dcast( float, model.get( Variable('mario-pos-z').with_frame(model.selected_frame))), )
def render_button(button: str) -> None: self.render_variable( tab, Variable('input-button-' + button).with_frame( self.model.selected_frame), 10, 25, )
def render_headers(self) -> None: header_labels = [ self.displayer.column_header(column.variable) for column in self.columns ] header_lines = max((len(label.split('\n')) for label in header_labels), default=1) ig.columns(len(self.columns) + 1) if len(self.columns) > 0: ig.set_column_width(-1, self.frame_column_width) ig.text('') ig.next_column() for index, column in enumerate(self.columns): initial_cursor_pos = ig.get_cursor_pos() ig.selectable( '##fs-col-' + str(id(self)) + '-' + str(id(column)), height=header_lines * ig.get_text_line_height(), ) # TODO: Width adjusting ig.set_column_width(-1, column.width) if ig.begin_drag_drop_source(): ig.text(header_labels[index]) ig.set_drag_drop_payload('fs-col', str(index).encode('utf-8')) ig.end_drag_drop_source() if ig.begin_drag_drop_target(): payload = ig.accept_drag_drop_payload('fs-col') if payload is not None: source = int(payload.decode('utf-8')) self._move_column(source, index) payload = ig.accept_drag_drop_payload('ve-var') if payload is not None: self._insert_variable(index, Variable.from_bytes(payload)) ig.end_drag_drop_target() if ig.is_item_hovered() and ig.is_mouse_clicked(2): self._remove_column(index) if ig.begin_popup_context_item('##fs-colctx-' + str(id(self)) + '-' + str(id(column))): if ig.selectable('Close')[0]: self._remove_column(index) ig.end_popup_context_item() ig.set_cursor_pos(initial_cursor_pos) ig.text(header_labels[index]) ig.next_column() ig.separator() ig.columns(1)
def save_m64(filename: str, metadata: TasMetadata, pipeline: Pipeline, length: int) -> None: with open(filename, 'wb') as f: # TODO: Remove blank frames at end f.write(b'\x4d\x36\x34\x1a') f.write(b'\x03\x00\x00\x00') f.write(b'\x00\x00\x00\x00') # movie uid f.write(b'\xff\xff\xff\xff') f.write(struct.pack('<I', (metadata.rerecords or 0) & 0xFFFFFFFF)) f.write(b'\x3c\x01\x00\x00') f.write(struct.pack('<I', length)) f.write(b'\x02\x00\x00\x00') # power-on f.write(b'\x01\x00\x00\x00') f.write(bytes(160)) f.write(bytes_to_buffer(b'SUPER MARIO 64', 32)) f.write(CRC_CODES[metadata.game_version]) f.write(COUNTRY_CODES[metadata.game_version]) f.write(bytes(57)) f.write(bytes(64)) f.write(bytes(64)) f.write(bytes(64)) f.write(bytes(64)) f.write(bytes_to_buffer(metadata.authors.encode('utf-8'), 222)) f.write(bytes_to_buffer(metadata.description.encode('utf-8'), 256)) for frame in range(length): buttons = dcast( int, pipeline.read(Variable('input-buttons').with_frame(frame))) stick_x = dcast( int, pipeline.read(Variable('input-stick-x').with_frame(frame))) stick_y = dcast( int, pipeline.read(Variable('input-stick-y').with_frame(frame))) f.write(struct.pack(b'>H', buttons & 0xFFFF)) f.write(struct.pack(b'=B', stick_x & 0xFF)) f.write(struct.pack(b'=B', stick_y & 0xFF))
def render_labeled_variable( id: str, label: str, variable: Variable, value: T, formatter: VariableFormatter, is_edited: bool, label_width=80, value_width=80, ) -> Tuple[Maybe[T], bool]: ig.push_id(id) ig.selectable(label + '##label', width=label_width) if ig.begin_drag_drop_source(): ig.text(label) ig.set_drag_drop_payload('ve-var', variable.to_bytes()) ig.end_drag_drop_source() ig.same_line() cell_width = value_width cell_height = ig.get_text_line_height( ) + 2 * ig.get_style().frame_padding[1] cell_cursor_pos = ig.get_cursor_pos() cell_cursor_pos = ( cell_cursor_pos[0] + ig.get_window_position()[0] - ig.get_scroll_x(), cell_cursor_pos[1] + ig.get_window_position()[1] - ig.get_scroll_y(), ) changed_data, _, _ = render_variable_value( 'value', value, formatter, (cell_width, cell_height), ) clear_edit = is_edited and ig.is_item_hovered() and ig.is_mouse_down(2) if is_edited: dl = ig.get_window_draw_list() spacing = ig.get_style().item_spacing spacing = (spacing[0] / 2, spacing[1] / 2) dl.add_rect( cell_cursor_pos[0] - spacing[0], cell_cursor_pos[1] - spacing[1], cell_cursor_pos[0] + cell_width + spacing[0] - 1, cell_cursor_pos[1] + cell_height + spacing[1] - 1, ig.get_color_u32_rgba(0.8, 0.6, 0, 1), ) ig.pop_id() return changed_data, clear_edit
def _insert_variable(self, index: int, variable: Variable) -> None: variable = variable.without_frame() if self.columns != self.next_columns: log.error('Multiple frame sheet column mods on same frame') return object_slot = variable.object column = FrameSheetColumn(variable) if column not in self.columns: self.next_columns.insert(index, column)
def render_stick_control(self, id: str, tab: TabId) -> None: stick_x_var = Variable('input-stick-x').with_frame( self.model.selected_frame) stick_y_var = Variable('input-stick-y').with_frame( self.model.selected_frame) self.render_variable(tab, stick_x_var, 60, 50) self.render_variable(tab, stick_y_var, 60, 50) stick_x = dcast(int, self.model.get(stick_x_var)) stick_y = dcast(int, self.model.get(stick_y_var)) n_x = 2 * ((stick_x + 128) / 255) - 1 n_y = 2 * ((stick_y + 128) / 255) - 1 new_n = ui.render_joystick_control(id, n_x, n_y) if new_n is not None: new_stick_x = int(0.5 * (new_n[0] + 1) * 255 - 128) new_stick_y = int(0.5 * (new_n[1] + 1) * 255 - 128) self.model.set(stick_x_var, new_stick_x) self.model.set(stick_y_var, new_stick_y)
def render_intended_stick_control(self, id: str) -> None: up_options = ['3d view', 'mario yaw', 'stick y', 'world x'] up_option = use_state('up-option', 0) ig.text('up =') ig.same_line() ig.push_item_width(100) _, up_option.value = ig.combo('##up-option', up_option.value, up_options) ig.pop_item_width() ig.dummy(1, 10) stick_x_var = Variable('input-stick-x').with_frame( self.model.selected_frame) stick_y_var = Variable('input-stick-y').with_frame( self.model.selected_frame) face_yaw = dcast( int, self.model.get( Variable('mario-face-yaw').with_frame( self.model.selected_frame))) camera_yaw = dcast( int, self.model.get( Variable('camera-yaw').with_frame(self.model.selected_frame)) or 0) squish_timer = dcast( int, self.model.get(self.model.selected_frame, 'gMarioState->squishTimer')) active_face_yaw = face_yaw events = self.model.pipeline.frame_log(self.model.selected_frame + 1) active_face_yaw_action = None for event in events: if event['type'] == 'FLT_EXECUTE_ACTION': action_name = self.model.action_names[event['action']] active_face_yaw = event['faceAngle'][1] active_face_yaw_action = action_name if action_name == 'idle': break up_angle = { 'mario yaw': active_face_yaw, 'stick y': camera_yaw + 0x8000, 'world x': 0x4000, '3d view': self.model.rotational_camera_yaw, }[up_options[up_option.value]] self.model.input_up_yaw = up_angle raw_stick_x = dcast(int, self.model.get(stick_x_var)) raw_stick_y = dcast(int, self.model.get(stick_y_var)) adjusted = stick_raw_to_adjusted(raw_stick_x, raw_stick_y) intended = stick_adjusted_to_intended( adjusted, face_yaw, camera_yaw, squish_timer != 0, ) def render_value(label: str, value: object, formatter: VariableFormatter) -> Optional[Any]: label_width = 60 value_size = ( 60 if label == 'dyaw' else 80, ig.get_text_line_height() + 2 * ig.get_style().frame_padding[1], ) ig.push_item_width(label_width) ig.selectable(label, width=label_width) ig.pop_item_width() ig.same_line() new_value, _, _ = ui.render_variable_value('value-' + label, value, formatter, value_size) return None if new_value is None else new_value.value target_yaw: Optional[int] = None target_dyaw: Optional[int] = None target_mag: Optional[float] = None target_mag = render_value('int mag', intended.mag, FloatFormatter()) target_yaw = render_value('int yaw', intended.yaw, DecimalIntFormatter()) dyaw = intended.yaw - active_face_yaw target_dyaw = render_value('dyaw', dyaw, DecimalIntFormatter()) ig.same_line() if ig.button('?'): ig.open_popup('active-yaw-expl') if ig.begin_popup('active-yaw-expl'): ig.text(f'{intended.yaw} - {active_face_yaw} = {dyaw}') ig.text(f'intended yaw = {intended.yaw}') if active_face_yaw == face_yaw: ig.text(f'face yaw = {face_yaw}') if active_face_yaw != face_yaw: ig.text( f'face yaw = {active_face_yaw} at start of {active_face_yaw_action} action' ) ig.text(f'(face yaw = {face_yaw} at start of frame)') ig.end_popup() if dyaw not in range(0, 16): if ig.button('dyaw = 0'): target_dyaw = 0 if target_yaw is not None or target_dyaw is not None or target_mag is not None: relative_to = 0 if target_yaw is not None else active_face_yaw if target_dyaw is not None: target_yaw = active_face_yaw + target_dyaw if target_yaw is None: target_yaw = intended.yaw if target_mag is None: target_mag = intended.mag new_raw_stick_x, new_raw_stick_y = intended_to_raw( face_yaw, camera_yaw, squish_timer, target_yaw, target_mag, relative_to) self.model.set(stick_x_var, new_raw_stick_x) self.model.set(stick_y_var, new_raw_stick_y) n_a = intended.yaw - up_angle n_x = intended.mag / 32 * math.sin(-n_a * math.pi / 0x8000) n_y = intended.mag / 32 * math.cos(n_a * math.pi / 0x8000) ig.set_cursor_pos((ig.get_cursor_pos().x + 155, 0)) new_n = ui.render_joystick_control(id, n_x, n_y, 'circle') if new_n is not None: new_n_a = int(math.atan2(-new_n[0], new_n[1]) * 0x8000 / math.pi) new_intended_yaw = up_angle + new_n_a new_intended_mag = 32 * math.sqrt(new_n[0]**2 + new_n[1]**2) new_raw_stick_x, new_raw_stick_y = intended_to_raw( face_yaw, camera_yaw, squish_timer, new_intended_yaw, new_intended_mag, relative_to=0) self.model.set(stick_x_var, new_raw_stick_x) self.model.set(stick_y_var, new_raw_stick_y)
from wafel_core import Variable INPUT_BUTTON_FLAGS = { Variable('input-button-a'): 0x8000, Variable('input-button-b'): 0x4000, Variable('input-button-z'): 0x2000, Variable('input-button-s'): 0x1000, Variable('input-button-l'): 0x0020, Variable('input-button-r'): 0x0010, Variable('input-button-cu'): 0x0008, Variable('input-button-cl'): 0x0002, Variable('input-button-cr'): 0x0001, Variable('input-button-cd'): 0x0004, Variable('input-button-du'): 0x0800, Variable('input-button-dl'): 0x0200, Variable('input-button-dr'): 0x0100, Variable('input-button-dd'): 0x0400, }