diff --git a/game/game.cpp b/game/game.cpp index 2d07b9f..0389e72 100644 --- a/game/game.cpp +++ b/game/game.cpp @@ -8,8 +8,8 @@ Game::Game() { - // advance simulation with 100 Hz - m_time_step = 1.0 / 100.0; + // advance simulation in fixed steps with 100 Hz + m_time_step = 1.0 / 100.0; m_time_for_next_step = 0.0; m_state = new game::State(); @@ -20,23 +20,42 @@ Game::Game() bool Game::cycle(float dt) { +#if 1 + // XXX the following is just testing code to do things + + static float total = 0.0; static float acc = 0.0; - acc += dt; + + //if (total == 0.0) { + // float speed = 0.005; + // m_state->players[0]->addCommand(new game::SetSpeedCommand(speed)); + // for (int i=0; i<100; i++) { + // float a = 2.0 * M_PI * util::randf_0_1(); + // m_state->players[0]->addCommand(new game::ShootCommand(a)); + // } + //} + + acc += dt; + total += dt; float spawnInterval = 0.1; while(acc > spawnInterval) { acc -= spawnInterval; - float a = 2.0 * M_PI * util::randf_0_1(); + float angle = 2.0 * M_PI * util::randf_0_1(); float speed = 0.005; - m_state->players[0]->addCommand(new game::ShootCommand(a, speed)); - } -#if 1 - if (dt >= 10.0) { - //std::cout<<"time to big: " << dt << std::endl; - dt = m_time_step; + m_state->players[0]->addCommand(new game::SetSpeedCommand(speed)); + m_state->players[0]->addCommand(new game::ShootCommand(angle)); + + //static bool done = false; + //if (total >= 10.0 && !done) { + // done = true; + + // m_state->players[0]->addCommand(new game::ClearTracesCommand()); + //} } +#endif //std::cout<<"adding dt: " << dt << std::endl; m_time_for_next_step += dt; @@ -53,11 +72,4 @@ bool Game::cycle(float dt) //std::cout << m_time_for_next_step << " s remaining time, " << steps << " steps taken." << std::endl; return true; -#else - (void) dt; - - m_state->advance(dt); - - return true; -#endif } diff --git a/game/game_window.hpp b/game/game_window.hpp index 02c1aa0..884a628 100644 --- a/game/game_window.hpp +++ b/game/game_window.hpp @@ -11,6 +11,7 @@ #include "state/player.hpp" #include "state/planet.hpp" #include "state/ship.hpp" +#include "state/explosion.hpp" class GameWindow : public endofthejedi::GLWindow { private: @@ -46,7 +47,11 @@ protected: drawTrace(trace); } - for (const game::Ship *ship : m_game->state()->ships) { + for (const game::Explosion *explosion : m_game.state()->explosions) { + drawExplosion(explosion); + } + + for (const game::Ship *ship : m_game.state()->ships) { drawShip(ship->position); } @@ -78,7 +83,6 @@ protected: } void drawMissile(const glm::vec2 &pos) - { glm::vec3 color = glm::vec3(1.0, 1.0, 0.3); m_renderer.drawCircle(pos.x, pos.y, 0.01, color.x, color.y, color.z, 6); @@ -92,6 +96,17 @@ protected: } } + void drawExplosion(const game::Explosion *explosion) + { + // TODO: transparent + // TODO: with glow in the middle + float r = explosion->maxRadius * (explosion->age / explosion->maxAge); + + // TODO: transparency? + glm::vec3 color = glm::vec3(1.0, 0.9, 0.1); + m_renderer.drawCircle(explosion->position.x, explosion->position.y, r, color.x, color.y, color.z, 64); + } + public: GameWindow(unsigned int width, unsigned int height, Game* game) : endofthejedi::GLWindow(width, height) { m_game = game; } diff --git a/game/state/commands.cpp b/game/state/commands.cpp index b84a214..1a64d89 100644 --- a/game/state/commands.cpp +++ b/game/state/commands.cpp @@ -2,6 +2,8 @@ #include "trace.hpp" +#include + namespace game { void ShootCommand::apply(Player *player, State *state) const { @@ -10,23 +12,72 @@ namespace game { // TODO: idea // shoot multiple rockets at once or from different positions after // level up / upgrade ... - Missile *missile = new Missile(player, player->ship->position, m_angle, m_speed); + Missile *missile = new Missile(player, player->ship->position, m_angle, player->speed); Trace *trace = new Trace(missile); missile->trace = trace; - player->energy -= m_speed; + player->energy -= player->speed; player->missiles.push_back(missile); state->addTrace(trace); } - bool ShootCommand::allowed(const Player *player, const State *state) const + bool ShootCommand::ready(const Player *player, const State *state) const { (void) state; // TODO - return player->alive && player->energy >= m_speed; + return player->alive && player->energy >= player->speed; //return player->alive; } + + void ChangeNameCommand::apply(Player *player, State *state) const + { + // discard if not unique + for (const Player *other : state->players) { + if (m_name == other->name) { + std::cout << "name '" << m_name << "' already given to player #" << other->id << std::endl; + return; + } + } + + player->name = m_name; + } + + void SetSpeedCommand::apply(Player *player, State *state) const + { + (void) state; + player->speed = m_speed; + } + + void ClearTracesCommand::apply(Player *player, State *state) const + { + std::cout<<"clearing traces!!!" << std::endl; + + size_t i = 0; + while(i < state->traces.size()) { + Trace *trace = state->traces[i]; + if (trace->missile == nullptr && trace->player == player) { + state->deleteTrace(i); + + } else { + i++; + } + } + } + +#if 0 + bool TakeOverPlayerCommand::ready(const Player *player, const State *state) const + { + (void) state; + return state->havePlayerGrantFor(player->id, m_otherPlayerId); + } + + void TakeOverPlayerCommand::apply(Player *player, State *state) const + { + if ( + state->playerGrant(player->id, m_otherPlayerId); + } +#endif } diff --git a/game/state/commands.hpp b/game/state/commands.hpp index 5c8ff97..dd88c0c 100644 --- a/game/state/commands.hpp +++ b/game/state/commands.hpp @@ -9,6 +9,10 @@ #include namespace game { + /** + * Base class for commands. + * must derive from this. + */ class Command { public: Command() @@ -19,7 +23,11 @@ namespace game { { } - virtual bool allowed(const Player *player, const State *state) const + // check whether the command is ready to execute. + // if not, wait. + // note: stuff like for admin should be done in execute() to discard the + // command and not block the queue. + virtual bool ready(const Player *player, const State *state) const { (void) player; (void) state; @@ -38,19 +46,95 @@ namespace game { virtual std::string name() const { return ""; } }; + /**********************************************************************/ + /* Implemented commands */ + /**********************************************************************/ + + /* + * Shoot at angle and optional speed. + * TODO + */ class ShootCommand : public Command { public: - ShootCommand(float angle, float speed) : m_angle(angle), m_speed(speed) + ShootCommand(float angle) : m_angle(angle) { } std::string name() const { return ""; } - void apply( Player *player, State *state) const; - bool allowed(const Player *player, const State *state) const; + void apply( Player *player, State *state) const; + bool ready(const Player *player, const State *state) const; private: float m_angle; + }; + + /** + * Change the name of the player if it will be uniuqe. + */ + class ChangeNameCommand : public Command { + public: + ChangeNameCommand(const std::string &name) : m_name(name) + { + } + + std::string name() const { return ""; } + + void apply(Player *player, State *state) const; + + private: + std::string m_name; + }; + + /** + * clear all traces of this player that are done + */ + class ClearTracesCommand : public Command { + public: + ClearTracesCommand() + { + } + + std::string name() const { return ""; } + + void apply(Player *player, State *state) const; + }; + + /** + * Set default speed of next shots for this player. + */ + class SetSpeedCommand : public Command { + public: + SetSpeedCommand(float speed) : m_speed(speed) + { + } + + std::string name() const { return ""; } + + void apply(Player *player, State *state) const; + + private: float m_speed; }; + +#if 0 + /** + * Take over a session for a player that left. + * An admin must confirm this command. + */ + class TakeOverPlayerCommand : public Command { + public: + TakeOverPlayerCommand(int otherPlayerId) : m_otherPlayerId(otherPlayerId) + { + } + + std::string name() const { return ""; } + + void apply( Player *player, State *state) const; + bool ready(const Player *player, const State *state) const; + + private: + int m_otherPlayerId; + }; +#endif } diff --git a/game/state/explosion.hpp b/game/state/explosion.hpp index 66c234f..32169eb 100644 --- a/game/state/explosion.hpp +++ b/game/state/explosion.hpp @@ -2,31 +2,32 @@ #include +#include "util.hpp" + +#include "missile_hit_type.hpp" + namespace game { /** * Explosion: just an effect which looks good. */ class Explosion { public: - enum class Kind { - MissileExplodesInSpace, // missile explode in free space - MissileAgainstPlanet, // explosion of missile when it hits a planet - MissileAgainstShip // bigger explosion: missile hits a ship which explodes with it - }; - - Explosion(const glm::vec2 &pos, Kind kind, float maxAge=1.0) - : position(pos), kind(kind), age(0.0), maxAge(maxAge) + Explosion(const glm::vec2 &pos, Hit hit, float maxAge=1.0) + : hit(hit) + , position(pos) + , age(0.0) + , maxAge(maxAge * (1.0 + 0.1*util::randf_0_1())) + , maxRadius(0.05) { } - const Kind kind; // kind of the explosion + const Hit hit; // kind of the explosion depends on the hit type const glm::vec2 position; // position where it starts float age; // age (in seconsd) of the explosion // age (in seconds) when the explosion is not visible // anymore and will disappear afterwards const float maxAge; + const float maxRadius; // current radius depends on time. }; } - -#endif diff --git a/game/state/missile.cpp b/game/state/missile.cpp index b789b5f..cbc3e58 100644 --- a/game/state/missile.cpp +++ b/game/state/missile.cpp @@ -49,7 +49,7 @@ namespace game { if (dist <= planet->radius) { // TODO: collect all hits and return the first one only // TODO: find exact hit position! - return Missile::Event(position, Missile::HitObject::HitPlanet); + return Missile::Event(position, planet->id); } dist *= 20.0; @@ -77,7 +77,7 @@ namespace game { // check if distance to center of the universe is getting too big float distToCenter = glm::length(position); if (distToCenter > state->maxMissileDistance()) { - return Missile::Event(position, Missile::HitObject::LeftUniverse); + return Missile::Event(position, Hit::BorderOfUniverse); } return Missile::Event(position); diff --git a/game/state/missile.hpp b/game/state/missile.hpp index c68706d..c7aa1df 100644 --- a/game/state/missile.hpp +++ b/game/state/missile.hpp @@ -3,6 +3,8 @@ #include #include +#include "missile_hit_type.hpp" + namespace game { class State; class Ship; @@ -15,39 +17,42 @@ namespace game { // trace then belongs to the player. class Missile { public: - enum class HitObject { - Nothing, - HitPlayer, - HitPlanet, - // HitMissile, - LeftUniverse - }; // missile advances to pos. if hit != Nothing, it hits something and // stops existing afterwards. class Event { public: - Event(const glm::vec2 &pos) : Event(pos, HitObject::Nothing) + Event(const glm::vec2 &pos) + : Event(pos, Hit::Nothing) { } - Event(const glm::vec2 &pos, HitObject hit) - : hit(hit), pos(pos) + Event(const glm::vec2 &pos, Hit hit) + : hit(hit), position(pos) { } + Event(const glm::vec2 &pos, int planetId) : Event(pos, Hit::Planet) + { + this->planetId = planetId; + } + Event(const glm::vec2 &pos, int playerIdKiller, int playerIdVictim) - : Event(pos, HitObject::HitPlayer) + : Event(pos, Hit::Ship) { this->playerIdKiller = playerIdKiller; this->playerIdVictim = playerIdVictim; } - HitObject hit; - glm::vec2 pos; + Hit hit; + glm::vec2 position; + // if a player was hit, these are valid. int playerIdKiller; int playerIdVictim; + + // if a planet was hit, this is valid + int planetId; }; Missile(Player *player, const glm::vec2 &pos, float angle, float speed); diff --git a/game/state/missile_hit_type.hpp b/game/state/missile_hit_type.hpp new file mode 100644 index 0000000..04f1af3 --- /dev/null +++ b/game/state/missile_hit_type.hpp @@ -0,0 +1,19 @@ +#pragma once + +namespace game { + /* + * A missile can hit different objects the type says which one. + * Used in various places so extra file for this. + */ + enum class Hit { + Nothing, + Ship, + Planet, + + // TODO could be needed for really fancy explosions because + // this is cool and should noh happen very often + // AgainstMissile, + + BorderOfUniverse + }; +} diff --git a/game/state/planet.hpp b/game/state/planet.hpp index 0407683..e6ff6e7 100644 --- a/game/state/planet.hpp +++ b/game/state/planet.hpp @@ -2,11 +2,39 @@ #include "object.hpp" +#include + namespace game { class Planet : public Object { public: - Planet(const glm::vec2 &pos, float r) : Object(pos, r) + /** + * Planets are build out of one material. + * TODO: support mixture or multiple material per planet. + */ + enum class Material { + Rock, + Metal, + Sand, + Gas, + Ice, + Water, + Sun + }; + + Planet(const glm::vec2 &pos, int id, float r) : Planet(pos, id, r, Material::Rock) { } + + Planet(const glm::vec2 &pos, int id, float r, Material mat) + : Object(pos, r) + , id(id) + , material(mat) + , seed(rand()) + { + } + + int id; + Material material; // for rendering and physics (can fly through sun and outer gas planets) + int seed; // just for rendering variation }; } diff --git a/game/state/player.hpp b/game/state/player.hpp index 199a411..3a6aeb4 100644 --- a/game/state/player.hpp +++ b/game/state/player.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -15,12 +16,14 @@ namespace game { public: int id; bool alive; + float speed; float energy; float deadTimeCounter; Ship *ship; + std::string name; std::vector missiles; - Player(int id) : id(id), alive(true), energy(0.0), ship(nullptr) + Player(int id) : id(id), alive(true), speed(0.01), energy(0.0), ship(nullptr), name("") { } diff --git a/game/state/state.cpp b/game/state/state.cpp index acc2502..63ef686 100644 --- a/game/state/state.cpp +++ b/game/state/state.cpp @@ -6,6 +6,7 @@ #include #include +#include "missile_hit_type.hpp" #include "object.hpp" #include "missile.hpp" #include "player.hpp" @@ -13,6 +14,7 @@ #include "ship.hpp" #include "commands.hpp" #include "trace.hpp" +#include "explosion.hpp" #include "util.hpp" @@ -23,6 +25,7 @@ namespace game { m_maxMissileDistance = 2.0; m_playerRespawnTime = 2.0; m_defaultEnergy = 10.0; + m_maxNumTraces = 10; bool planetsOnCircle = false; @@ -43,7 +46,7 @@ namespace game { } } while(glm::length(pos) < 0.2 && tries++ < 1000); - planets.push_back(new Planet(pos, 0.03 + 0.07*util::randf_0_1())); + planets.push_back(new Planet(pos, i, 0.03 + 0.07*util::randf_0_1())); } } @@ -108,7 +111,7 @@ namespace game { // try to execute as much queued commands as possible. while (player->hasCommandInQueue()) { Command *command = player->peekCommand(); - if (!command->allowed(player, this)) { + if (!command->ready(player, this)) { break; } @@ -145,12 +148,9 @@ namespace game { // TODO // add points - // + // TODO // message - // - // TODO - // respawn timer } void State::advancePlayerMissiles(float dt) @@ -164,26 +164,27 @@ namespace game { //std::cout<<"missile: " << (long unsigned int) missile << std::endl; const Missile::Event evt = missile->advance(this, dt); - missile->trace->addPointFromMissile(); + const bool isHit = (evt.hit != Hit::Nothing); - // TODO: - // spawn just if the path differs + if (missile->trace != nullptr) { + missile->trace->addPointFromMissile(isHit); // force point if missile gets destroyed a + } - if (evt.hit == Missile::HitObject::Nothing) { + if (!isHit) { i++; } else { switch(evt.hit) { - case Missile::HitObject::HitPlanet: + case Hit::Planet: //std::cout<<"hit Planet" << std::endl; break; - case Missile::HitObject::HitPlayer: + case Hit::Ship: //std::cout<<"hit Player" << std::endl; playerKillsPlayer(playerForId(evt.playerIdKiller), playerForId(evt.playerIdVictim)); break; - case Missile::HitObject::LeftUniverse: + case Hit::BorderOfUniverse: //std::cout<<"missile left the universe." << std::endl; break; @@ -191,6 +192,12 @@ namespace game { break; } + addExplosionFromHit(&evt); + + if (missile->trace != nullptr) { + missile->trace->finish(); + } + player->missiles.erase(player->missiles.begin() + i); delete(missile); //std::cout<age += dt; + if (explosion->age >= explosion->maxAge) { + delete(explosion); + explosions.erase(explosions.begin()+i); + continue; + } + + i++; + } + } + void State::advance(float dt) { - //std::cout<<"advance ship spawns" << std::endl; advancePlayerShipSpawns(dt); - //std::cout<<"advance commands" << std::endl; + advanceExplosions(dt); + advancePlayerCommands(dt); - //std::cout<<"advance missiles" << std::endl; advancePlayerMissiles(dt); + } Player *State::playerForId(int playerId) @@ -270,6 +293,56 @@ namespace game { 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(size_t index) + { + if (index >= traces.size()) { + std::cerr << "can't find trace with invalid index " << index << std::endl; + return; + } + + Trace *trace = traces[index]; + + //std::cout<<"removing a trace" << std::endl; + if (trace->missile != nullptr) { + // delete backlink. + // XXX: there's a missile without a trace now. + trace->missile->trace = nullptr; + } + + traces.erase(traces.begin() + index); + delete(trace); + } + + void State::deleteTrace(Trace *trace) + { + size_t i = 0; + while(i < traces.size()) { + if (traces[i] == trace) { + deleteTrace(i); + break; + } + } + } + + void State::addExplosionFromHit(const Missile::Event *evt) + { + if (evt->hit == Hit::Nothing || evt->hit == Hit::BorderOfUniverse) { + return; + } + + explosions.push_back(new Explosion(evt->position, evt->hit)); + } } diff --git a/game/state/state.hpp b/game/state/state.hpp index a526d06..40c71f6 100644 --- a/game/state/state.hpp +++ b/game/state/state.hpp @@ -7,14 +7,28 @@ #include +#include "missile.hpp" + +// TODO: +// give points for equipment / better weapons / more energy when: +// - player discovers the universe +// +// - the shot which made a kill was much longer than the direkt line between +// player and his target +// +// - add wormholes +// - add blackholes +// - shoot through suns which add a fire mantle to the rocket to make it more +// thick and dangerous but it gets destroyed after some time. + namespace game { // forward declarations class Command; - class Missile; class Player; class Planet; class Ship; class Trace; + class Explosion; class State { public: @@ -55,16 +69,21 @@ namespace game { // add a trace to the list of traces. void addTrace(Trace *trace); + // delete traces with this command + void deleteTrace(Trace *trace); // using a pointer + void deleteTrace(size_t index); // using an index + /*************************************************************************/ /* Rendering */ /*************************************************************************/ // Game items which should be rendered are here: // (access missiles by iterating over player's missiles attribute) - std::vector planets; - std::vector ships; - std::vector players; - std::vector traces; + std::vector planets; + std::vector ships; + std::vector players; + std::vector traces; + std::vector explosions; private: /*************************************************************************/ @@ -73,6 +92,9 @@ namespace game { void playerKillsPlayer(Player *killer, Player *victim); + void addExplosionFromHit(const Missile::Event *evt); + + void advanceExplosions(float dt); void advancePlayerShipSpawns(float dt); void advancePlayerCommands(float dt); void advancePlayerMissiles(float dt); @@ -90,5 +112,6 @@ namespace game { float m_shipRadius; float m_defaultEnergy; int m_nextId=0; + int m_maxNumTraces; }; }; diff --git a/game/state/trace.cpp b/game/state/trace.cpp index 4d93d2c..0b51695 100644 --- a/game/state/trace.cpp +++ b/game/state/trace.cpp @@ -4,6 +4,8 @@ #include "missile.hpp" +#include + namespace game { Trace::TracePoint::TracePoint(const Missile *missile) : position(missile->position) @@ -11,7 +13,16 @@ namespace game { { } - Trace::Trace(const Missile *missile) : missile(missile), fidelityCounter(0) + Trace::~Trace() + { + //std::cout<<"~Trace()" << std::endl; + } + + Trace::Trace(Missile *missile) + : missile(missile) + , player(missile->player) + , fidelityCounter(0) + , age(0.0) { points.push_back(TracePoint(missile)); } @@ -24,4 +35,10 @@ namespace game { points.push_back(TracePoint(missile)); } } + + void Trace::finish() + { + //std::cout<<"trace finished now!" << std::endl; + missile = nullptr; + } } diff --git a/game/state/trace.hpp b/game/state/trace.hpp index ab33b2f..2b4f4f0 100644 --- a/game/state/trace.hpp +++ b/game/state/trace.hpp @@ -5,6 +5,7 @@ namespace game { class Missile; + class Player; /* * Trace of a missile through the space. @@ -12,7 +13,8 @@ namespace game { */ class Trace { public: - Trace(const Missile *missile); + Trace(Missile *missile); + ~Trace(); // Add the current position of the missile as a new point on the // trace. @@ -21,6 +23,10 @@ namespace game { // the missile) in case the fidelityCounter would skip the current position. void addPointFromMissile(bool forceAdd=false); + // call this to mark the trace as finish and disconnect it from the + // missile. + void finish(); + // TODO: add extendLastPointToPosition() method for saving points / // optimization later on @@ -35,11 +41,16 @@ namespace game { }; std::vector points; - const Missile *missile; // missile which creates this path. + Missile *missile; // missile which creates this path. + Player *player; // counter which is incremented each time addPointFromMissile() is called. // when reaching a certain value the point is saved for // optimization. int fidelityCounter; + + // age of the trace. if too old, it can be removed to save + // space/power + float age; }; }