From 5a527541c821470415fa76c3091143b5d80af2f7 Mon Sep 17 00:00:00 2001 From: Argent77 <4519923+Argent77@users.noreply.github.com> Date: Fri, 28 Jul 2023 20:26:49 +0200 Subject: [PATCH] Area Viewer: Add new layer "Impeded Door Cells" Experimental implementation: may have some performance issues if many cell blocks are defined. --- .../gui/layeritem/AbstractLayerItem.java | 26 +- .../infinity/gui/layeritem/IconLayerItem.java | 27 +- .../gui/layeritem/ShapedLayerItem.java | 13 + .../resource/are/viewer/AreaViewer.java | 18 +- .../are/viewer/BasicCompositeLayer.java | 57 ++++ .../resource/are/viewer/BasicTargetLayer.java | 68 ----- .../resource/are/viewer/LayerContainer.java | 5 +- .../resource/are/viewer/LayerDoor.java | 18 +- .../resource/are/viewer/LayerDoorCells.java | 91 ++++++ .../resource/are/viewer/LayerManager.java | 15 +- .../are/viewer/LayerObjectAreActor.java | 8 +- .../are/viewer/LayerObjectContainer.java | 2 +- .../resource/are/viewer/LayerObjectDoor.java | 20 +- .../are/viewer/LayerObjectDoorCells.java | 263 ++++++++++++++++++ .../are/viewer/LayerObjectRegion.java | 2 +- .../resource/are/viewer/LayerRegion.java | 5 +- .../resource/are/viewer/Settings.java | 5 + .../resource/are/viewer/ViewerConstants.java | 2 + 18 files changed, 548 insertions(+), 97 deletions(-) create mode 100644 src/org/infinity/resource/are/viewer/BasicCompositeLayer.java delete mode 100644 src/org/infinity/resource/are/viewer/BasicTargetLayer.java create mode 100644 src/org/infinity/resource/are/viewer/LayerDoorCells.java create mode 100644 src/org/infinity/resource/are/viewer/LayerObjectDoorCells.java diff --git a/src/org/infinity/gui/layeritem/AbstractLayerItem.java b/src/org/infinity/gui/layeritem/AbstractLayerItem.java index 25c983a0b..24a08c1d4 100644 --- a/src/org/infinity/gui/layeritem/AbstractLayerItem.java +++ b/src/org/infinity/gui/layeritem/AbstractLayerItem.java @@ -33,20 +33,35 @@ public enum ItemState { private final Vector actionListener = new Vector<>(); private final Vector itemStateListener = new Vector<>(); private final Viewable viewable; + private final Point center; + private final int id; + private Object objData; private ItemState itemState; - private final Point center; /** * Initialize object with a associated viewable object and message for both info box and quick info. + * The item is automatically associated with the primary sublayer. * * @param viewable Associated Viewable object * @param tooltip A short text message shown as tooltip or menu item text */ public AbstractLayerItem(Viewable viewable, String tooltip) { + this(viewable, tooltip, 0); + } + + /** + * Initialize object with a associated viewable object and message for both info box and quick info. + * + * @param viewable Associated Viewable object + * @param tooltip A short text message shown as tooltip or menu item text + * @param id Identifier that associates this item with a specific sublayer. + */ + public AbstractLayerItem(Viewable viewable, String tooltip, int id) { this.viewable = viewable; this.itemState = ItemState.NORMAL; this.center = new Point(); + this.id = id; if (viewable instanceof StructEntry) { setToolTipText(((StructEntry) viewable).getName() + ": " + tooltip); } else { @@ -115,6 +130,15 @@ public ItemState getItemState() { return itemState; } + /** + * Returns the sublayer identifier associated with this layer item. + * + * @return Sublayer iIdentifier for this layer item. + */ + public int getId() { + return id; + } + /** * Attaches a custom data object to this layer item. * diff --git a/src/org/infinity/gui/layeritem/IconLayerItem.java b/src/org/infinity/gui/layeritem/IconLayerItem.java index 1c7af85c3..37362d5cf 100644 --- a/src/org/infinity/gui/layeritem/IconLayerItem.java +++ b/src/org/infinity/gui/layeritem/IconLayerItem.java @@ -45,6 +45,17 @@ public class IconLayerItem extends AbstractLayerItem implements LayerItemListene * @param message An arbitrary text message */ public IconLayerItem(Viewable viewable, String message) { + this(viewable, message, 0); + } + + /** + * Initialize object with an associated Viewable and an additional text message. + * + * @param viewable Associated Viewable object + * @param message An arbitrary text message + * @param id Identifier that associates this item with a specific sublayer. + */ + public IconLayerItem(Viewable viewable, String message, int id) { this(viewable, message, null, null); } @@ -58,7 +69,21 @@ public IconLayerItem(Viewable viewable, String message) { * @param center Logical center position within the icon */ public IconLayerItem(Viewable viewable, String tooltip, Image image, Point center) { - super(viewable, tooltip); + this(viewable, tooltip, 0, image, center); + } + + /** + * Initialize object with an associated Viewable, an additional text message, an image for the visual representation + * and a locical center position within the icon. + * + * @param viewable Associated Viewable object + * @param tooltip A short text message shown as tooltip or menu item text + * @param id Identifier that associates this item with a specific sublayer. + * @param image The image to display + * @param center Logical center position within the icon + */ + public IconLayerItem(Viewable viewable, String tooltip, int id, Image image, Point center) { + super(viewable, tooltip, id); setLayout(new BorderLayout()); // preparing icon rcCanvas = new FrameCanvas(this); diff --git a/src/org/infinity/gui/layeritem/ShapedLayerItem.java b/src/org/infinity/gui/layeritem/ShapedLayerItem.java index 98549082d..117f1b1df 100644 --- a/src/org/infinity/gui/layeritem/ShapedLayerItem.java +++ b/src/org/infinity/gui/layeritem/ShapedLayerItem.java @@ -44,6 +44,19 @@ public class ShapedLayerItem extends AbstractLayerItem implements LayerItemListe * @param shape The shape to display */ public ShapedLayerItem(Viewable viewable, String tooltip, Shape shape) { + this(viewable, tooltip, 0, shape); + } + + /** + * Initialize object with an associated Viewable, an additional text message and a shape for the visual + * representation. + * + * @param viewable Associated Viewable object + * @param tooltip A short text message shown as tooltip or menu item text + * @param id Identifier that associates this item with a specific sublayer. + * @param shape The shape to display + */ + public ShapedLayerItem(Viewable viewable, String tooltip, int id, Shape shape) { super(viewable, tooltip); setLayout(new BorderLayout()); label = new ShapeLabel(this); diff --git a/src/org/infinity/resource/are/viewer/AreaViewer.java b/src/org/infinity/resource/are/viewer/AreaViewer.java index c718f592c..b21d8a1fb 100644 --- a/src/org/infinity/resource/are/viewer/AreaViewer.java +++ b/src/org/infinity/resource/are/viewer/AreaViewer.java @@ -1640,7 +1640,7 @@ private void updateRegionTargets() { JCheckBox cb = cbLayers[LayerManager.getLayerTypeIndex(LayerType.REGION)]; cbLayerRegionTarget.setEnabled(cb.isSelected() && cb.isEnabled()); boolean state = cbLayerRegionTarget.isEnabled() && cbLayerRegionTarget.isSelected(); - layer.setIconLayerEnabled(state); + layer.setLayerEnabled(LayerRegion.LAYER_ICONS_TARGET, state); } else { cbLayerRegionTarget.setEnabled(false); } @@ -1659,7 +1659,7 @@ private void updateContainerTargets() { JCheckBox cb = cbLayers[LayerManager.getLayerTypeIndex(LayerType.CONTAINER)]; cbLayerContainerTarget.setEnabled(cb.isSelected() && cb.isEnabled()); boolean state = cbLayerContainerTarget.isEnabled() && cbLayerContainerTarget.isSelected(); - layer.setIconLayerEnabled(state); + layer.setLayerEnabled(LayerContainer.LAYER_ICONS_TARGET, state); } else { cbLayerContainerTarget.setEnabled(false); } @@ -1678,7 +1678,7 @@ private void updateDoorTargets() { JCheckBox cb = cbLayers[LayerManager.getLayerTypeIndex(LayerType.DOOR)]; cbLayerDoorTarget.setEnabled(cb.isSelected() && cb.isEnabled()); boolean state = cbLayerDoorTarget.isEnabled() && cbLayerDoorTarget.isSelected(); - layer.setIconLayerEnabled(state); + layer.setLayerEnabled(LayerDoor.LAYER_ICONS_TARGET, state); } else { cbLayerDoorTarget.setEnabled(false); } @@ -1840,6 +1840,7 @@ private void addLayerItem(LayerStackingType layer, LayerObject object) { type = ViewerConstants.LAYER_ITEM_POLY; break; case DOOR: + case DOOR_CELLS: type = ViewerConstants.DOOR_ANY | ViewerConstants.LAYER_ITEM_POLY; break; case CONTAINER_TARGET: @@ -1900,6 +1901,7 @@ private void removeLayerItem(LayerStackingType layer, LayerObject object) { type = ViewerConstants.LAYER_ITEM_POLY; break; case DOOR: + case DOOR_CELLS: type = ViewerConstants.DOOR_ANY | ViewerConstants.LAYER_ITEM_POLY; break; case CONTAINER_TARGET: @@ -1948,6 +1950,7 @@ private void orderLayerItems() { type = ViewerConstants.LAYER_ITEM_POLY; break; case DOOR: + case DOOR_CELLS: type = ViewerConstants.DOOR_ANY | ViewerConstants.LAYER_ITEM_POLY; break; case CONTAINER_TARGET: @@ -2136,9 +2139,12 @@ private void applySettings() { // applying animation active override settings ((LayerAnimation) layerManager.getLayer(LayerType.ANIMATION)) .setRealAnimationActiveIgnored(Settings.OverrideAnimVisibility); - ((LayerContainer) layerManager.getLayer(LayerType.CONTAINER)).setIconLayerEnabled(Settings.ShowContainerTargets); - ((LayerDoor) layerManager.getLayer(LayerType.DOOR)).setIconLayerEnabled(Settings.ShowDoorTargets); - ((LayerRegion) layerManager.getLayer(LayerType.REGION)).setIconLayerEnabled(Settings.ShowRegionTargets); + ((LayerContainer) layerManager.getLayer(LayerType.CONTAINER)).setLayerEnabled(LayerContainer.LAYER_ICONS_TARGET, + Settings.ShowContainerTargets); + ((LayerDoor) layerManager.getLayer(LayerType.DOOR)).setLayerEnabled(LayerDoor.LAYER_ICONS_TARGET, + Settings.ShowDoorTargets); + ((LayerRegion) layerManager.getLayer(LayerType.REGION)).setLayerEnabled(LayerRegion.LAYER_ICONS_TARGET, + Settings.ShowRegionTargets); // applying interpolation settings to animations switch (Settings.InterpolationAnim) { diff --git a/src/org/infinity/resource/are/viewer/BasicCompositeLayer.java b/src/org/infinity/resource/are/viewer/BasicCompositeLayer.java new file mode 100644 index 000000000..c4963cf10 --- /dev/null +++ b/src/org/infinity/resource/are/viewer/BasicCompositeLayer.java @@ -0,0 +1,57 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.are.viewer; + +import java.util.HashMap; + +import org.infinity.gui.layeritem.AbstractLayerItem; +import org.infinity.resource.AbstractStruct; +import org.infinity.resource.are.viewer.ViewerConstants.LayerType; + +/** + * Specialized base class for layer-specific managers that consists of multiple sublayers. + * + * @param Type of the layer item in the manager + * @param Type of the resource that contains layer items + */ +public abstract class BasicCompositeLayer extends BasicLayer { + /** Predefined identifier for use with the primary sublayer. */ + public static final int LAYER_PRIMARY = 0; + + private final HashMap layerEnabled = new HashMap<>(); + + public BasicCompositeLayer(R parent, LayerType type, AreaViewer viewer) { + super(parent, type, viewer); + setLayerEnabled(LAYER_PRIMARY, true); + } + + public boolean isLayerVisible(int id) { + return isLayerVisible() && layerEnabled.getOrDefault(id, false); + } + + @Override + public void setLayerVisible(boolean visible) { + setVisibilityState(visible); + + getLayerObjects().stream().forEach(obj -> { + AbstractLayerItem[] items = obj.getLayerItems(); + for (final AbstractLayerItem item : items) { + final int id = item.getId(); + item.setVisible(isLayerVisible(id) && isLayerEnabled(id)); + } + }); + } + + public boolean isLayerEnabled(int id) { + return layerEnabled.getOrDefault(id, false); + } + + public void setLayerEnabled(int id, boolean enable) { + if (isLayerEnabled(id) != enable) { + layerEnabled.put(id, enable); + setLayerVisible(isLayerVisible(LAYER_PRIMARY) || isLayerVisible(id)); + } + } +} diff --git a/src/org/infinity/resource/are/viewer/BasicTargetLayer.java b/src/org/infinity/resource/are/viewer/BasicTargetLayer.java deleted file mode 100644 index 09eeb3cc9..000000000 --- a/src/org/infinity/resource/are/viewer/BasicTargetLayer.java +++ /dev/null @@ -1,68 +0,0 @@ -// Near Infinity - An Infinity Engine Browser and Editor -// Copyright (C) 2001 Jon Olav Hauglid -// See LICENSE.txt for license information - -package org.infinity.resource.are.viewer; - -import org.infinity.gui.layeritem.AbstractLayerItem; -import org.infinity.resource.AbstractStruct; -import org.infinity.resource.are.viewer.ViewerConstants.LayerType; - -/** - * - */ -public abstract class BasicTargetLayer extends BasicLayer { - private boolean polyEnabled = true; - private boolean iconsEnabled; - - public BasicTargetLayer(R parent, LayerType type, AreaViewer viewer) { - super(parent, type, viewer); - } - - @Override - public void setLayerVisible(boolean visible) { - setVisibilityState(visible); - - getLayerObjects().stream().forEach(obj -> { - AbstractLayerItem[] items = obj.getLayerItems(ViewerConstants.LAYER_ITEM_POLY); - for (final AbstractLayerItem item : items) { - item.setVisible(isPolyLayerVisible() && isPolyLayerEnabled()); - } - - items = obj.getLayerItems(ViewerConstants.LAYER_ITEM_ICON); - for (final AbstractLayerItem item : items) { - item.setVisible(isIconsLayerVisible() && isIconsLayerEnabled()); - } - }); - } - - public boolean isPolyLayerVisible() { - return isLayerVisible() && polyEnabled; - } - - public boolean isIconsLayerVisible() { - return isLayerVisible() && iconsEnabled; - } - - public boolean isPolyLayerEnabled() { - return polyEnabled; - } - - public void setPolyLayerEnabled(boolean enable) { - if (enable != polyEnabled) { - polyEnabled = enable; - setLayerVisible(isPolyLayerVisible()); - } - } - - public boolean isIconsLayerEnabled() { - return iconsEnabled; - } - - public void setIconLayerEnabled(boolean enable) { - if (enable != iconsEnabled) { - iconsEnabled = enable; - setLayerVisible(isPolyLayerVisible() || isIconsLayerVisible()); - } - } -} diff --git a/src/org/infinity/resource/are/viewer/LayerContainer.java b/src/org/infinity/resource/are/viewer/LayerContainer.java index 10fb1f78c..3662b8b48 100644 --- a/src/org/infinity/resource/are/viewer/LayerContainer.java +++ b/src/org/infinity/resource/are/viewer/LayerContainer.java @@ -13,7 +13,10 @@ /** * Manages container layer objects. */ -public class LayerContainer extends BasicTargetLayer { +public class LayerContainer extends BasicCompositeLayer { + /** Identifier for the target icons sublayer. */ + public static final int LAYER_ICONS_TARGET = 1; + private static final String AVAILABLE_FMT = "Containers: %d"; public LayerContainer(AreResource are, AreaViewer viewer) { diff --git a/src/org/infinity/resource/are/viewer/LayerDoor.java b/src/org/infinity/resource/are/viewer/LayerDoor.java index 94b4dfa06..76785fd05 100644 --- a/src/org/infinity/resource/are/viewer/LayerDoor.java +++ b/src/org/infinity/resource/are/viewer/LayerDoor.java @@ -14,7 +14,10 @@ /** * Manages door layer objects. */ -public class LayerDoor extends BasicTargetLayer { +public class LayerDoor extends BasicCompositeLayer { + /** Identifier for the target icons sublayer. */ + public static final int LAYER_ICONS_TARGET = 1; + private static final String AVAILABLE_FMT = "Doors: %d"; private boolean doorClosed; @@ -42,17 +45,24 @@ public void setLayerVisible(boolean visible) { getLayerObjects().stream().forEach(obj -> { AbstractLayerItem[] items = obj.getLayerItems(ViewerConstants.DOOR_OPEN | ViewerConstants.LAYER_ITEM_POLY); for (final AbstractLayerItem item : items) { - item.setVisible(isPolyLayerVisible() && isPolyLayerEnabled() && !doorClosed); + item.setVisible(isLayerVisible(LAYER_PRIMARY) && isLayerEnabled(LAYER_PRIMARY) && !doorClosed); } items = obj.getLayerItems(ViewerConstants.DOOR_CLOSED | ViewerConstants.LAYER_ITEM_POLY); for (final AbstractLayerItem item : items) { - item.setVisible(isPolyLayerVisible() && isPolyLayerEnabled() && doorClosed); + item.setVisible(isLayerVisible(LAYER_PRIMARY) && isLayerEnabled(LAYER_PRIMARY) && doorClosed); } items = obj.getLayerItems(ViewerConstants.LAYER_ITEM_ICON); for (final AbstractLayerItem item : items) { - item.setVisible(isIconsLayerVisible() && isIconsLayerEnabled()); + switch (item.getId()) { + case LAYER_ICONS_TARGET: + item.setVisible(isLayerVisible(LAYER_ICONS_TARGET) && isLayerEnabled(LAYER_ICONS_TARGET)); + break; + default: + item.setVisible(false); + System.out.println("Unknown layer id: " + item.getId()); + } } }); } diff --git a/src/org/infinity/resource/are/viewer/LayerDoorCells.java b/src/org/infinity/resource/are/viewer/LayerDoorCells.java new file mode 100644 index 000000000..cc60979cd --- /dev/null +++ b/src/org/infinity/resource/are/viewer/LayerDoorCells.java @@ -0,0 +1,91 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.are.viewer; + +import static org.infinity.resource.are.AreResource.ARE_NUM_DOORS; +import static org.infinity.resource.are.AreResource.ARE_OFFSET_DOORS; + +import org.infinity.gui.layeritem.AbstractLayerItem; +import org.infinity.resource.are.AreResource; +import org.infinity.resource.are.Door; + +/** + * Manages blocked cells of door layer objects. + */ +public class LayerDoorCells extends BasicLayer { + private static final String AVAILABLE_FMT = "Doors: %d"; + + private boolean doorClosed; + + public LayerDoorCells(AreResource are, AreaViewer viewer) { + super(are, ViewerConstants.LayerType.DOOR_CELLS, viewer); + loadLayer(); + } + + @Override + protected void loadLayer() { + loadLayerItems(ARE_OFFSET_DOORS, ARE_NUM_DOORS, Door.class, d -> new LayerObjectDoorCells(parent, d)); + } + + @Override + public String getAvailability() { + int cnt = getLayerObjectCount(); + return String.format(AVAILABLE_FMT, cnt); + } + + @Override + public void setLayerVisible(boolean visible) { + setVisibilityState(visible); + +// getLayerObjects().stream().forEach(obj -> { +// AbstractLayerItem[] items = obj.getLayerItems(ViewerConstants.DOOR_OPEN | ViewerConstants.LAYER_ITEM_ICON); +// for (final AbstractLayerItem item : items) { +// item.setVisible(isLayerVisible() && !doorClosed); +// } +// +// items = obj.getLayerItems(ViewerConstants.DOOR_CLOSED | ViewerConstants.LAYER_ITEM_ICON); +// for (final AbstractLayerItem item : items) { +// item.setVisible(isLayerVisible() && doorClosed); +// } +// }); + + getLayerObjects().stream().forEach(obj -> { + AbstractLayerItem[] items = obj.getLayerItems(ViewerConstants.DOOR_OPEN | ViewerConstants.LAYER_ITEM_POLY); + for (final AbstractLayerItem item : items) { + item.setVisible(isLayerVisible() && !doorClosed); + } + + items = obj.getLayerItems(ViewerConstants.DOOR_CLOSED | ViewerConstants.LAYER_ITEM_POLY); + for (final AbstractLayerItem item : items) { + item.setVisible(isLayerVisible() && doorClosed); + } + }); + } + + /** + * Returns the current state of doors. + * + * @return Either {@code ViewerConstants.DOOR_OPEN} or {@code ViewerConstants.DOOR_CLOSED}. + */ + public int getDoorState() { + return doorClosed ? ViewerConstants.DOOR_CLOSED : ViewerConstants.DOOR_OPEN; + } + + /** + * Sets the state of doors for all door layer objects. + * + * @param state The door state (either {@code ViewerConstants.DOOR_OPEN} or {@code ViewerConstants.DOOR_CLOSED}). + */ + public void setDoorState(int state) { + boolean isClosed = (state == ViewerConstants.DOOR_CLOSED); + if (isClosed != doorClosed) { + doorClosed = isClosed; + if (isLayerVisible()) { + setLayerVisible(isLayerVisible()); + } + } + } + +} diff --git a/src/org/infinity/resource/are/viewer/LayerManager.java b/src/org/infinity/resource/are/viewer/LayerManager.java index f24bad590..b2a0dbf86 100644 --- a/src/org/infinity/resource/are/viewer/LayerManager.java +++ b/src/org/infinity/resource/are/viewer/LayerManager.java @@ -29,7 +29,8 @@ public final class LayerManager { LayerType.REGION, LayerType.TRANSITION, LayerType.DOOR_POLY, - LayerType.WALL_POLY + LayerType.WALL_POLY, + LayerType.DOOR_CELLS }; private final EnumMap> layers = new EnumMap<>(LayerType.class); @@ -360,6 +361,7 @@ public int getDoorState() { */ public void setDoorState(int state) { ((LayerDoor) getLayer(LayerType.DOOR)).setDoorState(state); + ((LayerDoorCells) getLayer(LayerType.DOOR_CELLS)).setDoorState(state); ((LayerDoorPoly) getLayer(LayerType.DOOR_POLY)).setDoorState(state); } @@ -586,6 +588,7 @@ private void init(AreResource are, WedResource wed, boolean forced) { case CONTAINER: case AMBIENT: case DOOR: + case DOOR_CELLS: case ANIMATION: case AUTOMAP: case SPAWN_POINT: @@ -669,6 +672,16 @@ private int loadLayer(LayerType layer, boolean forced) { } break; } + case DOOR_CELLS: { + if (layers.containsKey(layer)) { + retVal = layers.get(layer).loadLayer(forced); + } else { + LayerDoorCells obj = new LayerDoorCells(are, getViewer()); + layers.put(layer, obj); + retVal = obj.getLayerObjectCount(); + } + break; + } case ANIMATION: { if (layers.containsKey(layer)) { retVal = layers.get(layer).loadLayer(forced); diff --git a/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java b/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java index a01cd2325..da664290b 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectAreActor.java @@ -34,11 +34,13 @@ public class LayerObjectAreActor extends LayerObjectActor { static { ICONS.put(Allegiance.GOOD, new Image[] { ViewerIcons.ICON_ITM_ARE_ACTOR_G_1.getIcon().getImage(), - ViewerIcons.ICON_ITM_ARE_ACTOR_G_2.getIcon().getImage() }); + ViewerIcons.ICON_ITM_ARE_ACTOR_G_2.getIcon().getImage() }); + ICONS.put(Allegiance.NEUTRAL, new Image[] { ViewerIcons.ICON_ITM_ARE_ACTOR_B_1.getIcon().getImage(), - ViewerIcons.ICON_ITM_ARE_ACTOR_B_2.getIcon().getImage() }); + ViewerIcons.ICON_ITM_ARE_ACTOR_B_2.getIcon().getImage() }); + ICONS.put(Allegiance.ENEMY, new Image[] { ViewerIcons.ICON_ITM_ARE_ACTOR_R_1.getIcon().getImage(), - ViewerIcons.ICON_ITM_ARE_ACTOR_R_2.getIcon().getImage() }); + ViewerIcons.ICON_ITM_ARE_ACTOR_R_2.getIcon().getImage() }); } private final Actor actor; diff --git a/src/org/infinity/resource/are/viewer/LayerObjectContainer.java b/src/org/infinity/resource/are/viewer/LayerObjectContainer.java index f3bc49963..1bb11059c 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectContainer.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectContainer.java @@ -190,7 +190,7 @@ private IconLayerItem createValidatedLayerItem(Point pt, String label, Image[] i IconLayerItem retVal = null; if (pt.x > 0 && pt.y > 0) { - retVal = new IconLayerItem(container, label, icons[0], CENTER); + retVal = new IconLayerItem(container, label, LayerContainer.LAYER_ICONS_TARGET, icons[0], CENTER); retVal.setLabelEnabled(Settings.ShowLabelContainerTargets); retVal.setName(getCategory()); retVal.setImage(AbstractLayerItem.ItemState.HIGHLIGHTED, icons[1]); diff --git a/src/org/infinity/resource/are/viewer/LayerObjectDoor.java b/src/org/infinity/resource/are/viewer/LayerObjectDoor.java index 838859dbd..aaee4035c 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectDoor.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectDoor.java @@ -65,10 +65,10 @@ public LayerObjectDoor(AreResource parent, Door door) { doorMap.put(Integer.valueOf(ViewerConstants.DOOR_OPEN), doorOpen); final DoorInfo doorClosed = new DoorInfo(); doorMap.put(Integer.valueOf(ViewerConstants.DOOR_CLOSED), doorClosed); - String label = null; + String name = null; try { - String attr = getAttributes(); - final String name = door.getAttribute(Door.ARE_DOOR_NAME).toString(); + String attr = getAttributes(this.door); + name = door.getAttribute(Door.ARE_DOOR_NAME).toString(); final int vOfs = ((IsNumeric) parent.getAttribute(AreResource.ARE_OFFSET_VERTICES)).getValue(); // processing opened state door @@ -81,7 +81,6 @@ public LayerObjectDoor(AreResource parent, Door door) { vNum = ((IsNumeric) door.getAttribute(Door.ARE_DOOR_NUM_VERTICES_CLOSED)).getValue(); doorClosed.setCoords(loadVertices(door, vOfs, 0, vNum, ClosedVertex.class)); - label = door.getAttribute(Door.ARE_DOOR_NAME).toString(); closePoint.x = ((IsNumeric) door.getAttribute(Door.ARE_DOOR_LOCATION_CLOSE_X)).getValue(); closePoint.y = ((IsNumeric) door.getAttribute(Door.ARE_DOOR_LOCATION_CLOSE_Y)).getValue(); openPoint.x = ((IsNumeric) door.getAttribute(Door.ARE_DOOR_LOCATION_OPEN_X)).getValue(); @@ -109,9 +108,9 @@ public LayerObjectDoor(AreResource parent, Door door) { info.setItem(item); } - itemIconClose = createValidatedLayerItem(closePoint, label, getIcons(ICONS_CLOSE)); - itemIconOpen = createValidatedLayerItem(openPoint, label, getIcons(ICONS_OPEN)); - itemIconLaunch = createValidatedLayerItem(launchPoint, label, getIcons(ICONS_LAUNCH)); + itemIconClose = createValidatedLayerItem(closePoint, name, getIcons(ICONS_CLOSE)); + itemIconOpen = createValidatedLayerItem(openPoint, name, getIcons(ICONS_OPEN)); + itemIconLaunch = createValidatedLayerItem(launchPoint, name, getIcons(ICONS_LAUNCH)); } @Override @@ -218,7 +217,10 @@ public void update(double zoomFactor) { } } - private String getAttributes() { + /** + * Returns a descriptive string for the specified {@link Door} structure. + */ + public static String getAttributes(Door door) { final StringBuilder sb = new StringBuilder(); sb.append('['); @@ -256,7 +258,7 @@ private IconLayerItem createValidatedLayerItem(Point pt, String label, Image[] i IconLayerItem retVal = null; if (pt.x > 0 && pt.y > 0) { - retVal = new IconLayerItem(door, label, icons[0], CENTER); + retVal = new IconLayerItem(door, label, LayerDoor.LAYER_ICONS_TARGET, icons[0], CENTER); retVal.setLabelEnabled(Settings.ShowLabelDoorTargets); retVal.setName(getCategory()); retVal.setImage(AbstractLayerItem.ItemState.HIGHLIGHTED, icons[1]); diff --git a/src/org/infinity/resource/are/viewer/LayerObjectDoorCells.java b/src/org/infinity/resource/are/viewer/LayerObjectDoorCells.java new file mode 100644 index 000000000..a94971112 --- /dev/null +++ b/src/org/infinity/resource/are/viewer/LayerObjectDoorCells.java @@ -0,0 +1,263 @@ +// Near Infinity - An Infinity Engine Browser and Editor +// Copyright (C) 2001 Jon Olav Hauglid +// See LICENSE.txt for license information + +package org.infinity.resource.are.viewer; + +import java.awt.Color; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; + +import org.infinity.datatype.IsNumeric; +import org.infinity.gui.layeritem.AbstractLayerItem; +import org.infinity.gui.layeritem.ShapedLayerItem; +import org.infinity.resource.Profile; +import org.infinity.resource.Viewable; +import org.infinity.resource.are.AreResource; +import org.infinity.resource.are.Door; +import org.infinity.resource.vertex.ClosedVertexImpeded; +import org.infinity.resource.vertex.OpenVertexImpeded; + +/** + * Handles specific layer subtype: ARE/Door blocked cells + */ +public class LayerObjectDoorCells extends LayerObject { + private static final Color[] COLOR = { new Color(0xFF004000, true), new Color(0xFF004000, true), + new Color(0xC000C000, true), new Color(0xC000FF00, true) }; + + private final HashMap doorMap = new HashMap<>(4); + private final Door door; + + /** + * @param category + * @param classType + * @param parent + */ + public LayerObjectDoorCells(AreResource parent, Door door) { + super("Door", Door.class, parent); + this.door = door; + final DoorInfo doorOpen = new DoorInfo(); + doorMap.put(Integer.valueOf(ViewerConstants.DOOR_OPEN), doorOpen); + final DoorInfo doorClosed = new DoorInfo(); + doorMap.put(Integer.valueOf(ViewerConstants.DOOR_CLOSED), doorClosed); + try { + String attr = LayerObjectDoor.getAttributes(this.door); + final String name = door.getAttribute(Door.ARE_DOOR_NAME).toString(); + final int cOfs = ((IsNumeric) parent.getAttribute(AreResource.ARE_OFFSET_VERTICES)).getValue(); + + // processing opened state door cells + doorOpen.setMessage(String.format("%s (Open) %s", name, attr)); + int cNum = ((IsNumeric) door.getAttribute(Door.ARE_DOOR_NUM_VERTICES_IMPEDED_OPEN)).getValue(); + Point[][] itemCoords = createCellPolygons(loadVertices(door, cOfs, 0, cNum, OpenVertexImpeded.class)); + doorOpen.setCoords(itemCoords); + + // processing closed state door cells + doorClosed.setMessage(String.format("%s (Closed) %s", name, attr)); + cNum = ((IsNumeric) door.getAttribute(Door.ARE_DOOR_NUM_VERTICES_IMPEDED_CLOSED)).getValue(); + itemCoords = createCellPolygons(loadVertices(door, cOfs, 0, cNum, ClosedVertexImpeded.class)); + doorClosed.setCoords(itemCoords); + } catch (Exception e) { + e.printStackTrace(); + } + + for (final DoorInfo info: getDoors()) { + final Point[][] coords = info.getCoords(); + final ShapedLayerItem[] items = new ShapedLayerItem[coords.length]; + final Point[] locations = new Point[coords.length]; + for (int i = 0; i < coords.length; i++) { + final Polygon poly = createPolygon(coords[i], 1.0); + final Rectangle bounds = normalizePolygon(poly); + + locations[i] = new Point(bounds.x, bounds.y); + final ShapedLayerItem cell = new ShapedLayerItem(this.door, info.getMessage(), poly); + cell.setName(getCategory()); + cell.setStrokeColor(AbstractLayerItem.ItemState.NORMAL, COLOR[0]); + cell.setStrokeColor(AbstractLayerItem.ItemState.HIGHLIGHTED, COLOR[1]); + cell.setFillColor(AbstractLayerItem.ItemState.NORMAL, COLOR[2]); + cell.setFillColor(AbstractLayerItem.ItemState.HIGHLIGHTED, COLOR[3]); + cell.setStroked(false); + cell.setFilled(true); + cell.setVisible(isVisible()); + items[i] = cell; + } + info.setCellItems(items); + info.setLocations(locations); + } + } + + @Override + public Viewable getViewable() { + return door; + } + + @Override + public AbstractLayerItem[] getLayerItems(int type) { + boolean isClosed = (type & ViewerConstants.DOOR_CLOSED) == ViewerConstants.DOOR_CLOSED; + boolean isOpen = (type & ViewerConstants.DOOR_OPEN) == ViewerConstants.DOOR_OPEN; + + if (Profile.getEngine() == Profile.Engine.PST) { + // open/closed states are inverted for PST + boolean tmp = isClosed; + isClosed = isOpen; + isOpen = tmp; + } + + List list = new ArrayList<>(); + if (isOpen) { + final DoorInfo info = getDoor(ViewerConstants.DOOR_OPEN); + if (info != null && info.getCellItems() != null) { + final ShapedLayerItem[] items = info.getCellItems(); + for (int i = 0; i < items.length; i++) { + list.add(items[i]); + } + } + } + if (isClosed) { + final DoorInfo info = getDoor(ViewerConstants.DOOR_CLOSED); + if (info != null && info.getCellItems() != null) { + final ShapedLayerItem[] items = info.getCellItems(); + for (int i = 0; i < items.length; i++) { + list.add(items[i]); + } + } + } + + return list.toArray(new AbstractLayerItem[0]); + } + + @Override + public AbstractLayerItem[] getLayerItems() { + List list = new ArrayList<>(); + + for (final AbstractLayerItem[] items : getDoorItems()) { + if (items != null) { + for (int i = 0; i < items.length; i++) { + list.add(items[i]); + } + } + } + + return list.toArray(new AbstractLayerItem[0]); + } + + @Override + public void update(double zoomFactor) { + for (final DoorInfo info : getDoors()) { + final ShapedLayerItem[] items = info.getCellItems(); + final Point[] locations = info.getLocations(); + final Point[][] coords = info.getCoords(); + + for (int i = 0; i < items.length; i++) { + items[i].setItemLocation((int) (locations[i].x * zoomFactor + (zoomFactor / 2.0)), + (int) (locations[i].y * zoomFactor + (zoomFactor / 2.0))); + final Polygon poly = createPolygon(coords[i], zoomFactor); + normalizePolygon(poly); + items[i].setShape(poly); + } + } + } + + private Collection getDoors() { + return doorMap.values(); + } + + private Collection getDoorItems() { + return doorMap.values().stream().map(ci -> ci.getCellItems()).filter(cell -> cell != null).collect(Collectors.toList()); + } + + private DoorInfo getDoor(int id) { + return doorMap.get(Integer.valueOf(id)); + } + + /** + * Creates polygons out of the given cell block coordinates and returns them as arrays of Point objects (one array per + * polygon). + * + * @param cells Array of search map coordinates for blocked cells. + * @return Two-dimensional array of polygon points. First array dimension indicates the polygon, second dimension + * indicates coordinates for the polygon. + */ + private Point[][] createCellPolygons(Point[] cells) { + List polys = new ArrayList<>(); + + // add one polygon per cell + // TODO: combine as many cells as possible into one polygon to improve performance + if (cells != null && cells.length > 0) { + final int cw = 16; // search map cell width, in pixels + final int ch = 12; // search map cell height, in pixels + for (int i = 0; i < cells.length; i++) { + final int x = cells[i].x * cw; + final int y = cells[i].y * ch; + polys.add(new Point[] { new Point(x, y), new Point(x + cw, y), new Point(x + cw, y + ch), new Point(x, y + ch) }); + } + } + + Point[][] retVal = new Point[polys.size()][]; + for (int i = 0, size = polys.size(); i < size; i++) { + retVal[i] = polys.get(i); + } + return retVal; + } + + // ----------------------------- INNER CLASSES ----------------------------- + + /** Storage for open/close-based door cell information. */ + private static class DoorInfo { + private String message; + private ShapedLayerItem[] cells; + private Point[] locations; + private Point[][] coords; + + public DoorInfo() {} + + /** Returns a message or label associated with the door. */ + public String getMessage() { + return message; + } + + /** Defines a message or label associated with the door. */ + public DoorInfo setMessage(String msg) { + this.message = msg; + return this; + } + + /** Returns the layer for blocked door cells. */ + public ShapedLayerItem[] getCellItems() { + return this.cells; + } + + /** Defines the layer items for blocked door cells. */ + public DoorInfo setCellItems(ShapedLayerItem[] cells) { + this.cells = cells; + return this; + } + + /** Returns the origin of all blocked door cells. */ + public Point[] getLocations() { + return this.locations; + } + + /** Defines the origin of all blocked door cells. */ + public DoorInfo setLocations(Point[] locations) { + this.locations = locations; + return this; + } + + /** Returns the vertices for all blocked door cells. */ + public Point[][] getCoords() { + return this.coords; + } + + /** Defines the vertices for all blocked door cells. */ + public DoorInfo setCoords(Point[][] coords) { + this.coords = coords; + return this; + } + } +} diff --git a/src/org/infinity/resource/are/viewer/LayerObjectRegion.java b/src/org/infinity/resource/are/viewer/LayerObjectRegion.java index bb9d5ed52..64ad68065 100644 --- a/src/org/infinity/resource/are/viewer/LayerObjectRegion.java +++ b/src/org/infinity/resource/are/viewer/LayerObjectRegion.java @@ -246,7 +246,7 @@ private IconLayerItem createValidatedLayerItem(Point pt, String label, Image[] i IconLayerItem retVal = null; if (pt.x > 0 && pt.y > 0) { - retVal = new IconLayerItem(region, label, icons[0], CENTER); + retVal = new IconLayerItem(region, label, LayerRegion.LAYER_ICONS_TARGET, icons[0], CENTER); retVal.setLabelEnabled(Settings.ShowLabelRegionTargets); retVal.setName(getCategory()); retVal.setImage(AbstractLayerItem.ItemState.HIGHLIGHTED, icons[1]); diff --git a/src/org/infinity/resource/are/viewer/LayerRegion.java b/src/org/infinity/resource/are/viewer/LayerRegion.java index a32f0d4f8..1a3902153 100644 --- a/src/org/infinity/resource/are/viewer/LayerRegion.java +++ b/src/org/infinity/resource/are/viewer/LayerRegion.java @@ -13,7 +13,10 @@ /** * Manages region layer objects. */ -public class LayerRegion extends BasicTargetLayer { +public class LayerRegion extends BasicCompositeLayer { + /** Identifier for the target icons sublayer. */ + public static final int LAYER_ICONS_TARGET = 1; + private static final String AVAILABLE_FMT = "Regions: %d"; public LayerRegion(AreResource are, AreaViewer viewer) { diff --git a/src/org/infinity/resource/are/viewer/Settings.java b/src/org/infinity/resource/are/viewer/Settings.java index 254a48613..1aedb4f6b 100644 --- a/src/org/infinity/resource/are/viewer/Settings.java +++ b/src/org/infinity/resource/are/viewer/Settings.java @@ -34,6 +34,7 @@ public class Settings { ViewerConstants.LayerStackingType.DOOR, ViewerConstants.LayerStackingType.DOOR_POLY, ViewerConstants.LayerStackingType.WALL_POLY, + ViewerConstants.LayerStackingType.DOOR_CELLS, ViewerConstants.LayerStackingType.AMBIENT_RANGE, ViewerConstants.LayerStackingType.TRANSITION }; @@ -566,6 +567,8 @@ public static LayerType stackingToLayer(LayerStackingType type) { case DOOR: case DOOR_TARGET: return LayerType.DOOR; + case DOOR_CELLS: + return LayerType.DOOR_CELLS; case DOOR_POLY: return LayerType.DOOR_POLY; case ENTRANCE: @@ -601,6 +604,8 @@ public static LayerStackingType[] layerToStacking(LayerType type) { return new LayerStackingType[] { LayerStackingType.CONTAINER, LayerStackingType.CONTAINER_TARGET }; case DOOR: return new LayerStackingType[] { LayerStackingType.DOOR, LayerStackingType.DOOR_TARGET }; + case DOOR_CELLS: + return new LayerStackingType[] { LayerStackingType.DOOR_CELLS }; case DOOR_POLY: return new LayerStackingType[] { LayerStackingType.DOOR_POLY }; case ENTRANCE: diff --git a/src/org/infinity/resource/are/viewer/ViewerConstants.java b/src/org/infinity/resource/are/viewer/ViewerConstants.java index 8f34b92b9..b5f52246d 100644 --- a/src/org/infinity/resource/are/viewer/ViewerConstants.java +++ b/src/org/infinity/resource/are/viewer/ViewerConstants.java @@ -20,6 +20,7 @@ public static enum LayerType { CONTAINER("Containers", true), AMBIENT("Ambient Sounds", true), DOOR("Doors", true), + DOOR_CELLS("Impeded Door Cells", true), ANIMATION("Background Animations", true), AUTOMAP("Automap Notes", true), SPAWN_POINT("Spawn Points", true), @@ -64,6 +65,7 @@ public static enum LayerStackingType { AMBIENT("Ambient Sounds"), AMBIENT_RANGE("Ambient Sound Ranges"), DOOR("Doors"), + DOOR_CELLS("Impeded Door Cells"), ANIMATION("Background Animations"), AUTOMAP("Automap Notes"), SPAWN_POINT("Spawn Points"),