Package games.stendhal.client

Source Code of games.stendhal.client.GameScreen

/* $Id: GameScreen.java,v 1.195 2011/06/28 22:25:24 kiheru Exp $ */
/***************************************************************************
*                      (C) Copyright 2003 - Marauroa                      *
***************************************************************************
***************************************************************************
*                                                                         *
*   This program is free software; you can redistribute it and/or modify  *
*   it under the terms of the GNU General Public License as published by  *
*   the Free Software Foundation; either version 2 of the License, or     *
*   (at your option) any later version.                                   *
*                                                                         *
***************************************************************************/
package games.stendhal.client;

import games.stendhal.client.entity.IEntity;
import games.stendhal.client.gui.DropTarget;
import games.stendhal.client.gui.GroundContainer;
import games.stendhal.client.gui.j2d.AchievementBoxFactory;
import games.stendhal.client.gui.j2d.RemovableSprite;
import games.stendhal.client.gui.j2d.TextBoxFactory;
import games.stendhal.client.gui.j2d.entity.Entity2DView;
import games.stendhal.client.gui.j2d.entity.EntityView;
import games.stendhal.client.gui.j2d.entity.EntityViewFactory;
import games.stendhal.client.sprite.Sprite;
import games.stendhal.client.sprite.SpriteStore;
import games.stendhal.common.NotificationType;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.geom.Point2D;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;

import javax.swing.JComponent;
import javax.swing.SwingUtilities;

import org.apache.log4j.Logger;

