package org.gbcpainter.view;
import net.jcip.annotations.GuardedBy;
import org.gbcpainter.env.GameSettings;
import org.gbcpainter.env.GraphicsEnv;
import org.gbcpainter.env.LanguageDictionary;
import org.gbcpainter.game.levels.CampaignLevel;
import org.gbcpainter.game.levels.Level;
import org.gbcpainter.game.LevelManager;
import org.gbcpainter.game.LevelStopListener;
import org.gbcpainter.game.LevelManagerImpl;
import org.gbcpainter.geom.PERPENDICULAR_DIRECTION;
import org.gbcpainter.view.draw.DrawListener;
import org.gbcpainter.view.draw.RedrawJob;
import org.gbcpainter.view.draw.RedrawJobImpl;
import org.gbcpainter.view.menu.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
/**
* Canvas that holds the game.
* <p/>
* It dispatches keyboard events to a {@link org.gbcpainter.game.LevelManager}. Drawings are made from a {@link org.gbcpainter.view.draw.RedrawJob}.
* <p/>
* These two components notify the game holder via {@link org.gbcpainter.game.LevelStopListener} and {@link org.gbcpainter.view.draw.DrawListener} oberver methods.
*
* @author Lorenzo Pellegrini
*/
public class GameHolder extends Canvas implements LevelStopListener, DrawListener {
private static final int BEFORE_FADEOUT_END_VIEW_SWITCH = 10;
private static final int AFTER_REDRAW_MANAGER_DELAY = 100;
@NotNull
private final Level level;
@Nullable
@GuardedBy( "this" )
private volatile LevelManager manager = null;
@Nullable
@GuardedBy( "this" )
private volatile RedrawJob redrawer = null;
@GuardedBy( "this" )
private volatile boolean closing = false;
public GameHolder( @NotNull Level level ) {
super( GraphicsEnv.getInstance().getGraphicsConfiguration() );
this.level = level;
this.setFocusable( true );
this.setBackground( Color.LIGHT_GRAY );
this.setIgnoreRepaint( true );
this.createDrawJob();
this.addKeyListener( new KeyAdapter() {
@Override
public void keyPressed( final KeyEvent e ) {
PERPENDICULAR_DIRECTION direction = getDirection( e.getKeyCode() );
synchronized (GameHolder.this) {
if ( direction != null && GameHolder.this.manager != null ) {
GameHolder.this.manager.setDirectionSelected( direction, true );
}
}
}
@Override
public void keyReleased( final KeyEvent e ) {
PERPENDICULAR_DIRECTION direction = getDirection( e.getKeyCode() );
synchronized (GameHolder.this) {
if ( e.getKeyCode() == GameSettings.getInstance()
.getValue( GameSettings.INTEGER_SETTINGS_TYPE.KEY_PAUSE ) ) {
if ( GameHolder.this.manager != null ) {
GameHolder.this.manager.pause( true );
}
GameHolder.this.startFadeout( new Runnable() {
@Override
public void run() {
ViewManager.getMainView()
.swapToMenuView( new PauseMenu( GameHolder.this ) );
}
} );
} else if ( direction != null ) {
if ( GameHolder.this.manager != null ) {
GameHolder.this.manager.setDirectionSelected( direction, false );
}
}
}
}
} );
}
@Nullable
private static PERPENDICULAR_DIRECTION getDirection( int key ) {
final GameSettings singleton = GameSettings.getInstance();
if ( singleton.getValue( GameSettings.INTEGER_SETTINGS_TYPE.KEY_DOWN ) == key ) {
return PERPENDICULAR_DIRECTION.DOWN;
}
if ( singleton.getValue( GameSettings.INTEGER_SETTINGS_TYPE.KEY_UP ) == key ) {
return PERPENDICULAR_DIRECTION.UP;
}
if ( singleton.getValue( GameSettings.INTEGER_SETTINGS_TYPE.KEY_LEFT ) == key ) {
return PERPENDICULAR_DIRECTION.LEFT;
}
if ( singleton.getValue( GameSettings.INTEGER_SETTINGS_TYPE.KEY_RIGHT ) == key ) {
return PERPENDICULAR_DIRECTION.RIGHT;
}
return null;
}
/**
* {@inheritDoc}
* <p/>
* When this method is called, the level (manager + drawer) is started.
*/
@Override
public synchronized void addNotify() {
super.addNotify();
try {
this.closing = false;
this.requestFocus();
this.requestFocusInWindow();
this.createDrawJobIfNeeded();
startDrawJob();
} catch ( Exception e ) {
this.closing = true;
new ErrorDialog( ViewManager.getMainView().getFrame(), LanguageDictionary.ERROR_STRING,
LanguageDictionary.ERROR_DRAW_LEVEL, e );
EventQueue.invokeLater( new Runnable() {
@Override
public void run() {
ViewManager.getMainView().swapToMenuView( new MainMenu() );
}
} );
}
if(!closing) {
new Thread( new Runnable() {
@Override
public void run() {
try {
Thread.sleep( AFTER_REDRAW_MANAGER_DELAY );
} catch ( InterruptedException ignored ) {
}
synchronized (GameHolder.this) {
if ( ! closing ) {
/*manager = new LevelManagerImpl( level );
manager.pauseOnDeath( true );
manager.setInterval( GameSettings.getInstance()
.getValue( GameSettings.INTEGER_SETTINGS_TYPE.MOVE_MILLIS_TIMEOUT ) );
manager.addLevelStopListener( GameHolder.this );
manager.pause( false );*/
createLevelJob();
startLevelJob();
}
}
}
} ).start();
}
}
/**
* {@inheritDoc}
* <p/>
* When this method is called, the level (manager + drawer) is stopped.
*/
@Override
public synchronized void removeNotify() {
this.closing = true;
removeDrawJob();
super.removeNotify();
removeLevelJob();
}
/**
* Implementation for the {@link org.gbcpainter.game.LevelStopListener}, manages the lifetime of the level.
*
* @param stoppedLevel The stopped level.
*/
@Override
public synchronized void levelStopped( @NotNull final LevelManager ignored, @NotNull final Level stoppedLevel ) {
if( this.closing || this.manager == null || this.redrawer == null) {
return;
}
final Level level = getLevel();
@Nullable
final Runnable fadeoutAction;
if ( this.manager.hasFailed() ) {
/* Errore nel manager del livello */
this.closing = true;
fadeoutAction = null;
stopDrawJob();
new ErrorDialog( ViewManager.getMainView().getFrame(), LanguageDictionary.ERROR_STRING, LanguageDictionary.ERROR_UPDATING_LEVEL, this.manager.getError() );
EventQueue.invokeLater( new Runnable() {
@Override
public void run() {
ViewManager.getMainView().swapToMenuView( new MainMenu() );
}
} );
} else if ( level.isWin() ) {
/* Il giocatore ha vinto il livello */
this.closing = true;
final String nextLevel;
if ( level instanceof CampaignLevel ) {
nextLevel = ( (CampaignLevel) level ).getNextLevel();
} else {
nextLevel = null;
}
fadeoutAction = new Runnable() {
@Override
public void run() {
ViewManager.getMainView().swapToMenuView( new LevelWinMenu( nextLevel ) );
}
};
} else if ( ! level.isAlive() && level.isEnded() ) {
/* Il giocatore ha perso tutte le vite */
this.closing = true;
fadeoutAction = new Runnable() {
@Override
public void run() {
ViewManager.getMainView().swapToMenuView( new GameOverMenu( GameHolder.this ) );
}
};
} else if ( ! level.isAlive() ) {
/* Il giocatore ha perso una vita ma non tutte */
this.closing = true;
fadeoutAction = new Runnable() {
@Override
public void run() {
level.restart();
ViewManager.getMainView().swapToMenuView( new LevelLostMenu( GameHolder.this ) );
}
};
} else {
fadeoutAction = null;
}
if ( fadeoutAction != null ) {
startFadeout( fadeoutAction );
}
}
private void startFadeout( @NotNull final Runnable delayedAction ) {
new Thread( new Runnable() {
@Override
public void run() {
final RedrawJob drawJob;
synchronized (GameHolder.this) {
drawJob = GameHolder.this.redrawer;
}
if ( drawJob != null ) {
final long time = drawJob.startFadeOut();
try {
Thread.sleep( Math.max( 0, time - BEFORE_FADEOUT_END_VIEW_SWITCH ) );
} catch ( InterruptedException ignored ) {}
}
EventQueue.invokeLater( delayedAction );
}
} ).start();
}
/**
* Informs the observer that an error occurred when in the draw job
*
* @param job The drawing manager
* @param stoppedLevel The managed level
*/
@Override
public synchronized void drawStopped( @NotNull final RedrawJob job, @NotNull final Level stoppedLevel ) {
if( (!this.closing) && this.redrawer != null && this.redrawer.hasFailed()) {
this.closing = true;
this.removeLevelJob();
new ErrorDialog( ViewManager.getMainView().getFrame(), LanguageDictionary.ERROR_STRING, LanguageDictionary.ERROR_DRAW_LEVEL, this.redrawer.getError() );
EventQueue.invokeLater( new Runnable() {
@Override
public void run() {
ViewManager.getMainView().swapToMenuView( new MainMenu() );
}
} );
}
}
@NotNull
public Level getLevel() {
return this.level;
}
@GuardedBy( "this" )
private void createDrawJob() throws IllegalArgumentException {
final int FPS = GameSettings.getInstance()
.getValue( GameSettings.INTEGER_SETTINGS_TYPE.REDRAW_FPS );
if(this.redrawer != null) {
this.redrawer.terminate();
}
this.redrawer = new RedrawJobImpl( this, this.getLevel(), FPS );
this.redrawer.addLevelStopListener( this );
}
@GuardedBy( "this" )
private void createDrawJobIfNeeded() throws IllegalArgumentException {
if( this.redrawer == null || this.redrawer.isTerminated() ) {
this.createDrawJob();
}
}
@GuardedBy( "this" )
private void removeDrawJob() {
if ( this.redrawer != null ) {
this.redrawer.terminate();
this.redrawer = null;
}
}
@GuardedBy( "this" )
private boolean startDrawJob() {
if( this.redrawer != null && !this.redrawer.isTerminated()) {
this.redrawer.pauseDraw( false );
return true;
} else {
return false;
}
}
@GuardedBy( "this" )
private boolean stopDrawJob() {
if( this.redrawer != null) {
this.redrawer.terminate();
return true;
} else {
return false;
}
}
@GuardedBy( "this" )
private void createLevelJob() throws IllegalArgumentException {
if(this.manager != null) {
this.manager.terminate();
}
this.manager = new LevelManagerImpl( getLevel() );
this.manager.pauseOnDeath( true );
this.manager.setInterval( GameSettings.getInstance()
.getValue( GameSettings.INTEGER_SETTINGS_TYPE.MOVE_MILLIS_TIMEOUT ) );
this.manager.addLevelStopListener( GameHolder.this );
}
@GuardedBy( "this" )
private void createLevelJobIfNeeded() throws IllegalArgumentException {
if( this.manager == null || this.manager.isTerminated()) {
this.createDrawJob();
}
}
@GuardedBy( "this" )
private void removeLevelJob() {
if ( this.manager != null ) {
this.manager.terminate();
this.manager = null;
}
}
@GuardedBy( "this" )
private boolean startLevelJob() {
if( this.manager != null && !this.manager.isTerminated()) {
this.manager.pause( false );
return true;
} else {
return false;
}
}
@GuardedBy( "this" )
private boolean stopLevelJob() {
if( this.manager != null) {
this.manager.terminate();
return true;
} else {
return false;
}
}
}