/* ************************************************************************* * OldSkoolGravityGame (OSGG) Lunar Lander-like game for linux. Copyright (C) 2008 Jimmy Christensen ( dusted at dusted dot dk ) 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 3 of the License, 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, see . * ************************************************************************* */ #include #include #include #include #include #include #include #include #include #define VERSION "1.0SVN" #ifndef DATADIR #define DATADIR "./" #endif using namespace std; #define THRUSTINCRATE 0.01 #define GRAVITY 0.002 #define TURNINCRATE 0.15 enum entTypes { entLine, entShip, entBase, entWp, entEnemy }; enum gameStateEnum { GameStateEditor, GameStatePlaying, GameStatePause, GameStateQuit, GameStateNewGame, GameStateGameOver, GameStateNextLevel, GameStateStartEditor }; uint gameState = GameStateEditor; struct pStruct { int x,y; }; string levelFile; struct glPstruct { float x, y; }; struct structVert { float color[3]; struct glPstruct p; int collision; // 0 = none, 1 = normal(damage), 2 = platform/Landing gear }; typedef struct structVert vert; typedef struct glPstruct gPs; typedef struct pStruct pS; struct structEnt { struct glPstruct p; //position float rotation; //In DEG gPs vel; //Velocity, only used for enemies right now gPs baseP; //Base (start) position, used for nme int type; //type int id; bool base; // If it's a base, don't translate it every time.. }; typedef struct structEnt entity; struct structGameRules { int startLevel; //what level to start in float maxLandingRotForce; float maxLandingYel; float maxLandingXvel; int fuelConsumptionThrust; int fuelConsumptionTurn; int fuelMaxFuel; int ammoMaxAmmo; }; struct structGameRules gameRules; struct gameInfoStruct { float thrust; gPs velocity; float speed; //Relative speed float rotationForce; float distance; int fuel; int reloadTime; //After this amount of fames (60th's of 1 sec) player can shoot again int ammo; gPs radarDest; //Destination for package, points to the base where package should be delivered int destBase; // 0 = no package, id of base. int numBases; // Number of bases on map, set by loadMap int score; int numMissions; //number of finished missions int level; int lives; vector shipVerts; vector baseVerts; vector enemyVerts; vector shipStaticVerts; vector baseStaticVerts; vector enemyStaticVerts; vector nextObjective; //Where to deliver to next. int currentObjective; //What right now? }; struct gameInfoStruct gameInfo; float abs2(float x) { if (x<0) {return -x;} return x; } bool LinesCross(gPs aLineA, gPs aLineB, gPs bLineA, gPs bLineB) { //First line, first point float x0 = aLineA.x; float y0 = aLineA.y; //First Line, second point float x1 = aLineB.x; float y1 = aLineB.y; //Second Line, First point float x2 = bLineA.x; float y2 = bLineA.y; //Second Line, Second point float x3 = bLineB.x; float y3 = bLineB.y; float d=(x1-x0)*(y3-y2)-(y1-y0)*(x3-x2); if (abs2(d)<0.001f) {return 0;} float AB=((y0-y2)*(x3-x2)-(x0-x2)*(y3-y2))/d; if (AB>0.0 && AB<1.0) { float CD=((y0-y2)*(x1-x0)-(x0-x2)*(y1-y0))/d; if (CD>0.0 && CD<1.0) { return 1; } } return 0; } struct structShot { gPs pa,pb; gPs v; int age, life; bool fromShip; //if 1, wont collide with ship }; class classBullets { private: vector shots; public: void shoot(entity owner, gPs velocity); void render(); bool col(vector target, bool isShip); void envCol(vector< vector > polys); }; void classBullets::envCol(vector< vector > polys) { for(vector< vector >::iterator PolyIt = polys.begin(); PolyIt != polys.end(); ++PolyIt) { classBullets::col(*PolyIt, 0); } } bool classBullets::col(vector target, bool isShip) { gPs tLineA, tLineB; for(vector::iterator it = shots.begin(); it != shots.end(); ++it) { if(it->fromShip != isShip) { for(int ii=0; ii < target.size(); ii++) { if(ii==0) { tLineB = target[ii].p; } else { tLineA = tLineB; tLineB = target[ii].p; if(LinesCross(tLineA, tLineB, it->pa, it->pb)) { //Remove bullet. it = shots.erase(it); return(1); } } } } } } void classBullets::shoot(entity owner, gPs velocity) { struct structShot ts; ts.pb = owner.p; ts.pa.x = owner.p.x + 6.0* cos( owner.rotation * 0.0174532925 ); ts.pa.y = owner.p.y + 6.0* sin( owner.rotation * 0.0174532925 ); ts.v.x = velocity.x + 1.0 * cos( owner.rotation * 0.0174532925 ); ts.v.y = velocity.y + 1.0 * sin( owner.rotation * 0.0174532925 ); ts.age = 0; ts.life = 40; if(owner.type == entShip) { ts.fromShip=1; } else { ts.fromShip=0; } shots.push_back(ts); } void classBullets::render() { for(vector::iterator it = shots.begin(); it != shots.end(); ++it) { it->pa.x += it->v.x; it->pa.y += it->v.y; it->pb.x += it->v.x; it->pb.y += it->v.y; it->age++; if(it->age > it->life) { it = shots.erase(it); if(shots.size() < 1) break; } } } class classBullets bullets; //Move and rotate an entity according to ent.p and ent.rotation void updateEntVerts(entity ent, vector& entverts, vector statV ) { entverts = statV; float tx, ty; for(vector::iterator i = entverts.begin(); i != entverts.end(); ++i) { tx = i->p.x * cos( (ent.rotation-90.0) * 0.0174532925 ) - ( i->p.y *sin( (ent.rotation-90.0) * 0.0174532925 )) ; ty = i->p.x * sin( (ent.rotation-90.0) * 0.0174532925 ) + ( i->p.y *cos( (ent.rotation-90.0) * 0.0174532925 )); i->p.x=tx; i->p.y=ty; i->p.x += ent.p.x; i->p.y += ent.p.y; } } #define PI 3.14159265 void loadMap(vector< vector >& polys, vector& ents) { polys.clear(); ents.clear(); string line; ifstream load; vector tvs; //temp to store the poly as we read it vert tv; //temp to store the vert as we read it entity te; //temp to store the entity while we read it gameInfo.numBases=0; gameInfo.nextObjective.clear(); load.open(levelFile.data()); if(!load.is_open()) { cout << "could not load '" << levelFile << "'" << endl; return; } int parseState=0, dataNum=0; while(!load.eof()) { getline(load, line); if(line == "StartPoly") { parseState=1; } else if(line=="EndPoly") { parseState=0; polys.push_back(tvs); tvs.clear(); } else if(parseState==1) { if(line == "StartVert") { parseState=2; dataNum=0; } } else if(parseState==2) { if(line == "EndVert") { tvs.push_back(tv); parseState=1; } else { dataNum++; switch(dataNum) { case 1: tv.p.x = atof(line.data()); break; case 2: tv.p.y = atof(line.data()); break; case 3: tv.color[0] = atof(line.data()); break; case 4: tv.color[1] = atof(line.data()); break; case 5: tv.color[2] = atof(line.data()); break; case 6: tv.collision = atoi(line.data()); break; default: cout << "??" << line << endl; break; } } } else if(line =="StartEntity") { parseState=3; dataNum=0; } else if(parseState==3) { if(line=="EndEntity") { dataNum=0; te.baseP = te.p; ents.push_back(te); parseState=0; } else { dataNum++; switch(dataNum) { case 1: te.p.x = atof(line.data()); break; case 2: te.p.y = atof(line.data()); break; case 3: te.type = atoi(line.data()); if(te.type==entBase) { gameInfo.numBases++; } break; case 4: te.rotation = atof(line.data()); break; case 5: te.id = atoi(line.data()); if(te.type==entBase && te.id != gameInfo.numBases) { cout << "error: base entity have id:" << te.id << " but numBases is:" << gameInfo.numBases << endl; } break; default: cout << ">?" << line << endl; break; } } } else if(line=="StartMission") { parseState=4; } else if(parseState==4) { if(line=="EndMission") { parseState=0; } else { gameInfo.nextObjective.push_back( atoi(line.data()) ); } } } } void readEnt(string File, vector& verts) { string line; ifstream f; vert tv; //temp to store the vert as we read it f.open(File.data()); if(!f.is_open()) { cout << "Failed to open file" << endl; return; } int parseState=0, dataNum=0; while(!f.eof()) { getline(f, line); if(line == "StartVert") { parseState=1; dataNum=0; } else if(parseState==1) { if(line == "EndVert") { parseState=0; verts.push_back(tv); } else { dataNum++; switch(dataNum) { case 1: tv.p.x = atof(line.data()); break; case 2: tv.p.y = atof(line.data()); case 3: tv.color[0] = atof(line.data()); break; case 4: tv.color[1] = atof(line.data()); break; case 5: tv.color[2] = atof(line.data()); break; case 6: tv.collision = atoi(line.data()); break; default: cout << "??" << line << endl; break; } } } } } bool boxCol(gPs posa, gPs posb, float dist) { if(posa.x < posb.x+dist && posa.x > posb.x-dist) { if(posa.y < posb.y+dist && posa.y > posb.y-dist) { return(1); } } return(0); } bool PolyCol( vector PolyA, vector PolyB ) { gPs paLineA, paLineB, pbLineA, pbLineB; bool paFirst=1, pbFirst=1; int prevColType=0; int checks=0; for( vector::iterator paVertIt = PolyA.begin(); paVertIt != PolyA.end(); ++paVertIt) { if(paVertIt->collision) { if(paFirst || prevColType == 2) //if the last one was a 2, this one is the start of the next line to collision detect { paFirst=0; paLineB=paVertIt->p; } else { paLineA=paLineB; paLineB=paVertIt->p; pbFirst=1; //Great now we have a line, lets check it against every line in the other poly. for(vector::iterator pbVertIt = PolyB.begin(); pbVertIt != PolyB.end(); ++pbVertIt) { if(pbVertIt->collision) { if(pbFirst) { pbFirst=0; pbLineB=pbVertIt->p; } else { pbLineA=pbLineB; pbLineB=pbVertIt->p; //Here we have two lines. paLineA, paLineB, and pbLineA, pbLineB. checks++; if(LinesCross(paLineA, paLineB, pbLineA, pbLineB)) { return(1); } } } } } } prevColType = paVertIt->collision; } return(0); } bool landCol(vector baseVerts, vector shipVerts) { gPs baseA, baseB; gPs shipA, shipB; int prevColType=0; //Find points on ship for(vector::iterator shipIt = shipVerts.begin(); shipIt != shipVerts.end(); ++shipIt) { if(shipIt->collision == 2) { if(prevColType == 0) { shipA = shipIt->p; prevColType++; } else { shipB = shipIt->p; prevColType++; } } } prevColType=0; for(vector::iterator baseIt = baseVerts.begin(); baseIt != baseVerts.end(); ++baseIt) { //Find the line to check with if(prevColType==2) { baseB = baseIt->p; if(shipA.x > baseA.x && shipA.x < baseB.x) //Is left side inside { if(shipB.x > baseA.x && shipB.x < baseB.x) // Is right side inside { if(shipA.y < baseA.y && shipB.y < baseB.y) //Is left side under the line { if(shipB.y < baseA.y && shipB.y < baseB.y) //Is right side under the line { if(shipA.y > baseA.y-1.0 && shipB.y > baseA.y-1.0) //Check that left side is above the height { //check velocity: //cout << "Impact:"<= gameRules.maxLandingYel) //If it's not smaller, it's not faster { float temp = gameInfo.velocity.x; if(temp < 0) temp *= -1.0; if(temp <= gameRules.maxLandingXvel) //it have to be slower { temp = gameInfo.rotationForce; if(temp < 0) temp *= -1.0; if(temp <= gameRules.maxLandingRotForce) { return(1); } } } } } } } } } prevColType = baseIt->collision; if(prevColType==2) baseA = baseIt->p; } return(0); } void setMission(int missionId, vector ents) { gameInfo.destBase = gameInfo.nextObjective[missionId]; //Set destination on radar. for(vector::iterator searchIt = ents.begin(); searchIt != ents.end(); ++searchIt) { if(searchIt->id == gameInfo.destBase) { gameInfo.radarDest = searchIt->p; } } } void initGame(vector< vector >& polys, vector& ents) { loadMap(polys, ents); gameInfo.currentObjective=0; gameInfo.score=0; gameInfo.destBase=0; gameInfo.fuel = gameRules.fuelMaxFuel; gameInfo.ammo = gameRules.ammoMaxAmmo; if(gameInfo.nextObjective.size() > 1) setMission(0,ents); } void initNewGame(vector< vector >& polys, vector& ents) { gameRules.maxLandingRotForce = 1.0; //degrees, both ways gameRules.maxLandingYel = -0.20; //downward gameRules.maxLandingXvel= 0.15; //both sides gameRules.fuelConsumptionThrust = 15; gameRules.fuelConsumptionTurn = 4; gameRules.fuelMaxFuel = 5500; gameRules.ammoMaxAmmo = 1000; gameState = GameStatePlaying; gameInfo.numMissions=0; //Amount of missions taken gameInfo.level=gameRules.startLevel; initGame(polys, ents); } void shipCrash(entity ship) { gameInfo.velocity.x=0; gameInfo.velocity.y=0; gameInfo.thrust=0; gameInfo.rotationForce=0; gameState = GameStateGameOver; cout << endl; cout << "Dead" << endl; cout << "Level "< argc) { cout << "Error: Specify level file." << endl; return(0); } chosenLevel=1; levelFile = argv[i]; } else if(i!=1) { cout << "Error: unknown argument '" << argv[i] << "'" << endl; return(0); } } return(1); } int main(int argc, char **argv) { srand ( time(NULL) ); /* These can be overwritten by the call to parseCmdLine */ levelFile = DATADIR"levels/0.level"; gameState = GameStateNewGame; if(!parseCmdLine(argc, argv)) { return(1); } SDL_Event event; //Init sdl and screen if(SDL_Init(SDL_INIT_TIMER) <0 ) { cout << SDL_GetError() << endl; } cout << "Osgg Server"< testVerts; vert tempVert; gPs collisionPoint; bool crashed=0; char score[256]; vector ents; //entities vector activeVerts; vector< vector > polys; readEnt("ship.txt", gameInfo.shipStaticVerts); readEnt("base.txt", gameInfo.baseStaticVerts); readEnt("enemy.txt", gameInfo.enemyStaticVerts); //Enter Main loop while(gameState != GameStateQuit) { while(SDL_PollEvent(&event)) { switch(gameState) { case GameStateNewGame: initNewGame(polys,ents); gameState=GameStatePlaying; break; case GameStatePlaying: //Check if any bullets hit the enviroment: bullets.envCol(polys); bullets.render(); //Ents: for(vector::iterator it = ents.begin(); it != ents.end(); ++it) { if(it->type == entShip) { Uint8* keyStates = SDL_GetKeyState( NULL ); if(keyStates[SDLK_LEFT]) { if(gameInfo.fuel > 0) { gameInfo.rotationForce += TURNINCRATE; gameInfo.fuel -= gameRules.fuelConsumptionTurn; } } else if(keyStates[SDLK_RIGHT]) { if(gameInfo.fuel > 0) { gameInfo.rotationForce -= TURNINCRATE; gameInfo.fuel -= gameRules.fuelConsumptionTurn; } } if(keyStates[SDLK_UP]) { if(gameInfo.fuel > 0) { gameInfo.thrust = THRUSTINCRATE; gameInfo.fuel -= gameRules.fuelConsumptionThrust; } else { gameInfo.thrust = 0; } } else { gameInfo.thrust = 0; } if(gameInfo.reloadTime != 0) { gameInfo.reloadTime--; } if(keyStates[SDLK_SPACE]) { if(gameInfo.reloadTime == 0 && gameInfo.ammo >= 100) { gameInfo.ammo -= 100; gameInfo.reloadTime = 25; bullets.shoot(*it, gameInfo.velocity); } } //Update the speed gameInfo.speed = abs2(gameInfo.velocity.x*100)+abs2(gameInfo.velocity.y*100); //Update velocities gameInfo.velocity.x += gameInfo.thrust * cos( (it->rotation*0.0174532925) ); gameInfo.velocity.y += gameInfo.thrust * sin( (it->rotation*0.0174532925) ); gameInfo.velocity.y -= GRAVITY; if(gameInfo.rotationForce > 0) { gameInfo.rotationForce /= 1.01; if(gameInfo.rotationForce < 0) { gameInfo.rotationForce = 0; } } else if(gameInfo.rotationForce < 0) { gameInfo.rotationForce /= 1.01; if(gameInfo.rotationForce > 0) { gameInfo.rotationForce = 0; } } //Collision with terrain for(vector< vector >::iterator PolyIt = polys.begin(); PolyIt != polys.end(); ++PolyIt) { if(PolyCol(*PolyIt, gameInfo.shipVerts)) { shipCrash(*it); } } crashed=0; //Collision with ents for(vector::iterator entIt = ents.begin(); entIt != ents.end(); ++entIt) { //Collition with entities if(entIt->type==entBase) { updateEntVerts(*entIt, gameInfo.baseVerts, gameInfo.baseStaticVerts); crashed=PolyCol(gameInfo.baseVerts, gameInfo.shipVerts); } else if(entIt->type==entEnemy) { updateEntVerts(*entIt, gameInfo.enemyVerts, gameInfo.enemyStaticVerts); crashed=PolyCol(gameInfo.enemyVerts, gameInfo.shipVerts); //check if this enemy is being hit if(bullets.col(gameInfo.enemyVerts, 0)) { entIt = ents.erase(entIt); // gameInfo.score -= 3000; break; } } if(crashed) { shipCrash(*it); } else { //Is he landing on a base? if(entIt->type==entBase) { if(landCol(gameInfo.baseVerts, gameInfo.shipVerts)) { //Subtract 1 from score again // gameInfo.score -= 1; if(gameInfo.fuel < gameRules.fuelMaxFuel) gameInfo.fuel += 15; if(gameInfo.fuel > gameRules.fuelMaxFuel) gameInfo.fuel = gameRules.fuelMaxFuel; if(gameInfo.ammo < gameRules.ammoMaxAmmo) gameInfo.ammo += 3; if(gameInfo.ammo > gameRules.ammoMaxAmmo) gameInfo.ammo = gameRules.ammoMaxAmmo; it->rotation=90.0; if(gameInfo.velocity.y < 0.0) gameInfo.velocity.y=0; gameInfo.velocity.x=0; gameInfo.rotationForce=0; if(gameInfo.numBases > 1) { //Is this the destination base? if(gameInfo.destBase == entIt->id) { gameInfo.numMissions++; //Do he have more missions left? gameInfo.currentObjective++; if(gameInfo.currentObjective < gameInfo.nextObjective.size()) { //Give it to him setMission(gameInfo.currentObjective, ents); } else { gameState = GameStateNextLevel; } } } } } } //not crashing } it->rotation += gameInfo.rotationForce; it->p.x += gameInfo.velocity.x; it->p.y += gameInfo.velocity.y; } else /** Update enemies **/ if(it->type == entEnemy) { it->vel.y -= GRAVITY; if(it->p.y <= it->baseP.y-0.3) { it->vel.y += 0.01; } it->p.y += it->vel.y; } } break; /** END OF GAME **/ } } ticks += SDL_GetTicks() - lastTicks; lastTicks = SDL_GetTicks(); SDL_Delay(10); } return(0); }