/* * This file is part of the Illarion project. * * Copyright © 2015 - Illarion e.V. * * Illarion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Illarion is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ package illarion.client.graphics; import illarion.client.input.AbstractMouseLocationEvent; import illarion.client.world.World; import illarion.common.types.DisplayCoordinate; import illarion.common.types.Rectangle; import org.illarion.engine.GameContainer; import org.illarion.engine.graphic.Graphics; import org.illarion.engine.graphic.SceneEvent; import org.jetbrains.annotations.Contract; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.*; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @author Martin Karing <nitram@illarion.org> */ public class ItemStack implements DisplayItem, List<Item> { @Nonnull private static final Logger log = LoggerFactory.getLogger(ItemStack.class); private boolean shown; @Nonnull private final List<Item> items; private boolean rectangleDirty; @Nonnull private final Rectangle interactiveRectangle; @Nonnull private final DisplayCoordinate stackLocation; @Nonnull private final ReadWriteLock lock; public ItemStack(@Nonnull DisplayCoordinate location) { shown = false; items = new ArrayList<>(); rectangleDirty = false; interactiveRectangle = new Rectangle(); stackLocation = location; lock = new ReentrantReadWriteLock(); } @Nonnull public ReadWriteLock getLock() { return lock; } @Override public void hide() { if (shown) { World.getMapDisplay().getGameScene().removeElement(this); shown = false; } } @Override public void show() { if (shown) { log.error("Added item stack {} twice.", this); } else { World.getMapDisplay().getGameScene().addElement(this); shown = true; } } @Contract(pure = true) public boolean isShown() { return shown; } private void updateInteractiveRectangle() { if (!rectangleDirty) { return; } rectangleDirty = false; interactiveRectangle.reset(); lock.readLock().lock(); try { for (Item item : items) { Rectangle itemRect = item.getInteractionRect(); if (itemRect.isEmpty()) { rectangleDirty = true; } if (interactiveRectangle.isEmpty()) { interactiveRectangle.set(itemRect); } else { interactiveRectangle.add(itemRect); } } } finally { lock.readLock().unlock(); } } public int getItemCount() { return items.size(); } public int getElevation() { lock.readLock().lock(); try { if (items.isEmpty()) { return 0; } return getElevationForIndex(items.size() - 1); } finally { lock.readLock().unlock(); } } @Nonnull public Item getTopItem() { lock.readLock().lock(); try { int count = getItemCount(); if (count == 0) { throw new IllegalStateException("Requesting the top item of a empty item stack is not valid."); } return items.get(count - 1); } finally { lock.readLock().unlock(); } } public boolean hasItems() { return !isEmpty(); } private int getElevationForIndex(int index) { if ((index < 0) || (index >= size())) { throw new IndexOutOfBoundsException("Index must be >= 0 and < the amount of items on the stack."); } lock.readLock().lock(); try { int elevation = 0; for (int i = 0; i <= index; i++) { Item itemAtIndex = get(i); elevation += itemAtIndex.getTemplate().getItemInfo().getLevel(); } return elevation; } finally { lock.readLock().unlock(); } } private void postProcessItemInsert(int index, @Nonnull Item newItem) { int elevation = (index > 0) ? getElevationForIndex(index - 1) : 0; setScreenPos(newItem, elevation); if (newItem.getTemplate().getItemInfo().getLevel() != 0) { elevation += newItem.getTemplate().getItemInfo().getLevel(); for (int i = index + 1; i < items.size(); i++) { Item item = get(i); setScreenPos(item, elevation); elevation += item.getTemplate().getItemInfo().getLevel(); } } if (!shown) { show(); } rectangleDirty = true; newItem.show(this); } private void postProcessItemRemove(@Nonnull Item removedItem) { if (items.isEmpty()) { hide(); } else { if (removedItem.getTemplate().getItemInfo().getLevel() != 0) { /* Update the elevation of every item. */ int elevation = 0; for (Item item : items) { setScreenPos(item, elevation); elevation += item.getTemplate().getItemInfo().getLevel(); } } } rectangleDirty = true; removedItem.hide(); } private void setScreenPos(@Nonnull Item item, int elevation) { if (elevation == 0) { item.setScreenPos(stackLocation); } else { item.setScreenPos(new DisplayCoordinate(stackLocation, 0, -elevation, 0)); } } @Override public int getOrder() { return stackLocation.getLayer(); } @Override public void render(@Nonnull Graphics graphics) { lock.readLock().lock(); try { for (Item item : items) { item.render(graphics); } } finally { lock.readLock().unlock(); } } @Override public void update(@Nonnull GameContainer container, int delta) { lock.readLock().lock(); try { int size = items.size(); for (int i = 0; i < size; i++) { Item item = items.get(i); item.enableNumbers(i == (size - 1)); item.update(container, delta); } } finally { lock.readLock().unlock(); } updateInteractiveRectangle(); } @Override public boolean isEventProcessed(@Nonnull GameContainer container, int delta, @Nonnull SceneEvent event) { if (interactiveRectangle.isEmpty()) { return false; } if (event instanceof AbstractMouseLocationEvent) { AbstractMouseLocationEvent mouseEvent = (AbstractMouseLocationEvent) event; int mouseXonDisplay = mouseEvent.getX() + Camera.getInstance().getViewportOffsetX(); int mouseYonDisplay = mouseEvent.getY() + Camera.getInstance().getViewportOffsetY(); if (!interactiveRectangle.isInside(mouseXonDisplay, mouseYonDisplay)) { return false; } } lock.readLock().lock(); try { for (int i = items.size() - 1; i >= 0; i--) { Item item = items.get(i); if (item.isEventProcessed(container, delta, event)) { return true; } } } finally { lock.readLock().unlock(); } return false; } @Override public int size() { return items.size(); } @Override public boolean isEmpty() { return items.isEmpty(); } @Override public boolean contains(Object o) { return items.contains(o); } @Nonnull @Override public Iterator<Item> iterator() { return items.iterator(); } @Nonnull @Override public Object[] toArray() { lock.readLock().lock(); try { return items.toArray(); } finally { lock.readLock().unlock(); } } @SuppressWarnings("SuspiciousToArrayCall") @Nonnull @Override public <T> T[] toArray(@Nonnull T[] a) { lock.readLock().lock(); try { return items.toArray(a); } finally { lock.readLock().unlock(); } } @Override public boolean add(@Nonnull Item e) { lock.writeLock().lock(); try { if (items.add(e)) { postProcessItemInsert(items.size() - 1, e); return true; } } finally { lock.writeLock().unlock(); } return false; } @Override public boolean remove(@Nonnull Object o) { lock.writeLock().lock(); try { if (items.remove(o)) { postProcessItemRemove((Item) o); return true; } } finally { lock.writeLock().unlock(); } return false; } @Override public boolean containsAll(@Nonnull Collection<?> c) { lock.readLock().lock(); try { return items.containsAll(c); } finally { lock.readLock().unlock(); } } @Override public boolean addAll(@Nonnull Collection<? extends Item> c) { boolean result = false; lock.writeLock().lock(); try { for (Item item : c) { if (add(item)) { result = true; } } } finally { lock.writeLock().unlock(); } return result; } @Override public boolean addAll(int index, @Nonnull Collection<? extends Item> c) { int indexCounter = index; lock.writeLock().lock(); try { for (Item item : c) { add(indexCounter, item); indexCounter++; } } finally { lock.writeLock().unlock(); } return indexCounter != index; } @Override public boolean removeAll(@Nonnull Collection<?> c) { boolean result = false; lock.writeLock().lock(); try { for (Object item : c) { if (remove(item)) { result = true; } } } finally { lock.writeLock().unlock(); } return result; } @Override public boolean retainAll(@Nonnull Collection<?> c) { throw new UnsupportedOperationException(); } @Override public void clear() { lock.writeLock().lock(); try { items.clear(); } finally { lock.writeLock().unlock(); } rectangleDirty = true; hide(); } @Override @Nonnull public Item get(int index) { lock.readLock().lock(); try { return items.get(index); } finally { lock.readLock().unlock(); } } @Nullable @Override public Item set(int index, @Nonnull Item element) { Item oldItem; lock.writeLock().lock(); try { oldItem = items.set(index, element); postProcessItemInsert(index, element); if (oldItem != null) { postProcessItemRemove(oldItem); } } finally { lock.writeLock().unlock(); } return oldItem; } @Override public void add(int index, @Nonnull Item element) { lock.writeLock().lock(); try { items.add(index, element); postProcessItemInsert(index, element); } finally { lock.writeLock().unlock(); } } @Override @Nonnull public Item remove(int index) { Item removedItem; lock.writeLock().lock(); try { removedItem = items.remove(index); assert removedItem != null; postProcessItemRemove(removedItem); } finally { lock.writeLock().unlock(); } return removedItem; } @Override public int indexOf(Object o) { lock.readLock().lock(); try { return items.indexOf(o); } finally { lock.readLock().unlock(); } } @Override public int lastIndexOf(Object o) { lock.readLock().lock(); try { return items.lastIndexOf(o); } finally { lock.readLock().unlock(); } } @Nonnull @Override public ListIterator<Item> listIterator() { return items.listIterator(); } @Nonnull @Override public ListIterator<Item> listIterator(int index) { return items.listIterator(index); } @Nonnull @Override public List<Item> subList(int fromIndex, int toIndex) { lock.readLock().lock(); try { return items.subList(fromIndex, toIndex); } finally { lock.readLock().unlock(); } } }