/**
* The game screen. This manages and renders the visual elements of the game.
*/
public class GameScreen extends JComponent implements IGameScreen, DropTarget,
  GameObjects.GameObjectListener {
  /**
   * serial version uid
   */
  private static final long serialVersionUID = -4070406295913030925L;

  private static Logger logger = Logger.getLogger(GameScreen.class);

  /**
   * Comparator used to sort entities to display.
   */
  protected static final EntityViewComparator entityViewComparator = new EntityViewComparator();

  /**
   * A scale factor for panning delta (to allow non-float precision).
   */
  protected static final int PAN_SCALE = 8;
  /**
   * Speed factor for centering the screen. Smaller is faster,
   * and keeps the player closer to the center of the screen when walking.
   */
  private static final int PAN_INERTIA = 15;

  private static final Sprite offlineIcon;

  /** the singleton instance. */
  private static GameScreen screen;

  /**
   * Static game layers.
   */
  protected StaticGameLayers gameLayers;
 
  /**
   * The entity views.
   */
  protected List<EntityView> views;

  /**
   * The entity to view map.
   */
  protected Map<IEntity, EntityView> entities;

  /** Actual width of the world in world units. */
  protected int ww;
  /** Actual height of the world in world units. */
  protected int wh;

  /**
   * The ground layer.
   */
  private final GroundContainer ground;

 

  /**
   * The text bubbles.
   */
  private final List<RemovableSprite> texts;
 
  /**
   * Text boxes that are anchored to the screen coordinates.
   */
  private final List<RemovableSprite> staticSprites;

  /**
   * The text bubbles to remove.
   */
  private final List<RemovableSprite> textsToRemove;

 
 
  private boolean offline;

  private int blinkOffline;

  /**
   * The targeted center of view X coordinate (truncated).
   */
  private int x;

  /**
   * The targeted center of view Y coordinate (truncated).
   */
  private int y;

  /** Actual size of the screen in pixels. */
  private int sw;
  private int sh;
 


  /**
   * The difference between current and target screen view X.
   */
  private int dvx;

  /**
   * The difference between current and target screen view Y.
   */
  private int dvy;

  /**
   * The current screen view X.
   */
  private int svx;

  /**
   * The current screen view Y.
   */
  private int svy;

  /**
   * Current panning speed.
   */
  private int speed;

  /** A hack to avoid flashes at zone changes */
  private final AreaChangingLock areaChangingLock = new AreaChangingLock();

  static {
    offlineIcon = SpriteStore.get().getSprite("data/gui/offline.png");
  }

  private TextBoxFactory textBoxFactory;

  private AchievementBoxFactory achievementBoxFactory;
 
  /**
   * The canvas can be resized using a split pane. This is
   * for adjusting the internal parameters for the change.
   */
  private class CanvasResizeListener implements ComponentListener {
    public void componentHidden(ComponentEvent e) {
      // do nothing
    }

    public void componentMoved(ComponentEvent e) {
      // do nothing
    }

    public void componentResized(ComponentEvent e) {
      Dimension screenSize = stendhal.getScreenSize();
      sw = Math.min(getWidth(), screenSize.width);
      sh = Math.min(getHeight(), screenSize.height);
      // Reset the view so that the player is in the center
      calculateView(x, y);
      center();
    }

    public void componentShown(ComponentEvent e) {
      // do nothing
    }
  }

  /**
   * Create a game screen.
   *
   * @param client
   *            The client.
   */
  public GameScreen(final StendhalClient client) {
    setSize(stendhal.getScreenSize());
    addComponentListener(new CanvasResizeListener());
   
    gameLayers = client.getStaticGameLayers();

    sw = getWidth();
    sh = getHeight();

    x = 0;
    y = 0;
    svx = sw / -2;
    svy = sh / -2;
    dvx = 0;
    dvy = 0;

    speed = 0;

    // Drawing is done in EDT
    views = Collections.synchronizedList(new LinkedList<EntityView>());
    texts = Collections.synchronizedList(new LinkedList<RemovableSprite>());
    textsToRemove = Collections.synchronizedList(new LinkedList<RemovableSprite>());
    staticSprites = Collections.synchronizedList(new LinkedList<RemovableSprite>());
    entities = new HashMap<IEntity, EntityView>();

    // create ground
    ground = new GroundContainer(client, this, this);

    // register native event handler
    addMouseListener(ground);
    addMouseWheelListener(ground);
    addMouseMotionListener(ground);
    /*
     * Ignore OS level repaint requests to help systems that create too
     * many of those. In game DnD is done within AWT so that is not
     * affected.
     */
    setIgnoreRepaint(true);
    client.getGameObjects().addGameObjectListener(this);
  }

  /**
   * Set the default [singleton] screen.
   *
   * @param screen
   *            The screen.
   */
 
  public static void setDefaultScreen(final GameScreen screen) {
    GameScreen.screen = screen;
  }

  /** @return the GameScreen object. */
  public static GameScreen get() {
    return screen;
  }

  /** @return screen width in world units. */
  private int getViewWidth() {
    return sw / SIZE_UNIT_PIXELS;
  }

  /** @return screen height in world units .*/
  private int getViewHeight() {
    return sh / SIZE_UNIT_PIXELS;
  }
 
  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#nextFrame()
   */
  public void nextFrame() {
    adjustView();
  }
 
  private TextBoxFactory getTextFactory() {
    if (textBoxFactory == null) {
      textBoxFactory = new TextBoxFactory();
    }
   
    return textBoxFactory;
  }
 
  private AchievementBoxFactory getAchievementFactory() {
    if (achievementBoxFactory == null) {
      achievementBoxFactory = new AchievementBoxFactory();
    }
    return achievementBoxFactory;
  }

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#addEntity(games.stendhal.client.entity.Entity)
   */
  public void addEntity(final IEntity entity) {
    final EntityView view = EntityViewFactory.create(entity);

    if (view != null) {
      entities.put(entity, view);
      addEntityView(view);
      if (entity.isUser()) {
        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            center();
          }
        });
      }
    }
  }

  /**
   * Add an entity view.
   *
   * @param view
   *            A view.
   */
  private void addEntityView(final EntityView view) {
    views.add(view);
    if (view instanceof Entity2DView) {
      final Entity2DView inspectable = (Entity2DView) view;
     
      inspectable.setInspector(ground);
   
  }
 

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#removeEntity(games.stendhal.client.entity.Entity)
   */
  public void removeEntity(final IEntity entity) {
    final EntityView view = entities.remove(entity);

    if (view != null) {
      removeEntityView(view);
    }
  }

  /**
   * Remove an entity view.
   *
   * @param view
   *            A view.
   */
  private void removeEntityView(final EntityView view) {
    view.release();
    views.remove(view);
  }

  /**
   * Update the view position to center the target position.
   */
  private void adjustView() {
    /*
     * Already centered?
     */
    if ((dvx == 0) && (dvy == 0)) {
      return;
    }

    final int sx = convertWorldXToScreenView(x) + (SIZE_UNIT_PIXELS / 2);
    final int sy = convertWorldYToScreenView(y) + (SIZE_UNIT_PIXELS / 2);

    if ((sx < 0) || (sx >= sw) || (sy < -SIZE_UNIT_PIXELS) || (sy > sh)) {
      /*
       * If off screen, just center
       */
      center();
    } else {
      /*
       * Calculate the target speed. The farther away, the faster.
       */
      final int dux = dvx / PAN_INERTIA;
      final int duy = dvy / PAN_INERTIA;

      final int tspeed = ((dux * dux) + (duy * duy)) * PAN_SCALE;

      if (speed > tspeed) {
        speed = (speed + speed + tspeed) / 3;

        /*
         * Don't stall
         */
        if ((dvx != 0) || (dvy != 0)) {
          speed = Math.max(speed, 1);
        }
      } else if (speed < tspeed) {
        speed += 2;
      }

      /*
       * Moving?
       */
      if (speed != 0) {
        /*
         * Not a^2 + b^2 = c^2, but good enough
         */
        final int scalediv = (Math.abs(dvx) + Math.abs(dvy)) * PAN_SCALE;

        int dx = speed * dvx / scalediv;
        int dy = speed * dvy / scalediv;

        /*
         * Don't overshoot. Don't stall.
         */
        if (dvx < 0) {
          dx = Math.max(Math.min(-1, dx), dvx);
        } else if (dvx > 0) {
          dx = Math.min(Math.max(1, dx), dvx);
        }

        if (dvy < 0) {
          dy = Math.max(Math.min(-1, dy), dvy);
        } else if (dvy > 0) {
          dy = Math.min(Math.max(1, dy), dvy);
        }

        /*
         * Adjust view
         */
        svx += dx;
        dvx -= dx;

        svy += dy;
        dvy -= dy;
      }
    }
  }

  /**
   * Updates the target position of the view center.
   * Prefer top left if the map is smaller than the screen.
   *
   * @param x preferred x of center, if the map is large enough
   * @param y preferred y of center, if the map is large enough
   */
  private void calculateView(int x, int y) {
    // Coordinates for a screen centered on player
    int cvx = (x * SIZE_UNIT_PIXELS) + (SIZE_UNIT_PIXELS / 2) - (sw / 2);
    int cvy = (y * SIZE_UNIT_PIXELS) + (SIZE_UNIT_PIXELS / 2) - (sh / 2);

    /*
     * Keep the world within the screen view
     */
    final int maxX = ww * SIZE_UNIT_PIXELS - sw;
    cvx = Math.min(cvx, maxX);
    cvx = Math.max(cvx, 0);
   
    final int maxY = wh * SIZE_UNIT_PIXELS - sh;
    cvy = Math.min(cvy, maxY);
    cvy = Math.max(cvy, 0);
   
    // Differences from center
    dvx = cvx - svx;
    dvy = cvy - svy;
  }

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#center()
   */
  public void center() {
    svx += dvx;
    svy += dvy;

    dvx = 0;
    dvy = 0;

    speed = 0;
  }
 
  @Override
  public void paintImmediately(int x, int y, int w, int h) {
    /*
     * Try to keep the old screen while the user is switching maps.
     *
     * NOTE: Relies on the repaint() requests to eventually come to this,
     * so if swing internals change some time in the future, a new solution
     * may be needed.
     */
    if (StendhalClient.get().tryAcquireDrawingSemaphore()) {
      try {
        if (!areaChangingLock.locked()) {
          super.paintImmediately(x, y, w, h);
        } else {
          logger.debug("Skipped drawing");
        }
      } finally {
        StendhalClient.get().releaseDrawingSemaphore();
      }
    }
  }
 
  @Override
  protected void paintComponent(final Graphics g) {
    // sort uses iterators, so it must be wrapped in a synchronized block
    synchronized (views) {
      Collections.sort(views, entityViewComparator);
    }
    Graphics2D g2d = (Graphics2D) g;

    // Draw the GameLayers from bootom to top, relies on exact naming of the
    // layers
    final String set = gameLayers.getAreaName();
   
    // An adjusted graphics object so that the drawn objects do not need to
    // know about converting the position to screen
    Graphics2D graphics = (Graphics2D) g2d.create();
    if (graphics.getClipBounds() == null) {
      Dimension screenSize = stendhal.getScreenSize();
      graphics.setClip(0, 0, Math.min(getWidth(), screenSize.width),
          Math.min(getHeight(), screenSize.height));
    }

    int xAdjust = -getScreenViewX();
    int yAdjust = -getScreenViewY();
    graphics.translate(xAdjust, yAdjust);
    // End of the world (map falls short of the view)?
    if (xAdjust > 0) {
      g2d.setColor(Color.BLACK);
      g2d.fillRect(0, 0, xAdjust, sh);
    }
   
    if (yAdjust > 0) {
      g2d.setColor(Color.BLACK);
      g2d.fillRect(0, 0, sw, yAdjust);
    }
   
    int tmpY = yAdjust + convertWorldToScreen(wh);
    if (tmpY < sh) {
      g2d.setColor(Color.BLACK);
      g2d.fillRect(0, tmpY, sw, sh);
    }
   
    int tmpX = yAdjust + convertWorldToScreen(ww);
    if (tmpX < sw) {
      g2d.setColor(Color.BLACK);
      g2d.fillRect(tmpX, 0, sw, sh);
    }
   
    int startTileX = Math.max(0, (int) getViewX());
    int startTileY = Math.max(0, (int) getViewY());
   
    // Restrict the drawn area by the clip bounds. Smaller than gamescreen
    // draw requests can come for example from dragging items
    Rectangle clip = graphics.getClipBounds();
    startTileX = Math.max(startTileX, clip.x / IGameScreen.SIZE_UNIT_PIXELS);
    startTileY = Math.max(startTileY, clip.y / IGameScreen.SIZE_UNIT_PIXELS);
    int layerWidth = getViewWidth();
    int layerHeight = getViewHeight();
    // +2 is needed to ensure the drawn area is covered by the tiles
    layerWidth = Math.min(layerWidth, clip.width / IGameScreen.SIZE_UNIT_PIXELS) + 2;
    layerHeight = Math.min(layerHeight, clip.height / IGameScreen.SIZE_UNIT_PIXELS) + 2;
   
    LayerRenderer r = gameLayers.getMerged(set, "floor_bundle", "0_floor", "1_terrain", "2_object");
    if (r != null) {
      r.draw(graphics, startTileX, startTileY, layerWidth, layerHeight);
    } else {
      // Fall back to individual layers in case the layers can't be
      // merged. (Is that even possible?)
      gameLayers.draw(graphics, set, "0_floor", startTileX, startTileY, layerWidth, layerHeight);
      gameLayers.draw(graphics, set, "1_terrain", startTileX, startTileY, layerWidth, layerHeight);
      gameLayers.draw(graphics, set, "2_object", startTileX, startTileY, layerWidth, layerHeight);
    }
   
    drawEntities(graphics);

    gameLayers.draw(graphics, set, "3_roof", startTileX, startTileY, layerWidth, layerHeight);
    gameLayers.draw(graphics, set, "4_roof_add", startTileX, startTileY, layerWidth, layerHeight);
   
    drawTopEntities(graphics);
   
    drawText(g2d);

    // Offline
    if (offline && (blinkOffline > 0)) {
      offlineIcon.draw(g2d, 560, 420);
    }

    if (blinkOffline < -10) {
      blinkOffline = 20;
    } else {
      blinkOffline--;
    }

    graphics.dispose();
  }

  /**
   * Draw the screen entities.
   *
   * @param g destination graphics
   */
  private void drawEntities(final Graphics2D g) {
    // We are in EDT now. The main thread can add or remove new views at any
    // time. Manual synchronization on iteration is mandated by the spec.
    synchronized (views) {
      for (final EntityView view : views) {
        try {
          view.draw(g);
        } catch (RuntimeException e) {
          logger.error(e, e);
        }
      }
    }
  }

  /**
   * Draw the top portion screen entities (such as HP/title bars).
   *
   * @param g destination graphics
   */
  private void drawTopEntities(final Graphics2D g) {
    // We are in EDT now. The main thread can add or remove new views at any
    // time. Manual synchronization on iteration is mandated by the spec.
    synchronized (views) {
      for (final EntityView view : views) {
        view.drawTop(g);
      }
    }
  }

  /**
   * Draw the screen text bubbles.
   *
   * @param g2d destination graphics
   */
  private void drawText(final Graphics2D g2d) {
    texts.removeAll(textsToRemove);
    staticSprites.removeAll(textsToRemove);
    textsToRemove.clear();
   
    /*
     * Text objects know their original placement relative to the screen,
     * not to the map. Pass them a shifted coordinate system.
     */
    g2d.translate(-getScreenViewX(), -getScreenViewY());
   
    synchronized (texts) {
      for (final RemovableSprite text : texts) {
        if (!text.shouldBeRemoved()) {
          text.draw(g2d);
        } else {
          removeText(text);
        }
      }
    }
   
    // Restore the coordinates
    g2d.translate(getScreenViewX(), getScreenViewY());
    // These are anchored to the screen, so they can use the usual proper
    // coordinates.
    synchronized (staticSprites) {
      for (final RemovableSprite text : staticSprites) {
        if (!text.shouldBeRemoved()) {
          text.draw(g2d);
        } else {
          removeText(text);
        }
      }
    }
  }

  /**
   * Get the view X world coordinate.
   *
   * @return The X coordinate of the left side.
   */
  private double getViewX() {
    return (double) getScreenViewX() / SIZE_UNIT_PIXELS;
  }

  /**
   * Get the view Y world coordinate.
   *
   * @return The Y coordinate of the left side.
   */
  private double getViewY() {
    return (double) getScreenViewY() / SIZE_UNIT_PIXELS;
  }

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#setMaxWorldSize(double, double)
   */
  public void setMaxWorldSize(final double width, final double height) {
    ww = (int) width;
    wh = (int) height;
    calculateView(x, y);
    center();
   
    areaChangingLock.onSizeReceived();
  }

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#setOffline(boolean)
   */
  public void setOffline(final boolean offline) {
    this.offline = offline;
  }

  /**
   * Create a text box with the appropriate text color for a notification
   * type.
   *
   * @param text
   * @param type
   * @param isTalking if <code>true</code> create a text box with a bubble
   *   handle
   * @return text sprite
   */
  public Sprite createTextBox(final String text, final NotificationType type,
      final boolean isTalking) {
    return getTextFactory().createTextBox(text, 240, type.getColor(), Color.white, isTalking);
  }
 
  /**
   * Adds a text bubble at a give position.
   *
   * @param sprite
   * @param x
   * @param y
   * @param textLength
   */
  public void addTextBox(Sprite sprite, double x, double y, int textLength) {
    int sx = convertWorldToScreen(x);
    int sy = convertWorldToScreen(y);
    // Point alignment: left, bottom
    sy -= sprite.getHeight();

    sx = keepSpriteOnMapX(sprite, sx);
    sy = keepSpriteOnMapY(sprite, sy);
   
    /*
     * Adjust the position of boxes placed at the same point to make it
     * clear for the player there are more than one.
     */
    boolean found = true;

    while (found) {
      found = false;

      synchronized (texts) {
        for (final RemovableSprite item : texts) {
          if ((item.getX() == sx) && (item.getY() == sy)) {
            found = true;
            sx += (SIZE_UNIT_PIXELS / 2);
            sx = keepSpriteOnMapX(sprite, sx);
            sy += (SIZE_UNIT_PIXELS / 2);
            sy = keepSpriteOnMapY(sprite, sy);
            break;
          }
        }
      }
    }

    texts.add(new RemovableSprite(sprite, sx, sy, Math.max(
        RemovableSprite.STANDARD_PERSISTENCE_TIME, textLength
            * RemovableSprite.STANDARD_PERSISTENCE_TIME / 50)));
  }

  /**
   * Try to keep a sprite on the map. Adjust the Y coordinate.
   *
   * @param sprite
   * @param sy suggested Y coordinate on screen
   * @return new Y coordinate
   */
  private int keepSpriteOnMapY(Sprite sprite, int sy) {
    sy = Math.max(sy, 0);
    /*
     * Allow placing beyond the map, but only if the area is on the screen.
     * Do not try to adjust the coordinates if the world size is not known
     * yet (as in immediately after a zone change)
     */
    if (wh != 0) {
      sy = Math.min(sy, Math.max(getHeight() + svy,
          convertWorldToScreen(wh)) - sprite.getHeight());
    }
    return sy;
  }

  /**
   * Try to keep a sprite on the map. Adjust the X coordinate.
   *
   * @param sprite
   * @param sx suggested X coordinate on screen
   * @return new X coordinate
   */
  private int keepSpriteOnMapX(Sprite sprite, int sx) {
    sx = Math.max(sx, 0);
    /*
     * Allow placing beyond the map, but only if the area is on the screen.
     * Do not try to adjust the coordinates if the world size is not known
     * yet (as in immediately after a zone change)
     */
    if (ww != 0) {
      sx = Math.min(sx, Math.max(getWidth() + svx, convertWorldToScreen(ww)) - sprite.getWidth());
    }
    return sx;
  }

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#removeText(games.stendhal.client.gui.j2d.Text)
   */
  public void removeText(final RemovableSprite entity) {
    textsToRemove.add(entity);
  }

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#removeAll()
   */
  public void removeAllObjects() {
    views.clear();
    texts.clear();
    textsToRemove.clear();
    // staticSprites contents are not zone specific, so don't clear those
    // The user was removed as well
    areaChangingLock.lock();
  }

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#clearTexts()
   */
  public void clearTexts() {
    synchronized (texts) {
      for (final RemovableSprite text : texts) {
        textsToRemove.add(text);
      }
    }
    synchronized (staticSprites) {
      for (final RemovableSprite text : staticSprites) {
        textsToRemove.add(text);
      }
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#getEntityViewAt(double, double)
   */
  public EntityView getEntityViewAt(final double x, final double y) {
    ListIterator<EntityView> it;

    synchronized (views) {
      /*
       * Try the physical entity areas first
       */
      it = views.listIterator(views.size());

      while (it.hasPrevious()) {
        final EntityView view = it.previous();

        IEntity entity = view.getEntity();
        if (entity != null) {
          if (entity.getArea().contains(x, y)) {
            return view;
          }
        }
      }

      /*
       * Now the visual entity areas
       */
      final int sx = convertWorldToScreen(x);
      final int sy = convertWorldToScreen(y);

      it = views.listIterator(views.size());

      while (it.hasPrevious()) {
        final EntityView view = it.previous();

        if (view.getArea().contains(sx, sy)) {
          return view;
        }
      }
    }

    return null;
  }

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#getMovableEntityViewAt(double,
   *      double)
   */
  public EntityView getMovableEntityViewAt(final double x, final double y) {
    ListIterator<EntityView> it;

    synchronized (views) {
      /*
       * Try the physical entity areas first
       */
      it = views.listIterator(views.size());

      while (it.hasPrevious()) {
        final EntityView view = it.previous();

        if (view.isMovable()) {
          if (view.getEntity().getArea().contains(x, y)) {
            return view;
          }
        }
      }

      /*
       * Now the visual entity areas
       */
      final int sx = convertWorldToScreen(x);
      final int sy = convertWorldToScreen(y);

      it = views.listIterator(views.size());

      while (it.hasPrevious()) {
        final EntityView view = it.previous();

        if (view.isMovable()) {
          if (view.getArea().contains(sx, sy)) {
            return view;
          }
        }
      }
    }

    return null;
  }

  /*
   * (non-Javadoc)
   * @see games.stendhal.client.IGameScreen#getTextAt(int, int)
   */
  public RemovableSprite getTextAt(final int x, final int y) {
    // staticTexts are drawn on top of the others; those in the end of the
    // lists are above preceding texts
    synchronized (staticSprites) {
      final ListIterator<RemovableSprite> it = staticSprites.listIterator(staticSprites.size());

      while (it.hasPrevious()) {
        final RemovableSprite text = it.previous();

        if (text.getArea().contains(x, y)) {
          return text;
        }
      }
    }
    // map pixel coordinates
    final int tx = x + svx;
    final int ty = y + svy;
    synchronized (texts) {
      final ListIterator<RemovableSprite> it = texts.listIterator(texts.size());

      while (it.hasPrevious()) {
        final RemovableSprite text = it.previous();

        if (text.getArea().contains(tx, ty)) {
          return text;
        }
      }
    }

    return null;
  }

  /**
   * Convert world X coordinate to screen view coordinate.
   *
   * @param wx
   *            World X coordinate.
   *
   * @return Screen X coordinate (in integer value).
   */
  private int convertWorldXToScreenView(final double wx) {
    return convertWorldToScreen(wx) - svx;
  }

  /**
   * Convert world Y coordinate to screen view coordinate.
   *
   * @param wy
   *            World Y coordinate.
   *
   * @return Screen Y coordinate (in integer value).
   */
  private int convertWorldYToScreenView(final double wy) {
    return convertWorldToScreen(wy) - svy;
  }

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#convertWorldToScreenView(double,
   *      double)
   */
  public Point convertWorldToScreenView(final double wx, final double wy) {
    return new Point(convertWorldXToScreenView(wx),
        convertWorldYToScreenView(wy));
  }

  /**
   * Convert a world unit value to a screen unit value.
   *
   * @param w World value.
   *
   * @return A screen value (in pixels).
   */
  private int convertWorldToScreen(final double w) {
    return (int) (w * SIZE_UNIT_PIXELS);
  }

  /**
   * Convert screen coordinates to world coordinates.
   *
   * @param x
   *            The virtual screen X coordinate.
   * @param y
   *            The virtual screen Y coordinate.
   *
   * @return World coordinates.
   */
  private Point2D convertScreenToWorld(final int x, final int y) {
    return new Point.Double((double) x / SIZE_UNIT_PIXELS, (double) y
        / SIZE_UNIT_PIXELS);

  }

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#convertScreenViewToWorld(java.awt.Point)
   */
  public Point2D convertScreenViewToWorld(final Point p) {
    return convertScreenViewToWorld(p.x, p.y);
  }

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#convertScreenViewToWorld(int, int)
   */
  public Point2D convertScreenViewToWorld(final int x, final int y) {
    return convertScreenToWorld(x + getScreenViewX(), y + getScreenViewY());
  }

  /**
   * Get the view X screen coordinate.
   *
   * @return The X coordinate of the left side.
   */
  private int getScreenViewX() {
    return svx;
  }

  /**
   * Get the view Y screen coordinate.
   *
   * @return The Y coordinate of the left side.
   */
  private int getScreenViewY() {
    return svy;
  }

  //
  // PositionChangeListener
  //

  /*
   * (non-Javadoc)
   *
   * @see games.stendhal.client.IGameScreen#positionChanged(double, double)
   */
  public void positionChanged(final double x, final double y) {
    final int ix = (int) x;
    final int iy = (int) y;

    /*
     * Save CPU cycles
     */
    if ((ix != this.x) || (iy != this.y)) {
      this.x = ix;
      this.y = iy;

      calculateView(ix, iy);
    }
    areaChangingLock.onPositionReceived();
  }

  //
  //

  private static class EntityViewComparator implements
      Comparator<EntityView> {
    //
    // Comparator
    //

    public int compare(final EntityView view1, final EntityView view2) {
      int rv;

      rv = view1.getZIndex() - view2.getZIndex();

      if (rv == 0) {
        final Rectangle area1 = view1.getArea();
        final Rectangle area2 = view2.getArea();

        rv = (area1.y + area1.height) - (area2.y + area2.height);

        if (rv == 0) {
          rv = area1.x - area2.x;
          /*
           * Quick workaround to stack items in the same order they
           * were added.
           *
           * TODO: stack items in the same order they were added on server side.
           */
          if (rv == 0) {
            rv = view1.getEntity().getID().getObjectID()
                - view2.getEntity().getID().getObjectID();
          }
        }
      }

      return rv;
    }
  }

  public void dropEntity(IEntity entity, int amount, Point point) {
    // Just pass it to the ground container
    ground.dropEntity(entity, amount, point);
  }

  /**
   * Draw a box for a reached achievement with given title, description and category
   *
   * @param title
   * @param description
   * @param category
   */
  public void addAchievementBox(String title, String description,
      String category) {
    final Sprite sprite = getAchievementFactory().createAchievementBox(title, description, category);
   
    /*
     * Keep the achievements a bit longer on the screen. They do not leave
     * a line to the chat log, so we give the player a bit more time to
     * admire her prowess.
     */
    addStaticSprite(sprite, 2 * RemovableSprite.STANDARD_PERSISTENCE_TIME);
  }
 
  /**
   * Add a text box bound to the bottom of the screen, with a timeout
   * dependent on the text length.
   *
   * @param sprite
   * @param textLength
   */
  public void addStaticText(Sprite sprite, int textLength) {
    addStaticSprite(sprite,
        Math.max(RemovableSprite.STANDARD_PERSISTENCE_TIME,
        textLength * RemovableSprite.STANDARD_PERSISTENCE_TIME / 50));
  }
 
  /**
   * Add a sprite anchored to the screen bottom.
   *
   * @param sprite sprite
   * @param persistTime time to stay on the screen before being automatically
   *   removed
   */
  private void addStaticSprite(Sprite sprite, long persistTime) {
    int x = (getWidth() - sprite.getWidth()) / 2;
    int y = getHeight() - sprite.getHeight();
    staticSprites.add(new RemovableSprite(sprite, x, y, persistTime));
  }
 
  /**
   * A hack to suppress drawing until the GameScreen has the needed data.
   */
  private static class AreaChangingLock {
    private boolean playerReceived;
    private boolean sizeReceived;
   
    /**
     * Called when zone change starts. (Player is removed)
     */
    void lock() {
      playerReceived = false;
      sizeReceived = false;
    }
   
    /**
     * Called when the position information is received.
     */
    void onPositionReceived() {
      playerReceived = true;
    }
   
    /**
     * Called when map size information is received.
     */
    void onSizeReceived() {
      sizeReceived = true;
    }
   
    /**
     * Check if drawing should be suppressed.
     *
     * @return <code>true</code>, if drawing should be suppressed,
     *   <code>false</code> otherwise
     */
    boolean locked() {
      return !(sizeReceived && playerReceived);
    }
  }
}
TOP

Related Classes of games.stendhal.client.GameScreen

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.