/*************************************************************************** alienBlaster Copyright (C) 2004 Paul Grathwohl, Arne Hormann, Daniel Kuehn, Soenke Schwardt 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, or (at your option) any later version. This program 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. ***************************************************************************/ #include "formation.h" #include "enemy.h" #include "enemys.h" #include using namespace std; Formation::Formation( FormationTypes whichFormation, Vector2D centerAtStart, Vector2D startVel, int nrEnemys, FormationEnemySets enemyTypes, int flagsFormationChangePolicy, FormationShotPatterns shotPattern ) { formationType = whichFormation; formationCenter = centerAtStart; formationSpeed = startVel; changeOnKill = flagsFormationChangePolicy & FORMATION_CHANGE_ON_KILL; changeSpontaneous = flagsFormationChangePolicy & FORMATION_CHANGE_SPONTANEOUS; changeOften = flagsFormationChangePolicy & FORMATION_CHANGE_OFTEN; changeSeldom = flagsFormationChangePolicy & FORMATION_CHANGE_SELDOM; if ( changeSpontaneous && !changeSeldom && !changeOften ) changeSeldom = true; if ( changeOften ) { nextFormationChange = FORMATION_CHANGE_OFTEN_DELAY + rand() % (FORMATION_CHANGE_OFTEN_RAND_DELAY+1); } else if ( changeSeldom ) { nextFormationChange = FORMATION_CHANGE_SELDOM_DELAY + rand() % (FORMATION_CHANGE_SELDOM_RAND_DELAY+1); } if ( nrEnemys > FORMATION_MAX_NR_ENEMYS[ formationType ] ) nrInvolved = FORMATION_MAX_NR_ENEMYS[ formationType ]; else if ( nrEnemys < 0 ) nrInvolved = 0; else nrInvolved = nrEnemys; this->shotPattern = shotPattern; actShootingEnemy = 0; if ( shotPattern == FORMATION_SP_NONE ) formationFires = false; else formationFires = true; vector targetPos; fillTargetPos( targetPos ); for ( int i = 0; i < nrInvolved; i++ ) { Enemy *newOne; switch ( enemyTypes ) { case FORMATION_ENEMY_SET_DEFAULT: case FORMATION_ENEMY_SET_FIGHTER: { newOne = new Enemy( formationCenter + targetPos[ i ], formationSpeed, FIGHTER, true, formationFires ); break; } case FORMATION_ENEMY_SET_BOMBER: { newOne = new Enemy( formationCenter + targetPos[ i ], formationSpeed, BOMBER, true, formationFires ); break; } case FORMATION_ENEMY_SET_FIGHTER_BOMBER: { newOne = new Enemy( formationCenter + targetPos[ i ], formationSpeed, (EnemyTypes)(FIGHTER + (i % 2)), true, formationFires ); break; } } enemys->addEnemy( newOne ); involvedEnemys.push_back( newOne ); } enemyWasKilled = false; // wait at least 100 ms before the first shot nextFirePrimary = 100; nextFireSecondary = 100; // cout << "Type: " << formationType << " SP: " << shotPattern << endl; } Formation::~Formation() {} void Formation::enemyKilled( Enemy *killedEnemy ) { for ( int i = 0; i < nrInvolved; i++ ) { if ( involvedEnemys[i] == killedEnemy ) { enemyWasKilled = true; involvedEnemys.erase( involvedEnemys.begin() + i ); nrInvolved--; break; } } } void Formation::update( int dT ) { if ( changeSpontaneous ) handleSpontaneousFormationChange( dT ); if ( enemyWasKilled ) { // change the formation? if ( changeOnKill && (rand() % 100 < 70) ) chooseNewFormationType(); moveEnemyInFormation(); enemyWasKilled = false; } formationCenter += formationSpeed * dT / 1000.0; shoot( dT ); } void Formation::handleSpontaneousFormationChange( int dT ) { nextFormationChange -= dT; if ( changeSpontaneous && nextFormationChange < 0 ) { chooseNewFormationType(); moveEnemyInFormation(); if ( changeOften ) { nextFormationChange = FORMATION_CHANGE_OFTEN_DELAY + rand() % (FORMATION_CHANGE_OFTEN_RAND_DELAY+1); } else if ( changeSeldom ) { nextFormationChange = FORMATION_CHANGE_SELDOM_DELAY + rand() % (FORMATION_CHANGE_SELDOM_RAND_DELAY+1); } } } void Formation::moveEnemyInFormation() { // calc the target positions in the new formation (relative to the center of the formation) vector targetPos; fillTargetPos( targetPos ); // choose the best mapping from enemy to targetPosition // (shortest way for enemy to its position is best) vector relPosForEnemies; getBestMapping( targetPos, relPosForEnemies ); // give the enemy its order for ( int i = 0; i < nrInvolved; i++ ) { involvedEnemys[i]->setNewRelTargetPos( relPosForEnemies[ i ] ); } } void Formation::chooseNewFormationType() { bool found = false; int i = 0; while ( i < 10 && !found ) { FormationTypes newFormationType = (FormationTypes)(rand() % (NR_FORMATION_TYPES-1)); if ( formationType == newFormationType ) { newFormationType = (FormationTypes)(NR_FORMATION_TYPES - 1); } if ( nrInvolved <= FORMATION_MAX_NR_ENEMYS[ newFormationType ] ) { formationType = newFormationType; found = true; } i++; } } void Formation::fillTargetPos( vector &targetPos ) { switch ( formationType ) { case FORMATION_V: { fillTargetPosFormationV( targetPos ); break; } case FORMATION_REVERSE_V: { fillTargetPosFormationReverseV( targetPos ); break; } case FORMATION_BLOCK: { fillTargetPosFormationBlock( targetPos ); break; } case FORMATION_LINE: { fillTargetPosFormationLine( targetPos ); break; } default: { for ( int i = 0; i < nrInvolved; i++ ) { targetPos.push_back( Vector2D(0,0) ); } break; } } } void Formation::fillTargetPosFormationV( vector &targetPos ) { switch ( nrInvolved ) { case 1: { targetPos.push_back( Vector2D(0,0) ); break; } case 2: { targetPos.push_back( Vector2D(-30,0) ); targetPos.push_back( Vector2D(30,0) ); break; } case 3: { targetPos.push_back( Vector2D(-50,25) ); targetPos.push_back( Vector2D(0,-25) ); targetPos.push_back( Vector2D(50,25) ); break; } case 4: { targetPos.push_back( Vector2D(-80,25) ); targetPos.push_back( Vector2D(-30,-25) ); targetPos.push_back( Vector2D(30,-25) ); targetPos.push_back( Vector2D(80,25) ); break; } case 5: { targetPos.push_back( Vector2D(-100,50) ); targetPos.push_back( Vector2D(-50,0) ); targetPos.push_back( Vector2D(0,-50) ); targetPos.push_back( Vector2D(50,0) ); targetPos.push_back( Vector2D(100,50) ); break; } case 6: { targetPos.push_back( Vector2D(-130,50) ); targetPos.push_back( Vector2D(-80,0) ); targetPos.push_back( Vector2D(-30,-50) ); targetPos.push_back( Vector2D(30,-50) ); targetPos.push_back( Vector2D(80,0) ); targetPos.push_back( Vector2D(130,50) ); break; } case 7: { targetPos.push_back( Vector2D(-150,75) ); targetPos.push_back( Vector2D(-100,25) ); targetPos.push_back( Vector2D(-50,-25) ); targetPos.push_back( Vector2D(0,-75) ); targetPos.push_back( Vector2D(50,-25) ); targetPos.push_back( Vector2D(100,25) ); targetPos.push_back( Vector2D(150,75) ); break; } default: { cout << "fillTargetPosFormationV: too many enemys involved: " << nrInvolved << endl; for ( int i = 0; i < nrInvolved; i++ ) { targetPos.push_back( Vector2D(0,0) ); } break; } } } void Formation::fillTargetPosFormationReverseV( vector &targetPos ) { switch ( nrInvolved ) { case 1: { targetPos.push_back( Vector2D(0,0) ); break; } case 2: { targetPos.push_back( Vector2D(-30,0) ); targetPos.push_back( Vector2D(30,0) ); break; } case 3: { targetPos.push_back( Vector2D(-50,-25) ); targetPos.push_back( Vector2D(0,25) ); targetPos.push_back( Vector2D(50,-25) ); break; } case 4: { targetPos.push_back( Vector2D(-80,-25) ); targetPos.push_back( Vector2D(-30,25) ); targetPos.push_back( Vector2D(30,25) ); targetPos.push_back( Vector2D(80,-25) ); break; } case 5: { targetPos.push_back( Vector2D(-100,-50) ); targetPos.push_back( Vector2D(-50,0) ); targetPos.push_back( Vector2D(0,50) ); targetPos.push_back( Vector2D(50,0) ); targetPos.push_back( Vector2D(100,-50) ); break; } case 6: { targetPos.push_back( Vector2D(-130,-50) ); targetPos.push_back( Vector2D(-80,0) ); targetPos.push_back( Vector2D(-30,50) ); targetPos.push_back( Vector2D(30,50) ); targetPos.push_back( Vector2D(80,0) ); targetPos.push_back( Vector2D(130,-50) ); break; } case 7: { targetPos.push_back( Vector2D(-150,-75) ); targetPos.push_back( Vector2D(-100,-25) ); targetPos.push_back( Vector2D(-50,25) ); targetPos.push_back( Vector2D(0,75) ); targetPos.push_back( Vector2D(50,25) ); targetPos.push_back( Vector2D(100,-25) ); targetPos.push_back( Vector2D(150,-75) ); break; } default: { cout << "fillTargetPosFormationReverseV: too many enemys involved: " << nrInvolved << endl; for ( int i = 0; i < nrInvolved; i++ ) { targetPos.push_back( Vector2D(0,0) ); } break; } } } void Formation::fillTargetPosFormationBlock( vector &targetPos ) { switch ( nrInvolved ) { case 1: { targetPos.push_back( Vector2D(0,0) ); break; } case 2: { targetPos.push_back( Vector2D(-30,0) ); targetPos.push_back( Vector2D(30,0) ); break; } case 3: { targetPos.push_back( Vector2D(-30,25) ); targetPos.push_back( Vector2D(0,-25) ); targetPos.push_back( Vector2D(30,25) ); break; } case 4: { targetPos.push_back( Vector2D(-30,-25) ); targetPos.push_back( Vector2D(-30,25) ); targetPos.push_back( Vector2D(30,-25) ); targetPos.push_back( Vector2D(30,25) ); break; } case 5: { targetPos.push_back( Vector2D(-40,-30) ); targetPos.push_back( Vector2D(-40,30) ); targetPos.push_back( Vector2D(0,0) ); targetPos.push_back( Vector2D(40,-30) ); targetPos.push_back( Vector2D(40,30) ); break; } case 6: { targetPos.push_back( Vector2D(-60,-30) ); targetPos.push_back( Vector2D(-60,30) ); targetPos.push_back( Vector2D(0,-30) ); targetPos.push_back( Vector2D(0,30) ); targetPos.push_back( Vector2D(60,-30) ); targetPos.push_back( Vector2D(60,30) ); break; } case 7: { targetPos.push_back( Vector2D(-60,-50) ); targetPos.push_back( Vector2D(-60,0) ); targetPos.push_back( Vector2D(0,-50) ); targetPos.push_back( Vector2D(0,0) ); targetPos.push_back( Vector2D(0,50) ); targetPos.push_back( Vector2D(60,-50) ); targetPos.push_back( Vector2D(60,0) ); break; } default: { cout << "fillTargetPosFormationBlock: too many enemys involved: " << nrInvolved << endl; for ( int i = 0; i < nrInvolved; i++ ) { targetPos.push_back( Vector2D(0,0) ); } break; } } } void Formation::fillTargetPosFormationLine( vector &targetPos ) { switch ( nrInvolved ) { case 1: { targetPos.push_back( Vector2D(0,0) ); break; } case 2: { targetPos.push_back( Vector2D(-30,0) ); targetPos.push_back( Vector2D(30,0) ); break; } case 3: { targetPos.push_back( Vector2D(-60,0) ); targetPos.push_back( Vector2D(0,0) ); targetPos.push_back( Vector2D(60,0) ); break; } case 4: { targetPos.push_back( Vector2D(-90,0) ); targetPos.push_back( Vector2D(-30,0) ); targetPos.push_back( Vector2D(30,0) ); targetPos.push_back( Vector2D(90,0) ); break; } case 5: { targetPos.push_back( Vector2D(-120,0) ); targetPos.push_back( Vector2D(-60,0) ); targetPos.push_back( Vector2D(0,0) ); targetPos.push_back( Vector2D(60,0) ); targetPos.push_back( Vector2D(120,0) ); break; } case 6: { targetPos.push_back( Vector2D(-150,0) ); targetPos.push_back( Vector2D(-90,0) ); targetPos.push_back( Vector2D(-30,0) ); targetPos.push_back( Vector2D(30,0) ); targetPos.push_back( Vector2D(90,0) ); targetPos.push_back( Vector2D(150,0) ); break; } default: { cout << "fillTargetPosFormationLine: too many enemys involved: " << nrInvolved << endl; for ( int i = 0; i < nrInvolved; i++ ) { targetPos.push_back( Vector2D(0,0) ); } break; } } } /////////////////////////////////////////////// void Formation::getBestMapping( vector &targetPos, vector &relPosForFighters ) { int actPerm[nrInvolved]; vector *bestMapping = new vector(); vector *testMapping = new vector(); for ( int i = 0; i < nrInvolved; i++ ) { bestMapping->push_back( Vector2D( 0,0 ) ); testMapping->push_back( Vector2D( 0,0 ) ); actPerm[ i ] = i; } float mapCost = 1000000; int nrPerm = factorial( nrInvolved ); for ( int perm = 0; perm < nrPerm; perm++ ) { calcNextPerm( actPerm ); float testMappingCost = calcTestMapping( actPerm, targetPos, testMapping ); if ( mapCost > testMappingCost ) { vector *tmpMapping = bestMapping; bestMapping = testMapping; testMapping = tmpMapping; mapCost = testMappingCost; } } for ( int e = 0; e < nrInvolved; e++ ) { relPosForFighters.push_back( (*bestMapping)[e] ); } delete bestMapping; delete testMapping; } void Formation::calcNextPerm( int *perm ) { int n = nrInvolved; int i = n-1; int j = n; int tmp; while ( i != 0 && perm[ i-1 ] >= perm[ i ] ) { i--; } if ( i == 0 ) { for ( int k = 0; k < n/2; k++ ) { tmp = perm[ k ]; perm[ k ] = perm[ n - k - 1 ]; perm[ n - k - 1 ] = tmp; } return; } while ( perm[ j-1 ] <= perm[ i-1 ] ) { --j; } tmp = perm[ i-1 ]; perm[ i-1 ] = perm[ j-1 ]; perm[ j-1 ] = tmp; i++; j = n; while ( i < j ) { tmp = perm[ i-1 ]; perm[ i-1 ] = perm[ j-1 ]; perm[ j-1 ] = tmp; i++; j--; } } float Formation::calcTestMapping( int *perm, vector &targetPos, vector *mapping ) { float cost = 0; for ( int i = 0; i < nrInvolved; i++ ) { // enemy i shall fly to the position targetPos[perm[i]] // save the vector from its actual pos to its new targetpos in mapping (*mapping)[ i ] = targetPos[ perm[i] ] + formationCenter - involvedEnemys[ i ]->getPos(); if ( cost < (*mapping)[ i ].getLength() ) { cost = (*mapping)[ i ].getLength(); } } return cost; } int Formation::factorial( int n ) { int result = 1; for ( int i = 2; i <= n; i++ ) { result *= i; } return result; } void Formation::shoot( int dT ) { if ( shotPattern == FORMATION_SP_NONE ) return; nextFirePrimary -= dT; // nextFireSecondary -= dT; float enemyRatio = FORMATION_MAX_NR_ENEMYS[ formationType ] / (float)nrInvolved; while ( nextFirePrimary < 0 ) { switch ( shotPattern ) { case FORMATION_SP_VOLLEY_FAST: case FORMATION_SP_VOLLEY_MEDIUM: case FORMATION_SP_VOLLEY_SLOW: { for ( int i = 0; i < nrInvolved; i++ ) { involvedEnemys[ i ]->firePrimary(); } break; } case FORMATION_SP_RAND_FAST: case FORMATION_SP_RAND_MEDIUM: case FORMATION_SP_RAND_SLOW: { involvedEnemys[ rand() % nrInvolved ]->firePrimary(); break; } case FORMATION_SP_LEFT_RIGHT_FAST: case FORMATION_SP_LEFT_RIGHT_MEDIUM: { actShootingEnemy = (actShootingEnemy + 1) % nrInvolved; involvedEnemys[ actShootingEnemy ]->firePrimary(); break; } case FORMATION_SP_RIGHT_LEFT_FAST: case FORMATION_SP_RIGHT_LEFT_MEDIUM: { actShootingEnemy--; if ( actShootingEnemy < 0 ) actShootingEnemy = nrInvolved-1; involvedEnemys[ actShootingEnemy ]->firePrimary(); break; } default: { break; } } nextFirePrimary += lroundf( (FORMATION_SP_PRIMARY_DELAY[ shotPattern ] + rand() % (FORMATION_SP_PRIMARY_RAND_DELAY[ shotPattern ] + 1)) * enemyRatio ); } }