diff --git a/.gitattributes b/.gitattributes index c0750367b..276ed257c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,8 @@ *.xml text *.txt text *.md text +*.2da text +*.ids text *.flex text .project text .classpath text diff --git a/.github/workflows/ant.yml b/.github/workflows/ant.yml index 5be229a07..2a3cab5cc 100644 --- a/.github/workflows/ant.yml +++ b/.github/workflows/ant.yml @@ -3,14 +3,12 @@ name: Java CI with Apache Ant on: push: branches: [ devel ] - pull_request: - branches: [ devel ] jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up JDK 1.8 uses: actions/setup-java@v3 with: @@ -19,12 +17,12 @@ jobs: - name: Build with Ant run: | hash=$(echo "${{ github.sha }}" | sed -e 's/\(.\{7\}\).*/\1/') - sed -i "s/\(VERSION *= *\"v\?[0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\)[^\"]*\"/\1-$(date +%Y%m%d) (${hash})\"/" src/org/infinity/NearInfinity.java + sed -i "s/\(app_version\s*=\s*v\?[0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).*/\1-$(date +%Y%m%d) (${hash})/" src/nearinfinity.properties sed -i 's/debug="false"/debug="true"/' build.xml ant -noinput -buildfile build.xml - name: Upload artifact if: ${{ github.repository == 'Argent77/NearInfinity' }} - uses: pyTooling/Actions/releaser@r0 + uses: pyTooling/Actions/releaser@r2 with: tag: nightly rm: true diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3f0d71672..9e3206d64 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,16 +1,17 @@ -# This workflow will build a Java project with Apache Ant and upload the created artifacts -# on pushes to the master branch. +# This workflow will build a Java project with Apache Ant and: +# - on release: upload the created files to the current release or tag +# - on workflow_dispatch: upload the created files as artifacts to the workflow name: Java CD with Apache Ant on: - push: - branches: [ "master" ] + release: + types: [published] workflow_dispatch: branches: [ "master", "devel" ] permissions: - contents: read + contents: write jobs: # Build and upload NearInfinity.jar @@ -35,12 +36,23 @@ jobs: ant -noinput -buildfile build.xml echo "NI_VERSION=$(java -jar "NearInfinity.jar" -version 2>/dev/null | grep -Eo '[0-9]{8}')" >> "$GITHUB_OUTPUT" + # Required internally to build the installer packages - name: Upload JAR artifact uses: actions/upload-artifact@v4 with: name: NearInfinity-${{ steps.ni-build.outputs.NI_VERSION }} path: NearInfinity.jar + - name: Upload JAR asset to release + if: github.event_name == 'release' + uses: svenstaro/upload-release-action@2.9.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: NearInfinity.jar + tag: ${{ github.ref || github.event.release.tag_name }} + asset_name: NearInfinity-${{ steps.ni-build.outputs.NI_VERSION }}.jar + overwrite: true + # Build and upload installer versions for Windows, macOS-x86_64 and macOS-arm64 deploy-installer: if: ${{ github.repository == 'Argent77/NearInfinity' }} @@ -123,31 +135,86 @@ jobs: if: startsWith(matrix.os, 'macos-') run: ls -l - # Uploading + # Uploading artifacts (workflow_dispatch) - name: Upload portable artifact (windows) - if: startsWith(matrix.os, 'windows-') + if: startsWith(matrix.os, 'windows-') && github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 with: name: portable-windows path: NearInfinity-*.zip - name: Upload exe artifact (windows) - if: startsWith(matrix.os, 'windows-') + if: startsWith(matrix.os, 'windows-') && github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 with: name: installer-windows path: NearInfinity-*.exe - name: Upload pkg artifact (macos-x86_64) - if: (matrix.os == 'macos-13') + if: (matrix.os == 'macos-13') && github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 with: name: installer-macos-x86_64 path: NearInfinity-*.pkg - name: Upload pkg artifact (macos-arm64) - if: (matrix.os == 'macos-14') + if: (matrix.os == 'macos-14') && github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 with: name: installer-macos-arm64 path: NearInfinity-*.pkg + + # Uploading assets (release) + - name: Upload portable asset to release (windows) + if: startsWith(matrix.os, 'windows-') && github.event_name == 'release' + uses: svenstaro/upload-release-action@2.9.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file_glob: true + file: NearInfinity-*.zip + tag: ${{ github.ref || github.event.release.tag_name }} + overwrite: true + + - name: Upload exe asset to release (windows) + if: startsWith(matrix.os, 'windows-') && github.event_name == 'release' + uses: svenstaro/upload-release-action@2.9.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file_glob: true + file: NearInfinity-*.exe + tag: ${{ github.ref || github.event.release.tag_name }} + overwrite: true + + - name: Upload pkg asset to release (macos-x86_64) + if: (matrix.os == 'macos-13') && github.event_name == 'release' + uses: svenstaro/upload-release-action@2.9.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file_glob: true + file: NearInfinity-*.pkg + tag: ${{ github.ref || github.event.release.tag_name }} + overwrite: true + + - name: Upload pkg asset to release (macos-arm64) + if: (matrix.os == 'macos-14') && github.event_name == 'release' + uses: svenstaro/upload-release-action@2.9.0 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file_glob: true + file: NearInfinity-*.pkg + tag: ${{ github.ref || github.event.release.tag_name }} + overwrite: true + + # Remove JAR artifact (release) + cleanup: + if: ${{ github.repository == 'Argent77/NearInfinity' && github.event_name == 'release' }} + needs: [deploy-jar, deploy-installer] + runs-on: ubuntu-latest + name: Clean up artifacts + steps: + - uses: actions/checkout@v4 + + - name: Remove JAR artifact + uses: geekyeggo/delete-artifact@v5 + with: + name: NearInfinity-* diff --git a/src/nearinfinity.properties b/src/nearinfinity.properties new file mode 100644 index 000000000..93a4a83f4 --- /dev/null +++ b/src/nearinfinity.properties @@ -0,0 +1,2 @@ +app_version = v2.4-20241204 +java_version_min = 8 diff --git a/src/org/infinity/AppOption.java b/src/org/infinity/AppOption.java index d50838b46..72600c55b 100644 --- a/src/org/infinity/AppOption.java +++ b/src/org/infinity/AppOption.java @@ -80,7 +80,7 @@ public class AppOption { lang = Arrays .stream(Profile.Game.values()) .filter(Profile::isEnhancedEdition) - .map(g -> g.toString() + "=" + OptionsMenuItem.getDefaultGameLanguage()) + .map(g -> g + "=" + OptionsMenuItem.getDefaultGameLanguage()) .collect(Collectors.joining(";")); } return lang; @@ -140,6 +140,9 @@ public class AppOption { /** Menu Options: RememberChildFrameRect (Boolean, Default: false) */ public static final AppOption REMEMBER_CHILD_FRAME_RECT = new AppOption(OptionsMenuItem.OPTION_REMEMBER_CHILDFRAME_RECT, "Remember Last Child Frame Size and Position", false); + /** Menu Options: ShowCreaturesOnPanel (Boolean, Default: false) */ + public static final AppOption SHOW_CREATURES_ON_PANEL = new AppOption(OptionsMenuItem.OPTION_SHOW_CREATURES_ON_PANEL, + "Show creatures on main panel", false); /** * Menu Options: OptionFixedInternal (Integer, Default: 0). * Note: Used internally to fix incorrect default values after the public release. @@ -428,7 +431,7 @@ public static List getOptionsByPrefs(String prefsNode) { /** Discards any changes made to the options and resets them to their initial values. */ public static void revertAll() { - AppOption.getInstances().stream().forEach(AppOption::revert); + AppOption.getInstances().forEach(AppOption::revert); } /** Writes all options of this enum back to the persistent Preferences storage. */ @@ -446,7 +449,7 @@ public static void storePreferences(Collection collection) { if (collection == null) { collection = AppOption.getInstances(); } - collection.stream().forEach(AppOption::storeValue); + collection.forEach(AppOption::storeValue); } // /** Used internally to allow only supported types for the generic argument. */ diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index 42d67b029..8ddc3b6b1 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -27,10 +27,12 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; @@ -43,13 +45,16 @@ import java.util.List; import java.util.Locale; import java.util.Objects; +import java.util.Properties; import java.util.concurrent.ExecutionException; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; +import javax.swing.AbstractAction; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JButton; +import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; @@ -60,6 +65,7 @@ import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.JToolBar; +import javax.swing.KeyStroke; import javax.swing.LookAndFeel; import javax.swing.ProgressMonitor; import javax.swing.SwingConstants; @@ -88,7 +94,9 @@ import org.infinity.gui.PopupWindowListener; import org.infinity.gui.QuickSearch; import org.infinity.gui.ResourceTree; +import org.infinity.gui.SpriteAnimationPanel; import org.infinity.gui.StatusBar; +import org.infinity.gui.StringEditor; import org.infinity.gui.StructViewer; import org.infinity.gui.ViewFrame; import org.infinity.gui.ViewerUtil; @@ -136,12 +144,10 @@ import org.infinity.util.tuples.Couple; public final class NearInfinity extends JFrame implements ActionListener, ViewableContainer { - // the current Near Infinity version - private static final String VERSION = "v2.4-20240914"; - - // the minimum supported Java version - private static final int JAVA_VERSION_MIN = 8; + private static final String PROPERTIES_FILENAME = "nearinfinity.properties"; + private static final String PROP_APP_VERSION = "app_version"; + private static final String PROP_JAVA_VERSION_MIN = "java_version_min"; public static final String KEYFILENAME = "chitin.key"; public static final String WINDOW_SIZEX = "WindowSizeX"; @@ -162,6 +168,9 @@ public final class NearInfinity extends JFrame implements ActionListener, Viewab private static final String STATUSBAR_TEXT_FMT = "Welcome to Near Infinity! - %s @ %s - %d files available"; + // Input map key for triggering the quick search feature + private static final String ACTIONMAP_KEY_QUICK_SEARCH = "SHORTCUT_OPEN_QUICK_SEARCH"; + private static final List> CUSTOM_LOOK_AND_FEELS = new ArrayList<>(); static { @@ -224,7 +233,7 @@ public final class NearInfinity extends JFrame implements ActionListener, Viewab private static NearInfinity browser; - private final JPanel containerpanel; + private final SpriteAnimationPanel containerpanel; private final JSplitPane spSplitter; private final ResourceTree tree; private final StatusBar statusBar; @@ -279,7 +288,35 @@ public static NearInfinity getInstance() { /** Returns the current NearInfinity version. */ public static String getVersion() { - return VERSION; + return getAppProperty(PROP_APP_VERSION, "v1.0-19700101"); + } + + /** Returns the minimum supported Java version. */ + public static int getMinJavaVersion() { + try { + return Integer.parseInt(getAppProperty(PROP_JAVA_VERSION_MIN, "0")); + } catch (NumberFormatException e) { + Logger.error(e); + } + return 0; + } + + /** + * Returns the value of the specified property from the app-specific properties resource. + * + * @param key The property key. + * @param defValue Default value that is returned if the property could not be read. + * @return The value in the properties resource with the specified key value. + */ + private static String getAppProperty(String key, String defValue) { + try (final InputStream is = ClassLoader.getSystemResourceAsStream(PROPERTIES_FILENAME)) { + final Properties prop = new Properties(); + prop.load(is); + return prop.getProperty(key, defValue); + } catch (IOException e) { + Logger.error(e); + } + return defValue; } public static void printHelp(String jarFile) { @@ -378,8 +415,9 @@ public static void main(String[] args) { } // Checking Java version - if (Platform.JAVA_VERSION < JAVA_VERSION_MIN) { - JOptionPane.showMessageDialog(null, String.format("Java %d or later is required to run Near Infinity!", JAVA_VERSION_MIN), + final int minJavaVersion = getMinJavaVersion(); + if (Platform.JAVA_VERSION < minJavaVersion) { + JOptionPane.showMessageDialog(null, String.format("Java %d or later is required to run Near Infinity!", minJavaVersion), "Error", JOptionPane.ERROR_MESSAGE); System.exit(10); } @@ -529,12 +567,14 @@ public void windowClosing(WindowEvent event) { b.setMargin(new Insets(0, 0, 0, 0)); toolBar.add(b); toolBar.addSeparator(new Dimension(8, 24)); + + final String shortcutModifier = + Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() == KeyEvent.CTRL_MASK ? "Ctrl" : "⌘"; bpwQuickSearch = new ButtonPopupWindow(Icons.ICON_MAGNIFY_16.getIcon()); - bpwQuickSearch.setToolTipText("Find resource"); + bpwQuickSearch.setToolTipText("Find resource (Shortcut: " + shortcutModifier + "-/)"); bpwQuickSearch.setMargin(new Insets(4, 4, 4, 4)); toolBar.add(bpwQuickSearch); bpwQuickSearch.addPopupWindowListener(new PopupWindowListener() { - @Override public void popupWindowWillBecomeVisible(PopupWindowEvent event) { // XXX: Working around a visual glitch in QuickSearch's JComboBox popup list @@ -558,6 +598,17 @@ public void popupWindowWillBecomeInvisible(PopupWindowEvent event) { } }); + // registering window-global shortcut for the quick search option + final int ctrl = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + .put(KeyStroke.getKeyStroke(KeyEvent.VK_DIVIDE, ctrl), ACTIONMAP_KEY_QUICK_SEARCH); + getRootPane().getActionMap().put(ACTIONMAP_KEY_QUICK_SEARCH, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + bpwQuickSearch.showPopupWindow(); + } + }); + toolBar.add(Box.createHorizontalGlue()); btnLaunchGame = new JButton(Icons.ICON_LAUNCH_24.getIcon()); btnLaunchGame.setFocusable(false); @@ -573,7 +624,7 @@ public void popupWindowWillBecomeInvisible(PopupWindowEvent event) { leftPanel.add(tree, BorderLayout.CENTER); leftPanel.add(toolBar, BorderLayout.NORTH); - containerpanel = new JPanel(new BorderLayout()); + containerpanel = new SpriteAnimationPanel(new BorderLayout()); containerpanel.add(createJavaInfoPanel(), BorderLayout.CENTER); spSplitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, containerpanel); spSplitter.setBorder(BorderFactory.createEmptyBorder()); @@ -592,6 +643,8 @@ public void popupWindowWillBecomeInvisible(PopupWindowEvent event) { setVisible(true); setExtendedState(AppOption.APP_WINDOW_STATE.getIntValue()); + setSpriteAnimationPanelEnabled(true); + // XXX: Workaround to trigger standard window closing callback on OSX when using command-Q if (Platform.IS_MACOS) { enableOSXQuitStrategy(); @@ -662,11 +715,14 @@ public void actionPerformed(ActionEvent event) { ChildFrame.closeWindows(); ResourceTreeModel treemodel = ResourceFactory.getResourceTreeModel(); updateWindowTitle(); + updateLauncher(); final String msg = String.format(STATUSBAR_TEXT_FMT, Profile.getProperty(Profile.Key.GET_GAME_TITLE), Profile.getGameRoot(), Objects.requireNonNull(treemodel).size()); statusBar.setMessage(msg); BrowserMenuBar.getInstance().gameLoaded(oldGame, oldFile); tree.setModel(treemodel); + ChildFrame.fireGameReset(false); + resetSpriteAnimationPanel(); containerpanel.removeAll(); containerpanel.add(createJavaInfoPanel(), BorderLayout.CENTER); containerpanel.revalidate(); @@ -813,6 +869,7 @@ public void setViewable(Viewable newViewable) { statusBar.setMessage(resource.getResourceEntry().getActualPath().toString()); containerpanel.removeAll(); containerpanel.add(viewable.makeViewer(this), BorderLayout.CENTER); + setSpriteAnimationPanelEnabled(false); containerpanel.revalidate(); containerpanel.repaint(); toFront(); @@ -859,6 +916,8 @@ public void openGame(Path keyFile) { statusBar.setMessage(msg); BrowserMenuBar.getInstance().gameLoaded(oldGame, Objects.requireNonNull(oldKeyFile).toString()); tree.setModel(treemodel); + ChildFrame.fireGameReset(false); + resetSpriteAnimationPanel(); containerpanel.removeAll(); containerpanel.add(createJavaInfoPanel(), BorderLayout.CENTER); containerpanel.revalidate(); @@ -880,6 +939,7 @@ public boolean removeViewable() { tree.select(null); containerpanel.removeAll(); containerpanel.add(createJavaInfoPanel(), BorderLayout.CENTER); + setSpriteAnimationPanelEnabled(true); containerpanel.revalidate(); containerpanel.repaint(); return true; @@ -895,7 +955,8 @@ public void showResourceEntry(ResourceEntry resourceEntry, Operation doneOperati public void quit() { if (removeViewable()) { - ChildFrame.closeWindows(); + ChildFrame.closeWindows(true); + try { containerpanel.close(); } catch (Exception e) {} storePreferences(); clearCache(false); System.exit(0); @@ -924,6 +985,8 @@ public void refreshGame() { containerpanel.repaint(); } cacheResourceIcons(true); + ChildFrame.fireGameReset(true); + resetSpriteAnimationPanel(); } finally { blocker.setBlocked(false); } @@ -1183,6 +1246,7 @@ private static void clearCache(boolean refreshOnly) { CreMapCache.clearCache(); BaseOpcode.reset(); // SearchFrame.clearCache(); + StringEditor.saveModified(null, true, true, NearInfinity.getInstance()); StringTable.resetAll(); ProRef.clearCache(); Signatures.clearCache(); @@ -1324,6 +1388,32 @@ private void checkFileAccess(Path path) throws IOException { } } + /** Controls enabled state of the creature animation panel. */ + public void setSpriteAnimationPanelEnabled(boolean enable) { + if (BrowserMenuBar.getInstance().getOptions().showCreaturesOnPanel()) { + if (enable && !containerpanel.isRunning()) { + Logger.trace("Starting creature animation panel"); + containerpanel.start(); + } else if (!enable && containerpanel.isRunning()) { + Logger.trace("Stopping creature animation panel"); + containerpanel.stop(); + } + } else { + if (containerpanel.isRunning()) { + containerpanel.stop(); + } + } + } + + /** Resets the animation panel in a controlled manner. */ + public void resetSpriteAnimationPanel() { + if (BrowserMenuBar.getInstance().getOptions().showCreaturesOnPanel()) { + Logger.trace("Resetting creature animation panel"); + containerpanel.reset(); + } + setSpriteAnimationPanelEnabled(true); + } + /** * Shows Java Runtime information when there are no components attached to the main view. */ diff --git a/src/org/infinity/check/ResourceUseChecker.java b/src/org/infinity/check/ResourceUseChecker.java index 9edcd8e00..c97c9d7de 100644 --- a/src/org/infinity/check/ResourceUseChecker.java +++ b/src/org/infinity/check/ResourceUseChecker.java @@ -345,7 +345,7 @@ private void checkSound(StringRef ref) { *

* This method can be called from several threads * - * @param ref Reference to entry in string table that contains sound file name + * @param strref Reference to entry in string table that contains sound file name */ private void checkSound(int strref) { if (strref >= 0) { diff --git a/src/org/infinity/check/StringDuplicatesChecker.java b/src/org/infinity/check/StringDuplicatesChecker.java index 5d5c0686b..66cf58a94 100644 --- a/src/org/infinity/check/StringDuplicatesChecker.java +++ b/src/org/infinity/check/StringDuplicatesChecker.java @@ -12,10 +12,7 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import javax.swing.BorderFactory; import javax.swing.JButton; @@ -247,7 +244,7 @@ public List> toList() { for (final Map.Entry> entry : strings.entrySet()) { retVal.add(entry.getValue()); } - retVal.sort((a, b) -> a.get(0) - b.get(0)); + retVal.sort(Comparator.comparingInt(a -> a.get(0))); return retVal; } diff --git a/src/org/infinity/check/StringValidationChecker.java b/src/org/infinity/check/StringValidationChecker.java index 1c51c4f31..af78dadc9 100644 --- a/src/org/infinity/check/StringValidationChecker.java +++ b/src/org/infinity/check/StringValidationChecker.java @@ -12,20 +12,30 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.channels.FileChannel; +import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; +import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderMalfunctionError; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; import java.nio.charset.StandardCharsets; +import java.nio.charset.UnsupportedCharsetException; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.TreeSet; +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; import javax.swing.BorderFactory; @@ -34,7 +44,9 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.ProgressMonitor; import javax.swing.SwingConstants; +import javax.swing.SwingWorker; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; @@ -52,18 +64,24 @@ import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.search.AbstractSearcher; +import org.infinity.util.CharsetDetector; +import org.infinity.util.DynamicByteArray; import org.infinity.util.Logger; import org.infinity.util.Misc; import org.infinity.util.StringTable; import org.infinity.util.io.StreamUtils; +import org.infinity.util.tuples.Couple; public class StringValidationChecker extends AbstractSearcher - implements Runnable, ActionListener, ListSelectionListener { + implements Runnable, ActionListener, ListSelectionListener, PropertyChangeListener { private ChildFrame resultFrame; private SortableTable table; private JButton bSave; private JButton bOpenLookup; private JButton bOpenStringTable; + private JButton bRepair; + private SwingWorker, Void> repairWorker; + private ProgressMonitor repairProgress; public StringValidationChecker(Component parent) { super(CHECK_MULTI_TYPE_FORMAT, parent); @@ -127,6 +145,11 @@ private ChildFrame getResultFrame() { bOpenStringTable.setMnemonic('t'); bSave = new JButton("Save...", Icons.ICON_SAVE_16.getIcon()); bSave.setMnemonic('s'); + + bRepair = new JButton("Repair", Icons.ICON_CHECK_16.getIcon()); + bRepair.setToolTipText("Replace or remove malformed characters."); + bRepair.setMnemonic('r'); + JScrollPane scrollTable = new JScrollPane(table); scrollTable.getViewport().setBackground(table.getBackground()); resultFrame.getRootPane().setDefaultButton(bOpenLookup); @@ -145,6 +168,7 @@ private ChildFrame getResultFrame() { panel.add(bOpenLookup); panel.add(bOpenStringTable); panel.add(bSave); + panel.add(bRepair); JPanel pane = (JPanel) resultFrame.getContentPane(); pane.setLayout(new BorderLayout(0, 3)); @@ -178,6 +202,7 @@ public void mouseReleased(MouseEvent event) { bOpenLookup.addActionListener(this); bOpenStringTable.addActionListener(this); bSave.addActionListener(this); + bRepair.addActionListener(this); pane.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); resultFrame.setSize(1024, 576); Center.center(resultFrame, NearInfinity.getInstance().getBounds()); @@ -228,7 +253,7 @@ protected Runnable newWorker(ResourceEntry entry) { CharsetDecoder decoder = StringTable.getCharset().newDecoder(); decoder.onUnmappableCharacter(CodingErrorAction.REPORT); - decoder.onUnmappableCharacter(CodingErrorAction.REPORT); + decoder.onMalformedInput(CodingErrorAction.REPORT); int maxCharLength = (int) Math.ceil(decoder.maxCharsPerByte() * maxLength); final CharBuffer outBuf = CharBuffer.allocate(maxCharLength); @@ -300,13 +325,16 @@ private void validateInputUnicode(CharsetDecoder decoder, ByteBuffer inBuf, Char synchronized (this) { final String text = StringTable.getStringRef(isFemale ? StringTable.Type.FEMALE : StringTable.Type.MALE, strref); - final String infoBytes = (cr.length() == 1) ? " byte" : " bytes"; - final String info = "offset " + outBuf.position() + ", length: " + cr.length() + infoBytes; + final int offset = inBuf.position(); + final int length = cr.length(); + final byte[] buffer = Arrays.copyOf(inBuf.array(), inBuf.limit()); if (cr.isMalformed()) { - table.addTableItem(new StringErrorTableItem(entry, strref, text, "Malformed input data found at " + info)); + table.addTableItem(new StringErrorTableItem(entry, strref, text, + new StringError(isFemale, strref, buffer, offset, length, "malformed input data"))); } if (cr.isUnmappable()) { - table.addTableItem(new StringErrorTableItem(entry, strref, text, "Unmappable character found at " + info)); + table.addTableItem(new StringErrorTableItem(entry, strref, text, + new StringError(isFemale, strref, buffer, offset, length, "unmappable character"))); } } } @@ -330,36 +358,216 @@ private void validateInputAnsi(CharsetDecoder decoder, ByteBuffer inBuf, CharBuf outBuf.flip(); final String textAnsi = outBuf.toString(); + // performing detection of utf-8 characters in ansi byte code inBuf.flip(); - outBuf.limit(outBuf.capacity()); - outBuf.position(0); - final CharsetDecoder decoderUtf8 = StandardCharsets.UTF_8.newDecoder(); - final CoderResult cr = decoderUtf8.decode(inBuf, outBuf, true); - if (!cr.isError()) { - outBuf.flip(); - final String textUtf8 = outBuf.toString(); - - boolean isError = false; - for (int ofs = 0, len1 = textAnsi.length(), len2 = textUtf8.length(); ofs < len1 && ofs < len2 - && !isError; ofs++) { - final char ch1 = textAnsi.charAt(ofs); - final char ch2 = textUtf8.charAt(ofs); - if (ch1 != ch2) { - synchronized(this) { - final String msg = "Possible encoding error found at offset " + ofs; - table.addTableItem(new StringErrorTableItem(entry, strref, textAnsi, msg)); - isError = true; + final byte[] buffer = Arrays.copyOf(inBuf.array(), inBuf.limit()); + int offset = 0; + while (offset < buffer.length) { + final StringError se = validateUtf8Input(buffer, offset, buffer.length - offset, isFemale, strref); + if (se == null) { + break; + } + table.addTableItem(new StringErrorTableItem(entry, strref, textAnsi, se)); + offset = se.getOffset() + se.getLength(); + } + } + + /** + * Analyzes the specified buffer for multi-byte UTF-8 characters and returns them as {@link StringError} objects. + * + * @param buffer Byte buffer with raw text data. + * @param offset Start offset for the analyzation process. + * @param len Max. number of bytes to analyze. + * @return An initialized {@link StringError} if a multi-byte UTF-8 character was found, {@code null} otherwise. + */ + private StringError validateUtf8Input(byte[] buffer, int offset, int len, boolean isFemale, int strref) { + if (buffer == null || offset < 0 || len <= 0 || offset + len > buffer.length) { + return null; + } + + // Limiting max. number of bytes per utf-8 code to the specified ANSI code page + // to reduce detection of false positives. + final int maxBytes = CharsetDetector.getMaxAnsiUtf8Length(StringTable.getCharset()); + + final int length = offset + len; + for (int i = offset; i < length; i++) { + final int b1 = buffer[i] & 0xff; + if (b1 >= 0x80) { + switch (b1) { + case 0xc0: + case 0xc1: + case 0xf5: + case 0xf6: + case 0xf7: + case 0xf8: + case 0xf9: + case 0xfa: + case 0xfb: + case 0xfc: + case 0xfd: + case 0xfe: + case 0xff: + // not a legal UTF-8 code + continue; + } + + if (maxBytes > 1 && (b1 & 0xe0) == 0xc0 && i + 1 < length) { + // two bytes + final int b2 = buffer[i + 1] & 0xff; + if ((b2 & 0xc0) == 0x80) { + return new StringError(isFemale, strref, buffer, i, 2, "encoding error"); + } + } else if (maxBytes > 2 && (b1 & 0xf0) == 0xe0 && i + 2 < length) { + // three bytes + final int b2 = buffer[i + 1] & 0xff; + final int b3 = buffer[i + 2] & 0xff; + if ((b2 & 0xc0) == 0x80 && (b3 & 0xc0) == 0x80) { + return new StringError(isFemale, strref, buffer, i, 3, "encoding error"); + } + } else if (maxBytes > 3 && (b1 & 0xf8) == 0xf0 && i + 3 < length) { + // four bytes + final int b2 = buffer[i + 1] & 0xff; + final int b3 = buffer[i + 2] & 0xff; + final int b4 = buffer[i + 3] & 0xff; + if ((b2 & 0xc0) == 0x80 && (b3 & 0xc0) == 0x80 && (b4 & 0xc0) == 0x80) { + final int codePoint = ((b1 & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3f) << 6) | (b4 & 0x3f); + if (codePoint < 0x110000) { + return new StringError(isFemale, strref, buffer, i, 4, "encoding error"); + } } } + // else assume legal ANSI character } + } - if (!isError && textAnsi.length() > textUtf8.length()) { - synchronized (this) { - final String msg = "Possible encoding error found at offset " + textUtf8.length(); - table.addTableItem(new StringErrorTableItem(entry, strref, textAnsi, msg)); + return null; + } + + /** Executes the repair operation as a background task with UI feedback. */ + private void repairEntriesBackground() { + final String note = (table.getModel().getRowCount() >= 1000) ? "This may take a while..." : null; + repairProgress = new ProgressMonitor(getResultFrame(), "Repairing game strings", note, 0, table.getModel().getRowCount()); + repairProgress.setMillisToDecideToPopup(100); + repairProgress.setMillisToPopup(250); + + repairWorker = new SwingWorker, Void>() { + @Override + protected Couple doInBackground() { + return repairEntries(repairWorker); + } + }; + repairWorker.addPropertyChangeListener(this); + repairWorker.execute(); + } + + /** + * Performs the repair operation on all available table entries. + * + * @param worker Optional {@code SwingWorker} instance to provide update information. Specify {@code null} to ignore. + * @return A {@link Couple} with the number of replacements in the first slot and the number of removals in the second + * slot. Returns {@code null} if the operation was cancelled. + */ + private Couple repairEntries(SwingWorker worker) { + Couple retVal = null; + + // Collecting and sorting encoding issues: + // Correct sorting is required to properly repair multiple issues in a single string. + final List itemList = new ArrayList<>(); + for (int i = 0; i < table.getModel().getRowCount(); i++) { + final Object o = table.getTableItemAt(i).getObjectAt(3); + if (o instanceof StringError) { + itemList.add((StringError)o); + } else { + Logger.warn("Unexpected item type at row {}: {}", i, o.getClass().getSimpleName()); + } + } + itemList.sort(Comparator.reverseOrder()); + + // repairing issues + boolean isCancelled = false; + // for statistics: number of replaced and removed characters + int numReplaced = 0; + int numRemoved = 0; + // Stores errors found in a single string entry + final TreeSet issues = new TreeSet<>((se1, se2) -> se2.getOffset() - se1.getOffset()); + boolean isFemale = itemList.get(0).isFemale(); + int strref = -1; + for (int i = 0, count = itemList.size(); i < count; i++) { + final StringError error = itemList.get(i); + if (error.isFemale() != isFemale || error.getStrref() != strref) { + // applying fixes + final Couple result = repairStringEntry(issues, isFemale, strref); + numReplaced += result.getValue0(); + numRemoved += result.getValue1(); + issues.clear(); + isFemale = error.isFemale(); + strref = error.getStrref(); + } + issues.add(error); + if (worker != null) { + if (worker.isCancelled()) { + isCancelled = true; + break; } + worker.firePropertyChange("progress", i - 1, i); } } + + if (!isCancelled) { + if (!issues.isEmpty()) { + // applying fixes to remaining group of errors + final Couple result = repairStringEntry(issues, isFemale, strref); + numReplaced += result.getValue0(); + numRemoved += result.getValue1(); + } + + // writing changes back to disk + if (numReplaced + numRemoved > 0) { + StringTable.write(null); + } + + retVal = new Couple<>(numReplaced, numRemoved); + } + + return retVal; + } + + /** + * Performs repair operations on the string referenced by the parameters and returns the result. + * + * @param issues Set of {@link StringError} objects defining individual issues in the specified string. + * @param isFemale Specify {@code true} to process a string in the female talk table. Specify {@code false} to process + * a default/male string entry. + * @param strref The string reference index to repair. + * @return A {@link Couple} with the number of replacements in the first slot and the number of removals in the second + * slot. + */ + private Couple repairStringEntry(TreeSet issues, boolean isFemale, int strref) { + final Couple retVal = new Couple<>(0, 0); + if (!issues.isEmpty()) { + final StringTable.Type tlkType = isFemale ? StringTable.Type.FEMALE : StringTable.Type.MALE; + final DynamicByteArray array = new DynamicByteArray(StringTable.getStringEntry(tlkType, strref).getBuffer()); + for (final StringError issue : issues) { + final int ofs = issue.getOffset(); + final int len = issue.getLength(); + final String replacement = issue.getRepaired(); + array.delete(ofs, len); + if (replacement.isEmpty()) { + retVal.setValue1(retVal.getValue1() + 1); + } else { + try { + array.insert(ofs, replacement.getBytes(StringTable.getCharset())); + } catch (Exception e) { + Logger.error(e); + throw e; + } + retVal.setValue0(retVal.getValue0() + 1); + } + } + final String newText = new String(array.getArray(), StringTable.getCharset()); + StringTable.setStringRef(tlkType, strref, newText); + } + return retVal; } // --------------------- Begin Interface ActionListener --------------------- @@ -381,6 +589,30 @@ public void actionPerformed(ActionEvent e) { } } else if (e.getSource() == bSave) { table.saveCheckResult(resultFrame, "Encoding errors in game strings"); + } else if (e.getSource() == bRepair) { + // Check for modified string table + if (StringTable.isModified()) { + final int result = JOptionPane.showConfirmDialog(getResultFrame(), + "The string table contains unsaved data. Save?", "Question", + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE); + if (result == JOptionPane.YES_OPTION) { + StringTable.write(null); + } else if (result == JOptionPane.CANCEL_OPTION) { + return; + } + } + + // perform repair operation + final int count = table.getModel().getRowCount(); + String msg = (count > 1) ? "Repair all " + count + " issues?" : "Repair " + count + " issue?"; + if (!Profile.isEnhancedEdition()) { + msg += "\nCaution: Operation may be inaccurate for some languages."; + } + final int result = JOptionPane.showConfirmDialog(getResultFrame(), msg, "Question", JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (result == JOptionPane.YES_OPTION) { + repairEntriesBackground(); + } } } @@ -402,21 +634,322 @@ public void valueChanged(ListSelectionEvent e) { // --------------------- End Interface ListSelectionListener --------------------- + // --------------------- Start Interface PropertyChangeListener --------------------- + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getSource() == repairWorker) { + if ("progress".equals(evt.getPropertyName())) { + final int index = (Integer)evt.getNewValue(); + repairProgress.setProgress(index); + if (repairProgress.isCanceled()) { + repairWorker.cancel(false); + } + } else if ("state".equals(evt.getPropertyName())) { + if (SwingWorker.StateValue.DONE == evt.getNewValue()) { + // repair operation completed + repairProgress.close(); + Couple result = null; + boolean isCancelled = repairWorker.isCancelled(); + try { + result = repairWorker.get(); + } catch (InterruptedException | ExecutionException e) { + Logger.error(e); + } catch (CancellationException e) { + isCancelled = true; + } + + if (result != null) { + final int numReplaced = result.getValue0(); + final int numRemoved = result.getValue1(); + JOptionPane.showMessageDialog(getResultFrame(), + "Repair operation completed.\nReplaced characters: " + numReplaced + "\nRemoved characters: " + numRemoved, + "Information", JOptionPane.INFORMATION_MESSAGE); + } else { + if (isCancelled) { + JOptionPane.showMessageDialog(getResultFrame(), + "Repair operation was cancelled.\nString table may contain unsaved changes.", "Information", + JOptionPane.INFORMATION_MESSAGE); + } else { + StringTable.resetAll(); + JOptionPane.showMessageDialog(getResultFrame(), + "Repair operation failed.\nString table may contain unsaved changes.", "Error", + JOptionPane.ERROR_MESSAGE); + } + } + repairProgress = null; + repairWorker = null; + + getResultFrame().close(); + } + } + } + } + + // --------------------- End Interface PropertyChangeListener --------------------- + // -------------------------- INNER CLASSES -------------------------- + /** + * This class describes a single character encoding error in a game string. + */ + public static final class StringError implements Comparable { + private final StringTable.Type tlkType; + private final int strref; + private final byte[] buffer; + private final int offset; + private final int length; + private final String errorDesc; + + /** + * Initializes a new StringError object. + * + * @param isFemale Indicates whether the string belongs to the female or male/default string table. + * @param strref The string reference index. + * @param buffer Byte content of the whole string referenced by {@code strref}. + * @param offset Byte offset of malformed characters. + * @param length Number of malformed bytes. + * @param errorDesc A short error description. + * @throws IllegalArgumentException if one or more of the given parameters are invalid. + */ + public StringError(boolean isFemale, int strref, byte[] buffer, int offset, int length, String errorDesc) + throws IllegalArgumentException { + this.tlkType = isFemale ? StringTable.Type.FEMALE : StringTable.Type.MALE; + this.strref = validateStrref(strref); + this.buffer = validateBuffer(buffer); + this.offset = validateOffset(this.buffer, offset); + this.length = validateLength(this.buffer, this.offset, length); + this.errorDesc = validateErrorDesc(errorDesc); + } + + /** + * Returns {@code true} if the string belongs to the female string table {@code DIALOGF.TLK}. Otherwise it belongs + * to the male/default string table {@code DIALOG.TLK}. A female string table exists only for selected languages. + */ + public boolean isFemale() { + return tlkType == StringTable.Type.FEMALE; + } + + /** Returns the string reference index of the string. */ + public int getStrref() { + return strref; + } + + /** Returns the whole string as byte buffer. */ + public byte[] getBuffer() { + return buffer; + } + + /** Returns the byte offset of malformed characters in the original string. */ + public int getOffset() { + return offset; + } + + /** Returns the number of malformed bytes. */ + public int getLength() { + return length; + } + + /** Returns a short error description as string. */ + public String getErrorDesc() { + return errorDesc; + } + + /** + * Performs an analysis on the malformed data and attempts to repair it. Returns a string representation of the + * fixed data, or empty string if fixing is not possible. + * + * @return Repaired string representation of the malformed bytes if successful, an empty string otherwise. + */ + public String getRepaired() { + StringBuilder retVal = new StringBuilder(); + final byte[] data = Arrays.copyOfRange(buffer, offset, offset + length); + if (StringTable.getCharset().equals(StandardCharsets.UTF_8)) { + // Fixing UTF-8 charset + final String curLangCode = Profile.getProperty(Profile.Key.GET_GAME_LANG_FOLDER_NAME); + Charset cs = null; + try { + cs = Charset.forName(CharsetDetector.getDefaultCharset(curLangCode)); + } catch (UnsupportedCharsetException e) { + cs = StandardCharsets.US_ASCII; + } + + for (final Charset curCharset : new Charset[] { cs, Charset.forName("windows-1252") }) { + final CharsetDecoder csd = curCharset.newDecoder(); + csd.onMalformedInput(CodingErrorAction.REPORT); + csd.onUnmappableCharacter(CodingErrorAction.REPORT); + final int maxCharLength = (int) Math.ceil(csd.maxCharsPerByte() * data.length); + final CharBuffer outBuf = CharBuffer.allocate(maxCharLength); + final CoderResult cr = csd.decode(ByteBuffer.wrap(data), outBuf, true); + if (!cr.isError()) { + retVal = new StringBuilder(outBuf.flip().toString()); + break; + } + } + } else { + // Fixing ANSI/multi-byte charset + if (data.length > 0) { + // attempting to find a replacement string + int ofs = 0; + while (ofs < data.length) { + int codePoint = 0; + final int b1 = data[ofs] & 0xff; + if ((b1 & 0xe0) == 0xc0 && ofs + 1 < data.length) { + // two bytes + final int b2 = data[ofs + 1] & 0xff; + if ((b2 & 0xc0) == 0x80) { + codePoint = ((b1 & 0x1f) << 6) | (b2 & 0x3f); + } + ofs += 2; + } else if ((b1 & 0xf0) == 0xe0 && ofs + 2 < data.length) { + // three bytes + final int b2 = data[ofs + 1] & 0xff; + final int b3 = data[ofs + 2] & 0xff; + if ((b2 & 0xc0) == 0x80 && (b3 & 0xc0) == 0x80) { + codePoint = ((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f); + } + ofs += 3; + } else if ((b1 & 0xf8) == 0xf0 && ofs + 3 < data.length) { + // four bytes + final int b2 = data[ofs + 1] & 0xff; + final int b3 = data[ofs + 2] & 0xff; + final int b4 = data[ofs + 3] & 0xff; + if ((b2 & 0xc0) == 0x80 && (b3 & 0xc0) == 0x80 && (b4 & 0xc0) == 0x80) { + codePoint = ((b1 & 0x07) << 18) | ((b2 & 0x3f) << 12) | ((b3 & 0x3f) << 6) | (b4 & 0x3f); + } + ofs += 4; + } + + if (codePoint > 0) { + // Performing thorough analysis on byte data + boolean isUtf = true; + try { + final char[] chars = Character.toChars(codePoint); + final CharsetEncoder cse = StringTable.getCharset().newEncoder(); + cse.onMalformedInput(CodingErrorAction.REPORT); + cse.onUnmappableCharacter(CodingErrorAction.REPORT); + final CoderResult ecr = cse.encode(CharBuffer.wrap(chars), ByteBuffer.allocate(chars.length * 4), true); + if (!ecr.isError()) { + // Add only if utf-8 code point defines a valid character in the local charset of the string table + retVal.append(new String(chars)); + } else { + isUtf = false; + } + } catch (IllegalArgumentException e) { + // not a valid Unicode code point + isUtf = false; + } + + if (!isUtf) { + // Test if raw bytes already defined valid characters in the local charset of the string table + final CharsetDecoder csd = StringTable.getCharset().newDecoder(); + csd.onMalformedInput(CodingErrorAction.REPORT); + csd.onUnmappableCharacter(CodingErrorAction.REPORT); + final ByteBuffer bb = ByteBuffer.wrap(data); + final CharBuffer cb = CharBuffer.allocate(data.length * 2); + final CoderResult dcr = csd.decode(bb, cb, true); + if (!dcr.isError()) { + // Restoring original content + cb.flip(); + retVal.append(cb); + } + } + } + } + } + } + + return retVal.toString(); + } + + /** Returns the character set used for encoding bytes into string data. */ + private static Charset getCharset() { + return StringTable.getCharset(); + } + + @Override + public int compareTo(StringError o) { + int retVal = tlkType.ordinal() - o.tlkType.ordinal(); + if (retVal != 0) { + return retVal; + } + retVal = strref - o.strref; + if (retVal != 0) { + return retVal; + } + retVal = offset - o.offset; + return retVal; + } + + private static int validateStrref(int strref) throws IllegalArgumentException { + if (strref < 0 || strref >= StringTable.getNumEntries()) { + throw new IllegalArgumentException("Strref is out of bounds"); + } + return strref; + } + + private static byte[] validateBuffer(byte[] buffer) throws IllegalArgumentException { + if (buffer == null) { + throw new IllegalArgumentException("Buffer is null"); + } + return buffer; + } + + private static int validateOffset(byte[] buffer, int offset) throws IllegalArgumentException { + if (offset < 0) { + throw new IllegalArgumentException("Offset is negative"); + } else if (offset >= buffer.length) { + throw new IllegalArgumentException("Offset is out of bounds"); + } + return offset; + } + + private static int validateLength(byte[] buffer, int offset, int length) throws IllegalArgumentException { + if (length <= 0) { + throw new IllegalArgumentException("Length is <= 0"); + } else if (offset + length > buffer.length) { + throw new IllegalArgumentException("Invalid length"); + } + return length; + } + + private static String validateErrorDesc(String errorType) throws IllegalArgumentException { + if (errorType == null || errorType.isEmpty()) { + errorType = "encoding error"; + } + if (getCharset().equals(StandardCharsets.UTF_8)) { + errorType = Character.toUpperCase(errorType.charAt(0)) + errorType.substring(1); + } else { + errorType = Character.toLowerCase(errorType.charAt(0)) + errorType.substring(1); + } + return errorType; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + if (!getCharset().equals(StandardCharsets.UTF_8)) { + sb.append("Possible "); + } + sb.append(errorDesc).append(" found at offset ").append(offset).append(", length: ").append(length); + sb.append(length == 1 ? " byte" : " bytes"); + return sb.toString(); + } + } + private static final class StringErrorTableItem implements TableItem { private static final Pattern REGEX_LINEBREAK = Pattern.compile("\r?\n"); private final ResourceEntry resource; private final Integer strref; private final String text; - private final String message; + private final StringError error; - public StringErrorTableItem(ResourceEntry dlg, int strref, String text, String msg) { + public StringErrorTableItem(ResourceEntry dlg, int strref, String text, StringError error) { this.resource = dlg; this.strref = strref; this.text = text; - this.message = msg; + this.error = error; } /** Returns whether the dialog resource contains female strings. */ @@ -433,7 +966,7 @@ public Object getObjectAt(int columnIndex) { case 2: return text; case 3: - return message; + return error; default: return strref; } @@ -442,7 +975,7 @@ public Object getObjectAt(int columnIndex) { @Override public String toString() { return String.format("String table: %s, StringRef: %d /* %s */, Error message: %s", resource.getResourceName(), - strref, REGEX_LINEBREAK.matcher(text).replaceAll(" "), message); + strref, REGEX_LINEBREAK.matcher(text).replaceAll(" "), error.toString()); } } } diff --git a/src/org/infinity/check/StructChecker.java b/src/org/infinity/check/StructChecker.java index 9b50d5259..f9bd918e0 100644 --- a/src/org/infinity/check/StructChecker.java +++ b/src/org/infinity/check/StructChecker.java @@ -244,9 +244,9 @@ private void search(ResourceEntry entry, AbstractStruct struct) { // Checking signature and version fields StructInfo info = FILE_INFO.get(entry.getExtension()); if (info != null) { - String sig = ((TextString) struct.getAttribute(AbstractStruct.COMMON_SIGNATURE)).toString(); + String sig = struct.getAttribute(AbstractStruct.COMMON_SIGNATURE).toString(); if (info.isSignature(sig)) { - String ver = ((TextString) struct.getAttribute(AbstractStruct.COMMON_VERSION)).toString(); + String ver = struct.getAttribute(AbstractStruct.COMMON_VERSION).toString(); if (!info.isVersion(ver)) { // invalid version? synchronized (table) { @@ -489,8 +489,8 @@ private void showInViewer(Corruption corruption, boolean newWindow) { return; } + final ResourceEntry entry = corruption.getResourceEntry(); if (newWindow) { - final ResourceEntry entry = corruption.getResourceEntry(); final Resource res = ResourceFactory.getResource(entry); final int offset = corruption.getOffset(); new ViewFrame(resultFrame, res); @@ -502,7 +502,6 @@ private void showInViewer(Corruption corruption, boolean newWindow) { } } } else { - final ResourceEntry entry = corruption.getResourceEntry(); final int offset = corruption.getOffset(); NearInfinity.getInstance().showResourceEntry(entry); if (parent instanceof ViewFrame && parent.isVisible()) { diff --git a/src/org/infinity/datatype/AbstractBitmap.java b/src/org/infinity/datatype/AbstractBitmap.java index 0ef1a3f49..a481f076f 100644 --- a/src/org/infinity/datatype/AbstractBitmap.java +++ b/src/org/infinity/datatype/AbstractBitmap.java @@ -41,7 +41,7 @@ public class AbstractBitmap extends Datatype implements Editable, IsNumeric { public final BiFunction formatterDefault = (value, item) -> { String number = isShowAsHex() ? getHexValue(value) : value.toString(); if (item != null) { - return item.toString() + " - " + number; + return item + " - " + number; } else { return "Unknown - " + number; } @@ -54,7 +54,7 @@ public class AbstractBitmap extends Datatype implements Editable, IsNumeric { public final BiFunction formatterBitmap = (value, item) -> { String number = isShowAsHex() ? getHexValue(value) : value.toString(); if (item != null) { - return item.toString() + " (" + number + ")"; + return item + " (" + number + ")"; } else { return "Unknown (" + number + ")"; } @@ -67,7 +67,7 @@ public class AbstractBitmap extends Datatype implements Editable, IsNumeric { public final BiFunction formatterHashBitmapReverse = (value, item) -> { String number = isShowAsHex() ? getHexValue(value) : value.toString(); if (item != null) { - return number + " - " + item.toString(); + return number + " - " + item; } else { return number + " - Unknown - "; } @@ -80,7 +80,7 @@ public class AbstractBitmap extends Datatype implements Editable, IsNumeric { public final BiFunction formatterBitmapReverse = (value, item) -> { String number = isShowAsHex() ? getHexValue(value) : value.toString(); if (item != null) { - return "(" + number + ") " + item.toString(); + return "(" + number + ") " + item; } else { return "(" + number + ") Unknown"; } diff --git a/src/org/infinity/datatype/Bestiary.java b/src/org/infinity/datatype/Bestiary.java index b5678de8f..306a2c1a0 100644 --- a/src/org/infinity/datatype/Bestiary.java +++ b/src/org/infinity/datatype/Bestiary.java @@ -615,15 +615,12 @@ public Class getColumnClass(int columnIndex) { case 0: return Boolean.class; case 1: + case 5: return Integer.class; case 2: - return String.class; case 3: - return String.class; case 4: return String.class; - case 5: - return Integer.class; default: return null; } diff --git a/src/org/infinity/datatype/ColorPicker.java b/src/org/infinity/datatype/ColorPicker.java index 75a474029..9f9698efe 100644 --- a/src/org/infinity/datatype/ColorPicker.java +++ b/src/org/infinity/datatype/ColorPicker.java @@ -67,7 +67,7 @@ public enum Format { private final int shiftGreen; private final int shiftBlue; - private Format(int shiftRed, int shiftGreen, int shiftBlue) { + Format(int shiftRed, int shiftGreen, int shiftBlue) { this.shiftRed = shiftRed; this.shiftGreen = shiftGreen; this.shiftBlue = shiftBlue; diff --git a/src/org/infinity/datatype/ColorValue.java b/src/org/infinity/datatype/ColorValue.java index bebf7ab72..54bcc6c6e 100644 --- a/src/org/infinity/datatype/ColorValue.java +++ b/src/org/infinity/datatype/ColorValue.java @@ -248,7 +248,7 @@ public int getValue() { @Override public String toString() { - String retVal = "Color index " + Integer.toString(number); + String retVal = "Color index " + number; String name = getColorName(number); if (name != null) { retVal += " (" + name + ")"; @@ -401,9 +401,7 @@ public Image getElementAt(int index) { private void initEntries(int defaultWidth, int defaultHeight) { if (colorValue.colorEntry == null) { - if (ResourceFactory.resourceExists("RANGES12.BMP")) { - colorValue.colorEntry = ResourceFactory.getResourceEntry("RANGES12.BMP"); - } else if (ResourceFactory.resourceExists("MPALETTE.BMP")) { + if (ResourceFactory.resourceExists("MPALETTE.BMP")) { colorValue.colorEntry = ResourceFactory.getResourceEntry("MPALETTE.BMP"); } } @@ -411,7 +409,7 @@ private void initEntries(int defaultWidth, int defaultHeight) { // scanning range of colors int maxValue = 255; // default size if (colorValue.colorEntry != null) { - BufferedImage image = null; + BufferedImage image; try { image = new GraphicsResource(colorValue.colorEntry).getImage(); maxValue = Math.max(maxValue, image.getHeight() - 1); @@ -461,7 +459,7 @@ private BufferedImage getFixedColor(BufferedImage colorRanges, int index, int wi // Returns an image describing a random color or invalid color entry private BufferedImage getVirtualColor(int index, int width, int height) { - BufferedImage retVal = null; + BufferedImage retVal; Color invalidColor = new Color(0xe0e0e0); boolean isRandom = colorValue.randomColors.containsKey(index); diff --git a/src/org/infinity/datatype/DecNumber.java b/src/org/infinity/datatype/DecNumber.java index 5fa670c7c..856f21b9b 100644 --- a/src/org/infinity/datatype/DecNumber.java +++ b/src/org/infinity/datatype/DecNumber.java @@ -162,7 +162,7 @@ public boolean equals(Object obj) { } /** Attempts to parse the specified object into a decimal or, optionally, hexadecimal number. */ - public static long parseNumber(Object value, int size, boolean negativeAllowed, boolean hexAllowed) throws Exception { + public static long parseNumber(Object value, int size, boolean negativeAllowed, boolean hexAllowed) { if (value == null) { throw new NullPointerException(); } diff --git a/src/org/infinity/datatype/InlineEditable.java b/src/org/infinity/datatype/InlineEditable.java index 104075835..9836d32c8 100644 --- a/src/org/infinity/datatype/InlineEditable.java +++ b/src/org/infinity/datatype/InlineEditable.java @@ -17,17 +17,17 @@ public interface InlineEditable extends StructEntry { /** The background color used for table cells. */ - static final Color GRID_BACKGROUND = (UIManager.getColor("Table.focusCellBackground") != null) + Color GRID_BACKGROUND = (UIManager.getColor("Table.focusCellBackground") != null) ? UIManager.getColor("Table.focusCellBackground") : Color.WHITE; /** The border color used for table cells. */ - static final Color GRID_BORDER = (UIManager.getColor("Table.gridColor") != null) + Color GRID_BORDER = (UIManager.getColor("Table.gridColor") != null) ? UIManager.getColor("Table.gridColor") : Color.BLACK; /** The default component used for the inline editor. */ - static final JTextField DEFAULT_EDITOR = new JTextField() { + JTextField DEFAULT_EDITOR = new JTextField() { { setFont(Misc.getScaledFont(BrowserMenuBar.getInstance().getOptions().getScriptFont())); setBorder(new LineBorder(GRID_BORDER, 1)); diff --git a/src/org/infinity/datatype/ItemTypeBitmap.java b/src/org/infinity/datatype/ItemTypeBitmap.java index 909050206..d8eae1ee1 100644 --- a/src/org/infinity/datatype/ItemTypeBitmap.java +++ b/src/org/infinity/datatype/ItemTypeBitmap.java @@ -43,6 +43,57 @@ public class ItemTypeBitmap extends HashBitmap { "Greatswords", "Halberds", "Bolts", "Cloaks and robes", "Copper commons", "Gems", "Wands", "Eyeballs", "Bracelets", "Earrings", "Tattoos", "Lenses", "Teeth" }; + /** Category order map for non-PST item categories. */ + public static final int[] SUGGESTED_CATEGORY_ORDER; + + /** Category order map for PST item categories. */ + public static final int[] SUGGESTED_CATEGORY_ORDER_PST; + + // Creating inverse category index maps + static { + // non-PST item categories + final int[] items = { + // Weapons + 25, 44, 17, 23, 22, 15, 16, 24, 30, 21, 18, 29, 26, 57, 69, 20, 19, 28, 27, + // Ammo + 14, 31, 5, + // Armor + 2, 60, 61, 66, 62, 68, 63, 64, 65, 67, 32, 12, 41, 53, 49, 47, 7, 72, 6, 70, 73, 3, 4, + // Jewelry + 10, 1, + // Potions, scrolls, books, containers, ... + 9, 56, 11, 37, 50, 35, 51, 36, 58, + // Misc. stuff + 0, 13, 71, 34, 39, 55, 59, 74, 75, 76, 77, + // uncategorized + 8, 33, 38, 40, 42, 43, 45, 46, 48, 52, 54, + }; + SUGGESTED_CATEGORY_ORDER = new int[items.length]; + for (int i = 0; i < items.length; i++) { + SUGGESTED_CATEGORY_ORDER[items[i]] = i; + } + + // PST item categories + final int[] items11 = { + // Weapons + 25, 17, 23, 22, 15, 16, 24, 30, 21, 18, 26, 29, 20, 19, 28, 27, + // Ammo + 14, 31, 5, + // Armor + 2, 32, 12, 7, 6, 3, 4, 36, 38, 40, 41, + // Misc and jewelry + 0, 1, 37, 10, + // Potions, scrolls, wands, tattoos, ... + 9, 11, 13, 35, 39, + // uncategorized + 34, 8, 33, + }; + SUGGESTED_CATEGORY_ORDER_PST = new int[items11.length]; + for (int i = 0; i < items11.length; i++) { + SUGGESTED_CATEGORY_ORDER_PST[items11[i]] = i; + } + } + private static TreeMap CATEGORIES = null; public ItemTypeBitmap(ByteBuffer buffer, int offset, int length, String name) { @@ -78,7 +129,7 @@ private static TreeMap buildCategories() { for (int row = 0, count = table.getRowCount(); row < count; row++) { final String idxValue = table.get(row, 0); final int radix = idxValue.startsWith("0x") ? 16 : 10; - String catName = ""; + String catName; try { int idx = Integer.parseInt(idxValue, radix); if (idx >= 0 && idx < CATEGORIES_ARRAY.length) { diff --git a/src/org/infinity/datatype/MultiNumber.java b/src/org/infinity/datatype/MultiNumber.java index f58f44876..f7156d7df 100644 --- a/src/org/infinity/datatype/MultiNumber.java +++ b/src/org/infinity/datatype/MultiNumber.java @@ -354,7 +354,7 @@ public ValueTableModel(int value, int bits, int numValues, String[] labels, bool if (labels != null && i < labels.length && labels[i] != null) { data[ATTRIBUTE][i] = labels[i]; } else { - data[ATTRIBUTE][i] = "Value " + Integer.toString(i + 1); + data[ATTRIBUTE][i] = "Value " + (i + 1); } data[VALUE][i] = bitRangeAsNumber(value, bits, i * bits, this.signed); } diff --git a/src/org/infinity/datatype/ResourceBitmap.java b/src/org/infinity/datatype/ResourceBitmap.java index 26ee90e29..d9fa05fea 100644 --- a/src/org/infinity/datatype/ResourceBitmap.java +++ b/src/org/infinity/datatype/ResourceBitmap.java @@ -201,7 +201,7 @@ public RefEntry(long value, String ref, String search, List searchDirs) { this.searchString = (search != null) ? search : ""; this.name = ref; } - this.desc = String.format(FMT_REF_VALUE, getResourceName(), getSearchString(), Long.toString(value)); + this.desc = String.format(FMT_REF_VALUE, getResourceName(), getSearchString(), value); } @Override diff --git a/src/org/infinity/datatype/ResourceRef.java b/src/org/infinity/datatype/ResourceRef.java index 970ebc53d..a80c878b7 100644 --- a/src/org/infinity/datatype/ResourceRef.java +++ b/src/org/infinity/datatype/ResourceRef.java @@ -32,6 +32,7 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import org.infinity.gui.SoundPanel; import org.infinity.gui.StructViewer; import org.infinity.gui.TextListPanel; import org.infinity.gui.ViewFrame; @@ -43,7 +44,6 @@ import org.infinity.resource.Resource; import org.infinity.resource.ResourceFactory; import org.infinity.resource.key.ResourceEntry; -import org.infinity.resource.sound.SoundResource; import org.infinity.util.Logger; import org.infinity.util.Misc; import org.infinity.util.io.StreamUtils; @@ -61,12 +61,11 @@ * */ public class ResourceRef extends Datatype - implements Editable, IsTextual, IsReference, ActionListener, ListSelectionListener { + implements Editable, IsTextual, IsReference, Closeable, ActionListener, ListSelectionListener { private static final Comparator IGNORE_CASE_EXT_COMPARATOR = new IgnoreCaseExtComparator(); /** List of resource types that are can be used to display associated icons. */ - private static final HashSet ICON_EXTENSIONS = new HashSet<>( - Arrays.asList(new String[] { "BMP", "ITM", "SPL" })); + private static final HashSet ICON_EXTENSIONS = new HashSet<>(Arrays.asList("BMP", "ITM", "SPL")); /** Special constant that represents absense of resource in the field. */ private static final ResourceRefEntry NONE = new ResourceRefEntry("None"); @@ -85,8 +84,8 @@ public class ResourceRef extends Datatype /** Button that used to open editor of current selected element in the list. */ private JButton bView; - /** Button that used to play sound of current selected element in the list. */ - private JButton bPlay; + /** Handles playback of sound resources. */ + private SoundPanel soundPanel; /** * GUI component that lists all available resources that can be set to this resource reference and have edit field for @@ -128,15 +127,6 @@ public void actionPerformed(ActionEvent event) { if (isEditable(selected)) { new ViewFrame(list.getTopLevelAncestor(), ResourceFactory.getResource(selected.entry)); } - } else if (event.getSource() == bPlay) { - final ResourceRefEntry selected = list.getSelectedValue(); - if (isSound(selected)) { - // prevent overlapping sound playback - closeResource(currentResource); - SoundResource res = (SoundResource) ResourceFactory.getResource(selected.entry); - res.playSound(); - currentResource = res; - } } } @@ -200,11 +190,15 @@ public void mouseClicked(MouseEvent event) { JButton bUpdate = new JButton("Update value", Icons.ICON_REFRESH_16.getIcon()); bUpdate.addActionListener(container); bUpdate.setActionCommand(StructViewer.UPDATE_VALUE); + bView = new JButton("View/Edit", Icons.ICON_ZOOM_16.getIcon()); bView.addActionListener(this); - bPlay = new JButton("Play", Icons.ICON_PLAY_16.getIcon()); - bPlay.addActionListener(this); - bPlay.setVisible(ResourceEntry.isSound(types)); + + soundPanel = new SoundPanel(SoundPanel.Option.COMPACT_CONTROLS, SoundPanel.Option.TIME_LABEL, + SoundPanel.Option.PROGRESS_BAR); + soundPanel.setDisplayFormat(SoundPanel.DisplayFormat.ELAPSED_TOTAL_PRECISE); + soundPanel.setVisible(ResourceEntry.isSound(types)); + list.addListSelectionListener(this); setResourceEntryUpdated(list.getSelectedValue()); @@ -223,14 +217,14 @@ public void mouseClicked(MouseEvent event) { panel.add(spacerTop, gbc); gbc = ViewerUtil.setGBC(gbc, 1, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(3, 6, 3, 0), 0, 0); + new Insets(3, 6, 3, 6), 0, 0); panel.add(bUpdate, gbc); gbc = ViewerUtil.setGBC(gbc, 1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(3, 6, 3, 0), 0, 0); + new Insets(3, 6, 3, 6), 0, 0); panel.add(bView, gbc); gbc = ViewerUtil.setGBC(gbc, 1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, - new Insets(24, 6, 3, 0), 0, 0); - panel.add(bPlay, gbc); + new Insets(24, 6, 3, 6), 0, 0); + panel.add(soundPanel, gbc); // spacer keeps controls in the center final JPanel spacerBottom = new JPanel(); @@ -379,6 +373,13 @@ public String getResourceName() { return resname; } + @Override + public void close() throws Exception { + if (soundPanel != null) { + soundPanel.setPlaying(false); + } + } + public boolean isEmpty() { return (resname.equals(NONE.name));// FIXME: use null instead of NONE.name } @@ -418,14 +419,30 @@ private void setResourceEntryUpdated(ResourceRefEntry entry) { closeResource(currentResource); if (entry != null) { bView.setEnabled(isEditable(entry)); - bPlay.setEnabled(isSound(entry)); + if (soundPanel != null && soundPanel.isVisible()) { + try { + soundPanel.loadSound(entry.getEntry()); + soundPanel.setEnabled(isSound(entry)); + } catch (NullPointerException e) { + // expected + soundPanel.setEnabled(false); + } catch (Exception e) { + Logger.error(e); + soundPanel.setEnabled(false); + } + } } else { bView.setEnabled(false); - bPlay.setEnabled(false); + if (soundPanel != null && soundPanel.isVisible()) { + soundPanel.setEnabled(false); + } } } private void closeResource(Resource resource) { + if (soundPanel != null) { + soundPanel.unload(); + } if (resource instanceof Closeable) { try { ((Closeable) resource).close(); @@ -440,7 +457,7 @@ private boolean isEditable(ResourceRefEntry ref) { } private boolean isSound(ResourceRefEntry ref) { - return ref != null && ref != NONE && ref.entry != null && ref.entry.isSound(); + return ref != null && ref.entry != null && ref.entry.isSound(); } private void setValue(String newValue) { diff --git a/src/org/infinity/datatype/TableBitmap.java b/src/org/infinity/datatype/TableBitmap.java index c14506b0f..c11fa9a56 100644 --- a/src/org/infinity/datatype/TableBitmap.java +++ b/src/org/infinity/datatype/TableBitmap.java @@ -27,7 +27,7 @@ public TableBitmap(ByteBuffer buffer, int offset, int length, String name, Strin } private static String[] generateList(String tableName, int column, boolean normalize) { - String[] retVal = null; + String[] retVal; Table2da table = Table2daCache.get(tableName); if (table != null) { diff --git a/src/org/infinity/datatype/TextBitmap.java b/src/org/infinity/datatype/TextBitmap.java index 3426ec9a6..9f7cbe960 100644 --- a/src/org/infinity/datatype/TextBitmap.java +++ b/src/org/infinity/datatype/TextBitmap.java @@ -151,7 +151,7 @@ public int read(ByteBuffer buffer, int offset) { while (sb.length() < getSize() - text.length()) { sb.append(' '); } - text = text + sb.toString(); + text = text + sb; } return offset + getSize(); diff --git a/src/org/infinity/datatype/TextEdit.java b/src/org/infinity/datatype/TextEdit.java index 75565c78c..4e95c6925 100644 --- a/src/org/infinity/datatype/TextEdit.java +++ b/src/org/infinity/datatype/TextEdit.java @@ -42,11 +42,11 @@ * */ public final class TextEdit extends Datatype implements Editable, IsTextual { - public static enum EOLType { + public enum EOLType { UNIX, WINDOWS } - public static enum Align { + public enum Align { LEFT, RIGHT, TOP, BOTTOM } diff --git a/src/org/infinity/datatype/TextString.java b/src/org/infinity/datatype/TextString.java index 3db791e3f..9a9f417ce 100644 --- a/src/org/infinity/datatype/TextString.java +++ b/src/org/infinity/datatype/TextString.java @@ -66,7 +66,7 @@ public boolean update(Object value) { @Override public void write(OutputStream os) throws IOException { if (text != null) { - byte[] buf = text.getBytes(Misc.CHARSET_DEFAULT); + byte[] buf = text.getBytes(charset); buffer.position(0); buffer.put(buf, 0, Math.min(buf.length, buffer.limit())); while (buffer.remaining() > 0) { diff --git a/src/org/infinity/datatype/UpdateListener.java b/src/org/infinity/datatype/UpdateListener.java index 10feee90d..ac455fd72 100644 --- a/src/org/infinity/datatype/UpdateListener.java +++ b/src/org/infinity/datatype/UpdateListener.java @@ -16,5 +16,5 @@ public interface UpdateListener extends EventListener { * @param event Contains associated data * @return true if table data other than the current item has been changed, false otherwise. */ - public boolean valueUpdated(UpdateEvent event); + boolean valueUpdated(UpdateEvent event); } diff --git a/src/org/infinity/exceptions/AbortException.java b/src/org/infinity/exceptions/AbortException.java new file mode 100644 index 000000000..2d8b7823c --- /dev/null +++ b/src/org/infinity/exceptions/AbortException.java @@ -0,0 +1,43 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.exceptions; + +/** + * Thrown to indicate that the current operation was intentionally aborted. + */ +public class AbortException extends Exception { + /** Constructs an {@code AbortException} with no detail message. */ + public AbortException() { + super(); + } + + /** + * Constructs an {@code AbortException} with the specified detail message. + * + * @param message the detail message. + */ + public AbortException(String message) { + super(message); + } + + /** + * Constructs an {@code AbortException} with the specified detail message and cause. + * + * @param message the detail message. + * @param cause the cause for this exception to be thrown. + */ + public AbortException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs an {@code AbortException} with a cause but no detail message. + * + * @param cause the cause for this exception to be thrown. + */ + public AbortException(Throwable cause) { + super(cause); + } +} diff --git a/src/org/infinity/exceptions/ResourceException.java b/src/org/infinity/exceptions/ResourceException.java new file mode 100644 index 000000000..91c5396f8 --- /dev/null +++ b/src/org/infinity/exceptions/ResourceException.java @@ -0,0 +1,43 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.exceptions; + +/** + * A generic exception that is thrown if a resource-related problem occurs. + */ +public class ResourceException extends Exception { + /** Constructs an {@code ResourceException} with no detail message. */ + public ResourceException() { + super(); + } + + /** + * Constructs an {@code ResourceException} with the specified detail message. + * + * @param message the detail message. + */ + public ResourceException(String message) { + super(message); + } + + /** + * Constructs an {@code ResourceException} with the specified detail message and cause. + * + * @param message the detail message. + * @param cause the cause for this exception to be thrown. + */ + public ResourceException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs an {@code ResourceException} with a cause but no detail message. + * + * @param cause the cause for this exception to be thrown. + */ + public ResourceException(Throwable cause) { + super(cause); + } +} diff --git a/src/org/infinity/exceptions/ResourceNotFoundException.java b/src/org/infinity/exceptions/ResourceNotFoundException.java new file mode 100644 index 000000000..06513995b --- /dev/null +++ b/src/org/infinity/exceptions/ResourceNotFoundException.java @@ -0,0 +1,43 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.exceptions; + +/** + * Thrown if a game resource could not be found. + */ +public class ResourceNotFoundException extends ResourceException { + /** Constructs an {@code ResourceNotFoundException} with no detail message. */ + public ResourceNotFoundException() { + super(); + } + + /** + * Constructs an {@code ResourceNotFoundException} with the specified detail message. + * + * @param message the detail message. + */ + public ResourceNotFoundException(String message) { + super(message); + } + + /** + * Constructs an {@code ResourceNotFoundException} with the specified detail message and cause. + * + * @param message the detail message. + * @param cause the cause for this exception to be thrown. + */ + public ResourceNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs an {@code ResourceNotFoundException} with a cause but no detail message. + * + * @param cause the cause for this exception to be thrown. + */ + public ResourceNotFoundException(Throwable cause) { + super(cause); + } +} diff --git a/src/org/infinity/gui/BIFFEditorTable.java b/src/org/infinity/gui/BIFFEditorTable.java index b4fa3401e..cb4f57332 100644 --- a/src/org/infinity/gui/BIFFEditorTable.java +++ b/src/org/infinity/gui/BIFFEditorTable.java @@ -56,7 +56,7 @@ public enum State { private final ImageIcon icon; - private State(Icons icon) { + State(Icons icon) { this.icon = icon.getIcon(); } } diff --git a/src/org/infinity/gui/BookmarkEditor.java b/src/org/infinity/gui/BookmarkEditor.java index 185945e28..68985443c 100644 --- a/src/org/infinity/gui/BookmarkEditor.java +++ b/src/org/infinity/gui/BookmarkEditor.java @@ -292,7 +292,7 @@ private void initData(List bookmarks) { int platformIdx = Math.max(cbPlatformModel.getIndexOf(Platform.OS.getCurrentOS()), 0); cbPlatform.setSelectedIndex(platformIdx); for (int idx = 0; idx < cbPlatformModel.getSize(); idx++) { - listBinPathModels.put(cbPlatformModel.getElementAt(idx), new DefaultListModel()); + listBinPathModels.put(cbPlatformModel.getElementAt(idx), new DefaultListModel<>()); } listBinPaths.setModel(getBinPathModel()); listBinPaths.addListSelectionListener(this); @@ -496,7 +496,7 @@ private DefaultListModel getBinPathModel(Platform.OS os) { private void addBinPathInteractive() { JFileChooser fc = new JFileChooser(Profile.getGameRoot().toFile()); fc.setDialogTitle("Select game executable"); - FileFilter exeFilter = null; + FileFilter exeFilter; fc.removeChoosableFileFilter(fc.getAcceptAllFileFilter()); if (cbPlatform.getSelectedItem() == Platform.OS.WINDOWS) { exeFilter = new FileNameExtensionFilter("Executable Files", "exe", "lnk", "cmd", "bat", "ps1", "pif"); diff --git a/src/org/infinity/gui/ButtonPopupWindow.java b/src/org/infinity/gui/ButtonPopupWindow.java index bfb2d4ae2..17f45f52f 100644 --- a/src/org/infinity/gui/ButtonPopupWindow.java +++ b/src/org/infinity/gui/ButtonPopupWindow.java @@ -19,8 +19,6 @@ import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; -import java.util.ArrayList; -import java.util.List; import java.util.Locale; import javax.swing.AbstractAction; @@ -36,6 +34,7 @@ import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.border.BevelBorder; +import javax.swing.event.EventListenerList; /** * Provides a button component that pops up an associated window when the button is pressed. It works similar to @@ -54,7 +53,7 @@ public enum Align { } private final PopupWindow window = new PopupWindow(this); - private final List listeners = new ArrayList<>(); + private final EventListenerList listenerList = new EventListenerList(); private PopupWindow ignoredWindow; // used to determine whether to hide the current window on lost focus private Align windowAlign; @@ -138,28 +137,19 @@ public ButtonPopupWindow(String text, Icon icon, Component content, Align align) /** Adds a new PopupWindowListener to this component. */ public void addPopupWindowListener(PopupWindowListener listener) { if (listener != null) { - if (!listeners.contains(listener)) { - listeners.add(listener); - } + listenerList.add(PopupWindowListener.class, listener); } } /** Returns all registered PopupWindowListener object. */ public PopupWindowListener[] getPopupWindowListeners() { - PopupWindowListener[] retVal = new PopupWindowListener[listeners.size()]; - for (int i = 0, size = listeners.size(); i < size; i++) { - retVal[i] = listeners.get(i); - } - return retVal; + return listenerList.getListeners(PopupWindowListener.class); } /** Removes a PopupWindowListener from this component. */ public void removePopupWindowListener(PopupWindowListener listener) { if (listener != null) { - int idx = listeners.indexOf(listener); - if (idx >= 0) { - listeners.remove(idx); - } + listenerList.remove(PopupWindowListener.class, listener); } } @@ -274,15 +264,18 @@ public void removeGlobalKeyStroke(Object key, KeyStroke keyStroke) { } protected void firePopupWindowListener(boolean becomeVisible) { + final Object[] listeners = listenerList.getListenerList(); PopupWindowEvent event = null; - for (int i = 0, size = listeners.size(); i < size; i++) { - if (event == null) { - event = new PopupWindowEvent(this); - } - if (becomeVisible) { - listeners.get(i).popupWindowWillBecomeVisible(event); - } else { - listeners.get(i).popupWindowWillBecomeInvisible(event); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == PopupWindowListener.class) { + if (event == null) { + event = new PopupWindowEvent(this); + } + if (becomeVisible) { + ((PopupWindowListener)listeners[i + 1]).popupWindowWillBecomeVisible(event); + } else { + ((PopupWindowListener)listeners[i + 1]).popupWindowWillBecomeInvisible(event); + } } } } diff --git a/src/org/infinity/gui/ChildFrame.java b/src/org/infinity/gui/ChildFrame.java index e98c0214b..8d9c9e436 100644 --- a/src/org/infinity/gui/ChildFrame.java +++ b/src/org/infinity/gui/ChildFrame.java @@ -27,6 +27,7 @@ import javax.swing.WindowConstants; import org.infinity.NearInfinity; +import org.infinity.exceptions.AbortException; import org.infinity.gui.menu.BrowserMenuBar; import org.infinity.resource.AbstractStruct; import org.infinity.resource.Closeable; @@ -45,18 +46,34 @@ public class ChildFrame extends JFrame { private final boolean closeOnInvisible; - /** Closes all child windows. */ + // Indicates whether the window should be closed when the game is reset or closed + private boolean closeOnReset; + + /** Closes all child windows except for child windows with {@code closeOnReset} set to {@code false}. */ public static int closeWindows() { - return closeWindow((Class)null); + return closeWindow(null, false); } - /** Closes all windows of the specified window class. */ + /** Closes all child windows. */ + public static int closeWindows(boolean forced) { + return closeWindow(null, forced); + } + + /** + * Closes all windows of the specified window class except for child windows with {@code closeOnReset} set to + * {@code false}. + */ public static int closeWindow(Class frameClass) { + return closeWindow(frameClass, false); + } + + /** Closes all windows of the specified window class. */ + public static int closeWindow(Class frameClass, boolean forced) { int retVal = 0; WindowEvent event = new WindowEvent(NearInfinity.getInstance(), WindowEvent.WINDOW_CLOSING); for (Iterator i = WINDOWS.iterator(); i.hasNext();) { ChildFrame frame = i.next(); - if (frameClass == null || frame.getClass() == frameClass) { + if ((forced || frame.isCloseOnReset()) && (frameClass == null || frame.getClass() == frameClass)) { i.remove(); retVal++; closeWindow(frame, event); @@ -77,6 +94,30 @@ public static boolean closeWindow(ChildFrame frame) { return retVal; } + /** + * Signals all persistent {@link ChildFrame} instances that the game has been refreshed or a new game has been opened. + * + * @param refreshOnly Specify {@code true} if the game has only been refreshed. + */ + public static void fireGameReset(boolean refreshOnly) { + fireGameReset(null, refreshOnly); + } + + /** + * Signals all persistent {@link ChildFrame} instances of the given class that the game has been refreshed or a new + * game has been opened. + * + * @param frameClass Filter by the specific {@code ChildFrame} class. Specify {@code null} to process all instances. + * @param refreshOnly Specify {@code true} if the game has only been refreshed. + */ + public static void fireGameReset(Class frameClass, boolean refreshOnly) { + for (final ChildFrame frame : WINDOWS) { + if (!frame.isCloseOnReset() && (frameClass == null || frame.getClass() == frameClass)) { + frame.gameReset(refreshOnly); + } + } + } + /** * Returns first window with specified class or {@code null} if such window do not exist. * @@ -170,13 +211,18 @@ public static void updateWindowGUIs() { } protected ChildFrame(String title) { - this(title, false); + this(title, false, true); } public ChildFrame(String title, boolean closeOnInvisible) { + this(title, closeOnInvisible, true); + } + + public ChildFrame(String title, boolean closeOnInvisible, boolean closeOnReset) { super(title); setIconImages(NearInfinity.getInstance().getIconImages()); this.closeOnInvisible = closeOnInvisible; + this.closeOnReset = closeOnReset; WINDOWS.add(this); JPanel pane = new JPanel(); setContentPane(pane); @@ -185,33 +231,52 @@ public ChildFrame(String title, boolean closeOnInvisible) { pane.getActionMap().put(pane, new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { - if (ChildFrame.this.closeOnInvisible) { - try { + try { + if (ChildFrame.this.closeOnInvisible) { if (!ChildFrame.this.windowClosing(false)) { return; } - } catch (Exception e2) { - Logger.error(e2); - return; + } else { + if (!ChildFrame.this.windowHiding(false)) { + return; + } } + } catch (AbortException e2) { + Logger.debug(e2); + return; + } catch (Exception e2) { + Logger.error(e2); + return; + } + + if (ChildFrame.this.closeOnInvisible) { WINDOWS.remove(ChildFrame.this); } + ChildFrame.this.setVisible(false); } }); addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { - if (ChildFrame.this.closeOnInvisible) { - try { + try { + if (ChildFrame.this.closeOnInvisible) { if (!ChildFrame.this.windowClosing(false)) { return; } - } catch (Exception e2) { - throw new IllegalAccessError(); // ToDo: This is just too ugly + } else { + if (!ChildFrame.this.windowHiding(false)) { + return; + } } + } catch (Exception e2) { + throw new IllegalAccessError(); // ToDo: This is just too ugly + } + + if (ChildFrame.this.closeOnInvisible) { WINDOWS.remove(ChildFrame.this); } + ChildFrame.this.setVisible(false); } }); @@ -222,6 +287,33 @@ public void close() { WINDOWS.remove(this); } + /** + * Returns whether the {@code ChildFrame} instance is automatically closed when the game is refreshed or closed. + * + * @return {@code true} if the frame is closed automatically if the game state changes, {@code false} otherwise. + */ + public boolean isCloseOnReset() { + return closeOnReset; + } + + /** + * Specifies whether the {@code ChildFrame} instance is automatically closed when the game is refreshed or closed. + * + * @param b Specify whether to close the frame automatically if the game state changes. + */ + public void setCloseOnReset(boolean b) { + closeOnReset = b; + } + + /** + * This method is called whenever the game has been refreshed or a new game has been opened. It is only relevant for + * frames that persist after refreshing or reopening games, i.e. when {@link #isCloseOnReset()} returns {@code false}. + * + * @param refreshOnly {@code true} if the game content has only been refreshed. + */ + protected void gameReset(boolean refreshOnly) { + } + /** * This method is called whenever the dialog is about to be closed and removed from memory. * @@ -241,6 +333,18 @@ protected boolean windowClosing(boolean forced) throws Exception { return true; } + /** + * This method is called whenever the dialog is about to be hidden without being removed from memory. + * + * @param forced If {@code false}, the return value will be honored. If {@code true}, the return value will be + * disregarded. + * @return If {@code true}, the hiding procedure continues. If {@code false}, the hiding procedure will be cancelled. + * @throws Exception + */ + protected boolean windowHiding(boolean forced) throws Exception { + return true; + } + /** * Returns the size of the last created child frame. Falls back to the default size if information is not available. * @@ -330,7 +434,7 @@ private static void closeWindow(ChildFrame frame, WindowEvent event) { if (event == null) { event = new WindowEvent(NearInfinity.getInstance(), WindowEvent.WINDOW_CLOSING); } - WindowListener listeners[] = frame.getWindowListeners(); + WindowListener[] listeners = frame.getWindowListeners(); for (final WindowListener listener : listeners) { listener.windowClosing(event); } @@ -338,6 +442,8 @@ private static void closeWindow(ChildFrame frame, WindowEvent event) { frame.close(); } frame.dispose(); + } catch (AbortException e) { + Logger.debug(e); } catch (Exception e) { Logger.error(e); } diff --git a/src/org/infinity/gui/ColorGrid.java b/src/org/infinity/gui/ColorGrid.java index 9efcf982b..812e425e2 100644 --- a/src/org/infinity/gui/ColorGrid.java +++ b/src/org/infinity/gui/ColorGrid.java @@ -903,7 +903,7 @@ public void mouseMoved(MouseEvent event) { /** Defines an object which listens to MouseOverEvents. */ public interface MouseOverListener extends EventListener { - public void mouseOver(MouseOverEvent event); + void mouseOver(MouseOverEvent event); } /** MouseOverEvent is used to notify listeners that the mouse has been placed over a specific color entry. */ diff --git a/src/org/infinity/gui/FontChooser.java b/src/org/infinity/gui/FontChooser.java index 7feea6b9e..773562986 100644 --- a/src/org/infinity/gui/FontChooser.java +++ b/src/org/infinity/gui/FontChooser.java @@ -256,7 +256,7 @@ public int getSelectedFontStyle() { * @see #setSelectedFontSize **/ public int getSelectedFontSize() { - int fontSize = 1; + int fontSize; String fontSizeString = getFontSizeTextField().getText(); while (true) { try { diff --git a/src/org/infinity/gui/GameProperties.java b/src/org/infinity/gui/GameProperties.java index 3ff1849ee..8759654af 100644 --- a/src/org/infinity/gui/GameProperties.java +++ b/src/org/infinity/gui/GameProperties.java @@ -195,7 +195,7 @@ private void init() { if (sb.length() > 0) { sb.append("; "); } - sb.append(dlcPath.getFileSystem().toString()); + sb.append(dlcPath.getFileSystem()); } l = new JLabel("DLC archives:"); tf = createReadOnlyField(sb.toString(), true); @@ -227,6 +227,11 @@ private void init() { listControls.add(Couple.with(l, tf)); } + // Entry: Default charset + l = new JLabel("Default character set:"); + tf = createReadOnlyField(Profile.getDefaultCharset().displayName(), true); + listControls.add(Couple.with(l, tf)); + // Entry: Use female TLK file l = new JLabel("Uses female TLK file:"); tf = createReadOnlyField(Boolean.toString((Profile.getProperty(Profile.Key.GET_GAME_DIALOGF_FILE) != null)), true); diff --git a/src/org/infinity/gui/InfinityAmp.java b/src/org/infinity/gui/InfinityAmp.java index 0ca2de71d..a261e72cc 100644 --- a/src/org/infinity/gui/InfinityAmp.java +++ b/src/org/infinity/gui/InfinityAmp.java @@ -42,6 +42,13 @@ import org.infinity.util.SimpleListModel; import org.infinity.util.io.StreamUtils; +// TODO: remove class from project +/** + * A music player for MUS files. + * + * @deprecated Superseded by {@link InfinityAmpPlus}. + */ +@Deprecated public final class InfinityAmp extends ChildFrame implements ActionListener, ListSelectionListener, Runnable, Closeable { private final SimpleListModel allMusModel = new SimpleListModel<>(); diff --git a/src/org/infinity/gui/InfinityAmpPlus.java b/src/org/infinity/gui/InfinityAmpPlus.java new file mode 100644 index 000000000..063d1f13a --- /dev/null +++ b/src/org/infinity/gui/InfinityAmpPlus.java @@ -0,0 +1,2816 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.gui; + +import java.awt.Component; +import java.awt.Container; +import java.awt.Font; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.KeyEventDispatcher; +import java.awt.KeyboardFocusManager; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Random; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import java.util.prefs.Preferences; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.swing.AbstractAction; +import javax.swing.ButtonGroup; +import javax.swing.DefaultListCellRenderer; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JCheckBoxMenuItem; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JMenu; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.JScrollPane; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.ListSelectionModel; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.filechooser.FileFilter; +import javax.swing.filechooser.FileNameExtensionFilter; + +import org.infinity.NearInfinity; +import org.infinity.gui.ButtonPopupMenu.Align; +import org.infinity.icon.Icons; +import org.infinity.resource.Closeable; +import org.infinity.resource.Profile; +import org.infinity.resource.key.FileResourceEntry; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.resource.mus.Entry; +import org.infinity.resource.mus.MusResourceHandler; +import org.infinity.resource.sound.AudioStateEvent; +import org.infinity.resource.sound.AudioStateListener; +import org.infinity.resource.sound.StreamingAudioPlayer; +import org.infinity.util.InputKeyHelper; +import org.infinity.util.Logger; +import org.infinity.util.Misc; +import org.infinity.util.SimpleListModel; +import org.infinity.util.StopWatch; +import org.infinity.util.Threading; +import org.infinity.util.io.FileManager; +import org.infinity.util.tuples.Couple; + +/** + * A global music player for MUS files found in all supported IE games. It supersedes the original {@link InfinityAmp} + * music player. + */ +public class InfinityAmpPlus extends ChildFrame implements Closeable { + // static window title + private static final String TITLE_DEFAULT = "InfinityAmp"; + // format string for window title: resource name, directory + private static final String TITLE_FMT = "InfinityAmp - Playing %s (%s)"; + + // Time information for current soundtrack (elapsed time / total time) + private static final String PLAYTIME_FMT = "%02d:%02d / %02d:%02d"; + // Time information for current soundtrack (elapsed time only) + private static final String PLAYTIME_SHORT_FMT = "%02d:%02d"; + + // Title string for the available music list + private static final String TITLE_AVAILABLE_LIST = "Available Music"; + private static final String TITLE_AVAILABLE_LIST_FMT = "Available Music - %d item(s)"; + + // Title string for the playlist + private static final String TITLE_PLAY_LIST = "Playlist"; + private static final String TITLE_PLAY_LIST_FMT = "Playlist - %d item(s)"; + + // {@link Preferences} node for storing and restoring settings. + private static final String PREFS_NODE = "InfinityAmp"; + // Available preferences keys + private static final String PREFS_AVAILABLE_ENTRIES_COUNT = "AvailableEntryCount"; + private static final String PREFS_AVAILABLE_ENTRY_BASE = "AvailableEntry"; + private static final String PREFS_PLAYLIST_INDEX_COUNT = "PlaylistIndexCount"; + private static final String PREFS_PLAYLIST_INDEX_BASE = "PlaylistIndex"; + private static final String PREFS_PLAYLIST_SELECTED_INDEX = "PlaylistSelectedIndex"; + private static final String PREFS_FILTER_SOUND_COUNT = "FilterSoundCount"; + private static final String PREFS_FILTER_SOUND_BASE = "FilterSound"; + private static final String PREFS_FILTER_SOUND_ENABLED = "FilterSoundEnabled"; + private static final String PREFS_OPTION_CALC_DURATIONS = "CalculateDurations"; + private static final String PREFS_OPTION_USE_MEDIA_KEYS = "UseMediaKeys"; + private static final String PREFS_OPTION_LOOP = "Loop"; + private static final String PREFS_OPTION_SHUFFLE = "Shuffle"; + private static final String PREFS_VIEW_PREFIX = "ViewPathAsPrefix"; + private static final String PREFS_WINDOW_X = "WindowX"; + private static final String PREFS_WINDOW_Y = "WindowY"; + private static final String PREFS_WINDOW_WIDTH = "WindowWidth"; + private static final String PREFS_WINDOW_HEIGHT = "WindowHeight"; + + private final SimpleListModel availableListModel = new SimpleListModel<>(); + private final SimpleListModel playListModel = new SimpleListModel<>(); + private final DefaultListCellRenderer availableListRenderer = new IndexedCellRenderer(true); + private final DefaultListCellRenderer playListRenderer = new IndexedCellRenderer(true); + private final JList availableList = new JList<>(availableListModel); + private final JList playList = new JList<>(playListModel); + + private final TreeSet exclusionFilter = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + + private final JLabel availableListTitleLabel = new JLabel(TITLE_AVAILABLE_LIST); + private final JLabel playListTitleLabel = new JLabel(TITLE_PLAY_LIST); + + private final JButton toPlayListButton = new JButton(Icons.ICON_FORWARD_16.getIcon()); + private final JButton fromPlayListButton = new JButton(Icons.ICON_BACK_16.getIcon()); + private final JButton moveUpButton = new JButton(Icons.ICON_UP_16.getIcon()); + private final JButton moveDownButton = new JButton(Icons.ICON_DOWN_16.getIcon()); + + private final ButtonPopupMenu bpmAddMusic = new ButtonPopupMenu("Add..."); + private final JMenuItem miAddMusicCurrent = new JMenuItem("Music from current game"); + private final JMenuItem miAddMusicExternal = new JMenuItem("Music from directory..."); + private final JButton removeMusicButton = new JButton("Remove"); + private final JButton clearMusicButton = new JButton("Clear"); + + private final JButton importPlayListButton = new JButton("Import...", Icons.ICON_IMPORT_16.getIcon()); + private final JButton exportPlayListButton = new JButton("Export...", Icons.ICON_EXPORT_16.getIcon()); + + private final JButton prevMusicButton = new JButton(Icons.ICON_STEP_BACK_16.getIcon()); + private final JButton nextMusicButton = new JButton(Icons.ICON_STEP_FORWARD_16.getIcon()); + private final JButton playMusicButton = new JButton(Icons.ICON_PLAY_16.getIcon()); + private final JButton stopMusicButton = new JButton(Icons.ICON_STOP_16.getIcon()); + + private final JLabel elapsedTimeLabel = new JLabel(); + + private final JTextField tfMusicPath = new JTextField(5); + private final JCheckBox cbLoop = new JCheckBox("Loop"); + private final JCheckBox cbShuffle = new JCheckBox("Shuffle"); + + private final JCheckBoxMenuItem cbmiEnableExclusionFilter = new JCheckBoxMenuItem("Enable sound filter"); + private final JMenuItem miDefineExclusionFilter = new JMenuItem("Define sound filter..."); + private final JCheckBoxMenuItem cbmiCalcDurations = new JCheckBoxMenuItem("Calculate sound durations"); + private final JCheckBoxMenuItem cbmiUseMediaKeys = new JCheckBoxMenuItem("Allow media keys for playback"); + private final JRadioButtonMenuItem rbmiPathAsSuffix = new JRadioButtonMenuItem("Format: ()"); + private final JRadioButtonMenuItem rbmiPathAsPrefix = new JRadioButtonMenuItem("Format: () "); + + // Stores the previously played back MUS entries if Shuffle mode is enabled + private final List undoPlayListItems = new ArrayList<>(); + private final Listeners listeners = new Listeners(); + private final Random random = new Random(); + private final StopWatch timer = new StopWatch(1000L, false); + + private final StreamingAudioPlayer player; + + private MusResourceHandler musHandler; + private boolean requestNextItem = false; + + public InfinityAmpPlus() { + super(TITLE_DEFAULT, false, false); + player = initPlayer(true); + init(); + } + + @Override + public void close() { + closePlayer(); + timer.close(); + Entry.clearCache(); + KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(listeners::dispatchKeyEvent); + savePreferences(); + } + + @Override + protected void gameReset(boolean refreshOnly) { + // Update the window content to reflect a potentially changed game configuration + refreshAllResourceEntries(); + } + + /** Scans available music list and playlist for non-existing files. */ + public void refreshAllResourceEntries() { + for (int i = availableListModel.size() - 1; i >= 0; i--) { + final MusicResourceEntry mre = availableListModel.get(i); + if (Files.isRegularFile(mre.getActualPath())) { + availableListModel.set(i, mre); + } else { + availableListModel.remove(i); + } + } + + for (int i = playListModel.size() - 1; i >= 0; i--) { + final MusicResourceEntry mre = playListModel.get(i); + if (Files.isRegularFile(mre.getActualPath())) { + playListModel.set(i, mre); + } else { + playListModel.remove(i); + } + } + } + + /** Returns the currently selected "path-as-prefix" state for list items. */ + public boolean isPathAsPrefix() { + return rbmiPathAsPrefix.isSelected(); + } + + /** Updates all {@link MusicResourceEntry} instances in both lists with the specified display option. */ + public void updatePathAsPrefix(boolean b) { + for (int i = 0, size = availableListModel.size(); i < size; i++) { + final MusicResourceEntry mre = availableListModel.get(i); + mre.setPathAsPrefix(b); + availableListModel.set(i, mre); + } + + for (int i = 0, size = playListModel.size(); i < size; i++) { + final MusicResourceEntry mre = playListModel.get(i); + mre.setPathAsPrefix(b); + playListModel.set(i, mre); + } + } + + /** Returns whether playback is currently active. Paused state does not affect the result. */ + public boolean isPlaying() { + return player.isPlaying(); + } + + public synchronized void setPlaying(boolean play) { + if (play != isPlaying()) { + if (play) { + player.setPlaying(true); + } else { + player.setPlaying(false); + player.clearAudioQueue(); + } + } + } + + /** + * Returns whether playback is currently paused. + * + * @return {@code true} only if playback is enabled but paused, {@code false} otherwise. + */ + public boolean isPaused() { + return player.isPaused(); + } + + public synchronized void setPaused(boolean pause) { + player.setPaused(pause); + } + + /** Returns whether Loop mode is enabled. */ + public boolean isLoopEnabled() { + return cbLoop.isSelected(); + } + + /** Specifies whether Loop mode is enabled. */ + public void setLoopEnabled(boolean b) { + if (b != cbLoop.isSelected()) { + cbLoop.setSelected(b); + updatePlayListButtons(); + } + } + + /** Returns whether Shuffle mode is enabled. */ + public boolean isShuffleEnabled() { + return cbShuffle.isSelected(); + } + + /** Specifies whether Shuffle mode is enabled. */ + public void setShuffleEnabled(boolean b) { + if (b != cbShuffle.isSelected()) { + cbShuffle.setSelected(b); + updatePlayListButtons(); + clearUndoList(); + } + } + + /** Returns whether duration of soundtrack are calculated and displayed in the song lists. */ + public boolean isAutoCalcDurationsEnabled() { + return cbmiCalcDurations.isSelected(); + } + + /** Specifies whether duration of soundtrack are calculated and displayed in the song lists. */ + public void setAutoCalcDurationsEnabled(boolean b) { + if (b != cbmiCalcDurations.isSelected()) { + cbmiCalcDurations.setSelected(b); + if (cbmiCalcDurations.isSelected()) { + calculateDurations(); + } + } + } + + /** Returns whether media keys on the keyboard can be used for music playback controls. */ + public boolean isMediaKeysEnabled() { + return cbmiUseMediaKeys.isSelected(); + } + + /** Specifies whether media keys on the keyboard can be used for music playback controls. */ + public void setMediaKeysEnabled(boolean b) { + if (b != cbmiUseMediaKeys.isSelected()) { + cbmiUseMediaKeys.setSelected(b); + } + } + + /** + * Sets the playlist to the first selected entry. Otherwise, it selects the first playlist item. + * + * @return Index of the selected playlist item if available, -1 otherwise. + */ + public int setCurrentPlayListItem() { + int newIndex = playList.getSelectedIndex(); + if (newIndex < 0 && !playListModel.isEmpty()) { + newIndex = 0; + } + playList.setSelectedIndex(newIndex); + playList.ensureIndexIsVisible(newIndex); + return playList.getSelectedIndex(); + } + + /** + * Sets the playlist to the next music entry if available. + * + * @return Next playlist item index if available, -1 otherwise. + */ + public int setNextPlayListItem() { + int newIndex; + if (isShuffleEnabled()) { + int curIndex = playList.getSelectedIndex(); + newIndex = curIndex; + while (playListModel.size() > 1 && newIndex == curIndex) { + newIndex = random.nextInt(playListModel.size()); + } + if (curIndex >= 0) { + undoPlayListItems.add(curIndex); + } + } else { + newIndex = playList.getSelectedIndex() + 1; + if (isLoopEnabled()) { + newIndex %= playListModel.size(); + } + } + + if (newIndex >= 0 && newIndex < playListModel.size()) { + playList.setSelectedIndex(newIndex); + playList.ensureIndexIsVisible(newIndex); + updateWindowTitle(); + } else { + newIndex = -1; + } + + return newIndex; + } + + /** + * Sets the playlist to the previous music entry if available. For shuffled playback this entry is fetched from + * an undo buffer. Otherwise the entry is calculated. + * + * @return Previous playlist item index if available, -1 otherwise. + */ + public int setPrevPlayListItem() { + int newIndex; + if (isShuffleEnabled()) { + // first try to restore previous selections + if (!undoPlayListItems.isEmpty()) { + newIndex = undoPlayListItems.remove(undoPlayListItems.size() - 1); + } else { + int curIndex = playList.getSelectedIndex(); + newIndex = curIndex; + while (playListModel.size() > 1 && newIndex == curIndex) { + newIndex = random.nextInt(playListModel.size()); + } + } + } else { + newIndex = playList.getSelectedIndex() - 1; + if (isLoopEnabled() && newIndex < 0) { + newIndex = playListModel.size() - 1; + } + } + + if (newIndex >= 0 && newIndex < playListModel.size()) { + playList.setSelectedIndex(newIndex); + playList.ensureIndexIsVisible(newIndex); + updateWindowTitle(); + } else { + newIndex = -1; + } + + return newIndex; + } + + /** Returns an unmodifiable set of sound filter items. */ + public Set getExclusionFilters() { + return Collections.unmodifiableSet(exclusionFilter); + } + + /** + * Adds the specified sound filter to the exclusion list. + * + * @param filter Sound filter string without file extension. + * @return {@code true} if the filter was added, {@code false} otherwise. + */ + public boolean addExclusionFilter(String filter) { + boolean retVal = false; + + if (filter != null) { + final int pos = filter.lastIndexOf('.'); + if (pos >= 0 && filter.substring(pos).equalsIgnoreCase(".acm")) { + filter = filter.substring(0, pos); + } + + filter = filter.trim(); + if (!filter.isEmpty()) { + retVal = exclusionFilter.add(filter); + } + } + + return retVal; + } + + /** Replaces the current sound filters by the specified collection of filter items. */ + public int setExclusionFilters(Collection filterSet) { + int retVal = 0; + exclusionFilter.clear(); + if (filterSet != null) { + for (final String filter : filterSet) { + if (addExclusionFilter(filter)) { + retVal++; + } + } + } + return retVal; + } + + /** Calculates duration of all available song entries. */ + private void calculateDurations() { + for (int i = 0, size = availableListModel.size(); i < size; i++) { + availableListModel.get(i).calculateDuration(); + } + } + + /** Used internally to check if a request for playing back a new playlist item has been made. */ + private boolean isNextItemRequested() { + return requestNextItem; + } + + /** Used internally to signal that playback should restart at the currently selected playlist item. */ + private synchronized void requestNextItem() { + if (isPlaying() && !isNextItemRequested()) { + requestNextItem = true; + setPlaying(false); + } + } + + /** Clears the request for playback of a new playlist item. */ + private synchronized void acceptNextItem() { + if (isNextItemRequested()) { + requestNextItem = false; + setPlaying(true); + } + } + + /** + * Finds the specified {@link MusicResourceEntry} in the available music list and returns the item index. + * + * @param entry {@link MusicResourceEntry} to find. + * @return Index of the list item if found, -1 otherwise. + */ + private int getAvailableListIndex(MusicResourceEntry entry) { + int retVal = -1; + + if (entry != null) { + for (int i = 0, size = availableListModel.size(); i < size; i++) { + if (entry.equals(availableListModel.get(i))) { + retVal = i; + break; + } + } + } + + return retVal; + } + + /** + * Finds the specified {@link MusicResourceEntry} in the playlist and returns the item index. + * + * @param entry {@link MusicResourceEntry} to find. + * @return Index of the list item if found, -1 otherwise. + */ + @SuppressWarnings("unused") + private int getPlayListIndex(MusicResourceEntry entry) { + int retVal = -1; + + if (entry != null) { + for (int i = 0, size = playListModel.size(); i < size; i++) { + if (entry.equals(playListModel.get(i))) { + retVal = i; + break; + } + } + } + + return retVal; + } + + /** + * Adds the specified item from the available music list to the playlist. + * + * @param index Item index from the available music list to add. + * @return {@code true} if the item was successfully added, {@code false} otherwise. + */ + private boolean addToPlayList(int index) { + if (!playList.isEnabled() || index < 0 || index >= availableListModel.size()) { + return false; + } + + final MusicResourceEntry mre = availableListModel.get(index); + playListModel.add(mre); + updatePlayListButtons(); + updatePlayListTitle(); + return true; + } + + /** + * Removes the specified item from the playlist. + * + * @param index Item index from the playlist to remove. + * @return {@code true} if the item was successfully removed, {@code false} otherwise. + */ + private boolean removeFromPlayList(int index) { + if (!playList.isEnabled() || index < 0 || index >= playListModel.size()) { + return false; + } + + int curSelected = playList.getSelectedIndex(); + if (index == curSelected) { + if (curSelected < playListModel.size() - 1) { + playList.setSelectedIndex(curSelected + 1); + } else if (curSelected > 0) { + playList.setSelectedIndex(curSelected - 1); + } + } + + playListModel.remove(index); + updatePlayListButtons(); + updatePlayListTitle(); + return true; + } + + /** Moves all selected items in the playlist up by one position. */ + private void moveItemsUp() { + if (playList.isEnabled()) { + final int[] indices = playList.getSelectedIndices(); + if (indices.length == 0 || indices[0] <= 0) { + return; + } + + for (int i = 0; i < indices.length; i++) { + final MusicResourceEntry mre = playListModel.remove(indices[i]); + indices[i]--; + playListModel.add(indices[i], mre); + } + playList.setSelectedIndices(indices); + } + } + + /** Moves all selected items in the playlist down by one position. */ + private void moveItemsDown() { + if (playList.isEnabled()) { + final int[] indices = playList.getSelectedIndices(); + if (indices.length == 0 || indices[indices.length - 1] >= playListModel.size() - 1) { + return; + } + + for (int i = indices.length - 1; i >= 0; i--) { + final MusicResourceEntry mre = playListModel.remove(indices[i]); + indices[i]++; + playListModel.add(indices[i], mre); + } + playList.setSelectedIndices(indices); + } + } + + private void clearUndoList() { + undoPlayListItems.clear(); + } + + /** + * Functionally the same as {@link #addMusFiles(Path)} but also provides user feedback. + * + * @param musicDir Directory path of the game or "music" subfolder. + */ + private void addMusFilesInteractive(Path musicDir) { + try { + // change to "music" dir if needed + if (musicDir != null && !musicDir.getFileName().toString().equalsIgnoreCase("music")) { + final Path path = FileManager.queryExisting(musicDir, "music"); + if (path != null) { + musicDir = path; + } + } + + int count = addMusFiles(musicDir); + if (count == 0) { + JOptionPane.showMessageDialog(this, "No MUS files added.", "Information", JOptionPane.INFORMATION_MESSAGE); + } + } catch (Exception ex) { + Logger.error(ex); + JOptionPane.showMessageDialog(this, ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } + } + + /** Adds MUS files from the specified directory to the available music list. */ + private int addMusFiles(Path musicDir) throws Exception { + if (musicDir == null) { + return 0; + } + + if (!Files.isDirectory(musicDir)) { + throw new IOException("Directory does not exist: " + musicDir); + } + + try (final Stream fstream = Files.list(musicDir)) { + final List fileList = fstream + .map(file -> { + if (Files.isRegularFile(file) && file.getFileName().toString().toLowerCase().endsWith(".mus") && + !inAvailableList(file)) { + try { + return new MusicResourceEntry(file, isPathAsPrefix()); + } catch (Exception e) { + Logger.trace(e); + } + } + return null; + }) + .filter(Objects::nonNull) + .sorted((item1, item2) -> item1.getResourceName().compareToIgnoreCase(item2.getResourceName())) + .collect(Collectors.toList()); + + for (final MusicResourceEntry mre : fileList) { + availableListModel.add(mre); + if (isAutoCalcDurationsEnabled()) { + mre.calculateDuration(); + } + } + return fileList.size(); + } + } + + private boolean removeFromAvailableList(int index) { + if (!availableList.isEnabled() || index < 0 || index >= availableListModel.size()) { + return false; + } + + int curSelected = availableList.getSelectedIndex(); + if (index == curSelected) { + if (curSelected < availableListModel.size() - 1) { + availableList.setSelectedIndex(curSelected + 1); + } else { + availableList.setSelectedIndex(curSelected - 1); + } + } + + final MusicResourceEntry mre = availableListModel.get(index); + removeMusEntry(mre); + + updateAvailableListButtons(); + updateAvailableListTitle(); + updatePlayListTitle(); + return true; + } + + /** Returns {@code true} if the specified MUS file path is already present in the list of available MUS entries. */ + private boolean inAvailableList(Path musFile) { + boolean retVal = false; + if (musFile == null) { + return retVal; + } + + for (int i = 0, size = availableListModel.size(); i < size; i++) { + final MusicResourceEntry mre = availableListModel.get(i); + if (musFile.equals(mre.getActualPath())) { + retVal = true; + break; + } + } + + return retVal; + } + + /** Removes the specified {@link MusicResourceEntry} instance from both lists. */ + private boolean removeMusEntry(MusicResourceEntry entry) { + boolean retVal = false; + + if (entry != null) { + retVal = availableListModel.remove(entry); + if (retVal) { + playListModel.remove(entry); + } + } + + return retVal; + } + + /** Allows the user to choose a M3U playlist path to export the current music list. */ + private boolean exportPlayListInteractive() throws Exception { + final JFileChooser fc = new JFileChooser(Profile.getGameRoot().toFile()); + for (final FileFilter ff : fc.getChoosableFileFilters()) { + fc.removeChoosableFileFilter(ff); + } + final FileFilter m3uFilter = new FileNameExtensionFilter("M3U files (*.m3u)", "m3u"); + final FileFilter m3u8Filter = new FileNameExtensionFilter("M3U8 files (*.m3u8)", "m3u8"); + fc.addChoosableFileFilter(m3uFilter); + fc.addChoosableFileFilter(m3u8Filter); + fc.setFileFilter(m3u8Filter); + + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setMultiSelectionEnabled(false); + fc.setDialogTitle("Export playlist"); + final int retVal = fc.showSaveDialog(this); + if (retVal == JFileChooser.APPROVE_OPTION) { + Path file = fc.getSelectedFile().toPath(); + final String fileName = file.getFileName().toString(); + final String ext = (fc.getFileFilter() == m3uFilter) ? "m3u" : "m3u8"; + final Charset ch = (fc.getFileFilter() == m3uFilter) ? StandardCharsets.ISO_8859_1 : StandardCharsets.UTF_8; + if (fileName.lastIndexOf('.') < 0) { + file = file.getParent().resolve(fileName + "." + ext); + } + exportPlayList(file, ch); + return true; + } + return false; + } + + /** + * Exports MUS entries to the specified playlist file. + * + * @param playlist {@link Path} of the M3U playlist file to export. + * @param cs The character set to use when encoding playlist content. + * @throws Exception if the playlist could not be exported. + */ + private void exportPlayList(Path playlist, Charset cs) throws Exception { + final Playlist pl = new Playlist(); + for (int i = 0, size = availableListModel.size(); i < size; i++) { + final MusicResourceEntry mre = availableListModel.get(i); + final int length = mre.isDurationAvailable() ? (int) mre.getDuration() : -1; + pl.addEntry(mre.getActualPath(), length); + } + pl.exportFile(playlist, cs); + } + + /** Allows the user to choose a M3U playlist that will be imported to the player. */ + private boolean importPlayListInteractive() throws Exception { + final JFileChooser fc = new JFileChooser(Profile.getGameRoot().toFile()); + final FileFilter m3uFilter = new FileNameExtensionFilter("Playlist files (*.m3u, *.m3u8)", "m3u", "m3u8"); + fc.addChoosableFileFilter(m3uFilter); + fc.setFileFilter(m3uFilter); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setMultiSelectionEnabled(false); + fc.setDialogTitle("Import playlist"); + final int retVal = fc.showOpenDialog(this); + if (retVal == JFileChooser.APPROVE_OPTION) { + importPlayList(fc.getSelectedFile().toPath()); + return true; + } + + return false; + } + + /** Imports MUS files from the specified M3U playlist. Returns number of imported song entries. */ + private int importPlayList(Path playlist) throws Exception { + int retVal; + + final Playlist pl = new Playlist(playlist); + playListModel.clear(); + availableListModel.clear(); + for (int i = 0; i < pl.getEntriesCount(); i++) { + final int length = pl.getEntryLength(i); + String pathString = pl.getEntryPath(i).toString(); + if (length >= 0) { + pathString += ";" + length; + } + final MusicResourceEntry mre = getMusicResource(pathString, isPathAsPrefix()); + if (mre != null) { + availableListModel.add(mre); + if (isAutoCalcDurationsEnabled()) { + mre.calculateDuration(); + } + } + } + if (!availableListModel.isEmpty()) { + availableList.setSelectedIndex(0); + availableList.ensureIndexIsVisible(0); + } + + retVal = pl.getEntriesCount(); + + return retVal; + } + + /** + * Returns whether the specified MUS sound segment should be skipped (i.e. excluded from playback). + * + * @param musEntry {@link Entry} to test. + * @return {@code true} if {@code musEntry} is listed in the exclusion list or the argument is {@code null}. + * {@code false} otherwise. + */ + private boolean isSoundExcluded(Entry musEntry) { + return (musEntry == null) || exclusionFilter.contains(musEntry.getName()); + } + + /** Forces the specified resource entry to be updated in available music list and playlist. */ + private void refreshResourceEntry(MusicResourceEntry entry) { + if (entry != null) { + for (int i = 0, size = availableListModel.size(); i < size; i++) { + if (entry.equals(availableListModel.get(i))) { + final MusicResourceEntry mre = availableListModel.get(i); + availableListModel.set(i, mre); + break; + } + } + for (int i = 0, size = playListModel.size(); i < size; i++) { + if (entry.equals(playListModel.get(i))) { + final MusicResourceEntry mre = playListModel.get(i); + playListModel.set(i, mre); + break; + } + } + } + } + + /** Updates the item transfer buttons based on the current state of available music list and playlist. */ + private void updateTransferButtons() { + if (isPlaying()) { + toPlayListButton.setEnabled(false); + fromPlayListButton.setEnabled(false); + moveUpButton.setEnabled(false); + moveDownButton.setEnabled(false); + } else if (!isNextItemRequested()) { + toPlayListButton.setEnabled(!availableList.getSelectionModel().isSelectionEmpty()); + fromPlayListButton.setEnabled(!playList.getSelectionModel().isSelectionEmpty()); + + final int[] indices = playList.getSelectedIndices(); + if (indices.length == 0) { + moveUpButton.setEnabled(false); + moveDownButton.setEnabled(false); + } else { + moveUpButton.setEnabled(indices[0] > 0); + moveDownButton.setEnabled(indices[indices.length - 1] < playList.getModel().getSize() - 1); + } + } + } + + /** Updates the item management buttons for the available music list. */ + private void updateAvailableListButtons() { + bpmAddMusic.setEnabled(!isPlaying()); + removeMusicButton.setEnabled(!isPlaying() && !availableList.getSelectionModel().isSelectionEmpty()); + clearMusicButton.setEnabled(!isPlaying() && !availableListModel.isEmpty()); + importPlayListButton.setEnabled(!isPlaying()); + exportPlayListButton.setEnabled(!availableListModel.isEmpty()); + } + + /** Updates the available music list title. */ + private void updateAvailableListTitle() { + if (availableListModel.isEmpty()) { + availableListTitleLabel.setText(TITLE_AVAILABLE_LIST); + } else { + availableListTitleLabel.setText(String.format(TITLE_AVAILABLE_LIST_FMT, availableListModel.size())); + } + } + + /** Updates the media buttons for the playlist. */ + private void updatePlayListButtons() { + if (!isPlayerAvailable()) { + prevMusicButton.setEnabled(false); + nextMusicButton.setEnabled(false); + playMusicButton.setEnabled(false); + stopMusicButton.setEnabled(false); + return; + } + + final int[] indices = playList.getSelectedIndices(); + if (playListModel.isEmpty() || indices.length == 0) { + prevMusicButton.setEnabled(false); + nextMusicButton.setEnabled(false); + playMusicButton.setEnabled(!playListModel.isEmpty()); + stopMusicButton.setEnabled(false); + } else { + prevMusicButton.setEnabled(isLoopEnabled() || isShuffleEnabled() || indices[0] > 0); + nextMusicButton.setEnabled(isLoopEnabled() || isShuffleEnabled() || indices[0] < playListModel.size() - 1); + playMusicButton.setEnabled(true); + if (isPlaying() && !isPaused()) { + playMusicButton.setIcon(Icons.ICON_PAUSE_16.getIcon()); + } else { + playMusicButton.setIcon(Icons.ICON_PLAY_16.getIcon()); + } + stopMusicButton.setEnabled(isPlaying()); + } + } + + /** Updates the playlist title. */ + private void updatePlayListTitle() { + if (playListModel.isEmpty()) { + playListTitleLabel.setText(TITLE_PLAY_LIST); + } else { + playListTitleLabel.setText(String.format(TITLE_PLAY_LIST_FMT, playListModel.size())); + } + } + + /** Updates the MUS file info field depending on current list selection. */ + private void updateFileInfo() { + final int[] indices = availableList.getSelectedIndices(); + if (indices.length == 1 && indices[0] >= 0) { + final MusicResourceEntry mre = availableListModel.get(indices[0]); + tfMusicPath.setText(mre.getActualPath().toString()); + } else { + tfMusicPath.setText(""); + } + } + + /** + * Updates the display for elapsed and total playing time. + * + * @param resourceEntry {@link MusicResourceEntry} to be played back. Specify {@code null} to reset the time display. + */ + private void updateTimeDisplay(MusicResourceEntry resourceEntry) { + if (resourceEntry != null) { + final int elapsed = (int) StopWatch.toSeconds(timer.elapsed()); + final int elapsedMin = elapsed / 60; + final int elapsedSec = elapsed % 60; + if (resourceEntry.isDurationAvailable()) { + final int duration = resourceEntry.getDurationSeconds(); + final int totalMin = duration / 60; + final int totalSec = duration % 60; + elapsedTimeLabel.setText(String.format(PLAYTIME_FMT, elapsedMin, elapsedSec, totalMin, totalSec)); + } else { + elapsedTimeLabel.setText(String.format(PLAYTIME_SHORT_FMT, elapsedMin, elapsedSec)); + } + } else { + // default display + elapsedTimeLabel.setText(String.format(PLAYTIME_FMT, 0, 0, 0, 0)); + } + } + + /** Updates the window title. Displays currently playing title if available. */ + private void updateWindowTitle() { + String title = TITLE_DEFAULT; + + if (isPlaying()) { + int index = playList.getSelectedIndex(); + if (index >= 0 && index < playListModel.size()) { + final MusicResourceEntry mre = playListModel.get(index); + title = String.format(TITLE_FMT, mre.getResourceName(), mre.getLocation()); + } + } + + setTitle(title); + } + + /** + * Used internally to handle UI-related updates depending on the specified playback state. + * + * @param playing Specify {@code true} if audio playback started, and {@code false} if playback stopped. + */ + private void onAudioPlaying(boolean playing) { + if (playing) { + timer.reset(); + timer.resume(); + availableList.setEnabled(false); + playList.setEnabled(false); + elapsedTimeLabel.setEnabled(true); + updateAvailableListButtons(); + updatePlayListButtons(); + updateTransferButtons(); + updateTimeDisplay(playList.getSelectedValue()); + updateWindowTitle(); + } else { + timer.pause(); + timer.reset(); + availableList.setEnabled(true); + playList.setEnabled(true); + elapsedTimeLabel.setEnabled(false); + updateWindowTitle(); + updateTimeDisplay(null); + updateAvailableListButtons(); + updatePlayListButtons(); + updateTransferButtons(); + } + } + + /** Called when the audio player triggers an {@code OPEN} event. */ + private void handleAudioOpenEvent(Object value) { + // nothing to do + } + + /** Called when the audio player triggers a {@code CLOSE} event. */ + private void handleAudioCloseEvent(Object value) { + // nothing to do + } + + /** Called when the audio player triggers a {@code START} event. */ + private void handleAudioStartEvent() { + onAudioPlaying(true); + } + + /** Called when the audio player triggers a {@code STOP} event. */ + private void handleAudioStopEvent() { + if (musHandler != null) { + try { + musHandler.close(); + } catch (Exception e) { + Logger.error(e); + } + musHandler = null; + } + + if (isNextItemRequested()) { + acceptNextItem(); + } else { + onAudioPlaying(false); + } + } + + /** Called when the audio player triggers a {@code PAUSE} event. */ + private void handleAudioPauseEvent(Object value) { + timer.pause(); + updatePlayListButtons(); + } + + /** Called when the audio player triggers a {@code RESUME} event. */ + private void handleAudioResumeEvent(Object value) { + timer.resume(); + updatePlayListButtons(); + } + + /** Called when the audio player triggers a {@code BUFFER_EMPTY} event. */ + private void handleAudioBufferEmptyEvent(Object value) { + if (musHandler == null) { + // loading currently selected MUS resource + int index = setCurrentPlayListItem(); + if (index < 0) { + setPlaying(false); + return; + } + + try { + musHandler = new MusResourceHandler(playListModel.get(index), 0, true, false); + } catch (Exception e) { + handleAudioErrorEvent(e); + return; + } + } + + boolean advanced; + for (advanced = musHandler.advance(); advanced; advanced = musHandler.advance()) { + if (!isSoundExcluded(musHandler.getCurrentEntry())) { + player.addAudioBuffer(musHandler.getAudioBuffer()); + break; + } + } + + if (!advanced) { + if (setNextPlayListItem() >= 0) { + requestNextItem(); + } else { + setPlaying(false); + } + } + } + + /** Called when the audio player triggers an {@code ERROR} event. */ + private void handleAudioErrorEvent(Object value) { + requestNextItem = false; + setPlaying(false); + onAudioPlaying(false); + + final Exception e = (value instanceof Exception) ? (Exception)value : null; + if (e != null) { + Logger.error(e); + } + + final String msg = (e != null) ? "Error during playback:\n" + e.getMessage() : "Error during playback."; + JOptionPane.showMessageDialog(this, msg, "Error", JOptionPane.ERROR_MESSAGE); + } + + private void init() { + availableList.setCellRenderer(availableListRenderer); + playList.setCellRenderer(playListRenderer); + + loadPreferences(); + + addComponentListener(listeners); + + availableList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + availableList.addListSelectionListener(listeners); + availableList.addMouseListener(listeners); + availableList.addKeyListener(listeners); + if (!availableListModel.isEmpty()) { + availableList.setSelectedIndex(0); + availableList.ensureIndexIsVisible(0); + } + final JScrollPane availableScroll = new JScrollPane(availableList); + availableScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + availableScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + + playList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + playList.addListSelectionListener(listeners); + playList.addMouseListener(listeners); + playList.addKeyListener(listeners); + if (!playListModel.isEmpty()) { + playList.setSelectedIndex(0); + playList.ensureIndexIsVisible(0); + } + final JScrollPane playlistScroll = new JScrollPane(playList); + playlistScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + playlistScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + + toPlayListButton.addActionListener(listeners); + fromPlayListButton.addActionListener(listeners); + moveUpButton.addActionListener(listeners); + moveDownButton.addActionListener(listeners); + + bpmAddMusic.setIcon(Icons.ICON_ARROW_UP_15.getIcon()); + miAddMusicCurrent.addActionListener(listeners); + miAddMusicExternal.addActionListener(listeners); + bpmAddMusic.addItem(miAddMusicCurrent); + bpmAddMusic.addItem(miAddMusicExternal); + removeMusicButton.addActionListener(listeners); + clearMusicButton.addActionListener(listeners); + + if (isPlayerAvailable()) { + prevMusicButton.addActionListener(listeners); + playMusicButton.addActionListener(listeners); + stopMusicButton.addActionListener(listeners); + nextMusicButton.addActionListener(listeners); + } + + importPlayListButton.setToolTipText("Import music entries from M3U playlist file."); + importPlayListButton.addActionListener(listeners); + exportPlayListButton.setToolTipText("Export music entries to M3U playlist file."); + exportPlayListButton.addActionListener(listeners); + + cbLoop.addActionListener(listeners); + cbShuffle.addActionListener(listeners); + + tfMusicPath.setEditable(false); + + final Font monoFont = new Font(Font.MONOSPACED, Font.BOLD, elapsedTimeLabel.getFont().getSize() + 1); + elapsedTimeLabel.setFont(monoFont); + elapsedTimeLabel.setEnabled(false); + updateTimeDisplay(null); + timer.addActionListener(listeners); + + final JMenu viewMenu = new JMenu("Item View"); + viewMenu.setToolTipText("Display format of list items."); + viewMenu.add(rbmiPathAsPrefix); + viewMenu.add(rbmiPathAsSuffix); + + ButtonGroup bg = new ButtonGroup(); + bg.add(rbmiPathAsPrefix); + bg.add(rbmiPathAsSuffix); + rbmiPathAsPrefix.addItemListener(listeners); + rbmiPathAsSuffix.addItemListener(listeners); + + final JMenu filterMenu = new JMenu("Sound filter"); + filterMenu.setToolTipText("Sound segments to auto-skip at playback."); + filterMenu.add(cbmiEnableExclusionFilter); + filterMenu.add(miDefineExclusionFilter); + miDefineExclusionFilter.addActionListener(listeners); + + cbmiCalcDurations.addActionListener(listeners); + cbmiCalcDurations.setToolTipText("Enable to calculate total duration of listed music entries. (Operation may take a while.)"); + + // currently only supported on Windows platforms + cbmiUseMediaKeys.setEnabled(InputKeyHelper.isEnabled()); + if (cbmiUseMediaKeys.isEnabled()) { + cbmiUseMediaKeys.setToolTipText("Enable to use multimedia keys for music playback controls while the player window is open."); + } else { + cbmiUseMediaKeys.setToolTipText("Support for multimedia keys is not available on this platform."); + } + + final ButtonPopupMenu bpmOptions = new ButtonPopupMenu("Options"); + bpmOptions.setIcon(Icons.ICON_ARROW_DOWN_15.getIcon()); + bpmOptions.setMenuAlignment(Align.BOTTOM); + bpmOptions.addItem(viewMenu); + bpmOptions.addItem(filterMenu); + bpmOptions.addItem(cbmiCalcDurations); + bpmOptions.addItem(cbmiUseMediaKeys); + + final GridBagConstraints gbc = new GridBagConstraints(); + + for (final JButton button : new JButton[] { toPlayListButton, fromPlayListButton, moveUpButton, moveDownButton }) { + final Insets insets = button.getMargin(); + insets.top += 4; + insets.bottom += 4; + button.setMargin(insets); + } + + // buttons in vertical middle bar + final JPanel middleButtonsPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + middleButtonsPanel.add(bpmOptions, gbc); + ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(24, 0, 0, 0), 0, 0); + middleButtonsPanel.add(toPlayListButton, gbc); + ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + middleButtonsPanel.add(fromPlayListButton, gbc); + ViewerUtil.setGBC(gbc, 0, 3, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(24, 0, 0, 0), 0, 0); + middleButtonsPanel.add(moveUpButton, gbc); + ViewerUtil.setGBC(gbc, 0, 4, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + middleButtonsPanel.add(moveDownButton, gbc); + + // available music buttons + final JPanel availableButtonPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + availableButtonPanel.add(bpmAddMusic, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0), 0, 0); + availableButtonPanel.add(removeMusicButton, gbc); + ViewerUtil.setGBC(gbc, 2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0), 0, 0); + availableButtonPanel.add(clearMusicButton, gbc); + + // playlist buttons + final JPanel playlistButtonPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + playlistButtonPanel.add(prevMusicButton, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0), 0, 0); + playlistButtonPanel.add(playMusicButton, gbc); + ViewerUtil.setGBC(gbc, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0), 0, 0); + playlistButtonPanel.add(stopMusicButton, gbc); + ViewerUtil.setGBC(gbc, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0), 0, 0); + playlistButtonPanel.add(nextMusicButton, gbc); + + // playlist elapsed time + final JPanel playlistElapsedPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + final JLabel lElapsedLabel = new JLabel("Elapsed time:", SwingConstants.RIGHT); + playlistElapsedPanel.add(lElapsedLabel, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 8, 0, 0), 0, 0); + playlistElapsedPanel.add(elapsedTimeLabel, gbc); + + // available music file info + final JPanel availableInfoPanel = new JPanel(new GridBagLayout()); + final JLabel lInfo = new JLabel("File:"); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + availableInfoPanel.add(lInfo, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 8, 0, 0), 0, 0); + availableInfoPanel.add(tfMusicPath, gbc); + + // music file import/export buttons + final JPanel availableImportExportPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + availableImportExportPanel.add(importPlayListButton, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0), 0, 0); + availableImportExportPanel.add(exportPlayListButton, gbc); + + // playlist options + final JPanel playlistOptionsPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + playlistOptionsPanel.add(cbLoop, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 16, 0, 0), 0, 0); + playlistOptionsPanel.add(cbShuffle, gbc); + + // main panel + final JPanel mainPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + mainPanel.add(availableListTitleLabel, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + mainPanel.add(new JPanel(), gbc); + ViewerUtil.setGBC(gbc, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + mainPanel.add(playListTitleLabel, gbc); + + ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(availableScroll, gbc); + ViewerUtil.setGBC(gbc, 1, 1, 1, 1, 0.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.VERTICAL, + new Insets(8, 8, 0, 8), 0, 0); + mainPanel.add(middleButtonsPanel, gbc); + ViewerUtil.setGBC(gbc, 2, 1, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_END, GridBagConstraints.BOTH, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(playlistScroll, gbc); + + ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(4, 0, 0, 0), 0, 0); + mainPanel.add(availableInfoPanel, gbc); + ViewerUtil.setGBC(gbc, 1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + mainPanel.add(new JPanel(), gbc); + ViewerUtil.setGBC(gbc, 2, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(4, 0, 0, 0), 0, 0); + mainPanel.add(playlistElapsedPanel, gbc); + + ViewerUtil.setGBC(gbc, 0, 3, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(availableButtonPanel, gbc); + ViewerUtil.setGBC(gbc, 1, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + mainPanel.add(new JPanel(), gbc); + ViewerUtil.setGBC(gbc, 2, 3, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(playlistButtonPanel, gbc); + + ViewerUtil.setGBC(gbc, 0, 4, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(availableImportExportPanel, gbc); + ViewerUtil.setGBC(gbc, 1, 4, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + mainPanel.add(new JPanel(), gbc); + ViewerUtil.setGBC(gbc, 2, 4, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(playlistOptionsPanel, gbc); + + // content pane of the frame + final Container pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, + new Insets(8, 8, 8, 8), 0, 0); + pane.add(mainPanel, gbc); + + pack(); + setMinimumSize(getSize()); + + // restoring window size and position + final Rectangle bounds = loadWindowBounds(); + bounds.x = Math.max(0, bounds.x); + bounds.y = Math.max(0, bounds.y); + bounds.width = (bounds.width == 0) ? Misc.getScaledValue(650) : Math.max(getMinimumSize().width, bounds.width); + bounds.height = (bounds.height == 0) ? Misc.getScaledValue(450) : Math.max(getMinimumSize().height, bounds.height); + setBounds(bounds); + + updateAvailableListButtons(); + updatePlayListButtons(); + updateTransferButtons(); + updateAvailableListTitle(); + updatePlayListTitle(); + + KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(listeners::dispatchKeyEvent); + + setIconImage(Icons.ICON_MUSIC_16.getIcon().getImage()); + } + + /** + * Returns a new and fully initialized {@link StreamingAudioPlayer} instance. + * + * @param showError Whether to show an error message box if the player instance could not be created. + * @return {@link StreamingAudioPlayer} instance if successful, {@code null} otherwise. + */ + private StreamingAudioPlayer initPlayer(boolean showError) { + try { + return new StreamingAudioPlayer(listeners); + } catch (Exception e) { + Logger.error(e); + if (showError) { + JOptionPane.showMessageDialog(NearInfinity.getInstance(), + "Failed to initialize audio backend:\n" + e.getMessage() + "\n\nPlayback will be disabled.", + "Error", JOptionPane.ERROR_MESSAGE); + } + } + return null; + } + + /** + * Returns whether the global {@link StreamingAudioPlayer} instance is available. + * + * @return {@code true} if audio player instance is available, {@code false} otherwise. + */ + private boolean isPlayerAvailable() { + return Objects.nonNull(player); + } + + /** Closes the {@link StreamingAudioPlayer} instance. */ + private void closePlayer() { + if (!isPlayerAvailable()) { + return; + } + + try { + player.close(); + } catch (Exception e) { + Logger.error(e); + } + } + + /** + * Loads the window position and size from the preferences. + * + * @return {@link Rectangle} instances with position relative to the parent and size of the window. + */ + private Rectangle loadWindowBounds() { + final Rectangle retVal = new Rectangle(); + + final Preferences prefs = getPreferences(); + retVal.x = prefs.getInt(PREFS_WINDOW_X, 0); + retVal.y = prefs.getInt(PREFS_WINDOW_Y, 0); + retVal.width = prefs.getInt(PREFS_WINDOW_WIDTH, 0); + retVal.height = prefs.getInt(PREFS_WINDOW_HEIGHT, 0); + + return retVal; + } + + /** Loads the InfinityAmp configuration from the preferences. */ + private void loadPreferences() { + final Preferences prefs = getPreferences(); + + // restoring playback options + cbLoop.setSelected(prefs.getBoolean(PREFS_OPTION_LOOP, false)); + cbShuffle.setSelected(prefs.getBoolean(PREFS_OPTION_SHUFFLE, false)); + + cbmiCalcDurations.setSelected(prefs.getBoolean(PREFS_OPTION_CALC_DURATIONS, true)); + cbmiUseMediaKeys.setSelected(prefs.getBoolean(PREFS_OPTION_USE_MEDIA_KEYS, true) && InputKeyHelper.isEnabled()); + + // restoring list view options + if (prefs.getBoolean(PREFS_VIEW_PREFIX, true)) { + rbmiPathAsPrefix.setSelected(true); + } else { + rbmiPathAsSuffix.setSelected(true); + } + + // restoring sound exclusion filter + exclusionFilter.clear(); + final int numSounds = prefs.getInt(PREFS_FILTER_SOUND_COUNT, -1); + if (numSounds < 0) { + exclusionFilter.addAll(SoundFilterDialog.getDefaultSoundFilters()); + } else { + for (int i = 0; i < numSounds; i++) { + String sound = prefs.get(PREFS_FILTER_SOUND_BASE + i, "").trim(); + + // don't include file extension + final int sep = sound.indexOf('.'); + if (sep >= 0) { + sound = sound.substring(0, sep); + } + + if (!sound.isEmpty() && sound.length() <= 8) { + exclusionFilter.add(sound); + } + } + } + cbmiEnableExclusionFilter.setSelected(prefs.getBoolean(PREFS_FILTER_SOUND_ENABLED, true)); + + // restoring available music entries + availableListModel.clear(); + final int numAvailable = prefs.getInt(PREFS_AVAILABLE_ENTRIES_COUNT, 0); + for (int i = 0; i < numAvailable; i++) { + final MusicResourceEntry mre = getMusicResource(prefs.get(PREFS_AVAILABLE_ENTRY_BASE + i, null), + rbmiPathAsPrefix.isSelected()); + if (mre != null) { + availableListModel.add(mre); + if (isAutoCalcDurationsEnabled()) { + mre.calculateDuration(); + } + } + } + + // restoring playlist entries + playListModel.clear(); + final int numPlayList = prefs.getInt(PREFS_PLAYLIST_INDEX_COUNT, 0); + for (int i = 0; i < numPlayList; i++) { + final int index = prefs.getInt(PREFS_PLAYLIST_INDEX_BASE + i, -1); + if (index >= 0 && index < availableListModel.size()) { + playListModel.add(availableListModel.get(index)); + } + } + + // restoring selected playlist entry + if (!playListModel.isEmpty()) { + final int playListIndex = + Math.max(0, Math.min(playListModel.size() - 1, prefs.getInt(PREFS_PLAYLIST_SELECTED_INDEX, 0))); + SwingUtilities.invokeLater(() -> { + playList.setSelectedIndex(playListIndex); + playList.ensureIndexIsVisible(playListIndex); + }); + } + } + + /** Saves the current configuration to the preferences. */ + private void savePreferences() { + final Preferences prefs = getPreferences(); + + final Rectangle bounds = getBounds(); + prefs.putInt(PREFS_WINDOW_X, bounds.x); + prefs.putInt(PREFS_WINDOW_Y, bounds.y); + prefs.putInt(PREFS_WINDOW_WIDTH, bounds.width); + prefs.putInt(PREFS_WINDOW_HEIGHT, bounds.height); + + // clearing old available music entries + final int oldNumAvailable = prefs.getInt(PREFS_AVAILABLE_ENTRIES_COUNT, 0); + for (int i = 0; i < oldNumAvailable; i++) { + prefs.remove(PREFS_AVAILABLE_ENTRY_BASE + i); + } + prefs.putInt(PREFS_AVAILABLE_ENTRIES_COUNT, 0); + + // storing current available music entries + final int numAvailable = availableListModel.size(); + for (int i = 0; i < numAvailable; i++) { + final MusicResourceEntry mre = availableListModel.get(i); + String entry = mre.getActualPath().toString(); + // optional duration (ms) can be found after the file path + if (mre.isDurationAvailable()) { + entry += ";" + mre.getDuration(); + } + prefs.put(PREFS_AVAILABLE_ENTRY_BASE + i, entry); + } + prefs.putInt(PREFS_AVAILABLE_ENTRIES_COUNT, numAvailable); + + // clearing old playlist entries + final int oldNumPlayList = prefs.getInt(PREFS_PLAYLIST_INDEX_COUNT, 0); + for (int i = 0; i < oldNumPlayList; i++) { + prefs.remove(PREFS_PLAYLIST_INDEX_BASE + i); + } + prefs.putInt(PREFS_PLAYLIST_INDEX_COUNT, 0); + + // storing current playlist entries + int numPlayList = 0; + for (int i = 0, size = playListModel.size(); i < size; i++) { + final int index = getAvailableListIndex(playListModel.get(i)); + if (index >= 0) { + prefs.putInt(PREFS_PLAYLIST_INDEX_BASE + numPlayList, index); + numPlayList++; + } + } + prefs.putInt(PREFS_PLAYLIST_INDEX_COUNT, numPlayList); + + // storing selected playlist entry + final int playListIndex = playList.getSelectedIndex(); + prefs.putInt(PREFS_PLAYLIST_SELECTED_INDEX, playListIndex); + + // clearing old sound exclusion filter + final int oldNumSounds = prefs.getInt(PREFS_FILTER_SOUND_COUNT, 0); + for (int i = 0; i < oldNumSounds ; i++) { + prefs.remove(PREFS_FILTER_SOUND_BASE + i); + } + prefs.putInt(PREFS_FILTER_SOUND_COUNT, 0); + + // storing current sound exclusion entries + int numSounds = 0; + for (final String sound : exclusionFilter) { + if (!sound.isEmpty()) { + prefs.put(PREFS_FILTER_SOUND_BASE + numSounds, sound); + numSounds++; + } + } + prefs.putInt(PREFS_FILTER_SOUND_COUNT, numSounds); + prefs.putBoolean(PREFS_FILTER_SOUND_ENABLED, cbmiEnableExclusionFilter.isSelected()); + + prefs.putBoolean(PREFS_OPTION_CALC_DURATIONS, cbmiCalcDurations.isSelected()); + prefs.putBoolean(PREFS_OPTION_USE_MEDIA_KEYS, cbmiUseMediaKeys.isSelected()); + + // storing playback options + prefs.putBoolean(PREFS_OPTION_LOOP, cbLoop.isSelected()); + prefs.putBoolean(PREFS_OPTION_SHUFFLE, cbShuffle.isSelected()); + + // list view options + prefs.putBoolean(PREFS_VIEW_PREFIX, rbmiPathAsPrefix.isSelected()); + } + + /** Returns the {@link Preferences} instance for InfinityAmp. */ + private static Preferences getPreferences() { + final Preferences prefsRoot = Preferences.userNodeForPackage(InfinityAmpPlus.class); + return prefsRoot.node(PREFS_NODE); + } + + /** + * Returns a {@link MusicResourceEntry} object for the specified MUS file. + * + * @param musicFilePath {@link Path} to the MUS file. The path string can optionally be appended by the music duration + * in milliseconds, separated by semicolon. + * @return A {@link MusicResourceEntry} object for the MUS file. Returns {@code null} if not available. + */ + private static MusicResourceEntry getMusicResource(String musicFilePath, boolean pathAsPrefix) { + MusicResourceEntry retVal = null; + + if (musicFilePath != null) { + final String[] items = musicFilePath.split(";"); + if (items.length > 0) { + final Path musicPath = Paths.get(items[0]); + long duration = -1L; + if (items.length > 1) { + try { + duration = Long.parseLong(items[1]); + } catch (NumberFormatException e) { + Logger.debug(e); + } + } + if (Files.isRegularFile(musicPath)) { + try { + retVal = new MusicResourceEntry(musicPath, pathAsPrefix); + if (duration >= 0) { + retVal.setDuration(duration); + } + } catch (Exception e) { + Logger.error(e); + } + } + } + } + + return retVal; + } + + // -------------------------- INNER CLASSES -------------------------- + + private class Listeners implements ActionListener, ListSelectionListener, ItemListener, AudioStateListener, + MouseListener, KeyListener, ComponentListener { + public Listeners() { + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == timer) { + // Playback time + updateTimeDisplay(playList.getSelectedValue()); + } else if (e.getSource() == toPlayListButton) { + // Move item from available music list to playlist + final int[] indices = availableList.getSelectedIndices(); + for (final int index : indices) { + addToPlayList(index); + } + } else if (e.getSource() == fromPlayListButton) { + // remove item from playlist + final int[] indices = playList.getSelectedIndices(); + for (int i = indices.length - 1; i >= 0; i--) { + removeFromPlayList(indices[i]); + } + } else if (e.getSource() == moveUpButton) { + // move playlist item up by one position + moveItemsUp(); + } else if (e.getSource() == moveDownButton) { + // move playlist item down by one position + moveItemsDown(); + } else if (e.getSource() == miAddMusicCurrent) { + // add music of currently open game to the available music list + addMusFilesInteractive(Profile.getGameRoot()); + updateAvailableListTitle(); + } else if (e.getSource() == miAddMusicExternal) { + // add music from a selected directory to the available music list + final JFileChooser fc = new JFileChooser(); + fc.setDialogTitle("Select game folder"); + fc.setCurrentDirectory(Profile.getGameRoot().toFile()); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + if (fc.showOpenDialog(InfinityAmpPlus.this) == JFileChooser.APPROVE_OPTION) { + final Path folder = fc.getSelectedFile().toPath(); + addMusFilesInteractive(folder); + } + updateAvailableListTitle(); + } else if (e.getSource() == removeMusicButton) { + // remove selected items from the available music list + final int[] indices = availableList.getSelectedIndices(); + if (indices.length > 0) { + for (int i = indices.length - 1; i >= 0; i--) { + removeFromAvailableList(indices[i]); + } + } + } else if (e.getSource() == clearMusicButton) { + // remove all items from the available music list + int retVal = JOptionPane.showConfirmDialog(InfinityAmpPlus.this, "Remove all MUS entries?", "Question", + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (retVal == JOptionPane.YES_OPTION) { + playListModel.clear(); + availableListModel.clear(); + } + updateTransferButtons(); + updateAvailableListButtons(); + updatePlayListButtons(); + updateAvailableListTitle(); + updatePlayListTitle(); + } else if (e.getSource() == prevMusicButton) { + // jump to previously played music file + if (setPrevPlayListItem() >= 0) { + requestNextItem(); + } + updateTransferButtons(); + } else if (e.getSource() == nextMusicButton) { + // jump to next music file to play + if (setNextPlayListItem() >= 0) { + requestNextItem(); + } + updateTransferButtons(); + } else if (e.getSource() == playMusicButton) { + // play music from the playlist + if (isPlaying()) { + setPaused(!isPaused()); + } else { + setPlaying(true); + } + } else if (e.getSource() == stopMusicButton) { + // stop music playback + setPlaying(false); + } else if (e.getSource() == cbLoop) { + // Loop playback option has changed + updatePlayListButtons(); + } else if (e.getSource() == cbmiCalcDurations) { + // Shuffle playback option has changed + if (cbmiCalcDurations.isSelected()) { + calculateDurations(); + } + } else if (e.getSource() == miDefineExclusionFilter) { + // Manage list of sound exclusion filters + final Collection filters = + SoundFilterDialog.getSoundFiltersInteractive(InfinityAmpPlus.this, getExclusionFilters()); + if (filters != null) { + setExclusionFilters(filters); + } + } else if (e.getSource() == importPlayListButton) { + // Import M3U playlist file + try { + if (importPlayListInteractive()) { + updateAvailableListButtons(); + updatePlayListButtons(); + updateAvailableListTitle(); + updatePlayListTitle(); + } + } catch (Exception ex) { + Logger.error(ex); + JOptionPane.showMessageDialog(InfinityAmpPlus.this, "Failed to import playlist.\n" + ex.getMessage(), "Error", + JOptionPane.ERROR_MESSAGE); + } + } else if (e.getSource() == exportPlayListButton) { + // Export current available music to M3U playlist file + try { + if (exportPlayListInteractive()) { + updateAvailableListButtons(); + updatePlayListButtons(); + updateAvailableListTitle(); + updatePlayListTitle(); + JOptionPane.showMessageDialog(InfinityAmpPlus.this, "Playlist exported successfully.", "Information", + JOptionPane.INFORMATION_MESSAGE); + } + } catch (Exception ex) { + Logger.error(ex); + JOptionPane.showMessageDialog(InfinityAmpPlus.this, "Failed to export playlist.\n" + ex.getMessage(), "Error", + JOptionPane.ERROR_MESSAGE); + } + } + } + + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getSource() == availableList) { + // update file info + updateFileInfo(); + // update middle bar buttons + updateTransferButtons(); + // update bottom bar buttons + updateAvailableListButtons(); + } else if (e.getSource() == playList) { + // update middle bar buttons + updateTransferButtons(); + // update media buttons + updatePlayListButtons(); + } + } + + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getSource() == rbmiPathAsPrefix || e.getSource() == rbmiPathAsSuffix) { + if (e.getStateChange() == ItemEvent.SELECTED) { + updatePathAsPrefix(rbmiPathAsPrefix.isSelected()); + } + } + } + + @Override + public void audioStateChanged(AudioStateEvent event) { +// Logger.trace("{}.audioStateChanged: state={}({})", InfinityAmpPlus.class.getSimpleName(), event.getAudioState(), +// event.getValue()); + switch (event.getAudioState()) { + case OPEN: + handleAudioOpenEvent(event.getValue()); + break; + case CLOSE: + handleAudioCloseEvent(event.getValue()); + break; + case START: + handleAudioStartEvent(); + break; + case STOP: + handleAudioStopEvent(); + break; + case PAUSE: + handleAudioPauseEvent(event.getValue()); + break; + case RESUME: + handleAudioResumeEvent(event.getValue()); + break; + case BUFFER_EMPTY: + handleAudioBufferEmptyEvent(event.getValue()); + break; + case ERROR: + handleAudioErrorEvent(event.getValue()); + break; + } + } + + @Override + public void mouseClicked(MouseEvent e) { + if (e.getSource() == availableList) { + if (e.getClickCount() == 2) { + addToPlayList(availableList.getSelectedIndex()); + } + } else if (e.getSource() == playList) { + if (!isPlaying() && e.getClickCount() == 2) { + setPlaying(true); + } + } + } + + @Override + public void mousePressed(MouseEvent e) { + } + + @Override + public void mouseReleased(MouseEvent e) { + } + + @Override + public void mouseEntered(MouseEvent e) { + } + + @Override + public void mouseExited(MouseEvent e) { + } + + @Override + public void keyTyped(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + if (e.getSource() == availableList) { + if (e.getKeyCode() == KeyEvent.VK_DELETE && !availableList.isSelectionEmpty()) { + if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(InfinityAmpPlus.this, + "Remove selected entries?", "Question", JOptionPane.YES_NO_OPTION)) { + final int[] indices = availableList.getSelectedIndices(); + if (indices.length > 0) { + for (int i = indices.length - 1; i >= 0; i--) { + removeFromAvailableList(indices[i]); + } + } + } + } + } else if (e.getSource() == playList) { + if (e.getKeyCode() == KeyEvent.VK_DELETE && !playList.isSelectionEmpty()) { + if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(InfinityAmpPlus.this, + "Remove selected entries?", "Question", JOptionPane.YES_NO_OPTION)) { + final int[] indices = playList.getSelectedIndices(); + if (indices.length > 0) { + for (int i = indices.length - 1; i >= 0; i--) { + removeFromPlayList(indices[i]); + } + } + } + } + } + } + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void componentResized(ComponentEvent e) { + } + + @Override + public void componentMoved(ComponentEvent e) { + } + + @Override + public void componentShown(ComponentEvent e) { + } + + @Override + public void componentHidden(ComponentEvent e) { + setPlaying(false); + } + + /** Called by a {@link KeyEventDispatcher} instance. */ + public boolean dispatchKeyEvent(KeyEvent e) { + boolean retVal = false; + if (InfinityAmpPlus.this.isVisible() && + e.getID() == KeyEvent.KEY_PRESSED && + InfinityAmpPlus.this.isMediaKeysEnabled()) { + // process only if InfinityAmp window is open and a "keypressed" event is triggered + final int keyCode = InputKeyHelper.getExtendedKeyCode(e); + try { + switch (keyCode) { + case InputKeyHelper.VK_MEDIA_PLAY_PAUSE: + // play music from the playlist + if (isPlaying()) { + setPaused(!isPaused()); + } else { + setPlaying(true); + } + retVal = true; + break; + case InputKeyHelper.VK_MEDIA_STOP: + // stop music playback + setPlaying(false); + retVal = true; + break; + case InputKeyHelper.VK_MEDIA_PREV_TRACK: + // jump to previously played music file + if (setPrevPlayListItem() >= 0) { + requestNextItem(); + } + updateTransferButtons(); + retVal = true; + break; + case InputKeyHelper.VK_MEDIA_NEXT_TRACK: + // jump to next music file to play + if (setNextPlayListItem() >= 0) { + requestNextItem(); + } + updateTransferButtons(); + retVal = true; + break; + } + } catch (Exception ex) { + Logger.error(ex); + } + } + return retVal; + } + } + + /** A wrapper class for {@link ResourceEntry} objects to provide customized string output. */ + private static class MusicResourceEntry extends FileResourceEntry { + private static final Threading THREAD_POOL = new Threading(Threading.Priority.LOWEST); + + private boolean pathAsPrefix; + private boolean pendingCalculation; + private Long duration; + + public MusicResourceEntry(ResourceEntry musEntry, boolean pathAsPrefix) { + super(Objects.requireNonNull(musEntry).getActualPath()); + if (!musEntry.getExtension().equalsIgnoreCase("mus")) { + throw new IllegalArgumentException("Not a MUS resource: " + musEntry); + } + this.pathAsPrefix = pathAsPrefix; + } + + public MusicResourceEntry(Path musFile, boolean pathAsPrefix) throws Exception { + super(Objects.requireNonNull(musFile)); + final String fileName = musFile.getFileName().toString().toLowerCase(); + if (!fileName.endsWith(".mus")) { + throw new IllegalArgumentException("Not a MUS resource: " + fileName); + } else if (!Files.isRegularFile(musFile)) { + throw new IOException("File does not exist: " + musFile); + } + this.pathAsPrefix = pathAsPrefix; + } + + /** + * Returns the location of the resource entry in abbreviated form. + * + * @return Effective parent folder of the resource. Returns a symbolic name for "root folder" and "current game + * folder". + */ + public String getLocation() { + final Path path = getActualPath(); + final int nameCount = path.getNameCount(); + final String gameFolder; + if (path.startsWith(Profile.getGameRoot())) { + gameFolder = ""; + } else if (nameCount > 2 && path.getName(nameCount - 2).toString().equalsIgnoreCase("music")) { + gameFolder = path.getName(nameCount - 3).toString(); + } else if (nameCount > 1) { + gameFolder = path.getName(nameCount - 2).toString(); + } else { + gameFolder = ""; + } + + return gameFolder; + } + + /** + * Returns whether game folder should be printed before the resource name {@code true} or after the resource name + * {@code false}. + */ + public boolean isPathAsPrefix() { + return pathAsPrefix; + } + + /** + * Specifies whether game folder should be printed before the resource name {@code true} or after the resource name + * {@code false}. + */ + public void setPathAsPrefix(boolean b) { + pathAsPrefix = b; + } + + /** Returns whether duration of music file is available. */ + public boolean isDurationAvailable() { + return (duration != null); + } + + /** Returns the duration of the music file in seconds if available, -1 otherwise. */ + public int getDurationSeconds() { + final long duration = getDuration(); + if (duration >= 0) { + // duration is rounded to the nearest full second + return (int)((duration + 500L) / 1000L); + } + return -1; + } + + /** Returns the duration of the music file in milliseconds if available, -1 otherwise. */ + public long getDuration() { + return isDurationAvailable() ? duration : -1L; + } + + /** Sets music file duration manually. */ + public void setDuration(long duration) { + if (duration >= 0L) { + pendingCalculation = false; + this.duration = duration; + } + } + + /** Calculates the duration of the MUS soundtrack in a background task. */ + public void calculateDuration() { + if (duration != null) { + return; + } + + pendingCalculation = true; + final Supplier supplier = () -> { + final InfinityAmpPlus wnd = ChildFrame.getFirstFrame(InfinityAmpPlus.class); + if (wnd != null) { + if (wnd.getAvailableListIndex(this) < 0) { + throw new CancellationException(); + } + } + + try { + final List entries = MusResourceHandler.parseMusFile(this); + long timeMs = 0L; + int index = 0; + while (true) { + final Entry entry = entries.get(index); + timeMs += entry.getAudioBuffer().getDuration(); + final int nextIndex = entry.getNextNr(); + if (nextIndex <= index || nextIndex >= entries.size()) { + break; + } + index = nextIndex; + } + if (index < entries.size()) { + final Entry entry = entries.get(index); + if (entry.getEndBuffer() != null) { + timeMs += entry.getEndBuffer().getDuration(); + } + } + entries.forEach(Entry::close); + entries.clear(); + return timeMs; + } catch (Throwable t) { + Logger.debug(t, "Resource: " + this); + } + return -1L; + }; + CompletableFuture.supplyAsync(supplier, THREAD_POOL.getExecutor()).thenAccept(this::durationCalculated); + } + + /** Called by the background task when the task has ended. */ + private void durationCalculated(Long duration) { + pendingCalculation = false; + this.duration = duration; + final InfinityAmpPlus wnd = ChildFrame.getFirstFrame(InfinityAmpPlus.class); + if (wnd != null) { + SwingUtilities.invokeLater(() -> wnd.refreshResourceEntry(this)); + } + } + + @Override + public String toString() { + // getting relevant game folder name + final String gameFolder = getLocation(); + + final String time; + if (isDurationAvailable()) { + final int duration = getDurationSeconds(); + if (duration >= 0) { + final int hours = duration / 3600; + final int minutes = (duration / 60) % 60; + final int seconds = duration % 60; + time = String.format("%02d:%02d:%02d", hours, minutes, seconds); + } else { + time = ""; + } + } else if (pendingCalculation) { + time = ""; + } else { + time = ""; + } + + String fmt = isPathAsPrefix() ? "(%2$s) %1$s" : "%1$s (%2$s)"; + if (!time.isEmpty()) { + fmt += " [%3$s]"; + } + return String.format(fmt, super.toString(), gameFolder, time); + } + } + + /** + * A dialog for managing MUS sound segments to auto-skip at playback. + */ + private static class SoundFilterDialog extends JDialog implements ActionListener, ListSelectionListener { + // Default list of potential entries + private static final List DEFAULT_FILTERS = Arrays.asList("SPC", "SPC1", "MX0000A", "MX9000A"); + + private final SimpleListModel filterListModel = new SimpleListModel<>(); + private final JList filterList = new JList<>(filterListModel); + + private final JButton addButton = new JButton("Add..."); + private final JButton removeButton = new JButton("Remove"); + private final JButton clearButton = new JButton("Clear"); + private final JButton resetButton = new JButton("Reset"); + private final JButton acceptButton = new JButton("Accept"); + private final JButton cancelButton = new JButton("Cancel"); + + private boolean accepted; + + /** + * A convenience function that presents an initialized dialog for customization and returns the + * resulting sound filter items. + * + * @param owner Dialog owner as {@link Frame} instance. + * @param filterEntries Initial set of filter items to populate the filter list. Argument can be {@code null}. + * @return {@link Set} of user-defined filter items if accepted, {@code null} otherwise. + */ + public static Collection getSoundFiltersInteractive(Frame owner, Set filterEntries) { + final SoundFilterDialog dlg = new SoundFilterDialog(owner, filterEntries); + final Collection retVal = dlg.getResult(); + dlg.dispose(); + return retVal; + } + + /** Returns the default set of sound filters. */ + public static Collection getDefaultSoundFilters() { + return Collections.unmodifiableCollection(DEFAULT_FILTERS); + } + + public SoundFilterDialog(Frame owner, Set filterEntries) { + super(owner, "Define sound filters", true); + init(filterEntries); + } + + /** Returns whether the user accepted the filter list customization. */ + @SuppressWarnings("unused") + public boolean accepted() { + return accepted; + } + + /** + * Returns the sound filter items as a {@link Collection}. + * + * @return {@link Collection} of defined sound filters if accepted, {@code null} if the user cancelled the operation. + */ + public Collection getResult() { + final HashSet retVal; + if (accepted) { + retVal = new HashSet<>(); + for (int i = 0, size = filterListModel.size(); i < size; i++) { + retVal.add(filterListModel.get(i)); + } + } else { + retVal = null; + } + return retVal; + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == addButton) { + addItems(); + } else if (e.getSource() == removeButton) { + removeItems(filterList.getSelectedIndices()); + } else if (e.getSource() == clearButton) { + // remove all filter items fromthe list + int retVal = JOptionPane.showConfirmDialog(this, "Remove all filter entries?", "Question", + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (retVal == JOptionPane.YES_OPTION) { + filterListModel.clear(); + updateButtons(); + } + } else if (e.getSource() == resetButton) { + resetItems(true); + } else if (e.getSource() == acceptButton) { + accept(); + } else if (e.getSource() == cancelButton) { + cancel(); + } + } + + @Override + public void valueChanged(ListSelectionEvent e) { + updateButtons(); + } + + /** Updates button states depending on current configuration */ + private void updateButtons() { + removeButton.setEnabled(!filterList.isSelectionEmpty()); + clearButton.setEnabled(!filterListModel.isEmpty()); + } + + /** Adds new items interactively. */ + private void addItems() { + final JFileChooser fc = new JFileChooser(Profile.getGameRoot().toFile()); + for (final FileFilter ff : fc.getChoosableFileFilters()) { + fc.removeChoosableFileFilter(ff); + } + final FileFilter acmFilter = new FileNameExtensionFilter("ACM files", "acm"); + fc.addChoosableFileFilter(acmFilter); + fc.setFileFilter(acmFilter); + fc.setFileSelectionMode(JFileChooser.FILES_ONLY); + fc.setMultiSelectionEnabled(true); + fc.setDialogTitle("Select ACM sound files"); + final int retVal = fc.showOpenDialog(this); + if (retVal == JFileChooser.APPROVE_OPTION) { + for (final File file : fc.getSelectedFiles()) { + addItem(file.getName()); + } + } + } + + /** Adds the specified filter item to the list. Item is normalized and only added if not yet present. */ + private boolean addItem(String item) { + boolean retVal = false; + + item = normalizeItem(item, true); + if (item != null) { + // check if item is already present + if (!item.isEmpty()) { + retVal = true; + for (int i = 0, size = filterListModel.size(); i < size && retVal; i++) { + retVal = !item.equalsIgnoreCase(filterListModel.get(i)); + if (!retVal) { + filterList.setSelectedIndex(i); + } + } + } + + if (retVal) { + filterListModel.add(item); + } + } + + return retVal; + } + + /** Removes all list items specified by the item index array. */ + private void removeItems(int[] indices) { + if (indices != null && indices.length > 0) { + for (int i = indices.length - 1; i >= 0; i--) { + final int index = indices[i]; + if (index >= 0 && index < filterListModel.size()) { + filterListModel.remove(index); + } + } + } + } + + /** + * Resets the filter list content by a predefined list of items. + * + * @param prompt Whether to prompt for confirmation. + */ + private void resetItems(boolean prompt) { + int retVal = JOptionPane.YES_OPTION; + if (prompt) { + retVal = JOptionPane.showConfirmDialog(this, "Restore predefined filter entries?", "Question", + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + } + + if (retVal == JOptionPane.YES_OPTION) { + filterListModel.clear(); + DEFAULT_FILTERS.forEach(this::addItem); + filterList.setSelectedIndex(0); + filterList.ensureIndexIsVisible(0); + updateButtons(); + } + } + + /** Accepts the current filter list and closes the dialog. */ + private void accept() { + accepted = true; + setVisible(false); + } + + /** Discards the current filter list and closes the dialog. */ + private void cancel() { + accepted = false; + setVisible(false); + } + + /** + * Normalizes the item string presentation by dealing with path elements and file extensions. + * + * @param item The sound filter item string. + * @param withExtension Specify {@code true} to ensure it contains the ".acm" file extension. + * @return The normalized item. + */ + private String normalizeItem(String item, boolean withExtension) { + String retVal = item; + + if (retVal != null) { + retVal = retVal.trim(); + + // normalize item (no path; file extension: .acm) + for (final char ch : new char[] {'/', '\\'}) { + int pos = retVal.indexOf(ch); + if (pos >= 0) { + retVal = retVal.substring(pos); + } + } + + // ensure item has the right file extension + if (!retVal.isEmpty()) { + int pos = retVal.lastIndexOf('.'); + if (withExtension) { + if (pos < 0) { + retVal += ".acm"; + } else if (!retVal.substring(pos).equalsIgnoreCase(".acm")) { + retVal = retVal.substring(0, pos).trim(); + if (!retVal.isEmpty()) { + retVal += ".acm"; + } + } + } else { + if (pos >= 0 && retVal.substring(pos).equalsIgnoreCase(".acm")) { + retVal = retVal.substring(0, pos); + } + } + } + } + + return retVal; + } + + /** Adds the specified set of filters to the filter list. */ + private void loadFilters(Set filterEntries) { + filterListModel.clear(); + final Iterable iterable = (filterEntries != null) ? filterEntries : DEFAULT_FILTERS; + for (final String item : iterable) { + addItem(item); + } + } + + private void init(Set filterEntries) { + setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE); + getRootPane().setDefaultButton(acceptButton); + + filterList.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); + filterList.addListSelectionListener(this); + + final JScrollPane filterScroll = new JScrollPane(filterList); + filterScroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + filterScroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + + addButton.addActionListener(this); + removeButton.addActionListener(this); + clearButton.addActionListener(this); + resetButton.addActionListener(this); + acceptButton.addActionListener(this); + cancelButton.addActionListener(this); + addButton.requestFocusInWindow(); + + // setting ESC keypress functionality + getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), + this); + getRootPane().getActionMap().put(this, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + cancel(); + } + }); + + GridBagConstraints gbc = new GridBagConstraints(); + + final JPanel buttonPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + buttonPanel.add(addButton, gbc); + ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + buttonPanel.add(removeButton, gbc); + ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + buttonPanel.add(clearButton, gbc); + ViewerUtil.setGBC(gbc, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(24, 0, 0, 0), 0, 0); + buttonPanel.add(resetButton, gbc); + ViewerUtil.setGBC(gbc, 0, 4, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + buttonPanel.add(new JPanel(), gbc); + ViewerUtil.setGBC(gbc, 0, 5, 1, 1, 0.0, 0.0, GridBagConstraints.LAST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(16, 0, 0, 0), 0, 0); + buttonPanel.add(acceptButton, gbc); + ViewerUtil.setGBC(gbc, 0, 6, 1, 1, 0.0, 0.0, GridBagConstraints.LAST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + buttonPanel.add(cancelButton, gbc); + + final JPanel mainPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, GridBagConstraints.REMAINDER, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, + GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0); + final JLabel listTitle = new JLabel("Filter list"); + mainPanel.add(listTitle, gbc); + ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(filterScroll, gbc); + ViewerUtil.setGBC(gbc, 1, 1, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.VERTICAL, + new Insets(8, 8, 0, 0), 0, 0); + mainPanel.add(buttonPanel, gbc); + + final Container pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(8, 8, 8, 8), 0, 0); + pane.add(mainPanel, gbc); + + pack(); + setMinimumSize(getSize()); + Center.center(this, getOwner().getBounds()); + + loadFilters(filterEntries); + if (!filterListModel.isEmpty()) { + filterList.setSelectedIndex(0); + filterList.ensureIndexIsVisible(0); + } + + setVisible(true); + } + } + + /** + * Extends the {@link DefaultListCellRenderer} by an option to print the list index in front of the cell content. + */ + private static class IndexedCellRenderer extends DefaultListCellRenderer { + private boolean showIndex; + + public IndexedCellRenderer(boolean indexEnabled) { + super(); + showIndex = indexEnabled; + } + + /** Returns whether an index number is printed in front of the cell content. */ + @SuppressWarnings("unused") + public boolean isIndexEnabled() { + return showIndex; + } + + /** Specifies whether an index number should be printed in front of the cell content. */ + @SuppressWarnings("unused") + public void setIndexEnabled(boolean enable) { + if (enable != showIndex) { + showIndex = enable; + } + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, + boolean cellHasFocus) { + super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + if (showIndex) { + final String text = (index + 1) + ". " + getText(); + setText(text); + } + return this; + } + } + + /** + * A simple M3U file importer and exporter class. + */ + private static class Playlist { + private static final String UTF8_BOM = "\uFEFF"; + + private static final int MILLISECONDS_MULTIPLIER = 1000; + + // Stores individual MUS entries as {MUS file path, Track length in milliseconds} pairs. + // Unavailable track length is specified as a negative value. + private final List> entries = new ArrayList<>(); + + /** Initializes an empty {@link Playlist} object. */ + public Playlist() { + } + + /** + * Initializes a new {@link Playlist} object with the specified M3U playlist file. + * + * @param file {@link Path} of the UTF-8 encoded M3U playlist file. + * @throws IOException if an I/O error occurred. + * @throws ParseException if the playlist data contains invalid content. + */ + public Playlist(Path file) throws IOException, ParseException { + this(readFile(Objects.requireNonNull(file))); + } + + /** + * Initializes a new {@link Playlist} object with the text content of a M3U playlist file. + * + * @param content A {@link String} containing M3U playlist data. + * @throws ParseException if the playlist data contains invalid content. + */ + @SuppressWarnings("unused") + public Playlist(String content) throws ParseException { + this(Arrays.asList(Objects.requireNonNull(content).split("\r?\n"))); + } + + /** + * Initializes a new {@link Playlist} objects. + * + * @param lines M3U playlist data as a list of individual text lines. + * @throws ParseException if the playlist data contains invalid content. + */ + private Playlist(List lines) throws ParseException { + parse(Objects.requireNonNull(lines)); + } + + /** Returns the number of available soundtracks. */ + public int getEntriesCount() { + return entries.size(); + } + + /** + * Returns the file path of the specified entry. + * + * @param index The soundtrack index. + * @return {@link Path} of the soundtrack file. + * @throws IndexOutOfBoundsException if {@code index} is out of bounds. + */ + public Path getEntryPath(int index) throws IndexOutOfBoundsException { + return entries.get(index).getValue0(); + } + + /** + * Returns the soundtrack length of the specified entry, in milliseconds. + * + * @param index The soundtrack index. + * @return Length in milliseconds. A negative value indicates unavailable length information. + * @throws IndexOutOfBoundsException if {@code index} is out of bounds. + */ + public int getEntryLength(int index) throws IndexOutOfBoundsException { + return entries.get(index).getValue1(); + } + + /** + * Adds the specified music file to the playlist. + * + * @param musicFile Path of the music file as {@code String}. + * @param length Soundtrack length, in milliseconds. + */ + @SuppressWarnings("unused") + public void addEntry(String musicFile, int length) { + addEntry(Paths.get(Objects.requireNonNull(musicFile)), length); + } + + /** + * Adds the specified music file to the playlist. + * + * @param musicFile {@link Path} of the music file. + * @param length Soundtrack length, in milliseconds. + */ + public void addEntry(Path musicFile, int length) { + final Couple entry = new Couple<>(Objects.requireNonNull(musicFile), length); + entries.add(entry); + } + + /** + * Removes the specified playlist entry. + * + * @param index The soundtrack index. + * @throws IndexOutOfBoundsException if {@code index} is out of bounds. + */ + @SuppressWarnings("unused") + public void removeEntry(int index) throws IndexOutOfBoundsException { + entries.remove(index); + } + + /** + * Exports the playlist content to a M3U playlist file with UTF-8 text encoding. + * + * @param fileName Name of the playlist file. + * @param cs The character set to use for text encoding. Defaults to {@code UTF-8} if not specified. + * @throws IOException if an I/O error occurred. + */ + public void exportFile(Path fileName, Charset cs) throws IOException { + if (cs == null) { + cs = StandardCharsets.UTF_8; + } + boolean isUtf = (cs.name().toUpperCase().contains("UTF")); + + // preparing playlist content + final String crlf = "\r\n"; + final StringBuilder sb = new StringBuilder(); + if (isUtf) { + sb.append(UTF8_BOM); + } + sb.append("#EXTM3U").append(crlf); + for (final Couple entry : entries) { + final int length; + if (entry.getValue1() >= 0) { + // rounding up + length = (entry.getValue1() + (MILLISECONDS_MULTIPLIER / 2)) / MILLISECONDS_MULTIPLIER; + } else { + length = -1; + } + final Path path = entry.getValue0(); + final String name = path.getFileName().toString(); + sb.append("#EXTINF:").append(length).append(',').append(name).append(crlf); + sb.append(path).append(crlf); + } + + Files.write(fileName, sb.toString().getBytes(cs)); + } + + private void parse(List lines) throws ParseException { + Objects.requireNonNull(lines); + + // normalizing content and eliminating empty lines + final List data = new ArrayList<>(); + lines.forEach(line -> { + if (line != null) { + String s = line; + if (s.startsWith(UTF8_BOM)) { + s = s.substring(1); + } + s = s.trim(); + if (!s.isEmpty()) { + data.add(s); + } + } + }); + + int index = 0; + if (data.isEmpty()) { + throw new ParseException("Playlist is empty", index); + } + + // parsing header + final String header = data.get(index); + if (!header.equals("#EXTM3U")) { + throw new ParseException("Invalid header", index); + } + index++; + + // parsing content + Couple entry = null; + while (index < data.size()) { + String line = data.get(index); + if (line.startsWith("#")) { + // processing directive + final String token = extractToken(line, ":", 0); + switch (token) { + case "#EXTINF": + break; + case "#PLAYLIST": + case "#EXTGRP": + case "#EXTALB": + case "#EXTART": + case "#EXTGENRE": + case "#EXTM3A": + case "#EXTBYT": + case "#EXTBIN": + case "#EXTENC": + case "#EXTIMG": + // skipping unsupported directives + index++; + continue; + default: + throw new ParseException("Invalid directive", index); + } + + // track length, in seconds + line = line.substring(token.length() + 1); + final String lengthString = extractToken(line, ",", 0); + try { + final int length = Integer.parseInt(lengthString); + if (entry == null) { + // rounding up + entry = new Couple<>(null, length * MILLISECONDS_MULTIPLIER + (MILLISECONDS_MULTIPLIER / 2)); + } + } catch (NumberFormatException e) { + throw new ParseException("Missing or invalid track length", index); + } + } else { + // processing file path + Path musPath; + try { + musPath = Paths.get(line); + if (Files.isRegularFile(musPath)) { + final String name = musPath.getFileName().toString(); + if (!name.toLowerCase().endsWith(".mus")) { + Logger.info("Not a MUS file: {}", name); + } else { + if (entry == null) { + entry = new Couple<>(musPath, -1); + } else { + entry.setValue0(musPath); + } + entries.add(entry); + } + } else { + Logger.info("File does not exist: {}", musPath); + } + } catch (InvalidPathException e) { + throw new ParseException("Invalid ", index); + } + entry = null; + } + index++; + } + + if (entry != null) { + Logger.info("Dangling playlist directive at line {}", index - 1); + } + } + + /** Returns the token starting at {@code startOfs} and ending at the first occurrence of {@code separator}, exclusive. */ + private String extractToken(String content, String separator, int startOfs) { + if (content != null && separator != null) { + if (!separator.isEmpty()) { + int pos = content.indexOf(separator, startOfs); + if (pos >= 0) { + return content.substring(startOfs, pos); + } + } + + } + return content; + } + + /** + * Used internally to read a text file and return it as a list of individual lines. + * Attempts to autodetect the correct character encoding. + */ + private static List readFile(Path file) throws IOException { + final byte[] data = Files.readAllBytes(file); + Charset cs = StandardCharsets.ISO_8859_1; + if (data.length > 2) { + if (data[0] == (byte)0xef && data[1] == (byte)0xbb && data[2] == (byte)0xbf) { + cs = StandardCharsets.UTF_8; + } else if (data[0] == (byte)0xff && data[1] == (byte)0xfe) { + cs = StandardCharsets.UTF_16LE; + } else if (data[0] == (byte)0xfe && data[1] == (byte)0xff) { + cs = StandardCharsets.UTF_16BE; + } + } + final String content = new String(data, cs); + return Arrays.asList(content.split("\r?\n")); + } + } +} diff --git a/src/org/infinity/gui/InfinityTextArea.java b/src/org/infinity/gui/InfinityTextArea.java index cad8d7697..700e9c77e 100644 --- a/src/org/infinity/gui/InfinityTextArea.java +++ b/src/org/infinity/gui/InfinityTextArea.java @@ -8,6 +8,11 @@ import java.awt.Font; import java.awt.Point; import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.HierarchyEvent; +import java.awt.event.HierarchyListener; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; @@ -17,8 +22,15 @@ import java.util.TreeMap; import java.util.function.Supplier; +import javax.swing.Action; +import javax.swing.ActionMap; import javax.swing.Icon; +import javax.swing.InputMap; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JRootPane; import javax.swing.JViewport; +import javax.swing.KeyStroke; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.text.BadLocationException; @@ -49,7 +61,7 @@ /** * Extends {@link RSyntaxTextArea} by NearInfinity-specific features. */ -public class InfinityTextArea extends RSyntaxTextArea implements ChangeListener { +public class InfinityTextArea extends RSyntaxTextArea implements ChangeListener, KeyListener, HierarchyListener { /** Available languages for syntax highlighting. */ public enum Language { /** Disables syntax highlighting */ @@ -76,7 +88,7 @@ public enum Language { private final String style; - private Language(String style) { + Language(String style) { this.style = style; } @@ -113,7 +125,7 @@ public enum Scheme { // Stored as functional interface to react to dark/light UI theme changes private final Supplier scheme; - private Scheme(String label, Supplier scheme) { + Scheme(String label, Supplier scheme) { this.label = label; this.scheme = scheme; } @@ -175,6 +187,7 @@ public String getScheme() { private final SortedMap gutterIcons = new TreeMap<>(); private final Map gutterIconsActive = new HashMap<>(); + private final HashMap inputActionMap = new HashMap<>(); private RTextScrollPane scrollPane; @@ -296,8 +309,12 @@ public InfinityTextArea(RSyntaxDocument doc, String text, int rows, int cols, bo * * @param resetUndo Specifies whether the undo history will be discarded. */ - public static void applySettings(RSyntaxTextArea edit, boolean resetUndo) { + public static void applySettings(InfinityTextArea edit, boolean resetUndo) { if (edit != null) { + // Allows key strokes defined by a parent components to be processed when this component has focus + edit.addKeyListener(edit); + edit.addHierarchyListener(edit); + edit.setCurrentLineHighlightColor(DEFAULT_LINE_HIGHLIGHT_COLOR); if (BrowserMenuBar.isInstantiated()) { edit.setTabsEmulated(BrowserMenuBar.getInstance().getOptions().isTextTabEmulated()); @@ -484,6 +501,67 @@ public void stateChanged(ChangeEvent e) { // --------------------- End Interface ChangeListener --------------------- + // --------------------- Begin Interface KeyListener --------------------- + + @Override + public void keyTyped(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + // Processing key strokes defined in parent components. + // Registration of key strokes is handled by the HierarchyListener. + final KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers()); + final Action action = inputActionMap.get(keyStroke); + if (action != null) { + action.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "")); + e.consume(); + } + } + + @Override + public void keyReleased(KeyEvent e) { + } + + // --------------------- End Interface KeyListener --------------------- + + // --------------------- Begin Interface HierarchyListener --------------------- + + @Override + public void hierarchyChanged(HierarchyEvent e) { + // InfinityTextArea appears to discard or override certain global key stroke definitions. + // This method registers key strokes from parent components, so that they can still be processed. + // Registration process is placed into a HierarchyListener to register key strokes even if + // parent components are assigned later. + if (!(e.getChanged() instanceof JComponent && getTopLevelAncestor() instanceof JFrame)) { + return; + } + + final JRootPane rootPane = ((JFrame) getTopLevelAncestor()).getRootPane(); + if (rootPane == null) { + return; + } + + final InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); + final KeyStroke[] keyStrokes = inputMap.allKeys(); + final ActionMap actionMap = rootPane.getActionMap(); + if (inputMap == null || keyStrokes == null || keyStrokes.length == 0 || actionMap == null) { + return; + } + + for (final KeyStroke keyStroke : keyStrokes) { + final Object binding = inputMap.get(keyStroke); + if (binding != null) { + final Action action = actionMap.get(binding); + if (action != null) { + inputActionMap.put(keyStroke, action); + } + } + } + } + + // --------------------- End Interface HierarchyListener --------------------- + /** * Returns the underlying ScrollPane if available. Returns {@code null} otherwise. */ diff --git a/src/org/infinity/gui/ItemCategoryOrderDialog.java b/src/org/infinity/gui/ItemCategoryOrderDialog.java new file mode 100644 index 000000000..ddc4fb9da --- /dev/null +++ b/src/org/infinity/gui/ItemCategoryOrderDialog.java @@ -0,0 +1,511 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.gui; + +import java.awt.Container; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; +import java.util.prefs.Preferences; +import java.util.stream.Collectors; + +import javax.swing.AbstractAction; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.KeyStroke; +import javax.swing.ListSelectionModel; +import javax.swing.ScrollPaneConstants; +import javax.swing.WindowConstants; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +import org.infinity.datatype.ItemTypeBitmap; +import org.infinity.icon.Icons; +import org.infinity.resource.Profile; +import org.infinity.resource.sto.StoResource; +import org.infinity.util.Logger; + +/** + * Opens an application-modal dialog where the user can define a custom sort order for item categories. + * This sort order can be used to sort "Item for sale" entries in store resources. + */ +public class ItemCategoryOrderDialog extends JDialog implements ActionListener, ListSelectionListener { + /** Preferences base name key for the item category sort order list */ + private static final String PREF_STO_CATEGORY_ORDER_FMT= "ItemCategoryOrder"; + + private static final KeyStroke ESC_KEY = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + + private final JButton okButton = new JButton("Apply", Icons.ICON_CHECK_16.getIcon()); + private final JButton cancelButton = new JButton("Cancel", Icons.ICON_CHECK_NOT_16.getIcon()); + private final JButton resetButton = new JButton("Reset", Icons.ICON_REFRESH_16.getIcon()); + private final JButton reverseOrderButton = new JButton("Reverse"); + private final JButton moveUpButton = new JButton(Icons.ICON_UP_16.getIcon()); + private final JButton moveDownButton = new JButton(Icons.ICON_DOWN_16.getIcon()); + + private final DefaultListModel listModel = new DefaultListModel<>(); + private final JList list = new JList<>(listModel); + + private final boolean isPST; + + private boolean accepted = false; + + public ItemCategoryOrderDialog(Window parent, boolean isPST) { + super(parent, "Customize item category sort order", Dialog.ModalityType.APPLICATION_MODAL); + this.isPST = isPST; + init(); + } + + /** Returns whether the dialog was closed by applying a new item category sort order. */ + public boolean isAccepted() { + return accepted; + } + + /** + * Accepts the current item category sort order. + * Dialog will be closed and the current sort order state is preserved. + */ + public void accept() { + accepted = true; + setVisible(false); + + // storing current category sort order + storeCategoryIndices(getPreferencesKey(), getCategoryIndices()); + } + + /** Returns the indices of the current item category sort order. */ + public int[] getCategoryIndices() { + final int[] indexMap = new int[listModel.size()]; + for (int i = 0, size = listModel.size(); i < size; i++) { + indexMap[listModel.get(i).getIndex()] = i; + } + return indexMap; + } + + /** Cancels the dialog. Dialog will be closed and changes are discarded. */ + public void cancel() { + accepted = false; + setVisible(false); + } + + /** Resets the item category sort order to their initial state. */ + public void reset() { + final int[] indexMap = getDefaultCategoryIndices(isPST); + initList(indexMap); + list.setSelectedIndex(0); + list.ensureIndexIsVisible(0); + } + + /** Reverses the current sort order of the item categories. */ + public void reverseOrder() { + int minSelectedIndex = list.getSelectionModel().getMinSelectionIndex(); + int maxSelectedIndex = list.getSelectionModel().getMaxSelectionIndex(); + + int topIndex = 0; + int bottomIndex = listModel.size() - 1; + while (topIndex < bottomIndex) { + final ItemCategory itemBottom = listModel.remove(bottomIndex); + final ItemCategory itemTop = listModel.remove(topIndex); + listModel.add(topIndex, itemBottom); + listModel.add(bottomIndex, itemTop); + topIndex++; + bottomIndex--; + } + + // updating item selection + if (minSelectedIndex >= 0) { + minSelectedIndex = listModel.size() - 1 - minSelectedIndex; + maxSelectedIndex = listModel.size() - 1 - maxSelectedIndex; + list.getSelectionModel().setSelectionInterval(maxSelectedIndex, minSelectedIndex); + list.ensureIndexIsVisible(maxSelectedIndex); + list.ensureIndexIsVisible(minSelectedIndex); + } + } + + /** Moves the selected list items up (towards the beginning) by one step. */ + public void moveUp() { + final int minIdx = list.getSelectionModel().getMinSelectionIndex(); + final int maxIdx = list.getSelectionModel().getMaxSelectionIndex(); + if (minIdx > 0) { + for (int idx = minIdx; idx <= maxIdx; idx++) { + final ItemCategory item = listModel.remove(idx); + listModel.add(idx - 1, item); + } + list.getSelectionModel().setSelectionInterval(minIdx - 1, maxIdx - 1); + } + } + + /** Moves the selected list items down (towards the end) by one step. */ + public void moveDown() { + final int minIdx = list.getSelectionModel().getMinSelectionIndex(); + final int maxIdx = list.getSelectionModel().getMaxSelectionIndex(); + if (maxIdx < listModel.size() - 1) { + for (int idx = maxIdx; idx >= minIdx; idx--) { + final ItemCategory item = listModel.remove(idx); + listModel.add(idx + 1, item); + } + list.getSelectionModel().setSelectionInterval(minIdx + 1, maxIdx + 1); + } + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == okButton) { + accept(); + } else if (e.getSource() == cancelButton) { + cancel(); + } else if (e.getSource() == resetButton) { + reset(); + } else if (e.getSource() == reverseOrderButton) { + reverseOrder(); + } else if (e.getSource() == moveUpButton) { + moveUp(); + } else if (e.getSource() == moveDownButton) { + moveDown(); + } + } + + @Override + public void valueChanged(ListSelectionEvent e) { + final int minIdx = list.getMinSelectionIndex(); + final int maxIdx = list.getMaxSelectionIndex(); + moveUpButton.setEnabled(minIdx > 0); + moveDownButton.setEnabled(maxIdx >= 0 && maxIdx < listModel.size() - 1); + } + + /** Populates the list control with item category entries based on the specified category index map. */ + private void initList(int[] indexMap) { + final String[] defCategories = isPST ? ItemTypeBitmap.CATEGORIES11_ARRAY : ItemTypeBitmap.CATEGORIES_ARRAY; + + // generating item category name list dynamically + final String[] categories; + final TreeMap catMap = ItemTypeBitmap.getItemCategories(); + int numItems = catMap.keySet().stream().max((a, b) -> (int) (a - b)).orElse(-1L).intValue() + 1; + numItems = Math.max(numItems, defCategories.length); + if (numItems > 0) { + categories = new String[numItems]; + for (final Map.Entry entry : catMap.entrySet()) { + final int key = entry.getKey().intValue(); + if (key >= 0 && key < categories.length) { + categories[key] = entry.getValue(); + } + } + } else { + // falling back to predefined list if categories are not available + categories = isPST ? ItemTypeBitmap.CATEGORIES11_ARRAY : ItemTypeBitmap.CATEGORIES_ARRAY; + } + + listModel.clear(); + if (indexMap != null) { + // we need the inversed index map + final int[] invMap = new int[indexMap.length]; + for (int i = 0; i < indexMap.length; i++) { + invMap[indexMap[i]] = i; + } + + for (final int catIdx : invMap) { + // use default category list if category name is not defined in generated list + String catName = null; + if (catIdx >= 0 && catIdx < categories.length) { + if (categories[catIdx] != null) { + catName = categories[catIdx]; + } else if (catIdx < defCategories.length) { + catName = defCategories[catIdx]; + } + } + + if (catName != null) { + listModel.add(listModel.size(), new ItemCategory(catIdx, catName)); + } else { + Logger.debug("Item category name not available for index {}", catIdx); + } + } + } + } + + private void init() { + final int[] defIndexMap = getDefaultCategoryIndices(isPST); + // loading item category sort order + // from preferences? + int[] indexMap = loadCategoryIndices(getPreferencesKey()); + if (indexMap != null && indexMap.length != defIndexMap.length) { + // invalid map + indexMap = null; + } + + // or default values from ItemTypeBitmap class + if (indexMap == null) { + indexMap = Arrays.copyOf(defIndexMap, defIndexMap.length); + } + + // initializing dialog list + initList(indexMap); + + // setting up user controls + okButton.addActionListener(this); + cancelButton.addActionListener(this); + resetButton.addActionListener(this); + resetButton.setToolTipText("Reset item categories to suggested sort order."); + reverseOrderButton.addActionListener(this); + reverseOrderButton.setToolTipText("Reverse current item category sort order."); + + moveUpButton.setMargin(new Insets(16, 4, 16, 4)); + moveUpButton.addActionListener(this); + moveDownButton.setMargin(new Insets(16, 4, 16, 4)); + moveDownButton.addActionListener(this); + + list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); + list.addListSelectionListener(this); + + // calculating dimensions + final Dimension prefSize = list.getPreferredSize(); + if (prefSize != null && prefSize.width > 0 && prefSize.height > 0) { + final int prefW = prefSize.width * 3 / 2; + final int prefH = prefSize.height * 20 / listModel.size(); + prefSize.width = prefW; + prefSize.height = prefH; + } + + JScrollPane scroll = new JScrollPane(list); + scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + scroll.setPreferredSize(prefSize); + + // setting up UI + final Container pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + final JPanel mainPanel = new JPanel(new GridBagLayout()); + final GridBagConstraints gbc = new GridBagConstraints(); + + // Side bar with up/down buttons + final JPanel sideBar = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + sideBar.add(moveUpButton, gbc); + ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, + new Insets(8, 0, 0, 0), 0, 0); + sideBar.add(moveDownButton, gbc); + ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.VERTICAL, + new Insets(0, 0, 0, 0), 0, 0); + sideBar.add(new JPanel(), gbc); + + // bottom bar with reverse/reset buttons + final JPanel bottomBar = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + bottomBar.add(reverseOrderButton, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 4, 0, 0), 0, 0); + bottomBar.add(new JPanel(), gbc); + ViewerUtil.setGBC(gbc, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + bottomBar.add(resetButton, gbc); + + // dialog buttons (ok, cancel) + final JPanel buttonBar = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + buttonBar.add(new JPanel(), gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 4), 0, 0); + buttonBar.add(okButton, gbc); + ViewerUtil.setGBC(gbc, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, + new Insets(0, 4, 0, 0), 0, 0); + buttonBar.add(cancelButton, gbc); + ViewerUtil.setGBC(gbc, 3, 0, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + buttonBar.add(new JPanel(), gbc); + + // main panel + final JLabel caption = new JLabel("Item categories:"); + ViewerUtil.setGBC(gbc, 0, 0, 2, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + mainPanel.add(caption, gbc); + + ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, + new Insets(4, 0, 0, 0), 0, 0); + mainPanel.add(scroll, gbc); + ViewerUtil.setGBC(gbc, 1, 1, 1, 1, 0.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.VERTICAL, + new Insets(4, 8, 0, 0), 0, 0); + mainPanel.add(sideBar, gbc); + + ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(bottomBar, gbc); + ViewerUtil.setGBC(gbc, 1, 2, 1, 1, 0.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + mainPanel.add(new JPanel(), gbc); + + ViewerUtil.setGBC(gbc, 0, 3, 2, 1, 1.0, 0.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(16, 0, 0, 0), 0, 0); + mainPanel.add(buttonBar, gbc); + + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH, + new Insets(8, 8, 8, 8), 0, 0); + pane.add(mainPanel, gbc); + + // setting up a usable minimum dialog size + final Dimension dim = getPreferredSize(); + setMinimumSize(new Dimension(dim.width, dim.height * 2 / 3)); + + pack(); + setResizable(true); + setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); + setLocationRelativeTo(getOwner()); + + if (!listModel.isEmpty()) { + list.getSelectionModel().setSelectionInterval(0, 0); + list.ensureIndexIsVisible(0); + } + list.requestFocusInWindow(); + + // ESC cancels dialog + final String closeDialogKey = "CloseDialog"; + getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(ESC_KEY, closeDialogKey); + getRootPane().getActionMap().put(closeDialogKey, new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + cancel(); + } + }); + + setVisible(true); + } + + /** Returns the Preferences key for the current item category list. */ + public String getPreferencesKey() { + return getPreferencesKey(isPST); + } + + /** + * Returns the Preferences key for the specified store type. + * + * @param isPST Whether the store belongs to a PST game. + * @return Preferences key as string. + */ + public static String getPreferencesKey(boolean isPST) { + return PREF_STO_CATEGORY_ORDER_FMT + Profile.getGame().name(); + } + + /** + * Loads an integer array from the preferences. + * + * @param prefKey The Preferences key to use. + * @return {@code int[]} array of item category indices. Returns {@code null} if no preferences entry was found. + */ + public static int[] loadCategoryIndices(String prefKey) { + int[] retVal = null; + + final Preferences prefs = Preferences.userNodeForPackage(StoResource.class); + final String data = prefs.get(prefKey, null); + if (data != null) { + final String[] items = data.split(","); + retVal = new int[items.length]; + for (int i = 0; i < items.length; i++) { + try { + final int value = Integer.parseInt(items[i]); + retVal[i] = Math.max(0, value); + } catch (NumberFormatException e) { + Logger.debug(e); + } + } + } + + return retVal; + } + + /** + * Stores the specified integer array in the preferences. + * + * @param prefKey The Preferences key to use. + * @param indices Integer array to store. Specify {@code null} to remove an existing preferences entry. + */ + public static void storeCategoryIndices(String prefKey, int[] indices) { + final Preferences prefs = Preferences.userNodeForPackage(StoResource.class); + + if (indices == null) { + prefs.remove(prefKey); + } else { + final String data = + Arrays.stream(indices).mapToObj(Integer::toString).collect(Collectors.joining(",")); + prefs.put(prefKey, data); + } + } + + /** Returns the default index array of item categories for the specified store type. */ + public static int[] getDefaultCategoryIndices(boolean isPST) { + return isPST ? ItemTypeBitmap.SUGGESTED_CATEGORY_ORDER_PST : ItemTypeBitmap.SUGGESTED_CATEGORY_ORDER; + } + + // -------------------------- INNER CLASSES -------------------------- + + private static class ItemCategory implements Comparable { + private final int index; + private final String name; + + public ItemCategory(int index, String name) { + this.index = index; + this.name = name; + } + + public int getIndex() { + return index; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return getName() + " (" + getIndex() + ")"; + } + + @Override + public int hashCode() { + return Objects.hash(index); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ItemCategory other = (ItemCategory)obj; + return index == other.index; + } + + @Override + public int compareTo(ItemCategory o) { + if (o == null) { + return 1; + } + + return getIndex() - o.getIndex(); + } + } +} diff --git a/src/org/infinity/gui/LinkButton.java b/src/org/infinity/gui/LinkButton.java index 67e27d055..84b892038 100644 --- a/src/org/infinity/gui/LinkButton.java +++ b/src/org/infinity/gui/LinkButton.java @@ -4,6 +4,7 @@ package org.infinity.gui; +import java.awt.Color; import java.awt.Cursor; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -16,6 +17,7 @@ import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.SwingConstants; +import javax.swing.UIManager; import org.infinity.NearInfinity; import org.infinity.datatype.ResourceRef; @@ -27,11 +29,18 @@ /** * A JLabel-based control which supports either internal game resources or external URLs. */ -final public class LinkButton extends JLabel implements MouseListener, ActionListener { +public class LinkButton extends JLabel implements MouseListener, ActionListener { private static final String CMD_OPEN = "OPEN"; // open resource in same window private static final String CMD_OPEN_NEW = "OPEN_NEW"; // open resource in new window private static final String CMD_BROWSE = "BROWSE"; // open URL in system-default browser + /** Tooltip text if an alternate resource reference is used for the link button. */ + private static final String TOOLTIP_ALTERNATE_RESREF = "No resource assigned: Showing default resource."; + + /** Color of the linked text if an alternate resource reference is used (for light and dark L&F themes). */ + private static final Color LINK_COLOR_ALTERNATE_RESREF_LIGHT = new Color(0x800000); + private static final Color LINK_COLOR_ALTERNATE_RESREF_DARK = new Color(0xC04000); + private final List listeners = new ArrayList<>(); private ResourceEntry entry; @@ -60,6 +69,20 @@ public LinkButton(ResourceRef resourceRef, int maxLength) { setResource(resourceRef, maxLength); } + /** + * Creates a link button which points to an internal game resource as specified by the argument. + * + * @param resourceRef The game resource as ResourceRef object. + * @param maxLength Max. number of characters displayed in the label text. Full string is displayed as tooltip + * instead. + * @param isAlternate Specify {@code true} to change link color and tooltip to indicate that a fallback resource is + * used. + */ + public LinkButton(ResourceRef resourceRef, int maxLength, boolean isAlternate) { + setHorizontalAlignment(SwingConstants.LEFT); + setResource(resourceRef, maxLength, isAlternate); + } + /** * Creates a link button which points to an internal game resource as specified by the argument. * @@ -77,9 +100,22 @@ public LinkButton(String resourceName) { * instead. */ public LinkButton(String resourceName, int maxLength) { + this(resourceName, maxLength, false); + } + + /** + * Creates a link button which points to an internal game resource as specified by the argument. + * + * @param resourceName The game resource as string. + * @param maxLength Max. number of characters displayed in the label text. Full string is displayed as tooltip + * instead. + * @param isAlternate Specify {@code true} to change link color and tooltip to indicate that a fallback resource is + * used. + */ + public LinkButton(String resourceName, int maxLength, boolean isAlternate) { super(); setHorizontalAlignment(SwingConstants.LEFT); - setResource(resourceName, maxLength); + setResource(resourceName, maxLength, isAlternate); } /** @@ -113,8 +149,14 @@ public void setResource(ResourceRef resourceRef) { /** Creates a link from the specified resource reference. */ public void setResource(ResourceRef resourceRef, int maxLength) { + setResource(resourceRef, maxLength, false); + } + + /** Creates a link from the specified resource reference. */ + public void setResource(ResourceRef resourceRef, int maxLength, boolean isAlternate) { if (resourceRef != null) { - setResource(ResourceFactory.getResourceEntry(resourceRef.getResourceName()), resourceRef.toString(), maxLength); + setResource(ResourceFactory.getResourceEntry(resourceRef.getResourceName()), resourceRef.toString(), maxLength, + isAlternate); } else { setResource(null, null, maxLength); } @@ -127,16 +169,28 @@ public void setResource(String resourceName) { /** Attempts to create a link from the specified resource name. */ public void setResource(String resourceName, int maxLength) { - setResource(ResourceFactory.getResourceEntry(resourceName), resourceName, maxLength); + setResource(resourceName, maxLength, false); + } + + /** + * Attempts to create a link from the specified resource name. Link color and tooltip are changed if + * {@code isAlternate} is {@code true}. + */ + public void setResource(String resourceName, int maxLength, boolean isAlternate) { + setResource(ResourceFactory.getResourceEntry(resourceName), resourceName, maxLength, isAlternate); } private void setResource(ResourceEntry entry, String resourceName, int maxLength) { + setResource(entry, resourceName, maxLength, false); + } + + private void setResource(ResourceEntry entry, String resourceName, int maxLength, boolean isAlternate) { isResource = true; removeActionListener(this); this.entry = entry; if (entry != null) { addActionListener(this); - setLink(resourceName, entry.getResourceName(), true, maxLength); + setLink(resourceName, entry.getResourceName(), true, maxLength, isAlternate); setEnabled(true); // setToolTipText(null); } else { @@ -148,22 +202,40 @@ private void setResource(ResourceEntry entry, String resourceName, int maxLength /** Sets link or label text, depending on arguments. */ private void setLink(String text, String resource, boolean asLink, int maxLength) { + setLink(text, resource, asLink, maxLength, false); + } + + /** Sets link or label text, depending on arguments. */ + private void setLink(String text, String resource, boolean asLink, int maxLength, boolean isAlternate) { removeMouseListener(this); setCursor(null); if (text == null) { text = resource; } - String toolTip = null; + + String toolTip = isAlternate ? TOOLTIP_ALTERNATE_RESREF : null; + Color color = isAlternate ? getAlternateLinkColor() : null; + if (maxLength > 0 && text != null && text.length() > maxLength) { - toolTip = text; + if (toolTip == null || toolTip.isEmpty()) { + toolTip = text; + } else { + toolTip = text + " - " + toolTip; + } text = text.substring(0, maxLength) + "..."; } if (!asLink) { setText(text); } else if (resource != null && !resource.isEmpty()) { - setText("" + text + "" + text + "= 0xa0) { + return LINK_COLOR_ALTERNATE_RESREF_LIGHT; + } else { + return LINK_COLOR_ALTERNATE_RESREF_DARK; + } + } + /** Creates a link to an external URL. */ public void setUrl(String text, String url) { setUrl(text, url, 0); diff --git a/src/org/infinity/gui/OpenFileFrame.java b/src/org/infinity/gui/OpenFileFrame.java index f03396511..5f02fc7be 100644 --- a/src/org/infinity/gui/OpenFileFrame.java +++ b/src/org/infinity/gui/OpenFileFrame.java @@ -5,8 +5,8 @@ package org.infinity.gui; import java.awt.Component; +import java.awt.Container; import java.awt.Dimension; -import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; @@ -57,6 +57,7 @@ public final class OpenFileFrame extends ChildFrame implements ActionListener { private final JButton bOpen = new JButton("Open", Icons.ICON_OPEN_16.getIcon()); private final JButton bOpenNew = new JButton("Open in new window", Icons.ICON_OPEN_16.getIcon()); private final JCheckBox cbStayOpen = new JCheckBox("Keep this dialog open"); + private final JCheckBox cbAlwaysOnTop = new JCheckBox("Keep dialog always on top"); private final JLabel lExternalDrop = new JLabel("or drop file(s) here", SwingConstants.CENTER); private final JRadioButton rbExternal = new JRadioButton("Open external file"); private final JRadioButton rbInternal = new JRadioButton("Open internal file"); @@ -71,6 +72,8 @@ public OpenFileFrame() { rbExternal.setMnemonic('e'); rbInternal.setMnemonic('i'); cbStayOpen.setMnemonic('k'); + cbAlwaysOnTop.setMnemonic('t'); + cbAlwaysOnTop.addActionListener(this); ButtonGroup gb = new ButtonGroup(); gb.add(rbExternal); gb.add(rbInternal); @@ -102,6 +105,8 @@ public void changedUpdate(DocumentEvent e) { bOpenNew.setEnabled(false); bOpen.setMnemonic('o'); bOpenNew.setMnemonic('n'); + final Dimension dim = lExternalDrop.getPreferredSize(); + lExternalDrop.setPreferredSize(new Dimension(dim.width, dim.height * 4)); lExternalDrop.setBorder(BorderFactory.createLineBorder(UIManager.getColor("controlDkShadow"))); new DropTarget(lExternalDrop, new MyDropTargetListener()); rbExternal.setSelected(true); @@ -117,69 +122,64 @@ public void mouseClicked(MouseEvent event) { } }); - JPanel pane = (JPanel) getContentPane(); - GridBagLayout gbl = new GridBagLayout(); - GridBagConstraints gbc = new GridBagConstraints(); - pane.setLayout(gbl); - - gbc.weightx = 1.0; - gbc.gridwidth = GridBagConstraints.REMAINDER; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.insets = new Insets(6, 4, 3, 8); - gbl.setConstraints(rbExternal, gbc); - pane.add(rbExternal); - - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.weightx = 1.0; - gbc.gridwidth = 1; - gbc.insets = new Insets(0, 8, 3, 0); - gbl.setConstraints(tfExternalName, gbc); - pane.add(tfExternalName); - - gbc.fill = GridBagConstraints.NONE; - gbc.weightx = 0.0; - gbc.gridwidth = GridBagConstraints.REMAINDER; - gbc.insets = new Insets(0, 3, 3, 8); - gbl.setConstraints(bExternalBrowse, gbc); - pane.add(bExternalBrowse); - - gbc.fill = GridBagConstraints.BOTH; - gbc.weightx = 1.0; - gbc.weighty = 1.0; - gbc.insets = new Insets(0, 8, 3, 8); - gbl.setConstraints(lExternalDrop, gbc); - pane.add(lExternalDrop); - - gbc.weighty = 0.0; - gbc.weightx = 1.0; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.insets = new Insets(9, 4, 3, 8); - gbl.setConstraints(rbInternal, gbc); - pane.add(rbInternal); - - gbc.weighty = 3.0; - gbc.fill = GridBagConstraints.BOTH; - gbc.insets = new Insets(0, 8, 3, 8); - gbl.setConstraints(lpInternal, gbc); - pane.add(lpInternal); - - JPanel bPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); - bPanel.add(bOpen); - bPanel.add(bOpenNew); - - gbc.fill = GridBagConstraints.HORIZONTAL; - gbc.weighty = 0.0; - gbc.insets = new Insets(3, 8, 0, 8); - - gbl.setConstraints(bPanel, gbc); - pane.add(bPanel); - - gbc.insets.top = 0; - gbc.insets.bottom = 6; - gbc.anchor = GridBagConstraints.CENTER; - gbc.fill = GridBagConstraints.NONE; - gbl.setConstraints(cbStayOpen, gbc); - pane.add(cbStayOpen); + final Container pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + + final JPanel mainPanel = new JPanel(new GridBagLayout()); + final GridBagConstraints gbc = new GridBagConstraints(); + + final JPanel externalFilePanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + externalFilePanel.add(tfExternalName, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0), 0, 0); + externalFilePanel.add(bExternalBrowse, gbc); + + final JPanel buttonPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_END, GridBagConstraints.NONE, + new Insets(0, 0, 0, 4), 0, 0); + buttonPanel.add(bOpen, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 4, 0, 0), 0, 0); + buttonPanel.add(bOpenNew, gbc); + + final JPanel optionsPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + optionsPanel.add(cbStayOpen, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0), 0, 0); + optionsPanel.add(cbAlwaysOnTop, gbc); + ViewerUtil.setGBC(gbc, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + optionsPanel.add(new JPanel(), gbc); + + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + mainPanel.add(rbExternal, gbc); + ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(externalFilePanel, gbc); + ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(lExternalDrop, gbc); + ViewerUtil.setGBC(gbc, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(16, 0, 0, 0), 0, 0); + mainPanel.add(rbInternal, gbc); + ViewerUtil.setGBC(gbc, 0, 4, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(lpInternal, gbc); + ViewerUtil.setGBC(gbc, 0, 5, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(buttonPanel, gbc); + ViewerUtil.setGBC(gbc, 0, 6, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(optionsPanel, gbc); + + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, + new Insets(8, 8, 8, 8), 0, 0); + pane.add(mainPanel, gbc); pack(); setMinimumSize(getSize()); @@ -226,6 +226,8 @@ public void actionPerformed(ActionEvent event) { } else { openExternalFile(this, FileManager.resolve(tfExternalName.getText())); } + } else if (event.getSource() == cbAlwaysOnTop) { + setAlwaysOnTop(cbAlwaysOnTop.isSelected()); } } diff --git a/src/org/infinity/gui/OpenResourceDialog.java b/src/org/infinity/gui/OpenResourceDialog.java index d377ab36b..592a97e2e 100644 --- a/src/org/infinity/gui/OpenResourceDialog.java +++ b/src/org/infinity/gui/OpenResourceDialog.java @@ -80,7 +80,7 @@ public class OpenResourceDialog extends JDialog implements ItemListener, ListSel */ public static ResourceEntry[] showOpenDialog(Window owner, String title, String[] extensions, boolean multiSelection) { - ResourceEntry[] retVal = null; + ResourceEntry[] retVal; if (title == null) { title = "Select resource"; } diff --git a/src/org/infinity/gui/PreferencesDialog.java b/src/org/infinity/gui/PreferencesDialog.java index 8ab566f67..95160662d 100644 --- a/src/org/infinity/gui/PreferencesDialog.java +++ b/src/org/infinity/gui/PreferencesDialog.java @@ -119,7 +119,7 @@ public enum Category { private final String label; - private Category(String label) { + Category(String label) { this.label = label; } @@ -243,7 +243,13 @@ public String toString() { "With this option enabled Near Infinity remembers current size and position of child windows on the screen.
" + "This information is only remembered across the current session and will be discarded whenever " + "Near Infinity is closed.", - AppOption.REMEMBER_CHILD_FRAME_RECT) + AppOption.REMEMBER_CHILD_FRAME_RECT), + OptionCheckBox.create(AppOption.SHOW_CREATURES_ON_PANEL.getName(), AppOption.SHOW_CREATURES_ON_PANEL.getLabel(), + "With this option enabled creatures from the currently opened game will progressively populate the " + + "main panel and perform all kinds of random actions. This option is primarily meant as a gimmick. ;)" + + "

Note: Changing this option requires to open a new game or use the command " + + "\"Refresh Tree\" (Shortcut: F5)

", + AppOption.SHOW_CREATURES_ON_PANEL) ) ), OptionCategory.create(Category.SCRIPT_COMPILER, @@ -1129,13 +1135,10 @@ public void mouseExited(MouseEvent e) { } }); - cb.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - checkBox.setValue(cb.isSelected()); - checkBox.fireOnAction(); - setModified(true); - } + cb.addActionListener(e -> { + checkBox.setValue(cb.isSelected()); + checkBox.fireOnAction(); + setModified(true); }); return checkBox; @@ -1175,13 +1178,10 @@ public void mouseExited(MouseEvent e) { label.addMouseListener(adapter); comboBox.addMouseListener(adapter); - comboBox.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - groupBox.setSelectedIndex(comboBox.getSelectedIndex()); - groupBox.fireOnSelect(); - setModified(true); - } + comboBox.addActionListener(e -> { + groupBox.setSelectedIndex(comboBox.getSelectedIndex()); + groupBox.fireOnSelect(); + setModified(true); }); return groupBox; diff --git a/src/org/infinity/gui/QuickSearch.java b/src/org/infinity/gui/QuickSearch.java index f37fa24d1..cf407ddaf 100644 --- a/src/org/infinity/gui/QuickSearch.java +++ b/src/org/infinity/gui/QuickSearch.java @@ -239,7 +239,7 @@ private void close(Result result) { item = cbSearch.getItemAt(0); } - if (item instanceof ResourceEntry) { + if (item != null) { if (result == Result.OPEN) { tree.select((ResourceEntry) item); } else if (result == Result.OPEN_NEW) { @@ -306,10 +306,10 @@ private MapTree> generateNode(MapTree()); + retVal.setValue(new Vector<>()); } } else { - retVal = new MapTree<>(ch, new Vector()); + retVal = new MapTree<>(ch, new Vector<>()); } node.addChild(retVal); diff --git a/src/org/infinity/gui/ResourceTree.java b/src/org/infinity/gui/ResourceTree.java index 43e97ca4b..1104cf22b 100644 --- a/src/org/infinity/gui/ResourceTree.java +++ b/src/org/infinity/gui/ResourceTree.java @@ -304,7 +304,6 @@ public static void renameResource(FileResourceEntry entry) { JOptionPane.showMessageDialog(NearInfinity.getInstance(), "Error renaming file \"" + filename + "\"!", "Error", JOptionPane.ERROR_MESSAGE); Logger.error(e); - return; } // ResourceFactory.getResourceTreeModel().resourceEntryChanged(entry); } @@ -312,7 +311,7 @@ public static void renameResource(FileResourceEntry entry) { /** Attempts to delete the specified resource if it exists as a file in the game path. */ public static void deleteResource(ResourceEntry entry) { if (entry instanceof FileResourceEntry) { - String options[] = { "Delete", "Cancel" }; + String[] options = { "Delete", "Cancel" }; if (JOptionPane.showOptionDialog(NearInfinity.getInstance(), "Are you sure you want to delete " + entry + '?', "Delete file", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]) != 0) { return; @@ -531,7 +530,7 @@ private static Path getTempFile(Path file) { // -------------------------- INNER CLASSES -------------------------- - private final class TreeExpandListener implements TreeExpansionListener, TreeWillExpandListener { + private static final class TreeExpandListener implements TreeExpansionListener, TreeWillExpandListener { private final JTree tree; private Cursor defaultCursor; @@ -792,15 +791,15 @@ public void popupMenuWillBecomeVisible(PopupMenuEvent event) { miRestore.setEnabled(isBackupAvailable(entry)); miZip.setEnabled(entry instanceof FileResourceEntry && Profile.isSaveGame(entry.getActualPath())); - String path = ""; + final StringBuilder path = new StringBuilder(); if (tree.getLastSelectedPathComponent() instanceof ResourceTreeFolder) { ResourceTreeFolder folder = (ResourceTreeFolder) tree.getLastSelectedPathComponent(); while (folder != null && !folder.folderName().isEmpty()) { - path = folder.folderName() + "/" + path; + path.insert(0, folder.folderName() + "/"); folder = folder.getParentFolder(); } } - miZip.setEnabled(!path.isEmpty() && Profile.isSaveGame(FileManager.resolve(path))); + miZip.setEnabled((path.length() > 0) && Profile.isSaveGame(FileManager.resolve(path.toString()))); } @Override diff --git a/src/org/infinity/gui/ScriptTextArea.java b/src/org/infinity/gui/ScriptTextArea.java index 980b8e8a9..e309c6be0 100644 --- a/src/org/infinity/gui/ScriptTextArea.java +++ b/src/org/infinity/gui/ScriptTextArea.java @@ -471,7 +471,7 @@ private InteractiveToken updateSymbolToken(Token token) { Long value = IdsMapCache.getIdsValue(idsRef, token.getLexeme(), null); if (value != null) { retVal = new InteractiveToken(token.getOffset(), token.length(), - idsRef + ": " + value.toString() + " (0x" + Long.toHexString(value) + ")", + idsRef + ": " + value + " (0x" + Long.toHexString(value) + ")", ResourceFactory.getResourceEntry(idsRef), getForegroundForToken(token)); } } @@ -530,7 +530,7 @@ private InteractiveToken updateStringToken(Token token) { } } else if (type.equals(Signatures.Function.Parameter.RESTYPE_SPELL_LIST)) { // list of spell codes - String text = ""; + final StringBuilder text = new StringBuilder(); ArrayList resList = new ArrayList<>(); for (int i = 0, cnt = value.length() / 4; i < cnt; i++) { String snum = value.substring(i * 4, i * 4 + 4); @@ -547,12 +547,12 @@ private InteractiveToken updateStringToken(Token token) { } if (res != null) { if (i > 0) { - text += ", "; + text.append(", "); } - text += res; + text.append(res); } } - retVal = new InteractiveToken(token.getOffset() + 1, token.length() - 2, text, null, + retVal = new InteractiveToken(token.getOffset() + 1, token.length() - 2, text.toString(), null, getForegroundForToken(token)); retVal.resourceEntries.addAll(resList); retVal.resourceEntries.add(ResourceFactory.getResourceEntry("SPELL.IDS")); @@ -718,13 +718,9 @@ public void addResEntry(ResourceEntry resEntry) { } public void clearResEntries() { - itemsOpen.forEach(item -> { - item.removeActionListener(this); - }); + itemsOpen.forEach(item -> item.removeActionListener(this)); itemsOpen.clear(); - itemsOpenNew.forEach(item -> { - item.removeActionListener(this); - }); + itemsOpenNew.forEach(item -> item.removeActionListener(this)); itemsOpenNew.clear(); removeAll(); } diff --git a/src/org/infinity/gui/SoundPanel.java b/src/org/infinity/gui/SoundPanel.java new file mode 100644 index 000000000..1d668cdec --- /dev/null +++ b/src/org/infinity/gui/SoundPanel.java @@ -0,0 +1,1264 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.gui; + +import java.awt.Cursor; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.beans.PropertyChangeEvent; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.swing.BoundedRangeModel; +import javax.swing.DefaultBoundedRangeModel; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.SwingConstants; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.basic.BasicSliderUI; + +import org.infinity.exceptions.ResourceNotFoundException; +import org.infinity.icon.Icons; +import org.infinity.resource.Closeable; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.resource.sound.AudioBuffer; +import org.infinity.resource.sound.AudioFactory; +import org.infinity.resource.sound.AudioPlayback; +import org.infinity.resource.sound.AudioStateEvent; +import org.infinity.resource.sound.AudioStateListener; +import org.infinity.resource.sound.BufferedAudioPlayback; +import org.infinity.resource.sound.BufferedAudioPlayer; +import org.infinity.resource.sound.WavBuffer; +import org.infinity.util.Threading; +import org.tinylog.Logger; + +/** + * A customizable panel with controls for playing back individual sound clips. + *

+ * Playback state of sounds can be tracked by {@link AudioStateEvent}s. + *

+ */ +public class SoundPanel extends JPanel implements Closeable { + /** Optional UI controls for display. */ + public enum Option { + /** + * Specifies that the {@code Play} and {@code Pause} buttons are combined to a single button to save space. + */ + COMPACT_CONTROLS, + /** + * Specifies a label that displays the current playback time. The control is shown below the playback controls and + * progress bar if visible. + *

+ * The {@link DisplayFormat} enum can be used to further customize the display. + *

+ */ + TIME_LABEL, + /** + * Specifies a checkbox for enabling or disabling looped playback. The control is shown on the right side of the + * playback controls. + */ + LOOP_CHECKBOX, + /** + * Specifies a slider that shows the playback current progress and allows the user to jump to specific positions + * within the audio clip. The control is shown directly below the playback controls. + */ + PROGRESS_BAR, + /** + * Specifies that the progress bar should display labels for every major tick (e.g. every 10 or 30 seconds). + *

This option is only effective if {@link #PROGRESS_BAR} is enabled as well.

+ */ + PROGRESS_BAR_LABELS, + } + + /** List of available formatted strings for displaying playback time. */ + public enum DisplayFormat { + /** Display of elapsed time (minutes and seconds): {@code mm:ss}. */ + ELAPSED("%1$02d:%2$02d"), + + /** Display of elapsed time (minutes, seconds, deciseconds): {@code mm:ss,x}. */ + ELAPSED_PRECISE("%1$02d:%2$02d,%3$d"), + + /** Display of elapsed and total time (minutes and seconds): {@code mm:ss / mm:ss}. */ + ELAPSED_TOTAL("%1$02d:%2$02d / %4$02d:%5$02d"), + + /** Display of elapsed and total time (minutes, seconds, and deciseconds): {@code mm:ss,x / mm:ss,x}. */ + ELAPSED_TOTAL_PRECISE("%1$02d:%2$02d,%3$d / %4$02d:%5$02d,%6$d"), + ; + + /** + * Format string supports the following positional placeholders: + *
    + *
  • Index 1: Elapsed time, minutes portion (integer)
  • + *
  • Index 2: Elapsed time, seconds portion (integer)
  • + *
  • Index 3: Elapsed time, deciseconds portion (integer)
  • + * + *
  • Index 4: Total time, minutes portion (integer)
  • + *
  • Index 5: Total time, seconds portion (integer)
  • + *
  • Index 6: Total time, deciseconds portion (integer)
  • + *
+ */ + private final String fmt; + + DisplayFormat(String fmt) { + this.fmt = fmt; + } + + /** + * Returns the format string associated with the enum value. + * + * @return A format string. + */ + public String getFormatString() { + return fmt; + } + + /** + * Returns a formatted string based on the specified parameters. + * + * @param elapsed The elapsed playback time, in milliseconds. + * @param total The total playback time, in milliseconds. + * @return Formatted string based on {@link #getFormatString()}.. + */ + public String toString(long elapsed, long total) { + if (fmt.isEmpty()) { + // shortcut + return ""; + } else { + final int elapsedMinutes = (int) (elapsed / 60_000L); + final int elapsedSeconds = (int) ((elapsed / 1000L) % 60L); + final int elapsedFractional = (int) ((elapsed / 100L) % 10L); + final int totalMinutes = (int) (total / 60_000L); + final int totalSeconds = (int) ((total / 1000L) % 60L); + final int totalFractional = (int) ((total / 100L) % 10L); + return String.format(fmt, elapsedMinutes, elapsedSeconds, elapsedFractional, totalMinutes, totalSeconds, + totalFractional); + } + } + } + + /** + * Property name for {@link PropertyChangeEvent} calls. + * Associated parameter is of type {@link Boolean} and indicates the playback state (playing/stopped). + */ + public static final String PROPERTY_NAME_PLAYING = "soundPanelPlaying"; + + /** + * Property name for {@link PropertyChangeEvent} calls. + * Associated parameter is of type {@link Boolean} and indicates the paused state (paused/playing). + */ + public static final String PROPERTY_NAME_PAUSED = "soundPanelPaused"; + + private static final String CMD_PLAY = "play"; + private static final String CMD_PAUSE = "pause"; + private static final String CMD_STOP = "stop"; + private static final String CMD_LOOP = "loop"; + + private static final ImageIcon ICON_PLAY = Icons.ICON_PLAY_16.getIcon(); + private static final ImageIcon ICON_PAUSE = Icons.ICON_PAUSE_16.getIcon(); + private static final ImageIcon ICON_STOP = Icons.ICON_STOP_16.getIcon(); + + private static boolean looped = false; + + private final List stateListeners = new ArrayList<>(); + + private final Runner runner; + private final Listeners listener = new Listeners(); + + private JButton playButton; + private JButton pauseButton; + private JButton stopButton; + private JLabel displayLabel; + private JCheckBox loopCheckBox; + private FixedSlider progressSlider; + + private ResourceEntry soundEntry; + private DisplayFormat displayFormat; + private AudioBuffer audioBuffer; + + private boolean closed; + + private boolean progressAdjusting; + private boolean combinedPlayPause; + private boolean showProgressLabels; + + /** + * Creates a new sound panel. Initializes it with {@link DisplayFormat#ELAPSED_TOTAL} to display time but does not + * open a sound resource. + * + * @param options A set of {@link Option} values that controls visibility of optional control elements. + */ + public SoundPanel(Option... options) { + super(new GridBagLayout()); + init(options); + setDisplayFormat(DisplayFormat.ELAPSED_TOTAL); + runner = new Runner(); + } + + /** + * Creates a new sound panel, opens the specified sound resource and initializes it with + * {@link DisplayFormat#ELAPSED_TOTAL} to display time. + * + * @param entry {@link ResourceEntry} of the sound resource. + * @param options A set of {@link Option} values that controls visibility of optional control elements. + * @throws ResourceNotFoundException if the resource referenced by the {@code ResourceEntry} parameter does not exist. + * @throws NullPointerException if {@code entry} is {@code null}. + */ + public SoundPanel(ResourceEntry entry, Option... options) throws ResourceNotFoundException { + this(entry, DisplayFormat.ELAPSED_TOTAL, false, options); + } + + /** + * Creates a new sound panel and opens the specified sound resource. + * + * @param entry {@link ResourceEntry} of the sound resource. + * @param format {@link DisplayFormat} enum with the format description. {@code null} resolves to + * {@link DisplayFormat#ELAPSED_TOTAL}. + * @param options A set of {@link Option} values that controls visibility of optional control elements. + * @throws ResourceNotFoundException if the resource referenced by the {@code ResourceEntry} parameter does not exist. + * @throws NullPointerException if {@code entry} is {@code null}. + */ + public SoundPanel(ResourceEntry entry, DisplayFormat format, Option... options) throws ResourceNotFoundException { + this(entry, format, false, options); + } + + /** + * Creates a new sound panel and opens the specified sound resource. + * + * @param entry {@link ResourceEntry} of the sound resource. + * @param format {@link DisplayFormat} enum with the format description. {@code null} resolves to + * {@link DisplayFormat#ELAPSED_TOTAL}. + * @param playback Specifies whether sound playback should start automatically. + * @param options A set of {@link Option} values that controls visibility of optional control elements. + * @throws ResourceNotFoundException if the resource referenced by the {@code ResourceEntry} parameter does not exist. + * @throws NullPointerException if {@code entry} is {@code null}. + */ + public SoundPanel(ResourceEntry entry, DisplayFormat format, boolean playback, Option... options) + throws ResourceNotFoundException { + super(new GridBagLayout()); + init(options); + setDisplayFormat(format); + runner = new Runner(); + loadSound(entry); + if (playback) { + setPlaying(true); + } + } + + /** + * Stops playback of the old sound resource, if any, and loads the specified sound resource. + * + * @param entry {@link ResourceEntry} of the sound resource to load. + * @throws ResourceNotFoundException if the resource referenced by the {@code ResourceEntry} parameter does not exist. + * @throws NullPointerException if {@code entry} is {@code null}. + */ + public void loadSound(ResourceEntry entry) throws ResourceNotFoundException { + loadSound(entry, null); + } + + /** + * Stops playback of the old sound resource, if any, and loads the specified sound resource. + * + * @param entry {@link ResourceEntry} of the sound resource to load. + * @param onCompleted An optional {@link Consumer} operation that is executed when the resource has been loaded. The + * boolean parameter signals success ({@code true}) or failure ({@code false}) of the load + * operation. + * @throws ResourceNotFoundException if the resource referenced by the {@code ResourceEntry} parameter does not exist. + * @throws NullPointerException if {@code entry} is {@code null}. + */ + public void loadSound(ResourceEntry entry, Consumer onCompleted) throws ResourceNotFoundException { + if (isClosed()) { + return; + } + + if (entry == null) { + throw new NullPointerException("entry is null"); + } + + if (entry.getActualPath() != null && !Files.isRegularFile(entry.getActualPath())) { + throw new ResourceNotFoundException("Not found: " + entry); + } + + unload(); + + // sound resource is loaded asynchronously + setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); + + final Supplier operation = () -> { + try { + AudioBuffer.AudioOverride override = null; + AudioBuffer buffer; + + // ignore # channels in ACM headers + if (entry.getExtension().equalsIgnoreCase("ACM")) { + override = AudioBuffer.AudioOverride.overrideChannels(2); + } + + buffer = AudioFactory.getAudioBuffer(entry, override); + if (buffer != null) { + soundEntry = entry; + runner.setAudio(buffer); + runner.setLooped(isLooped()); + audioBuffer = buffer; + initSliderRange(); + } else { + throw new Exception("Audio format could not be determined."); + } + } catch (Throwable e) { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return e; + } + return null; + }; + + final Consumer finalize = ex -> { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + if (onCompleted != null) { + onCompleted.accept(ex == null); + } + if (ex != null) { + // operation failed to complete + Logger.error(ex); + Threading.invokeInEventThread(() -> JOptionPane.showMessageDialog(SoundPanel.this.getTopLevelAncestor(), + "Could not load sound resource: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE)); + } + }; + + CompletableFuture.supplyAsync(operation).thenAccept(finalize); + } + + /** Stops playback and releases the current sound resource, if any. */ + public void unload() { + if (isClosed()) { + return; + } + + setPlaying(false); + soundEntry = null; + audioBuffer = null; + try { + runner.setAudio(null); + } catch (Exception e) { + Logger.debug(e); + } + } + + /** + * Returns the currently loaded sound resource. + * + * @return {@link ResourceEntry} of the current sound resource. Returns {@code null} if no sound resource is loaded. + */ + public ResourceEntry getSoundResource() { + return soundEntry; + } + + /** + * Provides access to basic properties of the currently loaded audio resource. + * + * @return {@link AudioBuffer} instance of the currently loaded audio resource. Returns {@code null} if no audio + * resource is loaded. + */ + public AudioBuffer getAudioBuffer() { + return audioBuffer; + } + + /** + * Returns the current display format of playback time. + * + * @return a {@link DisplayFormat} object describing the current playback time display. + */ + public DisplayFormat getDisplayFormat() { + return displayFormat; + } + + /** + * Convenience method that returns whether the currently loaded sound resource contains uncompressed WAV audio data. + * + * @return {@code true} if a sound resource is loaded and contains uncompressed WAV audio data, {@code false} + * otherwise. + */ + public boolean isWavFile() { + return audioBuffer instanceof WavBuffer; + } + + /** + * Returns {@code true} if the control designated by the specified {@link Option} enum is visible. + * + * @param option {@link Option} enum to check. + * @return {@code true} if the associated control is visible, {@code false} otherwise. + * @throws NullPointerException if {@code option} is {@code null}. + */ + public boolean isOptionEnabled(Option option) { + if (option == null) { + throw new NullPointerException("option is null"); + } + + switch (option) { + case COMPACT_CONTROLS: + return combinedPlayPause; + case LOOP_CHECKBOX: + return loopCheckBox.isVisible(); + case PROGRESS_BAR: + return progressSlider.isVisible(); + case PROGRESS_BAR_LABELS: + return showProgressLabels; + case TIME_LABEL: + return displayLabel.isVisible(); + } + + return false; + } + + /** + * Specifies the display format for playback time of the sound resource. + * + * @param format {@link DisplayFormat} enum with the format description. {@code null} resolves to + * {@link DisplayFormat#ELAPSED_TOTAL}. + */ + public void setDisplayFormat(DisplayFormat format) { + displayFormat = (format != null) ? format : DisplayFormat.ELAPSED_TOTAL; + } + + /** Returns whether loop mode is enabled. */ + public boolean isLooped() { + return loopCheckBox.isSelected(); + } + + /** Enables or disable loop mode. */ + public void setLooped(boolean loop) { + if (isClosed()) { + return; + } + + if (loopCheckBox.isSelected() != loop) { + loopCheckBox.setSelected(loop); + } + looped = loop; + runner.setLooped(loop); + } + + /** + * Returns whether playback is currently active. Pausing playback does not affect the result. + * + * @return {@code true} if sound is currently played back or paused, {@code false} otherwise. + */ + public boolean isPlaying() { + if (isClosed()) { + return false; + } + + return runner.isPlaying(); + } + + /** + * Sets the playback state of the current sound resource. The paused state does not affect the playback state. + * + * @param play Specify {@code true} to start playback or {@code false} to stop playback. + */ + public void setPlaying(boolean play) { + if (isClosed()) { + return; + } + + if (runner.isPlaying() == play) { + return; + } + + runner.setPlaying(play); + } + + /** + * Returns whether playback is currently paused. + * + * @return {@code true} only if playback is active but paused, {@code false} otherwise. + */ + public boolean isPaused() { + if (isClosed()) { + return false; + } + + return runner.isPaused(); + } + + /** + * Pauses playback. Does nothing if playback currently not active. + * + * @param pause Specify {@code true} to pause current playback. Specify {@code false} to continue playback. + */ + public void setPaused(boolean pause) { + if (isClosed()) { + return; + } + + if (!runner.isPlaying() || runner.isPaused() == pause) { + return; + } + + runner.setPaused(pause); + } + + /** + * Returns whether this sound panel has been closed. A closed panel does not load or play sound files anymore. + * + * @return {@code true} if the sound panel has been closed, {@code false} otherwise. + */ + public boolean isClosed() { + return closed; + } + + /** + * Stops playback and releases any resources. After calling this method it is not possible to load or play sound + * resources anymore. + */ + @Override + public void close() throws Exception { + unload(); + runner.terminate(); + closed = true; + } + + @Override + public void setEnabled(boolean enabled) { + if (enabled) { + updateControls(); + } else { + playButton.setEnabled(false); + if (!combinedPlayPause) { + pauseButton.setEnabled(false); + } + stopButton.setEnabled(false); + progressSlider.setEnabled(false); + } + displayLabel.setEnabled(enabled); + loopCheckBox.setEnabled(enabled); + super.setEnabled(enabled); + } + + /** Adds a {@link AudioStateListener} to the sound panel instance. */ + public void addAudioStateListener(AudioStateListener l) { + if (l != null) { + listenerList.add(AudioStateListener.class, l); + } + } + + /** Returns all registered {@link AudioStateListener}s for this audio player instance. */ + public AudioStateListener[] getAudioStateListeners() { + return listenerList.getListeners(AudioStateListener.class); + } + + /** Removes a {@link AudioStateListener} from the audio player instance. */ + public void removeAudioStateListener(AudioStateListener l) { + if (l != null) { + listenerList.remove(AudioStateListener.class, l); + } + } + + /** Fires a {@link AudioStateListener} of the specified name to all registered listeners. */ + private void fireAudioStateEvent(AudioStateEvent.State state, Object value) { + // collecting + stateListeners.clear(); + final Object[] entries = getAudioStateListeners(); + for (int i = entries.length - 2; i >= 0; i -= 2) { + if (entries[i] == AudioStateListener.class) { + stateListeners.add((AudioStateListener) entries[i + 1]); + } + } + + // executing + if (!stateListeners.isEmpty()) { + final AudioStateEvent event = new AudioStateEvent(this, state, value); + SwingUtilities.invokeLater(() -> stateListeners.forEach(l -> l.audioStateChanged(event))); + } + } + + /** Fires a {@link AudioStateEvent} when a sound resource has been opened and is ready for playback. */ + private void fireSoundOpened() { + final String value = (getSoundResource() != null) ? getSoundResource().getResourceName() : null; + fireAudioStateEvent(AudioStateEvent.State.OPEN, value); + } + + /** Fires a {@link AudioStateEvent} when the current sound resource has been closed. */ + private void fireSoundClosed() { + final String value = (getSoundResource() != null) ? getSoundResource().getResourceName() : null; + fireAudioStateEvent(AudioStateEvent.State.CLOSE, value); + } + + /** Fires a {@link AudioStateEvent} when starting playback. */ + private void fireSoundStarted() { + fireAudioStateEvent(AudioStateEvent.State.START, null); + } + + /** Fires a {@link AudioStateEvent} when stopping playback. */ + private void fireSoundStopped() { + fireAudioStateEvent(AudioStateEvent.State.STOP, null); + } + + /** Fires a {@link AudioStateEvent} when entering the paused state. */ + private void fireSoundPaused() { + fireAudioStateEvent(AudioStateEvent.State.PAUSE, runner.getSoundPosition()); + } + + /** Fires a {@link AudioStateEvent} when resuming from the paused state. */ + private void fireSoundResumed() { + fireAudioStateEvent(AudioStateEvent.State.RESUME, runner.getSoundPosition()); + } + + private void updateLabel() { + final long elapsedTime = runner.getSoundPosition(); + final long totalTime = runner.getTotalLength(); + final String displayString = getDisplayFormat().toString(elapsedTime, totalTime); + displayLabel.setText(displayString); + + final AdjustingBoundedRangeModel model = (AdjustingBoundedRangeModel)progressSlider.getModel(); + model.setDirectValue((int)elapsedTime); + } + + /** Updates playback UI controls to reflect the current state. */ + private void updateControls() { +// Logger.trace("SoundPanel.updateControls: isAvailable={}, isPlaying={}, isPaused={}", runner.isAvailable(), +// runner.isPlaying(), runner.isPaused()); + if (runner.isAvailable()) { + if (runner.isPlaying()) { + if (combinedPlayPause) { + playButton.setIcon(runner.isPaused() ? ICON_PLAY : ICON_PAUSE); + playButton.setEnabled(true); + } else { + playButton.setEnabled(runner.isPaused()); + pauseButton.setEnabled(!runner.isPaused()); + } + stopButton.setEnabled(true); + progressSlider.setEnabled(true); + } else { + if (combinedPlayPause) { + playButton.setIcon(ICON_PLAY); + } else { + pauseButton.setEnabled(false); + } + playButton.setEnabled(true); + stopButton.setEnabled(false); + progressSlider.setEnabled(false); + } + } else { + playButton.setIcon(ICON_PLAY); + playButton.setEnabled(false); + if (!combinedPlayPause) { + pauseButton.setEnabled(false); + } + stopButton.setEnabled(false); + progressSlider.setEnabled(false); + } + } + + /** Resets slider settings of the progress bar. */ + private void initSliderRange() { + if (runner.isAvailable()) { + final int duration = (int) runner.getTotalLength(); + progressSlider.setMaximum(duration); + if (duration < 45_000) { + // major: per ten seconds, minor: per second + progressSlider.setMajorTickSpacing(10_000); + progressSlider.setMinorTickSpacing(1000); + } else { + // major: per minute, minor: per ten seconds + progressSlider.setMajorTickSpacing(30_000); + progressSlider.setMinorTickSpacing(5000); + } + initSliderTickLabels(); + } else { + progressSlider.setMaximum(1); + progressSlider.setMajorTickSpacing(1); + progressSlider.setMinorTickSpacing(1); + } + } + + /** Defines labels for the major progress bar ticks. */ + private void initSliderTickLabels() { + if (progressSlider == null || !progressSlider.isVisible() || !showProgressLabels) { + return; + } + + progressSlider.setLabelTable(null); + + final Hashtable labels = new Hashtable<>(); + final int spacing = progressSlider.getMajorTickSpacing(); + for (int pos = progressSlider.getMinimum(); pos < progressSlider.getMaximum(); pos += spacing) { + labels.put(pos, createSliderTickLabel(pos)); + } + + // add label for end of range if suitable + final int minSpace; + if (progressSlider.getMaximum() < 10_000) { + minSpace = 0; + } else if (progressSlider.getMaximum() < 45_000) { + minSpace = spacing * 2 / 3; + } else { + minSpace = spacing / 2; + } + final int remainingSpace = (progressSlider.getMaximum() - progressSlider.getMinimum()) % spacing; + if (remainingSpace > minSpace) { + labels.put(progressSlider.getMaximum(), createSliderTickLabel(progressSlider.getMaximum())); + } + + if (!labels.isEmpty()) { + progressSlider.setLabelTable(labels); + } + } + + /** Creates a label for a slider tick with the specified time value. */ + private static JLabel createSliderTickLabel(int timeMs) { + final int min = timeMs / 60_000; + final int sec = (timeMs / 1000) % 60; + final JLabel label = new JLabel(String.format("%02d:%02d", min, sec)); + final Font font = label.getFont(); + label.setFont(font.deriveFont(Font.PLAIN, font.getSize2D() * 0.75f)); + return label; + } + + /** Creates the sound panel. */ + private void init(Option[] options) { + closed = false; + + combinedPlayPause = isOption(options, Option.COMPACT_CONTROLS); + + playButton = new JButton(ICON_PLAY); + playButton.setActionCommand(CMD_PLAY); + playButton.addActionListener(listener); + + if (!combinedPlayPause) { + pauseButton = new JButton(ICON_PAUSE); + pauseButton.setActionCommand(CMD_PAUSE); + pauseButton.addActionListener(listener); + } else { + playButton.setToolTipText("Start or pause playback."); + } + + stopButton = new JButton(ICON_STOP); + stopButton.setActionCommand(CMD_STOP); + stopButton.addActionListener(listener); + + loopCheckBox = new JCheckBox("Loop", looped); + loopCheckBox.setActionCommand(CMD_LOOP); + loopCheckBox.addActionListener(listener); + loopCheckBox.setVisible(isOption(options, Option.LOOP_CHECKBOX)); + + displayLabel = new JLabel(DisplayFormat.ELAPSED_TOTAL.toString(0L, 0L), SwingConstants.LEADING); + displayLabel.setVisible(isOption(options, Option.TIME_LABEL)); + + progressSlider = new FixedSlider(new AdjustingBoundedRangeModel()); + progressSlider.setOrientation(SwingConstants.HORIZONTAL); + progressSlider.setPaintTicks(true); + progressSlider.addChangeListener(listener); + progressSlider.setVisible(isOption(options, Option.PROGRESS_BAR)); + + showProgressLabels = isOption(options, Option.PROGRESS_BAR_LABELS); + progressSlider.setPaintLabels(showProgressLabels); + + // assembling panel + final GridBagConstraints gbc = new GridBagConstraints(); + final JPanel playbackPanel = new JPanel(new GridBagLayout()); + int idx = 0; + ViewerUtil.setGBC(gbc, idx++, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + playbackPanel.add(playButton, gbc); + if (!combinedPlayPause) { + ViewerUtil.setGBC(gbc, idx++, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 8, 0, 0), 0, 0); + playbackPanel.add(pauseButton, gbc); + } + ViewerUtil.setGBC(gbc, idx++, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 8, 0, 0), 0, 0); + playbackPanel.add(stopButton, gbc); + ViewerUtil.setGBC(gbc, idx++, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0), 0, 0); + playbackPanel.add(loopCheckBox, gbc); + + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + add(playbackPanel, gbc); + + ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + add(progressSlider, gbc); + + ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + add(displayLabel, gbc); + + // cosmetic adjustment + if (progressSlider.isVisible() && !loopCheckBox.isVisible()) { + final int sliderWidth = playbackPanel.getPreferredSize().width; + final int sliderHeight = progressSlider.getPreferredSize().height; + progressSlider.setPreferredSize(new Dimension(sliderWidth, sliderHeight)); + } + if (displayLabel.isVisible() && !loopCheckBox.isVisible()) { + displayLabel.setHorizontalAlignment(SwingConstants.CENTER); + } + } + + /** + * Returns whether the specified option is listed in the options array. + * + * @param options Array of {@link Option} enums. + * @param option {@link Option} to test. + * @return {@code true} if {@code option} is found in {@code options}, {@code false} otherwise. + */ + private boolean isOption(Option[] options, Option option) { + boolean retVal = false; + if (options != null) { + for (final Option o : options) { + if (o == option) { + retVal = true; + break; + } + } + } + return retVal; + } + + // -------------------------- INNER CLASSES -------------------------- + + /** Listeners for the SoundPanel class. */ + private class Listeners implements ActionListener, ChangeListener { + public Listeners() { + } + + @Override + public void actionPerformed(ActionEvent e) { + switch (e.getActionCommand()) { + case CMD_PLAY: + if (SoundPanel.this.isPlaying()) { + if (SoundPanel.this.combinedPlayPause) { + SoundPanel.this.setPaused(!SoundPanel.this.isPaused()); + } else if (SoundPanel.this.isPaused()) { + SoundPanel.this.setPaused(false); + } + } else { + SoundPanel.this.setPlaying(true); + } + break; + case CMD_PAUSE: + if (SoundPanel.this.isPlaying()) { + SoundPanel.this.setPaused(true); + } + break; + case CMD_STOP: + SoundPanel.this.setPlaying(false); + break; + case CMD_LOOP: + setLooped(loopCheckBox.isSelected()); + break; + } + } + + @Override + public void stateChanged(ChangeEvent e) { + if (e.getSource() == progressSlider) { + if (progressSlider.isEnabled()) { + if (progressSlider.getValueIsAdjusting() && !progressAdjusting) { + // starting to manually drag the slider knob + progressAdjusting = true; + } else if (!progressSlider.getValueIsAdjusting() && progressAdjusting) { + // stopped dragging the slider knob + progressAdjusting = false; + final AdjustingBoundedRangeModel model = (AdjustingBoundedRangeModel)progressSlider.getModel(); + model.setValue(model.getAdjustedValue()); + runner.setSoundPosition(model.getAdjustedValue()); + } + } + } + } + } + + /** + * Background thread for the SoundPanel class that handles playback of sound clips. + */ + private class Runner implements Runnable, AudioStateListener { + private final ReentrantLock lock = new ReentrantLock(); + private final Thread thread; + + private AudioPlayback player; + private boolean running; + + /** Initializes the class instance and starts a background thread. */ + public Runner() { + thread = new Thread(this); + running = true; + thread.start(); + } + + /** + * Assigns a new {@link BufferedAudioPlayer} instance to the runner. The old player instance, if any, is properly + * closed before the new instance is assigned. + * + * @param newBuffer the {@link AudioBuffer} object to load. + * @throws IOException if an I/O error occurs. + * @throws UnsupportedAudioFileException if the audio data is incompatible with the player. + * @throws LineUnavailableException if the audio line is not available due to resource restrictions. + * @throws IllegalArgumentException if the audio data is invalid. + */ + public void setAudio(AudioBuffer newBuffer) throws Exception{ + lock.lock(); + try { + if (player != null) { + player.setPlaying(false); + player.close(); + player = null; + } + + if (newBuffer != null) { + // TODO: externalize audio player instantiation to abstract audio player backend + player = new BufferedAudioPlayer(newBuffer, this); + ((BufferedAudioPlayback)player).setLooped(SoundPanel.looped); + } + } finally { + lock.unlock(); + } + + signal(); + updatePanel(); + } + + /** Returns whether an audio player is currently initialized. */ + public boolean isAvailable() { + return (player != null); + } + + /** Returns whether loop mode is enabled. */ + @SuppressWarnings("unused") + public boolean isLooped() { + return player instanceof BufferedAudioPlayback && ((BufferedAudioPlayback) player).isLooped(); + } + + /** Enables or disable looped playback. */ + public void setLooped(boolean loop) { + if (player instanceof BufferedAudioPlayback) { + ((BufferedAudioPlayback)player).setLooped(loop); + } + } + + /** + * Returns whether the player is playing. Paused state does not affect the result. + * Returns {@code false} if the player is not initialized. + */ + public boolean isPlaying() { + return (player != null) && player.isPlaying(); + } + + /** + * Sets the playback state of the player. {@code true} starts playback, {@code false} stops playback. Paused + * state does not affect the current playing state. Does nothing if the player is not initialized. + */ + public void setPlaying(boolean play) { + if (player != null) { + player.setPlaying(play); + } + } + + /** + * Returns whether the player is currently in the paused state. This state does not affect the {@link #isPlaying()} + * state. Always returns {@code false} if playback is stopped or the player is not initialized. + */ + public boolean isPaused() { + return (player != null) && player.isPaused(); + } + + /** + * Sets the paused state of the player. {@code true} pauses playback, {@code false} resumes playback. Does nothing + * if playback is not active or the player has not been initialized. + */ + public void setPaused(boolean pause) { + if (player != null) { + player.setPaused(pause); + } + } + + /** + * Returns the total play length of the sound clip, in milliseconds. Returns {@code 0} if the player is not + * initialized. + */ + public long getTotalLength() { + return (player instanceof BufferedAudioPlayback) ? ((BufferedAudioPlayback)player).getTotalLength() : 0L; + } + + /** + * Returns the elapsed playback time of the sound clip, in milliseconds. Returns {@code 0} if the player is not + * initialized. + */ + public long getSoundPosition() { + return (player != null) ? player.getSoundPosition() : 0L; + } + + /** + * Sets an explicit playback position. + * + * @param position New playback position in milliseconds. Position is clamped to the available audio clip duration. + */ + public void setSoundPosition(int position) { + if (player instanceof BufferedAudioPlayback) { + ((BufferedAudioPlayback)player).setSoundPosition(position); + } + } + + /** Signals the runner to wake up from a waiting state. */ + public void signal() { + thread.interrupt(); + } + + /** Returns whether the runner is active. */ + public boolean isRunning() { + return running; + } + + /** Signals the runner to terminate. */ + public void terminate() { + running = false; + signal(); + try { + setAudio(null); + } catch (Exception e) { + Logger.debug(e); + } + updatePanel(); + } + + @Override + public void run() { + while (isRunning()) { + final boolean isActive; + lock.lock(); + try { + isActive = (player != null) && player.isPlaying() && !player.isPaused(); + } finally { + lock.unlock(); + } + if (isActive) { + try { + SwingUtilities.invokeLater(SoundPanel.this::updateLabel); + Thread.sleep(100L); + } catch (InterruptedException e) { + // waking up prematurely + } + } else { + try { + Thread.sleep(Integer.MAX_VALUE); + } catch (InterruptedException e) { + // waking up from sleep + } + } + } + } + + @Override + public void audioStateChanged(AudioStateEvent event) { +// Logger.trace("{}.audioStateChanged({})", SoundPanel.class.getSimpleName(), event); + switch (event.getAudioState()) { + case START: + SoundPanel.this.fireSoundStarted(); + break; + case STOP: + SoundPanel.this.fireSoundStopped(); + break; + case PAUSE: + SoundPanel.this.fireSoundPaused(); + break; + case RESUME: + SoundPanel.this.fireSoundResumed(); + break; + case OPEN: + SoundPanel.this.fireSoundOpened(); + break; + case CLOSE: + SoundPanel.this.fireSoundClosed(); + break; + default: + } + signal(); + updatePanel(); + } + + private void updatePanel() { + SwingUtilities.invokeLater(() -> { + SoundPanel.this.updateControls(); + SoundPanel.this.updateLabel(); + }); + } + } + + /** + * Extends the {@link JSlider} class to handle issues with setting the slider position if the component was disabled + * while the user was still dragging the slider knob. + */ + private static class FixedSlider extends JSlider { + @SuppressWarnings("unused") + public FixedSlider() { + super(); + init(); + } + + @SuppressWarnings("unused") + public FixedSlider(int orientation) { + super(orientation); + init(); + } + + @SuppressWarnings("unused") + public FixedSlider(int min, int max) { + super(min, max); + init(); + } + + @SuppressWarnings("unused") + public FixedSlider(int min, int max, int value) { + super(min, max, value); + init(); + } + + @SuppressWarnings("unused") + public FixedSlider(int orientation, int min, int max, int value) { + super(orientation, min, max, value); + init(); + } + + public FixedSlider(BoundedRangeModel brm) { + super(brm); + init(); + } + + @Override + public void setEnabled(boolean enabled) { + clearDragMode(); + super.setEnabled(enabled); + } + + /** + * Clears pending dragging mode that is initiated when the user clicks the slider knob and drags it around. + *

+ * Dragging mode is not properly cleared if the component is disabled while dragging is still active which results + * in an unresponsive position display when the component becomes active again, until the user intentionally clicks + * on the component again. + *

+ *

+ * This method simulates the mouse release by the user and should be called right before the component is disabled. + *

+ */ + private void clearDragMode() { + final MouseListener[] ml = getMouseListeners(); + if (ml.length > 0) { + final Point pt = getMousePosition(); + final int x = (pt != null) ? pt.x : 0; + final int y = (pt != null) ? pt.y : 0; + + final MouseEvent me = new MouseEvent(this, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), 0, x, y, 1, + false, MouseEvent.BUTTON1); + for (final MouseListener m : ml) { + m.mouseReleased(me); + } + } + } + + private void processMousePressed(MouseEvent e) { + if (getUI() instanceof BasicSliderUI) { + final BasicSliderUI ui = (BasicSliderUI)getUI(); + final int value; + if (getOrientation() == SwingConstants.VERTICAL) { + value = ui.valueForYPosition(e.getY()); + } else { + value = ui.valueForXPosition(e.getX()); + } + setValue(value); + } else { + Logger.debug("FixedSlider.getUI() not instance of BasicSliderUI"); + } + } + + private void init() { + addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + processMousePressed(e); + } + }); + } + } + + /** + * Specialization of the {@link DefaultBoundedRangeModel} class. It decouples values set during "adjusting" and direct + * value mode. The "adjusting" value can be received from the separate getter method {@link #getAdjustedValue()}. + */ + private static class AdjustingBoundedRangeModel extends DefaultBoundedRangeModel { + private int adjustedValue; + + public AdjustingBoundedRangeModel() { + super(); + } + + @SuppressWarnings("unused") + public AdjustingBoundedRangeModel(int value, int extent, int min, int max) { + super(value, extent, min, max); + this.adjustedValue = getValue(); + } + + private int getValidatedValue(int n) { + n = Math.min(n, Integer.MAX_VALUE - getExtent()); + int newValue = Math.max(n, getMinimum()); + if (newValue + getExtent() > getMaximum()) { + newValue = getMaximum() - getExtent(); + } + return newValue; + } + + public void setDirectValue(int n) { + super.setValue(n); + } + + @Override + public void setValue(int n) { + if (getValueIsAdjusting()) { + n = Math.min(n, Integer.MAX_VALUE - getExtent()); + int newValue = Math.max(n, getMinimum()); + if (newValue + getExtent() > getMaximum()) { + newValue = getMaximum() - getExtent(); + } + adjustedValue = getValidatedValue(newValue); + fireStateChanged(); + } else { + super.setValue(n); + } + } + + /** + * Returns the last known value that was assigned in "adjusting" mode. + * + * @return the model's last known "adjusting" value. + */ + public int getAdjustedValue() { + return adjustedValue; + } + } +} diff --git a/src/org/infinity/gui/SpriteAnimationPanel.java b/src/org/infinity/gui/SpriteAnimationPanel.java new file mode 100644 index 000000000..fce64a591 --- /dev/null +++ b/src/org/infinity/gui/SpriteAnimationPanel.java @@ -0,0 +1,2122 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.gui; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.DisplayMode; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsEnvironment; +import java.awt.Image; +import java.awt.LayoutManager; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.HierarchyEvent; +import java.awt.event.HierarchyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionListener; +import java.awt.event.WindowEvent; +import java.awt.event.WindowStateListener; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collections; +import java.util.Comparator; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.function.Function; + +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import javax.swing.Timer; +import javax.swing.event.EventListenerList; + +import org.infinity.resource.Closeable; +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.StructEntry; +import org.infinity.resource.cre.CreResource; +import org.infinity.resource.cre.decoder.SpriteDecoder; +import org.infinity.resource.cre.decoder.SpriteDecoder.SpriteBamControl; +import org.infinity.resource.cre.decoder.util.AnimationInfo; +import org.infinity.resource.cre.decoder.util.Direction; +import org.infinity.resource.cre.decoder.util.Sequence; +import org.infinity.resource.cre.decoder.util.SpriteUtils; +import org.infinity.resource.graphics.BamDecoder.BamControl; +import org.infinity.resource.graphics.PseudoBamDecoder.PseudoBamFrameEntry; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.IdsMapCache; +import org.infinity.util.IniMap; +import org.infinity.util.IniMapSection; +import org.infinity.util.Logger; +import org.infinity.util.StringTable; +import org.infinity.util.io.StreamUtils; + +/** + * Expand the {@link JPanel} class to allow creature sprites of the currently opened game moving around the panel and + * performing random actions. + */ +public class SpriteAnimationPanel extends JPanel + implements Runnable, Closeable, ActionListener, PropertyChangeListener, WindowStateListener, MouseMotionListener { + /** Limit sprite updates to 15 fps. */ + private static final long FRAME_DELAY = 1000L / 15L; + + /** Max. number of sprites that will be present at the same time. */ + private static final int MAX_SPRITES = calculateMaxSprites(); + + /** Default delay for triggering an event to create a new sprite (lower bound, in ms). */ + private static final int DELAY_CREATE_SPRITE_MIN = 10_000; + /** Default delay for triggering an event to create a new sprite (upper bound, in ms). */ + private static final int DELAY_CREATE_SPRITE_MAX = 60_000; + + /** + * Default delay for triggering an event to release an existing sprite (lower bound, in ms). + * Boundary is opened to allow it to vanish from existence eventually. + */ + private static final int DELAY_RELEASE_SPRITE_MIN = 30_000; + /** + * Default delay for triggering an event to release an existing sprite (upper bound, in ms). + * Boundary is opened to allow it to vanish from existence eventually. + */ + private static final int DELAY_RELEASE_SPRITE_MAX = 90_000; + + /** Used for sorting sprites for rendering. */ + private static final Comparator SPRITE_COMPARATOR = Comparator.comparingDouble(a -> a.y); + + /** Set of non-pst action sequences for use. */ + private static final Sequence[] DEF_SEQUENCES = { + Sequence.ATTACK, + Sequence.ATTACK_2, + Sequence.ATTACK_3, + Sequence.ATTACK_4, + Sequence.ATTACK_5, + Sequence.SPELL, + Sequence.SPELL1, + Sequence.SPELL2, + Sequence.SPELL3, + Sequence.SPELL4, + Sequence.STANCE, + Sequence.STANCE2, + Sequence.STAND, + Sequence.STAND2, + Sequence.STAND3, + }; + /** Set of pst-specific action sequences for use. */ + private static final Sequence[] DEF_SEQUENCES_PST = { + Sequence.PST_ATTACK1, + Sequence.PST_ATTACK2, + Sequence.PST_ATTACK3, + Sequence.PST_DIE_BACKWARD, + Sequence.PST_DIE_FORWARD, + Sequence.PST_DIE_COLLAPSE, + Sequence.PST_SPELL1, + Sequence.PST_SPELL2, + Sequence.PST_SPELL3, + Sequence.PST_STANCE, + Sequence.PST_STANCE_FIDGET1, + Sequence.PST_STANCE_FIDGET2, + Sequence.PST_STAND, + Sequence.PST_STAND_FIDGET1, + Sequence.PST_STAND_FIDGET2, + Sequence.PST_TALK1, + Sequence.PST_TALK2, + Sequence.PST_TALK3, + }; + + /** Collection of active creature sprites, sorted by vertical position to simplify correct drawing order. */ + private final List sprites = Collections.synchronizedList(new ArrayList<>(MAX_SPRITES + 1)); + private final List spritesWorking = Collections.synchronizedList(new ArrayList<>(MAX_SPRITES + 1)); + + /** Cached list of CRE resources available for the current game. */ + private final List creResources = new ArrayList<>(); + + /** Random numbers provider */ + private final Random random = new Random(); + + /** Controls the delay between creating new sprites. */ + private final Timer creationTimer = new Timer(createDelay(random, 0, DELAY_CREATE_SPRITE_MIN), this); + + /** Controls the delay between releasing existing sprites. */ + private final Timer releaseTimer = + new Timer(createDelay(random, DELAY_RELEASE_SPRITE_MIN, DELAY_RELEASE_SPRITE_MAX), this); + + /** Thread for periodically updating the sprite animations. */ + private Thread runner; + + /** Indicates whether the running thread is actively updating. */ + private boolean running; + + /** Indicates whether the running thread is currently in the paused state. */ + private boolean paused; + + /** Indicates whether the SpriteAnimator instance has been terminated. */ + private boolean closed; + + /** Max. number of sprites that will be present at the same time. */ + private int maxSprites; + + /** Delay for triggering an event to create a new sprite (lower bound, in ms). */ + private int createSpriteDelayMin; + /** Delay for triggering an event to create a new sprite (upper bound, in ms). */ + private int createSpriteDelayMax; + + /** + * Default delay for triggering an event to release an existing sprite (lower bound, in ms). + * Boundary is opened to allow it to vanish from existence eventually. + */ + private int releaseSpriteDelayMin; + /** + * Default delay for triggering an event to release an existing sprite (upper bound, in ms). + * Boundary is opened to allow it to vanish from existence eventually. + */ + private int releaseSpriteDelayMax; + + /** + * Creates a new sprite animation panel with a double buffer and a flow layout. The runner is initially set to the + * stopped state. + */ + public SpriteAnimationPanel() { + this(true); + } + + /** + * Creates a new sprite animation panel with a flow layout and the specified buffer strategy. The runner is initially + * set to the stopped state. + * + * @param isDoubleBuffered a boolean, true for double-buffering, whichuses additional memory space to achieve fast, + * flicker-freeupdates. + */ + public SpriteAnimationPanel(boolean isDoubleBuffered) { + this(new FlowLayout(), isDoubleBuffered); + } + + /** + * Creates a new sprite animation panel with a double buffer and the specified layout manager. The runner is initially + * set to the stopped state. + * + * @param layout the LayoutManager to use. + */ + public SpriteAnimationPanel(LayoutManager layout) { + this(layout, true); + } + + /** + * Creates a new sprite animation panel with the specified layout manager and buffer strategy. The runner is initially + * set to the stopped state. + * + * @param layout the LayoutManager to use. + * @param isDoubleBuffered a boolean, true for double-buffering, whichuses additional memory space to achieve fast, + * flicker-freeupdates. + */ + public SpriteAnimationPanel(LayoutManager layout, boolean isDoubleBuffered) { + super(layout, isDoubleBuffered); + init(); + } + + /** Returns the max. number of sprites that will be visible on the panel at the same time. */ + public int getMaxSprites() { + return maxSprites; + } + + /** + * Specifies the max. number of sprites that should be visible on the panel at the same time. Existing sprites are + * automatically removed if the new value is smaller than the current number of visible sprites. + * + * @param value A positive number. + */ + public void setMaxSprites(int value) { + maxSprites = Math.max(1, value); + while (sprites.size() > maxSprites) { + final SpriteInfo sprite; + synchronized (sprites) { + sprite = sprites.get(0); + Logger.trace("Removing sprite: {}", sprite.getDecoder().getCreatureInfo().getCreResource().getResourceEntry()); + sprites.remove(0); + } + try { + sprite.close(); + } catch (Exception e) { + Logger.debug(e); + } + } + } + + /** Specifies an appropriate max. number of sprites that will be visible on the panel at the same time. */ + public void setMaxSpritesAuto() { + setMaxSprites(calculateMaxSprites()); + } + + /** Returns the lower bound of the sprite creation delay, in ms. */ + public int getSpriteCreationDelayMin() { + return createSpriteDelayMin; + } + + /** Returns the upper bound of the sprite creation delay, in ms. */ + public int getSpriteCreationDelayMax() { + return createSpriteDelayMax; + } + + /** + * Specifies a new delay for sprite creation. + * + * @param minDelay The lower bound of the delay, in ms. + * @param maxDelay The upper bound of the delay, in ms. + */ + public void setSpriteCreationDelay(int minDelay, int maxDelay) { + minDelay = Math.max(100, minDelay); + maxDelay = Math.max(100, maxDelay); + if (minDelay > maxDelay) { + int v = minDelay; + minDelay = maxDelay; + maxDelay = v; + } + if (maxDelay == minDelay) { + maxDelay++; + } + createSpriteDelayMin = minDelay; + createSpriteDelayMax = maxDelay; + } + + /** Returns the lower bound of the sprite release delay, in ms. */ + public int getSpriteReleaseDelayMin() { + return releaseSpriteDelayMin; + } + + /** Returns the upper bound of the sprite release delay, in ms. */ + public int getSpriteReleaseDelayMax() { + return releaseSpriteDelayMax; + } + + /** + * Specifies a new delay for sprite release. + * + * @param minDelay The lower bound of the delay, in ms. + * @param maxDelay The upper bound of the delay, in ms. + */ + public void setSpriteReleaseDelay(int minDelay, int maxDelay) { + minDelay = Math.max(100, minDelay); + maxDelay = Math.max(100, maxDelay); + if (minDelay > maxDelay) { + int v = minDelay; + minDelay = maxDelay; + maxDelay = v; + } + if (maxDelay == minDelay) { + maxDelay++; + } + releaseSpriteDelayMin = minDelay; + releaseSpriteDelayMax = maxDelay; + } + + /** Returns the list of sprites */ + public List getSprites() { + return Collections.unmodifiableList(sprites); + } + + /** Stops the runner (if running), cleans up current data and initializes new data. */ + public void reset() { + if (isClosed()) { + return; + } + + boolean wasRunning = isRunning(); + boolean wasPaused = isPaused(); + stop(); + + cacheCreResources(); + + if (wasRunning) { + start(); + if (wasPaused) { + pause(); + } + } + } + + /** Starts the sprite runner. Does nothing if the runner has already started. */ + public void start() { + if (isClosed()) { + return; + } + + if (!isRunning()) { + initTimers(); + creationTimer.start(); + releaseTimer.start(); + running = true; + paused = false; + runner.interrupt(); + } + } + + /** Similar to {@link #stop()} this method will stop the sprite runner. However, it won't clean up existing sprites. */ + public void pause() { + if (isClosed()) { + return; + } + + if (isRunning()) { + releaseTimer.stop(); + creationTimer.stop(); + paused = true; + running = false; + } + } + + /** Stops the sprite runner. Does nothing if the runner has already been stopped. */ + public void stop() { + if (isClosed()) { + return; + } + + if (isRunning()) { + releaseTimer.stop(); + creationTimer.stop(); + running = false; + paused = false; + cleanup(); + SwingUtilities.invokeLater(this::repaint); + } + } + + /** + * Returns {@code true} if the sprite runner is in paused state. The paused state is only set if the runner was + * explicitly halted by the {@link #pause()} method. + */ + public boolean isPaused() { + return !isClosed() && paused; + } + + /** Returns {@code true} if the sprite runner is currently running. Returns {@code false} otherwise. */ + public boolean isRunning() { + return !isClosed() && running && !paused; + } + + /** + * Returns {@code true} if the {@link SpriteAnimationPanel} instance has been closed. + * A closed instance cannot be used further anymore. + */ + public boolean isClosed() { + return closed; + } + + /** + * Terminates the current runner. + * Calling {@link #start()} or {@link #stop()} won't have any effect after calling this method. + */ + @Override + public void close() throws Exception { + closed = true; + paused = false; + if (isRunning()) { + running = false; + } else { + runner.interrupt(); + } + cleanup(); + } + + @Override + public void run() { + while (!isClosed()) { + if (isRunning()) { + // playing + final long startTime = System.currentTimeMillis(); + advance(); + final long endTime = System.currentTimeMillis(); + final long elapsed = Math.max(0L, endTime - startTime); + final long remainingDelay = Math.max(1L, FRAME_DELAY - elapsed); + try { + Thread.sleep(remainingDelay); + } catch (InterruptedException e) { + // just bad timing + } + } else { + // paused or stopped + try { + Thread.sleep(Long.MAX_VALUE); + } catch (InterruptedException e) { + // called to wake up from sleep + } + } + } + } + + @Override + public void paint(Graphics g) { + super.paint(g); + final Graphics g2 = (g == null) ? null : g.create(); + try { + render(g2); + } finally { + g2.dispose(); + } + } + + @Override + public void windowStateChanged(WindowEvent e) { + if (isRunning() && !isPaused() && (e.getNewState() & Frame.ICONIFIED) != 0) { + Logger.trace("Window iconified: Stopping sprite actions"); + pause(); + } else if (isPaused()) { + Logger.trace("Window restored: Resuming sprite actions"); + start(); + } + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == creationTimer) { + if (sprites.size() < maxSprites) { + // creating a new sprite instance + final SpriteInfo sprite = createRandomSprite(); + if (sprite != null) { + Logger.trace("Adding new sprite: {}", sprite.getDecoder().getCreatureInfo().getCreResource().getResourceEntry()); + synchronized (sprites) { + sprites.add(sprite); + } + } + } + creationTimer.setDelay(createDelay(random, createSpriteDelayMin, createSpriteDelayMax)); + } else if (e.getSource() == releaseTimer) { + // (maybe) release an existing sprite instance + synchronized (sprites) { + for (final SpriteInfo si : sprites) { + if (si.isBounded() && random.nextBoolean()) { + Logger.trace("Removing sprite bounds: {}", si.getDecoder().getCreatureInfo().getCreResource().getResourceEntry()); + si.setBounded(false); + break; + } + } + } + releaseTimer.setDelay(createDelay(random, releaseSpriteDelayMin, releaseSpriteDelayMax)); + } + } + + @Override + public void propertyChange(PropertyChangeEvent e) { + if (e.getSource() instanceof SpriteInfo) { + final SpriteInfo si = (SpriteInfo) e.getSource(); + switch (e.getPropertyName()) { + case SpriteInfo.PROPERTY_SEQUENCE_ENDED: + onSpriteSequenceEnded(si, (Sequence)e.getNewValue()); + break; + case SpriteInfo.PROPERTY_BOUNDS_HIT: + onSpriteBoundsHit(si, (int)e.getNewValue()); + break; + case SpriteInfo.PROPERTY_COLLISION: + onSpriteCollision(si, (SpriteInfo)e.getNewValue()); + break; + case SpriteInfo.PROPERTY_VANISHED: + onSpriteVanished(si); + break; + } + } + } + + @Override + public void mouseDragged(MouseEvent e) { + // nothing to do + } + + @Override + public void mouseMoved(MouseEvent e) { + if (e.getSource() == this) { + final int x = e.getX(); + final int y = e.getY(); + synchronized (sprites) { + spritesWorking.clear(); + spritesWorking.addAll(sprites); + final Rectangle rect = new Rectangle(); + for (final SpriteInfo si : spritesWorking) { + si.getSpriteBounds((int)si.getX(), (int)si.getY(), rect); + if (rect.contains(x, y)) { + si.onSpriteBoundsEntered(); + } else { + si.onSpriteBoundsExited(); + } + } + } + } + } + + /** Handles a sprite when their animation cycle of the action sequence ended. */ + private void onSpriteSequenceEnded(SpriteInfo sprite, Sequence seq) { + updateSprite(sprite, seq); + } + + /** Handles a sprite when it collides with the canvas boundary. */ + private void onSpriteBoundsHit(SpriteInfo sprite, int boundsMask) { + // already handled by the sprite object itself + } + + /** Handles a sprite when it collides with another sprite. */ + private void onSpriteCollision(SpriteInfo sprite, SpriteInfo target) { + // sprite should move to opposite direction + final Direction dir = SpriteInfo.findDirection(sprite.getX(), sprite.getY(), target.getX(), target.getY()); + final int shift = random.nextInt(5) - 2; // adds a slight variation to the direction + final int newDirValue = (dir.getValue() + 8 + shift) % Direction.values().length; + final Direction newDir = Direction.from(newDirValue); + sprite.setDirection(newDir); + } + + /** Handles a sprite when it vanished from the canvas. */ + private void onSpriteVanished(SpriteInfo sprite) { + try { + Logger.trace("Removing sprite: {}", sprite.getDecoder().getCreatureInfo().getCreResource().getResourceEntry()); + synchronized (sprites) { + sprites.remove(sprite); + } + sprite.close(); + } catch (Exception e) { + Logger.debug(e); + } + } + + /** Advances the global sprite setup by one step. */ + private void advance() { + if (!isRunning() || sprites.isEmpty()) { + return; + } + + synchronized (sprites) { + for (final SpriteInfo si : sprites) { + si.advance(); + } + } + + SwingUtilities.invokeLater(this::repaint); + } + + /** Renders the current sprite setup to the specified graphics context. */ + private void render(Graphics g) { + if (!isRunning()) { + return; + } + + if (g instanceof Graphics2D) { + final Graphics2D g2 = (Graphics2D)g; + synchronized (sprites) { + spritesWorking.clear(); + spritesWorking.addAll(sprites); + spritesWorking.sort(SPRITE_COMPARATOR); + for (final SpriteInfo si : spritesWorking) { + render(g2, si); + } + } + } + } + + /** Draws the sprite onto the specified graphics context. */ + private void render(Graphics2D g, SpriteInfo sprite) { + if (sprite == null) { + return; + } + + final int x = (int) sprite.getX(); + final int y = (int) sprite.getY(); + final PseudoBamFrameEntry frameEntry = sprite.getDecoder().getFrameInfo(sprite.getControl().cycleGetFrameIndexAbsolute()); + final int centerX = frameEntry.getCenterX(); + final int centerY = frameEntry.getCenterY(); + final int dx = x - centerX; + final int dy = y - centerY; + + if (sprite.getDecoder().isSelectionCircleEnabled()) { + // drawing selection circle + sprite.getControl().getVisualMarkers(g, new Point(dx, dy), sprite.getControl().cycleGetFrameIndex()); + } + + // drawing current sprite animation frame + g.setComposite(AlphaComposite.SrcOver); + final Image image = sprite.getControl().cycleGetFrame(); + g.drawImage(image, dx, dy, null); + image.flush(); + + if (sprite.isTooltipEnabled()) { + // drawing name plate + renderTooltip(g, sprite); + } + } + + /** + * Renders a name plate of the creature above the sprite. + * + * @param g The graphics context of the panel. + * @param sprite {@link SpriteInfo} instance of the sprite. + */ + private void renderTooltip(Graphics2D g, SpriteInfo sprite) { + if (g == null || sprite == null) { + return; + } + + final int maxLength = 30; // max. length of name string + final double marginX = 8.0; // horizontal margin between tooltip background border and text + final double marginY = 4.0; // vertical margin between tooltip background border and text + + String name = sprite.getTooltip(); + if (name == null) { + name = '[' + sprite.getDecoder().getCreResource().getName() + ']'; + } + name = name.replaceAll("\r?\n", " "); + if (name.length() > maxLength) { + name = name.substring(0, maxLength) + "..."; + } + + g.setFont(g.getFont().deriveFont(Font.BOLD)); + final FontMetrics fm = g.getFontMetrics(); + final Rectangle2D textRect = fm.getStringBounds(name, g); + final Dimension spriteDim = sprite.getControl().getSharedDimension(); + + final double bgWidth = textRect.getWidth() + marginX * 2.0; + final double bgHeight = textRect.getHeight() + marginY * 2.0; + final double bgX = sprite.getX() - (bgWidth / 2.0); + final double bgY = sprite.getY() - (bgHeight / 2.0) - spriteDim.height; + g.setColor(new Color(0x80000000, true)); + g.fillRect((int)bgX, (int)bgY, (int)bgWidth, (int)bgHeight); + + final double textX = bgX + marginX - textRect.getX(); + final double textY = bgY + marginY - textRect.getY(); + g.setColor(Color.WHITE); + g.drawString(name, (int)textX, (int)textY); + } + + /** Resets the timer delays. */ + private void initTimers() { + creationTimer.setDelay(createDelay(random, createSpriteDelayMin, createSpriteDelayMax)); + releaseTimer.setDelay(createDelay(random, releaseSpriteDelayMin, releaseSpriteDelayMax)); + } + + /** Various first-time initializations. */ + private void init() { + // just needed to detect when we can register the WindowState listener + addHierarchyListener(new HierarchyListener() { + @Override + public void hierarchyChanged(HierarchyEvent e) { + if (e.getChangedParent() != null) { + if (SpriteAnimationPanel.this.getTopLevelAncestor() instanceof Window) { + SpriteAnimationPanel.this.removeHierarchyListener(this); + final Window wnd = (Window)SpriteAnimationPanel.this.getTopLevelAncestor(); + wnd.addWindowStateListener(SpriteAnimationPanel.this); + } + } + } + }); + + addMouseMotionListener(this); + + cacheCreResources(); + maxSprites = MAX_SPRITES; + createSpriteDelayMin = DELAY_CREATE_SPRITE_MIN; + createSpriteDelayMax = DELAY_CREATE_SPRITE_MAX; + releaseSpriteDelayMin = DELAY_RELEASE_SPRITE_MIN; + releaseSpriteDelayMax = DELAY_RELEASE_SPRITE_MAX; + closed = false; + running = false; + runner = new Thread(this); + runner.start(); + } + + /** Cleans up resources and timers. */ + private void cleanup() { + releaseTimer.stop(); + creationTimer.stop(); + Logger.trace("Clearing all sprites: {}", sprites.size()); + spritesWorking.clear(); + synchronized (sprites) { + sprites.clear(); + } + } + + /** Refreshes the CRE resource cache. */ + private void cacheCreResources() { + creResources.clear(); + creResources.addAll(ResourceFactory.getResources("CRE")); + } + + /** + * Creates a new {@link SpriteInfo} instance from a randomly chosen CRE resource. + * + * @return A fully initialized {@link SpriteInfo} instance. + */ + private SpriteInfo createRandomSprite() { + SpriteInfo retVal = null; + + if (!creResources.isEmpty()) { + while (true) { + final ResourceEntry creEntry = creResources.get(random.nextInt(creResources.size())); + try { + ByteBuffer buffer = creEntry.getResourceBuffer(); + final String signature = StreamUtils.readString(buffer, 8); + boolean isValid = false; + switch (signature) { + case "CRE V1.0": + isValid = isValidCreV10(buffer); + break; + case "CRE V1.2": + isValid = isValidCreV12(buffer); + break; + case "CRE V2.2": + isValid = isValidCreV22(buffer); + break; + case "CRE V9.0": + isValid = isValidCreV90(buffer); + break; + default: + } + + if (isValid) { + final CreResource cre = new CreResource(creEntry); + final int x = random.nextInt(getWidth()); + final int y = random.nextInt(getHeight()); + final Direction dir = Direction.from(random.nextInt(Direction.values().length)); + + final Sequence seq; + switch (Profile.getGame()) { + case PST: + seq = Sequence.PST_WALK; + break; + case PSTEE: { + final int animType = buffer.getInt(0x28) & 0xf000; + if (animType == 0xf000) { + seq = Sequence.PST_WALK; + } else { + seq = Sequence.WALK; + } + break; + } + default: + seq = Sequence.WALK; + break; + } + + retVal = new SpriteInfo(this, cre, true, x, y, seq, dir); + retVal.addPropertyChangeListener(this); + break; + } + } catch (Exception e) { + Logger.debug(e); + } + } + } + + return retVal; + } + + /** Checks if the specified CRE V1.0 data can be safely created. */ + private boolean isValidCreV10(ByteBuffer buffer) { + if (buffer != null) { + final int nameStrref = buffer.getInt(0x08); + final int tooltipStrref = buffer.getInt(0x0c); + if (nameStrref <= 0 || nameStrref >= StringTable.getNumEntries() || + tooltipStrref <= 0 || tooltipStrref >= StringTable.getNumEntries()) { + // possibly a meta creature + return false; + } + + final int xp = buffer.getInt(0x014); + final int xp2 = buffer.getInt(0x018); + if (xp <= 0 && xp2 <= 0) { + // possibly a meta creature + return false; + } + + final int status = buffer.getInt(0x20); + if ((status & 0xff0) != 0) { + // skip invisible or dead + return false; + } + + final int animType = buffer.getInt(0x28); + if ((animType & 0xf000) == 0x1000 || ((animType & 0xf000) == 0x3000)) { + // skip special animations + return false; + } + + final String animLabel = IdsMapCache.getIdsSymbol("animate", animType); + if (animLabel == null) { + // skip invalid creature animation + return false; + } + if (animLabel.toUpperCase().contains("DOOM_GUARD")) { + // skip invisible creature animation + return false; + } + + final IniMap ini = SpriteUtils.getAnimationInfo(animType); + if (ini == null) { + return false; + } + final IniMapSection generalSection = ini.getSection("general"); + if (generalSection != null) { + if (generalSection.getAsDouble("move_scale", -1.0) <= 0.0) { + // skip non-moving creature + return false; + } + } + + if (Profile.getGame() == Profile.Game.PSTEE) { + final IniMapSection animSection = ini.getSection("monster_planescape"); + if (animSection != null) { + final String resref = animSection.getAsString("walk"); + if (resref != null && resref.length() >= 4) { + if (!resref.substring(1, 4).equalsIgnoreCase("wlk")) { + // skip animation without dedicated walking sequence + return false; + } + } else { + // skip animation without walking sequence + return false; + } + } + } + + final int allegiance = buffer.get(0x270) & 0xff; + // possibly a meta creature + return allegiance > 1; + } + + return false; + } + + /** Checks if the specified CRE V1.2 data can be safely created. */ + private boolean isValidCreV12(ByteBuffer buffer) { + if (buffer != null) { + final int nameStrref = buffer.getInt(0x08); + final int tooltipStrref = buffer.getInt(0x0c); + if (nameStrref <= 0 || nameStrref >= StringTable.getNumEntries() || + tooltipStrref <= 0 || tooltipStrref >= StringTable.getNumEntries()) { + // possibly a meta creature + return false; + } + + final int xp = buffer.getInt(0x014); + if (xp <= 0) { + // possibly a meta creature + return false; + } + + final int status = buffer.getInt(0x20); + if ((status & 0xff0) != 0) { + // invisible or dead + return false; + } + + final int animType = buffer.getInt(0x28); + if (animType < 0x1000) { + // special effect animation + return false; + } + + final String animLabel = IdsMapCache.getIdsSymbol("animate", animType); + if (animLabel == null) { + // invalid creature animation + return false; + } + if (animLabel.indexOf('*') >= 0 || animLabel.indexOf('!') >= 0) { + // illegal creature animation + return false; + } + + final IniMap ini = SpriteUtils.getAnimationInfo(animType); + if (ini == null) { + return false; + } + final IniMapSection iniSection = ini.getSection("general"); + if (iniSection != null) { + if (iniSection.getAsDouble("move_scale", -1.0) <= 0.0) { + // non-moving creature + return false; + } + } + + final IniMapSection animSection = ini.getSection("monster_planescape"); + if (animSection != null) { + final String resref = animSection.getAsString("walk"); + if (resref != null && resref.length() >= 4) { + if (!resref.substring(1, 4).equalsIgnoreCase("wlk")) { + // skip animation without dedicated walking sequence + return false; + } + } else { + // skip animation without walking sequence + return false; + } + } + + final int allegiance = buffer.get(0x314) & 0xff; + // possibly a meta creature + return allegiance > 1; + } + + return false; + } + + /** Checks if the specified CRE V2.2 data can be safely created. */ + private boolean isValidCreV22(ByteBuffer buffer) { + if (buffer != null) { + final int nameStrref = buffer.getInt(0x08); + final int tooltipStrref = buffer.getInt(0x0c); + if (nameStrref <= 0 || nameStrref >= StringTable.getNumEntries() || + tooltipStrref <= 0 || tooltipStrref >= StringTable.getNumEntries()) { + // possibly a meta creature + return false; + } + + final int xp = buffer.getInt(0x014); + if (xp <= 0) { + // possibly a meta creature + return false; + } + + final int status = buffer.getInt(0x20); + if ((status & 0xff0) != 0) { + // invisible or dead + return false; + } + + final int animType = buffer.getInt(0x28); + if (animType < 0x1000) { + // special effect animation + return false; + } + + final String animLabel = IdsMapCache.getIdsSymbol("animate", animType); + if (animLabel == null) { + // invalid creature animation + return false; + } + if (animLabel.toUpperCase().contains("INVISIBLE") || animLabel.toUpperCase().contains("KEG")) { + // invisible creature animation + return false; + } + + final IniMap ini = SpriteUtils.getAnimationInfo(animType); + if (ini == null) { + return false; + } + final IniMapSection iniSection = ini.getSection("general"); + if (iniSection != null) { + // possibly non-moving creature + return !(iniSection.getAsDouble("move_scale", -1.0) <= 0.0); + } + + return true; + } + + return false; + } + + /** Checks if the specified CRE V9.0 data can be safely created. */ + private boolean isValidCreV90(ByteBuffer buffer) { + if (buffer != null) { + final int nameStrref = buffer.getInt(0x08); + final int tooltipStrref = buffer.getInt(0x0c); + if (nameStrref <= 0 || nameStrref >= StringTable.getNumEntries() || + tooltipStrref <= 0 || tooltipStrref >= StringTable.getNumEntries()) { + // possibly a meta creature + return false; + } + + final int xp = buffer.getInt(0x014); + if (xp <= 0) { + // possibly a meta creature + return false; + } + + final int status = buffer.getInt(0x20); + if ((status & 0xff0) != 0) { + // invisible or dead + return false; + } + + final int animType = buffer.getInt(0x28); + if (animType < 0x1000) { + // special effect animation + return false; + } + + final String animLabel = IdsMapCache.getIdsSymbol("animate", animType); + if (animLabel == null) { + // invalid creature animation + return false; + } + if (animLabel.toUpperCase().contains("DOOM_GUARD")) { + // invisible creature animation + return false; + } + + final IniMap ini = SpriteUtils.getAnimationInfo(animType); + if (ini == null) { + return false; + } + final IniMapSection iniSection = ini.getSection("general"); + if (iniSection != null) { + // possibly non-moving creature + return !(iniSection.getAsDouble("move_scale", -1.0) <= 0.0); + } + + return true; + } + + return false; + } + + /** Updates the action and/or direction of the specified sprite. */ + private void updateSprite(SpriteInfo sprite, Sequence seq) { + final Sequence[] sequences = sprite.isPstAnimation() ? DEF_SEQUENCES_PST: DEF_SEQUENCES; + final Function walkAction = si -> { + if (si.isPstAnimation()) { + if (random.nextInt(100) < 80) { + return si.getDefaultWalkingSequence(); + } else { + return si.getDefaultRunningSequence(); + } + } else { + return si.getDefaultWalkingSequence(); + } + }; + final boolean isMoving = (sprite.getMoveFactor() > 0.0); + + // determine next sequence + Sequence newSequence = null; + + if (newSequence == null) { + switch (seq) { + case SPELL: + newSequence = Sequence.CAST; + break; + case SPELL1: + newSequence = Sequence.CAST1; + break; + case SPELL2: + newSequence = Sequence.CAST2; + break; + case SPELL3: + newSequence = Sequence.CAST3; + break; + case SPELL4: + newSequence = Sequence.CAST4; + break; + default: + } + } + + if (newSequence == null) { + switch (seq) { + case PST_DIE_BACKWARD: + case PST_DIE_FORWARD: + case PST_DIE_COLLAPSE: + newSequence = Sequence.PST_GET_UP; + break; + case PST_STAND: + case PST_STAND_FIDGET1: + case PST_STAND_FIDGET2: + switch (random.nextInt(8)) { + case 0: + newSequence = Sequence.PST_STAND_TO_STANCE; + break; + case 1: + newSequence = Sequence.PST_STAND_FIDGET1; + break; + case 2: + newSequence = Sequence.PST_STAND_FIDGET2; + break; + case 3: + case 4: + case 5: + newSequence = Sequence.PST_STAND; + break; + } + break; + case PST_STANCE: + case PST_STANCE_FIDGET1: + case PST_STANCE_FIDGET2: + switch (random.nextInt(8)) { + case 0: + newSequence = Sequence.PST_STANCE_TO_STAND; + break; + case 1: + newSequence = Sequence.PST_STANCE_FIDGET1; + break; + case 2: + newSequence = Sequence.PST_STANCE_FIDGET2; + break; + case 3: + case 4: + case 5: + newSequence = Sequence.PST_STANCE; + break; + } + break; + case PST_STANCE_TO_STAND: + newSequence = Sequence.PST_STAND; + break; + case PST_STAND_TO_STANCE: + newSequence = Sequence.PST_STANCE; + break; + default: + } + } + + if (newSequence == null) { + if (isMoving && random.nextInt(100) < 5) { + newSequence = walkAction.apply(sprite); + } else if (!isMoving && random.nextBoolean()) { + newSequence = walkAction.apply(sprite); + } + } + + if (newSequence == null) { + if (random.nextInt(100) < 10) { + newSequence = sequences[random.nextInt(sequences.length)]; + } + } + + // validation check + if (newSequence != null) { + for (int i = 0; i < 3 && !sprite.getDecoder().isSequenceAvailable(newSequence); i++) { + switch (newSequence) { + case CAST: + case CAST1: + case CAST2: + case CAST3: + case CAST4: + newSequence = random.nextBoolean() ? Sequence.STAND : sprite.getDefaultWalkingSequence(); + break; + case PST_GET_UP: + newSequence = walkAction.apply(sprite); + break; + default: + newSequence = seq; + } + } + if (!sprite.getDecoder().isSequenceAvailable(newSequence)) { + newSequence = sprite.getDefaultWalkingSequence(); + } + } + + // applying sequence + if (newSequence != null) { + Logger.trace("Applying new sequence to {}: {}", + sprite.getDecoder().getCreatureInfo().getCreResource().getResourceEntry(), newSequence.name()); + try { + sprite.setSequence(newSequence); + } catch (Throwable t) { + Logger.debug("Sequence {} not available for {}: Falling back to walking", + newSequence.name(), sprite.getDecoder().getCreatureInfo().getCreResource().getResourceEntry()); + sprite.setSequence(sprite.getDefaultWalkingSequence()); + } + } + + // applying direction change + if (newSequence == null && random.nextInt(100) < 25) { + sprite.setDirection(Direction.from(random.nextInt(Direction.values().length))); + } + } + + /** + * Returns a randomized delay, based on the specified parameters. + * + * @param rnd The {@link Random} instance to generate a randomized value. + * @param min Lower bounds of the delay. + * @param max Upper bounds of the delay. + * @return Delay within the specified parameters. + */ + private static int createDelay(Random rnd, int min, int max) { + return min + rnd.nextInt(Math.abs(max - min)); + } + + /** Calculates an appropriate max. sprite count for the current system. */ + private static int calculateMaxSprites() { + int factor = 4; + try { + final DisplayMode dm = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode(); + final int w = dm.getWidth(); + if (w <= 1280) { + factor = 2; + } else if (w <= 1920) { + factor = 4; + } else if (w <= 2560) { + factor = 6; + } else if (w <= 3840) { + factor = 8; + } else { + factor = 10; + } + } catch (Throwable t) { + Logger.debug(t); + } + return 5 * factor; + } + + // -------------------------- INNER CLASSES -------------------------- + + /** Stores the state of a single creature sprite. */ + public static class SpriteInfo implements Closeable { + /** + * Name of a {@link PropertyChangeEvent} when the animation cycle of an action sequence ended. + *

+ * Value type: current action {@link Sequence} + *

+ */ + public static final String PROPERTY_SEQUENCE_ENDED = "sequenceEnded"; + + /** + * Name of a {@link PropertyChangeEvent} when the canvas boundary is about to be hit by the next advancement. + *

+ * Value type: Bits of the boundary sides as {@link Integer} bitfield (see {@link #BOUNDS_TOP}, + * {@link #BOUNDS_LEFT}, {@link #BOUNDS_BOTTOM}, {@link #BOUNDS_RIGHT}). + *

+ */ + public static final String PROPERTY_BOUNDS_HIT = "boundsHit"; + + /** + * Name of a {@link PropertyChangeEvent} when a collision with another {@link SpriteInfo} instance is about to + * happen. This event will be triggered for all involved sprites. + *

+ * Value type: {@link SpriteInfo} object of the collision target. + *

+ */ + public static final String PROPERTY_COLLISION = "collision"; + + /** + * Name of a {@link PropertyChangeEvent} when an unbounded sprite has completely vanished from the canvas. + *

+ * Value type: null (no argument) + *

+ */ + public static final String PROPERTY_VANISHED = "vanished"; + + /** Bit index for top boundary. */ + private static final int BOUNDS_TOP = 0; + /** Bit index for left boundary. */ + private static final int BOUNDS_LEFT = 1; + /** Bit index for bottom boundary. */ + private static final int BOUNDS_BOTTOM = 2; + /** Bit index for right boundary. */ + private static final int BOUNDS_RIGHT = 3; + + /** Defines a unit vector for all available {@link Direction}s. */ + private static final EnumMap DIRECTION = new EnumMap<>(Direction.class); + + /** Maps mirrored directions when hitting the top boundary from any direction. */ + private static final Map MIRRORED_DIR_TOP = new HashMap<>(); + /** Maps mirrored directions when hitting the left boundary from any direction. */ + private static final Map MIRRORED_DIR_LEFT = new HashMap<>(); + /** Maps mirrored directions when hitting the bottom boundary from any direction. */ + private static final Map MIRRORED_DIR_BOTTOM = new HashMap<>(); + /** Maps mirrored directions when hitting the right boundary from any direction. */ + private static final Map MIRRORED_DIR_RIGHT = new HashMap<>(); + + /** + * Maps a factor to individual action sequences which determines the effective move speed of the sprite. + *

+ * Walking has a move factor 1 (unaltered speed). Running has a move factor 2. Everything else has a move factor 0 + * (no move). + *

+ */ + private static final EnumMap SEQUENCE_MOVE_FACTOR = new EnumMap<>(Sequence.class); + + /** A global threadpool with reusable threads for executing general purpose tasks. */ + private static final ExecutorService CACHED_THREADPOOL = Executors.newCachedThreadPool(); + + /** Duration for displaying the selection circle of a sprite. */ + private static final long DISPLAY_CIRCLE_DURATION = 3000L; + + static { + // Normalized vectors for directions + DIRECTION.put(Direction.S, getUnitVector(0.0, 2.0, true)); + DIRECTION.put(Direction.SSW, getUnitVector(-1.0, 2.0, true)); + DIRECTION.put(Direction.SW, getUnitVector(-2.0, 2.0, true)); + DIRECTION.put(Direction.WSW, getUnitVector(-2.0, 1.0, true)); + DIRECTION.put(Direction.W, getUnitVector(-2.0, 0.0, true)); + DIRECTION.put(Direction.WNW, getUnitVector(-2.0, -1.0, true)); + DIRECTION.put(Direction.NW, getUnitVector(-2.0, -2.0, true)); + DIRECTION.put(Direction.NNW, getUnitVector(-1.0, -2.0, true)); + DIRECTION.put(Direction.N, getUnitVector(0.0, -2.0, true)); + DIRECTION.put(Direction.NNE, getUnitVector(1.0, -2.0, true)); + DIRECTION.put(Direction.NE, getUnitVector(2.0, -2.0, true)); + DIRECTION.put(Direction.ENE, getUnitVector(2.0, -1.0, true)); + DIRECTION.put(Direction.E, getUnitVector(2.0, 0.0, true)); + DIRECTION.put(Direction.ESE, getUnitVector(2.0, 1.0, true)); + DIRECTION.put(Direction.SE, getUnitVector(2.0, 2.0, true)); + DIRECTION.put(Direction.SSE, getUnitVector(1.0, 2.0, true)); + + for (final Direction dir : Direction.values()) { + MIRRORED_DIR_TOP.put(dir, dir); + MIRRORED_DIR_LEFT.put(dir, dir); + MIRRORED_DIR_BOTTOM.put(dir, dir); + MIRRORED_DIR_RIGHT.put(dir, dir); + switch (dir) { + case S: + MIRRORED_DIR_BOTTOM.put(dir, Direction.N); + break; + case SSW: + MIRRORED_DIR_LEFT.put(dir, Direction.SSE); + MIRRORED_DIR_BOTTOM.put(dir, Direction.NNW); + break; + case SW: + MIRRORED_DIR_LEFT.put(dir, Direction.SE); + MIRRORED_DIR_BOTTOM.put(dir, Direction.NW); + break; + case WSW: + MIRRORED_DIR_LEFT.put(dir, Direction.ESE); + MIRRORED_DIR_BOTTOM.put(dir, Direction.WNW); + break; + case W: + MIRRORED_DIR_LEFT.put(dir, Direction.E); + break; + case WNW: + MIRRORED_DIR_TOP.put(dir, Direction.WSW); + MIRRORED_DIR_LEFT.put(dir, Direction.ENE); + break; + case NW: + MIRRORED_DIR_TOP.put(dir, Direction.SW); + MIRRORED_DIR_LEFT.put(dir, Direction.NE); + break; + case NNW: + MIRRORED_DIR_TOP.put(dir, Direction.SSW); + MIRRORED_DIR_LEFT.put(dir, Direction.NNE); + break; + case N: + MIRRORED_DIR_TOP.put(dir, Direction.S); + break; + case NNE: + MIRRORED_DIR_TOP.put(dir, Direction.SSE); + MIRRORED_DIR_RIGHT.put(dir, Direction.NNW); + break; + case NE: + MIRRORED_DIR_TOP.put(dir, Direction.SE); + MIRRORED_DIR_RIGHT.put(dir, Direction.NW); + break; + case ENE: + MIRRORED_DIR_TOP.put(dir, Direction.ESE); + MIRRORED_DIR_RIGHT.put(dir, Direction.WNW); + break; + case E: + MIRRORED_DIR_RIGHT.put(dir, Direction.W); + break; + case ESE: + MIRRORED_DIR_BOTTOM.put(dir, Direction.ENE); + MIRRORED_DIR_RIGHT.put(dir, Direction.WSW); + break; + case SE: + MIRRORED_DIR_BOTTOM.put(dir, Direction.NE); + MIRRORED_DIR_RIGHT.put(dir, Direction.SW); + break; + case SSE: + MIRRORED_DIR_BOTTOM.put(dir, Direction.NNE); + MIRRORED_DIR_RIGHT.put(dir, Direction.SSW); + break; + } + } + + for (final Sequence seq : Sequence.values()) { + switch (seq) { + case WALK: + case PST_WALK: + SEQUENCE_MOVE_FACTOR.put(seq, 1.0); + break; + case PST_RUN: + SEQUENCE_MOVE_FACTOR.put(seq, 2.0); + break; + default: + SEQUENCE_MOVE_FACTOR.put(seq, 0.0); + break; + } + } + } + + /** Task is fired after a set amount of time to disable the selection circle display. */ + private final Callable circleEndedTask = () -> { + try { + Thread.sleep(DISPLAY_CIRCLE_DURATION); + } catch (InterruptedException e) { + // cancelled prematurely + return false; + } + + if (!isClosed()) { + getDecoder().setSelectionCircleEnabled(false); + setTooltipEnabled(false); + return true; + } + return false; + }; + + /** List for storing event listener objects. */ + private final EventListenerList listenerList = new EventListenerList(); + + /** Sprite decoder instance */ + private final SpriteDecoder decoder; + + /** Sprite controller instance */ + private final SpriteBamControl control; + + /** Moving speed, in unit vectors per tick */ + private final double speed; + + /** Creature space, radius in pixels */ + private final double space; + + /** The parent {@link SpriteAnimationPanel} instance. */ + private final SpriteAnimationPanel panel; + + /** A reusable {@link BitSet} object for storing the result of {@link #boundsHit()}. */ + private final BitSet boundsHit = new BitSet(4); + + /** Tooltip/Name of the sprite */ + private final String tooltip; + + /** x coordinate of cre location */ + private double x; + /** y coordinate of cre location */ + private double y; + + /** Indicates whether the sprite is bound to the canvas. */ + private boolean bounded; + + /** Currently used animation sequence */ + private Sequence sequence; + + /** Current moving direction */ + private Direction direction; + + /** Frame index of the current animation cycle */ + private int frameIndex; + + /** Indicates that the SpriteInfo object has been released. */ + private boolean closed; + + /** Indicates whether the tooltip of the creature should be displayed. */ + private boolean tooltipEnabled; + + /** A future that provides access to the background task for delayed deactivation of selection circle display. */ + private Future circleEndedTaskResult; + /** Tracks whether the mouse cursor has entered or left the bounds of the sprite. */ + private boolean spriteBoundsEntered; + + /** + * Initializes a new object for tracking the current state of a sprite. + * + * @param animator The associated {@link SpriteAnimationPanel} instance. + * @param cre {@link CreResource} to create the sprite of. + * @param bounded Whether the sprite should be confined within the boundaries of the animator panel. + * @param x Initial x coordinate on the panel. + * @param y Initial y coordinate on the panel. + * @param seq The action sequence to load. + * @param dir The direction to move or face. + * @throws Exception if the sprite could not be loaded. + */ + public SpriteInfo(SpriteAnimationPanel animator, CreResource cre, boolean bounded, int x, int y, Sequence seq, + Direction dir) throws Exception { + this.panel = Objects.requireNonNull(animator); + this.decoder = SpriteDecoder.importSprite(cre); + this.decoder.setSelectionCircleBitmap(isPstAnimation()); + this.control = this.decoder.createControl(); + this.control.setMode(BamControl.Mode.INDIVIDUAL); + this.speed = (int) this.decoder.getMoveScale(); + this.space = this.decoder.getPersonalSpace() * 8; + this.x = getAdjustedX(x); + this.y = getAdjustedY(y); + this.bounded = bounded; + this.direction = Direction.S; + setSequence(Objects.requireNonNull(seq)); + setDirection(Objects.requireNonNull(dir)); + this.closed = false; + + // retrieving name or tooltip of the creature + final StructEntry nameEntry = decoder.getCreResource().getEffectiveNameEntry(); + this.tooltip = (nameEntry != null) ? nameEntry.toString() : null; + } + + /** Returns whether the {@link SpriteInfo} instance has been released. A released instance cannot be used again. */ + public boolean isClosed() { + return closed; + } + + @Override + public void close() throws Exception { + closed = true; + direction = null; + sequence = null; + boundsHit.clear(); + decoder.close(); + } + + /** + * Advances the sprite by one frame. Updates frame and position. Sprite is automatically reflected from boundaries + * if bounded mode is enabled. + */ + public void advance() { + if (isClosed()) { + return; + } + + final boolean sequenceEnded = isSequenceEnded(); + if (getControl().cycleFrameCount() > 0) { + frameIndex = (getFrameIndex() + 1) % getControl().cycleFrameCount(); + } + getControl().cycleSetFrameIndex(getFrameIndex()); + + // advancing position: take boundary collision into account + for (int i = 0; isBounded() && i < 4; i++) { + final BitSet bounds = boundsHit(); + if (!bounds.isEmpty()) { + final Direction newDirection = getMirroredDirection(direction, bounds.get(BOUNDS_TOP), bounds.get(BOUNDS_LEFT), + bounds.get(BOUNDS_BOTTOM), bounds.get(BOUNDS_RIGHT)); + setDirection(newDirection); + break; + } + } + advancePosition(); + + // onSequenceEnded event + if (sequenceEnded) { + fireSequenceEnded(); + } + } + + /** Returns the associated {@link SpriteAnimationPanel} instance. */ + public SpriteAnimationPanel getPanel() { + return panel; + } + + /** Returns the {@link SpriteDecoder} instance of the sprite. */ + public SpriteDecoder getDecoder() { + return decoder; + } + + /** Returns the {@link SpriteBamControl} associated with the sprite decoder. */ + public SpriteBamControl getControl() { + return control; + } + + /** Returns the default movement speed of the sprite. */ + public double getSpeed() { + return speed; + } + + /** Returns the space that is occupied by the sprite, as radius in pixels. */ + public double getSpace() { + return space; + } + + /** Returns the current x coordinate of the sprite on the canvas. */ + public double getX() { + return x; + } + + /** Returns the current y coordinate of the sprite on the canvas. */ + public double getY() { + return y; + } + + /** Returns the current action {@link Sequence} of the sprite. */ + public Sequence getSequence() { + return sequence; + } + + /** + * Assigns a new sequence and direction to the sprite. + * + * @param newSequence The new {@link Sequence}. + * @return {@code true} if sprite data could be loaded successfully, {@code false} otherwise. + */ + public boolean setSequence(Sequence newSequence) { + boolean retVal = false; + if (newSequence != null && newSequence != getSequence()) { + try { + retVal = getDecoder().loadSequence(newSequence); + if (retVal) { + sequence = newSequence; + frameIndex = 0; + setDirection(getDirection()); + } + } catch (Exception e) { + Logger.debug(e); + } + } + return retVal; + } + + /** Returns {@code true} if the animation cycle for the current sequence has ended. */ + public boolean isSequenceEnded() { + return getFrameIndex() + 1 >= getControl().cycleFrameCount(); + } + + /** Returns the move factor for the current action sequence of the sprite. */ + public double getMoveFactor() { + return getMoveFactor(getSequence()); + } + + /** Returns the move factor for the specified action sequence. */ + public double getMoveFactor(Sequence seq) { + return SEQUENCE_MOVE_FACTOR.getOrDefault(seq, 0.0); + } + + /** Returns the current {@link Direction} of the sprite. */ + public Direction getDirection() { + return direction; + } + + /** Assigns the specified direction to the current sprite sequence. Frame advancement is preserved when possible. */ + public void setDirection(Direction newDirection) { + if (newDirection != null) { + newDirection = getDecoder().getExistingDirection(newDirection); + final int cycleIdx = getDecoder().getDirectionMap().getOrDefault(newDirection, -1); + if (cycleIdx >= 0) { + getControl().cycleSet(cycleIdx); + direction = newDirection; + if (control.cycleFrameCount() > 0) { + frameIndex = getFrameIndex() % getControl().cycleFrameCount(); + } + } + } + } + + /** Returns the frame index of the animation cycle used for the current action sequence. */ + public int getFrameIndex() { + return frameIndex; + } + + /** Returns whether the sprite should be confined to the canvas. */ + public boolean isBounded() { + return bounded; + } + + /** Specifies whether the sprite should be confined to the canvas. */ + public void setBounded(boolean b) { + this.bounded = b; + } + + /** Returns the horizontal distance for advancing the sprite by one step in the current direction. */ + public double getAdvanceX() { + return DIRECTION.get(getDirection()).x * getSpeed() * getMoveFactor(); + } + + /** Returns the vertical distance for advancing the sprite by one step in the current direction. */ + public double getAdvanceY() { + return DIRECTION.get(getDirection()).y * getSpeed() * getMoveFactor(); + } + + /** + * Returns the vector for advancing the sprite by one step in the current direction. + *

+ * Note that a call of this method involves allocation of a {@link Point2D} object. + *

+ */ + public Point2D.Double getAdvance() { + final Point2D.Double retVal = new Point2D.Double(); + final Point2D.Double unit = DIRECTION.get(getDirection()); + retVal.x = unit.x * getSpeed() * getMoveFactor(); + retVal.y = unit.y * getSpeed() * getMoveFactor(); + return retVal; + } + + /** Advances the sprite by one step. */ + public void advancePosition() { + x = getAdjustedX(x + getAdvanceX()); + y = getAdjustedY(y + getAdvanceY()); + + // onVanished event + if (isVanished()) { + fireVanished(); + } + + // onBoundsHit event + final BitSet bits = boundsHit(); + if (!bits.isEmpty()) { + fireBoundsHit(bits); + } + + // onCollision event + findCollisions(true, null); + } + + /** + * Returns the bounds of the sprite relative to the given coordinates. + * + * @param x x coordinate of the sprite center position. + * @param y y coordinate of the sprite center position. + * @param rect A {@link Rectangle} object for reuse. Specify {@code null} to create a new {@code Rectangle} + * instance. + * @return {@link Rectangle} with the bounds of the sprite. + */ + public Rectangle getSpriteBounds(int x, int y, Rectangle rect) { + if (rect == null) { + rect = new Rectangle(); + } + + final PseudoBamFrameEntry info = getDecoder().getFrameInfo(getControl().cycleGetFrameIndexAbsolute()); + rect.x = x - info.getCenterX(); + rect.y = y - info.getCenterY(); + rect.width = info.getWidth(); + rect.height = info.getHeight(); + return rect; + } + + /** + * Returns a {@link BitSet} with all boundaries that are hit by the next sprite advancement. + *

+ * Note: {@link BitSet} instance of the return value is reused for every call of this method. + *

+ * + * @return A {@link BitSet} containing all boundaries that are hit by the next sprite advancement. + * @see #BOUNDS_TOP + * @see #BOUNDS_LEFT + * @see #BOUNDS_BOTTOM + * @see #BOUNDS_RIGHT + */ + public BitSet boundsHit() { + boundsHit.clear(); + if (isBounded()) { + final Point2D.Double vec = getAdvance(); + boundsHit.set(BOUNDS_TOP, y + vec.y - getSpace() < 0); + boundsHit.set(BOUNDS_LEFT, x + vec.x - getSpace() < 0); + boundsHit.set(BOUNDS_BOTTOM, y + vec.y + getSpace() > getPanel().getHeight()); + boundsHit.set(BOUNDS_RIGHT, x + vec.x + getSpace() > getPanel().getWidth()); + } + return boundsHit; + } + + /** Returns whether the sprite has fully disappeared behind the canvas boundaries. */ + public boolean isVanished() { + if (isBounded()) { + return false; + } + + final PseudoBamFrameEntry info = getDecoder().getFrameInfo(getControl().cycleGetFrameIndexAbsolute()); + int left = -info.getCenterX(); + int top = -info.getCenterY(); + int right = info.getWidth() - info.getCenterX(); + int bottom = info.getHeight() - info.getCenterY(); + + boolean retVal = (getX() + right < 0); + if (!retVal) { + retVal = (getY() + bottom < 0); + } + if (!retVal) { + retVal = (getX() + left > getPanel().getWidth()); + } + if (!retVal) { + retVal = (getY() + top > getPanel().getHeight()); + } + return retVal; + } + + /** Returns whether the tooltip of the creature should be displayed. */ + public boolean isTooltipEnabled() { + return tooltipEnabled; + } + + /** Specifies whether the tooltip of the creature should be displayed. */ + public void setTooltipEnabled(boolean b) { + tooltipEnabled = b; + } + + /** Returns the tooltip string of the creature. Returns {@code null} if the tooltip is unavailable. */ + public String getTooltip() { + return tooltip; + } + + /** + * Returns the mirrored direction based on the specified parameters. + * + * @param dir The {@link Direction} to mirror. + * @param top Whether to mirror on the top border. + * @param left Whether to mirror on the left border. + * @param bottom Whether to mirror on the bottom border. + * @param right Whether to mirror on the right border. + * @return The mirrored {@link Direction}. + */ + public Direction getMirroredDirection(Direction dir, boolean top, boolean left, boolean bottom, boolean right) { + Direction retVal = dir; + if (retVal != null) { + if (top) { + retVal = MIRRORED_DIR_TOP.get(retVal); + } + if (left) { + retVal = MIRRORED_DIR_LEFT.get(retVal); + } + if (bottom) { + retVal = MIRRORED_DIR_BOTTOM.get(retVal); + } + if (right) { + retVal = MIRRORED_DIR_RIGHT.get(retVal); + } + } + return retVal; + } + + /** Adds a {@link PropertyChangeListener} to the SpriteInfo object. */ + public void addPropertyChangeListener(PropertyChangeListener l) { + if (l != null) { + listenerList.add(PropertyChangeListener.class, l); + } + } + + /** Returns all registered {@link PropertyChangeListener}s for this SpriteInfo object. */ + public PropertyChangeListener[] getPropertyChangeListeners() { + return listenerList.getListeners(PropertyChangeListener.class); + } + + /** Removes a {@link PropertyChangeListener} from the SpriteInfo object. */ + public void removePropertyChangeListener(PropertyChangeListener l) { + if (l != null) { + listenerList.remove(PropertyChangeListener.class, l); + } + } + + /** Generalized method for firing a {@link PropertyChangeEvent} to all registered listeners. */ + private void firePropertyChangePerformed(String name, Object oldValue, Object newValue) { + if (name != null) { + Object[] listeners = listenerList.getListenerList(); + PropertyChangeEvent e = null; + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == PropertyChangeListener.class) { + // Event object is lazily created + if (e == null) { + e = new PropertyChangeEvent(this, name, oldValue, newValue); + } + final PropertyChangeListener listener = (PropertyChangeListener)listeners[i + 1]; + final PropertyChangeEvent event = e; + SwingUtilities.invokeLater(() -> listener.propertyChange(event)); + } + } + } + } + + /** Fired when the animation cycle of the current action sequence ended. */ + private void fireSequenceEnded() { + firePropertyChangePerformed(PROPERTY_SEQUENCE_ENDED, null, sequence); + } + + /** Fired if the sprite will collide with the canvas boundary on the next frame. */ + private void fireBoundsHit(BitSet hitBounds) { + final int bounds = hitBounds.toByteArray()[0]; + firePropertyChangePerformed(PROPERTY_BOUNDS_HIT, 0, bounds); + } + + /** Fires if the sprite collide with another sprite at the next frame. */ + private void fireCollision(SpriteInfo target) { + firePropertyChangePerformed(PROPERTY_COLLISION, null, target); + } + + /** Fired when the sprite vanished completely from the canvas. */ + private void fireVanished() { + firePropertyChangePerformed(PROPERTY_VANISHED, null, null); + } + + /** + * Determines if any of the available sprites will collide with this sprite in the next update. + * + * @param fireEvent Indicates whether a {@link PropertyChangeEvent} is fired for any detected collisions. + * @param output An optional {@link List} that stores the collision targets. Specify {@code null} to skip. + */ + private void findCollisions(boolean fireEvent, List output) { + final double sx = getX() + getAdvanceX(); + final double sy = getY() + getAdvanceY(); + for (final SpriteInfo si : getPanel().getSprites()) { + if (si != this) { + final double dx = si.x + si.getAdvanceX(); + final double dy = si.y + si.getAdvanceY(); + final double spaceDist2 = (getSpace() + si.getSpace())*(getSpace() + si.getSpace()); + final double dist2 = (sx - dx)*(sx - dx) + (sy - dy)*(sy - dy); + if (dist2 < spaceDist2) { + if (fireEvent) { + fireCollision(si); + } + if (output != null) { + output.add(si); + } + } + } + } + } + + /** Adjust the specified x coordinate to a legal value. */ + private double getAdjustedX(double x) { + if (isBounded()) { + if (x - getSpace() < 0.0 ) { + x = getSpace(); + } + if (x + getSpace() > getPanel().getWidth()) { + x = getPanel().getWidth() - getSpace(); + } + } + return x; + } + + /** Adjust the specified y coordinate to a legal value. */ + private double getAdjustedY(double y) { + if (isBounded()) { + if (y - getSpace() < 0.0 ) { + y = getSpace(); + } + if (y + getSpace() > getPanel().getHeight()) { + y = getPanel().getHeight() - getSpace(); + } + } + return y; + } + + /** + * Enables the selection circle of the sprite for a set amount of time. + *

+ * This method should be called when the mouse cursor is inside the sprite bounds. Together with + * {@link #onSpriteBoundsExited()} it prevents redundant calls to enable the sprite selection circles while the + * mouse cursor is inside the sprite bounds. + *

+ */ + public void onSpriteBoundsEntered() { + if (!spriteBoundsEntered) { + spriteBoundsEntered = true; + fireCircleTimer(); + } + } + + /** + * Should be called when the mouse cursor is outside of the sprite bounds. This method is used together with + * {@link #onSpriteBoundsEntered()}. + */ + public void onSpriteBoundsExited() { + if (spriteBoundsEntered) { + spriteBoundsEntered = false; + } + } + + /** + * Enables display of the selection circle for the specified amount of time before it is disabled. It is called + * when the mouse cursor hovers over a creature sprite. + * + * @param millis Delay in milliseconds. + */ + private void fireCircleTimer() { + if (circleEndedTaskResult != null) { + circleEndedTaskResult.cancel(true); + circleEndedTaskResult = null; + } + getDecoder().setSelectionCircleEnabled(true); + setTooltipEnabled(true); + circleEndedTaskResult = CACHED_THREADPOOL.submit(circleEndedTask); + } + + /** Returns whether the sprite animation is of the Planescape sprite type (Type F000). */ + public boolean isPstAnimation() { + return (getDecoder().getAnimationType() == AnimationInfo.Type.MONSTER_PLANESCAPE); + } + + /** Returns the default "walking" action sequence for this sprite type. */ + public Sequence getDefaultWalkingSequence() { + if (isPstAnimation()) { + return Sequence.PST_WALK; + } + return Sequence.WALK; + } + + /** Returns the default "running" action sequence for this sprite type. */ + public Sequence getDefaultRunningSequence() { + if (isPstAnimation()) { + return Sequence.PST_RUN; + } + return Sequence.WALK; + } + + /** Returns a list of available action sequences for the sprite. */ + public List getAvailableSequences() { + final List retVal = new ArrayList<>(); + + for (final Sequence seq : Sequence.values()) { + if (getDecoder().isSequenceAvailable(seq)) { + boolean skip = isPstAnimation() && seq.name().contains("_MISC"); + switch (seq) { + case HIDE: + case EMERGE: + skip = true; + break; + default: + break; + } + + if (!skip) { + retVal.add(seq); + } + } + } + + return retVal; + } + + /** Determines the most suitable {@link Direction} that describes the vector {@code (x1, y1) -> (x2, y2)}. */ + public static Direction findDirection(double x1, double y1, double x2, double y2) { + Direction retVal = Direction.S; + + final Point2D.Double vec = getUnitVector(x2 - x1, y2 - y1, false); + if (vec.x == 0.0 && vec.y == 0.0) { + return retVal; + } + + double dist = Double.MAX_VALUE; + for (final Direction dir : Direction.values()) { + final Point2D.Double base = DIRECTION.get(dir); + double nom = (vec.x * base.x + vec.y * base.y); + double den = Math.sqrt(vec.x*vec.x + vec.y*vec.y) * Math.sqrt(base.x*base.x + base.y*base.y); + double theta = Math.acos(nom / den); + double thetaAbs = Math.abs(theta); + if (!Double.isNaN(theta) && thetaAbs < dist) { + retVal = dir; + dist = thetaAbs; + if (dist == 0.0) { + break; + } + } + } + + return retVal; + } + + /** + * Used internally to produce a unit vector. + * + * @param x X coordinate of the vector. + * @param y Y coordinate of the vector. + * @param perspective Whether to apply perspective correction to the vertical direction. + */ + private static Point2D.Double getUnitVector(double x, double y, boolean perspective) { + if (perspective) { + y *= 0.75; + } + + final double modulus = Math.sqrt(x*x + y*y); + if (modulus != 0.0) { + return new Point2D.Double(x / modulus, y / modulus); + } else { + return new Point2D.Double(); + } + } + } +} diff --git a/src/org/infinity/gui/StopWatchTester.java b/src/org/infinity/gui/StopWatchTester.java new file mode 100644 index 000000000..defaa9a42 --- /dev/null +++ b/src/org/infinity/gui/StopWatchTester.java @@ -0,0 +1,201 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.gui; + +import java.awt.Container; +import java.awt.Font; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; + +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JRadioButtonMenuItem; +import javax.swing.SwingConstants; + +import org.infinity.NearInfinity; +import org.infinity.icon.Icons; +import org.infinity.util.StopWatch; + +/** + * A test dialog for the {@link StopWatch} class. + */ +public class StopWatchTester extends ChildFrame implements ActionListener, ItemListener { + // Display for hours, minutes, seconds, and fractional seconds (one digit) + private static final String DISPLAY_HMSF_FMT = "%02d:%02d:%02d.%d"; + + private final StopWatch timer = new StopWatch(false); + + private final JLabel displayLabel = new JLabel("00:00:00.0"); + private final JButton resetButton = new JButton("Reset"); + private final JButton startButton = new JButton("Start"); + private final JButton pauseButton = new JButton("Pause/Resume"); + + private final JRadioButtonMenuItem rbmiInterval2000 = new JRadioButtonMenuItem("2 seconds"); + private final JRadioButtonMenuItem rbmiInterval1000 = new JRadioButtonMenuItem("1 second"); + private final JRadioButtonMenuItem rbmiInterval500 = new JRadioButtonMenuItem("500 ms"); + private final JRadioButtonMenuItem rbmiInterval100 = new JRadioButtonMenuItem("100 ms"); + + public StopWatchTester() { + super("Stop Watch Test Dialog"); + init(); + } + + public void resetTimer() { + timer.pause(); + timer.reset(); + startButton.setEnabled(true); + pauseButton.setEnabled(false); + updateDisplay(); + } + + public void startTimer() { + timer.resume(); + startButton.setEnabled(false); + pauseButton.setEnabled(true); + } + + public void pauseTimer() { + timer.pause(); + } + + public void resumeTimer() { + timer.resume(); + } + + @Override + public void actionPerformed(ActionEvent e) { + if (e.getSource() == timer) { + updateDisplay(); + } else if (e.getSource() == resetButton) { + resetTimer(); + } else if (e.getSource() == startButton) { + startTimer(); + } else if (e.getSource() == pauseButton) { + if (timer.isPaused()) { + resumeTimer(); + } else { + pauseTimer(); + } + } + } + + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + if (e.getSource() == rbmiInterval2000) { + timer.setDelay(2000L); + } else if (e.getSource() == rbmiInterval1000) { + timer.setDelay(1000L); + } else if (e.getSource() == rbmiInterval500) { + timer.setDelay(500L); + } else if (e.getSource() == rbmiInterval100) { + timer.setDelay(100L); + } + } + } + + private void updateDisplay() { + long elapsed = timer.elapsed(); + elapsed /= 100L; + final long fraction = elapsed % 10L; + elapsed /= 10L; + final long seconds = elapsed % 60L; + elapsed /= 60L; + final long minutes = elapsed % 60L; + elapsed /= 60L; + final long hours = elapsed % 60L; + + displayLabel.setText(String.format(DISPLAY_HMSF_FMT, hours, minutes, seconds, fraction)); + } + + private void init() { + addComponentListener(new ComponentAdapter() { + @Override + public void componentHidden(ComponentEvent e) { + resetTimer(); + } + }); + + final Font font = new Font(Font.MONOSPACED, Font.BOLD, 32); + displayLabel.setFont(font); + displayLabel.setHorizontalAlignment(SwingConstants.CENTER); + + resetButton.addActionListener(this); + startButton.addActionListener(this); + pauseButton.addActionListener(this); + + final ButtonGroup bg = new ButtonGroup(); + bg.add(rbmiInterval2000); + bg.add(rbmiInterval1000); + bg.add(rbmiInterval500); + bg.add(rbmiInterval100); + + final ButtonPopupMenu bpmInterval = new ButtonPopupMenu("Display Interval..."); + bpmInterval.setIcon(Icons.ICON_ARROW_DOWN_15.getIcon()); + bpmInterval.addItem(rbmiInterval2000); + bpmInterval.addItem(rbmiInterval1000); + bpmInterval.addItem(rbmiInterval500); + bpmInterval.addItem(rbmiInterval100); + + rbmiInterval2000.addItemListener(this); + rbmiInterval1000.addItemListener(this); + rbmiInterval500.addItemListener(this); + rbmiInterval100.addItemListener(this); + rbmiInterval1000.setSelected(true); + + resetButton.setEnabled(true); + startButton.setEnabled(true); + pauseButton.setEnabled(false); + + timer.addActionListener(this); + + final GridBagConstraints gbc = new GridBagConstraints(); + + // button panel + final JPanel buttonPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + buttonPanel.add(bpmInterval, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 24, 0, 0), 0, 0); + buttonPanel.add(resetButton, gbc); + ViewerUtil.setGBC(gbc, 2, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0), 0, 0); + buttonPanel.add(startButton, gbc); + ViewerUtil.setGBC(gbc, 3, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(0, 8, 0, 0), 0, 0); + buttonPanel.add(pauseButton, gbc); + + // main panel + final JPanel mainPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + mainPanel.add(displayLabel, gbc); + ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + mainPanel.add(buttonPanel, gbc); + + // dialog layout + final Container pane = getContentPane(); + pane.setLayout(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(8, 8, 8, 8), 0, 0); + pane.add(mainPanel, gbc); + + pack(); + setMinimumSize(getSize()); + setMaximumSize(getSize()); + Center.center(this, NearInfinity.getInstance().getBounds()); + } +} diff --git a/src/org/infinity/gui/StringEditor.java b/src/org/infinity/gui/StringEditor.java index 60fc85b6b..1ee6d1f07 100644 --- a/src/org/infinity/gui/StringEditor.java +++ b/src/org/infinity/gui/StringEditor.java @@ -5,6 +5,7 @@ package org.infinity.gui; import java.awt.BorderLayout; +import java.awt.Component; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Insets; @@ -34,6 +35,7 @@ import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.ProgressMonitor; +import javax.swing.RootPaneContainer; import javax.swing.SwingConstants; import javax.swing.SwingWorker; import javax.swing.UIManager; @@ -59,7 +61,9 @@ import org.infinity.search.StringReferenceSearcher; import org.infinity.util.Logger; import org.infinity.util.Misc; +import org.infinity.util.Operation; import org.infinity.util.StringTable; +import org.infinity.util.StringTable.ProgressCallback; import org.infinity.util.io.FileEx; import org.infinity.util.io.FileManager; @@ -115,24 +119,58 @@ public StringEditor() { showEntry(0); } - @Override - protected boolean windowClosing(boolean forced) throws Exception { + /** + * Performs the chosen operation on the string table. + *

+ * In non-interactive mode it will execute the code specified by the {@code saveOperation} parameter. + *

+ *

+ * In interactive mode it depends on the user choice: {@code YES} executes the code specified by the + * {@code saveOperation} parameter. {@code NO} resets the modified content of the string table. {@code CANCEL} skips + * all operations. + *

+ * + * @param saveOperation {@code Operation} that performs the actual string table save operation. If {@code null} is + * specified then a simple {@link StringTable#write(ProgressCallback)} is performed. + * @param interactive Specify {@code true} to provide a confirmation dialog before proceeding with the save + * operation. + * @param forced Specify {@code true} to prevent the user from cancelling the save operation. This parameter is + * only considered in interactive mode. + * @param parent Parent {@code Component} used for the confirmation dialog. Can be {@code null} if + * {@code interactive} is {@code false}. + * @return {@code true} if the save operation was completed as per request, {@code false} if the operation was + * cancelled. + */ + public static boolean saveModified(Operation saveOperation, boolean interactive, boolean forced, Component parent) { boolean retVal = true; - updateEntry(getSelectedEntry()); if (StringTable.isModified()) { - setVisible(true); - int optionType = forced ? JOptionPane.YES_NO_OPTION : JOptionPane.YES_NO_CANCEL_OPTION; - int result = JOptionPane.showConfirmDialog(this, "String table has been modified. Save changes to disk?", - "Save changes", optionType, JOptionPane.QUESTION_MESSAGE); + boolean shouldSave = true; + boolean shouldClear = false; - if (result == JOptionPane.YES_OPTION) { - SwingWorker worker = new SwingWorker() { + if (interactive) { + int optionType = forced ? JOptionPane.YES_NO_OPTION : JOptionPane.YES_NO_CANCEL_OPTION; + if (parent == null) { + parent = NearInfinity.getInstance(); + } + int result = JOptionPane.showConfirmDialog(parent, "String table has been modified. Save changes to disk?", + "Save changes", optionType, JOptionPane.QUESTION_MESSAGE); + shouldSave = (result == JOptionPane.YES_OPTION); + shouldClear = (result == JOptionPane.NO_OPTION); + retVal = (result != JOptionPane.CANCEL_OPTION); + } + + if (shouldSave) { + // performing specified save operation + final Operation op = (saveOperation != null) ? saveOperation : () -> StringTable.write(null); + final RootPaneContainer pane = + (parent instanceof RootPaneContainer) ? (RootPaneContainer)parent : NearInfinity.getInstance(); + final SwingWorker worker = new SwingWorker() { @Override protected Void doInBackground() throws Exception { - WindowBlocker blocker = new WindowBlocker(StringEditor.this); + WindowBlocker blocker = new WindowBlocker(pane); try { blocker.setBlocked(true); - save(false); + op.perform(); } finally { blocker.setBlocked(false); } @@ -148,7 +186,25 @@ protected Void doInBackground() throws Exception { } } - retVal = (result != JOptionPane.CANCEL_OPTION); + if (shouldClear) { + // removing "modified" flag from string table + StringTable.resetModified(StringTable.Type.MALE); + if (StringTable.hasFemaleTable()) { + StringTable.resetModified(StringTable.Type.FEMALE); + } + } + } + + return retVal; + } + + @Override + protected boolean windowClosing(boolean forced) throws Exception { + boolean retVal = true; + updateEntry(getSelectedEntry()); + if (StringTable.isModified()) { + setVisible(true); + retVal = saveModified(() -> save(false), true, forced, this); } return retVal; } @@ -702,7 +758,7 @@ private void save(boolean interactive) { if (!interactive) { return; } - String msg = "\"" + outFile.toString() + "\" is located within a write-protected archive." + String msg = "\"" + outFile + "\" is located within a write-protected archive." + "\nDo you want to export it to another location instead?"; int result = JOptionPane.showConfirmDialog(this, msg, "Save resource", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); @@ -728,7 +784,7 @@ private void save(boolean interactive) { ProgressTracker pt = null; if (interactive) { pt = new ProgressTracker("Saving " + outFile.getFileName().toString(), null, - "Error writing " + outFile.getFileName().toString()); + "Error writing " + outFile.getFileName()); } if (!StringTable.write(StringTable.Type.MALE, outFile, pt)) { return; @@ -738,7 +794,7 @@ private void save(boolean interactive) { outFile = outPath.resolve(StringTable.getPath(StringTable.Type.FEMALE).getFileName()); if (interactive) { pt = new ProgressTracker("Saving " + outFile.getFileName().toString(), null, - "Error writing " + outFile.getFileName().toString()); + "Error writing " + outFile.getFileName()); } if (!StringTable.write(StringTable.Type.FEMALE, outFile, pt)) { return; diff --git a/src/org/infinity/gui/StructViewer.java b/src/org/infinity/gui/StructViewer.java index d79eea0a9..6a96f11fd 100644 --- a/src/org/infinity/gui/StructViewer.java +++ b/src/org/infinity/gui/StructViewer.java @@ -72,10 +72,11 @@ import org.infinity.datatype.IsNumeric; import org.infinity.datatype.IsReference; import org.infinity.datatype.IsTextual; -import org.infinity.datatype.Readable; +import org.infinity.datatype.ProRef; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.SectionCount; import org.infinity.datatype.SectionOffset; +import org.infinity.datatype.Song2daBitmap; import org.infinity.datatype.TextBitmap; import org.infinity.datatype.TextString; import org.infinity.datatype.Unknown; @@ -137,6 +138,9 @@ public final class StructViewer extends JPanel implements ListSelectionListener, public static final String CMD_TORESLIST = "ToResList"; public static final String CMD_RESET = "ResetType"; public static final String CMD_GOTO_OFFSET = "GotoOffset"; + public static final String CMD_APPLY_TO_ALL = "ApplyToAllRemovables"; + public static final String CMD_APPLY_TO_NON_EMPTY = "ApplyToNonEmptyRemovables"; + public static final String CMD_APPLY_TO_EMPTY = "ApplyToEmptyRemovables"; public static final String CMD_ADD_ADV_SEARCH = "AddAdvSearch"; public static final String CMD_SHOW_IN_TREE = "ShowInTree"; public static final String CMD_SHOWVIEWER = "ShowView"; @@ -171,6 +175,9 @@ public final class StructViewer extends JPanel implements ListSelectionListener, private final JMenuItem miToHexInt = createMenuItem(CMD_TOHEXINT, "Edit as hexadecimal number", Icons.ICON_REFRESH_16.getIcon(), this); private final JMenuItem miToFlags = createMenuItem(CMD_TOFLAGS, "Edit as bit field", Icons.ICON_REFRESH_16.getIcon(), this); private final JMenuItem miReset = createMenuItem(CMD_RESET, "Reset field type", Icons.ICON_REFRESH_16.getIcon(), this); + private final JMenuItem miApplyToAllRemovables = createMenuItem(CMD_APPLY_TO_ALL, "Apply value to all removable structures", Icons.ICON_COPY_16.getIcon(), this); + private final JMenuItem miApplyToNonEmptyRemovables = createMenuItem(CMD_APPLY_TO_NON_EMPTY, "Apply value to non-empty removable structures", Icons.ICON_COPY_16.getIcon(), this); + private final JMenuItem miApplyToEmptyRemovables = createMenuItem(CMD_APPLY_TO_EMPTY, "Apply value to empty removable structures", Icons.ICON_COPY_16.getIcon(), this); private final JMenuItem miAddToAdvSearch = createMenuItem(CMD_ADD_ADV_SEARCH, "Add to Advanced Search", Icons.ICON_FIND_16.getIcon(), this); private final JMenuItem miGotoOffset = createMenuItem(CMD_GOTO_OFFSET, "Go to offset", null, this); private final JMenuItem miShowInTree = createMenuItem(CMD_SHOW_IN_TREE, "Show in tree", Icons.ICON_SELECT_IN_TREE_16.getIcon(), this); @@ -299,6 +306,10 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole popupmenu.add(miToString); popupmenu.add(miReset); popupmenu.addSeparator(); + popupmenu.add(miApplyToAllRemovables); + popupmenu.add(miApplyToNonEmptyRemovables); + popupmenu.add(miApplyToEmptyRemovables); + popupmenu.addSeparator(); popupmenu.add(miAddToAdvSearch); popupmenu.add(miGotoOffset); if (struct instanceof DlgResource) { @@ -323,6 +334,9 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole miToResref.setEnabled(false); miToString.setEnabled(false); miReset.setEnabled(false); + miApplyToAllRemovables.setEnabled(false); + miApplyToNonEmptyRemovables.setEnabled(false); + miApplyToEmptyRemovables.setEnabled(false); miAddToAdvSearch.setEnabled(false); miGotoOffset.setEnabled(false); miShowInTree.setEnabled(false); @@ -537,11 +551,11 @@ public void actionPerformed(ActionEvent event) { WindowBlocker.blockWindow(wnd, false); } } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE) == event.getSource()) { - if (ResourceFactory.saveResource((Resource) struct, getTopLevelAncestor())) { + if (ResourceFactory.saveResource((Resource) struct, getTopLevelAncestor()).isTrue()) { struct.setStructChanged(false); } } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE_AS) == event.getSource()) { - if (ResourceFactory.saveResourceAs((Resource) struct, getTopLevelAncestor())) { + if (ResourceFactory.saveResourceAs((Resource) struct, getTopLevelAncestor()).isTrue()) { struct.setStructChanged(false); } } else if (buttonPanel.getControlByType(ButtonPanel.Control.EXPORT_BUTTON) == event.getSource()) { @@ -629,6 +643,12 @@ public void actionPerformed(ActionEvent event) { if (cls != null) { selectFirstEntryOfType(cls); } + } else if (CMD_APPLY_TO_ALL.equals(cmd)) { + applyToAllRemovables((StructEntry) table.getValueAt(min, 1), false, false); + } else if (CMD_APPLY_TO_NON_EMPTY.equals(cmd)) { + applyToAllRemovables((StructEntry) table.getValueAt(min, 1), true, true); + } else if (CMD_APPLY_TO_EMPTY.equals(cmd)) { + applyToAllRemovables((StructEntry) table.getValueAt(min, 1), true, false); } else if (CMD_ADD_ADV_SEARCH.equals(cmd)) { addToAdvancedSearch((StructEntry) table.getValueAt(min, 1)); } else if (CMD_SHOW_IN_TREE.equals(cmd)) { @@ -735,6 +755,9 @@ public void valueChanged(ListSelectionEvent event) { miToResref.setEnabled(false); miToString.setEnabled(false); miReset.setEnabled(false); + miApplyToAllRemovables.setEnabled(false); + miApplyToNonEmptyRemovables.setEnabled(false); + miApplyToEmptyRemovables.setEnabled(false); miAddToAdvSearch.setEnabled(false); miGotoOffset.setEnabled(false); miShowInTree.setEnabled(false); @@ -799,6 +822,12 @@ public void valueChanged(ListSelectionEvent event) { || selected instanceof TextBitmap)); miReset.setEnabled(isDataType && getCachedStructEntry(((Datatype) selected).getOffset()) != null && !(selected instanceof AbstractCode)); + miApplyToAllRemovables.setEnabled(!(selected instanceof AbstractStruct) && struct instanceof AddRemovable && + struct.getParent() != null); + miApplyToNonEmptyRemovables.setEnabled(!(selected instanceof AbstractStruct) && struct instanceof AddRemovable && + struct.getParent() != null); + miApplyToEmptyRemovables.setEnabled(!(selected instanceof AbstractStruct) && struct instanceof AddRemovable && + struct.getParent() != null); miAddToAdvSearch.setEnabled(!(selected instanceof AbstractStruct || selected instanceof Unknown)); miGotoOffset.setEnabled(selected instanceof SectionOffset || selected instanceof SectionCount); final boolean isSpecialDlgTreeItem = (selected instanceof State || selected instanceof Transition); @@ -982,6 +1011,8 @@ public void close() { } tabbedPane = null; } + + closeEditor(); } public StructEntry getSelectedEntry() { @@ -1216,7 +1247,7 @@ private void convertAttribute(int index, JMenuItem menuitem) { if (newentry == null) { newentry = entry; } else { - ((Readable) newentry).read(bb, 0); + newentry.read(bb, 0); } } else { throw new NullPointerException(); @@ -1320,6 +1351,8 @@ private ViewFrame createViewFrame(Component parent, Viewable view) { * @throws NullPointerException if {@code editable} is {@code null} */ private void edit(Editable editable) { + closeEditor(); + // Save for handle UPDATE_VALUE events later this.editable = editable; editpanel.removeAll(); @@ -1343,6 +1376,16 @@ private void edit(Editable editable) { editable.select(); } + private void closeEditor() { + if (this.editable instanceof Closeable) { + try { + ((Closeable)this.editable).close(); + } catch (Exception e) { + Logger.error(e); + } + } + } + /** * Move viewport for this table in such way that specified entry becomes visible and selects it. * @@ -1414,6 +1457,103 @@ private Color getClassColor(Class cls) { return Misc.getDefaultColor("TextField.background", Color.WHITE); } + /** + * Applies the value of the specified {@code StructEntry} instance to all {@link AddRemovable}s of the same type at + * the same level as the current structure. + * + * @param entry The {@link StructEntry} instance to copy + * @param filterEmpty Whether to apply only to empty or zero field data. + * @param invertFilter If {@code filterEmpty} is true then this parameter specifies whether the filter test result + * should be inverted. + */ + private void applyToAllRemovables(StructEntry entry, boolean filterEmpty, boolean invertFilter) { + final AbstractStruct parentStruct = struct.getParent(); + final List removables = (parentStruct != null) ? parentStruct.getFields(struct.getClass()) : null; + if (removables != null && removables.size() > 1) { + final int retVal = JOptionPane.showConfirmDialog(getTopLevelAncestor(), + "Apply field value to " + (removables.size() - 1) + " more structure(s)?", + "Question", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + if (retVal == JOptionPane.YES_OPTION) { + try { + final ByteBuffer bb = StreamUtils.getByteBuffer(entry.getSize()); + try (ByteBufferOutputStream bbos = new ByteBufferOutputStream(bb)) { + entry.write(bbos); + } + bb.position(0); + + final int relOfs = entry.getOffset() - struct.getOffset(); + + int count = 0; + for (final StructEntry se : removables) { + final AbstractStruct as = (AbstractStruct) se; + if (as != struct) { + final StructEntry att = as.getAttribute(as.getOffset() + relOfs, false); + if (att != null) { + if (att.getSize() == entry.getSize() && att.getClass().equals(entry.getClass())) { + // testing whether to apply data + boolean shouldApply = !filterEmpty || isEmptyStructure(att); + if (filterEmpty && invertFilter) { + shouldApply = !shouldApply; + } + if (shouldApply) { + att.read(bb, 0); + as.setStructChanged(true); + as.getParent().fireTableDataChanged(); + count++; + } + } + } + } + } + JOptionPane.showMessageDialog(getTopLevelAncestor(), "Value applied to " + count + " more structure(s)."); + } catch (Exception e) { + Logger.error(e); + JOptionPane.showMessageDialog(getTopLevelAncestor(), "Could not apply value.", "Error", + JOptionPane.ERROR_MESSAGE); + } + } else { + JOptionPane.showMessageDialog(getTopLevelAncestor(), "Cancelled."); + } + } else { + // no more AddRemovables available + JOptionPane.showMessageDialog(getTopLevelAncestor(), "No further structures present.", "Information", + JOptionPane.INFORMATION_MESSAGE); + } + } + + /** + * Tests if the specified structure is considered "empty", i.e. numeric fields are 0, textual fields contain empty + * strings and binary fields contain only zeroed bytes. + * + * @param entry {@link StructEntry} field to test. + * @return {@code true} if the structure is considered empty, {@code false} otherwise. + * @throws Exception if the structure data could not be evaluated. + */ + private boolean isEmptyStructure(StructEntry entry) throws Exception { + boolean retVal = true; + if (entry != null) { + if (entry instanceof IsNumeric) { + retVal = ((IsNumeric) entry).getLongValue() == 0; + } else if (entry instanceof IsTextual) { + retVal = ((IsTextual) entry).getText().isEmpty(); + } else if (entry instanceof IsReference) { + retVal = ((IsReference) entry).getResourceName().isEmpty(); + } else { + final ByteBuffer data = StreamUtils.getByteBuffer(entry.getSize()); + try (ByteBufferOutputStream bbos = new ByteBufferOutputStream(data)) { + entry.write(bbos); + } + final byte[] ar = data.array(); + byte result = 0; + for (int i = 0, size = ar.length; i < size && result == 0; i++) { + result |= ar[i]; + } + retVal = (result == 0); + } + } + return retVal; + } + /** * Creates an Advanced Search filter out of the specified {@code StructEntry} instance and adds it to the Advanced * Search dialog. @@ -1424,10 +1564,13 @@ private void addToAdvancedSearch(StructEntry entry) { } // setting search value - SearchOptions so = null; + SearchOptions so; if (entry instanceof Flag) { so = new SearchOptions(); so.setValueBitfield(((Flag) entry).getValue(), SearchOptions.BitFieldMode.EXACT); + } else if (entry instanceof ProRef || entry instanceof Song2daBitmap) { + so = new SearchOptions(); + so.setValueNumber(((IsNumeric) entry).getValue()); } else if (entry instanceof IsReference) { so = new SearchOptions(); so.setValueResource(((IsReference) entry).getResourceName()); diff --git a/src/org/infinity/gui/TextListPanel.java b/src/org/infinity/gui/TextListPanel.java index 3ac858da0..6f9939828 100644 --- a/src/org/infinity/gui/TextListPanel.java +++ b/src/org/infinity/gui/TextListPanel.java @@ -61,7 +61,7 @@ public class TextListPanel extends JPanel private final JTextField tfield = new JTextField(); private final JToggleButton tbFilter = new JToggleButton(Icons.ICON_FILTER_16.getIcon(), filterEnabled); - private boolean sortValues = true; + private final boolean sortValues; public TextListPanel(List values) { this(values, true, false); @@ -299,7 +299,7 @@ private void ensurePreferredComponentWidth(JComponent c, boolean includeScrollBa cw += c.getInsets().left; cw += c.getInsets().right; if (includeScrollBar) { - int sbWidth = 0; + int sbWidth; try { sbWidth = ((Integer) UIManager.get("ScrollBar.width")); } catch (Exception ex) { diff --git a/src/org/infinity/gui/ViewFrame.java b/src/org/infinity/gui/ViewFrame.java index 54d441593..ba920d679 100644 --- a/src/org/infinity/gui/ViewFrame.java +++ b/src/org/infinity/gui/ViewFrame.java @@ -81,7 +81,7 @@ public void setViewable(Viewable viewable) { this.viewable = viewable; if (viewable instanceof Resource && ((Resource) viewable).getResourceEntry() != null) { ResourceEntry entry = ((Resource) viewable).getResourceEntry(); - // setTitle(entry.toString()); + setTitle(getViewableTitle(viewable)); setIconImage(entry.getIcon().getImage()); final String path; if (entry instanceof BIFFResourceEntry) { diff --git a/src/org/infinity/gui/ViewerUtil.java b/src/org/infinity/gui/ViewerUtil.java index cc288f004..1cdf72b64 100644 --- a/src/org/infinity/gui/ViewerUtil.java +++ b/src/org/infinity/gui/ViewerUtil.java @@ -115,7 +115,7 @@ public static void addLabelFieldPair(JPanel panel, StructEntry entry, GridBagLay return; } JLabel label = new JLabel(entry.getName()); - JComponent text = null; + JComponent text; if (entry instanceof ResourceRef) { text = new LinkButton((ResourceRef) entry, maxLength); } else { @@ -149,13 +149,13 @@ public static void addLabelFieldPair(JPanel panel, String name, String field, Gr GridBagConstraints gbc, boolean endline, int maxLength) { if (name != null) { JLabel label = new JLabel(name); - String s = (field != null) ? field : ""; + String labelText = (field != null) ? field : ""; String help = null; - if (maxLength > 0 && s.length() > maxLength) { - help = s; - s = s.substring(0, maxLength) + "..."; + if (maxLength > 0 && labelText.length() > maxLength) { + help = labelText; + labelText = labelText.substring(0, maxLength) + "..."; } - JComponent text = new JLabel((field != null) ? field : ""); + JComponent text = new JLabel(labelText); if (help != null) { text.setToolTipText(help); } @@ -579,7 +579,7 @@ private ViewerUtil() { * *

*/ - public static interface AttributeEntry extends Function { + public interface AttributeEntry extends Function { } public static final class StructListPanel extends JPanel implements TableModelListener, ActionListener { @@ -704,7 +704,7 @@ public void tableChanged(TableModelEvent event) { * Can be used to extend ListCellRenderer interfaces by a method that returns the textual representation of the * specified cell value. */ - public static interface ListValueRenderer { + public interface ListValueRenderer { /** Returns the textual representation of the specified value. */ String getListValue(Object value); } diff --git a/src/org/infinity/gui/converter/BamFilterBaseOutput.java b/src/org/infinity/gui/converter/BamFilterBaseOutput.java index 74d96c46d..55aa74cc3 100644 --- a/src/org/infinity/gui/converter/BamFilterBaseOutput.java +++ b/src/org/infinity/gui/converter/BamFilterBaseOutput.java @@ -9,6 +9,7 @@ import java.nio.file.Path; import java.util.Arrays; +import org.infinity.exceptions.AbortException; import org.infinity.resource.graphics.DxtEncoder; import org.infinity.resource.graphics.PseudoBamDecoder; import org.infinity.util.Logger; @@ -84,8 +85,10 @@ public static boolean convertBam(ConvertToBam converter, Path outFileName, Pseud DxtEncoder.DxtType dxtType = converter.getDxtType(); int pvrzIndex = converter.getPvrzIndex(); try { - return decoder.exportBamV2(outFileName, dxtType, pvrzIndex, converter.getProgressMonitor(), - converter.getProgressMonitorStage()); + return decoder.exportBamV2(outFileName, dxtType, pvrzIndex, BamOptionsDialog.getOverwritePvrzIndices(), + converter.getProgressMonitor(), converter.getProgressMonitorStage()); + } catch (AbortException e) { + Logger.debug(e); } catch (Exception e) { Logger.error(e); throw e; diff --git a/src/org/infinity/gui/converter/BamFilterColorSwap.java b/src/org/infinity/gui/converter/BamFilterColorSwap.java index d8b8f51af..36e68cb43 100644 --- a/src/org/infinity/gui/converter/BamFilterColorSwap.java +++ b/src/org/infinity/gui/converter/BamFilterColorSwap.java @@ -47,7 +47,7 @@ private enum SwapType { private final String label; private final int[] shift; - private SwapType(String label, int[] shift) { + SwapType(String label, int[] shift) { this.label = label; this.shift = shift; } diff --git a/src/org/infinity/gui/converter/BamFilterFactory.java b/src/org/infinity/gui/converter/BamFilterFactory.java index 483774e0a..53f372eb6 100644 --- a/src/org/infinity/gui/converter/BamFilterFactory.java +++ b/src/org/infinity/gui/converter/BamFilterFactory.java @@ -10,6 +10,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.List; import org.infinity.util.Logger; @@ -146,7 +147,7 @@ private static List scanFilters() { retVal.clear(); } - retVal.sort((a, b) -> a.getName().compareTo(b.getName())); + retVal.sort(Comparator.comparing(FilterInfo::getName)); return retVal; } diff --git a/src/org/infinity/gui/converter/BamFilterOutputGif.java b/src/org/infinity/gui/converter/BamFilterOutputGif.java index f61d8f7ce..6554b6b4a 100644 --- a/src/org/infinity/gui/converter/BamFilterOutputGif.java +++ b/src/org/infinity/gui/converter/BamFilterOutputGif.java @@ -66,10 +66,7 @@ public boolean process(PseudoBamDecoder decoder) throws Exception { @Override public String getConfiguration() { - StringBuilder sb = new StringBuilder(); - sb.append(spinnerFPS.getValue()).append(';'); - sb.append(cbLoopAnim.isSelected()); - return sb.toString(); + return String.valueOf(spinnerFPS.getValue()) + ';' + cbLoopAnim.isSelected(); } @Override diff --git a/src/org/infinity/gui/converter/BamFilterOutputOverlay.java b/src/org/infinity/gui/converter/BamFilterOutputOverlay.java index ea3523dd7..acbf65981 100644 --- a/src/org/infinity/gui/converter/BamFilterOutputOverlay.java +++ b/src/org/infinity/gui/converter/BamFilterOutputOverlay.java @@ -33,17 +33,7 @@ import java.nio.file.FileSystemNotFoundException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.TreeSet; +import java.util.*; import java.util.function.BiFunction; import javax.swing.DefaultListCellRenderer; @@ -83,10 +73,10 @@ import org.infinity.resource.key.BIFFResourceEntry; import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.Logger; import org.infinity.util.Misc; import org.infinity.util.Platform; import org.infinity.util.tuples.Couple; -import org.tinylog.Logger; /** * Output filter: Overlay the current animation with multiple BAM files. @@ -213,7 +203,7 @@ public String getConfiguration() { if (sb.length() > 0) { sb.append(';'); } - sb.append(rp.toString()).append(';').append(mode.ordinal()); + sb.append(rp).append(';').append(mode.ordinal()); } catch (Exception e) { Logger.warn(e, "Invalid resource: " + entry); } @@ -700,7 +690,7 @@ private boolean applyEffect(PseudoBamDecoder decoder) throws Exception { frames[i] = controls[i].cycleGetFrameIndexAbsolute(cycleIdx, frameIdx); } final int newIndex = frameIndexCache.size(); - frameIndexCache.computeIfAbsent(frames, key -> newIndex); + frameIndexCache.putIfAbsent(frames, newIndex); if (frameIndexCache.containsKey(frames)) { cycleFrameIndices[cycleIdx][frameIdx] = frameIndexCache.get(frames); } else { @@ -711,7 +701,7 @@ private boolean applyEffect(PseudoBamDecoder decoder) throws Exception { // reversing mapping direction to get a sorted set of "frame index -> array of source frame indices" pairs // this set should contain no gaps between the frame indices - final TreeSet> frameSet = new TreeSet<>((c1, c2) -> c1.getValue0() - c2.getValue0()); + final TreeSet> frameSet = new TreeSet<>(Comparator.comparingInt(Couple::getValue0)); for (final Map.Entry entry : frameIndexCache.entrySet()) { frameSet.add(Couple.with(entry.getValue(), entry.getKey())); } @@ -795,7 +785,7 @@ private PseudoBamFrameEntry createOverlayFrame(PseudoBamFrameEntry srcFrameEntry return srcFrameEntry; } - PseudoBamFrameEntry retVal = null; + PseudoBamFrameEntry retVal; // preparations final BamDecoder[] decoders = new BamDecoder[model.getRowCount()]; final BamControl[] controls = new BamControl[decoders.length]; @@ -1004,10 +994,9 @@ private static int pixelOpExclusive(int src, int dst) { * palette index 1. Magic color "green" is ignored. * @return {@link PseudoBamDecoder} instance with palette-based frames and the same cycle configuration as the source * decoder. - * @throws Exception if an unrecoverable error occurs. */ private static PseudoBamDecoder convertToPalettedBam(PseudoBamDecoder decoder, boolean useAlpha, - int transparencyThreshold, int... reservedColors) throws Exception { + int transparencyThreshold, int... reservedColors) { boolean isPalette = true; final List framesList = decoder.getFramesList(); for (int i = 0, size = framesList.size(); isPalette && i < size; i++) { @@ -1070,9 +1059,8 @@ private static PseudoBamDecoder convertToPalettedBam(PseudoBamDecoder decoder, b final int srcColorIdx = srcBuf[ofs] & 0xff; final int color = srcColors[(srcColorIdx < srcColors.length) ? srcColorIdx : 0]; if (!PseudoBamDecoder.isTransparentColor(color, transparencyThreshold)) { - final byte colorIdx = colorCache.computeIfAbsent(color, c -> { - return (byte) ColorConvert.getNearestColor(color, newPalette, alphaWeight, ColorConvert.COLOR_DISTANCE_CIE94); - }); + final byte colorIdx = colorCache.computeIfAbsent(color, + c -> (byte) ColorConvert.getNearestColor(color, newPalette, alphaWeight, ColorConvert.COLOR_DISTANCE_CIE94)); dstBuf[ofs] = colorIdx; } } @@ -1082,9 +1070,8 @@ private static PseudoBamDecoder convertToPalettedBam(PseudoBamDecoder decoder, b for (int ofs = 0; ofs < srcBuf.length; ofs++) { final int color = srcBuf[ofs]; if (!PseudoBamDecoder.isTransparentColor(color, transparencyThreshold)) { - final byte colorIdx = colorCache.computeIfAbsent(color, c -> { - return (byte) ColorConvert.getNearestColor(color, newPalette, alphaWeight, ColorConvert.COLOR_DISTANCE_CIE94); - }); + final byte colorIdx = colorCache.computeIfAbsent(color, + c -> (byte) ColorConvert.getNearestColor(color, newPalette, alphaWeight, ColorConvert.COLOR_DISTANCE_CIE94)); dstBuf[ofs] = colorIdx; } } @@ -1113,7 +1100,7 @@ private static PseudoBamDecoder convertToPalettedBam(PseudoBamDecoder decoder, b *

Pixel format is {@code 0xAARRGGBB}.

*/ @FunctionalInterface - public static interface OverlayFunc extends BiFunction { + public interface OverlayFunc extends BiFunction { } /** diff --git a/src/org/infinity/gui/converter/BamFilterOutputSplitted.java b/src/org/infinity/gui/converter/BamFilterOutputSplitted.java index ca6969917..eed7c070c 100644 --- a/src/org/infinity/gui/converter/BamFilterOutputSplitted.java +++ b/src/org/infinity/gui/converter/BamFilterOutputSplitted.java @@ -338,7 +338,7 @@ private boolean applyEffect(PseudoBamDecoder decoder) throws Exception { List> listSegments = new ArrayList<>(decoder.frameCount()); int segmentCount = segmentsX * segmentsY; for (int frameIdx = 0; frameIdx < decoder.frameCount(); frameIdx++) { - listSegments.add(new ArrayList(segmentCount)); + listSegments.add(new ArrayList<>(segmentCount)); final double fract = 0.499999; // fractions of .5 or less will be rounded down! int curHeight = decoder.getFrameInfo(frameIdx).getHeight(), y = 0; for (int curSegY = segmentsY; curSegY > 0; curSegY--) { @@ -413,7 +413,7 @@ private PseudoBamFrameEntry createFrameSegment(PseudoBamFrameEntry entry, Rectan if (entry != null && rect != null) { // preparations BufferedImage srcImage = entry.getFrame(); - BufferedImage dstImage = null; + BufferedImage dstImage; byte[] srcB = null, dstB = null; int[] srcI = null, dstI = null; Dimension dstDim = new Dimension(rect.width, rect.height); @@ -447,7 +447,7 @@ private PseudoBamFrameEntry createFrameSegment(PseudoBamFrameEntry entry, Rectan System.arraycopy(srcI, srcOfs, dstI, dstOfs, rect.width); } } - } else { + } else if (dstB != null && dstB.length > 0) { dstB[0] = 0; } diff --git a/src/org/infinity/gui/converter/BamFilterTransformResize.java b/src/org/infinity/gui/converter/BamFilterTransformResize.java index 09dfc46bc..f8faf9a5e 100644 --- a/src/org/infinity/gui/converter/BamFilterTransformResize.java +++ b/src/org/infinity/gui/converter/BamFilterTransformResize.java @@ -54,7 +54,7 @@ private enum ScalingType { private final String label; - private ScalingType(String label) { + ScalingType(String label) { this.label = label; } @@ -370,18 +370,6 @@ private void updateStatus() { spinnerFactorY.setEnabled(individualEnabled); break; case Bilinear: - taInfo.setText(String.format(fmtSupport1, ConvertToBam.BAM_VERSION_ITEMS[ConvertToBam.VERSION_BAMV2])); - setFactor(spinnerFactor, factor, 0.01, 10.0, 0.05); - setFactor(spinnerFactorX, factorX, 0.01, 10.0, 0.05); - setFactor(spinnerFactorY, factorY, 0.01, 10.0, 0.05); - rbScaleIndividually.setEnabled(true); - lFactor.setEnabled(uniformEnabled); - lFactorX.setEnabled(individualEnabled); - lFactorY.setEnabled(individualEnabled); - spinnerFactor.setEnabled(uniformEnabled); - spinnerFactorX.setEnabled(individualEnabled); - spinnerFactorY.setEnabled(individualEnabled); - break; case Bicubic: taInfo.setText(String.format(fmtSupport1, ConvertToBam.BAM_VERSION_ITEMS[ConvertToBam.VERSION_BAMV2])); setFactor(spinnerFactor, factor, 0.01, 10.0, 0.05); diff --git a/src/org/infinity/gui/converter/BamFilterTransformRotate.java b/src/org/infinity/gui/converter/BamFilterTransformRotate.java index 079b05882..d341ac7b8 100644 --- a/src/org/infinity/gui/converter/BamFilterTransformRotate.java +++ b/src/org/infinity/gui/converter/BamFilterTransformRotate.java @@ -42,7 +42,7 @@ private enum Angle { private final String label; - private Angle(String label) { + Angle(String label) { this.label = label; } @@ -204,7 +204,7 @@ private PseudoBamFrameEntry applyEffect(PseudoBamFrameEntry entry) { if (entry != null && entry.getFrame() != null) { int width = entry.getFrame().getWidth(); int height = entry.getFrame().getHeight(); - BufferedImage dstImage = null; + BufferedImage dstImage; int newWidth, newHeight; switch ((Angle) Objects.requireNonNull(cbAngle.getSelectedItem())) { case Angle90: diff --git a/src/org/infinity/gui/converter/BamFilterTransformTrim.java b/src/org/infinity/gui/converter/BamFilterTransformTrim.java index e9a6a2143..ac063e507 100644 --- a/src/org/infinity/gui/converter/BamFilterTransformTrim.java +++ b/src/org/infinity/gui/converter/BamFilterTransformTrim.java @@ -45,7 +45,7 @@ private enum Edge { private final String label; - private Edge(String label) { + Edge(String label) { this.label = label; } @@ -277,7 +277,7 @@ private PseudoBamFrameEntry applyEffect(PseudoBamFrameEntry entry) { if (entry != null && entry.getFrame() != null) { int width = entry.getFrame().getWidth(); int height = entry.getFrame().getHeight(); - BufferedImage dstImage = null; + BufferedImage dstImage; int newWidth, newHeight; byte[] srcB = null, dstB = null; int[] srcI = null, dstI = null; diff --git a/src/org/infinity/gui/converter/BamOptionsDialog.java b/src/org/infinity/gui/converter/BamOptionsDialog.java index ed5eccfc2..6cc0c7d1c 100644 --- a/src/org/infinity/gui/converter/BamOptionsDialog.java +++ b/src/org/infinity/gui/converter/BamOptionsDialog.java @@ -63,34 +63,37 @@ class BamOptionsDialog extends JDialog implements ActionListener, FocusListener, private static final String PREFS_COMPRESSBAM = "BCCompressBam"; private static final String PREFS_COMPRESSTYPE = "BCCompressionType"; private static final String PREFS_PVRZINDEX = "BCPvrzIndex"; + private static final String PREFS_OVERWRITE_PVRZ_INDICES = "BCOverwritePvrzIndices"; private static final String PREFS_RECENT_SESSIONS = "RecentSessions"; // Default settings - private static final int DEFAULT_BAM_VERSION = ConvertToBam.VERSION_BAMV1; - private static final String DEFAULT_PATH = ""; - private static final boolean DEFAULT_AUTO_CLEAR = true; - private static final boolean DEFAULT_CLOSE_ON_EXIT = false; - private static final int DEFAULT_TRANSPARENCY_THRESHOLD = 5; // in percent - private static final int DEFAULT_USE_ALPHA = ConvertToBam.ALPHA_AUTO; - private static final String DEFAULT_SORT_PALETTE = ColorConvert.SortType.None.toString(); - private static final boolean DEFAULT_COMPRESS_BAM = false; - private static final int DEFAULT_COMPRESSION_TYPE = ConvertToBam.COMPRESSION_AUTO; - private static final int DEFAULT_PVRZ_INDEX = 1000; - private static final int DEFAULT_RECENT_SESSIONS_MAX = 10; + private static final int DEFAULT_BAM_VERSION = ConvertToBam.VERSION_BAMV1; + private static final String DEFAULT_PATH = ""; + private static final boolean DEFAULT_AUTO_CLEAR = true; + private static final boolean DEFAULT_CLOSE_ON_EXIT = false; + private static final int DEFAULT_TRANSPARENCY_THRESHOLD = 5; // in percent + private static final int DEFAULT_USE_ALPHA = ConvertToBam.ALPHA_AUTO; + private static final String DEFAULT_SORT_PALETTE = ColorConvert.SortType.None.toString(); + private static final boolean DEFAULT_COMPRESS_BAM = false; + private static final int DEFAULT_COMPRESSION_TYPE = ConvertToBam.COMPRESSION_AUTO; + private static final int DEFAULT_PVRZ_INDEX = 1000; + private static final boolean DEFAULT_OVERWRITE_PVRZ_INDICES = false; + private static final int DEFAULT_RECENT_SESSIONS_MAX = 10; // Current settings private static final List recentSessions = new ArrayList<>(); - private static boolean settingsLoaded = false; - private static int bamVersion = DEFAULT_BAM_VERSION; - private static String path = DEFAULT_PATH; - private static boolean autoClear = DEFAULT_AUTO_CLEAR; - private static boolean closeOnExit = DEFAULT_CLOSE_ON_EXIT; - private static int transparencyThreshold = DEFAULT_TRANSPARENCY_THRESHOLD; - private static int useAlpha = DEFAULT_USE_ALPHA; - private static String sortPalette = DEFAULT_SORT_PALETTE; - private static boolean compressBam = DEFAULT_COMPRESS_BAM; - private static int compressionType = DEFAULT_COMPRESSION_TYPE; - private static int pvrzIndex = DEFAULT_PVRZ_INDEX; + private static boolean settingsLoaded = false; + private static int bamVersion = DEFAULT_BAM_VERSION; + private static String path = DEFAULT_PATH; + private static boolean autoClear = DEFAULT_AUTO_CLEAR; + private static boolean closeOnExit = DEFAULT_CLOSE_ON_EXIT; + private static int transparencyThreshold = DEFAULT_TRANSPARENCY_THRESHOLD; + private static int useAlpha = DEFAULT_USE_ALPHA; + private static String sortPalette = DEFAULT_SORT_PALETTE; + private static boolean compressBam = DEFAULT_COMPRESS_BAM; + private static int compressionType = DEFAULT_COMPRESSION_TYPE; + private static int pvrzIndex = DEFAULT_PVRZ_INDEX; + private static boolean overwritePvrzIndices = DEFAULT_OVERWRITE_PVRZ_INDICES; private final ConvertToBam converter; @@ -105,6 +108,7 @@ class BamOptionsDialog extends JDialog implements ActionListener, FocusListener, private JCheckBox cbCloseOnExit; private JCheckBox cbAutoClear; private JCheckBox cbCompressBam; + private JCheckBox cbOverwritePvrzIndices; private JSpinner sTransparency; private JSpinner sPvrzIndex; private JTextField tfPath; @@ -127,6 +131,7 @@ public static void loadSettings(boolean force) { compressBam = prefs.getBoolean(PREFS_COMPRESSBAM, DEFAULT_COMPRESS_BAM); compressionType = prefs.getInt(PREFS_COMPRESSTYPE, DEFAULT_COMPRESSION_TYPE); pvrzIndex = prefs.getInt(PREFS_PVRZINDEX, DEFAULT_PVRZ_INDEX); + overwritePvrzIndices = prefs.getBoolean(PREFS_OVERWRITE_PVRZ_INDICES, DEFAULT_OVERWRITE_PVRZ_INDICES); loadRecentSessions(prefs.node(PREFS_RECENT_SESSIONS)); validateSettings(); @@ -161,6 +166,7 @@ public static void saveSettings() { prefs.putBoolean(PREFS_COMPRESSBAM, compressBam); prefs.putInt(PREFS_COMPRESSTYPE, compressionType); prefs.putInt(PREFS_PVRZINDEX, pvrzIndex); + prefs.putBoolean(PREFS_OVERWRITE_PVRZ_INDICES, overwritePvrzIndices); } // Makes sure that all settings are valid. @@ -246,6 +252,11 @@ public static int getPvrzIndex() { return pvrzIndex; } + /** Returns whether to overwrite PVRZ indices of existing files in the target folder. */ + public static boolean getOverwritePvrzIndices() { + return overwritePvrzIndices; + } + /** Returns list of recently accessed session paths. */ public static List getRecentSessions() { return recentSessions; @@ -481,6 +492,7 @@ private void init() { cbCompressionType.setSelectedIndex(getCompressionType()); model = new SpinnerNumberModel(getPvrzIndex(), 0, 99999, 1); sPvrzIndex = new JSpinner(model); + cbOverwritePvrzIndices = new JCheckBox("Overwrite existing PVRZ files", getOverwritePvrzIndices()); JPanel pBamV2 = new JPanel(new GridBagLayout()); pBamV2.setBorder(BorderFactory.createTitledBorder("PVRZ-based BAM ")); c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, @@ -495,7 +507,10 @@ private void init() { c = ViewerUtil.setGBC(c, 1, 1, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(4, 4, 4, 4), 0, 0); pBamV2.add(sPvrzIndex, c); - c = ViewerUtil.setGBC(c, 0, 2, 2, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, + c = ViewerUtil.setGBC(c, 0, 2, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, + new Insets(4, 4, 0, 0), 0, 0); + pBamV2.add(cbOverwritePvrzIndices, c); + c = ViewerUtil.setGBC(c, 0, 3, 2, 1, 1.0, 1.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0); pBamV2.add(new JPanel(), c); // filler component @@ -565,6 +580,7 @@ private void setDefaults() { cbCompressBam.setSelected(DEFAULT_COMPRESS_BAM); cbCompressionType.setSelectedIndex(DEFAULT_COMPRESSION_TYPE); sPvrzIndex.setValue(DEFAULT_PVRZ_INDEX); + cbOverwritePvrzIndices.setSelected(DEFAULT_OVERWRITE_PVRZ_INDICES); } // Fetches the values from the dialog controls @@ -579,6 +595,7 @@ private void updateSettings() { compressBam = cbCompressBam.isSelected(); compressionType = cbCompressionType.getSelectedIndex(); pvrzIndex = (Integer) sPvrzIndex.getValue(); + overwritePvrzIndices = cbOverwritePvrzIndices.isSelected(); validateSettings(); // transparency options may have changed: force palette generation diff --git a/src/org/infinity/gui/converter/ConvertToBam.java b/src/org/infinity/gui/converter/ConvertToBam.java index 1b448d6a5..fc8b8cf9e 100644 --- a/src/org/infinity/gui/converter/ConvertToBam.java +++ b/src/org/infinity/gui/converter/ConvertToBam.java @@ -572,7 +572,7 @@ public void actionPerformed(ActionEvent event) { } else if (event.getSource() == bConvert) { if (workerConvert == null) { final String msg = "BAM output file already exists. Overwrite?"; - Path file = null; + Path file; do { file = setBamOutput(); if (file != null) { @@ -1004,6 +1004,18 @@ public void keyPressed(KeyEvent e) { // List is set to single selection mode doWithSelectedListItems(listFilters, list -> filterRemove(), true, "Remove selected filter?", null); } + } else if (e.getSource() == tfFrameCenterX) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + // Accept current input value + framesValidateCenterValue(tfFrameCenterX); + tfFrameCenterY.requestFocusInWindow(); + } + } else if (e.getSource() == tfFrameCenterY) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + // Accept current input value + framesValidateCenterValue(tfFrameCenterY); + cbCompressFrame.requestFocusInWindow(); + } } } @@ -1226,9 +1238,11 @@ private JPanel createFramesTab() { tfFrameCenterX = new JTextField("0", 6); tfFrameCenterX.setToolTipText(tip); tfFrameCenterX.addFocusListener(this); + tfFrameCenterX.addKeyListener(this); tfFrameCenterY = new JTextField("0", 6); tfFrameCenterY.setToolTipText(tip); tfFrameCenterY.addFocusListener(this); + tfFrameCenterY.addKeyListener(this); cbCompressFrame = new JCheckBox("Compress frame"); cbCompressFrame.setToolTipText("Selecting this option activates RLE compression for the current frame(s)."); cbCompressFrame.addActionListener(this); @@ -2072,7 +2086,7 @@ private void updateFrameInfo(int[] indices) { } // setting frame info - String title = null; + String title; if (indices.length > 1) { title = String.format("%d frames selected ", indices.length); } else { @@ -2115,7 +2129,7 @@ private void updateQuickPreview(RenderCanvas target, int[] indices, boolean show if (indices != null && indices.length == 1 && indices[0] >= 0 && indices[0] < modelFrames.getSize()) { // drawing frame - int left = 0, top = 0; + int left, top; float ratio = 1.0f; int frameIdx = indices[0]; @@ -2540,7 +2554,7 @@ private int framesAddBamFrame(int listIndex, BamDecoder decoder, BamDecoder.BamC boolean isCompressed; int rleIndex; BamDecoder.FrameEntry fe = decoder.getFrameInfo(frameIndex); - BufferedImage image = null; + BufferedImage image; if (cm != null) { if (fe.getWidth() > 0 && fe.getHeight() > 0) { image = new BufferedImage(fe.getWidth(), fe.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, cm); @@ -2635,7 +2649,7 @@ private boolean framesAddImage(int listIndex, ResourceEntry entry, int frameInde // transparency detection for paletted images if (image.getType() == BufferedImage.TYPE_BYTE_INDEXED) { - boolean hasAlpha = ((IndexColorModel) image.getColorModel()).hasAlpha(); + boolean hasAlpha = image.getColorModel().hasAlpha(); int[] cmap = new int[256]; int transIndex = -1; IndexColorModel srcCm = (IndexColorModel) image.getColorModel(); @@ -3758,7 +3772,7 @@ private void doWithSelectedListItems(JList list, Consumer> opera boolean accepted = !confirm; if (confirm) { String message = "Remove selected item(s)?"; - String fmt = null; + String fmt; if (selectedCount == 1) { fmt = (msgPromptSingle != null) ? msgPromptSingle : msgPromptMultiple; } else { @@ -4133,7 +4147,7 @@ private void updateFinalBamDecoder(int bamVersion) throws Exception { // processing frames IndexColorModel cm = new IndexColorModel(8, 256, palette, 0, getUseAlpha(), transIndex, DataBuffer.TYPE_BYTE); for (int i = 0; i < srcListFrames.size(); i++) { - PseudoBamFrameEntry srcEntry = srcListFrames.get(i); + final PseudoBamFrameEntry srcEntry = srcListFrames.get(i); BufferedImage srcImage = ColorConvert.toBufferedImage(srcEntry.getFrame(), true, true); int[] srcBuf = ((DataBufferInt) srcImage.getRaster().getDataBuffer()).getData(); BufferedImage dstImage = new BufferedImage(srcEntry.getWidth(), srcEntry.getHeight(), @@ -5306,7 +5320,7 @@ private boolean applyData(boolean silent) { } private boolean applyFramesData(boolean silent) throws Exception { - /** Storage for ResourceEntry and frame index for convenience. */ + /* Storage for ResourceEntry and frame index for convenience. */ class SourceFrame { public final ResourceEntry entry; public final int index; @@ -5317,7 +5331,7 @@ public SourceFrame(ResourceEntry entry, int index) { } } - /** Primarily used for caching BAM decoder instances. */ + /* Primarily used for caching BAM decoder instances. */ class SourceData { public final boolean isBam; // bam-specific @@ -5603,8 +5617,8 @@ private boolean saveData(Path outFile, boolean silent) { PseudoBamFrameEntry entry = bam.modelFrames.getElementAt(i); String path = entry.getOption(BAM_FRAME_OPTION_PATH).toString(); int index = ((Number) entry.getOption(BAM_FRAME_OPTION_SOURCE_INDEX)).intValue(); - sb.append(Integer.toString(i)).append('=').append(path); - sb.append(SEPARATOR_FRAME).append(Integer.toString(index)); + sb.append(i).append('=').append(path); + sb.append(SEPARATOR_FRAME).append(index); sb.append(Misc.LINE_SEPARATOR); } sb.append(Misc.LINE_SEPARATOR); @@ -5615,7 +5629,7 @@ private boolean saveData(Path outFile, boolean silent) { sb.append('[').append(SECTION_CENTER).append(']').append(Misc.LINE_SEPARATOR); for (int i = 0; i < bam.modelFrames.getSize(); i++) { PseudoBamFrameEntry entry = bam.modelFrames.getElementAt(i); - sb.append(Integer.toString(i)).append('='); + sb.append(i).append('='); sb.append(entry.getCenterX()).append(SEPARATOR_NUMBER).append(entry.getCenterY()); sb.append(Misc.LINE_SEPARATOR); } @@ -5627,7 +5641,7 @@ private boolean saveData(Path outFile, boolean silent) { sb.append('[').append(SECTION_CYCLES).append(']').append(Misc.LINE_SEPARATOR); for (int i = 0; i < bam.modelCycles.getSize(); i++) { PseudoBamCycleEntry entry = bam.modelCycles.getElementAt(i); - sb.append(Integer.toString(i)).append('='); + sb.append(i).append('='); for (int j = 0; j < entry.size(); j++) { if (j > 0) { sb.append(SEPARATOR_NUMBER); diff --git a/src/org/infinity/gui/converter/ConvertToBmp.java b/src/org/infinity/gui/converter/ConvertToBmp.java index da249156e..d7b84f901 100644 --- a/src/org/infinity/gui/converter/ConvertToBmp.java +++ b/src/org/infinity/gui/converter/ConvertToBmp.java @@ -653,7 +653,7 @@ private List convert() { if (failed > 0) { result.add(null); } - String msg = null; + String msg; if (failed + skipped == 1) { msg = "1 input file has been skipped."; } else { diff --git a/src/org/infinity/gui/converter/ConvertToPvrz.java b/src/org/infinity/gui/converter/ConvertToPvrz.java index 48711f73d..32e4b5ae8 100644 --- a/src/org/infinity/gui/converter/ConvertToPvrz.java +++ b/src/org/infinity/gui/converter/ConvertToPvrz.java @@ -579,7 +579,7 @@ private List convert() { String inFileName = inFile.getFileName().toString(); // generating output filename - String outFileName = null; + String outFileName; int n = inFileName.lastIndexOf('.'); if (n > 0) { outFileName = inFileName.substring(0, n) + ".PVRZ"; @@ -606,7 +606,7 @@ private List convert() { // loading source image data if (isPVR) { // handling PVR files - ByteBuffer bb = null; + ByteBuffer bb; try (SeekableByteChannel ch = Files.newByteChannel(inFile, StandardOpenOption.READ)) { bb = StreamUtils.getByteBuffer((int) ch.size()); ch.read(bb); @@ -664,8 +664,8 @@ private List convert() { } // preparing output - DxtEncoder.DxtType dxtType = null; - byte[] header = null; + DxtEncoder.DxtType dxtType; + byte[] header; switch (dxt) { case 3: dxtType = DxtEncoder.DxtType.DXT3; diff --git a/src/org/infinity/gui/converter/ConvertToTis.java b/src/org/infinity/gui/converter/ConvertToTis.java index c718ff9f7..133a1709a 100644 --- a/src/org/infinity/gui/converter/ConvertToTis.java +++ b/src/org/infinity/gui/converter/ConvertToTis.java @@ -1092,7 +1092,7 @@ public static class TileEntry { public int x; public int y; - public static Comparator CompareByIndex = (te1, te2) -> te1.tileIndex - te2.tileIndex; + public static Comparator CompareByIndex = Comparator.comparingInt(te -> te.tileIndex); public TileEntry(int index, int page, int x, int y) { this.tileIndex = index; diff --git a/src/org/infinity/gui/layeritem/AbstractLayerItem.java b/src/org/infinity/gui/layeritem/AbstractLayerItem.java index 7abf141e7..6de6b93ae 100644 --- a/src/org/infinity/gui/layeritem/AbstractLayerItem.java +++ b/src/org/infinity/gui/layeritem/AbstractLayerItem.java @@ -94,7 +94,7 @@ public void addLayerItemListener(LayerItemListener l) { } public LayerItemListener[] getLayerItemListeners() { - return itemStateListener.toArray(new LayerItemListener[itemStateListener.size()]); + return itemStateListener.toArray(new LayerItemListener[0]); } public void removeLayerItemListener(LayerItemListener l) { diff --git a/src/org/infinity/gui/layeritem/BasicAnimationProvider.java b/src/org/infinity/gui/layeritem/BasicAnimationProvider.java index c63b545bd..29347e6e1 100644 --- a/src/org/infinity/gui/layeritem/BasicAnimationProvider.java +++ b/src/org/infinity/gui/layeritem/BasicAnimationProvider.java @@ -15,27 +15,27 @@ public interface BasicAnimationProvider { * Returns the graphical data of the current frame. (Note: Subclasses have to make sure that this method always * returns a valid and up to date graphics object.) */ - public Image getImage(); + Image getImage(); /** * Advances the animation by one frame. Does not wrap around after reaching the last frame. * * @return Whether the frame has been advanced successfully. */ - public boolean advanceFrame(); + boolean advanceFrame(); /** * Selects the first frame of the animation. */ - public void resetFrame(); + void resetFrame(); /** * Returns whether the animation should be played back continuously. */ - public boolean isLooping(); + boolean isLooping(); /** * Returns the animation origin relative to the top-left corner of the image. */ - public Point getLocationOffset(); + Point getLocationOffset(); } diff --git a/src/org/infinity/gui/layeritem/LayerItemListener.java b/src/org/infinity/gui/layeritem/LayerItemListener.java index d6ab4fffc..e4af8b4a1 100644 --- a/src/org/infinity/gui/layeritem/LayerItemListener.java +++ b/src/org/infinity/gui/layeritem/LayerItemListener.java @@ -10,5 +10,5 @@ * Used in AbstractLayerItem */ public interface LayerItemListener extends EventListener { - public void layerItemChanged(LayerItemEvent e); + void layerItemChanged(LayerItemEvent e); } diff --git a/src/org/infinity/gui/menu/Bookmark.java b/src/org/infinity/gui/menu/Bookmark.java index 0ca844ce2..d1cc32575 100644 --- a/src/org/infinity/gui/menu/Bookmark.java +++ b/src/org/infinity/gui/menu/Bookmark.java @@ -122,7 +122,7 @@ public List getBinaryPaths(Platform.OS os) { if (os == null) { os = Platform.OS.getCurrentOS(); } - return Collections.unmodifiableList(binPaths.getOrDefault(os, new ArrayList(1))); + return Collections.unmodifiableList(binPaths.getOrDefault(os, new ArrayList<>(1))); } /** diff --git a/src/org/infinity/gui/menu/BrowserMenuBar.java b/src/org/infinity/gui/menu/BrowserMenuBar.java index 689536ffa..3393076cc 100644 --- a/src/org/infinity/gui/menu/BrowserMenuBar.java +++ b/src/org/infinity/gui/menu/BrowserMenuBar.java @@ -173,6 +173,7 @@ public HelpMenu getHelpMenu() { public void gameLoaded(Profile.Game oldGame, String oldFile) { gameMenu.gameLoaded(oldGame, oldFile); fileMenu.gameLoaded(); + editMenu.gameLoaded(); searchMenu.gameLoaded(); } diff --git a/src/org/infinity/gui/menu/EditMenu.java b/src/org/infinity/gui/menu/EditMenu.java index 5309abb1e..ba455b8fe 100644 --- a/src/org/infinity/gui/menu/EditMenu.java +++ b/src/org/infinity/gui/menu/EditMenu.java @@ -18,6 +18,7 @@ import org.infinity.gui.StringEditor; import org.infinity.icon.Icons; import org.infinity.resource.Profile; +import org.infinity.util.StringTable; /** * Handles Edit menu items for the {@link BrowserMenuBar}. @@ -46,6 +47,11 @@ public EditMenu(BrowserMenuBar parent) { add(editBIFF); } + public void gameLoaded() { + // String table may not be available + editString.setEnabled(StringTable.isAvailable()); + } + @Override public BrowserMenuBar getMenuBar() { return menuBar; diff --git a/src/org/infinity/gui/menu/HelpMenu.java b/src/org/infinity/gui/menu/HelpMenu.java index 083bf0063..2a278b1f4 100644 --- a/src/org/infinity/gui/menu/HelpMenu.java +++ b/src/org/infinity/gui/menu/HelpMenu.java @@ -197,7 +197,7 @@ public void actionPerformed(ActionEvent event) { } else if (event.getSource() == helpUpdateSettings) { UpdaterSettings.showDialog(NearInfinity.getInstance()); } else if (event.getSource() == helpUpdateCheck) { - UpdateInfo info = null; + UpdateInfo info; try { WindowBlocker.blockWindow(NearInfinity.getInstance(), true); info = Updater.getInstance().loadUpdateInfo(); diff --git a/src/org/infinity/gui/menu/OptionsMenuItem.java b/src/org/infinity/gui/menu/OptionsMenuItem.java index 559530130..9245876b3 100644 --- a/src/org/infinity/gui/menu/OptionsMenuItem.java +++ b/src/org/infinity/gui/menu/OptionsMenuItem.java @@ -68,7 +68,7 @@ public enum AutoAlign2da { private final String label; - private AutoAlign2da(String label) { + AutoAlign2da(String label) { this.label = label; } @@ -167,6 +167,7 @@ public String toString() { public static final String OPTION_SHOWMEMSTATUS = "ShowMemStatus"; public static final String OPTION_OPENBOOKMARKSPROMPT = "OpenBookmarksPrompt"; public static final String OPTION_REMEMBER_CHILDFRAME_RECT = "RememberChildFrameRect"; + public static final String OPTION_SHOW_CREATURES_ON_PANEL = "ShowCreaturesOnPanel"; public static final String OPTION_AUTOCHECK_BCS = "AutocheckBCS"; public static final String OPTION_AUTOGEN_BCS_COMMENTS = "AutogenBCSComments"; @@ -224,8 +225,10 @@ public String toString() { /** This preferences key can be used internally to reset incorrectly set default values after a public release. */ public static final String OPTION_OPTION_FIXED = "OptionFixedInternal"; - /** Mask used for one-time resets of options (kept track of in OPTION_OPTION_FIXED). */ - /** Bit for incorrect BCS Auto Indent default: {@code false} -> {@code true}. */ + /** + * Mask used for one-time resets of options (kept track of in OPTION_OPTION_FIXED). + *

Bit for incorrect BCS Auto Indent default: {@code false} -> {@code true}.

+ */ @Deprecated public static final int MASK_OPTION_FIXED_AUTO_INDENT = 0x00000001; @@ -410,9 +413,9 @@ public boolean showPreferencesDialog(Window owner) { } /** Attempts to determine the correct charset for the current game. */ - public String charsetName(String charset, boolean detect) { + public String charsetName(String charset) { if (DEFAULT_CHARSET.equalsIgnoreCase(charset)) { - charset = CharsetDetector.guessCharset(detect); + charset = Profile.getDefaultCharset().name(); } else { charset = CharsetDetector.setCharset(charset); } @@ -532,6 +535,11 @@ public boolean rememberChildFrameRect() { return AppOption.REMEMBER_CHILD_FRAME_RECT.getBoolValue(); } + /** Returns whether the main main panel should be progressively populated with creatures from the game. */ + public boolean showCreaturesOnPanel() { + return AppOption.SHOW_CREATURES_ON_PANEL.getBoolValue(); + } + /** Returns whether offset column is shown for structured resources. */ public boolean showTableOffsets() { return AppOption.TABLE_SHOW_OFFSETS.getBoolValue(); @@ -856,7 +864,7 @@ public boolean isCharsetAvailable(String charset) { /** Returns the character encoding of the string table. */ public String getSelectedCharset() { - return charsetName(AppOption.TLK_CHARSET_TYPE.getStringValue(), true); + return charsetName(AppOption.TLK_CHARSET_TYPE.getStringValue()); } /** Returns the currently selected game language. Returns empty string on autodetect. */ @@ -941,7 +949,7 @@ private void applyChanges(Collection options) { final String csName = option.getStringValue(); if (csName != null) { CharsetDetector.clearCache(); - StringTable.setCharset(charsetName(csName, true)); + StringTable.setCharset(charsetName(csName)); messages.add("TLK Character Encoding: " + csName); // enforce re-reading strings refresh = true; diff --git a/src/org/infinity/gui/menu/OverrideMode.java b/src/org/infinity/gui/menu/OverrideMode.java index b3bdf9ffd..82eb59432 100644 --- a/src/org/infinity/gui/menu/OverrideMode.java +++ b/src/org/infinity/gui/menu/OverrideMode.java @@ -23,7 +23,7 @@ public enum OverrideMode { private final String title; - private OverrideMode(String title) { + OverrideMode(String title) { this.title = title; } diff --git a/src/org/infinity/gui/menu/ResRefMode.java b/src/org/infinity/gui/menu/ResRefMode.java index 3ee026ab8..4268175cb 100644 --- a/src/org/infinity/gui/menu/ResRefMode.java +++ b/src/org/infinity/gui/menu/ResRefMode.java @@ -36,7 +36,7 @@ public String format(ResourceEntry entry) { private final int keyCode; private final String title; - private ResRefMode(int keyCode, String title) { + ResRefMode(int keyCode, String title) { this.keyCode = keyCode; this.title = title; } diff --git a/src/org/infinity/gui/menu/SearchMenu.java b/src/org/infinity/gui/menu/SearchMenu.java index ba2e73b3c..17e032d3b 100644 --- a/src/org/infinity/gui/menu/SearchMenu.java +++ b/src/org/infinity/gui/menu/SearchMenu.java @@ -22,6 +22,7 @@ import org.infinity.search.SearchResource; import org.infinity.search.TextResourceSearcher; import org.infinity.search.advanced.AdvancedSearch; +import org.infinity.util.StringTable; /** * Handles Search menu items for the {@link BrowserMenuBar}. @@ -85,12 +86,15 @@ public void gameLoaded() { if (textSearchMenu.getMenuComponent(i) instanceof JMenuItem) { JMenuItem mi = (JMenuItem) textSearchMenu.getMenuComponent(i); if ("INI".equals(mi.getText())) { - mi.setEnabled((Boolean) Profile.getProperty(Profile.Key.IS_SUPPORTED_INI)); + mi.setEnabled(Profile.getProperty(Profile.Key.IS_SUPPORTED_INI)); } else if ("LUA".equals(mi.getText())) { - mi.setEnabled((Boolean) Profile.getProperty(Profile.Key.IS_SUPPORTED_LUA)); + mi.setEnabled(Profile.getProperty(Profile.Key.IS_SUPPORTED_LUA)); } } } + + // String table may not be available + searchString.setEnabled(StringTable.isAvailable()); } @Override diff --git a/src/org/infinity/gui/menu/ToolsMenu.java b/src/org/infinity/gui/menu/ToolsMenu.java index 17dda2f39..3286325cf 100644 --- a/src/org/infinity/gui/menu/ToolsMenu.java +++ b/src/org/infinity/gui/menu/ToolsMenu.java @@ -37,7 +37,7 @@ import org.infinity.gui.ClipboardViewer; import org.infinity.gui.DebugConsole; import org.infinity.gui.IdsBrowser; -import org.infinity.gui.InfinityAmp; +import org.infinity.gui.InfinityAmpPlus; import org.infinity.gui.converter.ConvertToBam; import org.infinity.gui.converter.ConvertToBmp; import org.infinity.gui.converter.ConvertToMos; @@ -103,8 +103,8 @@ public ToolsMenu(BrowserMenuBar parent) { Icons.ICON_CRE_VIEWER_24.getIcon(), -1, this); add(toolCreatureBrowser); - toolInfinityAmp = BrowserMenuBar.makeMenuItem("InfinityAmp", KeyEvent.VK_I, Icons.ICON_VOLUME_16.getIcon(), -1, - this); + toolInfinityAmp = BrowserMenuBar.makeMenuItem("InfinityAmp", KeyEvent.VK_I, Icons.ICON_MUSIC_16.getIcon(), + -1, this); add(toolInfinityAmp); addSeparator(); @@ -314,7 +314,7 @@ public void actionPerformed(ActionEvent event) { if (event.getSource() == toolCreatureBrowser) { ChildFrame.show(CreatureBrowser.class, CreatureBrowser::new); } else if (event.getSource() == toolInfinityAmp) { - ChildFrame.show(InfinityAmp.class, InfinityAmp::new); + ChildFrame.show(InfinityAmpPlus.class, InfinityAmpPlus::new); } else if (event.getSource() == toolIDSBrowser) { ChildFrame.show(IdsBrowser.class, IdsBrowser::new); } else if (event.getSource() == toolClipBoard) { diff --git a/src/org/infinity/gui/menu/ViewMode.java b/src/org/infinity/gui/menu/ViewMode.java index be7ef06ff..7a13e1997 100644 --- a/src/org/infinity/gui/menu/ViewMode.java +++ b/src/org/infinity/gui/menu/ViewMode.java @@ -10,7 +10,7 @@ public enum ViewMode { private final String title; - private ViewMode(String title) { + ViewMode(String title) { this.title = title; } diff --git a/src/org/infinity/icon/Icons.java b/src/org/infinity/icon/Icons.java index 5be468cb2..38520a92b 100644 --- a/src/org/infinity/icon/Icons.java +++ b/src/org/infinity/icon/Icons.java @@ -58,6 +58,7 @@ public enum Icons { ICON_LAUNCH_PLUS_24("LaunchRedPlus24.png"), ICON_MAGNIFY_16("Magnify16.png"), ICON_MOVIE_16("Movie16.gif"), + ICON_MUSIC_16("Music16.png"), ICON_NEW_16("New16.gif"), ICON_OPEN_16("Open16.gif"), ICON_PASTE_16("Paste16.gif"), @@ -87,7 +88,7 @@ public enum Icons { private final String fileName; private ImageIcon icon; - private Icons(String fileName) { + Icons(String fileName) { this.fileName = fileName; } diff --git a/src/org/infinity/icon/Music16.png b/src/org/infinity/icon/Music16.png new file mode 100644 index 000000000..e0b48bf91 Binary files /dev/null and b/src/org/infinity/icon/Music16.png differ diff --git a/src/org/infinity/resource/AbstractStruct.java b/src/org/infinity/resource/AbstractStruct.java index dee566504..09a1cd972 100644 --- a/src/org/infinity/resource/AbstractStruct.java +++ b/src/org/infinity/resource/AbstractStruct.java @@ -11,14 +11,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -1065,7 +1058,7 @@ public void updateRemovableIndices(Class cls) { if (fieldList.isEmpty()) { return; } - fieldList.sort((f1, f2) -> f1.getOffset() - f2.getOffset()); + fieldList.sort(Comparator.comparingInt(StructEntry::getOffset)); int minIndex = Integer.MAX_VALUE; int maxIndex = Integer.MIN_VALUE; diff --git a/src/org/infinity/resource/Profile.java b/src/org/infinity/resource/Profile.java index 20348e325..1bf933b9a 100644 --- a/src/org/infinity/resource/Profile.java +++ b/src/org/infinity/resource/Profile.java @@ -11,6 +11,7 @@ import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.FileSystem; @@ -49,9 +50,11 @@ import org.infinity.gui.menu.BrowserMenuBar; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.key.ResourceTreeModel; +import org.infinity.util.CharsetDetector; import org.infinity.util.DataString; import org.infinity.util.DebugTimer; import org.infinity.util.Logger; +import org.infinity.util.Misc; import org.infinity.util.Platform; import org.infinity.util.Table2da; import org.infinity.util.Table2daCache; @@ -129,7 +132,7 @@ public enum Game { private final Engine engine; private final String title; - private Game(Engine engine, String title) { + Game(Engine engine, String title) { this.engine = Objects.requireNonNull(engine); this.title = Objects.requireNonNull(title); } @@ -162,7 +165,7 @@ public enum Engine { PST, /** Includes IWD, IWDHoW and IWDTotLM. */ IWD, - /** Includes IWD2. */ + /** Includes IWD2 and IWD2EE. */ IWD2, /** Includes BG1EE, BG1SoD, BG2EE, IWDEE, PSTEE and EET. */ EE, @@ -288,6 +291,11 @@ public enum Key { * generated on first call of {@code getEquippedAppearanceMap()}. */ GET_GAME_EQUIPPED_APPEARANCES, + /** + * Property: ({@code String}) The autodetected character set used to encode or decode strings in the game. + * Can be overridden by NI's preferences. + */ + GET_GAME_CHARSET, /** Property: ({@code Boolean}) Has current game been enhanced by TobEx? */ IS_GAME_TOBEX, /** Property: ({@code Boolean}) Has current game been enhanced by EEex? */ @@ -750,13 +758,12 @@ public static T getProperty(Key key, Object param) { return prop.getData(); } else { // handling properties which require an additional parameter - EnumMap map = null; switch (key) { case GET_GLOBAL_EXTRA_FOLDER_NAMES: case GET_GLOBAL_SAVE_FOLDER_NAMES: case GET_GLOBAL_HOME_FOLDER_NAME: if (param instanceof Game) { - map = prop.getData(); + final EnumMap map = prop.getData(); return map.get(param); } break; @@ -939,6 +946,21 @@ public static Path getChitinKey() { return (ret instanceof Path) ? (Path) ret : null; } + /** + * Returns the default character set used by the currently open game to encode or decode game strings. + * + *

+ * Note: Charset detection uses heuristics for the original games and may not be fully accurate. + * It falls back to {@code windows-1252} if the charset could not be autodetected. + *

+ * + * @return Character set as {@link Charset} object. + */ + public static Charset getDefaultCharset() { + final String retVal = getProperty(Key.GET_GAME_CHARSET); + return Charset.forName((retVal != null) ? retVal : Misc.CHARSET_DEFAULT.name()); + } + /** * Updates language-related Properties with the specified game language. (Enhanced Editions only) * @@ -1729,7 +1751,7 @@ private void init(Path keyFile, String desc, Game forcedGame) throws Exception { if (keyFile == null) { throw new Exception("No chitin.key specified"); } else if (!FileEx.create(keyFile).isFile()) { - throw new Exception(keyFile.toString() + " does not exist"); + throw new Exception(keyFile + " does not exist"); } if (desc != null) { @@ -1779,7 +1801,7 @@ private void initGame() throws Exception { gameRoots.addAll(Profile.getProperty(Key.GET_GAME_DLC_FOLDERS_AVAILABLE)); } - boolean isForced = (Boolean) getProperty(Key.IS_FORCED_GAME); + boolean isForced = getProperty(Key.IS_FORCED_GAME); if (isForced) { game = getGame(); } @@ -2050,7 +2072,7 @@ private void initRootDirs() { // process each root separately roots.forEach(root -> { // adding root of active language - Path langRoot = FileManager.query(root, (String) getProperty(Key.GET_GLOBAL_LANG_NAME), language); + Path langRoot = FileManager.query(root, getProperty(Key.GET_GLOBAL_LANG_NAME), language); if (langRoot != null && FileEx.create(langRoot).isDirectory()) { addEntry(Key.GET_GAME_LANG_FOLDER_NAME, Type.STRING, language); addEntry(Key.GET_GAME_LANG_FOLDER, Type.PATH, langRoot); @@ -2371,6 +2393,9 @@ private void initFeatures() { Game game = getGame(); Engine engine = getEngine(); + // Autodetect default charset used by game strings + addEntry(Key.GET_GAME_CHARSET, Type.STRING, CharsetDetector.guessCharset(true)); + // Are Kits supported? addEntry(Key.IS_SUPPORTED_KITS, Type.BOOLEAN, (engine == Engine.BG2 || engine == Engine.IWD2 || engine == Engine.EE)); diff --git a/src/org/infinity/resource/ResourceFactory.java b/src/org/infinity/resource/ResourceFactory.java index 3c9a341c3..478a1bf6d 100644 --- a/src/org/infinity/resource/ResourceFactory.java +++ b/src/org/infinity/resource/ResourceFactory.java @@ -37,6 +37,7 @@ import org.infinity.datatype.SecTypeBitmap; import org.infinity.datatype.Song2daBitmap; import org.infinity.datatype.Summon2daBitmap; +import org.infinity.exceptions.AbortException; import org.infinity.gui.ChildFrame; import org.infinity.gui.IdsBrowser; import org.infinity.gui.menu.BrowserMenuBar; @@ -85,7 +86,14 @@ import org.infinity.resource.video.WbmResource; import org.infinity.resource.wed.WedResource; import org.infinity.resource.wmp.WmpResource; -import org.infinity.util.*; +import org.infinity.util.CreMapCache; +import org.infinity.util.DynamicArray; +import org.infinity.util.IdsMapCache; +import org.infinity.util.Logger; +import org.infinity.util.Misc; +import org.infinity.util.Platform; +import org.infinity.util.StaticSimpleXorDecryptor; +import org.infinity.util.TriState; import org.infinity.util.io.FileEx; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; @@ -677,19 +685,27 @@ public static void saveCopyOfResource(ResourceEntry entry) { } } - public static boolean saveResource(Resource resource, Component parent) { + public static TriState saveResource(Resource resource, Component parent) { + return saveResource(resource, parent, false); + } + + public static TriState saveResource(Resource resource, Component parent, boolean overwrite) { if (getInstance() != null) { - return getInstance().saveResourceInternal(resource, parent); + return getInstance().saveResourceInternal(resource, parent, overwrite); } else { - return false; + return TriState.FALSE; } } - public static boolean saveResourceAs(Resource resource, Component parent) { + public static TriState saveResourceAs(Resource resource, Component parent) { + return saveResourceAs(resource, parent, false); + } + + public static TriState saveResourceAs(Resource resource, Component parent, boolean overwrite) { if (getInstance() != null) { - return getInstance().saveResourceAsInternal(resource, parent); + return getInstance().saveResourceAsInternal(resource, parent, overwrite); } else { - return false; + return TriState.FALSE; } } @@ -703,7 +719,7 @@ public static boolean saveResourceAs(Resource resource, Component parent) { * * @throws HeadlessException if {@link GraphicsEnvironment#isHeadless} returns {@code true} * @throws NullPointerException If any argument is {@code null} - * @throws Exception If save will be cancelled + * @throws AbortException If save will be cancelled */ public static void closeResource(Resource resource, ResourceEntry entry, JComponent parent) throws Exception { final Path output; @@ -725,7 +741,7 @@ public static void closeResource(Resource resource, ResourceEntry entry, JCompon * * @throws HeadlessException if {@link GraphicsEnvironment#isHeadless} returns {@code true} * @throws NullPointerException If {@code resource} or {@code parent} is {@code null} - * @throws Exception If save will be cancelled + * @throws AbortException If save will be cancelled */ public static void closeResource(Resource resource, Path output, JComponent parent) throws Exception { if (output != null) { @@ -733,9 +749,11 @@ public static void closeResource(Resource resource, Path output, JComponent pare final int result = JOptionPane.showOptionDialog(parent, "Save changes to " + output + '?', "Resource changed", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]); if (result == JOptionPane.YES_OPTION) { - saveResource(resource, parent.getTopLevelAncestor()); + if (saveResource(resource, parent.getTopLevelAncestor()) == TriState.UNDEFINED) { + throw new AbortException("Save aborted"); + } } else if (result != JOptionPane.NO_OPTION) { - throw new Exception("Save aborted"); + throw new AbortException("Save aborted"); } } } @@ -1251,7 +1269,7 @@ private void registerResourceInternal(Path resource, boolean autoselect) { // 3. checking override folders if (FileManager.isSamePath(resPath, Profile.getOverrideFolders(true))) { entry = getResourceEntry(resource.getFileName().toString()); - String folderName = null; + String folderName; if (entry instanceof BIFFResourceEntry) { final boolean overrideInOverride = (options.getOptions().getOverrideMode() == OverrideMode.InOverride); if (overrideInOverride) { @@ -1429,9 +1447,7 @@ private List getResourcesInternal(Pattern pattern, List ext if (extraDirs == null) { extraDirs = Profile.getProperty(Profile.Key.GET_GAME_EXTRA_FOLDERS); } - extraDirs.forEach(path -> { - fillResources(retList, path.getFileName().toString(), pattern); - }); + extraDirs.forEach(path -> fillResources(retList, path.getFileName().toString(), pattern)); // include override folders if (BrowserMenuBar.isInstantiated() && !BrowserMenuBar.getInstance().getOptions().ignoreOverrides()) { @@ -1539,27 +1555,27 @@ private void saveCopyOfResourceInternal(ResourceEntry entry) { } } - private boolean saveResourceAsInternal(Resource resource, Component parent) { + private TriState saveResourceAsInternal(Resource resource, Component parent, boolean overwrite) { final Path outFile = getExportFileDialogInternal(parent, resource.getResourceEntry().getResourceName(), true); if (outFile != null) { - return saveResourceInternal(resource, parent, outFile); + return saveResourceInternal(resource, parent, outFile, overwrite); } else { - return false; + return TriState.FALSE; } } - private boolean saveResourceInternal(Resource resource, Component parent) { - return saveResourceInternal(resource, parent, null); + private TriState saveResourceInternal(Resource resource, Component parent, boolean overwrite) { + return saveResourceInternal(resource, parent, null, overwrite); } - private boolean saveResourceInternal(Resource resource, Component parent, Path outFile) { + private TriState saveResourceInternal(Resource resource, Component parent, Path outFile, boolean overwrite) { if (!(resource instanceof Writeable)) { JOptionPane.showMessageDialog(parent, "Resource not savable", "Error", JOptionPane.ERROR_MESSAGE); - return false; + return TriState.FALSE; } final ResourceEntry entry = resource.getResourceEntry(); if (entry == null) { - return false; + return TriState.FALSE; } Path outPath; @@ -1575,7 +1591,7 @@ private boolean saveResourceInternal(Resource resource, Component parent, Path o JOptionPane.showMessageDialog(parent, "Unable to create override folder.", "Error", JOptionPane.ERROR_MESSAGE); Logger.error(e); - return false; + return TriState.FALSE; } } outPath = FileManager.query(overridePath, entry.getResourceName()); @@ -1592,7 +1608,7 @@ private boolean saveResourceInternal(Resource resource, Component parent, Path o JOptionPane.showMessageDialog(parent, "Unable to create folder: " + outPath.getParent(), "Error", JOptionPane.ERROR_MESSAGE); Logger.error(e); - return false; + return TriState.FALSE; } } } @@ -1601,24 +1617,34 @@ private boolean saveResourceInternal(Resource resource, Component parent, Path o if (FileEx.create(outPath).exists()) { outPath = outPath.toAbsolutePath(); - String[] options = { "Overwrite", "Cancel" }; - if (JOptionPane.showOptionDialog(parent, outPath + " exists. Overwrite?", "Save resource", - JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]) == 0) { - if (BrowserMenuBar.getInstance().getOptions().backupOnSave()) { - try { - Path bakPath = outPath.getParent().resolve(outPath.getFileName() + ".bak"); - if (FileEx.create(bakPath).isFile()) { - Files.delete(bakPath); - } - if (!FileEx.create(bakPath).exists()) { - Files.move(outPath, bakPath); + final int result; + if (overwrite) { + result = JOptionPane.YES_OPTION; + } else { + String[] options = { "Overwrite", "Discard", "Cancel" }; + result = JOptionPane.showOptionDialog(parent, outPath + " exists. Overwrite?", "Save resource", + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]); + } + switch (result) { + case JOptionPane.YES_OPTION: // Overwrite + if (BrowserMenuBar.getInstance().getOptions().backupOnSave()) { + try { + Path bakPath = outPath.getParent().resolve(outPath.getFileName() + ".bak"); + if (FileEx.create(bakPath).isFile()) { + Files.delete(bakPath); + } + if (!FileEx.create(bakPath).exists()) { + Files.move(outPath, bakPath); + } + } catch (IOException e) { + Logger.error(e); } - } catch (IOException e) { - Logger.error(e); } - } - } else { - return false; + break; + case JOptionPane.NO_OPTION: // Discard + return TriState.FALSE; + default: // Cancel + return TriState.UNDEFINED; } } @@ -1627,7 +1653,7 @@ private boolean saveResourceInternal(Resource resource, Component parent, Path o } catch (IOException e) { JOptionPane.showMessageDialog(parent, "Error while saving " + entry, "Error", JOptionPane.ERROR_MESSAGE); Logger.error(e); - return false; + return TriState.FALSE; } JOptionPane.showMessageDialog(parent, "File saved to \"" + outPath.toAbsolutePath() + '\"', "Save complete", @@ -1649,6 +1675,6 @@ private boolean saveResourceInternal(Resource resource, Component parent, Path o } else if (entry.getResourceName().equalsIgnoreCase(SecTypeBitmap.getTableName())) { SecTypeBitmap.resetTypeTable(); } - return true; + return TriState.TRUE; } } diff --git a/src/org/infinity/resource/StructureFactory.java b/src/org/infinity/resource/StructureFactory.java index ecb852a51..c752c7ccf 100644 --- a/src/org/infinity/resource/StructureFactory.java +++ b/src/org/infinity/resource/StructureFactory.java @@ -30,7 +30,7 @@ // Create different pre-initialized IE game resources from scratch and writes them to disk. public final class StructureFactory { // Supported resource types - public static enum ResType { + public enum ResType { RES_2DA, RES_ARE, RES_BAF, RES_BCS, RES_BIO, RES_CHR, RES_CRE, RES_EFF, RES_IDS, RES_INI, RES_ITM, RES_PRO, RES_RES, RES_SPL, RES_SRC, RES_STO, RES_VEF, RES_VVC, RES_WED, RES_WFX, RES_WMAP @@ -73,7 +73,7 @@ public static StructureFactory getInstance() { // Write a new resource of specified type to disk public void newResource(ResType type, Window parent) { // use most appropriate initial folder for each file type - Path savePath = null; + Path savePath; switch (type) { case RES_BIO: case RES_CHR: @@ -160,6 +160,7 @@ public ResourceStructure createStructure(ResType type, String fileName, Window p case RES_BCS: return createBCS(); case RES_BIO: + case RES_RES: return createRES(parent); case RES_CHR: return createCHR(parent); @@ -175,8 +176,6 @@ public ResourceStructure createStructure(ResType type, String fileName, Window p return createINI(); case RES_PRO: return createPRO(parent); - case RES_RES: - return createRES(parent); case RES_SPL: return createSPL(); case RES_SRC: @@ -697,7 +696,7 @@ private String normalizeString(String s) { // -------------------------- INNER CLASSES -------------------------- public static class StructureException extends Exception { - public static enum Reason { + public enum Reason { UNSPECIFIED, CANCELLED_OPERATION, UNSUPPORTED_TYPE, UNSUPPORTED_GAME } diff --git a/src/org/infinity/resource/are/AreResource.java b/src/org/infinity/resource/are/AreResource.java index 4cd743228..172258960 100644 --- a/src/org/infinity/resource/are/AreResource.java +++ b/src/org/infinity/resource/are/AreResource.java @@ -127,6 +127,7 @@ public final class AreResource extends AbstractStruct implements Resource, HasCh public static final String ARE_REST_MOVIE_DAY = "Rest movie (day)"; public static final String ARE_REST_MOVIE_NIGHT = "Rest movie (night)"; public static final String ARE_EXPLORED_BITMAP = "Explored bitmap"; + public static final String ARE_AREA_INI_FILE = "Area INI"; public static final String[] FLAGS_ARRAY = { "Indoors", "Outdoors", "Day/Night", "Weather", "City", "Forest", "Dungeon", "Extended night", "Can rest indoors" }; diff --git a/src/org/infinity/resource/are/Viewer.java b/src/org/infinity/resource/are/Viewer.java index 0ab255bcd..2af43a1ba 100644 --- a/src/org/infinity/resource/are/Viewer.java +++ b/src/org/infinity/resource/are/Viewer.java @@ -16,13 +16,19 @@ import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JComponent; +import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.ScrollPaneConstants; import org.infinity.datatype.Flag; +import org.infinity.datatype.IsTextual; +import org.infinity.datatype.ResourceRef; +import org.infinity.gui.LinkButton; import org.infinity.gui.ViewerUtil; import org.infinity.icon.Icons; +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; import org.infinity.resource.are.viewer.AreaViewer; final class Viewer extends JPanel implements ActionListener { @@ -47,7 +53,40 @@ private JComponent makeFieldPanel() { ViewerUtil.addLabelFieldPair(fieldPanel, are.getAttribute(AreResource.ARE_PROBABILITY_SNOW), gbl, gbc, true); ViewerUtil.addLabelFieldPair(fieldPanel, are.getAttribute(AreResource.ARE_PROBABILITY_FOG), gbl, gbc, true); ViewerUtil.addLabelFieldPair(fieldPanel, are.getAttribute(AreResource.ARE_PROBABILITY_LIGHTNING), gbl, gbc, true); - ViewerUtil.addLabelFieldPair(fieldPanel, are.getAttribute(AreResource.ARE_AREA_SCRIPT), gbl, gbc, true); + + // Special: IWD and IWD2 may fall back to a default area script if the script field is empty in the ARE resource + final boolean allowAlternate = (Profile.getEngine() == Profile.Engine.IWD) || + (Profile.getEngine() == Profile.Engine.IWD2); + final ResourceRef scriptRef = (ResourceRef) are.getAttribute(AreResource.ARE_AREA_SCRIPT); + String scriptName = scriptRef.getResourceName(); + boolean isAlternate = false; + if (allowAlternate && scriptRef.isEmpty()) { + String bcsName = null; + if (Profile.getEngine() == Profile.Engine.IWD) { + // IWD only: draws fallback area script name from WED resource name + // (IWD2 is hardcoded to use only WED resources of the same name as the ARE resource) + final String wedResref = ((IsTextual)are.getAttribute(AreResource.ARE_WED_RESOURCE)).getText(); + if (!wedResref.isEmpty()) { + bcsName = wedResref + ".BCS"; + } + } + if (bcsName == null) { + bcsName = are.getResourceEntry().getResourceRef() + ".BCS"; + } + if (ResourceFactory.resourceExists(bcsName)) { + scriptName = bcsName; + isAlternate = true; + } + } + ViewerUtil.addLabelFieldPair(fieldPanel, new JLabel(AreResource.ARE_AREA_SCRIPT), + new LinkButton(scriptName, 0, isAlternate), gbl, gbc, true); + + // IWDs and PSTs also have area INI spawn files + if ((Boolean) Profile.getProperty(Profile.Key.IS_SUPPORTED_INI)) { + String iniFile = are.getResourceEntry().getResourceRef() + ".INI"; + ViewerUtil.addLabelFieldPair(fieldPanel, new JLabel(AreResource.ARE_AREA_INI_FILE), + new LinkButton(iniFile, 0, false), gbl, gbc, true); + } JButton bView = new JButton("View Area", Icons.ICON_VOLUME_16.getIcon()); bView.setActionCommand(CMD_VIEWAREA); diff --git a/src/org/infinity/resource/are/viewer/BackgroundAnimationProvider.java b/src/org/infinity/resource/are/viewer/BackgroundAnimationProvider.java index 4e2e1ff85..83ead6d54 100644 --- a/src/org/infinity/resource/are/viewer/BackgroundAnimationProvider.java +++ b/src/org/infinity/resource/are/viewer/BackgroundAnimationProvider.java @@ -356,7 +356,7 @@ protected synchronized void updateGraphics() { if (image != null) { if (isActive() || isActiveIgnored()) { // preparing frames - int[] frameIndices = null; + int[] frameIndices; if (isMultiPart()) { frameIndices = new int[control.cycleCount()]; for (int i = 0, cCount = control.cycleCount(); i < cCount; i++) { @@ -379,11 +379,7 @@ protected synchronized void updateGraphics() { // fetching frame data int[] buffer = ((DataBufferInt) working.getRaster().getDataBuffer()).getData(); Arrays.fill(buffer, 0); - if (bam instanceof BamV1Decoder) { - ((BamV1Decoder) bam).frameGet(control, frameIndex, working); - } else { - bam.frameGet(control, frameIndex, working); - } + bam.frameGet(control, frameIndex, working); // post-processing frame buffer = ((DataBufferInt) working.getRaster().getDataBuffer()).getData(); @@ -411,11 +407,10 @@ protected synchronized void updateGraphics() { if (isMirrored()) { left = -imageRect.x - (bam.getFrameInfo(frameIndex).getWidth() - bam.getFrameInfo(frameIndex).getCenterX() - 1); - top = -imageRect.y - bam.getFrameInfo(frameIndex).getCenterY(); } else { left = -imageRect.x - bam.getFrameInfo(frameIndex).getCenterX(); - top = -imageRect.y - bam.getFrameInfo(frameIndex).getCenterY(); } + top = -imageRect.y - bam.getFrameInfo(frameIndex).getCenterY(); g.drawImage(working, left, top, left + frameWidth, top + frameHeight, 0, 0, frameWidth, frameHeight, null); } diff --git a/src/org/infinity/resource/are/viewer/BasicCompositeLayer.java b/src/org/infinity/resource/are/viewer/BasicCompositeLayer.java index c4963cf10..782637fda 100644 --- a/src/org/infinity/resource/are/viewer/BasicCompositeLayer.java +++ b/src/org/infinity/resource/are/viewer/BasicCompositeLayer.java @@ -35,7 +35,7 @@ public boolean isLayerVisible(int id) { public void setLayerVisible(boolean visible) { setVisibilityState(visible); - getLayerObjects().stream().forEach(obj -> { + getLayerObjects().forEach(obj -> { AbstractLayerItem[] items = obj.getLayerItems(); for (final AbstractLayerItem item : items) { final int id = item.getId(); diff --git a/src/org/infinity/resource/are/viewer/LayerDoor.java b/src/org/infinity/resource/are/viewer/LayerDoor.java index 116afea87..37cf826eb 100644 --- a/src/org/infinity/resource/are/viewer/LayerDoor.java +++ b/src/org/infinity/resource/are/viewer/LayerDoor.java @@ -43,7 +43,7 @@ public String getAvailability() { public void setLayerVisible(boolean visible) { setVisibilityState(visible); - getLayerObjects().stream().forEach(obj -> { + getLayerObjects().forEach(obj -> { AbstractLayerItem[] items = obj.getLayerItems(ViewerConstants.DOOR_OPEN | ViewerConstants.LAYER_ITEM_POLY); for (final AbstractLayerItem item : items) { item.setVisible(isLayerVisible(LAYER_PRIMARY) && isLayerEnabled(LAYER_PRIMARY) && !doorClosed); diff --git a/src/org/infinity/resource/are/viewer/LayerDoorCells.java b/src/org/infinity/resource/are/viewer/LayerDoorCells.java index cc60979cd..ab8dee2f2 100644 --- a/src/org/infinity/resource/are/viewer/LayerDoorCells.java +++ b/src/org/infinity/resource/are/viewer/LayerDoorCells.java @@ -51,7 +51,7 @@ public void setLayerVisible(boolean visible) { // } // }); - getLayerObjects().stream().forEach(obj -> { + getLayerObjects().forEach(obj -> { AbstractLayerItem[] items = obj.getLayerItems(ViewerConstants.DOOR_OPEN | ViewerConstants.LAYER_ITEM_POLY); for (final AbstractLayerItem item : items) { item.setVisible(isLayerVisible() && !doorClosed); diff --git a/src/org/infinity/resource/are/viewer/LayerObjectActor.java b/src/org/infinity/resource/are/viewer/LayerObjectActor.java index ef56b251c..ac77717d7 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectActor.java @@ -183,8 +183,8 @@ protected static Allegiance getAllegiance(int ea) { */ protected static ActorAnimationProvider createAnimationProvider(CreResource cre) throws Exception { Objects.requireNonNull(cre); - ActorAnimationProvider retVal = null; - SpriteDecoder decoder = null; + ActorAnimationProvider retVal; + SpriteDecoder decoder; final int maskDeath = 0xf00; // actor uses regular death sequence? final int maskFrozenDeath = 0x40; // actor status is "frozen death"? diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java b/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java index b442bd1b4..65b9963b0 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java @@ -152,7 +152,7 @@ public synchronized void loadAnimation() { /** Tooltip for actor object. */ private String getTooltip() { - String retVal = null; + String retVal; if (cre != null) { retVal = ((IsTextual) cre.getAttribute(CreResource.CRE_NAME)).getText(); } else { diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAutomapPSTIni.java b/src/org/infinity/resource/are/viewer/LayerObjectAutomapPSTIni.java index 643535e00..457eb75b4 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAutomapPSTIni.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAutomapPSTIni.java @@ -76,7 +76,7 @@ public void update(double zoomFactor) { } private IconLayerItem initLayerItem() throws IllegalArgumentException { - IconLayerItem retVal = null; + IconLayerItem retVal; final int count = areData.getAsInteger("count", 0); if (noteIndex < 0 || noteIndex >= count) { diff --git a/src/org/infinity/resource/are/viewer/LayerObjectGlobalActor.java b/src/org/infinity/resource/are/viewer/LayerObjectGlobalActor.java index 8516a54ed..5c0c484de 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectGlobalActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectGlobalActor.java @@ -139,7 +139,7 @@ public boolean isScheduled(int schedule) { /** Tooltip for actor object. */ private String getTooltip() { - String retVal = null; + String retVal; retVal = ((IsTextual) npc.getAttribute(PartyNPC.GAM_NPC_NAME)).getText().trim(); if (retVal.isEmpty()) { diff --git a/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java b/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java index 4801aa3ff..699c2ba79 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectIniActor.java @@ -68,7 +68,7 @@ public LayerObjectIniActor(PlainTextResource ini, IniMapSection creData, int cre throw new IllegalArgumentException(creData.getName() + ": Invalid CRE resref (" + creName + ")"); } - CreResource cre = null; + CreResource cre; try { cre = new CreResource(creEntry); } catch (Exception e) { diff --git a/src/org/infinity/resource/are/viewer/SettingsDialog.java b/src/org/infinity/resource/are/viewer/SettingsDialog.java index 765357fc8..12689ad90 100644 --- a/src/org/infinity/resource/are/viewer/SettingsDialog.java +++ b/src/org/infinity/resource/are/viewer/SettingsDialog.java @@ -616,7 +616,7 @@ public void actionPerformed(ActionEvent event) { // ----------------------------- INNER CLASSES ----------------------------- private static class IndexedCellRenderer extends DefaultListCellRenderer { - private int startIndex; + private final int startIndex; public IndexedCellRenderer(int startIndex) { super(); diff --git a/src/org/infinity/resource/are/viewer/SharedResourceCache.java b/src/org/infinity/resource/are/viewer/SharedResourceCache.java index 9eeed2ab6..55d8d29ac 100644 --- a/src/org/infinity/resource/are/viewer/SharedResourceCache.java +++ b/src/org/infinity/resource/are/viewer/SharedResourceCache.java @@ -12,7 +12,7 @@ */ public class SharedResourceCache { // Identifies the type of cache object to retrieve - public static enum Type { + public enum Type { ICON, ANIMATION, ACTOR } @@ -20,7 +20,7 @@ public static enum Type { static { for (final Type type : Type.values()) { - TABLES.put(type, new HashMap()); + TABLES.put(type, new HashMap<>()); } } diff --git a/src/org/infinity/resource/are/viewer/TilesetRenderer.java b/src/org/infinity/resource/are/viewer/TilesetRenderer.java index 48e627506..9929cec3e 100644 --- a/src/org/infinity/resource/are/viewer/TilesetRenderer.java +++ b/src/org/infinity/resource/are/viewer/TilesetRenderer.java @@ -182,7 +182,7 @@ public WedResource getWed() { public void dispose() { release(true); if (getImage() instanceof VolatileImage) { - ((VolatileImage) getImage()).flush(); + getImage().flush(); } } @@ -986,7 +986,7 @@ private synchronized void drawTile(Tile tile, boolean isDoorTile) { } } else { // no overlay or disabled overlay // preparing tile graphics - int[] srcTile = null; + int[] srcTile; int tileIdx = (!isDoorClosed || !isDoorTile) ? tile.getPrimaryIndex() : tile.getSecondaryIndex(); if (tileIdx < 0) { tileIdx = tile.getPrimaryIndex(); @@ -1017,7 +1017,7 @@ private synchronized void drawTile(Tile tile, boolean isDoorTile) { double curY = tile.getY() * scaleY; double nextY = Math.floor(curY) + 1.0; int startPixelX = (int) Math.floor(curX); - int curPixelX = startPixelX; + int curPixelX; int curPixelY = (int) Math.floor(curY); int srcAlpha = miniMapAlpha; diff --git a/src/org/infinity/resource/are/viewer/ViewerConstants.java b/src/org/infinity/resource/are/viewer/ViewerConstants.java index 61df2b7e1..34309278d 100644 --- a/src/org/infinity/resource/are/viewer/ViewerConstants.java +++ b/src/org/infinity/resource/are/viewer/ViewerConstants.java @@ -13,7 +13,7 @@ public final class ViewerConstants { /** * Supported layer types. */ - public static enum LayerType { + public enum LayerType { ACTOR("Actors", true), REGION("Regions", true), ENTRANCE("Entrances", true), @@ -32,7 +32,7 @@ public static enum LayerType { private final boolean isAre; private final String label; - private LayerType(String label, boolean isAre) { + LayerType(String label, boolean isAre) { this.label = label; this.isAre = isAre; } @@ -54,7 +54,7 @@ public boolean isWed() { } // Used for setting stacking order on map - public static enum LayerStackingType { + public enum LayerStackingType { ACTOR("Actors"), REGION_TARGET("Region targets"), CONTAINER_TARGET("Container targets"), @@ -76,7 +76,7 @@ public static enum LayerStackingType { ; private final String label; - private LayerStackingType(String label) { + LayerStackingType(String label) { this.label = label; } diff --git a/src/org/infinity/resource/are/viewer/icon/ViewerIcons.java b/src/org/infinity/resource/are/viewer/icon/ViewerIcons.java index 762e5b3f0..4f9ef2a20 100644 --- a/src/org/infinity/resource/are/viewer/icon/ViewerIcons.java +++ b/src/org/infinity/resource/are/viewer/icon/ViewerIcons.java @@ -100,7 +100,7 @@ public enum ViewerIcons { private final String fileName; private ImageIcon icon; - private ViewerIcons(String fileName) { + ViewerIcons(String fileName) { this.fileName = fileName; } diff --git a/src/org/infinity/resource/bcs/BafResource.java b/src/org/infinity/resource/bcs/BafResource.java index 483395ce3..7b5119b4b 100644 --- a/src/org/infinity/resource/bcs/BafResource.java +++ b/src/org/infinity/resource/bcs/BafResource.java @@ -352,9 +352,9 @@ public JComponent makeViewer(ViewableContainer container) { @Override public void write(OutputStream os) throws IOException { if (sourceText == null) { - StreamUtils.writeString(os, text, text.length()); + StreamUtils.writeString(os, text, text.length(), Profile.getDefaultCharset()); } else { - sourceText.write(new OutputStreamWriter(os)); + sourceText.write(new OutputStreamWriter(os, Profile.getDefaultCharset())); } } @@ -467,9 +467,9 @@ private void save(boolean interactive) { } final boolean result; if (interactive) { - result = ResourceFactory.saveResourceAs(this, panel.getTopLevelAncestor()); + result = ResourceFactory.saveResourceAs(this, panel.getTopLevelAncestor()).isTrue(); } else { - result = ResourceFactory.saveResource(this, panel.getTopLevelAncestor()); + result = ResourceFactory.saveResource(this, panel.getTopLevelAncestor()).isTrue(); } if (result) { bSave.setEnabled(false); diff --git a/src/org/infinity/resource/bcs/BcsResource.java b/src/org/infinity/resource/bcs/BcsResource.java index 4be23cc6e..e64d4afae 100644 --- a/src/org/infinity/resource/bcs/BcsResource.java +++ b/src/org/infinity/resource/bcs/BcsResource.java @@ -39,6 +39,7 @@ import javax.swing.filechooser.FileFilter; import javax.swing.text.BadLocationException; +import org.infinity.exceptions.AbortException; import org.infinity.gui.ButtonPanel; import org.infinity.gui.ButtonPopupMenu; import org.infinity.gui.DataMenuItem; @@ -360,11 +361,11 @@ public void close() throws Exception { if (result == JOptionPane.YES_OPTION) { ((JButton) bpDecompile.getControlByType(CTRL_COMPILE)).doClick(); if (bpDecompile.getControlByType(CTRL_ERRORS).isEnabled()) { - throw new Exception("Save aborted"); + throw new AbortException("Save aborted"); } ResourceFactory.saveResource(this, panel.getTopLevelAncestor()); } else if (result == JOptionPane.CANCEL_OPTION || result == JOptionPane.CLOSED_OPTION) { - throw new Exception("Save aborted"); + throw new AbortException("Save aborted"); } } else if (codeChanged) { ResourceFactory.closeResource(this, entry, panel); @@ -672,9 +673,9 @@ public JComponent makeViewer(ViewableContainer container) { @Override public void write(OutputStream os) throws IOException { if (codeText == null) { - StreamUtils.writeString(os, text, text.length()); + StreamUtils.writeString(os, text, text.length(), Profile.getDefaultCharset()); } else { - StreamUtils.writeString(os, codeText.getText(), codeText.getText().length()); + StreamUtils.writeString(os, codeText.getText(), codeText.getText().length(), Profile.getDefaultCharset()); } } @@ -779,9 +780,9 @@ private void save(boolean interactive) { } final boolean result; if (interactive) { - result = ResourceFactory.saveResourceAs(this, panel.getTopLevelAncestor()); + result = ResourceFactory.saveResourceAs(this, panel.getTopLevelAncestor()).isTrue(); } else { - result = ResourceFactory.saveResource(this, panel.getTopLevelAncestor()); + result = ResourceFactory.saveResource(this, panel.getTopLevelAncestor()).isTrue(); } if (result) { bSave.setEnabled(false); diff --git a/src/org/infinity/resource/bcs/BcsStructureBase.java b/src/org/infinity/resource/bcs/BcsStructureBase.java index dd3280a13..068b73313 100644 --- a/src/org/infinity/resource/bcs/BcsStructureBase.java +++ b/src/org/infinity/resource/bcs/BcsStructureBase.java @@ -68,7 +68,7 @@ protected Rectangle parseRectangle(StringBufferStream sbs) throws Exception { protected int[] parseNumberArray(StringBufferStream sbs, char tagOpen, char tagClose, char separator, int numItems) throws Exception { int[] retVal = new int[numItems]; - boolean success = true; + boolean success; success = (sbs.get() == tagOpen); for (int i = 0; i < numItems && success; i++) { if (i > 0) { diff --git a/src/org/infinity/resource/bcs/Compiler.java b/src/org/infinity/resource/bcs/Compiler.java index 40f865ed7..9324e92ec 100644 --- a/src/org/infinity/resource/bcs/Compiler.java +++ b/src/org/infinity/resource/bcs/Compiler.java @@ -621,7 +621,7 @@ private boolean isOverrideFunction(int code, boolean isTrigger) { Signatures.Function[] funcs = isTrigger ? triggers.getFunction(code) : actions.getFunction(code); if (funcs != null) { for (Signatures.Function func : funcs) { - char paramType = 0; + char paramType; if (isTrigger) { paramType = Signatures.Function.Parameter.TYPE_TRIGGER; } else { @@ -816,16 +816,16 @@ private String generateErrorMessage(ParseException e) { ArrayList expected = new ArrayList<>(); for (int[] element : e.expectedTokenSequences) { - String symbol = ""; + final StringBuilder symbol = new StringBuilder(); for (int offset : element) { String s = TOKEN_SYMBOL_TO_DESC_MAP.get(e.tokenImage[offset]); if (s == null) { s = e.tokenImage[offset]; } - symbol += s + ' '; + symbol.append(s).append(' '); } - if (!expected.contains(symbol)) { - expected.add(symbol); + if (!expected.contains(symbol.toString())) { + expected.add(symbol.toString()); } } diff --git a/src/org/infinity/resource/bcs/Decompiler.java b/src/org/infinity/resource/bcs/Decompiler.java index c4983de39..35585c3d1 100644 --- a/src/org/infinity/resource/bcs/Decompiler.java +++ b/src/org/infinity/resource/bcs/Decompiler.java @@ -693,7 +693,7 @@ private String decompileAction(BcsAction action) throws Exception { break; } case Signatures.Function.Parameter.TYPE_OBJECT: { - BcsObject value = null; + BcsObject value; try { value = action.getObjectParam(curObj); } catch (IllegalArgumentException e) { @@ -753,7 +753,7 @@ private String decompileObject(BcsObject object) { if (object == null) { sb.append(decompileObjectTarget(null, true)); } else { - String target = null; + String target; String rect = null; // getting target object @@ -825,7 +825,7 @@ private String decompileObject(BcsObject object) { } if (sbClosing != null) { - sb.append(sbClosing.toString()); + sb.append(sbClosing); } if (rect != null) { @@ -989,7 +989,7 @@ private String generateNumberComment(long value, Signatures.Function.Parameter p StringBuilder sb = new StringBuilder(); if (enable && (isGenerateComments() || isGenerateResourcesUsed())) { - ResourceEntry entry = null; + ResourceEntry entry; String[] types = param.getResourceType(); for (String type : types) { if (type.equals("TLK")) { diff --git a/src/org/infinity/resource/bcs/ScriptInfo.java b/src/org/infinity/resource/bcs/ScriptInfo.java index b07b9c697..b1ac0ac95 100644 --- a/src/org/infinity/resource/bcs/ScriptInfo.java +++ b/src/org/infinity/resource/bcs/ScriptInfo.java @@ -429,9 +429,8 @@ public static ScriptInfo getInfo(Profile.Engine engine) { * Internally used: Create a correctly formatted string for use as key for {@code FUNCTION_RESTYPE}. */ private static String key(Function.FunctionType ftype, int id, char type, int index) { - String s = String.valueOf(ftype == Function.FunctionType.TRIGGER ? 'T' : 'A') + ':' + id + ':' + + return String.valueOf(ftype == Function.FunctionType.TRIGGER ? 'T' : 'A') + ':' + id + ':' + Character.toUpperCase(type) + ':' + index; - return s; } /** diff --git a/src/org/infinity/resource/bcs/Signatures.java b/src/org/infinity/resource/bcs/Signatures.java index 2852a23ae..c3a92d442 100644 --- a/src/org/infinity/resource/bcs/Signatures.java +++ b/src/org/infinity/resource/bcs/Signatures.java @@ -123,7 +123,7 @@ public static Signatures getActions() { * @return a {@code Signatures} instance. Returns {@code null} on error. */ public static Signatures get(String resource, boolean isTrigger) { - Signatures retVal = null; + Signatures retVal; resource = normalizedName(resource); retVal = INSTANCES.get(resource); @@ -195,7 +195,7 @@ private static String normalizedName(String resource) { /** Full set of information about a single function definition. */ public static class Function { - public static enum FunctionType { + public enum FunctionType { /** Function signature belongs to an action. */ ACTION, /** Function signature belongs to a trigger. */ @@ -393,7 +393,7 @@ private static Function parse(String line, boolean isTrigger) { final Pattern patIdentifier = Pattern.compile("[a-zA-Z_#][0-9a-zA-Z_#]*"); final Pattern patBrackOpen = Pattern.compile("\\("); final Pattern patBrackClosed = Pattern.compile("\\)"); - boolean success = false; + boolean success; int pos = 0; // getting function id diff --git a/src/org/infinity/resource/bcs/parser/BafNode.java b/src/org/infinity/resource/bcs/parser/BafNode.java index 474bb3be8..26cd2f4f2 100644 --- a/src/org/infinity/resource/bcs/parser/BafNode.java +++ b/src/org/infinity/resource/bcs/parser/BafNode.java @@ -25,7 +25,7 @@ public String getTokenString() { Token token1 = jjtGetFirstToken(); Token token2 = jjtGetLastToken(); while (token1 != null) { - sb.append(token1.toString()); + sb.append(token1); if (token1 != token2) { token1 = token1.next; } else { diff --git a/src/org/infinity/resource/bcs/parser/BafNodeTransformer.java b/src/org/infinity/resource/bcs/parser/BafNodeTransformer.java index b081e1d76..b2a8c971a 100644 --- a/src/org/infinity/resource/bcs/parser/BafNodeTransformer.java +++ b/src/org/infinity/resource/bcs/parser/BafNodeTransformer.java @@ -55,7 +55,7 @@ public ScriptNode transformParseNodes(BafNode root) throws ParseException { throw new NullPointerException(); } - ScriptNode scriptRoot = null; + ScriptNode scriptRoot; if (root.getId() == BafParserTreeConstants.JJTSC) { scriptRoot = transformSC(root); } else if (root.getId() == BafParserTreeConstants.JJTSEQ_TR) { @@ -217,7 +217,7 @@ private void transformTR(ScriptNode parent, BafNode baf) throws ParseException { child.jjtGetFirstToken(), child.jjtGetLastToken())); return; } else if (verbose && !token.toString().equals(func.getName())) { - warnings.add(new ScriptMessage("No exact match: " + token.toString() + " vs. " + func.getName(), + warnings.add(new ScriptMessage("No exact match: " + token + " vs. " + func.getName(), child.jjtGetFirstToken(), child.jjtGetLastToken())); } nodeTR.code = func.getId(); @@ -258,7 +258,7 @@ private void transformAC(ScriptNode parent, BafNode baf) throws ParseException { child.jjtGetFirstToken(), child.jjtGetLastToken())); return; } else if (verbose && !token.toString().equals(func.getName())) { - warnings.add(new ScriptMessage("No exact match: " + token.toString() + " vs. " + func.getName(), + warnings.add(new ScriptMessage("No exact match: " + token + " vs. " + func.getName(), child.jjtGetFirstToken(), child.jjtGetLastToken())); } nodeAC.code = func.getId(); @@ -900,11 +900,11 @@ public void dump(PrintStream output, String prefix) { } public void dump(PrintStream output, String prefix, int level) { - String prefix2 = ""; + final StringBuilder prefix2 = new StringBuilder(); for (int i = 0; i < level; i++) { - prefix2 += prefix; + prefix2.append(prefix); } - System.out.println(toString(prefix2)); + System.out.println(toString(prefix2.toString())); Iterator iter = getChildren(); while (iter.hasNext()) { iter.next().dump(output, prefix, level + 1); diff --git a/src/org/infinity/resource/chu/Viewer.java b/src/org/infinity/resource/chu/Viewer.java index 5e8900559..cfec680b0 100644 --- a/src/org/infinity/resource/chu/Viewer.java +++ b/src/org/infinity/resource/chu/Viewer.java @@ -1845,7 +1845,7 @@ public void updateImage() { int srcHeight = textImage.getHeight(null); int dstWidth = image.getWidth(); int dstHeight = image.getHeight(); - int x = 0, y = 0; + int x, y; if (flags.isFlagSet(3)) { // left x = 0; } else if (flags.isFlagSet(4)) { // right diff --git a/src/org/infinity/resource/chu/Window.java b/src/org/infinity/resource/chu/Window.java index 953518521..0f9ddebfb 100644 --- a/src/org/infinity/resource/chu/Window.java +++ b/src/org/infinity/resource/chu/Window.java @@ -135,7 +135,7 @@ public void writeControls(OutputStream os) throws IOException { public void writeControlsTable(OutputStream os) throws IOException { for (final StructEntry o : getFields()) { if (o instanceof Control) { - ((Control) o).write(os); + o.write(os); } } } diff --git a/src/org/infinity/resource/cre/CreResource.java b/src/org/infinity/resource/cre/CreResource.java index b7b50d489..612b845b9 100644 --- a/src/org/infinity/resource/cre/CreResource.java +++ b/src/org/infinity/resource/cre/CreResource.java @@ -558,13 +558,13 @@ public static void addScriptName(Map> scriptNames, Re if (signature.equalsIgnoreCase("CRE ")) { String version = StreamUtils.readString(buffer, offset + 4, 4); if (version.equalsIgnoreCase("V1.0")) { - scriptName = StreamUtils.readString(buffer, offset + 640, 32); + scriptName = StreamUtils.readString(buffer, offset + 640, 32, Profile.getDefaultCharset()); } else if (version.equalsIgnoreCase("V1.1") || version.equalsIgnoreCase("V1.2")) { - scriptName = StreamUtils.readString(buffer, offset + 804, 32); + scriptName = StreamUtils.readString(buffer, offset + 804, 32, Profile.getDefaultCharset()); } else if (version.equalsIgnoreCase("V2.2")) { - scriptName = StreamUtils.readString(buffer, offset + 916, 32); + scriptName = StreamUtils.readString(buffer, offset + 916, 32, Profile.getDefaultCharset()); } else if (version.equalsIgnoreCase("V9.0") || version.equalsIgnoreCase("V9.1")) { - scriptName = StreamUtils.readString(buffer, offset + 744, 32); + scriptName = StreamUtils.readString(buffer, offset + 744, 32, Profile.getDefaultCharset()); } if (scriptName.isEmpty() || scriptName.equalsIgnoreCase("None")) { return; @@ -708,11 +708,11 @@ private static int copyStruct(List oldlist, List newli } public static String getSearchString(InputStream is) throws IOException { - String retVal = ""; + String retVal; String sig = StreamUtils.readString(is, 4); is.skip(4); if (sig.equals("CHR ")) { - retVal = StreamUtils.readString(is, 32); + retVal = StreamUtils.readString(is, 32, Profile.getDefaultCharset()); } else { final int strrefName = StreamUtils.readInt(is); final int strrefShortName = StreamUtils.readInt(is); @@ -734,7 +734,7 @@ public static String getSearchString(InputStream is) throws IOException { * @throws IOException If the buffer does not contain valid resource data. */ public static String getScriptName(ByteBuffer buffer) throws IOException { - String retVal = ""; + String retVal; String sig = StreamUtils.readString(buffer, 0, 4); String ver = StreamUtils.readString(buffer, 4, 4); int startOfs = 0; @@ -1266,7 +1266,8 @@ private int readIWD2(ByteBuffer buffer, int offset) throws Exception { addField(new DecNumber(buffer, offset + 612, 1, CRE_MORALE)); addField(new DecNumber(buffer, offset + 613, 1, CRE_MORALE_BREAK)); addField(new DecNumber(buffer, offset + 614, 2, CRE_MORALE_RECOVERY)); - addField(new KitIdsBitmap(buffer, offset + 616, CRE_KIT)); + addField(new Flag(buffer, offset + 616, 4, CRE_KIT, + IdsMapCache.getUpdatedIdsFlags(new String[] { "No Kit", "" }, "KIT.IDS", 4, true, true))); addField(new ResourceRef(buffer, offset + 620, CRE_SCRIPT_OVERRIDE, "BCS")); addField(new ResourceRef(buffer, offset + 628, CRE_SCRIPT_SPECIAL_2, "BCS", "BS")); addField(new ResourceRef(buffer, offset + 636, CRE_SCRIPT_COMBAT, "BCS")); diff --git a/src/org/infinity/resource/cre/browser/CreatureAnimationModel.java b/src/org/infinity/resource/cre/browser/CreatureAnimationModel.java index 6f332a1c4..7eb5436cc 100644 --- a/src/org/infinity/resource/cre/browser/CreatureAnimationModel.java +++ b/src/org/infinity/resource/cre/browser/CreatureAnimationModel.java @@ -59,7 +59,7 @@ public int getIndexOf(Object anItem) { if (anItem instanceof AnimateEntry) { return animationList.indexOf(anItem); } else { - int idx = -1; + int idx; if (anItem instanceof Number) { idx = ((Number) anItem).intValue(); } else { diff --git a/src/org/infinity/resource/cre/browser/CreatureBrowser.java b/src/org/infinity/resource/cre/browser/CreatureBrowser.java index 2f8ea1582..ebe659a02 100644 --- a/src/org/infinity/resource/cre/browser/CreatureBrowser.java +++ b/src/org/infinity/resource/cre/browser/CreatureBrowser.java @@ -305,7 +305,7 @@ public void componentHidden(ComponentEvent e) { } /** Represents a supplier of results. */ - public static interface Task { + public interface Task { /** Gets a result. */ Object get() throws Exception; } @@ -314,7 +314,7 @@ public static interface Task { * Represents an operation that accepts two arguments: {@code Object} returned by and potential {@code Exception} * thrown in the previous {@code Task} operation. */ - public static interface PostTask { + public interface PostTask { void accept(Object o, Exception e); default PostTask andThen(PostTask after) { diff --git a/src/org/infinity/resource/cre/browser/CreatureSelectionModel.java b/src/org/infinity/resource/cre/browser/CreatureSelectionModel.java index 6d34a6d94..67ab9040c 100644 --- a/src/org/infinity/resource/cre/browser/CreatureSelectionModel.java +++ b/src/org/infinity/resource/cre/browser/CreatureSelectionModel.java @@ -130,7 +130,7 @@ private void init() { removeAllElements(); selectedItem = null; - ResourceFactory.getResources("CRE").stream().forEach(re -> creList.add(new CreatureItem(re))); + ResourceFactory.getResources("CRE").forEach(re -> creList.add(new CreatureItem(re))); Collections.sort(creList); creList.add(0, CreatureItem.getDefault()); fireIntervalAdded(this, 0, creList.size() - 1); diff --git a/src/org/infinity/resource/cre/browser/CreatureStatusModel.java b/src/org/infinity/resource/cre/browser/CreatureStatusModel.java index a5bb84312..9da6441a5 100644 --- a/src/org/infinity/resource/cre/browser/CreatureStatusModel.java +++ b/src/org/infinity/resource/cre/browser/CreatureStatusModel.java @@ -32,7 +32,7 @@ public enum Status { private final int id; - private Status(int id) { + Status(int id) { this.id = id; } diff --git a/src/org/infinity/resource/cre/decoder/AmbientDecoder.java b/src/org/infinity/resource/cre/decoder/AmbientDecoder.java index 9236432c5..44a709726 100644 --- a/src/org/infinity/resource/cre/decoder/AmbientDecoder.java +++ b/src/org/infinity/resource/cre/decoder/AmbientDecoder.java @@ -171,7 +171,7 @@ protected SeqDef getSequenceDefinition(Sequence seq) { && SpriteUtils.bamCyclesExist(entryE, cycleE, SeqDef.DIR_REDUCED_E.length)) { retVal = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_W, false, entry, cycle, null, behavior); SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_REDUCED_E, false, entryE, cycleE, null, behavior); - retVal.addDirections(tmp.getDirections().toArray(new DirDef[tmp.getDirections().size()])); + retVal.addDirections(tmp.getDirections().toArray(new DirDef[0])); } } return retVal; diff --git a/src/org/infinity/resource/cre/decoder/CharacterBaseDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterBaseDecoder.java index 3d1cdaa83..39489f782 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterBaseDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterBaseDecoder.java @@ -146,7 +146,7 @@ public AttackType getAttackType(ItemInfo itm, int abilityIndex, boolean preferTw // include fake two-handed weapons (e.g. monk fists) isTwoHanded |= (itm.getFlags() & (1 << 12)) != 0; } - int abilType = -1; + int abilType; abilityIndex = Math.max(0, Math.min(itm.getAbilityCount() - 1, abilityIndex)); abilType = itm.getAbility(abilityIndex).getAbilityType(); diff --git a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java index 9795c4db9..10da1dae2 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterDecoder.java @@ -325,7 +325,7 @@ protected void setMaxArmorCode(int v) { @Override public List getAnimationFiles(boolean essential) { - ArrayList retVal = null; + ArrayList retVal; String resref1 = getAnimationResref(); String resref2 = getArmorSpecificResref(); diff --git a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java index 819ff0867..5baee77da 100644 --- a/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java +++ b/src/org/infinity/resource/cre/decoder/CharacterOldDecoder.java @@ -211,7 +211,7 @@ protected void setMaxArmorCode(int v) { @Override public List getAnimationFiles(boolean essential) { - ArrayList retVal = null; + ArrayList retVal; String resref = getAnimationResref(); if (essential) { diff --git a/src/org/infinity/resource/cre/decoder/EffectDecoder.java b/src/org/infinity/resource/cre/decoder/EffectDecoder.java index 33fd75446..c9a10e8cd 100644 --- a/src/org/infinity/resource/cre/decoder/EffectDecoder.java +++ b/src/org/infinity/resource/cre/decoder/EffectDecoder.java @@ -339,7 +339,7 @@ protected SeqDef getSequenceDefinition(Sequence seq) { ResourceEntry resEntry = ResourceFactory.getResourceEntry(resref + ".BAM"); BamControl ctrl = SpriteUtils.loadBamController(resEntry); if (ctrl != null) { - int cycle = 0; + int cycle; if (isRenderRandom()) { cycle = Math.abs(rnd.nextInt()) % ctrl.cycleCount(); } else if (getCycle() >= 0) { diff --git a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java index 12ca73b78..701a85d50 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterDecoder.java @@ -321,24 +321,26 @@ protected SeqDef getSequenceDefinition(Sequence seq) { } } + final Direction[] dirs = isSmoothPath() ? SeqDef.DIR_FULL_W : SeqDef.DIR_FULL_LIMITED_W; + final Direction[] dirsE = isSmoothPath() ? SeqDef.DIR_FULL_E : SeqDef.DIR_FULL_LIMITED_E; retVal = new SeqDef(seq); for (final Couple creInfo : creResList) { ResourceEntry entry = ResourceFactory.getResourceEntry(creInfo.getValue0()); SegmentDef.SpriteType type = creInfo.getValue1(); if (SpriteUtils.bamCyclesExist(entry, ofs, SeqDef.DIR_FULL_W.length)) { - SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_W, false, entry, ofs, type, behavior); - retVal.addDirections(tmp.getDirections().toArray(new DirDef[0])); - tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_E, true, entry, ofs + 1, type, behavior); - retVal.addDirections(tmp.getDirections().toArray(new DirDef[0])); + SeqDef tmp = SeqDef.createSequence(seq, dirs, false, entry, ofs, type, behavior); + retVal.addDirections(false, tmp.getDirections().toArray(new DirDef[0])); + tmp = SeqDef.createSequence(seq, dirsE, true, entry, ofs + 1, type, behavior); + retVal.addDirections(false, tmp.getDirections().toArray(new DirDef[0])); } else if (entry != null && SpriteUtils.getBamCycles(entry) == 1) { // fallback solution: just use first bam cycle (required by a few animations) - for (final Direction dir : SeqDef.DIR_FULL_W) { + for (final Direction dir : dirs) { SeqDef tmp = SeqDef.createSequence(seq, new Direction[] { dir }, false, entry, 0, type, behavior); - retVal.addDirections(tmp.getDirections().toArray(new DirDef[0])); + retVal.addDirections(false, tmp.getDirections().toArray(new DirDef[0])); } - for (final Direction dir : SeqDef.DIR_FULL_E) { + for (final Direction dir : dirsE) { SeqDef tmp = SeqDef.createSequence(seq, new Direction[] { dir }, true, entry, 0, type, behavior); - retVal.addDirections(tmp.getDirections().toArray(new DirDef[0])); + retVal.addDirections(false, tmp.getDirections().toArray(new DirDef[0])); } } } diff --git a/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java index c5bcd9f6b..c80cd5115 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterMultiDecoder.java @@ -163,7 +163,7 @@ protected void setDoubleBlit(boolean b) { /** Returns the palette resref for the specified BAM prefix. Falls back to "new_palette" if needed. */ public String getPalette(int idx) { - String retVal = null; + String retVal; switch (idx) { case 1: retVal = getAttribute(KEY_PALETTE_1); @@ -257,7 +257,7 @@ public boolean isSequenceAvailable(Sequence seq) { @Override protected int[] getNewPaletteData(ResourceEntry bamRes) { - int[] retVal = null; + int[] retVal; String bamResref = bamRes.getResourceRef(); String resref = getAnimationResref(); diff --git a/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java index 1d49636e0..a1ddd6c11 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterMultiNewDecoder.java @@ -14,6 +14,7 @@ import org.infinity.resource.cre.decoder.util.AnimationInfo; import org.infinity.resource.cre.decoder.util.DecoderAttribute; import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.Direction; import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; @@ -211,9 +212,11 @@ protected SeqDef getSequenceDefinition(Sequence seq) { } if (!cycleList.isEmpty() && valid) { - retVal = SeqDef.createSequence(seq, SeqDef.DIR_FULL_W, false, cycleList); - SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_E, true, cycleListE); - retVal.addDirections(tmp.getDirections().toArray(new DirDef[0])); + Direction[] dirs = isSmoothPath() ? SeqDef.DIR_FULL_W : SeqDef.DIR_FULL_LIMITED_W; + retVal = SeqDef.createSequence(false, seq, dirs, false, cycleList); + dirs = isSmoothPath() ? SeqDef.DIR_FULL_E : SeqDef.DIR_FULL_LIMITED_E; + SeqDef tmp = SeqDef.createSequence(false, seq, dirs, true, cycleListE); + retVal.addDirections(false, tmp.getDirections().toArray(new DirDef[0])); } return retVal; diff --git a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java index 32422ce5f..046b72cff 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterPlanescapeDecoder.java @@ -346,7 +346,7 @@ public int getColorLocation(int index) throws IndexOutOfBoundsException { /** Returns a list of all valid color locations. */ private List setColorLocations(IniMapSection section) { - List retVal = null; + List retVal; if (Profile.getGame() == Profile.Game.PST) { retVal = setColorLocationsCre(); if (retVal.isEmpty()) { diff --git a/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java b/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java index 564789be8..6df32faeb 100644 --- a/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java +++ b/src/org/infinity/resource/cre/decoder/MonsterQuadrantDecoder.java @@ -14,6 +14,7 @@ import org.infinity.resource.cre.decoder.util.AnimationInfo; import org.infinity.resource.cre.decoder.util.DecoderAttribute; import org.infinity.resource.cre.decoder.util.DirDef; +import org.infinity.resource.cre.decoder.util.Direction; import org.infinity.resource.cre.decoder.util.SegmentDef; import org.infinity.resource.cre.decoder.util.SeqDef; import org.infinity.resource.cre.decoder.util.Sequence; @@ -210,9 +211,11 @@ protected SeqDef getSequenceDefinition(Sequence seq) { } if (!cycleList.isEmpty() && valid) { - retVal = SeqDef.createSequence(seq, SeqDef.DIR_FULL_W, false, cycleList); - SeqDef tmp = SeqDef.createSequence(seq, SeqDef.DIR_FULL_E, !isExtendedDirection(), cycleListE); - retVal.addDirections(tmp.getDirections().toArray(new DirDef[0])); + Direction[] dirs = isSmoothPath() ? SeqDef.DIR_FULL_W : SeqDef.DIR_FULL_LIMITED_W; + retVal = SeqDef.createSequence(false, seq, dirs, false, cycleList); + dirs = isSmoothPath() ? SeqDef.DIR_FULL_E : SeqDef.DIR_FULL_LIMITED_E; + SeqDef tmp = SeqDef.createSequence(false, seq, dirs, !isExtendedDirection(), cycleListE); + retVal.addDirections(false, tmp.getDirections().toArray(new DirDef[0])); } return retVal; diff --git a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java index 36874620f..ba968ae7a 100644 --- a/src/org/infinity/resource/cre/decoder/SpriteDecoder.java +++ b/src/org/infinity/resource/cre/decoder/SpriteDecoder.java @@ -237,7 +237,7 @@ protected SpriteDecoder(AnimationInfo.Type type, int animationId, IniMap ini) th this.currentSequence = Sequence.NONE; init(); if (!isMatchingAnimationType()) { - throw new IllegalArgumentException("Animation id is incompatible with animation type: " + type.toString()); + throw new IllegalArgumentException("Animation id is incompatible with animation type: " + type); } } @@ -1012,7 +1012,7 @@ protected int addDirection(Direction dir, int cycleIndex) { if (value != null) { retVal = value; } - directionMap.put(dir, cycleIndex); + directionMap.putIfAbsent(dir, cycleIndex); return retVal; } @@ -1107,8 +1107,8 @@ protected Point getBlurInstanceShift(Point pt, Direction dir, int idx) { protected void createAnimation(SeqDef definition, List directions, BeforeSourceBam beforeSrcBam, BeforeSourceFrame beforeSrcFrame, AfterSourceFrame afterSrcFrame, AfterDestFrame afterDstFrame) { PseudoBamControl dstCtrl = createControl(); - BamV1Control srcCtrl = null; - ResourceEntry entry = null; + BamV1Control srcCtrl; + ResourceEntry entry; Objects.requireNonNull(definition, "Sequence definition cannot be null"); if (directions == null) { @@ -1573,7 +1573,7 @@ protected void applyFalseColors(BamV1Control control, SegmentDef sd) { palette[3] = 0xFF000000; } - control.setExternalPalette(palette); + control.setExternalPalette(palette, control.getTransparencyIndex(true)); } /** @@ -1615,14 +1615,14 @@ protected void applyColorEffects(BamV1Control control, SegmentDef sd) { } } - control.setExternalPalette(palette); + control.setExternalPalette(palette, control.getTransparencyIndex(true)); } else if (isBurnedEffect) { // isBurnedEffect: includes flame death status int opcode = 51; int color = 0x4b4b4b; int[] palette = control.getCurrentPalette(); palette = SpriteUtils.tintColors(palette, 2, 254, opcode, color); - control.setExternalPalette(palette); + control.setExternalPalette(palette, control.getTransparencyIndex(true)); } } @@ -1692,7 +1692,7 @@ protected void applyColorTint(BamV1Control control, SegmentDef sd) { palette = SpriteUtils.tintColors(palette, 2, 254, opcode, color); } - control.setExternalPalette(palette); + control.setExternalPalette(palette, control.getTransparencyIndex(true)); } /** @@ -1742,7 +1742,7 @@ protected void applyTranslucency(BamV1Control control, int minTranslucency) { palette[i] = alpha | (palette[i] & 0x00ffffff); } - control.setExternalPalette(palette); + control.setExternalPalette(palette, control.getTransparencyIndex(true)); } } diff --git a/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java b/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java index 960f3c377..f09ed45e9 100644 --- a/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java +++ b/src/org/infinity/resource/cre/decoder/tables/SpriteTables.java @@ -30,9 +30,9 @@ */ public class SpriteTables { // Number of non-PST table columns (id column included) - public static final int NUM_COLUMNS = 16; + public static final int NUM_COLUMNS = 17; // Number of PST table columns (id column included) - public static final int NUM_COLUMNS_PST = 9; + public static final int NUM_COLUMNS_PST = 10; // Header column index public static final int COLUMN_ID = 0; // int (hex/composite) @@ -52,6 +52,7 @@ public class SpriteTables { public static final int COLUMN_WEAPON = 13; // bool public static final int COLUMN_HEIGHT = 14; // string public static final int COLUMN_HEIGHT_SHIELD = 15; // string + public static final int COLUMN_MOVE_SCALE = 16; // int // Column indices for PST-related sprite tables public static final int COLUMN_PST_RESREF = 1; // string public static final int COLUMN_PST_RESREF2 = 2; // string @@ -61,6 +62,7 @@ public class SpriteTables { public static final int COLUMN_PST_CLOWN = 6; // int public static final int COLUMN_PST_ARMOR = 7; // int public static final int COLUMN_PST_BESTIARY = 8; // int + public static final int COLUMN_PST_MOVE_SCALE = 9; // decimal private static final EnumMap> TABLE_MAPS = new EnumMap<>(Profile.Game.class); @@ -73,6 +75,7 @@ public class SpriteTables { TABLE_MAPS.put(Profile.Game.IWDHowTotLM, TABLE_MAPS.get(Profile.Game.IWDHoW)); TABLE_MAPS.put(Profile.Game.IWD2, Collections.singletonList("avatars-iwd2.2da")); + TABLE_MAPS.put(Profile.Game.IWD2EE, Collections.singletonList("avatars-iwd2.2da")); TABLE_MAPS.put(Profile.Game.PST, Collections.singletonList("avatars-pst.2da")); @@ -311,6 +314,22 @@ public static int valueToInt(String[] arr, int arrIdx, int defValue) { return retVal; } + /** Convenience method: Converts an array item into a decimal value. */ + public static double valueToDouble(String[] arr, int arrIdx, double defValue) { + double retVal = defValue; + try { + String s = arr[arrIdx]; + if (s.startsWith("0x") || s.startsWith("0X")) { + retVal = valueToInt(arr, arrIdx, (int)defValue); + } else { + retVal = Double.parseDouble(s); + } + } catch (NullPointerException | ArrayIndexOutOfBoundsException | NumberFormatException e) { + Logger.trace(e); + } + return retVal; + } + /** Convenience method: Returns the animation id from the specified array value. */ public static int valueToAnimationId(String[] arr, int arrIdx, int defValue) { try { diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-bg1.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-bg1.2da index 3330ec8f0..35f307d80 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-bg1.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-bg1.2da @@ -1,212 +1,212 @@ 2DA V1.0 * - RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD -0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * -0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * -0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * -0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * -0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * -0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * -0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * -0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * -0x0410 GLPHWRDH 0 0 0 0 * * SPSHADOW * 0 1 * * * * -0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * -0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * -0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * -0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * -0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * -0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * -0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * -0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW -0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * -0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * -0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * -0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * -0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * -0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * -0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * -0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * -0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * -0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * -0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * -0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * -0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * -0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * -0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * -0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * -0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * -0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * -0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * -0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * -0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * -0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * -0x5000 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * -0x5001 CEMC 9 16 3 0 * * * * 1 0 1 * WPM * -0x5002 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * -0x5003 CIMC 9 16 3 0 * * * * 1 0 1 * WPS * -0x5010 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * -0x5011 CEFC 9 16 3 0 * * * * 1 0 1 * WPM * -0x5012 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * -0x5013 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * -0x5100 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * -0x5101 CEMF 9 16 3 0 * * * * 1 0 1 * WPM * -0x5102 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * -0x5103 CIMF 9 16 3 0 * * * * 1 0 1 * WPS * -0x5110 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * -0x5111 CEFF 9 16 3 0 * * * * 1 0 1 * WPM * -0x5112 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * -0x5113 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * -0x5200 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * -0x5201 CEMW 9 16 3 0 * * * * 1 0 1 * WPM * -0x5202 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x5203 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x5210 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * -0x5211 CEFW 9 16 3 0 * * * * 1 0 1 * WPM * -0x5212 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x5213 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x5300 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * -0x5301 CEMT 9 16 3 0 * * * * 1 0 0 * WPM * -0x5302 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * -0x5303 CIMT 9 16 3 0 * * * * 1 0 0 * WPS * -0x5310 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * -0x5311 CEFT 9 16 3 0 * * * * 1 0 0 * WPM * -0x5312 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * -0x5313 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * -0x6000 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * -0x6001 CEMC 9 16 3 0 * * * * 1 0 1 * WPM * -0x6002 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * -0x6003 CIMC 9 16 3 0 * * * * 1 0 1 * WPS * -0x6004 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * -0x6005 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * -0x6010 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * -0x6011 CEFC 9 16 3 0 * * * * 1 0 1 * WPM * -0x6012 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * -0x6013 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * -0x6014 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * -0x6015 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * -0x6100 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * -0x6101 CEMF 9 16 3 0 * * * * 1 0 1 * WPM * -0x6102 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * -0x6103 CIMF 9 16 3 0 * * * * 1 0 1 * WPS * -0x6104 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * -0x6105 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * -0x6110 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * -0x6111 CEFF 9 16 3 0 * * * * 1 0 1 * WPM * -0x6112 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * -0x6113 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * -0x6114 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * -0x6115 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * -0x6200 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * -0x6201 CEMW 9 16 3 0 * * * * 1 0 1 * WPM * -0x6202 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x6203 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x6204 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x6205 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * -0x6210 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * -0x6211 CEFW 9 16 3 0 * * * * 1 0 1 * WPM * -0x6212 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x6213 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x6214 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x6215 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * -0x6300 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * -0x6301 CEMT 9 16 3 0 * * * * 1 0 0 * WPM * -0x6302 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * -0x6303 CIMT 9 16 3 0 * * * * 1 0 0 * WPS * -0x6304 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * -0x6305 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * -0x6310 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * -0x6311 CEFT 9 16 3 0 * * * * 1 0 0 * WPM * -0x6312 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * -0x6313 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * -0x6314 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * -0x6315 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * -0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * -0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * -0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * -0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * -0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * -0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * -0x6406 MDGU 9 24 5 0 * * * * 1 * 1 * WPM * -0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * -0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * -0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * -0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * -0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * -0x7201 MBER 11 16 3 0 * * * * 0 * * * * * -0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * -0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * -0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * -0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * -0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * -0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * -0x7600 METT 11 16 3 0 * * * * 0 * * * * * -0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * -0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * -0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * -0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * -0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * -0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * -0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * -0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * -0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * -0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * -0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * -0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * -0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * -0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * -0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * -0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * -0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * -0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * -0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * -0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * -0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * -0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * -0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * -0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * -0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * -0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB -0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW -0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW -0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * -0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * -0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * -0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * -0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * -0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * -0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * -0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * -0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * -0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * -0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * -0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * -0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * -0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * -0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * -0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * -0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * -0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * -0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * -0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * -0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * -0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * -0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * -0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * -0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * -0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * -0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * -0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * -0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * -0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * -0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * -0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * -0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * -0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * -0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD MOVE_SCALE +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * 0 +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * 0 +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * 0 +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * 0 +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * 0 +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * 0 +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * 0 +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * 0 +0x0410 GLPHWRDH 0 0 0 0 * * SPSHADOW * 0 1 * * * * 0 +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * 0 +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * 0 +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * 0 +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * 0 +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * 0 +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * 8 +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * 8 +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW 6 +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * 6 +0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * 6 +0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * 7 +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * 6 +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * 0 +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * 0 +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * 0 +0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * 0 +0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * 0 +0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * 0 +0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * 0 +0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * 0 +0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x5000 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x5001 CEMC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5002 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5003 CIMC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5010 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5011 CEFC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5012 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5013 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5100 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x5101 CEMF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5102 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5103 CIMF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5110 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5111 CEFF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5112 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5113 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5200 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x5201 CEMW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5202 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5203 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5210 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5211 CEFW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5212 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5213 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5300 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * 9 +0x5301 CEMT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x5302 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x5303 CIMT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x5310 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x5311 CEFT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x5312 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x5313 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x6000 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x6001 CEMC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6002 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6003 CIMC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6004 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6005 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x6010 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6011 CEFC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6012 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6013 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6014 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6015 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6100 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x6101 CEMF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6102 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6103 CIMF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6104 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6105 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x6110 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6111 CEFF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6112 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6113 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6114 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6115 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6200 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x6201 CEMW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6202 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6203 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6204 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6205 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x6210 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6211 CEFW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6212 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6213 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6214 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6215 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6300 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * 9 +0x6301 CEMT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x6302 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x6303 CIMT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x6304 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x6305 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * 9 +0x6310 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x6311 CEFT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x6312 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x6313 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x6314 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x6315 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * 9 +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * 9 +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * 9 +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * 9 +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6406 MDGU 9 24 5 0 * * * * 1 * 1 * WPM * 9 +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * 7 +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * 6 +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * 6 +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * 6 +0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * 9 +0x7201 MBER 11 16 3 0 * * * * 0 * * * * * 9 +0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * 9 +0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * 9 +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * 6 +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * 6 +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * 6 +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * 6 +0x7600 METT 11 16 3 0 * * * * 0 * * * * * 6 +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * 4 +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * 4 +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * 4 +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * 6 +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * 2 +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * 2 +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * 2 +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * 2 +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * 2 +0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * 5 +0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * 5 +0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * 5 +0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * 5 +0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * 5 +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * 8 +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * 10 +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * 8 +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * 8 +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * 8 +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * 8 +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * 6 +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * 6 +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * 4 +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * 10 +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * 10 +0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB 5 +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW 5 +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW 6 +0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * 6 +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * 8 +0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * 6 +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * 0 +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * 0 +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * 0 +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * 0 +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * 0 +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * 0 +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * 0 +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * 0 +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * 0 +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * 0 +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * 3 +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * 4 +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * 6 +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * 6 +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * 5 +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * 5 +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * 5 +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * 5 +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * 6 +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * 6 +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * 6 +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * 6 +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * 6 +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-bg2ee.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-bg2ee.2da index f5176f20a..905c48703 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-bg2ee.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-bg2ee.2da @@ -1,515 +1,515 @@ 2DA V1.0 * - RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD -0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * -0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * -0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * -0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * -0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * -0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * -0x0101 BGLRYU 0 0 3 7 * * * * * 0 * * * * -0x0102 SPCL236U 0 0 3 7 * * * * * 0 * * * * -0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * -0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * -0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * -0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * -0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * -0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0301 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * -0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * -0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * -0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * -0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * -0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * -0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * -0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * -0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * -0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * -0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * -0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * -0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * -0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * -0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * -0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * -0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * -0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * -0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * -0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * -0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * -0x1003 MWYV 1 24 4 0 MWYV_WH * * * 0 * * * * * -0x1004 MWYV 1 24 4 0 MWYV_AL * * * 0 * * * * * -0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * -0x1101 MWDR 1 72 7 0 * * * * 0 * * * * * -0x1102 MTAN 1 32 5 0 MTAN_BL * * * 0 * 1 * * * -0x1103 MTAN 1 32 5 0 MTAN_GR * * * 0 * 1 * * * -0x1104 MTAN 1 32 5 0 MTAN_RD * * * 0 * 1 * * * -0x1105 MWDR 1 72 7 0 MWDR_GR * * * 0 * * * * * -0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * -0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * -0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * -0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * -0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * -0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * -0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * -0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * -0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * -0x1300 MDEM 3 32 5 0 * * * * 0 1 * * * * -0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW -0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * -0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * -0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * -0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * -0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * -0x4001 SNONE 7 0 0 0 * * * * 0 * * * * * -0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * -0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * -0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * -0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * -0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * -0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * -0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * -0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * -0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * -0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * -0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * -0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * -0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * -0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * -0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * -0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * -0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * -0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x5012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * -0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x5112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * -0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x5212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * -0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x5312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * -0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH -0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x6012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * -0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH -0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH -0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x6112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * -0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH -0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH -0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x6212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * -0x6214 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS WQH -0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH -0x6305 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x6312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * -0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH -0x6315 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * -0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * -0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * -0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * -0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * -0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * -0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * -0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * -0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * -0x6621 XHFF 9 16 3 0 * * * * 1 * 1 * WPM * -0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * -0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * -0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * -0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * -0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * -0x7201 MBER 11 16 3 0 * * * * 0 * * * * * -0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * -0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * -0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * -0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * -0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * -0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * -0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * -0x7312 MFIE 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * -0x7313 MFIS 10 16 3 4 MFISG1B MFISG2B * * 0 1 * * * * -0x7314 MFIE 10 16 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * -0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * -0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * -0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * -0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * -0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * -0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * -0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * -0x7600 METT 11 16 3 0 * * * * 0 * * * * * -0x7601 MGHL 11 16 3 0 MGHL_MA * * * 0 0 * * * * -0x7602 MSPI 11 16 3 0 MSPI_MY * * * 0 * * * * * -0x7603 MDOG 11 16 3 0 MDOG_GR * * * 0 * * * * * -0x7604 MSPI 11 16 3 0 MSPI_WA * * * 0 * * * * * -0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * -0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * -0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * -0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * -0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * -0x7801 MGIB 11 16 3 0 MGIB_BR * * * 0 * * * * * -0x7802 MSLI 11 24 4 0 MSLI_BL * * 0 0 * * * * * -0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * -0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * -0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * -0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * -0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * -0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * -0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * -0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * -0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * -0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * -0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * -0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * -0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * -0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * -0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * -0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * -0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * -0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * -0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * -0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * -0x7d01 NSLF 11 16 3 0 * * * * 1 0 * * * * -0x7d02 ACHB 11 12 3 0 * * * * 0 0 * * * * -0x7d03 ACHW 11 12 3 0 * * * * 0 0 * * * * -0x7d04 NPRF 11 16 3 0 * * * * 1 0 * * * * -0x7d05 NNMF 11 16 3 0 * * * * 1 0 * * * * -0x7d06 NNWF 11 16 3 0 * * * * 1 0 * * * * -0x7d07 NSMF 11 16 3 0 * * * * 1 0 * * * * -0x7d08 NSWF 11 16 3 0 * * * * 1 0 * * * * -0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * -0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * -0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * -0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * -0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * -0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * -0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * -0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * -0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * -0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * -0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * -0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * -0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * -0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * -0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * -0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * -0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * -0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * -0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * -0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * -0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * -0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * -0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * -0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * -0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * -0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * -0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * -0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * -0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * -0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * -0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * -0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * -0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * -0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * -0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * -0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * -0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * -0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * -0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * -0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * -0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * -0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * -0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * -0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * -0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * -0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * -0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * -0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * -0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * -0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * -0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * -0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * -0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * -0x7f3b MSOG 10 16 3 4 * * * 0 0 1 * * * * -0x7f3c MASG 10 16 3 4 * * * 0 0 1 * * * * -0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * -0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * -0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * -0x7f40 MGLM 10 16 3 0 * * * * 0 1 * * * * -0x7f41 MDJL 10 16 3 0 MDJL_E1 MDJL_E2 * * 0 1 * * * * -0x7f42 NIRO 10 16 3 0 NIRO_RD * * * 0 1 * * * * -0x7f43 MOTY 10 16 3 0 MOTY_Y1 MOTY_Y2 * * 0 1 * * * * -0x7f44 NELW 10 16 3 0 * * * * 0 0 * * * * -0x7f45 MSAI 10 16 3 0 * * * 1 0 0 * * * * -0x7f46 MBEG 10 16 3 0 * * * * 0 0 * * * * -0x7f47 MFI2 10 32 5 0 * * * * 0 0 * * * * -0x7f48 MSOF 10 16 3 0 * * * 0 0 1 * * * * -0x7f49 MDMF 10 16 3 0 * * * 0 0 1 * * * * -0x7f4a MDAS 10 16 3 7 * * MDAG 0 0 1 * * * * -0x7f4b MDAF 10 16 3 0 * * * 0 0 1 * * * * -0x7f4c MPLN 10 16 3 7 * * MPLG 0 0 1 * * * * -0x7f4d MPLF 10 16 3 0 * * * 0 0 1 * * * * -0x7f4e MDVM 10 16 3 7 * * MDVG 0 0 1 * * * * -0x7f4f MDVF 10 16 3 0 * * * 0 0 1 * * * * -0x7f50 MMST 10 16 3 1 MMST_NI * * 0 0 0 * * * * -0x7f51 MMST 10 16 3 1 MMST_HA * * 0 0 0 * * * * -0x7f52 NSAI 10 16 3 0 NSAI_G1 NSAI_G2 * * 0 1 * * * * -0x7f53 NSAI 10 16 3 0 NSAI_R1 NSAI_R2 * * 0 1 * * * * -0x7f54 NSAI 10 16 3 0 NSAI_D1 NSAI_D2 * * 0 1 * * * * -0x7f55 NSOL 10 16 3 0 NSOL_C1 NSOL_C2 * * 0 1 * * * * -0x7f56 MWIL 10 16 3 7 * * * * 0 0 * * * * -0x7f57 MWIS 10 12 3 7 * * * * 0 0 * * * * -0x7f58 MABB 10 24 3 0 * * * * 0 0 * * * * -0x7f59 MABG 10 24 3 0 * * * * 0 0 * * * * -0x7f5a MABR 10 24 3 0 * * * * 0 0 * * * * -0x7f5b MABR 10 24 3 0 MABR_BL * * * 0 0 * * * * -0x7f5c MHAG 10 16 3 0 * * * * 0 0 * * * * -0x7f5d MHAG 10 16 3 0 MHAG_GR * * * 0 0 * * * * -0x7f5e MHAG 10 16 3 0 MHAG_SE * * * 0 0 * * * * -0x7f5f MHAN 10 16 3 0 * * * * 0 0 * * * * -0x7f60 MSNK 10 16 3 0 MSNK_W1 MSNK_W2 * * 0 1 * * * * -0x7f61 MDRJ 10 48 9 0 * * * * 0 0 * * * * -0x7f62 MDRJ 10 48 9 0 MDRJ_GR * * * 0 0 * * * * -0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB -0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW -0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW -0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * -0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * -0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * -0xa200 MWYV 13 24 4 0 MWYV_WS * * * 0 * * * * * -0xa201 MCEN 13 16 3 0 * * * * 0 * * * * * -0xa202 MTUN 13 16 3 0 * * * * 0 * * * * * -0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * -0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * -0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * -0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * -0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * -0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * -0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * -0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * -0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * -0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * -0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * -0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * -0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * -0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * -0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * -0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * -0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * -0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * -0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * -0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * -0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * -0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * -0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * -0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * -0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * -0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * -0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * -0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * -0xcc00 MKG1 15 16 3 0 * * * * 0 * * * * * -0xcc01 MKG2 15 16 3 0 * * * * 0 * * * * * -0xcc02 MKG3 15 16 3 0 * * * * 0 * * * * * -0xcc04 ARAT 15 0 1 0 * * * * 0 * * * * * -0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * -0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * -0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * -0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xe000 MCYC 17 48 5 0 * * * * * * * * * * -0xe010 METN 17 48 5 0 * * * * * * * * * * -0xe020 MTAN 17 32 5 0 * * * * * * * * * * -0xe040 MHIS 17 16 3 0 * * * * * * * * * * -0xe050 MLER 17 16 3 0 * * * * * * * * * * -0xe060 MLIC 17 16 3 0 * * * * * * * * * * -0xe070 MMIN 17 24 3 0 * * * * * * * * * * -0xe080 MMUM 17 16 3 0 * * * * * * * * * * -0xe090 MTAN 17 16 3 0 * * * * * * * * * * -0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * -0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * -0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * -0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * -0xe0e0 MCOR 17 24 3 0 * * * * * * * * * * -0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * -0xe0f1 MGLA 17 24 3 0 * * * * * * * * * * -0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * -0xe200 MBET 17 24 3 0 * * * * * * * * * * -0xe210 MBFI 17 12 3 0 * * * * * * * * * * -0xe220 MBBM 17 16 3 0 * * * * * * * * * * -0xe230 MBRH 17 64 7 0 * * * * * * * * * * -0xe240 MANI 17 24 3 0 * * * * * * * * * * -0xe241 MAN2 17 24 3 0 * * * * * * * * * * -0xe242 MAN3 17 24 3 0 * * * * * * * * * * -0xe243 MARU 17 16 3 0 * * * * * * * * * * -0xe244 MBA1 17 16 3 0 * * * * * * * * * * -0xe245 MBA2 17 16 3 0 * * * * * * * * * * -0xe246 MBA3 17 16 3 0 * * * * * * * * * * -0xe247 MBA4 17 16 3 0 * * * * * * * * * * -0xe248 MBA5 17 16 3 0 * * * * * * * * * * -0xe249 MBA6 17 16 3 0 * * * * * * * * * * -0xe24a MBAI 17 16 3 0 * * * * * * * * * * -0xe24b MELE 17 36 3 0 * * * * * * * * * * -0xe24c MELF 17 36 3 0 * * * * * * * * * * -0xe24d MELW 17 36 3 0 * * * * * * * * * * -0xe24e MGFI 17 48 3 0 * * * * * * * * * * -0xe24f MGFR 17 48 5 0 * * * * * * * * * * -0xe250 MGIR 17 24 3 0 * * * * * * * * * * -0xe251 MGVE 17 32 5 0 * * * * * * * * * * -0xe252 MHAR 17 16 3 0 * * * * * * * * * * -0xe253 MREM 17 48 5 0 * * * * * * * * * * -0xe254 MSCR 17 24 3 0 * * * * * * * * * * -0xe255 MSEE 17 16 3 0 * * * * * * * * * * -0xe259 MSVI 17 16 3 0 * * * * * * * * * * -0xe25a MSV2 17 16 3 0 * * * * * * * * * * -0xe25b MUM2 17 24 3 0 * * * * * * * * * * -0xe25c MTA2 17 16 3 0 * * * * * * * * * * -0xe25d MYET 17 24 3 0 * * * * * * * * * * -0xe25e MWI4 17 16 3 0 * * * * * * * * * * -0xe25f MDRD 17 16 3 0 * * * * * * * * * * -0xe260 MCRD 17 24 3 0 * * * * * * * * * * -0xe261 MSAH 17 24 3 0 * * * * * * * * * * -0xe262 MSAT 17 24 3 0 * * * * * * * * * * -0xe263 MSV3 17 16 3 0 * * * * * * * * * * -0xe264 MSV4 17 16 3 0 * * * * * * * * * * -0xe265 MBOA 17 24 3 0 * * * * * * * * * * -0xe266 MBU2 17 16 3 0 * * * * * * * * * * -0xe267 MBUG 17 16 3 0 * * * * * * * * * * -0xe26a MDH2 17 24 3 0 * * * * * * * * * * -0xe26b MDTR 17 24 3 0 * * * * * * * * * * -0xe26d MFE2 17 24 3 0 * * * * * * * * * * -0xe26e MFEY 17 24 3 0 * * * * * * * * * * -0xe26f MGFO 17 24 4 0 * * * * * * * * * * -0xe270 MGO5 17 16 3 0 * * * * * * * * * * -0xe271 MGOC 17 16 3 0 * * * * * * * * * * -0xe272 MGWO 17 24 3 0 * * * * * * * * * * -0xe273 MGW2 17 24 3 0 * * * * * * * * * * -0xe274 MHOH 17 24 3 0 * * * * * * * * * * -0xe276 MLEM 17 16 3 0 * * * * * * * * * * -0xe279 MNOS 17 24 3 0 * * * * * * * * * * -0xe27d MWEB 17 16 3 0 * * * * * * * * * * -0xe27e MWRA 17 16 3 0 * * * * * * * * * * -0xe27f MBOA 17 24 3 0 MBOA_BR * * * * * * * * * -0xe280 MWOR 17 24 3 0 * * * * * * * * * * -0xe281 MYUH 17 16 3 0 * * * * * * * * * * -0xe282 MDRF 17 32 4 0 * * * * * * * * * * -0xe283 MDRM 17 32 4 0 * * * * * * * * * * -0xe288 MGWO 17 24 3 0 MGWO_DK * * * * * * * * * -0xe289 MGW2 17 24 3 0 MGW2_DK * * * * * * * * * -0xe28a MTRO 17 16 3 0 MTRO_DK * * * * * * * * * -0xe28b MABW 17 24 3 0 * * * * * * * * * * -0xe28c MWD2 17 36 5 0 * * * * * * * * * * -0xe28d MWD2 17 36 5 0 MWD2_SI * * * * * * * * * -0xe28e MWD2 17 36 5 0 MWD2_GR * * * * * * * * * -0xe28f MDRD 17 16 3 0 MDRD_RE * * * * * * * * * -0xe290 MDH2 17 24 3 0 MDH2_GR * * * * * * * * * -0xe291 MBU2 17 16 3 0 MBU2_SH * * * * * * * * * -0xe292 METN 17 48 5 0 METN_GH * * * * * * * * * -0xe293 MGHI 17 40 5 0 * * * * * * * * * * -0xe294 MBON 17 16 3 0 * * * * * * * * * * -0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * -0xe310 MGH2 17 16 3 0 * * * * * * * * * * -0xe320 MGH3 17 16 3 0 * * * * * * * * * * -0xe400 MGO1 17 16 3 0 * * * * * * * * * * -0xe410 MGO2 17 16 3 0 * * * * * * * * * * -0xe420 MGO3 17 16 3 0 * * * * * * * * * * -0xe430 MGO4 17 16 3 0 * * * * * * * * * * -0xe440 MGO6 17 16 3 0 * * * * * * * * * * -0xe441 MGO7 17 16 3 0 * * * * * * * * * * -0xe442 MGO8 17 16 3 0 * * * * * * * * * * -0xe443 MGO9 17 16 3 0 * * * * * * * * * * -0xe444 MGO10 17 16 3 0 * * * * * * * * * * -0xe500 MLIZ 17 24 3 0 * * * * * * * * * * -0xe510 MLI2 17 24 3 0 * * * * * * * * * * -0xe520 MLI3 17 32 5 0 * * * * * * * * * * -0xe600 MMYC 17 16 3 0 * * * * * * * * * * -0xe610 MMY2 17 16 3 0 * * * * * * * * * * -0xe700 MNO1 17 20 3 0 * * * * * * * * * * -0xe710 MNO2 17 20 3 0 * * * * * * * * * * -0xe720 MNO3 17 24 3 0 * * * * * * * * * * -0xe800 MOR1 17 16 3 0 * * * * * * * * * * -0xe810 MOR2 17 16 3 0 * * * * * * * * * * -0xe820 MOR3 17 16 3 0 * * * * * * * * * * -0xe830 MOR4 17 16 3 0 * * * * * * * * * * -0xe840 MOR5 17 16 3 0 * * * * * * * * * * -0xe900 MSAL 17 16 3 0 * * * * * * * * * * -0xe910 MSA2 17 16 3 0 * * * * * * * * * * -0xea00 MSHR 17 24 3 0 * * * * * * * * * * -0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * -0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * -0xeb00 MSKT 17 16 3 0 * * * * * * * * * * -0xeb10 MSKA 17 16 3 0 * * * * * * * * * * -0xeb20 MSKB 17 24 3 0 * * * * * * * * * * -0xec00 MWIG 17 16 3 0 * * * * * * * * * * -0xec10 MWI2 17 16 3 0 * * * * * * * * * * -0xec20 MWI3 17 16 3 0 * * * * * * * * * * -0xed00 MYU1 17 16 3 0 * * * * * * * * * * -0xed10 MYU2 17 16 3 0 * * * * * * * * * * -0xed20 MYU3 17 16 3 0 * * * * * * * * * * -0xee00 MZO2 17 16 3 0 * * * * * * * * * * -0xee10 MZO3 17 16 3 0 * * * * * * * * * * -0xef10 MWWE 17 24 3 0 * * * * * * * * * * + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD MOVE_SCALE +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * 0 +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * 0 +0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * 0 +0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * 0 +0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * 0 +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * 0 +0x0101 BGLRYU 0 0 3 7 * * * * * 0 * * * * 0 +0x0102 SPCL236U 0 0 3 7 * * * * * 0 * * * * 0 +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * 0 +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * 0 +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * 0 +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * 0 +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * 0 +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0301 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * 0 +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * 0 +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * 0 +0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * 0 +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * 0 +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * 0 +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * 0 +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * 0 +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * 0 +0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * 0 +0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * 0 +0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * 0 +0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * 8 +0x1003 MWYV 1 24 4 0 MWYV_WH * * * 0 * * * * * 8 +0x1004 MWYV 1 24 4 0 MWYV_AL * * * 0 * * * * * 8 +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * 8 +0x1101 MWDR 1 72 7 0 * * * * 0 * * * * * 12 +0x1102 MTAN 1 32 5 0 MTAN_BL * * * 0 * 1 * * * 8 +0x1103 MTAN 1 32 5 0 MTAN_GR * * * 0 * 1 * * * 8 +0x1104 MTAN 1 32 5 0 MTAN_RD * * * 0 * 1 * * * 8 +0x1105 MWDR 1 72 7 0 MWDR_GR * * * 0 * * * * * 12 +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * 14 +0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * 14 +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * 14 +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * 14 +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * 14 +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * 14 +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * 14 +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * 14 +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * 14 +0x1300 MDEM 3 32 5 0 * * * * 0 1 * * * * 10 +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW 6 +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * 6 +0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * 6 +0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * 7 +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * 6 +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4001 SNONE 7 0 0 0 * * * * 0 * * * * * 0 +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * 0 +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * 0 +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * 0 +0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * 0 +0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * 0 +0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * 0 +0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * 0 +0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * 0 +0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x5012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * 9 +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x5112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * 9 +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x5212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * 9 +0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x5312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * 9 +0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH 9 +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x6012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * 9 +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH 9 +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH 9 +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x6112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * 9 +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH 9 +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH 9 +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x6212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * 9 +0x6214 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS WQH 9 +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH 9 +0x6305 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x6312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * 9 +0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH 9 +0x6315 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * 9 +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * 9 +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * 9 +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * 9 +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * 9 +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * 9 +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * 9 +0x6621 XHFF 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * 7 +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * 6 +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * 6 +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * 6 +0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * 9 +0x7201 MBER 11 16 3 0 * * * * 0 * * * * * 9 +0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * 9 +0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * 9 +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * 10 +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * 7 +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * 10 +0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * 10 +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * 7 +0x7312 MFIE 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * 10 +0x7313 MFIS 10 16 3 4 MFISG1B MFISG2B * * 0 1 * * * * 7 +0x7314 MFIE 10 16 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * 10 +0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * 10 +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * 7 +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * 6 +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * 6 +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * 6 +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * 6 +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * 6 +0x7600 METT 11 16 3 0 * * * * 0 * * * * * 6 +0x7601 MGHL 11 16 3 0 MGHL_MA * * * 0 0 * * * * 4 +0x7602 MSPI 11 16 3 0 MSPI_MY * * * 0 * * * * * 5 +0x7603 MDOG 11 16 3 0 MDOG_GR * * * 0 * * * * * 6 +0x7604 MSPI 11 16 3 0 MSPI_WA * * * 0 * * * * * 5 +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * 4 +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * 4 +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * 4 +0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * 9 +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * 6 +0x7801 MGIB 11 16 3 0 MGIB_BR * * * 0 * * * * * 6 +0x7802 MSLI 11 24 4 0 MSLI_BL * * 0 0 * * * * * 2 +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * 2 +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * 2 +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * 2 +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * 2 +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * 2 +0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * 5 +0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * 5 +0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * 5 +0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * 5 +0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * 5 +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * 8 +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * 10 +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * 8 +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * 8 +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * 8 +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * 8 +0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * 8 +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * 6 +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * 6 +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * 4 +0x7d01 NSLF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d02 ACHB 11 12 3 0 * * * * 0 0 * * * * 3 +0x7d03 ACHW 11 12 3 0 * * * * 0 0 * * * * 3 +0x7d04 NPRF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d05 NNMF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d06 NNWF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d07 NSMF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d08 NSWF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * 10 +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * 10 +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * 8 +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * 13 +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * 4 +0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * 2 +0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * 12 +0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * 14 +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * 14 +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * 5 +0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * 7 +0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * 9 +0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * 9 +0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f3b MSOG 10 16 3 4 * * * 0 0 1 * * * * 9 +0x7f3c MASG 10 16 3 4 * * * 0 0 1 * * * * 9 +0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * 9 +0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * 9 +0x7f40 MGLM 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f41 MDJL 10 16 3 0 MDJL_E1 MDJL_E2 * * 0 1 * * * * 11 +0x7f42 NIRO 10 16 3 0 NIRO_RD * * * 0 1 * * * * 9 +0x7f43 MOTY 10 16 3 0 MOTY_Y1 MOTY_Y2 * * 0 1 * * * * 8 +0x7f44 NELW 10 16 3 0 * * * * 0 0 * * * * 7 +0x7f45 MSAI 10 16 3 0 * * * 1 0 0 * * * * 9 +0x7f46 MBEG 10 16 3 0 * * * * 0 0 * * * * 8 +0x7f47 MFI2 10 32 5 0 * * * * 0 0 * * * * 9 +0x7f48 MSOF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f49 MDMF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f4a MDAS 10 16 3 7 * * MDAG 0 0 1 * * * * 9 +0x7f4b MDAF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f4c MPLN 10 16 3 7 * * MPLG 0 0 1 * * * * 9 +0x7f4d MPLF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f4e MDVM 10 16 3 7 * * MDVG 0 0 1 * * * * 9 +0x7f4f MDVF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f50 MMST 10 16 3 1 MMST_NI * * 0 0 0 * * * * 9 +0x7f51 MMST 10 16 3 1 MMST_HA * * 0 0 0 * * * * 9 +0x7f52 NSAI 10 16 3 0 NSAI_G1 NSAI_G2 * * 0 1 * * * * 5 +0x7f53 NSAI 10 16 3 0 NSAI_R1 NSAI_R2 * * 0 1 * * * * 5 +0x7f54 NSAI 10 16 3 0 NSAI_D1 NSAI_D2 * * 0 1 * * * * 5 +0x7f55 NSOL 10 16 3 0 NSOL_C1 NSOL_C2 * * 0 1 * * * * 9 +0x7f56 MWIL 10 16 3 7 * * * * 0 0 * * * * 12 +0x7f57 MWIS 10 12 3 7 * * * * 0 0 * * * * 10 +0x7f58 MABB 10 24 3 0 * * * * 0 0 * * * * 8 +0x7f59 MABG 10 24 3 0 * * * * 0 0 * * * * 9 +0x7f5a MABR 10 24 3 0 * * * * 0 0 * * * * 10 +0x7f5b MABR 10 24 3 0 MABR_BL * * * 0 0 * * * * 10 +0x7f5c MHAG 10 16 3 0 * * * * 0 0 * * * * 8 +0x7f5d MHAG 10 16 3 0 MHAG_GR * * * 0 0 * * * * 12 +0x7f5e MHAG 10 16 3 0 MHAG_SE * * * 0 0 * * * * 14 +0x7f5f MHAN 10 16 3 0 * * * * 0 0 * * * * 14 +0x7f60 MSNK 10 16 3 0 MSNK_W1 MSNK_W2 * * 0 1 * * * * 13 +0x7f61 MDRJ 10 48 9 0 * * * * 0 0 * * * * 11 +0x7f62 MDRJ 10 48 9 0 MDRJ_GR * * * 0 0 * * * * 11 +0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB 5 +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW 5 +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW 6 +0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * 6 +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * 8 +0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * 6 +0xa200 MWYV 13 24 4 0 MWYV_WS * * * 0 * * * * * 8 +0xa201 MCEN 13 16 3 0 * * * * 0 * * * * * 11 +0xa202 MTUN 13 16 3 0 * * * * 0 * * * * * 6 +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * 0 +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * 0 +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * 0 +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * 0 +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * 0 +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * 0 +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * 0 +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * 0 +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * 0 +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * 0 +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * 3 +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * 4 +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * 6 +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * 6 +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * 5 +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * 5 +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * 5 +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * 5 +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * 6 +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * 6 +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * 6 +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * 6 +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * 6 +0xcc00 MKG1 15 16 3 0 * * * * 0 * * * * * 0 +0xcc01 MKG2 15 16 3 0 * * * * 0 * * * * * 0 +0xcc02 MKG3 15 16 3 0 * * * * 0 * * * * * 0 +0xcc04 ARAT 15 0 1 0 * * * * 0 * * * * * 4 +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xe000 MCYC 17 48 5 0 * * * * * * * * * * 4 +0xe010 METN 17 48 5 0 * * * * * * * * * * 4 +0xe020 MTAN 17 32 5 0 * * * * * * * * * * 4 +0xe040 MHIS 17 16 3 0 * * * * * * * * * * 4 +0xe050 MLER 17 16 3 0 * * * * * * * * * * 4 +0xe060 MLIC 17 16 3 0 * * * * * * * * * * 4 +0xe070 MMIN 17 24 3 0 * * * * * * * * * * 4 +0xe080 MMUM 17 16 3 0 * * * * * * * * * * 4 +0xe090 MTAN 17 16 3 0 * * * * * * * * * * 4 +0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * 4 +0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * 4 +0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * 4 +0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * 4 +0xe0e0 MCOR 17 24 3 0 * * * * * * * * * * 9 +0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * 9 +0xe0f1 MGLA 17 24 3 0 * * * * * * * * * * 9 +0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * 5 +0xe200 MBET 17 24 3 0 * * * * * * * * * * 7 +0xe210 MBFI 17 12 3 0 * * * * * * * * * * 7 +0xe220 MBBM 17 16 3 0 * * * * * * * * * * 7 +0xe230 MBRH 17 64 7 0 * * * * * * * * * * 7 +0xe240 MANI 17 24 3 0 * * * * * * * * * * 7 +0xe241 MAN2 17 24 3 0 * * * * * * * * * * 7 +0xe242 MAN3 17 24 3 0 * * * * * * * * * * 7 +0xe243 MARU 17 16 3 0 * * * * * * * * * * 7 +0xe244 MBA1 17 16 3 0 * * * * * * * * * * 8 +0xe245 MBA2 17 16 3 0 * * * * * * * * * * 8 +0xe246 MBA3 17 16 3 0 * * * * * * * * * * 8 +0xe247 MBA4 17 16 3 0 * * * * * * * * * * 8 +0xe248 MBA5 17 16 3 0 * * * * * * * * * * 8 +0xe249 MBA6 17 16 3 0 * * * * * * * * * * 8 +0xe24a MBAI 17 16 3 0 * * * * * * * * * * 8 +0xe24b MELE 17 36 3 0 * * * * * * * * * * 7 +0xe24c MELF 17 36 3 0 * * * * * * * * * * 7 +0xe24d MELW 17 36 3 0 * * * * * * * * * * 7 +0xe24e MGFI 17 48 3 0 * * * * * * * * * * 7 +0xe24f MGFR 17 48 5 0 * * * * * * * * * * 7 +0xe250 MGIR 17 24 3 0 * * * * * * * * * * 8 +0xe251 MGVE 17 32 5 0 * * * * * * * * * * 7 +0xe252 MHAR 17 16 3 0 * * * * * * * * * * 8 +0xe253 MREM 17 48 5 0 * * * * * * * * * * 8 +0xe254 MSCR 17 24 3 0 * * * * * * * * * * 8 +0xe255 MSEE 17 16 3 0 * * * * * * * * * * 5 +0xe259 MSVI 17 16 3 0 * * * * * * * * * * 7 +0xe25a MSV2 17 16 3 0 * * * * * * * * * * 7 +0xe25b MUM2 17 24 3 0 * * * * * * * * * * 7 +0xe25c MTA2 17 16 3 0 * * * * * * * * * * 8 +0xe25d MYET 17 24 3 0 * * * * * * * * * * 7 +0xe25e MWI4 17 16 3 0 * * * * * * * * * * 15 +0xe25f MDRD 17 16 3 0 * * * * * * * * * * 4 +0xe260 MCRD 17 24 3 0 * * * * * * * * * * 8 +0xe261 MSAH 17 24 3 0 * * * * * * * * * * 8 +0xe262 MSAT 17 24 3 0 * * * * * * * * * * 8 +0xe263 MSV3 17 16 3 0 * * * * * * * * * * 7 +0xe264 MSV4 17 16 3 0 * * * * * * * * * * 7 +0xe265 MBOA 17 24 3 0 * * * * * * * * * * 10 +0xe266 MBU2 17 16 3 0 * * * * * * * * * * 8 +0xe267 MBUG 17 16 3 0 * * * * * * * * * * 8 +0xe26a MDH2 17 24 3 0 * * * * * * * * * * 10 +0xe26b MDTR 17 24 3 0 * * * * * * * * * * 6 +0xe26d MFE2 17 24 3 0 * * * * * * * * * * 8 +0xe26e MFEY 17 24 3 0 * * * * * * * * * * 8 +0xe26f MGFO 17 24 4 0 * * * * * * * * * * 8 +0xe270 MGO5 17 16 3 0 * * * * * * * * * * 8 +0xe271 MGOC 17 16 3 0 * * * * * * * * * * 8 +0xe272 MGWO 17 24 3 0 * * * * * * * * * * 10 +0xe273 MGW2 17 24 3 0 * * * * * * * * * * 10 +0xe274 MHOH 17 24 3 0 * * * * * * * * * * 9 +0xe276 MLEM 17 16 3 0 * * * * * * * * * * 6 +0xe279 MNOS 17 24 3 0 * * * * * * * * * * 7 +0xe27d MWEB 17 16 3 0 * * * * * * * * * * 8 +0xe27e MWRA 17 16 3 0 * * * * * * * * * * 8 +0xe27f MBOA 17 24 3 0 MBOA_BR * * * * * * * * * 10 +0xe280 MWOR 17 24 3 0 * * * * * * * * * * 10 +0xe281 MYUH 17 16 3 0 * * * * * * * * * * 8 +0xe282 MDRF 17 32 4 0 * * * * * * * * * * 8 +0xe283 MDRM 17 32 4 0 * * * * * * * * * * 8 +0xe288 MGWO 17 24 3 0 MGWO_DK * * * * * * * * * 10 +0xe289 MGW2 17 24 3 0 MGW2_DK * * * * * * * * * 10 +0xe28a MTRO 17 16 3 0 MTRO_DK * * * * * * * * * 8 +0xe28b MABW 17 24 3 0 * * * * * * * * * * 8 +0xe28c MWD2 17 36 5 0 * * * * * * * * * * 9 +0xe28d MWD2 17 36 5 0 MWD2_SI * * * * * * * * * 9 +0xe28e MWD2 17 36 5 0 MWD2_GR * * * * * * * * * 9 +0xe28f MDRD 17 16 3 0 MDRD_RE * * * * * * * * * 4 +0xe290 MDH2 17 24 3 0 MDH2_GR * * * * * * * * * 10 +0xe291 MBU2 17 16 3 0 MBU2_SH * * * * * * * * * 8 +0xe292 METN 17 48 5 0 METN_GH * * * * * * * * * 8 +0xe293 MGHI 17 40 5 0 * * * * * * * * * * 8 +0xe294 MBON 17 16 3 0 * * * * * * * * * * 10 +0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * 7 +0xe310 MGH2 17 16 3 0 * * * * * * * * * * 7 +0xe320 MGH3 17 16 3 0 * * * * * * * * * * 7 +0xe400 MGO1 17 16 3 0 * * * * * * * * * * 7 +0xe410 MGO2 17 16 3 0 * * * * * * * * * * 7 +0xe420 MGO3 17 16 3 0 * * * * * * * * * * 7 +0xe430 MGO4 17 16 3 0 * * * * * * * * * * 7 +0xe440 MGO6 17 16 3 0 * * * * * * * * * * 9 +0xe441 MGO7 17 16 3 0 * * * * * * * * * * 9 +0xe442 MGO8 17 16 3 0 * * * * * * * * * * 9 +0xe443 MGO9 17 16 3 0 * * * * * * * * * * 9 +0xe444 MGO10 17 16 3 0 * * * * * * * * * * 9 +0xe500 MLIZ 17 24 3 0 * * * * * * * * * * 4 +0xe510 MLI2 17 24 3 0 * * * * * * * * * * 4 +0xe520 MLI3 17 32 5 0 * * * * * * * * * * 4 +0xe600 MMYC 17 16 3 0 * * * * * * * * * * 7 +0xe610 MMY2 17 16 3 0 * * * * * * * * * * 7 +0xe700 MNO1 17 20 3 0 * * * * * * * * * * 6 +0xe710 MNO2 17 20 3 0 * * * * * * * * * * 6 +0xe720 MNO3 17 24 3 0 * * * * * * * * * * 6 +0xe800 MOR1 17 16 3 0 * * * * * * * * * * 7 +0xe810 MOR2 17 16 3 0 * * * * * * * * * * 7 +0xe820 MOR3 17 16 3 0 * * * * * * * * * * 7 +0xe830 MOR4 17 16 3 0 * * * * * * * * * * 7 +0xe840 MOR5 17 16 3 0 * * * * * * * * * * 7 +0xe900 MSAL 17 16 3 0 * * * * * * * * * * 7 +0xe910 MSA2 17 16 3 0 * * * * * * * * * * 7 +0xea00 MSHR 17 24 3 0 * * * * * * * * * * 0 +0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * 7 +0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * 7 +0xeb00 MSKT 17 16 3 0 * * * * * * * * * * 6 +0xeb10 MSKA 17 16 3 0 * * * * * * * * * * 6 +0xeb20 MSKB 17 24 3 0 * * * * * * * * * * 6 +0xec00 MWIG 17 16 3 0 * * * * * * * * * * 6 +0xec10 MWI2 17 16 3 0 * * * * * * * * * * 6 +0xec20 MWI3 17 16 3 0 * * * * * * * * * * 6 +0xed00 MYU1 17 16 3 0 * * * * * * * * * * 7 +0xed10 MYU2 17 16 3 0 * * * * * * * * * * 7 +0xed20 MYU3 17 16 3 0 * * * * * * * * * * 7 +0xee00 MZO2 17 16 3 0 * * * * * * * * * * 3 +0xee10 MZO3 17 16 3 0 * * * * * * * * * * 3 +0xef10 MWWE 17 24 3 0 * * * * * * * * * * 7 diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-bg2soa.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-bg2soa.2da index a926cd892..0005e988b 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-bg2soa.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-bg2soa.2da @@ -1,357 +1,357 @@ 2DA V1.0 * - RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD -0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * -0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * -0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * -0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * -0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * -0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * -0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * -0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * -0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * -0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * -0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * -0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * -0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * -0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * -0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * -0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * -0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * -0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * -0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * -0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * -0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * -0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * -0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * -0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * -0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * -0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * -0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * -0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * -0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * -0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * -0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * -0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * -0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * -0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * -0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * -0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * -0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * -0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * -0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * -0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * -0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW -0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * -0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * -0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * -0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * -0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * -0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * -0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * -0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * -0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * -0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * -0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * -0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * -0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * -0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * -0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * -0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * -0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * -0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * -0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * -0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * -0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * -0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * -0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x5012 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x5112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x5212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x5300 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x5301 CEMB 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x5302 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x5303 CIMB 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x5310 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x5311 CEFB 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x5312 CDFB 8 16 3 0 * * CDFT * 1 1 0 * WQS * -0x5313 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH -0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x6012 CDMB 8 16 3 0 * * CDFC * 1 1 1 * WQS * -0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH -0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH -0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x6112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH -0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH -0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x6212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x6214 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH -0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6300 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6301 CEMB 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x6302 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x6303 CIMB 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x6304 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH -0x6305 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6310 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6311 CEFB 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x6312 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x6313 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6314 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH -0x6315 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * -0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * -0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * -0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * -0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * -0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * -0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * -0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * -0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * -0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * -0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * -0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * -0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * -0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * -0x7201 MBER 11 16 3 0 * * * * 0 * * * * * -0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * -0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * -0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * -0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * -0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * -0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * -0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * -0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * -0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * -0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * -0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * -0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * -0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * -0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * -0x7600 METT 11 16 3 0 * * * * 0 * * * * * -0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * -0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * -0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * -0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * -0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * -0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * -0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * -0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * -0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * -0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * -0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * -0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * -0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * -0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * -0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * -0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * -0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * -0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * -0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * -0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * -0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * -0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * -0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * -0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * -0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * -0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * -0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * -0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * -0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * -0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * -0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * -0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * -0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * -0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * -0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * -0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * -0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * -0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * -0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * -0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * -0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * -0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * -0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * -0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * -0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * -0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * -0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * -0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * -0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * -0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * -0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * -0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * -0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * -0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * -0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * -0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * -0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * -0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * -0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * -0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * -0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * -0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * -0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * -0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * -0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * -0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * -0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * -0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * -0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * -0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * -0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * -0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * -0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * -0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * -0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * -0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * -0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * -0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * -0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB -0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW -0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW -0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * -0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * -0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * -0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * -0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * -0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * -0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * -0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * -0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * -0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * -0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * -0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * -0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * -0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * -0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * -0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * -0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * -0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * -0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * -0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * -0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * -0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * -0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * -0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * -0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * -0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * -0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * -0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * -0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * -0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * -0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * -0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * -0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * -0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * -0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xe000 MCYC 17 48 5 0 * * * * * * * * * * -0xe010 METN 17 48 5 0 * * * * * * * * * * -0xe020 MGFR 17 32 5 0 * * * * * * * * * * -0xe040 MHIS 17 16 3 0 * * * * * * * * * * -0xe050 MLER 17 16 3 0 * * * * * * * * * * -0xe060 MLIC 17 16 3 0 * * * * * * * * * * -0xe070 MMIN 17 24 3 0 * * * * * * * * * * -0xe080 MMUM 17 16 3 0 * * * * * * * * * * -0xe090 MTAN 17 16 3 0 * * * * * * * * * * -0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * -0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * -0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * -0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * -0xe200 MBET 17 24 3 0 * * * * * * * * * * -0xe210 MBFI 17 12 3 0 * * * * * * * * * * -0xe220 MBBM 17 16 3 0 * * * * * * * * * * -0xe230 MBRH 17 64 7 0 * * * * * * * * * * -0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * -0xe310 MGH2 17 16 3 0 * * * * * * * * * * -0xe320 MGH3 17 16 3 0 * * * * * * * * * * -0xe400 MGO1 17 16 3 0 * * * * * * * * * * -0xe410 MGO2 17 16 3 0 * * * * * * * * * * -0xe420 MGO3 17 16 3 0 * * * * * * * * * * -0xe430 MGO4 17 16 3 0 * * * * * * * * * * -0xe500 MLIZ 17 24 3 0 * * * * * * * * * * -0xe510 MLI2 17 24 3 0 * * * * * * * * * * -0xe520 MLI3 17 32 5 0 * * * * * * * * * * -0xe600 MMYC 17 16 3 0 * * * * * * * * * * -0xe610 MMY2 17 16 3 0 * * * * * * * * * * -0xe700 MNO1 17 20 3 0 * * * * * * * * * * -0xe710 MNO2 17 20 3 0 * * * * * * * * * * -0xe720 MNO3 17 24 3 0 * * * * * * * * * * -0xe800 MOR1 17 16 3 0 * * * * * * * * * * -0xe810 MOR2 17 16 3 0 * * * * * * * * * * -0xe820 MOR3 17 16 3 0 * * * * * * * * * * -0xe830 MOR4 17 16 3 0 * * * * * * * * * * -0xe840 MOR5 17 16 3 0 * * * * * * * * * * -0xe900 MSAL 17 16 3 0 * * * * * * * * * * -0xe910 MSA2 17 16 3 0 * * * * * * * * * * -0xea00 MSHR 17 24 3 0 * * * * * * * * * * -0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * -0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * -0xeb00 MSKT 17 16 3 0 * * * * * * * * * * -0xeb10 MSKA 17 16 3 0 * * * * * * * * * * -0xeb20 MSKB 17 24 3 0 * * * * * * * * * * -0xec00 MWIG 17 16 3 0 * * * * * * * * * * -0xec10 MWI2 17 16 3 0 * * * * * * * * * * -0xec20 MWI3 17 16 3 0 * * * * * * * * * * -0xed00 MYU1 17 16 3 0 * * * * * * * * * * -0xed10 MYU2 17 16 3 0 * * * * * * * * * * -0xed20 MYU3 17 16 3 0 * * * * * * * * * * -0xee00 MZO2 17 16 3 0 * * * * * * * * * * -0xee10 MZO3 17 16 3 0 * * * * * * * * * * + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD MOVE_SCALE +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * 0 +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * 0 +0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * 0 +0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * 0 +0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * 0 +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * 0 +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * 0 +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * 0 +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * 0 +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * 0 +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * 0 +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * 0 +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * 0 +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * 0 +0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * 0 +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * 0 +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * 0 +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * 0 +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * 0 +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * 0 +0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * 0 +0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * 8 +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * 8 +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * 14 +0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * 14 +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * 14 +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * 14 +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * 14 +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * 14 +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * 14 +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * 14 +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * 14 +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW 6 +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * 6 +0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * 6 +0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * 7 +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * 6 +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * 0 +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * 0 +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * 0 +0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * 0 +0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * 0 +0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * 0 +0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * 0 +0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * 0 +0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x5012 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x5112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x5212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x5300 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x5301 CEMB 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x5302 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x5303 CIMB 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x5310 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x5311 CEFB 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x5312 CDFB 8 16 3 0 * * CDFT * 1 1 0 * WQS * 9 +0x5313 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH 9 +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x6012 CDMB 8 16 3 0 * * CDFC * 1 1 1 * WQS * 9 +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH 9 +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH 9 +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x6112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH 9 +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH 9 +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x6212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x6214 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH 9 +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6300 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6301 CEMB 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x6302 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x6303 CIMB 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x6304 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH 9 +0x6305 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6310 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6311 CEFB 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x6312 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x6313 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6314 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH 9 +0x6315 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * 9 +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * 9 +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * 9 +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * 9 +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * 9 +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * 9 +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * 9 +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * 7 +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * 6 +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * 6 +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * 6 +0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * 9 +0x7201 MBER 11 16 3 0 * * * * 0 * * * * * 9 +0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * 9 +0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * 9 +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * 10 +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * 7 +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * 10 +0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * 10 +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * 7 +0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * 10 +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * 7 +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * 6 +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * 6 +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * 6 +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * 6 +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * 6 +0x7600 METT 11 16 3 0 * * * * 0 * * * * * 6 +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * 4 +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * 4 +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * 4 +0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * 9 +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * 6 +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * 2 +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * 2 +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * 2 +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * 2 +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * 2 +0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * 5 +0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * 5 +0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * 5 +0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * 5 +0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * 5 +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * 8 +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * 10 +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * 8 +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * 8 +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * 8 +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * 8 +0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * 8 +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * 6 +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * 6 +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * 4 +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * 10 +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * 10 +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * 8 +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * 13 +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * 4 +0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * 2 +0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * 12 +0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * 14 +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * 14 +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * 5 +0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * 7 +0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * 9 +0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * 9 +0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB 5 +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW 5 +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW 6 +0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * 6 +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * 8 +0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * 6 +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * 0 +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * 0 +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * 0 +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * 0 +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * 0 +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * 0 +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * 0 +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * 0 +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * 0 +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * 0 +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * 3 +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * 4 +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * 6 +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * 6 +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * 5 +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * 5 +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * 5 +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * 5 +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * 6 +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * 6 +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * 6 +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * 6 +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * 6 +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xe000 MCYC 17 48 5 0 * * * * * * * * * * 4 +0xe010 METN 17 48 5 0 * * * * * * * * * * 4 +0xe020 MGFR 17 32 5 0 * * * * * * * * * * 4 +0xe040 MHIS 17 16 3 0 * * * * * * * * * * 4 +0xe050 MLER 17 16 3 0 * * * * * * * * * * 4 +0xe060 MLIC 17 16 3 0 * * * * * * * * * * 4 +0xe070 MMIN 17 24 3 0 * * * * * * * * * * 4 +0xe080 MMUM 17 16 3 0 * * * * * * * * * * 4 +0xe090 MTAN 17 16 3 0 * * * * * * * * * * 4 +0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * 4 +0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * 4 +0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * 4 +0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * 4 +0xe200 MBET 17 24 3 0 * * * * * * * * * * 7 +0xe210 MBFI 17 12 3 0 * * * * * * * * * * 7 +0xe220 MBBM 17 16 3 0 * * * * * * * * * * 7 +0xe230 MBRH 17 64 7 0 * * * * * * * * * * 7 +0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * 7 +0xe310 MGH2 17 16 3 0 * * * * * * * * * * 7 +0xe320 MGH3 17 16 3 0 * * * * * * * * * * 7 +0xe400 MGO1 17 16 3 0 * * * * * * * * * * 7 +0xe410 MGO2 17 16 3 0 * * * * * * * * * * 7 +0xe420 MGO3 17 16 3 0 * * * * * * * * * * 7 +0xe430 MGO4 17 16 3 0 * * * * * * * * * * 7 +0xe500 MLIZ 17 24 3 0 * * * * * * * * * * 4 +0xe510 MLI2 17 24 3 0 * * * * * * * * * * 4 +0xe520 MLI3 17 32 5 0 * * * * * * * * * * 4 +0xe600 MMYC 17 16 3 0 * * * * * * * * * * 7 +0xe610 MMY2 17 16 3 0 * * * * * * * * * * 7 +0xe700 MNO1 17 20 3 0 * * * * * * * * * * 6 +0xe710 MNO2 17 20 3 0 * * * * * * * * * * 6 +0xe720 MNO3 17 24 3 0 * * * * * * * * * * 6 +0xe800 MOR1 17 16 3 0 * * * * * * * * * * 7 +0xe810 MOR2 17 16 3 0 * * * * * * * * * * 7 +0xe820 MOR3 17 16 3 0 * * * * * * * * * * 7 +0xe830 MOR4 17 16 3 0 * * * * * * * * * * 7 +0xe840 MOR5 17 16 3 0 * * * * * * * * * * 7 +0xe900 MSAL 17 16 3 0 * * * * * * * * * * 7 +0xe910 MSA2 17 16 3 0 * * * * * * * * * * 7 +0xea00 MSHR 17 24 3 0 * * * * * * * * * * 0 +0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * 7 +0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * 7 +0xeb00 MSKT 17 16 3 0 * * * * * * * * * * 6 +0xeb10 MSKA 17 16 3 0 * * * * * * * * * * 6 +0xeb20 MSKB 17 24 3 0 * * * * * * * * * * 6 +0xec00 MWIG 17 16 3 0 * * * * * * * * * * 6 +0xec10 MWI2 17 16 3 0 * * * * * * * * * * 6 +0xec20 MWI3 17 16 3 0 * * * * * * * * * * 6 +0xed00 MYU1 17 16 3 0 * * * * * * * * * * 7 +0xed10 MYU2 17 16 3 0 * * * * * * * * * * 7 +0xed20 MYU3 17 16 3 0 * * * * * * * * * * 7 +0xee00 MZO2 17 16 3 0 * * * * * * * * * * 3 +0xee10 MZO3 17 16 3 0 * * * * * * * * * * 3 diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-bg2tob.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-bg2tob.2da index a1fe254c8..2f9962ca4 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-bg2tob.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-bg2tob.2da @@ -1,373 +1,373 @@ 2DA V1.0 * - RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD -0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * -0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * -0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * -0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * -0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * -0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * -0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * -0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * -0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * -0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * -0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * -0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * -0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * -0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * -0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * -0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * -0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * -0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * -0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * -0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * -0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * -0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * -0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * -0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * -0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * -0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * -0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * -0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * -0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * -0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * -0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * -0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * -0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * -0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * -0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * -0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * -0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * -0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * -0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * -0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * -0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * -0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * -0x1300 MDEM 3 32 5 0 * * * * 0 1 * * * * -0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW -0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * -0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * -0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * -0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * -0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * -0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * -0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * -0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * -0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * -0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * -0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * -0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * -0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * -0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * -0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * -0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * -0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * -0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * -0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * -0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * -0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * -0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * -0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x5012 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x5112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x5212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x5300 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x5301 CEMB 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x5302 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x5303 CIMB 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x5310 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x5311 CEFB 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x5312 CDFB 8 16 3 0 * * CDFT * 1 1 0 * WQS * -0x5313 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH -0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x6012 CDMB 8 16 3 0 * * CDFC * 1 1 1 * WQS * -0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH -0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH -0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x6112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH -0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH -0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x6212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x6214 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH -0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6300 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6301 CEMB 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x6302 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x6303 CIMB 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x6304 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH -0x6305 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6310 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6311 CEFB 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x6312 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x6313 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6314 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH -0x6315 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * -0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * -0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * -0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * -0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * -0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * -0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * -0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * -0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * -0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * -0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * -0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * -0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * -0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * -0x7201 MBER 11 16 3 0 * * * * 0 * * * * * -0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * -0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * -0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * -0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * -0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * -0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * -0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * -0x7312 MFIE 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * -0x7313 MFIS 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * -0x7314 MFIE 10 16 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * -0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * -0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * -0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * -0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * -0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * -0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * -0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * -0x7600 METT 11 16 3 0 * * * * 0 * * * * * -0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * -0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * -0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * -0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * -0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * -0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * -0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * -0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * -0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * -0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * -0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * -0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * -0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * -0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * -0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * -0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * -0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * -0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * -0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * -0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * -0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * -0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * -0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * -0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * -0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * -0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * -0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * -0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * -0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * -0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * -0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * -0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * -0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * -0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * -0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * -0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * -0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * -0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * -0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * -0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * -0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * -0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * -0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * -0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * -0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * -0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * -0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * -0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * -0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * -0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * -0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * -0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * -0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * -0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * -0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * -0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * -0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * -0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * -0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * -0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * -0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * -0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * -0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * -0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * -0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * -0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * -0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * -0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * -0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * -0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * -0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * -0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * -0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * -0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * -0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * -0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * -0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * -0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * -0x7f3b MSOG 10 16 3 4 * * * 0 0 1 * * * * -0x7f3c MASG 10 16 3 4 * * * 0 0 1 * * * * -0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * -0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * -0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * -0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB -0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW -0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW -0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * -0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * -0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * -0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * -0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * -0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * -0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * -0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * -0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * -0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * -0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * -0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * -0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * -0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * -0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * -0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * -0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * -0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * -0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * -0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * -0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * -0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * -0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * -0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * -0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * -0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * -0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * -0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * -0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * -0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * -0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * -0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * -0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * -0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * -0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xe000 MCYC 17 48 5 0 * * * * * * * * * * -0xe010 METN 17 48 5 0 * * * * * * * * * * -0xe020 MTAN 17 32 5 0 * * * * * * * * * * -0xe040 MHIS 17 16 3 0 * * * * * * * * * * -0xe050 MLER 17 16 3 0 * * * * * * * * * * -0xe060 MLIC 17 16 3 0 * * * * * * * * * * -0xe070 MMIN 17 24 3 0 * * * * * * * * * * -0xe080 MMUM 17 16 3 0 * * * * * * * * * * -0xe090 MTAN 17 16 3 0 * * * * * * * * * * -0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * -0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * -0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * -0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * -0xe0e0 MCOR 17 24 3 0 * * * * * * * * * * -0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * -0xe0f1 MGLA 17 24 3 0 * * * * * * * * * * -0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * -0xe200 MBET 17 24 3 0 * * * * * * * * * * -0xe210 MBFI 17 12 3 0 * * * * * * * * * * -0xe220 MBBM 17 16 3 0 * * * * * * * * * * -0xe230 MBRH 17 64 7 0 * * * * * * * * * * -0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * -0xe310 MGH2 17 16 3 0 * * * * * * * * * * -0xe320 MGH3 17 16 3 0 * * * * * * * * * * -0xe400 MGO1 17 16 3 0 * * * * * * * * * * -0xe410 MGO2 17 16 3 0 * * * * * * * * * * -0xe420 MGO3 17 16 3 0 * * * * * * * * * * -0xe430 MGO4 17 16 3 0 * * * * * * * * * * -0xe500 MLIZ 17 24 3 0 * * * * * * * * * * -0xe510 MLI2 17 24 3 0 * * * * * * * * * * -0xe520 MLI3 17 32 5 0 * * * * * * * * * * -0xe600 MMYC 17 16 3 0 * * * * * * * * * * -0xe610 MMY2 17 16 3 0 * * * * * * * * * * -0xe700 MNO1 17 20 3 0 * * * * * * * * * * -0xe710 MNO2 17 20 3 0 * * * * * * * * * * -0xe720 MNO3 17 24 3 0 * * * * * * * * * * -0xe800 MOR1 17 16 3 0 * * * * * * * * * * -0xe810 MOR2 17 16 3 0 * * * * * * * * * * -0xe820 MOR3 17 16 3 0 * * * * * * * * * * -0xe830 MOR4 17 16 3 0 * * * * * * * * * * -0xe840 MOR5 17 16 3 0 * * * * * * * * * * -0xe900 MSAL 17 16 3 0 * * * * * * * * * * -0xe910 MSA2 17 16 3 0 * * * * * * * * * * -0xea00 MSHR 17 24 3 0 * * * * * * * * * * -0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * -0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * -0xeb00 MSKT 17 16 3 0 * * * * * * * * * * -0xeb10 MSKA 17 16 3 0 * * * * * * * * * * -0xeb20 MSKB 17 24 3 0 * * * * * * * * * * -0xec00 MWIG 17 16 3 0 * * * * * * * * * * -0xec10 MWI2 17 16 3 0 * * * * * * * * * * -0xec20 MWI3 17 16 3 0 * * * * * * * * * * -0xed00 MYU1 17 16 3 0 * * * * * * * * * * -0xed10 MYU2 17 16 3 0 * * * * * * * * * * -0xed20 MYU3 17 16 3 0 * * * * * * * * * * -0xee00 MZO2 17 16 3 0 * * * * * * * * * * -0xee10 MZO3 17 16 3 0 * * * * * * * * * * -0xef10 MWWE 17 24 3 0 * * * * * * * * * * + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD MOVE_SCALE +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * 0 +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * 0 +0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * 0 +0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * 0 +0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * 0 +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * 0 +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * 0 +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * 0 +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * 0 +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * 0 +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * 0 +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * 0 +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * 0 +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * 0 +0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * 0 +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * 0 +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * 0 +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * 0 +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * 0 +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * 0 +0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * 0 +0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * 0 +0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * 0 +0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * 8 +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * 8 +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * 14 +0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * 14 +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * 14 +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * 14 +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * 14 +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * 14 +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * 14 +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * 14 +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * 14 +0x1300 MDEM 3 32 5 0 * * * * 0 1 * * * * 10 +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW 6 +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * 6 +0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * 6 +0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * 7 +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * 6 +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * 0 +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * 0 +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * 0 +0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * 0 +0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * 0 +0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * 0 +0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * 0 +0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * 0 +0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x5012 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x5112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x5212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x5300 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x5301 CEMB 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x5302 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x5303 CIMB 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x5310 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x5311 CEFB 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x5312 CDFB 8 16 3 0 * * CDFT * 1 1 0 * WQS * 9 +0x5313 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH 9 +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x6012 CDMB 8 16 3 0 * * CDFC * 1 1 1 * WQS * 9 +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH 9 +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH 9 +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x6112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH 9 +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH 9 +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x6212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x6214 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH 9 +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6300 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6301 CEMB 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x6302 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x6303 CIMB 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x6304 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH 9 +0x6305 CHMB 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6310 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6311 CEFB 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x6312 CDMB 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x6313 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6314 CIFB 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH 9 +0x6315 CHFB 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * 9 +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * 9 +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * 9 +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * 9 +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * 9 +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * 9 +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * 9 +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * 7 +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * 6 +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * 6 +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * 6 +0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * 9 +0x7201 MBER 11 16 3 0 * * * * 0 * * * * * 9 +0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * 9 +0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * 9 +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * 10 +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * 7 +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * 10 +0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * 10 +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * 7 +0x7312 MFIE 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * 10 +0x7313 MFIS 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * 7 +0x7314 MFIE 10 16 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * 10 +0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * 10 +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * 7 +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * 6 +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * 6 +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * 6 +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * 6 +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * 6 +0x7600 METT 11 16 3 0 * * * * 0 * * * * * 6 +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * 4 +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * 4 +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * 4 +0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * 9 +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * 6 +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * 2 +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * 2 +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * 2 +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * 2 +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * 2 +0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * 5 +0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * 5 +0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * 5 +0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * 5 +0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * 5 +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * 8 +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * 10 +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * 8 +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * 8 +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * 8 +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * 8 +0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * 8 +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * 6 +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * 6 +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * 4 +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * 10 +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * 10 +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * 8 +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * 13 +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * 4 +0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * 2 +0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * 12 +0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * 14 +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * 14 +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * 5 +0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * 7 +0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * 9 +0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * 9 +0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f3b MSOG 10 16 3 4 * * * 0 0 1 * * * * 9 +0x7f3c MASG 10 16 3 4 * * * 0 0 1 * * * * 9 +0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * 9 +0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * 9 +0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB 5 +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW 5 +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW 6 +0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * 6 +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * 8 +0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * 6 +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * 0 +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * 0 +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * 0 +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * 0 +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * 0 +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * 0 +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * 0 +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * 0 +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * 0 +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * 0 +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * 3 +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * 4 +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * 6 +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * 6 +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * 5 +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * 5 +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * 5 +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * 5 +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * 6 +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * 6 +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * 6 +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * 6 +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * 6 +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xe000 MCYC 17 48 5 0 * * * * * * * * * * 4 +0xe010 METN 17 48 5 0 * * * * * * * * * * 4 +0xe020 MTAN 17 32 5 0 * * * * * * * * * * 4 +0xe040 MHIS 17 16 3 0 * * * * * * * * * * 4 +0xe050 MLER 17 16 3 0 * * * * * * * * * * 4 +0xe060 MLIC 17 16 3 0 * * * * * * * * * * 4 +0xe070 MMIN 17 24 3 0 * * * * * * * * * * 4 +0xe080 MMUM 17 16 3 0 * * * * * * * * * * 4 +0xe090 MTAN 17 16 3 0 * * * * * * * * * * 4 +0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * 4 +0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * 4 +0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * 4 +0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * 4 +0xe0e0 MCOR 17 24 3 0 * * * * * * * * * * 9 +0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * 9 +0xe0f1 MGLA 17 24 3 0 * * * * * * * * * * 9 +0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * 5 +0xe200 MBET 17 24 3 0 * * * * * * * * * * 7 +0xe210 MBFI 17 12 3 0 * * * * * * * * * * 7 +0xe220 MBBM 17 16 3 0 * * * * * * * * * * 7 +0xe230 MBRH 17 64 7 0 * * * * * * * * * * 7 +0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * 7 +0xe310 MGH2 17 16 3 0 * * * * * * * * * * 7 +0xe320 MGH3 17 16 3 0 * * * * * * * * * * 7 +0xe400 MGO1 17 16 3 0 * * * * * * * * * * 7 +0xe410 MGO2 17 16 3 0 * * * * * * * * * * 7 +0xe420 MGO3 17 16 3 0 * * * * * * * * * * 7 +0xe430 MGO4 17 16 3 0 * * * * * * * * * * 7 +0xe500 MLIZ 17 24 3 0 * * * * * * * * * * 4 +0xe510 MLI2 17 24 3 0 * * * * * * * * * * 4 +0xe520 MLI3 17 32 5 0 * * * * * * * * * * 4 +0xe600 MMYC 17 16 3 0 * * * * * * * * * * 7 +0xe610 MMY2 17 16 3 0 * * * * * * * * * * 7 +0xe700 MNO1 17 20 3 0 * * * * * * * * * * 6 +0xe710 MNO2 17 20 3 0 * * * * * * * * * * 6 +0xe720 MNO3 17 24 3 0 * * * * * * * * * * 6 +0xe800 MOR1 17 16 3 0 * * * * * * * * * * 7 +0xe810 MOR2 17 16 3 0 * * * * * * * * * * 7 +0xe820 MOR3 17 16 3 0 * * * * * * * * * * 7 +0xe830 MOR4 17 16 3 0 * * * * * * * * * * 7 +0xe840 MOR5 17 16 3 0 * * * * * * * * * * 7 +0xe900 MSAL 17 16 3 0 * * * * * * * * * * 7 +0xe910 MSA2 17 16 3 0 * * * * * * * * * * 7 +0xea00 MSHR 17 24 3 0 * * * * * * * * * * 0 +0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * 7 +0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * 7 +0xeb00 MSKT 17 16 3 0 * * * * * * * * * * 6 +0xeb10 MSKA 17 16 3 0 * * * * * * * * * * 6 +0xeb20 MSKB 17 24 3 0 * * * * * * * * * * 6 +0xec00 MWIG 17 16 3 0 * * * * * * * * * * 6 +0xec10 MWI2 17 16 3 0 * * * * * * * * * * 6 +0xec20 MWI3 17 16 3 0 * * * * * * * * * * 6 +0xed00 MYU1 17 16 3 0 * * * * * * * * * * 7 +0xed10 MYU2 17 16 3 0 * * * * * * * * * * 7 +0xed20 MYU3 17 16 3 0 * * * * * * * * * * 7 +0xee00 MZO2 17 16 3 0 * * * * * * * * * * 3 +0xee10 MZO3 17 16 3 0 * * * * * * * * * * 3 +0xef10 MWWE 17 24 3 0 * * * * * * * * * * 7 diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-bgee.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-bgee.2da index df737deb8..358464ea6 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-bgee.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-bgee.2da @@ -1,519 +1,519 @@ 2DA V1.0 * - RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD -0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * -0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * -0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * -0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * -0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * -0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * -0x0101 BGLRYU 0 0 3 7 * * * * * 0 * * * * -0x0102 SPCL236U 0 0 3 7 * * * * * 0 * * * * -0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * -0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * -0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * -0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * -0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * -0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0301 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * -0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * -0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * -0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * -0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * -0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * -0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * -0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * -0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * -0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * -0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * -0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * -0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * -0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * -0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * -0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * -0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * -0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * -0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * -0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * -0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * -0x1003 MWYV 1 24 4 0 MWYV_WH * * * 0 * * * * * -0x1004 MWYV 1 24 4 0 MWYV_AL * * * 0 * * * * * -0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * -0x1101 MWDR 1 72 7 0 * * * * 0 * * * * * -0x1102 MTAN 1 32 5 0 MTAN_BL * * * 0 * 1 * * * -0x1103 MTAN 1 32 5 0 MTAN_GR * * * 0 * 1 * * * -0x1104 MTAN 1 32 5 0 MTAN_RD * * * 0 * 1 * * * -0x1105 MWDR 1 72 7 0 MWDR_GR * * * 0 * * * * * -0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * -0x1201 MDR2 2 72 13 0 * * * * 0 1 * * * * -0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * -0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * -0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * -0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * -0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * -0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * -0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * -0x1300 MDEM 3 32 5 0 * * * * 0 1 * * * * -0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW -0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * -0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * -0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * -0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * -0x3001 MNEO 6 60 5 0 * * * * 0 * * * * * -0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * -0x4001 SNONE 7 0 0 0 * * * * 0 * * * * * -0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * -0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * -0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * -0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * -0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * -0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * -0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * -0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * -0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * -0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * -0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * -0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * -0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * -0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * -0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * -0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * -0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * -0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x5012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * -0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x5112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * -0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x5212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * -0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x5312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * -0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH -0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x6012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * -0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH -0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH -0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x6112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * -0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH -0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH -0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x6212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * -0x6214 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS WQH -0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH -0x6305 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x6312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * -0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH -0x6315 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * -0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * -0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * -0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * -0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * -0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * -0x6406 MDGU 9 24 5 0 * * * * 1 * 1 * WPM * -0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * -0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * -0x6621 XHFF 9 16 3 0 * * * * 1 * 1 * WPM * -0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * -0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * -0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * -0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * -0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * -0x7201 MBER 11 16 3 0 * * * * 0 * * * * * -0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * -0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * -0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * -0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * -0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * -0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * -0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * -0x7312 MFIE 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * -0x7313 MFIS 10 16 3 4 MFISG1B MFISG2B * * 0 1 * * * * -0x7314 MFIE 10 16 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * -0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * -0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * -0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * -0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * -0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * -0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * -0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * -0x7600 METT 11 16 3 0 * * * * 0 * * * * * -0x7601 MGHL 11 16 3 0 MGHL_MA * * * 0 0 * * * * -0x7602 MSPI 11 16 3 0 MSPI_MY * * * 0 * * * * * -0x7603 MDOG 11 16 3 0 MDOG_GR * * * 0 * * * * * -0x7604 MSPI 11 16 3 0 MSPI_WA * * * 0 * * * * * -0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * -0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * -0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * -0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * -0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * -0x7801 MGIB 11 16 3 0 MGIB_BR * * * 0 * * * * * -0x7802 MSLI 11 24 4 0 MSLI_BL * * 0 0 * * * * * -0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * -0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * -0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * -0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * -0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * -0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * -0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * -0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * -0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * -0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * -0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * -0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * -0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * -0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * -0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * -0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * -0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * -0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * -0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * -0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * -0x7d01 NSLF 11 16 3 0 * * * * 1 0 * * * * -0x7d02 ACHB 11 12 3 0 * * * * 0 0 * * * * -0x7d03 ACHW 11 12 3 0 * * * * 0 0 * * * * -0x7d04 NPRF 11 16 3 0 * * * * 1 0 * * * * -0x7d05 NNMF 11 16 3 0 * * * * 1 0 * * * * -0x7d06 NNWF 11 16 3 0 * * * * 1 0 * * * * -0x7d07 NSMF 11 16 3 0 * * * * 1 0 * * * * -0x7d08 NSWF 11 16 3 0 * * * * 1 0 * * * * -0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * -0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * -0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * -0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * -0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * -0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * -0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * -0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * -0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * -0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * -0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * -0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * -0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * -0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * -0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * -0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * -0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * -0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * -0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * -0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * -0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * -0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * -0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * -0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * -0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * -0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * -0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * -0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * -0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * -0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * -0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * -0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * -0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * -0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * -0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * -0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * -0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * -0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * -0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * -0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * -0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * -0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * -0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * -0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * -0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * -0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * -0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * -0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * -0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * -0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * -0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * -0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * -0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * -0x7f3b MSOG 10 16 3 4 * * * 0 0 1 * * * * -0x7f3c MASL 10 16 3 7 * * MASG 0 0 1 * * * * -0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * -0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * -0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * -0x7f40 MGLM 10 16 3 0 * * * * 0 1 * * * * -0x7f41 MDJL 10 16 3 0 MDJL_E1 MDJL_E2 * * 0 1 * * * * -0x7f42 NIRO 10 16 3 0 NIRO_RD * * * 0 1 * * * * -0x7f43 MOTY 10 16 3 0 MOTY_Y1 MOTY_Y2 * * 0 1 * * * * -0x7f44 NELW 10 16 3 0 * * * * 0 0 * * * * -0x7f45 MSAI 10 16 3 0 * * * 1 0 0 * * * * -0x7f46 MBEG 10 16 3 0 * * * * 0 0 * * * * -0x7f47 MFI2 10 32 5 0 * * * * 0 0 * * * * -0x7f48 MSOF 10 16 3 0 * * * 0 0 1 * * * * -0x7f49 MDMF 10 16 3 0 * * * 0 0 1 * * * * -0x7f4a MDAS 10 16 3 7 * * MDAG 0 0 1 * * * * -0x7f4b MDAF 10 16 3 0 * * * 0 0 1 * * * * -0x7f4c MPLN 10 16 3 7 * * MPLG 0 0 1 * * * * -0x7f4d MPLF 10 16 3 0 * * * 0 0 1 * * * * -0x7f4e MDVM 10 16 3 7 * * MDVG 0 0 1 * * * * -0x7f4f MDVF 10 16 3 0 * * * 0 0 1 * * * * -0x7f50 MMST 10 16 3 1 MMST_NI * * 0 0 0 * * * * -0x7f51 MMST 10 16 3 1 MMST_HA * * 0 0 0 * * * * -0x7f52 NSAI 10 16 3 0 NSAI_G1 NSAI_G2 * * 0 1 * * * * -0x7f53 NSAI 10 16 3 0 NSAI_R1 NSAI_R2 * * 0 1 * * * * -0x7f54 NSAI 10 16 3 0 NSAI_D1 NSAI_D2 * * 0 1 * * * * -0x7f55 NSOL 10 16 3 0 NSOL_C1 NSOL_C2 * * 0 1 * * * * -0x7f56 MWIL 10 16 3 7 * * * * 0 0 * * * * -0x7f57 MWIS 10 12 3 7 * * * * 0 0 * * * * -0x7f58 MABB 10 24 3 0 * * * * 0 0 * * * * -0x7f59 MABG 10 24 3 0 * * * * 0 0 * * * * -0x7f5a MABR 10 24 3 0 * * * * 0 0 * * * * -0x7f5b MABR 10 24 3 0 MABR_BL * * * 0 0 * * * * -0x7f5c MHAG 10 16 3 0 * * * * 0 0 * * * * -0x7f5d MHAG 10 16 3 0 MHAG_GR * * * 0 0 * * * * -0x7f5e MHAG 10 16 3 0 MHAG_SE * * * 0 0 * * * * -0x7f5f MHAN 10 16 3 0 * * * * 0 0 * * * * -0x7f60 MSNK 10 16 3 0 MSNK_W1 MSNK_W2 * * 0 1 * * * * -0x7f61 MDRJ 10 48 9 0 * * * * 0 0 * * * * -0x7f62 MDRJ 10 72 9 0 MDRJ_GR * * * 0 0 * * * * -0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB -0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW -0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW -0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * -0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * -0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * -0xa200 MWYV 13 24 4 0 MWYV_WS * * * 0 * * * * * -0xa201 MCEN 13 16 3 0 * * * * 0 * * * * * -0xa202 MTUN 13 16 3 0 * * * * 0 * * * * * -0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * -0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * -0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * -0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * -0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * -0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * -0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * -0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * -0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * -0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * -0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * -0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * -0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * -0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * -0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * -0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * -0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * -0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * -0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * -0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * -0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * -0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * -0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * -0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * -0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * -0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * -0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * -0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * -0xcc00 MKG1 15 16 3 0 * * * * 0 * * * * * -0xcc01 MKG2 15 16 3 0 * * * * 0 * * * * * -0xcc02 MKG3 15 16 3 0 * * * * 0 * * * * * -0xcc04 ARAT 15 0 1 0 * * * * 0 * * * * * -0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * -0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * -0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * -0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xe000 MCYC 17 48 5 0 * * * * * * * * * * -0xe010 METN 17 48 5 0 * * * * * * * * * * -0xe020 MTAN 17 32 5 0 * * * * * * * * * * -0xe040 MHIS 17 16 3 0 * * * * * * * * * * -0xe050 MLER 17 16 3 0 * * * * * * * * * * -0xe060 MLIC 17 16 3 0 * * * * * * * * * * -0xe070 MMIN 17 24 3 0 * * * * * * * * * * -0xe080 MMUM 17 16 3 0 * * * * * * * * * * -0xe090 MTAN 17 16 3 0 * * * * * * * * * * -0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * -0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * -0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * -0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * -0xe0e0 MCOR 17 24 3 0 * * * * * * * * * * -0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * -0xe0f1 MGLA 17 24 3 0 * * * * * * * * * * -0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * -0xe200 MBET 17 24 3 0 * * * * * * * * * * -0xe210 MBFI 17 12 3 0 * * * * * * * * * * -0xe220 MBBM 17 16 3 0 * * * * * * * * * * -0xe230 MBRH 17 64 7 0 * * * * * * * * * * -0xe240 MANI 17 24 3 0 * * * * * * * * * * -0xe241 MAN2 17 24 3 0 * * * * * * * * * * -0xe242 MAN3 17 24 3 0 * * * * * * * * * * -0xe243 MARU 17 16 3 0 * * * * * * * * * * -0xe244 MBA1 17 16 3 0 * * * * * * * * * * -0xe245 MBA2 17 16 3 0 * * * * * * * * * * -0xe246 MBA3 17 16 3 0 * * * * * * * * * * -0xe247 MBA4 17 16 3 0 * * * * * * * * * * -0xe248 MBA5 17 16 3 0 * * * * * * * * * * -0xe249 MBA6 17 16 3 0 * * * * * * * * * * -0xe24a MBAI 17 16 3 0 * * * * * * * * * * -0xe24b MELE 17 36 3 0 * * * * * * * * * * -0xe24c MELF 17 36 3 0 * * * * * * * * * * -0xe24d MELW 17 36 3 0 * * * * * * * * * * -0xe24e MGFI 17 48 3 0 * * * * * * * * * * -0xe24f MGFR 17 48 5 0 * * * * * * * * * * -0xe250 MGIR 17 24 3 0 * * * * * * * * * * -0xe251 MGVE 17 32 5 0 * * * * * * * * * * -0xe252 MHAR 17 16 3 0 * * * * * * * * * * -0xe253 MREM 17 48 5 0 * * * * * * * * * * -0xe254 MSCR 17 24 3 0 * * * * * * * * * * -0xe255 MSEE 17 16 3 0 * * * * * * * * * * -0xe256 MBE1 17 32 4 0 * * * * * * * * * * -0xe257 MBE2 17 16 3 0 * * * * * * * * * * -0xe258 MBE2 17 16 3 0 MBE2_HE * * * * * * * * * -0xe259 MSVI 17 16 3 0 * * * * * * * * * * -0xe25a MSV2 17 16 3 0 * * * * * * * * * * -0xe25b MUM2 17 24 3 0 * * * * * * * * * * -0xe25c MTA2 17 16 3 0 * * * * * * * * * * -0xe25d MYET 17 24 3 0 * * * * * * * * * * -0xe25e MWI4 17 16 3 0 * * * * * * * * * * -0xe25f MDRD 17 16 3 0 * * * * * * * * * * -0xe260 MCRD 17 24 3 0 * * * * * * * * * * -0xe261 MSAH 17 24 3 0 * * * * * * * * * * -0xe262 MSAT 17 24 3 0 * * * * * * * * * * -0xe263 MSV3 17 16 3 0 * * * * * * * * * * -0xe264 MSV4 17 16 3 0 * * * * * * * * * * -0xe265 MBOA 17 24 3 0 * * * * * * * * * * -0xe266 MBU2 17 16 3 0 * * * * * * * * * * -0xe267 MBUG 17 16 3 0 * * * * * * * * * * -0xe26a MDH2 17 24 3 0 * * * * * * * * * * -0xe26b MDTR 17 24 3 0 * * * * * * * * * * -0xe26d MFE2 17 24 3 0 * * * * * * * * * * -0xe26e MFEY 17 24 3 0 * * * * * * * * * * -0xe26f MGFO 17 24 4 0 * * * * * * * * * * -0xe270 MGO5 17 16 3 0 * * * * * * * * * * -0xe271 MGOC 17 16 3 0 * * * * * * * * * * -0xe272 MGWO 17 24 3 0 * * * * * * * * * * -0xe273 MGW2 17 24 3 0 * * * * * * * * * * -0xe274 MHOH 17 24 3 0 * * * * * * * * * * -0xe276 MLEM 17 16 3 0 * * * * * * * * * * -0xe279 MNOS 17 24 3 0 * * * * * * * * * * -0xe27d MWEB 17 16 3 0 * * * * * * * * * * -0xe27e MWRA 17 16 3 0 * * * * * * * * * * -0xe27f MBOA 17 24 3 0 MBOA_BR * * * * * * * * * -0xe280 MWOR 17 24 3 0 * * * * * * * * * * -0xe281 MYUH 17 16 3 0 * * * * * * * * * * -0xe282 MDRF 17 32 4 0 * * * * * * * * * * -0xe283 MDRM 17 32 4 0 * * * * * * * * * * -0xe288 MGWO 17 24 3 0 MGWO_DK * * * * * * * * * -0xe289 MGW2 17 24 3 0 MGW2_DK * * * * * * * * * -0xe28a MTRO 17 16 3 0 MTRO_DK * * * * * * * * * -0xe28b MABW 17 24 3 0 * * * * * * * * * * -0xe28c MWD2 17 36 5 0 * * * * * * * * * * -0xe28d MWD2 17 36 5 0 MWD2_SI * * * * * * * * * -0xe28e MWD2 17 36 5 0 MWD2_GR * * * * * * * * * -0xe28f MDRD 17 16 3 0 MDRD_RE * * * * * * * * * -0xe290 MDH2 17 24 3 0 MDH2_GR * * * * * * * * * -0xe291 MBU2 17 16 3 0 MBU2_SH * * * * * * * * * -0xe292 METN 17 48 5 0 METN_GH * * * * * * * * * -0xe293 MGVE 17 40 5 0 * * * * * * * * * * -0xe294 MBON 17 16 3 0 * * * * * * * * * * -0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * -0xe310 MGH2 17 16 3 0 * * * * * * * * * * -0xe320 MGH3 17 16 3 0 * * * * * * * * * * -0xe400 MGO1 17 16 3 0 * * * * * * * * * * -0xe410 MGO2 17 16 3 0 * * * * * * * * * * -0xe420 MGO3 17 16 3 0 * * * * * * * * * * -0xe430 MGO4 17 16 3 0 * * * * * * * * * * -0xe440 MGO6 17 16 3 0 * * * * * * * * * * -0xe441 MGO7 17 16 3 0 * * * * * * * * * * -0xe442 MGO8 17 16 3 0 * * * * * * * * * * -0xe443 MGO9 17 16 3 0 * * * * * * * * * * -0xe444 MGO10 17 16 3 0 * * * * * * * * * * -0xe500 MLIZ 17 24 3 0 * * * * * * * * * * -0xe510 MLI2 17 24 3 0 * * * * * * * * * * -0xe520 MLI3 17 32 5 0 * * * * * * * * * * -0xe600 MMYC 17 16 3 0 * * * * * * * * * * -0xe610 MMY2 17 16 3 0 * * * * * * * * * * -0xe700 MNO1 17 20 3 0 * * * * * * * * * * -0xe710 MNO2 17 20 3 0 * * * * * * * * * * -0xe720 MNO3 17 24 3 0 * * * * * * * * * * -0xe800 MOR1 17 16 3 0 * * * * * * * * * * -0xe810 MOR2 17 16 3 0 * * * * * * * * * * -0xe820 MOR3 17 16 3 0 * * * * * * * * * * -0xe830 MOR4 17 16 3 0 * * * * * * * * * * -0xe840 MOR5 17 16 3 0 * * * * * * * * * * -0xe900 MSAL 17 16 3 0 * * * * * * * * * * -0xe910 MSA2 17 16 3 0 * * * * * * * * * * -0xea00 MSHR 17 24 3 0 * * * * * * * * * * -0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * -0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * -0xeb00 MSKT 17 16 3 0 * * * * * * * * * * -0xeb10 MSKA 17 16 3 0 * * * * * * * * * * -0xeb20 MSKB 17 24 3 0 * * * * * * * * * * -0xec00 MWIG 17 16 3 0 * * * * * * * * * * -0xec10 MWI2 17 16 3 0 * * * * * * * * * * -0xec20 MWI3 17 16 3 0 * * * * * * * * * * -0xed00 MYU1 17 16 3 0 * * * * * * * * * * -0xed10 MYU2 17 16 3 0 * * * * * * * * * * -0xed20 MYU3 17 16 3 0 * * * * * * * * * * -0xee00 MZO2 17 16 3 0 * * * * * * * * * * -0xee10 MZO3 17 16 3 0 * * * * * * * * * * -0xef10 MWWE 17 24 3 0 * * * * * * * * * * + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD MOVE_SCALE +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * 0 +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * 0 +0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * 0 +0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * 0 +0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * 0 +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * 0 +0x0101 BGLRYU 0 0 3 7 * * * * * 0 * * * * 0 +0x0102 SPCL236U 0 0 3 7 * * * * * 0 * * * * 0 +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * 0 +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * 0 +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * 0 +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * 0 +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * 0 +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0301 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * 0 +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * 0 +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * 0 +0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * 0 +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * 0 +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * 0 +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * 0 +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * 0 +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * 0 +0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * 0 +0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * 0 +0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * 0 +0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * 8 +0x1003 MWYV 1 24 4 0 MWYV_WH * * * 0 * * * * * 8 +0x1004 MWYV 1 24 4 0 MWYV_AL * * * 0 * * * * * 8 +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * 8 +0x1101 MWDR 1 72 7 0 * * * * 0 * * * * * 12 +0x1102 MTAN 1 32 5 0 MTAN_BL * * * 0 * 1 * * * 8 +0x1103 MTAN 1 32 5 0 MTAN_GR * * * 0 * 1 * * * 8 +0x1104 MTAN 1 32 5 0 MTAN_RD * * * 0 * 1 * * * 8 +0x1105 MWDR 1 72 7 0 MWDR_GR * * * 0 * * * * * 12 +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * 14 +0x1201 MDR2 2 72 13 0 * * * * 0 1 * * * * 14 +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * 14 +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * 14 +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * 14 +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * 14 +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * 14 +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * 14 +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * 14 +0x1300 MDEM 3 32 5 0 * * * * 0 1 * * * * 10 +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW 6 +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * 6 +0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * 6 +0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * 7 +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * 6 +0x3001 MNEO 6 60 5 0 * * * * 0 * * * * * 6 +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4001 SNONE 7 0 0 0 * * * * 0 * * * * * 0 +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * 0 +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * 0 +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * 0 +0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * 0 +0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * 0 +0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * 0 +0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * 0 +0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * 0 +0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x5012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * 9 +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x5112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * 9 +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x5212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * 9 +0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x5312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * 9 +0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH 9 +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x6012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * 9 +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH 9 +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH 9 +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x6112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * 9 +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH 9 +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH 9 +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x6212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * 9 +0x6214 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS WQH 9 +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH 9 +0x6305 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x6312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * 9 +0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH 9 +0x6315 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * 9 +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * 9 +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * 9 +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * 9 +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6406 MDGU 9 24 5 0 * * * * 1 * 1 * WPM * 9 +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * 9 +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * 9 +0x6621 XHFF 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * 7 +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * 6 +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * 6 +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * 6 +0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * 9 +0x7201 MBER 11 16 3 0 * * * * 0 * * * * * 9 +0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * 9 +0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * 9 +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * 10 +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * 7 +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * 10 +0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * 10 +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * 7 +0x7312 MFIE 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * 10 +0x7313 MFIS 10 16 3 4 MFISG1B MFISG2B * * 0 1 * * * * 7 +0x7314 MFIE 10 16 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * 10 +0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * 10 +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * 7 +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * 6 +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * 6 +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * 6 +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * 6 +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * 6 +0x7600 METT 11 16 3 0 * * * * 0 * * * * * 6 +0x7601 MGHL 11 16 3 0 MGHL_MA * * * 0 0 * * * * 4 +0x7602 MSPI 11 16 3 0 MSPI_MY * * * 0 * * * * * 5 +0x7603 MDOG 11 16 3 0 MDOG_GR * * * 0 * * * * * 6 +0x7604 MSPI 11 16 3 0 MSPI_WA * * * 0 * * * * * 5 +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * 4 +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * 4 +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * 4 +0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * 9 +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * 6 +0x7801 MGIB 11 16 3 0 MGIB_BR * * * 0 * * * * * 6 +0x7802 MSLI 11 24 4 0 MSLI_BL * * 0 0 * * * * * 2 +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * 2 +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * 2 +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * 2 +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * 2 +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * 2 +0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * 5 +0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * 5 +0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * 5 +0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * 5 +0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * 5 +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * 8 +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * 10 +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * 8 +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * 8 +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * 8 +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * 8 +0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * 8 +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * 6 +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * 6 +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * 4 +0x7d01 NSLF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d02 ACHB 11 12 3 0 * * * * 0 0 * * * * 3 +0x7d03 ACHW 11 12 3 0 * * * * 0 0 * * * * 3 +0x7d04 NPRF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d05 NNMF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d06 NNWF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d07 NSMF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d08 NSWF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * 10 +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * 10 +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * 8 +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * 13 +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * 4 +0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * 2 +0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * 12 +0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * 14 +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * 14 +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * 5 +0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * 7 +0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * 9 +0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * 9 +0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f3b MSOG 10 16 3 4 * * * 0 0 1 * * * * 9 +0x7f3c MASL 10 16 3 7 * * MASG 0 0 1 * * * * 9 +0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * 9 +0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * 9 +0x7f40 MGLM 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f41 MDJL 10 16 3 0 MDJL_E1 MDJL_E2 * * 0 1 * * * * 11 +0x7f42 NIRO 10 16 3 0 NIRO_RD * * * 0 1 * * * * 9 +0x7f43 MOTY 10 16 3 0 MOTY_Y1 MOTY_Y2 * * 0 1 * * * * 8 +0x7f44 NELW 10 16 3 0 * * * * 0 0 * * * * 7 +0x7f45 MSAI 10 16 3 0 * * * 1 0 0 * * * * 9 +0x7f46 MBEG 10 16 3 0 * * * * 0 0 * * * * 8 +0x7f47 MFI2 10 32 5 0 * * * * 0 0 * * * * 9 +0x7f48 MSOF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f49 MDMF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f4a MDAS 10 16 3 7 * * MDAG 0 0 1 * * * * 9 +0x7f4b MDAF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f4c MPLN 10 16 3 7 * * MPLG 0 0 1 * * * * 9 +0x7f4d MPLF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f4e MDVM 10 16 3 7 * * MDVG 0 0 1 * * * * 9 +0x7f4f MDVF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f50 MMST 10 16 3 1 MMST_NI * * 0 0 0 * * * * 9 +0x7f51 MMST 10 16 3 1 MMST_HA * * 0 0 0 * * * * 9 +0x7f52 NSAI 10 16 3 0 NSAI_G1 NSAI_G2 * * 0 1 * * * * 5 +0x7f53 NSAI 10 16 3 0 NSAI_R1 NSAI_R2 * * 0 1 * * * * 5 +0x7f54 NSAI 10 16 3 0 NSAI_D1 NSAI_D2 * * 0 1 * * * * 5 +0x7f55 NSOL 10 16 3 0 NSOL_C1 NSOL_C2 * * 0 1 * * * * 9 +0x7f56 MWIL 10 16 3 7 * * * * 0 0 * * * * 12 +0x7f57 MWIS 10 12 3 7 * * * * 0 0 * * * * 10 +0x7f58 MABB 10 24 3 0 * * * * 0 0 * * * * 8 +0x7f59 MABG 10 24 3 0 * * * * 0 0 * * * * 9 +0x7f5a MABR 10 24 3 0 * * * * 0 0 * * * * 10 +0x7f5b MABR 10 24 3 0 MABR_BL * * * 0 0 * * * * 10 +0x7f5c MHAG 10 16 3 0 * * * * 0 0 * * * * 8 +0x7f5d MHAG 10 16 3 0 MHAG_GR * * * 0 0 * * * * 12 +0x7f5e MHAG 10 16 3 0 MHAG_SE * * * 0 0 * * * * 14 +0x7f5f MHAN 10 16 3 0 * * * * 0 0 * * * * 14 +0x7f60 MSNK 10 16 3 0 MSNK_W1 MSNK_W2 * * 0 1 * * * * 13 +0x7f61 MDRJ 10 48 9 0 * * * * 0 0 * * * * 11 +0x7f62 MDRJ 10 72 9 0 MDRJ_GR * * * 0 0 * * * * 11 +0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB 5 +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW 5 +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW 6 +0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * 6 +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * 8 +0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * 6 +0xa200 MWYV 13 24 4 0 MWYV_WS * * * 0 * * * * * 8 +0xa201 MCEN 13 16 3 0 * * * * 0 * * * * * 11 +0xa202 MTUN 13 16 3 0 * * * * 0 * * * * * 6 +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * 0 +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * 0 +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * 0 +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * 0 +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * 0 +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * 0 +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * 0 +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * 0 +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * 0 +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * 0 +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * 3 +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * 4 +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * 6 +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * 6 +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * 5 +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * 5 +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * 5 +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * 5 +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * 6 +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * 6 +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * 6 +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * 6 +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * 6 +0xcc00 MKG1 15 16 3 0 * * * * 0 * * * * * 0 +0xcc01 MKG2 15 16 3 0 * * * * 0 * * * * * 0 +0xcc02 MKG3 15 16 3 0 * * * * 0 * * * * * 0 +0xcc04 ARAT 15 0 1 0 * * * * 0 * * * * * 4 +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xe000 MCYC 17 48 5 0 * * * * * * * * * * 4 +0xe010 METN 17 48 5 0 * * * * * * * * * * 4 +0xe020 MTAN 17 32 5 0 * * * * * * * * * * 4 +0xe040 MHIS 17 16 3 0 * * * * * * * * * * 4 +0xe050 MLER 17 16 3 0 * * * * * * * * * * 4 +0xe060 MLIC 17 16 3 0 * * * * * * * * * * 4 +0xe070 MMIN 17 24 3 0 * * * * * * * * * * 4 +0xe080 MMUM 17 16 3 0 * * * * * * * * * * 4 +0xe090 MTAN 17 16 3 0 * * * * * * * * * * 4 +0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * 4 +0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * 4 +0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * 4 +0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * 4 +0xe0e0 MCOR 17 24 3 0 * * * * * * * * * * 9 +0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * 9 +0xe0f1 MGLA 17 24 3 0 * * * * * * * * * * 9 +0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * 5 +0xe200 MBET 17 24 3 0 * * * * * * * * * * 7 +0xe210 MBFI 17 12 3 0 * * * * * * * * * * 7 +0xe220 MBBM 17 16 3 0 * * * * * * * * * * 7 +0xe230 MBRH 17 64 7 0 * * * * * * * * * * 7 +0xe240 MANI 17 24 3 0 * * * * * * * * * * 7 +0xe241 MAN2 17 24 3 0 * * * * * * * * * * 7 +0xe242 MAN3 17 24 3 0 * * * * * * * * * * 7 +0xe243 MARU 17 16 3 0 * * * * * * * * * * 7 +0xe244 MBA1 17 16 3 0 * * * * * * * * * * 8 +0xe245 MBA2 17 16 3 0 * * * * * * * * * * 8 +0xe246 MBA3 17 16 3 0 * * * * * * * * * * 8 +0xe247 MBA4 17 16 3 0 * * * * * * * * * * 8 +0xe248 MBA5 17 16 3 0 * * * * * * * * * * 8 +0xe249 MBA6 17 16 3 0 * * * * * * * * * * 8 +0xe24a MBAI 17 16 3 0 * * * * * * * * * * 8 +0xe24b MELE 17 36 3 0 * * * * * * * * * * 7 +0xe24c MELF 17 36 3 0 * * * * * * * * * * 7 +0xe24d MELW 17 36 3 0 * * * * * * * * * * 7 +0xe24e MGFI 17 48 3 0 * * * * * * * * * * 7 +0xe24f MGFR 17 48 5 0 * * * * * * * * * * 7 +0xe250 MGIR 17 24 3 0 * * * * * * * * * * 8 +0xe251 MGVE 17 32 5 0 * * * * * * * * * * 7 +0xe252 MHAR 17 16 3 0 * * * * * * * * * * 8 +0xe253 MREM 17 48 5 0 * * * * * * * * * * 8 +0xe254 MSCR 17 24 3 0 * * * * * * * * * * 8 +0xe255 MSEE 17 16 3 0 * * * * * * * * * * 5 +0xe256 MBE1 17 32 4 0 * * * * * * * * * * 8 +0xe257 MBE2 17 16 3 0 * * * * * * * * * * 8 +0xe258 MBE2 17 16 3 0 MBE2_HE * * * * * * * * * 8 +0xe259 MSVI 17 16 3 0 * * * * * * * * * * 7 +0xe25a MSV2 17 16 3 0 * * * * * * * * * * 7 +0xe25b MUM2 17 24 3 0 * * * * * * * * * * 7 +0xe25c MTA2 17 16 3 0 * * * * * * * * * * 8 +0xe25d MYET 17 24 3 0 * * * * * * * * * * 7 +0xe25e MWI4 17 16 3 0 * * * * * * * * * * 15 +0xe25f MDRD 17 16 3 0 * * * * * * * * * * 4 +0xe260 MCRD 17 24 3 0 * * * * * * * * * * 8 +0xe261 MSAH 17 24 3 0 * * * * * * * * * * 8 +0xe262 MSAT 17 24 3 0 * * * * * * * * * * 8 +0xe263 MSV3 17 16 3 0 * * * * * * * * * * 7 +0xe264 MSV4 17 16 3 0 * * * * * * * * * * 7 +0xe265 MBOA 17 24 3 0 * * * * * * * * * * 10 +0xe266 MBU2 17 16 3 0 * * * * * * * * * * 8 +0xe267 MBUG 17 16 3 0 * * * * * * * * * * 8 +0xe26a MDH2 17 24 3 0 * * * * * * * * * * 10 +0xe26b MDTR 17 24 3 0 * * * * * * * * * * 6 +0xe26d MFE2 17 24 3 0 * * * * * * * * * * 8 +0xe26e MFEY 17 24 3 0 * * * * * * * * * * 8 +0xe26f MGFO 17 24 4 0 * * * * * * * * * * 8 +0xe270 MGO5 17 16 3 0 * * * * * * * * * * 8 +0xe271 MGOC 17 16 3 0 * * * * * * * * * * 8 +0xe272 MGWO 17 24 3 0 * * * * * * * * * * 10 +0xe273 MGW2 17 24 3 0 * * * * * * * * * * 10 +0xe274 MHOH 17 24 3 0 * * * * * * * * * * 9 +0xe276 MLEM 17 16 3 0 * * * * * * * * * * 6 +0xe279 MNOS 17 24 3 0 * * * * * * * * * * 7 +0xe27d MWEB 17 16 3 0 * * * * * * * * * * 8 +0xe27e MWRA 17 16 3 0 * * * * * * * * * * 8 +0xe27f MBOA 17 24 3 0 MBOA_BR * * * * * * * * * 10 +0xe280 MWOR 17 24 3 0 * * * * * * * * * * 10 +0xe281 MYUH 17 16 3 0 * * * * * * * * * * 8 +0xe282 MDRF 17 32 4 0 * * * * * * * * * * 8 +0xe283 MDRM 17 32 4 0 * * * * * * * * * * 8 +0xe288 MGWO 17 24 3 0 MGWO_DK * * * * * * * * * 10 +0xe289 MGW2 17 24 3 0 MGW2_DK * * * * * * * * * 10 +0xe28a MTRO 17 16 3 0 MTRO_DK * * * * * * * * * 8 +0xe28b MABW 17 24 3 0 * * * * * * * * * * 8 +0xe28c MWD2 17 36 5 0 * * * * * * * * * * 9 +0xe28d MWD2 17 36 5 0 MWD2_SI * * * * * * * * * 9 +0xe28e MWD2 17 36 5 0 MWD2_GR * * * * * * * * * 9 +0xe28f MDRD 17 16 3 0 MDRD_RE * * * * * * * * * 4 +0xe290 MDH2 17 24 3 0 MDH2_GR * * * * * * * * * 10 +0xe291 MBU2 17 16 3 0 MBU2_SH * * * * * * * * * 8 +0xe292 METN 17 48 5 0 METN_GH * * * * * * * * * 8 +0xe293 MGVE 17 40 5 0 * * * * * * * * * * 8 +0xe294 MBON 17 16 3 0 * * * * * * * * * * 10 +0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * 7 +0xe310 MGH2 17 16 3 0 * * * * * * * * * * 7 +0xe320 MGH3 17 16 3 0 * * * * * * * * * * 7 +0xe400 MGO1 17 16 3 0 * * * * * * * * * * 7 +0xe410 MGO2 17 16 3 0 * * * * * * * * * * 7 +0xe420 MGO3 17 16 3 0 * * * * * * * * * * 7 +0xe430 MGO4 17 16 3 0 * * * * * * * * * * 7 +0xe440 MGO6 17 16 3 0 * * * * * * * * * * 9 +0xe441 MGO7 17 16 3 0 * * * * * * * * * * 9 +0xe442 MGO8 17 16 3 0 * * * * * * * * * * 9 +0xe443 MGO9 17 16 3 0 * * * * * * * * * * 9 +0xe444 MGO10 17 16 3 0 * * * * * * * * * * 9 +0xe500 MLIZ 17 24 3 0 * * * * * * * * * * 4 +0xe510 MLI2 17 24 3 0 * * * * * * * * * * 4 +0xe520 MLI3 17 32 5 0 * * * * * * * * * * 4 +0xe600 MMYC 17 16 3 0 * * * * * * * * * * 7 +0xe610 MMY2 17 16 3 0 * * * * * * * * * * 7 +0xe700 MNO1 17 20 3 0 * * * * * * * * * * 6 +0xe710 MNO2 17 20 3 0 * * * * * * * * * * 6 +0xe720 MNO3 17 24 3 0 * * * * * * * * * * 6 +0xe800 MOR1 17 16 3 0 * * * * * * * * * * 7 +0xe810 MOR2 17 16 3 0 * * * * * * * * * * 7 +0xe820 MOR3 17 16 3 0 * * * * * * * * * * 7 +0xe830 MOR4 17 16 3 0 * * * * * * * * * * 7 +0xe840 MOR5 17 16 3 0 * * * * * * * * * * 7 +0xe900 MSAL 17 16 3 0 * * * * * * * * * * 7 +0xe910 MSA2 17 16 3 0 * * * * * * * * * * 7 +0xea00 MSHR 17 24 3 0 * * * * * * * * * * 0 +0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * 7 +0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * 7 +0xeb00 MSKT 17 16 3 0 * * * * * * * * * * 6 +0xeb10 MSKA 17 16 3 0 * * * * * * * * * * 6 +0xeb20 MSKB 17 24 3 0 * * * * * * * * * * 6 +0xec00 MWIG 17 16 3 0 * * * * * * * * * * 6 +0xec10 MWI2 17 16 3 0 * * * * * * * * * * 6 +0xec20 MWI3 17 16 3 0 * * * * * * * * * * 6 +0xed00 MYU1 17 16 3 0 * * * * * * * * * * 7 +0xed10 MYU2 17 16 3 0 * * * * * * * * * * 7 +0xed20 MYU3 17 16 3 0 * * * * * * * * * * 7 +0xee00 MZO2 17 16 3 0 * * * * * * * * * * 3 +0xee10 MZO3 17 16 3 0 * * * * * * * * * * 3 +0xef10 MWWE 17 24 3 0 * * * * * * * * * * 7 diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-eet.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-eet.2da index 265437607..01d66eff1 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-eet.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-eet.2da @@ -1,519 +1,519 @@ 2DA V1.0 * - RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD -0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * -0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * -0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * -0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * -0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * -0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * -0x0101 BGLRYU 0 0 3 7 * * * * * 0 * * * * -0x0102 SPCL236U 0 0 3 7 * * * * * 0 * * * * -0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * -0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * -0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * -0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * -0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * -0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0301 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * -0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * -0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * -0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * -0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * -0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * -0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * -0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * -0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * -0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * -0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * -0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * -0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * -0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * -0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * -0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * -0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * -0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * -0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * -0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * -0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * -0x1003 MWYV 1 24 4 0 MWYV_WH * * * 0 * * * * * -0x1004 MWYV 1 24 4 0 MWYV_AL * * * 0 * * * * * -0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * -0x1101 MWDR 1 72 7 0 * * * * 0 * * * * * -0x1102 MTAN 1 32 5 0 MTAN_BL * * * 0 * 1 * * * -0x1103 MTAN 1 32 5 0 MTAN_GR * * * 0 * 1 * * * -0x1104 MTAN 1 32 5 0 MTAN_RD * * * 0 * 1 * * * -0x1105 MWDR 1 72 7 0 MWDR_GR * * * 0 * * * * * -0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * -0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * -0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * -0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * -0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * -0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * -0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * -0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * -0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * -0x1300 MDEM 3 32 5 0 * * * * 0 1 * * * * -0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW -0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * -0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * -0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * -0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * -0x3001 MNEO 6 60 5 0 * * * * 0 * * * * * -0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * -0x4001 SNONE 7 0 0 0 * * * * 0 * * * * * -0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * -0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * -0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * -0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * -0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * -0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * -0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * -0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * -0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * -0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * -0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * -0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * -0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * -0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * -0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * -0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * -0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * -0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x5012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * -0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x5112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * -0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x5212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * -0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x5312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * -0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH -0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x6012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * -0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH -0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH -0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x6112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * -0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH -0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH -0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x6212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * -0x6214 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS WQH -0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH -0x6305 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x6312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * -0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH -0x6315 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * -0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * -0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * -0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * -0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * -0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * -0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * -0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * -0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * -0x6621 XHFF 9 16 3 0 * * * * 1 * 1 * WPM * -0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * -0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * -0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * -0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * -0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * -0x7201 MBER 11 16 3 0 * * * * 0 * * * * * -0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * -0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * -0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * -0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * -0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * -0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * -0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * -0x7312 MFIE 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * -0x7313 MFIS 10 16 3 4 MFISG1B MFISG2B * * 0 1 * * * * -0x7314 MFIE 10 16 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * -0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * -0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * -0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * -0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * -0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * -0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * -0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * -0x7600 METT 11 16 3 0 * * * * 0 * * * * * -0x7601 MGHL 11 16 3 0 MGHL_MA * * * 0 0 * * * * -0x7602 MSPI 11 16 3 0 MSPI_MY * * * 0 * * * * * -0x7603 MDOG 11 16 3 0 MDOG_GR * * * 0 * * * * * -0x7604 MSPI 11 16 3 0 MSPI_WA * * * 0 * * * * * -0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * -0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * -0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * -0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * -0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * -0x7801 MGIB 11 16 3 0 MGIB_BR * * * 0 * * * * * -0x7802 MSLI 11 24 4 0 MSLI_BL * * 0 0 * * * * * -0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * -0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * -0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * -0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * -0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * -0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * -0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * -0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * -0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * -0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * -0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * -0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * -0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * -0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * -0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * -0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * -0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * -0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * -0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * -0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * -0x7d01 NSLF 11 16 3 0 * * * * 1 0 * * * * -0x7d02 ACHB 11 12 3 0 * * * * 0 0 * * * * -0x7d03 ACHW 11 12 3 0 * * * * 0 0 * * * * -0x7d04 NPRF 11 16 3 0 * * * * 1 0 * * * * -0x7d05 NNMF 11 16 3 0 * * * * 1 0 * * * * -0x7d06 NNWF 11 16 3 0 * * * * 1 0 * * * * -0x7d07 NSMF 11 16 3 0 * * * * 1 0 * * * * -0x7d08 NSWF 11 16 3 0 * * * * 1 0 * * * * -0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * -0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * -0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * -0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * -0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * -0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * -0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * -0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * -0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * -0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * -0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * -0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * -0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * -0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * -0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * -0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * -0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * -0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * -0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * -0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * -0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * -0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * -0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * -0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * -0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * -0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * -0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * -0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * -0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * -0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * -0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * -0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * -0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * -0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * -0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * -0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * -0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * -0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * -0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * -0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * -0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * -0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * -0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * -0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * -0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * -0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * -0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * -0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * -0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * -0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * -0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * -0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * -0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * -0x7f3b MSOG 10 16 3 4 * * * 0 0 1 * * * * -0x7f3c MASG 10 16 3 4 * * * 0 0 1 * * * * -0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * -0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * -0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * -0x7f40 MGLM 10 16 3 0 * * * * 0 1 * * * * -0x7f41 MDJL 10 16 3 0 MDJL_E1 MDJL_E2 * * 0 1 * * * * -0x7f42 NIRO 10 16 3 0 NIRO_RD * * * 0 1 * * * * -0x7f43 MOTY 10 16 3 0 MOTY_Y1 MOTY_Y2 * * 0 1 * * * * -0x7f44 NELW 10 16 3 0 * * * * 0 0 * * * * -0x7f45 MSAI 10 16 3 0 * * * 1 0 0 * * * * -0x7f46 MBEG 10 16 3 0 * * * * 0 0 * * * * -0x7f47 MFI2 10 32 5 0 * * * * 0 0 * * * * -0x7f48 MSOF 10 16 3 0 * * * 0 0 1 * * * * -0x7f49 MDMF 10 16 3 0 * * * 0 0 1 * * * * -0x7f4a MDAS 10 16 3 7 * * MDAG 0 0 1 * * * * -0x7f4b MDAF 10 16 3 0 * * * 0 0 1 * * * * -0x7f4c MPLN 10 16 3 7 * * MPLG 0 0 1 * * * * -0x7f4d MPLF 10 16 3 0 * * * 0 0 1 * * * * -0x7f4e MDVM 10 16 3 7 * * MDVG 0 0 1 * * * * -0x7f4f MDVF 10 16 3 0 * * * 0 0 1 * * * * -0x7f50 MMST 10 16 3 1 MMST_NI * * 0 0 0 * * * * -0x7f51 MMST 10 16 3 1 MMST_HA * * 0 0 0 * * * * -0x7f52 NSAI 10 16 3 0 NSAI_G1 NSAI_G2 * * 0 1 * * * * -0x7f53 NSAI 10 16 3 0 NSAI_R1 NSAI_R2 * * 0 1 * * * * -0x7f54 NSAI 10 16 3 0 NSAI_D1 NSAI_D2 * * 0 1 * * * * -0x7f55 NSOL 10 16 3 0 NSOL_C1 NSOL_C2 * * 0 1 * * * * -0x7f56 MWIL 10 16 3 7 * * * * 0 0 * * * * -0x7f57 MWIS 10 12 3 7 * * * * 0 0 * * * * -0x7f58 MABB 10 24 3 0 * * * * 0 0 * * * * -0x7f59 MABG 10 24 3 0 * * * * 0 0 * * * * -0x7f5a MABR 10 24 3 0 * * * * 0 0 * * * * -0x7f5b MABR 10 24 3 0 MABR_BL * * * 0 0 * * * * -0x7f5c MHAG 10 16 3 0 * * * * 0 0 * * * * -0x7f5d MHAG 10 16 3 0 MHAG_GR * * * 0 0 * * * * -0x7f5e MHAG 10 16 3 0 MHAG_SE * * * 0 0 * * * * -0x7f5f MHAN 10 16 3 0 * * * * 0 0 * * * * -0x7f60 MSNK 10 16 3 0 MSNK_W1 MSNK_W2 * * 0 1 * * * * -0x7f61 MDRJ 10 48 9 0 * * * * 0 0 * * * * -0x7f62 MDRJ 10 48 9 0 MDRJ_GR * * * 0 0 * * * * -0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB -0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW -0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW -0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * -0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * -0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * -0xa200 MWYV 13 24 4 0 MWYV_WS * * * 0 * * * * * -0xa201 MCEN 13 16 3 0 * * * * 0 * * * * * -0xa202 MTUN 13 16 3 0 * * * * 0 * * * * * -0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * -0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * -0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * -0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * -0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * -0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * -0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * -0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * -0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * -0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * -0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * -0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * -0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * -0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * -0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * -0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * -0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * -0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * -0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * -0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * -0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * -0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * -0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * -0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * -0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * -0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * -0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * -0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * -0xcc00 MKG1 15 16 3 0 * * * * 0 * * * * * -0xcc01 MKG2 15 16 3 0 * * * * 0 * * * * * -0xcc02 MKG3 15 16 3 0 * * * * 0 * * * * * -0xcc04 ARAT 15 0 1 0 * * * * 0 * * * * * -0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * -0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * -0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * -0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xe000 MCYC 17 48 5 0 * * * * * * * * * * -0xe010 METN 17 48 5 0 * * * * * * * * * * -0xe020 MTAN 17 32 5 0 * * * * * * * * * * -0xe040 MHIS 17 16 3 0 * * * * * * * * * * -0xe050 MLER 17 16 3 0 * * * * * * * * * * -0xe060 MLIC 17 16 3 0 * * * * * * * * * * -0xe070 MMIN 17 24 3 0 * * * * * * * * * * -0xe080 MMUM 17 16 3 0 * * * * * * * * * * -0xe090 MTAN 17 16 3 0 * * * * * * * * * * -0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * -0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * -0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * -0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * -0xe0e0 MCOR 17 24 3 0 * * * * * * * * * * -0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * -0xe0f1 MGLA 17 24 3 0 * * * * * * * * * * -0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * -0xe200 MBET 17 24 3 0 * * * * * * * * * * -0xe210 MBFI 17 12 3 0 * * * * * * * * * * -0xe220 MBBM 17 16 3 0 * * * * * * * * * * -0xe230 MBRH 17 64 7 0 * * * * * * * * * * -0xe240 MANI 17 24 3 0 * * * * * * * * * * -0xe241 MAN2 17 24 3 0 * * * * * * * * * * -0xe242 MAN3 17 24 3 0 * * * * * * * * * * -0xe243 MARU 17 16 3 0 * * * * * * * * * * -0xe244 MBA1 17 16 3 0 * * * * * * * * * * -0xe245 MBA2 17 16 3 0 * * * * * * * * * * -0xe246 MBA3 17 16 3 0 * * * * * * * * * * -0xe247 MBA4 17 16 3 0 * * * * * * * * * * -0xe248 MBA5 17 16 3 0 * * * * * * * * * * -0xe249 MBA6 17 16 3 0 * * * * * * * * * * -0xe24a MBAI 17 16 3 0 * * * * * * * * * * -0xe24b MELE 17 36 3 0 * * * * * * * * * * -0xe24c MELF 17 36 3 0 * * * * * * * * * * -0xe24d MELW 17 36 3 0 * * * * * * * * * * -0xe24e MGFI 17 48 3 0 * * * * * * * * * * -0xe24f MGFR 17 48 5 0 * * * * * * * * * * -0xe250 MGIR 17 24 3 0 * * * * * * * * * * -0xe251 MGVE 17 32 5 0 * * * * * * * * * * -0xe252 MHAR 17 16 3 0 * * * * * * * * * * -0xe253 MREM 17 48 5 0 * * * * * * * * * * -0xe254 MSCR 17 24 3 0 * * * * * * * * * * -0xe255 MSEE 17 16 3 0 * * * * * * * * * * -0xe256 MBE1 17 32 4 0 * * * * * * * * * * -0xe257 MBE2 17 16 3 0 * * * * * * * * * * -0xe258 MBE2 17 16 3 0 MBE2_HE * * * * * * * * * -0xe259 MSVI 17 16 3 0 * * * * * * * * * * -0xe25a MSV2 17 16 3 0 * * * * * * * * * * -0xe25b MUM2 17 24 3 0 * * * * * * * * * * -0xe25c MTA2 17 16 3 0 * * * * * * * * * * -0xe25d MYET 17 24 3 0 * * * * * * * * * * -0xe25e MWI4 17 16 3 0 * * * * * * * * * * -0xe25f MDRD 17 16 3 0 * * * * * * * * * * -0xe260 MCRD 17 24 3 0 * * * * * * * * * * -0xe261 MSAH 17 24 3 0 * * * * * * * * * * -0xe262 MSAT 17 24 3 0 * * * * * * * * * * -0xe263 MSV3 17 16 3 0 * * * * * * * * * * -0xe264 MSV4 17 16 3 0 * * * * * * * * * * -0xe265 MBOA 17 24 3 0 * * * * * * * * * * -0xe266 MBU2 17 16 3 0 * * * * * * * * * * -0xe267 MBUG 17 16 3 0 * * * * * * * * * * -0xe26a MDH2 17 24 3 0 * * * * * * * * * * -0xe26b MDTR 17 24 3 0 * * * * * * * * * * -0xe26d MFE2 17 24 3 0 * * * * * * * * * * -0xe26e MFEY 17 24 3 0 * * * * * * * * * * -0xe26f MGFO 17 24 4 0 * * * * * * * * * * -0xe270 MGO5 17 16 3 0 * * * * * * * * * * -0xe271 MGOC 17 16 3 0 * * * * * * * * * * -0xe272 MGWO 17 24 3 0 * * * * * * * * * * -0xe273 MGW2 17 24 3 0 * * * * * * * * * * -0xe274 MHOH 17 24 3 0 * * * * * * * * * * -0xe276 MLEM 17 16 3 0 * * * * * * * * * * -0xe279 MNOS 17 24 3 0 * * * * * * * * * * -0xe27d MWEB 17 16 3 0 * * * * * * * * * * -0xe27e MWRA 17 16 3 0 * * * * * * * * * * -0xe27f MBOA 17 24 3 0 MBOA_BR * * * * * * * * * -0xe280 MWOR 17 24 3 0 * * * * * * * * * * -0xe281 MYUH 17 16 3 0 * * * * * * * * * * -0xe282 MDRF 17 32 4 0 * * * * * * * * * * -0xe283 MDRM 17 32 4 0 * * * * * * * * * * -0xe288 MGWO 17 24 3 0 MGWO_DK * * * * * * * * * -0xe289 MGW2 17 24 3 0 MGW2_DK * * * * * * * * * -0xe28a MTRO 17 16 3 0 MTRO_DK * * * * * * * * * -0xe28b MABW 17 24 3 0 * * * * * * * * * * -0xe28c MWD2 17 36 5 0 * * * * * * * * * * -0xe28d MWD2 17 36 5 0 MWD2_SI * * * * * * * * * -0xe28e MWD2 17 36 5 0 MWD2_GR * * * * * * * * * -0xe28f MDRD 17 16 3 0 MDRD_RE * * * * * * * * * -0xe290 MDH2 17 24 3 0 MDH2_GR * * * * * * * * * -0xe291 MBU2 17 16 3 0 MBU2_SH * * * * * * * * * -0xe292 METN 17 48 5 0 METN_GH * * * * * * * * * -0xe293 MGHI 17 40 5 0 * * * * * * * * * * -0xe294 MBON 17 16 3 0 * * * * * * * * * * -0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * -0xe310 MGH2 17 16 3 0 * * * * * * * * * * -0xe320 MGH3 17 16 3 0 * * * * * * * * * * -0xe400 MGO1 17 16 3 0 * * * * * * * * * * -0xe410 MGO2 17 16 3 0 * * * * * * * * * * -0xe420 MGO3 17 16 3 0 * * * * * * * * * * -0xe430 MGO4 17 16 3 0 * * * * * * * * * * -0xe440 MGO6 17 16 3 0 * * * * * * * * * * -0xe441 MGO7 17 16 3 0 * * * * * * * * * * -0xe442 MGO8 17 16 3 0 * * * * * * * * * * -0xe443 MGO9 17 16 3 0 * * * * * * * * * * -0xe444 MGO10 17 16 3 0 * * * * * * * * * * -0xe500 MLIZ 17 24 3 0 * * * * * * * * * * -0xe510 MLI2 17 24 3 0 * * * * * * * * * * -0xe520 MLI3 17 32 5 0 * * * * * * * * * * -0xe600 MMYC 17 16 3 0 * * * * * * * * * * -0xe610 MMY2 17 16 3 0 * * * * * * * * * * -0xe700 MNO1 17 20 3 0 * * * * * * * * * * -0xe710 MNO2 17 20 3 0 * * * * * * * * * * -0xe720 MNO3 17 24 3 0 * * * * * * * * * * -0xe800 MOR1 17 16 3 0 * * * * * * * * * * -0xe810 MOR2 17 16 3 0 * * * * * * * * * * -0xe820 MOR3 17 16 3 0 * * * * * * * * * * -0xe830 MOR4 17 16 3 0 * * * * * * * * * * -0xe840 MOR5 17 16 3 0 * * * * * * * * * * -0xe900 MSAL 17 16 3 0 * * * * * * * * * * -0xe910 MSA2 17 16 3 0 * * * * * * * * * * -0xea00 MSHR 17 24 3 0 * * * * * * * * * * -0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * -0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * -0xeb00 MSKT 17 16 3 0 * * * * * * * * * * -0xeb10 MSKA 17 16 3 0 * * * * * * * * * * -0xeb20 MSKB 17 24 3 0 * * * * * * * * * * -0xec00 MWIG 17 16 3 0 * * * * * * * * * * -0xec10 MWI2 17 16 3 0 * * * * * * * * * * -0xec20 MWI3 17 16 3 0 * * * * * * * * * * -0xed00 MYU1 17 16 3 0 * * * * * * * * * * -0xed10 MYU2 17 16 3 0 * * * * * * * * * * -0xed20 MYU3 17 16 3 0 * * * * * * * * * * -0xee00 MZO2 17 16 3 0 * * * * * * * * * * -0xee10 MZO3 17 16 3 0 * * * * * * * * * * -0xef10 MWWE 17 24 3 0 * * * * * * * * * * + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD MOVE_SCALE +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * 0 +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * 0 +0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * 0 +0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * 0 +0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * 0 +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * 0 +0x0101 BGLRYU 0 0 3 7 * * * * * 0 * * * * 0 +0x0102 SPCL236U 0 0 3 7 * * * * * 0 * * * * 0 +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * 0 +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * 0 +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * 0 +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * 0 +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * 0 +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0301 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * 0 +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * 0 +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * 0 +0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * 0 +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * 0 +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * 0 +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * 0 +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * 0 +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * 0 +0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * 0 +0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * 0 +0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * 0 +0x1000 MWYV 1 24 4 0 * * * * 0 * * * * * 8 +0x1003 MWYV 1 24 4 0 MWYV_WH * * * 0 * * * * * 8 +0x1004 MWYV 1 24 4 0 MWYV_AL * * * 0 * * * * * 8 +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * 8 +0x1101 MWDR 1 72 7 0 * * * * 0 * * * * * 12 +0x1102 MTAN 1 32 5 0 MTAN_BL * * * 0 * 1 * * * 8 +0x1103 MTAN 1 32 5 0 MTAN_GR * * * 0 * 1 * * * 8 +0x1104 MTAN 1 32 5 0 MTAN_RD * * * 0 * 1 * * * 8 +0x1105 MWDR 1 72 7 0 MWDR_GR * * * 0 * * * * * 12 +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * 14 +0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * 14 +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * 14 +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * 14 +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * 14 +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * 14 +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * 14 +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * 14 +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * 14 +0x1300 MDEM 3 32 5 0 * * * * 0 1 * * * * 10 +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW 6 +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * 6 +0x2200 MOGM 5 16 3 0 * * * * 1 * 1 1 S1 * 6 +0x2300 MDKN 5 16 3 0 * * * * 0 * 1 1 * * 7 +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * 6 +0x3001 MNEO 6 60 5 0 * * * * 0 * * * * * 6 +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4001 SNONE 7 0 0 0 * * * * 0 * * * * * 0 +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * 0 +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * 0 +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * 0 +0x4400 LHMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4410 LHFC 7 16 3 0 * * * * 1 * * * * * 0 +0x4500 LFAM 7 16 3 0 * * * * 1 * * * * * 0 +0x4600 LDMF 7 16 3 0 * * * * 1 * * * * * 0 +0x4700 LEMF 7 16 3 0 * * * * 1 * * * * * 0 +0x4710 LEFF 7 16 3 0 * * * * 1 * * * * * 0 +0x4800 LIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x5012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * 9 +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x5112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * 9 +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x5212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * 9 +0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x5312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * 9 +0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH 9 +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x6012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * 9 +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH 9 +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH 9 +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x6112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * 9 +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH 9 +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH 9 +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x6212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * 9 +0x6214 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS WQH 9 +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH 9 +0x6305 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x6312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * 9 +0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH 9 +0x6315 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * 9 +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * 9 +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * 9 +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * 9 +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * 9 +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * 9 +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * 9 +0x6621 XHFF 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * 7 +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * 6 +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * 6 +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * 6 +0x7200 MBER 11 16 3 0 MBER_BL * * * 0 * * * * * 9 +0x7201 MBER 11 16 3 0 * * * * 0 * * * * * 9 +0x7202 MBER 11 16 3 0 MBER_CA * * * 0 * * * * * 9 +0x7203 MBER 11 16 3 0 MBER_PO * * * 0 * * * * * 9 +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * 10 +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * 7 +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * 10 +0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * 10 +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * 7 +0x7312 MFIE 10 16 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * 10 +0x7313 MFIS 10 16 3 4 MFISG1B MFISG2B * * 0 1 * * * * 7 +0x7314 MFIE 10 16 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * 10 +0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * 10 +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * 7 +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * 6 +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * 6 +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * 6 +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * 6 +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * 6 +0x7600 METT 11 16 3 0 * * * * 0 * * * * * 6 +0x7601 MGHL 11 16 3 0 MGHL_MA * * * 0 0 * * * * 4 +0x7602 MSPI 11 16 3 0 MSPI_MY * * * 0 * * * * * 5 +0x7603 MDOG 11 16 3 0 MDOG_GR * * * 0 * * * * * 6 +0x7604 MSPI 11 16 3 0 MSPI_WA * * * 0 * * * * * 5 +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * 4 +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * 4 +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * 4 +0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * 9 +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * 6 +0x7801 MGIB 11 16 3 0 MGIB_BR * * * 0 * * * * * 6 +0x7802 MSLI 11 24 4 0 MSLI_BL * * 0 0 * * * * * 2 +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * 2 +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * 2 +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * 2 +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * 2 +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * 2 +0x7a00 MSPI 11 16 3 0 MSPI_GI * * * 0 * * * * * 5 +0x7a01 MSPI 11 16 3 0 MSPI_HU * * * 0 * * * * * 5 +0x7a02 MSPI 11 16 3 0 MSPI_PH * * * 0 * * * * * 5 +0x7a03 MSPI 11 16 3 0 MSPI_SW * * * 0 * * * * * 5 +0x7a04 MSPI 11 16 3 0 MSPI_WR * * * 0 * * * * * 5 +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * 8 +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * 10 +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * 8 +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * 8 +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * 8 +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * 8 +0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * 8 +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * 6 +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * 6 +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * 4 +0x7d01 NSLF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d02 ACHB 11 12 3 0 * * * * 0 0 * * * * 3 +0x7d03 ACHW 11 12 3 0 * * * * 0 0 * * * * 3 +0x7d04 NPRF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d05 NNMF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d06 NNWF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d07 NSMF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d08 NSWF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * 10 +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * 10 +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * 8 +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * 13 +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * 4 +0x7f09 MSAH 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0c MKUO 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * 2 +0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0f MTRS 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f11 MUMB 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * 12 +0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f23 MSAT 10 16 3 0 * * * * 0 1 * * * * 14 +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f28 MKUL 10 16 3 0 * * * * 0 1 * * * * 14 +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * 5 +0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * 7 +0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * 9 +0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f39 MMST 10 16 3 1 * * * 0 0 0 * * * * 9 +0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f3b MSOG 10 16 3 4 * * * 0 0 1 * * * * 9 +0x7f3c MASG 10 16 3 4 * * * 0 0 1 * * * * 9 +0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * 9 +0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * 9 +0x7f40 MGLM 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f41 MDJL 10 16 3 0 MDJL_E1 MDJL_E2 * * 0 1 * * * * 11 +0x7f42 NIRO 10 16 3 0 NIRO_RD * * * 0 1 * * * * 9 +0x7f43 MOTY 10 16 3 0 MOTY_Y1 MOTY_Y2 * * 0 1 * * * * 8 +0x7f44 NELW 10 16 3 0 * * * * 0 0 * * * * 7 +0x7f45 MSAI 10 16 3 0 * * * 1 0 0 * * * * 9 +0x7f46 MBEG 10 16 3 0 * * * * 0 0 * * * * 8 +0x7f47 MFI2 10 32 5 0 * * * * 0 0 * * * * 9 +0x7f48 MSOF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f49 MDMF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f4a MDAS 10 16 3 7 * * MDAG 0 0 1 * * * * 9 +0x7f4b MDAF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f4c MPLN 10 16 3 7 * * MPLG 0 0 1 * * * * 9 +0x7f4d MPLF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f4e MDVM 10 16 3 7 * * MDVG 0 0 1 * * * * 9 +0x7f4f MDVF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f50 MMST 10 16 3 1 MMST_NI * * 0 0 0 * * * * 9 +0x7f51 MMST 10 16 3 1 MMST_HA * * 0 0 0 * * * * 9 +0x7f52 NSAI 10 16 3 0 NSAI_G1 NSAI_G2 * * 0 1 * * * * 5 +0x7f53 NSAI 10 16 3 0 NSAI_R1 NSAI_R2 * * 0 1 * * * * 5 +0x7f54 NSAI 10 16 3 0 NSAI_D1 NSAI_D2 * * 0 1 * * * * 5 +0x7f55 NSOL 10 16 3 0 NSOL_C1 NSOL_C2 * * 0 1 * * * * 9 +0x7f56 MWIL 10 16 3 7 * * * * 0 0 * * * * 12 +0x7f57 MWIS 10 12 3 7 * * * * 0 0 * * * * 10 +0x7f58 MABB 10 24 3 0 * * * * 0 0 * * * * 8 +0x7f59 MABG 10 24 3 0 * * * * 0 0 * * * * 9 +0x7f5a MABR 10 24 3 0 * * * * 0 0 * * * * 10 +0x7f5b MABR 10 24 3 0 MABR_BL * * * 0 0 * * * * 10 +0x7f5c MHAG 10 16 3 0 * * * * 0 0 * * * * 8 +0x7f5d MHAG 10 16 3 0 MHAG_GR * * * 0 0 * * * * 12 +0x7f5e MHAG 10 16 3 0 MHAG_SE * * * 0 0 * * * * 14 +0x7f5f MHAN 10 16 3 0 * * * * 0 0 * * * * 14 +0x7f60 MSNK 10 16 3 0 MSNK_W1 MSNK_W2 * * 0 1 * * * * 13 +0x7f61 MDRJ 10 48 9 0 * * * * 0 0 * * * * 11 +0x7f62 MDRJ 10 48 9 0 MDRJ_GR * * * 0 0 * * * * 11 +0x8000 MGNL 4 16 3 0 * * * * * * * * S1 HB 5 +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW 5 +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW 6 +0x9000 MOGR 12 16 3 0 * * * * 1 * * * * * 6 +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * 8 +0xa100 MCAR 13 16 3 0 * * * * 0 * * * * * 6 +0xa200 MWYV 13 24 4 0 MWYV_WS * * * 0 * * * * * 8 +0xa201 MCEN 13 16 3 0 * * * * 0 * * * * * 11 +0xa202 MTUN 13 16 3 0 * * * * 0 * * * * * 6 +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * 0 +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * 0 +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * 0 +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * 0 +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * 0 +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * 0 +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * 0 +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * 0 +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * 0 +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * 0 +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * 3 +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * 4 +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * 6 +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * 6 +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * 5 +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * 5 +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * 5 +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * 5 +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * 6 +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * 6 +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * 6 +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * 6 +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * 6 +0xcc00 MKG1 15 16 3 0 * * * * 0 * * * * * 0 +0xcc01 MKG2 15 16 3 0 * * * * 0 * * * * * 0 +0xcc02 MKG3 15 16 3 0 * * * * 0 * * * * * 0 +0xcc04 ARAT 15 0 1 0 * * * * 0 * * * * * 4 +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xe000 MCYC 17 48 5 0 * * * * * * * * * * 4 +0xe010 METN 17 48 5 0 * * * * * * * * * * 4 +0xe020 MTAN 17 32 5 0 * * * * * * * * * * 4 +0xe040 MHIS 17 16 3 0 * * * * * * * * * * 4 +0xe050 MLER 17 16 3 0 * * * * * * * * * * 4 +0xe060 MLIC 17 16 3 0 * * * * * * * * * * 4 +0xe070 MMIN 17 24 3 0 * * * * * * * * * * 4 +0xe080 MMUM 17 16 3 0 * * * * * * * * * * 4 +0xe090 MTAN 17 16 3 0 * * * * * * * * * * 4 +0xe0a0 MTIC 17 20 3 0 * * * * * * * * * * 4 +0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * 4 +0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * 4 +0xe0d0 MUMB 17 24 3 0 * * * * * * * * * * 4 +0xe0e0 MCOR 17 24 3 0 * * * * * * * * * * 9 +0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * 9 +0xe0f1 MGLA 17 24 3 0 * * * * * * * * * * 9 +0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * 5 +0xe200 MBET 17 24 3 0 * * * * * * * * * * 7 +0xe210 MBFI 17 12 3 0 * * * * * * * * * * 7 +0xe220 MBBM 17 16 3 0 * * * * * * * * * * 7 +0xe230 MBRH 17 64 7 0 * * * * * * * * * * 7 +0xe240 MANI 17 24 3 0 * * * * * * * * * * 7 +0xe241 MAN2 17 24 3 0 * * * * * * * * * * 7 +0xe242 MAN3 17 24 3 0 * * * * * * * * * * 7 +0xe243 MARU 17 16 3 0 * * * * * * * * * * 7 +0xe244 MBA1 17 16 3 0 * * * * * * * * * * 8 +0xe245 MBA2 17 16 3 0 * * * * * * * * * * 8 +0xe246 MBA3 17 16 3 0 * * * * * * * * * * 8 +0xe247 MBA4 17 16 3 0 * * * * * * * * * * 8 +0xe248 MBA5 17 16 3 0 * * * * * * * * * * 8 +0xe249 MBA6 17 16 3 0 * * * * * * * * * * 8 +0xe24a MBAI 17 16 3 0 * * * * * * * * * * 8 +0xe24b MELE 17 36 3 0 * * * * * * * * * * 7 +0xe24c MELF 17 36 3 0 * * * * * * * * * * 7 +0xe24d MELW 17 36 3 0 * * * * * * * * * * 7 +0xe24e MGFI 17 48 3 0 * * * * * * * * * * 7 +0xe24f MGFR 17 48 5 0 * * * * * * * * * * 7 +0xe250 MGIR 17 24 3 0 * * * * * * * * * * 8 +0xe251 MGVE 17 32 5 0 * * * * * * * * * * 7 +0xe252 MHAR 17 16 3 0 * * * * * * * * * * 8 +0xe253 MREM 17 48 5 0 * * * * * * * * * * 8 +0xe254 MSCR 17 24 3 0 * * * * * * * * * * 8 +0xe255 MSEE 17 16 3 0 * * * * * * * * * * 5 +0xe256 MBE1 17 32 4 0 * * * * * * * * * * 8 +0xe257 MBE2 17 16 3 0 * * * * * * * * * * 8 +0xe258 MBE2 17 16 3 0 MBE2_HE * * * * * * * * * 8 +0xe259 MSVI 17 16 3 0 * * * * * * * * * * 7 +0xe25a MSV2 17 16 3 0 * * * * * * * * * * 7 +0xe25b MUM2 17 24 3 0 * * * * * * * * * * 7 +0xe25c MTA2 17 16 3 0 * * * * * * * * * * 8 +0xe25d MYET 17 24 3 0 * * * * * * * * * * 7 +0xe25e MWI4 17 16 3 0 * * * * * * * * * * 15 +0xe25f MDRD 17 16 3 0 * * * * * * * * * * 4 +0xe260 MCRD 17 24 3 0 * * * * * * * * * * 8 +0xe261 MSAH 17 24 3 0 * * * * * * * * * * 8 +0xe262 MSAT 17 24 3 0 * * * * * * * * * * 8 +0xe263 MSV3 17 16 3 0 * * * * * * * * * * 7 +0xe264 MSV4 17 16 3 0 * * * * * * * * * * 7 +0xe265 MBOA 17 24 3 0 * * * * * * * * * * 10 +0xe266 MBU2 17 16 3 0 * * * * * * * * * * 8 +0xe267 MBUG 17 16 3 0 * * * * * * * * * * 8 +0xe26a MDH2 17 24 3 0 * * * * * * * * * * 10 +0xe26b MDTR 17 24 3 0 * * * * * * * * * * 6 +0xe26d MFE2 17 24 3 0 * * * * * * * * * * 8 +0xe26e MFEY 17 24 3 0 * * * * * * * * * * 8 +0xe26f MGFO 17 24 4 0 * * * * * * * * * * 8 +0xe270 MGO5 17 16 3 0 * * * * * * * * * * 8 +0xe271 MGOC 17 16 3 0 * * * * * * * * * * 8 +0xe272 MGWO 17 24 3 0 * * * * * * * * * * 10 +0xe273 MGW2 17 24 3 0 * * * * * * * * * * 10 +0xe274 MHOH 17 24 3 0 * * * * * * * * * * 9 +0xe276 MLEM 17 16 3 0 * * * * * * * * * * 6 +0xe279 MNOS 17 24 3 0 * * * * * * * * * * 7 +0xe27d MWEB 17 16 3 0 * * * * * * * * * * 8 +0xe27e MWRA 17 16 3 0 * * * * * * * * * * 8 +0xe27f MBOA 17 24 3 0 MBOA_BR * * * * * * * * * 10 +0xe280 MWOR 17 24 3 0 * * * * * * * * * * 10 +0xe281 MYUH 17 16 3 0 * * * * * * * * * * 8 +0xe282 MDRF 17 32 4 0 * * * * * * * * * * 8 +0xe283 MDRM 17 32 4 0 * * * * * * * * * * 8 +0xe288 MGWO 17 24 3 0 MGWO_DK * * * * * * * * * 10 +0xe289 MGW2 17 24 3 0 MGW2_DK * * * * * * * * * 10 +0xe28a MTRO 17 16 3 0 MTRO_DK * * * * * * * * * 8 +0xe28b MABW 17 24 3 0 * * * * * * * * * * 8 +0xe28c MWD2 17 36 5 0 * * * * * * * * * * 9 +0xe28d MWD2 17 36 5 0 MWD2_SI * * * * * * * * * 9 +0xe28e MWD2 17 36 5 0 MWD2_GR * * * * * * * * * 9 +0xe28f MDRD 17 16 3 0 MDRD_RE * * * * * * * * * 4 +0xe290 MDH2 17 24 3 0 MDH2_GR * * * * * * * * * 10 +0xe291 MBU2 17 16 3 0 MBU2_SH * * * * * * * * * 8 +0xe292 METN 17 48 5 0 METN_GH * * * * * * * * * 8 +0xe293 MGHI 17 40 5 0 * * * * * * * * * * 8 +0xe294 MBON 17 16 3 0 * * * * * * * * * * 10 +0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * 7 +0xe310 MGH2 17 16 3 0 * * * * * * * * * * 7 +0xe320 MGH3 17 16 3 0 * * * * * * * * * * 7 +0xe400 MGO1 17 16 3 0 * * * * * * * * * * 7 +0xe410 MGO2 17 16 3 0 * * * * * * * * * * 7 +0xe420 MGO3 17 16 3 0 * * * * * * * * * * 7 +0xe430 MGO4 17 16 3 0 * * * * * * * * * * 7 +0xe440 MGO6 17 16 3 0 * * * * * * * * * * 9 +0xe441 MGO7 17 16 3 0 * * * * * * * * * * 9 +0xe442 MGO8 17 16 3 0 * * * * * * * * * * 9 +0xe443 MGO9 17 16 3 0 * * * * * * * * * * 9 +0xe444 MGO10 17 16 3 0 * * * * * * * * * * 9 +0xe500 MLIZ 17 24 3 0 * * * * * * * * * * 4 +0xe510 MLI2 17 24 3 0 * * * * * * * * * * 4 +0xe520 MLI3 17 32 5 0 * * * * * * * * * * 4 +0xe600 MMYC 17 16 3 0 * * * * * * * * * * 7 +0xe610 MMY2 17 16 3 0 * * * * * * * * * * 7 +0xe700 MNO1 17 20 3 0 * * * * * * * * * * 6 +0xe710 MNO2 17 20 3 0 * * * * * * * * * * 6 +0xe720 MNO3 17 24 3 0 * * * * * * * * * * 6 +0xe800 MOR1 17 16 3 0 * * * * * * * * * * 7 +0xe810 MOR2 17 16 3 0 * * * * * * * * * * 7 +0xe820 MOR3 17 16 3 0 * * * * * * * * * * 7 +0xe830 MOR4 17 16 3 0 * * * * * * * * * * 7 +0xe840 MOR5 17 16 3 0 * * * * * * * * * * 7 +0xe900 MSAL 17 16 3 0 * * * * * * * * * * 7 +0xe910 MSA2 17 16 3 0 * * * * * * * * * * 7 +0xea00 MSHR 17 24 3 0 * * * * * * * * * * 0 +0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * 7 +0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * 7 +0xeb00 MSKT 17 16 3 0 * * * * * * * * * * 6 +0xeb10 MSKA 17 16 3 0 * * * * * * * * * * 6 +0xeb20 MSKB 17 24 3 0 * * * * * * * * * * 6 +0xec00 MWIG 17 16 3 0 * * * * * * * * * * 6 +0xec10 MWI2 17 16 3 0 * * * * * * * * * * 6 +0xec20 MWI3 17 16 3 0 * * * * * * * * * * 6 +0xed00 MYU1 17 16 3 0 * * * * * * * * * * 7 +0xed10 MYU2 17 16 3 0 * * * * * * * * * * 7 +0xed20 MYU3 17 16 3 0 * * * * * * * * * * 7 +0xee00 MZO2 17 16 3 0 * * * * * * * * * * 3 +0xee10 MZO3 17 16 3 0 * * * * * * * * * * 3 +0xef10 MWWE 17 24 3 0 * * * * * * * * * * 7 diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-iwd.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-iwd.2da index 74f59561f..ab6768598 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-iwd.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-iwd.2da @@ -1,273 +1,273 @@ 2DA V1.0 * - RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD -0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * -0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * -0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * -0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * -0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * -0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * -0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * -0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * -0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * -0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * -0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * -0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * -0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * -0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * -0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x1000 MWYV 1 24 5 0 * * * * 0 * * * * * -0x1001 MWYV 1 24 5 0 * * * * 0 * * * * * -0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW -0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * -0x2200 MOGM 5 24 5 0 * * * * 1 * 1 1 S1 * -0x2300 MDKN 5 24 5 0 * * * * 0 * 1 1 * * -0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * -0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * -0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * -0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * -0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * -0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * -0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * -0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * -0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * -0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * -0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * -0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * -0x4400 LHMC 7 20 3 0 * * * * 1 * * * * * -0x4410 LHFC 7 20 3 0 * * * * 1 * * * * * -0x4500 LFAM 7 20 3 0 * * * * 1 * * * * * -0x4600 LDMF 7 20 3 0 * * * * 1 * * * * * -0x4700 LEMF 7 20 3 0 * * * * 1 * * * * * -0x4710 LEFF 7 20 3 0 * * * * 1 * * * * * -0x4800 LIMC 7 20 3 0 * * * * 1 * * * * * -0x5000 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * -0x5001 CEMC 9 16 3 0 * * * * 1 0 1 * WPM * -0x5002 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * -0x5003 CIMC 9 16 3 0 * * * * 1 0 1 * WPS * -0x5010 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * -0x5011 CEFC 9 16 3 0 * * * * 1 0 1 * WPM * -0x5012 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * -0x5013 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * -0x5100 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * -0x5101 CEMF 9 16 3 0 * * * * 1 0 1 * WPM * -0x5102 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * -0x5103 CIMF 9 16 3 0 * * * * 1 0 1 * WPS * -0x5110 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * -0x5111 CEFF 9 16 3 0 * * * * 1 0 1 * WPM * -0x5112 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * -0x5113 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * -0x5200 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * -0x5201 CEMW 9 16 3 0 * * * * 1 0 1 * WPM * -0x5202 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x5203 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x5210 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * -0x5211 CEFW 9 16 3 0 * * * * 1 0 1 * WPM * -0x5212 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x5213 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x5300 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * -0x5301 CEMT 9 16 3 0 * * * * 1 0 0 * WPM * -0x5302 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * -0x5303 CIMT 9 16 3 0 * * * * 1 0 0 * WPS * -0x5310 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * -0x5311 CEFT 9 16 3 0 * * * * 1 0 0 * WPM * -0x5312 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * -0x5313 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * -0x6000 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * -0x6001 CEMC 9 16 3 0 * * * * 1 0 1 * WPM * -0x6002 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * -0x6003 CIMC 9 16 3 0 * * * * 1 0 1 * WPS * -0x6010 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * -0x6011 CEFC 9 16 3 0 * * * * 1 0 1 * WPM * -0x6012 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * -0x6013 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * -0x6100 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * -0x6101 CEMF 9 16 3 0 * * * * 1 0 1 * WPM * -0x6102 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * -0x6103 CIMF 9 16 3 0 * * * * 1 0 1 * WPS * -0x6110 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * -0x6111 CEFF 9 16 3 0 * * * * 1 0 1 * WPM * -0x6112 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * -0x6113 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * -0x6200 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * -0x6201 CEMW 9 16 3 0 * * * * 1 0 1 * WPM * -0x6202 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x6203 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x6210 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * -0x6211 CEFW 9 16 3 0 * * * * 1 0 1 * WPM * -0x6212 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x6213 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * -0x6300 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * -0x6301 CEMT 9 16 3 0 * * * * 1 0 0 * WPM * -0x6302 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * -0x6303 CIMT 9 16 3 0 * * * * 1 0 0 * WPS * -0x6310 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * -0x6311 CEFT 9 16 3 0 * * * * 1 0 0 * WPM * -0x6312 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * -0x6313 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * -0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * -0x6401 CTES 9 16 3 0 * * * * 1 * 0 0 WPM * -0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * -0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * -0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * -0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * -0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * -0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * -0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * -0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * -0x7200 MBER 11 24 5 0 MBER_BL * * * 0 * * * * * -0x7201 MBER 11 24 5 0 * * * * 0 * * * * * -0x7202 MBER 11 24 5 0 MBER_CA * * * 0 * * * * * -0x7203 MBER 11 24 5 0 MBER_PO * * * 0 * * * * * -0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * -0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * -0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * -0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * -0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * -0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * -0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * -0x7600 METT 11 16 3 0 * * * * 0 * * * * * -0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * -0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * -0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * -0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * -0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * -0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * -0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * -0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * -0x7a00 MSPI 11 24 4 0 MSPI_GI * * * 0 * * * * * -0x7a01 MSPI 11 24 4 0 MSPI_HU * * * 0 * * * * * -0x7a02 MSPI 11 24 4 0 MSPI_PH * * * 0 * * * * * -0x7a03 MSPI 11 24 4 0 MSPI_SW * * * 0 * * * * * -0x7a04 MSPI 11 24 4 0 MSPI_WR * * * 0 * * * * * -0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * -0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * -0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * -0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * -0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * -0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * -0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * -0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * -0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * -0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * -0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * -0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * -0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * -0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * -0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * -0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * -0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * -0x8000 MGNL 4 20 3 0 * * * * * * * * S1 HB -0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW -0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW -0x9000 MOGR 12 24 5 0 * * * * 1 * * * * * -0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * -0xa100 MCAR 13 32 5 0 * * * * 0 * * * * * -0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * -0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * -0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * -0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * -0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * -0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * -0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * -0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * -0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * -0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * -0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * -0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * -0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * -0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * -0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * -0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * -0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * -0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * -0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * -0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * -0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * -0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * -0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * -0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * -0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * -0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * -0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * -0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * -0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * -0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * -0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * -0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xe000_000f MBET 17 24 3 0 * * * * * * * * * * -0xe010_000f MBBM 17 20 3 0 * * * * * * * * * * -0xe020_000f MBBR 17 16 3 0 * * * * * * * * * * -0xe030_000f MBFI 17 12 3 0 * * * * * * * * * * -0xe040_000f MBRH 17 64 7 0 * * * * * * * * * * -0xe100_000f MCYC 17 48 7 0 * * * * * * * * * * -0xe110_000f METN 17 48 5 0 * * * * * * * * * * -0xe130_000f MGFR 17 40 5 0 * * * * * * * * * * -0xe140_000f MGVE 17 32 5 0 * * * * * * * * * * -0xe210_000f MELE 17 32 5 0 * * * * * * * * * * -0xe220_000f MELF 17 32 5 0 * * * * * * * * * * -0xe230_000f MELW 17 32 5 0 * * * * * * * * * * -0xe300_000f MGH2 17 16 3 0 * * * * * * * * * * -0xe310_000f MGH3 17 16 3 0 * * * * * * * * * * -0xe320_000f MWIG 17 16 3 0 * * * * * * * * * * -0xe330_000f MZO2 17 16 3 0 * * * * * * * * * * -0xe340_000f MWI2 17 16 3 0 * * * * * * * * * * -0xe350_000f MZO3 17 16 3 0 * * * * * * * * * * -0xe360_000f MWI3 17 16 3 0 * * * * * * * * * * -0xe380_000f MMUM 17 16 3 0 * * * * * * * * * * -0xe390_000f MHIS 17 16 3 0 * * * * * * * * * * -0xe400_000f MGO1 17 16 3 0 * * * * * * * * * * -0xe410_000f MGO2 17 16 3 0 * * * * * * * * * * -0xe420_000f MGO3 17 16 3 0 * * * * * * * * * * -0xe430_000f MGO4 17 16 3 0 * * * * * * * * * * -0xe440_000f MSVI 17 16 3 0 * * * * * * * * * * -0xe450_000f MSV2 17 16 3 0 * * * * * * * * * * -0xe510_000f MGIR 17 24 3 0 * * * * * * * * * * -0xe620_000f MSKB 17 24 3 0 * * * * * * * * * * -0xe700_000f MMIN 17 24 3 0 * * * * * * * * * * -0xe710_000f MTRO 17 16 3 0 * * * * * * * * * * -0xe720_000f MTIC 17 16 3 0 * * * * * * * * * * -0xe730_000f MTSN 17 24 3 0 * * * * * * * * * * -0xe750_000f MUMB 17 24 5 0 * * * * * * * * * * -0xe760_000f MYET 17 24 3 0 * * * * * * * * * * -0xe810_000f MOR1 17 16 3 0 * * * * * * * * * * -0xe820_000f MOR2 17 16 3 0 * * * * * * * * * * -0xe830_000f MOR3 17 16 3 0 * * * * * * * * * * -0xe840_000f MOR4 17 16 3 0 * * * * * * * * * * -0xe850_000f MOR5 17 16 3 0 * * * * * * * * * * -0xe860_000f MNO1 17 16 3 0 * * * * * * * * * * -0xe870_000f MNO2 17 16 3 0 * * * * * * * * * * -0xe880_000f MNO3 17 24 5 0 * * * * * * * * * * -0xe890_000f MLI3 17 32 5 0 * * * * * * * * * * -0xe8a0_000f MYU3 17 16 3 0 * * * * * * * * * * -0xe900_000f MSH1 17 16 3 0 * * * 1 * * * * * * -0xe910_000f MSH2 17 24 3 0 * * * 1 * * * * * * -0xe920_000f MGHO 17 16 3 0 * * * 1 * * * * * * -0xea20_000f MCRD 17 24 3 0 * * * * * * * * * * -0xeb00_000f MANI 17 24 3 0 * * * * * * * * * * -0xeb10_000f MAN2 17 24 3 0 * * * * * * * * * * -0xeb20_000f MAN3 17 24 3 0 * * * * * * * * * * -0xeb30_000f MBE1 17 32 3 0 * * * * * * * * * * -0xeb40_000f MBE2 17 16 3 0 * * * * * * * * * * -0xeb60_000f MLIC 17 16 3 0 * * * * * * * * * * -0xeb70_000f MLER 17 16 3 0 * * * * * * * * * * -0xeb90_000f MMYC 17 16 3 0 * * * * * * * * * * -0xeba0_000f MMY2 17 16 3 0 * * * * * * * * * * -0xebb0_000f MSHR 17 24 3 0 * * * * * * * * * * -0xebc0_000f MTAN 17 24 3 0 * * * * * * * * * * -0xebd0_000f MSAL 17 16 3 0 * * * * * * * * * * -0xebe0_000f MSA2 17 16 3 0 * * * * * * * * * * -0xebf1_000f MARU 17 16 3 0 * * * * * * * * * * -0xf000_000f MSKA 17 16 3 0 * * * * * * * * * * -0xf010_000f MSKT 17 16 3 0 * * * * * * * * * * -0xf100_000f MYU1 17 16 3 0 * * * * * * * * * * -0xf110_000f MYU2 17 16 3 0 * * * * * * * * * * -0xf200_000f MLIZ 17 24 5 0 * * * * * * * * * * -0xf210_000f MLI2 17 24 5 0 * * * * * * * * * * -0xf300_000f MGFI 17 40 5 0 * * * * * * * * * * + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD MOVE_SCALE +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * 0 +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * 0 +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * 0 +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * 0 +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * 0 +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * 0 +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * 0 +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * 0 +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * 0 +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * 0 +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * 0 +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * 0 +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * 0 +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * 0 +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x1000 MWYV 1 24 5 0 * * * * 0 * * * * * 8 +0x1001 MWYV 1 24 5 0 * * * * 0 * * * * * 8 +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW 6 +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * 6 +0x2200 MOGM 5 24 5 0 * * * * 1 * 1 1 S1 * 6 +0x2300 MDKN 5 24 5 0 * * * * 0 * 1 1 * * 7 +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * 6 +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * 0 +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * 0 +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * 0 +0x4400 LHMC 7 20 3 0 * * * * 1 * * * * * 0 +0x4410 LHFC 7 20 3 0 * * * * 1 * * * * * 0 +0x4500 LFAM 7 20 3 0 * * * * 1 * * * * * 0 +0x4600 LDMF 7 20 3 0 * * * * 1 * * * * * 0 +0x4700 LEMF 7 20 3 0 * * * * 1 * * * * * 0 +0x4710 LEFF 7 20 3 0 * * * * 1 * * * * * 0 +0x4800 LIMC 7 20 3 0 * * * * 1 * * * * * 0 +0x5000 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x5001 CEMC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5002 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5003 CIMC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5010 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5011 CEFC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5012 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5013 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5100 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x5101 CEMF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5102 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5103 CIMF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5110 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5111 CEFF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5112 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5113 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5200 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x5201 CEMW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5202 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5203 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5210 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5211 CEFW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x5212 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5213 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x5300 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * 9 +0x5301 CEMT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x5302 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x5303 CIMT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x5310 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x5311 CEFT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x5312 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x5313 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x6000 CHMC 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x6001 CEMC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6002 CDMC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6003 CIMC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6010 CHFC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6011 CEFC 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6012 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6013 CIFC 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6100 CHMF 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x6101 CEMF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6102 CDMF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6103 CIMF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6110 CHFF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6111 CEFF 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6112 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6113 CIFF 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6200 CHMW 9 16 3 0 * * * * 1 0 1 * WPL * 9 +0x6201 CEMW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6202 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6203 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6210 CHFW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6211 CEFW 9 16 3 0 * * * * 1 0 1 * WPM * 9 +0x6212 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6213 CDMW 9 16 3 0 * * * * 1 0 1 * WPS * 9 +0x6300 CHMT 9 16 3 0 * * * * 1 0 0 * WPL * 9 +0x6301 CEMT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x6302 CDMT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x6303 CIMT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x6310 CHFT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x6311 CEFT 9 16 3 0 * * * * 1 0 0 * WPM * 9 +0x6312 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x6313 CIFT 9 16 3 0 * * * * 1 0 0 * WPS * 9 +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * 9 +0x6401 CTES 9 16 3 0 * * * * 1 * 0 0 WPM * 9 +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * 9 +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * 9 +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * 7 +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * 6 +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * 6 +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * 6 +0x7200 MBER 11 24 5 0 MBER_BL * * * 0 * * * * * 4 +0x7201 MBER 11 24 5 0 * * * * 0 * * * * * 4 +0x7202 MBER 11 24 5 0 MBER_CA * * * 0 * * * * * 4 +0x7203 MBER 11 24 5 0 MBER_PO * * * 0 * * * * * 4 +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * 10 +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * 10 +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * 6 +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * 6 +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * 6 +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * 6 +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * 6 +0x7600 METT 11 16 3 0 * * * * 0 * * * * * 6 +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * 8 +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * 4 +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * 6 +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * 2 +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * 2 +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * 2 +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * 2 +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * 2 +0x7a00 MSPI 11 24 4 0 MSPI_GI * * * 0 * * * * * 5 +0x7a01 MSPI 11 24 4 0 MSPI_HU * * * 0 * * * * * 5 +0x7a02 MSPI 11 24 4 0 MSPI_PH * * * 0 * * * * * 5 +0x7a03 MSPI 11 24 4 0 MSPI_SW * * * 0 * * * * * 5 +0x7a04 MSPI 11 24 4 0 MSPI_WR * * * 0 * * * * * 5 +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * 8 +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * 8 +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * 8 +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * 8 +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * 8 +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * 8 +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * 6 +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * 6 +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * 4 +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * 10 +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * 10 +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * 8 +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * 10 +0x8000 MGNL 4 20 3 0 * * * * * * * * S1 HB 5 +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW 5 +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW 6 +0x9000 MOGR 12 24 5 0 * * * * 1 * * * * * 6 +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * 8 +0xa100 MCAR 13 32 5 0 * * * * 0 * * * * * 6 +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * 0 +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * 0 +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * 0 +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * 0 +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * 0 +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * 0 +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * 0 +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * 0 +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * 0 +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * 0 +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * 3 +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * 4 +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * 6 +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * 6 +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * 5 +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * 5 +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * 5 +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * 5 +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * 6 +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * 6 +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * 6 +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * 6 +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * 6 +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xe000_000f MBET 17 24 3 0 * * * * * * * * * * 4 +0xe010_000f MBBM 17 20 3 0 * * * * * * * * * * 4 +0xe020_000f MBBR 17 16 3 0 * * * * * * * * * * 4 +0xe030_000f MBFI 17 12 3 0 * * * * * * * * * * 8 +0xe040_000f MBRH 17 64 7 0 * * * * * * * * * * 4 +0xe100_000f MCYC 17 48 7 0 * * * * * * * * * * 4 +0xe110_000f METN 17 48 5 0 * * * * * * * * * * 4 +0xe130_000f MGFR 17 40 5 0 * * * * * * * * * * 8 +0xe140_000f MGVE 17 32 5 0 * * * * * * * * * * 8 +0xe210_000f MELE 17 32 5 0 * * * * * * * * * * 8 +0xe220_000f MELF 17 32 5 0 * * * * * * * * * * 6 +0xe230_000f MELW 17 32 5 0 * * * * * * * * * * 8 +0xe300_000f MGH2 17 16 3 0 * * * * * * * * * * 8 +0xe310_000f MGH3 17 16 3 0 * * * * * * * * * * 6 +0xe320_000f MWIG 17 16 3 0 * * * * * * * * * * 6 +0xe330_000f MZO2 17 16 3 0 * * * * * * * * * * 4 +0xe340_000f MWI2 17 16 3 0 * * * * * * * * * * 8 +0xe350_000f MZO3 17 16 3 0 * * * * * * * * * * 4 +0xe360_000f MWI3 17 16 3 0 * * * * * * * * * * 6 +0xe380_000f MMUM 17 16 3 0 * * * * * * * * * * 5 +0xe390_000f MHIS 17 16 3 0 * * * * * * * * * * 4 +0xe400_000f MGO1 17 16 3 0 * * * * * * * * * * 8 +0xe410_000f MGO2 17 16 3 0 * * * * * * * * * * 8 +0xe420_000f MGO3 17 16 3 0 * * * * * * * * * * 8 +0xe430_000f MGO4 17 16 3 0 * * * * * * * * * * 8 +0xe440_000f MSVI 17 16 3 0 * * * * * * * * * * 8 +0xe450_000f MSV2 17 16 3 0 * * * * * * * * * * 8 +0xe510_000f MGIR 17 24 3 0 * * * * * * * * * * 5 +0xe620_000f MSKB 17 24 3 0 * * * * * * * * * * 4 +0xe700_000f MMIN 17 24 3 0 * * * * * * * * * * 6 +0xe710_000f MTRO 17 16 3 0 * * * * * * * * * * 6 +0xe720_000f MTIC 17 16 3 0 * * * * * * * * * * 6 +0xe730_000f MTSN 17 24 3 0 * * * * * * * * * * 8 +0xe750_000f MUMB 17 24 5 0 * * * * * * * * * * 8 +0xe760_000f MYET 17 24 3 0 * * * * * * * * * * 8 +0xe810_000f MOR1 17 16 3 0 * * * * * * * * * * 6 +0xe820_000f MOR2 17 16 3 0 * * * * * * * * * * 6 +0xe830_000f MOR3 17 16 3 0 * * * * * * * * * * 6 +0xe840_000f MOR4 17 16 3 0 * * * * * * * * * * 6 +0xe850_000f MOR5 17 16 3 0 * * * * * * * * * * 6 +0xe860_000f MNO1 17 16 3 0 * * * * * * * * * * 6 +0xe870_000f MNO2 17 16 3 0 * * * * * * * * * * 6 +0xe880_000f MNO3 17 24 5 0 * * * * * * * * * * 6 +0xe890_000f MLI3 17 32 5 0 * * * * * * * * * * 7 +0xe8a0_000f MYU3 17 16 3 0 * * * * * * * * * * 6 +0xe900_000f MSH1 17 16 3 0 * * * 1 * * * * * * 6 +0xe910_000f MSH2 17 24 3 0 * * * 1 * * * * * * 8 +0xe920_000f MGHO 17 16 3 0 * * * 1 * * * * * * 8 +0xea20_000f MCRD 17 24 3 0 * * * * * * * * * * 8 +0xeb00_000f MANI 17 24 3 0 * * * * * * * * * * 6 +0xeb10_000f MAN2 17 24 3 0 * * * * * * * * * * 6 +0xeb20_000f MAN3 17 24 3 0 * * * * * * * * * * 4 +0xeb30_000f MBE1 17 32 3 0 * * * * * * * * * * 8 +0xeb40_000f MBE2 17 16 3 0 * * * * * * * * * * 8 +0xeb60_000f MLIC 17 16 3 0 * * * * * * * * * * 8 +0xeb70_000f MLER 17 16 3 0 * * * * * * * * * * 8 +0xeb90_000f MMYC 17 16 3 0 * * * * * * * * * * 8 +0xeba0_000f MMY2 17 16 3 0 * * * * * * * * * * 8 +0xebb0_000f MSHR 17 24 3 0 * * * * * * * * * * 8 +0xebc0_000f MTAN 17 24 3 0 * * * * * * * * * * 8 +0xebd0_000f MSAL 17 16 3 0 * * * * * * * * * * 6 +0xebe0_000f MSA2 17 16 3 0 * * * * * * * * * * 8 +0xebf1_000f MARU 17 16 3 0 * * * * * * * * * * 8 +0xf000_000f MSKA 17 16 3 0 * * * * * * * * * * 6 +0xf010_000f MSKT 17 16 3 0 * * * * * * * * * * 6 +0xf100_000f MYU1 17 16 3 0 * * * * * * * * * * 6 +0xf110_000f MYU2 17 16 3 0 * * * * * * * * * * 6 +0xf200_000f MLIZ 17 24 5 0 * * * * * * * * * * 6 +0xf210_000f MLI2 17 24 5 0 * * * * * * * * * * 5 +0xf300_000f MGFI 17 40 5 0 * * * * * * * * * * 6 diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-iwd2.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-iwd2.2da index f71fce26b..a49854c14 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-iwd2.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-iwd2.2da @@ -1,384 +1,384 @@ 2DA V1.0 * - RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD -0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * -0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * -0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * -0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * -0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * -0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * -0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * -0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * -0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * -0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * -0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * -0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * -0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * -0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * -0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * -0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x1000 MWYV 1 24 5 0 * * * * 0 * * * * * -0x1001 MWY2 1 24 5 0 * * * * 0 * * * * * -0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * -0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * -0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * -0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * -0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * -0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * -0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * -0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * -0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * -0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW -0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * -0x2200 MOGM 5 24 5 0 * * * * 1 * 1 1 S1 * -0x2300 MDKN 5 24 5 0 * * * * 0 * 1 1 * * -0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * -0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * -0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * -0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * -0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * -0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * -0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * -0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * -0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * -0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * -0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * -0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * -0x4400 LHMC 7 20 3 0 * * * * 1 * * * * * -0x4410 LHFC 7 20 3 0 * * * * 1 * * * * * -0x4500 LFAM 7 20 3 0 * * * * 1 * * * * * -0x4600 LDMF 7 20 3 0 * * * * 1 * * * * * -0x4700 LEMF 7 20 3 0 * * * * 1 * * * * * -0x4710 LEFF 7 20 3 0 * * * * 1 * * * * * -0x4800 LIMC 7 20 3 0 * * * * 1 * * * * * -0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x5012 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x5112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x5212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x5312 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH -0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL WQH -0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x6012 CIFB 8 16 3 0 * * CIFB * 1 1 1 * WQS * -0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH -0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQL WQH -0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH -0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x6112 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH -0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH -0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x6212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x6214 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH -0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6300 CHMT 8 16 3 0 * * CHMF * 1 1 0 * WQL * -0x6301 CEMT 8 16 3 0 * * CEMF * 1 1 0 * WQM * -0x6302 CDMT 8 16 3 0 * * CDMF * 1 1 0 * WQS * -0x6303 CIMT 8 16 3 0 * * CIMF * 1 1 0 * WQS WQH -0x6304 CDMT 8 16 3 0 * * CDMF * 1 1 0 * WQS WQH -0x6305 CHMT 8 16 3 0 * * CHMF * 1 1 0 * WQL * -0x6310 CHFT 8 16 3 0 * * CHFF * 1 1 0 * WQN * -0x6311 CEFT 8 16 3 0 * * CEFF * 1 1 0 * WQM * -0x6312 CIFT 8 16 3 0 * * CIFF * 1 1 0 * WQS * -0x6313 CIFT 8 16 3 0 * * CIFF * 1 1 0 * WQS * -0x6314 CIFT 8 16 3 0 * * CIFF * 1 1 0 * WQS WQH -0x6315 CHFT 8 16 3 0 * * CHFF * 1 1 0 * WQN * -0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * -0x6401 CTES 9 16 3 0 * * * * 1 * 0 0 WPM * -0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * -0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * -0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * -0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * -0x6406 MDGU 9 16 3 0 * * * * 1 * 1 * WPL * -0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * -0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * -0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * -0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * -0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * -0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * -0x7200 MBER 11 24 5 0 MBER_BL * * * 0 * * * * * -0x7201 MBER 11 24 5 0 * * * * 0 * * * * * -0x7202 MBER 11 24 5 0 MBER_CA * * * 0 * * * * * -0x7203 MBER 11 24 5 0 MBER_PO * * * 0 * * * * * -0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * -0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * -0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * -0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * -0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * -0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * -0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * -0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * -0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * -0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * -0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * -0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * -0x7600 METT 11 16 3 0 * * * * 0 * * * * * -0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * -0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * -0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * -0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * -0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * -0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * -0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * -0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * -0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * -0x7a00 MSPI 11 24 4 0 MSPI_GI * * * 0 * * * * * -0x7a01 MSPI 11 24 4 0 MSPI_HU * * * 0 * * * * * -0x7a02 MSPI 11 24 4 0 MSPI_PH * * * 0 * * * * * -0x7a03 MSPI 11 24 4 0 MSPI_SW * * * 0 * * * * * -0x7a04 MSPI 11 24 4 0 MSPI_WR * * * 0 * * * * * -0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * -0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * -0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * -0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * -0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * -0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * -0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * -0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * -0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * -0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * -0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * -0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * -0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * -0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * -0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * -0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * -0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * -0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * -0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * -0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * -0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * -0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * -0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * -0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * -0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * -0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * -0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * -0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * -0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * -0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * -0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * -0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * -0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * -0x8000 MGNL 4 20 3 0 * * * * * * * * S1 HB -0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW -0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW -0x9000 MOGR 12 24 5 0 * * * * 1 * * * * * -0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * -0xa100 MCAR 13 32 5 0 * * * * 0 * * * * * -0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * -0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * -0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * -0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * -0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * -0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * -0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * -0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * -0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * -0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * -0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * -0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * -0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * -0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * -0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * -0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * -0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * -0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * -0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * -0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * -0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * -0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * -0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * -0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * -0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * -0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * -0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * -0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * -0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * -0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * -0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * -0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xe000_000f MBET 17 24 3 0 * * * * * * * * * * -0xe010_000f MBBM 17 20 3 0 * * * * * * * * * * -0xe020_000f MBBR 17 16 3 0 * * * * * * * * * * -0xe030_000f MBFI 17 12 3 0 * * * * * * * * * * -0xe040_000f MBRH 17 64 7 0 * * * * * * * * * * -0xe050_000f MREM 17 48 5 0 * * * * * * * * * * -0xe060_000f MHOH 17 24 3 0 * * * * * * * * * * -0xe100_000f MCYC 17 48 7 0 * * * * * * * * * * -0xe110_000f METN 17 48 5 0 * * * * * * * * * * -0xe130_000f MGFR 17 40 5 0 * * * * * * * * * * -0xe140_000f MGVE 17 32 5 0 * * * * * * * * * * -0xe150_000f MGFO 17 32 5 0 * * * * * * * * * * -0xe210_000f MELE 17 32 5 0 * * * * * * * * * * -0xe220_000f MELF 17 32 5 0 * * * * * * * * * * -0xe230_000f MELW 17 32 5 0 * * * * * * * * * * -0xe240_000f MHAR 17 16 3 0 * * * * * * * * * * -0xe250_000f MWWE 17 24 3 0 * * * * * * * * * * -0xe260_000f MFEY 17 24 3 0 * * * * * * * * * * -0xe270_000f MDTR 17 24 3 0 * * * * * * * * * * -0xe280_000f MFE2 17 24 3 0 * * * * * * * * * * -0xe290_000f MEW2 17 24 3 0 * * * * * * * * * * -0xe300_000f MGH2 17 16 3 0 * * * * * * * * * * -0xe310_000f MGH3 17 16 3 0 * * * * * * * * * * -0xe320_000f MWIG 17 16 3 0 * * * * * * * * * * -0xe330_000f MZO2 17 16 3 0 * * * * * * * * * * -0xe340_000f MZO3 17 16 3 0 * * * * * * * * * * -0xe350_000f MWI2 17 16 3 0 * * * * * * * * * * -0xe360_000f MWI3 17 16 3 0 * * * * * * * * * * -0xe380_000f MMUM 17 16 3 0 * * * * * * * * * * -0xe390_000f MHIS 17 16 3 0 * * * * * * * * * * -0xe3a0_000f MDRD 17 24 3 0 * * * * * * * * * * -0xe3b0_000f MWAV 17 16 3 0 * * * 1 * * * * * * -0xe400_000f MGO1 17 16 3 0 * * * * * * * * * * -0xe410_000f MGO2 17 16 3 0 * * * * * * * * * * -0xe420_000f MGO3 17 16 3 0 * * * * * * * * * * -0xe430_000f MGO4 17 16 3 0 * * * * * * * * * * -0xe440_000f MSVI 17 16 3 0 * * * * * * * * * * -0xe450_000f MSV2 17 16 3 0 * * * * * * * * * * -0xe460_000f MGWO 17 24 3 0 * * * * * * * * * * -0xe470_000f MGOC 17 16 3 0 * * * * * * * * * * -0xe480_000f MGW2 17 24 3 0 * * * * * * * * * * -0xe490_000f MGO5 17 16 3 0 * * * * * * * * * * -0xe510_000f MGIR 17 24 3 0 * * * * * * * * * * -0xe520_000f MGIC 17 32 5 0 * * * * * * * * * * -0xe620_000f MSKB 17 24 3 0 * * * * * * * * * * -0xe700_000f MMIN 17 24 3 0 * * * * * * * * * * -0xe710_000f MTRO 17 16 3 0 * * * * * * * * * * -0xe720_000f MTIC 17 16 3 0 * * * * * * * * * * -0xe730_000f MTSN 17 24 3 0 * * * * * * * * * * -0xe750_000f MUMB 17 24 5 0 * * * * * * * * * * -0xe760_000f MYET 17 24 3 0 * * * * * * * * * * -0xe770_000f MBA4 17 16 3 0 * * * * * * * * * * -0xe780_000f MBA5 17 16 3 0 * * * * * * * * * * -0xe790_000f MBA6 17 16 3 0 * * * * * * * * * * -0xe7a0_000f MBAI 17 16 3 0 * * * * * * * * * * -0xe7b0_000f MBOA 17 24 3 0 * * * * * * * * * * -0xe7c0_000f MABW 17 24 3 0 * * * * * * * * * * -0xe7d0_000f MMAL 17 16 3 0 * * * * * * * * * * -0xe7e0_000f MSCR 17 24 3 0 * * * * * * * * * * -0xe7f0_000f MUM2 17 24 5 0 * * * * * * * * * * -0xe800_000f MOR6 17 16 3 0 * * * * * * * * * * -0xe810_000f MOR1 17 16 3 0 * * * * * * * * * * -0xe820_000f MOR2 17 16 3 0 * * * * * * * * * * -0xe830_000f MOR3 17 16 3 0 * * * * * * * * * * -0xe840_000f MOR4 17 16 3 0 * * * * * * * * * * -0xe850_000f MOR5 17 16 3 0 * * * * * * * * * * -0xe860_000f MNO1 17 16 3 0 * * * * * * * * * * -0xe870_000f MNO2 17 16 3 0 * * * * * * * * * * -0xe880_000f MNO3 17 24 5 0 * * * * * * * * * * -0xe890_000f MLI3 17 32 5 0 * * * * * * * * * * -0xe8a0_000f MYU3 17 16 3 0 * * * * * * * * * * -0xe8b0_000f MYUH 17 16 3 0 * * * * * * * * * * -0xe8c0_000f MBUG 17 16 3 0 * * * * * * * * * * -0xe8d0_000f MNOS 17 24 3 0 * * * * * * * * * * -0xe8e0_000f MBU2 17 16 3 0 * * * * * * * * * * -0xe8f0_000f MOR7 17 16 3 0 * * * * * * * * * * -0xe900_000f MSH1 17 16 3 0 * * * 1 * * * * * * -0xe910_000f MSH2 17 24 3 0 * * * 1 * * * * * * -0xe920_000f MGHO 17 16 3 0 * * * 1 * * * * * * -0xea20_000f MCRD 17 24 3 0 * * * * * * * * * * -0xeb00_000f MANI 17 24 3 0 * * * * * * * * * * -0xeb10_000f MAN2 17 24 3 0 * * * * * * * * * * -0xeb20_000f MAN3 17 24 3 0 * * * * * * * * * * -0xeb30_000f MBE1 17 32 3 0 * * * * * * * * * * -0xeb40_000f MBE2 17 16 3 0 * * * * * * * * * * -0xeb51 MSEE 17 16 3 0 * * * * * * * * * * -0xeb60_000f MLIC 17 16 3 0 * * * * * * * * * * -0xeb70_000f MLER 17 16 3 0 * * * * * * * * * * -0xeb90_000f MMYC 17 16 3 0 * * * * * * * * * * -0xeba0_000f MMY2 17 16 3 0 * * * * * * * * * * -0xebb0_000f MSHR 17 24 3 0 * * * * * * * * * * -0xebc0_000f MTAN 17 24 3 0 * * * * * * * * * * -0xebd0_000f MSAL 17 16 3 0 * * * * * * * * * * -0xebe0_000f MSA2 17 16 3 0 * * * * * * * * * * -0xebf1_000f MARU 17 16 3 0 * * * * * * * * * * -0xec00_000f MWDR 17 72 7 0 * * * * * * * * * * -0xec10_000f MCHY 17 16 3 0 * * * * * * * * * * -0xec20_000f MSHE 17 24 3 0 * * * * * * * * * * -0xec30_000f MCHI 17 48 5 0 * * * * * * * * * * -0xec40_000f MDH1 17 24 3 0 * * * * * * * * * * -0xec50_000f MDH2 17 24 3 0 * * * * * * * * * * -0xed00_000f MCOR 17 24 5 0 * * * * * * * * * * -0xed10_000f MGLA 17 24 5 0 * * * * * * * * * * -0xed20_000f MLEM 17 16 3 0 * * * * * * * * * * -0xee00_000f MWEB 17 24 3 0 * * * * * * * * * * -0xee10_000f MWRA 17 24 3 0 * * * * * * * * * * -0xef00_000f MISA 17 24 3 0 * * * * * * * * * * -0xef10_000f MMAD 17 24 3 0 * * * * * * * * * * -0xef20_000f MWOR 17 24 5 0 * * * * * * * * * * -0xef50_000f MKG1 17 16 3 0 * * * * * * * * * * -0xef60_000f MKG2 17 16 3 0 * * * * * * * * * * -0xef70_000f MKG3 17 16 3 0 * * * * * * * * * * -0xef90_000f MWIL 17 16 3 0 * * * * * * * * * * -0xefa0_000f MGEN 17 16 2 0 * * * * * * * * * * -0xefb0_000f MGEN 17 24 3 0 * * * * * * * * * * -0xefc0_000f MGEN 17 36 4 0 * * * * * * * * * * -0xefd0_000f MGEN 17 48 5 0 * * * * * * * * * * -0xefe0_000f MGEN 17 12 3 0 * * * * * * * * * * -0xeff0_000f MGEN 17 64 7 0 * * * * * * * * * * -0xf000_000f MSKA 17 16 3 0 * * * * * * * * * * -0xf010_000f MSKT 17 16 3 0 * * * * * * * * * * -0xf020_000f MWI4 17 16 3 0 * * * * * * * * * * -0xf100_000f MYU1 17 16 3 0 * * * * * * * * * * -0xf110_000f MYU2 17 16 3 0 * * * * * * * * * * -0xf200_000f MLIZ 17 24 5 0 * * * * * * * * * * -0xf210_000f MLI2 17 24 5 0 * * * * * * * * * * -0xf300_000f MGFI 17 40 5 0 * * * * * * * * * * -0xf400_000f MSAH 17 24 3 0 * * * * * * * * * * -0xf410_000f MSAT 17 32 5 0 * * * * * * * * * * -0xf500_000f MDRM 17 32 4 0 * * * * * * * * * * -0xf510_000f MDRF 17 32 4 0 * * * * * * * * * * -0xf770_000f MBA1 17 16 3 0 * * * * * * * * * * -0xf780_000f MBA2 17 16 3 0 * * * * * * * * * * -0xf790_000f MBA3 17 16 3 0 * * * * * * * * * * + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD MOVE_SCALE +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * 0 +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * 0 +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * 0 +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * 0 +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * 0 +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * 0 +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * 0 +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * 0 +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * 0 +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * 0 +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * 0 +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * 0 +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * 0 +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * 0 +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * 0 +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x1000 MWYV 1 24 5 0 * * * * 0 * * * * * 8 +0x1001 MWY2 1 24 5 0 * * * * 0 * * * * * 8 +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * 14 +0x1201 MDR2 2 93 13 0 * * * * 0 1 * * * * 14 +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * 14 +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * 14 +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * 14 +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * 14 +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * 14 +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * 14 +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * 14 +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW 6 +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * 6 +0x2200 MOGM 5 24 5 0 * * * * 1 * 1 1 S1 * 6 +0x2300 MDKN 5 24 5 0 * * * * 0 * 1 1 * * 7 +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * 6 +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * 0 +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * 0 +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * 0 +0x4400 LHMC 7 20 3 0 * * * * 1 * * * * * 0 +0x4410 LHFC 7 20 3 0 * * * * 1 * * * * * 0 +0x4500 LFAM 7 20 3 0 * * * * 1 * * * * * 0 +0x4600 LDMF 7 20 3 0 * * * * 1 * * * * * 0 +0x4700 LEMF 7 20 3 0 * * * * 1 * * * * * 0 +0x4710 LEFF 7 20 3 0 * * * * 1 * * * * * 0 +0x4800 LIMC 7 20 3 0 * * * * 1 * * * * * 0 +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x5012 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x5112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x5212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x5312 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH 9 +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL WQH 9 +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x6012 CIFB 8 16 3 0 * * CIFB * 1 1 1 * WQS * 9 +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH 9 +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQL WQH 9 +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH 9 +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x6112 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH 9 +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH 9 +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x6212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x6214 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH 9 +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6300 CHMT 8 16 3 0 * * CHMF * 1 1 0 * WQL * 9 +0x6301 CEMT 8 16 3 0 * * CEMF * 1 1 0 * WQM * 9 +0x6302 CDMT 8 16 3 0 * * CDMF * 1 1 0 * WQS * 9 +0x6303 CIMT 8 16 3 0 * * CIMF * 1 1 0 * WQS WQH 9 +0x6304 CDMT 8 16 3 0 * * CDMF * 1 1 0 * WQS WQH 9 +0x6305 CHMT 8 16 3 0 * * CHMF * 1 1 0 * WQL * 9 +0x6310 CHFT 8 16 3 0 * * CHFF * 1 1 0 * WQN * 9 +0x6311 CEFT 8 16 3 0 * * CEFF * 1 1 0 * WQM * 9 +0x6312 CIFT 8 16 3 0 * * CIFF * 1 1 0 * WQS * 9 +0x6313 CIFT 8 16 3 0 * * CIFF * 1 1 0 * WQS * 9 +0x6314 CIFT 8 16 3 0 * * CIFF * 1 1 0 * WQS WQH 9 +0x6315 CHFT 8 16 3 0 * * CHFF * 1 1 0 * WQN * 9 +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * 9 +0x6401 CTES 9 16 3 0 * * * * 1 * 0 0 WPM * 9 +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * 9 +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * 9 +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6406 MDGU 9 16 3 0 * * * * 1 * 1 * WPL * 9 +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * 9 +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * 9 +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * 7 +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * 6 +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * 6 +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * 6 +0x7200 MBER 11 24 5 0 MBER_BL * * * 0 * * * * * 4 +0x7201 MBER 11 24 5 0 * * * * 0 * * * * * 4 +0x7202 MBER 11 24 5 0 MBER_CA * * * 0 * * * * * 4 +0x7203 MBER 11 24 5 0 MBER_PO * * * 0 * * * * * 4 +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * 10 +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * 7 +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * 10 +0x7310 MFIE 10 16 3 4 * * * * 0 1 * * * * 10 +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * 7 +0x7320 MAIR 10 32 5 0 * * * 1 0 1 * * * * 10 +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * 7 +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * 6 +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * 6 +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * 6 +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * 6 +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * 6 +0x7600 METT 11 16 3 0 * * * * 0 * * * * * 6 +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * 4 +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * 8 +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * 4 +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * 6 +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * 2 +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * 2 +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * 2 +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * 2 +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * 2 +0x7a00 MSPI 11 24 4 0 MSPI_GI * * * 0 * * * * * 5 +0x7a01 MSPI 11 24 4 0 MSPI_HU * * * 0 * * * * * 5 +0x7a02 MSPI 11 24 4 0 MSPI_PH * * * 0 * * * * * 5 +0x7a03 MSPI 11 24 4 0 MSPI_SW * * * 0 * * * * * 5 +0x7a04 MSPI 11 24 4 0 MSPI_WR * * * 0 * * * * * 5 +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * 8 +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * 8 +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * 8 +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * 8 +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * 8 +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * 8 +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * 6 +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * 6 +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * 4 +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * 10 +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * 10 +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * 8 +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f07 MGLC 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f08 MOTY 10 24 5 0 * * * * 0 1 * * * * 4 +0x7f0a MGCP 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0b MGCL 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f13 MSNK 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * 5 +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f32 MSLY 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * 12 +0x8000 MGNL 4 20 3 0 * * * * * * * * S1 HB 5 +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW 5 +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW 6 +0x9000 MOGR 12 24 5 0 * * * * 1 * * * * * 6 +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * 8 +0xa100 MCAR 13 32 5 0 * * * * 0 * * * * * 6 +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * 0 +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * 0 +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * 0 +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * 0 +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * 0 +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * 0 +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * 0 +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * 0 +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * 0 +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * 0 +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * 3 +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * 4 +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * 6 +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * 6 +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * 5 +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * 5 +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * 5 +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * 5 +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * 6 +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * 6 +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * 6 +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * 6 +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * 6 +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xe000_000f MBET 17 24 3 0 * * * * * * * * * * 4 +0xe010_000f MBBM 17 20 3 0 * * * * * * * * * * 4 +0xe020_000f MBBR 17 16 3 0 * * * * * * * * * * 4 +0xe030_000f MBFI 17 12 3 0 * * * * * * * * * * 8 +0xe040_000f MBRH 17 64 7 0 * * * * * * * * * * 4 +0xe050_000f MREM 17 48 5 0 * * * * * * * * * * 8 +0xe060_000f MHOH 17 24 3 0 * * * * * * * * * * 8 +0xe100_000f MCYC 17 48 7 0 * * * * * * * * * * 4 +0xe110_000f METN 17 48 5 0 * * * * * * * * * * 4 +0xe130_000f MGFR 17 40 5 0 * * * * * * * * * * 8 +0xe140_000f MGVE 17 32 5 0 * * * * * * * * * * 8 +0xe150_000f MGFO 17 32 5 0 * * * * * * * * * * 7 +0xe210_000f MELE 17 32 5 0 * * * * * * * * * * 8 +0xe220_000f MELF 17 32 5 0 * * * * * * * * * * 6 +0xe230_000f MELW 17 32 5 0 * * * * * * * * * * 8 +0xe240_000f MHAR 17 16 3 0 * * * * * * * * * * 8 +0xe250_000f MWWE 17 24 3 0 * * * * * * * * * * 8 +0xe260_000f MFEY 17 24 3 0 * * * * * * * * * * 8 +0xe270_000f MDTR 17 24 3 0 * * * * * * * * * * 8 +0xe280_000f MFE2 17 24 3 0 * * * * * * * * * * 8 +0xe290_000f MEW2 17 24 3 0 * * * * * * * * * * 8 +0xe300_000f MGH2 17 16 3 0 * * * * * * * * * * 8 +0xe310_000f MGH3 17 16 3 0 * * * * * * * * * * 6 +0xe320_000f MWIG 17 16 3 0 * * * * * * * * * * 6 +0xe330_000f MZO2 17 16 3 0 * * * * * * * * * * 4 +0xe340_000f MZO3 17 16 3 0 * * * * * * * * * * 4 +0xe350_000f MWI2 17 16 3 0 * * * * * * * * * * 8 +0xe360_000f MWI3 17 16 3 0 * * * * * * * * * * 6 +0xe380_000f MMUM 17 16 3 0 * * * * * * * * * * 5 +0xe390_000f MHIS 17 16 3 0 * * * * * * * * * * 4 +0xe3a0_000f MDRD 17 24 3 0 * * * * * * * * * * 4 +0xe3b0_000f MWAV 17 16 3 0 * * * 1 * * * * * * 5 +0xe400_000f MGO1 17 16 3 0 * * * * * * * * * * 8 +0xe410_000f MGO2 17 16 3 0 * * * * * * * * * * 8 +0xe420_000f MGO3 17 16 3 0 * * * * * * * * * * 8 +0xe430_000f MGO4 17 16 3 0 * * * * * * * * * * 8 +0xe440_000f MSVI 17 16 3 0 * * * * * * * * * * 8 +0xe450_000f MSV2 17 16 3 0 * * * * * * * * * * 8 +0xe460_000f MGWO 17 24 3 0 * * * * * * * * * * 12 +0xe470_000f MGOC 17 16 3 0 * * * * * * * * * * 8 +0xe480_000f MGW2 17 24 3 0 * * * * * * * * * * 12 +0xe490_000f MGO5 17 16 3 0 * * * * * * * * * * 8 +0xe510_000f MGIR 17 24 3 0 * * * * * * * * * * 5 +0xe520_000f MGIC 17 32 5 0 * * * * * * * * * * 7 +0xe620_000f MSKB 17 24 3 0 * * * * * * * * * * 4 +0xe700_000f MMIN 17 24 3 0 * * * * * * * * * * 6 +0xe710_000f MTRO 17 16 3 0 * * * * * * * * * * 6 +0xe720_000f MTIC 17 16 3 0 * * * * * * * * * * 6 +0xe730_000f MTSN 17 24 3 0 * * * * * * * * * * 8 +0xe750_000f MUMB 17 24 5 0 * * * * * * * * * * 8 +0xe760_000f MYET 17 24 3 0 * * * * * * * * * * 8 +0xe770_000f MBA4 17 16 3 0 * * * * * * * * * * 8 +0xe780_000f MBA5 17 16 3 0 * * * * * * * * * * 8 +0xe790_000f MBA6 17 16 3 0 * * * * * * * * * * 8 +0xe7a0_000f MBAI 17 16 3 0 * * * * * * * * * * 8 +0xe7b0_000f MBOA 17 24 3 0 * * * * * * * * * * 10 +0xe7c0_000f MABW 17 24 3 0 * * * * * * * * * * 8 +0xe7d0_000f MMAL 17 16 3 0 * * * * * * * * * * 8 +0xe7e0_000f MSCR 17 24 3 0 * * * * * * * * * * 8 +0xe7f0_000f MUM2 17 24 5 0 * * * * * * * * * * 8 +0xe800_000f MOR6 17 16 3 0 * * * * * * * * * * 6 +0xe810_000f MOR1 17 16 3 0 * * * * * * * * * * 6 +0xe820_000f MOR2 17 16 3 0 * * * * * * * * * * 6 +0xe830_000f MOR3 17 16 3 0 * * * * * * * * * * 6 +0xe840_000f MOR4 17 16 3 0 * * * * * * * * * * 6 +0xe850_000f MOR5 17 16 3 0 * * * * * * * * * * 6 +0xe860_000f MNO1 17 16 3 0 * * * * * * * * * * 6 +0xe870_000f MNO2 17 16 3 0 * * * * * * * * * * 6 +0xe880_000f MNO3 17 24 5 0 * * * * * * * * * * 6 +0xe890_000f MLI3 17 32 5 0 * * * * * * * * * * 7 +0xe8a0_000f MYU3 17 16 3 0 * * * * * * * * * * 6 +0xe8b0_000f MYUH 17 16 3 0 * * * * * * * * * * 8 +0xe8c0_000f MBUG 17 16 3 0 * * * * * * * * * * 8 +0xe8d0_000f MNOS 17 24 3 0 * * * * * * * * * * 8 +0xe8e0_000f MBU2 17 16 3 0 * * * * * * * * * * 8 +0xe8f0_000f MOR7 17 16 3 0 * * * * * * * * * * 8 +0xe900_000f MSH1 17 16 3 0 * * * 1 * * * * * * 6 +0xe910_000f MSH2 17 24 3 0 * * * 1 * * * * * * 8 +0xe920_000f MGHO 17 16 3 0 * * * 1 * * * * * * 8 +0xea20_000f MCRD 17 24 3 0 * * * * * * * * * * 8 +0xeb00_000f MANI 17 24 3 0 * * * * * * * * * * 6 +0xeb10_000f MAN2 17 24 3 0 * * * * * * * * * * 6 +0xeb20_000f MAN3 17 24 3 0 * * * * * * * * * * 4 +0xeb30_000f MBE1 17 32 3 0 * * * * * * * * * * 8 +0xeb40_000f MBE2 17 16 3 0 * * * * * * * * * * 8 +0xeb51 MSEE 17 16 3 0 * * * * * * * * * * 6 +0xeb60_000f MLIC 17 16 3 0 * * * * * * * * * * 8 +0xeb70_000f MLER 17 16 3 0 * * * * * * * * * * 8 +0xeb90_000f MMYC 17 16 3 0 * * * * * * * * * * 8 +0xeba0_000f MMY2 17 16 3 0 * * * * * * * * * * 8 +0xebb0_000f MSHR 17 24 3 0 * * * * * * * * * * 8 +0xebc0_000f MTAN 17 24 3 0 * * * * * * * * * * 8 +0xebd0_000f MSAL 17 16 3 0 * * * * * * * * * * 6 +0xebe0_000f MSA2 17 16 3 0 * * * * * * * * * * 8 +0xebf1_000f MARU 17 16 3 0 * * * * * * * * * * 8 +0xec00_000f MWDR 17 72 7 0 * * * * * * * * * * 8 +0xec10_000f MCHY 17 16 3 0 * * * * * * * * * * 8 +0xec20_000f MSHE 17 24 3 0 * * * * * * * * * * 6 +0xec30_000f MCHI 17 48 5 0 * * * * * * * * * * 8 +0xec40_000f MDH1 17 24 3 0 * * * * * * * * * * 8 +0xec50_000f MDH2 17 24 3 0 * * * * * * * * * * 8 +0xed00_000f MCOR 17 24 5 0 * * * * * * * * * * 6 +0xed10_000f MGLA 17 24 5 0 * * * * * * * * * * 6 +0xed20_000f MLEM 17 16 3 0 * * * * * * * * * * 6 +0xee00_000f MWEB 17 24 3 0 * * * * * * * * * * 4 +0xee10_000f MWRA 17 24 3 0 * * * * * * * * * * 4 +0xef00_000f MISA 17 24 3 0 * * * * * * * * * * 8 +0xef10_000f MMAD 17 24 3 0 * * * * * * * * * * 8 +0xef20_000f MWOR 17 24 5 0 * * * * * * * * * * 14 +0xef50_000f MKG1 17 16 3 0 * * * * * * * * * * 0 +0xef60_000f MKG2 17 16 3 0 * * * * * * * * * * 0 +0xef70_000f MKG3 17 16 3 0 * * * * * * * * * * 0 +0xef90_000f MWIL 17 16 3 0 * * * * * * * * * * 11 +0xefa0_000f MGEN 17 16 2 0 * * * * * * * * * * 8 +0xefb0_000f MGEN 17 24 3 0 * * * * * * * * * * 8 +0xefc0_000f MGEN 17 36 4 0 * * * * * * * * * * 8 +0xefd0_000f MGEN 17 48 5 0 * * * * * * * * * * 8 +0xefe0_000f MGEN 17 12 3 0 * * * * * * * * * * 8 +0xeff0_000f MGEN 17 64 7 0 * * * * * * * * * * 8 +0xf000_000f MSKA 17 16 3 0 * * * * * * * * * * 6 +0xf010_000f MSKT 17 16 3 0 * * * * * * * * * * 6 +0xf020_000f MWI4 17 16 3 0 * * * * * * * * * * 6 +0xf100_000f MYU1 17 16 3 0 * * * * * * * * * * 6 +0xf110_000f MYU2 17 16 3 0 * * * * * * * * * * 6 +0xf200_000f MLIZ 17 24 5 0 * * * * * * * * * * 6 +0xf210_000f MLI2 17 24 5 0 * * * * * * * * * * 5 +0xf300_000f MGFI 17 40 5 0 * * * * * * * * * * 6 +0xf400_000f MSAH 17 24 3 0 * * * * * * * * * * 8 +0xf410_000f MSAT 17 32 5 0 * * * * * * * * * * 8 +0xf500_000f MDRM 17 32 4 0 * * * * * * * * * * 8 +0xf510_000f MDRF 17 32 4 0 * * * * * * * * * * 8 +0xf770_000f MBA1 17 16 3 0 * * * * * * * * * * 8 +0xf780_000f MBA2 17 16 3 0 * * * * * * * * * * 8 +0xf790_000f MBA3 17 16 3 0 * * * * * * * * * * 8 diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-iwdee.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-iwdee.2da index 4f0d14602..f4ca47aa0 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-iwdee.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-iwdee.2da @@ -1,453 +1,453 @@ 2DA V1.0 * - RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD -0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * -0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * -0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * -0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * -0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * -0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * -0x0101 BGLRYU 0 0 3 7 * * * * * 0 * * * * -0x0102 SPCL236U 0 0 3 7 * * * * * 0 * * * * -0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * -0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * -0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * -0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * -0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * -0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0301 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * -0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * -0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * -0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * -0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * -0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * -0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * -0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * -0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * -0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * -0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * -0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * -0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * -0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * -0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * -0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * -0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * -0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * -0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * -0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * -0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * -0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * -0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * -0x1000 MWYV 1 24 5 0 * * * * 0 * * * * * -0x1003 MWYV 1 24 5 0 MWYV_WH * * * 0 * * * * * -0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * -0x1101 MWDR 1 72 7 0 * * * * 0 * * * * * -0x1102 MTAN 1 32 5 0 MTAN_BL * * * 0 * 1 * * * -0x1103 MTAN 1 32 5 0 MTAN_GR * * * 0 * 1 * * * -0x1104 MTAN 1 32 5 0 MTAN_RD * * * 0 * 1 * * * -0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * -0x1201 MDR2 2 72 13 0 * * * * 0 1 * * * * -0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * -0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * -0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * -0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * -0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * -0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * -0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * -0x1300 MDEM 3 40 5 0 * * * * 0 1 * * * * -0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW -0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * -0x2200 MOGM 5 24 5 0 * * * * 1 * 1 1 S1 * -0x2300 MDKN 5 24 5 0 * * * * 0 * 1 1 * * -0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * -0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * -0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * -0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * -0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * -0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * -0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * -0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * -0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * -0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * -0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * -0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * -0x4400 LHMC 7 20 3 0 * * * * 1 * * * * * -0x4410 LHFC 7 20 3 0 * * * * 1 * * * * * -0x4500 LFAM 7 20 3 0 * * * * 1 * * * * * -0x4600 LDMF 7 20 3 0 * * * * 1 * * * * * -0x4700 LEMF 7 20 3 0 * * * * 1 * * * * * -0x4710 LEFF 7 20 3 0 * * * * 1 * * * * * -0x4800 LIMC 7 20 3 0 * * * * 1 * * * * * -0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x5012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * -0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x5112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * -0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x5212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * -0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x5312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * -0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH -0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x6012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * -0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH -0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH -0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x6112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * -0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH -0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH -0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x6212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * -0x6214 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS WQH -0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH -0x6305 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x6312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * -0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH -0x6315 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * -0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * -0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * -0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * -0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * -0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * -0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * -0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * -0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * -0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * -0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * -0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * -0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * -0x7200 MBER 11 24 5 0 MBER_BL * * * 0 * * * * * -0x7201 MBER 11 24 5 0 * * * * 0 * * * * * -0x7202 MBER 11 24 5 0 MBER_CA * * * 0 * * * * * -0x7203 MBER 11 24 5 0 MBER_PO * * * 0 * * * * * -0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * -0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * -0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * -0x7310 MFIE 10 24 3 4 * * * * 0 1 * * * * -0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * -0x7312 MFIE 10 24 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * -0x7313 MFIS 10 16 3 4 MFISG1B MFISG2B * * 0 1 * * * * -0x7314 MFIE 10 24 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * -0x7320 MAIR 10 24 5 0 * * * 1 0 1 * * * * -0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * -0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * -0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * -0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * -0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * -0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * -0x7600 METT 11 16 3 0 * * * * 0 * * * * * -0x7601 MGHL 11 16 3 0 MGHL_MA * * * 0 0 * * * * -0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * -0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * -0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * -0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * -0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * -0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * -0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * -0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * -0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * -0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * -0x7a00 MSPI 11 24 4 0 MSPI_GI * * * 0 * * * * * -0x7a01 MSPI 11 24 4 0 MSPI_HU * * * 0 * * * * * -0x7a02 MSPI 11 24 4 0 MSPI_PH * * * 0 * * * * * -0x7a03 MSPI 11 24 4 0 MSPI_SW * * * 0 * * * * * -0x7a04 MSPI 11 24 4 0 MSPI_WR * * * 0 * * * * * -0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * -0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * -0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * -0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * -0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * -0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * -0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * -0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * -0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * -0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * -0x7d01 NSLF 11 16 3 0 * * * * 1 0 * * * * -0x7d02 MCHB 11 12 3 0 * * * * 0 0 * * * * -0x7d03 MCHW 11 12 3 0 * * * * 0 0 * * * * -0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * -0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * -0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * -0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * -0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * -0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * -0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * -0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * -0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * -0x7f07 MGLC 10 24 3 0 * * * * 0 1 * * * * -0x7f08 MOTY 10 24 7 0 * * * * 0 1 * * * * -0x7f09 MSAH 10 24 3 0 * * * * 0 1 * * * * -0x7f0a MGCP 10 24 5 0 * * * * 0 1 * * * * -0x7f0b MGCL 10 24 5 0 * * * * 0 1 * * * * -0x7f0c MKUO 10 24 3 0 * * * * 0 1 * * * * -0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * -0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * -0x7f0f MTRS 10 12 3 0 * * * * 0 1 * * * * -0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * -0x7f11 MUMB 10 24 5 0 * * * * 0 1 * * * * -0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * -0x7f13 MSNK 10 24 5 0 * * * * 0 1 * * * * -0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * -0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * -0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * -0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * -0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * -0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * -0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * -0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * -0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * -0x7f23 MSAT 10 32 5 0 * * * * 0 1 * * * * -0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * -0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * -0x7f28 MKUL 10 32 5 0 * * * * 0 1 * * * * -0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * -0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * -0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * -0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * -0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * -0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * -0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * -0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * -0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * -0x7f32 MSLY 10 24 5 0 * * * * 0 1 * * * * -0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * -0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * -0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * -0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * -0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * -0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * -0x7f39 MMST 10 24 3 1 * * * 0 0 0 * * * * -0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * -0x7f3b MSOL 10 16 3 7 * * MSOG 0 0 1 * * * * -0x7f3c MASL 10 16 3 7 * * MASG 0 0 1 * * * * -0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * -0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * -0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * -0x7f40 MGLM 10 16 3 0 * * * * 0 1 * * * * -0x7f41 MDJL 10 16 3 0 MDJL_E1 MDJL_E2 * * 0 1 * * * * -0x7f42 NIRO 10 16 3 0 NIRO_RD * * * 0 1 * * * * -0x7f43 MOTY 10 24 7 0 MOTY_Y1 MOTY_Y2 * * 0 1 * * * * -0x7f44 NELW 10 16 3 0 * * * * 0 0 * * * * -0x7f45 MSAI 10 16 3 0 * * * 1 0 0 * * * * -0x7f46 MBEG 10 32 5 0 * * * * 0 0 * * * * -0x7f47 MFI2 10 32 5 0 * * * * 0 0 * * * * -0x7f48 MSOF 10 16 3 0 * * * 0 0 1 * * * * -0x7f49 MDMF 10 16 3 0 * * * 0 0 1 * * * * -0x7f4a MDAS 10 16 3 7 * * MDAG 0 0 1 * * * * -0x7f4b MDAF 10 16 3 0 * * * 0 0 1 * * * * -0x7f4c MPLN 10 16 3 7 * * MPLG 0 0 1 * * * * -0x7f4d MPLF 10 16 3 0 * * * 0 0 1 * * * * -0x7f4e MDVM 10 16 3 7 * * MDVG 0 0 1 * * * * -0x7f4f MDVF 10 16 3 0 * * * 0 0 1 * * * * -0x7f50 MMST 10 24 3 1 MMST_NI * * 0 0 0 * * * * -0x7f51 MMST 10 24 3 1 MMST_HA * * 0 0 0 * * * * -0x7f52 NSAI 10 16 3 0 NSAI_G1 NSAI_G2 * * 0 1 * * * * -0x8000 MGNL 4 20 3 0 * * * * * * * * S1 HB -0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW -0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW -0x9000 MOGR 12 24 5 0 * * * * 1 * * * * * -0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * -0xa100 MCAR 13 32 5 0 * * * * 0 * * * * * -0xa200 MWYV 13 16 3 0 MWYV_WS * * * 0 * * * * * -0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * -0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * -0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * -0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * -0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * -0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * -0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * -0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * -0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * -0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * -0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * -0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * -0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * -0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * -0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * -0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * -0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * -0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * -0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * -0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * -0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * -0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * -0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * -0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * -0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * -0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * -0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * -0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * -0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * -0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * -0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * -0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xe000 MCYC 17 48 7 0 * * * * * * * * * * -0xe010 METN 17 48 5 0 * * * * * * * * * * -0xe020 MTAN 17 32 7 0 * * * * * * * * * * -0xe040 MHIS 17 16 3 0 * * * * * * * * * * -0xe050 MLER 17 16 3 0 * * * * * * * * * * -0xe060 MLIC 17 16 3 0 * * * * * * * * * * -0xe070 MMIN 17 24 3 0 * * * * * * * * * * -0xe080 MMUM 17 16 3 0 * * * * * * * * * * -0xe090 MTAN 17 24 3 0 * * * * * * * * * * -0xe0a0 MTIC 17 16 3 0 * * * * * * * * * * -0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * -0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * -0xe0d0 MUMB 17 24 5 0 * * * * * * * * * * -0xe0e0 MCOR 17 24 5 0 * * * * * * * * * * -0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * -0xe0f1 MGLA 17 24 5 0 * * * * * * * * * * -0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * -0xe200 MBET 17 24 3 0 * * * * * * * * * * -0xe210 MBFI 17 12 3 0 * * * * * * * * * * -0xe220 MBBM 17 20 3 0 * * * * * * * * * * -0xe230 MBRH 17 64 7 0 * * * * * * * * * * -0xe240 MANI 17 24 3 0 * * * * * * * * * * -0xe241 MAN2 17 24 3 0 * * * * * * * * * * -0xe242 MAN3 17 24 3 0 * * * * * * * * * * -0xe243 MARU 17 16 3 0 * * * * * * * * * * -0xe244 MBA1 17 16 3 0 * * * * * * * * * * -0xe245 MBA2 17 16 3 0 * * * * * * * * * * -0xe246 MBA3 17 16 3 0 * * * * * * * * * * -0xe247 MBA4 17 16 3 0 * * * * * * * * * * -0xe248 MBA5 17 16 3 0 * * * * * * * * * * -0xe249 MBA6 17 16 3 0 * * * * * * * * * * -0xe24a MBAI 17 16 3 0 * * * * * * * * * * -0xe24b MELE 17 32 5 0 * * * * * * * * * * -0xe24c MELF 17 32 5 0 * * * * * * * * * * -0xe24d MELW 17 32 5 0 * * * * * * * * * * -0xe24e MGFI 17 40 5 0 * * * * * * * * * * -0xe24f MGFR 17 40 5 0 * * * * * * * * * * -0xe250 MGIR 17 24 3 0 * * * * * * * * * * -0xe251 MGVE 17 32 5 0 * * * * * * * * * * -0xe252 MHAR 17 16 3 0 * * * * * * * * * * -0xe253 MREM 17 48 5 0 * * * * * * * * * * -0xe254 MSCR 17 24 3 0 * * * * * * * * * * -0xe255 MSEE 17 16 3 0 * * * * * * * * * * -0xe256 MBE1 17 32 3 0 * * * * * * * * * * -0xe257 MBE2 17 16 3 0 * * * * * * * * * * -0xe258 MBE3 17 16 3 0 * * * * * * * * * * -0xe259 MSVI 17 16 3 0 * * * * * * * * * * -0xe25a MSV2 17 16 3 0 * * * * * * * * * * -0xe25b MUM2 17 24 5 0 * * * * * * * * * * -0xe25c MTA2 17 24 3 0 * * * * * * * * * * -0xe25d MYET 17 24 3 0 * * * * * * * * * * -0xe25e MWI4 17 16 3 0 * * * * * * * * * * -0xe25f MDRD 17 24 3 0 * * * * * * * * * * -0xe260 MCRD 17 24 3 0 * * * * * * * * * * -0xe261 MSAH 17 24 3 0 * * * * * * * * * * -0xe262 MSAT 17 32 5 0 * * * * * * * * * * -0xe263 MSV3 17 16 3 0 * * * * * * * * * * -0xe264 MSV4 17 16 3 0 * * * * * * * * * * -0xe265 MBOA 17 24 3 0 * * * * * * * * * * -0xe26b MDTR 17 24 5 0 * * * * * * * * * * -0xe26e MFEY 17 24 3 0 * * * * * * * * * * -0xe270 MGO5 17 16 3 0 * * * * * * * * * * -0xe276 MLEM 17 24 3 0 * * * * * * * * * * -0xe283 MDRM 17 24 5 0 * * * * * * * * * * -0xe288 MKG1 17 16 3 0 * * * * * * * * * * -0xe289 MKG2 17 16 3 0 * * * * * * * * * * -0xe28a MKG3 17 16 3 0 * * * * * * * * * * -0xe28b MABW 17 24 3 0 * * * * * * * * * * -0xe28c MWD2 17 24 5 0 * * * * * * * * * * -0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * -0xe310 MGH2 17 16 3 0 * * * * * * * * * * -0xe320 MGH3 17 16 3 0 * * * * * * * * * * -0xe400 MGO1 17 16 3 0 * * * * * * * * * * -0xe410 MGO2 17 16 3 0 * * * * * * * * * * -0xe420 MGO3 17 16 3 0 * * * * * * * * * * -0xe430 MGO4 17 16 3 0 * * * * * * * * * * -0xe500 MLIZ 17 24 5 0 * * * * * * * * * * -0xe510 MLI2 17 24 5 0 * * * * * * * * * * -0xe520 MLI3 17 32 5 0 * * * * * * * * * * -0xe600 MMYC 17 16 3 0 * * * * * * * * * * -0xe610 MMY2 17 16 3 0 * * * * * * * * * * -0xe700 MNO1 17 16 3 0 * * * * * * * * * * -0xe710 MNO2 17 16 3 0 * * * * * * * * * * -0xe720 MNO3 17 24 5 0 * * * * * * * * * * -0xe800 MOR1 17 16 3 0 * * * * * * * * * * -0xe810 MOR2 17 16 3 0 * * * * * * * * * * -0xe820 MOR3 17 16 3 0 * * * * * * * * * * -0xe830 MOR4 17 16 3 0 * * * * * * * * * * -0xe840 MOR5 17 16 3 0 * * * * * * * * * * -0xe900 MSAL 17 16 3 0 * * * * * * * * * * -0xe910 MSA2 17 16 3 0 * * * * * * * * * * -0xea00 MSHR 17 24 3 0 * * * * * * * * * * -0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * -0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * -0xeb00 MSKT 17 16 3 0 * * * * * * * * * * -0xeb10 MSKA 17 16 3 0 * * * * * * * * * * -0xeb20 MSKB 17 24 3 0 * * * * * * * * * * -0xec00 MWIG 17 16 3 0 * * * * * * * * * * -0xec10 MWI2 17 16 3 0 * * * * * * * * * * -0xec20 MWI3 17 16 3 0 * * * * * * * * * * -0xed00 MYU1 17 16 3 0 * * * * * * * * * * -0xed10 MYU2 17 16 3 0 * * * * * * * * * * -0xed20 MYU3 17 16 3 0 * * * * * * * * * * -0xee00 MZO2 17 16 3 0 * * * * * * * * * * -0xee10 MZO3 17 16 3 0 * * * * * * * * * * -0xef10 MWWE 17 24 3 0 * * * * * * * * * * + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD MOVE_SCALE +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * 0 +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * 0 +0x0002 SPRDRASI 0 0 0 7 * * * * 0 0 * * * * 0 +0x0003 SPFLAMES 0 0 0 7 SPFLAMEB * * * 0 0 * * * * 0 +0x0004 SPRDRASI 0 0 0 7 SPGDRASI * * * 0 0 * * * * 0 +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * 0 +0x0101 BGLRYU 0 0 3 7 * * * * * 0 * * * * 0 +0x0102 SPCL236U 0 0 3 7 * * * * * 0 * * * * 0 +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * 0 +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * 0 +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * 0 +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * 0 +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * 0 +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0301 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * 0 +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * 0 +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * 0 +0x0520 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0610 SPICESTM 0 0 0 1 * * * * 0 0 0 * * * 0 +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * 0 +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * 0 +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * 0 +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * 0 +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0910 SPMETSWM 0 0 0 1 * * * * 0 1 0 * * * 0 +0x0a00 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0a01 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a02 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a03 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a04 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a10 SPHORPUF 0 0 0 1 * * * * 0 0 * * * * 0 +0x0a11 SPWRDFLD 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a12 SPENTAAI 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a13 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a14 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a23 SPHLYSM2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0a24 SPUNHBL2 0 0 0 1 * * * * 0 1 * * * * 0 +0x0b00 SPCSPRA2 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b01 SPCCOLDL 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b02 SPPRISM2 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b03 SPCSPRA3 0 0 0 1 * * * * 0 0 * * * * 0 +0x0b04 SPPRISM3 0 0 0 1 * * * * 0 0 * * * * 0 +0x0c00 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * 0 +0x0c10 SPSTRMVA 0 0 0 1 * * * * 0 1 * * SPSTRMVB * 0 +0x1000 MWYV 1 24 5 0 * * * * 0 * * * * * 8 +0x1003 MWYV 1 24 5 0 MWYV_WH * * * 0 * * * * * 8 +0x1100 MTAN 1 32 5 0 * * * * 0 * 1 * * * 8 +0x1101 MWDR 1 72 7 0 * * * * 0 * * * * * 10 +0x1102 MTAN 1 32 5 0 MTAN_BL * * * 0 * 1 * * * 8 +0x1103 MTAN 1 32 5 0 MTAN_GR * * * 0 * 1 * * * 8 +0x1104 MTAN 1 32 5 0 MTAN_RD * * * 0 * 1 * * * 8 +0x1200 MDR1 2 72 13 0 * * * * 0 1 * * * * 14 +0x1201 MDR2 2 72 13 0 * * * * 0 1 * * * * 14 +0x1202 MDR3 2 72 13 0 * * * * 0 1 * * * * 14 +0x1203 MDR1 2 72 13 0 MDR1_GR * * * 0 1 * * * * 14 +0x1204 MDR1 2 72 13 0 MDR1_AQ * * * 0 1 * * * * 14 +0x1205 MDR1 2 72 13 0 MDR1_BL * * * 0 1 * * * * 14 +0x1206 MDR1 2 72 13 0 MDR1_BR * * * 0 1 * * * * 14 +0x1207 MDR1 2 72 13 0 MDR1_MC * * * 0 1 * * * * 14 +0x1208 MDR1 2 72 13 0 MDR1_PU * * * 0 1 * * * * 14 +0x1300 MDEM 3 40 5 0 * * * * 0 1 * * * * 10 +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW 6 +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * 6 +0x2200 MOGM 5 24 5 0 * * * * 1 * 1 1 S1 * 6 +0x2300 MDKN 5 24 5 0 * * * * 0 * 1 1 * * 7 +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * 6 +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * 0 +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * 0 +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * 0 +0x4400 LHMC 7 20 3 0 * * * * 1 * * * * * 0 +0x4410 LHFC 7 20 3 0 * * * * 1 * * * * * 0 +0x4500 LFAM 7 20 3 0 * * * * 1 * * * * * 0 +0x4600 LDMF 7 20 3 0 * * * * 1 * * * * * 0 +0x4700 LEMF 7 20 3 0 * * * * 1 * * * * * 0 +0x4710 LEFF 7 20 3 0 * * * * 1 * * * * * 0 +0x4800 LIMC 7 20 3 0 * * * * 1 * * * * * 0 +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x5012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * 9 +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x5112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * 9 +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x5212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * 9 +0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x5312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * 9 +0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH 9 +0x6005 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x6012 CDFB 8 16 3 0 * * CDFC * 1 1 1 * WQS * 9 +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH 9 +0x6015 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH 9 +0x6105 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x6112 CDFB 8 16 3 0 * * CDFF * 1 1 1 * WQS * 9 +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH 9 +0x6115 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH 9 +0x6205 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x6212 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS * 9 +0x6214 CDFW 8 16 3 0 * * CDFW * 1 1 1 * WQS WQH 9 +0x6215 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH 9 +0x6305 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x6312 CDFT 8 16 3 0 * * CDFT * 1 1 0 * WQS * 9 +0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH 9 +0x6315 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * 9 +0x6401 UELM 9 16 3 0 * * * * 0 * 0 0 WPM * 9 +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * 9 +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * 9 +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6406 MDGU 9 24 7 0 * * * * 1 * 1 * WPM * 9 +0x6500 CHMM 8 16 3 0 * * CHMM * 1 1 1 * WQL * 9 +0x6510 CHFM 8 16 3 0 * * CHFM * 1 1 1 * WQN * 9 +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * 7 +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * 6 +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * 6 +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * 6 +0x7200 MBER 11 24 5 0 MBER_BL * * * 0 * * * * * 4 +0x7201 MBER 11 24 5 0 * * * * 0 * * * * * 4 +0x7202 MBER 11 24 5 0 MBER_CA * * * 0 * * * * * 4 +0x7203 MBER 11 24 5 0 MBER_PO * * * 0 * * * * * 4 +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * 10 +0x7301 MEAS 10 16 3 0 * * * * 0 1 * * * * 7 +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * 10 +0x7310 MFIE 10 24 3 4 * * * * 0 1 * * * * 10 +0x7311 MFIS 10 16 3 4 * * * * 0 1 * * * * 7 +0x7312 MFIE 10 24 3 4 MFIEG1B MFIEG2B * * 0 1 * * * * 10 +0x7313 MFIS 10 16 3 4 MFISG1B MFISG2B * * 0 1 * * * * 7 +0x7314 MFIE 10 24 3 4 MFIEG31 MFIEG3B * * 0 1 * * * * 10 +0x7320 MAIR 10 24 5 0 * * * 1 0 1 * * * * 10 +0x7321 MAIS 10 16 3 0 * * * 1 0 1 * * * * 7 +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * 6 +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * 6 +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * 6 +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * 6 +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * 6 +0x7600 METT 11 16 3 0 * * * * 0 * * * * * 6 +0x7601 MGHL 11 16 3 0 MGHL_MA * * * 0 0 * * * * 8 +0x7700 MGHL 11 16 3 0 * * * * 0 * * * * * 4 +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * 8 +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * 4 +0x7703 MSHD 10 16 3 0 * * * 1 0 1 * * * * 9 +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * 6 +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * 2 +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * 2 +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * 2 +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * 2 +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * 2 +0x7a00 MSPI 11 24 4 0 MSPI_GI * * * 0 * * * * * 5 +0x7a01 MSPI 11 24 4 0 MSPI_HU * * * 0 * * * * * 5 +0x7a02 MSPI 11 24 4 0 MSPI_PH * * * 0 * * * * * 5 +0x7a03 MSPI 11 24 4 0 MSPI_SW * * * 0 * * * * * 5 +0x7a04 MSPI 11 24 4 0 MSPI_WR * * * 0 * * * * * 5 +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * 8 +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * 8 +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * 8 +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * 8 +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * 8 +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * 8 +0x7b06 MWLS 11 16 3 0 * * * * 0 * * * * * 8 +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * 6 +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * 6 +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * 4 +0x7d01 NSLF 11 16 3 0 * * * * 1 0 * * * * 6 +0x7d02 MCHB 11 12 3 0 * * * * 0 0 * * * * 3 +0x7d03 MCHW 11 12 3 0 * * * * 0 0 * * * * 3 +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * 10 +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * 10 +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * 8 +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f04 MIGO 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f05 MDJI 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f07 MGLC 10 24 3 0 * * * * 0 1 * * * * 10 +0x7f08 MOTY 10 24 7 0 * * * * 0 1 * * * * 4 +0x7f09 MSAH 10 24 3 0 * * * * 0 1 * * * * 11 +0x7f0a MGCP 10 24 5 0 * * * * 0 1 * * * * 12 +0x7f0b MGCL 10 24 5 0 * * * * 0 1 * * * * 12 +0x7f0c MKUO 10 24 3 0 * * * * 0 1 * * * * 12 +0x7f0d MLIC 10 16 3 0 * * * * 0 1 * * * * 2 +0x7f0e MDLI 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f0f MTRS 10 12 3 0 * * * * 0 1 * * * * 10 +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f11 MUMB 10 24 5 0 * * * * 0 1 * * * * 13 +0x7f12 MVAM 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f13 MSNK 10 24 5 0 * * * * 0 1 * * * * 13 +0x7f14 MGIT 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f15 MBES 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f16 AMOO 10 32 5 0 * * * * 0 1 * * * * 12 +0x7f17 ARAB 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f18 ADER 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f19 MDSW 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f20 AGRO 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f21 APHE 10 12 3 0 * * * * 0 1 * * * * 9 +0x7f22 MVAF 10 16 3 0 * * * * 0 1 * * * * 13 +0x7f23 MSAT 10 32 5 0 * * * * 0 1 * * * * 14 +0x7f24 NPIR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f27 MDRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f28 MKUL 10 32 5 0 * * * * 0 1 * * * * 14 +0x7f29 MFDR 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2a NSAI 10 16 3 0 * * * * 0 1 * * * * 5 +0x7f2b MMAX 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f2c NSOL 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2d MWFM 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f2e MRAV 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f2f MSPS 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f30 NBOH 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f31 NELL 10 16 3 0 * * * * 0 1 * * * * 7 +0x7f32 MSLY 10 24 5 0 * * * * 0 1 * * * * 9 +0x7f33 MKUR 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f34 MDOC 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f35 MMIS 10 16 3 0 * * * 1 0 1 * * * * 9 +0x7f36 NSHD 10 16 3 0 * * * * 0 1 * * * * 12 +0x7f37 NIRE 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f38 MEYE 10 16 3 0 * * * * 0 0 * * * * 12 +0x7f39 MMST 10 24 3 1 * * * 0 0 0 * * * * 9 +0x7f3a NIRO 10 16 3 0 * * * * 0 1 * * * * 9 +0x7f3b MSOL 10 16 3 7 * * MSOG 0 0 1 * * * * 9 +0x7f3c MASL 10 16 3 7 * * MASG 0 0 1 * * * * 9 +0x7f3d MMEL 10 24 3 0 * * * * 0 0 * * * * 9 +0x7f3e MFIG 10 32 5 0 * * * * 0 1 * * * * 9 +0x7f3f MFIG 10 32 5 0 MFIGG1B * * * 0 1 * * * * 9 +0x7f40 MGLM 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f41 MDJL 10 16 3 0 MDJL_E1 MDJL_E2 * * 0 1 * * * * 11 +0x7f42 NIRO 10 16 3 0 NIRO_RD * * * 0 1 * * * * 9 +0x7f43 MOTY 10 24 7 0 MOTY_Y1 MOTY_Y2 * * 0 1 * * * * 8 +0x7f44 NELW 10 16 3 0 * * * * 0 0 * * * * 7 +0x7f45 MSAI 10 16 3 0 * * * 1 0 0 * * * * 9 +0x7f46 MBEG 10 32 5 0 * * * * 0 0 * * * * 8 +0x7f47 MFI2 10 32 5 0 * * * * 0 0 * * * * 9 +0x7f48 MSOF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f49 MDMF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f4a MDAS 10 16 3 7 * * MDAG 0 0 1 * * * * 9 +0x7f4b MDAF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f4c MPLN 10 16 3 7 * * MPLG 0 0 1 * * * * 9 +0x7f4d MPLF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f4e MDVM 10 16 3 7 * * MDVG 0 0 1 * * * * 9 +0x7f4f MDVF 10 16 3 0 * * * 0 0 1 * * * * 9 +0x7f50 MMST 10 24 3 1 MMST_NI * * 0 0 0 * * * * 9 +0x7f51 MMST 10 24 3 1 MMST_HA * * 0 0 0 * * * * 9 +0x7f52 NSAI 10 16 3 0 NSAI_G1 NSAI_G2 * * 0 1 * * * * 5 +0x8000 MGNL 4 20 3 0 * * * * * * * * S1 HB 5 +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW 5 +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW 6 +0x9000 MOGR 12 24 5 0 * * * * 1 * * * * * 6 +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * 8 +0xa100 MCAR 13 32 5 0 * * * * 0 * * * * * 6 +0xa200 MWYV 13 16 3 0 MWYV_WS * * * 0 * * * * * 8 +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * 0 +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * 0 +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * 0 +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * 0 +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * 0 +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * 0 +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * 0 +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * 0 +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * 0 +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * 0 +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * 3 +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * 4 +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * 6 +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * 6 +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * 5 +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * 5 +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * 5 +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * 5 +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * 6 +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * 6 +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * 6 +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * 6 +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * 6 +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xe000 MCYC 17 48 7 0 * * * * * * * * * * 4 +0xe010 METN 17 48 5 0 * * * * * * * * * * 4 +0xe020 MTAN 17 32 7 0 * * * * * * * * * * 4 +0xe040 MHIS 17 16 3 0 * * * * * * * * * * 4 +0xe050 MLER 17 16 3 0 * * * * * * * * * * 8 +0xe060 MLIC 17 16 3 0 * * * * * * * * * * 8 +0xe070 MMIN 17 24 3 0 * * * * * * * * * * 8 +0xe080 MMUM 17 16 3 0 * * * * * * * * * * 5 +0xe090 MTAN 17 24 3 0 * * * * * * * * * * 8 +0xe0a0 MTIC 17 16 3 0 * * * * * * * * * * 8 +0xe0b0 MTRO 17 16 3 0 * * * * * * * * * * 8 +0xe0c0 MTSN 17 24 3 0 * * * * * * * * * * 8 +0xe0d0 MUMB 17 24 5 0 * * * * * * * * * * 8 +0xe0e0 MCOR 17 24 5 0 * * * * * * * * * * 9 +0xe0f0 MGIC 17 32 5 0 * * * * * * * * * * 8 +0xe0f1 MGLA 17 24 5 0 * * * * * * * * * * 9 +0xe0f2 MWAV 17 16 3 0 * * * 1 * * * * * * 5 +0xe200 MBET 17 24 3 0 * * * * * * * * * * 8 +0xe210 MBFI 17 12 3 0 * * * * * * * * * * 8 +0xe220 MBBM 17 20 3 0 * * * * * * * * * * 6 +0xe230 MBRH 17 64 7 0 * * * * * * * * * * 8 +0xe240 MANI 17 24 3 0 * * * * * * * * * * 8 +0xe241 MAN2 17 24 3 0 * * * * * * * * * * 8 +0xe242 MAN3 17 24 3 0 * * * * * * * * * * 8 +0xe243 MARU 17 16 3 0 * * * * * * * * * * 8 +0xe244 MBA1 17 16 3 0 * * * * * * * * * * 8 +0xe245 MBA2 17 16 3 0 * * * * * * * * * * 8 +0xe246 MBA3 17 16 3 0 * * * * * * * * * * 8 +0xe247 MBA4 17 16 3 0 * * * * * * * * * * 8 +0xe248 MBA5 17 16 3 0 * * * * * * * * * * 8 +0xe249 MBA6 17 16 3 0 * * * * * * * * * * 8 +0xe24a MBAI 17 16 3 0 * * * * * * * * * * 8 +0xe24b MELE 17 32 5 0 * * * * * * * * * * 8 +0xe24c MELF 17 32 5 0 * * * * * * * * * * 8 +0xe24d MELW 17 32 5 0 * * * * * * * * * * 8 +0xe24e MGFI 17 40 5 0 * * * * * * * * * * 6 +0xe24f MGFR 17 40 5 0 * * * * * * * * * * 8 +0xe250 MGIR 17 24 3 0 * * * * * * * * * * 8 +0xe251 MGVE 17 32 5 0 * * * * * * * * * * 8 +0xe252 MHAR 17 16 3 0 * * * * * * * * * * 8 +0xe253 MREM 17 48 5 0 * * * * * * * * * * 8 +0xe254 MSCR 17 24 3 0 * * * * * * * * * * 8 +0xe255 MSEE 17 16 3 0 * * * * * * * * * * 6 +0xe256 MBE1 17 32 3 0 * * * * * * * * * * 8 +0xe257 MBE2 17 16 3 0 * * * * * * * * * * 8 +0xe258 MBE3 17 16 3 0 * * * * * * * * * * 6 +0xe259 MSVI 17 16 3 0 * * * * * * * * * * 8 +0xe25a MSV2 17 16 3 0 * * * * * * * * * * 8 +0xe25b MUM2 17 24 5 0 * * * * * * * * * * 8 +0xe25c MTA2 17 24 3 0 * * * * * * * * * * 8 +0xe25d MYET 17 24 3 0 * * * * * * * * * * 8 +0xe25e MWI4 17 16 3 0 * * * * * * * * * * 6 +0xe25f MDRD 17 24 3 0 * * * * * * * * * * 4 +0xe260 MCRD 17 24 3 0 * * * * * * * * * * 8 +0xe261 MSAH 17 24 3 0 * * * * * * * * * * 8 +0xe262 MSAT 17 32 5 0 * * * * * * * * * * 8 +0xe263 MSV3 17 16 3 0 * * * * * * * * * * 8 +0xe264 MSV4 17 16 3 0 * * * * * * * * * * 8 +0xe265 MBOA 17 24 3 0 * * * * * * * * * * 10 +0xe26b MDTR 17 24 5 0 * * * * * * * * * * 6 +0xe26e MFEY 17 24 3 0 * * * * * * * * * * 9 +0xe270 MGO5 17 16 3 0 * * * * * * * * * * 8 +0xe276 MLEM 17 24 3 0 * * * * * * * * * * 6 +0xe283 MDRM 17 24 5 0 * * * * * * * * * * 9 +0xe288 MKG1 17 16 3 0 * * * * * * * * * * 0 +0xe289 MKG2 17 16 3 0 * * * * * * * * * * 0 +0xe28a MKG3 17 16 3 0 * * * * * * * * * * 0 +0xe28b MABW 17 24 3 0 * * * * * * * * * * 8 +0xe28c MWD2 17 24 5 0 * * * * * * * * * * 8 +0xe300 MGHO 17 16 3 0 * * * 1 * * * * * * 8 +0xe310 MGH2 17 16 3 0 * * * * * * * * * * 6 +0xe320 MGH3 17 16 3 0 * * * * * * * * * * 6 +0xe400 MGO1 17 16 3 0 * * * * * * * * * * 8 +0xe410 MGO2 17 16 3 0 * * * * * * * * * * 8 +0xe420 MGO3 17 16 3 0 * * * * * * * * * * 8 +0xe430 MGO4 17 16 3 0 * * * * * * * * * * 8 +0xe500 MLIZ 17 24 5 0 * * * * * * * * * * 6 +0xe510 MLI2 17 24 5 0 * * * * * * * * * * 5 +0xe520 MLI3 17 32 5 0 * * * * * * * * * * 7 +0xe600 MMYC 17 16 3 0 * * * * * * * * * * 8 +0xe610 MMY2 17 16 3 0 * * * * * * * * * * 8 +0xe700 MNO1 17 16 3 0 * * * * * * * * * * 6 +0xe710 MNO2 17 16 3 0 * * * * * * * * * * 6 +0xe720 MNO3 17 24 5 0 * * * * * * * * * * 6 +0xe800 MOR1 17 16 3 0 * * * * * * * * * * 6 +0xe810 MOR2 17 16 3 0 * * * * * * * * * * 6 +0xe820 MOR3 17 16 3 0 * * * * * * * * * * 6 +0xe830 MOR4 17 16 3 0 * * * * * * * * * * 6 +0xe840 MOR5 17 16 3 0 * * * * * * * * * * 6 +0xe900 MSAL 17 16 3 0 * * * * * * * * * * 6 +0xe910 MSA2 17 16 3 0 * * * * * * * * * * 8 +0xea00 MSHR 17 24 3 0 * * * * * * * * * * 8 +0xea10 MSH1 17 16 3 0 * * * 1 * * * * * * 8 +0xea20 MSH2 17 24 3 0 * * * 1 * * * * * * 8 +0xeb00 MSKT 17 16 3 0 * * * * * * * * * * 6 +0xeb10 MSKA 17 16 3 0 * * * * * * * * * * 6 +0xeb20 MSKB 17 24 3 0 * * * * * * * * * * 4 +0xec00 MWIG 17 16 3 0 * * * * * * * * * * 8 +0xec10 MWI2 17 16 3 0 * * * * * * * * * * 8 +0xec20 MWI3 17 16 3 0 * * * * * * * * * * 6 +0xed00 MYU1 17 16 3 0 * * * * * * * * * * 6 +0xed10 MYU2 17 16 3 0 * * * * * * * * * * 6 +0xed20 MYU3 17 16 3 0 * * * * * * * * * * 6 +0xee00 MZO2 17 16 3 0 * * * * * * * * * * 4 +0xee10 MZO3 17 16 3 0 * * * * * * * * * * 4 +0xef10 MWWE 17 24 3 0 * * * * * * * * * * 8 diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-iwdhow.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-iwdhow.2da index 9d9223d78..c3fe4e0be 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-iwdhow.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-iwdhow.2da @@ -1,303 +1,303 @@ 2DA V1.0 * - RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD -0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * -0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * -0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * -0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * -0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * -0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * -0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * -0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * -0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * -0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * -0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * -0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * -0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * -0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * -0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * -0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * -0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * -0x1000 MWYV 1 24 5 0 * * * * 0 * * * * * -0x1001 MWYV 1 24 5 0 * * * * 0 * * * * * -0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW -0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * -0x2200 MOGM 5 24 5 0 * * * * 1 * 1 1 S1 * -0x2300 MDKN 5 24 5 0 * * * * 0 * 1 1 * * -0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * -0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * -0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * -0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * -0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * -0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * -0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * -0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * -0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * -0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * -0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * -0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * -0x4400 LHMC 7 20 3 0 * * * * 1 * * * * * -0x4410 LHFC 7 20 3 0 * * * * 1 * * * * * -0x4500 LFAM 7 20 3 0 * * * * 1 * * * * * -0x4600 LDMF 7 20 3 0 * * * * 1 * * * * * -0x4700 LEMF 7 20 3 0 * * * * 1 * * * * * -0x4710 LEFF 7 20 3 0 * * * * 1 * * * * * -0x4800 LIMC 7 20 3 0 * * * * 1 * * * * * -0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x5012 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x5112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x5212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x5312 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * -0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * -0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * -0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH -0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH -0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * -0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * -0x6012 CIFB 8 16 3 0 * * CIFB * 1 1 1 * WQS * -0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * -0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH -0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * -0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * -0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * -0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH -0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH -0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * -0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * -0x6112 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * -0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH -0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * -0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * -0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH -0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * -0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * -0x6212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * -0x6214 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH -0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * -0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * -0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * -0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH -0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH -0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * -0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * -0x6312 CIFT 8 16 3 0 * * CIFF * 1 1 0 * WQS * -0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * -0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH -0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * -0x6401 CTES 9 16 3 0 * * * * 1 * 0 0 WPM * -0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * -0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * -0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * -0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * -0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * -0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * -0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * -0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * -0x7200 MBER 11 24 5 0 MBER_BL * * * 0 * * * * * -0x7201 MBER 11 24 5 0 * * * * 0 * * * * * -0x7202 MBER 11 24 5 0 MBER_CA * * * 0 * * * * * -0x7203 MBER 11 24 5 0 MBER_PO * * * 0 * * * * * -0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * -0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * -0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * -0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * -0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * -0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * -0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * -0x7600 METT 11 16 3 0 * * * * 0 * * * * * -0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * -0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * -0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * -0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * -0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * -0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * -0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * -0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * -0x7a00 MSPI 11 24 4 0 MSPI_GI * * * 0 * * * * * -0x7a01 MSPI 11 24 4 0 MSPI_HU * * * 0 * * * * * -0x7a02 MSPI 11 24 4 0 MSPI_PH * * * 0 * * * * * -0x7a03 MSPI 11 24 4 0 MSPI_SW * * * 0 * * * * * -0x7a04 MSPI 11 24 4 0 MSPI_WR * * * 0 * * * * * -0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * -0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * -0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * -0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * -0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * -0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * -0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * -0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * -0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * -0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * -0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * -0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * -0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * -0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * -0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * -0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * -0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * -0x8000 MGNL 4 20 3 0 * * * * * * * * S1 HB -0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW -0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW -0x9000 MOGR 12 24 5 0 * * * * 1 * * * * * -0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * -0xa100 MCAR 13 32 5 0 * * * * 0 * * * * * -0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * -0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * -0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * -0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * -0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * -0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * -0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * -0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * -0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * -0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * -0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * -0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * -0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * -0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * -0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * -0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * -0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * -0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * -0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * -0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * -0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * -0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * -0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * -0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * -0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * -0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * -0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * -0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * -0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * -0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * -0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * -0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * -0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * -0xe000_000f MBET 17 24 3 0 * * * * * * * * * * -0xe010_000f MBBM 17 20 3 0 * * * * * * * * * * -0xe020_000f MBBR 17 16 3 0 * * * * * * * * * * -0xe030_000f MBFI 17 12 3 0 * * * * * * * * * * -0xe040_000f MBRH 17 64 7 0 * * * * * * * * * * -0xe050_000f MREM 17 48 5 0 * * * * * * * * * * -0xe100_000f MCYC 17 48 7 0 * * * * * * * * * * -0xe110_000f METN 17 48 5 0 * * * * * * * * * * -0xe130_000f MGFR 17 40 5 0 * * * * * * * * * * -0xe140_000f MGVE 17 32 5 0 * * * * * * * * * * -0xe210_000f MELE 17 32 5 0 * * * * * * * * * * -0xe220_000f MELF 17 32 5 0 * * * * * * * * * * -0xe230_000f MELW 17 32 5 0 * * * * * * * * * * -0xe240_000f MHAR 17 16 3 0 * * * * * * * * * * -0xe250_000f MWWE 17 24 3 0 * * * * * * * * * * -0xe300_000f MGH2 17 16 3 0 * * * * * * * * * * -0xe310_000f MGH3 17 16 3 0 * * * * * * * * * * -0xe320_000f MWIG 17 16 3 0 * * * * * * * * * * -0xe330_000f MZO2 17 16 3 0 * * * * * * * * * * -0xe340_000f MWI2 17 16 3 0 * * * * * * * * * * -0xe350_000f MZO3 17 16 3 0 * * * * * * * * * * -0xe360_000f MWI3 17 16 3 0 * * * * * * * * * * -0xe380_000f MMUM 17 16 3 0 * * * * * * * * * * -0xe390_000f MHIS 17 16 3 0 * * * * * * * * * * -0xe3a0_000f MDRD 17 24 3 0 * * * * * * * * * * -0xe3b0_000f MWAV 17 16 3 0 * * * 1 * * * * * * -0xe400_000f MGO1 17 16 3 0 * * * * * * * * * * -0xe410_000f MGO2 17 16 3 0 * * * * * * * * * * -0xe420_000f MGO3 17 16 3 0 * * * * * * * * * * -0xe430_000f MGO4 17 16 3 0 * * * * * * * * * * -0xe440_000f MSVI 17 16 3 0 * * * * * * * * * * -0xe450_000f MSV2 17 16 3 0 * * * * * * * * * * -0xe510_000f MGIR 17 24 3 0 * * * * * * * * * * -0xe520_000f MGIC 17 32 5 0 * * * * * * * * * * -0xe620_000f MSKB 17 24 3 0 * * * * * * * * * * -0xe700_000f MMIN 17 24 3 0 * * * * * * * * * * -0xe710_000f MTRO 17 16 3 0 * * * * * * * * * * -0xe720_000f MTIC 17 16 3 0 * * * * * * * * * * -0xe730_000f MTSN 17 24 3 0 * * * * * * * * * * -0xe750_000f MUMB 17 24 5 0 * * * * * * * * * * -0xe760_000f MYET 17 24 3 0 * * * * * * * * * * -0xe770_000f MBA4 17 16 3 0 * * * * * * * * * * -0xe780_000f MBA5 17 16 3 0 * * * * * * * * * * -0xe790_000f MBA6 17 16 3 0 * * * * * * * * * * -0xe7a0_000f MBAI 17 16 3 0 * * * * * * * * * * -0xe7e0_000f MSCR 17 24 3 0 * * * * * * * * * * -0xe7f0_000f MUM2 17 24 5 0 * * * * * * * * * * -0xe810_000f MOR1 17 16 3 0 * * * * * * * * * * -0xe820_000f MOR2 17 16 3 0 * * * * * * * * * * -0xe830_000f MOR3 17 16 3 0 * * * * * * * * * * -0xe840_000f MOR4 17 16 3 0 * * * * * * * * * * -0xe850_000f MOR5 17 16 3 0 * * * * * * * * * * -0xe860_000f MNO1 17 16 3 0 * * * * * * * * * * -0xe870_000f MNO2 17 16 3 0 * * * * * * * * * * -0xe880_000f MNO3 17 24 5 0 * * * * * * * * * * -0xe890_000f MLI3 17 32 5 0 * * * * * * * * * * -0xe8a0_000f MYU3 17 16 3 0 * * * * * * * * * * -0xe900_000f MSH1 17 16 3 0 * * * 1 * * * * * * -0xe910_000f MSH2 17 24 3 0 * * * 1 * * * * * * -0xe920_000f MGHO 17 16 3 0 * * * 1 * * * * * * -0xea20_000f MCRD 17 24 3 0 * * * * * * * * * * -0xeb00_000f MANI 17 24 3 0 * * * * * * * * * * -0xeb10_000f MAN2 17 24 3 0 * * * * * * * * * * -0xeb20_000f MAN3 17 24 3 0 * * * * * * * * * * -0xeb30_000f MBE1 17 32 3 0 * * * * * * * * * * -0xeb40_000f MBE2 17 16 3 0 * * * * * * * * * * -0xeb51 MSEE 17 16 3 0 * * * * * * * * * * -0xeb52 MFIR 17 16 3 0 * * * * * * * * * * -0xeb60_000f MLIC 17 16 3 0 * * * * * * * * * * -0xeb70_000f MLER 17 16 3 0 * * * * * * * * * * -0xeb80_000f MMAN 17 32 5 0 * * * * * * * * * * -0xeb90_000f MMYC 17 16 3 0 * * * * * * * * * * -0xeba0_000f MMY2 17 16 3 0 * * * * * * * * * * -0xebb0_000f MSHR 17 24 3 0 * * * * * * * * * * -0xebc0_000f MTAN 17 24 3 0 * * * * * * * * * * -0xebd0_000f MSAL 17 16 3 0 * * * * * * * * * * -0xebe0_000f MSA2 17 16 3 0 * * * * * * * * * * -0xebf1_000f MARU 17 16 3 0 * * * * * * * * * * -0xec00_000f MWDR 17 72 7 0 * * * * * * * * * * -0xec10_000f MCHY 17 16 3 0 * * * * * * * * * * -0xed00_000f MCOR 17 24 5 0 * * * * * * * * * * -0xed10_000f MGLA 17 24 5 0 * * * * * * * * * * -0xf000_000f MSKA 17 16 3 0 * * * * * * * * * * -0xf010_000f MSKT 17 16 3 0 * * * * * * * * * * -0xf020_000f MWI4 17 16 3 0 * * * * * * * * * * -0xf100_000f MYU1 17 16 3 0 * * * * * * * * * * -0xf110_000f MYU2 17 16 3 0 * * * * * * * * * * -0xf200_000f MLIZ 17 24 5 0 * * * * * * * * * * -0xf210_000f MLI2 17 24 5 0 * * * * * * * * * * -0xf300_000f MGFI 17 40 5 0 * * * * * * * * * * -0xf400_000f MSAH 17 24 3 0 * * * * * * * * * * -0xf410_000f MSAT 17 32 5 0 * * * * * * * * * * -0xf770_000f MBA1 17 16 3 0 * * * * * * * * * * -0xf780_000f MBA2 17 16 3 0 * * * * * * * * * * -0xf790_000f MBA3 17 16 3 0 * * * * * * * * * * + RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD MOVE_SCALE +0x0000 SPRING 0 0 0 0 * * * * 1 0 * * * * 0 +0x0001 SPFLAMES 0 0 0 7 * * * * 0 0 * * * * 0 +0x0100 SPCHUNKS 0 0 0 0 * * SPSHADOW * 1 1 * * * * 0 +0x0200 SPBLOOD 0 0 0 0 * * * * 0 0 0 * * * 0 +0x0210 SPBLOOD 0 0 0 0 * * * * 0 0 1 * * * 0 +0x0220 SPBLOOD 0 0 0 0 * * * * 0 0 2 * * * 0 +0x0230 SPBLOOD 0 0 0 0 * * * * 0 0 3 * * * 0 +0x0240 SPBLOOD 0 0 0 0 * * * * 0 0 4 * * * 0 +0x0300 SPSMPUFF 0 0 0 0 * * * 1 1 * * * * * 0 +0x0400 SKLH 0 0 0 0 * * SPSHADOW * 0 1 * * * * 0 +0x0410 GLPHWRDH 0 0 0 1 * * SPSHADOW * 0 1 * * * * 0 +0x0500 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0510 STNKCLDD 0 0 0 0 * * * * 1 0 0 * * * 0 +0x0600 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x0700 GREASEH 0 0 0 0 * * * * 0 0 * * * * 0 +0x0710 GREASED 0 0 0 0 * * * * 0 1 * * * * 0 +0x0800 WEBENTH 0 0 0 0 * * * * 1 0 * * * * 0 +0x0810 WEBENTD 0 0 0 0 * * * * 1 0 * * * * 0 +0x0900 STNKCLDD 0 0 0 0 * * * * 1 0 1 * * * 0 +0x1000 MWYV 1 24 5 0 * * * * 0 * * * * * 8 +0x1001 MWYV 1 24 5 0 * * * * 0 * * * * * 8 +0x2000 MSIR 5 16 3 0 * * * * 1 * 1 * * BW 6 +0x2100 UVOL 5 16 3 0 * * * * 0 * 1 * MS * 6 +0x2200 MOGM 5 24 5 0 * * * * 1 * 1 1 S1 * 6 +0x2300 MDKN 5 24 5 0 * * * * 0 * 1 1 * * 7 +0x3000 MAKH 6 24 5 0 * * * * 0 * * * * * 6 +0x4000 SNOMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4002 SNOMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4010 SNOWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4012 SNOWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4100 SSIMC 7 16 3 0 * * * * 1 * * * * * 0 +0x4101 SSIMS 7 16 3 0 * * * * 1 * * * * * 0 +0x4102 SSIMM 7 16 3 0 * * * * 1 * * * * * 0 +0x4110 SSIWC 7 16 3 0 * * * * 1 * * * * * 0 +0x4112 SSIWM 7 16 3 0 * * * * 1 * * * * * 0 +0x4200 SHMCM 7 16 3 0 * * * * 1 * * * * * 0 +0x4300 MSPLG1 7 32 5 0 * * * * 0 * * * * * 0 +0x4400 LHMC 7 20 3 0 * * * * 1 * * * * * 0 +0x4410 LHFC 7 20 3 0 * * * * 1 * * * * * 0 +0x4500 LFAM 7 20 3 0 * * * * 1 * * * * * 0 +0x4600 LDMF 7 20 3 0 * * * * 1 * * * * * 0 +0x4700 LEMF 7 20 3 0 * * * * 1 * * * * * 0 +0x4710 LEFF 7 20 3 0 * * * * 1 * * * * * 0 +0x4800 LIMC 7 20 3 0 * * * * 1 * * * * * 0 +0x5000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x5001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x5002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x5003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x5010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x5011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x5012 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x5013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x5100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x5101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x5102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x5103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x5110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x5111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x5112 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x5113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x5200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x5201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x5202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x5210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x5211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x5212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x5300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x5301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x5302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x5303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x5310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x5311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x5312 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x5313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6000 CHMB 8 16 3 0 * * CHMC * 1 1 1 * WQL * 9 +0x6001 CEMB 8 16 3 0 * * CEMC * 1 1 1 * WQM * 9 +0x6002 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS * 9 +0x6003 CIMB 8 16 3 0 * * CIMC * 1 1 1 * WQS WQH 9 +0x6004 CDMB 8 16 3 0 * * CDMC * 1 1 1 * WQS WQH 9 +0x6010 CHFB 8 16 3 0 * * CHFC * 1 1 1 * WQN * 9 +0x6011 CEFB 8 16 3 0 * * CEFC * 1 1 1 * WQM * 9 +0x6012 CIFB 8 16 3 0 * * CIFB * 1 1 1 * WQS * 9 +0x6013 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS * 9 +0x6014 CIFB 8 16 3 0 * * CIFC * 1 1 1 * WQS WQH 9 +0x6100 CHMB 8 16 3 0 * * CHMF * 1 1 1 * WQL * 9 +0x6101 CEMB 8 16 3 0 * * CEMF * 1 1 1 * WQM * 9 +0x6102 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS * 9 +0x6103 CIMB 8 16 3 0 * * CIMF * 1 1 1 * WQS WQH 9 +0x6104 CDMB 8 16 3 0 * * CDMF * 1 1 1 * WQS WQH 9 +0x6110 CHFB 8 16 3 0 * * CHFF * 1 1 1 * WQN * 9 +0x6111 CEFB 8 16 3 0 * * CEFF * 1 1 1 * WQM * 9 +0x6112 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x6113 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS * 9 +0x6114 CIFB 8 16 3 0 * * CIFF * 1 1 1 * WQS WQH 9 +0x6200 CHMW 8 16 3 0 * * CHMW * 1 1 1 * WQL * 9 +0x6201 CEMW 8 16 3 0 * * CEMW * 1 1 1 * WQM * 9 +0x6202 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x6204 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH 9 +0x6210 CHFW 8 16 3 0 * * CHFW * 1 1 1 * WQN * 9 +0x6211 CEFW 8 16 3 0 * * CEFW * 1 1 1 * WQM * 9 +0x6212 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS * 9 +0x6214 CDMW 8 16 3 0 * * CDMW * 1 1 1 * WQS WQH 9 +0x6300 CHMT 8 16 3 0 * * CHMT * 1 1 0 * WQL * 9 +0x6301 CEMT 8 16 3 0 * * CEMT * 1 1 0 * WQM * 9 +0x6302 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS * 9 +0x6303 CIMT 8 16 3 0 * * CIMT * 1 1 0 * WQS WQH 9 +0x6304 CDMT 8 16 3 0 * * CDMT * 1 1 0 * WQS WQH 9 +0x6310 CHFT 8 16 3 0 * * CHFT * 1 1 0 * WQN * 9 +0x6311 CEFT 8 16 3 0 * * CEFT * 1 1 0 * WQM * 9 +0x6312 CIFT 8 16 3 0 * * CIFF * 1 1 0 * WQS * 9 +0x6313 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS * 9 +0x6314 CIFT 8 16 3 0 * * CIFT * 1 1 0 * WQS WQH 9 +0x6400 UDRZ 9 16 3 0 * * * * 1 * 1 0 WPM * 9 +0x6401 CTES 9 16 3 0 * * * * 1 * 0 0 WPM * 9 +0x6402 CMNK 9 16 3 0 * * * * 1 * 0 * WPM * 9 +0x6403 MSKL 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x6404 USAR 9 16 3 0 * * * * 0 * 0 0 WPL * 9 +0x6405 MDGU 9 16 3 0 * * * * 1 * 1 * WPM * 9 +0x7000 MOGH 11 16 3 0 * * * * 1 * * * * * 7 +0x7001 MOGN 11 16 3 0 * * * * 1 * * * * * 6 +0x7100 MBAS 11 32 5 0 * * * * 0 * * * * * 6 +0x7101 MBAS 11 32 5 0 MBAS_GR * * * 0 * * * * * 6 +0x7200 MBER 11 24 5 0 MBER_BL * * * 0 * * * * * 4 +0x7201 MBER 11 24 5 0 * * * * 0 * * * * * 4 +0x7202 MBER 11 24 5 0 MBER_CA * * * 0 * * * * * 4 +0x7203 MBER 11 24 5 0 MBER_PO * * * 0 * * * * * 4 +0x7300 MEAE 10 32 5 0 * * * * 0 1 * * * * 10 +0x7302 MEAE 10 32 5 0 MEAE_SH * * * 0 1 * * * * 10 +0x7400 MDOG 11 16 3 0 MDOG_WI * * * 0 * * * * * 6 +0x7401 MDOG 11 16 3 0 MDOG_WA * * * 0 * * * * * 6 +0x7402 MDOG 11 16 3 0 MDOG_MO * * * 0 * * * * * 6 +0x7500 MDOP 11 16 3 0 * * * * 0 * * * * * 6 +0x7501 MDOP 11 16 3 0 MDOP_GR * * * 0 * * * * * 6 +0x7600 METT 11 16 3 0 * * * * 0 * * * * * 6 +0x7701 MGHL 11 16 3 0 MGHL_RE * * * 0 * * * * * 8 +0x7702 MGHL 11 16 3 0 MGHL_GA * * * 0 * * * * * 4 +0x7800 MGIB 11 16 3 0 * * * * 0 * * * * * 6 +0x7900 MSLI 11 24 4 0 MSLI_GR * * 1 0 * * * * * 2 +0x7901 MSLI 11 24 4 0 MSLI_OL * * 1 0 * * * * * 2 +0x7902 MSLI 11 24 4 0 MSLI_MU * * 1 0 * * * * * 2 +0x7903 MSLI 11 24 4 0 MSLI_OC * * 1 0 * * * * * 2 +0x7904 MSLI 11 24 4 0 * * * 1 0 * * * * * 2 +0x7a00 MSPI 11 24 4 0 MSPI_GI * * * 0 * * * * * 5 +0x7a01 MSPI 11 24 4 0 MSPI_HU * * * 0 * * * * * 5 +0x7a02 MSPI 11 24 4 0 MSPI_PH * * * 0 * * * * * 5 +0x7a03 MSPI 11 24 4 0 MSPI_SW * * * 0 * * * * * 5 +0x7a04 MSPI 11 24 4 0 MSPI_WR * * * 0 * * * * * 5 +0x7b00 MWLF 11 16 3 0 * * * * 0 * * * * * 8 +0x7b01 MWLF 11 16 3 0 MWLF_WO * * * 0 * * * * * 8 +0x7b02 MWLF 11 16 3 0 MWLF_DI * * * 0 * * * * * 8 +0x7b03 MWLF 11 16 3 0 MWLF_WI * * * 0 * * * * * 8 +0x7b04 MWLF 11 16 3 0 MWLF_VA * * * 0 * * * * * 8 +0x7b05 MWLF 11 16 3 0 MWLF_DR * * * 0 * * * * * 8 +0x7c00 MXVT 11 16 3 0 * * * * 1 * * * * * 6 +0x7c01 MTAS 11 16 3 0 * * * * 0 * * * * * 6 +0x7d00 MZOM 11 16 3 0 * * * * 1 * * * * * 4 +0x7e00 MWER 11 16 3 0 * * * * 0 * * * * * 10 +0x7e01 MGWE 11 16 3 0 * * * * 0 * * * * * 10 +0x7f00 MTRO 10 16 3 0 * * * * 0 1 * * * * 10 +0x7f01 MMIN 10 16 3 0 * * * * 0 1 * * * * 8 +0x7f02 MBEH 10 32 5 0 * * * * 0 1 * * * * 8 +0x7f03 MIMP 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f06 MDJL 10 16 3 0 * * * * 0 1 * * * * 11 +0x7f10 MRAK 10 16 3 0 * * * * 0 1 * * * * 10 +0x8000 MGNL 4 20 3 0 * * * * * * * * S1 HB 5 +0x8100 MHOB 4 16 3 0 * * * * * * * * S1 BW 5 +0x8200 MKOB 4 16 3 0 * * * * * * * * SS BW 6 +0x9000 MOGR 12 24 5 0 * * * * 1 * * * * * 6 +0xa000 MWYV 13 16 3 0 * * * * 0 * * * * * 8 +0xa100 MCAR 13 32 5 0 * * * * 0 * * * * * 6 +0xb000 ACOW 14 32 5 0 * * * * 0 * * * * * 0 +0xb100 AHRS 14 32 5 0 * * * * 0 * * * * * 0 +0xb200 NBEGL 14 16 3 0 * * * * 1 * * * * * 0 +0xb210 NPROL 14 16 3 0 * * * * 1 * * * * * 0 +0xb300 NBOYL 14 16 3 0 * * * * 1 * * * * * 0 +0xb310 NGRLL 14 16 3 0 * * * * 1 * * * * * 0 +0xb400 NFAML 14 16 3 0 * * * * 1 * * * * * 0 +0xb410 NFAWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb500 NSIML 14 16 3 0 * * * * 1 * * * * * 0 +0xb510 NSIWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb600 NNOML 14 16 3 0 * * * * 1 * * * * * 0 +0xb610 NNOWL 14 16 3 0 * * * * 1 * * * * * 0 +0xb700 NSLVL 14 16 3 0 * * * * 1 * * * * * 0 +0xc000 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc100 ACAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc200 ACHK 15 12 3 0 * * * * 0 * * * * * 3 +0xc300 ARAT 15 12 3 0 * * * * 0 * * * * * 4 +0xc400 ASQU 15 12 3 0 * * * * 0 * * * * * 4 +0xc500 ABAT 15 12 3 0 * * * * 0 * * * * * 8 +0xc600 NBEGH 15 16 3 0 * * * * 1 * * * * * 6 +0xc610 NPROH 15 16 3 0 * * * * 1 * * * * * 6 +0xc700 NBOYH 15 16 3 0 * * * * 1 * * * * * 5 +0xc710 NGRLH 15 16 3 0 * * * * 1 * * * * * 5 +0xc800 NFAMH 15 16 3 0 * * * * 1 * * * * * 5 +0xc810 NFAWH 15 16 3 0 * * * * 1 * * * * * 5 +0xc900 NSIMH 15 16 3 0 * * * * 1 * * * * * 6 +0xc910 NSIWH 15 16 3 0 * * * * 1 * * * * * 6 +0xca00 NNOMH 15 16 3 0 * * * * 1 * * * * * 6 +0xca10 NNOWH 15 16 3 0 * * * * 1 * * * * * 6 +0xcb00 NSLVH 15 16 3 0 * * * * 1 * * * * * 6 +0xd000 AEAGG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd100 AGULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd200 AVULG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd300 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xd400 ABIRG1 16 0 3 0 * * * * 0 * * * * * 8 +0xe000_000f MBET 17 24 3 0 * * * * * * * * * * 4 +0xe010_000f MBBM 17 20 3 0 * * * * * * * * * * 4 +0xe020_000f MBBR 17 16 3 0 * * * * * * * * * * 4 +0xe030_000f MBFI 17 12 3 0 * * * * * * * * * * 8 +0xe040_000f MBRH 17 64 7 0 * * * * * * * * * * 4 +0xe050_000f MREM 17 48 5 0 * * * * * * * * * * 8 +0xe100_000f MCYC 17 48 7 0 * * * * * * * * * * 4 +0xe110_000f METN 17 48 5 0 * * * * * * * * * * 4 +0xe130_000f MGFR 17 40 5 0 * * * * * * * * * * 8 +0xe140_000f MGVE 17 32 5 0 * * * * * * * * * * 8 +0xe210_000f MELE 17 32 5 0 * * * * * * * * * * 8 +0xe220_000f MELF 17 32 5 0 * * * * * * * * * * 6 +0xe230_000f MELW 17 32 5 0 * * * * * * * * * * 8 +0xe240_000f MHAR 17 16 3 0 * * * * * * * * * * 8 +0xe250_000f MWWE 17 24 3 0 * * * * * * * * * * 8 +0xe300_000f MGH2 17 16 3 0 * * * * * * * * * * 8 +0xe310_000f MGH3 17 16 3 0 * * * * * * * * * * 6 +0xe320_000f MWIG 17 16 3 0 * * * * * * * * * * 6 +0xe330_000f MZO2 17 16 3 0 * * * * * * * * * * 4 +0xe340_000f MWI2 17 16 3 0 * * * * * * * * * * 8 +0xe350_000f MZO3 17 16 3 0 * * * * * * * * * * 4 +0xe360_000f MWI3 17 16 3 0 * * * * * * * * * * 6 +0xe380_000f MMUM 17 16 3 0 * * * * * * * * * * 5 +0xe390_000f MHIS 17 16 3 0 * * * * * * * * * * 4 +0xe3a0_000f MDRD 17 24 3 0 * * * * * * * * * * 4 +0xe3b0_000f MWAV 17 16 3 0 * * * 1 * * * * * * 5 +0xe400_000f MGO1 17 16 3 0 * * * * * * * * * * 8 +0xe410_000f MGO2 17 16 3 0 * * * * * * * * * * 8 +0xe420_000f MGO3 17 16 3 0 * * * * * * * * * * 8 +0xe430_000f MGO4 17 16 3 0 * * * * * * * * * * 8 +0xe440_000f MSVI 17 16 3 0 * * * * * * * * * * 8 +0xe450_000f MSV2 17 16 3 0 * * * * * * * * * * 8 +0xe510_000f MGIR 17 24 3 0 * * * * * * * * * * 5 +0xe520_000f MGIC 17 32 5 0 * * * * * * * * * * 7 +0xe620_000f MSKB 17 24 3 0 * * * * * * * * * * 4 +0xe700_000f MMIN 17 24 3 0 * * * * * * * * * * 6 +0xe710_000f MTRO 17 16 3 0 * * * * * * * * * * 6 +0xe720_000f MTIC 17 16 3 0 * * * * * * * * * * 6 +0xe730_000f MTSN 17 24 3 0 * * * * * * * * * * 8 +0xe750_000f MUMB 17 24 5 0 * * * * * * * * * * 8 +0xe760_000f MYET 17 24 3 0 * * * * * * * * * * 8 +0xe770_000f MBA4 17 16 3 0 * * * * * * * * * * 8 +0xe780_000f MBA5 17 16 3 0 * * * * * * * * * * 8 +0xe790_000f MBA6 17 16 3 0 * * * * * * * * * * 8 +0xe7a0_000f MBAI 17 16 3 0 * * * * * * * * * * 8 +0xe7e0_000f MSCR 17 24 3 0 * * * * * * * * * * 8 +0xe7f0_000f MUM2 17 24 5 0 * * * * * * * * * * 8 +0xe810_000f MOR1 17 16 3 0 * * * * * * * * * * 6 +0xe820_000f MOR2 17 16 3 0 * * * * * * * * * * 6 +0xe830_000f MOR3 17 16 3 0 * * * * * * * * * * 6 +0xe840_000f MOR4 17 16 3 0 * * * * * * * * * * 6 +0xe850_000f MOR5 17 16 3 0 * * * * * * * * * * 6 +0xe860_000f MNO1 17 16 3 0 * * * * * * * * * * 6 +0xe870_000f MNO2 17 16 3 0 * * * * * * * * * * 6 +0xe880_000f MNO3 17 24 5 0 * * * * * * * * * * 6 +0xe890_000f MLI3 17 32 5 0 * * * * * * * * * * 7 +0xe8a0_000f MYU3 17 16 3 0 * * * * * * * * * * 6 +0xe900_000f MSH1 17 16 3 0 * * * 1 * * * * * * 6 +0xe910_000f MSH2 17 24 3 0 * * * 1 * * * * * * 8 +0xe920_000f MGHO 17 16 3 0 * * * 1 * * * * * * 8 +0xea20_000f MCRD 17 24 3 0 * * * * * * * * * * 8 +0xeb00_000f MANI 17 24 3 0 * * * * * * * * * * 6 +0xeb10_000f MAN2 17 24 3 0 * * * * * * * * * * 6 +0xeb20_000f MAN3 17 24 3 0 * * * * * * * * * * 4 +0xeb30_000f MBE1 17 32 3 0 * * * * * * * * * * 8 +0xeb40_000f MBE2 17 16 3 0 * * * * * * * * * * 8 +0xeb51 MSEE 17 16 3 0 * * * * * * * * * * 6 +0xeb52 MFIR 17 16 3 0 * * * * * * * * * * 8 +0xeb60_000f MLIC 17 16 3 0 * * * * * * * * * * 8 +0xeb70_000f MLER 17 16 3 0 * * * * * * * * * * 8 +0xeb80_000f MMAN 17 32 5 0 * * * * * * * * * * 8 +0xeb90_000f MMYC 17 16 3 0 * * * * * * * * * * 8 +0xeba0_000f MMY2 17 16 3 0 * * * * * * * * * * 8 +0xebb0_000f MSHR 17 24 3 0 * * * * * * * * * * 8 +0xebc0_000f MTAN 17 24 3 0 * * * * * * * * * * 8 +0xebd0_000f MSAL 17 16 3 0 * * * * * * * * * * 6 +0xebe0_000f MSA2 17 16 3 0 * * * * * * * * * * 8 +0xebf1_000f MARU 17 16 3 0 * * * * * * * * * * 8 +0xec00_000f MWDR 17 72 7 0 * * * * * * * * * * 8 +0xec10_000f MCHY 17 16 3 0 * * * * * * * * * * 8 +0xed00_000f MCOR 17 24 5 0 * * * * * * * * * * 6 +0xed10_000f MGLA 17 24 5 0 * * * * * * * * * * 6 +0xf000_000f MSKA 17 16 3 0 * * * * * * * * * * 6 +0xf010_000f MSKT 17 16 3 0 * * * * * * * * * * 6 +0xf020_000f MWI4 17 16 3 0 * * * * * * * * * * 6 +0xf100_000f MYU1 17 16 3 0 * * * * * * * * * * 6 +0xf110_000f MYU2 17 16 3 0 * * * * * * * * * * 6 +0xf200_000f MLIZ 17 24 5 0 * * * * * * * * * * 6 +0xf210_000f MLI2 17 24 5 0 * * * * * * * * * * 5 +0xf300_000f MGFI 17 40 5 0 * * * * * * * * * * 6 +0xf400_000f MSAH 17 24 3 0 * * * * * * * * * * 8 +0xf410_000f MSAT 17 32 5 0 * * * * * * * * * * 8 +0xf770_000f MBA1 17 16 3 0 * * * * * * * * * * 8 +0xf780_000f MBA2 17 16 3 0 * * * * * * * * * * 8 +0xf790_000f MBA3 17 16 3 0 * * * * * * * * * * 8 diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-pst.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-pst.2da index 759be528a..4f8cdf5ca 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-pst.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-pst.2da @@ -1,110 +1,110 @@ 2DA V1.0 * - RESREF RESREF2 TYPE ELLIPSE SPACE CLOWN ARMOR BESTIARY -0x0000 cabsb * 18 20 4 * 5 0 -0x0001 cadvb * 18 16 3 * 0 3 -0x0002 dannb * 18 16 3 1 0 4 -0x0003 cbaub * 18 16 3 * * 69 -0x0004 ccamb * 18 22 5 * 2 6 -0x0005 ccolb * 18 16 3 * 0 7 -0x0006 dcthb * 18 16 3 4 1 78 -0x0007 ccorb * 18 20 4 * 5 8 -0x0008 ccrnb * 18 12 3 * 5 9 -0x0009 ccgdb * 18 16 3 * 2 10 -0x000a dctfb * 18 16 3 4 0 85 -0x000b dctmb * 18 16 3 4 0 11 -0x000c cdabb * 18 16 3 * 0 12 -0x000d ddkkb * 18 16 3 1 1 13 -0x000e cdeib * 18 16 3 * 6 14 -0x000f cdhlb * 18 20 4 * 0 15 -0x0010 dhkmb * 18 20 4 1 2 16 -0x0011 clkmb * 18 16 5 * 2 17 -0x0012 cwizb * 18 16 3 * 0 18 -0x0013 ddtfb * 18 16 3 2 0 86 -0x0014 ddtmb * 18 16 3 2 0 19 -0x0015 cebbb * 18 16 3 * 2 20 -0x0016 cffgb * 18 16 3 * 0 21 -0x0017 cfhjb * 18 20 4 * 5 23 -0x0018 cgarb * 18 16 3 * 5 24 -0x0019 dghfb * 18 16 3 4 0 87 -0x001a dghmb * 18 16 3 4 0 25 -0x001b cgstb * 18 16 3 * 3 26 -0x001c cacab * 18 16 3 * 0 27 -0x001d dgytb * 18 16 3 4 1 28 -0x001e cglab * 18 30 5 * 5 29 -0x001f dgodb * 18 16 3 4 1 30 -0x0020 cgdgb * 18 16 3 * 2 31 -0x0021 cgrib * 18 16 3 * 5 32 -0x0022 cgrkb * 18 30 5 * 5 33 -0x0023 chrsb * 18 16 5 * 5 34 -0x0024 cigyb * 18 16 3 * 10 35 -0x0025 cjbrb * 18 16 3 * 5 36 -0x0026 cjftb * 18 16 3 * 5 37 -0x0027 clopb * 18 30 5 * 0 38 -0x0028 clemb * 18 20 3 * 5 39 -0x0029 clthb * 18 16 3 * 0 40 -0x002a dmhtb * 18 16 3 4 0 74 -0x002b cmklb * 18 16 3 * 2 41 -0x002c dmidb * 18 16 3 * 0 75 -0x002d cmdrb * 18 16 3 * 7 42 -0x002e cmrtb * 18 16 3 * 4 43 -0x002f dnoab * 18 16 3 * 8 76 -0x0030 dnocb * 18 16 3 * 8 76 -0x0031 dnodb * 18 16 3 * 8 76 -0x0032 dnofb * 18 16 3 * 8 76 -0x0033 dnohb * 18 16 3 * 8 76 -0x0034 cndmb * 18 16 3 * 7 44 -0x0035 cnprb * 18 16 3 * 5 45 -0x0036 cphdb * 18 16 3 * 0 46 -0x0037 dprob * 18 16 3 4 0 47 -0x0038 cpuzb * 18 16 3 * 0 48 -0x0039 csm1b * 18 16 3 * 9 49 -0x003a carmb * 18 20 4 * 2 52 -0x003b dsptb * 18 16 3 3 0 53 -0x003c dskwb * 18 16 3 3 4 54 -0x003d dhlfb * 18 16 3 4 0 72 -0x003e dhlmb * 18 16 3 4 0 73 -0x003f dhufb * 18 16 3 4 0 80 -0x0040 dhumb * 18 16 3 4 0 81 -0x0041 ctrzb * 18 20 4 * 6 55 -0x0042 ctgtb * 18 16 3 * 5 56 -0x0043 ctreb * 18 16 3 * 5 57 -0x0044 ctrsb * 18 16 3 * 5 58 -0x0045 ctrcb * 18 32 5 * 5 59 -0x0046 cvgyb * 18 16 3 * 5 60 -0x0047 dvhab * 18 16 3 1 2 61 -0x0048 dwreb * 18 16 3 1 0 62 -0x0049 cwrmb * 18 16 3 * 1 63 -0x004a cwurb * 18 16 3 * 5 64 -0x004b dzmfb * 18 16 3 4 5 83 -0x004c dzmmb * 18 16 3 4 5 65 -0x004d cfelb * 18 16 3 * 0 22 -0x004e csm2b * 18 16 3 * 9 50 -0x004f csm3b * 18 22 5 * 9 51 -0x0050 dthkb * 18 22 5 4 5 5 -0x0051 dnosb * 18 16 3 * 8 76 -0x0052 dnozb * 18 16 3 * 5 76 -0x0053 dtffb * 18 16 3 4 0 84 -0x0054 dtlrb * 18 16 3 4 1 77 -0x0055 dtfmb * 18 16 3 4 1 79 -0x0056 dbaub * 18 16 3 4 1 69 -0x0057 charb * 18 16 5 * 2 71 -0x0058 dmidb * 18 16 3 4 0 75 -0x0059 daafb * 18 16 3 4 0 70 -0x005a cgabb * 18 20 4 * 5 1 -0x005b crabb * 18 20 4 * 5 2 -0x005c dtwzb * 18 16 3 4 0 82 -0x005d dnomb * 18 16 3 * 0 19 -0x005e cuhdb * 18 16 3 * 3 * -0x005f dquib * 18 16 3 4 3 * -0x0060 ctrdb * 18 16 5 * 5 58 -0x0061 dnofb * 18 16 3 * 8 76 -0x0062 dvhstdb * 18 16 3 1 2 * -0x0063 dctmb * 18 16 3 4 0 * -0x0064 daafb * 18 16 3 4 0 80 -0x0066 dctfb * 18 16 3 4 0 * -0x022e carmb * 18 16 3 * 2 52 -0x080e cdeib * 18 16 3 * 6 14 -0x2000 GEARS1 GEARS2 18 60 13 * * * -0x3000 IGHEAD IGARM 18 15 13 * * 66 -0xf000 POSMAIN POSSHAD 18 60 13 * * 67 + RESREF RESREF2 TYPE ELLIPSE SPACE CLOWN ARMOR BESTIARY MOVE_SCALE +0x0000 cabsb * 18 20 4 * 5 0 4.3 +0x0001 cadvb * 18 16 3 * 0 3 4.5 +0x0002 dannb * 18 16 3 1 0 4 5 +0x0003 cbaub * 18 16 3 * * 69 6.0 +0x0004 ccamb * 18 22 5 * 2 6 6.0 +0x0005 ccolb * 18 16 3 * 0 7 2.8 +0x0006 dcthb * 18 16 3 4 1 78 5 +0x0007 ccorb * 18 20 4 * 5 8 4.4 +0x0008 ccrnb * 18 12 3 * 5 9 5 +0x0009 ccgdb * 18 16 3 * 2 10 6.0 +0x000a dctfb * 18 16 3 4 0 85 6.0 +0x000b dctmb * 18 16 3 4 0 11 3.8 +0x000c cdabb * 18 16 3 * 0 12 6.0 +0x000d ddkkb * 18 16 3 1 1 13 6.0 +0x000e cdeib * 18 16 3 * 6 14 6.0 +0x000f cdhlb * 18 20 4 * 0 15 6.0 +0x0010 dhkmb * 18 20 4 1 2 16 6.8 +0x0011 clkmb * 18 16 5 * 2 17 5 +0x0012 cwizb * 18 16 3 * 0 18 6.0 +0x0013 ddtfb * 18 16 3 2 0 86 2.6 +0x0014 ddtmb * 18 16 3 2 0 19 3.9 +0x0015 cebbb * 18 16 3 * 2 20 5.5 +0x0016 cffgb * 18 16 3 * 0 21 4.24 +0x0017 cfhjb * 18 20 4 * 5 23 4.4 +0x0018 cgarb * 18 16 3 * 5 24 9.85 +0x0019 dghfb * 18 16 3 4 0 87 1.5 +0x001a dghmb * 18 16 3 4 0 25 3 +0x001b cgstb * 18 16 3 * 3 26 5 +0x001c cacab * 18 16 3 * 0 27 2.9 +0x001d dgytb * 18 16 3 4 1 28 5 +0x001e cglab * 18 30 5 * 5 29 6.75 +0x001f dgodb * 18 16 3 4 1 30 5 +0x0020 cgdgb * 18 16 3 * 2 31 6.0 +0x0021 cgrib * 18 16 3 * 5 32 6.0 +0x0022 cgrkb * 18 30 5 * 5 33 6.0 +0x0023 chrsb * 18 16 5 * 5 34 4.25 +0x0024 cigyb * 18 16 3 * 10 35 6.0 +0x0025 cjbrb * 18 16 3 * 5 36 7 +0x0026 cjftb * 18 16 3 * 5 37 6.0 +0x0027 clopb * 18 30 5 * 0 38 4.5 +0x0028 clemb * 18 20 3 * 5 39 1.3 +0x0029 clthb * 18 16 3 * 0 40 4.5 +0x002a dmhtb * 18 16 3 4 0 74 3.55 +0x002b cmklb * 18 16 3 * 2 41 6.35 +0x002c dmidb * 18 16 3 * 0 75 3.5 +0x002d cmdrb * 18 16 3 * 7 42 6.5 +0x002e cmrtb * 18 16 3 * 4 43 6.0 +0x002f dnoab * 18 16 3 * 8 76 5.15 +0x0030 dnocb * 18 16 3 * 8 76 5.15 +0x0031 dnodb * 18 16 3 * 8 76 5.15 +0x0032 dnofb * 18 16 3 * 8 76 5.15 +0x0033 dnohb * 18 16 3 * 8 76 4.5 +0x0034 cndmb * 18 16 3 * 7 44 6.5 +0x0035 cnprb * 18 16 3 * 5 45 4.5 +0x0036 cphdb * 18 16 3 * 0 46 2.5 +0x0037 dprob * 18 16 3 4 0 47 3.5 +0x0038 cpuzb * 18 16 3 * 0 48 3.85 +0x0039 csm1b * 18 16 3 * 9 49 6.0 +0x003a carmb * 18 20 4 * 2 52 4.5 +0x003b dsptb * 18 16 3 3 0 53 5.2 +0x003c dskwb * 18 16 3 3 4 54 4.35 +0x003d dhlfb * 18 16 3 4 0 72 5 +0x003e dhlmb * 18 16 3 4 0 73 3.55 +0x003f dhufb * 18 16 3 4 0 80 2.9 +0x0040 dhumb * 18 16 3 4 0 81 3.95 +0x0041 ctrzb * 18 20 4 * 6 55 10 +0x0042 ctgtb * 18 16 3 * 5 56 6.0 +0x0043 ctreb * 18 16 3 * 5 57 6.45 +0x0044 ctrsb * 18 16 3 * 5 58 6.0 +0x0045 ctrcb * 18 32 5 * 5 59 6.45 +0x0046 cvgyb * 18 16 3 * 5 60 9.5 +0x0047 dvhab * 18 16 3 1 2 61 5.5 +0x0048 dwreb * 18 16 3 1 0 62 6.0 +0x0049 cwrmb * 18 16 3 * 1 63 6.0 +0x004a cwurb * 18 16 3 * 5 64 5.5 +0x004b dzmfb * 18 16 3 4 5 83 2.4 +0x004c dzmmb * 18 16 3 4 5 65 2.1 +0x004d cfelb * 18 16 3 * 0 22 4.5 +0x004e csm2b * 18 16 3 * 9 50 6.0 +0x004f csm3b * 18 22 5 * 9 51 7.8 +0x0050 dthkb * 18 22 5 4 5 5 6.0 +0x0051 dnosb * 18 16 3 * 8 76 5.15 +0x0052 dnozb * 18 16 3 * 5 76 2.1 +0x0053 dtffb * 18 16 3 4 0 84 6.0 +0x0054 dtlrb * 18 16 3 4 1 77 6.0 +0x0055 dtfmb * 18 16 3 4 1 79 6.0 +0x0056 dbaub * 18 16 3 4 1 69 6.0 +0x0057 charb * 18 16 5 * 2 71 6.0 +0x0058 dmidb * 18 16 3 4 0 75 6.0 +0x0059 daafb * 18 16 3 4 0 70 6.0 +0x005a cgabb * 18 20 4 * 5 1 6.0 +0x005b crabb * 18 20 4 * 5 2 6.0 +0x005c dtwzb * 18 16 3 4 0 82 6.0 +0x005d dnomb * 18 16 3 * 0 19 3.4 +0x005e cuhdb * 18 16 3 * 3 * 0 +0x005f dquib * 18 16 3 4 3 * 0 +0x0060 ctrdb * 18 16 5 * 5 58 1 +0x0061 dnofb * 18 16 3 * 8 76 5.15 +0x0062 dvhstdb * 18 16 3 1 2 * 0 +0x0063 dctmb * 18 16 3 4 0 * 0 +0x0064 daafb * 18 16 3 4 0 80 0 +0x0066 dctfb * 18 16 3 4 0 * 0 +0x022e carmb * 18 16 3 * 2 52 4.5 +0x080e cdeib * 18 16 3 * 6 14 6.0 +0x2000 GEARS1 GEARS2 18 60 13 * * * 0.0 +0x3000 IGHEAD IGARM 18 15 13 * * 66 0.0 +0xf000 POSMAIN POSSHAD 18 60 13 * * 67 4.3 diff --git a/src/org/infinity/resource/cre/decoder/tables/avatars-pstee.2da b/src/org/infinity/resource/cre/decoder/tables/avatars-pstee.2da index 62d43422c..934070c0c 100644 --- a/src/org/infinity/resource/cre/decoder/tables/avatars-pstee.2da +++ b/src/org/infinity/resource/cre/decoder/tables/avatars-pstee.2da @@ -1,105 +1,105 @@ 2DA V1.0 * - RESREF RESREF2 TYPE ELLIPSE SPACE CLOWN ARMOR BESTIARY -0x0000 cabsb * 18 20 4 * 5 0 -0x0001 cadvb * 18 16 3 * 0 3 -0x0002 dannb * 18 16 3 1 0 4 -0x0003 cbaub * 18 16 3 * * 69 -0x0004 ccamb * 18 22 5 * 2 6 -0x0005 ccolb * 18 16 3 * 0 7 -0x0006 dcthb * 18 16 3 4 1 78 -0x0007 ccorb * 18 20 4 * 5 8 -0x0008 ccrnb * 18 12 3 * 5 9 -0x0009 ccgdb * 18 16 3 * 2 10 -0x000a dctfb * 18 16 3 4 0 85 -0x000b dctmb * 18 16 3 4 0 11 -0x000c cdabb * 18 16 3 * 0 12 -0x000d ddkkb * 18 16 3 1 1 13 -0x000e cdeib * 18 16 3 * 6 14 -0x000f cdhlb * 18 20 4 * 0 15 -0x0010 dhkmb * 18 20 4 1 2 16 -0x0011 clkmb * 18 16 5 * 2 17 -0x0012 cwizb * 18 16 3 * 0 18 -0x0013 ddtfb * 18 16 3 2 0 86 -0x0014 ddtmb * 18 16 3 2 0 19 -0x0015 cebbb * 18 16 3 * 2 20 -0x0016 cffgb * 18 16 3 * 0 21 -0x0017 cfhjb * 18 20 4 * 5 23 -0x0018 cgarb * 18 16 3 * 5 24 -0x0019 dghfb * 18 16 3 4 0 87 -0x001a dghmb * 18 16 3 4 0 25 -0x001b cgstb * 18 16 3 * 3 26 -0x001c cacab * 18 16 3 * 0 27 -0x001d dgytb * 18 16 3 4 1 28 -0x001e cglab * 18 30 5 * 5 29 -0x001f dgodb * 18 16 3 4 1 30 -0x0020 cgdgb * 18 16 3 * 2 31 -0x0021 cgrib * 18 16 3 * 5 32 -0x0022 cgrkb * 18 30 5 * 5 33 -0x0023 chrsb * 18 16 5 * 5 34 -0x0024 cigyb * 18 16 3 * 10 35 -0x0025 cjbrb * 18 16 3 * 5 36 -0x0026 cjftb * 18 16 3 * 5 37 -0x0027 clopb * 18 30 5 * 0 38 -0x0028 clemb * 18 20 3 * 5 39 -0x0029 clthb * 18 16 3 * 0 40 -0x002a dmhtb * 18 16 3 4 0 74 -0x002b cmklb * 18 16 3 * 2 41 -0x002c dmidb * 18 16 3 * 0 75 -0x002d cmdrb * 18 16 3 * 7 42 -0x002e cmrtb * 18 16 3 * 4 43 -0x002f dnoab * 18 16 3 * 8 76 -0x0030 dnocb * 18 16 3 * 8 76 -0x0031 dnodb * 18 16 3 * 8 76 -0x0032 dnofb * 18 16 3 * 8 76 -0x0033 dnohb * 18 16 3 * 8 76 -0x0034 cndmb * 18 16 3 * 7 44 -0x0035 cnprb * 18 16 3 * 5 45 -0x0036 cphdb * 18 16 3 * 0 46 -0x0037 dprob * 18 16 3 4 0 47 -0x0038 cpuzb * 18 16 3 * 0 48 -0x0039 csm1b * 18 16 3 * 9 49 -0x003a carmb * 18 20 4 * 2 52 -0x003b dsptb * 18 16 3 3 0 53 -0x003c dskwb * 18 16 3 3 4 54 -0x003d dhlfb * 18 16 3 4 0 72 -0x003e dhlmb * 18 16 3 4 0 73 -0x003f dhufb * 18 16 3 4 0 80 -0x0040 dhumb * 18 16 3 4 0 81 -0x0041 ctrzb * 18 20 4 * 6 55 -0x0042 ctgtb * 18 16 3 * 5 56 -0x0043 ctreb * 18 16 3 * 5 57 -0x0044 ctrsb * 18 16 3 * 5 58 -0x0045 ctrcb * 18 32 5 * 5 59 -0x0046 cvgyb * 18 16 3 * 5 60 -0x0047 dvhab * 18 16 3 1 2 61 -0x0048 dwreb * 18 16 3 1 0 62 -0x0049 cwrmb * 18 16 3 * 1 63 -0x004a cwurb * 18 16 3 * 5 64 -0x004b dzmfb * 18 16 3 4 5 83 -0x004c dzmmb * 18 16 3 4 5 65 -0x004d cfelb * 18 16 3 * 0 22 -0x004e csm2b * 18 16 3 * 9 50 -0x004f csm3b * 18 22 5 * 9 51 -0x0050 dthkb * 18 22 5 4 5 5 -0x0051 dnosb * 18 16 3 * 8 76 -0x0052 dnozb * 18 16 3 * 5 76 -0x0053 dtffb * 18 16 3 4 0 84 -0x0054 dtlrb * 18 16 3 4 1 77 -0x0055 dtfmb * 18 16 3 4 1 79 -0x0056 dbaub * 18 16 3 4 1 69 -0x0057 charb * 18 16 5 * 2 71 -0x0058 dmidb * 18 16 3 4 0 75 -0x0059 daafb * 18 16 3 4 0 70 -0x005a cgabb * 18 20 4 * 5 1 -0x005b crabb * 18 20 4 * 5 2 -0x005c dtwzb * 18 16 3 4 0 82 -0x005d dnomb * 18 16 3 * 0 19 -0x005e cuhdb * 18 16 3 * 3 * -0x005f dquib * 18 16 3 4 3 * -0x0060 ctrdb * 18 16 5 * 5 58 -0x0061 dnofb * 18 16 3 * 8 76 -0x0062 dvhstdb * 18 16 3 1 2 * -0x2000 GEARS1 GEARS2 18 60 13 * * * -0xf100 POSMAIN POSSHAD 18 60 13 * * 67 -0xf101 IGHEAD IGARM 18 15 13 * * 66 + RESREF RESREF2 TYPE ELLIPSE SPACE CLOWN ARMOR BESTIARY MOVE_SCALE +0x0000 cabsb * 18 20 4 * 5 0 4.3 +0x0001 cadvb * 18 16 3 * 0 3 4.5 +0x0002 dannb * 18 16 3 1 0 4 5 +0x0003 cbaub * 18 16 3 * * 69 6.0 +0x0004 ccamb * 18 22 5 * 2 6 6.0 +0x0005 ccolb * 18 16 3 * 0 7 2.8 +0x0006 dcthb * 18 16 3 4 1 78 5 +0x0007 ccorb * 18 20 4 * 5 8 4.4 +0x0008 ccrnb * 18 12 3 * 5 9 5 +0x0009 ccgdb * 18 16 3 * 2 10 6.0 +0x000a dctfb * 18 16 3 4 0 85 6.0 +0x000b dctmb * 18 16 3 4 0 11 3.8 +0x000c cdabb * 18 16 3 * 0 12 6.0 +0x000d ddkkb * 18 16 3 1 1 13 6.0 +0x000e cdeib * 18 16 3 * 6 14 6.0 +0x000f cdhlb * 18 20 4 * 0 15 6.0 +0x0010 dhkmb * 18 20 4 1 2 16 6.8 +0x0011 clkmb * 18 16 5 * 2 17 5 +0x0012 cwizb * 18 16 3 * 0 18 6.0 +0x0013 ddtfb * 18 16 3 2 0 86 2.6 +0x0014 ddtmb * 18 16 3 2 0 19 3.9 +0x0015 cebbb * 18 16 3 * 2 20 5.5 +0x0016 cffgb * 18 16 3 * 0 21 4.24 +0x0017 cfhjb * 18 20 4 * 5 23 4.4 +0x0018 cgarb * 18 16 3 * 5 24 9.85 +0x0019 dghfb * 18 16 3 4 0 87 1.5 +0x001a dghmb * 18 16 3 4 0 25 3 +0x001b cgstb * 18 16 3 * 3 26 5 +0x001c cacab * 18 16 3 * 0 27 2.9 +0x001d dgytb * 18 16 3 4 1 28 5 +0x001e cglab * 18 30 5 * 5 29 6.75 +0x001f dgodb * 18 16 3 4 1 30 5 +0x0020 cgdgb * 18 16 3 * 2 31 6.0 +0x0021 cgrib * 18 16 3 * 5 32 6.0 +0x0022 cgrkb * 18 30 5 * 5 33 6.0 +0x0023 chrsb * 18 16 5 * 5 34 4.25 +0x0024 cigyb * 18 16 3 * 10 35 6.0 +0x0025 cjbrb * 18 16 3 * 5 36 7 +0x0026 cjftb * 18 16 3 * 5 37 6.0 +0x0027 clopb * 18 30 5 * 0 38 4.5 +0x0028 clemb * 18 20 3 * 5 39 1.3 +0x0029 clthb * 18 16 3 * 0 40 4.5 +0x002a dmhtb * 18 16 3 4 0 74 3.55 +0x002b cmklb * 18 16 3 * 2 41 6.35 +0x002c dmidb * 18 16 3 * 0 75 3.5 +0x002d cmdrb * 18 16 3 * 7 42 6.5 +0x002e cmrtb * 18 16 3 * 4 43 6.0 +0x002f dnoab * 18 16 3 * 8 76 5.15 +0x0030 dnocb * 18 16 3 * 8 76 5.15 +0x0031 dnodb * 18 16 3 * 8 76 5.15 +0x0032 dnofb * 18 16 3 * 8 76 5.15 +0x0033 dnohb * 18 16 3 * 8 76 4.5 +0x0034 cndmb * 18 16 3 * 7 44 6.5 +0x0035 cnprb * 18 16 3 * 5 45 4.5 +0x0036 cphdb * 18 16 3 * 0 46 2.5 +0x0037 dprob * 18 16 3 4 0 47 3.5 +0x0038 cpuzb * 18 16 3 * 0 48 3.85 +0x0039 csm1b * 18 16 3 * 9 49 6.0 +0x003a carmb * 18 20 4 * 2 52 4.5 +0x003b dsptb * 18 16 3 3 0 53 5.2 +0x003c dskwb * 18 16 3 3 4 54 4.35 +0x003d dhlfb * 18 16 3 4 0 72 5 +0x003e dhlmb * 18 16 3 4 0 73 3.55 +0x003f dhufb * 18 16 3 4 0 80 2.9 +0x0040 dhumb * 18 16 3 4 0 81 3.95 +0x0041 ctrzb * 18 20 4 * 6 55 10 +0x0042 ctgtb * 18 16 3 * 5 56 6.0 +0x0043 ctreb * 18 16 3 * 5 57 6.45 +0x0044 ctrsb * 18 16 3 * 5 58 6.0 +0x0045 ctrcb * 18 32 5 * 5 59 6.45 +0x0046 cvgyb * 18 16 3 * 5 60 9.5 +0x0047 dvhab * 18 16 3 1 2 61 5.5 +0x0048 dwreb * 18 16 3 1 0 62 6.0 +0x0049 cwrmb * 18 16 3 * 1 63 6.0 +0x004a cwurb * 18 16 3 * 5 64 5.5 +0x004b dzmfb * 18 16 3 4 5 83 2.4 +0x004c dzmmb * 18 16 3 4 5 65 2.1 +0x004d cfelb * 18 16 3 * 0 22 4.5 +0x004e csm2b * 18 16 3 * 9 50 6.0 +0x004f csm3b * 18 22 5 * 9 51 7.8 +0x0050 dthkb * 18 22 5 4 5 5 6.0 +0x0051 dnosb * 18 16 3 * 8 76 5.15 +0x0052 dnozb * 18 16 3 * 5 76 2.1 +0x0053 dtffb * 18 16 3 4 0 84 6.0 +0x0054 dtlrb * 18 16 3 4 1 77 6.0 +0x0055 dtfmb * 18 16 3 4 1 79 6.0 +0x0056 dbaub * 18 16 3 4 1 69 6.0 +0x0057 charb * 18 16 5 * 2 71 6.0 +0x0058 dmidb * 18 16 3 4 0 75 6.0 +0x0059 daafb * 18 16 3 4 0 70 6.0 +0x005a cgabb * 18 20 4 * 5 1 6.0 +0x005b crabb * 18 20 4 * 5 2 6.0 +0x005c dtwzb * 18 16 3 4 0 82 6.0 +0x005d dnomb * 18 16 3 * 0 19 3.4 +0x005e cuhdb * 18 16 3 * 3 * 0 +0x005f dquib * 18 16 3 4 3 * 0 +0x0060 ctrdb * 18 16 5 * 5 58 1 +0x0061 dnofb * 18 16 3 * 8 76 5.15 +0x0062 dvhstdb * 18 16 3 1 2 * 0 +0x2000 GEARS1 GEARS2 18 60 13 * * * 0 +0xf100 POSMAIN POSSHAD 18 60 13 * * 67 1 +0xf101 IGHEAD IGARM 18 15 13 * * 66 0 diff --git a/src/org/infinity/resource/cre/decoder/tables/notes.txt b/src/org/infinity/resource/cre/decoder/tables/notes.txt index 00fbd4189..59046f70d 100644 --- a/src/org/infinity/resource/cre/decoder/tables/notes.txt +++ b/src/org/infinity/resource/cre/decoder/tables/notes.txt @@ -44,6 +44,8 @@ Non-PST: (all games except PST and PST:EE) - HEIGHT_SHIELD (string) - animation type "character": overlay prefix for shields - animation type "monster_layered": specifies "resref_weapon2" attribute +- MOVE_SCALE (int) + - the movement speed PST: (PST and PST:EE only) @@ -71,3 +73,5 @@ PST: (PST and PST:EE only) - (currently) unused by NI - BESTIARY (int) - (currently) unused by NI +- MOVE_SCALE (int or decimal) + - the movement speed diff --git a/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java b/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java index 9dea2dfa6..4930c2db3 100644 --- a/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/AnimationInfo.java @@ -189,7 +189,7 @@ public enum Type { * @param sectionName INI section name * @param entries list of games and their associated slot ranges. */ - private Type(int type, String sectionName, List, List>> entries) + Type(int type, String sectionName, List, List>> entries) throws IllegalArgumentException { this(new int[] { type }, sectionName, entries, null); } @@ -201,7 +201,7 @@ private Type(int type, String sectionName, List, Li * @param infinityAnimationRanges * @throws IllegalArgumentException */ - private Type(int type, String sectionName, List, List>> entries, + Type(int type, String sectionName, List, List>> entries, List infinityAnimationRanges) throws IllegalArgumentException { this(new int[] { type }, sectionName, entries, infinityAnimationRanges); } @@ -212,7 +212,7 @@ private Type(int type, String sectionName, List, Li * @param entries * @throws IllegalArgumentException */ - private Type(int[] types, String sectionName, List, List>> entries) + Type(int[] types, String sectionName, List, List>> entries) throws IllegalArgumentException { this(types, sectionName, entries, null); } @@ -223,7 +223,7 @@ private Type(int[] types, String sectionName, List, * @param entries list of games and their associated slot ranges. * @throws IllegalArgumentException */ - private Type(int[] types, String sectionName, List, List>> entries, + Type(int[] types, String sectionName, List, List>> entries, List infinityAnimationRanges) throws IllegalArgumentException { try { Misc.requireCondition(types != null && types.length > 0, "Type cannot be empty", diff --git a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java index cfd91bb68..67bec72fa 100644 --- a/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/CreatureInfo.java @@ -200,7 +200,7 @@ public int getEffectiveTranslucency() { * Specify {@code true} to return the highest level of all (active and inactive) classes. */ public int getClassLevel(boolean highestOnly) { - int retVal = 0; + int retVal; int ofsBase = cre.getExtraOffset(); String creVersion = getCreatureVersion(); @@ -670,7 +670,7 @@ public boolean isEffectActive(SegmentDef.SpriteType type, int opcode) { * @return {@code true} if the effect with matching parameters exists. Returns {@code false} otherwise. */ public boolean isEffectActive(SegmentDef.SpriteType type, Predicate pred) { - boolean retVal = false; + boolean retVal; // checking creature effects EffectInfo.Effect fx = getEffectInfo().getFirstEffect(this, type, pred); @@ -964,7 +964,7 @@ private int getEffectiveWeaponIndex(int slotIndex) { } // loading referenced item - ItemInfo info = null; + ItemInfo info; int ofsItems = Objects.requireNonNull(cre).getExtraOffset() + ((IsNumeric) cre.getAttribute(CreResource.CRE_OFFSET_ITEMS)).getValue(); try { diff --git a/src/org/infinity/resource/cre/decoder/util/DecoderAttribute.java b/src/org/infinity/resource/cre/decoder/util/DecoderAttribute.java index 9da589577..98fc32647 100644 --- a/src/org/infinity/resource/cre/decoder/util/DecoderAttribute.java +++ b/src/org/infinity/resource/cre/decoder/util/DecoderAttribute.java @@ -29,7 +29,7 @@ public Object getDefaultValue() { private final Object defValue; - private DataType(Object defValue) { + DataType(Object defValue) { this.defValue = defValue; } } diff --git a/src/org/infinity/resource/cre/decoder/util/DirDef.java b/src/org/infinity/resource/cre/decoder/util/DirDef.java index 048752955..a6a76ee83 100644 --- a/src/org/infinity/resource/cre/decoder/util/DirDef.java +++ b/src/org/infinity/resource/cre/decoder/util/DirDef.java @@ -103,7 +103,7 @@ public DirDef clone() { @Override public String toString() { - return "direction=" + direction.toString() + ", mirrored=" + Boolean.toString(mirrored) + ", cycle={" + return "direction=" + direction.toString() + ", mirrored=" + mirrored + ", cycle={" + cycle.toString() + "}"; } diff --git a/src/org/infinity/resource/cre/decoder/util/Direction.java b/src/org/infinity/resource/cre/decoder/util/Direction.java index 738fcc9b2..4a93e581d 100644 --- a/src/org/infinity/resource/cre/decoder/util/Direction.java +++ b/src/org/infinity/resource/cre/decoder/util/Direction.java @@ -43,7 +43,7 @@ public enum Direction { private final int dir; - private Direction(int dir) { + Direction(int dir) { this.dir = dir; } diff --git a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java index 3efaebb39..ce64fd5d7 100644 --- a/src/org/infinity/resource/cre/decoder/util/ItemInfo.java +++ b/src/org/infinity/resource/cre/decoder/util/ItemInfo.java @@ -4,7 +4,6 @@ package org.infinity.resource.cre.decoder.util; -import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -330,7 +329,7 @@ public enum SlotType { * @throws Exception if the ITM resource could not be loaded. */ public static ItemInfo get(ResourceEntry itmEntry) throws Exception { - ItemInfo retVal = null; + ItemInfo retVal; if (itmEntry == null) { return EMPTY; } @@ -353,7 +352,7 @@ public static ItemInfo get(ResourceEntry itmEntry) throws Exception { * @throws Exception if the ITM resource could not be loaded. */ public static ItemInfo getValidated(ResourceEntry itmEntry) throws Exception { - ItemInfo retVal = null; + ItemInfo retVal; if (itmEntry == null) { return EMPTY; } @@ -701,7 +700,7 @@ private void initDefault() { } /** Initializes relevant item attributes. */ - private void init() throws IOException, Exception { + private void init() throws Exception { if (itmEntry == null) { initDefault(); return; diff --git a/src/org/infinity/resource/cre/decoder/util/SeqDef.java b/src/org/infinity/resource/cre/decoder/util/SeqDef.java index fa6f68ead..c608a5eef 100644 --- a/src/org/infinity/resource/cre/decoder/util/SeqDef.java +++ b/src/org/infinity/resource/cre/decoder/util/SeqDef.java @@ -19,7 +19,7 @@ */ public class SeqDef implements Cloneable { /** Definition of an empty {@code SeqDef} object. */ - public static final SeqDef DEFAULT = new SeqDef(Sequence.NONE, new DirDef[0]); + public static final SeqDef DEFAULT = new SeqDef(Sequence.NONE); /** Full set of 16 directions (east & west). */ public static final Direction[] DIR_FULL = Direction.values(); @@ -36,6 +36,10 @@ public class SeqDef implements Cloneable { public static final Direction[] DIR_REDUCED_W = { Direction.S, Direction.SW, Direction.W, Direction.NW, Direction.N }; /** Reduced set of 3 eastern directions. */ public static final Direction[] DIR_REDUCED_E = { Direction.NE, Direction.E, Direction.SE }; + public static final Direction[] DIR_FULL_LIMITED_W = { Direction.S, Direction.S, Direction.SW, Direction.SW, + Direction.W, Direction.W, Direction.NW, Direction.NW, Direction.N }; + public static final Direction[] DIR_FULL_LIMITED_E = { Direction.N, Direction.NE, Direction.NE, Direction.E, + Direction.E, Direction.SE, Direction.SE }; private final Sequence sequence; private final ArrayList directions; @@ -75,8 +79,20 @@ public List getDirections() { * Appends list of direction definitions. Cycles of existing directions are appended. */ public void addDirections(DirDef... directions) { + addDirections(true, directions); + } + + /** + * Appends list of direction definitions. Cycles of existing directions are only appended if {@code group} is + * {@code true}. This is useful if only a limited direction set is available, but the animation format itself + * provides a full set of directions (e.g. via {@code path_smooth=0} parameter). + */ + public void addDirections(boolean group, DirDef... directions) { for (final DirDef dd : directions) { - DirDef dir = this.directions.stream().filter(d -> d.getDirection() == dd.getDirection()).findAny().orElse(null); + DirDef dir = null; + if (group) { + dir = this.directions.stream().filter(d -> d.getDirection() == dd.getDirection()).findAny().orElse(null); + } if (dir != null) { dir.getCycle().addCycles(dd.getCycle().getCycles()); } else { @@ -230,6 +246,22 @@ public static SeqDef createSequence(Sequence seq, Direction[] directions, boolea */ public static SeqDef createSequence(Sequence seq, Direction[] directions, boolean mirrored, Collection cycleInfo) { + return createSequence(false, seq, directions, mirrored, cycleInfo); + } + + /** + * Convenience method: Creates a fully defined sequence for all directions and their associated segment definitions. + * + * @param group Whether to group identical directions. Useful if only a limited direction set is available, but + * the animation format itself provides a full set of directions (e.g. via {@code path_smooth=0} + * parameter). + * @param seq the animation {@link Sequence}. + * @param directions List of directions to add. Cycle indices are advanced accordingly for each direction. + * @param mirrored indicates whether cycle indices are calculated in reversed direction + * @param cycleInfo collection of {@link SegmentDef} instances that are to be associated with the directions. + */ + public static SeqDef createSequence(boolean group, Sequence seq, Direction[] directions, boolean mirrored, + Collection cycleInfo) { SeqDef retVal = new SeqDef(seq); DirDef[] dirs = new DirDef[Objects.requireNonNull(directions, @@ -244,7 +276,7 @@ public static SeqDef createSequence(Sequence seq, Direction[] directions, boolea } dirs[i] = new DirDef(retVal, directions[i], mirrored, new CycleDef(null, cycleDefs)); } - retVal.addDirections(dirs); + retVal.addDirections(group, dirs); return retVal; } diff --git a/src/org/infinity/resource/cre/decoder/util/Sequence.java b/src/org/infinity/resource/cre/decoder/util/Sequence.java index 6938ab26d..cd9a11bb1 100644 --- a/src/org/infinity/resource/cre/decoder/util/Sequence.java +++ b/src/org/infinity/resource/cre/decoder/util/Sequence.java @@ -140,12 +140,12 @@ public enum Sequence { private final String desc; /** Creates a new {@code AnimationSequence} with an empty label. */ - private Sequence() { + Sequence() { this(null); } /** Creates a new {@code AnimationSequence} with the specified label. */ - private Sequence(String desc) { + Sequence(String desc) { this.desc = desc; } diff --git a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java index 8db51f420..3aa4bb039 100644 --- a/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java +++ b/src/org/infinity/resource/cre/decoder/util/SpriteUtils.java @@ -17,6 +17,7 @@ import java.util.EnumMap; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.Random; @@ -142,8 +143,8 @@ public static void clearColorCache() { */ public static CreResource getPseudoCre(int animationId, HashMap colors, HashMap equipment) throws Exception { - CreResource entry = null; - ByteBuffer buffer = null; + CreResource entry; + ByteBuffer buffer; if ((Boolean) Profile.getProperty(Profile.Key.IS_SUPPORTED_CRE_V90)) { // IWD int sizeBase = 0x33c; @@ -549,7 +550,7 @@ public static void fixShadowColor(BamV1Control control, boolean isTransparentSha int[] palette = control.getCurrentPalette(); palette[0] = 0x0000FF00; palette[1] = isTransparentShadow ? 0x80000000 : 0xFF000000; - control.setExternalPalette(palette); + control.setExternalPalette(palette, control.getTransparencyIndex(true)); } } @@ -921,7 +922,7 @@ public static Rectangle updateFrameDimension(Rectangle rect, Dimension dim) { * @return a {@code Color} object with the associated allegiance or status color. */ public static Color getAllegianceColor(int value) { - Color c = null; + Color c; if (value < 0) { // treat as panic c = new Color(0xffff20, false); @@ -947,7 +948,7 @@ public static Color getAllegianceColor(int value) { * @return */ public static Image getAllegianceImage(int value) { - Image retVal = null; + Image retVal; if (value < 0) { // treat as panic retVal = Icons.getImage(Icons.ICON_CIRCLE_YELLOW); @@ -1109,6 +1110,7 @@ public static List processTableDataGeneral(String[] data, AnimationInfo. if (id < 0) { return retVal; } + int moveScale = SpriteTables.valueToInt(data, SpriteTables.COLUMN_MOVE_SCALE, 8); int ellipse = SpriteTables.valueToInt(data, SpriteTables.COLUMN_ELLIPSE, 16); int space = SpriteTables.valueToInt(data, SpriteTables.COLUMN_SPACE, 3); int blending = SpriteTables.valueToInt(data, SpriteTables.COLUMN_BLENDING, 0); @@ -1129,6 +1131,7 @@ public static List processTableDataGeneral(String[] data, AnimationInfo. retVal.add("[general]"); retVal.add(String.format("animation_type=%04X", animType)); + retVal.add("move_scale=" + moveScale); retVal.add("ellipse=" + ellipse); retVal.add("personal_space=" + space); if ((blending & 1) == 1) { @@ -1164,11 +1167,13 @@ public static List processTableDataGeneralPst(String[] data) { if (id < 0) { return retVal; } + double moveScale = SpriteTables.valueToDouble(data, SpriteTables.COLUMN_PST_MOVE_SCALE, 6.0); int ellipse = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_ELLIPSE, 16); int space = SpriteTables.valueToInt(data, SpriteTables.COLUMN_PST_SPACE, 3); retVal.add("[general]"); retVal.add("animation_type=F000"); + retVal.add(String.format(Locale.US, "move_scale=%f", moveScale)); retVal.add("ellipse=" + ellipse); retVal.add("personal_space=" + space); @@ -1477,14 +1482,12 @@ private static List guessIniMaps(int animationId) { if (!tableEntries.isEmpty()) { for (final String line : tableEntries) { - StringBuilder sb = new StringBuilder(); - sb.append("2DA V1.0").append('\n'); - sb.append("*").append('\n'); - sb.append( - " RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD") - .append('\n'); - sb.append(line).append('\n'); - ResourceEntry entry = new BufferedResourceEntry(ByteBuffer.wrap(sb.toString().getBytes()), + String sb = "2DA V1.0" + '\n' + + "*" + '\n' + + " RESREF TYPE ELLIPSE SPACE BLENDING PALETTE PALETTE2 RESREF2 TRANSLUCENT CLOWN SPLIT HELMET WEAPON HEIGHT HEIGHT_SHIELD" + + '\n' + + line + '\n'; + ResourceEntry entry = new BufferedResourceEntry(ByteBuffer.wrap(sb.getBytes()), Integer.toString(animationId, 16) + ".2DA"); Table2da table = new Table2da(entry); retVal.addAll(SpriteTables.processTable(Profile.getGame(), table, animationId)); @@ -1556,12 +1559,11 @@ private static List guessIniMapsPst(int animationId) { int armor = iniSection.getAsInteger("armor", 0); int bestiary = iniSection.getAsInteger("bestiary", 0); - StringBuilder sb = new StringBuilder(); - sb.append("2DA V1.0").append('\n'); - sb.append("*").append('\n'); - sb.append(" RESREF RESREF2 TYPE ELLIPSE SPACE CLOWN ARMOR BESTIARY").append('\n'); - sb.append(String.format("0x%04x %s * 18 16 3 %d %d %d", id, resref, clown, armor, bestiary)).append('\n'); - ResourceEntry entry = new BufferedResourceEntry(ByteBuffer.wrap(sb.toString().getBytes()), + String sb = "2DA V1.0" + '\n' + + "*" + '\n' + + " RESREF RESREF2 TYPE ELLIPSE SPACE CLOWN ARMOR BESTIARY" + '\n' + + String.format("0x%04x %s * 18 16 3 %d %d %d", id, resref, clown, armor, bestiary) + '\n'; + ResourceEntry entry = new BufferedResourceEntry(ByteBuffer.wrap(sb.getBytes()), Integer.toString(animationId, 16) + ".2DA"); Table2da table = new Table2da(entry); retVal = SpriteTables.processTable(Profile.getGame(), table, animationId); diff --git a/src/org/infinity/resource/dlg/AbstractCode.java b/src/org/infinity/resource/dlg/AbstractCode.java index 5e42cee6d..ab6d84fec 100644 --- a/src/org/infinity/resource/dlg/AbstractCode.java +++ b/src/org/infinity/resource/dlg/AbstractCode.java @@ -41,6 +41,7 @@ import org.infinity.icon.Icons; import org.infinity.resource.AbstractStruct; import org.infinity.resource.AddRemovable; +import org.infinity.resource.Profile; import org.infinity.resource.StructEntry; import org.infinity.resource.bcs.Compiler; import org.infinity.resource.bcs.ScriptMessage; @@ -78,7 +79,7 @@ protected AbstractCode(String name) { protected AbstractCode(ByteBuffer buffer, int offset, String name) { super(offset, 8, name); read(buffer, offset); - this.text = (len.getValue() > 0) ? StreamUtils.readString(buffer, off.getValue(), len.getValue()) : ""; + this.text = (len.getValue() > 0) ? StreamUtils.readString(buffer, off.getValue(), len.getValue(), Profile.getDefaultCharset()) : ""; } // --------------------- Begin Interface ActionListener --------------------- @@ -322,7 +323,7 @@ public void addFlatList(List flatList) { flatList.add(off); flatList.add(len); try { - TextString ts = new TextString(StreamUtils.getByteBuffer(text.getBytes()), 0, len.getValue(), DLG_CODE_TEXT); + TextString ts = new TextString(StreamUtils.getByteBuffer(text.getBytes(Profile.getDefaultCharset())), 0, len.getValue(), DLG_CODE_TEXT); ts.setOffset(off.getValue()); flatList.add(ts); } catch (Exception e) { @@ -345,7 +346,7 @@ public int updateOffset(int offs) { } public void writeString(OutputStream os) throws IOException { - StreamUtils.writeString(os, text, len.getValue()); + StreamUtils.writeString(os, text, len.getValue(), Profile.getDefaultCharset()); } private void highlightLine(int linenr) { diff --git a/src/org/infinity/resource/dlg/DlgItem.java b/src/org/infinity/resource/dlg/DlgItem.java index e9b2e51bb..51e239cad 100644 --- a/src/org/infinity/resource/dlg/DlgItem.java +++ b/src/org/infinity/resource/dlg/DlgItem.java @@ -51,7 +51,7 @@ public DlgItem(DlgTreeModel parent, DlgResource dlg) { numActions = getAttribute(DlgResource.DLG_NUM_ACTIONS); final StructEntry entry = dlg.getAttribute(DlgResource.DLG_THREAT_RESPONSE); - flags = entry instanceof Flag ? ((Flag) entry).toString() : null; + flags = entry instanceof Flag ? entry.toString() : null; final boolean alwaysShow = BrowserMenuBar.getInstance().getOptions().alwaysShowState0(); // finding and storing initial states @@ -167,11 +167,11 @@ private int getAttribute(String attrName) { public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getDialogName()); - sb.append(" (states: ").append(Integer.toString(numStates)); - sb.append(", responses: ").append(Integer.toString(numTransitions)); - sb.append(", state triggers: ").append(Integer.toString(numStateTriggers)); - sb.append(", response triggers: ").append(Integer.toString(numResponseTriggers)); - sb.append(", actions: ").append(Integer.toString(numActions)); + sb.append(" (states: ").append(numStates); + sb.append(", responses: ").append(numTransitions); + sb.append(", state triggers: ").append(numStateTriggers); + sb.append(", response triggers: ").append(numResponseTriggers); + sb.append(", actions: ").append(numActions); if (flags != null) { sb.append(", flags: ").append(flags); } diff --git a/src/org/infinity/resource/dlg/DlgResource.java b/src/org/infinity/resource/dlg/DlgResource.java index e569044fc..3436f582d 100644 --- a/src/org/infinity/resource/dlg/DlgResource.java +++ b/src/org/infinity/resource/dlg/DlgResource.java @@ -549,7 +549,7 @@ private void updateReferences(AddRemovable datatype, boolean added) { } /** Exports DLG resource as WeiDU D file. */ - private boolean exportDlgAsText(PrintWriter writer) { + public boolean exportDlgAsText(PrintWriter writer) { boolean retVal = false; if (writer != null) { @@ -566,12 +566,12 @@ private boolean exportDlgAsText(PrintWriter writer) { writer.println("// source : " + Profile.getGameRoot().relativize(getResourceEntry().getActualPath())); Path path = Profile.getGameRoot().relativize(Profile.getProperty(Profile.Key.GET_GAME_DIALOG_FILE)); - writer.println("// dialog : " + path.toString()); + writer.println("// dialog : " + path); path = Profile.getProperty(Profile.Key.GET_GAME_DIALOGF_FILE); if (path != null) { path = Profile.getGameRoot().relativize(path); - writer.println("// dialogF : " + path.toString()); + writer.println("// dialogF : " + path); } else { writer.println("// dialogF : (none)"); } diff --git a/src/org/infinity/resource/dlg/DlgTreeModel.java b/src/org/infinity/resource/dlg/DlgTreeModel.java index 22271de2d..f0eea99c1 100644 --- a/src/org/infinity/resource/dlg/DlgTreeModel.java +++ b/src/org/infinity/resource/dlg/DlgTreeModel.java @@ -29,7 +29,7 @@ import javax.swing.tree.TreePath; import org.infinity.datatype.ResourceRef; -import org.infinity.gui.menu.BrowserMenuBar; +import org.infinity.gui.menu.OptionsMenuItem; import org.infinity.resource.AbstractStruct; import org.infinity.resource.Resource; import org.infinity.resource.ResourceFactory; @@ -428,7 +428,7 @@ private void changeStateTransCount(State state, List items, Object old /** * Adds continuous range of tree items that represent transitions from specified state and notifies listeners. If - * state is not main state and option {@link BrowserMenuBar#breakCyclesInDialogs()} is enabled, do nothing. + * state is not main state and option {@link OptionsMenuItem#breakCyclesInDialogs()} is enabled, do nothing. * * @param parent Parent state under which tree items must be added * @param startTransition First transition index that state has diff --git a/src/org/infinity/resource/dlg/Viewer.java b/src/org/infinity/resource/dlg/Viewer.java index 355a24276..b64bc19fd 100644 --- a/src/org/infinity/resource/dlg/Viewer.java +++ b/src/org/infinity/resource/dlg/Viewer.java @@ -573,7 +573,6 @@ private void updateViewerLists() { } private void showExternState(DlgResource newdlg, int state, boolean isUndo) { - alive = false; Container window = getTopLevelAncestor(); if (window instanceof ViewFrame && window.isVisible()) { diff --git a/src/org/infinity/resource/effects/BaseOpcode.java b/src/org/infinity/resource/effects/BaseOpcode.java index 6d89e7e50..aa59e0dcf 100644 --- a/src/org/infinity/resource/effects/BaseOpcode.java +++ b/src/org/infinity/resource/effects/BaseOpcode.java @@ -86,7 +86,7 @@ public class BaseOpcode { /** * Used in conjunction with {@code getEffectStructure} to address specific fields within an effect structure. */ - public static enum EffectEntry { + public enum EffectEntry { // EFF all versions // table index abs. structure offset IDX_OPCODE, OFS_OPCODE, @@ -139,6 +139,7 @@ public static enum EffectEntry { public static final TreeMap DURATIONS_V1_MAP = new TreeMap<>(); public static final TreeMap DURATIONS_V2_MAP = new TreeMap<>(); + public static final TreeMap AC_TYPES_MAP = new TreeMap<>(); public static final TreeMap COLOR_LOCATIONS_MAP = new TreeMap<>(); public static final TreeMap PROJECTILES_IWD_MAP = new TreeMap<>(); public static final TreeMap INC_TYPES_MAP = new TreeMap<>(); @@ -304,11 +305,6 @@ public static enum EffectEntry { "", "Black", "Blue", "Chromatic", "Gold", "Green", "Purple", "Red", "White", "Ice", "Stone", "Magenta", "Orange" }; - public static final String[] AC_TYPES = { - "All weapons", "Crushing weapons", "Missile weapons", "Piercing weapons", "Slashing weapons", - "Set base AC to value" - }; - public static final String[] DAMAGE_TYPES = { "All", "Fire damage", "Cold damage", "Electricity damage", "Acid damage", "Magic damage", "Poison damage", "Slashing damage", "Piercing damage", "Crushing damage", "Missile damage" @@ -406,6 +402,13 @@ public static enum EffectEntry { DURATIONS_V2_MAP.put(10L, "Instant/Limited (ticks)"); DURATIONS_V2_MAP.put(4096L, "Absolute duration"); + AC_TYPES_MAP.put(0x0L, "All weapons"); + AC_TYPES_MAP.put(0x1L, "Crushing weapons"); + AC_TYPES_MAP.put(0x2L, "Missile weapons"); + AC_TYPES_MAP.put(0x4L, "Piercing weapons"); + AC_TYPES_MAP.put(0x8L, "Slashing weapons"); + AC_TYPES_MAP.put(0x10L, "Set base AC to value"); + COLOR_LOCATIONS_MAP.put(0x00L, "Armor (grey): Belt/Amulet"); COLOR_LOCATIONS_MAP.put(0x01L, "Armor (teal): Minor color"); COLOR_LOCATIONS_MAP.put(0x02L, "Armor (pink): Major color"); diff --git a/src/org/infinity/resource/effects/Opcode000.java b/src/org/infinity/resource/effects/Opcode000.java index f1c227f28..5e2793630 100644 --- a/src/org/infinity/resource/effects/Opcode000.java +++ b/src/org/infinity/resource/effects/Opcode000.java @@ -10,7 +10,7 @@ import org.infinity.datatype.Bitmap; import org.infinity.datatype.Datatype; import org.infinity.datatype.DecNumber; -import org.infinity.datatype.Flag; +import org.infinity.datatype.HashBitmap; import org.infinity.resource.StructEntry; /** @@ -36,7 +36,7 @@ public Opcode000() { protected String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int offset, List list, boolean isVersion1) { list.add(new DecNumber(buffer, offset, 4, EFFECT_AC_VALUE)); - list.add(new Flag(buffer, offset + 4, 4, EFFECT_BONUS_TO, AC_TYPES)); + list.add(new HashBitmap(buffer, offset + 4, 4, EFFECT_BONUS_TO, AC_TYPES_MAP, false)); return null; } diff --git a/src/org/infinity/resource/effects/Opcode015.java b/src/org/infinity/resource/effects/Opcode015.java index d98b86926..298146160 100644 --- a/src/org/infinity/resource/effects/Opcode015.java +++ b/src/org/infinity/resource/effects/Opcode015.java @@ -84,12 +84,11 @@ protected boolean update(AbstractStruct struct) throws Exception { if ((isVersion1 || isTobEx()) && param2 == 3) { replaceEntry(struct, EffectEntry.IDX_PARAM1, EffectEntry.OFS_PARAM1, new DecNumber(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, AbstractStruct.COMMON_UNUSED)); - retVal = true; } else { replaceEntry(struct, EffectEntry.IDX_PARAM1, EffectEntry.OFS_PARAM1, new DecNumber(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, EFFECT_VALUE)); - retVal = true; } + retVal = true; } } return retVal; diff --git a/src/org/infinity/resource/effects/Opcode020.java b/src/org/infinity/resource/effects/Opcode020.java index a99d5146e..5f806379a 100644 --- a/src/org/infinity/resource/effects/Opcode020.java +++ b/src/org/infinity/resource/effects/Opcode020.java @@ -56,10 +56,4 @@ protected String makeEffectParamsPST(Datatype parent, ByteBuffer buffer, int off boolean isVersion1) { return makeEffectParamsBG1(parent, buffer, offset, list, isVersion1); } - - @Override - protected String makeEffectParamsIWD2(Datatype parent, ByteBuffer buffer, int offset, List list, - boolean isVersion1) { - return super.makeEffectParamsGeneric(parent, buffer, offset, list, isVersion1); - } } diff --git a/src/org/infinity/resource/effects/Opcode023.java b/src/org/infinity/resource/effects/Opcode023.java index 70489da5e..e9ba3c67d 100644 --- a/src/org/infinity/resource/effects/Opcode023.java +++ b/src/org/infinity/resource/effects/Opcode023.java @@ -114,7 +114,7 @@ protected int makeEffectSpecial(Datatype parent, ByteBuffer buffer, int offset, if (Profile.isEnhancedEdition()) { final Bitmap bmp = new Bitmap(buffer, offset, 4, EFFECT_MODE, MODES_EE); list.add(bmp); - if (parent != null && parent instanceof UpdateListener) { + if (parent instanceof UpdateListener) { bmp.addUpdateListener((UpdateListener)parent); } return offset + 4; diff --git a/src/org/infinity/resource/effects/Opcode143.java b/src/org/infinity/resource/effects/Opcode143.java index 7b6e633aa..9d8c08a59 100644 --- a/src/org/infinity/resource/effects/Opcode143.java +++ b/src/org/infinity/resource/effects/Opcode143.java @@ -23,7 +23,7 @@ public class Opcode143 extends BaseOpcode { private static final String RES_TYPE = "ITM"; private static final String[] SLOT_TYPES_PST = { "Hand", "Eyeball/Earring (left)", "Tattoo", "Bracelet", - "Ring (right)", "Tattoo (top left)", "Ring (left)", "Earring (right)/Lens", "Armor", "Tattoo (bottomr right)", + "Ring (right)", "Tattoo (top left)", "Ring (left)", "Earring (right)/Lens", "Armor", "Tattoo (bottom right)", "Temporary weapon", "Ammo 1", "Ammo 2", "Ammo 3", "Ammo 4", "Ammo 5", "Ammo 6", "Quick item 1", "Quick item 2", "Quick item 3", "Quick item 4", "Quick item 5", "Inventory 1", "Inventory 2", "Inventory 3", "Inventory 4", "Inventory 5", "Inventory 6", "Inventory 7", "Inventory 8", "Inventory 9", "Inventory 10", "Inventory 11", diff --git a/src/org/infinity/resource/effects/Opcode242.java b/src/org/infinity/resource/effects/Opcode242.java index 7b8e8bec5..f506a3201 100644 --- a/src/org/infinity/resource/effects/Opcode242.java +++ b/src/org/infinity/resource/effects/Opcode242.java @@ -22,7 +22,7 @@ public class Opcode242 extends BaseOpcode { private static final String[] OVERLAY_TYPES_IWD = { "Globe of invulnerability", "Shroud of flame", "Antimagic shell", "Otiluke's resilient sphere", "Protection from normal missiles", "Cloak of fear", "Entropy shield", "Fire aura", - "Frost aura", "Insect plague", "Storm shell", "Shield of lathander", "Greater shield of lathander", + "Frost aura", "Insect plague", "Storm shell", "Shield of Lathander", "Greater shield of Lathander", "Seven eyes" }; /** Returns the opcode name for the current game variant. */ diff --git a/src/org/infinity/resource/effects/Opcode342.java b/src/org/infinity/resource/effects/Opcode342.java index ea110ff18..f0bd63f37 100644 --- a/src/org/infinity/resource/effects/Opcode342.java +++ b/src/org/infinity/resource/effects/Opcode342.java @@ -65,7 +65,7 @@ protected String makeEffectParamsEE(Datatype parent, ByteBuffer buffer, int offs protected boolean update(AbstractStruct struct) throws Exception { if (struct != null && Profile.isEnhancedEdition()) { int param2 = ((IsNumeric)getEntry(struct, EffectEntry.IDX_PARAM2)).getValue(); - StructEntry newEntry = null; + StructEntry newEntry; switch (param2) { case 1: newEntry = new Bitmap(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, EFFECT_ENABLED, diff --git a/src/org/infinity/resource/effects/Opcode375.java b/src/org/infinity/resource/effects/Opcode375.java index 11d265748..cc35c9f08 100644 --- a/src/org/infinity/resource/effects/Opcode375.java +++ b/src/org/infinity/resource/effects/Opcode375.java @@ -23,7 +23,7 @@ public class Opcode375 extends BaseOpcode { private static final String RES_TYPE = "BAM"; private static final String[] EFFECTS = { "Cloak of warding", "Shield", "Black-barbed shield", "Pain mirror", - "Guardian mantle", "", "Enoll eva's duplication", "Armor", "Antimagic shell", "", "", "Flame walk", + "Guardian mantle", "", "Enoll Eva's duplication", "Armor", "Antimagic shell", "", "", "Flame walk", "Protection from evil", "Conflagration", "Infernal shield", "Submerge the will", "Balance in all things" }; /** Returns the opcode name for the current game variant. */ diff --git a/src/org/infinity/resource/effects/Opcode413.java b/src/org/infinity/resource/effects/Opcode413.java index 2357a1cf8..4f2895936 100644 --- a/src/org/infinity/resource/effects/Opcode413.java +++ b/src/org/infinity/resource/effects/Opcode413.java @@ -23,7 +23,7 @@ public class Opcode413 extends BaseOpcode { private static final String[] ANIMATIONS_IWD2 = { "Sanctuary", "Entangle", "Wisp", "Shield", "Grease", "Web", "Minor globe of invulnerability", "Globe of invulnerability", "Shroud of flame", "Antimagic shell", "Otiluke's resilient sphere", "Protection from normal missiles", "Cloak of fear", "Entrophy shield", "Fire aura", - "Frost aura", "Insect plague", "Storm shell", "Shield of lathander", "", "Greater shield of lathander", "", + "Frost aura", "Insect plague", "Storm shell", "Shield of Lathander", "", "Greater shield of Lathander", "", "Seven eyes", "", "Blur", "Invisibility", "Fire shield (red)", "Fire shield (blue)", "", "", "Tortoise shell", "Death armor" }; diff --git a/src/org/infinity/resource/gam/ModronMazeEntry.java b/src/org/infinity/resource/gam/ModronMazeEntry.java index 4b19c2aac..1e5af8339 100644 --- a/src/org/infinity/resource/gam/ModronMazeEntry.java +++ b/src/org/infinity/resource/gam/ModronMazeEntry.java @@ -19,7 +19,7 @@ public final class ModronMazeEntry extends AbstractStruct { public static final String GAM_MAZE_ENTRY_IS_TRAPPED = "Is trapped"; public static final String GAM_MAZE_ENTRY_TRAP_TYPE = "Trap type"; public static final String GAM_MAZE_ENTRY_EXITS = "Exits"; - public static final String GAM_MAZE_ENTRY_POLULATED = "Populated"; + public static final String GAM_MAZE_ENTRY_POPULATED = "Populated"; private static final String[] TRAPS_ARRAY = { "TrapA", "TrapB", "TrapC" }; @@ -37,7 +37,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception { addField(new Bitmap(buffer, offset + 12, 4, GAM_MAZE_ENTRY_IS_TRAPPED, OPTION_NOYES)); addField(new Bitmap(buffer, offset + 16, 4, GAM_MAZE_ENTRY_TRAP_TYPE, TRAPS_ARRAY)); addField(new Flag(buffer, offset + 20, 2, GAM_MAZE_ENTRY_EXITS, WALLS_ARRAY)); - addField(new Bitmap(buffer, offset + 22, 4, GAM_MAZE_ENTRY_POLULATED, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 22, 4, GAM_MAZE_ENTRY_POPULATED, OPTION_NOYES)); return offset + 26; } } diff --git a/src/org/infinity/resource/gam/PartyNPC.java b/src/org/infinity/resource/gam/PartyNPC.java index 5c8661477..2a99f9557 100644 --- a/src/org/infinity/resource/gam/PartyNPC.java +++ b/src/org/infinity/resource/gam/PartyNPC.java @@ -420,7 +420,7 @@ private int readCharStats(ByteBuffer buffer, int offset) { } protected static ByteBuffer createEmptyBuffer() { - int size = 0; + int size; if (Profile.getEngine() == Profile.Engine.BG1 || Profile.getEngine() == Profile.Engine.BG2 || Profile.isEnhancedEdition()) { size = 352; diff --git a/src/org/infinity/resource/graphics/BamDecoder.java b/src/org/infinity/resource/graphics/BamDecoder.java index 505324c06..db7107b52 100644 --- a/src/org/infinity/resource/graphics/BamDecoder.java +++ b/src/org/infinity/resource/graphics/BamDecoder.java @@ -214,19 +214,19 @@ public boolean equals(Object obj) { * Interface for basic BAM frame properties */ public interface FrameEntry { - public int getWidth(); + int getWidth(); - public int getHeight(); + int getHeight(); - public int getCenterX(); + int getCenterX(); - public int getCenterY(); + int getCenterY(); - public void setCenterX(int x); + void setCenterX(int x); - public void setCenterY(int y); + void setCenterY(int y); - public void resetCenter(); + void resetCenter(); } /** diff --git a/src/org/infinity/resource/graphics/BamResource.java b/src/org/infinity/resource/graphics/BamResource.java index 7120f0090..9724c2e30 100644 --- a/src/org/infinity/resource/graphics/BamResource.java +++ b/src/org/infinity/resource/graphics/BamResource.java @@ -242,11 +242,11 @@ public void actionPerformed(ActionEvent event) { } else if (buttonPanel.getControlByType(ButtonPanel.Control.FIND_REFERENCES) == event.getSource()) { searchReferences(panelMain.getTopLevelAncestor()); } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE) == event.getSource()) { - if (ResourceFactory.saveResource(this, panelMain.getTopLevelAncestor())) { + if (ResourceFactory.saveResource(this, panelMain.getTopLevelAncestor()).isTrue()) { setRawModified(false); } } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE_AS) == event.getSource()) { - if (ResourceFactory.saveResourceAs(this, panelMain.getTopLevelAncestor())) { + if (ResourceFactory.saveResourceAs(this, panelMain.getTopLevelAncestor()).isTrue()) { setRawModified(false); } } else if (event.getSource() == timer) { @@ -318,7 +318,7 @@ public void actionPerformed(ActionEvent event) { if (fc.showSaveDialog(panelMain.getTopLevelAncestor()) == JFileChooser.APPROVE_OPTION) { Path filePath = fc.getSelectedFile().toPath().getParent(); String fileName = fc.getSelectedFile().getName(); - String fileExt = null; + String fileExt; String format = ((FileNameExtensionFilter) fc.getFileFilter()).getExtensions()[0].toLowerCase(Locale.ENGLISH); int extIdx = fileName.lastIndexOf('.'); if (extIdx > 0) { @@ -694,7 +694,7 @@ public void updateCanvas() { bamControl.cycleGetFrame(image); } else { if (decoder instanceof BamV1Decoder && bamControl instanceof BamV1Decoder.BamV1Control) { - ((BamV1Decoder) decoder).frameGet(bamControl, curFrame, image); + decoder.frameGet(bamControl, curFrame, image); } else { decoder.frameGet(null, curFrame, image); } @@ -746,7 +746,7 @@ private void showProperties() { sb.append("# cycles: ").append(cycleCount).append(br); if (decoder.getType() == BamDecoder.Type.BAMV2) { sb.append(br).append("Referenced PVRZ pages:").append(br); - sb.append(pageList.toString()).append(br); + sb.append(pageList).append(br); } sb.append(""); JOptionPane.showMessageDialog(panelMain, sb.toString(), "Properties of " + resName, @@ -861,7 +861,7 @@ public static String exportFrames(BamDecoder decoder, Path filePath, String file } // displaying results - String msg = null; + String msg; if (failCounter == 0 && counter == max) { msg = String.format("All %d frames exported successfully.", max); } else { @@ -896,7 +896,7 @@ private static BufferedImage prepareFrameImage(BamDecoder decoder, int frameIdx) /** Exports frames as graphics, specified by "format". */ private void exportFrames(Path filePath, String fileBase, String fileExt, String format) { - String msg = null; + String msg; WindowBlocker blocker = new WindowBlocker(NearInfinity.getInstance()); try { blocker.setBlocked(true); diff --git a/src/org/infinity/resource/graphics/BamV1Decoder.java b/src/org/infinity/resource/graphics/BamV1Decoder.java index 387eeb165..ce3a8633b 100644 --- a/src/org/infinity/resource/graphics/BamV1Decoder.java +++ b/src/org/infinity/resource/graphics/BamV1Decoder.java @@ -429,7 +429,7 @@ public boolean isCompressed() { @Override public String toString() { return "[width=" + getWidth() + ", height=" + getHeight() + ", centerX=" + getCenterX() + ", centerY=" - + getCenterY() + ", compressed=" + Boolean.toString(isCompressed()) + "]"; + + getCenterY() + ", compressed=" + isCompressed() + "]"; } @Override @@ -491,12 +491,31 @@ public void setTransparencyEnabled(boolean enable) { /** Returns the transparency index of the current palette. */ public int getTransparencyIndex() { - int idx = currentPalette.length - 1; - for (; idx > 0; idx--) { - if ((currentPalette[idx] & 0xff000000) == 0) { - break; + return getTransparencyIndex(false); + } + + /** + * Returns the transparency index of the current palette. {@code firstAvailable} defines the search direction in the + * palette. + */ + public int getTransparencyIndex(boolean firstAvailable) { + int idx; + if (firstAvailable) { + idx = 0; + for (int length = currentPalette.length; idx < length; idx++) { + if ((currentPalette[idx] & 0xff000000) == 0) { + break; + } + } + } else { + idx = currentPalette.length - 1; + for (; idx > 0; idx--) { + if ((currentPalette[idx] & 0xff000000) == 0) { + break; + } } } + return idx; } @@ -529,6 +548,18 @@ public int[] getExternalPalette() { * @param palette An external palette. Specify {@code null} to use the default palette. */ public void setExternalPalette(int[] palette) { + setExternalPalette(palette, -1); + } + + /** + * Applies the colors of the specified palette to the active BAM palette. Note: Must be called whenever any + * changes to the external palette have been done. + * + * @param palette An external palette. Specify {@code null} to use the default palette. + * @param transparencyIndex Index of the palette entry that should be considered transparent. Specify {@code -1} to + * autodetect palette entry. + */ + public void setExternalPalette(int[] palette, int transparencyIndex) { if (palette != null) { externalPalette = new int[palette.length]; for (int i = 0; i < palette.length; i++) { @@ -536,6 +567,9 @@ public void setExternalPalette(int[] palette) { if ((externalPalette[i] & 0xff000000) == 0) { externalPalette[i] |= 0xff000000; } + if (i == transparencyIndex) { + externalPalette[i] = 0x00ff00; + } } } preparePalette(externalPalette); diff --git a/src/org/infinity/resource/graphics/BmpDecoder.java b/src/org/infinity/resource/graphics/BmpDecoder.java index 44758ca02..7133def0c 100644 --- a/src/org/infinity/resource/graphics/BmpDecoder.java +++ b/src/org/infinity/resource/graphics/BmpDecoder.java @@ -239,7 +239,7 @@ public enum Compression { private final int code; private final String label; - private Compression(int code, String label) { + Compression(int code, String label) { this.code = code; this.label = label; } diff --git a/src/org/infinity/resource/graphics/ColorConvert.java b/src/org/infinity/resource/graphics/ColorConvert.java index a62432b82..4b2492a4e 100644 --- a/src/org/infinity/resource/graphics/ColorConvert.java +++ b/src/org/infinity/resource/graphics/ColorConvert.java @@ -1248,10 +1248,10 @@ public int getElement(int index) { public static final List> PIXEL_COMPARATOR = new ArrayList>(MAX_SIZE) { { - add((p1, p2) -> p1.getElement(0) - p2.getElement(0)); - add((p1, p2) -> p1.getElement(1) - p2.getElement(1)); - add((p1, p2) -> p1.getElement(2) - p2.getElement(2)); - add((p1, p2) -> p1.getElement(3) - p2.getElement(3)); + add(Comparator.comparingInt(p -> p.getElement(0))); + add(Comparator.comparingInt(p -> p.getElement(1))); + add(Comparator.comparingInt(p -> p.getElement(2))); + add(Comparator.comparingInt(p -> p.getElement(3))); } }; } diff --git a/src/org/infinity/resource/graphics/Compressor.java b/src/org/infinity/resource/graphics/Compressor.java index 0a54989c7..1e7dc40e8 100644 --- a/src/org/infinity/resource/graphics/Compressor.java +++ b/src/org/infinity/resource/graphics/Compressor.java @@ -121,7 +121,7 @@ public static int compress(InputStream is, OutputStream os, int len, boolean pre byte[] buffer = new byte[Math.min(65536, len)]; final DeflaterInputStream dis = new DeflaterInputStream(is); - int bufLen = 0; + int bufLen; while ((bufLen = dis.read(buffer)) > 0) { os.write(buffer, 0, bufLen); result += bufLen; diff --git a/src/org/infinity/resource/graphics/DxtEncoder.java b/src/org/infinity/resource/graphics/DxtEncoder.java index 80fc3da97..04add6860 100644 --- a/src/org/infinity/resource/graphics/DxtEncoder.java +++ b/src/org/infinity/resource/graphics/DxtEncoder.java @@ -44,7 +44,7 @@ public final class DxtEncoder { /** * Supported DXT compression types */ - public static enum DxtType { + public enum DxtType { DXT1, DXT3, DXT5 } @@ -209,7 +209,7 @@ public static void encodeBlock(final int[] pixels, final byte[] block, final Dxt final ColorSet colors = new ColorSet(pixels, dxtType); // check the compression type and compress color - ColorFit fit = null; + ColorFit fit; if (colors.getCount() == 1) { // always do a single color fit fit = new SingleColorFit(colors, dxtType); diff --git a/src/org/infinity/resource/graphics/GifSequenceReader.java b/src/org/infinity/resource/graphics/GifSequenceReader.java index 6ee7d12c4..bc687fc0d 100644 --- a/src/org/infinity/resource/graphics/GifSequenceReader.java +++ b/src/org/infinity/resource/graphics/GifSequenceReader.java @@ -132,7 +132,7 @@ private Frame initFrame(int index, IIOImage ioimg) throws Exception { } // Converting RenderedImage into BufferedImage object - BufferedImage gifImg = null; + BufferedImage gifImg; if (rimg instanceof BufferedImage) { gifImg = (BufferedImage) rimg; } else { @@ -150,8 +150,8 @@ private Frame initFrame(int index, IIOImage ioimg) throws Exception { gifImg.copyData(raster); } - BufferedImage bimg = null; - Graphics2D g = null; + BufferedImage bimg; + Graphics2D g; if (framePrev != null) { bimg = new BufferedImage(framePrev.getRenderedImage().getWidth(), framePrev.getRenderedImage().getHeight(), BufferedImage.TYPE_INT_ARGB); diff --git a/src/org/infinity/resource/graphics/GraphicsResource.java b/src/org/infinity/resource/graphics/GraphicsResource.java index ccd3b9f54..4656cf79f 100644 --- a/src/org/infinity/resource/graphics/GraphicsResource.java +++ b/src/org/infinity/resource/graphics/GraphicsResource.java @@ -125,12 +125,12 @@ private void showProperties() { final Function space = (i) -> new String(new char[i]).replace("\0", " "); final String br = "
"; final String resName = entry.getResourceName().toUpperCase(Locale.ENGLISH); - final StringBuilder sb = new StringBuilder("
"); - sb.append("Width:").append(space.apply(7)).append(getInfo().getWidth()).append(br); - sb.append("Height:").append(space.apply(6)).append(getInfo().getHeight()).append(br); - sb.append("Bits/Pixel:").append(space.apply(2)).append(getInfo().getBitsPerPixel()).append(br); - sb.append("
"); + String sb = "
" + + "Width:" + space.apply(7) + getInfo().getWidth() + br + + "Height:" + space.apply(6) + getInfo().getHeight() + br + + "Bits/Pixel:" + space.apply(2) + getInfo().getBitsPerPixel() + br + + "
"; - JOptionPane.showMessageDialog(panel, sb.toString(), "Properties of " + resName, JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(panel, sb, "Properties of " + resName, JOptionPane.INFORMATION_MESSAGE); } } diff --git a/src/org/infinity/resource/graphics/MosResource.java b/src/org/infinity/resource/graphics/MosResource.java index 4e9d3e2bc..709979bce 100644 --- a/src/org/infinity/resource/graphics/MosResource.java +++ b/src/org/infinity/resource/graphics/MosResource.java @@ -166,7 +166,7 @@ public void actionPerformed(ActionEvent event) { } else if (event.getSource() == miExportPNG) { try (final ByteArrayOutputStream os = new ByteArrayOutputStream()) { final String fileName = StreamUtils.replaceFileExtension(entry.getResourceName(), "PNG"); - boolean bRet = false; + boolean bRet; WindowBlocker.blockWindow(true); try { BufferedImage image = getImage(); @@ -436,7 +436,7 @@ private void showProperties() { sb.append("Height: ").append(height).append(br); if (decoder.getType() == MosDecoder.Type.MOSV2) { sb.append(br).append("Referenced PVRZ pages:").append(br); - sb.append(pageList.toString()).append(br); + sb.append(pageList).append(br); } sb.append(""); JOptionPane.showMessageDialog(panel, sb.toString(), "Properties of " + resName, JOptionPane.INFORMATION_MESSAGE); diff --git a/src/org/infinity/resource/graphics/PltResource.java b/src/org/infinity/resource/graphics/PltResource.java index 833876f88..d76d1a7e0 100644 --- a/src/org/infinity/resource/graphics/PltResource.java +++ b/src/org/infinity/resource/graphics/PltResource.java @@ -273,7 +273,7 @@ public void actionPerformed(ActionEvent e) { } else if (e.getSource() == miExportPNG) { try (final ByteArrayOutputStream os = new ByteArrayOutputStream()) { final String fileName = StreamUtils.replaceFileExtension(entry.getResourceName(), "PNG"); - boolean bRet = false; + boolean bRet; WindowBlocker.blockWindow(true); try { bRet = ImageIO.write(image, "png", os); @@ -292,11 +292,11 @@ public void actionPerformed(ActionEvent e) { JOptionPane.ERROR_MESSAGE); } } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE) == e.getSource()) { - if (ResourceFactory.saveResource(this, panelMain.getTopLevelAncestor())) { + if (ResourceFactory.saveResource(this, panelMain.getTopLevelAncestor()).isTrue()) { setRawModified(false); } } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE_AS) == e.getSource()) { - if (ResourceFactory.saveResourceAs(this, panelMain.getTopLevelAncestor())) { + if (ResourceFactory.saveResourceAs(this, panelMain.getTopLevelAncestor()).isTrue()) { setRawModified(false); } } @@ -417,7 +417,7 @@ private BufferedImage updateImage() { int v = buffer.getShort(); int index = v & 0xff; int type = (v >> 8) & 0x7f; // ignore bit 8 - int color = 0; + int color; if (type < cbColors.length) { // color color = getSelectedColorItem(type).getColorAt(index); diff --git a/src/org/infinity/resource/graphics/PseudoBamDecoder.java b/src/org/infinity/resource/graphics/PseudoBamDecoder.java index b2badc75b..2affee0cc 100644 --- a/src/org/infinity/resource/graphics/PseudoBamDecoder.java +++ b/src/org/infinity/resource/graphics/PseudoBamDecoder.java @@ -17,6 +17,7 @@ import java.awt.image.IndexColorModel; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; @@ -27,6 +28,7 @@ import javax.swing.ProgressMonitor; +import org.infinity.exceptions.AbortException; import org.infinity.gui.converter.ConvertToPvrz; import org.infinity.resource.Profile; import org.infinity.util.BinPack2D; @@ -78,11 +80,11 @@ public class PseudoBamDecoder extends BamDecoder { private PseudoBamControl defaultControl; public PseudoBamDecoder() { - this(null, (BufferedImage[]) null, (Point[]) null); + this(null, null, (Point[]) null); } public PseudoBamDecoder(List framesList) { - this(framesList, (BufferedImage[]) null, (Point[]) null); + this(framesList, null, (Point[]) null); } public PseudoBamDecoder(BufferedImage image) { @@ -121,7 +123,7 @@ public PseudoBamDecoder(List framesList, BufferedImage[] im /** Returns all available options by name. */ public String[] getOptionNames() { - String[] retVal = new String[mapOptions.keySet().size()]; + String[] retVal = new String[mapOptions.size()]; Iterator iter = mapOptions.keySet().iterator(); int idx = 0; while (iter.hasNext()) { @@ -885,8 +887,8 @@ public boolean exportBamV1(Path fileName, ProgressMonitor progress, int curProgr * @return {@code true} if the export was successful, {@code false} otherwise. * @throws Exception If an unrecoverable error occured. */ - public boolean exportBamV2(Path fileName, DxtEncoder.DxtType dxtType, int pvrzIndex, ProgressMonitor progress, - int curProgress) throws Exception { + public boolean exportBamV2(Path fileName, DxtEncoder.DxtType dxtType, int pvrzIndex, boolean overwrite, + ProgressMonitor progress, int curProgress) throws Exception { final int FrameEntrySize = 12; final int CycleEntrySize = 4; final int BlockEntrySize = 28; @@ -924,6 +926,28 @@ public boolean exportBamV2(Path fileName, DxtEncoder.DxtType dxtType, int pvrzIn return false; } + if (!overwrite) { + // adjusting pvrz indices + final HashMap indexMap = new HashMap<>(Math.max(4, listGrid.size() + listGrid.size() / 2)); + for (final FrameDataV2 frame : listFrameData) { + final int newIndex = indexMap.computeIfAbsent(frame.page, index -> { + for (int i = index; i < 100_000; i++) { + if (!indexMap.containsValue(i)) { + final Path pvrzPath = pvrzFilePath.resolve(getPvrzFileName(i)); + if (!Files.exists(pvrzPath)) { + return i; + } + } + } + return -1; + }); + if (newIndex < 0) { + throw new Exception("Effective PVRZ index is out of range [0..99999]."); + } + frame.page = newIndex; + } + } + // generating remaining info blocks List listFrameDataBlocks = new ArrayList<>(); List listFrameEntries = new ArrayList<>(); @@ -1216,6 +1240,17 @@ public static void unregisterColors(HashMap colorMap, Buffered } } + /** + * Returns a PVRZ filename (without path) for the specified index. Throws an {@link IndexOutOfBoundsException} + * if the index is out of bounds. + */ + public static String getPvrzFileName(int index) throws IndexOutOfBoundsException { + if (index < 0 || index > 99999) { + throw new IndexOutOfBoundsException("Pvrz index is out of bounds: " + index); + } + return String.format("MOS%04d.PVRZ", index); + } + // Calculates the locations of all frames on PVRZ textures and stores the results in framesList and gridList. private boolean buildFrameDataList(List framesList, List gridList, int pvrzPageIndex) throws Exception { @@ -1271,50 +1306,51 @@ private boolean createPvrzPages(Path path, DxtEncoder.DxtType dxtType, List pageSet = new HashSet<>(); for (FrameDataV2 entry : framesList) { - pageMin = Math.min(pageMin, entry.page); - pageMax = Math.max(pageMax, entry.page); + pageSet.add(entry.page); } + final List pageList = new ArrayList<>(pageSet); + pageList.sort(null); String note = "Generating PVRZ file %s / %s"; if (progress != null) { if (curProgress < 0) { curProgress = 0; } - progress.setMaximum(curProgress + pageMax - pageMin + 1); + progress.setMaximum(curProgress + pageList.size()); progress.setProgress(curProgress++); } // processing each PVRZ page - for (int i = pageMin; i <= pageMax; i++) { + for (int i = 0; i < pageList.size(); i++) { if (progress != null) { if (progress.isCanceled()) { - throw new Exception("Conversion has been cancelled by the user."); + throw new AbortException("Conversion has been cancelled by the user."); } progress.setProgress(curProgress); - progress.setNote(String.format(note, curProgress, pageMax - pageMin + 1)); + progress.setNote(String.format(note, curProgress, pageList.size())); curProgress++; } - Path pvrzName = path.resolve(String.format("MOS%04d.PVRZ", i)); - BinPack2D packer = gridList.get(i - pageMin); + final int pageIndex = pageList.get(i); + final Path pvrzName = path.resolve(getPvrzFileName(pageIndex)); + final BinPack2D packer = gridList.get(i); packer.shrinkBin(true); // generating texture image int tw = packer.getBinWidth(); int th = packer.getBinHeight(); - BufferedImage texture = ColorConvert.createCompatibleImage(tw, th, true); + final BufferedImage texture = ColorConvert.createCompatibleImage(tw, th, true); Graphics2D g = texture.createGraphics(); try { g.setComposite(AlphaComposite.Src); g.setColor(ColorConvert.TRANSPARENT_COLOR); g.fillRect(0, 0, texture.getWidth(), texture.getHeight()); for (int frameIdx = 0; frameIdx < listFrames.size(); frameIdx++) { - BufferedImage image = listFrames.get(frameIdx).frame; - FrameDataV2 frame = framesList.get(frameIdx); - if (frame.page == i) { + final BufferedImage image = listFrames.get(frameIdx).frame; + final FrameDataV2 frame = framesList.get(frameIdx); + if (frame.page == pageIndex) { int sx = frame.dx, sy = frame.dy; int dx = frame.sx, dy = frame.sy; int w = frame.width, h = frame.height; @@ -1504,7 +1540,7 @@ public boolean equals(Object obj) { /** Returns all available options by name. */ public String[] getOptionNames() { - String[] retVal = new String[mapOptions.keySet().size()]; + String[] retVal = new String[mapOptions.size()]; Iterator iter = mapOptions.keySet().iterator(); int idx = 0; while (iter.hasNext()) { @@ -2026,7 +2062,7 @@ protected PseudoBamCycleEntry(int[] indices) { /** Returns all available options by name. */ public String[] getOptionNames() { - String[] retVal = new String[mapOptions.keySet().size()]; + String[] retVal = new String[mapOptions.size()]; Iterator iter = mapOptions.keySet().iterator(); int idx = 0; while (iter.hasNext()) { @@ -2111,7 +2147,7 @@ public boolean remove(int pos, int count) { public String toString() { StringBuilder sb = new StringBuilder("["); for (int i = 0; i < frames.size(); i++) { - sb.append(Integer.toString(frames.get(i))); + sb.append(frames.get(i)); if (i < frames.size() - 1) { sb.append(", "); } @@ -2181,5 +2217,11 @@ public boolean equals(Object obj) { return dx == other.dx && dy == other.dy && height == other.height && page == other.page && sx == other.sx && sy == other.sy && width == other.width; } + + @Override + public String toString() { + return "FrameDataV2 [page=" + page + ", sx=" + sx + ", sy=" + sy + ", width=" + width + ", height=" + height + + ", dx=" + dx + ", dy=" + dy + "]"; + } } } diff --git a/src/org/infinity/resource/graphics/PvrDecoder.java b/src/org/infinity/resource/graphics/PvrDecoder.java index 820e99fc0..1a9261273 100644 --- a/src/org/infinity/resource/graphics/PvrDecoder.java +++ b/src/org/infinity/resource/graphics/PvrDecoder.java @@ -46,7 +46,7 @@ public static PvrDecoder loadPvr(ResourceEntry entry) { } final String key; if (entry instanceof FileResourceEntry) { - key = ((FileResourceEntry) entry).getActualPath().toString(); + key = entry.getActualPath().toString(); } else { key = entry.getResourceName(); } diff --git a/src/org/infinity/resource/graphics/PvrzResource.java b/src/org/infinity/resource/graphics/PvrzResource.java index f16cad5a1..d3c42f521 100644 --- a/src/org/infinity/resource/graphics/PvrzResource.java +++ b/src/org/infinity/resource/graphics/PvrzResource.java @@ -203,7 +203,7 @@ public BufferedImage getImage() { } private void showProperties() { - PvrDecoder decoder = null; + PvrDecoder decoder; try { decoder = PvrDecoder.loadPvr(entry); String resName = entry.getResourceName().toUpperCase(Locale.ENGLISH); @@ -213,12 +213,12 @@ private void showProperties() { String type; type = decoder.getInfo().getPixelFormat().toString(); - StringBuilder sb = new StringBuilder("
"); - sb.append("Type:   ").append(type).append(br); - sb.append("Width:  ").append(width).append(br); - sb.append("Height: ").append(height).append(br); - sb.append(""); - JOptionPane.showMessageDialog(panel, sb.toString(), "Properties of " + resName, JOptionPane.INFORMATION_MESSAGE); + String sb = "
" + + "Type:   " + type + br + + "Width:  " + width + br + + "Height: " + height + br + + "
"; + JOptionPane.showMessageDialog(panel, sb, "Properties of " + resName, JOptionPane.INFORMATION_MESSAGE); } catch (Exception e) { Logger.error(e); } finally { diff --git a/src/org/infinity/resource/graphics/TisConvert.java b/src/org/infinity/resource/graphics/TisConvert.java index 78b2b24fc..1e9208013 100644 --- a/src/org/infinity/resource/graphics/TisConvert.java +++ b/src/org/infinity/resource/graphics/TisConvert.java @@ -156,7 +156,7 @@ public enum OverlayConversion { private final OverlayMapUpdater updater; private final OverlayTileConverter converter; - private OverlayConversion(String label, OverlayMapUpdater updater, OverlayTileConverter converter, + OverlayConversion(String label, OverlayMapUpdater updater, OverlayTileConverter converter, boolean implemented) { this.label = label; this.implemented = implemented; @@ -220,7 +220,7 @@ public static Status exportPNG(List tiles, int tileCols, Path pngFile, bo } if (tileCols > 0 && tileRows > 0) { - BufferedImage image = null; + BufferedImage image; ProgressMonitor progress = null; if (showProgress) { progress = new ProgressMonitor(parent, "Exporting TIS to PNG...", "", 0, 2); diff --git a/src/org/infinity/resource/graphics/TisResource.java b/src/org/infinity/resource/graphics/TisResource.java index c81a97ca7..2c55325f8 100644 --- a/src/org/infinity/resource/graphics/TisResource.java +++ b/src/org/infinity/resource/graphics/TisResource.java @@ -968,7 +968,7 @@ public BufferedImage get(int width, int height) { * @return A new {@link BufferedImage} object. */ private BufferedImage createImage(int width, int height) { - BufferedImage retVal = null; + BufferedImage retVal; width = Math.max(1, width); height = Math.max(1, height); @@ -984,7 +984,7 @@ private BufferedImage createImage(int width, int height) { imageCache.addLast(entry); } else { retVal = ColorConvert.createCompatibleImage(imageWidth, imageHeight, true); - entry = new Couple(dim, retVal); + entry = new Couple<>(dim, retVal); imageCache.addLast(entry); // trimming cache if (imageCache.size() > CACHE_SIZE_MAX) { @@ -1530,9 +1530,9 @@ private void initPvrz() { final Vector> previewImageZoomValues = new Vector<>(); for (final int zoom : new int[] {1, 2, 3, 4, 6, 8, 12, 16}) { - previewImageZoomValues.add(new DataString(String.format("%dx", zoom), zoom)); + previewImageZoomValues.add(new DataString<>(String.format("%dx", zoom), zoom)); } - cbPreviewImageZoom = new JComboBox>(previewImageZoomValues); + cbPreviewImageZoom = new JComboBox<>(previewImageZoomValues); cbPreviewImageZoom.setSelectedIndex(getPreviewZoomIndex(tisPreview.getTileSize(), 2)); cbPreviewImageZoom.addMouseMotionListener(listeners.mouseMotion); cbPreviewImageZoom.addItemListener(listeners.itemPreviewImageZoom); @@ -1732,7 +1732,7 @@ private JComboBox> initOverlayModeCombo final Vector> overlayModes = new Vector<>(); int overlayModeIndex = 0; for (final TisConvert.OverlayConversion mode : TisConvert.OverlayConversion.values()) { - boolean add = false; + boolean add; boolean select = false; switch (mode) { case BG1_TO_BGEE: @@ -1778,7 +1778,7 @@ private JComboBox> initOverlayModeCombo if (mode.isImplemented() && add) { final String modeText = mode.toString(); final DataString entry = - new DataString(modeText, mode, DataString.FMT_STRING_ONLY); + new DataString<>(modeText, mode, DataString.FMT_STRING_ONLY); overlayModes.add(entry); if (select) { overlayModeIndex = overlayModes.size() - 1; @@ -2108,14 +2108,12 @@ public void mouseMoved(MouseEvent e) { }; /** ChangeListener: JSlider sSegmentSize */ - private final ChangeListener changeSegmentSize = e -> { - lSegmentSize.setText(String.format(SEGMENT_SIZE_LABEL_FMT, getSegmentSize())); - }; + private final ChangeListener changeSegmentSize = + e -> lSegmentSize.setText(String.format(SEGMENT_SIZE_LABEL_FMT, getSegmentSize())); /** ChangeListener: JSlider: sPvrzBaseIndex */ - private final ChangeListener changePvrzBaseIndex = e -> { - lPvrzBaseIndex.setText(String.format(PVRZ_BASE_INDEX_LABEL_FMT, getPvrzBaseIndex())); - }; + private final ChangeListener changePvrzBaseIndex = + e -> lPvrzBaseIndex.setText(String.format(PVRZ_BASE_INDEX_LABEL_FMT, getPvrzBaseIndex())); /** ChangeListener: JSlider sTilesPerRow */ private final ChangeListener changeTilesPerRow = e -> { diff --git a/src/org/infinity/resource/graphics/decoder/DxtDecoder.java b/src/org/infinity/resource/graphics/decoder/DxtDecoder.java index acdaa6f17..715027852 100644 --- a/src/org/infinity/resource/graphics/decoder/DxtDecoder.java +++ b/src/org/infinity/resource/graphics/decoder/DxtDecoder.java @@ -45,15 +45,15 @@ private boolean decodeDXT(BufferedImage image, Rectangle region) throws Exceptio int imgWidth = image.getWidth(); int imgHeight = image.getHeight(); - int[] imgData = null; + int[] imgData; // checking region bounds and alignment if (region.x < 0) { - region.width += -region.x; + region.width -= region.x; region.x = 0; } if (region.y < 0) { - region.height += -region.y; + region.height -= region.y; region.y = 0; } if (region.x + region.width > info.width) diff --git a/src/org/infinity/resource/graphics/decoder/Etc2Decoder.java b/src/org/infinity/resource/graphics/decoder/Etc2Decoder.java index f9a38f582..1db01d3bc 100644 --- a/src/org/infinity/resource/graphics/decoder/Etc2Decoder.java +++ b/src/org/infinity/resource/graphics/decoder/Etc2Decoder.java @@ -148,15 +148,15 @@ private boolean decodeETC(BufferedImage image, Rectangle region) throws Exceptio final int imgWidth = image.getWidth(); final int imgHeight = image.getHeight(); - int[] imgData = null; + int[] imgData; // checking region bounds and alignment if (region.x < 0) { - region.width += -region.x; + region.width -= region.x; region.x = 0; } if (region.y < 0) { - region.height += -region.y; + region.height -= region.y; region.y = 0; } if (region.x + region.width > info.width) @@ -241,7 +241,7 @@ private void decodeData(int[] imgData, Rectangle rect, int imgWidth, boolean has // X and Y coordinates are transposed later when copying pixels to target image. int[] pixels = new int[4 * 4 * 4]; - long colorWord = 0, alphaWord = 0; + long colorWord, alphaWord = 0; int pvrOfs = (wordPosY * wordImageWidth + wordPosX) * wordSize; final LongBuffer longBuf = ByteBuffer.wrap(info.data).order(ByteOrder.BIG_ENDIAN).asLongBuffer(); for (int y = 0; y < wordRectHeight; y++) { diff --git a/src/org/infinity/resource/graphics/decoder/PvrtcDecoder.java b/src/org/infinity/resource/graphics/decoder/PvrtcDecoder.java index 51aa559d6..877d6e8e0 100644 --- a/src/org/infinity/resource/graphics/decoder/PvrtcDecoder.java +++ b/src/org/infinity/resource/graphics/decoder/PvrtcDecoder.java @@ -23,7 +23,7 @@ public class PvrtcDecoder implements Decodable { // The local cache list for decoded PVR textures. The "key" has to be a unique PvrInfo structure. private static final Map TEXTURE_CACHE = Collections - .synchronizedMap(new LinkedHashMap()); + .synchronizedMap(new LinkedHashMap<>()); // The max. number of cache entries to hold private static final int MAX_CACHE_ENTRIES = 8; @@ -86,15 +86,15 @@ private boolean decodePVRT(BufferedImage image, Rectangle region, boolean is2bpp int imgWidth = image.getWidth(); int imgHeight = image.getHeight(); - int[] imgData = null; + int[] imgData; // bounds checking if (region.x < 0) { - region.width += -region.x; + region.width -= region.x; region.x = 0; } if (region.y < 0) { - region.height += -region.y; + region.height -= region.y; region.y = 0; } if (region.x + region.width > info.width) diff --git a/src/org/infinity/resource/itm/Viewer.java b/src/org/infinity/resource/itm/Viewer.java index a38d31c96..ce8658379 100644 --- a/src/org/infinity/resource/itm/Viewer.java +++ b/src/org/infinity/resource/itm/Viewer.java @@ -70,28 +70,28 @@ final class Viewer extends JPanel { kitUsabilityPanel.setLayout(new BoxLayout(kitUsabilityPanel, BoxLayout.Y_AXIS)); int kitPanelCount = 0; - JPanel kitUsabilityPanel1 = null; + JPanel kitUsabilityPanel1; StructEntry unusableEntry = itm.getAttribute(ItmResource.ITM_UNUSABLE_BY_1); if (unusableEntry != null) { kitUsabilityPanel1 = ViewerUtil.makeCheckPanel((Flag) unusableEntry, 1); kitUsabilityPanel.add(kitUsabilityPanel1); kitPanelCount++; } - JPanel kitUsabilityPanel2 = null; + JPanel kitUsabilityPanel2; unusableEntry = itm.getAttribute(ItmResource.ITM_UNUSABLE_BY_2); if (unusableEntry != null) { kitUsabilityPanel2 = ViewerUtil.makeCheckPanel((Flag) unusableEntry, 1); kitUsabilityPanel.add(kitUsabilityPanel2); kitPanelCount++; } - JPanel kitUsabilityPanel3 = null; + JPanel kitUsabilityPanel3; unusableEntry = itm.getAttribute(ItmResource.ITM_UNUSABLE_BY_3); if (unusableEntry != null) { kitUsabilityPanel3 = ViewerUtil.makeCheckPanel((Flag) unusableEntry, 1); kitUsabilityPanel.add(kitUsabilityPanel3); kitPanelCount++; } - JPanel kitUsabilityPanel4 = null; + JPanel kitUsabilityPanel4; unusableEntry = itm.getAttribute(ItmResource.ITM_UNUSABLE_BY_4); if (unusableEntry != null) { kitUsabilityPanel4 = ViewerUtil.makeCheckPanel((Flag) unusableEntry, 1); diff --git a/src/org/infinity/resource/key/AbstractBIFFReader.java b/src/org/infinity/resource/key/AbstractBIFFReader.java index 35e190eb8..b2f256968 100644 --- a/src/org/infinity/resource/key/AbstractBIFFReader.java +++ b/src/org/infinity/resource/key/AbstractBIFFReader.java @@ -109,7 +109,7 @@ public Path getFile() { public int[] getResourceInfo(int locator) throws IOException { Entry entry = getEntry(locator); if (entry != null) { - int[] retVal = null; + int[] retVal; if (entry.isTile) { retVal = new int[] { entry.count, entry.size }; } else { diff --git a/src/org/infinity/resource/key/FileResourceEntry.java b/src/org/infinity/resource/key/FileResourceEntry.java index 632b850c6..02024b81a 100644 --- a/src/org/infinity/resource/key/FileResourceEntry.java +++ b/src/org/infinity/resource/key/FileResourceEntry.java @@ -25,7 +25,7 @@ import org.infinity.util.io.ByteBufferInputStream; import org.infinity.util.io.StreamUtils; -public final class FileResourceEntry extends ResourceEntry { +public class FileResourceEntry extends ResourceEntry { private final boolean override; private Path file; diff --git a/src/org/infinity/resource/key/Keyfile.java b/src/org/infinity/resource/key/Keyfile.java index 977da67bc..53e1038c0 100644 --- a/src/org/infinity/resource/key/Keyfile.java +++ b/src/org/infinity/resource/key/Keyfile.java @@ -529,7 +529,7 @@ private void init() throws IOException { String sig = StreamUtils.readString(buffer, 0, 4); String ver = StreamUtils.readString(buffer, 4, 4); if (!sig.equals(KEY_SIGNATURE) || !ver.equals(KEY_VERSION)) { - throw new IOException("Unsupported key file: " + file.toString()); + throw new IOException("Unsupported key file: " + file); } int numBif = buffer.getInt(0x08); diff --git a/src/org/infinity/resource/key/ResourceEntry.java b/src/org/infinity/resource/key/ResourceEntry.java index dddac92f7..723557744 100644 --- a/src/org/infinity/resource/key/ResourceEntry.java +++ b/src/org/infinity/resource/key/ResourceEntry.java @@ -210,7 +210,7 @@ public String getSearchString() { } } catch (Exception e) { if ((NearInfinity.getInstance() != null) && !BrowserMenuBar.getInstance().getOptions().ignoreReadErrors()) { - JOptionPane.showMessageDialog(NearInfinity.getInstance(), "Error reading " + toString(), "Error", + JOptionPane.showMessageDialog(NearInfinity.getInstance(), "Error reading " + this, "Error", JOptionPane.ERROR_MESSAGE); } searchString = "Error"; diff --git a/src/org/infinity/resource/key/ResourceTreeFolder.java b/src/org/infinity/resource/key/ResourceTreeFolder.java index c5410efd1..12a147109 100644 --- a/src/org/infinity/resource/key/ResourceTreeFolder.java +++ b/src/org/infinity/resource/key/ResourceTreeFolder.java @@ -245,7 +245,7 @@ public Spliterator spliterator() { @Override public Comparator comparator() { - return (o1, o2) -> o1.compareTo(o2); + return Comparator.naturalOrder(); } @Override diff --git a/src/org/infinity/resource/key/ResourceTreeModel.java b/src/org/infinity/resource/key/ResourceTreeModel.java index ddb515c2f..913c7129e 100644 --- a/src/org/infinity/resource/key/ResourceTreeModel.java +++ b/src/org/infinity/resource/key/ResourceTreeModel.java @@ -18,6 +18,7 @@ import java.util.Map; import java.util.TreeMap; +import javax.swing.event.EventListenerList; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; @@ -28,7 +29,7 @@ import org.infinity.util.io.FileEx; public final class ResourceTreeModel implements TreeModel { - private final List treeModelListeners = new ArrayList<>(); + private final EventListenerList treeModelListeners = new EventListenerList(); private final Map entries = new HashMap<>(25000); private final Map folders = new TreeMap<>(Misc.getIgnoreCaseComparator()); private final ResourceTreeFolder root = new ResourceTreeFolder(null, ""); @@ -79,12 +80,16 @@ public int getIndexOfChild(Object parent, Object child) { @Override public void addTreeModelListener(TreeModelListener l) { - treeModelListeners.add(l); + if (l != null) { + treeModelListeners.add(TreeModelListener.class, l); + } } @Override public void removeTreeModelListener(TreeModelListener l) { - treeModelListeners.remove(l); + if (l != null) { + treeModelListeners.remove(TreeModelListener.class, l); + } } // --------------------- End Interface TreeModel --------------------- @@ -113,7 +118,6 @@ public void addDirectory(ResourceTreeFolder parentFolder, Path directory, boolea } } catch (IOException e) { Logger.error(e); - return; } } @@ -137,7 +141,7 @@ public List getBIFFResourceEntries(Path keyFile) { for (int i = 0; i < root.getFolders().size(); i++) { List entries = root.getFolders().get(i).getResourceEntries(); for (int j = 0; j < entries.size(); j++) { - ResourceEntry o = entries.get(j); + final ResourceEntry o = entries.get(j); if (o instanceof BIFFResourceEntry) { BIFFResourceEntry bre = (BIFFResourceEntry) o; if (keyFile == null || bre.getKeyfile().equals(keyFile)) { @@ -278,32 +282,26 @@ public void removeResourceEntry(ResourceEntry entry) { } public void removeResourceEntry(ResourceEntry entry, String folder) { - ResourceTreeFolder parent = folders.get(folder); + final ResourceTreeFolder parent = folders.get(folder); if (parent == null) { return; } - TreePath path = getPathToNode(entry).getParentPath(); - TreeModelEvent event = new TreeModelEvent(this, path, new int[] { getIndexOfChild(parent, entry) }, - new Object[] { entry }); + final TreePath path = getPathToNode(entry).getParentPath(); + final int[] childIndices = { getIndexOfChild(parent, entry) }; + final Object[] children = { entry }; parent.removeResourceEntry(entry); entries.remove(entry.getResourceName().toUpperCase(Locale.ENGLISH)); if (parent.getChildCount() == 0) { root.removeFolder(parent); folders.remove(parent.folderName()); } - for (TreeModelListener treeModelListener : treeModelListeners) { - treeModelListener.treeNodesRemoved(event); - } + fireTreeNodesRemoved(path, childIndices, children); } public void resourceEntryChanged(FileResourceEntry entry) { - TreePath parentPath = getPathToNode(entry).getParentPath(); - ResourceTreeFolder parentFolder = (ResourceTreeFolder) parentPath.getLastPathComponent(); - TreeModelEvent event = new TreeModelEvent(this, parentPath, new int[] { getIndexOfChild(parentFolder, entry) }, - new Object[] { entry }); - for (TreeModelListener treeModelListener : treeModelListeners) { - treeModelListener.treeNodesChanged(event); - } + final TreePath parentPath = getPathToNode(entry).getParentPath(); + final ResourceTreeFolder parentFolder = (ResourceTreeFolder) parentPath.getLastPathComponent(); + fireTreeNodesChanged(parentPath, new int[] { getIndexOfChild(parentFolder, entry) }, new Object[] { entry }); } public int size() { @@ -326,9 +324,50 @@ public void updateFolders(ResourceTreeFolder... folders) { } private void fireTreeStructureChanged(TreePath changed) { - TreeModelEvent event = new TreeModelEvent(this, changed); - for (TreeModelListener treeModelListener : treeModelListeners) { - treeModelListener.treeStructureChanged(event); + if (changed != null) { + final Object[] listeners = treeModelListeners.getListenerList(); + TreeModelEvent event = null; + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == TreeModelListener.class) { + // Event object is lazily created + if (event == null) { + event = new TreeModelEvent(this, changed); + } + ((TreeModelListener) listeners[i + 1]).treeStructureChanged(event); + } + } + } + } + + private void fireTreeNodesChanged(TreePath path, int[] childIndices, Object[] children) { + if (path != null) { + final Object[] listeners = treeModelListeners.getListenerList(); + TreeModelEvent event = null; + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == TreeModelListener.class) { + // Event object is lazily created + if (event == null) { + event = new TreeModelEvent(this, path, childIndices, children); + } + ((TreeModelListener) listeners[i + 1]).treeNodesChanged(event); + } + } + } + } + + private void fireTreeNodesRemoved(TreePath path, int[] childIndices, Object[] children) { + if (path != null) { + final Object[] listeners = treeModelListeners.getListenerList(); + TreeModelEvent event = null; + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == TreeModelListener.class) { + // Event object is lazily created + if (event == null) { + event = new TreeModelEvent(this, path, childIndices, children); + } + ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(event); + } + } } } } diff --git a/src/org/infinity/resource/maze/MazeEntry.java b/src/org/infinity/resource/maze/MazeEntry.java index 89662195a..7be55bac4 100644 --- a/src/org/infinity/resource/maze/MazeEntry.java +++ b/src/org/infinity/resource/maze/MazeEntry.java @@ -28,7 +28,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception { addField(new Bitmap(buffer, offset + 12, 4, ModronMazeEntry.GAM_MAZE_ENTRY_IS_TRAPPED, OPTION_NOYES)); addField(new Bitmap(buffer, offset + 16, 4, ModronMazeEntry.GAM_MAZE_ENTRY_TRAP_TYPE, TRAPS_ARRAY)); addField(new Flag(buffer, offset + 20, 4, ModronMazeEntry.GAM_MAZE_ENTRY_EXITS, WALLS_ARRAY)); - addField(new Bitmap(buffer, offset + 24, 4, ModronMazeEntry.GAM_MAZE_ENTRY_POLULATED, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 24, 4, ModronMazeEntry.GAM_MAZE_ENTRY_POPULATED, OPTION_NOYES)); return offset + 28; } } diff --git a/src/org/infinity/resource/mus/Entry.java b/src/org/infinity/resource/mus/Entry.java index a3d282566..c297f80b1 100644 --- a/src/org/infinity/resource/mus/Entry.java +++ b/src/org/infinity/resource/mus/Entry.java @@ -124,13 +124,24 @@ public void close() { @Override public String toString() { - return line; + final String time; + if (audioBuffer != null) { + final int duration = (int)(audioBuffer.getDuration() / 1_000L); + time = String.format("[%02d:%02d] ", duration / 60, duration % 60); + } else { + time = "[??:??] "; + } + return time + line; } public AudioBuffer getEndBuffer() { return endBuffer; } + public String getName() { + return name; + } + public int getNextNr() { return nextnr; } diff --git a/src/org/infinity/resource/mus/MusResource.java b/src/org/infinity/resource/mus/MusResource.java index 0d4c4cfc1..71756a986 100644 --- a/src/org/infinity/resource/mus/MusResource.java +++ b/src/org/infinity/resource/mus/MusResource.java @@ -184,12 +184,12 @@ public MusResource(ResourceEntry entry) throws Exception { @Override public void actionPerformed(ActionEvent event) { if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE) == event.getSource()) { - if (ResourceFactory.saveResource(this, panel.getTopLevelAncestor())) { + if (ResourceFactory.saveResource(this, panel.getTopLevelAncestor()).isTrue()) { setDocumentModified(false); } viewer.loadMusResource(this); } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE_AS) == event.getSource()) { - if (ResourceFactory.saveResourceAs(this, panel.getTopLevelAncestor())) { + if (ResourceFactory.saveResourceAs(this, panel.getTopLevelAncestor()).isTrue()) { setDocumentModified(false); } viewer.loadMusResource(this); diff --git a/src/org/infinity/resource/mus/MusResourceHandler.java b/src/org/infinity/resource/mus/MusResourceHandler.java new file mode 100644 index 000000000..51cdaef3d --- /dev/null +++ b/src/org/infinity/resource/mus/MusResourceHandler.java @@ -0,0 +1,425 @@ +package org.infinity.resource.mus; + +import java.io.FileNotFoundException; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; + +import org.infinity.resource.Closeable; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.resource.sound.AudioBuffer; +import org.infinity.util.Logger; +import org.infinity.util.io.StreamUtils; + +/** + * Customizable helper class for handling the details of MUS playback progress. + */ +public class MusResourceHandler implements Closeable { + /** List of sound segments of the loaded MUS resource. */ + private List entryList; + /** The initial sound entry index. */ + private int startEntryIndex; + /** Index of the current sound entry. */ + private int currentEntryIndex; + /** Whether to auto-switch to "end" segment when advancing from last regular sound segment. */ + private boolean allowEnding; + /** Whether "ending" flag should be set by the next advancement. */ + private boolean signalEnding; + /** Whether "end" sound segment is selected. */ + private boolean ending; + /** Whether advancing sound segments should consider looping back to previous segments. */ + private boolean loop; + + /** + * Loads the specified MUS resource for playback with the specified options. + * + * @param musEntry {@link ResourceEntry} of the MUS resource. + * @param startIndex The initially selected sound segment index. + * @param allowEnding Whether to switch to the "end" sound segment automatically when the last regular sound segment + * was processed. Setting this option has only an effect if {@code looping} is disabled or the + * MUS resource does not loop. + * @param looping Whether soundtrack is alloed to loop back to previously played entries. + * @throws NullPointerException if {@code musEntry} is {@code null}. + * @throws FileNotFoundException if the MUS resource does not exist. + * @throws Exception if the MUS resource could not be parsed. + */ + public MusResourceHandler(ResourceEntry musEntry, int startIndex, boolean allowEnding, boolean looping) + throws Exception { + if (Objects.isNull(musEntry)) { + throw new NullPointerException("musEntry is null"); + } + if (!Files.isRegularFile(musEntry.getActualPath())) { + throw new FileNotFoundException("MUS file not found: " + musEntry.getActualPath()); + } + init(MusResourceHandler.parseMusFile(musEntry), startIndex, allowEnding, looping); + } + + /** + * Loads a parsed MUS entry list for playback with the specified options. + * + * @param musEntries Collection of {@link Entry} instances of a MUS resource. + * @param startIndex The initially selected sound segment index. + * @param allowEnding Whether to switch to the "end" sound segment automatically when the last regular sound segment + * was processed. Setting this option has only an effect if {@code looping} is disabled or the + * MUS resource does not loop. + * @param looping Whether soundtrack is alloed to loop back to previously played entries. + * @throws NullPointerException if {@code musEntries} is {@code null}. + */ + public MusResourceHandler(Collection musEntries, int startIndex, boolean allowEnding, boolean looping) { + if (Objects.isNull(musEntries)) { + throw new NullPointerException("musEntries is null"); + } + init(musEntries, startIndex, allowEnding, looping); + } + + /** + * Returns the number of sound segments defined by this MUS resource, not counting the special "end" segment. + * + * @return Number of available sound segments in the MUS resource. + */ + public int size() { + return entryList.size(); + } + + /** + * Returns whether a subsequent call of {@link #advance()} method will return successfully with the current configuration. + * + * @return {@code true} if the current sound segment is the last segment in the sound list, {@code false} otherwise. + */ + public boolean hasNextEntry() { + if (currentEntryIndex == Integer.MIN_VALUE) { + return validIndex(startEntryIndex); + } else if (!isEnding() && validIndex(currentEntryIndex)) { + final int index = entryList.get(currentEntryIndex).getNextNr(); + boolean retVal = (isLooping() || (index > currentEntryIndex || (isAllowEnding() && getCurrentEntry().getEndBuffer() != null))); + retVal = retVal && ((isAllowEnding() && getCurrentEntry().getEndBuffer() != null) || validIndex(index)); + return retVal; + } + return false; + } + + /** + * Returns whether advancing the soundtrack should allow to return to previously selected sound segments + * + * @return {@code true} if looping back to previous sound segments is allowed, {@code false} otherwise. + */ + public boolean isLooping() { + return loop; + } + + /** + * Specify whether looping back to previously selected sound segments should be allowed by the {@link #advance()} + * method. + * + * @param loop Specify {@code true} to allow looping, {@code false} to end advancement when reaching the last sound + * segment. + */ + public void setLooping(boolean loop) { + this.loop = loop; + } + + /** + * Returns whether the "end" sound segment is automatically returned when the last regular segment has been + * processed. + * + * @return {@code true} to allow advancing to "end" sound segment + */ + public boolean isAllowEnding() { + return allowEnding; + } + + /** + * Specify whether the "end" sound segment should be automatically selected after the last regular sound segment has + * been processed. + * + * @param allow Whether to enable or disable automatic availability of the "end" sound segment. + */ + public void setAllowEnding(boolean allow) { + allowEnding = allow; + } + + /** + * Returns whether the "end" sound segment is enabled as current sound segment. Advancing sound segments if "end" + * segment is enabled finishes advancement instantly. + * + * @return {@code true} if "end" sound segment is enabled, {@code false} otherwise. + */ + public boolean isEnding() { + return ending; + } + + /** + * Specify to activate the "end" sound segment for the currently selected entry. This flag indicates which audio + * segment is returned by a call of {@link #getAudioBuffer()}. + * + * @param ending whether to activate or deactivate "end" sound segment. + */ + public void setEnding(boolean ending) { + this.ending = ending; + if (this.ending) { + signalEnding = false; + } + } + + /** + * Returns whether the handler has been signaled to enable the "end" sound segment flag with the next call of + * {@link #advance()}. + *

+ * Note: The signal is cleared automatically when the ending flag has been set. + *

+ * + * @return {@code true} if signal has been set, {@code false} otherwise. + */ + public boolean isEndingSignaled() { + return signalEnding; + } + + /** + * Specifies whether to signal the handler to set the ending flag at the next call of {@link #advance()}. Does nothing + * if the ending flag has already been enabled. + *

+ * Note: The signal is cleared automatically when the ending flag has been set. + *

+ */ + public void setSignalEnding(boolean signal) { + if (signal != signalEnding && (!signal || !ending)) { + signalEnding = signal; + } + } + + /** + * Returns the sound segment index that was explicitly or implicitly passed to the constructor of this + * {@link MusResourceHandler} instance. + * + * @return initial sound segment index. + */ + public int getStartIndex() { + return startEntryIndex; + } + + /** + * Specify the initially selected sound segment index when resetting the handler instance. + * + * @param newIndex index value. + */ + public void setStartIndex(int newIndex) { + startEntryIndex = Math.max(0, Math.min(entryList.size() - 1, newIndex)); + } + + /** + * Returns the index of the current sound segment. + * + * @return Index of current sound segment. Returns {@code -1} if {@link #advance()} was not yet called after constructing or + * resetting this {@link MusResourceHandler} instance. Returns {@link #size()} if no more sound segments are + * available. + */ + public int getCurrentIndex() { + return (currentEntryIndex == Integer.MIN_VALUE) ? -1 : currentEntryIndex; + } + + /** + * Specify the index for the currently selected sound segment. + * + * @param newIndex new sound segment index. Specify {@code -1} to reset advancement. Specify {@link #size()} to + * indicate that the soundtrack has ended. + * @throws IndexOutOfBoundsException if {@code newIndex} is out of bounds. + * @throws IllegalArgumentException if {@link #isEnding()} is set and the end buffer is {@code null}. + */ + public void setCurrentIndex(int newIndex) { + if (!validIndex(newIndex) && newIndex != -1 && newIndex != size()) { + throw new IndexOutOfBoundsException("index = " + newIndex); + } + if (isEnding() && newIndex != -1 && newIndex != size() && entryList.get(newIndex).getEndBuffer() == null) { + throw new IllegalArgumentException("end buffer is null for index = " + newIndex); + } + currentEntryIndex = (newIndex == -1) ? Integer.MIN_VALUE : newIndex; + } + + /** + * Returns the {@link Entry} instance of the current sound segment. + * + * @return {@link Entry} of the current segment if available, {@code null} otherwise. + */ + public Entry getCurrentEntry() { + if (validIndex(currentEntryIndex)) { + return entryList.get(currentEntryIndex); + } + return null; + } + + /** + * Returns the {@link Entry} instance at the specified position in the sound entry list. + * + * @param index Index of the entry instance. + * @return the {@link Entry} instance. + * @throws IndexOutOfBoundsException if the index is out of range. + */ + public Entry getEntry(int index) { + return entryList.get(index); + } + + /** + * Returns the audio buffer of the current sound segment. Returns the buffer of the "end" sound segment if + * {@link #isEnding()} is {@code true}. + * + * @return {@link AudioBuffer} of the current sound segment. Returns {@code null} if audio buffer is not available. + */ + public AudioBuffer getAudioBuffer() { + final Entry entry = getCurrentEntry(); + if (Objects.nonNull(entry)) { + return isEnding() ? entry.getEndBuffer() : entry.getAudioBuffer(); + } + return null; + } + + /** + * Advances MUS segment list to the next sound entry. Enables playback of "end" sound segment if + * {@link #isAllowEnding()} is set and the last sound segment was selected by a previous call of {@code #advance()}. + * + * @return {@code true} if the soundtrack could be advanced to the next segment, {@code false} otherwise. + */ + public boolean advance() { + if (!hasNextEntry()) { + currentEntryIndex = size(); + } else if (currentEntryIndex == Integer.MIN_VALUE) { + currentEntryIndex = startEntryIndex; + } else if (validIndex(currentEntryIndex)) { + if (signalEnding) { + if (entryList.get(currentEntryIndex).getEndBuffer() != null) { + ending = true; + } else { + currentEntryIndex = size(); + } + } else { + // advance to next sound segment or switch to end segment if needed + final int index = entryList.get(currentEntryIndex).getNextNr(); + if ((!isLooping() && index <= currentEntryIndex) || !validIndex(index)) { + if (isAllowEnding() && !isEnding()) { + // switch to "end" segment after processing the last regular sound segment + ending = true; + } else { + currentEntryIndex = size(); + } + } else { + currentEntryIndex = index; + } + } + } else { + // no further sound segments available + currentEntryIndex = size(); + } + + signalEnding = false; + + return validIndex(currentEntryIndex); + } + + /** + * Resets the playback position back to the index that was explicitly or implicitly passed to the constructor. + */ + public void reset() { + currentEntryIndex = Integer.MIN_VALUE; + ending = false; + } + + /** Releases all sound segment resources. */ + @Override + public void close() throws Exception { + ending = false; + currentEntryIndex = 0; + for (int i = entryList.size() - 1; i >= 0; i--) { + final Entry entry = entryList.get(i); + entry.close(); + entryList.remove(i); + } + } + + /** Returns {@code true} only if {@code index} is a valid list index. */ + private boolean validIndex(int index) { + return (index >= 0 && index < entryList.size()); + } + + /** Initializes the MusResourceHandler instance. */ + private void init(Collection entries, int startIndex, boolean allowEnding, boolean looping) { + if (Objects.isNull(entries)) { + throw new NullPointerException("MUS entry list is null"); + } + + entryList = new ArrayList<>(entries); + currentEntryIndex = Integer.MIN_VALUE; + setStartIndex(startIndex); + setAllowEnding(allowEnding); + setEnding(false); + setLooping(looping); + } + + /** + * Creates a parsed list of sound entries from the specified MUS resource. + * + * @param resource MUS resource as {@link ResourceEntry} instance. + * @return List of MUS {@link Entry} objects for each of the parsed MUS sound segments. + * @throws NullPointerException if {@code resource} is {@code null}. + * @throws Exception if the MUS resource could not be parsed. + */ + public static List parseMusFile(ResourceEntry resource) throws Exception { + Objects.requireNonNull(resource); + List retVal = new ArrayList<>(); + + final ByteBuffer bb = resource.getResourceBuffer(); + final String[] lines = StreamUtils.readString(bb, bb.limit()).split("\r?\n"); + int idx = 0; + + String acmFolder = null; + while (acmFolder == null && idx < lines.length) { + final String s = getNormalizedString(lines[idx++]); + if (!s.isEmpty()) { + acmFolder = s; + } + } + + int numEntries = 0; + while (idx < lines.length) { + final String s = getNormalizedString(lines[idx++]); + if (!s.isEmpty()) { + numEntries = Integer.parseInt(s); + break; + } + } + + int counter = 0; + while (idx < lines.length && counter < numEntries) { + String line = getNormalizedString(lines[idx++]); + if (!line.isEmpty()) { + retVal.add(new Entry(resource, acmFolder, retVal, line, counter)); + counter++; + } + } + + if (counter != numEntries) { + Logger.warn("{}: Unexpected number of parsed sound segments (found: {}, expected: {})", resource, counter, + numEntries); + } + + for (final Entry entry : retVal) { + entry.init(); + } + + return retVal; + } + + private static String getNormalizedString(String s) { + if (s == null) { + return ""; + } + String retVal = s; + final int pos = retVal.indexOf('#'); + if (pos >= 0) { + retVal = retVal.substring(0, pos); + } + retVal = retVal.trim(); + return retVal; + } +} diff --git a/src/org/infinity/resource/mus/Viewer.java b/src/org/infinity/resource/mus/Viewer.java index 8ffe83718..ee612742a 100644 --- a/src/org/infinity/resource/mus/Viewer.java +++ b/src/org/infinity/resource/mus/Viewer.java @@ -5,56 +5,58 @@ package org.infinity.resource.mus; import java.awt.BorderLayout; +import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; -import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; -import java.util.HashMap; -import java.util.List; -import java.util.StringTokenizer; -import java.util.Vector; import javax.swing.BorderFactory; -import javax.swing.Icon; +import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.SwingConstants; import javax.swing.SwingWorker; import javax.swing.UIManager; +import org.infinity.gui.ViewerUtil; import org.infinity.gui.menu.BrowserMenuBar; import org.infinity.icon.Icons; -import org.infinity.resource.sound.AudioPlayer; +import org.infinity.resource.sound.AudioStateEvent; +import org.infinity.resource.sound.AudioStateListener; +import org.infinity.resource.sound.StreamingAudioPlayer; import org.infinity.util.Logger; import org.infinity.util.Misc; import org.infinity.util.SimpleListModel; +import org.infinity.util.StopWatch; -public class Viewer extends JPanel implements Runnable, ActionListener { - /** Provides quick access to the "play" and "pause" image icon. */ - private static final HashMap PLAY_ICONS = new HashMap<>(); +public class Viewer extends JPanel implements ActionListener, AudioStateListener { + private static final ImageIcon ICON_PLAY = Icons.ICON_PLAY_16.getIcon(); + private static final ImageIcon ICON_PAUSE = Icons.ICON_PAUSE_16.getIcon(); + private static final ImageIcon ICON_END = Icons.ICON_END_16.getIcon(); + private static final ImageIcon ICON_STOP = Icons.ICON_STOP_16.getIcon(); - static { - PLAY_ICONS.put(true, Icons.ICON_PLAY_16.getIcon()); - PLAY_ICONS.put(false, Icons.ICON_PAUSE_16.getIcon()); - } + /** Display format of elapsed time (minutes, seconds) */ + private static final String DISPLAY_TIME_FORMAT = "Elapsed time: %02d:%02d"; private final SimpleListModel listModel = new SimpleListModel<>(); private final JList list = new JList<>(listModel); - private final AudioPlayer player = new AudioPlayer(); - private final List entryList = new Vector<>(); + private final StopWatch elapsedTimer = new StopWatch(1000L, false); + private MusResourceHandler musHandler; + private StreamingAudioPlayer player; private JLabel playList; private JButton bPlay; private JButton bEnd; private JButton bStop; - private boolean play= false; - private boolean end = false; - private boolean closed = false; + private JLabel displayLabel; + + private boolean closed; public Viewer(MusResource mus) { initGUI(); @@ -65,22 +67,30 @@ public Viewer(MusResource mus) { @Override public void actionPerformed(ActionEvent event) { - if (event.getSource() == bPlay) { - if (player == null || !player.isRunning()) { - new Thread(this).start(); - } else if (player.isRunning()) { - setPlayButtonState(player.isPaused()); + if (event.getSource() == elapsedTimer) { + updateTimeLabel(); + } else if (event.getSource() == bPlay) { + if (player == null) { + try { + player = new StreamingAudioPlayer(this); + } catch (Exception e) { + updateControls(); + Logger.error(e); + JOptionPane.showMessageDialog(this, "Error during playback:\n" + e.getMessage(), "Error", + JOptionPane.ERROR_MESSAGE); + } + } + if (player.isPlaying()) { player.setPaused(!player.isPaused()); + } else { + musHandler.setStartIndex(list.getSelectedIndex()); + player.setPlaying(true); } } else if (event.getSource() == bStop) { - bStop.setEnabled(false); - bEnd.setEnabled(false); - setPlayButtonState(false); - play = false; - player.stopPlay(); + player.setPlaying(false); } else if (event.getSource() == bEnd) { - bEnd.setEnabled(false); - end = true; + musHandler.setSignalEnding(true); + updateControls(); } } @@ -89,60 +99,55 @@ public void actionPerformed(ActionEvent event) { // --------------------- Begin Interface Runnable --------------------- @Override - public void run() { - setPlayButtonState(true); - bStop.setEnabled(true); - bEnd.setEnabled(true); - list.setEnabled(false); - int nextnr = list.getSelectedIndex(); - if (nextnr == -1) { - nextnr = 0; - } - play = true; - end = false; - try { - while (play) { - if (!end) { - list.setSelectedIndex(nextnr); - list.ensureIndexIsVisible(nextnr); - list.repaint(); - player.playContinuous(entryList.get(nextnr).getAudioBuffer()); - } else if (entryList.get(nextnr).getEndBuffer() != null) { - player.play(entryList.get(nextnr).getEndBuffer()); - play = false; - } - if (!end) { - nextnr = entryList.get(nextnr).getNextNr(); - if (nextnr == -1 || nextnr == entryList.size()) { - play = false; - } - } - } - } catch (Exception e) { - JOptionPane.showMessageDialog(this, "Error during playback", "Error", JOptionPane.ERROR_MESSAGE); - Logger.error(e); + public void audioStateChanged(AudioStateEvent event) { +// Logger.trace("{}.audioStateChanged({})", Viewer.class.getName(), event); + switch (event.getAudioState()) { + case OPEN: + handleAudioOpenEvent(event.getValue()); + break; + case CLOSE: + handleAudioCloseEvent(event.getValue()); + break; + case START: + handleAudioStartEvent(); + break; + case STOP: + handleAudioStopEvent(); + break; + case PAUSE: + handleAudioPauseEvent(event.getValue()); + break; + case RESUME: + handleAudioResumeEvent(event.getValue()); + break; + case BUFFER_EMPTY: + handleAudioBufferEmptyEvent(event.getValue()); + break; + case ERROR: + handleAudioErrorEvent(event.getValue()); + break; } - player.stopPlay(); - setPlayButtonState(false); - bStop.setEnabled(false); - bEnd.setEnabled(false); - list.setEnabled(true); - list.setSelectedIndex(0); - list.ensureIndexIsVisible(0); } // --------------------- End Interface Runnable --------------------- + /** Closes the MUS resource viewer and all releases resource. */ public void close() { - setClosed(true); - stopPlay(); - for (final Entry entry : entryList) { - entry.close(); + closed = true; + resetPlayer(); + elapsedTimer.close(); + try { + musHandler.close(); + } catch (Exception e) { + Logger.error(e); } - entryList.clear(); + updateControls(); } - // Creates a new music list and loads all associated soundtracks + /** + * Creates a new music list and loads all associated soundtracks. Load operation is performed in a background task to + * prevent the UI from blocking. + */ public void loadMusResource(final MusResource mus) { if (mus != null) { // Parse and load soundtracks in a separate thread @@ -155,126 +160,220 @@ public Boolean doInBackground() { } } + /** Parses the specified {@link MusResource} instances for playback. */ private boolean parseMusFile(MusResource mus) { if (!isClosed()) { - stopPlay(); + resetPlayer(); bPlay.setEnabled(false); list.setEnabled(false); - StringTokenizer tokenizer = new StringTokenizer(mus.getText(), "\r\n"); - String dir = getNextToken(tokenizer, true); listModel.clear(); - entryList.clear(); - int count = Integer.parseInt(getNextToken(tokenizer, true)); - for (int i = 0; i < count; i++) { - if (isClosed()) { - return false; - } - Entry entry = new Entry(mus.getResourceEntry(), dir, entryList, getNextToken(tokenizer, true), i); - entryList.add(entry); - listModel.addElement(entry); - } - list.setSelectedIndex(0); - validate(); - - for (final Entry entry : entryList) { - if (isClosed()) { - return false; + try { + musHandler = new MusResourceHandler(mus.getResourceEntry(), 0, false, true); + for (int i = 0, size = musHandler.size(); i < size; i++) { + listModel.add(musHandler.getEntry(i)); } - try { - entry.init(); - } catch (Exception e) { - Logger.error(e); - JOptionPane.showMessageDialog(getTopLevelAncestor(), - "Error loading " + entry.toString() + '\n' + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); - } - } - - boolean enable = (!entryList.isEmpty() && entryList.get(0).getAudioBuffer() != null); - bPlay.setEnabled(enable); - list.setEnabled(enable); - return true; - } - return false; - } - - /** - * Returns the next valid token from the given {@code StringTokenizer}. - * - * @param tokenizer {@link StringTokenizer} containing string tokens. - * @param ignoreComments Whether comments should be skipped. - * @return The next string token if available, an empty string otherwise. - */ - private String getNextToken(StringTokenizer tokenizer, boolean ignoreComments) { - String retVal = ""; - while (tokenizer != null && tokenizer.hasMoreTokens()) { - retVal = tokenizer.nextToken().trim(); - if (!ignoreComments || !retVal.startsWith("#")) { - break; + list.setSelectedIndex(0); + validate(); + } catch (Exception e) { + Logger.error(e); + JOptionPane.showMessageDialog(getTopLevelAncestor(), + "Error loading " + mus.getResourceEntry() + ":\n" + e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); } + updateControls(); } - return retVal; + return !isClosed(); } + /** Sets up the UI of the viewer. */ private void initGUI() { bPlay = new JButton(); - setPlayButtonState(false); bPlay.addActionListener(this); - bEnd = new JButton("Finish", Icons.ICON_END_16.getIcon()); - bEnd.setEnabled(false); + + // prevent Play button state change from affecting the overall layout + setPlayButtonState(true); + int minWidth = bPlay.getPreferredSize().width; + setPlayButtonState(false); + minWidth = Math.max(minWidth, bPlay.getPreferredSize().width); + bPlay.setPreferredSize(new Dimension(minWidth, bPlay.getPreferredSize().height)); + + bEnd = new JButton("Finish", ICON_END); bEnd.addActionListener(this); - bStop = new JButton("Stop", Icons.ICON_STOP_16.getIcon()); - bStop.setEnabled(false); + + bStop = new JButton("Stop", ICON_STOP); bStop.addActionListener(this); - JPanel buttonPanel = new JPanel(new GridLayout(1, 0, 6, 0)); - buttonPanel.add(bPlay); - buttonPanel.add(bEnd); - buttonPanel.add(bStop); + displayLabel = new JLabel("", SwingConstants.LEADING); + updateTimeLabel(0L); - list.setEnabled(false); list.setBorder(BorderFactory.createLineBorder(UIManager.getColor("controlShadow"))); list.setFont(Misc.getScaledFont(BrowserMenuBar.getInstance().getOptions().getScriptFont())); + JScrollPane listScroll = new JScrollPane(list); + playList = new JLabel("Playlist:"); - JScrollPane scroll = new JScrollPane(list); - JPanel centerPanel = new JPanel(); - GridBagLayout gbl = new GridBagLayout(); - GridBagConstraints gbc = new GridBagConstraints(); - centerPanel.setLayout(gbl); - gbc.insets = new Insets(3, 3, 3, 3); - gbc.gridwidth = GridBagConstraints.REMAINDER; - gbc.fill = GridBagConstraints.HORIZONTAL; - gbl.setConstraints(playList, gbc); - centerPanel.add(playList); - gbl.setConstraints(scroll, gbc); - centerPanel.add(scroll); - gbl.setConstraints(buttonPanel, gbc); - centerPanel.add(buttonPanel); + elapsedTimer.addActionListener(this); + + final GridBagConstraints gbc = new GridBagConstraints(); + + final JPanel buttonPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + buttonPanel.add(bPlay, gbc); + ViewerUtil.setGBC(gbc, 1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 8, 0, 0), 0, 0); + buttonPanel.add(bEnd, gbc); + ViewerUtil.setGBC(gbc, 2, 0, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 8, 0, 0), 0, 0); + buttonPanel.add(bStop, gbc); + + final JPanel centerPanel = new JPanel(new GridBagLayout()); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(0, 0, 0, 0), 0, 0); + centerPanel.add(playList, gbc); + ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.BOTH, + new Insets(8, 0, 0, 0), 0, 0); + centerPanel.add(listScroll, gbc); + ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(4, 0, 0, 0), 0, 0); + centerPanel.add(displayLabel, gbc); + ViewerUtil.setGBC(gbc, 0, 3, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, + new Insets(8, 0, 0, 0), 0, 0); + centerPanel.add(buttonPanel, gbc); + + // default list height is rather small + final Dimension dim = listScroll.getPreferredSize(); + dim.height *= 2; + listScroll.setPreferredSize(dim); setLayout(new BorderLayout()); add(centerPanel, BorderLayout.CENTER); + + updateControls(); + } + + /** Called when the audio player triggers a {@code OPEN} event. */ + private void handleAudioOpenEvent(Object value) { + musHandler.reset(); + } + + /** Called when the audio player triggers a {@code CLOSE} event. */ + private void handleAudioCloseEvent(Object value) { + // nothing to do + } + + /** Called when the audio player triggers a {@code START} event. */ + private void handleAudioStartEvent() { + elapsedTimer.reset(); + elapsedTimer.resume(); + updateTimeLabel(); + updateControls(); + } + + /** Called when the audio player triggers a {@code STOP} event. */ + private void handleAudioStopEvent() { + if (player == null) { + return; + } + + player.clearAudioQueue(); + elapsedTimer.pause(); + elapsedTimer.reset(); + updateTimeLabel(); + updateControls(); + list.setSelectedIndex(0); + list.ensureIndexIsVisible(0); + musHandler.reset(); + } + + /** Called when the audio player triggers a {@code PAUSE} event. */ + private void handleAudioPauseEvent(Object value) { + elapsedTimer.pause(); + setPlayButtonState(false); } - public void stopPlay() { + /** Called when the audio player triggers a {@code RESUME} event. */ + private void handleAudioResumeEvent(Object value) { + elapsedTimer.resume(); + setPlayButtonState(true); + } + + /** Called when the audio player triggers a {@code BUFFER_EMPTY} event. */ + private void handleAudioBufferEmptyEvent(Object value) { + if (player == null) { + return; + } + + if (musHandler.advance()) { + player.addAudioBuffer(musHandler.getAudioBuffer()); + list.setSelectedIndex(musHandler.getCurrentIndex()); + list.ensureIndexIsVisible(musHandler.getCurrentIndex()); + } else { + player.setPlaying(false); + } + } + + /** Called when the audio player triggers an {@code ERROR} event. */ + private void handleAudioErrorEvent(Object value) { if (player != null) { - play = false; - player.stopPlay(); + player.setPlaying(false); } + final Exception e = (value instanceof Exception) ? (Exception)value : null; + if (e != null) { + Logger.error(e); + } + final String msg = (e != null) ? "Error during playback:\n" + e.getMessage() : "Error during playback."; + JOptionPane.showMessageDialog(this, msg, "Error", JOptionPane.ERROR_MESSAGE); } - private synchronized void setClosed(boolean b) { - if (b != closed) { - closed = b; + /** Closes the audio player and releases associated resources. */ + private void resetPlayer() { + if (player != null) { + try { + player.close(); + } catch (Exception e) { + Logger.debug(e); + } + player = null; } } - private synchronized boolean isClosed() { + /** Returns whether the MUS resource viewer has been closed. */ + private boolean isClosed() { return closed; } - // Sets icon and text for the Play button according to the specified parameter. + /** Updates the elapsed time label with the elapsed playback time. */ + private void updateTimeLabel() { + updateTimeLabel(elapsedTimer.elapsed()); + } + + /** Updates the elapsed time label with the specified time value. */ + private void updateTimeLabel(long millis) { + final long minutes = millis / 60_000L; + final long seconds = (millis / 1000L) % 60L; + displayLabel.setText(String.format(DISPLAY_TIME_FORMAT, minutes, seconds)); + } + + /** Updates audio controls depending on current playback state. */ + private void updateControls() { + if (musHandler != null && player != null && player.isPlaying()) { + setPlayButtonState(!player.isPaused()); + bPlay.setEnabled(true); + bEnd.setEnabled(!musHandler.isEndingSignaled() && !musHandler.isEnding()); + bStop.setEnabled(true); + list.setEnabled(false); + } else { + setPlayButtonState(false); + bPlay.setEnabled(!listModel.isEmpty()); + bEnd.setEnabled(false); + bStop.setEnabled(false); + list.setEnabled(!listModel.isEmpty()); + } + } + + /** Sets icon and text for the Play button according to the specified parameter. */ private void setPlayButtonState(boolean paused) { - bPlay.setIcon(PLAY_ICONS.get(!paused)); + bPlay.setIcon(paused ? ICON_PAUSE : ICON_PLAY); bPlay.setText(paused ? "Pause" : "Play"); } } diff --git a/src/org/infinity/resource/other/TtfResource.java b/src/org/infinity/resource/other/TtfResource.java index 4f2a6a3eb..c39d3b7a7 100644 --- a/src/org/infinity/resource/other/TtfResource.java +++ b/src/org/infinity/resource/other/TtfResource.java @@ -194,7 +194,7 @@ private void updateText(String text) { // adding current text in different sizes int pos = 0; for (int element : FONT_SIZE) { - String label = Integer.toString(element) + " "; + String label = element + " "; SimpleAttributeSet as = new SimpleAttributeSet(); StyleConstants.setFontFamily(as, "SansSerif"); StyleConstants.setFontSize(as, FONT_SIZE[0]); @@ -227,10 +227,10 @@ private void showProperties() { String fontName = font.getFontName(); String fontFamily = font.getFamily(); - StringBuilder sb = new StringBuilder(""); - sb.append(""); - sb.append(""); - sb.append("
Font name:").append(fontName).append("
Font family:").append(fontFamily).append("
"); - JOptionPane.showMessageDialog(panel, sb.toString(), "Properties of " + resName, JOptionPane.INFORMATION_MESSAGE); + String sb = "" + + "" + + "" + + "
Font name:" + fontName + "
Font family:" + fontFamily + "
"; + JOptionPane.showMessageDialog(panel, sb, "Properties of " + resName, JOptionPane.INFORMATION_MESSAGE); } } diff --git a/src/org/infinity/resource/other/UnknownResource.java b/src/org/infinity/resource/other/UnknownResource.java index 015721c06..24824e594 100644 --- a/src/org/infinity/resource/other/UnknownResource.java +++ b/src/org/infinity/resource/other/UnknownResource.java @@ -117,12 +117,12 @@ public void actionPerformed(ActionEvent event) { } else if (event.getSource() == buttonPanel.getControlByType(ButtonPanel.Control.EXPORT_BUTTON)) { ResourceFactory.exportResource(entry, panelMain.getTopLevelAncestor()); } else if (event.getSource() == buttonPanel.getControlByType(ButtonPanel.Control.SAVE)) { - if (ResourceFactory.saveResource(this, panelMain.getTopLevelAncestor())) { + if (ResourceFactory.saveResource(this, panelMain.getTopLevelAncestor()).isTrue()) { setTextModified(false); setRawModified(false); } } else if (event.getSource() == buttonPanel.getControlByType(ButtonPanel.Control.SAVE_AS)) { - if (ResourceFactory.saveResourceAs(this, panelMain.getTopLevelAncestor())) { + if (ResourceFactory.saveResourceAs(this, panelMain.getTopLevelAncestor()).isTrue()) { setTextModified(false); setRawModified(false); } @@ -363,7 +363,7 @@ private void updateStatusBar() { if (isEditorActive() && tabbedPane.getSelectedIndex() == TAB_TEXT) { int row = editor.getCaretLineNumber() + 1; int col = editor.getCaretOffsetFromLineStart() + 1; - NearInfinity.getInstance().getStatusBar().setCursorText(Integer.toString(row) + ":" + Integer.toString(col)); + NearInfinity.getInstance().getStatusBar().setCursorText(row + ":" + col); } else if (isRawActive() && tabbedPane.getSelectedIndex() == TAB_RAW) { hexViewer.updateStatusBar(); } else { diff --git a/src/org/infinity/resource/sav/SavResource.java b/src/org/infinity/resource/sav/SavResource.java index 5756c524e..c61ae6fba 100644 --- a/src/org/infinity/resource/sav/SavResource.java +++ b/src/org/infinity/resource/sav/SavResource.java @@ -117,7 +117,7 @@ public SavResource(ResourceEntry entry) throws Exception { @Override public void actionPerformed(ActionEvent event) { if (buttonPanel.getControlByType(CTRL_COMPRESS) == event.getSource()) { - compressData(true); + compressData(true, true); } else if (buttonPanel.getControlByType(CTRL_DECOMPRESS) == event.getSource()) { decompressData(true); } else if (buttonPanel.getControlByType(CTRL_EDIT) == event.getSource()) { @@ -184,7 +184,7 @@ public void close() { final String msg = getResourceEntry().getResourceName() + " is still decompressed. Compress it?"; if (JOptionPane.showConfirmDialog(panel.getTopLevelAncestor(), msg, "Question", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION) { - compressData(true); + compressData(true, false); } } handler.close(); @@ -262,7 +262,7 @@ public void mouseClicked(MouseEvent event) { GridBagConstraints gbc = new GridBagConstraints(); centerpanel.setLayout(gbl); - JLabel label = new JLabel("Contents of " + entry.toString()); + JLabel label = new JLabel("Contents of " + entry); JScrollPane scroll = new JScrollPane(filelist); Dimension size = scroll.getPreferredSize(); scroll.setPreferredSize(new Dimension(2 * (int) size.getWidth(), 2 * (int) size.getHeight())); @@ -316,13 +316,29 @@ public IOHandler getFileHandler() { return handler; } - private boolean compressData(boolean showError) { + private boolean compressData(boolean showError, boolean confirm) { + final int result; + if (confirm) { + final String[] options = { "Save", "Don't save", "Cancel" }; + result = JOptionPane.showOptionDialog(panel.getTopLevelAncestor(), + "Compress any changes to " + getResourceEntry().getResourceName() + "?", "Question", + JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[0]); + } else { + result = JOptionPane.YES_OPTION; + } + + if (result == JOptionPane.CANCEL_OPTION) { + return false; + } + try { WindowBlocker block = new WindowBlocker(NearInfinity.getInstance()); try { block.setBlocked(true); handler.compress(entries); - ResourceFactory.saveResource(this, panel.getTopLevelAncestor()); + if (result == JOptionPane.YES_OPTION) { + ResourceFactory.saveResource(this, panel.getTopLevelAncestor(), true); + } buttonPanel.getControlByType(CTRL_DECOMPRESS).setEnabled(true); filelist.setEnabled(false); buttonPanel.getControlByType(CTRL_EDIT).setEnabled(false); diff --git a/src/org/infinity/resource/sound/AbstractAudioPlayer.java b/src/org/infinity/resource/sound/AbstractAudioPlayer.java new file mode 100644 index 000000000..c549e3ab0 --- /dev/null +++ b/src/org/infinity/resource/sound/AbstractAudioPlayer.java @@ -0,0 +1,153 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.sound; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Line; +import javax.sound.sampled.LineListener; +import javax.sound.sampled.Mixer; +import javax.sound.sampled.SourceDataLine; +import javax.swing.SwingUtilities; +import javax.swing.event.EventListenerList; + +import org.infinity.util.Logger; + +/** + * Common base for audio player classes. + */ +public abstract class AbstractAudioPlayer implements AudioPlayback, LineListener { + private final EventListenerList listenerList = new EventListenerList(); + + private boolean listenersEnabled; + + /** Adds a {@link AudioStateListener} to the audio player instance. */ + public void addAudioStateListener(AudioStateListener l) { + if (l != null) { + listenerList.add(AudioStateListener.class, l); + } + } + + /** Returns all registered {@link AudioStateListener}s for this audio player instance. */ + public AudioStateListener[] getAudioStateListeners() { + return listenerList.getListeners(AudioStateListener.class); + } + + /** Removes a {@link AudioStateListener} from the audio player instance. */ + public void removeAudioStateListener(AudioStateListener l) { + if (l != null) { + listenerList.remove(AudioStateListener.class, l); + } + } + + /** Fires an {@link AudioStateListener} of the specified name to all registered listeners. */ + protected void fireAudioStateEvent(AudioStateEvent.State state, Object value) { + // collect and execute + synchronized (listenerList) { + final AudioStateListener[] listeners = listenerList.getListeners(AudioStateListener.class); + if (listeners.length > 0) { + final AudioStateEvent event = new AudioStateEvent(this, state, value); + SwingUtilities.invokeLater(() -> { + for (AudioStateListener listener : listeners) { + listener.audioStateChanged(event); + } + }); + } + } + } + + /** Fires if an unrecoverable error occurs during audio playback. */ + protected void fireError(Exception e) { + fireAudioStateEvent(AudioStateEvent.State.ERROR, e); + } + + /** Fires when the the audio device is opened. */ + protected void firePlayerOpened() { + fireAudioStateEvent(AudioStateEvent.State.OPEN, null); + } + + /** Fires when the audio device is closed. */ + protected void firePlayerClosed() { + fireAudioStateEvent(AudioStateEvent.State.CLOSE, null); + } + + /** Fires when playback has started. */ + protected void firePlaybackStarted() { + fireAudioStateEvent(AudioStateEvent.State.START, null); + } + + /** Fires when playback has stopped. */ + protected void firePlaybackStopped() { + fireAudioStateEvent(AudioStateEvent.State.STOP, null); + } + + /** Fires when playback is set to paused mode. */ + protected void firePlaybackPaused() { + fireAudioStateEvent(AudioStateEvent.State.PAUSE, getSoundPosition()); + } + + /** Fires when paused playback is resumed. */ + protected void firePlaybackResumed() { + fireAudioStateEvent(AudioStateEvent.State.RESUME, getSoundPosition()); + } + + /** + * Returns the audio line responsible for playing back audio data. + * + * @return {@link Line} instance of the player. + */ + protected abstract Line getLine(); + + /** Returns whether {@link Line}'s status changes are tracked by this class instance. */ + protected boolean isLineListenersEnabled() { + return listenersEnabled; + } + + /** Specifies whether {@link Line}'s status changes should be tracked by this class instance. */ + protected void setLineListenersEnabled(boolean enable) { + if (getLine() == null) { + listenersEnabled = false; + return; + } + + if (enable != listenersEnabled) { + if (enable) { + getLine().addLineListener(this); + } else { + getLine().removeLineListener(this); + } + listenersEnabled = enable; + } + } + + /** + * Diagnostic method that prints all available sound mixers and their supported audio formats to {@code stdout}. + */ + public static void printMixerInfo() { + final StringBuilder sb = new StringBuilder("Available sound mixers:\n"); + try { + final Mixer.Info[] mixersInfo = AudioSystem.getMixerInfo(); + for (final Mixer.Info mi : mixersInfo) { + final Mixer mixer = AudioSystem.getMixer(mi); + sb.append(" Mixer: ").append(mixer).append('\n'); + Line.Info[] sourceLinesInfo = mixer.getSourceLineInfo(); + for (final Line.Info sli : sourceLinesInfo) { + sb.append(" Line: ").append(sli).append('\n'); + if (sli instanceof SourceDataLine.Info) { + final SourceDataLine.Info info = (SourceDataLine.Info)sli; + final AudioFormat[] formats = info.getFormats(); + for (final AudioFormat af : formats) { + sb.append(" Format: ").append(af).append('\n'); + } + } + } + } + System.out.println(sb); + } catch (Throwable t) { + System.out.println(sb); + Logger.warn(t); + } + } +} diff --git a/src/org/infinity/resource/sound/AudioFactory.java b/src/org/infinity/resource/sound/AudioFactory.java index 6b3a558c7..9ddd12528 100644 --- a/src/org/infinity/resource/sound/AudioFactory.java +++ b/src/org/infinity/resource/sound/AudioFactory.java @@ -12,7 +12,7 @@ public class AudioFactory { // supported audio formats - private static enum AudioFormat { + private enum AudioFormat { FMT_UNKNOWN, FMT_WAV, FMT_ACM, FMT_WAVC, FMT_OGG } diff --git a/src/org/infinity/resource/sound/AudioPlayback.java b/src/org/infinity/resource/sound/AudioPlayback.java new file mode 100644 index 000000000..12664b7c9 --- /dev/null +++ b/src/org/infinity/resource/sound/AudioPlayback.java @@ -0,0 +1,59 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.sound; + +import org.infinity.resource.Closeable; + +/** + * Provides basic playback functionality for audio data. + */ +public interface AudioPlayback extends Closeable { + /** + * Returns the elapsed playback time of audio data. + *

+ * Caution: Depending on the implementation, return value may be inaccurate if source and target + * audio formats specify different sample rates. + *

+ * + * @return Elapsed playback time, in milliseconds. + */ + long getSoundPosition(); + + /** + * Returns whether playback is active. Pausing and resuming playback does not affect the result. + * + * @return {@code true} if playback is active, {@code false} otherwise. + */ + boolean isPlaying(); + + /** + * Starts or stops playback of audio data. + * + * @param play Specify {@code true} to start playback or {@code false} to stop playback. + */ + void setPlaying(boolean play); + + /** + * Returns whether if playback is paused. Enabling or disabled the paused state does not affect the result of + * {@link #isPlaying()}.. + * + * @return {@code true} if current playback is paused, {@code false} otherwise. + */ + boolean isPaused(); + + /** + * Enters or leaves paused state when playback is active. Does nothing is playback is stopped. + * + * @param pause Specify {@code true} to pause current playback or {@code false} to resume playback. + */ + void setPaused(boolean pause); + + /** + * Returns whether the player has been closed. A closed audio player does not accept new playback commands. + * + * @return {@code true} if {@link #close()} was called on this audio player instance, {@code false} otherwise. + */ + boolean isClosed(); +} diff --git a/src/org/infinity/resource/sound/AudioPlayer.java b/src/org/infinity/resource/sound/AudioPlayer.java index f8b2846ea..ccce637bc 100644 --- a/src/org/infinity/resource/sound/AudioPlayer.java +++ b/src/org/infinity/resource/sound/AudioPlayer.java @@ -15,6 +15,14 @@ import org.infinity.util.Logger; +// TODO: remove class from project +/** + * This class provides a conventional way to play back sound data. It is mostly suited for playing streamed sound data. + * For playback of single sound clips the {@link BufferedAudioPlayer} class is more suited. + * + * @deprecated Superseded by {@link StreamingAudioPlayer} and {@link BufferedAudioPlayer}. + */ +@Deprecated public class AudioPlayer { private final byte[] buffer = new byte[8196]; diff --git a/src/org/infinity/resource/sound/AudioStateEvent.java b/src/org/infinity/resource/sound/AudioStateEvent.java new file mode 100644 index 000000000..58649c4dd --- /dev/null +++ b/src/org/infinity/resource/sound/AudioStateEvent.java @@ -0,0 +1,74 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.sound; + +import java.util.EventObject; + +/** + * An AudioStateEvent is triggered when the state of a sound clip has changed. + *

+ * States may include opening or closing a sound resource, starting or stopping playback, and pausing or resuming + * playback. + *

+ */ +public class AudioStateEvent extends EventObject { + /** Provides available audio states. */ + public enum State { + /** An unrecoverable error was triggered during audio playback. Associated value: the thrown {@link Exception}. */ + ERROR, + /** + * A sound resource has been successfully opened and is ready for playback. Associated value: Sound resource name + * {@link String}) if available, {@code null} otherwise. + */ + OPEN, + /** + * The current sound clip is released. Associated value: Sound resource name {@link String}) if available, + * {@code null} otherwise. + */ + CLOSE, + /** + * Playback of the current sound clip has started from the beginning. Associated value: {@code null} + *

Note: This state is only triggered if the audio line starts processing actual audio data.

+ */ + START, + /** Playback of the current sound clip has stopped. Associated value: {@code null} */ + STOP, + /** Playback of the current sound clip is paused. Associated value: Elapsed time in milliseconds ({@link Long}) */ + PAUSE, + /** Paused playback is resumed. Associated value: Elapsed time in milliseconds ({@link Long}) */ + RESUME, + /** + * Streamed audio playback only: The current audio buffer contains no more data. Associated value: {@link Boolean} + * that indicates whether more audio buffers are queued. + */ + BUFFER_EMPTY, + } + + private final AudioStateEvent.State audioState; + private final Object value; + + public AudioStateEvent(Object source, AudioStateEvent.State audioState, Object value) { + super(source); + this.audioState = audioState; + this.value = value; + } + + /** Returns the state that triggered this event. */ + public AudioStateEvent.State getAudioState() { + return audioState; + } + + /** Returns the value associated with the state. Value may be {@code null} if the state doesn't provide a value. */ + public Object getValue() { + return value; + } + + public String toString() { + return getClass().getName() + "[audioState=" + getAudioState() + + "; value=" + getValue() + + "; source=" + getSource() + + "]"; + } +} diff --git a/src/org/infinity/resource/sound/AudioStateListener.java b/src/org/infinity/resource/sound/AudioStateListener.java new file mode 100644 index 000000000..7398a8f80 --- /dev/null +++ b/src/org/infinity/resource/sound/AudioStateListener.java @@ -0,0 +1,20 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.sound; + +import java.util.EventListener; + +/** + * Instances of classes that implement the {@code AudioStateListener} interface can receive events when the state + * of the audio player has changed. + */ +public interface AudioStateListener extends EventListener { + /** + * Informs the listener that the audio state has changed. + * + * @param event a {@link AudioStateEvent} that describes the changed state. + */ + void audioStateChanged(AudioStateEvent event); +} diff --git a/src/org/infinity/resource/sound/BufferedAudioPlayback.java b/src/org/infinity/resource/sound/BufferedAudioPlayback.java new file mode 100644 index 000000000..72bb95fed --- /dev/null +++ b/src/org/infinity/resource/sound/BufferedAudioPlayback.java @@ -0,0 +1,43 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.sound; + +/** + * Provides specialized playback functionality for prebuffered audio data. + */ +public interface BufferedAudioPlayback extends AudioPlayback { + /** + * Returns the total length of the audio clip. + * + * @return Total sound length, in milliseconds. Returns {@code 0} if unavailable. + */ + long getTotalLength(); + + /** + * Sets an explicit playback position. + *

+ * Caution: Depending on the implementation, specified value may point to the wrong position if + * source and target audio formats specify different sample rates. + *

+ * + * @param position New playback position in milliseconds. Position is clamped to the available audio clip duration. + */ + void setSoundPosition(long position); + + /** + * Returns whether loop mode is enabled. + * + * @return {@code true} if sound playback is in loop mode, {@code false} otherwise. + */ + boolean isLooped(); + + /** + * Enables or disables looped playback. + * + * @param loop Specify {@code true} to start playback from the beginning if the end is reached. Specify {@code false} + * to stop playback when the end of the audio data is reached. + */ + void setLooped(boolean loop); +} diff --git a/src/org/infinity/resource/sound/BufferedAudioPlayer.java b/src/org/infinity/resource/sound/BufferedAudioPlayer.java new file mode 100644 index 000000000..dc51a4ef0 --- /dev/null +++ b/src/org/infinity/resource/sound/BufferedAudioPlayer.java @@ -0,0 +1,343 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.sound; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Objects; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.Clip; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.UnsupportedAudioFileException; + +import org.tinylog.Logger; + +/** + * A class for providing smooth and responsive playback of a single sound clip. + *

+ * None of the methods block execution. Playback state changes are propagated through {@link AudioStateEvent}s. + *

+ */ +public class BufferedAudioPlayer extends AbstractAudioPlayer implements BufferedAudioPlayback { + private final AudioBuffer audioBuffer; + private final Clip audioClip; + + private AudioInputStream audioStream; + + /** Indicates whether the audio player has been closed. */ + private boolean closed; + /** Indicates whether playback of the audio player is active. Pausing playback doesn't affect this flag. */ + private boolean playing; + /** Indicates whether playback is currently paused. Changing the paused state doesn't affect playback activity. */ + private boolean paused; + /** Indicates whether playback is looped. */ + private boolean looped; + /** Marks pause/resume state changes internally to fire correct state events. */ + private boolean pauseResume; + + /** + * Creates a new audio player and initializes it with the specified audio buffer. + * + * @param audioBuffer {@link AudioBuffer} to load into the audio player. + * @throws NullPointerException if {@code audioBuffer} is {@code null}. + * @throws IOException if an I/O error occurs. + * @throws UnsupportedAudioFileException if the audio data is incompatible with the player. + * @throws LineUnavailableException if the audio line is not available due to resource restrictions. + * @throws IllegalArgumentException if the audio data is invalid. + */ + public BufferedAudioPlayer(AudioBuffer audioBuffer) throws Exception { + this(audioBuffer, null); + } + + /** + * Creates a new audio player and initializes it with the specified audio buffer. + * + * @param audioBuffer {@link AudioBuffer} to load into the audio player. + * @param listener {@link AudioStateListener} that receives audio state changes. + * @throws NullPointerException if {@code audioBuffer} is {@code null}. + * @throws IOException if an I/O error occurs. + * @throws UnsupportedAudioFileException if the audio data is incompatible with the player. + * @throws LineUnavailableException if the audio line is not available due to resource restrictions. + * @throws IllegalArgumentException if the audio data is invalid. + */ + public BufferedAudioPlayer(AudioBuffer audioBuffer, AudioStateListener listener) throws Exception { + this.audioBuffer = Objects.requireNonNull(audioBuffer); + addAudioStateListener(listener); + audioStream = AudioSystem.getAudioInputStream(new ByteArrayInputStream(this.audioBuffer.getAudioData())); + final AudioFormat audioFormat = audioStream.getFormat(); + final DataLine.Info audioInfo = new DataLine.Info(Clip.class, audioFormat); + audioClip = (Clip) AudioSystem.getLine(audioInfo); + setLineListenersEnabled(true); + audioClip.open(audioStream); + audioClip.setLoopPoints(0, -1); + } + + @Override + public long getTotalLength() { + if (isClosed()) { + return 0L; + } + return audioClip.getMicrosecondLength() / 1000L; + } + + @Override + public long getSoundPosition() { + if (isClosed()) { + return 0L; + } + long position = audioClip.getMicrosecondPosition() % audioClip.getMicrosecondLength(); + return position / 1000L; + } + + @Override + public void setSoundPosition(long position) { + if (isClosed()) { + return; + } + + position = Math.max(0L, Math.min(audioClip.getMicrosecondLength(), position * 1000L)); + + try { + setLineListenersEnabled(false); + final boolean isPlaying = isPlaying() && !isPaused(); + if (isPlaying) { + audioClip.stop(); + audioClip.flush(); + } + audioClip.setMicrosecondPosition(position); + if (isPlaying) { + audioClip.start(); + setLooped(); + } + } finally { + setLineListenersEnabled(true); + } + } + + @Override + public boolean isLooped() { + return looped; + } + + @Override + public void setLooped(boolean loop) { + if (isClosed()) { + return; + } + + if (isLooped() == loop) { + return; + } + + looped = loop; + setLooped(); + } + + @Override + public boolean isPlaying() { + return !isClosed() && playing; + } + + /** + * Starts or stops playback of audio data. Triggers an {@link AudioStateEvent} if playback is started or stopped. + * + * @param play Specify {@code true} to start playback or {@code false} to stop playback. + */ + @Override + public void setPlaying(boolean play) { + if (play) { + play(); + } else { + stop(); + } + } + + /** + * Returns {@code true} if playback is paused. Enabling or disabled the paused state does not affect playback + * activity. + */ + @Override + public boolean isPaused() { + return isPlaying() && paused; + } + + /** + * Enters or leaves paused state when playback is active. Does nothing is playback is stopped. + * Triggers an {@link AudioStateEvent} if playback is paused or resumed. + * + * @param pause Specify {@code true} to pause current playback or {@code false} to resume playback. + */ + @Override + public void setPaused(boolean pause) { + if (pause) { + pause(); + } else { + resume(); + } + } + + @Override + public boolean isClosed() { + return closed; + } + + /** Closes the player and releases all resources. A closed audio player does not accept new playback commands. */ + @Override + public void close() throws Exception { + if (isClosed()) { + return; + } + + closed = true; + playing = false; + paused = false; + + synchronized (audioClip) { + if (audioClip.isRunning()) { + audioClip.stop(); + } + audioClip.flush(); + audioClip.close(); + } + + try { + audioStream.close(); + } catch (IOException e) { + Logger.warn(e); + } + audioStream = null; + + // removing listeners + final AudioStateListener[] items = getAudioStateListeners(); + for (int i = items.length - 1; i >= 0; i--) { + removeAudioStateListener(items[i]); + } + } + + @Override + public void update(LineEvent event) { + if (event.getType() == LineEvent.Type.START) { + if (!pauseResume) { + firePlaybackStarted(); + } else { + pauseResume = false; + } + } else if (event.getType() == LineEvent.Type.STOP) { + if (!pauseResume) { + playing = false; + firePlaybackStopped(); + } else { + playing = true; // override if paused to keep state consistent + pauseResume = false; + } + } else if (event.getType() == LineEvent.Type.OPEN) { + firePlayerOpened(); + } else if (event.getType() == LineEvent.Type.CLOSE) { + firePlayerClosed(); + } + } + + @Override + protected Clip getLine() { + return audioClip; + } + + /** Use internally after each call {@link Clip#start()} to set up looping mode. */ + private void setLooped() { + if (isClosed()) { + return; + } + if (isPlaying() && !isPaused()) { + audioClip.loop(isLooped() ? Clip.LOOP_CONTINUOUSLY : 0); + } + } + + /** Starts playback of the associated audio data. Does nothing if the player is closed or already playing. */ + private void play() { + if (isClosed() || isPlaying()) { + return; + } + + synchronized (audioClip) { + if (audioClip.isRunning()) { + audioClip.stop(); + audioClip.flush(); + } + playing = true; + paused = false; + audioClip.setFramePosition(0); + audioClip.start(); + setLooped(); + } + } + + /** + * Stops active playback and sets position to the start of the clip. Does nothing if the player is closed or has + * stopped playback. + */ + private void stop() { + if (isClosed() || !isPlaying()) { + return; + } + + synchronized (audioClip) { + final boolean isPaused = isPaused(); + playing = false; + paused = false; + if (isPaused) { + // Pause mode is technically "stop" mode, so we need to trigger a "STOP" event manually + update(new LineEvent(audioClip, LineEvent.Type.STOP, audioClip.getLongFramePosition())); + } else { + audioClip.stop(); + } + audioClip.flush(); + audioClip.setFramePosition(0); + } + } + + /** + * Pauses active playback. Does nothing if the player is closed, playback is not active, or already in the paused + * state. + */ + private void pause() { + if (isClosed()) { + return; + } + + synchronized (audioClip) { + if (isPlaying() && !isPaused()) { + pauseResume = true; + paused = true; + audioClip.stop(); + firePlaybackPaused(); + } + } + } + + /** + * Resumes previously paused playback. Does nothing if the player is closed, playback is not active, or not in the + * paused state. + */ + private void resume() { + if (isClosed()) { + return; + } + + synchronized (audioClip) { + if (isPlaying() && isPaused()) { + pauseResume = true; + paused = false; + audioClip.start(); + setLooped(); + firePlaybackResumed(); + } + } + } +} diff --git a/src/org/infinity/resource/sound/EmptyQueueException.java b/src/org/infinity/resource/sound/EmptyQueueException.java new file mode 100644 index 000000000..1fa59beec --- /dev/null +++ b/src/org/infinity/resource/sound/EmptyQueueException.java @@ -0,0 +1,43 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.sound; + +/** + * An I/O exception that is thrown if the audio queue does not contain any more elements. + */ +public class EmptyQueueException extends Exception { + /** Constructs an {@code EmptyQueueException} with no detail message. */ + public EmptyQueueException() { + super(); + } + + /** + * Constructs an {@code EmptyQueueException} with the specified detail message. + * + * @param message the detail message. + */ + public EmptyQueueException(String message) { + super(message); + } + + /** + * Constructs an {@code EmptyQueueException} with the specified detail message and cause. + * + * @param message the detail message. + * @param cause the cause for this exception to be thrown. + */ + public EmptyQueueException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs an {@code EmptyQueueException} with a cause but no detail message. + * + * @param cause the cause for this exception to be thrown. + */ + public EmptyQueueException(Throwable cause) { + super(cause); + } +} diff --git a/src/org/infinity/resource/sound/SoundResource.java b/src/org/infinity/resource/sound/SoundResource.java index 05919abc2..acde6ef25 100644 --- a/src/org/infinity/resource/sound/SoundResource.java +++ b/src/org/infinity/resource/sound/SoundResource.java @@ -15,24 +15,19 @@ import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.nio.ByteBuffer; -import java.util.HashMap; import java.util.Locale; -import java.util.Timer; -import java.util.TimerTask; import javax.swing.BorderFactory; -import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JComponent; -import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; -import javax.swing.SwingWorker; import org.infinity.NearInfinity; import org.infinity.gui.ButtonPanel; import org.infinity.gui.ButtonPopupMenu; +import org.infinity.gui.SoundPanel; import org.infinity.gui.ViewerUtil; import org.infinity.icon.Icons; import org.infinity.resource.Closeable; @@ -48,59 +43,27 @@ /** * Handles all kinds of supported single track audio files. */ -public class SoundResource implements Resource, ActionListener, ItemListener, Closeable, Referenceable, Runnable { - /** Formatted string with 4 placeholders: elapsed minute, elapsed second, total minutes, total seconds */ - private static final String FMT_PLAY_TIME = "%02d:%02d / %02d:%02d"; - +public class SoundResource implements Resource, ActionListener, ItemListener, Closeable, Referenceable { private static final ButtonPanel.Control PROPERTIES = ButtonPanel.Control.CUSTOM_1; - /** Provides quick access to the "play" and "pause" image icon. */ - private static final HashMap PLAY_ICONS = new HashMap<>(); - - static { - PLAY_ICONS.put(true, Icons.ICON_PLAY_16.getIcon()); - PLAY_ICONS.put(false, Icons.ICON_PAUSE_16.getIcon()); - } - private final ResourceEntry entry; private final ButtonPanel buttonPanel = new ButtonPanel(); + private final SoundPanel soundPanel = new SoundPanel(SoundPanel.Option.TIME_LABEL, SoundPanel.Option.PROGRESS_BAR, + SoundPanel.Option.PROGRESS_BAR_LABELS, SoundPanel.Option.LOOP_CHECKBOX); - private AudioPlayer player; - private AudioBuffer audioBuffer = null; - private JButton bPlay; - private JButton bStop; - private JLabel lTime; private JMenuItem miExport; private JMenuItem miConvert; private JPanel panel; - private boolean isWAV; - private boolean isReference; - private boolean isClosed; public SoundResource(ResourceEntry entry) throws Exception { this.entry = entry; - player = new AudioPlayer(); - isWAV = false; - isReference = (entry.getExtension().equalsIgnoreCase("WAV")); - isClosed = false; } // --------------------- Begin Interface ActionListener --------------------- @Override public void actionPerformed(ActionEvent event) { - if (event.getSource() == bPlay) { - if (player == null || !player.isRunning()) { - new Thread(this).start(); - } else if (player.isRunning()) { - bPlay.setIcon(PLAY_ICONS.get(!player.isPaused())); - player.setPaused(!player.isPaused()); - } - } else if (event.getSource() == bStop) { - bStop.setEnabled(false); - player.stopPlay(); - bPlay.setIcon(PLAY_ICONS.get(true)); - } else if (buttonPanel.getControlByType(ButtonPanel.Control.FIND_REFERENCES) == event.getSource()) { + if (buttonPanel.getControlByType(ButtonPanel.Control.FIND_REFERENCES) == event.getSource()) { searchReferences(panel.getTopLevelAncestor()); } else if (buttonPanel.getControlByType(PROPERTIES) == event.getSource()) { showProperties(); @@ -119,7 +82,7 @@ public void itemStateChanged(ItemEvent event) { ResourceFactory.exportResource(entry, panel.getTopLevelAncestor()); } else if (bpmExport.getSelectedItem() == miConvert) { final String fileName = StreamUtils.replaceFileExtension(entry.getResourceName(), "WAV"); - ByteBuffer buffer = StreamUtils.getByteBuffer(audioBuffer.getAudioData()); + ByteBuffer buffer = StreamUtils.getByteBuffer(soundPanel.getAudioBuffer().getAudioData()); ResourceFactory.exportResource(entry, buffer, fileName, panel.getTopLevelAncestor()); } } @@ -131,13 +94,7 @@ public void itemStateChanged(ItemEvent event) { @Override public void close() throws Exception { - setClosed(true); - if (player != null) { - player.stopPlay(); - player = null; - } - audioBuffer = null; - panel = null; + soundPanel.close(); } // --------------------- End Interface Closeable --------------------- @@ -155,7 +112,7 @@ public ResourceEntry getResourceEntry() { @Override public boolean isReferenceable() { - return isReference; + return entry.getExtension().equalsIgnoreCase("WAV"); } @Override @@ -165,70 +122,19 @@ public void searchReferences(Component parent) { // --------------------- End Interface Referenceable --------------------- - // --------------------- Begin Interface Runnable --------------------- - - @Override - public void run() { - if (bPlay != null) { - bPlay.setIcon(PLAY_ICONS.get(false)); - bStop.setEnabled(true); - } - if (audioBuffer != null) { - final TimerElapsedTask timerTask = new TimerElapsedTask(250L); - try { - timerTask.start(); - player.play(audioBuffer); - } catch (Exception e) { - JOptionPane.showMessageDialog(panel, "Error during playback", "Error", JOptionPane.ERROR_MESSAGE); - Logger.error(e); - } - player.stopPlay(); - timerTask.stop(); - } - if (bPlay != null) { - bStop.setEnabled(false); - bPlay.setIcon(PLAY_ICONS.get(true)); - } - } - - // --------------------- End Interface Runnable --------------------- - // --------------------- Begin Interface Viewable --------------------- @Override public JComponent makeViewer(ViewableContainer container) { - JPanel controlPanel = new JPanel(new GridBagLayout()); - - bPlay = new JButton(PLAY_ICONS.get(true)); - bPlay.addActionListener(this); - GridBagConstraints c = new GridBagConstraints(); - c = ViewerUtil.setGBC(c, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, - new Insets(4, 8, 4, 0), 0, 0); - controlPanel.add(bPlay, c); - - bStop = new JButton(Icons.ICON_STOP_16.getIcon()); - bStop.addActionListener(this); - bStop.setEnabled(false); - c = ViewerUtil.setGBC(c, 1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.REMAINDER, - new Insets(4, 8, 4, 8), 0, 0); - controlPanel.add(bStop, c); - - lTime = new JLabel(String.format(FMT_PLAY_TIME, 0, 0, 0, 0)); - c = ViewerUtil.setGBC(c, 0, 1, 2, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.REMAINDER, - new Insets(4, 8, 4, 8), 0, 0); - controlPanel.add(lTime, c); - - JPanel centerPanel = new JPanel(new BorderLayout()); - centerPanel.add(controlPanel, BorderLayout.CENTER); - - if (isReference) { + if (isReferenceable()) { // only available for WAV resource types ((JButton) buttonPanel.addControl(ButtonPanel.Control.FIND_REFERENCES)).addActionListener(this); } + soundPanel.setDisplayFormat(SoundPanel.DisplayFormat.ELAPSED_TOTAL_PRECISE); miExport = new JMenuItem("original"); miConvert = new JMenuItem("as WAV"); - miConvert.setEnabled(!isWAV); + miConvert.setEnabled(!soundPanel.isWavFile()); ButtonPopupMenu bpmExport = (ButtonPopupMenu) buttonPanel.addControl(ButtonPanel.Control.EXPORT_MENU); bpmExport.setMenuItems(new JMenuItem[] { miExport, miConvert }); bpmExport.addItemListener(this); @@ -238,44 +144,23 @@ public JComponent makeViewer(ViewableContainer container) { bProperties.addActionListener(this); buttonPanel.addControl(bProperties, PROPERTIES); + // wrapper panel prevents the sound panel from auto-scaling + final JPanel soundPanelWrapper = new JPanel(new GridBagLayout()); + final GridBagConstraints gbc = new GridBagConstraints(); + ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, + new Insets(16, 16, 16, 16), 0, 0); + soundPanelWrapper.add(soundPanel, gbc); + panel = new JPanel(new BorderLayout()); - panel.add(centerPanel, BorderLayout.CENTER); + panel.add(soundPanelWrapper, BorderLayout.CENTER); panel.add(buttonPanel, BorderLayout.SOUTH); - centerPanel.setBorder(BorderFactory.createLoweredBevelBorder()); + soundPanelWrapper.setBorder(BorderFactory.createLoweredBevelBorder()); loadSoundResource(); return panel; } - // Updates the time label with total duration and specified elapsed time (in milliseconds). - private synchronized void updateTimeLabel(long elapsed) { - long duration = (audioBuffer != null) ? audioBuffer.getDuration() : 0L; - long em = elapsed / 1000 / 60; - long es = (elapsed / 1000) - (em * 60); - long dm = duration / 1000 / 60; - long ds = (duration / 1000) - (dm * 60); - lTime.setText(String.format(FMT_PLAY_TIME, em, es, dm, ds)); - } - - /** - * Returns a formatted representation of the total duration of the sound clip. - * - * @param exact Whether the seconds part should contain the fractional amount. - * @return A formatted string representing the sound clip duration. - */ - private String getTotalDurationString(boolean exact) { - long duration = (audioBuffer != null) ? audioBuffer.getDuration() : 0L; - long m = duration / 1000 / 60; - if (exact) { - double s = (duration / 1000.0) - (m * 60); - return String.format("%02d:%06.3f", m, s); - } else { - long s = (duration / 1000) - (m * 60); - return String.format("%02d:%02d", m, s); - } - } - // Returns the top level container associated with this viewer private Container getContainer() { if (panel != null) { @@ -287,74 +172,34 @@ private Container getContainer() { private void loadSoundResource() { setLoaded(false); - (new SwingWorker() { - @Override - public Boolean doInBackground() { - return loadAudio(); - } - }).execute(); + try { + soundPanel.loadSound(getResourceEntry(), this::setLoaded); + } catch (Exception e) { + Logger.error(e); + JOptionPane.showMessageDialog(getContainer(), e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); + } } private synchronized void setLoaded(boolean b) { - if (bPlay != null) { - bPlay.setEnabled(b); - bPlay.setIcon(PLAY_ICONS.get(true)); - - updateTimeLabel(0); + if (miConvert != null) { miConvert.setEnabled(b); buttonPanel.getControlByType(PROPERTIES).setEnabled(true); } } - private synchronized void setClosed(boolean b) { - if (b != isClosed) { - isClosed = b; - } - } - - private synchronized boolean isClosed() { - return isClosed; - } - - private boolean loadAudio() { - try { - AudioBuffer.AudioOverride override = null; - AudioBuffer buffer = null; - synchronized (entry) { - // ignore # channels in ACM headers - if (entry.getExtension().equalsIgnoreCase("ACM")) { - override = AudioBuffer.AudioOverride.overrideChannels(2); - } - buffer = AudioFactory.getAudioBuffer(entry, override); - } - if (buffer != null && !isClosed()) { - synchronized (this) { - audioBuffer = buffer; - isWAV = (audioBuffer instanceof WavBuffer); - isReference = (entry.getExtension().compareToIgnoreCase("WAV") == 0); - } - setLoaded(true); - return true; - } - } catch (Exception e) { - Logger.error(e); - JOptionPane.showMessageDialog(getContainer(), e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); - } - return false; - } - /** Shows a message dialog with basic properties of the current sound resource. */ private void showProperties() { - if (audioBuffer == null) { + if (soundPanel.getAudioBuffer() == null) { return; } + final AudioBuffer audioBuffer = soundPanel.getAudioBuffer(); final String resName = entry.getResourceName().toUpperCase(Locale.ENGLISH); String format; int rate; int channels; String channelsDesc; - String duration = getTotalDurationString(true); + String duration = SoundPanel.DisplayFormat.ELAPSED_PRECISE.toString(audioBuffer.getDuration(), 0L); final String extra; if (audioBuffer instanceof OggBuffer) { format = "Ogg Vorbis"; @@ -395,80 +240,15 @@ private void showProperties() { } final String br = "
"; - final StringBuilder sb = new StringBuilder("
"); - sb.append("Format:      ").append(format).append(br); - sb.append("Duration:    ").append(duration).append(br); - sb.append(extra).append(br); - sb.append("Sample Rate: ").append(rate).append(" Hz").append(br); - sb.append("Channels:    ").append(channels).append(channelsDesc).append(br); - sb.append("
"); - JOptionPane.showMessageDialog(panel, sb.toString(), "Properties of " + resName, JOptionPane.INFORMATION_MESSAGE); + String sb = "
" + + "Format:      " + format + br + + "Duration:    " + duration + br + + extra + br + + "Sample Rate: " + rate + " Hz" + br + + "Channels:    " + channels + channelsDesc + br + + "
"; + JOptionPane.showMessageDialog(panel, sb, "Properties of " + resName, JOptionPane.INFORMATION_MESSAGE); } // --------------------- End Interface Viewable --------------------- - - // -------------------------- INNER CLASSES -------------------------- - - private class TimerElapsedTask extends TimerTask { - private final long delay; - - private Timer timer; - private boolean paused; - - /** Initializes a new timer task with the given delay, in milliseconds. */ - public TimerElapsedTask(long delay) { - this.delay = Math.max(1L, delay); - this.timer = null; - this.paused = false; - } - - /** - * Starts a new scheduled run. - */ - public void start() { - if (timer == null) { - timer = new Timer(); - timer.schedule(this, 0L, delay); - } - } - -// /** Returns whether a task has been initialized via {@link #start()}. */ -// public boolean isRunning() { -// return (timer != null); -// } - -// /** Pauses or unpauses a scheduled run. */ -// public void setPaused(boolean paused) { -// this.paused = paused; -// } - -// /** Returns whether a scheduled run is in paused state. */ -// public boolean isPaused() { -// return paused; -// } - - /** Stops a scheduled run. */ - public void stop() { - if (timer != null) { - timer.cancel(); - timer = null; - paused = false; - if (bPlay != null) { - updateTimeLabel(0L); - } - } - } - - @Override - public void run() { - if (!paused && timer != null && player != null && player.getDataLine() != null && bPlay != null) { - updateTimeLabel(player.getDataLine().getMicrosecondPosition() / 1000L); - } - } - } - - public void playSound() { - loadAudio(); - new Thread(this).start(); - } } diff --git a/src/org/infinity/resource/sound/StreamingAudioPlayback.java b/src/org/infinity/resource/sound/StreamingAudioPlayback.java new file mode 100644 index 000000000..ead229d5e --- /dev/null +++ b/src/org/infinity/resource/sound/StreamingAudioPlayback.java @@ -0,0 +1,38 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.sound; + +/** + * Provides specialized playback functionality for streamed audio data. + */ +public interface StreamingAudioPlayback extends AudioPlayback { + /** + * Returns whether the audio queue is empty. Audio may still be available in the currently processed audio buffer. + * + * @return {@code true} if the audio queue is empty, {@code false} otherwise. + */ + boolean isAudioQueueEmpty(); + + /** Removes all remaining {@link AudioBuffer} instances in the audio queue. */ + void clearAudioQueue(); + + /** + * Adds more sound data to the audio queue. + * + * @param audioBuffer {@link AudioBuffer} to add. + * @throws NullPointerException if the {@code audioBuffer} argument or the associated audio data array is + * {@code null}. + * @throws IllegalArgumentException if the audio buffer contains no data. + */ + void addAudioBuffer(AudioBuffer audioBuffer); + + /** + * Removes a single instance of the specified {@link AudioBuffer} object if present in the audio queue. + * + * @param audioBuffer {@link AudioBuffer} object to be removed from the audio queue, if present. + * @return {@code true} if an element could be successfully removed, {@code false} otherwise. + */ + boolean removeAudioBuffer(AudioBuffer audioBuffer); +} diff --git a/src/org/infinity/resource/sound/StreamingAudioPlayer.java b/src/org/infinity/resource/sound/StreamingAudioPlayer.java new file mode 100644 index 000000000..0554bd80a --- /dev/null +++ b/src/org/infinity/resource/sound/StreamingAudioPlayer.java @@ -0,0 +1,505 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.sound; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.DataLine; +import javax.sound.sampled.LineEvent; +import javax.sound.sampled.LineUnavailableException; +import javax.sound.sampled.SourceDataLine; +import javax.sound.sampled.UnsupportedAudioFileException; +import javax.swing.SwingUtilities; + +import org.tinylog.Logger; + +/** + * A class for providing smooth playback of streamed audio data. + *

+ * None of the methods block execution. Playback state changes are propagated through {@link AudioStateEvent}s. + *

+ */ +public class StreamingAudioPlayer extends AbstractAudioPlayer implements StreamingAudioPlayback { + private final ConcurrentLinkedQueue audioBufferQueue = new ConcurrentLinkedQueue<>(); + private final Runner runner = new Runner(); + + private AudioFormat audioFormat; + private SourceDataLine dataLine; + private byte[] bufferBytes; + + private boolean closed; + private boolean playing; + private boolean paused; + + /** Marks pause/resume state changes internally to fire correct state events. */ + private boolean pauseResume; + + /** + * Creates a new audio player and initializes it with the specified audio buffers. + * + * @param audioBuffers Optional {@link AudioBuffer} instances to load into the playback queue. + * @throws NullPointerException if {@code audioBuffer} is {@code null}. + * @throws IOException if an I/O error occurs. + * @throws UnsupportedAudioFileException if the audio data is incompatible with the player. + * @throws LineUnavailableException if the audio line is not available due to resource restrictions. + * @throws IllegalArgumentException if the audio data is invalid. + */ + public StreamingAudioPlayer(AudioBuffer... audioBuffers) throws Exception { + this(null, audioBuffers); + } + + /** + * Creates a new audio player and initializes it with the specified audio buffers. + * + * @param listener {@link AudioStateListener} that receives audio state changes. + * @param audioBuffers Optional {@link AudioBuffer} instances to load into the playback queue. + * @throws NullPointerException if {@code audioBuffer} is {@code null}. + * @throws IOException if an I/O error occurs. + * @throws UnsupportedAudioFileException if the audio data is incompatible with the player. + * @throws LineUnavailableException if the audio line is not available due to resource restrictions. + * @throws IllegalArgumentException if the audio data is invalid. + */ + public StreamingAudioPlayer(AudioStateListener listener, AudioBuffer... audioBuffers) throws Exception { + super(); + addAudioStateListener(listener); + init(); + for (final AudioBuffer ab : audioBuffers) { + addAudioBuffer(ab); + } + } + + @Override + public boolean isAudioQueueEmpty() { + return audioBufferQueue.isEmpty(); + } + + @Override + public void clearAudioQueue() { + audioBufferQueue.clear(); + } + + @Override + public void addAudioBuffer(AudioBuffer audioBuffer) { + if (audioBuffer == null) { + throw new NullPointerException("audioBuffer is null"); + } + if (audioBuffer.getAudioData() == null) { + throw new NullPointerException("audio data is null"); + } + if (audioBuffer.getAudioData().length == 0) { + throw new IllegalArgumentException("No audio data"); + } + + audioBufferQueue.offer(audioBuffer); + runner.signalBufferAvailable(); + } + + @Override + public boolean removeAudioBuffer(AudioBuffer audioBuffer) { + return audioBufferQueue.remove(audioBuffer); + } + + // --------------------- Begin Interface AudioPlayback --------------------- + + @Override + public long getSoundPosition() { + if (isClosed() || dataLine == null) { + return 0L; + } + long position = dataLine.getMicrosecondPosition(); + return position / 1000L; + } + + @Override + public boolean isPlaying() { + return !isClosed() && playing; + } + + @Override + public void setPlaying(boolean play) { + if (isClosed()) { + return; + } + + if (play) { + play(); + } else { + stop(); + } + } + + @Override + public boolean isPaused() { + return isPlaying() && paused; + } + + @Override + public void setPaused(boolean pause) { + if (isClosed()) { + return; + } + + if (pause) { + pause(); + } else { + resume(); + } + } + + @Override + public boolean isClosed() { + return closed; + } + + // --------------------- End Interface AudioPlayback --------------------- + + // --------------------- Begin Interface Closeable --------------------- + + @Override + public void close() throws Exception { + if (isClosed()) { + return; + } + + closed = true; + playing = false; + paused = false; + + runner.close(); + dataLine.stop(); + dataLine.flush(); + dataLine.close(); + + dataLine = null; + audioFormat = null; + bufferBytes = null; + + // removing listeners + final AudioStateListener[] items = getAudioStateListeners(); + for (int i = items.length - 1; i >= 0; i--) { + removeAudioStateListener(items[i]); + } + } + + // --------------------- End Interface Closeable --------------------- + + // --------------------- Begin Interface LineListener --------------------- + + @Override + public void update(LineEvent event) { + if (event.getType() == LineEvent.Type.START) { + if (!pauseResume) { + firePlaybackStarted(); + } else { + pauseResume = false; + } + } else if (event.getType() == LineEvent.Type.STOP) { + if (!pauseResume) { + firePlaybackStopped(); + } else { + pauseResume = false; + } + } else if (event.getType() == LineEvent.Type.OPEN) { + firePlayerOpened(); + } else if (event.getType() == LineEvent.Type.CLOSE) { + firePlayerClosed(); + } + } + + // --------------------- End Interface LineListener --------------------- + + @Override + protected SourceDataLine getLine() { + return dataLine; + } + + /** Fires when the current audio buffer contains no more data. */ + protected void fireBufferEmpty() { + fireAudioStateEvent(AudioStateEvent.State.BUFFER_EMPTY, !isAudioQueueEmpty()); + } + + private void play() { + if (isClosed() || isPlaying()) { + return; + } + + if (dataLine.isRunning()) { + dataLine.stop(); + dataLine.flush(); + } + + playing = true; + paused = false; + dataLine.start(); + runner.signal(); + } + + private void stop() { + if (isClosed() || !isPlaying()) { + return; + } + + final boolean isPaused = isPaused(); + playing = false; + paused = false; + if (isPaused) { + // Pause mode is technically "stop" mode, so we need to trigger a "STOP" event manually + SwingUtilities + .invokeLater(() -> update(new LineEvent(dataLine, LineEvent.Type.STOP, dataLine.getLongFramePosition()))); + } else { + dataLine.stop(); + } + dataLine.flush(); + runner.signal(); + } + + private void pause() { + if (isClosed() || !isPlaying() || isPaused()) { + return; + } + + pauseResume = true; + paused = true; + dataLine.stop(); + firePlaybackPaused(); + } + + private void resume() { + if (isClosed() || !isPlaying() || !isPaused()) { + return; + } + + pauseResume = true; + paused = false; + dataLine.start(); + runner.signal(); + firePlaybackResumed(); + } + + /** + * Creates and returns a new {@link AudioInputStream} instance from the next available {@link AudioBuffer} in the + * audio queue. + * + * @return an {@link AudioInputStream} instance that can be fed directly to the source data line. + * @throws UnsupportedAudioFileException if the stream does not point to valid audio file data recognized by the + * system. + * @throws LineUnavailableException if a matching source data line is not available due to resource restrictions. + * @throws IllegalArgumentException if the audio buffer data is not compatible with the source data line. + * @throws EmptyQueueException if no further audio buffer is queued. + * @throws IOException if an I/O exception occurs. + */ + private AudioInputStream pollAudioBuffer() throws Exception { + final AudioBuffer audioBuffer = audioBufferQueue.poll(); + if (audioBuffer != null) { + final AudioInputStream sourceStream = AudioSystem.getAudioInputStream(new ByteArrayInputStream(audioBuffer.getAudioData())); + if (!AudioSystem.isConversionSupported(audioFormat, sourceStream.getFormat())) { + throw new IllegalArgumentException("Incompatible audio format: " + sourceStream.getFormat()); + } + return AudioSystem.getAudioInputStream(audioFormat, sourceStream); + } else { + throw new EmptyQueueException("No audio buffer available"); + } + } + + /** + * Initializes and opens the audio device for playback. + * + * @throws UnsupportedAudioFileException if the stream does not point to valid audio file data recognized by the + * system. + * @throws LineUnavailableException if a matching source data line is not available due to resource restrictions. + * @throws IllegalArgumentException if the system does not support at least one source data line supporting + * the specified audio format through any installed mixer. + * @throws IOException if an I/O exception occurs. + */ + private void init() throws Exception { + try { + audioFormat = getCompatibleAudioFormat(); + if (audioFormat == null) { + throw new LineUnavailableException("Could not find compatible audio format"); + } + + int bufferSize = getBufferSize(audioFormat, 100); + dataLine = AudioSystem.getSourceDataLine(audioFormat); + dataLine.addLineListener(this); + dataLine.open(audioFormat, bufferSize); + + // allocated buffer size may differ from requested buffer size + bufferSize = dataLine.getBufferSize(); + bufferBytes = new byte[bufferSize]; + } catch (Exception e) { + closed = true; + throw e; + } + } + + /** + * Attempts to find an audio format that can be universally used to play back streamed audio. + * + * @return {@code AudioFormat} definition that is compatible with the default {@link SourceDataLine}, {@code null} + * otherwise. + */ + private static AudioFormat getCompatibleAudioFormat() { + AudioFormat format = null; + + // arrays of sensible values, sorted by usability in descending order + final boolean[] bigEndianArray = { false, true }; + final AudioFormat.Encoding[] encodingArray = { AudioFormat.Encoding.PCM_SIGNED, AudioFormat.Encoding.PCM_UNSIGNED, + AudioFormat.Encoding.PCM_FLOAT }; + final int[] channelsArray = { 2, 1 }; + final int[] sampleBitsArray = { 16, 32, 8 }; + final float[] sampleRateArray = { 48000.0f, 44100.0f, 32000.0f, 24000.0f, 22050.0f }; + + // for-loops sorted by importance in ascending order (innermost loop: highest importance) + out: // label for outermost loop: saves us to put conditional breaks in any of the nested loops + for (final boolean bigEndian : bigEndianArray) { + for (final AudioFormat.Encoding encoding : encodingArray) { + for (final int channels : channelsArray) { + for (final int sampleBits : sampleBitsArray) { + for (final float sampleRate : sampleRateArray) { + final int frameSize = sampleBits * channels / 8; + final float frameRate = sampleRate * frameSize; + final AudioFormat f = new AudioFormat(encoding, sampleRate, sampleBits, channels, frameSize, frameRate, + bigEndian); + final DataLine.Info info = new DataLine.Info(SourceDataLine.class, f); + if (AudioSystem.isLineSupported(info)) { + format = f; + break out; + } + } + } + } + } + } + return format; + } + + /** + * Calculates the buffer size to hold {@code lagMs} millisecond worth of audio data. + * + * @param format {@link AudioFormat} of the data. + * @param lagMs Storage capacity of the internal audio buffer, in milliseconds. Supported range: 10 to 1000 + * @return Buffer size in bytes. Buffer size always represents an integral number of audio frames. Returns a default + * buffer size if the audio format could not be determined. + */ + private static int getBufferSize(AudioFormat format, int lagMs) { + int retVal = 0x4000; + + if (format != null) { + // allowed sound lag: 10..1000 ms + lagMs = Math.max(10, Math.min(1000, lagMs)); + int bufSize = (int)(format.getFrameRate() * format.getFrameSize() * lagMs / 1000.0f); + retVal = Math.max(0x100, Math.min(0x80000, bufSize)); + if (retVal % format.getFrameSize() != 0) { + retVal = (retVal / format.getFrameSize() * format.getFrameSize()) + format.getFrameSize(); + } + } + + return retVal; + } + + // -------------------------- INNER CLASSES -------------------------- + + /** + * Handles feeding audio data to the source line without blocking the main execution thread. + */ + private class Runner implements Runnable { + private final AtomicBoolean waitingForData = new AtomicBoolean(); + private final Thread thread; + + boolean running; + + public Runner() { + running = true; + thread = new Thread(this); + thread.start(); + } + + /** Returns whether the background task is active. */ + public boolean isRunning() { + return running; + } + + /** Signals the runner to terminate the background task. */ + public void close() { + running = false; + signal(); + } + + /** Specialized signal that is triggered only if the background task waits for more audio data to play back. */ + public void signalBufferAvailable() { + if (isRunning() && waitingForData.get()) { + signal(); + } + } + + /** Signals the background task to reevaluate state changes. */ + public void signal() { + if (isRunning()) { + thread.interrupt(); + } + } + + /** Puts the current thread to sleep until interrupted. */ + private void sleep() { + sleep(Long.MAX_VALUE); + } + + /** Puts the current thread to sleep until interrupted or the specified time has passed. */ + private void sleep(long millis) { + try { + Thread.sleep(Math.max(0, millis)); + } catch (InterruptedException e) { + // waking up + } + } + + @Override + public void run() { + while (isRunning()) { + if (isPlaying()) { + try (final AudioInputStream ais = pollAudioBuffer()) { + int readBytes = -1; + while (isPlaying() && (readBytes = ais.read(bufferBytes)) != -1) { + final int numWritten = dataLine.write(bufferBytes, 0, readBytes); + + if (isPaused()) { + sleep(); + if (isPlaying() && !isPaused() && numWritten < readBytes) { + // prevent clicks or "hickups" in audio playback + dataLine.write(bufferBytes, numWritten, readBytes - numWritten); + } + } + } + + if (readBytes == -1) { + fireBufferEmpty(); + } + } catch (EmptyQueueException e) { + // no audio queued + fireBufferEmpty(); + } catch (Exception e) { + fireError(e); + Logger.error(e); + } + + if (isPlaying() && !isPaused() && isAudioQueueEmpty()) { + // waiting for more audio data or a state change + waitingForData.set(true); + sleep(); + waitingForData.set(false); + } + } else { + sleep(); + } + } + } + } +} diff --git a/src/org/infinity/resource/sound/WavBuffer.java b/src/org/infinity/resource/sound/WavBuffer.java index ed82a1516..898d028da 100644 --- a/src/org/infinity/resource/sound/WavBuffer.java +++ b/src/org/infinity/resource/sound/WavBuffer.java @@ -141,7 +141,7 @@ private static class WaveFmt { private static final short ID_TYPE_PCM = 0x01; // PCM private static final short ID_TYPE_ADPCM = 0x11; // IMA ADPCM - private static final HashSet AUDIO_TYPES_SET = new HashSet<>(Arrays.asList(new Short[] { ID_TYPE_PCM, ID_TYPE_ADPCM })); + private static final HashSet AUDIO_TYPES_SET = new HashSet<>(Arrays.asList(ID_TYPE_PCM, ID_TYPE_ADPCM)); private final AudioOverride override; diff --git a/src/org/infinity/resource/sto/StoResource.java b/src/org/infinity/resource/sto/StoResource.java index 2c1ba64ec..a1d698025 100644 --- a/src/org/infinity/resource/sto/StoResource.java +++ b/src/org/infinity/resource/sto/StoResource.java @@ -4,14 +4,26 @@ package org.infinity.resource.sto; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; import javax.swing.BorderFactory; import javax.swing.JComponent; +import javax.swing.JMenuItem; import javax.swing.JScrollPane; +import org.infinity.NearInfinity; import org.infinity.datatype.Bitmap; import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; @@ -23,6 +35,9 @@ import org.infinity.datatype.TextString; import org.infinity.datatype.Unknown; import org.infinity.datatype.UnsignDecNumber; +import org.infinity.gui.ButtonPanel; +import org.infinity.gui.ButtonPopupMenu; +import org.infinity.gui.ItemCategoryOrderDialog; import org.infinity.gui.StructViewer; import org.infinity.gui.hexview.BasicColorMap; import org.infinity.gui.hexview.StructHexViewer; @@ -32,11 +47,15 @@ import org.infinity.resource.HasViewerTabs; import org.infinity.resource.Profile; import org.infinity.resource.Resource; +import org.infinity.resource.ResourceFactory; import org.infinity.resource.StructEntry; +import org.infinity.resource.ViewableContainer; import org.infinity.resource.key.ResourceEntry; import org.infinity.search.SearchOptions; import org.infinity.util.Logger; import org.infinity.util.StringTable; +import org.infinity.util.Table2da; +import org.infinity.util.Table2daCache; import org.infinity.util.io.StreamUtils; /** @@ -46,7 +65,8 @@ * @see * https://gibberlings3.github.io/iesdp/file_formats/ie_formats/sto_v1.htm */ -public final class StoResource extends AbstractStruct implements Resource, HasChildStructs, HasViewerTabs { +public final class StoResource extends AbstractStruct + implements Resource, HasChildStructs, HasViewerTabs, ActionListener { // STO-specific field labels public static final String STO_TYPE = "Type"; public static final String STO_NAME = "Name"; @@ -74,6 +94,11 @@ public final class StoResource extends AbstractStruct implements Resource, HasCh public static final String STO_OFFSET_CURES = "Cures for sale offset"; public static final String STO_NUM_CURES = "# cures for sale"; + private static final String SORT_ORDER_SUGGESTED = "sortOrderSuggested"; + private static final String SORT_ORDER_ASCENDING = "sortOrderAscending"; + private static final String SORT_ORDER_DESCENDING = "sortOrderDescending"; + private static final String SORT_ORDER_CUSTOMIZE = "sortOrderCustomize"; + // private static final String[] TYPE_ARRAY = {"Store", "Tavern", "Inn", "Temple"}; public static final String[] TYPE9_ARRAY = { "Store", "Tavern", "Inn", "Temple", "Container" }; @@ -102,6 +127,16 @@ public StoResource(ResourceEntry entry) throws Exception { super(entry); } + @Override + public JComponent makeViewer(ViewableContainer container) { + final JComponent c = super.makeViewer(container); + if (c instanceof StructViewer) { + final StructViewer viewer = (StructViewer) c; + addSortItemsButton(viewer); + } + return c; + } + @Override public AddRemovable[] getPrototypes() throws Exception { TextString version = (TextString) getAttribute(COMMON_VERSION); @@ -304,6 +339,158 @@ protected void datatypeRemovedInChild(AbstractStruct child, AddRemovable datatyp } } + @Override + public void actionPerformed(ActionEvent e) { + switch (e.getActionCommand()) { + case SORT_ORDER_ASCENDING: + sortItems(Comparator.comparingInt(a -> a)); + break; + case SORT_ORDER_DESCENDING: + sortItems((a, b) -> b - a); + break; + case SORT_ORDER_SUGGESTED: + case SORT_ORDER_CUSTOMIZE: + { + final int[] indexMap; + if (SORT_ORDER_SUGGESTED.equals(e.getActionCommand())) { + indexMap = ItemCategoryOrderDialog.getDefaultCategoryIndices(isPstStore()); + } else { + final boolean interactive = (e.getModifiers() & KeyEvent.CTRL_MASK) == 0; + indexMap = getCustomSortOrder(interactive); + } + sortItems((a, b) -> { + final int a1 = (a >= 0 && a < indexMap.length) ? indexMap[a] : a; + final int b1 = (b >= 0 && b < indexMap.length) ? indexMap[b] : b; + return a1 - b1; + }); + break; + } + } + } + + /** Returns {@code true} if the current resource is a STOR V1.1 resource. */ + private boolean isPstStore() { + boolean retVal = false; + final StructEntry se = getAttribute(COMMON_VERSION); + if (se instanceof TextString) { + retVal = ((TextString)se).getText().equals("V1.1"); + } + return retVal; + } + + /** + * Returns a custom load order to use by the sort operation. + * + * @param interactive Indicates whether to open a dialog where the user can further customize the sort order. + * @return An index map for item categories. Returns {@code null} if the user cancelled the operation. + */ + private int[] getCustomSortOrder(boolean interactive) { + final boolean isPST = isPstStore(); + int[] indexMap = null; + if (interactive) { + final Window wnd; + if (getViewer().getTopLevelAncestor() instanceof Window) { + wnd = (Window)getViewer().getTopLevelAncestor(); + } else { + wnd = NearInfinity.getInstance(); + } + final ItemCategoryOrderDialog dlg = new ItemCategoryOrderDialog(wnd, isPST); + if (dlg.isAccepted()) { + indexMap = dlg.getCategoryIndices(); + } + } else { + indexMap = ItemCategoryOrderDialog.loadCategoryIndices(ItemCategoryOrderDialog.getPreferencesKey(isPST)); + if (indexMap == null) { + indexMap = ItemCategoryOrderDialog.getDefaultCategoryIndices(isPST); + } + } + + return indexMap; + } + + /** + * Adds a new menu button to the button bar that allows the user to sort items for sale. + * + * @param viewer {@link StructViewer} instance of the STO resource. + */ + private void addSortItemsButton(StructViewer viewer) { + if (viewer != null) { + final ButtonPanel buttons = viewer.getButtonPanel(); + final ButtonPopupMenu bpmSort = new ButtonPopupMenu("Sort items..."); + final JMenuItem miSuggested = new JMenuItem("In suggested order"); + miSuggested.setToolTipText("Sort order: weapons, armor, jewelry, potions, scrolls, wands, ..."); + miSuggested.setActionCommand(SORT_ORDER_SUGGESTED); + miSuggested.addActionListener(this); + final JMenuItem miAscending = new JMenuItem("In ascending order"); + miAscending.setToolTipText("Sort by item category number in ascending order."); + miAscending.setActionCommand(SORT_ORDER_ASCENDING); + miAscending.addActionListener(this); + final JMenuItem miDescending = new JMenuItem("In descending order"); + miDescending.setToolTipText("Sort by item category number in descending order."); + miDescending.setActionCommand(SORT_ORDER_DESCENDING); + miDescending.addActionListener(this); + final JMenuItem miCustomize = new JMenuItem("In user-defined order..."); + final String keyName = (Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() == KeyEvent.CTRL_MASK) ? "CTRL" : "COMMAND"; + miCustomize.setToolTipText("Specify sort order manually. Press " + keyName + " key to auto-apply last used item category order."); + miCustomize.setActionCommand(SORT_ORDER_CUSTOMIZE); + miCustomize.addActionListener(this); + bpmSort.addItem(miSuggested); + bpmSort.addItem(miAscending); + bpmSort.addItem(miDescending); + bpmSort.addItem(miCustomize); + buttons.addControl(0, bpmSort, ButtonPanel.Control.CUSTOM_1); + } + } + + /** + * Sorts {@link ItemSale} or{@link ItemSale11} structures by their item category index. + * Category index of non-existing items is treated as 0. + * + * @param cmp A {@link Comparator} object that compares item category indices. + */ + private void sortItems(Comparator cmp) { + // assembling item list + final SectionOffset soItemsForSale = (SectionOffset) getAttribute(STO_OFFSET_ITEMS_FOR_SALE); + if (soItemsForSale == null) { + return; + } + final Class itemClass = soItemsForSale.getSection(); + final List fieldList = getFields(itemClass); + + final ArrayList> itemList = new ArrayList<>(); + for (final StructEntry se : fieldList) { + try { + if (se instanceof ItemSale) { + final ItemSale newItem = (ItemSale) ((ItemSale)se).clone(); + itemList.add(new SortableItem<>(newItem)); + } else if (se instanceof ItemSale11) { + itemList.add(new SortableItem<>(new ItemSale11(null, se.getDataBuffer(), 0, 0))); + } + } catch (Exception e) { + Logger.error(e); + } + } + + // sorting item list + itemList.sort((a, b) -> cmp.compare(a.getCategory(), b.getCategory())); + + // replacing existing item list + for (int i = 0, size = Math.min(itemList.size(), fieldList.size()); i < size; i++) { + final AbstractStruct curItem = (AbstractStruct) fieldList.get(i); + final AbstractStruct newItem = itemList.get(i).getItem(); + newItem.setOffset(curItem.getOffset()); + // TODO: externalize name generation to separate function + if (newItem instanceof ItemSale11) { + newItem.setName(ItemSale11.STO_SALE + " " + i); + } else { + newItem.setName(ItemSale.STO_SALE + " " + i); + } + replaceField(newItem); + } + fireTableDataChanged(); + setStructChanged(!fieldList.isEmpty()); + } + /** * Checks whether the specified resource entry matches all available search options. Called by "Extended Search" */ @@ -451,4 +638,85 @@ public static boolean matchSearchOptions(ResourceEntry entry, SearchOptions sear } return false; } + + // -------------------------- INNER CLASSES -------------------------- + + /** Helper class that associates an {@link ItemSale} or {@link ItemSale11} instance with the related item category. */ + private static class SortableItem { + private final T item; + private final int category; + + public SortableItem(T item) { + this.item = Objects.requireNonNull(item); + this.category = readItemCategory(this.item, Integer.MAX_VALUE); + } + + /** Returns the wrapped item instance ({@link ItemSale} or {@link ItemSale11}). */ + public T getItem() { + return item; + } + + /** Returns the category index of the item. Returns {@link Integer#MAX_VALUE} if not available. */ + public int getCategory() { + return category; + } + + /** + * Fetches the item category from the ITM resref stored in the item's resref field. Returns a default category if + * not available. + * + * @param item The {@link ItemSale} or {@link ItemSale11} object. + * @param defCat A default category to return if ITM resource is not available. + * @return Item category index. + */ + private int readItemCategory(T item, int defCat) { + if (item != null) { + final StructEntry se = item.getAttribute(ItemSale.STO_SALE_ITEM); + if (se instanceof ResourceRef) { + final String itemResref = resolveRandomItem(((ResourceRef)se).getText()); + if (itemResref != null && !itemResref.isEmpty()) { + final ResourceEntry re = ResourceFactory.getResourceEntry(itemResref + ".ITM"); + if (re != null) { + try { + final ByteBuffer bb = re.getResourceBuffer(); + return bb.order(ByteOrder.LITTLE_ENDIAN).getShort(0x1c); + } catch (Exception e) { + Logger.info("Could not read item category from {}: {}", re, e.getMessage()); + } + } + } + } + } + return defCat; + } + + /** + * Attempts to resolve random treasure to actual items. + * This is currently only performed for IWD2. + * Random treasure in other games doesn't appear to be resolved in stores. + */ + private String resolveRandomItem(String itmResref) { + if (itmResref == null || itmResref.isEmpty()) { + return itmResref; + } + + String retVal = itmResref; + if (Profile.getGame() == Profile.Game.IWD2) { + final Table2da table = Table2daCache.get("rt_norm.2da"); + if (table != null) { + for (int row = 0, numRows = table.getRowCount(); row < numRows; ++row) { + final String s = table.get(row, 0); + if (itmResref.equalsIgnoreCase(s)) { + if (table.getColCount(row) > 1) { + retVal = table.get(row, 1); + } + break; + } + } + } + } + + return retVal; + } + } } diff --git a/src/org/infinity/resource/sto/Viewer.java b/src/org/infinity/resource/sto/Viewer.java index 7d640b255..7105910e0 100644 --- a/src/org/infinity/resource/sto/Viewer.java +++ b/src/org/infinity/resource/sto/Viewer.java @@ -37,6 +37,8 @@ private static JPanel makeFieldPanel(StoResource sto) { if (capacityEntry != null && ((IsNumeric) sto.getAttribute(StoResource.STO_STORAGE_CAPACITY)).getValue() > 0) { ViewerUtil.addLabelFieldPair(fieldPanel, sto.getAttribute(StoResource.STO_STORAGE_CAPACITY), gbl, gbc, true); } + ViewerUtil.addLabelFieldPair(fieldPanel, sto.getAttribute(StoResource.STO_RUMORS_DRINKS), gbl, gbc, true); + ViewerUtil.addLabelFieldPair(fieldPanel, sto.getAttribute(StoResource.STO_RUMORS_DONATIONS), gbl, gbc, true); return fieldPanel; } diff --git a/src/org/infinity/resource/text/PlainTextResource.java b/src/org/infinity/resource/text/PlainTextResource.java index 1a4461325..0f95aa622 100644 --- a/src/org/infinity/resource/text/PlainTextResource.java +++ b/src/org/infinity/resource/text/PlainTextResource.java @@ -254,7 +254,7 @@ public static String alignTableColumns(String text, int spaces, boolean alignPer } } } - newText.append(sb.toString()).append(newline); + newText.append(sb).append(newline); } retVal = newText.toString(); } @@ -369,11 +369,11 @@ public PlainTextResource(ResourceEntry entry) throws Exception { @Override public void actionPerformed(ActionEvent event) { if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE) == event.getSource()) { - if (ResourceFactory.saveResource(this, panel.getTopLevelAncestor())) { + if (ResourceFactory.saveResource(this, panel.getTopLevelAncestor()).isTrue()) { resourceChanged = false; } } else if (buttonPanel.getControlByType(ButtonPanel.Control.SAVE_AS) == event.getSource()) { - if (ResourceFactory.saveResourceAs(this, panel.getTopLevelAncestor())) { + if (ResourceFactory.saveResourceAs(this, panel.getTopLevelAncestor()).isTrue()) { resourceChanged = false; } } else if (buttonPanel.getControlByType(ButtonPanel.Control.FIND_REFERENCES) == event.getSource()) { @@ -582,9 +582,9 @@ public JComponent makeViewer(ViewableContainer container) { @Override public void write(OutputStream os) throws IOException { if (editor == null) { - StreamUtils.writeString(os, text, text.length()); + StreamUtils.writeString(os, text, text.length(), Profile.getDefaultCharset()); } else { - editor.write(new OutputStreamWriter(os)); + editor.write(new OutputStreamWriter(os, Profile.getDefaultCharset())); } } @@ -679,7 +679,7 @@ private void setSyntaxHighlightingEnabled(InfinityTextArea edit, InfinityScrollP private String applyTransformText(String data) { if (data == null) { - return data; + return null; } final String ext = (entry != null) ? entry.getExtension() : ""; diff --git a/src/org/infinity/resource/text/QuestsPanel.java b/src/org/infinity/resource/text/QuestsPanel.java index 00c47c12f..06873342d 100644 --- a/src/org/infinity/resource/text/QuestsPanel.java +++ b/src/org/infinity/resource/text/QuestsPanel.java @@ -163,7 +163,7 @@ public Object getValueAt(int rowIndex, int columnIndex) { } } - private class CompletedCellRenderer extends DefaultTableCellRenderer { + private static class CompletedCellRenderer extends DefaultTableCellRenderer { protected final Color defaultBackground = getBackground(); @SuppressWarnings("unchecked") diff --git a/src/org/infinity/resource/text/QuestsResource.java b/src/org/infinity/resource/text/QuestsResource.java index e100d4363..9ef4428ea 100644 --- a/src/org/infinity/resource/text/QuestsResource.java +++ b/src/org/infinity/resource/text/QuestsResource.java @@ -191,7 +191,7 @@ public enum Condition { /** Humanized name оf condition operator. */ final String title; - private Condition(String title) { + Condition(String title) { this.title = title; } } @@ -203,7 +203,7 @@ public enum State { /** Quest taken by player but not yet completed, active quest. */ ASSIGNED, /** Quest is finished. */ - COMPLETED; + COMPLETED } public QuestsResource() throws Exception { diff --git a/src/org/infinity/resource/to/StringEntry.java b/src/org/infinity/resource/to/StringEntry.java index 5fc12053b..5c24ef508 100644 --- a/src/org/infinity/resource/to/StringEntry.java +++ b/src/org/infinity/resource/to/StringEntry.java @@ -15,7 +15,7 @@ public final class StringEntry extends AbstractStruct { // TOT/StringEntry-specific field labels public static final String TOT_STRING = "String entry"; public static final String TOT_STRING_OFFSET_FREE_REGION = "Offset to next free region"; - public static final String TOT_STRING_OFFSET_PREV_ENTRY = "Offset of preceeding entry"; + public static final String TOT_STRING_OFFSET_PREV_ENTRY = "Offset of preceding entry"; public static final String TOT_STRING_TEXT = "Text"; public static final String TOT_STRING_OFFSET_NEXT_ENTRY = "Offset of following entry"; diff --git a/src/org/infinity/resource/to/TohResource.java b/src/org/infinity/resource/to/TohResource.java index d8ce327d9..2de81675e 100644 --- a/src/org/infinity/resource/to/TohResource.java +++ b/src/org/infinity/resource/to/TohResource.java @@ -160,12 +160,11 @@ public static String getOverrideString(ResourceEntry tohEntry, ResourceEntry tot * @return String referenced by {@code strref} if found, {@code null} otherwise. */ public static String getOverrideString(TohResource toh, TotResource tot, int strref) { - String retVal = null; - if (strref < 0 || toh == null || (!Profile.isEnhancedEdition() && tot == null)) { - return retVal; + return null; } + String retVal = null; if (Profile.isEnhancedEdition()) { // Only TOH resource is needed IsNumeric so = (IsNumeric) toh.getAttribute(TohResource.TOH_OFFSET_ENTRIES); diff --git a/src/org/infinity/resource/to/TotResource.java b/src/org/infinity/resource/to/TotResource.java index f084436ab..ddd9b7c42 100644 --- a/src/org/infinity/resource/to/TotResource.java +++ b/src/org/infinity/resource/to/TotResource.java @@ -152,7 +152,7 @@ private TohResource loadAssociatedToh(ResourceEntry totResource) { String fileName = totPath.getName(totPath.getNameCount() - 1).toString(); char ch = fileName.charAt(fileName.length() - 1); // last character of file extension (TOT) ch -= 12; // TOT -> TOH (considers case) - fileName = fileName.substring(0, fileName.length() - 1) + String.valueOf(ch); + fileName = fileName.substring(0, fileName.length() - 1) + ch; final Path tohPath = FileManager.queryExisting(totPath.getParent(), fileName); try { toh = new TohResource(new FileResourceEntry(tohPath)); diff --git a/src/org/infinity/resource/video/MveAudioDecoder.java b/src/org/infinity/resource/video/MveAudioDecoder.java index 871448829..39b6246b1 100644 --- a/src/org/infinity/resource/video/MveAudioDecoder.java +++ b/src/org/infinity/resource/video/MveAudioDecoder.java @@ -218,7 +218,7 @@ private void processCompressedAudio(MveSegment segment) throws Exception { } updateTimer(len); - short[] predictor = null; + short[] predictor; byte[] block = null; AudioQueue queue; for (int i = 0; i < MveInfo.AUDIOSTREAMS_MAX; i++) { diff --git a/src/org/infinity/resource/video/MvePlayer.java b/src/org/infinity/resource/video/MvePlayer.java index 749291118..4ace303fa 100644 --- a/src/org/infinity/resource/video/MvePlayer.java +++ b/src/org/infinity/resource/video/MvePlayer.java @@ -114,7 +114,7 @@ public void play(ImageRenderer renderer, MveDecoder decoder) throws Exception { } // waiting for the next frame to be displayed - sleepUntil(5000000L); + sleepUntil(5_000_000L); renderer.updateRenderer(); audioQueue.skipNext(); } @@ -203,7 +203,7 @@ private void setTimerDelay(long nanoseconds) { } private long timeRemaining() { - long res = 0L; + long res; long curTime = System.nanoTime() & Long.MAX_VALUE; if (curTime < startTime) { res = delayTime - (Long.MAX_VALUE - startTime + curTime); @@ -218,7 +218,7 @@ private long timeRemaining() { // waits until only 'remaining' time (in ns) of the current timer remains private void sleepUntil(long remaining) { - if (timeRemaining() > 2000000L) { + if (timeRemaining() > 2_000_000L) { while (timeRemaining() > remaining) { // sleep as much as possible try { @@ -228,7 +228,7 @@ private void sleepUntil(long remaining) { } } } - if (timeRemaining() <= 2000000L) { + if (timeRemaining() <= 2_000_000L) { while (timeRemaining() > remaining) { // waste remaining nanoseconds try { diff --git a/src/org/infinity/resource/video/MveResource.java b/src/org/infinity/resource/video/MveResource.java index a7e0ff659..ff941cbe9 100644 --- a/src/org/infinity/resource/video/MveResource.java +++ b/src/org/infinity/resource/video/MveResource.java @@ -231,7 +231,7 @@ public void run() { buttonPanel.getControlByType(CTRL_STOP).setEnabled(false); } - // --------------------- End Interface Runable --------------------- + // --------------------- End Interface Runnable --------------------- // --------------------- Begin Interface Viewable --------------------- @@ -402,7 +402,7 @@ public static boolean convertAvi(ResourceEntry inEntry, Path outFile, Window par int trackVideo = writer.addTrack(videoFormat); // initializing audio track - Format audioFormat = null; + Format audioFormat; int channels = decoder.getAudioFormat().getChannels(); int sampleRate = (int) decoder.getAudioFormat().getSampleRate(); int sampleBits = decoder.getAudioFormat().getSampleSizeInBits(); diff --git a/src/org/infinity/resource/video/MveVideoDecoder.java b/src/org/infinity/resource/video/MveVideoDecoder.java index 9a637e125..d11b191ea 100644 --- a/src/org/infinity/resource/video/MveVideoDecoder.java +++ b/src/org/infinity/resource/video/MveVideoDecoder.java @@ -31,10 +31,10 @@ public class MveVideoDecoder { private BufferedImage blackImage; // used when no video buffer has been updated in the current video chunk private MveSegment codeSegment; // contains the code map of the last MVE_OC_CODE_MAP segment private boolean isVideoDrawn; // set when the current video buffer has been updated - private boolean isVideoInit; // set when video initialization occured in the current chunk + private boolean isVideoInit; // set when video initialization occurred in the current chunk /** - * Returns a new MveVideoDecoder object, asociated with the specified MveDecoder. + * Returns a new MveVideoDecoder object, associated with the specified MveDecoder. * * @param info The parent MveDecoder object * @return A new MveVideoDecoder object associated with the specified MveDecoder or null on error. @@ -186,7 +186,7 @@ private void processOutputFrame(MveSegment segment) throws Exception { if (info.videoOutput != null) { // image is drawn centered Image dstImage = info.videoOutput.backBuffer(); - Image srcImage = null; + Image srcImage; if (isVideoDrawn) { srcImage = workingBuffer.frontBuffer(); } else if (blackImage != null) { diff --git a/src/org/infinity/resource/video/VideoBuffer.java b/src/org/infinity/resource/video/VideoBuffer.java index 495223ea7..737925607 100644 --- a/src/org/infinity/resource/video/VideoBuffer.java +++ b/src/org/infinity/resource/video/VideoBuffer.java @@ -16,7 +16,7 @@ public interface VideoBuffer { * @return The BufferedImage object of the currently visible image. * @see #backBuffer() */ - public Image frontBuffer(); + Image frontBuffer(); /** * Returns the buffer that is prepared to be shown. @@ -24,20 +24,20 @@ public interface VideoBuffer { * @return The BufferedImage object of the image in preparation. * @see #frontBuffer() */ - public Image backBuffer(); + Image backBuffer(); /** * The buffer chain advances one step forward (e.g. in a double buffered chain, the front buffer becomes the back * buffer and vice versa). Optional data attached to the front buffer should be discarded. */ - public void flipBuffers(); + void flipBuffers(); /** * Returns the number of video buffers in the buffer chain. * * @return Number of video buffers in the buffer chain. */ - public int bufferCount(); + int bufferCount(); /** * Attaches the specified data object to the current back buffer. Note: The object's life time should be @@ -46,7 +46,7 @@ public interface VideoBuffer { * @param data The data object that will be attached to the current back buffer. * @see #fetchData() */ - public void attachData(Object data); + void attachData(Object data); /** * Returns the data object associated with the current front buffer. The data should be discarded automatically with @@ -55,5 +55,5 @@ public interface VideoBuffer { * @return The data object associated with the current front buffer, or {@code null} if no data is available. * @see #attachData(Object) */ - public Object fetchData(); + Object fetchData(); } diff --git a/src/org/infinity/resource/video/WbmResource.java b/src/org/infinity/resource/video/WbmResource.java index 96920edc7..e66e00393 100644 --- a/src/org/infinity/resource/video/WbmResource.java +++ b/src/org/infinity/resource/video/WbmResource.java @@ -154,10 +154,10 @@ public JComponent makeViewer(ViewableContainer container) { // Returns a (temporary) file based on the current WBM resource private Path getVideoFile() { - Path retVal = null; + Path retVal; if (entry instanceof FileResourceEntry - && FileManager.isDefaultFileSystem(((FileResourceEntry) entry).getActualPath())) { - retVal = ((FileResourceEntry) entry).getActualPath(); + && FileManager.isDefaultFileSystem(entry.getActualPath())) { + retVal = entry.getActualPath(); isTempFile = false; } else { String fileBase = entry.getResourceRef(); diff --git a/src/org/infinity/resource/wed/Door.java b/src/org/infinity/resource/wed/Door.java index 2299c3f46..37aee0b83 100644 --- a/src/org/infinity/resource/wed/Door.java +++ b/src/org/infinity/resource/wed/Door.java @@ -80,7 +80,7 @@ public void updatePolygonsOffset(int offset) { int polyOffset = Integer.MAX_VALUE; for (final StructEntry o : getFields()) { if (o instanceof Polygon) { - polyOffset = Math.min(polyOffset, ((Polygon) o).getOffset()); + polyOffset = Math.min(polyOffset, o.getOffset()); } } if (polyOffset != Integer.MAX_VALUE) { diff --git a/src/org/infinity/resource/wed/WedResource.java b/src/org/infinity/resource/wed/WedResource.java index 6d7284414..498e22485 100644 --- a/src/org/infinity/resource/wed/WedResource.java +++ b/src/org/infinity/resource/wed/WedResource.java @@ -9,6 +9,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; +import java.util.Comparator; import javax.swing.JComponent; @@ -220,7 +221,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception { HexNumber[] offsets = new HexNumber[] { offsetOverlays, offsetHeader2, offsetDoors, offsetDoortile, offsetPolygons, offsetWallgroups, offsetPolytable, new HexNumber( ByteBuffer.wrap(Misc.intToArray(buffer.limit() - startOffset)).order(ByteOrder.LITTLE_ENDIAN), 0, 4, "") }; - Arrays.sort(offsets, (s1, s2) -> s1.getValue() - s2.getValue()); + Arrays.sort(offsets, Comparator.comparingInt(DecNumber::getValue)); offset = offsetDoors.getValue(); for (int i = 0; i < countDoors.getValue(); i++) { diff --git a/src/org/infinity/resource/wmp/ViewerMap.java b/src/org/infinity/resource/wmp/ViewerMap.java index 6f4c66210..60fb3b72a 100644 --- a/src/org/infinity/resource/wmp/ViewerMap.java +++ b/src/org/infinity/resource/wmp/ViewerMap.java @@ -314,7 +314,7 @@ private void showMapIconLabels() { // printing label if (!mapCode.isEmpty()) { - String[] labels = null; + String[] labels; if (mapName != null) { labels = new String[2]; labels[0] = mapName; @@ -859,7 +859,7 @@ private String getListValue(Object value, boolean showFull) { StringRef areaName = (StringRef) struct.getAttribute(AreaEntry.WMP_AREA_NAME); IsReference areaRef = (IsReference) struct.getAttribute(AreaEntry.WMP_AREA_CURRENT); - String text1 = null, text2 = null; + String text1, text2; if (areaName.getValue() >= 0) { StringTable.Format fmt = BrowserMenuBar.getInstance().getOptions().showStrrefs() ? StringTable.Format.STRREF_SUFFIX : StringTable.Format.NONE; diff --git a/src/org/infinity/resource/wmp/WmpResource.java b/src/org/infinity/resource/wmp/WmpResource.java index 718db1425..d54377b7e 100644 --- a/src/org/infinity/resource/wmp/WmpResource.java +++ b/src/org/infinity/resource/wmp/WmpResource.java @@ -29,7 +29,7 @@ /** * This resource describes the top-level map structure of the game. It details the x/y coordinate location of areas, the * graphics used to represent the area on the map (both {@link MosResource MOS} and {@link BamResource BAM}) and stores - * flag information used to decide how the map icon is displayed (visable, reachable, already visited etc.) + * flag information used to decide how the map icon is displayed (visible, reachable, already visited etc.) *

* Engine specific notes: Areas may be also displayed on the WorldMap in ToB using 2DA files: *

    diff --git a/src/org/infinity/search/ReferenceSearcher.java b/src/org/infinity/search/ReferenceSearcher.java index c8955b370..47ad30d69 100644 --- a/src/org/infinity/search/ReferenceSearcher.java +++ b/src/org/infinity/search/ReferenceSearcher.java @@ -51,7 +51,7 @@ public ReferenceSearcher(ResourceEntry targetEntry, String[] fileTypes, Componen CreResource cre = new CreResource(targetEntry); StructEntry nameEntry = cre.getAttribute(CreResource.CRE_SCRIPT_NAME); if (nameEntry instanceof TextString) { - creDeathVar = ((TextString) nameEntry).toString().trim(); + creDeathVar = nameEntry.toString().trim(); // ignore specific script names if (creDeathVar.isEmpty() || creDeathVar.equalsIgnoreCase("None")) { creDeathVar = null; diff --git a/src/org/infinity/search/SearchOptions.java b/src/org/infinity/search/SearchOptions.java index 3f3fafe5c..1a12767d9 100644 --- a/src/org/infinity/search/SearchOptions.java +++ b/src/org/infinity/search/SearchOptions.java @@ -440,7 +440,7 @@ public Object setOption(String name, Object value) { * @return A list of available option keys. */ public String[] getOptionKeys() { - String[] retVal = new String[mapOptions.keySet().size()]; + String[] retVal = new String[mapOptions.size()]; Iterator iter = mapOptions.keySet().iterator(); int idx = 0; while (iter.hasNext()) { diff --git a/src/org/infinity/search/SearchResource.java b/src/org/infinity/search/SearchResource.java index 88a26a1b3..bca6d194c 100644 --- a/src/org/infinity/search/SearchResource.java +++ b/src/org/infinity/search/SearchResource.java @@ -5756,20 +5756,17 @@ private static class AutoDocument extends PlainDocument { private JTextComponent editor; private boolean selecting = false; + @SuppressWarnings("unchecked") public AutoDocument(final JComboBox comboBox) { this.comboBox = comboBox; this.model = this.comboBox.getModel(); - this.comboBox.addPropertyChangeListener(new PropertyChangeListener() { - @SuppressWarnings("unchecked") - @Override - public void propertyChange(PropertyChangeEvent evt) { - if (evt.getPropertyName().equals("editor")) { - configureEditor((ComboBoxEditor) evt.getNewValue()); - } - if (evt.getPropertyName().equals("model")) { - model = (ComboBoxModel) evt.getNewValue(); - } + this.comboBox.addPropertyChangeListener(evt -> { + if (evt.getPropertyName().equals("editor")) { + configureEditor((ComboBoxEditor) evt.getNewValue()); + } + if (evt.getPropertyName().equals("model")) { + model = (ComboBoxModel) evt.getNewValue(); } }); diff --git a/src/org/infinity/search/advanced/AdvancedSearch.java b/src/org/infinity/search/advanced/AdvancedSearch.java index a10eea936..96c9db20c 100644 --- a/src/org/infinity/search/advanced/AdvancedSearch.java +++ b/src/org/infinity/search/advanced/AdvancedSearch.java @@ -843,7 +843,7 @@ public void actionPerformed(ActionEvent event) { } else if (event.getSource() instanceof JMenuItem) { // process menu items from filter popup menu JMenuItem mi = (JMenuItem) event.getSource(); - List list = Arrays.asList(menuFilters.getComponents()).stream().filter(c -> c instanceof JMenuItem) + List list = Arrays.stream(menuFilters.getComponents()).filter(c -> c instanceof JMenuItem) .collect(Collectors.toList()); switch (list.indexOf(mi)) { case MENU_FILTER_ADD: diff --git a/src/org/infinity/search/advanced/FilterInput.java b/src/org/infinity/search/advanced/FilterInput.java index 39961f17d..32e5f6cfa 100644 --- a/src/org/infinity/search/advanced/FilterInput.java +++ b/src/org/infinity/search/advanced/FilterInput.java @@ -835,7 +835,7 @@ private void removeSelectedTreeNode(JTree tree, boolean prompt) { Object node = path.getLastPathComponent(); if (node instanceof MutableTreeNode && ((MutableTreeNode) node).getParent() != null) { if (!prompt || JOptionPane.showConfirmDialog(FilterInput.this, - String.format("Remove substructure \"%s\"?", node.toString()), "Confirm removal", + String.format("Remove substructure \"%s\"?", node), "Confirm removal", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) { TreePath parentPath = getParentTreePath(tree, (TreeNode) node); if (parentPath != null) { diff --git a/src/org/infinity/search/advanced/NumberFormatterEx.java b/src/org/infinity/search/advanced/NumberFormatterEx.java index 9ea19c3d0..d624d3295 100644 --- a/src/org/infinity/search/advanced/NumberFormatterEx.java +++ b/src/org/infinity/search/advanced/NumberFormatterEx.java @@ -157,7 +157,7 @@ public long getNumericValue() { * {@code ParseException} if the parameter can not be resolved into a number. */ public long getNumericValue(Object value) throws ParseException { - Number n = null; + Number n; if (value instanceof DataString && ((DataString) value).getData() instanceof Number) { n = (Number) ((DataString) value).getData(); } else if (value instanceof Number) { diff --git a/src/org/infinity/search/advanced/SearchOptions.java b/src/org/infinity/search/advanced/SearchOptions.java index 1a523ee35..fd2fac499 100644 --- a/src/org/infinity/search/advanced/SearchOptions.java +++ b/src/org/infinity/search/advanced/SearchOptions.java @@ -105,7 +105,7 @@ public SearchOptions() { /** Initializes a SearchOptions instance with settings from the specified SearchOption argument. */ public SearchOptions(SearchOptions so) { structure = new ArrayList<>(); - emptyStructure = Collections.unmodifiableList(new ArrayList(0)); + emptyStructure = Collections.unmodifiableList(new ArrayList<>(0)); init(so); } diff --git a/src/org/infinity/search/advanced/XmlConfig.java b/src/org/infinity/search/advanced/XmlConfig.java index 8b13d7130..b675a0c18 100644 --- a/src/org/infinity/search/advanced/XmlConfig.java +++ b/src/org/infinity/search/advanced/XmlConfig.java @@ -148,7 +148,7 @@ public static boolean Export(OutputStream xmlOut, String resourceType, AdvancedS DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); domFactory.setValidating(true); - DocumentBuilder builder = null; + DocumentBuilder builder; try { builder = domFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { @@ -401,7 +401,7 @@ public void error(SAXParseException exception) throws SAXException { } }); - Document document = null; + Document document; try (ByteArrayInputStream bais = new ByteArrayInputStream(sb.toString().getBytes())) { document = builder.parse(bais); } diff --git a/src/org/infinity/updater/UpdateCheck.java b/src/org/infinity/updater/UpdateCheck.java index ad405edbc..f2f550a71 100644 --- a/src/org/infinity/updater/UpdateCheck.java +++ b/src/org/infinity/updater/UpdateCheck.java @@ -66,7 +66,7 @@ public enum UpdateAction { /** Shows update check dialog and returns the action selected by the user. */ public static UpdateAction showDialog(Window owner, UpdateInfo updateInfo) { - UpdateCheck dlg = null; + UpdateCheck dlg; try { try { dlg = new UpdateCheck(owner, updateInfo); @@ -367,7 +367,7 @@ private String getChangelog() { // only changelog list entries are relevant for us final String[] lines = release.body.split("\r?\n"); int indexStart = 0; - int indexEnd = 0; + int indexEnd; for (; indexStart < lines.length; indexStart++) { final String line = lines[indexStart].toLowerCase(); if (line.contains("changelog:") || line.contains("changes:")) { diff --git a/src/org/infinity/updater/UpdateInfo.java b/src/org/infinity/updater/UpdateInfo.java index 1a0dc3dc2..c153b95cb 100644 --- a/src/org/infinity/updater/UpdateInfo.java +++ b/src/org/infinity/updater/UpdateInfo.java @@ -319,7 +319,7 @@ private Release parseGitHub(Element elemRelease) throws Exception { } children = elemGitHub.getChildNodes(); for (int idx = 0, size = children.getLength(); idx < size; idx++) { - Element elem = null; + Element elem; if (children.item(idx).getNodeType() == Node.ELEMENT_NODE) { elem = (Element) children.item(idx); } else { diff --git a/src/org/infinity/updater/Updater.java b/src/org/infinity/updater/Updater.java index fddce3f33..8efcc6c59 100644 --- a/src/org/infinity/updater/Updater.java +++ b/src/org/infinity/updater/Updater.java @@ -48,7 +48,7 @@ public enum Interval { private final String label; private final Duration interval; - private Interval(int id, String label, Duration interval) { + Interval(int id, String label, Duration interval) { this.id = id; this.label = label; this.interval = interval; diff --git a/src/org/infinity/updater/Utils.java b/src/org/infinity/updater/Utils.java index bdbeeb15e..3a30fc0d1 100644 --- a/src/org/infinity/updater/Utils.java +++ b/src/org/infinity/updater/Utils.java @@ -207,6 +207,7 @@ public static boolean isUrlValid(String url) { new URL(url); return true; } catch (MalformedURLException e) { + Logger.debug(e); } } return false; @@ -229,7 +230,7 @@ public static boolean isUrlAvailable(URL url, Proxy proxy) if (url.getProtocol().equalsIgnoreCase("http") || url.getProtocol().equalsIgnoreCase("https")) { // We only need to check header for HTTP protocol HttpURLConnection.setFollowRedirects(true); - HttpURLConnection conn = null; + HttpURLConnection conn; if (proxy != null) { conn = (HttpURLConnection) url.openConnection(proxy); } else { @@ -250,7 +251,7 @@ public static boolean isUrlAvailable(URL url, Proxy proxy) } } else { // more generic method - URLConnection conn = null; + URLConnection conn; if (proxy != null) { conn = url.openConnection(proxy); } else { @@ -483,7 +484,7 @@ public static boolean downloadFromUrl(URL url, Proxy proxy, OutputStream os, Upd List listeners) throws IOException, ProtocolException, UnknownServiceException, ZipException, FileNotFoundException { if (url != null && os != null) { - URLConnection conn = null; + URLConnection conn; if (proxy != null) { conn = url.openConnection(proxy); } else { @@ -675,7 +676,7 @@ private Utils() { // -------------------------- INNER CLASSES -------------------------- - public static interface ProgressListener extends EventListener { + public interface ProgressListener extends EventListener { void dataProgressed(ProgressEvent event); } diff --git a/src/org/infinity/util/CharsetDetector.java b/src/org/infinity/util/CharsetDetector.java index 2161f7485..250706ed7 100644 --- a/src/org/infinity/util/CharsetDetector.java +++ b/src/org/infinity/util/CharsetDetector.java @@ -188,7 +188,6 @@ public static String guessCharset(boolean detect) { } } } - ch.close(); } catch (Exception e) { Logger.error(e); } @@ -230,6 +229,94 @@ public static CharLookup getLookup() { return lookup; } + /** + * Returns the most likely non-UTF character encoding name for the specified language code. + * + * @param langCode Language code (e.g. {@code en_US}). + * @return Charset name associated with the specified language. Returns {@code windows-1252} if a match could not be + * determined. + */ + public static String getDefaultCharset(String langCode) { + String retVal = "window-1252"; + if (langCode != null) { + switch (langCode.toLowerCase()) { + case "cs_cz": + case "hu_hu": + case "pl_pl": + retVal = "window-1250"; + break; + case "ru_ru": + case "uk_ua": + retVal = "window-1251"; + break; + case "tr_tr": + retVal = "window-1254"; + break; + case "ja_jp": + retVal = "windows-31j"; + break; + case "ko_kr": + retVal = "ibm-949"; + break; + case "zh_cn": + retVal = "gbk"; + break; + default: + } + } + return retVal; + } + + /** + * Returns the max. number of bytes required to encode a character supported by the specified ANSI charset mapped to + * UTF-8. + * + * @param charset ANSI codepage as {@code Charset} object. + * @return Number of required bytes which should range from 1 to 4. + */ + public static int getMaxAnsiUtf8Length(Charset charset) { + int retVal = 4; + if (charset != null) { + if (charsetIs(charset, "us-ascii")) { + retVal = 1; + } else if (charsetIs(charset, "windows-1252")) { + retVal = 2; + } else if (charsetIs(charset, "windows-1250")) { + retVal = 2; + } else if (charsetIs(charset, "windows-1251")) { + retVal = 2; + } else if (charsetIs(charset, "windows-1254")) { + retVal = 2; + } else if (charsetIs(charset, "windows-31j")) { + retVal = 3; + } else if (charsetIs(charset, "ibm-949")) { + retVal = 3; + } else if (charsetIs(charset, "gbk")) { + retVal = 3; + } + } + return retVal; + } + + /** + * Returns whether the name of the specified charset matches the search name. + * + * @param charset {@link Charset} to check. + * @param name Charset name. + * @return {@code true} if {@code name} matches the name or any of the name aliases of the specified {@code charset}. + * Returns {@code false} otherwise. + */ + private static boolean charsetIs(Charset charset, String name) { + boolean retVal = false; + if (charset != null && name != null) { + retVal = charset.name().equalsIgnoreCase(name); + if (!retVal) { + retVal = charset.aliases().stream().anyMatch(n -> n.equalsIgnoreCase(name)); + } + } + return retVal; + } + //-------------------------- INNER CLASSES -------------------------- // Handles character decoding and encoding diff --git a/src/org/infinity/util/DynamicArray.java b/src/org/infinity/util/DynamicArray.java index d7a099fb0..2fcec061d 100644 --- a/src/org/infinity/util/DynamicArray.java +++ b/src/org/infinity/util/DynamicArray.java @@ -23,7 +23,7 @@ public class DynamicArray { /** * Supported data type sizes. */ - public static enum ElementType { + public enum ElementType { BYTE, SHORT, INTEGER24, INTEGER, LONG } diff --git a/src/org/infinity/util/DynamicByteArray.java b/src/org/infinity/util/DynamicByteArray.java new file mode 100644 index 000000000..99dfe51c0 --- /dev/null +++ b/src/org/infinity/util/DynamicByteArray.java @@ -0,0 +1,577 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Objects; + +/** + * Implementation of a mutable sequence of bytes. The class provides methods for manipulating byte content and + * modifying the buffer structure, such as adding or removing data. + */ +public class DynamicByteArray implements Iterable { + private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; + + /** Internal byte buffer */ + private byte[] buffer; + + /** The current number of bytes used. */ + private int size; + + /** + * Constructs an empty byte array with a capacity of 32 bytes. + */ + public DynamicByteArray() { + this(32); + } + + /** + * Constructs an empty byte array with the specified capacity. + * + * @param capacity the initial capacity in bytes. + * @throws NegativeArraySizeException if {@code capacity} is less than {@code 0}. + */ + public DynamicByteArray(int capacity) { + this.buffer = new byte[capacity]; + } + + /** + * Constructs a byte array initialized with the content of the specified buffer. The initial capacity is {@code 16} + * plus the buffer length. + * + * @param buffer initial content of the byte buffer. + * @throws NullPointerException if {@code buffer} argument is {@code null}. + */ + public DynamicByteArray(byte[] buffer) { + this(buffer, 0, buffer.length); + } + + /** + * Constructs a byte array initialized with the content of the specified buffer ranger. The initial capacity is + * {@code 16} plus the length of the buffer range. @param buffer initial content of the byte buffer. + * + * @param offset index of the first byte to append. + * @param len number of bytes to append. + * @return a reference to this object. + * @throws NullPointerException if {@code buffer} argument is {@code null}. + * @throws ArrayIndexOutOfBoundsException if {@code offset < 0} or {@code len < 0} or + * {@code offset+len > buffer.length}. + */ + public DynamicByteArray(byte[] buffer, int offset, int len) { + this(Math.max(0, len) + 16); + append(buffer, offset, len); + } + + /** Returns the length of the buffer in bytes. */ + public int length() { + return size; + } + + /** + * Sets the length of the buffer in bytes. + *

    + * If the new length is less than the current length then excess bytes are discarded. If the new length is greater + * than the current length then sufficient 0 byte values are appended so that length becomes the {@code newLength} + * argument. + *

    + *

    + * The {@code newLength} argument must be greater than or equal to {@code 0}. + *

    + * + * @param newLength the new length. + * @throws ArrayIndexOutOfBoundsException if the {@code newLength} argument is negative. + */ + public void setLength(int newLength) { + if (newLength < 0) { + throw new ArrayIndexOutOfBoundsException("newLength " + newLength); + } + if (newLength > size) { + ensureCapacityInternal(newLength); + Arrays.fill(buffer, size, newLength, (byte)0); + } + size = newLength; + } + + /** + * Returns {@code true} if, and only if, {@link #length} is {@code null}. + * + * @return {@code true} if {@link #length} is {@code 0}, otherwise {@code false}. + */ + public boolean isEmpty() { + return (length() == 0); + } + + /** + * Returns the capacity of the internal buffer in bytes. This is the maximum number of bytes the internal buffer + * can hold without having to resize the buffer. + */ + public int capacity() { + return buffer.length; + } + + /** + * A maintenance method that truncates the internal buffer to the currently used buffer length. + */ + public void compact() { + if (buffer.length > size) { + buffer = Arrays.copyOf(buffer, size); + } + } + + public DynamicByteArray append(byte b) { + ensureCapacityInternal(size + 1); + this.buffer[size] = b; + size += 1; + return this; + } + + public DynamicByteArray append(byte[] buf) { + return append(buf, 0, buf.length); + } + + public DynamicByteArray append(byte[] buf, int offset, int len) { + if (len > 0) { + ensureCapacityInternal(size + len); + } + System.arraycopy(buf, offset, this.buffer, size, len); + size += len; + return this; + } + + public DynamicByteArray insert(int dstOffset, byte b) { + ensureOffsetInclusive(dstOffset, 1); + ensureCapacityInternal(size + 1); + System.arraycopy(buffer, dstOffset, buffer, dstOffset + 1, size - dstOffset); + buffer[dstOffset] = b; + size += 1; + return this; + } + + public DynamicByteArray insert(int dstOffset, byte[] buf) { + return insert(dstOffset, buf, 0, buf.length); + } + + public DynamicByteArray insert(int dstOffset, byte[] buf, int srcOffset, int len) { + ensureOffsetInclusive(dstOffset, len); + ensureSrcBuffer(buf.length, srcOffset, len); + ensureCapacityInternal(size + len); + System.arraycopy(buffer, dstOffset, buffer, dstOffset + len, size - dstOffset); + System.arraycopy(buf, srcOffset, buffer, dstOffset, len); + size += len; + return this; + } + + public DynamicByteArray delete(int offset, int len) { + ensureOffsetInclusive(offset, len); + + if (offset + len > size) { + len = size - offset; + } + if (len > 0) { + System.arraycopy(buffer, offset + len, buffer, offset, size - offset - len); + size -= len; + } + return this; + } + + public DynamicByteArray replace(int dstOffset, int len, byte[] buf) { + return replace(dstOffset, len, buf, 0, buf.length); + } + + public DynamicByteArray replace(int dstOffset, int dstLen, byte[] buf, int srcOffset, int srcLen) { + ensureOffsetInclusive(dstOffset, dstLen); + ensureSrcBuffer(buf.length, srcOffset, srcLen); + delete(dstOffset, dstLen); + insert(dstOffset, buf, srcOffset, srcLen); + return this; + } + + public DynamicByteArray fill(byte value) { + return fill(value, 0, size); + } + + public DynamicByteArray fill(byte value, int offset, int len) { + ensureOffsetInclusive(offset, len); + if (offset + len > size) { + len = size - offset; + } + Arrays.fill(buffer, offset, offset + len, value); + return this; + } + + /** + * Returns a copy of the byte array content. + * + * @return A new {@code byte[]} object with the content of this byte array. + */ + public byte[] getArray() { + return Arrays.copyOf(buffer, size); + } + + public byte byteAt(int offset) { + ensureOffsetExclusive(offset, 0); + if (offset >= size) { + throw new ArrayIndexOutOfBoundsException("offset " + offset + ", length() " + size); + } + return buffer[offset]; + } + + public byte[] subrange(int offset) { + ensureOffsetInclusive(offset, 0); + return subrange(offset, size - offset); + } + + public byte[] subrange(int offset, int len) { + ensureOffsetInclusive(offset, len); + if (offset + len > size) { + throw new ArrayIndexOutOfBoundsException("offset " + offset + ", len " + len + ", buffer.length() " + size); + } + return Arrays.copyOfRange(buffer, offset, offset + len); + } + + public int indexOf(byte b) { + return indexOf(b, 0); + } + + public int indexOf(byte b, int startIndex) { + if (startIndex < 0) { + startIndex = 0; + } + for (int i = size - 1; i >= startIndex; i--) { + if (buffer[i] == b) { + return i; + } + } + return -1; + } + + public int indexOf(byte[] buf) { + return indexOf(buf, 0, buf.length); + } + + public int indexOf(byte[] buf, int offset, int len) { + return indexOf(buf, offset, len, 0); + } + + public int indexOf(byte[] buf, int offset, int len, int startIndex) { + ensureSrcBuffer(buf.length, offset, len); + return indexOf(buffer, 0, size, buf, offset, len, startIndex); + } + + public int lastIndexOf(byte b) { + for (int i = size - 1; i >= 0; i--) { + if (buffer[i] == b) { + return i; + } + } + return -1; + } + + public int lastIndexOf(byte b, int startIndex) { + if (startIndex < 0) { + return -1; + } + if (startIndex >= size) { + startIndex = size - 1; + } + for (int i = startIndex; i >= 0; i--) { + if (buffer[i] == b) { + return i; + } + } + return -1; + } + + public int lastIndexOf(byte[] buf) { + return lastIndexOf(buf, 0, buf.length); + } + + public int lastIndexOf(byte[] buf, int offset, int len) { + return indexOf(buf, offset, len, 0); + } + + public int lastIndexOf(byte[] buf, int offset, int len, int startIndex) { + ensureSrcBuffer(buf.length, offset, len); + return lastIndexOf(buffer, 0, size, buf, offset, len, startIndex); + } + + /** + * Causes the byte sequence to be replaced by the reverse of the sequence. + * + * @return a reference to this object. + */ + public DynamicByteArray reverse() { + for (int left = 0, right = size - 1; left < right; left++, right--) { + final byte b = buffer[left]; + buffer[left] = buffer[right]; + buffer[right] = b; + } + return this; + } + + + @Override + public Iterator iterator() { + return new ByteIterator(this); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + for (int i = 0; i < size; i++) { + result = prime * result + buffer[i]; + } + result = prime * result + Objects.hash(size); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DynamicByteArray other = (DynamicByteArray)obj; + + boolean result = (size == other.size); + for (int i = 0; i < size && result; i++) { + result = (buffer[i] == other.buffer[i]); + } + return result; + } + + @Override + public String toString() { + return "DynamicByteArray [size=" + size + ", capacity=" + buffer.length + "]"; + } + + /** + * Searches a source buffer for the content of the dest buffer. + * + * @param src the source buffer being searched. + * @param srcOffset offset of the source buffer. + * @param srcLen length of the source buffer in bytes. + * @param dst the bytes being searched for. + * @param dstOffset offset of the search buffer. + * @param dstLen length of the search buffer in bytes. + * @param startIndex index to begin the search in the source buffer from. + * @return Index of the first match in the source buffer. Returns -1 if a match was not found. + */ + private static int indexOf(byte[] src, int srcOffset, int srcLen, byte[] dst, int dstOffset, int dstLen, + int startIndex) { + if (startIndex >= srcLen) { + return (dstLen == 0) ? srcLen : -1; + } + if (startIndex < 0) { + startIndex = 0; + } + if (dstLen == 0) { + return startIndex; + } + + final byte first = dst[dstOffset]; + final int max = srcOffset + (srcLen - dstLen); + + for (int i = srcOffset + startIndex; i <= max; i++) { + // looking for first byte + if (src[i] != first) { + while (++i <= max && src[i] != first) + ; + } + + // found first character, now looking at the rest of the array + if (i <= max) { + int j = i + 1; + final int end = j + dstLen - 1; + for (int k = dstOffset + 1; j < end && src[j] == dst[k]; j++, k++) + ; + if (j == end) { + // found whole array + return i - srcOffset; + } + } + } + return -1; + } + + /** + * Searches a source buffer for the content of the dest buffer. + * + * @param src the source buffer being searched. + * @param srcOffset offset of the source buffer. + * @param srcLen length of the source buffer in bytes. + * @param dst the bytes being searched for. + * @param dstOffset offset of the search buffer. + * @param dstLen length of the search buffer in bytes. + * @param startIndex index to begin the search in the source buffer from. + * @return Index of the last match in the source buffer. Returns -1 if a match was not found. + */ + private static int lastIndexOf(byte[] src, int srcOffset, int srcLen, byte[] dst, int dstOffset, int dstLen, + int startIndex) { + int rightIndex = srcLen - dstLen; + if (startIndex < 0) { + return -1; + } + if (startIndex > rightIndex) { + startIndex = rightIndex; + } + if (dstLen == 0) { + return startIndex; + } + + final int lastIndex = dstOffset + dstLen - 1; + final byte lastByte = dst[lastIndex]; + final int min = srcOffset + dstLen - 1; + int i = min + startIndex; + + startSearchForLastByte: while (true) { + while (i >= min && src[i] != lastByte) { + i--; + } + if (i < min) { + return -1; + } + int j = i - 1; + final int start = j - (dstLen - 1); + int k = lastIndex - 1; + + while (j > start) { + if (src[j--] != dst[k--]) { + i--; + continue startSearchForLastByte; + } + } + return start - srcOffset + 1; + } + } + + /** + * Ensures that the capacity is at least equal to the specified minimum. If the current capacity is less than the + * argument, then a new internal buffer is allocated with greater capacity. + *

    + * If the {@code capacity} is nonpositive (e.g. due to numeric overflow), then a {@link OutOfMemoryError} is thrown. + *

    + * + * @param capacity the minimum desired capacity. + */ + private void ensureCapacityInternal(int capacity) { + if (capacity - buffer.length > 0) { + buffer = Arrays.copyOf(buffer, newCapacity(capacity)); + } + } + + /** + * Throws a {@link ArrayIndexOutOfBoundsException} if {@code dstOffset} or {@code len} contain invalid values. + * + * @param dstOffset Offset in this byte buffer. + * @param len number of bytes to operate on. + */ + private void ensureOffsetExclusive(int dstOffset, int len) { + if (dstOffset < 0 || dstOffset >= length()) { + throw new ArrayIndexOutOfBoundsException("dstOffset " + dstOffset); + } + if (len < 0) { + throw new ArrayIndexOutOfBoundsException("len " + len); + } + } + + /** + * Throws a {@link ArrayIndexOutOfBoundsException} if {@code dstOffset} or {@code len} contain invalid values. + * + * @param dstOffset Offset in this byte buffer. + * @param len number of bytes to operate on. + */ + private void ensureOffsetInclusive(int dstOffset, int len) { + if (dstOffset < 0 || dstOffset > length()) { + throw new ArrayIndexOutOfBoundsException("dstOffset " + dstOffset); + } + if (len < 0) { + throw new ArrayIndexOutOfBoundsException("len " + len); + } + } + + /** + * Throws a {@link ArrayIndexOutOfBoundsException} if {@code srcOffset} or {@code len} contain invalid values. + * + * @param srcSize Size of the reference buffer. + * @param srcOffset Offset in the reference buffer. + * @param len number of bytes to operate on. + */ + private void ensureSrcBuffer(int srcSize, int srcOffset, int len) { + if (srcOffset < 0 || srcOffset >= srcSize) { + throw new ArrayIndexOutOfBoundsException("srcOffset " + srcOffset); + } + if (len < 0 || srcOffset + len > srcSize) { + throw new ArrayIndexOutOfBoundsException("srcOffset " + srcOffset + ", len " + len + ", buf.length " + srcSize); + } + } + + /** + * Returns the capacity at least as large as the given minimum capacity. Returns the current capacity increased by the + * same amount + 2 if that suffices. Will not return a capacity greater than {@code MAX_BUFFER_SIZE} unless the given + * minimum capacity is greater than that. + * + * @param minCapacity the desirec minimum capacity. + * @return the calculated capacity. + * @throws OutOfMemoryError if {@code minCapacity} is less than zero or greater than {@link Integer#MAX_VALUE}. + */ + private int newCapacity(int minCapacity) { + int newCapacity = (buffer.length << 1) + 2; + if (newCapacity - minCapacity < 0) { + newCapacity = minCapacity; + } + return (newCapacity >= 0 || MAX_BUFFER_SIZE - newCapacity < 0) ? hugeCapacity(minCapacity) : newCapacity; + } + + private int hugeCapacity(int minCapacity) { + if (Integer.MAX_VALUE - minCapacity < 0) { + // overflow + throw new OutOfMemoryError(); + } + return Math.max(minCapacity, MAX_BUFFER_SIZE); + } + + // -------------------------- INNER CLASSES -------------------------- + + private static class ByteIterator implements Iterator { + private final DynamicByteArray array; + + private int index; + + public ByteIterator(DynamicByteArray array) { + this.array = array; + this.index = 0; + } + + @Override + public boolean hasNext() { + return index < array.length(); + } + + @Override + public Byte next() { + if (index < array.length()) { + return array.byteAt(index++); + } else { + throw new NoSuchElementException("index " + index + ", length() " + array.length()); + } + } + + @Override + public void remove() { + if (index > 0) { + array.delete(--index, 1); + } + } + } +} diff --git a/src/org/infinity/util/FilteredListModel.java b/src/org/infinity/util/FilteredListModel.java index 6ab88297e..c087d4650 100644 --- a/src/org/infinity/util/FilteredListModel.java +++ b/src/org/infinity/util/FilteredListModel.java @@ -4,12 +4,7 @@ package org.infinity.util; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Objects; -import java.util.Vector; +import java.util.*; import javax.swing.AbstractListModel; import javax.swing.DefaultListModel; @@ -310,7 +305,7 @@ public boolean contains(E item) { /** Returns true if this list contains all of the elements in the specified Collection. */ public boolean containsAll(Collection c) { - return filteredList.containsAll(c); + return new HashSet<>(filteredList).containsAll(c); } /** diff --git a/src/org/infinity/util/IdsMapCache.java b/src/org/infinity/util/IdsMapCache.java index 210df221c..c7b118ae5 100644 --- a/src/org/infinity/util/IdsMapCache.java +++ b/src/org/infinity/util/IdsMapCache.java @@ -110,23 +110,27 @@ public static Long getIdsValue(String idsRef, String symbol, boolean exact, Long } if (retVal == null) { - if (symbol.equals("ANYONE")) { - int p = idsRef.lastIndexOf('.'); - if (p >= 0) { - idsRef = idsRef.substring(0, p); - } - idsRef = idsRef.toUpperCase(Locale.ENGLISH); - String[] ids = ScriptInfo.getInfo().getObjectIdsList(); - for (final String s : ids) { - if (s.equals(idsRef)) { - retVal = 0L; - break; + switch (symbol) { + case "ANYONE": + int p = idsRef.lastIndexOf('.'); + if (p >= 0) { + idsRef = idsRef.substring(0, p); } - } - } else if (symbol.equals("FALSE")) { - retVal = 0L; - } else if (symbol.equals("TRUE")) { - retVal = 1L; + idsRef = idsRef.toUpperCase(Locale.ENGLISH); + String[] ids = ScriptInfo.getInfo().getObjectIdsList(); + for (final String s : ids) { + if (s.equals(idsRef)) { + retVal = 0L; + break; + } + } + break; + case "FALSE": + retVal = 0L; + break; + case "TRUE": + retVal = 1L; + break; } } @@ -202,8 +206,8 @@ public static boolean isCaseSensitiveMatch(String idsRef) { * @param overwrite If {@code true}, then static flag label will be overwritten with entries from the IDS resource. If * {@code false}, then entries from IDS resource will be used only for missing or empty entries in * the {@code flags} array. - * @param prettify Indicates whether to improve readability of flag names. The operation includes: - a capitalized - * first letter - lowercased remaining letters - underscores are replaced by space + * @param prettify Indicates whether to improve readability of flag names. The operation includes a capitalized + * first letter of each word, lowercased remaining letters, underscores are replaced by space. * @return The updated list of flag names. */ public static String[] getUpdatedIdsFlags(String[] flags, String idsFile, int size, boolean overwrite, @@ -253,15 +257,21 @@ public static String[] getUpdatedIdsFlags(String[] flags, String idsFile, int si } /** - * Improves readability of given string by capitalizing first letter, lowercasing remaining characters and replacing - * underscores by space. + * Improves readability of given string by capitalizing first letter of each word, lowercasing remaining characters + * and replacing underscores by space. */ private static String prettifyName(String name) { if (name == null || name.trim().isEmpty()) { return name; } + String retVal = name.replace("_", " ").toLowerCase(Locale.ENGLISH); - retVal = Character.toUpperCase(retVal.charAt(0)) + retVal.substring(1); + final String[] items = retVal.split(" "); + for (int i = 0; i < items.length; i++) { + items[i] = Character.toUpperCase(items[i].charAt(0)) + items[i].substring(1); + } + retVal = String.join(" ", items); + return retVal; } diff --git a/src/org/infinity/util/IniMapEntry.java b/src/org/infinity/util/IniMapEntry.java index 490c96c8e..ef9feecaf 100644 --- a/src/org/infinity/util/IniMapEntry.java +++ b/src/org/infinity/util/IniMapEntry.java @@ -85,7 +85,7 @@ public static String[] splitValues(String value) { /** Helper routine: Splits values and returns them as array of individual values. */ public static String[] splitValues(String value, char separator) { - String[] retVal = null; + String[] retVal; if (value != null) { retVal = value.split(Character.toString(separator)); } else { @@ -99,7 +99,7 @@ public static String[] splitValues(String value, char separator) { * individual values. */ public static String[] splitValues(String value, String pattern) { - String[] retVal = null; + String[] retVal; if (value != null) { List results = new ArrayList<>(); try { diff --git a/src/org/infinity/util/InputKeyHelper.java b/src/org/infinity/util/InputKeyHelper.java new file mode 100644 index 000000000..6b27b3faf --- /dev/null +++ b/src/org/infinity/util/InputKeyHelper.java @@ -0,0 +1,365 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util; + +import java.awt.event.KeyEvent; + +/** + * A helper class that identifies and returns the stored but normally inaccessible virtual key codes from + * {@link KeyEvent} objects. + * + *

    Note: Functionality is currently only available on Windows platforms.

    + */ +public class InputKeyHelper { + /** Left mouse button */ + public static final int VK_LBUTTON = 0x01; + /** Right mouse button */ + public static final int VK_RBUTTON = 0x02; + /** Control-break processing */ + public static final int VK_CANCEL = 0x03; + /** Middle mouse button */ + public static final int VK_MBUTTON = 0x04; + /** X1 mouse button */ + public static final int VK_XBUTTON1 = 0x05; + /** X2 mouse button */ + public static final int VK_XBUTTON2 = 0x06; + /** BACKSPACE key */ + public static final int VK_BACK = 0x08; + /** TAB key */ + public static final int VK_TAB = 0x09; + /** CLEAR key */ + public static final int VK_CLEAR = 0x0C; + /** ENTER key */ + public static final int VK_RETURN = 0x0D; + /** SHIFT key */ + public static final int VK_SHIFT = 0x10; + /** CTRL key */ + public static final int VK_CONTROL = 0x11; + /** ALT key */ + public static final int VK_MENU = 0x12; + /** ALT key */ + public static final int VK_ALT = VK_MENU; + /** PAUSE key */ + public static final int VK_PAUSE = 0x13; + /** CAPS LOCK key */ + public static final int VK_CAPITAL = 0x14; + /** IME Kana mode */ + public static final int VK_KANA = 0x15; + /** IME Hangul mode */ + public static final int VK_HANGUL = 0x15; + /** IME On */ + public static final int VK_IME_ON = 0x16; + /** IME Junja mode */ + public static final int VK_JUNJA = 0x17; + /** IME final mode */ + public static final int VK_FINAL = 0x18; + /** IME Hanja mode */ + public static final int VK_HANJA = 0x19; + /** IME Kanji mode */ + public static final int VK_KANJI = 0x19; + /** IME Off */ + public static final int VK_IME_OFF = 0x1A; + /** ESC key */ + public static final int VK_ESCAPE = 0x1B; + /** IME convert */ + public static final int VK_CONVERT = 0x1C; + /** IME nonconvert */ + public static final int VK_NONCONVERT = 0x1D; + /** IME accept */ + public static final int VK_ACCEPT = 0x1E; + /** IME mode change request */ + public static final int VK_MODECHANGE = 0x1F; + /** SPACEBAR */ + public static final int VK_SPACE = 0x20; + /** PAGE UP key */ + public static final int VK_PRIOR = 0x21; + public static final int VK_PAGE_UP = VK_PRIOR; + /** PAGE DOWN key */ + public static final int VK_NEXT = 0x22; + public static final int VK_PAGE_DOWN = VK_NEXT; + /** END key */ + public static final int VK_END = 0x23; + /** HOME key */ + public static final int VK_HOME = 0x24; + /** LEFT ARROW key */ + public static final int VK_LEFT = 0x25; + /** UP ARROW key */ + public static final int VK_UP = 0x26; + /** RIGHT ARROW key */ + public static final int VK_RIGHT = 0x27; + /** DOWN ARROW key */ + public static final int VK_DOWN = 0x28; + /** SELECT key */ + public static final int VK_SELECT = 0x29; + /** PRINT key */ + public static final int VK_PRINT = 0x2A; + /** EXECUTE key */ + public static final int VK_EXECUTE = 0x2B; + /** PRINT SCREEN key */ + public static final int VK_SNAPSHOT = 0x2C; + /** INS key */ + public static final int VK_INSERT = 0x2D; + /** DEL key */ + public static final int VK_DELETE = 0x2E; + /** HELP key */ + public static final int VK_HELP = 0x2F; + /** Left WINDOWS key */ + public static final int VK_LWIN = 0x5B; + /** Right WINDOWS key */ + public static final int VK_RWIN = 0x5C; + /** APPLICATIONS key */ + public static final int VK_APPS = 0x5D; + /** Computer SLEEP key */ + public static final int VK_SLEEP = 0x5F; + /** Numeric keypad 0 key */ + public static final int VK_NUMPAD0 = 0x60; + /** Numeric keypad 1 key */ + public static final int VK_NUMPAD1 = 0x61; + /** Numeric keypad 2 key */ + public static final int VK_NUMPAD2 = 0x62; + /** Numeric keypad 3 key */ + public static final int VK_NUMPAD3 = 0x63; + /** Numeric keypad 4 key */ + public static final int VK_NUMPAD4 = 0x64; + /** Numeric keypad 5 key */ + public static final int VK_NUMPAD5 = 0x65; + /** Numeric keypad 6 key */ + public static final int VK_NUMPAD6 = 0x66; + /** Numeric keypad 7 key */ + public static final int VK_NUMPAD7 = 0x67; + /** Numeric keypad 8 key */ + public static final int VK_NUMPAD8 = 0x68; + /** Numeric keypad 9 key */ + public static final int VK_NUMPAD9 = 0x69; + /** Multiply key */ + public static final int VK_MULTIPLY = 0x6A; + /** Add key */ + public static final int VK_ADD = 0x6B; + /** Separator key */ + public static final int VK_SEPARATOR = 0x6C; + /** Subtract key */ + public static final int VK_SUBTRACT = 0x6D; + /** Decimal key */ + public static final int VK_DECIMAL = 0x6E; + /** Divide key */ + public static final int VK_DIVIDE = 0x6F; + /** F1 key */ + public static final int VK_F1 = 0x70; + /** F2 key */ + public static final int VK_F2 = 0x71; + /** F3 key */ + public static final int VK_F3 = 0x72; + /** F4 key */ + public static final int VK_F4 = 0x73; + /** F5 key */ + public static final int VK_F5 = 0x74; + /** F6 key */ + public static final int VK_F6 = 0x75; + /** F7 key */ + public static final int VK_F7 = 0x76; + /** F8 key */ + public static final int VK_F8 = 0x77; + /** F9 key */ + public static final int VK_F9 = 0x78; + /** F10 key */ + public static final int VK_F10 = 0x79; + /** F11 key */ + public static final int VK_F11 = 0x7A; + /** F12 key */ + public static final int VK_F12 = 0x7B; + /** F13 key */ + public static final int VK_F13 = 0x7C; + /** F14 key */ + public static final int VK_F14 = 0x7D; + /** F15 key */ + public static final int VK_F15 = 0x7E; + /** F16 key */ + public static final int VK_F16 = 0x7F; + /** F17 key */ + public static final int VK_F17 = 0x80; + /** F18 key */ + public static final int VK_F18 = 0x81; + /** F19 key */ + public static final int VK_F19 = 0x82; + /** F20 key */ + public static final int VK_F20 = 0x83; + /** F21 key */ + public static final int VK_F21 = 0x84; + /** F22 key */ + public static final int VK_F22 = 0x85; + /** F23 key */ + public static final int VK_F23 = 0x86; + /** F24 key */ + public static final int VK_F24 = 0x87; + /** NUM LOCK key */ + public static final int VK_NUMLOCK = 0x90; + /** SCROLL LOCK key */ + public static final int VK_SCROLL = 0x91; + /** Left SHIFT key */ + public static final int VK_LSHIFT = 0xA0; + /** Right SHIFT key */ + public static final int VK_RSHIFT = 0xA1; + /** Left CTRL key */ + public static final int VK_LCONTROL = 0xA2; + /** Right CTRL key */ + public static final int VK_RCONTROL = 0xA3; + /** Left ALT key */ + public static final int VK_LMENU = 0xA4; + public static final int VK_LALT = VK_LMENU; + /** Right ALT key */ + public static final int VK_RMENU = 0xA5; + public static final int VK_RALT = VK_RMENU; + /** Browser Back key */ + public static final int VK_BROWSER_BACK = 0xA6; + /** Browser Forward key */ + public static final int VK_BROWSER_FORWARD = 0xA7; + /** Browser Refresh key */ + public static final int VK_BROWSER_REFRESH = 0xA8; + /** Browser Stop key */ + public static final int VK_BROWSER_STOP = 0xA9; + /** Browser Search key */ + public static final int VK_BROWSER_SEARCH = 0xAA; + /** Browser Favorites key*/ + public static final int VK_BROWSER_FAVORITES = 0xAB; + /** Browser Start and Home key */ + public static final int VK_BROWSER_HOME = 0xAC; + /** Volume Mute key */ + public static final int VK_VOLUME_MUTE = 0xAD; + /** Volume Down key */ + public static final int VK_VOLUME_DOWN = 0xAE; + /** Volume Up key */ + public static final int VK_VOLUME_UP = 0xAF; + /** Next Track key */ + public static final int VK_MEDIA_NEXT_TRACK = 0xB0; + /** Previous Track key */ + public static final int VK_MEDIA_PREV_TRACK = 0xB1; + /** Stop Media key */ + public static final int VK_MEDIA_STOP = 0xB2; + /** Play/Pause Media key */ + public static final int VK_MEDIA_PLAY_PAUSE = 0xB3; + /** Start Mail key */ + public static final int VK_LAUNCH_MAIL = 0xB4; + /** Select Media key */ + public static final int VK_LAUNCH_MEDIA_SELECT = 0xB5; + /** Start Application 1 key */ + public static final int VK_LAUNCH_APP1 = 0xB6; + /** Start Application 2 key */ + public static final int VK_LAUNCH_APP2 = 0xB7; + /** Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the {@code ;:} key */ + public static final int VK_OEM_1 = 0xBA; + /** For any country/region, the {@code +} key */ + public static final int VK_OEM_PLUS = 0xBB; + /** For any country/region, the {@code ,} key */ + public static final int VK_OEM_COMMA = 0xBC; + /** For any country/region, the {@code -} key */ + public static final int VK_OEM_MINUS = 0xBD; + /** For any country/region, the {@code .} key */ + public static final int VK_OEM_PERIOD = 0xBE; + /** Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the {@code /?} key */ + public static final int VK_OEM_2 = 0xBF; + /** Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the {@code `~} key */ + public static final int VK_OEM_3 = 0xC0; + /** Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the {@code [{} key */ + public static final int VK_OEM_4 = 0xDB; + /** Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the {@code \} key */ + public static final int VK_OEM_5 = 0xDC; + /** Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the {@code ]}} key */ + public static final int VK_OEM_6 = 0xDD; + /** Used for miscellaneous characters; it can vary by keyboard. For the US standard keyboard, the {@code '"} key */ + public static final int VK_OEM_7 = 0xDE; + /** Used for miscellaneous characters; it can vary by keyboard. */ + public static final int VK_OEM_8 = 0xDF; + /** The {@code <>} keys on the US standard keyboard, or the {@code \|} key on the non-US 102-key keyboard */ + public static final int VK_OEM_102 = 0xE2; + /** IME PROCESS key */ + public static final int VK_PROCESSKEY = 0xE5; + /** + * Used to pass Unicode characters as if they were keystrokes. The VK_PACKET key is the low word of a 32-bit Virtual + * Key value used for non-keyboard input methods. + */ + public static final int VK_PACKET = 0xE7; + /** Attn key */ + public static final int VK_ATTN = 0xF6; + /** CrSel key */ + public static final int VK_CRSEL = 0xF7; + /** ExSel key */ + public static final int VK_EXSEL = 0xF8; + /** Erase EOF key */ + public static final int VK_EREOF = 0xF9; + /** Play key */ + public static final int VK_PLAY = 0xFA; + /** Zoom key */ + public static final int VK_ZOOM = 0xFB; + /** Reserved */ + public static final int VK_NONAME = 0xFC; + /** PA1 key */ + public static final int VK_PA1 = 0xFD; + /** Clear key */ + public static final int VK_OEM_CLEAR = 0xFE; + + // TODO: check if adding Linux or macOS support is possible + private static boolean enabled = Platform.IS_WINDOWS; + + /** Returns whether raw keycode support is enabled on this platform. */ + public static boolean isEnabled() { + return enabled; + } + + /** Overrides the autodetected raw keycode support. */ + public static void setEnabled(boolean b) { + enabled = b; + } + + /** Restores autodetected raw keycode support for this platform. */ + public static void setAutodetect() { + enabled = Platform.IS_WINDOWS; + } + + /** + * Returns the virtual key code from the specified KeyEvent object. + * + * @param e {@link KeyEvent} object with the code to extract. + * @return The extracted virtual key code. Returns 0 if the code could not be determined. + */ + public static int getExtendedKeyCode(KeyEvent e) { + int retVal = 0; + if (e != null && isEnabled()) { + // extracting value from KeyEvent::toString() output + final String value = getValue(e.toString(), "rawCode"); + if (!value.isEmpty()) { + try { + retVal = Integer.parseInt(value); + } catch (NumberFormatException ex) { + Logger.debug(ex); + } + } + } + return retVal; + } + + /** Internal helper method that extracts the value associated with {@code key} in the specified {@code input}. */ + private static String getValue(String input, String key) { + String retVal = ""; + if (input != null && key != null && !key.isEmpty()) { + int spos = input.indexOf(key); + if (spos >= 0) { + spos += key.length() + 1; + int epos1 = input.indexOf(',', spos); + if (epos1 < 0) { + epos1 = Integer.MAX_VALUE; + } + int epos2 = input.indexOf(']', spos); + if (epos2 < 0) { + epos2 = Integer.MAX_VALUE; + } + int epos = Math.min(epos1, epos2); + if (epos >= spos && epos < Integer.MAX_VALUE) { + retVal = input.substring(spos, epos); + } + } + } + return retVal; + } +} diff --git a/src/org/infinity/util/Logger.java b/src/org/infinity/util/Logger.java index c1e057f17..ed71c7753 100644 --- a/src/org/infinity/util/Logger.java +++ b/src/org/infinity/util/Logger.java @@ -34,6 +34,256 @@ public static TaggedLogger tags(final String... tags) { return org.tinylog.Logger.tags(tags); } + + /** + * Logs a message at the specified log level. + * + * @param message String or any other object with a meaningful {@link #toString()} method + */ + public static void log(final Level logLevel, final Object message) { + switch (logLevel) { + case TRACE: + trace(message); + break; + case DEBUG: + debug(message); + break; + case INFO: + info(message); + break; + case WARN: + warn(message); + break; + case ERROR: + error(message); + break; + default: + } + } + + /** + * Logs a lazy message at the specified log level. The message will be only evaluated if the log entry is + * really output. + * + * @param message Function that produces the message + */ + public static void log(final Level logLevel, final Supplier message) { + switch (logLevel) { + case TRACE: + trace(message); + break; + case DEBUG: + debug(message); + break; + case INFO: + info(message); + break; + case WARN: + warn(message); + break; + case ERROR: + error(message); + break; + default: + } + } + + /** + * Logs a formatted message at the specified log level. "{}" placeholders will be replaced by given arguments. + * + * @param message Formatted text message to log + * @param arguments Arguments for formatted text message + */ + public static void log(final Level logLevel, final String message, final Object... arguments) { + switch (logLevel) { + case TRACE: + trace(message, arguments); + break; + case DEBUG: + debug(message, arguments); + break; + case INFO: + info(message, arguments); + break; + case WARN: + warn(message, arguments); + break; + case ERROR: + error(message, arguments); + break; + default: + } + } + + /** + * Logs a formatted message at the specified log level. "{}" placeholders will be replaced by given lazy + * arguments. The arguments will be only evaluated if the log entry is really output. + * + * @param message Formatted text message to log + * @param arguments Functions that produce the arguments for formatted text message + */ + public static void log(final Level logLevel, final String message, final Supplier... arguments) { + switch (logLevel) { + case TRACE: + trace(message, arguments); + break; + case DEBUG: + debug(message, arguments); + break; + case INFO: + info(message, arguments); + break; + case WARN: + warn(message, arguments); + break; + case ERROR: + error(message, arguments); + break; + default: + } + } + + /** + * Logs an exception at the specified log level. + * + * @param exception Caught exception or any other throwable to log + */ + public static void log(final Level logLevel, final Throwable exception) { + switch (logLevel) { + case TRACE: + trace(exception); + break; + case DEBUG: + debug(exception); + break; + case INFO: + info(exception); + break; + case WARN: + warn(exception); + break; + case ERROR: + error(exception); + break; + default: + } + } + + /** + * Logs an exception with a custom message at the specified log level. + * + * @param exception Caught exception or any other throwable to log + * @param message Text message to log + */ + public static void log(final Level logLevel, final Throwable exception, final String message) { + switch (logLevel) { + case TRACE: + trace(exception, message); + break; + case DEBUG: + debug(exception, message); + break; + case INFO: + info(exception, message); + break; + case WARN: + warn(exception, message); + break; + case ERROR: + error(exception, message); + break; + default: + } + } + + /** + * Logs an exception with a custom lazy message at the specified log level. The message will be only evaluated if the + * log entry is really output. + * + * @param exception Caught exception or any other throwable to log + * @param message Function that produces the message + */ + public static void log(final Level logLevel, final Throwable exception, final Supplier message) { + switch (logLevel) { + case TRACE: + trace(exception, message); + break; + case DEBUG: + debug(exception, message); + break; + case INFO: + info(exception, message); + break; + case WARN: + warn(exception, message); + break; + case ERROR: + error(exception, message); + break; + default: + } + } + + /** + * Logs an exception with a formatted custom message at the specified log level. "{}" placeholders will be replaced by + * given arguments. + * + * @param exception Caught exception or any other throwable to log + * @param message Formatted text message to log + * @param arguments Arguments for formatted text message + */ + public static void log(final Level logLevel, final Throwable exception, final String message, + final Object... arguments) { + switch (logLevel) { + case TRACE: + trace(exception, message, arguments); + break; + case DEBUG: + debug(exception, message, arguments); + break; + case INFO: + info(exception, message, arguments); + break; + case WARN: + warn(exception, message, arguments); + break; + case ERROR: + error(exception, message, arguments); + break; + default: + } + } + + /** + * Logs an exception with a formatted message at the specified log level. "{}" placeholders will be replaced by given + * lazy arguments. The arguments will be only evaluated if the log entry is really output. + * + * @param exception Caught exception or any other throwable to log + * @param message Formatted text message to log + * @param arguments Functions that produce the arguments for formatted text message + */ + public static void log(final Level logLevel, final Throwable exception, final String message, + final Supplier... arguments) { + switch (logLevel) { + case TRACE: + trace(exception, message, arguments); + break; + case DEBUG: + debug(exception, message, arguments); + break; + case INFO: + info(exception, message, arguments); + break; + case WARN: + warn(exception, message, arguments); + break; + case ERROR: + error(exception, message, arguments); + break; + default: + } + } + /** * Checks whether log entries at {@link Level#TRACE TRACE} level will be output. * diff --git a/src/org/infinity/util/LuaEntry.java b/src/org/infinity/util/LuaEntry.java index a679ad843..636a9b548 100644 --- a/src/org/infinity/util/LuaEntry.java +++ b/src/org/infinity/util/LuaEntry.java @@ -73,11 +73,11 @@ private String toString(int indent) { } if (value != null) { if (value instanceof Boolean) { - sb.append(Boolean.toString((Boolean) value)); + sb.append(value); } else if (value instanceof Integer) { - sb.append(Integer.toString((Integer) value)); + sb.append(value); } else { - sb.append('"').append(value.toString()).append('"'); + sb.append('"').append(value).append('"'); } } else if (children != null) { sb.append("{\n"); diff --git a/src/org/infinity/util/LuaParser.java b/src/org/infinity/util/LuaParser.java index eafa6f0ca..5fd4740a2 100644 --- a/src/org/infinity/util/LuaParser.java +++ b/src/org/infinity/util/LuaParser.java @@ -177,7 +177,7 @@ private static LuaEntry ParseElement(CharBuffer buffer, LuaEntry parent) throws } else if (ch == ',' || ch == '}') { // special case: true/false cannot be correctly determined before fully parsed if (key.toString().equals("true") || key.toString().equals("false")) { - value.append(key.toString()); + value.append(key); key.setLength(0); curToken = Token.VALUE_BOOLEAN; buffer.position(buffer.position() - 1); diff --git a/src/org/infinity/util/MassExporter.java b/src/org/infinity/util/MassExporter.java index 3bf53193a..3063ef1c9 100644 --- a/src/org/infinity/util/MassExporter.java +++ b/src/org/infinity/util/MassExporter.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PrintWriter; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.file.Files; @@ -68,6 +69,7 @@ import org.infinity.resource.bcs.BcsResource; import org.infinity.resource.bcs.Decompiler; import org.infinity.resource.cre.CreResource; +import org.infinity.resource.dlg.DlgResource; import org.infinity.resource.graphics.BamDecoder; import org.infinity.resource.graphics.BamResource; import org.infinity.resource.graphics.ColorConvert; @@ -90,14 +92,14 @@ public final class MassExporter extends ChildFrame implements ActionListener, ListSelectionListener, DocumentListener, Runnable { private static final String FMT_PROGRESS = "Processing resource %d/%d"; - private static final Set TYPES_BLACKLIST = new HashSet<>(Arrays.asList(new String[] {"BIK", "LOG", "SAV"})); + private static final Set TYPES_BLACKLIST = new HashSet<>(Arrays.asList("BIK", "LOG", "SAV")); private final JButton bExport = new JButton("Export", Icons.ICON_EXPORT_16.getIcon()); private final JButton bCancel = new JButton("Cancel", Icons.ICON_DELETE_16.getIcon()); private final JButton bDirectory = new JButton(Icons.ICON_OPEN_16.getIcon()); private final JCheckBox cbPattern = new JCheckBox("Use regular expressions", false); private final JCheckBox cbIncludeExtraDirs = new JCheckBox("Include extra folders", true); - private final JCheckBox cbDecompile = new JCheckBox("Decompile scripts", true); + private final JCheckBox cbDecompile = new JCheckBox("Decompile scripts and dialogs", true); private final JCheckBox cbDecrypt = new JCheckBox("Decrypt text files", true); private final JCheckBox cbConvertWAV = new JCheckBox("Convert sounds", true); private final JCheckBox cbConvertCRE = new JCheckBox("Convert CHR=>CRE", false); @@ -554,6 +556,16 @@ private void exportText(ResourceEntry entry, Class resourceT } } + private void decompileDialog(ResourceEntry entry, Path output) throws Exception { + output = output.getParent().resolve(StreamUtils.replaceFileExtension(output.getFileName().toString(), "D")); + final DlgResource dlg = new DlgResource(entry); + try (PrintWriter writer = new PrintWriter(output.toFile(), BrowserMenuBar.getInstance().getOptions().getSelectedCharset())) { + if (!dlg.exportDlgAsText(writer)) { + Logger.error("Failed to decompile: ", entry); + } + } + } + private void decompressBamMos(ResourceEntry entry, Path output) throws Exception { ByteBuffer bb = entry.getResourceBuffer(); if (bb.limit() > 0) { @@ -768,7 +780,7 @@ private void exportResource(ResourceEntry entry, Path output) throws Exception { try (OutputStream os = tryOpenOutputStream(output, 10, 100)) { int bytesWritten = (int) StreamUtils.writeBytes(os, is, size); if (bytesWritten < size) { - throw new EOFException(entry.toString() + ": " + bytesWritten + " of " + size + " bytes written"); + throw new EOFException(entry + ": " + bytesWritten + " of " + size + " bytes written"); } } } @@ -788,6 +800,8 @@ private void export(ResourceEntry entry) { if (isTextResource) { exportText(entry, resourceType, output); + } else if (entry.getExtension().equalsIgnoreCase("DLG") && cbDecompile.isSelected()) { + decompileDialog(entry, output); } else if (entry.getExtension().equalsIgnoreCase("MOS") && cbConvertToPNG.isSelected()) { mosToPng(entry, output); } else if (entry.getExtension().equalsIgnoreCase("PVRZ") && cbConvertToPNG.isSelected()) { diff --git a/src/org/infinity/util/Misc.java b/src/org/infinity/util/Misc.java index c7984ab7f..27afe4d9d 100644 --- a/src/org/infinity/util/Misc.java +++ b/src/org/infinity/util/Misc.java @@ -126,15 +126,6 @@ public static Preferences getPrefs(String className) { return Preferences.userRoot().node(Misc.prefsNodeName(className)); } - /** - * Returns a default charset depending on the current game type. - * - * @return {@link #CHARSET_UTF8} for Enhanced Edition games, {@link #CHARSET_DEFAULT} otherwise. - */ - public static Charset getDefaultCharset() { - return Profile.isEnhancedEdition() ? CHARSET_UTF8 : CHARSET_DEFAULT; - } - /** * A convenience method that attempts to return the charset specified by the given name or the next best match * depending on the current game type. @@ -143,7 +134,7 @@ public static Charset getDefaultCharset() { * @return The desired charset if successful, a game-specific default charset otherwise. */ public static Charset getCharsetFrom(String charsetName) { - return getCharsetFrom(charsetName, getDefaultCharset()); + return getCharsetFrom(charsetName, Profile.getDefaultCharset()); } /** @@ -443,8 +434,8 @@ public static String prettifySymbol(String symbol) { StringBuilder sb = new StringBuilder(); boolean isUpper = false; boolean isDigit = false; - boolean isPrevUpper = false; - boolean isPrevDigit = false; + boolean isPrevUpper; + boolean isPrevDigit; boolean toUpper = true; for (int idx = 0, len = symbol.length(); idx < len; idx++) { char ch = symbol.charAt(idx); @@ -675,7 +666,7 @@ public static void requireCondition(boolean cond, String message, Class cls : new Class[] { classEx, Exception.class }) { - Object ex = null; + Object ex; if (message != null) { Constructor ctor = cls.getConstructor(String.class); ex = ctor.newInstance(message); diff --git a/src/org/infinity/util/SimpleListModel.java b/src/org/infinity/util/SimpleListModel.java index 49f451883..2330313d0 100644 --- a/src/org/infinity/util/SimpleListModel.java +++ b/src/org/infinity/util/SimpleListModel.java @@ -125,7 +125,7 @@ public boolean contains(E item) { /** Returns true if this list contains all of the elements in the specified Collection. */ public boolean containsAll(Collection c) { - return delegate.containsAll(c); + return new HashSet<>(delegate).containsAll(c); } /** diff --git a/src/org/infinity/util/StopWatch.java b/src/org/infinity/util/StopWatch.java new file mode 100644 index 000000000..796fdc7bb --- /dev/null +++ b/src/org/infinity/util/StopWatch.java @@ -0,0 +1,307 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.SwingUtilities; +import javax.swing.event.EventListenerList; + +import org.infinity.resource.Closeable; + +/** + * A "Stopwatch" implementation that provides functionality for starting/stopping, pausing/resuming, and querying + * elapsed time. + * + *

    The class provides {@link ActionListener} support to fire in regular intervals. Time is measured in + * milliseconds.

    + */ +public class StopWatch implements Closeable { + /** Factor for converting between milliseconds and seconds. */ + private static final long SECONDS_MULTIPLIER = 1000L; + + private final EventListenerList listenerList = new EventListenerList(); + + // Runner for triggering delayed action events. + private final Runner runner = new Runner(); + + // relative starting time of a timer session + private long timeBase; + + // time value taken when the timer is paused + private long timePaused; + + // user-defined delay between events + private long delay; + + // time when the next event should be fired + private long timeDelay; + + // indicates whether the timer is paused + private boolean paused; + + // indicates whether the StopWatch instance has been closed + private boolean closed; + + /** + * Converts a measured time value from milliseconds to seconds. + * + * @param time Time in milliseconds. + * @return Time in seconds. + */ + public static long toSeconds(long time) { + return time / SECONDS_MULTIPLIER; + } + + /** + * Initializes a new stopwatch timer. + * + * @param start Specify {@code true} to automatically start the timer. + */ + public StopWatch(boolean start) { + this(0L, start); + } + + /** + * Initializes a new stopwatch timer. + * + * @param delay Interval delay for firing action events, in milliseconds. + * @param start Specify {@code true} to automatically start the timer. + */ + public StopWatch(long delay, boolean start) { + setDelay(delay); + paused = !start; + reset(); + if (!start) { + pause(); + } + } + + /** Starts a new timer session. Old timer session is discarded. */ + public synchronized void reset() { + if (isClosed()) { + return; + } + + timeBase = System.currentTimeMillis(); + if (hasDelay()) { + calculateTimer(timeBase); + } + if (paused) { + timePaused = timeBase; + } + } + + /** Returns whether the timer is paused. */ + public boolean isPaused() { + return paused; + } + + /** Pauses the current timer session. */ + public synchronized void pause() { + if (isClosed()) { + return; + } + + if (!paused) { + paused = true; + timePaused = System.currentTimeMillis(); + } + } + + /** Resumes a previously paused timer session. */ + public synchronized void resume() { + if (isClosed()) { + return; + } + + if (paused) { + final long timeDiff = System.currentTimeMillis() - timePaused; + timeBase += timeDiff; + paused = false; + runner.signal(); + } + } + + /** + * Returns the total elapsed time since the current timer session started, in milliseconds. Paused durations are + * ignored. + */ + public long elapsed() { + if (isClosed()) { + return 0L; + } + + if (isPaused()) { + return timePaused - timeBase; + } else { + final long curTime = System.currentTimeMillis(); + return curTime - timeBase; + } + } + + /** Internally used to query whether a delay for firing action events has been defined. */ + private boolean hasDelay() { + return (delay > 0); + } + + /** + * Returns the defined delay for firing action events, in milliseconds. + * A value of 0 indicates that no delay has been defined. + */ + public long getDelay() { + return delay; + } + + /** + * Sets the delay for firing action events. + * + * @param delay Delay between event intervals, in milliseconds. Specify {@code 0} to disable firing events. + */ + public synchronized void setDelay(long delay) { + delay = Math.max(0L, delay); + if (delay != this.delay) { + this.delay = delay; + calculateTimer(System.currentTimeMillis()); + } + } + + /** + * Returns whether this {@link StopWatch} instance has been closed. A closed instance cannot measure time + * further. + * + * @return {@code true} if the instance has been closed, {@code false} otherwise. + */ + public boolean isClosed() { + return closed; + } + + @Override + public void close() { + if (isClosed()) { + return; + } + + pause(); + closed = true; + runner.close(); + } + + /** Adds an {@link ActionListener} to the stopwatch. */ + public void addActionListener(ActionListener l) { + if (l != null) { + listenerList.add(ActionListener.class, l); + } + } + + /** Returns all registered {@link ActionListener}s for this stopwatch instance. */ + public ActionListener[] getActionListeners() { + return listenerList.getListeners(ActionListener.class); + } + + /** Removes an {@link ActionListener} from the stopwatch instance. */ + public void removeActionListener(ActionListener l) { + if (l != null) { + listenerList.remove(ActionListener.class, l); + } + } + + @Override + public String toString() { + final String state = isPaused() ? "paused" : "running"; + final long curTime = elapsed(); + return "Elapsed time: " + curTime + " ms [state=" + state + "]"; + } + + private void fireActionPerformed(ActionEvent event) { + if (event != null) { + Object[] listeners = listenerList.getListenerList(); + ActionEvent e = null; + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == ActionListener.class) { + // Event object is lazily created + if (e == null) { + final String actionCommand = event.getActionCommand() != null ? event.getActionCommand() : ""; + e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, actionCommand, event.getWhen(), + event.getModifiers()); + } + ((ActionListener)listeners[i + 1]).actionPerformed(e); + } + } + } + } + + /** Used internally to calculate the time for firing the next action event. */ + private void calculateTimer(long timeBase) { + if (hasDelay()) { + timeDelay = timeBase + delay; + } + } + + // -------------------------- INNER CLASSES -------------------------- + + /** Internal helper class for firing timed action events. */ + private class Runner implements Runnable, Closeable { + private final Thread thread; + + private boolean running; + + public Runner() { + running = true; + thread = new Thread(this); + thread.start(); + } + + /** Signals the thread to interrupt the current state. */ + public void signal() { + if (isRunning()) { + thread.interrupt(); + } + } + + /** Returns whether this runner instance is active. */ + public boolean isRunning() { + return running; + } + + /** + * Signals the thread to terminate. + */ + @Override + public void close() { + if (isRunning()) { + running = false; + thread.interrupt(); + } + } + + @Override + public void run() { + while (running) { + if (StopWatch.this.isPaused()) { + try { + Thread.sleep(Long.MAX_VALUE); + } catch (InterruptedException e) { + } + } else { + if (StopWatch.this.hasDelay() && !StopWatch.this.isPaused()) { + final long curTime = System.currentTimeMillis(); + if (curTime >= StopWatch.this.timeDelay) { + StopWatch.this.calculateTimer(curTime); + final ActionEvent actionEvent = new ActionEvent(StopWatch.this, 0, "", curTime, 0); + SwingUtilities.invokeLater(() -> fireActionPerformed(actionEvent)); + } + } + + try { + Thread.sleep(1L); + } catch (InterruptedException e) { + } + } + } + } + } +} diff --git a/src/org/infinity/util/StringTable.java b/src/org/infinity/util/StringTable.java index 3013ee7e8..5b20cddf6 100644 --- a/src/org/infinity/util/StringTable.java +++ b/src/org/infinity/util/StringTable.java @@ -14,6 +14,7 @@ import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; import java.util.ArrayList; +import java.util.Arrays; import java.util.EnumMap; import java.util.HashMap; @@ -21,6 +22,7 @@ import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; import org.infinity.datatype.ResourceRef; +import org.infinity.exceptions.AbortException; import org.infinity.gui.StringEditor; import org.infinity.gui.menu.BrowserMenuBar; import org.infinity.resource.AbstractStruct; @@ -61,7 +63,7 @@ public enum Format { */ final String format; - private Format(String format) { + Format(String format) { this.format = format; } @@ -88,6 +90,17 @@ public String format(String text, int strRef) { private static Format format = Format.NONE; private static Boolean hasFemaleTable = null; + /** + * Returns whether a string table is available for the current game. Only the non-interactive BG1 demo should return + * {@code false}. + * + * @return {@code true} if a string table is available, {@code false} otherwise. + */ + public static boolean isAvailable() { + final Path tlkPath = Profile.getProperty(Profile.Key.GET_GAME_DIALOG_FILE); + return (tlkPath != null); + } + /** * Returns whether the current language provides a separate string table for female text. Important: This is * the only way to determine whether a method deals with the female string table when {@code Type.FEMALE} is @@ -108,7 +121,7 @@ public static Charset getCharset() { setCharset(BrowserMenuBar.getInstance().getOptions().getSelectedCharset()); } catch (Throwable t) { // returns a temporary value if BrowserMenuBar has not yet been initialized - return Profile.isEnhancedEdition() ? Misc.CHARSET_UTF8 : Misc.CHARSET_DEFAULT; + return Profile.getDefaultCharset(); } } return charset; @@ -175,7 +188,11 @@ public static void reset(Type type) { * available.) */ public static void resetModified(Type type) { - instance(type)._resetEntries(); + try { + instance(type)._resetEntries(); + } catch (StringTableUnavailableException e) { + // ignore + } } /** @@ -235,7 +252,12 @@ public static Path getPath() { * available.) */ public static Path getPath(Type type) { - return instance(type)._getPath(); + try { + return instance(type)._getPath(); + } catch (StringTableUnavailableException e) { + // ignore + } + return null; } /** Return language id of male string table. */ @@ -247,7 +269,12 @@ public static int getLanguageId() { * Return language id of specified string table. (Defaults to {@code Type.MALE} if specified type is not available.) */ public static int getLanguageId(Type type) { - return instance(type)._getLanguageId(); + try { + return instance(type)._getLanguageId(); + } catch (StringTableUnavailableException e) { + // ignore + } + return 0; } /** @@ -270,7 +297,12 @@ public static int getNumEntries() { * type is not available.) */ public static int getNumEntries(Type type) { - return instance(type)._getNumEntries(); + try { + return instance(type)._getNumEntries(); + } catch (StringTableUnavailableException e) { + // ignore + } + return 0; } /** @@ -289,7 +321,11 @@ public static String getStringRef(int index) throws IndexOutOfBoundsException { * @throws IndexOutOfBoundsException if index is outside of range. */ public static String getStringRef(Type type, int index) throws IndexOutOfBoundsException { - return instance(type)._getStringRef(index, getDisplayFormat()); + try { + return instance(type)._getStringRef(index, getDisplayFormat()); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -308,7 +344,11 @@ public static String getStringRef(int index, Format fmt) throws IndexOutOfBounds * @throws IndexOutOfBoundsException if index is outside of range. */ public static String getStringRef(Type type, int index, Format fmt) throws IndexOutOfBoundsException { - return instance(type)._getStringRef(index, fmt); + try { + return instance(type)._getStringRef(index, fmt); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -322,7 +362,11 @@ public static String getStringRef(Type type, int index, Format fmt) throws Index */ public static int getTranslatedIndex(int index) { if (index >= STRREF_VIRTUAL) { - index = instance(Type.MALE)._getTranslatedIndex(index); + try { + index = instance(Type.MALE)._getTranslatedIndex(index); + } catch (StringTableUnavailableException e) { + // ignore + } } return index; } @@ -345,7 +389,11 @@ public static void setStringRef(int index, String text) throws IndexOutOfBoundsE * @throws IndexOutOfBoundsException if index is outside of range. */ public static void setStringRef(Type type, int index, String text) throws IndexOutOfBoundsException { - instance(type)._setStringRef(index, text); + try { + instance(type)._setStringRef(index, text); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -365,7 +413,11 @@ public static String getSoundResource(int index) throws IndexOutOfBoundsExceptio * @throws IndexOutOfBoundsException if index is outside of range. */ public static String getSoundResource(Type type, int index) throws IndexOutOfBoundsException { - return instance(type)._getSoundResource(index); + try { + return instance(type)._getSoundResource(index); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -387,7 +439,11 @@ public static void setSoundResource(int index, String resRef) throws IndexOutOfB * @throws IndexOutOfBoundsException if index is outside of range. */ public static void setSoundResource(Type type, int index, String resRef) throws IndexOutOfBoundsException { - instance(type)._setSoundResource(index, resRef); + try { + instance(type)._setSoundResource(index, resRef); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -405,7 +461,11 @@ public static short getFlags(int index) throws IndexOutOfBoundsException { * @throws IndexOutOfBoundsException if index is outside of range. */ public static short getFlags(Type type, int index) throws IndexOutOfBoundsException { - return instance(type)._getFlags(index); + try { + return instance(type)._getFlags(index); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -427,7 +487,11 @@ public static void setFlags(int index, short value) throws IndexOutOfBoundsExcep * @throws IndexOutOfBoundsException if index is outside of range. */ public static void setFlags(Type type, int index, short value) throws IndexOutOfBoundsException { - instance(type)._setFlags(index, value); + try { + instance(type)._setFlags(index, value); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -445,7 +509,11 @@ public static int getVolume(int index) throws IndexOutOfBoundsException { * @throws IndexOutOfBoundsException if index is outside of range. */ public static int getVolume(Type type, int index) throws IndexOutOfBoundsException { - return instance(type)._getVolume(index); + try { + return instance(type)._getVolume(index); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -467,7 +535,11 @@ public static void setVolume(int index, int value) throws IndexOutOfBoundsExcept * @throws IndexOutOfBoundsException if index is outside of range. */ public static void setVolume(Type type, int index, int value) throws IndexOutOfBoundsException { - instance(type)._setVolume(index, value); + try { + instance(type)._setVolume(index, value); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -485,7 +557,11 @@ public static int getPitch(int index) throws IndexOutOfBoundsException { * @throws IndexOutOfBoundsException if index is outside of range. */ public static int getPitch(Type type, int index) throws IndexOutOfBoundsException { - return instance(type)._getPitch(index); + try { + return instance(type)._getPitch(index); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -507,7 +583,11 @@ public static void setPitch(int index, int value) throws IndexOutOfBoundsExcepti * @throws IndexOutOfBoundsException if index is outside of range. */ public static void setPitch(Type type, int index, int value) throws IndexOutOfBoundsException { - instance(type)._setPitch(index, value); + try { + instance(type)._setPitch(index, value); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -517,7 +597,11 @@ public static void setPitch(Type type, int index, int value) throws IndexOutOfBo * @throws IndexOutOfBoundsException if index is outside of range. */ public static StringEntry getStringEntry(Type type, int index) throws IndexOutOfBoundsException { - return instance(type)._getEntry(index); + try { + return instance(type)._getEntry(index); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -537,7 +621,12 @@ public static boolean isModified() { * specified type is not available.) */ public static boolean isModified(Type type) { - return instance(type)._isModified(); + try { + return instance(type)._isModified(); + } catch (StringTableUnavailableException e) { + // ignore + } + return false; } /** @@ -546,7 +635,11 @@ public static boolean isModified(Type type) { * @param type The string table */ public static void ensureFullyLoaded(Type type) { - instance(type)._ensureFullyLoaded(); + try { + instance(type)._ensureFullyLoaded(); + } catch (StringTableUnavailableException e) { + // ignore + } } /** @@ -623,7 +716,11 @@ public static int insertEntry(int index) throws IndexOutOfBoundsException { * @throws IndexOutOfBoundsException if index is outside of range. */ public static int insertEntry(Type type, int index) throws IndexOutOfBoundsException { - return instance(type)._insertEntry(index); + try { + return instance(type)._insertEntry(index); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -655,7 +752,11 @@ public static int insertEntry(int index, StringEntry newEntry) throws IndexOutOf * @throws IndexOutOfBoundsException if index is outside of range. */ public static int insertEntry(Type type, int index, StringEntry newEntry) throws IndexOutOfBoundsException { - return instance(type)._insertEntry(index, newEntry); + try { + return instance(type)._insertEntry(index, newEntry); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -680,7 +781,11 @@ public static void removeEntry(int index) throws IndexOutOfBoundsException { * @throws IndexOutOfBoundsException if index is outside of range. */ public static void removeEntry(Type type, int index) throws IndexOutOfBoundsException { - instance(type)._removeEntry(index); + try { + instance(type)._removeEntry(index); + } catch (StringTableUnavailableException e) { + throw new IndexOutOfBoundsException(index + " >= 0"); + } } /** @@ -711,7 +816,11 @@ public static boolean writeModified(Type type, ProgressCallback callback) { try { instance(type)._writeModified(callback); retVal = true; - } catch (IOException e) { + } catch (StringTableUnavailableException e) { + // ignore + } catch (AbortException e) { + Logger.debug(e); + } catch (Exception e) { Logger.error(e); } @@ -746,7 +855,9 @@ public static boolean write(Type type, ProgressCallback callback) { try { instance(type)._write(callback); retVal = true; - } catch (IOException e) { + } catch (StringTableUnavailableException e) { + // ignore + } catch (Exception e) { Logger.trace(e); } @@ -766,7 +877,9 @@ public static boolean write(Type type, Path tlkFile, ProgressCallback callback) try { instance(type)._write(tlkFile, callback); retVal = true; - } catch (IOException e) { + } catch (StringTableUnavailableException e) { + // ignore + } catch (Exception e) { Logger.trace(e); } @@ -784,6 +897,8 @@ public static boolean exportText(Type type, Path outFile, ProgressCallback callb try { instance(type)._exportText(outFile, callback); retVal = true; + } catch (StringTableUnavailableException e) { + // ignore } catch (IOException e) { Logger.error(e); } @@ -835,11 +950,18 @@ public static boolean exportTra(Path outFile, ProgressCallback callback) { return false; } - StringTable tableMale = instance(Type.MALE); - tableMale._ensureFullyLoaded(); - StringTable tableFemale = hasFemaleTable() ? instance(Type.FEMALE) : null; - if (tableFemale != null) { - tableFemale._ensureFullyLoaded(); + StringTable tableMale; + StringTable tableFemale; + + try { + tableMale = instance(Type.MALE); + tableMale._ensureFullyLoaded(); + tableFemale = hasFemaleTable() ? instance(Type.FEMALE) : null; + if (tableFemale != null) { + tableFemale._ensureFullyLoaded(); + } + } catch (StringTableUnavailableException e) { + return false; } if (callback != null) { @@ -947,7 +1069,7 @@ public static boolean exportTra(Path outFile, ProgressCallback callback) { } // Returns specified talk table instance (defaults to male if specified type not available) - private static StringTable instance(Type type) { + private static StringTable instance(Type type) throws StringTableUnavailableException { if (type == null) { type = Type.MALE; } @@ -959,6 +1081,9 @@ private static StringTable instance(Type type) { if (retVal == null) { Path tlkPath = Profile .getProperty((type == Type.FEMALE) ? Profile.Key.GET_GAME_DIALOGF_FILE : Profile.Key.GET_GAME_DIALOG_FILE); + if (tlkPath == null) { + throw new StringTableUnavailableException(); + } retVal = new StringTable(type, tlkPath); TLK_TABLE.put(type, retVal); } @@ -1155,11 +1280,16 @@ private StringEntry _loadEntry(FileChannel ch, int index) int ofsString = ofsStrings + headerData.getInt(); int lenString = headerData.getInt(); headerData.position(0); - String text = null; + String text; + byte[] buffer = null; if (lenString > 0) { try { ch.position(ofsString); - text = StreamUtils.readString(ch, lenString, getCharset()); + final ByteBuffer bb = ByteBuffer.allocate(lenString); + ch.read(bb); + bb.flip(); + buffer = bb.array(); + text = new String(buffer, getCharset()); if (!CharsetDetector.getLookup().isExcluded(index)) { text = CharsetDetector.getLookup().decodeString(text); } @@ -1170,7 +1300,7 @@ private StringEntry _loadEntry(FileChannel ch, int index) } else { text = ""; } - entry = new StringEntry(this, flags, soundRef, volume, pitch, text); + entry = new StringEntry(this, flags, soundRef, volume, pitch, text, buffer); } return entry; } @@ -1279,19 +1409,19 @@ private void _resetModified() { } // Writes data back to disk only if entries have been modified - private void _writeModified(ProgressCallback callback) throws IOException { + private void _writeModified(ProgressCallback callback) throws Exception { if (_isModified()) { _write(callback); } } // Writes currently loaded data to disk regardless of its modified state - private void _write(ProgressCallback callback) throws IOException { + private void _write(ProgressCallback callback) throws Exception { _write(_getPath(), callback); } // Writes currently loaded data to disk under specified filename regardless of its modified state - private void _write(Path tlkPath, ProgressCallback callback) throws IOException { + private void _write(Path tlkPath, ProgressCallback callback) throws Exception { if (tlkPath == null) { throw new NullPointerException(); } @@ -1372,7 +1502,7 @@ private void _write(Path tlkPath, ProgressCallback callback) throws IOException if (callback != null) { success = callback.progress(index++); if (!success) { - throw new Exception("Operation cancelled"); + throw new AbortException("Operation cancelled"); } } buffer = StreamUtils.getByteBuffer(data); @@ -1447,7 +1577,7 @@ private void _exportText(Path outFile, ProgressCallback callback) throws IOExcep // Manages a single string entry public static class StringEntry extends AbstractStruct { // Default entry for non-existing indices - private static final StringEntry INVALID = new StringEntry(null, FLAGS_HAS_TEXT, "", 0, 0, "No such index"); + private static final StringEntry INVALID = new StringEntry(null, FLAGS_HAS_TEXT, "", 0, 0, "No such index", null); private StringTable parent; private short flags; @@ -1455,6 +1585,7 @@ public static class StringEntry extends AbstractStruct { private int volume; private int pitch; private String text; + private byte[] buffer; private boolean modified; public static StringEntry getInvalidEntry() { @@ -1469,14 +1600,15 @@ public StringEntry(StringTable parent) { this.volume = 0; this.pitch = 0; this.text = ""; + this.buffer = new byte[0]; resetModified(); } public StringEntry(StringTable parent, short flags) { - this(parent, flags, "", 0, 0, ""); + this(parent, flags, "", 0, 0, "", null); } - public StringEntry(StringTable parent, short flags, String soundRef, int volume, int pitch, String text) { + public StringEntry(StringTable parent, short flags, String soundRef, int volume, int pitch, String text, byte[] buffer) { super(null, null, 0, 4); this.parent = parent; this.flags = flags; @@ -1484,6 +1616,7 @@ public StringEntry(StringTable parent, short flags, String soundRef, int volume, this.volume = volume; this.pitch = pitch; this.text = text; + this.buffer = (buffer != null) ? Arrays.copyOf(buffer, buffer.length) : new byte[0]; resetModified(); } @@ -1564,6 +1697,10 @@ public void setText(String newText) { } } + public byte[] getBuffer() { + return Arrays.copyOf(buffer, buffer.length); + } + public boolean isModified() { return modified; } @@ -1586,7 +1723,11 @@ public byte[] getSoundRefBytes() { } public byte[] getTextBytes() { - return text.getBytes(StringTable.getCharset()); + if (isModified()) { + return getTextBytes(text); + } else { + return buffer; + } } public byte[] getTextBytes(String text) { @@ -1610,7 +1751,7 @@ public String toString() { @Override public StringEntry clone() { - return new StringEntry(parent, flags, soundRef, volume, pitch, text); + return new StringEntry(parent, flags, soundRef, volume, pitch, text, buffer); } @Override @@ -1684,4 +1825,25 @@ public void done(boolean success) { */ public abstract boolean progress(int index); } + + /** + * A specialized exception class that is only thrown if a string table is not available for the current game. + */ + public static class StringTableUnavailableException extends Exception { + public StringTableUnavailableException() { + super(); + } + + public StringTableUnavailableException(String message) { + super(message); + } + + public StringTableUnavailableException(String message, Throwable cause) { + super(message, cause); + } + + public StringTableUnavailableException(Throwable cause) { + super(cause); + } + } } diff --git a/src/org/infinity/util/StructClipboard.java b/src/org/infinity/util/StructClipboard.java index ca458fb25..2e509d200 100644 --- a/src/org/infinity/util/StructClipboard.java +++ b/src/org/infinity/util/StructClipboard.java @@ -63,7 +63,7 @@ private StructClipboard() { public String toString() { final StringBuilder sb = new StringBuilder(); for (final StructEntry datatype : contents) { - sb.append(datatype.getName()).append(": ").append(datatype.toString()).append('\n'); + sb.append(datatype.getName()).append(": ").append(datatype).append('\n'); } return sb.toString(); } diff --git a/src/org/infinity/util/Threading.java b/src/org/infinity/util/Threading.java index e915a223d..4f49fc07d 100644 --- a/src/org/infinity/util/Threading.java +++ b/src/org/infinity/util/Threading.java @@ -4,10 +4,14 @@ package org.infinity.util; +import java.awt.EventQueue; +import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.LinkedList; import java.util.List; import java.util.Objects; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; import java.util.concurrent.Executors; import java.util.concurrent.ForkJoinTask; @@ -16,6 +20,11 @@ import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + +import javax.swing.SwingUtilities; /** * A convenience class for performing multiple tasks in parallel. @@ -46,7 +55,7 @@ public enum Priority { private final double factor; - private Priority(double factor) { + Priority(double factor) { this.factor = Math.max(-1.0, Math.min(1.0, factor)); } @@ -161,6 +170,15 @@ public int getQueuedSubmissionCount() { return executor.getQueue().size(); } + /** + * Returns the {@link ThreadPoolExecutor} object used to perform background tasks. + * + * @return {@link ThreadPoolExecutor}. + */ + public ThreadPoolExecutor getExecutor() { + return executor; + } + /** * Returns {@code true} if this pool has been shut down. * @@ -271,7 +289,6 @@ public List> invokeAll(Collection> tasks) th * first. Unlike {@link #awaitTermination(long, TimeUnit)} this method does not depend on a shutdown request, which * allows to submit more tasks after completion. * - * @return {@code true} if all submitted tasks terminated and {@code false} if the timeout elapsed before termination. * @throws InterruptedException if interrupted while waiting. */ public void waitForCompletion() throws InterruptedException { @@ -426,6 +443,112 @@ private synchronized boolean releaseThreads() { return false; } + /** + * A helper method that invokes an operation in the event dispatching thread. + * + * @param operation The {@link Operation} to perform. + * @return {@code true} if the operation was completed successfully, {@code false} otherwise. + */ + public static boolean invokeInEventThread(Operation operation) { + if (operation != null) { + try { + if (EventQueue.isDispatchThread()) { + operation.perform(); + } else { + SwingUtilities.invokeAndWait(operation::perform); + } + return true; + } catch (InvocationTargetException | InterruptedException e) { + Logger.debug(e); + } + } + return false; + } + + /** + * A helper method that invokes a consumer in the event dispatching thread. + * + * @param consumer The {@link Consumer} operation to perform. + * @param arg The consumer argument. + * @param Type of the argument. + * @return {@code true} if the operation was completed successfully, {@code false} otherwise. + */ + public static boolean invokeInEventThread(Consumer consumer, T arg) { + if (consumer != null) { + try { + if (EventQueue.isDispatchThread()) { + consumer.accept(arg); + } else { + SwingUtilities.invokeAndWait(() -> consumer.accept(arg)); + } + return true; + } catch (InvocationTargetException | InterruptedException e) { + Logger.debug(e); + } + } + return false; + } + + /** + * A helper method that invokes a function with return value in the event dispatching thread. + * + * @param supplier The {@link Supplier} operation to perform. + * @param defValue Used as return value if the specified operation could not be completed. + * @param Type of the return value. + * @return Return value of the {@code supplier} operation if successful, {@code defValue} otherwise. + */ + public static R invokeInEventThread(Supplier supplier, R defValue) { + final BlockingQueue queue = new ArrayBlockingQueue<>(1); + if (supplier != null) { + try { + if (EventQueue.isDispatchThread()) { + queue.add(supplier.get()); + } else { + SwingUtilities.invokeAndWait(() -> queue.add(supplier.get())); + } + } catch (InvocationTargetException | InterruptedException e) { + Logger.debug(e); + } + } + + if (queue.isEmpty()) { + return defValue; + } else { + return queue.poll(); + } + } + + /** + * A helper method that invokes a function with a single parameter and return value in the event dispatching thread. + * + * @param function The {@link Function} operation to perform. + * @param arg The function argument. + * @param defValue Used as return value if the specified operation could not be completed. + * @param Type of the function parameter. + * @param Type of the return value. + * @return Return value of the {@code function} operation if successful, {@code defValue} otherwise. + */ + public static R invokeInEventThread(Function function, T arg, R defValue) { + final BlockingQueue queue = new ArrayBlockingQueue<>(1); + if (function != null) { + try { + if (EventQueue.isDispatchThread()) { + queue.add(function.apply(arg)); + } else { + SwingUtilities.invokeAndWait(() -> queue.add(function.apply(arg))); + } + } catch (InvocationTargetException | InterruptedException e) { + Logger.debug(e); + } + } + + if (queue.isEmpty()) { + return defValue; + } else { + return queue.poll(); + } + } + /** * Calculates the number of threads to reserve based on the given {@code priority}. * diff --git a/src/org/infinity/util/TriState.java b/src/org/infinity/util/TriState.java new file mode 100644 index 000000000..9a82bb578 --- /dev/null +++ b/src/org/infinity/util/TriState.java @@ -0,0 +1,89 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.util; + +/** + * This enum type acts as a boolean with a third, undefined, state. + */ +public enum TriState { + /** Represents the boolean value of {@code true}. */ + TRUE(Boolean.TRUE), + /** Represents the boolean value of {@code false}. */ + FALSE(Boolean.FALSE), + /** Represent the absence of a defined boolean result. */ + UNDEFINED(null), + ; + + /** + * Returns a {@link TriState} enum instance that represents the specified parameter. + * + * @param b {@code boolean} value to represent. {@code null} is supported and represents the {@link #UNDEFINED} state. + * @return {@link TriState} representation of the argument. + */ + public static TriState of(Boolean b) { + if (b == null) { + return UNDEFINED; + } else { + return b ? TRUE : FALSE; + } + } + + private final Boolean value; + + TriState(Boolean b) { + this.value = b; + } + + /** + * Returns {@code true} if the specified parameter is equal to the state of this {@link TriState} enum instance. + * + * @param b {@code boolean} value to test. {@code null} is supported and represents the {@link #UNDEFINED} state. + * @return {@code true} if the specified argument is equal to the current {@link TriState} enum, {@code false} + * otherwise. + */ + public boolean is(Boolean b) { + if (b == null) { + return isUndefined(); + } else { + return (b.equals(value)); + } + } + + /** + * Returns whether this {@link TriState} enum instance represents the boolean value {@code true}. + * + * @return {@code true} for {@link #TRUE}, {@code false} otherwise. + */ + public boolean isTrue() { + return (value != null && value); + } + + /** + * Returns whether this {@link TriState} enum instance represents the boolean value {@code false}. + * + * @return {@code true} for {@link #FALSE}, {@code false} otherwise. + */ + public boolean isFalse() { + return (value != null && !value); + } + + /** + * Returns whether this {@link TriState} enum instance represents the undefined state. + * + * @return {@code true} for {@link #UNDEFINED}, {@code false} otherwise. + */ + public boolean isUndefined() { + return (value == null); + } + + @Override + public String toString() { + if (value != null) { + return value.toString(); + } else { + return "undefined"; + } + } +} diff --git a/src/org/infinity/util/io/StreamUtils.java b/src/org/infinity/util/io/StreamUtils.java index ed6dd5bf6..3a402eee9 100644 --- a/src/org/infinity/util/io/StreamUtils.java +++ b/src/org/infinity/util/io/StreamUtils.java @@ -225,7 +225,7 @@ public static int copyBytes(ByteBuffer src, int srcOffset, ByteBuffer dst, int d * Reads "length" number of bytes from the specified input stream and returns them as new {@link ByteBuffer} object. */ public static ByteBuffer readBytes(InputStream is, int length) throws IOException { - ByteBuffer bb = null; + ByteBuffer bb; if (length > 0) { bb = getByteBuffer(length); readBytes(is, bb); diff --git a/src/org/infinity/util/io/zip/DlcFileSystem.java b/src/org/infinity/util/io/zip/DlcFileSystem.java index 990a7ac8e..66e34a925 100644 --- a/src/org/infinity/util/io/zip/DlcFileSystem.java +++ b/src/org/infinity/util/io/zip/DlcFileSystem.java @@ -66,7 +66,7 @@ public class DlcFileSystem extends FileSystem { private static final String REGEX_SYNTAX = "regex"; // the outstanding input streams that need to be closed - private final Set streams = Collections.synchronizedSet(new HashSet()); + private final Set streams = Collections.synchronizedSet(new HashSet<>()); // configurable by env map private final String nameEncoding; // default encoding for name/comment @@ -237,7 +237,7 @@ protected FileStore getFileStore(DlcPath path) { } protected DlcFileAttributes getFileAttributes(byte[] path) { - ZipNode folder = null; + ZipNode folder; beginRead(); try { ensureOpen(); @@ -643,11 +643,11 @@ private void checkOptions(Set options) { } } - private final void beginRead() { + private void beginRead() { rwlock.readLock().lock(); } - private final void endRead() { + private void endRead() { rwlock.readLock().unlock(); } @@ -658,7 +658,7 @@ private void ensureOpen() { } private InputStream getInputStream(ZipNode folder) throws IOException { - InputStream is = null; + InputStream is; if (folder == null) { throw new NullPointerException(); diff --git a/src/org/infinity/util/io/zip/DlcFileSystemProvider.java b/src/org/infinity/util/io/zip/DlcFileSystemProvider.java index b44e14d0f..36f29dcea 100644 --- a/src/org/infinity/util/io/zip/DlcFileSystemProvider.java +++ b/src/org/infinity/util/io/zip/DlcFileSystemProvider.java @@ -101,7 +101,7 @@ public FileSystem newFileSystem(Path path, Map env) throws IOExceptio throw new FileSystemAlreadyExistsException(); } } - DlcFileSystem dlcfs = null; + DlcFileSystem dlcfs; try { dlcfs = new DlcFileSystem(this, path, env); } catch (DlcError de) { diff --git a/src/org/infinity/util/io/zip/DlcPath.java b/src/org/infinity/util/io/zip/DlcPath.java index 7193e3af2..d285d9f5f 100644 --- a/src/org/infinity/util/io/zip/DlcPath.java +++ b/src/org/infinity/util/io/zip/DlcPath.java @@ -269,7 +269,7 @@ public DlcPath resolve(Path other) { if (o.isAbsolute()) { return o; } - byte[] resolved = null; + byte[] resolved; if (this.path[path.length - 1] == '/') { resolved = new byte[path.length + o.path.length]; System.arraycopy(path, 0, resolved, 0, path.length); @@ -363,7 +363,7 @@ public DlcPath toAbsolutePath() { byte[] defaultdir = dfs.getDefaultDir().path; int defaultlen = defaultdir.length; boolean endsWith = (defaultdir[defaultlen - 1] == '/'); - byte[] t = null; + byte[] t; if (endsWith) { t = new byte[defaultlen + path.length]; } else { @@ -486,14 +486,14 @@ private DlcPath checkPath(Path path) { private boolean equalsNameAt(DlcPath other, int index) { int mbegin = offsets[index]; - int mlen = 0; + int mlen; if (index == (offsets.length - 1)) { mlen = path.length - mbegin; } else { mlen = offsets[index + 1] - mbegin - 1; } int obegin = other.offsets[index]; - int olen = 0; + int olen; if (index == (other.offsets.length - 1)) { olen = other.path.length - obegin; } else { @@ -727,8 +727,8 @@ protected void setTimes(FileTime mtime, FileTime atime, FileTime ctime) { } protected Map readAttributes(String attributes, LinkOption... options) throws IOException { - String view = null; - String attrs = null; + String view; + String attrs; int colonPos = attributes.indexOf(':'); if (colonPos == -1) { view = "basic"; diff --git a/src/org/infinity/util/tuples/TupleValue0.java b/src/org/infinity/util/tuples/TupleValue0.java index 063f881c6..2cb4deed5 100644 --- a/src/org/infinity/util/tuples/TupleValue0.java +++ b/src/org/infinity/util/tuples/TupleValue0.java @@ -13,7 +13,7 @@ public interface TupleValue0 { * * @return first element of the tuple. */ - public T getValue0(); + T getValue0(); /** * Assigns a new value to the first element of the tuple. @@ -21,5 +21,5 @@ public interface TupleValue0 { * @param newValue the new value to assign. * @return the previously assigned value of the first tuple element. */ - public T setValue0(T newValue); + T setValue0(T newValue); } diff --git a/src/org/infinity/util/tuples/TupleValue1.java b/src/org/infinity/util/tuples/TupleValue1.java index 454198cdb..e305e922f 100644 --- a/src/org/infinity/util/tuples/TupleValue1.java +++ b/src/org/infinity/util/tuples/TupleValue1.java @@ -13,7 +13,7 @@ public interface TupleValue1 { * * @return second element of the tuple. */ - public T getValue1(); + T getValue1(); /** * Assigns a new value to the second element of the tuple. @@ -21,5 +21,5 @@ public interface TupleValue1 { * @param newValue the new value to assign. * @return the previously assigned value of the second tuple element. */ - public T setValue1(T newValue); + T setValue1(T newValue); } diff --git a/src/org/infinity/util/tuples/TupleValue2.java b/src/org/infinity/util/tuples/TupleValue2.java index 9fe8b43c1..b25d4d0d3 100644 --- a/src/org/infinity/util/tuples/TupleValue2.java +++ b/src/org/infinity/util/tuples/TupleValue2.java @@ -13,7 +13,7 @@ public interface TupleValue2 { * * @return third element of the tuple. */ - public T getValue2(); + T getValue2(); /** * Assigns a new value to the third element of the tuple. @@ -21,5 +21,5 @@ public interface TupleValue2 { * @param newValue the new value to assign. * @return the previously assigned value of the third tuple element. */ - public T setValue2(T newValue); + T setValue2(T newValue); } diff --git a/src/org/infinity/util/tuples/TupleValue3.java b/src/org/infinity/util/tuples/TupleValue3.java index d554788e9..7b2c3bee0 100644 --- a/src/org/infinity/util/tuples/TupleValue3.java +++ b/src/org/infinity/util/tuples/TupleValue3.java @@ -13,7 +13,7 @@ public interface TupleValue3 { * * @return fourth element of the tuple. */ - public T getValue3(); + T getValue3(); /** * Assigns a new value to the fourth element of the tuple. @@ -21,5 +21,5 @@ public interface TupleValue3 { * @param newValue the new value to assign. * @return the previously assigned value of the fourth tuple element. */ - public T setValue3(T newValue); + T setValue3(T newValue); } diff --git a/src/org/infinity/util/tuples/TupleValue4.java b/src/org/infinity/util/tuples/TupleValue4.java index b6a0f6656..639b32c8b 100644 --- a/src/org/infinity/util/tuples/TupleValue4.java +++ b/src/org/infinity/util/tuples/TupleValue4.java @@ -13,7 +13,7 @@ public interface TupleValue4 { * * @return fifth element of the tuple. */ - public T getValue4(); + T getValue4(); /** * Assigns a new value to the fifth element of the tuple. @@ -21,5 +21,5 @@ public interface TupleValue4 { * @param newValue the new value to assign. * @return the previously assigned value of the fifth tuple element. */ - public T setValue4(T newValue); + T setValue4(T newValue); } diff --git a/src/org/infinity/util/tuples/TupleValue5.java b/src/org/infinity/util/tuples/TupleValue5.java index cf7ce48dc..e37ad19ef 100644 --- a/src/org/infinity/util/tuples/TupleValue5.java +++ b/src/org/infinity/util/tuples/TupleValue5.java @@ -13,7 +13,7 @@ public interface TupleValue5 { * * @return sixth element of the tuple. */ - public T getValue5(); + T getValue5(); /** * Assigns a new value to the sixth element of the tuple. @@ -21,5 +21,5 @@ public interface TupleValue5 { * @param newValue the new value to assign. * @return the previously assigned value of the sixth tuple element. */ - public T setValue5(T newValue); + T setValue5(T newValue); }