com.threerings.puzzle.drop.client.DropSprite Maven / Gradle / Ivy
The newest version!
//
// $Id$
//
// Vilya library - tools for developing networked games
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
// http://code.google.com/p/vilya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library 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
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.puzzle.drop.client;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import com.samskivert.util.ObserverList;
import com.threerings.util.DirectionUtil;
import com.threerings.media.image.Mirage;
import com.threerings.media.sprite.Sprite;
/**
* The drop sprite is a sprite that displays one or more pieces falling toward the bottom of the
* board.
*/
public class DropSprite extends Sprite
{
/**
* Constructs a drop sprite and starts it dropping.
*
* @param view the board view upon which this sprite will be displayed.
* @param col the column of the sprite.
* @param row the row of the bottom-most piece.
* @param pieces the pieces displayed by the sprite.
* @param dist the distance the sprite is to drop in rows.
*/
public DropSprite (DropBoardView view, int col, int row, int[] pieces, int dist)
{
this(view, col, row, pieces, dist, -1);
}
/**
* Constructs a drop sprite and starts it dropping.
*
* @param view the board view upon which this sprite will be displayed.
* @param col the column of the sprite.
* @param row the row of the bottom-most piece.
* @param pieces the pieces displayed by the sprite.
* @param dist the distance the sprite is to drop in rows.
* @param renderOrder the render order.
*/
public DropSprite (
DropBoardView view, int col, int row, int[] pieces, int dist, int renderOrder)
{
_view = view;
_col = col;
_row = row;
_pieces = pieces;
_dist = (dist == 0) ? 1 : dist;
_orient = NORTH;
_unit = _view.getPieceHeight();
setRenderOrder(renderOrder);
}
@Override
protected void init ()
{
super.init();
// size the bounds to fit our pieces
updateBounds();
// set up the piece location
setBoardLocation(_row, _col);
// calculate vertical render offset based on the number of pieces
setRowOffset(-(_pieces.length - 1));
}
/**
* Returns the remaining number of columns to drop.
*/
public int getDistance ()
{
return _dist;
}
/**
* Returns the column the piece is located in.
*/
public int getColumn ()
{
return _col;
}
/**
* Returns the row the piece is located in.
*/
public int getRow ()
{
return _row;
}
/**
* Returns the pieces the sprite is displaying.
*/
public int[] getPieces ()
{
return _pieces;
}
/**
* Returns the velocity of this sprite.
*/
public float getVelocity ()
{
return _vel;
}
/**
* Sets the row and column the piece is located in.
*/
public void setBoardLocation (int row, int col)
{
_row = row;
_col = col;
updatePosition();
}
/**
* Sets the column the piece is located in.
*/
public void setColumn (int col)
{
_col = col;
updatePosition();
}
/**
* Set the row the piece is located in.
*/
public void setRow (int row)
{
_row = row;
updatePosition();
}
/**
* Sets the column offset of the sprite image.
*/
public void setColumnOffset (int count)
{
_offx = count;
updateRenderOffset();
updateRenderOrigin();
}
/**
* Sets the row offset of the sprite image.
*/
public void setRowOffset (int count)
{
_offy = count;
updateRenderOffset();
updateRenderOrigin();
}
/**
* Sets the pieces the sprite is displaying.
*/
public void setPieces (int[] pieces)
{
_pieces = pieces;
}
/**
* Sets the velocity of this sprite. The time at which the current row was entered is modified
* so that the sprite position will remain the same when calculated using the new velocity
* since the piece sprite may have its velocity modified in the middle of a row traversal.
*/
public void setVelocity (float velocity)
{
// bail if we've already got the requested velocity
if (_vel == velocity) {
return;
}
if (_rowstamp > 0) {
// get our current distance along the row
long now = _view.getTimeStamp();
float pctdone = getPercentDone(now);
// revise the current row entry time to account for the new velocity
float travpix = pctdone * _unit;
long msecs = (long)(travpix / velocity);
_rowstamp = now - msecs;
}
// update the velocity
_vel = velocity;
}
/**
* Starts the piece dropping toward the next row.
*/
public void drop ()
{
// Log.info("Dropping piece [piece=" + this + "].");
// drop one row by default
if (_dist <= 0) {
_dist = 1;
}
if (_stopstamp > 0) {
// we're dropping from a stand-still
long delta = _view.getTimeStamp() - _stopstamp;
_rowstamp += delta;
_stopstamp = 0;
} else {
// we're continuing a previous drop, so make use of any previously existing time
_rowstamp = _endstamp;
}
}
/**
* Returns true if this drop sprite is dropping, false if it has been {@link #stop}ped or has
* not yet been {@link #drop}ped.
*/
public boolean isDropping ()
{
return (_stopstamp == 0) && (_rowstamp != 0);
}
/**
* Stops the piece from dropping.
*/
public void stop ()
{
if (_stopstamp == 0) {
_stopstamp = _view.getTimeStamp();
// log.info("Stopped piece", "piece", this);
}
}
/**
* Puts the drop sprite into (or takes it out of) bouncing mode. Bouncing mode is used to put
* the sprite into limbo after it lands but before we commit the landing, giving the user a
* last moment change move or rotate the piece. While the sprite is "bouncing" it will be
* rendered one pixel below its at rest state.
*/
public void setBouncing (boolean bouncing)
{
_bouncing = bouncing;
if (_bouncing) {
// if we've activated bouncing, shift the sprite slightly to illustrate its new state
shiftForBounce();
// to prevent funny business in the event that we were a long ways past the end of the
// row when we landed, we warp the sprite back to the exact point of landing for the
// purposes of the bounce and any subsequent antics
_endstamp = _rowstamp = _view.getTimeStamp();
// log.info("Adjusted rowstap due to bounce", "time", _endstamp);
}
}
/**
* Returns true if this sprite is bouncing.
*/
public boolean isBouncing ()
{
return _bouncing;
}
/**
* Updates the sprite's location to illustrate that it is currently in the "bouncing" state.
*/
protected void shiftForBounce ()
{
setLocation(_ox, _srcPos.y+1);
}
@Override
public boolean inside (Shape shape)
{
return shape.contains(_bounds);
}
/**
* Returns a value between 0.0
and 1.0
representing how far the
* piece has moved toward the next row as of the given time stamp.
*/
public float getPercentDone (long timestamp)
{
// if we've never been ticked and so haven't yet initialized our row start timestamp, just
// let the caller know that we've not traversed our row at all
if (_rowstamp == 0) {
return 0.0f;
}
long msecs = Math.max(0, timestamp - _rowstamp);
float travpix = msecs * _vel;
float pctdone = (travpix / _unit);
// log.info("getPercentDone",
// "timestamp", timestamp, "rowstamp", _rowstamp, "msecs", msecs, "travpix", travpix,
// "pctdone", pctdone, "vel", _vel);
return pctdone;
}
@Override
public void paint (Graphics2D gfx)
{
// get the column and row increment based on the sprite's orientation
int oidx = _orient/2;
int incx = ORIENT_DX[oidx];
int incy = ORIENT_DY[oidx];
// determine offset from the start of each actual row and column
int dx = _ox - _srcPos.x, dy = _oy - _srcPos.y;
int pcol = _col, prow = _row;
for (int ii = 0; ii < _pieces.length; ii++) {
// ask the board for the render position of this piece
_view.getPiecePosition(pcol, prow, _renderPos);
// draw the piece image
paintPieceImage(gfx, ii, pcol, prow, _orient, _renderPos.x + dx, _renderPos.y + dy);
// increment the target column and row
pcol += incx;
prow += incy;
}
}
/**
* Paints the specified piece with the supplied parameters.
*/
protected void paintPieceImage (
Graphics2D gfx, int pieceidx, int col, int row, int orient, int x, int y)
{
Mirage image = _view.getPieceImage(_pieces[pieceidx], col, row, orient);
image.paint(gfx, x, y);
}
@Override
public void tick (long timestamp)
{
super.tick(timestamp);
// initialize our rowstamp if we haven't done so already
if (_rowstamp == 0) {
_rowstamp = timestamp;
}
// if we're bouncing or paused, do nothing here
if (_bouncing || _stopstamp > 0) {
return;
}
PieceMovedOp pmop = null;
// figure out how far along the current board coordinate we should be
float pctdone = getPercentDone(timestamp);
if (pctdone >= 1.0f) {
// note that we've reached the next row
advancePosition();
// update remaining drop distance
_dist--;
// calculate any remaining time to be used
long used = (long)(_unit / _vel);
_endstamp = _rowstamp + used;
_rowstamp = _endstamp;
// update our percent done because we've moved down a row
pctdone -= 1.0;
// inform observers that we've reached our destination
pmop = new PieceMovedOp(this, timestamp, _col, _row);
}
// constrain the sprite's position to the destination row
pctdone = Math.min(pctdone, 1.0f);
// calculate the latest sprite position
int nx = _srcPos.x + (int)((_destPos.x - _srcPos.x) * pctdone);
int ny = _srcPos.y + (int)((_destPos.y - _srcPos.y) * pctdone);
// log.info("Drop sprite tick",
// "dist", _dist, "pctdone", pctdone, "row", _row, "col", _col, "nx", nx, "ny", ny);
// only update the sprite's location if it actually moved
if (_ox != nx || _oy != ny) {
setLocation(nx, ny);
}
// lastly notify our observers if we made it to the next row
if (pmop != null) {
_observers.apply(pmop);
}
}
/**
* Called when the sprite has finished traversing its current row to advance its board
* coordinates to the next row.
*/
protected void advancePosition ()
{
setRow(_row + 1);
// Log.info("Moved to row " + _row);
}
@Override
public void fastForward (long timeDelta)
{
if (_rowstamp > 0) {
_rowstamp += timeDelta;
}
}
@Override
public void toString (StringBuilder buf)
{
super.toString(buf);
buf.append(", orient=").append(DirectionUtil.toShortString(_orient));
buf.append(", row=").append(_row);
buf.append(", col=").append(_col);
buf.append(", offx=").append(_offx);
buf.append(", offy=").append(_offy);
buf.append(", dist=").append(_dist);
}
/**
* Updates internal pixel coordinates used when the piece is moving.
*/
protected void updatePosition ()
{
_view.getPiecePosition(_col, _row, _srcPos);
_view.getPiecePosition(_col, _row+1, _destPos);
setLocation(_srcPos.x, _srcPos.y);
}
@Override
public void setOrientation (int orient)
{
invalidate();
super.setOrientation(orient);
updateBounds();
invalidate();
}
/**
* Updates the bounds for this sprite based on the sprite display dimensions in the view.
*/
protected void updateBounds ()
{
Dimension size = _view.getPieceSegmentSize(_col, _row, _orient, _pieces.length);
_bounds.width = size.width;
_bounds.height = size.height;
}
/**
* Adjusts our render origin such that our location is not in the upper left of the sprite's
* rendered image but is in fact offset by some number of rows and columns.
*/
protected void updateRenderOffset ()
{
_oxoff = -(_view.getPieceWidth() * _offx);
_oyoff = -(_view.getPieceHeight() * _offy);
}
/** Used to dispatch {@link DropSpriteObserver#pieceMoved}. */
protected static class PieceMovedOp implements ObserverList.ObserverOp