From 7f6e1162bf3a7f9723d24d9c7a5f89e9e6985d66 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:50:34 +0100 Subject: [PATCH] Restructure WMP viewer and add XNEWAREA.2DA support for BG2:SoA worldmap --- src/org/infinity/gui/ViewerUtil.java | 5 + .../resource/key/BufferedResourceEntry.java | 10 +- .../resource/text/PlainTextResource.java | 4 +- src/org/infinity/resource/wmp/AreaEntry.java | 4 +- src/org/infinity/resource/wmp/AreaLink.java | 6 +- .../infinity/resource/wmp/AreaLinkEast.java | 6 +- .../infinity/resource/wmp/AreaLinkNorth.java | 6 +- .../infinity/resource/wmp/AreaLinkSouth.java | 6 +- .../infinity/resource/wmp/AreaLinkWest.java | 6 +- src/org/infinity/resource/wmp/MapEntry.java | 13 +- src/org/infinity/resource/wmp/ViewerArea.java | 4 +- .../infinity/resource/wmp/WmpResource.java | 2 +- .../wmp/viewer/AreaListCellRenderer.java | 122 +++ .../resource/wmp/{ => viewer}/ViewerMap.java | 712 +++++++++--------- .../resource/wmp/viewer/VirtualAreaEntry.java | 354 +++++++++ .../resource/wmp/viewer/VirtualMapEntry.java | 115 +++ .../wmp/viewer/VirtualStructEntry.java | 138 ++++ .../resource/wmp/viewer/WmpAreaInfo.java | 272 +++++++ .../resource/wmp/viewer/WmpLinkInfo.java | 165 ++++ .../resource/wmp/viewer/WmpMapInfo.java | 228 ++++++ 20 files changed, 1803 insertions(+), 375 deletions(-) create mode 100644 src/org/infinity/resource/wmp/viewer/AreaListCellRenderer.java rename src/org/infinity/resource/wmp/{ => viewer}/ViewerMap.java (50%) create mode 100644 src/org/infinity/resource/wmp/viewer/VirtualAreaEntry.java create mode 100644 src/org/infinity/resource/wmp/viewer/VirtualMapEntry.java create mode 100644 src/org/infinity/resource/wmp/viewer/VirtualStructEntry.java create mode 100644 src/org/infinity/resource/wmp/viewer/WmpAreaInfo.java create mode 100644 src/org/infinity/resource/wmp/viewer/WmpLinkInfo.java create mode 100644 src/org/infinity/resource/wmp/viewer/WmpMapInfo.java diff --git a/src/org/infinity/gui/ViewerUtil.java b/src/org/infinity/gui/ViewerUtil.java index 269eee85d..4845ec90f 100644 --- a/src/org/infinity/gui/ViewerUtil.java +++ b/src/org/infinity/gui/ViewerUtil.java @@ -668,6 +668,11 @@ public JList getList() { return list; } + /** Provides access to the model of the list component. */ + public SimpleListModel getListModel() { + return listModel; + } + @Override public void actionPerformed(ActionEvent event) { new ViewFrame(getTopLevelAncestor(), (Viewable) list.getSelectedValue()); diff --git a/src/org/infinity/resource/key/BufferedResourceEntry.java b/src/org/infinity/resource/key/BufferedResourceEntry.java index 3272d4b11..3e59d4fec 100644 --- a/src/org/infinity/resource/key/BufferedResourceEntry.java +++ b/src/org/infinity/resource/key/BufferedResourceEntry.java @@ -48,7 +48,15 @@ public String getExtension() { @Override public ByteBuffer getResourceBuffer(boolean ignoreOverride) throws Exception { - return buffer; + // returns a deep copy + final ByteBuffer retVal = + buffer.isDirect() ? ByteBuffer.allocateDirect(buffer.capacity()) : ByteBuffer.allocate(buffer.capacity()); + retVal.order(buffer.order()); + final ByteBuffer bbRead = buffer.asReadOnlyBuffer(); + bbRead.position(0); + retVal.put(bbRead); + retVal.flip(); + return retVal; } @Override diff --git a/src/org/infinity/resource/text/PlainTextResource.java b/src/org/infinity/resource/text/PlainTextResource.java index 03abecac1..8e9a27ab8 100644 --- a/src/org/infinity/resource/text/PlainTextResource.java +++ b/src/org/infinity/resource/text/PlainTextResource.java @@ -63,8 +63,7 @@ public class PlainTextResource protected final String text; - private final ButtonPanel buttonPanel = new ButtonPanel(); - + private ButtonPanel buttonPanel; private JMenuItem iFindAll; private JMenuItem iFindThis; private JMenuItem miFormatTrim; @@ -528,6 +527,7 @@ public JComponent makeViewer(ViewableContainer container) { editor.setWrapStyleWord(true); } + buttonPanel = new ButtonPanel(); iFindAll = new JMenuItem("in all " + ext + " files"); iFindThis = new JMenuItem("in this file only"); ButtonPopupMenu bpmFind = (ButtonPopupMenu) buttonPanel.addControl(ButtonPanel.Control.FIND_MENU); diff --git a/src/org/infinity/resource/wmp/AreaEntry.java b/src/org/infinity/resource/wmp/AreaEntry.java index ae9d6cd6e..d8a7b0190 100644 --- a/src/org/infinity/resource/wmp/AreaEntry.java +++ b/src/org/infinity/resource/wmp/AreaEntry.java @@ -20,7 +20,7 @@ import org.infinity.resource.AbstractStruct; import org.infinity.resource.HasViewerTabs; -final public class AreaEntry extends AbstractStruct implements HasViewerTabs { +public class AreaEntry extends AbstractStruct implements HasViewerTabs { // WMP/AreaEntry-specific field labels public static final String WMP_AREA = "Area"; public static final String WMP_AREA_CURRENT = "Current area"; @@ -45,7 +45,7 @@ final public class AreaEntry extends AbstractStruct implements HasViewerTabs { private static final String[] FLAGS_ARRAY = { "No flags set", "Visible", "Reveal from linked area", "Can be visited", "Has been visited" }; - AreaEntry(AbstractStruct superStruct, ByteBuffer buffer, int offset, int nr) throws Exception { + public AreaEntry(AbstractStruct superStruct, ByteBuffer buffer, int offset, int nr) throws Exception { super(superStruct, WMP_AREA + " " + nr, buffer, offset); } diff --git a/src/org/infinity/resource/wmp/AreaLink.java b/src/org/infinity/resource/wmp/AreaLink.java index f972f2e20..f569076d2 100644 --- a/src/org/infinity/resource/wmp/AreaLink.java +++ b/src/org/infinity/resource/wmp/AreaLink.java @@ -14,7 +14,7 @@ import org.infinity.resource.AbstractStruct; import org.infinity.util.io.StreamUtils; -abstract class AreaLink extends AbstractStruct { +public abstract class AreaLink extends AbstractStruct { // WMP/AreaLink-specific field labels public static final String WMP_LINK_TARGET_AREA = "Target area"; public static final String WMP_LINK_TARGET_ENTRANCE = "Target entrance"; @@ -25,11 +25,11 @@ abstract class AreaLink extends AbstractStruct { public static final String[] ENTRANCE_ARRAY = { "No default set", "North", "East", "South", "West" }; - AreaLink(String name) throws Exception { + public AreaLink(String name) throws Exception { super(null, name, StreamUtils.getByteBuffer(216), 0); } - AreaLink(AbstractStruct superStruct, ByteBuffer buffer, int offset, String name) throws Exception { + public AreaLink(AbstractStruct superStruct, ByteBuffer buffer, int offset, String name) throws Exception { super(superStruct, name, buffer, offset); } diff --git a/src/org/infinity/resource/wmp/AreaLinkEast.java b/src/org/infinity/resource/wmp/AreaLinkEast.java index dbc6a7346..6c713aee0 100644 --- a/src/org/infinity/resource/wmp/AreaLinkEast.java +++ b/src/org/infinity/resource/wmp/AreaLinkEast.java @@ -8,15 +8,15 @@ import org.infinity.resource.AbstractStruct; -class AreaLinkEast extends AreaLink { +public class AreaLinkEast extends AreaLink { // WMP/AreaLinkEast-specific field labels public static final String WMP_LINK_EAST = "East link"; - AreaLinkEast() throws Exception { + public AreaLinkEast() throws Exception { super(WMP_LINK_EAST); } - AreaLinkEast(AbstractStruct superStruct, ByteBuffer buffer, int offset, int number) throws Exception { + public AreaLinkEast(AbstractStruct superStruct, ByteBuffer buffer, int offset, int number) throws Exception { super(superStruct, buffer, offset, WMP_LINK_EAST + " " + number); } } diff --git a/src/org/infinity/resource/wmp/AreaLinkNorth.java b/src/org/infinity/resource/wmp/AreaLinkNorth.java index b330632b1..c913c0753 100644 --- a/src/org/infinity/resource/wmp/AreaLinkNorth.java +++ b/src/org/infinity/resource/wmp/AreaLinkNorth.java @@ -8,15 +8,15 @@ import org.infinity.resource.AbstractStruct; -class AreaLinkNorth extends AreaLink { +public class AreaLinkNorth extends AreaLink { // WMP/AreaLinkNorth-specific field labels public static final String WMP_LINK_NORTH = "North link"; - AreaLinkNorth() throws Exception { + public AreaLinkNorth() throws Exception { super(WMP_LINK_NORTH); } - AreaLinkNorth(AbstractStruct superStruct, ByteBuffer buffer, int offset, int number) throws Exception { + public AreaLinkNorth(AbstractStruct superStruct, ByteBuffer buffer, int offset, int number) throws Exception { super(superStruct, buffer, offset, WMP_LINK_NORTH + " " + number); } } diff --git a/src/org/infinity/resource/wmp/AreaLinkSouth.java b/src/org/infinity/resource/wmp/AreaLinkSouth.java index e159b96af..e18b39582 100644 --- a/src/org/infinity/resource/wmp/AreaLinkSouth.java +++ b/src/org/infinity/resource/wmp/AreaLinkSouth.java @@ -8,15 +8,15 @@ import org.infinity.resource.AbstractStruct; -class AreaLinkSouth extends AreaLink { +public class AreaLinkSouth extends AreaLink { // WMP/AreaLinkSouth-specific field labels public static final String WMP_LINK_SOUTH = "South link"; - AreaLinkSouth() throws Exception { + public AreaLinkSouth() throws Exception { super(WMP_LINK_SOUTH); } - AreaLinkSouth(AbstractStruct superStruct, ByteBuffer buffer, int offset, int number) throws Exception { + public AreaLinkSouth(AbstractStruct superStruct, ByteBuffer buffer, int offset, int number) throws Exception { super(superStruct, buffer, offset, WMP_LINK_SOUTH + " " + number); } } diff --git a/src/org/infinity/resource/wmp/AreaLinkWest.java b/src/org/infinity/resource/wmp/AreaLinkWest.java index 357e6560e..4bb40b1ae 100644 --- a/src/org/infinity/resource/wmp/AreaLinkWest.java +++ b/src/org/infinity/resource/wmp/AreaLinkWest.java @@ -8,15 +8,15 @@ import org.infinity.resource.AbstractStruct; -class AreaLinkWest extends AreaLink { +public class AreaLinkWest extends AreaLink { // WMP/AreaLinkWest-specific field labels public static final String WMP_LINK_WEST = "West link"; - AreaLinkWest() throws Exception { + public AreaLinkWest() throws Exception { super(WMP_LINK_WEST); } - AreaLinkWest(AbstractStruct superStruct, ByteBuffer buffer, int offset, int number) throws Exception { + public AreaLinkWest(AbstractStruct superStruct, ByteBuffer buffer, int offset, int number) throws Exception { super(superStruct, buffer, offset, WMP_LINK_WEST + " " + number); } } diff --git a/src/org/infinity/resource/wmp/MapEntry.java b/src/org/infinity/resource/wmp/MapEntry.java index 5dddd0863..b7672c6f4 100644 --- a/src/org/infinity/resource/wmp/MapEntry.java +++ b/src/org/infinity/resource/wmp/MapEntry.java @@ -19,8 +19,10 @@ import org.infinity.resource.AbstractStruct; import org.infinity.resource.HasViewerTabs; import org.infinity.resource.Profile; +import org.infinity.resource.wmp.viewer.ViewerMap; +import org.tinylog.Logger; -final public class MapEntry extends AbstractStruct implements HasViewerTabs { +public class MapEntry extends AbstractStruct implements HasViewerTabs { // WMP/MapEntry-specific field labels public static final String WMP_MAP = "Map"; public static final String WMP_MAP_RESREF = "Map"; @@ -38,7 +40,7 @@ final public class MapEntry extends AbstractStruct implements HasViewerTabs { private static final String[] FLAGS_ARRAY = { "No flags set", "Colored icon", "Ignore palette" }; - MapEntry(AbstractStruct superStruct, ByteBuffer buffer, int offset, int nr) throws Exception { + public MapEntry(AbstractStruct superStruct, ByteBuffer buffer, int offset, int nr) throws Exception { super(superStruct, WMP_MAP + " " + nr, buffer, offset); } @@ -56,7 +58,12 @@ public String getViewerTabName(int index) { @Override public JComponent getViewerTab(int index) { - return new ViewerMap(this); + try { + return new ViewerMap(this); + } catch (Exception e) { + Logger.error(e); + } + return null; } @Override diff --git a/src/org/infinity/resource/wmp/ViewerArea.java b/src/org/infinity/resource/wmp/ViewerArea.java index 28e1ccc11..f4a6b758a 100644 --- a/src/org/infinity/resource/wmp/ViewerArea.java +++ b/src/org/infinity/resource/wmp/ViewerArea.java @@ -25,7 +25,7 @@ import org.infinity.icon.Icons; import org.infinity.resource.Viewable; -final class ViewerArea extends JPanel implements ActionListener { +class ViewerArea extends JPanel implements ActionListener { private final JButton bOpen = new JButton("View/Edit", Icons.ICON_ZOOM_16.getIcon()); private JList list; @@ -41,7 +41,7 @@ private static JPanel makeInfoPanel(AreaEntry areaEntry) { return panel; } - ViewerArea(AreaEntry areaEntry) { + public ViewerArea(AreaEntry areaEntry) { JPanel flagPanel = ViewerUtil.makeCheckPanel((Flag) areaEntry.getAttribute(AreaEntry.WMP_AREA_FLAGS), 1); JPanel infoPane = makeInfoPanel(areaEntry); JComponent icon = ViewerUtil.makeBamPanel((ResourceRef) areaEntry.getParent().getAttribute(MapEntry.WMP_MAP_ICONS), diff --git a/src/org/infinity/resource/wmp/WmpResource.java b/src/org/infinity/resource/wmp/WmpResource.java index d54377b7e..98b04c1a3 100644 --- a/src/org/infinity/resource/wmp/WmpResource.java +++ b/src/org/infinity/resource/wmp/WmpResource.java @@ -39,7 +39,7 @@ *

* A WMP resource must have at least one area entry, and one area link to be considered valid. */ -public final class WmpResource extends AbstractStruct implements Resource, HasViewerTabs { +public class WmpResource extends AbstractStruct implements Resource, HasViewerTabs { // WMP-specific field labels public static final String WMP_NUM_MAPS = "# maps"; public static final String WMP_OFFSET_MAPS = "Maps offset"; diff --git a/src/org/infinity/resource/wmp/viewer/AreaListCellRenderer.java b/src/org/infinity/resource/wmp/viewer/AreaListCellRenderer.java new file mode 100644 index 000000000..bf16fccf1 --- /dev/null +++ b/src/org/infinity/resource/wmp/viewer/AreaListCellRenderer.java @@ -0,0 +1,122 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.wmp.viewer; + +import java.awt.Component; +import java.util.Locale; + +import javax.swing.DefaultListCellRenderer; +import javax.swing.ImageIcon; +import javax.swing.JLabel; +import javax.swing.JList; + +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsReference; +import org.infinity.datatype.StringRef; +import org.infinity.gui.ViewerUtil.ListValueRenderer; +import org.infinity.gui.menu.BrowserMenuBar; +import org.infinity.resource.AbstractStruct; +import org.infinity.resource.graphics.BamDecoder; +import org.infinity.resource.graphics.BamDecoder.BamControl; +import org.infinity.resource.graphics.BamDecoder.FrameEntry; +import org.infinity.resource.wmp.AreaEntry; +import org.infinity.util.StringTable; + +/** + * Specialized list item renderer for worldmap area entries. + */ +final class AreaListCellRenderer extends DefaultListCellRenderer implements ListValueRenderer { + private static final int TEXT_GAP = 4; + + private final BamDecoder bam; + private final BamControl ctrl; + private final int maxFrameWidth; + + public AreaListCellRenderer(BamDecoder decoder) { + bam = decoder; + ctrl = (bam != null) ? bam.createControl() : null; + + int maxWidth = 0; + for (int i = 0, count = ctrl.cycleCount(); i < count; i++) { + if (ctrl.cycleFrameCount(i) > 0) { + final int frameIndex = ctrl.cycleGetFrameIndexAbsolute(i, 0); + if (frameIndex >= 0) { + final FrameEntry fe = bam.getFrameInfo(frameIndex); + if (fe != null) { + maxWidth = Math.max(maxWidth, fe.getWidth()); + } + } + } + } + maxFrameWidth = maxWidth; + } + + @Override + public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, + boolean cellHasFocus) { + JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); + label.setText(getListValue(value, true)); + + int iconIndex = -1; + if (value instanceof AbstractStruct) { + final AbstractStruct struct = (AbstractStruct)value; + iconIndex = ((IsNumeric) struct.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(); + } else if (value instanceof VirtualAreaEntry) { + final VirtualAreaEntry vae = (VirtualAreaEntry)value; + iconIndex = vae.getAreaIconIndex(); + } + + if (ctrl != null && iconIndex >= 0) { + final int frameIndex = ctrl.cycleGetFrameIndexAbsolute(iconIndex, 0); + setIcon(new ImageIcon(bam.frameGet(ctrl, frameIndex))); + + // properly aligning label text + final FrameEntry fe = bam.getFrameInfo(frameIndex); + final int gap = Math.max(0, maxFrameWidth - fe.getWidth()); + setIconTextGap(gap + TEXT_GAP); + } else { + setIcon(null); + setIconTextGap(maxFrameWidth + TEXT_GAP); + } + + return label; + } + + @Override + public String getListValue(Object value) { + return getListValue(value, false); + } + + private String getListValue(Object value, boolean showFull) { + String text1 = ""; + String text2 = ""; + StringTable.Format fmt = BrowserMenuBar.getInstance().getOptions().showStrrefs() ? StringTable.Format.STRREF_SUFFIX + : StringTable.Format.NONE; + + if (value instanceof AbstractStruct) { + final AbstractStruct struct = (AbstractStruct)value; + + StringRef areaName = (StringRef)struct.getAttribute(AreaEntry.WMP_AREA_NAME); + IsReference areaRef = (IsReference)struct.getAttribute(AreaEntry.WMP_AREA_CURRENT); + if (areaName.getValue() >= 0) { + text1 = StringTable.getStringRef(areaName.getValue(), fmt); + } + text2 = areaRef.getResourceName(); + } else if (value instanceof VirtualAreaEntry) { + final VirtualAreaEntry vae = (VirtualAreaEntry)value; + text1 = StringTable.getStringRef(vae.getAreaLabelStrref(), fmt); + text2 = vae.getAreaResource().getResourceName(); + } + + if (!text2.equalsIgnoreCase("NONE")) { + text2 = text2.toUpperCase(Locale.ENGLISH).replace(".ARE", ""); + } + + if (showFull) { + return '[' + text2 + "] " + text1; + } + return text2; + } +} \ No newline at end of file diff --git a/src/org/infinity/resource/wmp/ViewerMap.java b/src/org/infinity/resource/wmp/viewer/ViewerMap.java similarity index 50% rename from src/org/infinity/resource/wmp/ViewerMap.java rename to src/org/infinity/resource/wmp/viewer/ViewerMap.java index 60fb3b72a..403835d65 100644 --- a/src/org/infinity/resource/wmp/ViewerMap.java +++ b/src/org/infinity/resource/wmp/viewer/ViewerMap.java @@ -2,7 +2,7 @@ // Copyright (C) 2001 Jon Olav Hauglid // See LICENSE.txt for license information -package org.infinity.resource.wmp; +package org.infinity.resource.wmp.viewer; import java.awt.AlphaComposite; import java.awt.BasicStroke; @@ -24,14 +24,13 @@ import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.ArrayList; +import java.util.EnumMap; import java.util.List; -import java.util.Locale; import javax.imageio.ImageIO; import javax.swing.BorderFactory; -import javax.swing.DefaultListCellRenderer; -import javax.swing.ImageIcon; import javax.swing.JCheckBoxMenuItem; import javax.swing.JLabel; import javax.swing.JList; @@ -47,36 +46,43 @@ import javax.swing.event.ListSelectionListener; import org.infinity.NearInfinity; -import org.infinity.datatype.Flag; import org.infinity.datatype.IsNumeric; -import org.infinity.datatype.IsReference; -import org.infinity.datatype.StringRef; import org.infinity.gui.RenderCanvas; import org.infinity.gui.ViewerUtil; -import org.infinity.gui.ViewerUtil.ListValueRenderer; import org.infinity.gui.ViewerUtil.StructListPanel; import org.infinity.gui.WindowBlocker; -import org.infinity.gui.menu.BrowserMenuBar; -import org.infinity.resource.AbstractStruct; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.StructEntry; -import org.infinity.resource.graphics.BamDecoder; -import org.infinity.resource.graphics.BamDecoder.BamControl; import org.infinity.resource.graphics.ColorConvert; import org.infinity.resource.graphics.MosDecoder; import org.infinity.resource.key.ResourceEntry; +import org.infinity.resource.wmp.AreaEntry; +import org.infinity.resource.wmp.MapEntry; import org.infinity.util.Logger; import org.infinity.util.Misc; +import org.infinity.util.SimpleListModel; import org.infinity.util.StringTable; import org.infinity.util.io.StreamUtils; +/** + * Provides a visual representation of a single WMP map instance. + */ public class ViewerMap extends JPanel { /** Needed to determine map edges to travel from/to. */ - private enum Direction { + public enum Direction { NORTH, WEST, SOUTH, EAST } + private static final EnumMap TRAVEL_COLORS = new EnumMap<>(Direction.class); + + static { + TRAVEL_COLORS.put(Direction.NORTH, Color.GREEN); + TRAVEL_COLORS.put(Direction.WEST, Color.RED); + TRAVEL_COLORS.put(Direction.SOUTH, Color.CYAN); + TRAVEL_COLORS.put(Direction.EAST, Color.YELLOW); + } + private final JPopupMenu pmOptions = new JPopupMenu("Options"); private final JMenuItem miExportMap = new JMenuItem("Export as PNG..."); private final JCheckBoxMenuItem miShowIcons = new JCheckBoxMenuItem("Show all map icons", true); @@ -86,33 +92,34 @@ private enum Direction { Profile.getGame() == Profile.Game.PSTEE); private final BufferedImage iconDot; private final Listeners listeners = new Listeners(); - private final MapEntry mapEntry; + private final WmpMapInfo mapInfo; private RenderCanvas rcMap; private BufferedImage mapOrig; - private BamDecoder mapIcons; - private BamControl mapIconsCtrl; private float mapScaleX, mapScaleY; private StructListPanel listPanel; private BufferedImage dotBackup; private int dotX, dotY; private JLabel lInfoSize, lInfoPos; - ViewerMap(MapEntry wmpMap) { + public ViewerMap(MapEntry wmpMap) throws Exception { super(); WindowBlocker.blockWindow(true); try { - mapEntry = wmpMap; + mapInfo = new WmpMapInfo(wmpMap); // creating marker for selected map icon iconDot = new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB); Graphics2D g = iconDot.createGraphics(); - g.setColor(Color.RED); - g.fillRect(0, 0, iconDot.getWidth(), iconDot.getHeight()); - g.setColor(Color.YELLOW); - g.drawRect(0, 0, iconDot.getWidth() - 1, iconDot.getHeight() - 1); - g.dispose(); - g = null; + try { + g.setColor(Color.RED); + g.fillRect(0, 0, iconDot.getWidth(), iconDot.getHeight()); + g.setColor(Color.YELLOW); + g.drawRect(0, 0, iconDot.getWidth() - 1, iconDot.getHeight() - 1); + } finally { + g.dispose(); + g = null; + } dotBackup = new BufferedImage(iconDot.getWidth(), iconDot.getHeight(), iconDot.getType()); dotX = dotY = -1; @@ -137,23 +144,13 @@ private enum Direction { pmOptions.add(miScaling); try { - mapIcons = null; - IsReference iconRef = (IsReference) wmpMap.getAttribute(MapEntry.WMP_MAP_ICONS); - if (iconRef != null) { - ResourceEntry iconEntry = ResourceFactory.getResourceEntry(iconRef.getResourceName()); - if (iconEntry != null) { - mapIcons = BamDecoder.loadBam(iconEntry); - mapIconsCtrl = mapIcons.createControl(); - } - } - mapOrig = loadMap(); - int mapTargetWidth = ((IsNumeric) mapEntry.getAttribute(MapEntry.WMP_MAP_WIDTH)).getValue(); + int mapTargetWidth = mapInfo.getWidth(); if (mapTargetWidth <= 0) { mapTargetWidth = (mapOrig != null) ? mapOrig.getWidth() : 640; } - int mapTargetHeight = ((IsNumeric) mapEntry.getAttribute(MapEntry.WMP_MAP_HEIGHT)).getValue(); + int mapTargetHeight = mapInfo.getHeight(); if (mapTargetHeight <= 0) { mapTargetHeight = (mapOrig != null) ? mapOrig.getHeight() : 480; } @@ -166,20 +163,23 @@ private enum Direction { rcMap.addMouseListener(listeners); rcMap.addMouseMotionListener(listeners); + final JScrollPane mapScroll = new JScrollPane(rcMap); + mapScroll.getVerticalScrollBar().setUnitIncrement(16); + mapScroll.getHorizontalScrollBar().setUnitIncrement(16); + mapScroll.setBorder(BorderFactory.createEmptyBorder()); + mapScaleX = (float) mapOrig.getWidth() / (float) mapTargetWidth; mapScaleY = (float) mapOrig.getHeight() / (float) mapTargetHeight; listPanel = ViewerUtil.makeListPanel("Areas", wmpMap, AreaEntry.class, AreaEntry.WMP_AREA_CURRENT, - new WmpAreaListRenderer(mapIcons), listeners); + new AreaListCellRenderer(mapInfo.getMapIcons()), listeners); + for (final StructEntry se : mapInfo.getVirtualAreas()) { + listPanel.getListModel().add(se); + } listPanel.getList().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - JScrollPane mapScroll = new JScrollPane(rcMap); - mapScroll.getVerticalScrollBar().setUnitIncrement(16); - mapScroll.getHorizontalScrollBar().setUnitIncrement(16); - mapScroll.setBorder(BorderFactory.createEmptyBorder()); JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, mapScroll, listPanel); - int viewerWidth = NearInfinity.getInstance().getWidth() - - NearInfinity.getInstance().getResourceTree().getWidth(); + int viewerWidth = NearInfinity.getInstance().getWidth() - NearInfinity.getInstance().getResourceTree().getWidth(); split.setDividerLocation(viewerWidth - viewerWidth / 4); // have area list occupy ca. 25% of resource view width setLayout(new BorderLayout()); add(split, BorderLayout.CENTER); @@ -198,7 +198,6 @@ private enum Direction { // applying preselected overlays showOverlays(miShowIcons.isSelected(), miShowIconLabels.isSelected(), miShowDistances.isSelected()); - } finally { WindowBlocker.blockWindow(false); } @@ -206,14 +205,13 @@ private enum Direction { /** Returns current map entry structure. */ private MapEntry getEntry() { - return mapEntry; + return mapInfo.getMapEntry(); } /** Load and return map graphics. */ private BufferedImage loadMap() { - String mapName = ((IsReference) getEntry().getAttribute(MapEntry.WMP_MAP_RESREF)).getResourceName(); - if (ResourceFactory.resourceExists(mapName)) { - MosDecoder mos = MosDecoder.loadMos(ResourceFactory.getResourceEntry(mapName)); + if (mapInfo.getBackgroundResource() != null) { + MosDecoder mos = MosDecoder.loadMos(mapInfo.getBackgroundResource()); if (mos != null) { return (BufferedImage) mos.getImage(); } @@ -238,32 +236,26 @@ private void showOverlays(boolean showIcons, boolean showIconLabels, boolean sho showMapDistances(listPanel.getList().getSelectedIndices()); } } - showDot((AreaEntry) listPanel.getList().getSelectedValue(), false); + showDot(listPanel.getList().getSelectedValue(), false); rcMap.repaint(); } /** Draws map icons onto the map. */ private void showMapIcons() { - if (mapIcons != null) { + if (mapInfo.getMapIcons() != null) { Graphics2D g = ((BufferedImage) rcMap.getImage()).createGraphics(); try { - for (int i = 0, count = listPanel.getList().getModel().getSize(); i < count; i++) { - AreaEntry area = getAreaEntry(i, true); - if (area != null) { - int iconIndex = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(); - int frameIndex = mapIconsCtrl.cycleGetFrameIndexAbsolute(iconIndex, 0); - if (frameIndex >= 0) { - final Image mapIcon = mapIcons.frameGet(mapIconsCtrl, frameIndex); - Point p = getAreaEntryPosition(area, isScaling()); - int width = mapIcons.getFrameInfo(frameIndex).getWidth(); - int height = mapIcons.getFrameInfo(frameIndex).getHeight(); - int cx = mapIcons.getFrameInfo(frameIndex).getCenterX(); - int cy = mapIcons.getFrameInfo(frameIndex).getCenterY(); - p.x -= cx; - p.y -= cy; - g.drawImage(mapIcon, p.x, p.y, p.x + width, p.y + height, 0, 0, width, height, null); - } + for (int i = 0, count = listPanel.getListModel().size(); i < count; i++) { + int iconIndex = -1; + Point p = null; + + final WmpAreaInfo wai = getAreaInfo(i, true); + if (wai != null) { + iconIndex = wai.getIconIndex(); + p = getAreaEntryPosition(wai); } + + drawMapIcon(iconIndex, p, g); } } finally { g.dispose(); @@ -272,77 +264,58 @@ private void showMapIcons() { } } + /** + * Draws a single map icon onto the map. + * + * @param iconIndex Index of the map icon. + * @param coords Map icon coordinates. + * @param g Graphics context to draw the icon onto. + */ + private void drawMapIcon(int iconIndex, Point coords, Graphics2D g) { + if (iconIndex < 0 || coords == null || g == null) { + return; + } + + final int frameIndex = mapInfo.getMapIconsControl().cycleGetFrameIndexAbsolute(iconIndex, 0); + if (frameIndex >= 0) { + final Image mapIcon = mapInfo.getMapIcons().frameGet(mapInfo.getMapIconsControl(), frameIndex); + int width = mapInfo.getMapIcons().getFrameInfo(frameIndex).getWidth(); + int height = mapInfo.getMapIcons().getFrameInfo(frameIndex).getHeight(); + int cx = mapInfo.getMapIcons().getFrameInfo(frameIndex).getCenterX(); + int cy = mapInfo.getMapIcons().getFrameInfo(frameIndex).getCenterY(); + coords.x -= cx; + coords.y -= cy; + g.drawImage(mapIcon, coords.x, coords.y, coords.x + width, coords.y + height, 0, 0, width, height, null); + } + } + /** Draws map icon labels onto the map. */ private void showMapIconLabels() { - if (mapIcons != null) { + if (mapInfo.getMapIcons() != null) { Graphics2D g = ((BufferedImage) rcMap.getImage()).createGraphics(); try { g.setFont(Misc.getScaledFont(g.getFont())); g.setFont(g.getFont().deriveFont(g.getFont().getSize2D() * 0.9f)); - final Color WHITE_BLENDED = new Color(0xc0ffffff, true); - for (int i = 0, count = listPanel.getList().getModel().getSize(); i < count; i++) { - AreaEntry area = getAreaEntry(i, true); - if (area != null) { - int iconIndex = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(); - int frameIndex = mapIconsCtrl.cycleGetFrameIndexAbsolute(iconIndex, 0); - if (frameIndex >= 0) { - // getting area name - int strref = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NAME)).getValue(); - if (strref < 0) { - strref = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_TOOLTIP)).getValue(); - } - String mapName = (strref >= 0) ? StringTable.getStringRef(strref) : null; - if (mapName != null && mapName.trim().isEmpty()) { - mapName = null; - } - - // getting area code - String mapCode = ((IsReference) area.getAttribute(AreaEntry.WMP_AREA_CURRENT)).getResourceName(); - if (ResourceFactory.resourceExists(mapCode)) { - mapCode = mapCode.replace(".ARE", ""); - } else { - mapCode = ""; - } - - Point p = getAreaEntryPosition(area, isScaling()); - int width = mapIcons.getFrameInfo(frameIndex).getWidth(); - int height = mapIcons.getFrameInfo(frameIndex).getHeight(); - int cx = mapIcons.getFrameInfo(frameIndex).getCenterX(); - int cy = mapIcons.getFrameInfo(frameIndex).getCenterY(); - p.x -= cx; - p.y -= cy; - - // printing label - if (!mapCode.isEmpty()) { - String[] labels; - if (mapName != null) { - labels = new String[2]; - labels[0] = mapName; - labels[1] = "(" + mapCode + ")"; - } else { - labels = new String[1]; - labels[0] = mapCode; - } - - int ofsY = 0; - for (final String label : labels) { - Rectangle2D rectText = g.getFont().getStringBounds(label, g.getFontRenderContext()); - int boxWidth = rectText.getBounds().width; - int boxHeight = rectText.getBounds().height; - int textX = p.x + (width - boxWidth) / 2; - int textY = p.y + height + ofsY; - - g.setColor(WHITE_BLENDED); - g.fillRect(textX - 2, textY, boxWidth + 4, boxHeight); - - g.setColor(Color.BLACK); - LineMetrics lm = g.getFont().getLineMetrics(label, g.getFontRenderContext()); - g.drawString(label, textX, textY + lm.getAscent() + lm.getLeading()); - ofsY += boxHeight; - } - } + for (int i = 0, count = listPanel.getListModel().size(); i < count; i++) { + int iconIndex = -1; + int strref = -1; + String mapCode = null; + Point p = null; + + final WmpAreaInfo wai = getAreaInfo(i, true); + if (wai != null) { + iconIndex = wai.getIconIndex(); + strref = wai.getAreaNameStrref(); + if (strref < 0) { + strref = wai.getAreaTooltipStrref(); } + if (wai.getCurrentArea() != null) { + mapCode = wai.getCurrentArea().getResourceName(); + } + p = getAreaEntryPosition(wai); } + + drawMapIconLabel(iconIndex, strref, mapCode, p, g); } } finally { g.dispose(); @@ -351,161 +324,231 @@ private void showMapIconLabels() { } } + /** + * Draws a single map icon label onto the map. + * + * @param iconIndex Index of the map icon. + * @param mapNameStrref Strref of the worldmap location. + * @param mapCode Area code of the worldmap location. + * @param coords Map icon coordinates. + * @param g Graphics context to draw the label onto. + */ + private void drawMapIconLabel(int iconIndex, int mapNameStrref, String mapCode, Point coords, Graphics2D g) { + if (iconIndex < 0 || coords == null || g == null) { + return; + } + + int frameIndex = mapInfo.getMapIconsControl().cycleGetFrameIndexAbsolute(iconIndex, 0); + if (frameIndex < 0) { + return; + } + + String mapName = (mapNameStrref >= 0) ? StringTable.getStringRef(mapNameStrref) : null; + if (mapName != null && mapName.trim().isEmpty()) { + mapName = null; + } + + if (ResourceFactory.resourceExists(mapCode)) { + mapCode = mapCode.replace(".ARE", ""); + } else { + mapCode = ""; + } + + int width = mapInfo.getMapIcons().getFrameInfo(frameIndex).getWidth(); + int height = mapInfo.getMapIcons().getFrameInfo(frameIndex).getHeight(); + int cx = mapInfo.getMapIcons().getFrameInfo(frameIndex).getCenterX(); + int cy = mapInfo.getMapIcons().getFrameInfo(frameIndex).getCenterY(); + coords.x -= cx; + coords.y -= cy; + + // printing label + if (!mapCode.isEmpty()) { + String[] labels; + if (mapName != null) { + labels = new String[2]; + labels[0] = mapName; + labels[1] = "(" + mapCode + ")"; + } else { + labels = new String[1]; + labels[0] = mapCode; + } + + final Color whiteBlended = new Color(0xc0ffffff, true); + int ofsY = 0; + for (final String label : labels) { + Rectangle2D rectText = g.getFont().getStringBounds(label, g.getFontRenderContext()); + int boxWidth = rectText.getBounds().width; + int boxHeight = rectText.getBounds().height; + int textX = coords.x + (width - boxWidth) / 2; + int textY = coords.y + height + ofsY; + + g.setColor(whiteBlended); + g.fillRect(textX - 2, textY, boxWidth + 4, boxHeight); + + g.setColor(Color.BLACK); + LineMetrics lm = g.getFont().getLineMetrics(label, g.getFontRenderContext()); + g.drawString(label, textX, textY + lm.getAscent() + lm.getLeading()); + ofsY += boxHeight; + } + } + + } + /** * Displays all map distances from the specified area (by index). * * @param areaIndices Sequence of map indices for showing distances. Specify no parameters to show distances for all * available maps. */ - private void showMapDistances(int... areaIndices) { + private void showMapDistances(int[] areaIndices) { final List areaIndicesList = new ArrayList<>(); if (areaIndices.length == 0) { - int numAreas = ((IsNumeric) mapEntry.getAttribute(MapEntry.WMP_MAP_NUM_AREAS)).getValue(); - for (int i = 0; i < numAreas; i++) { + for (int i = 0, count = mapInfo.getAreaList().size(); i < count; i++) { areaIndicesList.add(i); } } else { - for (final int idx : areaIndices) { - areaIndicesList.add(idx); + for (int i = 0; i < areaIndices.length; i++) { + final WmpAreaInfo wai = getAreaInfo(areaIndices[i], true); + if (wai != null) { + final int areaIndex = wai.getAreaIndex(); + areaIndicesList.add(areaIndex); + } } } - for (final int curAreaIndex : areaIndicesList) { - AreaEntry area = getAreaEntry(curAreaIndex, true); - if (area != null) { - final Direction[] srcDir = { Direction.NORTH, Direction.WEST, Direction.SOUTH, Direction.EAST }; - final Color[] dirColor = { Color.GREEN, Color.RED, Color.CYAN, Color.YELLOW }; - final int[] links = new int[8]; - final int linkSize = 216; // size of a single area link structure - int ofsLinkBase = ((IsNumeric) getEntry().getAttribute(MapEntry.WMP_MAP_OFFSET_AREA_LINKS)).getValue(); - - links[0] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_NORTH)).getValue(); - links[1] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_NORTH)).getValue(); - links[2] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_WEST)).getValue(); - links[3] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_WEST)).getValue(); - links[4] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_SOUTH)).getValue(); - links[5] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_SOUTH)).getValue(); - links[6] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_FIRST_LINK_EAST)).getValue(); - links[7] = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_NUM_LINKS_EAST)).getValue(); - for (int dir = 0; dir < srcDir.length; dir++) { - Direction curDir = srcDir[dir]; - Point ptOrigin = getMapIconCoordinate(curAreaIndex, curDir, true); - for (int dirIndex = 0, dirCount = links[dir * 2 + 1]; dirIndex < dirCount; dirIndex++) { - int ofsLink = ofsLinkBase + (links[dir * 2] + dirIndex) * linkSize; - AreaLink destLink = (AreaLink) area.getAttribute(ofsLink, false); - - // finding corresponding travel distances between selected areas - if (destLink != null && areaIndices.length > 1) { - final int destAreaIdx = ((IsNumeric) destLink.getAttribute(AreaLink.WMP_LINK_TARGET_AREA)).getValue(); - final AreaEntry destArea = getAreaEntry(destAreaIdx, false); + Graphics2D g = ((BufferedImage)rcMap.getImage()).createGraphics(); + try { + g.setFont(g.getFont().deriveFont(g.getFont().getSize2D() * 0.8f)); + for (final int curAreaIndex : areaIndicesList) { + final WmpAreaInfo srcAreaInfo = getAreaInfo(curAreaIndex, false); + if (srcAreaInfo != null) { + for (final WmpLinkInfo wli : srcAreaInfo.getLinksList()) { + final Point ptOrigin = getMapIconCoordinate(curAreaIndex, wli.getDirection(), false); + final int targetAreaIndex = wli.getTargetAreaIndex(); + if (targetAreaIndex < 0 || targetAreaIndex >= mapInfo.getAreaList().size()) { + Logger.warn("Invalid target area link: {}", targetAreaIndex); + continue; + } + final WmpAreaInfo dstAreaInfo = mapInfo.getAreaList().get(wli.getTargetAreaIndex()); + final int dstAreaIndex = dstAreaInfo.getAreaIndex(); + if (areaIndicesList.size() > 1 && areaIndices.length != 0) { + // finding corresponding travel distances between selected areas boolean found = false; - if (destArea != null) { - found = areaIndicesList - .stream() - .anyMatch(idx -> curAreaIndex != idx && destArea.equals(getAreaEntry(idx, true))); + for (final int idx : areaIndicesList) { + if (idx == dstAreaIndex) { + found = true; + break; + } } if (!found) { - destLink = null; + continue; } } - if (destLink != null) { - int dstAreaIndex = ((IsNumeric) destLink.getAttribute(AreaLink.WMP_LINK_TARGET_AREA)).getValue(); - Flag flag = (Flag) destLink.getAttribute(AreaLink.WMP_LINK_DEFAULT_ENTRANCE); - Direction dstDir = Direction.NORTH; - if (flag.isFlagSet(1)) { + final Direction dstDir; + switch (wli.getDefaultEntrance()) { + case 2: dstDir = Direction.EAST; - } else if (flag.isFlagSet(2)) { + break; + case 4: dstDir = Direction.SOUTH; - } else if (flag.isFlagSet(3)) { + break; + case 8: dstDir = Direction.WEST; - } - Point ptTarget = getMapIconCoordinate(dstAreaIndex, dstDir, false); - - // checking for random encounters during travels - final int randomEncounterProb = - ((IsNumeric) destLink.getAttribute(AreaLink.WMP_LINK_RANDOM_ENCOUNTER_PROBABILITY)).getValue(); - boolean hasRandomEncounters = false; - if (randomEncounterProb > 0) { - for (int rnd = 1; rnd < 6; rnd++) { - String rndArea = ((IsReference) destLink - .getAttribute(String.format(AreaLink.WMP_LINK_RANDOM_ENCOUNTER_AREA_FMT, rnd))).getResourceName(); - if (ResourceFactory.resourceExists(rndArea)) { - hasRandomEncounters = true; - break; - } - } - } - - Graphics2D g = ((BufferedImage) rcMap.getImage()).createGraphics(); - g.setFont(g.getFont().deriveFont(g.getFont().getSize2D() * 0.8f)); - try { - // drawing line - g.setColor(dirColor[dir]); - if (hasRandomEncounters) { - g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.0f, - new float[] { 6.0f, 4.0f }, 0.0f)); - } else { - g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); - } - g.drawLine(ptOrigin.x, ptOrigin.y, ptTarget.x, ptTarget.y); - - // printing travel time (in hours) - final String duration; - final int distScale = ((IsNumeric) destLink.getAttribute(AreaLink.WMP_LINK_DISTANCE_SCALE)).getValue() * 4; - if (hasRandomEncounters) { - duration = String.format("%d h (%d%%)", distScale, randomEncounterProb); - } else { - duration = String.format("%d h", distScale); - } - LineMetrics lm = g.getFont().getLineMetrics(duration, g.getFontRenderContext()); - Rectangle2D rectText = g.getFont().getStringBounds(duration, g.getFontRenderContext()); - int textX = ptOrigin.x + ((ptTarget.x - ptOrigin.x) - rectText.getBounds().width) / 3; - int textY = ptOrigin.y + ((ptTarget.y - ptOrigin.y) - rectText.getBounds().height) / 3; - int textWidth = rectText.getBounds().width; - int textHeight = rectText.getBounds().height; - g.setColor(Color.LIGHT_GRAY); - g.fillRect(textX - 2, textY, textWidth + 4, textHeight); - g.setColor(Color.BLUE); - g.drawString(duration, textX, textY + lm.getAscent() + lm.getLeading()); - } finally { - g.dispose(); - g = null; - } + break; + default: + dstDir = Direction.NORTH; + break; } + final Point ptTarget = getMapIconCoordinate(dstAreaIndex, dstDir, false); + + // checking random encounters + int rndEncProb = (wli.getRandomEncounterProbability() > 0 && wli.getRandomEncounterAreaCount() != 0) + ? wli.getRandomEncounterProbability() + : 0; + + drawMapDistance(ptOrigin, ptTarget, wli.getDistanceScale() * 4, rndEncProb, + TRAVEL_COLORS.get(wli.getDirection()), g); } } } + } finally { + g.dispose(); + g = null; + } + } + + /** + * Visualizes a single travel distance. + * + * @param origin Coordinate of the starting location. + * @param target Coordinate of the target location. + * @param distance Distance, in hours. + * @param rndEncProb Probability of random encounters (0 = no encounters). + * @param color Color of the visualized travel route. + * @param g Graphics context to draw the travel distance onto. + */ + private void drawMapDistance(Point origin, Point target, int distance, int rndEncProb, Color color, Graphics2D g) { + if (origin == null || target == null || color == null || g == null) { + return; } + + // drawing line + g.setColor(color); + if (rndEncProb > 0) { + g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 1.0f, + new float[] { 6.0f, 4.0f }, 0.0f)); + } else { + g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL)); + } + g.drawLine(origin.x, origin.y, target.x, target.y); + + // printing travel time (in hours) + final String duration; + if (rndEncProb > 0) { + duration = String.format("%d h (%d%%)", distance, rndEncProb); + } else { + duration = String.format("%d h", distance); + } + final LineMetrics lm = g.getFont().getLineMetrics(duration, g.getFontRenderContext()); + final Rectangle2D rectText = g.getFont().getStringBounds(duration, g.getFontRenderContext()); + int textX = origin.x + ((target.x - origin.x) - rectText.getBounds().width) / 3; + int textY = origin.y + ((target.y - origin.y) - rectText.getBounds().height) / 3; + int textWidth = rectText.getBounds().width; + int textHeight = rectText.getBounds().height; + g.setColor(Color.LIGHT_GRAY); + g.fillRect(textX - 2, textY, textWidth + 4, textHeight); + g.setColor(Color.BLUE); + g.drawString(duration, textX, textY + lm.getAscent() + lm.getLeading()); } /** Returns a properly scaled map icon position. */ - private Point getAreaEntryPosition(AreaEntry areaEntry, boolean scaled) { - Point p = new Point(); - if (areaEntry != null) { - p.x = (int) (((IsNumeric) areaEntry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_X)).getValue() * getScaleFactorX()); - p.y = (int) (((IsNumeric) areaEntry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_Y)).getValue() * getScaleFactorY()); + private Point getAreaEntryPosition(WmpAreaInfo areaInfo) { + final Point p = new Point(); + if (areaInfo != null) { + p.x = (int) (areaInfo.getLocationX() * getScaleFactorX()); + p.y = (int) (areaInfo.getLocationY() * getScaleFactorY()); } return p; } /** Returns a pixel coordinate for one of the edges of the specified area icon. */ private Point getMapIconCoordinate(int areaIndex, Direction dir, boolean byPanel) { - AreaEntry area = getAreaEntry(areaIndex, byPanel); - if (area != null) { - Point p = getAreaEntryPosition(area, isScaling()); - int iconIndex = ((IsNumeric) area.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(); - int frameIndex = mapIconsCtrl.cycleGetFrameIndexAbsolute(iconIndex, 0); + final WmpAreaInfo wai = getAreaInfo(areaIndex, byPanel); + if (wai != null) { + final Point p = getAreaEntryPosition(wai); + int iconIndex = wai.getIconIndex(); + int frameIndex = mapInfo.getMapIconsControl().cycleGetFrameIndexAbsolute(iconIndex, 0); int width, height; if (frameIndex >= 0) { - width = mapIcons.getFrameInfo(frameIndex).getWidth(); - height = mapIcons.getFrameInfo(frameIndex).getHeight(); - p.x -= mapIcons.getFrameInfo(frameIndex).getCenterX(); - p.y -= mapIcons.getFrameInfo(frameIndex).getCenterY(); + width = mapInfo.getMapIcons().getFrameInfo(frameIndex).getWidth(); + height = mapInfo.getMapIcons().getFrameInfo(frameIndex).getHeight(); + p.x -= mapInfo.getMapIcons().getFrameInfo(frameIndex).getCenterX(); + p.y -= mapInfo.getMapIcons().getFrameInfo(frameIndex).getCenterY(); } else { width = height = 0; } - Point retVal = new Point(); + final Point retVal = new Point(); switch (dir) { case NORTH: retVal.x = p.x + (width / 2); @@ -529,40 +572,52 @@ private Point getMapIconCoordinate(int areaIndex, Direction dir, boolean byPanel return null; } - /** Returns area structure of specified item index. */ - private AreaEntry getAreaEntry(int index, boolean byPanel) { - AreaEntry retVal = null; + /** Returns area info structure of specified item index. */ + private WmpAreaInfo getAreaInfo(int index, boolean byPanel) { + WmpAreaInfo retVal = null; if (byPanel) { - if (index >= 0 && index < listPanel.getList().getModel().getSize()) { - retVal = (AreaEntry) listPanel.getList().getModel().getElementAt(index); + final SimpleListModel listModel = listPanel.getListModel(); + if (index >= 0 && index < listModel.size()) { + final int areaIndex = mapInfo.indexOfArea(listModel.get(index)); + if (areaIndex >= 0) { + retVal = mapInfo.getAreaList().get(areaIndex); + } } } else { - if (index >= 0 && index < ((IsNumeric) mapEntry.getAttribute(MapEntry.WMP_MAP_NUM_AREAS)).getValue()) { - StructEntry e = mapEntry.getAttribute(AreaEntry.WMP_AREA + " " + index); - if (e instanceof AreaEntry) { - retVal = (AreaEntry) e; - } + if (index >= 0 && index < mapInfo.getAreaList().size()) { + retVal = mapInfo.getAreaList().get(index); } } return retVal; } /** Show "dot" on specified map icon, optionally restore background graphics. */ - private void showDot(AreaEntry entry, boolean restore) { + private void showDot(StructEntry entry, boolean restore) { if (restore) { restoreDot(); } - if (entry != null) { - storeDot(entry); - int x = ((IsNumeric) entry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_X)).getValue(); + + int x = -1; + int y = -1; + if (entry instanceof AreaEntry) { + final AreaEntry areaEntry = (AreaEntry)entry; + storeDot(areaEntry); + x = ((IsNumeric) areaEntry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_X)).getValue(); x = (int) (x * getScaleFactorX()); - int y = ((IsNumeric) entry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_Y)).getValue(); + y = ((IsNumeric) areaEntry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_Y)).getValue(); y = (int) (y * getScaleFactorY()); + } else if (entry instanceof VirtualAreaEntry) { + final VirtualAreaEntry vae = (VirtualAreaEntry)entry; + x = (int) (vae.getAreaLocationX() * getScaleFactorX()); + y = (int) (vae.getAreaLocationY() * getScaleFactorY()); + } + + if (x >= 0 && y >= 0) { int width = iconDot.getWidth(); int height = iconDot.getHeight(); int xofs = width / 2; int yofs = height / 2; - Graphics2D g = ((BufferedImage) rcMap.getImage()).createGraphics(); + Graphics2D g = ((BufferedImage)rcMap.getImage()).createGraphics(); try { g.setComposite(AlphaComposite.Src); g.drawImage(iconDot, x - xofs, y - yofs, x - xofs + width, y - yofs + height, 0, 0, width, height, null); @@ -574,12 +629,23 @@ private void showDot(AreaEntry entry, boolean restore) { } /** Stores background graphics of "dot". */ - private void storeDot(AreaEntry entry) { - if (entry != null) { - int x = ((IsNumeric) entry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_X)).getValue(); + private void storeDot(StructEntry entry) { + int x = -1; + int y = -1; + + if (entry instanceof AreaEntry) { + final AreaEntry areaEntry = (AreaEntry)entry; + x = ((IsNumeric) areaEntry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_X)).getValue(); x = (int) (x * getScaleFactorX()); - int y = ((IsNumeric) entry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_Y)).getValue(); + y = ((IsNumeric) areaEntry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_Y)).getValue(); y = (int) (y * getScaleFactorY()); + } else if (entry instanceof VirtualAreaEntry) { + final VirtualAreaEntry vae = (VirtualAreaEntry)entry; + x = (int) (vae.getAreaLocationX() * getScaleFactorX()); + y = (int) (vae.getAreaLocationY() * getScaleFactorY()); + } + + if (x >= 0 && y >= 0) { int width = dotBackup.getWidth(); int height = dotBackup.getHeight(); int xofs = width / 2; @@ -605,7 +671,7 @@ private void restoreDot() { int y = dotY; int width = dotBackup.getWidth(); int height = dotBackup.getHeight(); - Graphics2D g = ((BufferedImage) rcMap.getImage()).createGraphics(); + Graphics2D g = ((BufferedImage)rcMap.getImage()).createGraphics(); try { g.setComposite(AlphaComposite.Src); g.drawImage(dotBackup, x, y, x + width, y + height, 0, 0, width, height, null); @@ -622,7 +688,7 @@ private void restoreDot() { private void resetMap() { Graphics2D g = ((BufferedImage) rcMap.getImage()).createGraphics(); try { - Composite comp = g.getComposite(); + final Composite comp = g.getComposite(); g.setComposite(AlphaComposite.Src); g.drawImage(mapOrig, 0, 0, null); g.setComposite(comp); @@ -664,33 +730,36 @@ private void exportMap() { WindowBlocker.blockWindow(wnd, true); SwingUtilities.invokeLater(() -> { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - boolean bRet = false; - try { - restoreDot(); - BufferedImage srcImage = (BufferedImage) rcMap.getImage(); - BufferedImage dstImage = ColorConvert.createCompatibleImage(srcImage.getWidth(), srcImage.getHeight(), - srcImage.getTransparency()); - Graphics2D g = dstImage.createGraphics(); - g.drawImage(srcImage, 0, 0, null); - g.dispose(); - srcImage = null; - bRet = ImageIO.write(dstImage, "png", os); - dstImage.flush(); - dstImage = null; - } catch (Exception e) { + try (ByteArrayOutputStream os = new ByteArrayOutputStream()) { + boolean bRet = false; + try { + restoreDot(); + BufferedImage srcImage = (BufferedImage) rcMap.getImage(); + BufferedImage dstImage = ColorConvert.createCompatibleImage(srcImage.getWidth(), srcImage.getHeight(), + srcImage.getTransparency()); + Graphics2D g = dstImage.createGraphics(); + g.drawImage(srcImage, 0, 0, null); + g.dispose(); + srcImage = null; + bRet = ImageIO.write(dstImage, "png", os); + dstImage.flush(); + dstImage = null; + } catch (Exception e) { + Logger.error(e); + } finally { + showDot(listPanel.getList().getSelectedValue(), false); + WindowBlocker.blockWindow(wnd, false); + } + if (bRet) { + final ResourceEntry entry = getEntry().getParent().getResourceEntry(); + final String fileName = StreamUtils.replaceFileExtension(entry.getResourceName(), "PNG"); + ResourceFactory.exportResource(entry, StreamUtils.getByteBuffer(os.toByteArray()), fileName, wnd); + } else { + JOptionPane.showMessageDialog(wnd, "Error while exporting map as graphics.", "Error", + JOptionPane.ERROR_MESSAGE); + } + } catch (IOException e) { Logger.error(e); - } finally { - showDot((AreaEntry) listPanel.getList().getSelectedValue(), false); - WindowBlocker.blockWindow(wnd, false); - } - if (bRet) { - final ResourceEntry entry = getEntry().getParent().getResourceEntry(); - final String fileName = StreamUtils.replaceFileExtension(entry.getResourceName(), "PNG"); - ResourceFactory.exportResource(entry, StreamUtils.getByteBuffer(os.toByteArray()), fileName, wnd); - } else { - JOptionPane.showMessageDialog(wnd, "Error while exporting map as graphics.", "Error", - JOptionPane.ERROR_MESSAGE); } }); } @@ -698,6 +767,9 @@ private void exportMap() { // -------------------------- INNER CLASSES -------------------------- private class Listeners implements ActionListener, MouseListener, MouseMotionListener, ListSelectionListener { + public Listeners() { + } + // --------------------- Begin Interface ActionListener --------------------- @Override @@ -815,7 +887,7 @@ public void valueChanged(ListSelectionEvent event) { if (miShowDistances.isSelected()) { showOverlays(miShowIcons.isSelected(), miShowIconLabels.isSelected(), miShowDistances.isSelected()); } else { - showDot((AreaEntry) list.getSelectedValue(), true); + showDot((StructEntry)list.getSelectedValue(), true); } repaint(); } @@ -823,62 +895,4 @@ public void valueChanged(ListSelectionEvent event) { // --------------------- End Interface ListSelectionListener --------------------- } - - private static final class WmpAreaListRenderer extends DefaultListCellRenderer implements ListValueRenderer { - private final BamDecoder bam; - private final BamControl ctrl; - - private WmpAreaListRenderer(BamDecoder decoder) { - bam = decoder; - ctrl = (bam != null) ? bam.createControl() : null; - } - - @Override - public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, - boolean cellHasFocus) { - JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); - label.setText(getListValue(value, true)); - - AbstractStruct struct = (AbstractStruct) value; - IsNumeric animNr = (IsNumeric) struct.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX); - setIcon(null); - if (ctrl != null) { - setIcon(new ImageIcon(bam.frameGet(ctrl, ctrl.cycleGetFrameIndexAbsolute(animNr.getValue(), 0)))); - } - return label; - } - - @Override - public String getListValue(Object value) { - return getListValue(value, false); - } - - private String getListValue(Object value, boolean showFull) { - if (value instanceof AbstractStruct) { - AbstractStruct struct = (AbstractStruct) value; - - StringRef areaName = (StringRef) struct.getAttribute(AreaEntry.WMP_AREA_NAME); - IsReference areaRef = (IsReference) struct.getAttribute(AreaEntry.WMP_AREA_CURRENT); - String text1, text2; - if (areaName.getValue() >= 0) { - StringTable.Format fmt = BrowserMenuBar.getInstance().getOptions().showStrrefs() ? StringTable.Format.STRREF_SUFFIX - : StringTable.Format.NONE; - text1 = areaName.toString(fmt); - } else { - text1 = ""; - } - text2 = areaRef.getResourceName(); - if (!text2.equalsIgnoreCase("NONE")) { - text2 = text2.toUpperCase(Locale.ENGLISH).replace(".ARE", ""); - } - - if (showFull) { - return '[' + text2 + "] " + text1; - } else { - return text2; - } - } - return ""; - } - } } diff --git a/src/org/infinity/resource/wmp/viewer/VirtualAreaEntry.java b/src/org/infinity/resource/wmp/viewer/VirtualAreaEntry.java new file mode 100644 index 000000000..c37f99a25 --- /dev/null +++ b/src/org/infinity/resource/wmp/viewer/VirtualAreaEntry.java @@ -0,0 +1,354 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.wmp.viewer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.Misc; +import org.infinity.util.Table2da; +import org.infinity.util.Table2daCache; + +/** + * Parser for a single table-based worldmap area and associated travel links. + */ +public class VirtualAreaEntry extends VirtualStructEntry { + // Cached row from XNEWAREA.2DA + // Area entries are parsed ResourceEntry, String or Integer objects, depending on the column value + private final Object[] cachedArea = new Object[15]; + + // Cached rows from the link table of the area + // Link entries are parsed ResourceEntry, String or Integer objects, depending on the column value + // May contain "null" for undefined entries + private final List cachedLinks = new ArrayList<>(); + + private final VirtualMapEntry parent; + private final int areaIndex; + private final Table2da areaTable; + + public VirtualAreaEntry(VirtualMapEntry parentMap, int index) throws Exception { + super(getAreaResourceEntry(parentMap, index)); + parent = parentMap; + areaIndex = index; + areaTable = Table2daCache.get(getResourceEntry()); + validate(); + } + + /** Returns the unprocessed parent map table with area definitions for the worldmap. */ + public VirtualMapEntry getParentMap() { + return parent; + } + + /** Returns the row index of the area definition in the parent map table. */ + public int getAreaIndex() { + return areaIndex; + } + + /** Returns the unprocessed 2DA table with link definitions for the current area. */ + public Table2da getArea() { + return areaTable; + } + + /** Returns the area resource linked to the worldmap location. */ + public ResourceEntry getAreaResource() { + return (ResourceEntry)cachedArea[1]; + } + + /** Returns the script name of the area. */ + public String getAreaScript() { + return (String)cachedArea[2]; + } + + /** Returns visibility flags of the worldmap location. */ + public int getAreaFlags() { + return (Integer)cachedArea[3]; + } + + /** Returns the index of the worldmap icon. */ + public int getAreaIconIndex() { + return (Integer)cachedArea[4]; + } + + /** Returns the x coordinate of the worldmap location. */ + public int getAreaLocationX() { + return (Integer)cachedArea[5]; + } + + /** Returns the y coordinate of the worldmap location. */ + public int getAreaLocationY() { + return (Integer)cachedArea[6]; + } + + /** Returns the strref of the location label. Returns -1 if not available. */ + public int getAreaLabelStrref() { + return (Integer)cachedArea[7]; + } + + /** Returns the strref of the location name. Returns -1 if not available. */ + public int getAreaNameStrref() { + return (Integer)cachedArea[8]; + } + + /** Returns the number of link definitions for travelling to a target area from the northern edge. */ + public int getNumAreaLinksNorth() { + return (Integer)cachedArea[10]; + } + + /** Returns the first link definition index for travelling to a target area from the northern edge. */ + public int getFirstAreaLinkNorth() { + return 0; + } + + /** Returns the number of link definitions for travelling to a target area from the eastern edge. */ + public int getNumAreaLinksEast() { + return (Integer)cachedArea[11]; + } + + /** Returns the first link definition index for travelling to a target area from the eastern edge. */ + public int getFirstAreaLinkEast() { + return getNumAreaLinksNorth(); + } + + /** Returns the number of link definitions for travelling to a target area from the southern edge. */ + public int getNumAreaLinksSouth() { + return (Integer)cachedArea[12]; + } + + /** Returns the first link definition index for travelling to a target area from the southern edge. */ + public int getFirstAreaLinkSouth() { + return getNumAreaLinksNorth() + getNumAreaLinksEast(); + } + + /** Returns the number of link definitions for travelling to a target area from the western edge. */ + public int getNumAreaLinksWest() { + return (Integer)cachedArea[13]; + } + + /** Returns the first link definition index for travelling to a target area from the western edge. */ + public int getFirstAreaLinkWest() { + return getNumAreaLinksNorth() + getNumAreaLinksEast() + getNumAreaLinksSouth(); + } + + /** Returns the number of link definitions for travelling towards the current area. */ + public int getNumAreaLinksTo() { + return (Integer)cachedArea[14]; + } + + /** Returns the first link definition index for travelling towards the current area. */ + public int getFirstAreaLinkTo() { + return getNumAreaLinksNorth() + getNumAreaLinksEast() + getNumAreaLinksSouth() + getNumAreaLinksWest(); + } + + /** + * Returns the target or source area for the specified link definition. + * + * @param index Travel link index. + * @return Target or source area depending on the travel direction, as {@link ResourceEntry} instance. + */ + public ResourceEntry getLinkArea(int index) { + return (ResourceEntry)cachedLinks.get(index)[1]; + } + + /** + * Returns the location flags for the specified link definition. + * + * @param index Travel link index. + * @return Location flags. + */ + public int getLinkFlags(int index) { + return (Integer)cachedLinks.get(index)[2]; + } + + /** + * Returns the entry point name for the specified link definition. + * + * @param index Travel link index. + * @return Entry point name. + */ + public String getLinkEntryPoint(int index) { + return (String)cachedLinks.get(index)[3]; + } + + /** + * Returns the distance in units of 4 hours for the specified link definition. + * + * @param index Travel link index. + * @return Distance between source and target location, in 4 hours units. + */ + public int getLinkDistanceScale(int index) { + return (Integer)cachedLinks.get(index)[4]; + } + + /** + * Returns the encounter probability for the specified link definition. + * + * @param index Travel link index. + * @return Probability in percent. + */ + public int getLinkEncounterProbability(int index) { + return (Integer)cachedLinks.get(index)[5]; + } + + /** + * Returns the encounter area for the specified link definition. + * + * @param index Travel link index. + * @param num The encounter area index in range 0 to 4. + * @return {@link ResourceEntry} of the encounter area if available, {@code null} otherwise. + */ + public ResourceEntry getLinkEncounter(int index, int num) { + switch (num) { + case 0: + return (ResourceEntry)cachedLinks.get(index)[6]; + case 1: + return (ResourceEntry)cachedLinks.get(index)[7]; + case 2: + return (ResourceEntry)cachedLinks.get(index)[8]; + case 3: + return (ResourceEntry)cachedLinks.get(index)[9]; + case 4: + return (ResourceEntry)cachedLinks.get(index)[10]; + } + return null; + } + + /** + * Returns the default edge of the entrance for the specified link definition. + * + * @param index Travel link index. + * @return Entrance type for links towards the source location: 0=north, 1=east, 2=south, 3=west). Returns -1 for + * links towards the target location. + */ + public int getLinkEntrance(int index) { + final Object retVal = cachedLinks.get(index)[11]; + if (retVal instanceof Integer) { + return (Integer)retVal; + } + return -1; + } + + @Override + public int hashCode() { + return Objects.hash(parent, areaIndex); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj instanceof VirtualAreaEntry) { + final VirtualAreaEntry vae = (VirtualAreaEntry)obj; + return areaIndex == vae.areaIndex && Objects.equals(parent, vae.parent); + } else { + return false; + } + } + + @Override + public String toString() { + return "VirtualAreaEntry [areaTable=" + areaTable + ", areaIndex=" + areaIndex + ", parent=" + parent + "]"; + } + + /** Validates and caches the table content. */ + private void validate() throws Exception { + // caching area definitions + cachedArea[0] = Misc.toNumber(parent.getMap().get(areaIndex, 0), areaIndex + 1); + cachedArea[1] = ResourceFactory.getResourceEntry(parent.getMap().get(areaIndex, 1) + ".ARE"); + cachedArea[2] = parent.getMap().get(areaIndex, 2); + cachedArea[3] = Misc.toNumber(parent.getMap().get(areaIndex, 3), 0); + cachedArea[4] = Misc.toNumber(parent.getMap().get(areaIndex, 4), -1); + cachedArea[5] = Misc.toNumber(parent.getMap().get(areaIndex, 5), -1); + cachedArea[6] = Misc.toNumber(parent.getMap().get(areaIndex, 6), -1); + cachedArea[7] = Misc.toNumber(parent.getMap().get(areaIndex, 7), -1); + cachedArea[8] = Misc.toNumber(parent.getMap().get(areaIndex, 8), -1); + cachedArea[9] = parent.getMap().get(areaIndex, 9); + cachedArea[10] = Misc.toNumber(parent.getMap().get(areaIndex, 10), 0); + cachedArea[11] = Misc.toNumber(parent.getMap().get(areaIndex, 11), 0); + cachedArea[12] = Misc.toNumber(parent.getMap().get(areaIndex, 12), 0); + cachedArea[13] = Misc.toNumber(parent.getMap().get(areaIndex, 13), 0); + cachedArea[14] = Misc.toNumber(parent.getMap().get(areaIndex, 14), 0); + final int numLinks = (Integer)cachedArea[10] + (Integer)cachedArea[11] + (Integer)cachedArea[12] + + (Integer)cachedArea[13] + (Integer)cachedArea[14]; + if (areaTable.getRowCount() < numLinks) { + throw new Exception("Unexpected number of links (expected: " + numLinks + ", found: " + areaTable.getRowCount() + ")"); + } + + // validating and caching link definitions + final int colCount = 12; + final String defValue = areaTable.getDefaultValue(); + for (int row = 0; row < numLinks; row++) { + if (areaTable.getColCount(row) < colCount) { + throw new Exception("Incomplete link definition at row " + row); + } + + final Object[] cachedRow = new Object[colCount]; + for (int col = 0; col < colCount; col++) { + final String value = areaTable.get(row, col); + switch (col) { + case 0: + case 2: + case 4: + case 5: + // valid number required + cachedRow[col] = Integer.parseInt(value); + break; + case 1: + { + // validate ARE resref + final ResourceEntry entry = ResourceFactory.getResourceEntry(value + ".ARE"); + if (entry != null) { + cachedRow[col] = entry; + } else { + throw new Exception("Resource does not exist: " + value); + } + break; + } + case 3: + // any string + if (value.length() <= 32) { + cachedRow[col] = value; + } else { + throw new Exception("Row=3: string is too long (expected: length <= 32, found: length = " + value.length() + ")"); + } + break; + case 6: + case 7: + case 8: + case 9: + case 10: + // random encounter area + if (!defValue.equals(value)) { + cachedRow[col] = ResourceFactory.getResourceEntry(value + ".ARE"); + } + break; + default: + // number or default value + if (!defValue.equals(value)) { + final int number = Integer.parseInt(value); + cachedRow[col] = number; + } + } + } + cachedLinks.add(cachedRow); + } + } + + private static ResourceEntry getAreaResourceEntry(VirtualMapEntry map, int index) throws Exception { + if (map != null && index >= 0 && index < map.getAreaCount()) { + final String linkName = map.getMap().get(index, 9); + if (!map.getMap().getDefaultValue().equals(linkName)) { + return ResourceFactory.getResourceEntry(linkName + ".2DA"); + } + } + throw new Exception("Area entry does not exist at index=" + index); + } +} \ No newline at end of file diff --git a/src/org/infinity/resource/wmp/viewer/VirtualMapEntry.java b/src/org/infinity/resource/wmp/viewer/VirtualMapEntry.java new file mode 100644 index 000000000..d9fd8c08a --- /dev/null +++ b/src/org/infinity/resource/wmp/viewer/VirtualMapEntry.java @@ -0,0 +1,115 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.wmp.viewer; + +import java.util.HashMap; + +import org.infinity.resource.Profile; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.util.Table2da; +import org.infinity.util.Table2daCache; + +/** + * Parser for table-based worldmap definitions. + */ +public class VirtualMapEntry extends VirtualStructEntry { + private final HashMap areaCache = new HashMap<>(); + + private final Table2da mapTable; + + public VirtualMapEntry(ResourceEntry wmpResource) throws Exception { + super(getMapResourceEntry(wmpResource)); + mapTable = Table2daCache.get(getResourceEntry()); + validate(); + } + + public Table2da getMap() { + return mapTable; + } + + public int getAreaCount() { + return mapTable.getRowCount(); + } + + public VirtualAreaEntry getAreaEntry(int index) throws IndexOutOfBoundsException { + if (index < 0 || index >= getAreaCount()) { + throw new IndexOutOfBoundsException("Index out of bounds: " + index); + } + + return areaCache.computeIfAbsent(index, idx -> { + VirtualAreaEntry retVal = null; + try { + retVal = new VirtualAreaEntry(this, idx); + } catch (Exception e) { + } + return retVal; + }); + } + + @Override + public String toString() { + return "VirtualMapEntry [mapTable=" + mapTable + "]"; + } + + /** Validates the table content. */ + private void validate() throws Exception { + final int colCount = 15; + for (int row = 0, rowCount = mapTable.getRowCount(); row < rowCount; row++) { + if (mapTable.getColCount(row) < colCount) { + throw new Exception("Incomplete definition at row " + row); + } + + for (int col = 0; col < colCount; col++) { + final String value = mapTable.get(row, col); + switch (col) { + case 1: + // validate ARE resref + if (!ResourceFactory.resourceExists(value + ".ARE")) { + throw new Exception("Resource does not exist: " + value); + } + break; + case 2: + // ignored + break; + case 9: + // validate 2DA resref + if (!ResourceFactory.resourceExists(value + ".2DA")) { + throw new Exception("Resource does not exist: " + value); + } + break; + default: + // validate number + Integer.parseInt(value); + } + } + } + } + + /** + * Returns the worldmap definition table if available. + *

+ * Note: Map definitions are only available for the default worldmap on BG2:SoA. + *

+ * + * @param wmpResource The worldmap resource. + * @return {@link ResourceEntry} instance of the map definition table if applicable, {@code null} otherwise. + */ + private static ResourceEntry getMapResourceEntry(ResourceEntry wmpResource) { + ResourceEntry retVal = null; + if ("WORLDMAP.WMP".equalsIgnoreCase(wmpResource.getResourceName())) { + switch (Profile.getGame()) { + case BG2ToB: + case BG2EE: + case BGT: + case EET: + retVal = ResourceFactory.getResourceEntry("XNEWAREA.2DA"); + break; + default: + } + } + return retVal; + } +} \ No newline at end of file diff --git a/src/org/infinity/resource/wmp/viewer/VirtualStructEntry.java b/src/org/infinity/resource/wmp/viewer/VirtualStructEntry.java new file mode 100644 index 000000000..c579c06fe --- /dev/null +++ b/src/org/infinity/resource/wmp/viewer/VirtualStructEntry.java @@ -0,0 +1,138 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.wmp.viewer; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.infinity.resource.AbstractStruct; +import org.infinity.resource.StructEntry; +import org.infinity.resource.key.BufferedResourceEntry; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.resource.text.PlainTextResource; + +/** + * Common base class for table-based worldmap area definitions that provides a minimal {@link StructEntry} + * implementation. + */ +public abstract class VirtualStructEntry extends PlainTextResource implements StructEntry { + private static final String EMPTY_TABLE = + "2DA V1.0\n*\nAREA SCRIPT FLAGS ICON LOCX LOCY LABEL NAME LINK_2DA N_LINKS E_LINKS S_LINKS W_LINKS LINKS_TO\n"; + protected static final ResourceEntry EMPTY_RESOURCE = + new BufferedResourceEntry(ByteBuffer.wrap(EMPTY_TABLE.getBytes()), "NONE.2DA"); + + protected VirtualStructEntry(ResourceEntry entry) throws Exception { + super(Objects.nonNull(entry) ? entry : EMPTY_RESOURCE); + } + + /** Returns whether the class instance holds an empty placeholder entry. */ + public boolean isEmpty() { + return (getResourceEntry() == EMPTY_RESOURCE); + } + + @Override + public StructEntry clone() throws CloneNotSupportedException { + throw new CloneNotSupportedException(); + } + + @Override + public int compareTo(StructEntry o) { + return (o == this) ? 1 : 0; + } + + @Override + public void write(OutputStream os) throws IOException { + throw new IOException("Unsupported operation"); + } + + @Override + public int read(ByteBuffer buffer, int offset) throws Exception { + return (getResourceEntry() != null) ? (int) getResourceEntry().getResourceSize() : 0; + } + + @Override + public void copyNameAndOffset(StructEntry fromEntry) { + } + + @Override + public String getName() { + return (getResourceEntry() != null) ? getResourceEntry().getResourceName() : "NONE"; + } + + @Override + public void setName(String newName) { + } + + @Override + public int getOffset() { + return 0; + } + + @Override + public AbstractStruct getParent() { + return null; + } + + @Override + public int getSize() { + return (getResourceEntry() != null) ? (int) getResourceEntry().getResourceSize() : 0; + } + + @Override + public ByteBuffer getDataBuffer() { + try { + if (getResourceEntry() != null) { + getResourceEntry().getResourceBuffer(); + } + } catch (Exception e) { + } + return null; + } + + @Override + public List getStructChain() { + final List list = new ArrayList<>(); + list.add(this); + return list; + } + + @Override + public void setOffset(int newoffset) { + } + + @Override + public void setParent(AbstractStruct parent) { + } + + @Override + public int hashCode() { + return getResourceEntry().hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (obj instanceof VirtualStructEntry) { + final VirtualStructEntry vse = (VirtualStructEntry)obj; + return getResourceEntry().equals(vse.getResourceEntry()); + } else { + return false; + } + } + + @Override + public String toString() { + return getName(); + } +} \ No newline at end of file diff --git a/src/org/infinity/resource/wmp/viewer/WmpAreaInfo.java b/src/org/infinity/resource/wmp/viewer/WmpAreaInfo.java new file mode 100644 index 000000000..67e38b0c2 --- /dev/null +++ b/src/org/infinity/resource/wmp/viewer/WmpAreaInfo.java @@ -0,0 +1,272 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.wmp.viewer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsReference; +import org.infinity.datatype.IsTextual; +import org.infinity.resource.Closeable; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.StructEntry; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.resource.wmp.AreaEntry; +import org.infinity.resource.wmp.AreaLink; +import org.infinity.resource.wmp.AreaLinkEast; +import org.infinity.resource.wmp.AreaLinkNorth; +import org.infinity.resource.wmp.AreaLinkSouth; +import org.infinity.resource.wmp.AreaLinkWest; +import org.infinity.resource.wmp.viewer.ViewerMap.Direction; + +public class WmpAreaInfo implements Closeable { + private final List linksList = new ArrayList<>(); + + private final WmpMapInfo mapInfo; + private final AreaEntry areaEntry; + private final VirtualAreaEntry virtualAreaEntry; + + private ResourceEntry currentArea; + private String originalArea; + private String scriptName; + private int flags; + private int iconIndex; + private int locationX; + private int locationY; + private int nameStrref; + private int tooltipStrref; + private String loadingImage; + + public WmpAreaInfo(WmpMapInfo parent, AreaEntry areaEntry) throws Exception { + this.mapInfo = Objects.requireNonNull(parent); + this.areaEntry = Objects.requireNonNull(areaEntry); + this.virtualAreaEntry = null; + init(); + } + + public WmpAreaInfo(WmpMapInfo parent, VirtualAreaEntry areaEntry) throws Exception { + this.mapInfo = Objects.requireNonNull(parent); + this.virtualAreaEntry = areaEntry; + this.areaEntry = null; + init(); + } + + @Override + public void close() throws Exception { + while (!linksList.isEmpty()) { + linksList.remove(linksList.size() - 1).close(); + } + } + + /** Discards existing and reinitializes new map data. */ + public void reset() throws Exception { + close(); + init(); + } + + public WmpMapInfo getParent() { + return mapInfo; + } + + /** Returns the index of this area in the area list of the parent map structure. */ + public int getAreaIndex() { + for (int i = 0, count = mapInfo.getAreaList().size(); i < count; i++) { + if (mapInfo.getAreaList().get(i) == this) { + return i; + } + } + return mapInfo.getAreaList().size(); + } + + public AreaEntry getAreaEntry() { + return areaEntry; + } + + public VirtualAreaEntry getVirtualAreaEntry() { + return virtualAreaEntry; + } + + public ResourceEntry getCurrentArea() { + return currentArea; + } + + public String getOriginalArea() { + return originalArea; + } + + public String getScriptName() { + return scriptName; + } + + public int getFlags() { + return flags; + } + + public int getIconIndex() { + return iconIndex; + } + + public int getLocationX() { + return locationX; + } + + public int getLocationY() { + return locationY; + } + + public int getAreaNameStrref() { + return nameStrref; + } + + public int getAreaTooltipStrref() { + return tooltipStrref; + } + + public String getLoadImage() { + return loadingImage; + } + + public List getLinksList() { + return Collections.unmodifiableList(linksList); + } + + @Override + public String toString() { + return "WmpAreaInfo [currentArea=" + currentArea + ", originalArea=" + originalArea + ", scriptName=" + scriptName + + ", flags=" + flags + ", iconIndex=" + iconIndex + ", locationX=" + locationX + ", locationY=" + locationY + + ", nameStrref=" + nameStrref + ", tooltipStrref=" + tooltipStrref + ", loadingImage=" + loadingImage + + ", areaEntry=" + areaEntry + ", virtualAreaEntry=" + virtualAreaEntry + "]"; + } + + private void init() throws Exception { + if (areaEntry != null) { + initStruct(); + } else if (virtualAreaEntry != null) { + initTable(); + } else { + throw new Exception("No area definitions available"); + } + } + + private void initStruct() throws Exception { + final String resName = ((IsReference)areaEntry.getAttribute(AreaEntry.WMP_AREA_CURRENT)).getResourceName(); + currentArea = ResourceFactory.getResourceEntry(resName); + originalArea = ((IsTextual)areaEntry.getAttribute(AreaEntry.WMP_AREA_ORIGINAL)).getText(); + scriptName = ((IsTextual)areaEntry.getAttribute(AreaEntry.WMP_AREA_SCRIPT_NAME)).getText(); + flags = ((IsNumeric)areaEntry.getAttribute(AreaEntry.WMP_AREA_FLAGS)).getValue(); + iconIndex = ((IsNumeric)areaEntry.getAttribute(AreaEntry.WMP_AREA_ICON_INDEX)).getValue(); + locationX = ((IsNumeric)areaEntry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_X)).getValue(); + locationY = ((IsNumeric)areaEntry.getAttribute(AreaEntry.WMP_AREA_COORDINATE_Y)).getValue(); + nameStrref = ((IsNumeric)areaEntry.getAttribute(AreaEntry.WMP_AREA_NAME)).getValue(); + tooltipStrref = ((IsNumeric)areaEntry.getAttribute(AreaEntry.WMP_AREA_TOOLTIP)).getValue(); + loadingImage = ((IsTextual)areaEntry.getAttribute(AreaEntry.WMP_AREA_LOADING_IMAGE)).getText(); + + for (final Direction dir : Direction.values()) { + Class clsLink = null; + switch (dir) { + case NORTH: + clsLink = AreaLinkNorth.class; + break; + case WEST: + clsLink = AreaLinkWest.class; + break; + case SOUTH: + clsLink = AreaLinkSouth.class; + break; + case EAST: + clsLink = AreaLinkEast.class; + break; + default: + } + for (final StructEntry se : areaEntry.getFields(clsLink)) { + if (se instanceof AreaLink) { + final WmpLinkInfo wli = new WmpLinkInfo(this, dir, (AreaLink)se); + linksList.add(wli); + } + } + } + } + + private void initTable() throws Exception { + currentArea = virtualAreaEntry.getAreaResource(); + originalArea = currentArea.getResourceRef(); + scriptName = virtualAreaEntry.getAreaScript(); + flags = virtualAreaEntry.getAreaFlags(); + iconIndex = virtualAreaEntry.getAreaIconIndex(); + locationX = virtualAreaEntry.getAreaLocationX(); + locationY = virtualAreaEntry.getAreaLocationY(); + nameStrref = virtualAreaEntry.getAreaLabelStrref(); + tooltipStrref = virtualAreaEntry.getAreaNameStrref(); + + // initializing travel links + int startIndex = 0; + for (final Direction dir : new Direction[] { Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST }) { + int linksCount = 0; + switch (dir) { + case NORTH: + linksCount = virtualAreaEntry.getNumAreaLinksNorth(); + break; + case EAST: + linksCount = virtualAreaEntry.getNumAreaLinksEast(); + break; + case SOUTH: + linksCount = virtualAreaEntry.getNumAreaLinksSouth(); + break; + case WEST: + linksCount = virtualAreaEntry.getNumAreaLinksWest(); + break; + default: + } + + for (int i = 0; i < linksCount; i++) { + final WmpLinkInfo wli = new WmpLinkInfo(this, dir, virtualAreaEntry, startIndex + i); + linksList.add(wli); + } + + startIndex += linksCount; + } + + // initializing return links + final int linksTo = virtualAreaEntry.getNumAreaLinksTo(); + final List areas = mapInfo.getAreaList(); + for (int i = 0; i < linksTo; i++) { + final int rowIdx = startIndex + i; + + final String srcArea = virtualAreaEntry.getLinkArea(rowIdx).getResourceRef(); + final WmpAreaInfo srcAreaInfo = areas + .stream() + .filter(wai -> srcArea.equalsIgnoreCase(wai.currentArea.getResourceRef())) + .findFirst() + .orElse(null); + if (srcAreaInfo != null) { + final int dirValueTo = virtualAreaEntry.getLinkEntrance(rowIdx); + Direction dirTo = null; + switch (dirValueTo) { + case 0: + dirTo = Direction.NORTH; + break; + case 1: + dirTo = Direction.EAST; + break; + case 2: + dirTo = Direction.SOUTH; + break; + case 3: + dirTo = Direction.WEST; + break; + default: + } + + if (dirTo != null) { + final WmpLinkInfo wli = new WmpLinkInfo(srcAreaInfo, dirTo, virtualAreaEntry, rowIdx); + srcAreaInfo.linksList.add(wli); + } + } + } + } +} \ No newline at end of file diff --git a/src/org/infinity/resource/wmp/viewer/WmpLinkInfo.java b/src/org/infinity/resource/wmp/viewer/WmpLinkInfo.java new file mode 100644 index 000000000..494c2abbd --- /dev/null +++ b/src/org/infinity/resource/wmp/viewer/WmpLinkInfo.java @@ -0,0 +1,165 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.wmp.viewer; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsTextual; +import org.infinity.resource.Closeable; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.resource.wmp.AreaLink; +import org.infinity.resource.wmp.viewer.ViewerMap.Direction; + +public class WmpLinkInfo implements Closeable { + private final List randomEncounterAreaList = new ArrayList<>(); + + private final WmpAreaInfo areaInfo; + private final Direction dir; + private final AreaLink link; + private final VirtualAreaEntry virtualAreaEntry; + private final int virtualLinkIndex; + + private int targetAreaIndex; + private String targetEntrance; + private int distanceScale; + private int defaultEntrance; + private int randomEncounterProbability; + + public WmpLinkInfo(WmpAreaInfo parent, Direction dir, AreaLink link) throws Exception { + this.areaInfo = Objects.requireNonNull(parent); + this.dir = Objects.requireNonNull(dir); + this.link = Objects.requireNonNull(link); + this.virtualAreaEntry = null; + this.virtualLinkIndex = -1; + init(); + } + + public WmpLinkInfo(WmpAreaInfo parent, Direction dir, VirtualAreaEntry areaEntry, int linkIndex) throws Exception { + this.areaInfo = Objects.requireNonNull(parent); + this.dir = Objects.requireNonNull(dir); + this.virtualAreaEntry = Objects.requireNonNull(areaEntry); + this.virtualLinkIndex = linkIndex; + this.link = null; + init(); + } + + @Override + public void close() throws Exception { + randomEncounterAreaList.clear(); + } + + /** Discards existing and reinitializes new map data. */ + public void reset() throws Exception { + close(); + init(); + } + + public WmpAreaInfo getParent() { + return areaInfo; + } + + public Direction getDirection() { + return dir; + } + + public AreaLink getLink() { + return link; + } + + public int getTargetAreaIndex() { + return targetAreaIndex; + } + + public String getTargetEntrance() { + return targetEntrance; + } + + public int getDistanceScale() { + return distanceScale; + } + + public int getDefaultEntrance() { + return defaultEntrance; + } + + public int getRandomEncounterProbability() { + return randomEncounterProbability; + } + + public int getRandomEncounterAreaCount() { + return randomEncounterAreaList.size(); + } + + public ResourceEntry getRandomEncounterArea(int index) throws IndexOutOfBoundsException { + return randomEncounterAreaList.get(index); + } + + @Override + public String toString() { + return "WmpLinkInfo [dir=" + dir + ", link=" + link + ", virtualAreaEntry=" + virtualAreaEntry + + ", virtualLinkIndex=" + virtualLinkIndex + ", targetAreaIndex=" + targetAreaIndex + ", targetEntrance=" + + targetEntrance + ", distanceScale=" + distanceScale + ", defaultEntrance=" + defaultEntrance + + ", randomEncounterProbability=" + randomEncounterProbability + ", randomEncounterAreaList=" + + randomEncounterAreaList + "]"; + } + + private void init() throws Exception { + if (link != null) { + initStruct(); + } else if (virtualAreaEntry != null) { + initTable(); + } else { + throw new Exception("No link definitions available"); + } + } + + private void initStruct() throws Exception { + targetAreaIndex = ((IsNumeric)link.getAttribute(AreaLink.WMP_LINK_TARGET_AREA)).getValue(); + targetEntrance = ((IsTextual)link.getAttribute(AreaLink.WMP_LINK_TARGET_ENTRANCE)).getText(); + distanceScale = ((IsNumeric)link.getAttribute(AreaLink.WMP_LINK_DISTANCE_SCALE)).getValue(); + defaultEntrance = ((IsNumeric)link.getAttribute(AreaLink.WMP_LINK_DEFAULT_ENTRANCE)).getValue(); + + randomEncounterProbability = + ((IsNumeric)link.getAttribute(AreaLink.WMP_LINK_RANDOM_ENCOUNTER_PROBABILITY)).getValue(); + for (int i = 0; i < 5; i++) { + final String resref = + ((IsTextual)link.getAttribute(String.format(AreaLink.WMP_LINK_RANDOM_ENCOUNTER_AREA_FMT, i + 1))).getText(); + if (!resref.isEmpty()) { + final ResourceEntry entry = ResourceFactory.getResourceEntry(resref + ".ARE"); + if (entry != null) { + randomEncounterAreaList.add(entry); + } + } + } + } + + private void initTable() throws Exception { + final boolean isLinkTo = virtualLinkIndex >= virtualAreaEntry.getFirstAreaLinkTo(); + + final ResourceEntry areaResource = + isLinkTo ? virtualAreaEntry.getAreaResource() : virtualAreaEntry.getLinkArea(virtualLinkIndex); + int areaIndex = getParent().getParent().indexOfArea(areaResource.getResourceRef()); + if (areaIndex < 0) { + // about to be added later + areaIndex = getParent().getParent().getAreaList().size(); + } + targetAreaIndex = areaIndex; + targetEntrance = virtualAreaEntry.getLinkEntryPoint(virtualLinkIndex); + distanceScale = virtualAreaEntry.getLinkDistanceScale(virtualLinkIndex); + defaultEntrance = virtualAreaEntry.getLinkFlags(virtualLinkIndex); + + randomEncounterProbability = virtualAreaEntry.getLinkEncounterProbability(virtualLinkIndex); + for (int i = 0; i < 5; i++) { + final ResourceEntry entry = virtualAreaEntry.getLinkEncounter(virtualLinkIndex, 0); + if (entry != null) { + randomEncounterAreaList.add(entry); + } + } + } +} \ No newline at end of file diff --git a/src/org/infinity/resource/wmp/viewer/WmpMapInfo.java b/src/org/infinity/resource/wmp/viewer/WmpMapInfo.java new file mode 100644 index 000000000..fc465d4c0 --- /dev/null +++ b/src/org/infinity/resource/wmp/viewer/WmpMapInfo.java @@ -0,0 +1,228 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.wmp.viewer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.infinity.datatype.IsNumeric; +import org.infinity.datatype.IsReference; +import org.infinity.resource.Closeable; +import org.infinity.resource.ResourceFactory; +import org.infinity.resource.StructEntry; +import org.infinity.resource.graphics.BamDecoder; +import org.infinity.resource.graphics.BamDecoder.BamControl; +import org.infinity.resource.key.ResourceEntry; +import org.infinity.resource.wmp.AreaEntry; +import org.infinity.resource.wmp.MapEntry; +import org.infinity.resource.wmp.WmpResource; + +public class WmpMapInfo implements Closeable { + private final List areasList = new ArrayList<>(); + + private final MapEntry mapEntry; + private final boolean autoLoadVirtualMap; + + private VirtualMapEntry virtualMapEntry; + private ResourceEntry backgroundResource; + private ResourceEntry iconsResource; + private BamDecoder mapIcons; + private BamControl mapIconsControl; + private int width; + private int height; + private int id; + private int nameStrref; + private int centerX; + private int centerY; + + public WmpMapInfo(MapEntry mapEntry) throws Exception { + this(mapEntry, true); + } + + public WmpMapInfo(MapEntry mapEntry, boolean includeVirtualMap) throws Exception { + this.mapEntry = Objects.requireNonNull(mapEntry); + this.autoLoadVirtualMap = includeVirtualMap; + init(); + } + + @Override + public void close() throws Exception { + if (mapIcons != null) { + mapIcons.close(); + } + mapIcons = null; + mapIconsControl = null; + virtualMapEntry = null; + while (areasList.isEmpty()) { + areasList.remove(areasList.size() - 1).close(); + } + } + + /** Discards existing and reinitializes new map data. */ + public void reset() throws Exception { + close(); + init(); + } + + /** + * Imports definitions from a {@link VirtualMapEntry} instance. + * + * @param vme The {@link VirtualMapEntry} instance to import. + */ + public void loadVirtualMap(VirtualMapEntry vme) throws Exception { + if (vme != null && virtualMapEntry == null) { + virtualMapEntry = vme; + for (int i = 0, count = vme.getAreaCount(); i < count; i++) { + final WmpAreaInfo wai = new WmpAreaInfo(this, vme.getAreaEntry(i)); + areasList.add(wai); + } + } + } + + /** + * Returns a list of available virtual area definitions. + * + * @return List of {@link VirtualAreaEntry} instances available for this map. + */ + public List getVirtualAreas() { + final List retVal = new ArrayList<>(); + if (virtualMapEntry != null) { + for (int i = 0, count = virtualMapEntry.getAreaCount(); i < count; i++) { + retVal.add(virtualMapEntry.getAreaEntry(i)); + } + } + return retVal; + } + + public MapEntry getMapEntry() { + return mapEntry; + } + + public ResourceEntry getBackgroundResource() { + return backgroundResource; + } + + public ResourceEntry getMapIconsResource() { + return iconsResource; + } + + public BamDecoder getMapIcons() { + return mapIcons; + } + + public BamControl getMapIconsControl() { + return mapIconsControl; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public int getMapId() { + return id; + } + + public int getMapNameStrref() { + return nameStrref; + } + + public int getCenterX() { + return centerX; + } + + public int getCenterY() { + return centerY; + } + + /** Returns a read-only list of the defined areas for this map. */ + public List getAreaList() { + return Collections.unmodifiableList(areasList); + } + + /** + * Returns the index of the area in the areas list. + * + * @param areaName Name of the area (with or without file extension). + * @return Index of the matching area if available, -1 otherwise. + */ + public int indexOfArea(String areaName) { + if (areaName != null) { + if (areaName.toUpperCase().endsWith(".ARE")) { + areaName = areaName.substring(0, areaName.length() - 4); + } + for (int i = 0, count = areasList.size(); i < count; i++) { + final ResourceEntry curArea = areasList.get(i).getCurrentArea(); + if (curArea != null && areaName.equalsIgnoreCase(areasList.get(i).getCurrentArea().getResourceRef())) { + return i; + } + } + } + return -1; + } + + /** + * Returns the index of the area in the areas list. + * + * @param areaEntry {@link StructEntry} instance of the area definition. + * Supported classes: {@link AreaEntry}, {@link VirtualAreaEntry} + * @return Index of the matching area if available, -1 otherwise. + */ + public int indexOfArea(StructEntry areaEntry) { + int retVal = -1; + if (areaEntry != null) { + for (int i = 0, count = areasList.size(); i < count; i++) { + final WmpAreaInfo wai = areasList.get(i); + if (Objects.equals(wai.getAreaEntry(), areaEntry) || + Objects.equals(wai.getVirtualAreaEntry(), areaEntry)) { + return i; + } + } + } + return retVal; + } + + @Override + public String toString() { + return "WmpMapInfo [backgroundResource=" + backgroundResource + ", iconsResource=" + iconsResource + ", mapIcons=" + + mapIcons + ", mapIconsControl=" + mapIconsControl + ", width=" + width + ", height=" + height + ", id=" + id + + ", nameStrref=" + nameStrref + ", centerX=" + centerX + ", centerY=" + centerY + ", autoLoadVirtualMap=" + + autoLoadVirtualMap + ", mapEntry=" + mapEntry + ", virtualMapEntry=" + virtualMapEntry + "]"; + } + + private void init() throws Exception { + String resName = ((IsReference)mapEntry.getAttribute(MapEntry.WMP_MAP_RESREF)).getResourceName(); + backgroundResource = ResourceFactory.getResourceEntry(resName); + + resName = ((IsReference)mapEntry.getAttribute(MapEntry.WMP_MAP_ICONS)).getResourceName(); + iconsResource = ResourceFactory.getResourceEntry(resName); + mapIcons = BamDecoder.loadBam(iconsResource); + mapIconsControl = mapIcons.createControl(); + + width = ((IsNumeric)mapEntry.getAttribute(MapEntry.WMP_MAP_WIDTH)).getValue(); + height = ((IsNumeric)mapEntry.getAttribute(MapEntry.WMP_MAP_HEIGHT)).getValue(); + id = ((IsNumeric)mapEntry.getAttribute(MapEntry.WMP_MAP_ID)).getValue(); + nameStrref = ((IsNumeric)mapEntry.getAttribute(MapEntry.WMP_MAP_NAME)).getValue(); + centerX = ((IsNumeric)mapEntry.getAttribute(MapEntry.WMP_MAP_CENTER_X)).getValue(); + centerY = ((IsNumeric)mapEntry.getAttribute(MapEntry.WMP_MAP_CENTER_Y)).getValue(); + + for (final StructEntry se : mapEntry.getFields(AreaEntry.class)) { + if (se instanceof AreaEntry) { + final AreaEntry ae = (AreaEntry)se; + areasList.add(new WmpAreaInfo(this, ae)); + } + } + + if (autoLoadVirtualMap && mapEntry.getParent() instanceof WmpResource) { + final VirtualMapEntry vme = new VirtualMapEntry(mapEntry.getParent().getResourceEntry()); + loadVirtualMap(vme); + } + } +} \ No newline at end of file