#include "state.hpp" #include #include #include #include #include "missile_hit_type.hpp" #include "object.hpp" #include "missile.hpp" #include "player.hpp" #include "ship.hpp" #include "commands.hpp" #include "trace.hpp" #include "explosion.hpp" #include "util.hpp" namespace game { void State::init(int numPlanets, bool devMode) { for (Planet *planet : planets) { delete(planet); } planets.clear(); //for (Player *player : players) { //} m_nextId = 0; m_time = 0.0; m_shipRadius = 0.02; m_maxMissileDistance = 2.0; m_playerRespawnTime = 2.0; m_defaultEnergy = 10.0; m_maxNumTraces = 10; m_developerMode = devMode; m_nextExplosionId = 0; setPlayingFieldCenter(0, 0); // TODO: need aspect ratio or data! //setPlayingFieldSize(1000, 300); setupPlanets(numPlanets); } bool State::findPlanetSpawnPosition(bool planetIsSun, float radius, glm::vec2 *pos) { (void) planetIsSun; bool tooNearToCenter = true; bool collidesWithOtherPlanet = true; const glm::vec2 spawnArea = 0.9f * (m_playingFieldSize/std::max(m_playingFieldSize.x, m_playingFieldSize.y)); // distribute but not in the center and not next to other planets int tries = 0; do { *pos = spawnArea * util::randv2_m1_1(); collidesWithOtherPlanet = false; tooNearToCenter = glm::length(*pos) < 0.1; if (!tooNearToCenter) { for (const Planet *other : planets) { float d = glm::distance(other->position, *pos); float extraDist = (other->material == Planet::Material::Sun) ? 4.0 : 1.0; if (d < extraDist*other->radius + radius) { collidesWithOtherPlanet = true; break; } } } if (tries++ > 1000) { return false; } } while(collidesWithOtherPlanet || tooNearToCenter); return true; } void State::setupPlanets(int numPlanets) { for (int i=0; i 0.9) { radius = 0.9; } } glm::vec2 pos; if (findPlanetSpawnPosition(mat == Planet::Material::Sun, radius, &pos)) { planets.push_back(new Planet(pos, i, radius, mat)); } } } Planet::Material State::materialForStandardPlanetDistribution(int index) { // a few sun/water/sand/metall planents and the rest rocks switch(index) { case 0: return Planet::Material::Sun; case 1: case 2: return Planet::Material::Water; case 3: case 4: return Planet::Material::Sand; case 5: return Planet::Material::Metal; default: return Planet::Material::Rock; } } bool State::spawnShipForPlayer(Player *player) { glm::vec2 spawnPos; if (!findFreePositionWithRadius(5.0 * m_shipRadius, spawnPos)) { return false; } player->ship = new Ship(spawnPos, m_shipRadius); ships.push_back(player->ship); player->energy = m_defaultEnergy; return true; } int State::addPlayer() { int playerId = m_nextId++; Player *player = new Player(playerId); players.push_back(player); return playerId; } void State::quitPlayer(int playerId) { std::cout << playerId << " quit" << std::endl; Player *player = playerForId(playerId); if (player != nullptr) { for (Missile *missile : player->missiles) { missile->player = nullptr; } players.remove(player); } } void State::clear(int playerId) { std::cout << playerId << " clear" << std::endl; } void State::setName(int playerId, std::string name) { // discard if not unique for (const Player *other : players) { if (name == other->name) { std::cout << "name '" << name << "' already given to player #" << other->id << std::endl; return; } } playerForId(playerId)->name = name; } void State::setSpeed(int playerId, double speed) { playerForId(playerId)->speed = speed; } void State::advancePlayerShipSpawns(float dt) { (void) dt; for (Player *player : players) { if (!player->alive) { player->deadTimeCounter += dt; if (player->deadTimeCounter >= m_playerRespawnTime) { player->deadTimeCounter = 0; player->alive = true; std::cout<<"respawning player " << player->id << " now!" << std::endl; } } if (player->alive && player->ship == NULL) { if (!spawnShipForPlayer(player)) { std::cout<<"could not spawn ship for player!" << std::endl; } } } } void State::advancePlayerCommands(float dt) { (void) dt; for (Player *player : players) { if (player->alive) { player->energy += dt; } // try to execute as much queued commands as possible. while (player->hasCommandInQueue()) { Command *command = player->peekCommand(); if (!command->ready(player, this)) { break; } command->apply(player, this); player->popCommand(); } } } void State::playerKillsPlayer(Player *killer, Player *victim) { if (killer == nullptr || victim == nullptr) { std::cerr <<"error: killer / victim is NULL!" << std::endl; exit(-1); return; } std::cout<<"player " << killer->id << " killed " << victim->id << std::endl; // destroy ship ships.remove(victim->ship); delete(victim->ship); victim->ship = nullptr; victim->alive = false; victim->deadTimeCounter = 0.0; // TODO // add points // TODO // message } void State::advancePlayerMissiles(float dt) { // advance missiles for (Player *player : players) { std::vector rm; for (Missile *missile : player->missiles) { //std::cout<<"missile: " << (long unsigned int) missile << std::endl; const Missile::Event evt = missile->advance(this, dt); const bool isHit = (evt.hit != Hit::Nothing); if (missile->trace != nullptr) { missile->trace->addPointFromMissile(isHit); // force point if missile gets destroyed a } if (!isHit) { continue; } bool spawnExplosion = true; switch(evt.hit) { case Hit::Planet: // TODO: add black spot on the planet. // TODO: if water planet, add waves // TODO: if gas planet, add nice gas explosion effect // and start burning on this spot for some time. //std::cout<<"hit Planet" << std::endl; break; case Hit::Ship: //std::cout<<"hit Player" << std::endl; playerKillsPlayer(playerForId(evt.playerIdKiller), playerForId(evt.playerIdVictim)); break; case Hit::BorderOfUniverse: //std::cout<<"missile left the universe." << std::endl; spawnExplosion = false; break; default: break; } if (spawnExplosion) { addExplosionFromHit(&evt); } if (missile->trace != nullptr) { missile->trace->finish(); } rm.push_back(missile); } for (Missile *missile : rm) { player->missiles.remove(missile); delete(missile); } } } void State::advanceExplosions(float dt) { std::vector rm; for (Explosion *explosion : explosions) { explosion->age += dt; if (explosion->age >= explosion->maxAge) { rm.push_back(explosion); } } for (Explosion *explosion : rm) { explosions.remove(explosion); delete(explosion); } } void State::advance(float dt) { m_time += dt; advancePlayerShipSpawns(dt); advanceExplosions(dt); advanceTraceAges(dt); advancePlayerCommands(dt); advancePlayerMissiles(dt); } Player *State::playerForId(int playerId) { for (Player *p : players) { if (p->id == playerId) { return p; } } return nullptr; } bool State::findFreePositionWithRadius(float radius, glm::vec2 &pos) { bool first = ships.size() == 0; int tries = 0; while(true) { bool noCollision = true; //pos = glm::linearRand2(-1.0, 1.0); if (first) { first = false; pos = glm::vec2(0.0, 0.0); } else { pos = glm::vec2(util::randf_0_1(), util::randf_m1_1()); } for (Planet *p : planets) { if (glm::distance(p->position, pos) <= radius) { noCollision = false; break; } } if (noCollision) { return true; } if (tries++ >= 1000) { pos = glm::vec2(0.0, 0.0); return false; } } } void State::commandForPlayer(int playerId, game::Command *cmd) { Player *player = playerForId(playerId); if (player != nullptr) { player->addCommand(cmd); } } void State::addTrace(Trace *trace) { //int count = 0; //for (Trace *old : traces) { // if (old->playerId == trace->playerId) { // count++; // } //} //if (count > m_maxNumTraces) { //} traces.push_back(trace); } void State::deleteTrace(Trace *trace) { if (trace->missile != nullptr) { // delete backlink. // XXX: there's a missile without a trace now. trace->missile->trace = nullptr; } delete(trace); } void State::addExplosionFromHit(const Missile::Event *evt) { if (evt->hit == Hit::Nothing || evt->hit == Hit::BorderOfUniverse) { return; } explosions.push_back(new Explosion(m_nextExplosionId++, evt->position, evt->missileVelocity, evt->hit)); } void State::advanceTraceAges(float dt) { std::vector rm; for (Trace *trace : traces) { if (trace->missile == nullptr) { trace->age += dt; if (trace->age >= trace->maxAge) { rm.push_back(trace); } } } for (Trace *trace : rm) { traces.remove(trace); deleteTrace(trace); } } void State::setPlayingFieldCenter(int center_x, int center_y) { m_playingFieldCenter = glm::vec2((float) center_x, (float) center_y); } void State::setPlayingFieldSize(int width, int height) { m_playingFieldSize = glm::vec2(width, height); } }