diff --git a/src/org/infinity/NearInfinity.java b/src/org/infinity/NearInfinity.java index 44425266d..330bfc4a8 100644 --- a/src/org/infinity/NearInfinity.java +++ b/src/org/infinity/NearInfinity.java @@ -68,9 +68,11 @@ import org.infinity.gui.QuickSearch; import org.infinity.gui.ResourceTree; import org.infinity.gui.StatusBar; +import org.infinity.gui.StructViewer; import org.infinity.gui.ViewFrame; import org.infinity.gui.WindowBlocker; import org.infinity.icon.Icons; +import org.infinity.resource.AbstractStruct; import org.infinity.resource.Closeable; import org.infinity.resource.EffectFactory; import org.infinity.resource.Profile; @@ -550,6 +552,15 @@ public void actionPerformed(ActionEvent event) } finally { WindowBlocker.blockWindow(this, false); } + } else if (event.getActionCommand().equals("RefreshView")) { + // repaint UI controls of current view + if (getViewable() instanceof AbstractStruct) { + StructViewer sv = ((AbstractStruct)getViewable()).getViewer(); + if (sv != null) + SwingUtilities.updateComponentTreeUI(sv); + } + // repaint UI controls of child windows + ChildFrame.updateWindowGUIs(); } else if (event.getActionCommand().equals("ChangeLook")) { try { LookAndFeelInfo info = BrowserMenuBar.getInstance().getLookAndFeel(); diff --git a/src/org/infinity/datatype/ResourceRef.java b/src/org/infinity/datatype/ResourceRef.java index 4a1b087d4..089283ab7 100644 --- a/src/org/infinity/datatype/ResourceRef.java +++ b/src/org/infinity/datatype/ResourceRef.java @@ -126,7 +126,7 @@ public JComponent edit(final ActionListener container) //FIXME: ResRefChecker check only that point is exist, so this must be // the same check or this check must be inside isLegalEntry(...) // There only 2 places where isLegalEntry is called: this and ResRefChecker - if (isLegalEntry(entry) && entry.getResourceName().lastIndexOf('.') <= 8) { + if (isLegalEntry(entry) && entry.getResourceRef().length() <= 8) { values.add(new ResourceRefEntry(entry)); } } @@ -226,19 +226,17 @@ public boolean updateValue(AbstractStruct struct) if (entry == null) { setValue(selected.name); } else { - int i = -1; + boolean found = false; for (final String type : types) { - //TODO: It seems that instead of toString getExtension must be used - i = entry.getResourceName().indexOf('.' + type.toUpperCase(Locale.ENGLISH)); - if (i != -1) { + found = entry.getExtension().equalsIgnoreCase(type); + if (found) { this.type = type; - setValue(entry.getResourceName().substring(0, i)); + setValue(entry.getResourceRef()); break; } } - if (i == -1) { + if (!found) return false; - } } // notifying listeners diff --git a/src/org/infinity/gui/BIFFEditor.java b/src/org/infinity/gui/BIFFEditor.java index f3f9371f6..27b4cf8e4 100644 --- a/src/org/infinity/gui/BIFFEditor.java +++ b/src/org/infinity/gui/BIFFEditor.java @@ -371,7 +371,7 @@ private void setProgress(int level, boolean ok) if (ok) boxes[level - 1].setSelected(true); else - boxes[level - 1].setForeground(Color.red); + boxes[level - 1].setForeground(Color.RED); bok.setEnabled(level == boxes.length || !ok); } diff --git a/src/org/infinity/gui/BrowserMenuBar.java b/src/org/infinity/gui/BrowserMenuBar.java index 96ffaa228..44abd01d9 100644 --- a/src/org/infinity/gui/BrowserMenuBar.java +++ b/src/org/infinity/gui/BrowserMenuBar.java @@ -106,7 +106,7 @@ public final class BrowserMenuBar extends JMenuBar implements KeyEventDispatcher { - public static final String VERSION = "v2.1-20200419"; + public static final String VERSION = "v2.1-20200425"; public static final LookAndFeelInfo DEFAULT_LOOKFEEL = new LookAndFeelInfo("Metal", "javax.swing.plaf.metal.MetalLookAndFeel"); @@ -343,7 +343,7 @@ public boolean showDlgTechInfo() public boolean getColoredOffsetsEnabled() { - return optionsMenu.optionShowColoredOffsets.isSelected(); + return optionsMenu.optionShowColoredStructures.isSelected(); } public boolean getHexColorMapEnabled() @@ -1756,7 +1756,7 @@ private static final class OptionsMenu extends JMenu implements ActionListener, private static final String OPTION_CACHEOVERRIDE = "CacheOverride"; private static final String OPTION_MORECOMPILERWARNINGS = "MoreCompilerWarnings"; private static final String OPTION_SHOWSTRREFS = "ShowStrrefs"; - private static final String OPTION_SHOWCOLOREDOFFSETS = "ShowColoredOffsets"; + private static final String OPTION_SHOWCOLOREDSTRUCTURES = "ShowColoredStructures"; private static final String OPTION_SHOWHEXCOLORED = "ShowHexColored"; private static final String OPTION_KEEPVIEWONCOPY = "UpdateTreeOnCopy"; private static final String OPTION_SHOWTREESEARCHNAMES = "ShowTreeSearchNames"; @@ -1832,7 +1832,7 @@ private static final class OptionsMenu extends JMenu implements ActionListener, private JCheckBoxMenuItem optionBackupOnSave, optionShowOffset, optionIgnoreOverride, optionIgnoreReadErrors, optionCacheOverride, optionShowStrrefs, - optionShowColoredOffsets, optionShowHexColored, optionShowUnknownResources, + optionShowColoredStructures, optionShowHexColored, optionShowUnknownResources, optionKeepViewOnCopy, optionTreeSearchNames, optionHighlightOverridden; // optionMonitorFileChanges; @@ -1861,27 +1861,28 @@ private OptionsMenu() new JCheckBoxMenuItem("Ignore Overrides", getPrefs().getBoolean(OPTION_IGNOREOVERRIDE, false)); add(optionIgnoreOverride); optionIgnoreReadErrors = - new JCheckBoxMenuItem("Ignore Read Errors", getPrefs().getBoolean(OPTION_IGNOREREADERRORS, false)); + new JCheckBoxMenuItem("Ignore read errors", getPrefs().getBoolean(OPTION_IGNOREREADERRORS, false)); add(optionIgnoreReadErrors); optionShowUnknownResources = - new JCheckBoxMenuItem("Show Unknown Resource Types", getPrefs().getBoolean(OPTION_SHOWUNKNOWNRESOURCES, true)); + new JCheckBoxMenuItem("Show unknown resource types", getPrefs().getBoolean(OPTION_SHOWUNKNOWNRESOURCES, true)); optionShowUnknownResources.setActionCommand("Refresh"); optionShowUnknownResources.addActionListener(NearInfinity.getInstance()); optionShowUnknownResources.setToolTipText("Uncheck this option to hide unknown or unsupported resource types and invalid filenames."); add(optionShowUnknownResources); optionShowOffset = - new JCheckBoxMenuItem("Show Hex Offsets", getPrefs().getBoolean(OPTION_SHOWOFFSETS, false)); + new JCheckBoxMenuItem("Show hex offsets", getPrefs().getBoolean(OPTION_SHOWOFFSETS, false)); add(optionShowOffset); - optionTreeSearchNames = new JCheckBoxMenuItem("Show Search Names in Resource Tree", getPrefs().getBoolean(OPTION_SHOWTREESEARCHNAMES, true)); + optionTreeSearchNames = + new JCheckBoxMenuItem("Show search names in resource tree", getPrefs().getBoolean(OPTION_SHOWTREESEARCHNAMES, true)); optionTreeSearchNames.setActionCommand("RefreshTree"); optionTreeSearchNames.addActionListener(NearInfinity.getInstance()); add(optionTreeSearchNames); - optionHighlightOverridden = new JCheckBoxMenuItem("Show Overridden Files in Bold in Resource Tree", getPrefs().getBoolean(OPTION_HIGHLIGHT_OVERRIDDEN, true)); + optionHighlightOverridden = + new JCheckBoxMenuItem("Show overridden files in bold in resource tree", getPrefs().getBoolean(OPTION_HIGHLIGHT_OVERRIDDEN, true)); optionHighlightOverridden.setActionCommand("RefreshTree"); optionHighlightOverridden.addActionListener(NearInfinity.getInstance()); - optionHighlightOverridden.setToolTipText("If checked, files, that contains in game index (.key file) and located " - + "in the Override folder, will be shown in bold in the Resource Tree. " - + "This setting has no effect if override files are shown only in the Override folder"); + optionHighlightOverridden.setToolTipText("If checked, files that are listed in the chitin.key and are located in the Override folder, will be shown
" + + "in bold in the Resource Tree. This setting has no effect if override files are shown only in the Override folder."); add(optionHighlightOverridden); // optionMonitorFileChanges = // new JCheckBoxMenuItem("Autoupdate resource tree", getPrefs().getBoolean(OPTION_MONITORFILECHANGES, true)); @@ -1900,9 +1901,11 @@ private OptionsMenu() optionShowStrrefs = new JCheckBoxMenuItem("Show Strrefs in View tabs", getPrefs().getBoolean(OPTION_SHOWSTRREFS, false)); add(optionShowStrrefs); - optionShowColoredOffsets = - new JCheckBoxMenuItem("Show colored offset fields in Edit tabs", getPrefs().getBoolean(OPTION_SHOWCOLOREDOFFSETS, true)); - add(optionShowColoredOffsets); + optionShowColoredStructures = + new JCheckBoxMenuItem("Show colored structures in Edit tabs", getPrefs().getBoolean(OPTION_SHOWCOLOREDSTRUCTURES, true)); + optionShowColoredStructures.setActionCommand("RefreshView"); + optionShowColoredStructures.addActionListener(NearInfinity.getInstance()); + add(optionShowColoredStructures); optionShowHexColored = new JCheckBoxMenuItem("Show colored blocks in Raw tabs", getPrefs().getBoolean(OPTION_SHOWHEXCOLORED, true)); add(optionShowHexColored); @@ -2523,7 +2526,7 @@ private void storePreferences() getPrefs().putBoolean(OPTION_MORECOMPILERWARNINGS, optionMoreCompileWarnings.isSelected()); getPrefs().putBoolean(OPTION_SHOWSTRREFS, optionShowStrrefs.isSelected()); dialogViewerMenu.storePreferences(getPrefs()); - getPrefs().putBoolean(OPTION_SHOWCOLOREDOFFSETS, optionShowColoredOffsets.isSelected()); + getPrefs().putBoolean(OPTION_SHOWCOLOREDSTRUCTURES, optionShowColoredStructures.isSelected()); getPrefs().putBoolean(OPTION_SHOWHEXCOLORED, optionShowHexColored.isSelected()); getPrefs().putBoolean(OPTION_KEEPVIEWONCOPY, optionKeepViewOnCopy.isSelected()); getPrefs().putBoolean(OPTION_SHOWTREESEARCHNAMES, optionTreeSearchNames.isSelected()); diff --git a/src/org/infinity/gui/StructViewer.java b/src/org/infinity/gui/StructViewer.java index 8a7720b22..264c6423b 100644 --- a/src/org/infinity/gui/StructViewer.java +++ b/src/org/infinity/gui/StructViewer.java @@ -29,6 +29,10 @@ import java.awt.print.PrinterJob; import java.nio.ByteBuffer; import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.swing.BorderFactory; import javax.swing.Icon; @@ -62,6 +66,8 @@ import org.infinity.datatype.Flag; import org.infinity.datatype.HexNumber; import org.infinity.datatype.InlineEditable; +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsReference; import org.infinity.datatype.IsTextual; import org.infinity.datatype.Readable; import org.infinity.datatype.ResourceRef; @@ -92,6 +98,8 @@ import org.infinity.search.AttributeSearcher; import org.infinity.search.DialogItemRefSearcher; import org.infinity.search.DialogStateReferenceSearcher; +import org.infinity.search.advanced.AdvancedSearch; +import org.infinity.search.advanced.SearchOptions; import org.infinity.util.Misc; import org.infinity.util.Pair; import org.infinity.util.StructClipboard; @@ -123,6 +131,7 @@ 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_ADD_ADV_SEARCH = "AddAdvSearch"; public static final String CMD_SHOW_IN_TREE = "ShowInTree"; public static final String CMD_SHOWVIEWER = "ShowView"; public static final String CMD_SHOWNEWVIEWER = "ShowNewView"; @@ -134,10 +143,12 @@ public final class StructViewer extends JPanel implements ListSelectionListener, private static final String CARD_EDIT = "Edit"; private static final String CARD_TEXT = "Text"; + private static Class lastNameStruct, lastIndexStruct; private static String lastName; private static int lastIndex; private final AbstractStruct struct; + private final Map, Color> fieldColors = new HashMap<>(); private final CardLayout cards = new CardLayout(); private final JMenuItem miCopyValue = createMenuItem(CMD_COPYVALUE, "Copy value", Icons.getIcon(Icons.ICON_COPY_16), this); private final JMenuItem miPasteValue = createMenuItem(CMD_PASTEVALUE, "Replace value", Icons.getIcon(Icons.ICON_PASTE_16), this); @@ -152,6 +163,7 @@ public final class StructViewer extends JPanel implements ListSelectionListener, private final JMenuItem miToHexInt = createMenuItem(CMD_TOHEXINT, "Edit as hex number", Icons.getIcon(Icons.ICON_REFRESH_16), this); private final JMenuItem miToFlags = createMenuItem(CMD_TOFLAGS, "Edit as bit field", Icons.getIcon(Icons.ICON_REFRESH_16), this); private final JMenuItem miReset = createMenuItem(CMD_RESET, "Reset field type", Icons.getIcon(Icons.ICON_REFRESH_16), this); + private final JMenuItem miAddToAdvSearch = createMenuItem(CMD_ADD_ADV_SEARCH, "Add to Advanced Search", Icons.getIcon(Icons.ICON_FIND_16), 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.getIcon(Icons.ICON_SELECT_IN_TREE_16), this); private final JMenuItem miShowViewer = createMenuItem(CMD_SHOWVIEWER, "Show in viewer", Icons.getIcon(Icons.ICON_ROW_INSERT_AFTER_16), this); @@ -224,9 +236,12 @@ public void mouseClicked(MouseEvent e) Object selected = table.getModel().getValueAt(table.getSelectedRow(), 1); if (selected instanceof Viewable) { createViewFrame(table.getTopLevelAncestor(), (Viewable)selected); - } else - if (selected instanceof SectionOffset) { - selectOffset((SectionOffset)selected); + } + else if (selected instanceof SectionOffset) { + selectFirstEntryOfType(((SectionOffset)selected).getSection()); + } + else if (selected instanceof SectionCount) { + selectFirstEntryOfType(((SectionCount)selected).getSection()); } } } @@ -239,9 +254,18 @@ public Component getTableCellRendererComponent(JTable table, Object value, int column) { final StructEntry field = (StructEntry)table.getModel().getValueAt(row, 1); - final boolean isColored = BrowserMenuBar.getInstance().getColoredOffsetsEnabled() && - field instanceof SectionOffset; - setBackground(isColored ? Color.cyan : null); + Class cls = null; + if (BrowserMenuBar.getInstance().getColoredOffsetsEnabled()) { + if (field instanceof SectionOffset) + cls = ((SectionOffset)field).getSection(); + else if (field instanceof SectionCount) + cls = ((SectionCount)field).getSection(); + else if (field instanceof AbstractStruct) + cls = field.getClass(); + else if (fieldColors.containsKey(field.getClass())) // consider only referenced simple field types + cls = field.getClass(); + } + setBackground(getClassColor(cls)); super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if (column == 2) @@ -257,6 +281,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, popupmenu.add(miCut); popupmenu.add(miCopy); popupmenu.add(miPaste); + popupmenu.addSeparator(); popupmenu.add(miToHex); popupmenu.add(miToBin); popupmenu.add(miToDec); @@ -266,6 +291,8 @@ public Component getTableCellRendererComponent(JTable table, Object value, popupmenu.add(miToResref); popupmenu.add(miToString); popupmenu.add(miReset); + popupmenu.addSeparator(); + popupmenu.add(miAddToAdvSearch); popupmenu.add(miGotoOffset); if (struct instanceof DlgResource) { popupmenu.add(miShowInTree); @@ -288,6 +315,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, miToResref.setEnabled(false); miToString.setEnabled(false); miReset.setEnabled(false); + miAddToAdvSearch.setEnabled(false); miGotoOffset.setEnabled(false); miShowInTree.setEnabled(false); miShowViewer.setEnabled(false); @@ -416,7 +444,9 @@ public Component getTableCellRendererComponent(JTable table, Object value, sViewer = struct.getParent().getParent().getViewer(); } if (sViewer != null && sViewer.tabbedPane != null) { - tabbedPane.setSelectedIndex(sViewer.tabbedPane.getSelectedIndex()); + // make sure tab index is within bounds + int idx = Math.max(Math.min(sViewer.tabbedPane.getSelectedIndex(), tabbedPane.getTabCount() - 1), 0); + tabbedPane.setSelectedIndex(idx); } } else if (lastIndexStruct == struct.getClass()) { tabbedPane.setSelectedIndex(lastIndex); @@ -565,7 +595,16 @@ public void actionPerformed(ActionEvent event) } else if (CMD_RESET.equals(cmd)) { convertAttribute(min, miReset); } else if (CMD_GOTO_OFFSET.equals(cmd)) { - selectOffset((SectionOffset)table.getValueAt(min, 1)); + final StructEntry se = (StructEntry)table.getValueAt(min, 1); + Class cls = null; + if (se instanceof SectionOffset) + cls = ((SectionOffset)se).getSection(); + else if (se instanceof SectionCount) + cls = ((SectionCount)se).getSection(); + if (cls != null) + selectFirstEntryOfType(cls); + } else if (CMD_ADD_ADV_SEARCH.equals(cmd)) { + addToAdvancedSearch((StructEntry)table.getValueAt(min, 1)); } else if (CMD_SHOW_IN_TREE.equals(cmd)) { // this should only be available for DlgResources final DlgResource dlgRes = (DlgResource) struct; @@ -684,6 +723,7 @@ public void valueChanged(ListSelectionEvent event) miToResref.setEnabled(false); miToString.setEnabled(false); miReset.setEnabled(false); + miAddToAdvSearch.setEnabled(false); miGotoOffset.setEnabled(false); miShowInTree.setEnabled(false); miShowViewer.setEnabled(false); @@ -765,7 +805,8 @@ public void valueChanged(ListSelectionEvent event) miReset.setEnabled(isDataType && isReadable && getCachedStructEntry(((Datatype)selected).getOffset()) instanceof Readable && !(selected instanceof AbstractCode)); - miGotoOffset.setEnabled(selected instanceof SectionOffset); + 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); final boolean isSpecialDlgStruct = isSpecialDlgTreeItem @@ -1361,26 +1402,114 @@ private void select(StructEntry field) } } +// /** +// * Selects in the table field that corresponds to the specified offset entry +// * or opens new child structure viewer, if corresponding field not in this table +// * +// * @param entry Offset to show +// */ +// private void selectOffset(SectionOffset entry) +// { +// // Select entry at offset +// final int offset = entry.getValue(); +// final StructEntry field = struct.getAttribute(offset, entry.getSection()); +// if (field != null) { +// final AbstractStruct parent = field.getParent(); +// if (parent != struct) { +// new ViewFrame(this, parent); +// } +// parent.getViewer().select(field); +// } +// } + /** - * Selects in the table field that corresponds to the specified offset entry - * or opens new child structure viewer, if corresponding field not in this table - * - * @param entry Offset to show + * Selects the first structure of the specified class type. + * @param cls Class of the structure to search. */ - private void selectOffset(SectionOffset entry) + private void selectFirstEntryOfType(Class cls) { - // Select entry at offset - final int offset = entry.getValue(); - final StructEntry field = struct.getAttribute(offset, entry.getSection()); - if (field != null) { - final AbstractStruct parent = field.getParent(); - if (parent != struct) { - new ViewFrame(this, parent); + if (cls != null) { + final StructEntry field = struct.getField(cls, 0); + if (field != null) { + final AbstractStruct parent = field.getParent(); + if (parent != struct ) { + new ViewFrame(this, parent); + } + parent.getViewer().select(field); } - parent.getViewer().select(field); } } + /** + * Returns the color associated with the specified class type. Returns Color.WHITE if no class type specified. + * @param cls The class associated with the field value. + * @return Color corresponding to the specified field class type. {@code Color.WHITE} by default. + */ + private Color getClassColor(Class cls) + { + if (cls != null) { + return fieldColors.computeIfAbsent(cls, + c -> ViewerUtil.BACKGROUND_COLORS[fieldColors.size() % ViewerUtil.BACKGROUND_COLORS.length]); + } + return Color.WHITE; + } + + /** + * Creates an Advanced Search filter out of the specified {@code StructEntry} instance + * and adds it to the Advanced Search dialog. + */ + private void addToAdvancedSearch(StructEntry entry) + { + if (entry == null || entry instanceof AbstractStruct) + return; + + // setting search value + SearchOptions so = null; + if (entry instanceof Flag) { + so = new SearchOptions(); + so.setValueBitfield(((Flag)entry).getValue(), SearchOptions.BitFieldMode.Exact); + } else if (entry instanceof IsReference) { + so = new SearchOptions(); + so.setValueResource(((IsReference)entry).getResourceName()); + } else if (entry instanceof IsNumeric) { + so = new SearchOptions(); + so.setValueNumber(((IsNumeric)entry).getValue()); + } else if (!(entry instanceof Unknown)) { + so = new SearchOptions(); + so.setValueText(entry.toString(), false, false); + } else { + return; + } + + // setting structure level and field name + List structure = so.getStructure(); + for (AbstractStruct struct = entry.getParent(); struct != null && struct.getParent() != null; struct = struct.getParent()) + structure.add(0, getStrippedFieldName(struct.getName())); + so.setSearchName(entry.getName(), true, false); + + // root structure of resource needed for resource name + AbstractStruct root = struct; + while (root.getParent() != null) + root = root.getParent(); + if (root == null || root.getResourceEntry() == null) + return; + + AdvancedSearch dlg = ChildFrame.show(AdvancedSearch.class, () -> new AdvancedSearch()); + dlg.setResourceType(root.getResourceEntry().getExtension()); + dlg.addFilter(so); + } + + /** Strips numeric indices from the specified field name. */ + private String getStrippedFieldName(String name) + { + Pattern p = Pattern.compile("(.+)\\s+\\d+"); + Matcher m = p.matcher(name); + if (m.find()) + return m.group(1); + + return name; + } + // -------------------------- INNER CLASSES -------------------------- private final class PopupListener extends MouseAdapter @@ -1414,7 +1543,7 @@ private StructTable() public int print(Graphics graphics, PageFormat pageFormat, int pageIndex) throws PrinterException { Graphics2D g2 = (Graphics2D)graphics; - g2.setColor(Color.black); + g2.setColor(Color.BLACK); int fontHeight = g2.getFontMetrics().getHeight(); int fontDesent = g2.getFontMetrics().getDescent(); diff --git a/src/org/infinity/gui/ViewerUtil.java b/src/org/infinity/gui/ViewerUtil.java index af0a2b91e..1cb61024d 100644 --- a/src/org/infinity/gui/ViewerUtil.java +++ b/src/org/infinity/gui/ViewerUtil.java @@ -5,6 +5,7 @@ package org.infinity.gui; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; @@ -65,6 +66,29 @@ public final class ViewerUtil { + /** + * A collection of pastel shaded colors that can be used to colorize the background of + * list, table or tree items. + */ + public static final Color[] BACKGROUND_COLORS = { + new Color(0xceccff), + new Color(0xffcce6), + new Color(0xccffe9), + new Color(0xfaffcc), + new Color(0xccddff), + new Color(0xffccf9), + new Color(0xccffd7), + new Color(0xfff2cc), + new Color(0xccf0ff), + new Color(0xf4ccff), + new Color(0xd5ffcc), + new Color(0xffdfcc), + new Color(0xccfffc), + new Color(0xe1ccff), + new Color(0xe8ffcc), + new Color(0xffcccc), + }; + public static void addLabelFieldPair(JPanel panel, StructEntry entry, GridBagLayout gbl, GridBagConstraints gbc, boolean endline) { diff --git a/src/org/infinity/gui/converter/BamFilterColorReplace.java b/src/org/infinity/gui/converter/BamFilterColorReplace.java index 61e93142b..c66b5ab75 100644 --- a/src/org/infinity/gui/converter/BamFilterColorReplace.java +++ b/src/org/infinity/gui/converter/BamFilterColorReplace.java @@ -23,6 +23,7 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; import javax.swing.AbstractAction; import javax.swing.BorderFactory; @@ -292,6 +293,9 @@ public void loadPalette(Path paletteFile) throws Exception int[] palette = null; if ("BM".equals(new String(signature, 0, 2))) { palette = ColorConvert.loadPaletteBMP(paletteFile); + } else if (Arrays.equals(Arrays.copyOfRange(signature, 0, 4), + new byte[]{(byte)0x89, 0x50, 0x4e, 0x47})) { + palette = ColorConvert.loadPalettePNG(paletteFile, ConvertToBam.getUseAlpha()); } else if ("RIFF".equals(new String(signature, 0, 4))) { palette = ColorConvert.loadPalettePAL(paletteFile); } else { @@ -299,7 +303,7 @@ public void loadPalette(Path paletteFile) throws Exception String ver = new String(signature, 4, 4); if ("BAM ".equals(sig) || "BAMC".equals(sig)) { if ("V1 ".equals(ver)) { - palette = ColorConvert.loadPaletteBAM(paletteFile); + palette = ColorConvert.loadPaletteBAM(paletteFile, ConvertToBam.getUseAlpha()); } else { throw new Exception(String.format("BAM file \"%s\" does not contain palette data.", paletteFile.getFileName())); diff --git a/src/org/infinity/gui/converter/BamPaletteDialog.java b/src/org/infinity/gui/converter/BamPaletteDialog.java index 3b1ce8f5f..37a54e5c4 100644 --- a/src/org/infinity/gui/converter/BamPaletteDialog.java +++ b/src/org/infinity/gui/converter/BamPaletteDialog.java @@ -264,13 +264,13 @@ public void loadExternalPalette(int type, Path paletteFile) throws Exception } else if (Arrays.equals(Arrays.copyOfRange(signature, 0, 4), new byte[]{(byte)0x89, 0x50, 0x4e, 0x47})) { // PNG supports palette with alpha channel - palette = ColorConvert.loadPalettePNG(paletteFile); + palette = ColorConvert.loadPalettePNG(paletteFile, ConvertToBam.getUseAlpha()); } else if ("RIFF".equals(new String(signature, 0, 4))) { palette = ColorConvert.loadPalettePAL(paletteFile); } else { String s = new String(signature); if ("BAM V1 ".equals(s) || "BAMCV1 ".equals(s)) { - palette = ColorConvert.loadPaletteBAM(paletteFile); + palette = ColorConvert.loadPaletteBAM(paletteFile, ConvertToBam.getUseAlpha()); } else { // Photoshop ACT files don't have a header palette = ColorConvert.loadPaletteACT(paletteFile); diff --git a/src/org/infinity/resource/AbstractStruct.java b/src/org/infinity/resource/AbstractStruct.java index 34a659e8b..3086f51fb 100644 --- a/src/org/infinity/resource/AbstractStruct.java +++ b/src/org/infinity/resource/AbstractStruct.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.stream.Collectors; import javax.swing.JComponent; import javax.swing.JOptionPane; @@ -50,6 +51,20 @@ public abstract class AbstractStruct extends AbstractTableModel implements Struc public static final String COMMON_UNUSED = "Unused"; public static final String COMMON_UNUSED_BYTES = "Unused bytes?"; + // Commonly used string arrays + public static final String[] OPTION_NOYES = {"No", "Yes"}; + public static final String[] OPTION_YESNO = {"Yes", "No"}; + public static final String[] OPTION_SCHEDULE = {"Not active", + "00:30-01:29", "01:30-02:29", "02:30-03:29", "03:30-04:29", + "04:30-05:29", "05:30-06:29", "06:30-07:29", "07:30-08:29", + "08:30-09:29", "09:30-10:29", "10:30-11:29", "11:30-12:29", + "12:30-13:29", "13:30-14:29", "14:30-15:29", "15:30-16:29", + "16:30-17:29", "17:30-18:29", "18:30-19:29", "19:30-20:29", + "20:30-21:29", "21:30-22:29", "22:30-23:29", "23:30-00:29"}; + public static final String[] OPTION_ORIENTATION = { "South", "SSW", "SW", "WSW", "West", "WNW", "NW", "NNW", + "North", "NNE", "NE", "ENE", "East", "ESE", "SE", "SSE" }; + + /** Identifies the intention to removal of rows or columns. */ public static final int WILL_BE_DELETE = -2; @@ -410,7 +425,7 @@ public void write(OutputStream os) throws IOException public String toString() { // limit text length to speed things up - int capacity = 160; + int capacity = 256; final StringBuilder sb = new StringBuilder(capacity); for (int i = 0, count = fields.size(); i < count; i++) { final StructEntry field = fields.get(i); @@ -744,6 +759,35 @@ public List getFields() return fields; } + /** + * Returns an unmodifiable list of fields of this structure matching the specified structure type. + * @param type The class type to filter. Specify {@code null} to return all fields of this structure. + * @return Unmodifiable list of fields of {@code type}. + */ + public List getFields(Class type) + { + return Collections.unmodifiableList( + fields + .stream() + .filter(se -> type == null || type.isAssignableFrom(se.getClass())) + .collect(Collectors.toList())); + } + + /** + * Returns the first {@code StructEntry} object of the specified class type. + * @param type Class of the {@code StructEntry} object to return. + * @param offset Start offset to search {@code StructEntry} instances. + * @return First available {@code StructEntry} instance, {@code null} otherwise. + */ + public StructEntry getField(Class type, int offset) + { + return fields + .stream() + .filter(se -> se.getOffset() >= offset && (type == null || type.isAssignableFrom(se.getClass()))) + .findFirst() + .orElse(null); + } + /** * Returns the StructEntry object at the specified index. * @param index The index of the desired StructEntry object. diff --git a/src/org/infinity/resource/EffectFactory.java b/src/org/infinity/resource/EffectFactory.java index 01951c2bf..5966c884a 100644 --- a/src/org/infinity/resource/EffectFactory.java +++ b/src/org/infinity/resource/EffectFactory.java @@ -34,7 +34,6 @@ import org.infinity.datatype.Unknown; import org.infinity.datatype.UnsignDecNumber; import org.infinity.datatype.UpdateListener; -import org.infinity.resource.are.Actor; import org.infinity.resource.itm.ItmResource; import org.infinity.util.IdsMapEntry; import org.infinity.util.LongIntegerHashMap; @@ -129,8 +128,6 @@ public static enum EffectEntry { public static final LongIntegerHashMap m_colorloc = new LongIntegerHashMap(); public static final LongIntegerHashMap m_proj_iwd = new LongIntegerHashMap(); public static final String[] s_inctype = {"Increment", "Set", "Set % of"}; - public static final String[] s_yesno = {"Yes", "No"}; - public static final String[] s_noyes = {"No", "Yes"}; public static final String[] s_visuals = { // 0..9 @@ -940,7 +937,7 @@ private static boolean updateOpcode342(AbstractStruct struct) throws Exception StructEntry newEntry = null; switch (param2) { case 1: - newEntry = new Bitmap(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, "Enabled?", s_noyes); + newEntry = new Bitmap(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, "Enabled?", AbstractStruct.OPTION_NOYES); break; case 2: newEntry = new ColorValue(getEntryData(struct, EffectEntry.IDX_PARAM1), 0, 4, "Color"); @@ -2029,7 +2026,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o if (Profile.getEngine() == Profile.Engine.PST) { s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); } else { - s.add(new Bitmap(buffer, offset, 4, "Display text?", s_yesno)); + s.add(new Bitmap(buffer, offset, 4, "Display text?", AbstractStruct.OPTION_YESNO)); } final String[] s_type; if (Profile.getEngine() == Profile.Engine.BG1) { @@ -2209,7 +2206,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o case 32: // Raise dead s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); if (Profile.isEnhancedEdition()) { - s.add(new Bitmap(buffer, offset + 4, 4, "Restore creature animation?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Restore creature animation?", AbstractStruct.OPTION_NOYES)); } else { s.add(new DecNumber(buffer, offset + 4, 4, AbstractStruct.COMMON_UNUSED)); } @@ -2246,7 +2243,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); if (Profile.getEngine() == Profile.Engine.IWD || Profile.getEngine() == Profile.Engine.IWD2 || Profile.isEnhancedEdition()) { - s.add(new Bitmap(buffer, offset + 4, 4, "Wake on damage?", s_yesno)); + s.add(new Bitmap(buffer, offset + 4, 4, "Wake on damage?", AbstractStruct.OPTION_YESNO)); } else { s.add(new DecNumber(buffer, offset + 4, 4, AbstractStruct.COMMON_UNUSED)); } @@ -2410,7 +2407,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o break; case 68: // Unsummon creature - s.add(new Bitmap(buffer, offset, 4, "Display text?", s_noyes)); + s.add(new Bitmap(buffer, offset, 4, "Display text?", AbstractStruct.OPTION_NOYES)); s.add(new DecNumber(buffer, offset + 4, 4, AbstractStruct.COMMON_UNUSED)); if (Profile.isEnhancedEdition()) { restype = "VEF:VVC:BAM"; @@ -2851,7 +2848,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o new String[]{"Cast normally", "Cast instantly (ignore level)", "Cast instantly (at level)"})); } else { - s.add(new Bitmap(buffer, offset + 4, 4, "Cast instantly?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Cast instantly?", AbstractStruct.OPTION_NOYES)); } restype = "SPL"; break; @@ -3051,7 +3048,7 @@ private String makeEffectParamsGeneric(Datatype parent, ByteBuffer buffer, int o makeEffectParamsDefault(buffer, offset, s); } else { s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); - s.add(new Bitmap(buffer, offset + 4, 4, "Pass walls?", s_yesno)); + s.add(new Bitmap(buffer, offset + 4, 4, "Pass walls?", AbstractStruct.OPTION_YESNO)); } break; @@ -3141,14 +3138,14 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse if (Profile.isEnhancedEdition() && ResourceFactory.resourceExists("DIR.IDS")) { s.add(new IdsBitmap(buffer, offset + 4, 4, "Orientation", "DIR.IDS")); } else { - s.add(new Bitmap(buffer, offset + 4, 4, "Orientation", Actor.s_orientation)); + s.add(new Bitmap(buffer, offset + 4, 4, "Orientation", AbstractStruct.OPTION_ORIENTATION)); } restype = "ARE"; break; case 188: // Increase spells cast per round s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); - s.add(new Bitmap(buffer, offset + 4, 4, "Cleanse aura?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Cleanse aura?", AbstractStruct.OPTION_NOYES)); break; case 189: // Increase casting speed factor @@ -3313,7 +3310,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 218: // Stoneskin effect s.add(new DecNumber(buffer, offset, 4, "# skins")); if (Profile.isEnhancedEdition()) { - s.add(new Bitmap(buffer, offset + 4, 4, "Use dice?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Use dice?", AbstractStruct.OPTION_NOYES)); } else { s.add(new DecNumber(buffer, offset + 4, 4, AbstractStruct.COMMON_UNUSED)); } @@ -3471,7 +3468,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 239: // Farsight s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); - s.add(new Bitmap(buffer, offset + 4, 4, "Can view unexplored?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Can view unexplored?", AbstractStruct.OPTION_NOYES)); break; case 240: // Remove portrait icon @@ -3500,7 +3497,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse } case 243: // Drain item charges - s.add(new Bitmap(buffer, offset, 4, "Include weapons?", s_noyes)); + s.add(new Bitmap(buffer, offset, 4, "Include weapons?", AbstractStruct.OPTION_NOYES)); if (Profile.isEnhancedEdition()) { s.add(new DecNumber(buffer, offset + 4, 4, "# to drain")); } else { @@ -3578,7 +3575,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 264: // Drop item s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); - s.add(new Bitmap(buffer, offset + 4, 4, "Only quick weapons?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Only quick weapons?", AbstractStruct.OPTION_NOYES)); break; case 265: // Set global variable @@ -3704,7 +3701,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse s.add(new Bitmap(buffer, offset, 4, "Store party location", new String[]{ "Use pocket plane field", "Use party location field", "Do not store" })); - s.add(new Bitmap(buffer, offset + 4, 4, "Use custom script?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Use custom script?", AbstractStruct.OPTION_NOYES)); restype = "BCS"; } else { s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); @@ -4021,7 +4018,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse new String[]{"Unknown", "Body heat", "Blood color", "Unknown", "Personal space"}); switch (bmp.getValue()) { - case 1: s.add(new Bitmap(buffer, offset, 4, "Enabled?", s_noyes)); break; + case 1: s.add(new Bitmap(buffer, offset, 4, "Enabled?", AbstractStruct.OPTION_NOYES)); break; case 2: s.add(new ColorValue(buffer, offset, 4, "Color")); break; default: s.add(new DecNumber(buffer, offset, 4, "Value")); } @@ -4179,7 +4176,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 361: // Cast spell on critical miss if (Profile.isEnhancedEdition()) { s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); - s.add(new Bitmap(buffer, offset + 4, 4, "Current weapon only?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Current weapon only?", AbstractStruct.OPTION_NOYES)); restype = "SPL"; } else { makeEffectParamsDefault(buffer, offset, s); @@ -4189,7 +4186,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 362: // Critical miss bonus if (Profile.isEnhancedEdition()) { s.add(new DecNumber(buffer, offset, 4, "Value")); - s.add(new Bitmap(buffer, offset + 4, 4, "Current weapon only?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Current weapon only?", AbstractStruct.OPTION_NOYES)); } else { makeEffectParamsDefault(buffer, offset, s); } @@ -4206,8 +4203,8 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 365: // Make unselectable if (Profile.isEnhancedEdition()) { - s.add(new Bitmap(buffer, offset, 4, "Disable dialogue?", s_yesno)); - s.add(new Bitmap(buffer, offset + 4, 4, "Disable AI?", s_yesno)); + s.add(new Bitmap(buffer, offset, 4, "Disable dialogue?", AbstractStruct.OPTION_YESNO)); + s.add(new Bitmap(buffer, offset + 4, 4, "Disable AI?", AbstractStruct.OPTION_YESNO)); } else { makeEffectParamsDefault(buffer, offset, s); } @@ -4226,7 +4223,7 @@ private String makeEffectParamsBG2(Datatype parent, ByteBuffer buffer, int offse case 367: // Minimum base stats if (Profile.isEnhancedEdition()) { s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); - s.add(new Bitmap(buffer, offset + 4, 4, "Enabled?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Enabled?", AbstractStruct.OPTION_NOYES)); } else { makeEffectParamsDefault(buffer, offset, s); } @@ -4482,7 +4479,7 @@ private String makeEffectParamsIWD(Datatype parent, ByteBuffer buffer, int offse case 188: // Increase spells cast per round s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); - s.add(new Bitmap(buffer, offset + 4, 4, "Cleanse aura?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Cleanse aura?", AbstractStruct.OPTION_NOYES)); break; case 189: // Increase casting speed factor @@ -4717,7 +4714,7 @@ private String makeEffectParamsIWD(Datatype parent, ByteBuffer buffer, int offse case 285: // Force sleep s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); - s.add(new Bitmap(buffer, offset + 4, 4, "Wake on damage?", s_yesno)); + s.add(new Bitmap(buffer, offset + 4, 4, "Wake on damage?", AbstractStruct.OPTION_YESNO)); break; case 288: // Set spell state @@ -4757,7 +4754,7 @@ private String makeEffectParamsIWD2(Datatype parent, ByteBuffer buffer, int offs case 188: // Increase spells cast per round s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); - s.add(new Bitmap(buffer, offset + 4, 4, "Cleanse aura?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Cleanse aura?", AbstractStruct.OPTION_NOYES)); break; case 189: // Increase casting speed factor @@ -4833,7 +4830,7 @@ private String makeEffectParamsIWD2(Datatype parent, ByteBuffer buffer, int offs case 193: // Invisibility detection s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); - s.add(new Bitmap(buffer, offset + 4, 4, "Ignore visibility?", s_noyes)); + s.add(new Bitmap(buffer, offset + 4, 4, "Ignore visibility?", AbstractStruct.OPTION_NOYES)); break; case 206: // Protection from spell @@ -4975,7 +4972,7 @@ private String makeEffectParamsIWD2(Datatype parent, ByteBuffer buffer, int offs case 285: // Force sleep case 419: // Unconsciousness s.add(new DecNumber(buffer, offset, 4, AbstractStruct.COMMON_UNUSED)); - s.add(new Bitmap(buffer, offset + 4, 4, "Wake on damage?", s_yesno)); + s.add(new Bitmap(buffer, offset + 4, 4, "Wake on damage?", AbstractStruct.OPTION_YESNO)); break; case 288: // Set spell state @@ -5260,7 +5257,7 @@ private int makeEffectParam25(Datatype parent, ByteBuffer buffer, int offset, Li break; case 145: // Disable spellcasting - s.add(new Bitmap(buffer, offset, 4, "Display message?", s_yesno)); + s.add(new Bitmap(buffer, offset, 4, "Display message?", AbstractStruct.OPTION_YESNO)); break; case 178: // THAC0 vs. type bonus @@ -5349,7 +5346,7 @@ private int makeEffectParam25(Datatype parent, ByteBuffer buffer, int offset, Li break; case 365: // Make unselectable - s.add(new Bitmap(buffer, offset, 4, "Use purple selection color?", s_yesno)); + s.add(new Bitmap(buffer, offset, 4, "Use purple selection color?", AbstractStruct.OPTION_YESNO)); break; case 366: // Apply spell on movement diff --git a/src/org/infinity/resource/Profile.java b/src/org/infinity/resource/Profile.java index e5ce1c52b..759683be4 100644 --- a/src/org/infinity/resource/Profile.java +++ b/src/org/infinity/resource/Profile.java @@ -118,7 +118,7 @@ public enum Engine { IWD, /** Includes IWD2. */ IWD2, - /** Includes BG1EE, BG1SoD, BG2EE, IWDEE and EET. */ + /** Includes BG1EE, BG1SoD, BG2EE, IWDEE, PSTEE and EET. */ EE, } diff --git a/src/org/infinity/resource/are/Actor.java b/src/org/infinity/resource/are/Actor.java index f8d41d155..29d04443f 100644 --- a/src/org/infinity/resource/are/Actor.java +++ b/src/org/infinity/resource/are/Actor.java @@ -65,20 +65,10 @@ public final class Actor extends AbstractStruct implements AddRemovable, HasView public static final String ARE_ACTOR_CRE_FILE = "CRE file"; public static final String ARE_ACTOR_NAME_ALT = "Alternate actor name"; - public static final String[] s_orientation = { "South", "SSW", "SW", "WSW", "West", "WNW", "NW", "NNW", - "North", "NNE", "NE", "ENE", "East", "ESE", "SE", "SSE" }; - public static final String[] s_noyes = {"No", "Yes"}; public static final String[] s_flags = {"CRE attached", "CRE not attached", "Has seen party", "Toggle invulnerability", "Override script name"}; public static final String[] s_flags_iwd = {"CRE attached", "CRE not attached", "Has seen party", "Toggle invulnerability"}; - public static final String[] s_schedule = {"Not active", "00:30-01:29", "01:30-02:29", "02:30-03:29", - "03:30-04:29", "04:30-05:29", "05:30-06:29", "06:30-07:29", - "07:30-08:29", "08:30-09:29", "09:30-10:29", "10:30-11:29", - "11:30-12:29", "12:30-13:29", "13:30-14:29", "14:30-15:29", - "15:30-16:29", "16:30-17:29", "17:30-18:29", "18:30-19:29", - "19:30-20:29", "20:30-21:29", "21:30-22:29", "22:30-23:29", - "23:30-00:29"}; public static final String[] s_diff = {"None", "Level 1", "Level 2", "Level 3"}; public Actor() throws Exception @@ -199,7 +189,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception } else { addField(new Flag(buffer, offset + 40, 4, ARE_ACTOR_FLAGS, s_flags)); } - addField(new Bitmap(buffer, offset + 44, 2, ARE_ACTOR_IS_SPAWNED, s_noyes)); + addField(new Bitmap(buffer, offset + 44, 2, ARE_ACTOR_IS_SPAWNED, OPTION_NOYES)); addField(new TextString(buffer, offset + 46, 1, ARE_ACTOR_RESREF_LETTER)); if (Profile.getEngine() == Profile.Engine.IWD2) { addField(new Flag(buffer, offset + 47, 1, ARE_ACTOR_DIFFICULTY, s_diff)); @@ -207,12 +197,12 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new Unknown(buffer, offset + 47, 1)); } addField(new AnimateBitmap(buffer, offset + 48, 4, ARE_ACTOR_ANIMATION)); - addField(new Bitmap(buffer, offset + 52, 2, ARE_ACTOR_ORIENTATION, s_orientation)); + addField(new Bitmap(buffer, offset + 52, 2, ARE_ACTOR_ORIENTATION, OPTION_ORIENTATION)); addField(new Unknown(buffer, offset + 54, 2)); addField(new DecNumber(buffer, offset + 56, 4, ARE_ACTOR_EXPIRY_TIME)); addField(new DecNumber(buffer, offset + 60, 2, ARE_ACTOR_WANDER_DISTANCE)); addField(new DecNumber(buffer, offset + 62, 2, ARE_ACTOR_FOLLOW_DISTANCE)); - addField(new Flag(buffer, offset + 64, 4, ARE_ACTOR_PRESENT_AT, s_schedule)); + addField(new Flag(buffer, offset + 64, 4, ARE_ACTOR_PRESENT_AT, OPTION_SCHEDULE)); addField(new DecNumber(buffer, offset + 68, 4, ARE_ACTOR_NUM_TIMES_TALKED_TO)); addField(new ResourceRef(buffer, offset + 72, ARE_ACTOR_DIALOG, "DLG")); addField(new ResourceRef(buffer, offset + 80, ARE_ACTOR_SCRIPT_OVERRIDE, "BCS")); diff --git a/src/org/infinity/resource/are/Ambient.java b/src/org/infinity/resource/are/Ambient.java index f1f24ac24..86193ce59 100644 --- a/src/org/infinity/resource/are/Ambient.java +++ b/src/org/infinity/resource/are/Ambient.java @@ -75,7 +75,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new Unknown(buffer, offset + 130, 2)); addField(new DecNumber(buffer, offset + 132, 4, ARE_AMBIENT_INTERVAL_BASE)); addField(new DecNumber(buffer, offset + 136, 4, ARE_AMBIENT_INTERVAL_VARIATION)); - addField(new Flag(buffer, offset + 140, 4, ARE_AMBIENT_ACTIVE_AT, Actor.s_schedule)); + addField(new Flag(buffer, offset + 140, 4, ARE_AMBIENT_ACTIVE_AT, OPTION_SCHEDULE)); addField(new Flag(buffer, offset + 144, 4, ARE_AMBIENT_FLAGS, s_flag)); addField(new Unknown(buffer, offset + 148, 64)); return offset + 212; diff --git a/src/org/infinity/resource/are/Animation.java b/src/org/infinity/resource/are/Animation.java index f40e39884..060e0477c 100644 --- a/src/org/infinity/resource/are/Animation.java +++ b/src/org/infinity/resource/are/Animation.java @@ -11,6 +11,7 @@ import org.infinity.datatype.ResourceRef; import org.infinity.datatype.TextString; import org.infinity.datatype.Unknown; +import org.infinity.datatype.UnsignDecNumber; import org.infinity.resource.AbstractStruct; import org.infinity.resource.AddRemovable; import org.infinity.resource.Profile; @@ -38,9 +39,9 @@ public final class Animation extends AbstractStruct implements AddRemovable public static final String[] s_flag = {"Not shown", "Is shown", "No shadow", "Not light source", "Partial animation", - "Synchronized draw", "Random start","Not covered by wall", "Static animation", + "Synchronized draw", "Random start","Not covered by wall", "Disable on slow machines", "Draw as background", "Play all frames", "Recolored by palette", "Mirror Y axis", - "Don't remove in combat", "EE: Use WBM", "EE: Under ground", "EE: Use PVRZ"}; + "Don't remove in combat", "EE: Use WBM", "EE: Draw stenciled", "EE: Use PVRZ"}; Animation() throws Exception { @@ -68,7 +69,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new TextString(buffer, offset, 32, ARE_ANIMATION_NAME)); addField(new DecNumber(buffer, offset + 32, 2, ARE_ANIMATION_LOCATION_X)); addField(new DecNumber(buffer, offset + 34, 2, ARE_ANIMATION_LOCATION_Y)); - addField(new Flag(buffer, offset + 36, 4, ARE_ANIMATION_ACTIVE_AT, Actor.s_schedule)); + addField(new Flag(buffer, offset + 36, 4, ARE_ANIMATION_ACTIVE_AT, OPTION_SCHEDULE)); if (Profile.isEnhancedEdition()) { addField(new ResourceRef(buffer, offset + 40, ARE_ANIMATION_RESREF, "BAM", "WBM", "PVRZ")); } else { @@ -81,7 +82,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 58, 2, ARE_ANIMATION_TRANSLUCENCY)); addField(new DecNumber(buffer, offset + 60, 2, ARE_ANIMATION_START_RANGE)); addField(new DecNumber(buffer, offset + 62, 1, ARE_ANIMATION_LOOP_PROBABILITY)); - addField(new DecNumber(buffer, offset + 63, 1, ARE_ANIMATION_START_DELAY)); + addField(new UnsignDecNumber(buffer, offset + 63, 1, ARE_ANIMATION_START_DELAY)); if (Profile.getEngine() == Profile.Engine.BG2 || Profile.getEngine() == Profile.Engine.IWD2 || Profile.isEnhancedEdition()) { diff --git a/src/org/infinity/resource/are/AreResource.java b/src/org/infinity/resource/are/AreResource.java index f28a56397..970551cba 100644 --- a/src/org/infinity/resource/are/AreResource.java +++ b/src/org/infinity/resource/are/AreResource.java @@ -8,6 +8,7 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Set; @@ -20,6 +21,8 @@ import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; import org.infinity.datatype.HexNumber; +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsReference; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.SectionCount; import org.infinity.datatype.SectionOffset; @@ -40,6 +43,8 @@ import org.infinity.resource.are.viewer.AreaViewer; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.vertex.Vertex; +import org.infinity.resource.wmp.AreaEntry; +import org.infinity.resource.wmp.WmpResource; import org.infinity.search.SearchOptions; import org.infinity.util.IdsMapCache; import org.infinity.util.LuaEntry; @@ -198,6 +203,9 @@ private static void initMapNames(boolean force) { } else if (ResourceFactory.resourceExists("MAPNAME.2DA")) { // PST map names mapNames = createMapNamesFromTable(); + } else { + // try getting map names from worldmaps + mapNames = createMapNamesFromWorldmap(); } } } @@ -251,6 +259,49 @@ private static HashMap createMapNamesFromTable() return retVal; } + // Collect area names from worldmaps + private static HashMap createMapNamesFromWorldmap() + { + HashMap retVal = new HashMap<>(); + List wmpList = ResourceFactory.getResources("WMP", Collections.emptyList()); + if (wmpList != null) { + for (ResourceEntry wmpEntry : wmpList) { + try { + WmpResource wmp = (WmpResource)ResourceFactory.getResource(wmpEntry); + if (wmp != null) { + List mapList = wmp.getFields(org.infinity.resource.wmp.MapEntry.class); + for (StructEntry mapEntry : mapList) { + List areaList = ((AbstractStruct)mapEntry).getFields(AreaEntry.class); + for (StructEntry areaEntry : areaList) { + AreaEntry area = (AreaEntry)areaEntry; + + String resref = ((IsReference)area.getAttribute(AreaEntry.WMP_AREA_CURRENT)).getResourceName(); + if (resref == null || resref.isEmpty()) + continue; + int pos = resref.lastIndexOf('.'); + if (pos > 0) + resref = resref.substring(0, pos); + resref = resref.toUpperCase(); + + int strref = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_TOOLTIP)).getValue(); + if (!StringTable.isValidStringRef(strref)) + strref = ((IsNumeric)area.getAttribute(AreaEntry.WMP_AREA_NAME)).getValue(); + if (StringTable.isValidStringRef(strref)) { + String name = StringTable.getStringRef(strref); + if (name != null && !name.isEmpty()) + retVal.put(resref, name); + } + } + } + } + } catch (Exception e) { + // no need to report anything + } + } + } + return retVal; + } + public static void addScriptNames(Set scriptNames, ByteBuffer buffer) { int offset = 0; @@ -654,10 +705,10 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new HexNumber(buffer, offset + 144, 4, ARE_OFFSET_OBJECT_FLAGS)); addField(new ResourceRef(buffer, offset + 148, ARE_AREA_SCRIPT, "BCS")); SectionCount size_exploredbitmap = new SectionCount(buffer, offset + 156, 4, ARE_SIZE_EXPLORED_BITMAP, - Unknown.class); + Explored.class); addField(size_exploredbitmap); SectionOffset offset_exploredbitmap = new SectionOffset(buffer, offset + 160, ARE_OFFSET_EXPLORED_BITMAP, - Unknown.class); + Explored.class); addField(offset_exploredbitmap); SectionCount count_doors = new SectionCount(buffer, offset + 164, 4, ARE_NUM_DOORS, Door.class); @@ -778,7 +829,7 @@ else if (Profile.getEngine() == Profile.Engine.IWD2) { offset = offset_exploredbitmap.getValue(); if (size_exploredbitmap.getValue() > 0) { - addField(new Unknown(buffer, offset, size_exploredbitmap.getValue(), ARE_EXPLORED_BITMAP)); + addField(new Explored(buffer, offset, size_exploredbitmap.getValue(), ARE_EXPLORED_BITMAP)); } offset = offset_doors.getValue(); diff --git a/src/org/infinity/resource/are/AutomapNotePST.java b/src/org/infinity/resource/are/AutomapNotePST.java index 96f9f59ab..c7d503c08 100644 --- a/src/org/infinity/resource/are/AutomapNotePST.java +++ b/src/org/infinity/resource/are/AutomapNotePST.java @@ -24,8 +24,6 @@ public final class AutomapNotePST extends AbstractStruct implements AddRemovable public static final String ARE_AUTOMAP_TEXT = "Text"; public static final String ARE_AUTOMAP_READ_ONLY = "Is read only?"; - public static final String[] s_noyes = { "No", "Yes" }; - AutomapNotePST() throws Exception { super(null, ARE_AUTOMAP, StreamUtils.getByteBuffer(532), 0); @@ -52,7 +50,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset, 4, ARE_AUTOMAP_LOCATION_X)); addField(new DecNumber(buffer, offset + 4, 4, ARE_AUTOMAP_LOCATION_Y)); addField(new TextString(buffer, offset + 8, 500, ARE_AUTOMAP_TEXT)); - addField(new Bitmap(buffer, offset + 508, 4, ARE_AUTOMAP_READ_ONLY, s_noyes)); + addField(new Bitmap(buffer, offset + 508, 4, ARE_AUTOMAP_READ_ONLY, OPTION_NOYES)); addField(new Unknown(buffer, offset + 512, 20)); return offset + 532; } diff --git a/src/org/infinity/resource/are/Container.java b/src/org/infinity/resource/are/Container.java index e19f1bd28..228ffe5e2 100644 --- a/src/org/infinity/resource/are/Container.java +++ b/src/org/infinity/resource/are/Container.java @@ -59,7 +59,6 @@ public final class Container extends AbstractStruct implements AddRemovable, Has public static final String[] s_type = { "", "Bag", "Chest", "Drawer", "Pile", "Table", "Shelf", "Altar", "Non-visible", "Spellbook", "Body", "Barrel", "Crate"}; - public static final String[] s_noyes = {"No", "Yes"}; public static final String[] s_flag = { "No flags set", "Locked", "Disable if no owner", "Magical lock", "Trap resets", "Remove only", "Disabled", "EE: Don't clear" }; @@ -225,8 +224,8 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new Flag(buffer, offset + 40, 4, ARE_CONTAINER_FLAGS, s_flag)); addField(new DecNumber(buffer, offset + 44, 2, ARE_CONTAINER_TRAP_DETECTION_DIFFICULTY)); addField(new DecNumber(buffer, offset + 46, 2, ARE_CONTAINER_TRAP_REMOVAL_DIFFICULTY)); - addField(new Bitmap(buffer, offset + 48, 2, ARE_CONTAINER_TRAPPED, s_noyes)); - addField(new Bitmap(buffer, offset + 50, 2, ARE_CONTAINER_TRAP_DETECTED, s_noyes)); + addField(new Bitmap(buffer, offset + 48, 2, ARE_CONTAINER_TRAPPED, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 50, 2, ARE_CONTAINER_TRAP_DETECTED, OPTION_NOYES)); addField(new DecNumber(buffer, offset + 52, 2, ARE_CONTAINER_LAUNCH_POINT_X)); addField(new DecNumber(buffer, offset + 54, 2, ARE_CONTAINER_LAUNCH_POINT_Y)); addField(new DecNumber(buffer, offset + 56, 2, ARE_CONTAINER_BOUNDING_BOX_LEFT)); diff --git a/src/org/infinity/resource/are/Door.java b/src/org/infinity/resource/are/Door.java index e2f0fe6e5..b323e92dd 100644 --- a/src/org/infinity/resource/are/Door.java +++ b/src/org/infinity/resource/are/Door.java @@ -74,7 +74,6 @@ public final class Door extends AbstractStruct implements AddRemovable, HasVerti public static final String ARE_DOOR_SPEAKER_NAME = "Speaker name"; public static final String ARE_DOOR_DIALOG = "Dialogue"; - public static final String[] s_noyes = {"No", "Yes"}; public static final String[] s_flag = {"No flags set", "Door open", "Door locked", "Trap resets", "Detectable trap", "Door forced", "Cannot close", "Door located", "Door secret", "Secret door detected", "Can be looked through", @@ -246,8 +245,8 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 104, 4, ARE_DOOR_CURSOR_INDEX)); addField(new DecNumber(buffer, offset + 108, 2, ARE_DOOR_TRAP_DETECTION_DIFFICULTY)); addField(new DecNumber(buffer, offset + 110, 2, ARE_DOOR_TRAP_REMOVAL_DIFFICULTY)); - addField(new Bitmap(buffer, offset + 112, 2, ARE_DOOR_TRAPPED, s_noyes)); - addField(new Bitmap(buffer, offset + 114, 2, ARE_DOOR_TRAP_DETECTED, s_noyes)); + addField(new Bitmap(buffer, offset + 112, 2, ARE_DOOR_TRAPPED, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 114, 2, ARE_DOOR_TRAP_DETECTED, OPTION_NOYES)); addField(new DecNumber(buffer, offset + 116, 2, ARE_DOOR_LAUNCH_POINT_X)); addField(new DecNumber(buffer, offset + 118, 2, ARE_DOOR_LAUNCH_POINT_Y)); addField(new ResourceRef(buffer, offset + 120, ARE_DOOR_KEY, "ITM")); diff --git a/src/org/infinity/resource/are/Entrance.java b/src/org/infinity/resource/are/Entrance.java index 67bc0ca92..59bfc8ad5 100644 --- a/src/org/infinity/resource/are/Entrance.java +++ b/src/org/infinity/resource/are/Entrance.java @@ -49,7 +49,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new TextString(buffer, offset, 32, ARE_ENTRANCE_NAME)); addField(new DecNumber(buffer, offset + 32, 2, ARE_ENTRANCE_LOCATION_X)); addField(new DecNumber(buffer, offset + 34, 2, ARE_ENTRANCE_LOCATION_Y)); - addField(new Bitmap(buffer, offset + 36, 4, ARE_ENTRANCE_ORIENTATION, Actor.s_orientation)); + addField(new Bitmap(buffer, offset + 36, 4, ARE_ENTRANCE_ORIENTATION, OPTION_ORIENTATION)); addField(new Unknown(buffer, offset + 40, 64)); return offset + 104; } diff --git a/src/org/infinity/resource/are/Explored.java b/src/org/infinity/resource/are/Explored.java new file mode 100644 index 000000000..086a1afd7 --- /dev/null +++ b/src/org/infinity/resource/are/Explored.java @@ -0,0 +1,27 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2020 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.are; + +import java.nio.ByteBuffer; + +import org.infinity.datatype.Unknown; + +/** + * Subclassed from {@code Unknown} to make field type identifiable by reflection. + */ +public class Explored extends Unknown +{ + + public Explored(ByteBuffer buffer, int offset, int length) + { + super(buffer, offset, length); + } + + public Explored(ByteBuffer buffer, int offset, int length, String name) + { + super(buffer, offset, length, name); + } + +} diff --git a/src/org/infinity/resource/are/ITEPoint.java b/src/org/infinity/resource/are/ITEPoint.java index 50fc1e733..2c4445e8f 100644 --- a/src/org/infinity/resource/are/ITEPoint.java +++ b/src/org/infinity/resource/are/ITEPoint.java @@ -65,7 +65,6 @@ public final class ITEPoint extends AbstractStruct implements AddRemovable, HasV "Trap set off by enemy", "Tutorial trigger", "Trap set off by NPC", "Trigger silent", "Trigger deactivated", "Cannot be passed by NPC", "Use activation point", "Connected to door"}; - public static final String[] s_noyes = {"No", "Yes"}; public ITEPoint() throws Exception { @@ -174,8 +173,8 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new StringRef(buffer, offset + 100, ARE_TRIGGER_INFO_POINT_TEXT)); addField(new DecNumber(buffer, offset + 104, 2, ARE_TRIGGER_TRAP_DETECTION_DIFFICULTY)); addField(new DecNumber(buffer, offset + 106, 2, ARE_TRIGGER_TRAP_REMOVAL_DIFFICULTY)); - addField(new Bitmap(buffer, offset + 108, 2, ARE_TRIGGER_TRAPPED, s_noyes)); - addField(new Bitmap(buffer, offset + 110, 2, ARE_TRIGGER_TRAP_DETECTED, s_noyes)); + addField(new Bitmap(buffer, offset + 108, 2, ARE_TRIGGER_TRAPPED, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 110, 2, ARE_TRIGGER_TRAP_DETECTED, OPTION_NOYES)); addField(new DecNumber(buffer, offset + 112, 2, ARE_TRIGGER_LAUNCH_POINT_X)); addField(new DecNumber(buffer, offset + 114, 2, ARE_TRIGGER_LAUNCH_POINT_Y)); addField(new ResourceRef(buffer, offset + 116, ARE_TRIGGER_KEY, "ITM")); diff --git a/src/org/infinity/resource/are/RestSpawn.java b/src/org/infinity/resource/are/RestSpawn.java index eed2368ec..ceed24f60 100644 --- a/src/org/infinity/resource/are/RestSpawn.java +++ b/src/org/infinity/resource/are/RestSpawn.java @@ -54,7 +54,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 160, 2, ARE_RESTSPAWN_WANDER_DISTANCE)); addField(new DecNumber(buffer, offset + 162, 2, ARE_RESTSPAWN_FOLLOW_DISTANCE)); addField(new DecNumber(buffer, offset + 164, 2, ARE_RESTSPAWN_MAX_CREATURES)); - addField(new Bitmap(buffer, offset + 166, 2, ARE_RESTSPAWN_ACTIVE, new String[]{"No", "Yes"})); + addField(new Bitmap(buffer, offset + 166, 2, ARE_RESTSPAWN_ACTIVE, OPTION_NOYES)); addField(new DecNumber(buffer, offset + 168, 2, ARE_RESTSPAWN_PROBABILITY_DAY)); addField(new DecNumber(buffer, offset + 170, 2, ARE_RESTSPAWN_PROBABILITY_NIGHT)); addField(new Unknown(buffer, offset + 172, 56)); diff --git a/src/org/infinity/resource/are/SpawnPoint.java b/src/org/infinity/resource/are/SpawnPoint.java index 00cd7811b..404c64103 100644 --- a/src/org/infinity/resource/are/SpawnPoint.java +++ b/src/org/infinity/resource/are/SpawnPoint.java @@ -41,7 +41,6 @@ public final class SpawnPoint extends AbstractStruct implements AddRemovable public static final String ARE_SPAWN_COUNTDOWN = "Countdown"; public static final String ARE_SPAWN_WEIGHT_FMT = "Spawn weight %d"; - public static final String[] s_noyes = { "No", "Yes" }; public static final String[] s_method = {"No flags set", "Spawn until paused", "Disable after spawn", "Spawn paused"}; @@ -83,8 +82,8 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 128, 2, ARE_SPAWN_WANDER_DISTANCE)); addField(new DecNumber(buffer, offset + 130, 2, ARE_SPAWN_FOLLOW_DISTANCE)); addField(new DecNumber(buffer, offset + 132, 2, ARE_SPAWN_MAX_CREATURES)); - addField(new Bitmap(buffer, offset + 134, 2, ARE_SPAWN_ACTIVE, s_noyes)); - addField(new Flag(buffer, offset + 136, 4, ARE_SPAWN_ACTIVE_AT, Actor.s_schedule)); + addField(new Bitmap(buffer, offset + 134, 2, ARE_SPAWN_ACTIVE, OPTION_NOYES)); + addField(new Flag(buffer, offset + 136, 4, ARE_SPAWN_ACTIVE_AT, OPTION_SCHEDULE)); addField(new DecNumber(buffer, offset + 140, 2, ARE_SPAWN_PROBABILITY_DAY)); addField(new DecNumber(buffer, offset + 142, 2, ARE_SPAWN_PROBABILITY_NIGHT)); if (Profile.isEnhancedEdition()) { diff --git a/src/org/infinity/resource/are/viewer/AreaViewer.java b/src/org/infinity/resource/are/viewer/AreaViewer.java index 0068c5e5a..c0a760f89 100644 --- a/src/org/infinity/resource/are/viewer/AreaViewer.java +++ b/src/org/infinity/resource/are/viewer/AreaViewer.java @@ -81,6 +81,7 @@ import org.infinity.NearInfinity; import org.infinity.datatype.Flag; +import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; import org.infinity.datatype.SectionOffset; import org.infinity.gui.ButtonPopupWindow; @@ -289,7 +290,7 @@ private void init() // Creating main view area pCanvas = new JPanel(new GridBagLayout()); - rcCanvas = new TilesetRenderer(); + rcCanvas = new TilesetRenderer(map.getOverlayTransparency()); rcCanvas.addComponentListener(getListeners()); rcCanvas.addMouseListener(getListeners()); rcCanvas.addMouseMotionListener(getListeners()); @@ -865,7 +866,7 @@ private synchronized void setVisualState(int hour) initProgressMonitor(this, "Loading tileset...", null, 1, 0, 0); } if (!rcCanvas.isMapLoaded() || rcCanvas.getWed() != map.getWed(ViewerConstants.AREA_DAY)) { - rcCanvas.loadMap(map.getWed(ViewerConstants.AREA_DAY)); + rcCanvas.loadMap(map.getOverlayTransparency(), map.getWed(ViewerConstants.AREA_DAY)); reloadWedLayers(true); } rcCanvas.setLighting(index); @@ -875,7 +876,7 @@ private synchronized void setVisualState(int hour) initProgressMonitor(this, "Loading tileset...", null, 1, 0, 0); } if (!rcCanvas.isMapLoaded() || rcCanvas.getWed() != map.getWed(ViewerConstants.AREA_DAY)) { - rcCanvas.loadMap(map.getWed(ViewerConstants.AREA_DAY)); + rcCanvas.loadMap(map.getOverlayTransparency(), map.getWed(ViewerConstants.AREA_DAY)); reloadWedLayers(true); } rcCanvas.setLighting(index); @@ -886,7 +887,7 @@ private synchronized void setVisualState(int hour) } if (!rcCanvas.isMapLoaded() || map.hasExtendedNight()) { if (rcCanvas.getWed() != map.getWed(ViewerConstants.AREA_NIGHT)) { - rcCanvas.loadMap(map.getWed(ViewerConstants.AREA_NIGHT)); + rcCanvas.loadMap(map.getOverlayTransparency(), map.getWed(ViewerConstants.AREA_NIGHT)); reloadWedLayers(true); } } @@ -2506,6 +2507,7 @@ private static class Map private final GraphicsResource[] mapLight = new GraphicsResource[]{null, null}; private final AreResource are; + private int overlayTransparency; private boolean hasDayNight, hasExtendedNight; private AbstractLayerItem areItem, songItem, restItem; private GraphicsResource mapSearch, mapHeight; @@ -2727,6 +2729,15 @@ public boolean hasExtendedNight() return hasExtendedNight; } + /** + * Returns the alpha blending strength of overlays in the range [0, 255] + * where 0 is fully opaque and 255 is fully transparent. + * Default for classic BG1: 0 (fully opaque); everything else: 128 (50% transparency) */ + public int getOverlayTransparency() + { + return overlayTransparency; + } + private static GraphicsResource loadMap(String mapName, GraphicsResource def) { try { @@ -2738,10 +2749,10 @@ private static GraphicsResource loadMap(String mapName, GraphicsResource def) private void init() { - // fetching important flags + // fetching important options final Flag flags = (Flag)are.getAttribute(AreResource.ARE_LOCATION); if (flags != null) { - if (Profile.getEngine() == Profile.Engine.PST) { + if (Profile.getEngine() == Profile.Engine.PST || Profile.getGame() == Profile.Game.PSTEE) { hasDayNight = flags.isFlagSet(10); hasExtendedNight = false; } else { @@ -2749,6 +2760,13 @@ private void init() hasExtendedNight = flags.isFlagSet(6); } } + if (Profile.isEnhancedEdition()) { + overlayTransparency = ((IsNumeric)are.getAttribute(AreResource.ARE_OVERLAY_TRANSPARENCY)).getValue(); + if (overlayTransparency == 0) + overlayTransparency = 128; + } else { + overlayTransparency = (Profile.getEngine() == Profile.Engine.BG1) ? 0 : 128; + } // initializing pseudo layer items areItem = new IconLayerItem(are, are.getName()); diff --git a/src/org/infinity/resource/are/viewer/BackgroundAnimationProvider.java b/src/org/infinity/resource/are/viewer/BackgroundAnimationProvider.java index c4b9e1856..baf4b5dc2 100644 --- a/src/org/infinity/resource/are/viewer/BackgroundAnimationProvider.java +++ b/src/org/infinity/resource/are/viewer/BackgroundAnimationProvider.java @@ -75,9 +75,6 @@ public void setAnimation(BamDecoder bam) this.control = this.bam.createControl(); this.control.setMode(BamDecoder.BamControl.Mode.INDIVIDUAL); this.control.setSharedPerCycle(!isMultiPart()); - if (this.control instanceof BamV1Decoder.BamV1Control) { - ((BamV1Decoder.BamV1Control)this.control).setTransparencyMode(BamV1Decoder.TransparencyMode.NORMAL); - } resetFrame(); updateCanvas(); diff --git a/src/org/infinity/resource/are/viewer/LayerObjectEntrance.java b/src/org/infinity/resource/are/viewer/LayerObjectEntrance.java index 0ba295907..92f4b614c 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectEntrance.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectEntrance.java @@ -11,8 +11,8 @@ import org.infinity.gui.layeritem.AbstractLayerItem; import org.infinity.gui.layeritem.IconLayerItem; import org.infinity.icon.Icons; +import org.infinity.resource.AbstractStruct; import org.infinity.resource.Viewable; -import org.infinity.resource.are.Actor; import org.infinity.resource.are.AreResource; import org.infinity.resource.are.Entrance; import org.infinity.resource.are.viewer.icon.ViewerIcons; @@ -40,9 +40,9 @@ public LayerObjectEntrance(AreResource parent, Entrance entrance) location.x = ((IsNumeric)entrance.getAttribute(Entrance.ARE_ENTRANCE_LOCATION_X)).getValue(); location.y = ((IsNumeric)entrance.getAttribute(Entrance.ARE_ENTRANCE_LOCATION_Y)).getValue(); int o = ((IsNumeric)entrance.getAttribute(Entrance.ARE_ENTRANCE_ORIENTATION)).getValue(); - if (o < 0) o = 0; else if (o >= Actor.s_orientation.length) o = Actor.s_orientation.length - 1; + if (o < 0) o = 0; else if (o >= AbstractStruct.OPTION_ORIENTATION.length) o = AbstractStruct.OPTION_ORIENTATION.length - 1; final String name = entrance.getAttribute(Entrance.ARE_ENTRANCE_NAME).toString(); - msg = String.format("%s (%s)", name, Actor.s_orientation[o]); + msg = String.format("%s (%s)", name, AbstractStruct.OPTION_ORIENTATION[o]); } catch (Exception e) { e.printStackTrace(); } diff --git a/src/org/infinity/resource/are/viewer/TilesetRenderer.java b/src/org/infinity/resource/are/viewer/TilesetRenderer.java index a710ae283..256358ff6 100644 --- a/src/org/infinity/resource/are/viewer/TilesetRenderer.java +++ b/src/org/infinity/resource/are/viewer/TilesetRenderer.java @@ -43,20 +43,22 @@ public class TilesetRenderer extends RenderCanvas { public static final String[] LabelVisualStates = {"Day", "Twilight", "Night"}; - // Rendering modes for tiles (affects how to render overlayed tiles) - public static final int MODE_AUTO = 0; // mode based on current game id - public static final int MODE_BG1 = 1; // forces BG1 rendering mode - public static final int MODE_BG2 = 2; // forces BG2 rendering mode + /** Available rendering modes for tiles. Affects how overlays are rendered. */ + public enum RenderMode { + /** Determine rendering mode based on detected game engine. */ + Auto, + /** Masked overlays supported by original BG1 engine. */ + Masked, + /** Blended overlays supported by IWD, BG2 and EE engines. */ + Blended, + } private static final int MaxOverlays = 8; // max. supported overlay entries private static final double MinZoomFactor = 1.0/64.0; // lower zoom factor limit private static final double MaxZoomFactor = 16.0; // upper zoom factor limit // Placeholder for missing tile data - private static final int[] DEFAULT_TILE_DATA = new int[64*64]; - static { - initDefaultTile(DEFAULT_TILE_DATA); - } + private static final int[] DEFAULT_TILE_DATA = createDefaultTile(); // Lighting adjustment for day/twilight/night times (multiplied by 10.24 for faster calculations) // Formula: @@ -77,7 +79,8 @@ public class TilesetRenderer extends RenderCanvas private final BufferedImage workingTile = ColorConvert.createCompatibleImage(64, 64, true); // internally used for drawing tile graphics private WedResource wed; // current wed resource - private int renderingMode = MODE_AUTO; // the rendering mode to use for processing overlayed tiles + private int overlayTransparency; // overlay transparency strength from 0 (opaque) to 255 (transparent) + private RenderMode renderingMode = RenderMode.Auto; // the rendering mode to use for processing overlayed tiles private boolean overlaysEnabled = true; // indicates whether to draw overlays private boolean blendedOverlays; // indicates whether to blend overlays with tile graphics private boolean hasChangedMap, hasChangedAppearance, hasChangedOverlays, hasChangedDoorState; @@ -98,15 +101,15 @@ public static int getLightingModesCount() return LabelVisualStates.length; } - public TilesetRenderer() + public TilesetRenderer(int overlayTransparency) { - this(null); + this(overlayTransparency, null); } - public TilesetRenderer(WedResource wed) + public TilesetRenderer(int overlayTransparency, WedResource wed) { super(); - init(wed); + init(overlayTransparency, wed); } /** @@ -149,10 +152,10 @@ public void removeChangeListener(TilesetChangeListener listener) * @param wed WED resource structure used to construct a map. * @return true if map has been initialized successfully, false otherwise. */ - public boolean loadMap(WedResource wed) + public boolean loadMap(int defaultTransparency, WedResource wed) { if (this.wed != wed) { - return init(wed); + return init(defaultTransparency, wed); } else { return true; } @@ -185,7 +188,7 @@ public void clear() /** * Returns the current mode for processing overlays. */ - public int getRenderingMode() + public RenderMode getRenderingMode() { return renderingMode; } @@ -194,9 +197,9 @@ public int getRenderingMode() * Specify how to draw overlayed tiles. Possible choices are MODE_AUTO, MODE_BG1 and MODE_BG2. * @param mode The new rendering mode */ - public void setRenderingMode(int mode) + public void setRenderingMode(RenderMode mode) { - if (mode < MODE_AUTO) mode = MODE_AUTO; else if (mode > MODE_BG2) mode = MODE_BG2; + if (mode == null) mode = RenderMode.Auto; if (mode != renderingMode) { renderingMode = mode; hasChangedOverlays = true; @@ -561,19 +564,19 @@ protected void paintCanvas(Graphics g) } } - private static void initDefaultTile(int[] buffer) + private static int[] createDefaultTile() { - if (buffer != null) { - BufferedImage image = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB); - Graphics2D g = image.createGraphics(); - g.setColor(Color.GRAY); - g.fillRect(0, 0, image.getWidth(), image.getHeight()); - g.dispose(); - int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); - for (int i = 0, cnt = Math.min(buffer.length, pixels.length); i < cnt; i++) { - buffer[i] = pixels[i]; - } + int[] buffer = new int[64*64]; + BufferedImage image = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = image.createGraphics(); + g.setColor(Color.GRAY); + g.fillRect(0, 0, image.getWidth(), image.getHeight()); + g.dispose(); + int[] pixels = ((DataBufferInt)image.getRaster().getDataBuffer()).getData(); + for (int i = 0, cnt = Math.min(buffer.length, pixels.length); i < cnt; i++) { + buffer[i] = pixels[i]; } + return buffer; } // Resizes the current image or creates a new one if needed @@ -591,15 +594,16 @@ private boolean updateImageSize() } // Initializes a new map - private boolean init(WedResource wed) + private boolean init(int overlayTransparency, WedResource wed) { release(false); // resetting states - blendedOverlays = Profile.getProperty(Profile.Key.IS_TILESET_STENCILED); + blendedOverlays = Profile.getEngine() != Profile.Engine.BG1; lighting = ViewerConstants.LIGHTING_DAY; // loading map data + this.overlayTransparency = overlayTransparency; if (wed != null) { if (initWed(wed)) { this.wed = wed; @@ -787,6 +791,131 @@ private void drawDoorTiles() } } + // render tile graphics without overlays + private void drawTileSimple(int[] sourceTile, int[] renderTarget) + { + if (sourceTile != null) { + int pixel, fr, fg, fb; + for (int ofs = 0; ofs < 4096; ofs++) { + pixel = sourceTile[ofs]; + fr = (pixel >>> 16) & 0xff; + fg = (pixel >>> 8) & 0xff; + fb = pixel & 0xff; + + // applying lighting conditions + fr = (fr * LightingAdjustment[lighting][0]) >>> LightingAdjustmentShift; + fg = (fg * LightingAdjustment[lighting][1]) >>> LightingAdjustmentShift; + fb = (fb * LightingAdjustment[lighting][2]) >>> LightingAdjustmentShift; + renderTarget[ofs] = 0xff000000 | (fr << 16) | (fg << 8) | fb; + } + } else { + // no tile = transparent pixel data (work-around for faulty tiles in BG1's WEDs) + for (int ofs = 0; ofs < 4096; ofs++) + renderTarget[ofs] = 0; + } + } + + // compose tile graphics in BG1 mode + private void drawTileMasked(int[] primaryTile, int[] secondaryTile, int[] overlayTile, int[] renderTarget, boolean isDoorTile, boolean isDoorClosed) + { + if (renderTarget != null) { + int[] src = (isDoorTile && isDoorClosed) ? secondaryTile : primaryTile; + int fr, fg, fb, pixel; + for (int ofs = 0; ofs < 4096; ofs++) { + // composing pixel data + if (src != null && (src[ofs] & 0xff000000) != 0) + pixel = src[ofs]; + else if (overlayTile != null) + pixel = overlayTile[ofs]; + else + pixel = 0; + fr = (pixel >>> 16) & 0xff; + fg = (pixel >>> 8) & 0xff; + fb = pixel & 0xff; + + // applying lighting conditions + fr = (fr * LightingAdjustment[lighting][0]) >>> LightingAdjustmentShift; + fg = (fg * LightingAdjustment[lighting][1]) >>> LightingAdjustmentShift; + fb = (fb * LightingAdjustment[lighting][2]) >>> LightingAdjustmentShift; + renderTarget[ofs] = 0xff000000 | (fr << 16) | (fg << 8) | fb; + } + } + } + + // compose tile graphics in BG2 mode + private void drawTileBlended(int[] primaryTile, int[] secondaryTile, int[] overlayTile, int[] renderTarget, boolean isPaletted) + { + if (renderTarget != null) { + int pixel, fr, fg, fb; + boolean pa = false, sa = false; + int pr = 0, pg = 0, pb = 0, sr = 0, sg = 0, sb = 0, or = 0, og = 0, ob = 0; + int alphaSrc = overlayTransparency, alphaDst = 255 - overlayTransparency; + for (int ofs = 0; ofs < 4096; ofs++) { + // getting source pixels + if (primaryTile != null) { + pixel = primaryTile[ofs]; + pa = (pixel & 0xff000000) != 0; + pr = (pixel >>> 16) & 0xff; + pg = (pixel >>> 8) & 0xff; + pb = pixel & 0xff; + } + + if (secondaryTile != null) { + pixel = secondaryTile[ofs]; + sa = (pixel & 0xff000000) != 0; + sr = (pixel >>> 16) & 0xff; + sg = (pixel >>> 8) & 0xff; + sb = pixel & 0xff; + } + + if (overlayTile != null) { + pixel = overlayTile[ofs]; + or = (pixel >>> 16) & 0xff; + og = (pixel >>> 8) & 0xff; + ob = pixel & 0xff; + } + + // composing pixel data + // blending modes depend on transparency states of primary and secondary pixels + if (pa && !sa) { + if (isPaletted) { + fr = (pr * alphaSrc) + (or * alphaDst) >>> 8; + fg = (pg * alphaSrc) + (og * alphaDst) >>> 8; + fb = (pb * alphaSrc) + (ob * alphaDst) >>> 8; + } else { + if (secondaryTile != null) { + fr = pr; + fg = pg; + fb = pb; + } else { + fr = (pr * alphaSrc) + (or * alphaDst) >>> 8; + fg = (pg * alphaSrc) + (og * alphaDst) >>> 8; + fb = (pb * alphaSrc) + (ob * alphaDst) >>> 8; + } + } + } else if (pa && sa) { + fr = (pr * alphaSrc) + (sr * alphaDst) >>> 8; + fg = (pg * alphaSrc) + (sg * alphaDst) >>> 8; + fb = (pb * alphaSrc) + (sb * alphaDst) >>> 8; + } else if (!pa && !sa) { + fr = or; + fg = og; + fb = ob; + } else { // !pa && sa + fr = (sr * alphaSrc) + (or * alphaDst) >>> 8; + fg = (sg * alphaSrc) + (og * alphaDst) >>> 8; + fb = (sb * alphaSrc) + (ob * alphaDst) >>> 8; + } + + // applying lighting conditions + fr = (fr * LightingAdjustment[lighting][0]) >>> LightingAdjustmentShift; + fg = (fg * LightingAdjustment[lighting][1]) >>> LightingAdjustmentShift; + fb = (fb * LightingAdjustment[lighting][2]) >>> LightingAdjustmentShift; + renderTarget[ofs] = 0xff000000 | (fr << 16) | (fg << 8) | fb; + } + } + } + // draws the specified tile into the target graphics buffer private synchronized void drawTile(Tile tile, boolean isDoorTile) { @@ -794,7 +923,6 @@ private synchronized void drawTile(Tile tile, boolean isDoorTile) boolean isDoorClosed = (Profile.getEngine() == Profile.Engine.PST) ? !isClosed : isClosed; int[] target = ((DataBufferInt)workingTile.getRaster().getDataBuffer()).getData(); - int fa = 255, fr = 0, fg = 0, fb = 0; if (overlaysEnabled && tile.hasOverlay() && hasOverlay(tile.getOverlayIndex())) { // overlayed tile // preparing graphics data int overlay = tile.getOverlayIndex(); @@ -816,92 +944,13 @@ private synchronized void drawTile(Tile tile, boolean isDoorTile) } // determining correct rendering mode - boolean blended = (renderingMode == MODE_AUTO && blendedOverlays) || (renderingMode == MODE_BG2); + boolean blended = (renderingMode == RenderMode.Auto && blendedOverlays) || (renderingMode == RenderMode.Blended); // drawing tile graphics - boolean pa, sa; - int pr, pg, pb, sr, sg, sb, or, og, ob; - for (int ofs = 0; ofs < 4096; ofs++) { - if (blended) { // BG2/BGEE mode overlays - // extracting color components - if (srcPri != null) { - pa = (srcPri[ofs] & 0xff000000) != 0; - pr = (srcPri[ofs] >>> 16) & 0xff; - pg = (srcPri[ofs] >>> 8) & 0xff; - pb = srcPri[ofs] & 0xff; - } else { - pa = false; - pr = pg = pb = 0; - } - if (srcSec != null) { - sa = (srcSec[ofs] & 0xff000000) != 0; - sr = (srcSec[ofs] >>> 16) & 0xff; - sg = (srcSec[ofs] >>> 8) & 0xff; - sb = srcSec[ofs] & 0xff; - } else { - sa = false; - sr = sg = sb = 0; - } - if (srcOvl != null) { - or = (srcOvl[ofs] >>> 16) & 0xff; - og = (srcOvl[ofs] >>> 8) & 0xff; - ob = srcOvl[ofs] & 0xff; - } else { - or = og = ob = 0; - } - - // blending modes depend on transparency states of primary and secondary pixels - if (pa && !sa) { - if (tile.isTisV1()) { - fr = (pr + or) >>> 1; - fg = (pg + og) >>> 1; - fb = (pb + ob) >>> 1; - } else { - if (srcSec != null) { - fr = pr; - fg = pg; - fb = pb; - } else { - fr = (pr + or) >>> 1; - fg = (pg + og) >>> 1; - fb = (pb + ob) >>> 1; - } - } - } else if (pa && sa) { - fr = (pr + sr) >>> 1; - fg = (pg + sg) >>> 1; - fb = (pb + sb) >>> 1; - } else if (!pa && !sa) { - fr = or; - fg = og; - fb = ob; - } else if (!pa && sa) { - fr = (sr + or) >>> 1; - fg = (sg + og) >>> 1; - fb = (sb + ob) >>> 1; - } - } else { // BG1 mode overlays - int[] src = (isDoorTile && isDoorClosed) ? srcSec : srcPri; - if (src != null) { - if ((src[ofs] & 0xff000000) != 0 && src != null) { - fr = (src[ofs] >>> 16) & 0xff; - fg = (src[ofs] >>> 8) & 0xff; - fb = src[ofs] & 0xff; - } else if (srcOvl != null) { - fr = (srcOvl[ofs] >>> 16) & 0xff; - fg = (srcOvl[ofs] >>> 8) & 0xff; - fb = srcOvl[ofs] & 0xff; - } - } else { - fa = fr = fg = fb = 0; - } - } - - // applying lighting conditions - fr = (fr * LightingAdjustment[lighting][0]) >>> LightingAdjustmentShift; - fg = (fg * LightingAdjustment[lighting][1]) >>> LightingAdjustmentShift; - fb = (fb * LightingAdjustment[lighting][2]) >>> LightingAdjustmentShift; - target[ofs] = (fa << 24) | (fr << 16) | (fg << 8) | fb; + if (blended) { + drawTileBlended(srcPri, srcSec, srcOvl, target, tile.isTisV1()); + } else { + drawTileMasked(srcPri, srcSec, srcOvl, target, isDoorTile, isDoorClosed); } srcOvl = null; srcPri = null; @@ -920,22 +969,7 @@ private synchronized void drawTile(Tile tile, boolean isDoorTile) } // drawing tile graphics - if (srcTile != null) { - for (int ofs = 0; ofs < 4096; ofs++) { - fr = (srcTile[ofs] >>> 16) & 0xff; - fg = (srcTile[ofs] >>> 8) & 0xff; - fb = srcTile[ofs] & 0xff; - fr = (fr * LightingAdjustment[lighting][0]) >>> LightingAdjustmentShift; - fg = (fg * LightingAdjustment[lighting][1]) >>> LightingAdjustmentShift; - fb = (fb * LightingAdjustment[lighting][2]) >>> LightingAdjustmentShift; - target[ofs] = 0xff000000 | (fr << 16) | (fg << 8) | fb; - } - } else { - // no tile = transparent pixel data (work-around for faulty tiles in BG1's WEDs) - for (int ofs = 0; ofs < 4096; ofs++) { - target[ofs] = 0; - } - } + drawTileSimple(srcTile, target); srcTile = null; } @@ -1046,6 +1080,7 @@ private static class Tileset public final List listOverlayTiles = new ArrayList(); public int tilesX, tilesY; // stores number of tiles per row/column + public boolean isTisPalette; // whether tileset is palette-based public Tileset(WedResource wed, Overlay ovl) { @@ -1070,12 +1105,12 @@ private void init(WedResource wed, Overlay ovl) { if (wed != null && ovl != null) { // storing tile data - boolean isTilesetV1 = true; + isTisPalette = !Profile.isEnhancedEdition(); // choose sane default ResourceEntry tisEntry = getTisResource(wed, ovl); if (tisEntry != null) { try { TisDecoder decoder = TisDecoder.loadTis(tisEntry); - isTilesetV1 = decoder.getType() == TisDecoder.Type.PALETTE; + isTisPalette = decoder.getType() == TisDecoder.Type.PALETTE; BufferedImage tileImage = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB); for (int i = 0, tCount = decoder.getTileCount(); i < tCount; i++) { decoder.getTile(i, tileImage); @@ -1133,7 +1168,7 @@ private void init(WedResource wed, Overlay ovl) Flag drawOverlays = (Flag)tile.getAttribute(Tilemap.WED_TILEMAP_DRAW_OVERLAYS); int flags = (int)drawOverlays.getValue() & 255; - listTiles.add(new Tile(x, y, count, tileIdx, tileIdx2, flags, isTilesetV1)); + listTiles.add(new Tile(x, y, count, tileIdx, tileIdx2, flags, isTisPalette)); curOfs += tile.getSize(); } else { listTiles.add(new Tile(x, y, 0, new int[]{}, -1, 0, true)); // needed as placeholder diff --git a/src/org/infinity/resource/chu/Window.java b/src/org/infinity/resource/chu/Window.java index 1bb195f52..0e2cfc048 100644 --- a/src/org/infinity/resource/chu/Window.java +++ b/src/org/infinity/resource/chu/Window.java @@ -38,7 +38,6 @@ final class Window extends AbstractStruct // implements AddRemovable public static final String CHU_WINDOW_FIRST_CONTROL_INDEX = "First control index"; public static final String CHU_WINDOW_FLAGS = "Flags"; - private static final String hasb[] = {"No", "Yes"}; private static final String s_flag[] = {"No flags set", "Don't dim background"}; Window() throws Exception @@ -168,7 +167,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 6, 2, CHU_WINDOW_POSITION_Y)); addField(new DecNumber(buffer, offset + 8, 2, CHU_WINDOW_WIDTH)); addField(new DecNumber(buffer, offset + 10, 2, CHU_WINDOW_HEIGHT)); - addField(new Bitmap(buffer, offset + 12, 2, CHU_WINDOW_HAS_BACKGROUND, hasb)); + addField(new Bitmap(buffer, offset + 12, 2, CHU_WINDOW_HAS_BACKGROUND, OPTION_NOYES)); addField(new UnsignDecNumber(buffer, offset + 14, 2, CHU_WINDOW_NUM_CONTROLS)); addField(new ResourceRef(buffer, offset + 16, CHU_WINDOW_BACKGROUND, "MOS")); addField(new UnsignDecNumber(buffer, offset + 24, 2, CHU_WINDOW_FIRST_CONTROL_INDEX)); diff --git a/src/org/infinity/resource/cre/CreResource.java b/src/org/infinity/resource/cre/CreResource.java index 331b20e89..c474444e5 100644 --- a/src/org/infinity/resource/cre/CreResource.java +++ b/src/org/infinity/resource/cre/CreResource.java @@ -465,7 +465,6 @@ public final class CreResource extends AbstractStruct public static final String[] s_attributes_iwd2 = {"No flags set", "Mental fortitude", "Critical hit immunity", "Cannot be paladin", "Cannot be monk"}; public static final String[] s_attacks = {"0", "1", "2", "3", "4", "5", "1/2", "3/2", "5/2", "7/2", "9/2"}; - public static final String[] s_noyes = {"No", "Yes"}; public static final String[] s_visible = {"Shown", "Hidden"}; public static final String[] s_profLabels = {"Active class", "Original class"}; public static final String[] s_effversion = {"Version 1", "Version 2"}; @@ -1175,15 +1174,15 @@ private int readIWD2(ByteBuffer buffer, int offset) throws Exception addField(new ResourceRef(buffer, offset + 644, CRE_SCRIPT_SPECIAL_3, "BCS")); addField(new ResourceRef(buffer, offset + 652, CRE_SCRIPT_MOVEMENT, "BCS")); addField(new Bitmap(buffer, offset + 660, 1, CRE_DEFAULT_VISIBILITY, s_visible)); - addField(new Bitmap(buffer, offset + 661, 1, CRE_SET_EXTRA_DEATH_VAR, s_noyes)); - addField(new Bitmap(buffer, offset + 662, 1, CRE_INCREMENT_KILL_COUNT, s_noyes)); + addField(new Bitmap(buffer, offset + 661, 1, CRE_SET_EXTRA_DEATH_VAR, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 662, 1, CRE_INCREMENT_KILL_COUNT, OPTION_NOYES)); addField(new Unknown(buffer, offset + 663, 1)); for (int i = 0; i < 5; i++) { addField(new DecNumber(buffer, offset + 664 + (i * 2), 2, String.format(CRE_INTERNAL_FMT, i+1))); } addField(new TextString(buffer, offset + 674, 32, CRE_DEATH_VAR_SET)); addField(new TextString(buffer, offset + 706, 32, CRE_DEATH_VAR_INC)); - addField(new Bitmap(buffer, offset + 738, 2, CRE_LOCATION_SAVED, s_noyes)); + addField(new Bitmap(buffer, offset + 738, 2, CRE_LOCATION_SAVED, OPTION_NOYES)); addField(new DecNumber(buffer, offset + 740, 2, CRE_SAVED_LOCATION_X)); addField(new DecNumber(buffer, offset + 742, 2, CRE_SAVED_LOCATION_Y)); addField(new DecNumber(buffer, offset + 744, 2, CRE_SAVED_ORIENTATION)); @@ -1342,7 +1341,7 @@ private int readIWD2(ByteBuffer buffer, int offset) throws Exception shape_num, CRE_SHAPES, Iwd2Struct.TYPE_SHAPE); addField(shape_str); - SectionOffset itemslots_offset = new SectionOffset(buffer, offset + 1546, CRE_OFFSET_ITEM_SLOTS, DecNumber.class); + SectionOffset itemslots_offset = new SectionOffset(buffer, offset + 1546, CRE_OFFSET_ITEM_SLOTS, IndexNumber.class); addField(itemslots_offset); SectionOffset items_offset = new SectionOffset(buffer, offset + 1550, CRE_OFFSET_ITEMS, Item.class); @@ -1387,33 +1386,33 @@ private int readIWD2(ByteBuffer buffer, int offset) throws Exception } offset = getExtraOffset() + itemslots_offset.getValue(); - addField(new DecNumber(buffer, offset, 2, CRE_ITEM_SLOT_HELMET)); - addField(new DecNumber(buffer, offset + 2, 2, CRE_ITEM_SLOT_ARMOR)); - addField(new DecNumber(buffer, offset + 4, 2, CRE_ITEM_SLOT_SHIELD)); - addField(new DecNumber(buffer, offset + 6, 2, CRE_ITEM_SLOT_GAUNTLETS)); - addField(new DecNumber(buffer, offset + 8, 2, CRE_ITEM_SLOT_LEFT_RING)); - addField(new DecNumber(buffer, offset + 10, 2, CRE_ITEM_SLOT_RIGHT_RING)); - addField(new DecNumber(buffer, offset + 12, 2, CRE_ITEM_SLOT_AMULET)); - addField(new DecNumber(buffer, offset + 14, 2, CRE_ITEM_SLOT_BELT)); - addField(new DecNumber(buffer, offset + 16, 2, CRE_ITEM_SLOT_BOOTS)); + addField(new IndexNumber(buffer, offset, 2, CRE_ITEM_SLOT_HELMET)); + addField(new IndexNumber(buffer, offset + 2, 2, CRE_ITEM_SLOT_ARMOR)); + addField(new IndexNumber(buffer, offset + 4, 2, CRE_ITEM_SLOT_SHIELD)); + addField(new IndexNumber(buffer, offset + 6, 2, CRE_ITEM_SLOT_GAUNTLETS)); + addField(new IndexNumber(buffer, offset + 8, 2, CRE_ITEM_SLOT_LEFT_RING)); + addField(new IndexNumber(buffer, offset + 10, 2, CRE_ITEM_SLOT_RIGHT_RING)); + addField(new IndexNumber(buffer, offset + 12, 2, CRE_ITEM_SLOT_AMULET)); + addField(new IndexNumber(buffer, offset + 14, 2, CRE_ITEM_SLOT_BELT)); + addField(new IndexNumber(buffer, offset + 16, 2, CRE_ITEM_SLOT_BOOTS)); for (int i = 0; i < 4; i++) { - addField(new DecNumber(buffer, offset + 18 + (i * 4), 2, String.format(CRE_ITEM_SLOT_WEAPON_FMT, i+1))); - addField(new DecNumber(buffer, offset + 20 + (i * 4), 2, String.format(CRE_ITEM_SLOT_SHIELD_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 18 + (i * 4), 2, String.format(CRE_ITEM_SLOT_WEAPON_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 20 + (i * 4), 2, String.format(CRE_ITEM_SLOT_SHIELD_FMT, i+1))); } for (int i = 0; i < 4; i++) { - addField(new DecNumber(buffer, offset + 34 + (i * 2), 2, String.format(CRE_ITEM_SLOT_QUIVER_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 34 + (i * 2), 2, String.format(CRE_ITEM_SLOT_QUIVER_FMT, i+1))); } - addField(new DecNumber(buffer, offset + 42, 2, CRE_ITEM_SLOT_CLOAK)); + addField(new IndexNumber(buffer, offset + 42, 2, CRE_ITEM_SLOT_CLOAK)); for (int i = 0; i < 3; i++) { - addField(new DecNumber(buffer, offset + 44 + (i * 2), 2, String.format(CRE_ITEM_SLOT_QUICK_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 44 + (i * 2), 2, String.format(CRE_ITEM_SLOT_QUICK_FMT, i+1))); } for (int i = 0; i < 24; i++) { - addField(new DecNumber(buffer, offset + 50 + (i * 2), 2, - String.format(CRE_ITEM_SLOT_INVENTORY_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 50 + (i * 2), 2, + String.format(CRE_ITEM_SLOT_INVENTORY_FMT, i+1))); } - addField(new DecNumber(buffer, offset + 98, 2, CRE_ITEM_SLOT_MAGIC_WEAPON)); - addField(new DecNumber(buffer, offset + 100, 2, CRE_SELECTED_WEAPON_SLOT)); - addField(new DecNumber(buffer, offset + 102, 2, CRE_SELECTED_WEAPON_ABILITY)); + addField(new IndexNumber(buffer, offset + 98, 2, CRE_ITEM_SLOT_MAGIC_WEAPON)); + addField(new IndexNumber(buffer, offset + 100, 2, CRE_SELECTED_WEAPON_SLOT)); + addField(new IndexNumber(buffer, offset + 102, 2, CRE_SELECTED_WEAPON_ABILITY)); int endoffset = offset; for (final StructEntry entry : getFields()) { @@ -1522,7 +1521,7 @@ private int readOther(String version, ByteBuffer buffer, int offset) throws Exce } else { addField(new Unknown(buffer, offset + 110, 7)); } - addField(new Bitmap(buffer, offset + 117, 1, CRE_NIGHTMARE_MODE, s_noyes)); + addField(new Bitmap(buffer, offset + 117, 1, CRE_NIGHTMARE_MODE, OPTION_NOYES)); addField(new UnsignDecNumber(buffer, offset + 118, 1, CRE_TRANSLUCENCY)); if (Profile.getGame() == Profile.Game.PSTEE) { addField(new DecNumber(buffer, offset + 119, 1, CRE_MURDER_INC)); @@ -1689,15 +1688,15 @@ else if (version.equalsIgnoreCase("V9.0") || version.equalsIgnoreCase("V9.1")) { } else if (version.equalsIgnoreCase("V9.0") || version.equalsIgnoreCase("V9.1")) { addField(new Bitmap(buffer, offset + 616, 1, CRE_DEFAULT_VISIBILITY, s_visible)); - addField(new Bitmap(buffer, offset + 617, 1, CRE_SET_EXTRA_DEATH_VAR, s_noyes)); - addField(new Bitmap(buffer, offset + 618, 1, CRE_INCREMENT_KILL_COUNT, s_noyes)); + addField(new Bitmap(buffer, offset + 617, 1, CRE_SET_EXTRA_DEATH_VAR, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 618, 1, CRE_INCREMENT_KILL_COUNT, OPTION_NOYES)); addField(new Unknown(buffer, offset + 619, 1)); for (int i = 0; i < 5; i++) { addField(new DecNumber(buffer, offset + 620 + (i * 2), 2, String.format(CRE_INTERNAL_FMT, i+1))); } addField(new TextString(buffer, offset + 630, 32, CRE_DEATH_VAR_SET)); addField(new TextString(buffer, offset + 662, 32, CRE_DEATH_VAR_INC)); - addField(new Bitmap(buffer, offset + 694, 2, CRE_LOCATION_SAVED, s_noyes)); + addField(new Bitmap(buffer, offset + 694, 2, CRE_LOCATION_SAVED, OPTION_NOYES)); addField(new DecNumber(buffer, offset + 696, 2, CRE_SAVED_LOCATION_X)); addField(new DecNumber(buffer, offset + 698, 2, CRE_SAVED_LOCATION_Y)); addField(new DecNumber(buffer, offset + 700, 2, CRE_SAVED_ORIENTATION)); @@ -1736,7 +1735,7 @@ else if (version.equalsIgnoreCase("V9.0") || version.equalsIgnoreCase("V9.1")) { SectionCount countMemSpells = new SectionCount(buffer, offset + 684, 4, CRE_NUM_MEMORIZED_SPELLS, MemorizedSpells.class); addField(countMemSpells); - SectionOffset offsetItemslots = new SectionOffset(buffer, offset + 688, CRE_OFFSET_ITEM_SLOTS, DecNumber.class); + SectionOffset offsetItemslots = new SectionOffset(buffer, offset + 688, CRE_OFFSET_ITEM_SLOTS, IndexNumber.class); addField(offsetItemslots); SectionOffset offsetItems = new SectionOffset(buffer, offset + 692, CRE_OFFSET_ITEMS, Item.class); addField(offsetItems); @@ -1796,88 +1795,88 @@ else if (version.equalsIgnoreCase("V9.0") || version.equalsIgnoreCase("V9.1")) { offset = getExtraOffset() + offsetItemslots.getValue(); int slotCount = 0; if (version.equalsIgnoreCase("V1.2")) { - addField(new DecNumber(buffer, offset, 2, CRE_ITEM_SLOT_RIGHT_EARRING)); - addField(new DecNumber(buffer, offset + 2, 2, CRE_ITEM_SLOT_CHEST)); - addField(new DecNumber(buffer, offset + 4, 2, CRE_ITEM_SLOT_LEFT_TATTOO)); - addField(new DecNumber(buffer, offset + 6, 2, CRE_ITEM_SLOT_HAND)); - addField(new DecNumber(buffer, offset + 8, 2, CRE_ITEM_SLOT_LEFT_RING)); - addField(new DecNumber(buffer, offset + 10, 2, CRE_ITEM_SLOT_RIGHT_RING)); - addField(new DecNumber(buffer, offset + 12, 2, CRE_ITEM_SLOT_LEFT_EARRING)); - addField(new DecNumber(buffer, offset + 14, 2, CRE_ITEM_SLOT_RIGHT_TATTOO_LOWER)); - addField(new DecNumber(buffer, offset + 16, 2, CRE_ITEM_SLOT_WRIST)); + addField(new IndexNumber(buffer, offset, 2, CRE_ITEM_SLOT_RIGHT_EARRING)); + addField(new IndexNumber(buffer, offset + 2, 2, CRE_ITEM_SLOT_CHEST)); + addField(new IndexNumber(buffer, offset + 4, 2, CRE_ITEM_SLOT_LEFT_TATTOO)); + addField(new IndexNumber(buffer, offset + 6, 2, CRE_ITEM_SLOT_HAND)); + addField(new IndexNumber(buffer, offset + 8, 2, CRE_ITEM_SLOT_LEFT_RING)); + addField(new IndexNumber(buffer, offset + 10, 2, CRE_ITEM_SLOT_RIGHT_RING)); + addField(new IndexNumber(buffer, offset + 12, 2, CRE_ITEM_SLOT_LEFT_EARRING)); + addField(new IndexNumber(buffer, offset + 14, 2, CRE_ITEM_SLOT_RIGHT_TATTOO_LOWER)); + addField(new IndexNumber(buffer, offset + 16, 2, CRE_ITEM_SLOT_WRIST)); slotCount += 9; for (int i = 0; i < 4; i++) { - addField(new DecNumber(buffer, offset + 18 + (i * 2), 2, - String.format(CRE_ITEM_SLOT_WEAPON_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 18 + (i * 2), 2, + String.format(CRE_ITEM_SLOT_WEAPON_FMT, i+1))); } slotCount += 4; for (int i = 0; i < 6; i++) { - addField(new DecNumber(buffer, offset + 26 + (i * 2), 2, String.format(CRE_ITEM_SLOT_QUIVER_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 26 + (i * 2), 2, String.format(CRE_ITEM_SLOT_QUIVER_FMT, i+1))); } - addField(new DecNumber(buffer, offset + 38, 2, CRE_ITEM_SLOT_RIGHT_TATTOO_UPPER)); + addField(new IndexNumber(buffer, offset + 38, 2, CRE_ITEM_SLOT_RIGHT_TATTOO_UPPER)); slotCount += 7; for (int i = 0; i < 5; i++) { - addField(new DecNumber(buffer, offset + 40 + (i * 2), 2, String.format(CRE_ITEM_SLOT_QUICK_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 40 + (i * 2), 2, String.format(CRE_ITEM_SLOT_QUICK_FMT, i+1))); } slotCount += 5; for (int i = 0; i < 20; i++) { - addField(new DecNumber(buffer, offset + 50 + (i * 2), 2, String.format(CRE_ITEM_SLOT_INVENTORY_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 50 + (i * 2), 2, String.format(CRE_ITEM_SLOT_INVENTORY_FMT, i+1))); } slotCount += 20; - addField(new DecNumber(buffer, offset + 90, 2, CRE_ITEM_SLOT_MAGIC_WEAPON)); - addField(new DecNumber(buffer, offset + 92, 2, CRE_SELECTED_WEAPON_SLOT)); - addField(new DecNumber(buffer, offset + 94, 2, CRE_SELECTED_WEAPON_ABILITY)); + addField(new IndexNumber(buffer, offset + 90, 2, CRE_ITEM_SLOT_MAGIC_WEAPON)); + addField(new IndexNumber(buffer, offset + 92, 2, CRE_SELECTED_WEAPON_SLOT)); + addField(new IndexNumber(buffer, offset + 94, 2, CRE_SELECTED_WEAPON_ABILITY)); slotCount += 3; } else { if (Profile.getGame() == Profile.Game.PSTEE) { // REMEMBER: ITMSLOTS.2DA can be used as reference for item slot layout - addField(new DecNumber(buffer, offset, 2, CRE_ITEM_SLOT_LEFT_EARRING)); - addField(new DecNumber(buffer, offset + 2, 2, CRE_ITEM_SLOT_CHEST)); - addField(new DecNumber(buffer, offset + 4, 2, CRE_ITEM_SLOT_RIGHT_TATTOO_LOWER)); - addField(new DecNumber(buffer, offset + 6, 2, CRE_ITEM_SLOT_HAND)); - addField(new DecNumber(buffer, offset + 8, 2, CRE_ITEM_SLOT_RIGHT_RING)); - addField(new DecNumber(buffer, offset + 10, 2, CRE_ITEM_SLOT_LEFT_RING)); - addField(new DecNumber(buffer, offset + 12, 2, CRE_ITEM_SLOT_RIGHT_EARRING)); - addField(new DecNumber(buffer, offset + 14, 2, CRE_ITEM_SLOT_LEFT_TATTOO)); - addField(new DecNumber(buffer, offset + 16, 2, CRE_ITEM_SLOT_WRIST)); + addField(new IndexNumber(buffer, offset, 2, CRE_ITEM_SLOT_LEFT_EARRING)); + addField(new IndexNumber(buffer, offset + 2, 2, CRE_ITEM_SLOT_CHEST)); + addField(new IndexNumber(buffer, offset + 4, 2, CRE_ITEM_SLOT_RIGHT_TATTOO_LOWER)); + addField(new IndexNumber(buffer, offset + 6, 2, CRE_ITEM_SLOT_HAND)); + addField(new IndexNumber(buffer, offset + 8, 2, CRE_ITEM_SLOT_RIGHT_RING)); + addField(new IndexNumber(buffer, offset + 10, 2, CRE_ITEM_SLOT_LEFT_RING)); + addField(new IndexNumber(buffer, offset + 12, 2, CRE_ITEM_SLOT_RIGHT_EARRING)); + addField(new IndexNumber(buffer, offset + 14, 2, CRE_ITEM_SLOT_LEFT_TATTOO)); + addField(new IndexNumber(buffer, offset + 16, 2, CRE_ITEM_SLOT_WRIST)); slotCount += 9; } else { - addField(new DecNumber(buffer, offset, 2, CRE_ITEM_SLOT_HELMET)); - addField(new DecNumber(buffer, offset + 2, 2, CRE_ITEM_SLOT_ARMOR)); - addField(new DecNumber(buffer, offset + 4, 2, CRE_ITEM_SLOT_SHIELD)); - addField(new DecNumber(buffer, offset + 6, 2, CRE_ITEM_SLOT_GLOVES)); - addField(new DecNumber(buffer, offset + 8, 2, CRE_ITEM_SLOT_LEFT_RING)); - addField(new DecNumber(buffer, offset + 10, 2, CRE_ITEM_SLOT_RIGHT_RING)); - addField(new DecNumber(buffer, offset + 12, 2, CRE_ITEM_SLOT_AMULET)); - addField(new DecNumber(buffer, offset + 14, 2, CRE_ITEM_SLOT_BELT)); - addField(new DecNumber(buffer, offset + 16, 2, CRE_ITEM_SLOT_BOOTS)); + addField(new IndexNumber(buffer, offset, 2, CRE_ITEM_SLOT_HELMET)); + addField(new IndexNumber(buffer, offset + 2, 2, CRE_ITEM_SLOT_ARMOR)); + addField(new IndexNumber(buffer, offset + 4, 2, CRE_ITEM_SLOT_SHIELD)); + addField(new IndexNumber(buffer, offset + 6, 2, CRE_ITEM_SLOT_GLOVES)); + addField(new IndexNumber(buffer, offset + 8, 2, CRE_ITEM_SLOT_LEFT_RING)); + addField(new IndexNumber(buffer, offset + 10, 2, CRE_ITEM_SLOT_RIGHT_RING)); + addField(new IndexNumber(buffer, offset + 12, 2, CRE_ITEM_SLOT_AMULET)); + addField(new IndexNumber(buffer, offset + 14, 2, CRE_ITEM_SLOT_BELT)); + addField(new IndexNumber(buffer, offset + 16, 2, CRE_ITEM_SLOT_BOOTS)); slotCount += 9; } for (int i = 0; i < 4; i++) { - addField(new DecNumber(buffer, offset + 18 + (i * 2), 2, String.format(CRE_ITEM_SLOT_WEAPON_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 18 + (i * 2), 2, String.format(CRE_ITEM_SLOT_WEAPON_FMT, i+1))); slotCount++; } for (int i = 0; i < 4; i++) { - addField(new DecNumber(buffer, offset + 26 + (i * 2), 2, String.format(CRE_ITEM_SLOT_QUIVER_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 26 + (i * 2), 2, String.format(CRE_ITEM_SLOT_QUIVER_FMT, i+1))); slotCount++; } if (Profile.getGame() == Profile.Game.PSTEE) { - addField(new DecNumber(buffer, offset + 34, 2, CRE_ITEM_SLOT_RIGHT_TATTOO_UPPER)); + addField(new IndexNumber(buffer, offset + 34, 2, CRE_ITEM_SLOT_RIGHT_TATTOO_UPPER)); slotCount++; } else { - addField(new DecNumber(buffer, offset + 34, 2, CRE_ITEM_SLOT_CLOAK)); + addField(new IndexNumber(buffer, offset + 34, 2, CRE_ITEM_SLOT_CLOAK)); slotCount++; } for (int i = 0; i < 3; i++) { - addField(new DecNumber(buffer, offset + 36 + (i * 2), 2, String.format(CRE_ITEM_SLOT_QUICK_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 36 + (i * 2), 2, String.format(CRE_ITEM_SLOT_QUICK_FMT, i+1))); slotCount++; } for (int i = 0; i < 16; i++) { - addField(new DecNumber(buffer, offset + 42 + (i * 2), 2, String.format(CRE_ITEM_SLOT_INVENTORY_FMT, i+1))); + addField(new IndexNumber(buffer, offset + 42 + (i * 2), 2, String.format(CRE_ITEM_SLOT_INVENTORY_FMT, i+1))); slotCount++; } - addField(new DecNumber(buffer, offset + 74, 2, CRE_ITEM_SLOT_MAGIC_WEAPON)); + addField(new IndexNumber(buffer, offset + 74, 2, CRE_ITEM_SLOT_MAGIC_WEAPON)); slotCount++; StructEntry se = getAttribute(CRE_NUM_ITEM_SLOTS); int maxSlotCount = 0; @@ -1887,30 +1886,30 @@ else if (version.equalsIgnoreCase("V9.0") || version.equalsIgnoreCase("V9.1")) { if (Profile.getGame() == Profile.Game.PSTEE && slotCount + 8 < maxSlotCount) { // registered characters gain additional item slots int idxUnused = 1; - addField(new DecNumber(buffer, offset + 76, 2, String.format(CRE_ITEM_SLOT_QUIVER_FMT, 5))); - addField(new DecNumber(buffer, offset + 78, 2, String.format(CRE_ITEM_SLOT_UNUSED_FMT, idxUnused++))); - addField(new DecNumber(buffer, offset + 80, 2, String.format(CRE_ITEM_SLOT_QUICK_FMT, 4))); - addField(new DecNumber(buffer, offset + 82, 2, String.format(CRE_ITEM_SLOT_QUICK_FMT, 5))); + addField(new IndexNumber(buffer, offset + 76, 2, String.format(CRE_ITEM_SLOT_QUIVER_FMT, 5))); + addField(new IndexNumber(buffer, offset + 78, 2, String.format(CRE_ITEM_SLOT_UNUSED_FMT, idxUnused++))); + addField(new IndexNumber(buffer, offset + 80, 2, String.format(CRE_ITEM_SLOT_QUICK_FMT, 4))); + addField(new IndexNumber(buffer, offset + 82, 2, String.format(CRE_ITEM_SLOT_QUICK_FMT, 5))); slotCount += 4; offset += 84; for (int i = 0; i < 4; i++) { - addField(new DecNumber(buffer, offset, 2, String.format(CRE_ITEM_SLOT_INVENTORY_FMT, i+17))); + addField(new IndexNumber(buffer, offset, 2, String.format(CRE_ITEM_SLOT_INVENTORY_FMT, i+17))); slotCount++; offset += 2; } // filling the gap with placeholder slots for (int i = 0, imax = maxSlotCount - slotCount - 1; i < imax; i++) { - addField(new DecNumber(buffer, offset, 2, String.format(CRE_ITEM_SLOT_UNUSED_FMT, idxUnused++))); + addField(new IndexNumber(buffer, offset, 2, String.format(CRE_ITEM_SLOT_UNUSED_FMT, idxUnused++))); slotCount++; offset += 2; } - addField(new DecNumber(buffer, offset, 2, CRE_SELECTED_WEAPON_SLOT)); + addField(new IndexNumber(buffer, offset, 2, CRE_SELECTED_WEAPON_SLOT)); offset += 2; - addField(new DecNumber(buffer, offset, 2, CRE_SELECTED_WEAPON_ABILITY)); + addField(new IndexNumber(buffer, offset, 2, CRE_SELECTED_WEAPON_ABILITY)); offset += 2; } else { - addField(new DecNumber(buffer, offset + 76, 2, CRE_SELECTED_WEAPON_SLOT)); - addField(new DecNumber(buffer, offset + 78, 2, CRE_SELECTED_WEAPON_ABILITY)); + addField(new IndexNumber(buffer, offset + 76, 2, CRE_SELECTED_WEAPON_SLOT)); + addField(new IndexNumber(buffer, offset + 78, 2, CRE_SELECTED_WEAPON_ABILITY)); offset += 80; } } diff --git a/src/org/infinity/resource/cre/IndexNumber.java b/src/org/infinity/resource/cre/IndexNumber.java new file mode 100644 index 000000000..1c6c3c422 --- /dev/null +++ b/src/org/infinity/resource/cre/IndexNumber.java @@ -0,0 +1,26 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2020 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.cre; + +import java.nio.ByteBuffer; + +import org.infinity.datatype.DecNumber; + +/** + * Subclassed from {@code DecNumber} to make field type identifiable by reflection. + */ +public class IndexNumber extends DecNumber +{ + public IndexNumber(ByteBuffer buffer, int offset, int length, String name) + { + super(buffer, offset, length, name); + } + + public IndexNumber(ByteBuffer buffer, int offset, int length, String name, boolean signed) + { + super(buffer, offset, length, name, signed); + } + +} diff --git a/src/org/infinity/resource/cre/KnownSpells.java b/src/org/infinity/resource/cre/KnownSpells.java index e052232c3..cbd97d3e0 100644 --- a/src/org/infinity/resource/cre/KnownSpells.java +++ b/src/org/infinity/resource/cre/KnownSpells.java @@ -21,7 +21,7 @@ public final class KnownSpells extends AbstractStruct implements AddRemovable public static final String CRE_KNOWN_LEVEL = "Level"; public static final String CRE_KNOWN_TYPE = "Type"; - private static final String[] s_spelltype = {"Priest", "Wizard", "Innate"}; + public static final String[] s_spelltype = {"Priest", "Wizard", "Innate"}; KnownSpells() throws Exception { diff --git a/src/org/infinity/resource/cre/SpellMemorization.java b/src/org/infinity/resource/cre/SpellMemorization.java index e6410c800..faff0765e 100644 --- a/src/org/infinity/resource/cre/SpellMemorization.java +++ b/src/org/infinity/resource/cre/SpellMemorization.java @@ -26,8 +26,6 @@ public final class SpellMemorization extends AbstractStruct implements AddRemova public static final String CRE_MEMORIZATION_SPELL_TABLE_INDEX = "Spell table index"; public static final String CRE_MEMORIZATION_SPELL_COUNT = "Spell count"; - private static final String[] s_spelltype = {"Priest", "Wizard", "Innate"}; - SpellMemorization() throws Exception { super(null, CRE_MEMORIZATION, StreamUtils.getByteBuffer(16), 0); @@ -91,7 +89,7 @@ public int read(ByteBuffer buffer, int offset) addField(new DecNumber(buffer, offset, 2, CRE_MEMORIZATION_LEVEL)); addField(new DecNumber(buffer, offset + 2, 2, CRE_MEMORIZATION_NUM_MEMORIZABLE_TOTAL)); addField(new DecNumber(buffer, offset + 4, 2, CRE_MEMORIZATION_NUM_MEMORIZABLE_CURRENT)); - addField(new Bitmap(buffer, offset + 6, 2, CRE_MEMORIZATION_TYPE, s_spelltype)); + addField(new Bitmap(buffer, offset + 6, 2, CRE_MEMORIZATION_TYPE, KnownSpells.s_spelltype)); addField(new DecNumber(buffer, offset + 8, 4, CRE_MEMORIZATION_SPELL_TABLE_INDEX)); addField(new DecNumber(buffer, offset + 12, 4, CRE_MEMORIZATION_SPELL_COUNT)); return offset + 16; diff --git a/src/org/infinity/resource/dlg/DlgResource.java b/src/org/infinity/resource/dlg/DlgResource.java index f729b87fd..1b9762881 100644 --- a/src/org/infinity/resource/dlg/DlgResource.java +++ b/src/org/infinity/resource/dlg/DlgResource.java @@ -609,11 +609,7 @@ private boolean exportDlgAsText(PrintWriter writer) writer.println(); // *** start of WeiDU D script *** - String dlgResRef = getResourceEntry().getResourceName(); - int p = dlgResRef.lastIndexOf('.'); - if (p >= 0) { - dlgResRef = dlgResRef.substring(0, p); - } + String dlgResRef = getResourceEntry().getResourceRef(); writer.print("BEGIN ~" + dlgResRef + "~"); StructEntry entry = getAttribute(DLG_THREAT_RESPONSE); diff --git a/src/org/infinity/resource/dlg/DlgTreeCellRenderer.java b/src/org/infinity/resource/dlg/DlgTreeCellRenderer.java index e74eb45db..74aba78c5 100644 --- a/src/org/infinity/resource/dlg/DlgTreeCellRenderer.java +++ b/src/org/infinity/resource/dlg/DlgTreeCellRenderer.java @@ -12,6 +12,7 @@ import javax.swing.tree.DefaultTreeCellRenderer; import org.infinity.gui.BrowserMenuBar; +import org.infinity.gui.ViewerUtil; /** * Renderer for dialogue tree, drawing elements of each dialog with its own color @@ -25,23 +26,6 @@ */ final class DlgTreeCellRenderer extends DefaultTreeCellRenderer { - /** - * This array contains background colors for other dialogs to which viewed dialog - * refers. Colors are assigned to other resources from this array on rotation basis. - */ - private static final Color[] OTHER_DIALOG_COLORS = { - new Color(0xd8d8ff), - new Color(0xb8ffb8), - new Color(0xffd0d0), - new Color(0xffc8ff), - new Color(0xffffa0), - new Color(0xe0e0e0), - new Color(0x85ffc2), - new Color(0xffd3a6), - new Color(0x99ccff), - new Color(0xffa3d1), - }; - /** Background colors for text in dialogs to that can refer main dialog. */ private final HashMap dialogColors = new HashMap<>(); /** Main dialogue that shown in the tree. */ @@ -91,7 +75,7 @@ private Color getColor(DlgResource dialog) return null; } return dialogColors.computeIfAbsent(dialog, - d -> OTHER_DIALOG_COLORS[dialogColors.size() % OTHER_DIALOG_COLORS.length] + d -> ViewerUtil.BACKGROUND_COLORS[dialogColors.size() % ViewerUtil.BACKGROUND_COLORS.length] ); } } diff --git a/src/org/infinity/resource/gam/GamResource.java b/src/org/infinity/resource/gam/GamResource.java index e3477df85..89e5d7c2a 100644 --- a/src/org/infinity/resource/gam/GamResource.java +++ b/src/org/infinity/resource/gam/GamResource.java @@ -20,6 +20,7 @@ import org.infinity.datatype.Bitmap; import org.infinity.datatype.DecNumber; import org.infinity.datatype.Flag; +import org.infinity.datatype.HashBitmap; import org.infinity.datatype.HexNumber; import org.infinity.datatype.IsNumeric; import org.infinity.datatype.ResourceRef; @@ -61,7 +62,7 @@ public final class GamResource extends AbstractStruct implements Resource, HasAd public static final String GAM_SELECTED_FORMATION = "Selected formation"; public static final String GAM_FORMATION_BUTTON_FMT = "Formation button %d"; public static final String GAM_PARTY_GOLD = "Party gold"; - public static final String GAM_NUM_NPCS_IN_PARTY = "# NPCs in party"; + public static final String GAM_VIEW_PLAYER_AREA = "View area of party member at"; public static final String GAM_WEATHER = "Weather"; public static final String GAM_OFFSET_PARTY_MEMBERS = "Party members offset"; public static final String GAM_NUM_PARTY_MEMBERS = "# party members"; @@ -104,7 +105,6 @@ public final class GamResource extends AbstractStruct implements Resource, HasAd public static final String GAM_UNKNOWN_STRUCTURE = "Unknown structure"; public static final String GAM_POCKET_PLANE = "Pocket plane"; - public static final String[] s_noyes = {"No", "Yes"}; public static final String[] s_formation = {"Button 1", "Button 2", "Button 3", "Button 4", "Button 5"}; public static final String[] s_weather = {"No weather", "Raining", "Snowing", "Light weather", "Medium weather", "Light wind", "Medium wind", "Rare lightning", @@ -312,7 +312,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 14 + (i * 2), 2, String.format(GAM_FORMATION_BUTTON_FMT, i+1))); } addField(new DecNumber(buffer, offset + 24, 4, GAM_PARTY_GOLD)); - addField(new DecNumber(buffer, offset + 28, 2, GAM_NUM_NPCS_IN_PARTY)); + addField(new HashBitmap(buffer, offset + 28, 2, GAM_VIEW_PLAYER_AREA, PartyNPC.m_partyOrder)); addField(new Flag(buffer, offset + 30, 2, GAM_WEATHER, s_weather)); SectionOffset offset_partynpc = new SectionOffset(buffer, offset + 32, GAM_OFFSET_PARTY_MEMBERS, PartyNPC.class); @@ -417,7 +417,7 @@ else if (Profile.getEngine() == Profile.Engine.IWD2) { // V2.2 (V1.1 & V2.0 in B addField(numIWD2); offIWD2 = new SectionOffset(buffer, offset + 104, GAM_OFFSET_UNKNOWN, UnknownSection3.class); addField(offIWD2); - addField(new Bitmap(buffer, offset + 108, 4, GAM_NIGHTMARE_MODE, s_noyes)); + addField(new Bitmap(buffer, offset + 108, 4, GAM_NIGHTMARE_MODE, OPTION_NOYES)); addField(new Unknown(buffer, offset + 112, 68)); } diff --git a/src/org/infinity/resource/gam/ModronMaze.java b/src/org/infinity/resource/gam/ModronMaze.java index f4cf3ef94..10e89b608 100644 --- a/src/org/infinity/resource/gam/ModronMaze.java +++ b/src/org/infinity/resource/gam/ModronMaze.java @@ -29,8 +29,6 @@ public final class ModronMaze extends AbstractStruct public static final String GAM_MAZE_MAZE_BLOCKER_MADE = "Foyer maze blocker made"; public static final String GAM_MAZE_ENGINE_BLOCKER_MADE = "Foyer engine blocker made"; - private static final String[] s_noyes = {"No", "Yes"}; - public ModronMaze(AbstractStruct superStruct, ByteBuffer buffer, int offset) throws Exception { super(superStruct, GAM_MAZE, buffer, offset); @@ -71,11 +69,11 @@ public int read(ByteBuffer buffer, int offset) throws Exception curOfs += 4; addField(new DecNumber(buffer, curOfs, 4, GAM_MAZE_NUM_TRAPS)); curOfs += 4; - addField(new Bitmap(buffer, curOfs, 4, GAM_MAZE_INITIALIZED, s_noyes)); + addField(new Bitmap(buffer, curOfs, 4, GAM_MAZE_INITIALIZED, OPTION_NOYES)); curOfs += 4; - addField(new Bitmap(buffer, curOfs, 4, GAM_MAZE_MAZE_BLOCKER_MADE, s_noyes)); + addField(new Bitmap(buffer, curOfs, 4, GAM_MAZE_MAZE_BLOCKER_MADE, OPTION_NOYES)); curOfs += 4; - addField(new Bitmap(buffer, curOfs, 4, GAM_MAZE_ENGINE_BLOCKER_MADE, s_noyes)); + addField(new Bitmap(buffer, curOfs, 4, GAM_MAZE_ENGINE_BLOCKER_MADE, OPTION_NOYES)); curOfs += 4; return curOfs; diff --git a/src/org/infinity/resource/gam/ModronMazeEntry.java b/src/org/infinity/resource/gam/ModronMazeEntry.java index 6cdd61587..d291ab567 100644 --- a/src/org/infinity/resource/gam/ModronMazeEntry.java +++ b/src/org/infinity/resource/gam/ModronMazeEntry.java @@ -22,7 +22,6 @@ public final class ModronMazeEntry extends AbstractStruct public static final String GAM_MAZE_ENTRY_EXITS = "Exits"; public static final String GAM_MAZE_ENTRY_POLULATED = "Populated"; - private static final String[] s_noyes = {"No", "Yes"}; private static final String[] s_traps = {"TrapA", "TrapB", "TrapC"}; private static final String[] s_walls = {"None", "East", "West", "North", "South"}; @@ -34,13 +33,13 @@ public ModronMazeEntry(AbstractStruct superStruct, ByteBuffer buffer, int offset @Override public int read(ByteBuffer buffer, int offset) throws Exception { - addField(new Bitmap(buffer, offset, 4, GAM_MAZE_ENTRY_USED, s_noyes)); - addField(new Bitmap(buffer, offset + 4, 4, GAM_MAZE_ENTRY_ACCESSIBLE, s_noyes)); - addField(new Bitmap(buffer, offset + 8, 4, GAM_MAZE_ENTRY_IS_VALID, s_noyes)); - addField(new Bitmap(buffer, offset + 12, 4, GAM_MAZE_ENTRY_IS_TRAPPED, s_noyes)); + addField(new Bitmap(buffer, offset, 4, GAM_MAZE_ENTRY_USED, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 4, 4, GAM_MAZE_ENTRY_ACCESSIBLE, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 8, 4, GAM_MAZE_ENTRY_IS_VALID, OPTION_NOYES)); + 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, s_traps)); addField(new Flag(buffer, offset + 20, 2, GAM_MAZE_ENTRY_EXITS, s_walls)); - addField(new Bitmap(buffer, offset + 22, 4, GAM_MAZE_ENTRY_POLULATED, s_noyes)); + addField(new Bitmap(buffer, offset + 22, 4, GAM_MAZE_ENTRY_POLULATED, OPTION_NOYES)); return offset + 26; } } diff --git a/src/org/infinity/resource/gam/PartyNPC.java b/src/org/infinity/resource/gam/PartyNPC.java index 1e1f0f995..1077fdc7e 100644 --- a/src/org/infinity/resource/gam/PartyNPC.java +++ b/src/org/infinity/resource/gam/PartyNPC.java @@ -10,6 +10,7 @@ import org.infinity.datatype.Bitmap; import org.infinity.datatype.DecNumber; +import org.infinity.datatype.Flag; import org.infinity.datatype.HashBitmap; import org.infinity.datatype.HexNumber; import org.infinity.datatype.IdsBitmap; @@ -25,7 +26,6 @@ import org.infinity.resource.HasViewerTabs; import org.infinity.resource.Profile; import org.infinity.resource.StructEntry; -import org.infinity.resource.are.Actor; import org.infinity.resource.cre.CreResource; import org.infinity.util.LongIntegerHashMap; import org.infinity.util.io.StreamUtils; @@ -85,23 +85,24 @@ public class PartyNPC extends AbstractStruct implements HasViewerTabs, HasAddRem public static final String GAM_NPC_STAT_FAV_WEAPON_FMT = "Favorite weapon %d"; public static final String GAM_NPC_STAT_FAV_WEAPON_COUNT_FMT = "Favorite weapon counter %d"; - private static final LongIntegerHashMap partyOrder = new LongIntegerHashMap(); - private static final LongIntegerHashMap m_selected = new LongIntegerHashMap(); - private static final String s_noyes[] = {"No", "Yes"}; + public static final LongIntegerHashMap m_partyOrder = new LongIntegerHashMap(); +// private static final LongIntegerHashMap m_selected = new LongIntegerHashMap(); + + private static final String[] s_selected = {"Not selected", "Selected", null, null, null, null, null, null, null, null, null, null, null, null, null, null, "Dead" }; static { - partyOrder.put(0L, "Slot 1"); - partyOrder.put(1L, "Slot 2"); - partyOrder.put(2L, "Slot 3"); - partyOrder.put(3L, "Slot 4"); - partyOrder.put(4L, "Slot 5"); - partyOrder.put(5L, "Slot 6"); + m_partyOrder.put(0L, "Slot 1"); + m_partyOrder.put(1L, "Slot 2"); + m_partyOrder.put(2L, "Slot 3"); + m_partyOrder.put(3L, "Slot 4"); + m_partyOrder.put(4L, "Slot 5"); + m_partyOrder.put(5L, "Slot 6"); // partyOrder.put(0x8000L, "In party, dead"); - partyOrder.put(0xffffL, "Not in party"); + m_partyOrder.put(0xffffL, "Not in party"); - m_selected.put(0L, "Not selected"); - m_selected.put(1L, "Selected"); - m_selected.put(0x8000L, "Dead"); +// m_selected.put(0L, "Not selected"); +// m_selected.put(1L, "Selected"); +// m_selected.put(0x8000L, "Dead"); } PartyNPC() throws Exception @@ -216,8 +217,8 @@ void updateCREOffset() @Override public int read(ByteBuffer buffer, int offset) throws Exception { - addField(new HashBitmap(buffer, offset, 2, GAM_NPC_SELECTION_STATE, m_selected)); - addField(new HashBitmap(buffer, offset + 2, 2, GAM_NPC_PARTY_POSITION, partyOrder)); + addField(new Flag(buffer, offset, 2, GAM_NPC_SELECTION_STATE, s_selected)); + addField(new HashBitmap(buffer, offset + 2, 2, GAM_NPC_PARTY_POSITION, m_partyOrder)); HexNumber creOffset = new HexNumber(buffer, offset + 4, 4, GAM_NPC_OFFSET_CRE); addField(creOffset); addField(new DecNumber(buffer, offset + 8, 4, GAM_NPC_CRE_SIZE)); @@ -226,7 +227,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception } else { addField(new ResourceRef(buffer, offset + 12, GAM_NPC_CHARACTER, "CRE")); } - addField(new Bitmap(buffer, offset + 20, 4, GAM_NPC_ORIENTATION, Actor.s_orientation)); + addField(new Bitmap(buffer, offset + 20, 4, GAM_NPC_ORIENTATION, OPTION_ORIENTATION)); addField(new ResourceRef(buffer, offset + 24, GAM_NPC_CURRENT_AREA, "ARE")); addField(new DecNumber(buffer, offset + 32, 2, GAM_NPC_LOCATION_X)); addField(new DecNumber(buffer, offset + 34, 2, GAM_NPC_LOCATION_Y)); @@ -433,7 +434,7 @@ private int readCharStats(ByteBuffer buffer, int offset) addField(new DecNumber(buffer, offset + 4, 4, GAM_NPC_STAT_XP_FOE_VANQUISHED)); addField(new DecNumber(buffer, offset + 8, 4, GAM_NPC_STAT_TIME_IN_PARTY)); addField(new DecNumber(buffer, offset + 12, 4, GAM_NPC_STAT_JOIN_TIME)); - addField(new Bitmap(buffer, offset + 16, 1, GAM_NPC_STAT_IN_PARTY, s_noyes)); + addField(new Bitmap(buffer, offset + 16, 1, GAM_NPC_STAT_IN_PARTY, OPTION_NOYES)); addField(new Unknown(buffer, offset + 17, 2)); addField(new TextString(buffer, offset + 19, 1, GAM_NPC_STAT_INITIAL_CHAR)); addField(new DecNumber(buffer, offset + 20, 4, GAM_NPC_STAT_KILLS_XP_CHAPTER)); diff --git a/src/org/infinity/resource/graphics/BamResource.java b/src/org/infinity/resource/graphics/BamResource.java index 0d61bf2c2..a971a10f2 100644 --- a/src/org/infinity/resource/graphics/BamResource.java +++ b/src/org/infinity/resource/graphics/BamResource.java @@ -154,7 +154,6 @@ public BamResource(ResourceEntry entry) bamControl.setMode(BamDecoder.BamControl.Mode.SHARED); if (bamControl instanceof BamV1Decoder.BamV1Control) { ((BamV1Decoder.BamV1Control)bamControl).setTransparencyEnabled(transparencyEnabled); - ((BamV1Decoder.BamV1Control)bamControl).setTransparencyMode(BamV1Decoder.TransparencyMode.NORMAL); } } catch (Throwable t) { t.printStackTrace(); diff --git a/src/org/infinity/resource/graphics/BamV1Decoder.java b/src/org/infinity/resource/graphics/BamV1Decoder.java index 4540232b1..2b6ff9466 100644 --- a/src/org/infinity/resource/graphics/BamV1Decoder.java +++ b/src/org/infinity/resource/graphics/BamV1Decoder.java @@ -25,15 +25,6 @@ */ public class BamV1Decoder extends BamDecoder { - /** - * Definitions on how to handle palette transparency:
- * {@code Normal} looks for the first entry containing RGB(0, 255, 0). It falls back to palette - * index 0 if no entry has been found.
- * {@code FirstIndexOnly} automatically uses palette index 0 without looking for entries - * containing RGB(0, 255, 0). - */ - public enum TransparencyMode { NORMAL, FIRST_INDEX_ONLY } - private final List listFrames = new ArrayList(); private final List listCycles = new ArrayList(); private final BamV1FrameEntry defaultFrameInfo = new BamV1FrameEntry(null, 0); @@ -300,28 +291,34 @@ private void decodeFrame(BamControl control, int frameIdx, Image canvas) srcOfs = ofsData; dstOfs = 0; } - for (int y = 0; y < maxHeight; y++) { - for (int x = 0; x < srcWidth; x++, dstOfs++) { - if (count > 0) { - // writing remaining RLE compressed pixels - count--; - if (x < maxWidth) { - if (bufferB != null) bufferB[dstOfs] = pixel; - if (bufferI != null) bufferI[dstOfs] = color; - } - } else { - pixel = bamBuffer.get(srcOfs++); - color = palette[pixel & 0xff]; - if (isCompressed && (pixel & 0xff) == rleIndex) { - count = bamBuffer.get(srcOfs++) & 0xff; - } - if (x < maxWidth) { - if (bufferB != null) bufferB[dstOfs] = pixel; - if (bufferI != null) bufferI[dstOfs] = color; + try { + for (int y = 0; y < maxHeight; y++) { + for (int x = 0; x < srcWidth; x++, dstOfs++) { + if (count > 0) { + // writing remaining RLE compressed pixels + count--; + if (x < maxWidth) { + if (bufferB != null) bufferB[dstOfs] = pixel; + if (bufferI != null) bufferI[dstOfs] = color; + } + } else { + pixel = bamBuffer.get(srcOfs++); + color = palette[pixel & 0xff]; + if (isCompressed && (pixel & 0xff) == rleIndex) { + count = bamBuffer.get(srcOfs++) & 0xff; + } + if (x < maxWidth) { + if (bufferB != null) bufferB[dstOfs] = pixel; + if (bufferI != null) bufferI[dstOfs] = color; + } } } + dstOfs += dstWidth - srcWidth; } - dstOfs += dstWidth - srcWidth; + } catch (Exception e) { + System.err.printf("Error [%s]: input (offset=%d, size=%d), output (offset=%d, size=%d)\n", + e.getClass().getName(), srcOfs, bamBuffer.limit(), dstOfs, + bufferB != null ? bufferB.length : bufferI.length); } bufferB = null; bufferI = null; @@ -383,7 +380,6 @@ public static class BamV1Control extends BamControl { private int[] currentPalette, externalPalette; private boolean transparencyEnabled; - private TransparencyMode transparencyMode; private int currentCycle, currentFrame; protected BamV1Control(BamV1Decoder decoder) @@ -411,37 +407,14 @@ public void setTransparencyEnabled(boolean enable) } } - /** - * Returns the currently used transparency mode for palettes. - */ - public TransparencyMode getTransparencyMode() - { - return transparencyMode; - } - - /** - * Sets the mode on how to handle transparency in palettes. - * @param transparencyMode The transparency mode to set. - */ - public void setTransparencyMode(TransparencyMode transparencyMode) - { - if (transparencyMode != null) { - if (this.transparencyMode != transparencyMode) { - this.transparencyMode = transparencyMode; - preparePalette(externalPalette); - } - } - } - /** Returns the transparency index of the current palette. */ public int getTransparencyIndex() { - for (int i = 0; i < currentPalette.length; i++) { - if ((currentPalette[i] & 0xff000000) == 0) { - return i; - } - } - return 0; + int idx = currentPalette.length - 1; + for (; idx > 0; idx--) + if ((currentPalette[idx] & 0xff000000) == 0) + break; + return idx; } /** Returns whether the palette makes use of alpha transparency. */ @@ -653,11 +626,6 @@ public int cycleGetFrameIndexAbsolute(int cycleIdx, int frameIdx) private void init() { transparencyEnabled = true; - if (Profile.getEngine() == Profile.Engine.BG1 || Profile.getEngine() == Profile.Engine.PST) { - transparencyMode = TransparencyMode.NORMAL; - } else { - transparencyMode = TransparencyMode.FIRST_INDEX_ONLY; - } currentPalette = null; externalPalette = null; currentCycle = currentFrame = 0; @@ -670,16 +638,15 @@ private void init() // Prepares the palette to be used for decoding BAM frames private void preparePalette(int[] externalPalette) { - if (currentPalette == null) { + if (currentPalette == null) currentPalette = new int[256]; - } // some optimizations: don't prepare if the palette hasn't changed - boolean isNormalMode = (getTransparencyMode() == TransparencyMode.NORMAL); int idx = 0; - int transIndex = -1; + List transIndices = new ArrayList<>(); // multiple transparent palette indices are supported int alphaMask = Profile.isEnhancedEdition() ? 0 : 0xff000000; boolean alphaUsed = false; // determines whether alpha is actually used + if (externalPalette != null) { // filling palette entries from external palette, as much as possible for (; idx < externalPalette.length && idx < 256; idx++) { @@ -688,44 +655,37 @@ private void preparePalette(int[] externalPalette) currentPalette[idx] |= alphaMask; } alphaUsed |= (currentPalette[idx] & 0xff000000) != 0; - if (isNormalMode && transIndex < 0 && (currentPalette[idx] & 0x00ffffff) == 0x0000ff00) { - transIndex = idx; - } + if (idx == 0 || (currentPalette[idx] & 0x00ffffff) == 0x0000ff00) + transIndices.add(idx); } } - // filling remaining entries with BAM palette + if (getDecoder().bamPalette != null) { + // filling remaining entries with BAM palette for (; idx < getDecoder().bamPalette.length; idx++) { currentPalette[idx] = getDecoder().bamPalette[idx]; if ((currentPalette[idx] & 0xff000000) == 0) { currentPalette[idx] |= alphaMask; } alphaUsed |= (currentPalette[idx] & 0xff000000) != 0; - if (isNormalMode && transIndex < 0 && (currentPalette[idx] & 0x00ffffff) == 0x0000ff00) { - transIndex = idx; - } + if (idx == 0 || (currentPalette[idx] & 0x00ffffff) == 0x0000ff00) + transIndices.add(idx); } } - // removing alpha support if needed if (!alphaUsed) { + // discarding alpha for (int i = 0; i < currentPalette.length; i++) { currentPalette[i] |= 0xff000000; } } - // applying transparent index - if (isNormalMode && transIndex >= 0) { - if (transparencyEnabled) { - currentPalette[transIndex] = 0; - } else { - currentPalette[transIndex] |= 0xff000000; - } - } - - // falling back to transparency at color index 0 - if (transparencyEnabled && transIndex < 0) { - currentPalette[0] = 0; + // applying transparent indices + for (int i : transIndices) { + if (transparencyEnabled) + currentPalette[i] = 0; + else + currentPalette[i] |= 0xff000000; } } } diff --git a/src/org/infinity/resource/graphics/ColorConvert.java b/src/org/infinity/resource/graphics/ColorConvert.java index ba2987244..777b2117f 100644 --- a/src/org/infinity/resource/graphics/ColorConvert.java +++ b/src/org/infinity/resource/graphics/ColorConvert.java @@ -458,7 +458,7 @@ public static int[] loadPaletteBMP(Path file) throws Exception is.read(palette); int[] retVal = new int[colorCount]; for (int i =0; i < colorCount; i++) { - retVal[i] = DynamicArray.getInt(palette, i << 2) & 0x00ffffff; + retVal[i] = 0xff000000 | (DynamicArray.getInt(palette, i << 2) & 0x00ffffff); } return retVal; } else { @@ -479,10 +479,11 @@ public static int[] loadPaletteBMP(Path file) throws Exception /** * Attempts to load a palette from the specified PNG file. * @param file The PNG file to extract the palette from. + * @param preserveAlpha Whether to preserve original alpha transparency of the palette. * @return The palette as ARGB integers. * @throws Exception on error. */ - public static int[] loadPalettePNG(Path file) throws Exception + public static int[] loadPalettePNG(Path file, boolean preserveAlpha) throws Exception { if (file != null && Files.isRegularFile(file)) { try (InputStream is = StreamUtils.getInputStream(file)) { @@ -491,9 +492,13 @@ public static int[] loadPalettePNG(Path file) throws Exception IndexColorModel cm = (IndexColorModel)img.getColorModel(); int[] retVal = new int[cm.getMapSize()]; cm.getRGBs(retVal); + if (!preserveAlpha) { + for (int i = 0; i < retVal.length; i++) + retVal[i] |= 0xff000000; + } return retVal; } else { - throw new Exception("Error loading palette from PNG fille " + file.getFileName()); + throw new Exception("Error loading palette from PNG file " + file.getFileName()); } } catch (IOException e) { e.printStackTrace(); @@ -505,7 +510,7 @@ public static int[] loadPalettePNG(Path file) throws Exception } /** - * Attempts to load a palette from the specified Windows PAL file. + * Attempts to load a palette from the specified Windows PAL file. Does not support alpha transparency. * @param file The Windows PAL file to load. * @return The palette as ARGB integers. * @throws Exception on error. @@ -514,30 +519,35 @@ public static int[] loadPalettePAL(Path file) throws Exception { if (file != null && Files.isRegularFile(file)) { try (InputStream is = StreamUtils.getInputStream(file)) { - byte[] signature = new byte[8]; - is.read(signature); - if ("RIFF".equals(new String(signature, 0, 4))) { - // extracting palette from Windows palette file + byte[] signature = new byte[12]; + boolean eof = is.read(signature) != signature.length; + if ("RIFF".equals(new String(signature, 0, 4)) && "PAL ".equals(new String(signature, 8, 4))) { byte[] signature2 = new byte[8]; - is.read(signature2); - if ("PAL data".equals(new String(signature2))) { - byte[] header = new byte[8]; - is.read(header); - int numColors = DynamicArray.getUnsignedShort(header, 6); - if (numColors >= 2 && numColors <= 256) { - byte[] palData = new byte[numColors << 2]; - is.read(palData); - int[] retVal = new int[numColors]; - for (int i = 0; i < numColors; i++) { - int col = DynamicArray.getInt(palData, i << 2); - retVal[i] = ((col << 16) & 0xff0000) | (col & 0x00ff00) | ((col >> 16) & 0x0000ff); - } - return retVal; - } else { - throw new Exception("Invalid number of color entries in Windows palette file " + file.getFileName()); + + // find palette data block + eof = is.read(signature2) != signature2.length; + while (!eof && !"data".equals(new String(signature2, 0, 4))) { + is.skip(DynamicArray.getInt(signature2, 4) - 4); + eof = is.read(signature2) != signature2.length; + } + if (eof) + throw new Exception(); + + // extracting palette from Windows palette file + byte[] header = new byte[4]; + is.read(header); + int numColors = DynamicArray.getUnsignedShort(header, 2); + if (numColors >= 2 && numColors <= 256) { + byte[] palData = new byte[numColors << 2]; + is.read(palData); + int[] retVal = new int[numColors]; + for (int i = 0; i < numColors; i++) { + int col = DynamicArray.getInt(palData, i << 2); + retVal[i] = 0xff000000 | ((col << 16) & 0xff0000) | (col & 0x00ff00) | ((col >> 16) & 0x0000ff); } + return retVal; } else { - throw new Exception("Error loading palette from Windows palette file " + file.getFileName()); + throw new Exception("Invalid number of color entries in Windows palette file " + file.getFileName()); } } else { throw new Exception("Invalid Windows palette file " + file.getFileName()); @@ -552,7 +562,7 @@ public static int[] loadPalettePAL(Path file) throws Exception } /** - * Attempts to load a palette from the specified Adobe Color Table file. + * Attempts to load a palette from the specified Adobe Color Table file. Does not support alpha transparency. * @param file The Adobe Color Table file to load. * @return The palette as ARGB integers. * @throws Exception on error. @@ -576,7 +586,7 @@ public static int[] loadPaletteACT(Path file) throws Exception if (i == transColor) { retVal[i] = 0x00ff00; } else { - retVal[i] = ((palData[ofs] & 0xff) << 16) | ((palData[ofs+1] & 0xff) << 8) | (palData[ofs+2] & 0xff); + retVal[i] = 0xff000000 | ((palData[ofs] & 0xff) << 16) | ((palData[ofs+1] & 0xff) << 8) | (palData[ofs+2] & 0xff); } } return retVal; @@ -595,10 +605,11 @@ public static int[] loadPaletteACT(Path file) throws Exception /** * Attempts to load a palette from the specified BAM file. * @param file The BAM file to extract the palette from. + * @param preserveAlpha Whether to preserve original alpha transparency of the BAM palette. * @return The palette as ARGB integers. * @throws Exception on error. */ - public static int[] loadPaletteBAM(Path file) throws Exception + public static int[] loadPaletteBAM(Path file, boolean preserveAlpha) throws Exception { if (file != null && Files.isRegularFile(file)) { try (InputStream is = StreamUtils.getInputStream(file)) { @@ -617,7 +628,10 @@ public static int[] loadPaletteBAM(Path file) throws Exception if (ofs >= 0x18 && ofs < bamData.length - 1024) { int[] retVal = new int[256]; for (int i = 0; i < 256; i++) { - retVal[i] = DynamicArray.getInt(bamData, ofs+(i << 2)) & 0x00ffffff; + retVal[i] = DynamicArray.getInt(bamData, ofs+(i << 2)); + // backwards compatibility with non-EE BAM files + if (!preserveAlpha || (retVal[i] & 0xff000000) == 0) + retVal[i] |= 0xff000000; } return retVal; } else { diff --git a/src/org/infinity/resource/graphics/TisResource.java b/src/org/infinity/resource/graphics/TisResource.java index 8cff95be9..3fecf273f 100644 --- a/src/org/infinity/resource/graphics/TisResource.java +++ b/src/org/infinity/resource/graphics/TisResource.java @@ -1232,10 +1232,7 @@ public static int calcTileWidth(ResourceEntry entry, int tileCount) // Try to fetch the correct width from an associated WED if available if (entry != null) { try { - String tisNameBase = entry.getResourceName(); - if (tisNameBase.lastIndexOf('.') > 0) { - tisNameBase = tisNameBase.substring(0, tisNameBase.lastIndexOf('.')); - } + String tisNameBase = entry.getResourceRef(); ResourceEntry wedEntry = null; while (tisNameBase.length() >= 6) { String wedFileName = tisNameBase + ".WED"; diff --git a/src/org/infinity/resource/graphics/TisV2Decoder.java b/src/org/infinity/resource/graphics/TisV2Decoder.java index 87219d2d9..b91ce2a1f 100644 --- a/src/org/infinity/resource/graphics/TisV2Decoder.java +++ b/src/org/infinity/resource/graphics/TisV2Decoder.java @@ -167,11 +167,9 @@ private void init() } tisBuffer = getResourceEntry().getResourceBuffer(); - String name = getResourceEntry().getResourceName(); - int idx = name.lastIndexOf('.'); - if (idx < 0) idx = name.length(); + String name = getResourceEntry().getResourceRef(); pvrzNameBase = getResourceEntry().getResourceName().substring(0, 1) + - getResourceEntry().getResourceName().substring(2, idx); + getResourceEntry().getResourceName().substring(2, name.length()); setType(Type.PVRZ); diff --git a/src/org/infinity/resource/itm/Ability.java b/src/org/infinity/resource/itm/Ability.java index 9ae9fd040..168f34cc0 100644 --- a/src/org/infinity/resource/itm/Ability.java +++ b/src/org/infinity/resource/itm/Ability.java @@ -47,12 +47,11 @@ public final class Ability extends AbstractAbility implements AddRemovable, HasA public static final String ITM_ABIL_IS_BOLT = "Is bolt?"; public static final String ITM_ABIL_IS_BULLET = "Is bullet?"; - public static final String[] s_noyes = {"No", "Yes"}; public static final String[] s_launcher = {"None", "Bow", "Crossbow", "Sling"}; public static final String[] s_abilityuse = {"", "Weapon", "Spell", "Item", "Ability", "reserved"}; public static final String[] s_recharge = { "No flags set", "Add strength bonus", "Breakable", "EE: Damage strength bonus", - "EE: THAC0 strength bonus", null, null, null, null, null, null, + "EE: THAC0 strength bonus", null, null, null, null, null, "EE: Break Sanctuary;Ignored for Target: Caster", "Hostile", "Recharge after resting", null, null, null, null, "Bypass armor", "Keen edge", null, null, null, null, null, null, null, "Ex: Toggle backstab", "EE/Ex: Cannot target invisible"}; @@ -182,9 +181,9 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 44, 2, ITM_ABIL_ANIM_OVERHAND)); addField(new DecNumber(buffer, offset + 46, 2, ITM_ABIL_ANIM_BACKHAND)); addField(new DecNumber(buffer, offset + 48, 2, ITM_ABIL_ANIM_THRUST)); - addField(new Bitmap(buffer, offset + 50, 2, ITM_ABIL_IS_ARROW, s_noyes)); - addField(new Bitmap(buffer, offset + 52, 2, ITM_ABIL_IS_BOLT, s_noyes)); - addField(new Bitmap(buffer, offset + 54, 2, ITM_ABIL_IS_BULLET, s_noyes)); + addField(new Bitmap(buffer, offset + 50, 2, ITM_ABIL_IS_ARROW, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 52, 2, ITM_ABIL_IS_BOLT, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 54, 2, ITM_ABIL_IS_BULLET, OPTION_NOYES)); return offset + 56; } diff --git a/src/org/infinity/resource/itm/Viewer.java b/src/org/infinity/resource/itm/Viewer.java index 2a2b93e7b..d1d73bc2f 100644 --- a/src/org/infinity/resource/itm/Viewer.java +++ b/src/org/infinity/resource/itm/Viewer.java @@ -125,10 +125,7 @@ private static String getItemDialog(ItmResource itm) Table2da table = Table2daCache.get("itemdial.2da"); if (table != null && table.getColCount() > 2) { // getting item resref - String resref = itm.getResourceEntry().getResourceName(); - if (resref.lastIndexOf('.') > 0) { - resref = resref.substring(0, resref.lastIndexOf('.')); - } + String resref = itm.getResourceEntry().getResourceRef(); // fetching item dialog file, if available if (resref != null) { diff --git a/src/org/infinity/resource/itm/ViewerAbility.java b/src/org/infinity/resource/itm/ViewerAbility.java index d61280558..8b5f3b9c1 100644 --- a/src/org/infinity/resource/itm/ViewerAbility.java +++ b/src/org/infinity/resource/itm/ViewerAbility.java @@ -112,10 +112,7 @@ private static String getAbilityName(Ability ability) Table2da table = Table2daCache.get("tooltip.2da"); if (table != null) { // getting parent item resref - String resref = ability.getParent().getResourceEntry().getResourceName(); - if (resref.lastIndexOf('.') > 0) { - resref = resref.substring(0, resref.lastIndexOf('.')); - } + String resref = ability.getParent().getResourceEntry().getResourceRef(); // fetching tooltip label for ability, if available if (resref != null) { diff --git a/src/org/infinity/resource/key/BIFFResourceEntry.java b/src/org/infinity/resource/key/BIFFResourceEntry.java index dd322df2b..030710ff7 100644 --- a/src/org/infinity/resource/key/BIFFResourceEntry.java +++ b/src/org/infinity/resource/key/BIFFResourceEntry.java @@ -240,6 +240,12 @@ public String getResourceName() return resourceName + '.' + extension; } + @Override + public String getResourceRef() + { + return resourceName; + } + @Override public String getTreeFolderName() { diff --git a/src/org/infinity/resource/key/FileResourceEntry.java b/src/org/infinity/resource/key/FileResourceEntry.java index d43f73ec0..6e0ac03d5 100644 --- a/src/org/infinity/resource/key/FileResourceEntry.java +++ b/src/org/infinity/resource/key/FileResourceEntry.java @@ -103,6 +103,15 @@ public String getResourceName() return file.getFileName().toString(); } + public String getResourceRef() + { + String fileName = file.getFileName().toString(); + int pos = fileName.lastIndexOf('.'); + if (pos >= 0) + fileName = fileName.substring(0, pos); + return fileName; + } + @Override public String getTreeFolderName() { diff --git a/src/org/infinity/resource/key/ResourceEntry.java b/src/org/infinity/resource/key/ResourceEntry.java index 615a8ff01..6dc8f770c 100644 --- a/src/org/infinity/resource/key/ResourceEntry.java +++ b/src/org/infinity/resource/key/ResourceEntry.java @@ -254,7 +254,7 @@ public boolean isVisible() // 1. Options->Show Unknown Resource Types == true OR resource type is supported // 2. NOT Resource type part of skippedExtensions // 3. Filename length is valid - int resLen = getResourceName().lastIndexOf('.'); + int resLen = getResourceRef().length(); boolean bRet = (BrowserMenuBar.getInstance() != null && BrowserMenuBar.getInstance().showUnknownResourceTypes()) || Profile.isResourceTypeSupported(getExtension()) && !skippedExtensions.contains(getExtension().toUpperCase(Locale.ENGLISH)) && @@ -266,6 +266,7 @@ public boolean isVisible() public abstract long getResourceSize(boolean ignoreOverride); + /** Returns the type of the resource (extension without leading dot). */ public abstract String getExtension(); public abstract ByteBuffer getResourceBuffer(boolean ignoreOverride) throws Exception; @@ -274,8 +275,12 @@ public boolean isVisible() public abstract int[] getResourceInfo(boolean ignoreOverride) throws Exception; + /** Returns the full resource name (name dot extension) */ public abstract String getResourceName(); + /** Returns the resource name without extension. */ + public abstract String getResourceRef(); + /** Returns name of folder in the resource tree in which this entry appears. */ public abstract String getTreeFolderName(); diff --git a/src/org/infinity/resource/maze/MazeEntry.java b/src/org/infinity/resource/maze/MazeEntry.java index 9e4de105e..95269dda3 100644 --- a/src/org/infinity/resource/maze/MazeEntry.java +++ b/src/org/infinity/resource/maze/MazeEntry.java @@ -13,7 +13,6 @@ public class MazeEntry extends AbstractStruct { - private static final String[] s_noyes = {"No", "Yes"}; private static final String[] s_traps = {"TrapA", "TrapB", "TrapC"}; private static final String[] s_walls = {"None", "East", "West", "North", "South"}; @@ -25,13 +24,13 @@ public MazeEntry(AbstractStruct superStruct, ByteBuffer buffer, int offset, int @Override public int read(ByteBuffer buffer, int offset) throws Exception { - addField(new Bitmap(buffer, offset, 4, ModronMazeEntry.GAM_MAZE_ENTRY_USED, s_noyes)); - addField(new Bitmap(buffer, offset + 4, 4, ModronMazeEntry.GAM_MAZE_ENTRY_ACCESSIBLE, s_noyes)); - addField(new Bitmap(buffer, offset + 8, 4, ModronMazeEntry.GAM_MAZE_ENTRY_IS_VALID, s_noyes)); - addField(new Bitmap(buffer, offset + 12, 4, ModronMazeEntry.GAM_MAZE_ENTRY_IS_TRAPPED, s_noyes)); + addField(new Bitmap(buffer, offset, 4, ModronMazeEntry.GAM_MAZE_ENTRY_USED, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 4, 4, ModronMazeEntry.GAM_MAZE_ENTRY_ACCESSIBLE, OPTION_NOYES)); + addField(new Bitmap(buffer, offset + 8, 4, ModronMazeEntry.GAM_MAZE_ENTRY_IS_VALID, OPTION_NOYES)); + 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, s_traps)); addField(new Flag(buffer, offset + 20, 4, ModronMazeEntry.GAM_MAZE_ENTRY_EXITS, s_walls)); - addField(new Bitmap(buffer, offset + 24, 4, ModronMazeEntry.GAM_MAZE_ENTRY_POLULATED, s_noyes)); + addField(new Bitmap(buffer, offset + 24, 4, ModronMazeEntry.GAM_MAZE_ENTRY_POLULATED, OPTION_NOYES)); return offset + 28; } } diff --git a/src/org/infinity/resource/maze/MazeResource.java b/src/org/infinity/resource/maze/MazeResource.java index b10e371d6..480cc8b14 100644 --- a/src/org/infinity/resource/maze/MazeResource.java +++ b/src/org/infinity/resource/maze/MazeResource.java @@ -35,8 +35,6 @@ */ public class MazeResource extends AbstractStruct implements Resource, HasViewerTabs { - private static final String[] s_noyes = {"No", "Yes"}; - private StructHexViewer hexViewer; public MazeResource(ResourceEntry entry) throws Exception @@ -83,11 +81,11 @@ public int read(ByteBuffer buffer, int offset) throws Exception curOfs += 4; addField(new DecNumber(buffer, curOfs, 4, ModronMaze.GAM_MAZE_NUM_TRAPS)); curOfs += 4; - addField(new Bitmap(buffer, curOfs, 4, ModronMaze.GAM_MAZE_INITIALIZED, s_noyes)); + addField(new Bitmap(buffer, curOfs, 4, ModronMaze.GAM_MAZE_INITIALIZED, OPTION_NOYES)); curOfs += 4; - addField(new Bitmap(buffer, curOfs, 4, ModronMaze.GAM_MAZE_MAZE_BLOCKER_MADE, s_noyes)); + addField(new Bitmap(buffer, curOfs, 4, ModronMaze.GAM_MAZE_MAZE_BLOCKER_MADE, OPTION_NOYES)); curOfs += 4; - addField(new Bitmap(buffer, curOfs, 4, ModronMaze.GAM_MAZE_ENGINE_BLOCKER_MADE, s_noyes)); + addField(new Bitmap(buffer, curOfs, 4, ModronMaze.GAM_MAZE_ENGINE_BLOCKER_MADE, OPTION_NOYES)); curOfs += 4; return curOfs; diff --git a/src/org/infinity/resource/other/FntResource.java b/src/org/infinity/resource/other/FntResource.java index 226f3f250..df65d50e3 100644 --- a/src/org/infinity/resource/other/FntResource.java +++ b/src/org/infinity/resource/other/FntResource.java @@ -46,9 +46,7 @@ public void close() throws Exception @Override public int read(ByteBuffer buffer, int startoffset) throws Exception { - String resName = getResourceEntry().getResourceName(); - if (resName.lastIndexOf('.') > 0) - resName = resName.substring(0, resName.lastIndexOf('.')); + String resName = getResourceEntry().getResourceRef(); byte[] b = new byte[8]; System.arraycopy(resName.getBytes(), 0, b, 0, resName.length()); diff --git a/src/org/infinity/resource/other/VvcResource.java b/src/org/infinity/resource/other/VvcResource.java index 894d8e416..b0604f546 100644 --- a/src/org/infinity/resource/other/VvcResource.java +++ b/src/org/infinity/resource/other/VvcResource.java @@ -98,7 +98,6 @@ public final class VvcResource extends AbstractStruct implements Resource, HasVi "Purgeable", "Not covered by wall", "Mid-level brighten", "High-level brighten"}; public static final String[] s_face = {"Use current", "Face target", "Follow target", "Follow path", "Lock orientation"}; - public static final String[] s_noyes = {"No", "Yes"}; private StructHexViewer hexViewer; @@ -121,7 +120,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new Unknown(buffer, offset + 36, 4)); addField(new DecNumber(buffer, offset + 40, 4, VVC_POSITION_X)); addField(new DecNumber(buffer, offset + 44, 4, VVC_POSITION_Y)); - addField(new Bitmap(buffer, offset + 48, 4, VVC_DRAW_ORIENTED, s_noyes)); + addField(new Bitmap(buffer, offset + 48, 4, VVC_DRAW_ORIENTED, OPTION_NOYES)); addField(new DecNumber(buffer, offset + 52, 4, VVC_FRAME_RATE)); addField(new DecNumber(buffer, offset + 56, 4, VVC_NUM_ORIENTATIONS)); addField(new DecNumber(buffer, offset + 60, 4, VVC_PRIMARY_ORIENTATION)); @@ -136,7 +135,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception addField(new DecNumber(buffer, offset + 104, 4, VVC_FIRST_ANIMATION_INDEX)); addField(new DecNumber(buffer, offset + 108, 4, VVC_SECOND_ANIMATION_INDEX)); addField(new DecNumber(buffer, offset + 112, 4, VVC_CURRENT_ANIMATION_INDEX)); - addField(new Bitmap(buffer, offset + 116, 4, VVC_CONTINUOUS_PLAYBACK, s_noyes)); + addField(new Bitmap(buffer, offset + 116, 4, VVC_CONTINUOUS_PLAYBACK, OPTION_NOYES)); addField(new ResourceRef(buffer, offset + 120, VVC_SOUND_STARTING, "WAV")); addField(new ResourceRef(buffer, offset + 128, VVC_SOUND_DURATION, "WAV")); addField(new ResourceRef(buffer, offset + 136, VVC_ALPHA_MASK, "BAM")); diff --git a/src/org/infinity/resource/sav/SavResourceEntry.java b/src/org/infinity/resource/sav/SavResourceEntry.java index ebd5b62db..bfee957a3 100644 --- a/src/org/infinity/resource/sav/SavResourceEntry.java +++ b/src/org/infinity/resource/sav/SavResourceEntry.java @@ -78,6 +78,16 @@ public String getResourceName() return fileName; } + @Override + public String getResourceRef() + { + int pos = fileName.lastIndexOf('.'); + if (pos >= 0) + return fileName.substring(0, pos); + else + return fileName; + } + @Override public String getExtension() { diff --git a/src/org/infinity/resource/spl/Viewer.java b/src/org/infinity/resource/spl/Viewer.java index 3c7d2ac83..a213f7ad2 100644 --- a/src/org/infinity/resource/spl/Viewer.java +++ b/src/org/infinity/resource/spl/Viewer.java @@ -47,14 +47,12 @@ public final class Viewer extends JPanel public static String getSymbolicName(ResourceEntry entry, boolean formatted) { if (entry != null) { - String resName = entry.getResourceName().toUpperCase(Locale.ENGLISH); - int idx = resName.lastIndexOf('.'); - String ext = (idx >= 0) ? resName.substring(idx+1) : ""; - String name = (idx >= 0) ? resName.substring(0, idx) : resName; + String ext = entry.getExtension().toUpperCase(Locale.ENGLISH); + String name = entry.getResourceRef().toUpperCase(Locale.ENGLISH); if ("SPL".equals(ext) && name.length() >= 7) { // fetching spell type - String s = resName.substring(0, 4); + String s = name.substring(0, 4); int type = 0; if (SpellType.containsKey(s)) { type = SpellType.get(s).intValue(); diff --git a/src/org/infinity/resource/sto/ItemSale.java b/src/org/infinity/resource/sto/ItemSale.java index a7db3caab..c50426bfc 100644 --- a/src/org/infinity/resource/sto/ItemSale.java +++ b/src/org/infinity/resource/sto/ItemSale.java @@ -27,7 +27,6 @@ public final class ItemSale extends AbstractStruct implements AddRemovable public static final String[] s_itemflag = {"No flags set", "Identified", "Not stealable", "Stolen", "Undroppable"}; - public static final String[] s_noyes = { "No", "Yes" }; ItemSale() throws Exception { @@ -59,7 +58,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception } addField(new Flag(buffer, offset + 16, 4, STO_SALE_FLAGS, s_itemflag)); addField(new DecNumber(buffer, offset + 20, 4, STO_SALE_NUM_IN_STOCK)); - addField(new Bitmap(buffer, offset + 24, 4, STO_SALE_INFINITE_SUPPLY, s_noyes)); + addField(new Bitmap(buffer, offset + 24, 4, STO_SALE_INFINITE_SUPPLY, OPTION_NOYES)); return offset + 28; } } diff --git a/src/org/infinity/resource/sto/ItemSale11.java b/src/org/infinity/resource/sto/ItemSale11.java index c29c5a4a6..f4e7445e3 100644 --- a/src/org/infinity/resource/sto/ItemSale11.java +++ b/src/org/infinity/resource/sto/ItemSale11.java @@ -29,7 +29,6 @@ public final class ItemSale11 extends AbstractStruct implements AddRemovable public static final String STO_SALE_TRIGGER = "Sale trigger"; public static final String[] s_itemflag = {"No flags set", "Identified", "Not stealable", "Stolen"}; - public static final String[] s_noyes = { "No", "Yes" }; ItemSale11() throws Exception { @@ -61,7 +60,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception } addField(new Flag(buffer, offset + 16, 4, STO_SALE_FLAGS, s_itemflag)); addField(new DecNumber(buffer, offset + 20, 4, STO_SALE_NUM_IN_STOCK)); - addField(new Bitmap(buffer, offset + 24, 4, STO_SALE_INFINITE_SUPPLY, s_noyes)); + addField(new Bitmap(buffer, offset + 24, 4, STO_SALE_INFINITE_SUPPLY, OPTION_NOYES)); addField(new StringRef(buffer, offset + 28, STO_SALE_TRIGGER)); addField(new Unknown(buffer, offset + 32, 56)); return offset + 88; diff --git a/src/org/infinity/resource/text/QuestsPanel.java b/src/org/infinity/resource/text/QuestsPanel.java index 17f7a9f32..469789180 100644 --- a/src/org/infinity/resource/text/QuestsPanel.java +++ b/src/org/infinity/resource/text/QuestsPanel.java @@ -157,7 +157,7 @@ private class CompletedCellRenderer extends DefaultTableCellRenderer protected void setupCompleted(boolean isSelected) { if (!isSelected) { - setBackground(Color.cyan); + setBackground(Color.CYAN); } final Font font = getFont(); // Because of capture (symbol ?) in type signature raw type usage is required... @@ -189,7 +189,7 @@ public Component getTableCellRendererComponent(JTable table, Object value, boole final QuestsModel model = (QuestsModel)table.getModel(); final Quest quest = model.quests.get(row); switch (quest.evaluate(vars)) { - case Unassigned: setForeground(Color.gray); break; + case Unassigned: setForeground(Color.GRAY); break; case Completed: setupCompleted(isSelected); break; default: break; diff --git a/src/org/infinity/resource/vef/AbstractComponent.java b/src/org/infinity/resource/vef/AbstractComponent.java index 0ad579320..7bb599cbd 100644 --- a/src/org/infinity/resource/vef/AbstractComponent.java +++ b/src/org/infinity/resource/vef/AbstractComponent.java @@ -23,8 +23,6 @@ public class AbstractComponent extends AbstractStruct implements AddRemovable public static final String VEF_COMP_TICKS_LOOP = "Ticks until loop"; public static final String VEF_COMP_CONTINUOUS = "Continuous cycles?"; - public static final String[] s_noyes = {"No", "Yes"}; - protected AbstractComponent(String label) throws Exception { super(null, label, StreamUtils.getByteBuffer(224), 0); @@ -58,7 +56,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception offset = type.readAttributes(buffer, offset + 16, list); addFields(getFields().size() - 1, list); - addField(new Bitmap(buffer, offset, 4, VEF_COMP_CONTINUOUS, s_noyes)); + addField(new Bitmap(buffer, offset, 4, VEF_COMP_CONTINUOUS, OPTION_NOYES)); addField(new Unknown(buffer, offset + 4, 196)); offset += 200; return offset; diff --git a/src/org/infinity/resource/video/MveResource.java b/src/org/infinity/resource/video/MveResource.java index c1ff46927..98365b064 100644 --- a/src/org/infinity/resource/video/MveResource.java +++ b/src/org/infinity/resource/video/MveResource.java @@ -322,12 +322,7 @@ private static void exportAsAvi(ResourceEntry inEntry, Window parent) if (inEntry != null) { JFileChooser fc = new JFileChooser(Profile.getGameRoot().toFile()); fc.setDialogTitle("Export MVE as AVI"); - String name = inEntry.getResourceName(); - if (name.lastIndexOf('.') > 0) { - name = name.substring(0, name.lastIndexOf('.')) + ".avi"; - } else { - name = name + ".avi"; - } + String name = inEntry.getResourceRef() + ".avi"; fc.setSelectedFile(new File(fc.getCurrentDirectory(), name)); fc.setDialogType(JFileChooser.SAVE_DIALOG); fc.setFileSelectionMode(JFileChooser.FILES_ONLY); diff --git a/src/org/infinity/resource/video/WbmResource.java b/src/org/infinity/resource/video/WbmResource.java index c94412414..5361c20cd 100644 --- a/src/org/infinity/resource/video/WbmResource.java +++ b/src/org/infinity/resource/video/WbmResource.java @@ -167,18 +167,12 @@ private Path getVideoFile() retVal = ((FileResourceEntry)entry).getActualPath(); isTempFile = false; } else { - String fileName = entry.getResourceName(); - String fileBase, fileExt; - int p = fileName.lastIndexOf('.'); - if (p > 0) { - fileBase = fileName.substring(0, p); - fileExt = fileName.substring(p); - } else { - fileBase = fileName; - fileExt = ".wbm"; - } + String fileBase = entry.getResourceRef(); + String fileExt = entry.getExtension(); + if (fileExt.isEmpty()) + fileExt = "wbm"; try { - Path outFile = Files.createTempFile(fileBase + "-", fileExt); + Path outFile = Files.createTempFile(fileBase + "-", "." + fileExt); if (Files.isRegularFile(outFile)) { try (InputStream is = entry.getResourceDataAsStream()) { try (OutputStream os = StreamUtils.getOutputStream(outFile, true)) { diff --git a/src/org/infinity/resource/wed/Door.java b/src/org/infinity/resource/wed/Door.java index 81521df89..a55971388 100644 --- a/src/org/infinity/resource/wed/Door.java +++ b/src/org/infinity/resource/wed/Door.java @@ -33,8 +33,6 @@ public final class Door extends AbstractStruct implements AddRemovable, HasAddRe public static final String WED_DOOR_OFFSET_POLYGONS_CLOSED = "Polygons closed offset"; public static final String WED_DOOR_TILEMAP_INDEX = "Tilemap index"; - public static final String[] s_noyes = {"No", "Yes"}; - public Door() throws Exception { super(null, WED_DOOR, StreamUtils.getByteBuffer(26), 0); @@ -130,7 +128,7 @@ public void updatePolygonsOffset(int offset) public int read(ByteBuffer buffer, int offset) throws Exception { addField(new TextString(buffer, offset, 8, WED_DOOR_NAME)); - addField(new Bitmap(buffer, offset + 8, 2, WED_DOOR_IS_DOOR, s_noyes)); + addField(new Bitmap(buffer, offset + 8, 2, WED_DOOR_IS_DOOR, OPTION_NOYES)); DecNumber indexTileCell = new DecNumber(buffer, offset + 10, 2, WED_DOOR_TILEMAP_LOOKUP_INDEX); addField(indexTileCell); SectionCount countTileCell = new SectionCount(buffer, offset + 12, 2, WED_DOOR_NUM_TILEMAP_INDICES, diff --git a/src/org/infinity/resource/wed/IndexNumber.java b/src/org/infinity/resource/wed/IndexNumber.java new file mode 100644 index 000000000..3e71a81bd --- /dev/null +++ b/src/org/infinity/resource/wed/IndexNumber.java @@ -0,0 +1,26 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 - 2020 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.wed; + +import java.nio.ByteBuffer; + +import org.infinity.datatype.DecNumber; + +/** + * Subclassed from {@code DecNumber} to make field type identifiable by reflection. + */ +public class IndexNumber extends DecNumber +{ + public IndexNumber(ByteBuffer buffer, int offset, int length, String name) + { + super(buffer, offset, length, name); + } + + public IndexNumber(ByteBuffer buffer, int offset, int length, String name, boolean signed) + { + super(buffer, offset, length, name, signed); + } + +} diff --git a/src/org/infinity/resource/wed/Overlay.java b/src/org/infinity/resource/wed/Overlay.java index dbdc2b3fd..4da9f974e 100644 --- a/src/org/infinity/resource/wed/Overlay.java +++ b/src/org/infinity/resource/wed/Overlay.java @@ -63,7 +63,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception } final SectionOffset offset_tilemap = new SectionOffset(buffer, offset + 16, WED_OVERLAY_OFFSET_TILEMAP, Tilemap.class); addField(offset_tilemap); - final SectionOffset offset_tilelookup = new SectionOffset(buffer, offset + 20, WED_OVERLAY_OFFSET_TILEMAP_LOOKUP, DecNumber.class); + final SectionOffset offset_tilelookup = new SectionOffset(buffer, offset + 20, WED_OVERLAY_OFFSET_TILEMAP_LOOKUP, IndexNumber.class); addField(offset_tilelookup); int retoff = offset + 24; @@ -82,7 +82,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception // readLookuptable offset = offset_tilelookup.getValue(); for (int i = 0; i < lookuptablesize; i++) { - addField(new DecNumber(buffer, offset + i * 2, 2, WED_OVERLAY_TILEMAP_INDEX + " " + i)); + addField(new IndexNumber(buffer, offset + i * 2, 2, WED_OVERLAY_TILEMAP_INDEX + " " + i)); } return retoff; } diff --git a/src/org/infinity/resource/wed/WedResource.java b/src/org/infinity/resource/wed/WedResource.java index 915395fa3..5d10ef271 100644 --- a/src/org/infinity/resource/wed/WedResource.java +++ b/src/org/infinity/resource/wed/WedResource.java @@ -259,7 +259,7 @@ public int read(ByteBuffer buffer, int offset) throws Exception Wallgroup.class); addField(offsetWallgroups); SectionOffset offsetPolytable = new SectionOffset(buffer, offset + 16, WED_OFFSET_WALL_POLYGON_LOOKUP, - RemovableDecNumber.class); + IndexNumber.class); addField(offsetPolytable); HexNumber offsets[] = new HexNumber[]{offsetOverlays, offsetHeader2, offsetDoors, offsetDoortile, @@ -306,7 +306,7 @@ public int compare(HexNumber s1, HexNumber s2) offset = offsetPolytable.getValue(); for (int i = 0; i < countPolytable; i++) { - addField(new DecNumber(buffer, offset + i * 2, 2, WED_WALL_POLYGON_INDEX + " " + i)); + addField(new IndexNumber(buffer, offset + i * 2, 2, WED_WALL_POLYGON_INDEX + " " + i)); } int endoffset = offset; diff --git a/src/org/infinity/resource/wmp/AreaEntry.java b/src/org/infinity/resource/wmp/AreaEntry.java index 25f6874b2..5cdd2d189 100644 --- a/src/org/infinity/resource/wmp/AreaEntry.java +++ b/src/org/infinity/resource/wmp/AreaEntry.java @@ -19,7 +19,7 @@ import org.infinity.resource.AbstractStruct; import org.infinity.resource.HasViewerTabs; -final class AreaEntry extends AbstractStruct implements HasViewerTabs +final public class AreaEntry extends AbstractStruct implements HasViewerTabs { // WMP/AreaEntry-specific field labels public static final String WMP_AREA = "Area"; diff --git a/src/org/infinity/resource/wmp/MapEntry.java b/src/org/infinity/resource/wmp/MapEntry.java index 52f92a7dc..dc7db1dff 100644 --- a/src/org/infinity/resource/wmp/MapEntry.java +++ b/src/org/infinity/resource/wmp/MapEntry.java @@ -20,7 +20,7 @@ import org.infinity.resource.HasViewerTabs; import org.infinity.resource.Profile; -final class MapEntry extends AbstractStruct implements HasViewerTabs +final public class MapEntry extends AbstractStruct implements HasViewerTabs { // WMP/MapEntry-specific field labels public static final String WMP_MAP = "Map"; diff --git a/src/org/infinity/search/AbstractReferenceSearcher.java b/src/org/infinity/search/AbstractReferenceSearcher.java index edfc1a20c..9d4bed6ff 100644 --- a/src/org/infinity/search/AbstractReferenceSearcher.java +++ b/src/org/infinity/search/AbstractReferenceSearcher.java @@ -139,15 +139,27 @@ protected Runnable newWorker(ResourceEntry entry) /** * Registers match hit. * - * @param entry Pointer to resource in which match found - * @param name Localized name of the matched resource - * @param ref Field in the matched resource that contains founded object + * @param entry Resource in which match is found. + * @param name Localized name of the matched resource. + * @param ref Field in the matched resource that contains found object. */ synchronized void addHit(ResourceEntry entry, String name, StructEntry ref) { hitFrame.addHit(entry, name, ref); } + /** + * Registers textual match hit. + * + * @param entry Resource in which match is found. + * @param line Text content of line where match is found. + * @param lineNr Line number of the match. + */ + synchronized void addHit(ResourceEntry entry, String line, int lineNr) + { + hitFrame.addHit(entry, line, lineNr); + } + /** * Performs actual search necessary information in the resource. Search procedure * must register their results by calling method {@link #addHit}. diff --git a/src/org/infinity/search/ReferenceHitFrame.java b/src/org/infinity/search/ReferenceHitFrame.java index 92c73a99c..e8957e501 100644 --- a/src/org/infinity/search/ReferenceHitFrame.java +++ b/src/org/infinity/search/ReferenceHitFrame.java @@ -35,6 +35,7 @@ import org.infinity.resource.Resource; import org.infinity.resource.ResourceFactory; import org.infinity.resource.StructEntry; +import org.infinity.resource.TextResource; import org.infinity.resource.Viewable; import org.infinity.resource.dlg.DlgResource; import org.infinity.resource.key.FileResourceEntry; @@ -64,7 +65,7 @@ public ReferenceHitFrame(Object query, Component parent) this.parent = parent; setIconImage(Icons.getIcon(Icons.ICON_HISTORY_16).getImage()); - table = new SortableTable(new String[]{"File", "Name", "Attribute"}, + table = new SortableTable(new String[]{"File", "Name/Text", "Attribute/Line"}, new Class[]{ResourceEntry.class, String.class, String.class}, new Integer[]{100, 100, 300}); @@ -154,19 +155,20 @@ else if (event.getSource() == bsave) { } private void showEntryInViewer(int row, Viewable viewable) { + ReferenceHit hit = (ReferenceHit)table.getTableItemAt(row); if (viewable instanceof DlgResource) { DlgResource dlgRes = (DlgResource) viewable; JComponent detailViewer = dlgRes.getViewerTab(0); JTabbedPane parent = (JTabbedPane) detailViewer.getParent(); - dlgRes.selectInEdit( - ((ReferenceHit)table.getTableItemAt(row)).getStructEntry()); + dlgRes.selectInEdit(hit.getStructEntry()); // make sure we see the detail viewer parent.getModel().setSelectedIndex(parent.indexOfComponent(detailViewer)); - } else if (viewable instanceof AbstractStruct) { - ((AbstractStruct)viewable).getViewer().selectEntry( - ((ReferenceHit)table.getTableItemAt(row)).getStructEntry().getOffset()); + ((AbstractStruct)viewable).getViewer().selectEntry(hit.getStructEntry().getOffset()); + } + else if (viewable instanceof TextResource) { + ((TextResource)viewable).highlightText(hit.getLineNr(), hit.getLine()); } } @@ -200,20 +202,46 @@ public void addHit(ResourceEntry entry, String name, StructEntry ref) table.addTableItem(new ReferenceHit(entry, name, ref)); } + public void addHit(ResourceEntry entry, String line, int lineNr) + { + table.addTableItem(new ReferenceHit(entry, line, lineNr)); + } + // -------------------------- INNER CLASSES -------------------------- /** Stores a reference to a specific resource field. */ public static final class ReferenceHit implements TableItem, Comparable { + public enum Mode { + Struct, + Text, + } + + private final Mode mode; private final ResourceEntry entry; private final String name; private final StructEntry ref; + private final String line; + private final int lineNr; public ReferenceHit(ResourceEntry entry, String name, StructEntry ref) { + this.mode = Mode.Struct; this.entry = entry; this.name = name; this.ref = ref; + this.line = ""; + this.lineNr = 0; + } + + public ReferenceHit(ResourceEntry entry, String line, int lineNr) + { + this.mode = Mode.Text; + this.entry = entry; + this.name = ""; + this.ref = null; + this.line = line; + this.lineNr = lineNr; } @Override @@ -223,24 +251,33 @@ public Object getObjectAt(int columnIndex) case 0: return entry; case 1: - if (name != null) { - return name; + if (mode == Mode.Text) { + return line; } else { - if (entry instanceof FileResourceEntry) { + if (name != null) { + return name; + } else if (entry instanceof FileResourceEntry) { return entry.getActualPath().getParent().toString(); } else { return ""; } } default: - if (ref != null) { - return ref.getName() + '=' + ref; + if (mode == Mode.Text) { + return lineNr; } else { + if (ref != null) + return ref.getName() + '=' + ref; return null; } } } + public Mode getMode() + { + return mode; + } + public ResourceEntry getResource() { return entry; @@ -256,16 +293,30 @@ public StructEntry getStructEntry() return ref; } + public String getLine() + { + return line; + } + + public int getLineNr() + { + return lineNr; + } + @Override public String toString() { - final StringBuilder buf = new StringBuilder("File: "); - buf.append(entry.getResourceName()); - if (name != null) - buf.append(", Name: ").append(name); - if (ref != null) - buf.append(", Attribute: ").append(ref.getName()).append('=').append(ref); - return buf.toString(); + if (mode == Mode.Text) { + return String.format("File: %s, Line: %d, Text: %s", entry.getResourceName(), lineNr, line); + } else { + final StringBuilder buf = new StringBuilder("File: "); + buf.append(entry.getResourceName()); + if (name != null) + buf.append(", Name: ").append(name); + if (ref != null) + buf.append(", Attribute: ").append(ref.getName()).append('=').append(ref); + return buf.toString(); + } } @Override diff --git a/src/org/infinity/search/ReferenceSearcher.java b/src/org/infinity/search/ReferenceSearcher.java index 391fbaa79..bcfc3026b 100644 --- a/src/org/infinity/search/ReferenceSearcher.java +++ b/src/org/infinity/search/ReferenceSearcher.java @@ -5,6 +5,8 @@ package org.infinity.search; import java.awt.Component; +import java.io.BufferedReader; +import java.io.StringReader; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; @@ -98,34 +100,34 @@ else if (o instanceof AbstractCode) { try { final ScriptType type = sourceCode instanceof Action ? ScriptType.ACTION : ScriptType.TRIGGER; final Compiler compiler = new Compiler(sourceCode.getText(), type); - final String code = compiler.getCode(); + String code = compiler.getCode(); if (compiler.getErrors().isEmpty()) { final Decompiler decompiler = new Decompiler(code, type, true); decompiler.setGenerateComments(false); decompiler.setGenerateResourcesUsed(true); - decompiler.decompile(); + code = decompiler.decompile(); - boolean hit = false; - for (final ResourceEntry resourceUsed : decompiler.getResourcesUsed()) { - final String resName = resourceUsed.getResourceName(); - if (targetName.equalsIgnoreCase(resName)) { - hit = true; - addHit(entry, entry.getSearchString(), sourceCode); - } else if (targetEntry == resourceUsed) { - // searching for symbolic spell names - String s = org.infinity.resource.spl.Viewer.getSymbolicName(targetEntry, false); - if (s != null && s.equalsIgnoreCase(resName)) { - addHit(entry, s, sourceCode); - } - } - } - if (!hit && creDeathVar != null) { - Pattern p = Pattern.compile("\\b" + creDeathVar + "\\b", Pattern.CASE_INSENSITIVE); - Matcher m = p.matcher(code); - if (m.find()) { - addHit(entry, creDeathVar, sourceCode); - } + // resref match + Pattern regName = Pattern.compile("\"" + Pattern.quote(targetEntry.getResourceRef()) + "\"", Pattern.CASE_INSENSITIVE); + // script name match + Pattern regVar = null; + if (creDeathVar != null && !creDeathVar.equalsIgnoreCase(targetEntry.getResourceRef())) + regVar = Pattern.compile("\"" + Pattern.quote(creDeathVar) + "\"", Pattern.CASE_INSENSITIVE); + // symbolic spell name match + String symbol = null; + Pattern regSymbol = null; + if (targetEntry.getExtension().equalsIgnoreCase("SPL")) { + symbol = org.infinity.resource.spl.Viewer.getSymbolicName(targetEntry, false); + if (symbol != null && !symbol.isEmpty()) + regSymbol = Pattern.compile("\\b" + Pattern.quote(symbol) + "\\b"); } + + if (decompiler.getResourcesUsed().contains(targetEntry) && regName.matcher(code).find()) + addHit(entry, entry.getSearchString(), sourceCode); + else if (regVar != null && regVar.matcher(code).find()) + addHit(entry, creDeathVar, sourceCode); + else if (regSymbol != null && regSymbol.matcher(code).find()) + addHit(entry, symbol, sourceCode); } } catch (Exception e) { System.out.println("Exception in " + dialog.getName() + " - " + sourceCode.getName()); @@ -160,21 +162,35 @@ private void searchSave(ResourceEntry entry, SavResource savfile) private void searchScript(ResourceEntry entry, BcsResource bcsfile) { - boolean hit = false; Decompiler decompiler = new Decompiler(bcsfile.getCode(), true); decompiler.setGenerateComments(false); decompiler.setGenerateResourcesUsed(true); try { String code = decompiler.decompile(); - if (decompiler.getResourcesUsed().contains(targetEntry)) { - hit = true; - addHit(entry, entry.getSearchString(), null); - } - if (!hit && creDeathVar != null) { - Pattern p = Pattern.compile("\\b" + creDeathVar + "\\b", Pattern.CASE_INSENSITIVE); - Matcher m = p.matcher(code); - if (m.find()) { - addHit(entry, creDeathVar, null); + boolean resourceExists = decompiler.getResourcesUsed().contains(targetEntry); + try (final BufferedReader br = new BufferedReader(new StringReader(code))) { + // resref match + Pattern regName = Pattern.compile("\"" + Pattern.quote(targetEntry.getResourceRef()) + "\"", Pattern.CASE_INSENSITIVE); + // script name match + Pattern regVar = null; + if (creDeathVar != null && !creDeathVar.equalsIgnoreCase(targetEntry.getResourceRef())) + regVar = Pattern.compile("\"" + Pattern.quote(creDeathVar) + "\"", Pattern.CASE_INSENSITIVE); + // symbolic spell name match + Pattern regSymbol = null; + if (targetEntry.getExtension().equalsIgnoreCase("SPL")) { + String symbol = org.infinity.resource.spl.Viewer.getSymbolicName(targetEntry, false); + if (symbol != null && !symbol.isEmpty()) + regSymbol = Pattern.compile("\\b" + Pattern.quote(symbol) + "\\b"); + } + + String line; + int lineNr = 0; + while ((line = br.readLine()) != null) { + lineNr++; + if (resourceExists && regName.matcher(line).find() || + regVar != null && regVar.matcher(line).find() || + regSymbol != null && regSymbol.matcher(line).find()) + addHit(entry, line.trim(), lineNr); } } } catch (Exception e) { @@ -288,22 +304,24 @@ private void searchTis(ResourceEntry entry, TisResource tis) private void searchText(ResourceEntry entry, PlainTextResource text) { - String name = getTargetEntry().getResourceName(); - int idx = name.lastIndexOf('.'); - if (idx > 0) { - name = name.substring(0, idx); - } - Pattern p = Pattern.compile("\\b(AP_|GA_)?" + name + "\\b", Pattern.CASE_INSENSITIVE); - Matcher m = p.matcher(text.getText()); - if (m.find()) { - addHit(entry, entry.getSearchString(), null); - } - if (creDeathVar != null && !creDeathVar.equalsIgnoreCase(name)) { - p = Pattern.compile("\\b" + creDeathVar + "\\b", Pattern.CASE_INSENSITIVE); - m = p.matcher(text.getText()); - if (m.find()) { - addHit(entry, creDeathVar, null); + String name = getTargetEntry().getResourceRef(); + Pattern regName = Pattern.compile("\\b(AP_|GA_)?" + name + "\\b", Pattern.CASE_INSENSITIVE); + Pattern regVar = null; + if (creDeathVar != null && !creDeathVar.equalsIgnoreCase(name)) + regVar = Pattern.compile("\\b" + creDeathVar + "\\b", Pattern.CASE_INSENSITIVE); + + try (final BufferedReader br = new BufferedReader(new StringReader(text.getText()))) { + String line; + int lineNr = 0; + while ((line = br.readLine()) != null) { + lineNr++; + if (regName.matcher(line).find() || + regVar != null && regVar.matcher(line).find()) { + addHit(entry, line.trim(), lineNr); + } } + } catch (Exception e) { + e.printStackTrace(); } } } diff --git a/src/org/infinity/search/ScriptReferenceSearcher.java b/src/org/infinity/search/ScriptReferenceSearcher.java index f93210fbc..570e8366c 100644 --- a/src/org/infinity/search/ScriptReferenceSearcher.java +++ b/src/org/infinity/search/ScriptReferenceSearcher.java @@ -79,11 +79,7 @@ else if (o instanceof AbstractCode) { private void searchText(ResourceEntry entry, String text) { - String name = targetEntry.getResourceName(); - int idx = name.lastIndexOf('.');//TODO: add special method to get name without extension - if (idx > 0) { - name = name.substring(0, idx); - } + String name = targetEntry.getResourceRef(); final Pattern p = Pattern.compile("\\b" + name + "\\b", Pattern.CASE_INSENSITIVE); final Matcher m = p.matcher(text); if (m.find()) { @@ -93,11 +89,7 @@ private void searchText(ResourceEntry entry, String text) private void searchScript(ResourceEntry entry, String script, StructEntry ref) { - String name = targetEntry.getResourceName(); - int idx = name.lastIndexOf('.');//TODO: add special method to get name without extension - if (idx > 0) { - name = name.substring(0, idx); - } + String name = targetEntry.getResourceRef(); final Pattern p = Pattern.compile("\"" + name + "\"", Pattern.CASE_INSENSITIVE); final Matcher m = p.matcher(script); if (m.find()) { diff --git a/src/org/infinity/search/WavReferenceSearcher.java b/src/org/infinity/search/WavReferenceSearcher.java index e5cc7af0b..1d1b1f1d6 100644 --- a/src/org/infinity/search/WavReferenceSearcher.java +++ b/src/org/infinity/search/WavReferenceSearcher.java @@ -54,15 +54,11 @@ else if (o instanceof AbstractStruct) { private void searchText(ResourceEntry entry, PlainTextResource text) { - String name = getTargetEntry().getResourceName(); - int idx = name.lastIndexOf('.'); - if (idx > 0) { - String nameBase = name.substring(0, idx); - Pattern p = Pattern.compile("\\b" + nameBase + "\\b", Pattern.CASE_INSENSITIVE); - Matcher m = p.matcher(text.getText()); - if (m.find()) { - addHit(entry, null, null); - } + String nameBase = getTargetEntry().getResourceRef(); + Pattern p = Pattern.compile("\\b" + nameBase + "\\b", Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(text.getText()); + if (m.find()) { + addHit(entry, null, null); } } } diff --git a/src/org/infinity/search/advanced/AdvancedSearch.java b/src/org/infinity/search/advanced/AdvancedSearch.java index d8bdce23f..b8472ecc5 100644 --- a/src/org/infinity/search/advanced/AdvancedSearch.java +++ b/src/org/infinity/search/advanced/AdvancedSearch.java @@ -170,6 +170,28 @@ public Void doInBackground() chooser.setFileFilter(new FileNameExtensionFilter("XML Configuration (*.xml)", "xml")); } + /** Returns the currently selected resource type. */ + public String getResourceType() + { + return cbResourceTypes.getSelectedItem().toString(); + } + + /** Sets the specified resource type if available. Returns success state. */ + public boolean setResourceType(String type) + { + if (type == null) + return false; + + type = type.toUpperCase(); + for (int i = 0, cnt = cbResourceTypes.getModel().getSize(); i < cnt; i++) { + if (cbResourceTypes.getModel().getElementAt(i).equals(type)) { + cbResourceTypes.setSelectedIndex(i); + return true; + } + } + return false; + } + /** * Adds the specified SearchOptions instance to the filter list. * If the specified filter already exists it will be updated instead. @@ -189,6 +211,48 @@ public void addFilter(SearchOptions filter) } } + /** Returns the number of defined filters. */ + public int getFilterCount() + { + return filterList.getModel().getSize(); + } + + /** Returns the filter instance at the specified index. */ + public SearchOptions getFilter(int index) + { + if (index >= 0 && index < filterList.getModel().getSize()) + return filterList.getModel().getElementAt(index); + return null; + } + + /** + * Removes the filter at the specified index from the filter list. + * Optionally selects the next available entry in the list. + */ + public boolean removeFilter(int idx, boolean autoSelect) + { + SimpleListModel model = (SimpleListModel)filterList.getModel(); + if (idx >= 0 && idx < model.getSize()) { + model.remove(idx); + if (autoSelect) { + if (idx >= model.getSize()) + idx--; + if (idx >= 0) + filterList.setSelectedIndex(idx); + } + return true; + } + + return false; + } + + /** Removes all filters from the filter list. */ + private void removeAllFilters() + { + SimpleListModel model = (SimpleListModel)filterList.getModel(); + model.clear(); + } + private void init() throws Exception { setIconImage(Icons.getIcon(Icons.ICON_FIND_16).getImage()); @@ -557,31 +621,6 @@ private void setFilterMode(FilterMode mode) } } - /** Removes the filter at the specified index from the filter list. Optionally selects the next available entry in the list. */ - private boolean removeFilter(int idx, boolean autoSelect) - { - SimpleListModel model = (SimpleListModel)filterList.getModel(); - if (idx >= 0 && idx < model.getSize()) { - model.remove(idx); - if (autoSelect) { - if (idx >= model.getSize()) - idx--; - if (idx >= 0) - filterList.setSelectedIndex(idx); - } - return true; - } - - return false; - } - - /** Removes all filters from the filter list. */ - private void removeAllFilters() - { - SimpleListModel model = (SimpleListModel)filterList.getModel(); - model.clear(); - } - /** Imports a new search configuration from the specified xml file. */ private boolean importConfig(File xmlFile) { diff --git a/src/org/infinity/search/advanced/AdvancedSearchWorker.java b/src/org/infinity/search/advanced/AdvancedSearchWorker.java index e876aa4e2..b51e104a7 100644 --- a/src/org/infinity/search/advanced/AdvancedSearchWorker.java +++ b/src/org/infinity/search/advanced/AdvancedSearchWorker.java @@ -6,8 +6,11 @@ import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; import javax.swing.JProgressBar; @@ -58,28 +61,49 @@ public void run() Resource res = ResourceFactory.getResource(entry); if (res instanceof AbstractStruct) { AbstractStruct structRoot = (AbstractStruct)res; + // storage for evaluated matches List entryMatches = new ArrayList<>(); - Map>> entryStructures = new HashMap<>(); + // stores number of grouped filters applied per structure level + Map, Integer> groupFilters = new HashMap<>(); + // storage for potential grouped matches + Map, Set> groupCache = new HashMap<>(); int matches = 0; for (int filterIdx = 0; filterIdx < searchOptions.size(); filterIdx++) { SearchOptions so = searchOptions.get(filterIdx); + // keep track of grouped filter count per structure + if (so.isStructureGroup()) { + Integer count = groupFilters.get(so.getStructure()); + if (count == null) + count = Integer.valueOf(0); + groupFilters.put(so.getStructure(), Integer.valueOf(count.intValue() + 1)); + } + // list of structures to search + boolean isMatch = false; List structs = collectStructures(structRoot, so, 0); for (AbstractStruct struct : structs) { - if (findMatches(entryMatches, struct, so) && - isGroupedMatch(entryStructures, struct, so)) { - matches++; - break; - } + isMatch |= findMatches(entryMatches, groupCache, struct, so); + } + if (isMatch) + matches++; + } + + // evaluating grouped matches + collapseGroupFilters(groupCache, groupFilters); + for (Set set : groupCache.values()) { + // all StructEntry instances found in the map are considered valid matches + for (StructEntry ref : set) { + entryMatches.add(new ReferenceHitFrame.ReferenceHit(entry, entry.getSearchString(), ref)); + matches++; } } // evaluating filter mode switch (filterOp) { case MatchAll: - if (matches == searchOptions.size()) + if (matches >= searchOptions.size()) matched.addAll(entryMatches); break; case MatchAny: @@ -150,37 +174,65 @@ private List collectStructures(AbstractStruct struct, SearchOpti return list; } - // Return if a group match exists - private boolean isGroupedMatch(Map>> mapCache, AbstractStruct struct, SearchOptions so) + // Remove all incomplete group matches from the map + private void collapseGroupFilters(Map, Set> groupCache, Map, Integer> groupFilters) { - boolean retVal = true; - if (so.isStructureGroup()) { - // getting current struct chain - List> curPath = new ArrayList<>(); - for (AbstractStruct as = struct; as != null; as = as.getParent()) { - curPath.add(0, (Class)as.getClass()); - } + Iterator, Set>> iter = groupCache.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry, Set> entry = iter.next(); + Integer count = groupFilters.get(entry.getKey()); + if (count != null) { + // grouping entries of the current structure level in a temporary map + Set groupSet = entry.getValue(); + Map> structureMap = new HashMap<>(); + for (StructEntry se : groupSet) { + Set structureSet = structureMap.computeIfAbsent(se.getParent(), e -> new HashSet()); + structureSet.add(se); + } - // getting mapped struct chain - for (AbstractStruct as : mapCache.keySet()) { - if (as.getClass().equals(struct.getClass())) { - List> existingPath = mapCache.get(as); - // checking group - if (curPath.equals(existingPath) && !struct.equals(as)) { - retVal = false; - break; + // removing entries of incomplete group matches + for (AbstractStruct as : structureMap.keySet()) { + Set structureSet = structureMap.get(as); + if (structureSet != null) { + switch (filterOp) { + case MatchAll: + if (structureSet.size() < count) + groupSet.removeAll(structureSet); + break; + case MatchAny: + if (structureSet.size() == 0) + groupSet.removeAll(structureSet); + break; + case MatchOne: + if (structureSet.size() != 1) + groupSet.removeAll(structureSet); + break; + } } } - } - // adding current struct chain to map - mapCache.put(struct, curPath); + // structure level entry can be removed if it contains no matches + if (groupSet.size() == 0) { + iter.remove(); + } + } else { + System.err.println("Skipping unidentified group match"); + iter.remove(); + } } - return retVal; + } + + // Collect filters grouped by structure + private void addGroupFilter(Map, Set> groupCache, StructEntry se, SearchOptions so) + { + Set set = groupCache.computeIfAbsent(so.getStructure(), s -> new HashSet()); + set.add(se); } // Search for matching fields in specified structure - private boolean findMatches(List matchList, AbstractStruct struct, SearchOptions so) + private boolean findMatches(List matchList, + Map, Set> groupCache, + AbstractStruct struct, SearchOptions so) { if (struct != null && so != null) { if (so.getSearchType() == SearchOptions.FieldMode.ByName) { @@ -195,7 +247,7 @@ private boolean findMatches(List matchList, Abst boolean result = false; for (final StructEntry se : struct.getFields()) { if (pattern.matcher(se.getName()).find()) { - result |= isMatch(matchList, se, so); + result |= isMatch(matchList, groupCache, se, so); } } return result; @@ -205,14 +257,15 @@ private boolean findMatches(List matchList, Abst if (so.getSearchType() == SearchOptions.FieldMode.ByRelativeOffset) { offset += struct.getOffset(); } - return isMatch(matchList, struct.getAttribute(offset), so); + return isMatch(matchList, groupCache, struct.getAttribute(offset), so); } } return false; } // Match value against search options - private boolean isMatch(List matchList, StructEntry se, SearchOptions so) + private boolean isMatch(List matchList, Map, Set> groupCache, + StructEntry se, SearchOptions so) { boolean retVal = false; if (se != null && so != null) { @@ -232,10 +285,18 @@ private boolean isMatch(List matchList, StructEn } if (so.isInvertMatch()) - retVal = !retVal; + retVal = !retVal; - if (retVal) - matchList.add(new ReferenceHitFrame.ReferenceHit(entry, entry.getSearchString(), se)); + if (retVal) { + if (so.isStructureGroup()) { + // grouped matches are evaluated later + addGroupFilter(groupCache, se, so); + retVal = false; + } else { + // add ungrouped matches directly to the results list + matchList.add(new ReferenceHitFrame.ReferenceHit(entry, entry.getSearchString(), se)); + } + } } return retVal; } diff --git a/src/org/infinity/search/advanced/FilterInput.java b/src/org/infinity/search/advanced/FilterInput.java index 821d21a89..c107fdb17 100644 --- a/src/org/infinity/search/advanced/FilterInput.java +++ b/src/org/infinity/search/advanced/FilterInput.java @@ -559,6 +559,7 @@ private void importOptions(SearchOptions so) cbFieldType.setSelectedIndex(idx); tfFieldName.setText(so.getSearchName()); + cbFieldNameCase.setSelected(so.isSearchNameCaseSensitive()); cbFieldNameRegex.setSelected(so.isSearchNameRegex()); ftfFieldOffsetInput.setValue(Long.toString(so.getSearchOffset(), 16) + "h"); } @@ -577,6 +578,7 @@ private void importOptions(SearchOptions so) cbValueType.setSelectedIndex(idx); tfValueStringInput.setText(so.getValueText()); + cbValueStringCase.setSelected(so.isValueTextCaseSensitive()); cbValueStringRegex.setSelected(so.isValueTextRegex()); ftfValueInputMin.setValue(so.getValueNumberMin()); ftfValueInputMax.setValue(so.getValueNumberMax());