553 lines
14 KiB
C++
553 lines
14 KiB
C++
#include "state.hpp"
|
|
|
|
#include <cmath>
|
|
|
|
#include <glm/vec2.hpp>
|
|
#include <glm/vec3.hpp>
|
|
#include <glm/gtx/norm.hpp>
|
|
|
|
#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 "state_update_event.hpp"
|
|
#include "events/explosion_event.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_nextPlayerId = 0;
|
|
m_nextMissileId = 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<numPlanets; i++) {
|
|
Planet::Material mat = materialForStandardPlanetDistribution(i);
|
|
|
|
float radius = 0.03 + 0.07*util::randf_0_1();
|
|
if (i == 0) {
|
|
// sun is bigger but not too big
|
|
radius += 0.05;
|
|
if (radius > 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;
|
|
}
|
|
|
|
size_t State::addPlayer()
|
|
{
|
|
size_t playerId = m_nextPlayerId++;
|
|
Player *player = new Player(playerId);
|
|
players.push_back(player);
|
|
return playerId;
|
|
}
|
|
|
|
void State::quitPlayer(size_t 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(size_t playerId)
|
|
{
|
|
std::cout << playerId << " clear" << std::endl;
|
|
}
|
|
|
|
void State::setName(size_t 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(size_t 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<Missile*> 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<Explosion*> rm;
|
|
|
|
for (Explosion *explosion : explosions) {
|
|
explosion->age += dt;
|
|
if (explosion->age >= explosion->maxAge) {
|
|
rm.push_back(explosion);
|
|
}
|
|
}
|
|
|
|
for (Explosion *explosion : rm) {
|
|
explosions.remove(explosion);
|
|
m_nextEvents.push_back(new ExplosionEvent(StateUpdateEvent::LifeCycle::Destroy, explosion));
|
|
//delete(explosion);
|
|
}
|
|
}
|
|
|
|
void State::advance(float dt)
|
|
{
|
|
//std::cout<<"[state] (init) cycle: update events length is " << m_nextEvents.size() << std::endl;
|
|
|
|
m_time += dt;
|
|
|
|
advancePlayerShipSpawns(dt);
|
|
|
|
advanceExplosions(dt);
|
|
|
|
advanceTraceAges(dt);
|
|
|
|
advancePlayerCommands(dt);
|
|
|
|
advancePlayerMissiles(dt);
|
|
|
|
//std::cout<<"[state] (before move) cycle: update events length is " << m_nextEvents.size() << std::endl;
|
|
|
|
// put collected events into that list.
|
|
//m_allEvents.push_back(std::move(m_nextEvents));
|
|
for (StateUpdateEvent *evt : m_nextEvents) {
|
|
m_allEvents.push_back(evt);
|
|
}
|
|
m_nextEvents.clear();
|
|
|
|
//std::cout<<"[state] (after move) cycle: update events length is " << m_nextEvents.size() << std::endl;
|
|
|
|
// finally remove things.
|
|
//m_nextEvents.clear();
|
|
}
|
|
|
|
Player *State::playerForId(size_t 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(size_t 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::addMissile(Missile *missile)
|
|
{
|
|
Trace *trace = new Trace(missile);
|
|
missile->trace = trace;
|
|
|
|
addTrace(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;
|
|
}
|
|
|
|
Explosion *explosion = new Explosion(m_nextExplosionId++, evt->position, evt->missileVelocity, evt->hit);
|
|
explosions.push_back(explosion);
|
|
|
|
m_nextEvents.push_back(new ExplosionEvent(StateUpdateEvent::LifeCycle::Create, explosion));
|
|
}
|
|
|
|
void State::advanceTraceAges(float dt)
|
|
{
|
|
std::vector<Trace*> 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);
|
|
}
|
|
|
|
size_t State::generateMissileId()
|
|
{
|
|
return m_nextMissileId++;
|
|
}
|
|
|
|
void State::applyAndClearAllOldStateUpdates()
|
|
{
|
|
// TODO: delete the items for events that are to be removed in proper
|
|
// way
|
|
|
|
//for (std::list<StateUpdateEvent*> list : m_allEvents) {
|
|
// for (StateUpdateEvent *evt : list) {
|
|
// delete(evt);
|
|
// }
|
|
// list.clear();
|
|
//}
|
|
|
|
for (StateUpdateEvent *evt : m_allEvents) {
|
|
if (evt->lifeCycle() == StateUpdateEvent::LifeCycle::Destroy) {
|
|
switch(evt->eventType()) {
|
|
case StateUpdateEvent::EventType::Explosion:
|
|
{
|
|
ExplosionEvent *ee = static_cast<ExplosionEvent*>(evt);
|
|
std::cout<<"got explosion delete event, finally deleting explosion #"
|
|
<< ee->explosion->id << std::endl;
|
|
|
|
delete(ee->explosion);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
std::cout<<"warning: unhandled deletion event for: event type "
|
|
<< (int) evt->eventType() << std::endl;
|
|
}
|
|
}
|
|
delete(evt);
|
|
}
|
|
|
|
m_allEvents.clear();
|
|
}
|
|
|
|
std::list<StateUpdateEvent*> State::currentStateUpdateEvents() const
|
|
{
|
|
return m_allEvents;
|
|
}
|
|
}
|