├── .editorconfig ├── .idea ├── codeStyles │ └── codeStyleConfig.xml ├── vcs.xml ├── discord.xml ├── .gitignore ├── artifacts │ └── TAS_Editor.xml ├── compiler.xml ├── misc.xml ├── jarRepositories.xml └── inspectionProfiles │ └── Project_Default.xml ├── src └── main │ └── java │ └── io │ └── github │ └── jadefalke2 │ ├── actions │ ├── Action.java │ ├── InsertEmptyLineAction.java │ ├── ButtonAction.java │ ├── StickAction.java │ └── LineAction.java │ ├── util │ ├── ScriptObserver.java │ ├── TriFunction.java │ ├── Logger.java │ ├── SimpleDocumentListener.java │ ├── Button.java │ ├── CorruptedScriptException.java │ ├── CustomPianoRollCellRenderer.java │ ├── ObservableProperty.java │ ├── ScriptTableModel.java │ ├── Util.java │ ├── ByteConversions.java │ ├── Settings.java │ ├── InputDrawMouseListener.java │ └── ByteDataStream.java │ ├── stickRelatedClasses │ ├── CustomChangeListener.java │ ├── ChangeObject.java │ ├── StickPosition.java │ ├── SmoothTransitionDialog.java │ ├── JoystickPanel.java │ └── Joystick.java │ ├── script │ ├── Format.java │ ├── NXTas.java │ └── STas.java │ ├── TAS.java │ ├── components │ ├── ColumnRightClickMenu.java │ ├── AddLinesDialog.java │ ├── TasFileChooser.java │ ├── LineRightClickMenu.java │ ├── TabbedScriptsPane.java │ ├── SelectLinesDialog.java │ ├── MainEditorWindow.java │ ├── ScriptTab.java │ ├── SideJoystickPanel.java │ ├── SettingsDialog.java │ ├── PianoRoll.java │ └── MainJMenuBar.java │ ├── InputLine.java │ └── Script.java ├── README.md ├── LICENSE ├── .gitignore ├── .github └── workflows │ └── main.yml ├── dependency-reduced-pom.xml └── pom.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | end_of_line = lf 6 | 7 | [*.java] 8 | indent_style = tab 9 | indent_size = 4 10 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Datasource local storage ignored files 5 | /../../../../../:\New Projects\Privat\TAS editor\.idea/dataSources/ 6 | /dataSources.local.xml 7 | # Editor-based HTTP Client requests 8 | /httpRequests/ 9 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/actions/Action.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.actions; 2 | 3 | /** 4 | * Represents a user action that gets added to the undo history and is able to be reverted 5 | */ 6 | public interface Action { 7 | 8 | void execute(); 9 | 10 | void revert(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/util/ScriptObserver.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.util; 2 | 3 | import java.io.File; 4 | 5 | public interface ScriptObserver { 6 | 7 | default void onFileChange(File file) {} 8 | default void onLengthChange(int length) {} 9 | default void onDirtyChange(boolean dirty) {} 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/stickRelatedClasses/CustomChangeListener.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.stickRelatedClasses; 2 | 3 | import java.util.EventListener; 4 | 5 | //custom version of event.ChangeListener to use ChangeObject 6 | public interface CustomChangeListener extends EventListener { 7 | void stateChanged(ChangeObject e); 8 | default void silentStateChanged(ChangeObject e) {} 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/stickRelatedClasses/ChangeObject.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.stickRelatedClasses; 2 | 3 | import javax.swing.event.ChangeEvent; 4 | 5 | public class ChangeObject extends ChangeEvent { 6 | 7 | private final E value; 8 | 9 | public ChangeObject(E value, Object source){ 10 | super(source); 11 | this.value = value; 12 | } 13 | 14 | public E getValue(){ 15 | return value; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /.idea/artifacts/TAS_Editor.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | $PROJECT_DIR$/out/artifacts/TAS_Editor 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/util/TriFunction.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.util; 2 | 3 | import java.util.Objects; 4 | import java.util.function.Function; 5 | 6 | @FunctionalInterface 7 | public 8 | interface TriFunction { 9 | 10 | R apply(A a, B b, C c); 11 | 12 | default TriFunction andThen( 13 | Function after) { 14 | Objects.requireNonNull(after); 15 | return (A a, B b, C c) -> after.apply(apply(a, b, c)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/util/Logger.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.util; 2 | 3 | import java.time.LocalDateTime; 4 | import java.time.format.DateTimeFormatter; 5 | 6 | public class Logger { 7 | 8 | public static void log (String s) { 9 | // using this to make it easier to later change this behaviour 10 | System.out.println(getTimeString() + s); 11 | } 12 | 13 | private static String getTimeString () { 14 | DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss.ms"); 15 | LocalDateTime now = LocalDateTime.now(); 16 | return "[" + dtf.format(now) + "]: "; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/util/SimpleDocumentListener.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.util; 2 | 3 | import javax.swing.event.DocumentEvent; 4 | import javax.swing.event.DocumentListener; 5 | 6 | @FunctionalInterface 7 | public interface SimpleDocumentListener extends DocumentListener { 8 | void update(DocumentEvent e); 9 | 10 | @Override 11 | default void insertUpdate(DocumentEvent e) { 12 | update(e); 13 | } 14 | @Override 15 | default void removeUpdate(DocumentEvent e) { 16 | update(e); 17 | } 18 | @Override 19 | default void changedUpdate(DocumentEvent e) { 20 | update(e); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/util/Button.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.util; 2 | 3 | //TODO still not happy with this, as it handles both frontend and backend... 4 | //toString used for displaying, name for script files 5 | 6 | public enum Button { 7 | KEY_A, 8 | KEY_B, 9 | KEY_X, 10 | KEY_Y, 11 | 12 | KEY_ZR, 13 | KEY_ZL, 14 | 15 | KEY_R, 16 | KEY_L, 17 | 18 | KEY_PLUS, 19 | KEY_MINUS, 20 | 21 | KEY_DLEFT, 22 | KEY_DRIGHT, 23 | KEY_DUP, 24 | KEY_DDOWN, 25 | 26 | KEY_LSTICK, 27 | KEY_RSTICK; 28 | 29 | @Override 30 | public String toString(){ 31 | return name().replace("KEY_",""); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/util/CorruptedScriptException.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.util; 2 | 3 | import javax.swing.JOptionPane; 4 | 5 | public class CorruptedScriptException extends Exception { 6 | 7 | public CorruptedScriptException (String errorMessage, int frame, Throwable cause) { 8 | super(errorMessage + (frame == -1 ? "" : " \nat frame " + frame), cause); 9 | 10 | Logger.log(errorMessage); 11 | String fullErrorMessage = errorMessage + " \nPlease check your script manually " + (frame == -1 ? "" : " \nat frame " + frame); 12 | JOptionPane.showMessageDialog(null, fullErrorMessage, "An exception occured", JOptionPane.ERROR_MESSAGE); 13 | } 14 | public CorruptedScriptException (String errorMessage, int frame) { 15 | this(errorMessage, frame, null); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/util/CustomPianoRollCellRenderer.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.util; 2 | 3 | import javax.swing.JTable; 4 | import javax.swing.SwingConstants; 5 | import javax.swing.table.DefaultTableCellRenderer; 6 | import java.awt.Component; 7 | 8 | public class CustomPianoRollCellRenderer extends DefaultTableCellRenderer { 9 | 10 | public CustomPianoRollCellRenderer() { 11 | super(); 12 | setHorizontalAlignment(SwingConstants.CENTER); 13 | } 14 | 15 | @Override 16 | public Component getTableCellRendererComponent( 17 | JTable table, Object value, boolean isSelected, 18 | boolean hasFocus, int row, int column) { 19 | // wrap in HTML to enable HTML rendering, which disables ellipsis when text is too long 20 | return super.getTableCellRendererComponent( 21 | table, ""+value+"", isSelected, hasFocus, row, column); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/script/Format.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.script; 2 | 3 | import io.github.jadefalke2.Script; 4 | import io.github.jadefalke2.util.CorruptedScriptException; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | 9 | public enum Format { 10 | 11 | STAS, 12 | nxTAS; 13 | 14 | public static Script read(File file, Format format) throws IOException, CorruptedScriptException { 15 | if(format == nxTAS) return NXTas.read(file); 16 | else if(format == STAS) return STas.read(file); 17 | else throw new IllegalStateException("Unexpected value: " + format); 18 | } 19 | public static void write(Script script, File file, Format format) throws IOException { 20 | if(format == nxTAS) NXTas.write(script, file); 21 | else if(format == STAS) STas.write(script, file); 22 | else throw new IllegalStateException("Unexpected value: " + format); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TAS-Editor 2 | 3 | A simple-to-use GUI for Nintendo Switch TAS editing. 4 | 5 | # Set-up 6 | 7 | *Do not download the version from this repo. Use the [TAS-Editor-Launcher](https://github.com/MonsterDruide1/TAS-Editor-Launcher/releases/latest) release which has all the information on how to set it up.* 8 | 9 | # Usage 10 | The program itself should be self-explanatory. To see all possible ways of interacting with this program, please refer to the [wiki](https://github.com/MonsterDruide1/TAS-Editor/wiki), where you can read for example about how to [select frames](https://github.com/MonsterDruide1/TAS-Editor/wiki/Change-Stick-Input) or [change stick inputs](https://github.com/MonsterDruide1/TAS-Editor/wiki/Selecting-frames). 11 | 12 | # Support 13 | If you need help with anything, have questions or suggestions, you can contact me (MonsterDruide1) either on the [Super Mario Odyssey TAS Discord](https://discord.gg/YMJ9Njzvcd), or using the [Issues](https://github.com/MonsterDruide1/TAS-Editor/issues). 14 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/actions/InsertEmptyLineAction.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.actions; 2 | 3 | import io.github.jadefalke2.InputLine; 4 | import io.github.jadefalke2.Script; 5 | 6 | public class InsertEmptyLineAction implements Action { 7 | 8 | private final Script script; 9 | private final int position; 10 | private final int amount; 11 | 12 | public InsertEmptyLineAction(Script script, int position, int amount) { 13 | this.script = script; 14 | this.position = position; 15 | this.amount = amount; 16 | } 17 | 18 | @Override 19 | public void execute() { 20 | for (int i = 0; i < amount; i++){ 21 | int actualIndex = position + i; 22 | script.insertRow(actualIndex, InputLine.getEmpty()); 23 | } 24 | } 25 | 26 | @Override 27 | public void revert() { 28 | for (int i = 0; i < amount; i++){ 29 | script.removeRow(position); 30 | } 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "InsertEmptyLineAction for "+amount+" frames at frame "+position; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/util/ObservableProperty.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class ObservableProperty { 7 | 8 | @FunctionalInterface 9 | public interface PropertyChangeListener { 10 | default void onChange(T newValue, T oldValue) { onChange(newValue); } 11 | void onChange(T newValue); 12 | } 13 | 14 | private T value; 15 | private final List> listeners; 16 | 17 | public ObservableProperty(T value) { 18 | this.value = value; 19 | listeners = new ArrayList<>(); 20 | } 21 | 22 | public void set(T value) { 23 | if(value.equals(this.value)) return; 24 | 25 | this.value = value; 26 | for(PropertyChangeListener listener : listeners) { 27 | listener.onChange(value, this.value); 28 | } 29 | } 30 | public T get() { 31 | return value; 32 | } 33 | 34 | public void attachListener(PropertyChangeListener listener) { 35 | listeners.add(listener); 36 | } 37 | public void detachListener(PropertyChangeListener listener) { 38 | listeners.remove(listener); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 MonsterDruide1 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/actions/ButtonAction.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.actions; 2 | 3 | import io.github.jadefalke2.Script; 4 | import io.github.jadefalke2.util.Button; 5 | 6 | public class ButtonAction implements Action { 7 | 8 | private final Script script; 9 | private final Button button; 10 | private final boolean enabled; 11 | private final int start, end; 12 | private boolean[] oldValues; 13 | 14 | public ButtonAction(Script script, Button button, boolean enabled, int start, int end) { 15 | this.script = script; 16 | this.button = button; 17 | this.enabled = enabled; 18 | this.start = start; 19 | this.end = end; 20 | } 21 | 22 | @Override 23 | public void execute() { 24 | oldValues = new boolean[end-start+1]; 25 | for(int i=0; i newPosition).toArray(StickPosition[]::new)); 29 | } 30 | 31 | @Override 32 | public void execute() { 33 | for (int i=0; i data) { 40 | int totalLength = 0; 41 | for (byte[] bytes : data) { 42 | totalLength += bytes.length; 43 | } 44 | byte[] result = new byte[totalLength]; 45 | int currentPos = 0; 46 | for (byte[] bytes : data) { 47 | System.arraycopy(bytes, 0, result, currentPos, bytes.length); 48 | currentPos += bytes.length; 49 | } 50 | return result; 51 | } 52 | public static byte[] merge(byte[]... data) { 53 | int totalLength = 0; 54 | for (byte[] bytes : data) { 55 | totalLength += bytes.length; 56 | } 57 | byte[] result = new byte[totalLength]; 58 | int currentPos = 0; 59 | for (byte[] bytes : data) { 60 | System.arraycopy(bytes, 0, result, currentPos, bytes.length); 61 | currentPos += bytes.length; 62 | } 63 | return result; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /dependency-reduced-pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | io.github.jadefalke2 5 | TAS-Editor 6 | 1.0 7 | 8 | 9 | 10 | maven-jar-plugin 11 | 3.1.0 12 | 13 | 14 | 15 | true 16 | lib/ 17 | io.github.jadefalke2.TAS 18 | 19 | 20 | 21 | 22 | 23 | maven-dependency-plugin 24 | 3.1.2 25 | 26 | 27 | copy-dependencies 28 | package 29 | 30 | copy-dependencies 31 | 32 | 33 | ${project.build.directory}/lib 34 | false 35 | false 36 | true 37 | 38 | 39 | 40 | 41 | 42 | maven-shade-plugin 43 | 3.2.4 44 | 45 | 46 | package 47 | 48 | shade 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 8 57 | 8 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/TAS.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2; 2 | 3 | import com.formdev.flatlaf.FlatDarkLaf; 4 | import com.formdev.flatlaf.FlatLightLaf; 5 | import io.github.jadefalke2.components.MainEditorWindow; 6 | import io.github.jadefalke2.util.Logger; 7 | import io.github.jadefalke2.util.Settings; 8 | 9 | import javax.swing.JFrame; 10 | import javax.swing.SwingUtilities; 11 | import javax.swing.UIManager; 12 | import javax.swing.UnsupportedLookAndFeelException; 13 | import java.awt.Window; 14 | 15 | public class TAS { 16 | 17 | private MainEditorWindow mainEditorWindow; 18 | 19 | public static void main(String[] args) { 20 | new TAS(); 21 | } 22 | 23 | public TAS() { 24 | startProgram(); 25 | } 26 | 27 | /** 28 | * starts the program by opening a new window with the two options of either creating a new script or loading in a preexisting one. After this it will start the editor. 29 | */ 30 | 31 | public void startProgram() { 32 | Logger.log("boot up"); 33 | 34 | // initialise preferences 35 | setLookAndFeel(Settings.INSTANCE.darkTheme.get()); 36 | Settings.INSTANCE.darkTheme.attachListener(this::setLookAndFeel); 37 | 38 | mainEditorWindow = new MainEditorWindow(this); 39 | mainEditorWindow.openScript(Script.getEmptyScript(10)); 40 | mainEditorWindow.setVisible(true); 41 | 42 | UIManager.put("FileChooser.useSystemExtensionHiding", false); 43 | UIManager.put("FileChooser.readOnly", true); 44 | } 45 | 46 | // set look and feels 47 | 48 | public void setLookAndFeel(boolean darkTheme){ 49 | Logger.log("Changing theme: " + (darkTheme ? "Dark theme" : "Light theme")); 50 | 51 | try { 52 | UIManager.setLookAndFeel(darkTheme ? new FlatDarkLaf() : new FlatLightLaf()); 53 | for(Window window : JFrame.getWindows()) { 54 | SwingUtilities.updateComponentTreeUI(window); 55 | } 56 | } catch (UnsupportedLookAndFeelException e) { 57 | e.printStackTrace(); 58 | } 59 | } 60 | 61 | public void exit() { 62 | Logger.log("exiting program"); 63 | mainEditorWindow.dispose(); 64 | } 65 | 66 | public void newWindow() { 67 | Logger.log("opening new window"); 68 | new TAS(); // TODO not the right way, as for example settings won't sync properly 69 | } 70 | 71 | public boolean closeAllScripts(){ 72 | return mainEditorWindow.closeAllScripts(); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/components/ColumnRightClickMenu.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.components; 2 | 3 | import io.github.jadefalke2.InputLine; 4 | import io.github.jadefalke2.Script; 5 | import io.github.jadefalke2.actions.Action; 6 | import io.github.jadefalke2.actions.InsertEmptyLineAction; 7 | import io.github.jadefalke2.actions.LineAction; 8 | import io.github.jadefalke2.util.Button; 9 | 10 | import javax.swing.JCheckBoxMenuItem; 11 | import javax.swing.JMenuItem; 12 | import javax.swing.JPopupMenu; 13 | import javax.swing.table.TableColumnModel; 14 | import java.awt.Component; 15 | import java.awt.Point; 16 | import java.awt.datatransfer.UnsupportedFlavorException; 17 | import java.io.IOException; 18 | 19 | public class ColumnRightClickMenu extends JPopupMenu { 20 | 21 | private final JCheckBoxMenuItem[] items; 22 | 23 | public ColumnRightClickMenu(PianoRoll pianoRoll){ 24 | TableColumnModel model = pianoRoll.getColumnModel(); 25 | items = new JCheckBoxMenuItem[Button.values().length + 3]; 26 | 27 | items[0] = new JCheckBoxMenuItem("Frame"); 28 | items[1] = new JCheckBoxMenuItem("L-Stick"); 29 | items[2] = new JCheckBoxMenuItem("R-Stick"); 30 | for(int i = 3; i < items.length; i++){ 31 | items[i] = new JCheckBoxMenuItem(Button.values()[i-3].toString()); 32 | } 33 | 34 | for(int i=0; i { 37 | JCheckBoxMenuItem item = (JCheckBoxMenuItem) e.getSource(); 38 | int index = -1; 39 | for(int j=0; j> 8), (byte)s}; 12 | } else { 13 | return new byte[] {(byte)s, (byte)(s >> 8)}; 14 | } 15 | } 16 | 17 | public static byte[] fromInt(int i, ByteOrder order) { 18 | if(order.equals(ByteOrder.BIG_ENDIAN)) { 19 | return new byte[] { 20 | (byte)(i >> 24), 21 | (byte)(i >> 16), 22 | (byte)(i >> 8), 23 | (byte)i}; 24 | } else { 25 | return new byte[] { 26 | (byte)i, 27 | (byte)(i >> 8), 28 | (byte)(i >> 16), 29 | (byte)(i >> 24)}; 30 | } 31 | } 32 | 33 | public static byte[] fromLong(long l, ByteOrder order) { 34 | if(order.equals(ByteOrder.BIG_ENDIAN)) { 35 | return new byte[] { 36 | (byte) (l >> 56), 37 | (byte) (l >> 48), 38 | (byte) (l >> 40), 39 | (byte) (l >> 32), 40 | (byte) (l >> 24), 41 | (byte) (l >> 16), 42 | (byte) (l >> 8), 43 | (byte) l}; 44 | } else { 45 | return new byte[] { 46 | (byte) l, 47 | (byte) (l >> 8), 48 | (byte) (l >> 16), 49 | (byte) (l >> 24), 50 | (byte) (l >> 32), 51 | (byte) (l >> 40), 52 | (byte) (l >> 48), 53 | (byte) (l >> 56)}; 54 | } 55 | } 56 | 57 | public static byte[] fromFloat(float f, ByteOrder order) { 58 | return fromInt(Float.floatToIntBits(f), order); 59 | } 60 | 61 | public static byte[] fromDouble(double d, ByteOrder order) { 62 | return fromLong(Double.doubleToLongBits(d), order); 63 | } 64 | 65 | public static byte[] fromBooleans(boolean... bools) { 66 | BitSet bits = new BitSet(bools.length); 67 | for (int i = 0; i < bools.length; i++) { 68 | if (bools[i]) { 69 | bits.set(i); 70 | } 71 | } 72 | 73 | byte[] bytes = bits.toByteArray(); 74 | if (bytes.length * 8 >= bools.length) { 75 | return bytes; 76 | } else { 77 | return Arrays.copyOf(bytes, bools.length / 8 + (bools.length % 8 == 0 ? 0 : 1)); 78 | } 79 | } 80 | 81 | public static int toInt(byte[] data, ByteOrder order) { 82 | if(order.equals(ByteOrder.BIG_ENDIAN)) { 83 | return 84 | ((data[0] & 0xff) << 24) | 85 | ((data[1] & 0xff) << 16) | 86 | ((data[2] & 0xff) << 8) | 87 | (data[3] & 0xff); 88 | } else { 89 | return 90 | ((int)data[0] & 0xff) | 91 | (((int)data[1] & 0xff) << 8) | 92 | (((int)data[2] & 0xff) << 16) | 93 | (((int)data[3] & 0xff) << 24); 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/components/AddLinesDialog.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.components; 2 | 3 | import io.github.jadefalke2.util.SimpleDocumentListener; 4 | 5 | import javax.swing.*; 6 | import java.awt.GridBagConstraints; 7 | import java.awt.GridBagLayout; 8 | import java.awt.Insets; 9 | import java.awt.Window; 10 | import java.awt.event.ActionListener; 11 | import java.awt.event.KeyEvent; 12 | import java.awt.event.WindowEvent; 13 | import java.util.ArrayList; 14 | import java.util.regex.Matcher; 15 | import java.util.regex.Pattern; 16 | import java.util.stream.Collectors; 17 | import java.util.stream.IntStream; 18 | 19 | public class AddLinesDialog extends JDialog { 20 | private final int defaultValue; 21 | 22 | private final JTextField input; 23 | private boolean accepted = false; 24 | public AddLinesDialog(Window owner, int defaultValue) { 25 | super(owner, "Add multiple lines", ModalityType.APPLICATION_MODAL); 26 | this.defaultValue = defaultValue; 27 | 28 | getRootPane().registerKeyboardAction(e -> dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)), KeyStroke.getKeyStroke( 29 | KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW); 30 | 31 | JButton okButton = new JButton("OK"); 32 | ActionListener accept = e -> { 33 | if(!isValidInput()) return; 34 | accepted = true; 35 | dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); 36 | }; 37 | 38 | setDefaultCloseOperation(DISPOSE_ON_CLOSE); 39 | JPanel mainPanel = new JPanel(new GridBagLayout()); 40 | mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 15, 10, 15)); 41 | GridBagConstraints c = new GridBagConstraints(); 42 | c.fill = GridBagConstraints.HORIZONTAL; 43 | c.ipadx = 15; 44 | c.insets = new Insets(5, 0, 0, 0); 45 | c.weighty = 1; 46 | c.weightx = 1; 47 | c.gridx = 0; 48 | c.gridy = 0; 49 | 50 | input = new JTextField(); 51 | input.getDocument().addDocumentListener((SimpleDocumentListener) e -> okButton.setEnabled(isValidInput())); 52 | input.addActionListener(accept); 53 | input.putClientProperty("JTextField.placeholderText", ""+defaultValue); 54 | mainPanel.add(input, c); 55 | 56 | c.gridy++; 57 | okButton.addActionListener(accept); 58 | mainPanel.add(okButton, c); 59 | 60 | add(mainPanel); 61 | pack(); 62 | setLocationRelativeTo(owner); 63 | } 64 | 65 | public boolean isAccepted() { 66 | return accepted; 67 | } 68 | 69 | private Integer getValue() { 70 | String text = input.getText(); 71 | if(text.isEmpty()) 72 | return defaultValue; 73 | 74 | try { 75 | return Integer.parseInt(text); 76 | } catch(NumberFormatException e) { 77 | return null; 78 | } 79 | } 80 | 81 | private boolean isValidInput() { 82 | Integer val = getValue(); 83 | return val != null && val >= 0; 84 | } 85 | 86 | public int getNumLines() { 87 | if(!isValidInput()) return -1; 88 | 89 | return getValue(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/components/TasFileChooser.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.components; 2 | 3 | import io.github.jadefalke2.script.Format; 4 | import io.github.jadefalke2.util.Settings; 5 | import io.github.jadefalke2.util.Util; 6 | 7 | import javax.swing.JFileChooser; 8 | import javax.swing.JOptionPane; 9 | import javax.swing.filechooser.FileNameExtensionFilter; 10 | import javax.swing.filechooser.FileSystemView; 11 | import java.awt.Dimension; 12 | import java.io.File; 13 | 14 | public class TasFileChooser extends JFileChooser { 15 | 16 | private static final FileNameExtensionFilter sTAS = new FileNameExtensionFilter("SwitchTAS files (.stas)", "stas"); 17 | private static final FileNameExtensionFilter nxTAS = new FileNameExtensionFilter("nxTAS files (.txt)", "txt"); 18 | 19 | private final File defaultDir; 20 | /** 21 | * Constructor 22 | */ 23 | public TasFileChooser(File defaultDir){ 24 | super(FileSystemView.getFileSystemView()); 25 | this.defaultDir = defaultDir; 26 | setPreferredSize(new Dimension(1000,600)); 27 | } 28 | 29 | /** 30 | * returns the chosen file 31 | * @param openFile whether it should display the opening or saving dialog 32 | * @return the chosen file 33 | */ 34 | public File getFile (boolean openFile){ 35 | setDialogTitle(openFile ? "Choose existing TAS file" : "Choose place to save"); 36 | setCurrentDirectory(defaultDir); 37 | addChoosableFileFilter(sTAS); 38 | addChoosableFileFilter(nxTAS); 39 | setFileFilter(Settings.INSTANCE.defaultScriptFormat.get() == Format.STAS ? sTAS : nxTAS); 40 | int option = openFile ? showOpenDialog(null) : showSaveDialog(null); 41 | 42 | if (option == JFileChooser.APPROVE_OPTION) { 43 | return getSelectedFile(); 44 | } 45 | 46 | return null; 47 | } 48 | 49 | public Format getFormat() { 50 | if(getFileFilter() == sTAS) return Format.STAS; 51 | else if(getFileFilter() == nxTAS) return Format.nxTAS; 52 | else throw new IllegalStateException("Unexpected value: " + getFileFilter()); 53 | } 54 | 55 | @Override 56 | public void approveSelection(){ 57 | File file = getSelectedFile(); 58 | 59 | if (getFileFilter() == sTAS) { 60 | if(!file.getAbsolutePath().endsWith(".stas")) { 61 | file = new File(file.getAbsolutePath() + ".stas"); 62 | setSelectedFile(file); 63 | } 64 | } else if(getFileFilter() == nxTAS) { 65 | if(!file.getAbsolutePath().endsWith(".txt")) { 66 | file = new File(file.getAbsolutePath() + ".txt"); 67 | setSelectedFile(file); 68 | } 69 | } 70 | 71 | if(file.exists() && getDialogType() == SAVE_DIALOG){ 72 | int result = JOptionPane.showConfirmDialog(this, "This file already exists, overwrite it?", "Existing file", JOptionPane.YES_NO_CANCEL_OPTION); 73 | switch (result) { 74 | case JOptionPane.YES_OPTION: 75 | super.approveSelection(); 76 | return; 77 | case JOptionPane.NO_OPTION: 78 | case JOptionPane.CLOSED_OPTION: 79 | return; 80 | case JOptionPane.CANCEL_OPTION: 81 | cancelSelection(); 82 | return; 83 | } 84 | } 85 | super.approveSelection(); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.github.jadefalke2 8 | TAS-Editor 9 | 1.0 10 | 11 | 12 | 8 13 | 8 14 | 15 | 16 | 17 | 18 | 19 | 20 | org.apache.maven.plugins 21 | maven-jar-plugin 22 | 3.1.0 23 | 24 | 25 | 26 | true 27 | lib/ 28 | io.github.jadefalke2.TAS 29 | 30 | 31 | 32 | 33 | 34 | org.apache.maven.plugins 35 | maven-dependency-plugin 36 | 3.1.2 37 | 38 | 39 | copy-dependencies 40 | package 41 | 42 | copy-dependencies 43 | 44 | 45 | ${project.build.directory}/lib 46 | false 47 | false 48 | true 49 | 50 | 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-shade-plugin 56 | 3.2.4 57 | 58 | 59 | package 60 | 61 | shade 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | com.formdev 72 | flatlaf 73 | 2.4 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/components/LineRightClickMenu.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.components; 2 | 3 | import io.github.jadefalke2.InputLine; 4 | import io.github.jadefalke2.Script; 5 | import io.github.jadefalke2.actions.Action; 6 | import io.github.jadefalke2.actions.InsertEmptyLineAction; 7 | import io.github.jadefalke2.actions.LineAction; 8 | 9 | import javax.swing.JMenuItem; 10 | import javax.swing.JPopupMenu; 11 | import java.awt.Component; 12 | import java.awt.Point; 13 | import java.awt.datatransfer.UnsupportedFlavorException; 14 | import java.io.IOException; 15 | 16 | public class LineRightClickMenu extends JPopupMenu { 17 | 18 | private final JMenuItem deleteOption; 19 | private final JMenuItem insertOption; 20 | private final JMenuItem cloneOption; 21 | 22 | private final JMenuItem cutOption; 23 | private final JMenuItem copyOption; 24 | private final JMenuItem pasteOption; 25 | private final JMenuItem replaceOption; 26 | 27 | private final Script script; 28 | private final ScriptTab scriptTab; 29 | 30 | public LineRightClickMenu(Script script, ScriptTab scriptTab){ 31 | this.script = script; 32 | this.scriptTab = scriptTab; 33 | 34 | cutOption = add("cut"); 35 | copyOption = add("copy"); 36 | pasteOption = add("paste"); 37 | replaceOption = add("replace"); 38 | 39 | addSeparator(); 40 | 41 | deleteOption = add("delete"); 42 | insertOption = add("insert"); 43 | cloneOption = add("clone"); 44 | 45 | pack(); 46 | } 47 | 48 | /** 49 | * opens the popup menu with the line actions 50 | * @param rows all rows affected by this method 51 | * @param point the point at which the menu "spawns" 52 | */ 53 | public void openPopUpMenu(int[] rows, Point point, Component invoker){ 54 | 55 | setListener(deleteOption, rows, LineAction.Type.DELETE); 56 | setListener(insertOption, new InsertEmptyLineAction(script, rows[rows.length-1]+1, rows.length)); 57 | // insert dummy InputLines - unused, but the size is required for undo 58 | setListener(cloneOption, new LineAction(script, rows, new InputLine[rows.length], LineAction.Type.CLONE)); 59 | 60 | setListener(cutOption, () -> scriptTab.getPianoRoll().cut()); 61 | setListener(copyOption, () -> scriptTab.getPianoRoll().copy()); 62 | setListener(pasteOption, () -> { 63 | try { 64 | scriptTab.getPianoRoll().paste(); 65 | } catch (IOException | UnsupportedFlavorException e) { 66 | e.printStackTrace(); //TODO error handling 67 | } 68 | }); 69 | setListener(replaceOption, () -> { 70 | try { 71 | scriptTab.getPianoRoll().replace(); 72 | } catch (IOException | UnsupportedFlavorException e) { 73 | e.printStackTrace(); //TODO error handling 74 | } 75 | }); 76 | show(invoker,(int)point.getX(),(int)point.getY()); 77 | } 78 | 79 | public void setListener(JMenuItem item, Runnable action){ 80 | while (item.getActionListeners().length > 0){ 81 | item.removeActionListener(item.getActionListeners()[0]); 82 | } 83 | 84 | item.addActionListener(e -> action.run()); 85 | } 86 | 87 | public void setListener(JMenuItem item, int[] rows, LineAction.Type type){ 88 | setListener(item, new LineAction(script, rows, type)); 89 | } 90 | public void setListener(JMenuItem item, Action action){ 91 | setListener(item, () -> scriptTab.executeAction(action)); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/util/Settings.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.util; 2 | 3 | import io.github.jadefalke2.TAS; 4 | import io.github.jadefalke2.script.Format; 5 | import io.github.jadefalke2.stickRelatedClasses.SmoothTransitionDialog; 6 | 7 | import java.io.File; 8 | import java.util.prefs.BackingStoreException; 9 | import java.util.prefs.Preferences; 10 | 11 | public class Settings { 12 | 13 | public static final Settings INSTANCE = new Settings(Preferences.userRoot().node(TAS.class.getName())); 14 | static { 15 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 16 | try { 17 | INSTANCE.storeSettings(); 18 | } catch (BackingStoreException e) { 19 | e.printStackTrace(); 20 | } 21 | })); 22 | } 23 | 24 | public final ObservableProperty directory; 25 | public final ObservableProperty darkTheme; 26 | public final ObservableProperty lastStickPositionCount; 27 | public final ObservableProperty joystickPanelPosition; 28 | public final ObservableProperty smoothTransitionType; 29 | public final ObservableProperty redoKeybind; 30 | public final ObservableProperty defaultScriptFormat; 31 | public final ObservableProperty authorName; 32 | 33 | 34 | private final Preferences backingPrefs; 35 | 36 | private Settings(Preferences prefs) { 37 | this.backingPrefs = prefs; 38 | 39 | File yuzuDir = new File(System.getProperty("user.home")+"/AppData/Roaming/yuzu/tas"); 40 | 41 | directory = new ObservableProperty<>(new File(prefs.get("directory", System.getProperty("user.home")+(yuzuDir.exists() ? "/AppData/Roaming/yuzu/tas" : "")))); 42 | darkTheme = new ObservableProperty<>(prefs.get("darkTheme", "false").equals("true")); 43 | lastStickPositionCount = new ObservableProperty<>(Integer.parseInt(prefs.get("lastStickPositionCount", "3"))); 44 | joystickPanelPosition = new ObservableProperty<>(JoystickPanelPosition.valueOf(prefs.get("joystickPanelPosition", "RIGHT"))); 45 | smoothTransitionType = new ObservableProperty<>(SmoothTransitionDialog.SmoothTransitionType.valueOf(prefs.get("smoothTransitionType", "ANGULAR_CLOSEST"))); 46 | redoKeybind = new ObservableProperty<>(RedoKeybind.valueOf(prefs.get("redoKeybind", "CTRL_SHIFT_Z"))); 47 | defaultScriptFormat = new ObservableProperty<>(Format.valueOf(prefs.get("defaultScriptFormat", "nxTAS"))); 48 | authorName = new ObservableProperty<>(prefs.get("authorName", "")); 49 | } 50 | 51 | public void storeSettings() throws BackingStoreException { 52 | backingPrefs.clear(); 53 | 54 | backingPrefs.put("directory", directory.get() + ""); 55 | backingPrefs.put("darkTheme", darkTheme.get() + ""); 56 | backingPrefs.put("lastStickPositionCount", lastStickPositionCount.get() + ""); 57 | backingPrefs.put("joystickPanelPosition", joystickPanelPosition.get() + ""); 58 | backingPrefs.put("smoothTransitionType", smoothTransitionType.get() + ""); 59 | backingPrefs.put("redoKeybind", redoKeybind.get() + ""); 60 | backingPrefs.put("defaultScriptFormat", defaultScriptFormat.get() + ""); 61 | backingPrefs.put("authorName", authorName.get()); 62 | 63 | backingPrefs.flush(); 64 | } 65 | 66 | public enum JoystickPanelPosition { 67 | LEFT, RIGHT, HIDDEN 68 | } 69 | 70 | public enum RedoKeybind { 71 | CTRL_SHIFT_Z, CTRL_Y 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/io/github/jadefalke2/script/NXTas.java: -------------------------------------------------------------------------------- 1 | package io.github.jadefalke2.script; 2 | 3 | import io.github.jadefalke2.InputLine; 4 | import io.github.jadefalke2.Script; 5 | import io.github.jadefalke2.stickRelatedClasses.StickPosition; 6 | import io.github.jadefalke2.util.Button; 7 | import io.github.jadefalke2.util.CorruptedScriptException; 8 | import io.github.jadefalke2.util.Logger; 9 | import io.github.jadefalke2.util.Util; 10 | 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.util.ArrayList; 14 | import java.util.EnumSet; 15 | import java.util.List; 16 | import java.util.stream.Collectors; 17 | import java.util.stream.IntStream; 18 | 19 | public class NXTas { 20 | 21 | public static void write(Script script, File file) throws IOException { 22 | Logger.log("saving script to " + file.getAbsolutePath()); 23 | Util.writeFile(write(script), file); 24 | } 25 | 26 | public static String write(Script script) { 27 | InputLine[] inputLines = script.getLines(); 28 | return IntStream.range(0, inputLines.length).filter(i -> !inputLines[i].isEmpty()).mapToObj(i -> inputLines[i].getFull(i) + "\n").collect( 29 | Collectors.joining()); 30 | } 31 | 32 | public static Script read(File file) throws CorruptedScriptException, IOException { 33 | Script s = read(Util.fileToString(file)); 34 | s.setFile(file, Format.nxTAS); 35 | return s; 36 | } 37 | 38 | public static Script read(String script) throws CorruptedScriptException { 39 | List inputLines = new ArrayList<>(); 40 | String[] lines = script.split("\n"); 41 | 42 | int currentFrame = 0; 43 | 44 | for (String line : lines) { 45 | InputLine currentInputLine = readLine(line); 46 | int frame = Integer.parseInt(line.split(" ")[0]); 47 | 48 | if (frame < currentFrame){ 49 | throw new CorruptedScriptException("Line numbers misordered", currentFrame); 50 | } 51 | 52 | while(currentFrame < frame){ 53 | inputLines.add(InputLine.getEmpty()); 54 | currentFrame++; 55 | } 56 | 57 | inputLines.add(currentInputLine); 58 | currentFrame++; 59 | } 60 | return new Script(inputLines.toArray(new InputLine[0]), 0); 61 | } 62 | 63 | public static InputLine readLine(String full) throws CorruptedScriptException { 64 | if (full.isEmpty()){ 65 | throw new CorruptedScriptException("Empty InputLine", -1); 66 | } 67 | 68 | int frame = 0; 69 | try { 70 | String[] components = full.split(" "); 71 | 72 | EnumSet