/*
* 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.ClickOnMapEvent;
import illarion.client.input.CurrentMouseLocationEvent;
import illarion.client.input.PointOnMapEvent;
import illarion.client.resources.OverlayFactory;
import illarion.client.resources.Resource;
import illarion.client.resources.TileFactory;
import illarion.client.resources.data.OverlayTemplate;
import illarion.client.resources.data.TileTemplate;
import illarion.client.world.MapGroup;
import illarion.client.world.MapTile;
import illarion.client.world.World;
import illarion.client.world.movement.TargetMovementHandler;
import illarion.common.graphics.MapVariance;
import illarion.common.graphics.TileInfo;
import illarion.common.types.Direction;
import illarion.common.types.Rectangle;
import illarion.common.types.ServerCoordinate;
import org.illarion.engine.EngineException;
import org.illarion.engine.GameContainer;
import org.illarion.engine.graphic.Color;
import org.illarion.engine.graphic.Graphics;
import org.illarion.engine.graphic.SceneEvent;
import org.illarion.engine.graphic.effects.TextureEffect;
import org.illarion.engine.graphic.effects.TileLightEffect;
import org.illarion.engine.input.Button;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.EnumSet;
/**
* This class represents one tile on the screen.
*
* @author Martin Karing <nitram@illarion.org>
* @author Nop
*/
@SuppressWarnings("ClassNamingConvention")
public class Tile extends AbstractEntity<TileTemplate> implements Resource {
/**
* The animation that is applied to this tile or {@code null} in case there is none.
*/
@Nullable
private final FrameAnimation animation;
/**
* The template of the overlay that is rendered on top of the tile or {@code null} in case just the plain tile is
* rendered.
*/
@Nullable
private OverlayTemplate overlay;
/**
* The shape of the overlay that is rendered on the tile.
*/
private int overlayShape;
/**
* The parent map tile reference.
*/
@Nonnull
private final MapTile parentTile;
private int showHighlight;
@Override
public int getHighlight() {
return showHighlight;
}
/**
* The instance of the logging class for this class.
*/
private static final Logger log = LoggerFactory.getLogger(Tile.class);
public Tile(int tileId, @Nonnull MapTile parentTile) {
this(TileFactory.getInstance().getTemplate(TileInfo.getBaseID(tileId)), tileId, parentTile);
}
public Tile(@Nonnull TileTemplate template, int tileId, @Nonnull MapTile parentTile) {
super(template);
if (template.getAnimationSpeed() > 0) {
// start animation right away. All tiles of this type will share it
animation = template.getSharedAnimation();
} else if (template.getFrames() > 1) { // a tile with variants
animation = null;
ServerCoordinate location = parentTile.getCoordinates();
setFrame(MapVariance.getTileFrameVariance(location.getX(), location.getY(), template.getFrames()));
} else {
animation = null;
}
this.parentTile = parentTile;
if (TileInfo.hasOverlay(tileId)) {
overlay = OverlayFactory.getInstance().getTemplate(TileInfo.getOverlayID(tileId));
overlayShape = TileInfo.getShapeId(tileId) - 1;
setFadingCorridorEffectEnabled(false);
}
}
/**
* Draw tile and its overlay
*
* @param g the graphics object that is used to render the tile.
*/
@Override
public void render(@Nonnull Graphics g) {
if (performRendering()) {
MapTile obstructingTile = parentTile.getObstructingTile();
if ((obstructingTile != null) && obstructingTile.isOpaque()) {
return;
}
super.render(g);
showHighlight = 0;
}
}
@Override
protected void renderSprite(
@Nonnull Graphics g, int x, int y, @Nonnull Color light, @Nonnull TextureEffect... effects) {
Color centerLight = parentTile.getLight();
if ((topColor != null) && (leftColor != null) && (rightColor != null) && (bottomColor != null)) {
g.drawTileSprite(getTemplate().getSprite(), x, y, topColor, bottomColor, leftColor, rightColor,
centerLight, getCurrentFrame(), effects);
if (overlay != null) {
g.drawTileSprite(overlay.getSprite(), x, y, topColor, bottomColor, leftColor, rightColor,
centerLight, overlayShape, effects);
}
} else {
g.drawTileSprite(getTemplate().getSprite(), x, y, centerLight, centerLight, centerLight, centerLight,
centerLight, getCurrentFrame(), effects);
if (overlay != null) {
g.drawTileSprite(overlay.getSprite(), x, y, centerLight, centerLight, centerLight, centerLight,
centerLight, overlayShape, effects);
}
}
}
@Override
public void show() {
MapGroup group = parentTile.getMapGroup();
if ((group != null) && group.isHidden()) {
setAlphaTarget(0);
setAlpha(0);
setFadingCorridorEffectEnabled(false);
} else {
setFadingCorridorEffectEnabled(true);
}
super.show();
if (animation != null) {
animation.addTarget(this, true);
}
}
@Override
public void hide() {
super.hide();
if (animation != null) {
animation.removeTarget(this);
}
}
private TileLightEffect tileLightEffect;
private Color topColor;
private Color leftColor;
private Color rightColor;
private Color bottomColor;
@Override
public void update(@Nonnull GameContainer container, int delta) {
parentTile.updateColor(delta);
if (tileLightEffect == null) {
try {
tileLightEffect = container.getEngine().getAssets().getEffectManager().getTileLightEffect(true);
} catch (EngineException ignored) {
}
}
if (parentTile.isHidden()) {
setAlphaTarget(0);
setFadingCorridorEffectEnabled(false);
} else {
setFadingCorridorEffectEnabled(true);
}
super.update(container, delta);
if (parentTile.hasLightGradient()) {
topColor = getCornerColor(topColor, EnumSet.of(Direction.North, Direction.NorthEast, Direction.East));
leftColor = getCornerColor(leftColor, EnumSet.of(Direction.North, Direction.NorthWest, Direction.West));
bottomColor = getCornerColor(bottomColor, EnumSet.of(Direction.West, Direction.SouthWest, Direction.South));
rightColor = getCornerColor(rightColor, EnumSet.of(Direction.East, Direction.SouthEast, Direction.South));
} else {
topColor = null;
leftColor = null;
bottomColor = null;
rightColor = null;
}
}
@Nonnull
private Color getCornerColor(@Nullable Color storage, @Nonnull Collection<Direction> sourceDirections) {
Color usedStorage = storage;
if (usedStorage == null) {
usedStorage = new Color(Color.BLACK);
}
usedStorage.setColor(parentTile.getLight());
for (Direction sourceDirection : sourceDirections) {
Color directionLight = parentTile.getLight(sourceDirection);
usedStorage.add((directionLight == null) ? parentTile.getLight() : directionLight);
}
usedStorage.multiply(1.f / (sourceDirections.size() + 1));
return usedStorage;
}
@Override
public boolean isEventProcessed(
@Nonnull GameContainer container, int delta, @Nonnull SceneEvent event) {
if (event instanceof PointOnMapEvent) {
if (!isVisible()) {
return false;
}
PointOnMapEvent pointEvent = (PointOnMapEvent) event;
if (isMouseInInteractionRect(pointEvent.getX(), pointEvent.getY())) {
return true;
}
}
if (!parentTile.isAtPlayerLevel()) {
return false;
}
if (event instanceof ClickOnMapEvent) {
ClickOnMapEvent clickEvent = (ClickOnMapEvent) event;
if (clickEvent.getKey() != Button.Left) {
return false;
}
if (!isMouseInInteractionRect(clickEvent.getX(), clickEvent.getY())) {
return false;
}
log.debug("Single click on tile at {}", parentTile.getCoordinates());
TargetMovementHandler handler = World.getPlayer().getMovementHandler().getTargetMovementHandler();
handler.walkTo(parentTile.getCoordinates(), parentTile.isBlocked() ? 1 : 0);
handler.assumeControl();
return true;
}
if (event instanceof CurrentMouseLocationEvent) {
CurrentMouseLocationEvent moveEvent = (CurrentMouseLocationEvent) event;
if (!isMouseInInteractionRect(moveEvent.getX(), moveEvent.getY())) {
return false;
}
World.getPlayer().getMovementHandler().getTargetMouseMovementHandler()
.walkTo(parentTile.getCoordinates(), parentTile.isBlocked() ? 1 : 0);
if (!moveEvent.isHighlightHandled()) {
showHighlight = 1;
moveEvent.setHighlightHandled(true);
}
return true;
}
return false;
}
@Override
protected boolean isMouseInInteractionRect(int mouseX, int mouseY) {
int mouseXonDisplay = mouseX + Camera.getInstance().getViewportOffsetX();
int mouseYonDisplay = mouseY + Camera.getInstance().getViewportOffsetY();
Rectangle interactionRect = getInteractionRect();
if (!interactionRect.isInside(mouseXonDisplay, mouseYonDisplay)) {
return false;
}
/* Get the pixel location on the tile. */
int mouseXonTile = mouseXonDisplay - interactionRect.getLeft();
int mouseYonTile = mouseYonDisplay - interactionRect.getBottom();
/* Fold the location into one quarter */
if (mouseXonTile > 37) {
mouseXonTile = 75 - mouseXonTile;
}
if (mouseYonTile > 18) {
mouseYonTile = 36 - mouseYonTile;
}
/*
* Check if the mouse is on a opaque pixel. This calculation is only valid as long as the shape of the tiles
* does not change.
*/
return (mouseXonTile / 2) >= (18 - mouseYonTile);
}
/**
* {@inheritDoc}
* <p/>
* This implementation of the parent light fetches the light of the parent tile in order to ensure that the same
* color value is used.
*/
@Override
@Nonnull
protected Color getParentLight() {
return parentTile.getLight();
}
}