You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegames/kasteroids/view.cpp

858 lines
23 KiB

/*
* KAsteroids - Copyright (c) Martin R. Jones 1997
*
* Part of the KDE project
*/
#include <stdlib.h>
#include <math.h>
#include <kapplication.h>
#include <kstandarddirs.h>
#include <kglobalsettings.h>
#include "settings.h"
#include "view.h"
#include "view.moc"
#define IMG_BACKGROUND "bg.png"
#define SPRITES_PREFIX kapp->kde_datadir() + "/kasteroids/"
#define REFRESH_DELAY 33
#define SHIP_SPEED 0.3
#define MISSILE_SPEED 10.0
#define SHIP_STEPS 64
#define ROTATE_RATE 2
#define SHIELD_ON_COST 1
#define SHIELD_HIT_COST 30
#define BRAKE_ON_COST 4
#define MAX_ROCK_SPEED 2.5
#define MAX_POWERUP_SPEED 1.5
#define MAX_SHIP_SPEED 12
#define MAX_BRAKES 5
#define MAX_SHIELDS 5
#define MAX_FIREPOWER 5
#define TEXT_SPEED 4
#define PI_X_2 6.283185307
static struct
{
int id;
const char *path;
int frames;
}
kas_animations [] =
{
{ ID_ROCK_LARGE, "rock1/rock1%1.png", 32 },
{ ID_ROCK_MEDIUM, "rock2/rock2%1.png", 32 },
{ ID_ROCK_SMALL, "rock3/rock3%1.png", 32 },
{ ID_SHIP, "ship/ship%1.png", 64 },
{ ID_MISSILE, "missile/missile.png", 1 },
{ ID_BIT, "bits/bits%1.png", 16 },
{ ID_EXHAUST, "exhaust/exhaust.png", 1 },
{ ID_ENERGY_POWERUP, "powerups/energy.png", 1 },
// { ID_TELEPORT_POWERUP, "powerups/teleport%1.png", 12 },
{ ID_BRAKE_POWERUP, "powerups/brake.png", 1 },
{ ID_SHIELD_POWERUP, "powerups/shield.png", 1 },
{ ID_SHOOT_POWERUP, "powerups/shoot.png", 1 },
{ ID_SHIELD, "shield/shield%1.png", 6 },
{ 0, 0, 0 }
};
KAsteroidsView::KAsteroidsView( TQWidget *parent, const char *name )
: TQWidget( parent, name ),
field(640, 440),
view(&field,this)
{
view.setVScrollBarMode( TQScrollView::AlwaysOff );
view.setHScrollBarMode( TQScrollView::AlwaysOff );
rocks.setAutoDelete( true );
missiles.setAutoDelete( true );
bits.setAutoDelete( true );
powerups.setAutoDelete( true );
exhaust.setAutoDelete( true );
field.setBackgroundColor(black);
TQPixmap pm( locate("sprite", IMG_BACKGROUND) );
field.tqsetBackgroundPixmap( pm );
textSprite = new TQCanvasText( &field );
TQFont font( KGlobalSettings::generalFont().family(), 18 );
textSprite->setFont( font );
shield = 0;
shieldOn = false;
refreshRate = REFRESH_DELAY;
readSprites();
shieldTimer = new TQTimer( this );
connect( shieldTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(hideShield()) );
mTimerId = -1;
shipPower = MAX_POWER_LEVEL;
vitalsChanged = true;
mBrakeCount = 0;
mShootCount = 0;
mShieldCount = 0;
}
// - - -
KAsteroidsView::~KAsteroidsView()
{
}
// - - -
void KAsteroidsView::reset()
{
rocks.clear();
missiles.clear();
bits.clear();
powerups.clear();
exhaust.clear();
shotsFired = 0;
shotsHit = 0;
rockSpeed = 1.0;
powerupSpeed = 1.0;
mFrameNum = 0;
mPaused = false;
ship->hide();
shield->hide();
if ( mTimerId >= 0 ) {
killTimer( mTimerId );
mTimerId = -1;
}
}
// - --
void KAsteroidsView::newGame()
{
if ( shieldOn )
{
shield->hide();
shieldOn = false;
}
reset();
if ( mTimerId < 0 )
mTimerId = startTimer( REFRESH_DELAY );
emit updateVitals();
}
// - - -
void KAsteroidsView::endGame()
{
}
void KAsteroidsView::pause( bool p )
{
if ( !mPaused && p ) {
if ( mTimerId >= 0 ) {
killTimer( mTimerId );
mTimerId = -1;
}
} else if ( mPaused && !p )
mTimerId = startTimer( REFRESH_DELAY );
mPaused = p;
}
// - - -
void KAsteroidsView::newShip()
{
ship->move( field.width()/2, field.height()/2, 0 );
shield->move( field.width()/2, field.height()/2, 0 );
ship->setVelocity( 0.0, 0.0 );
shipDx = 0;
shipDy = 0;
shipAngle = 0;
rotateL = false;
rotateR = false;
thrustShip = false;
shootShip = false;
brakeShip = false;
teleportShip = false;
shieldOn = true;
shootDelay = 0;
shipPower = MAX_POWER_LEVEL;
rotateRate = ROTATE_RATE;
rotateSlow = 0;
mBrakeCount = 0;
mTeleportCount = 0;
mShootCount = 0;
ship->show();
shield->show();
mShieldCount = 1; // just in case the ship appears on a rock.
shieldTimer->start( 1000, true );
}
void KAsteroidsView::setShield( bool s )
{
if ( shieldTimer->isActive() && !s ) {
shieldTimer->stop();
hideShield();
} else {
shieldOn = s && mShieldCount;
}
}
void KAsteroidsView::brake( bool b )
{
if ( mBrakeCount )
{
if ( brakeShip && !b )
{
rotateL = false;
rotateR = false;
thrustShip = false;
rotateRate = ROTATE_RATE;
}
brakeShip = b;
}
}
// - - -
void KAsteroidsView::readSprites()
{
TQString sprites_prefix =
KGlobal::dirs()->findResourceDir("sprite", "rock1/rock10000.png");
int i = 0;
while ( kas_animations[i].id )
{
animation.insert( kas_animations[i].id,
new TQCanvasPixmapArray( sprites_prefix + kas_animations[i].path,
kas_animations[i].frames ) );
i++;
}
ship = new TQCanvasSprite( animation[ID_SHIP], &field );
ship->hide();
shield = new KShield( animation[ID_SHIELD], &field );
shield->hide();
}
// - - -
void KAsteroidsView::addRocks( int num )
{
for ( int i = 0; i < num; i++ )
{
KRock *rock = new KRock( animation[ID_ROCK_LARGE], &field,
ID_ROCK_LARGE, krandom.getLong(2),
krandom.getLong(2) ? -1 : 1 );
double dx = (2.0 - krandom.getDouble()*4.0) * rockSpeed;
double dy = (2.0 - krandom.getDouble()*4.0) * rockSpeed;
rock->setVelocity( dx, dy );
rock->setFrame( krandom.getLong( rock->frameCount() ) );
if ( dx > 0 )
{
if ( dy > 0 )
rock->move( 5, 5, 0 );
else
rock->move( 5, field.height() - 25, 0 );
}
else
{
if ( dy > 0 )
rock->move( field.width() - 25, 5, 0 );
else
rock->move( field.width() - 25, field.height() - 25, 0 );
}
rock->show( );
rocks.append( rock );
}
}
// - - -
void KAsteroidsView::showText( const TQString &text, const TQColor &color, bool scroll )
{
// textSprite->setTextFlags( AlignHCenter | AlignVCenter );
textSprite->setText( text );
textSprite->setColor( color );
if ( scroll ) {
textSprite->move( (field.width()-textSprite->boundingRect().width()) / 2,
-textSprite->boundingRect().height() );
textDy = TEXT_SPEED;
} else {
textSprite->move( (field.width()-textSprite->boundingRect().width()) / 2,
(field.height()-textSprite->boundingRect().height()) / 2 );
textDy = 0;
}
textSprite->show();
}
// - - -
void KAsteroidsView::hideText()
{
textDy = -TEXT_SPEED;
}
// - - -
void KAsteroidsView::resizeEvent(TQResizeEvent* event)
{
TQWidget::resizeEvent(event);
field.resize(width()-4, height()-4);
view.resize(width(),height());
}
// - - -
void KAsteroidsView::timerEvent( TQTimerEvent * )
{
field.advance();
TQCanvasSprite *rock;
// move rocks forward
for ( rock = rocks.first(); rock; rock = rocks.next() ) {
((KRock *)rock)->nextFrame();
wrapSprite( rock );
}
wrapSprite( ship );
// check for missile collision with rocks.
processMissiles();
// these are generated when a ship explodes
for ( KBit *bit = bits.first(); bit; bit = bits.next() )
{
if ( bit->expired() )
{
bits.removeRef( bit );
}
else
{
bit->growOlder();
bit->setFrame( ( bit->frame()+1 ) % bit->frameCount() );
}
}
for ( KExhaust *e = exhaust.first(); e; e = exhaust.next() )
exhaust.removeRef( e );
// move / rotate ship.
// check for collision with a rock.
processShip();
// move powerups and check for collision with player and missiles
processPowerups();
if ( textSprite->isVisible() )
{
if ( textDy < 0 &&
textSprite->boundingRect().y() <= -textSprite->boundingRect().height() )
textSprite->hide();
else
{
textSprite->moveBy( 0, textDy );
}
if ( textSprite->boundingRect().y() > (field.height()-textSprite->boundingRect().height())/2 )
textDy = 0;
}
if ( vitalsChanged && !(mFrameNum % 10) ) {
emit updateVitals();
vitalsChanged = false;
}
mFrameNum++;
}
void KAsteroidsView::wrapSprite( TQCanvasItem *s )
{
int x = int(s->x() + s->boundingRect().width() / 2);
int y = int(s->y() + s->boundingRect().height() / 2);
if ( x > field.width() )
s->move( s->x() - field.width(), s->y() );
else if ( x < 0 )
s->move( field.width() + s->x(), s->y() );
if ( y > field.height() )
s->move( s->x(), s->y() - field.height() );
else if ( y < 0 )
s->move( s->x(), field.height() + s->y() );
}
// - - -
void KAsteroidsView::rockHit( TQCanvasItem *hit )
{
KPowerup *nPup = 0;
int rnd = static_cast<int>(krandom.getDouble()*30.0) % 30;
switch( rnd )
{
case 4:
case 5:
nPup = new KPowerup( animation[ID_ENERGY_POWERUP], &field,
ID_ENERGY_POWERUP );
break;
case 10:
// nPup = new KPowerup( animation[ID_TELEPORT_POWERUP], &field,
// ID_TELEPORT_POWERUP );
break;
case 15:
nPup = new KPowerup( animation[ID_BRAKE_POWERUP], &field,
ID_BRAKE_POWERUP );
break;
case 20:
nPup = new KPowerup( animation[ID_SHIELD_POWERUP], &field,
ID_SHIELD_POWERUP );
break;
case 24:
case 25:
nPup = new KPowerup( animation[ID_SHOOT_POWERUP], &field,
ID_SHOOT_POWERUP );
break;
}
if ( nPup )
{
double r = 0.5 - krandom.getDouble();
nPup->move( hit->x(), hit->y(), 0 );
nPup->setVelocity( hit->xVelocity() + r, hit->yVelocity() + r );
nPup->show( );
powerups.append( nPup );
}
if ( hit->rtti() == ID_ROCK_LARGE || hit->rtti() == ID_ROCK_MEDIUM )
{
// break into smaller rocks
double addx[4] = { 1.0, 1.0, -1.0, -1.0 };
double addy[4] = { -1.0, 1.0, -1.0, 1.0 };
double dx = hit->xVelocity();
double dy = hit->yVelocity();
double maxRockSpeed = MAX_ROCK_SPEED * rockSpeed;
if ( dx > maxRockSpeed )
dx = maxRockSpeed;
else if ( dx < -maxRockSpeed )
dx = -maxRockSpeed;
if ( dy > maxRockSpeed )
dy = maxRockSpeed;
else if ( dy < -maxRockSpeed )
dy = -maxRockSpeed;
TQCanvasSprite *nrock;
for ( int i = 0; i < 4; i++ )
{
double r = rockSpeed/2 - krandom.getDouble()*rockSpeed;
if ( hit->rtti() == ID_ROCK_LARGE )
{
nrock = new KRock( animation[ID_ROCK_MEDIUM], &field,
ID_ROCK_MEDIUM, krandom.getLong(2),
krandom.getLong(2) ? -1 : 1 );
emit rockHit( 0 );
}
else
{
nrock = new KRock( animation[ID_ROCK_SMALL], &field,
ID_ROCK_SMALL, krandom.getLong(2),
krandom.getLong(2) ? -1 : 1 );
emit rockHit( 1 );
}
nrock->move( hit->x(), hit->y(), 0 );
nrock->setVelocity( dx+addx[i]*rockSpeed+r, dy+addy[i]*rockSpeed+r );
nrock->setFrame( krandom.getLong( nrock->frameCount() ) );
nrock->show( );
rocks.append( nrock );
}
}
else if ( hit->rtti() == ID_ROCK_SMALL )
emit rockHit( 2 );
rocks.removeRef( (TQCanvasSprite *)hit );
if ( rocks.count() == 0 )
emit rocksRemoved();
}
void KAsteroidsView::reducePower( int val )
{
shipPower -= val;
if ( shipPower <= 0 )
{
shipPower = 0;
thrustShip = false;
if ( shieldOn )
{
shieldOn = false;
shield->hide();
}
}
vitalsChanged = true;
}
void KAsteroidsView::addExhaust( double x, double y, double dx,
double dy, int count )
{
for ( int i = 0; i < count; i++ )
{
KExhaust *e = new KExhaust( animation[ID_EXHAUST], &field );
e->move( x + 2 - krandom.getDouble()*4, y + 2 - krandom.getDouble()*4 );
e->setVelocity( dx, dy );
e->show( );
exhaust.append( e );
}
}
void KAsteroidsView::processMissiles()
{
KMissile *missile;
// if a missile has hit a rock, remove missile and break rock into smaller
// rocks or remove completely.
TQPtrListIterator<KMissile> it(missiles);
for ( ; it.current(); ++it )
{
missile = it.current();
missile->growOlder();
if ( missile->expired() )
{
missiles.removeRef( missile );
continue;
}
wrapSprite( missile );
TQCanvasItemList hits = missile->collisions( true );
TQCanvasItemList::Iterator hit;
for ( hit = hits.begin(); hit != hits.end(); ++hit )
{
if ( (*hit)->rtti() >= ID_ROCK_LARGE &&
(*hit)->rtti() <= ID_ROCK_SMALL )
{
shotsHit++;
rockHit( *hit );
missiles.removeRef( missile );
break;
}
}
}
}
// - - -
void KAsteroidsView::processShip()
{
if ( ship->isVisible() )
{
if ( shieldOn )
{
shield->show();
reducePower( SHIELD_ON_COST );
static int sf = 0;
sf++;
if ( sf % 2 )
shield->setFrame( (shield->frame()+1) % shield->frameCount() );
shield->move( ship->x() - 9, ship->y() - 9 );
TQCanvasItemList hits = shield->collisions( true );
TQCanvasItemList::Iterator it;
for ( it = hits.begin(); it != hits.end(); ++it )
{
if ( (*it)->rtti() >= ID_ROCK_LARGE &&
(*it)->rtti() <= ID_ROCK_SMALL )
{
int factor;
switch ( (*it)->rtti() )
{
case ID_ROCK_LARGE:
factor = 3;
break;
case ID_ROCK_MEDIUM:
factor = 2;
break;
default:
factor = 1;
}
if ( factor > mShieldCount )
{
// shield not strong enough
shieldOn = false;
break;
}
rockHit( *it );
// the more shields we have the less costly
reducePower( factor * (SHIELD_HIT_COST - mShieldCount*2) );
}
}
}
if ( !shieldOn )
{
shield->hide();
TQCanvasItemList hits = ship->collisions( true );
TQCanvasItemList::Iterator it;
for ( it = hits.begin(); it != hits.end(); ++it )
{
if ( (*it)->rtti() >= ID_ROCK_LARGE &&
(*it)->rtti() <= ID_ROCK_SMALL )
{
KBit *bit;
for ( int i = 0; i < 12; i++ )
{
bit = new KBit( animation[ID_BIT], &field );
bit->move( ship->x() + 5 - krandom.getDouble() * 10,
ship->y() + 5 - krandom.getDouble() * 10,
krandom.getLong(bit->frameCount()) );
bit->setVelocity( 1-krandom.getDouble()*2,
1-krandom.getDouble()*2 );
bit->setDeath( 60 + krandom.getLong(60) );
bit->show( );
bits.append( bit );
}
ship->hide();
shield->hide();
emit shipKilled();
break;
}
}
}
if ( rotateSlow )
rotateSlow--;
if ( rotateL )
{
shipAngle -= rotateSlow ? 1 : rotateRate;
if ( shipAngle < 0 )
shipAngle += SHIP_STEPS;
}
if ( rotateR )
{
shipAngle += rotateSlow ? 1 : rotateRate;
if ( shipAngle >= SHIP_STEPS )
shipAngle -= SHIP_STEPS;
}
double angle = shipAngle * PI_X_2 / SHIP_STEPS;
double cosangle = cos( angle );
double sinangle = sin( angle );
if ( brakeShip )
{
thrustShip = false;
rotateL = false;
rotateR = false;
rotateRate = ROTATE_RATE;
if ( fabs(shipDx) < 2.5 && fabs(shipDy) < 2.5 )
{
shipDx = 0.0;
shipDy = 0.0;
ship->setVelocity( shipDx, shipDy );
brakeShip = false;
}
else
{
double motionAngle = atan2( -shipDy, -shipDx );
if ( angle > M_PI )
angle -= PI_X_2;
double angleDiff = angle - motionAngle;
if ( angleDiff > M_PI )
angleDiff = PI_X_2 - angleDiff;
else if ( angleDiff < -M_PI )
angleDiff = PI_X_2 + angleDiff;
double fdiff = fabs( angleDiff );
if ( fdiff > 0.08 )
{
if ( angleDiff > 0 )
rotateL = true;
else if ( angleDiff < 0 )
rotateR = true;
if ( fdiff > 0.6 )
rotateRate = mBrakeCount + 1;
else if ( fdiff > 0.4 )
rotateRate = 2;
else
rotateRate = 1;
if ( rotateRate > 5 )
rotateRate = 5;
}
else if ( fabs(shipDx) > 1 || fabs(shipDy) > 1 )
{
thrustShip = true;
// we'll make braking a bit faster
shipDx += cosangle/6 * (mBrakeCount - 1);
shipDy += sinangle/6 * (mBrakeCount - 1);
reducePower( BRAKE_ON_COST );
addExhaust( ship->x() + 20 - cosangle*22,
ship->y() + 20 - sinangle*22,
shipDx-cosangle, shipDy-sinangle,
mBrakeCount+1 );
}
}
}
if ( thrustShip )
{
// The ship has a terminal velocity, but trying to go faster
// still uses fuel (can go faster diagonally - don't care).
double thrustx = cosangle/4;
double thrusty = sinangle/4;
if ( fabs(shipDx + thrustx) < MAX_SHIP_SPEED )
shipDx += thrustx;
if ( fabs(shipDy + thrusty) < MAX_SHIP_SPEED )
shipDy += thrusty;
ship->setVelocity( shipDx, shipDy );
reducePower( 1 );
addExhaust( ship->x() + 20 - cosangle*20,
ship->y() + 20 - sinangle*20,
shipDx-cosangle, shipDy-sinangle, 3 );
}
ship->setFrame( shipAngle );
if ( shootShip )
{
if ( !shootDelay && (int)missiles.count() < mShootCount + 2 )
{
KMissile *missile = new KMissile( animation[ID_MISSILE], &field );
missile->move( 21+ship->x()+cosangle*10,
21+ship->y()+sinangle*10, 0 );
missile->setVelocity( shipDx + cosangle*MISSILE_SPEED,
shipDy + sinangle*MISSILE_SPEED );
missile->show( );
missiles.append( missile );
shotsFired++;
reducePower( 1 );
shootDelay = 5;
}
if ( shootDelay )
shootDelay--;
}
if ( teleportShip )
{
int ra = rand() % 10;
if( ra == 0 )
ra += rand() % 20;
int xra = ra * 60 + ( (rand() % 20) * (rand() % 20) );
int yra = ra * 50 - ( (rand() % 20) * (rand() % 20) );
ship->move( xra, yra );
}
vitalsChanged = true;
}
}
// - - -
void KAsteroidsView::processPowerups()
{
if ( !powerups.isEmpty() )
{
// if player gets the powerup remove it from the screen, if option
// "Can destroy powerups" is enabled and a missile hits the powerup
// destroy it
KPowerup *pup;
TQPtrListIterator<KPowerup> it( powerups );
for( ; (pup = it.current()); )
{
++it; // We have to increase here, because pup may get deleted.
pup->growOlder();
if( pup->expired() )
{
powerups.removeRef( pup );
continue;
}
wrapSprite( pup );
TQCanvasItemList hits = pup->collisions( true );
TQCanvasItemList::Iterator it;
for ( it = hits.begin(); it != hits.end(); ++it )
{
if ( (*it) == ship || (*it) == shield )
{
switch( pup->rtti() )
{
case ID_ENERGY_POWERUP:
shipPower += 150;
if ( shipPower > MAX_POWER_LEVEL )
shipPower = MAX_POWER_LEVEL;
break;
case ID_TELEPORT_POWERUP:
mTeleportCount++;
break;
case ID_BRAKE_POWERUP:
if ( mBrakeCount < MAX_BRAKES )
mBrakeCount++;
break;
case ID_SHIELD_POWERUP:
if ( mShieldCount < MAX_SHIELDS )
mShieldCount++;
break;
case ID_SHOOT_POWERUP:
if ( mShootCount < MAX_FIREPOWER )
mShootCount++;
break;
}
powerups.removeRef( pup );
vitalsChanged = true;
break;
}
if ( (*it)->rtti() == ID_MISSILE )
{
if ( Settings::canDestroyPowerups() )
{
powerups.removeRef( pup );
break;
}
}
}
}
} // -- if( powerups.isEmpty() )
}
// - - -
void KAsteroidsView::hideShield()
{
shield->hide();
mShieldCount = 0;
shieldOn = false;
}
// - - -