adding event meachinsm for game items that change and sound support.
This commit is contained in:
parent
9ce106c179
commit
f49d07fdc5
15 changed files with 1468 additions and 43 deletions
|
@ -22,6 +22,10 @@ set(GAME_SRC
|
||||||
util.cpp
|
util.cpp
|
||||||
game.cpp
|
game.cpp
|
||||||
|
|
||||||
|
# TODO: make optional!
|
||||||
|
sound/sound.cpp
|
||||||
|
sound/sound_effects.cpp
|
||||||
|
|
||||||
state/object.cpp
|
state/object.cpp
|
||||||
state/explosion.cpp
|
state/explosion.cpp
|
||||||
state/trace.cpp
|
state/trace.cpp
|
||||||
|
@ -43,4 +47,4 @@ include_directories(${assimp_INCLUDE_DIRS})
|
||||||
add_executable(game ${GAME_SRC})
|
add_executable(game ${GAME_SRC})
|
||||||
setup_target(game)
|
setup_target(game)
|
||||||
|
|
||||||
target_link_libraries(game X11 epoxy pthread ${assimp_LIBRARIES} assimp)
|
target_link_libraries(game X11 epoxy pthread ${assimp_LIBRARIES} assimp -lportaudio -lsndfile)
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
#include "network/server.hpp"
|
#include "network/server.hpp"
|
||||||
#include "options.hpp"
|
#include "options.hpp"
|
||||||
|
|
||||||
|
#include "sound/sound_effects.hpp"
|
||||||
|
#include "state/state_update_event.hpp"
|
||||||
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
||||||
uint64_t optionsFlags;
|
uint64_t optionsFlags;
|
||||||
|
@ -19,6 +22,7 @@ using asio::ip::tcp;
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
bool devMode = false;
|
bool devMode = false;
|
||||||
|
bool soundEnabled = false;
|
||||||
char port[]="3490";
|
char port[]="3490";
|
||||||
|
|
||||||
static struct option long_options[] =
|
static struct option long_options[] =
|
||||||
|
@ -33,6 +37,7 @@ int main(int argc, char *argv[])
|
||||||
// {"delete", required_argument, 0, 'd'},
|
// {"delete", required_argument, 0, 'd'},
|
||||||
{"autorun", required_argument, 0, 'a'},
|
{"autorun", required_argument, 0, 'a'},
|
||||||
{"port", required_argument, 0, 'p'},
|
{"port", required_argument, 0, 'p'},
|
||||||
|
{"sound", no_argument, 0, 's'},
|
||||||
{"fps", no_argument, 0, 'f'},
|
{"fps", no_argument, 0, 'f'},
|
||||||
{"dev", no_argument, 0, 'd'},
|
{"dev", no_argument, 0, 'd'},
|
||||||
{0, 0, 0, 0}
|
{0, 0, 0, 0}
|
||||||
|
@ -41,7 +46,7 @@ int main(int argc, char *argv[])
|
||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
|
|
||||||
while(1){
|
while(1){
|
||||||
char c = getopt_long (argc, argv, "p:fad",
|
char c = getopt_long (argc, argv, "p:fads",
|
||||||
long_options, &option_index);
|
long_options, &option_index);
|
||||||
if (c == -1)
|
if (c == -1)
|
||||||
break;
|
break;
|
||||||
|
@ -51,6 +56,7 @@ int main(int argc, char *argv[])
|
||||||
case 'f':
|
case 'f':
|
||||||
SET_FLAG(SHOW_FPS,true);
|
SET_FLAG(SHOW_FPS,true);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'd':
|
case 'd':
|
||||||
std::cout<<"enabling developer mode" << std::endl;
|
std::cout<<"enabling developer mode" << std::endl;
|
||||||
devMode = true;
|
devMode = true;
|
||||||
|
@ -60,6 +66,10 @@ int main(int argc, char *argv[])
|
||||||
strcpy(port,optarg);
|
strcpy(port,optarg);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 's':
|
||||||
|
soundEnabled = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case 'a':
|
case 'a':
|
||||||
SET_FLAG(TEST_AUTORUN, true);
|
SET_FLAG(TEST_AUTORUN, true);
|
||||||
break;
|
break;
|
||||||
|
@ -75,6 +85,13 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
srand(time(NULL));
|
srand(time(NULL));
|
||||||
|
|
||||||
|
//sound::SoundEffects *sounds = nullptr;
|
||||||
|
if (soundEnabled) {
|
||||||
|
//if (sound::initSound()) {
|
||||||
|
//sounds = new sound::SoundEffects();
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
Game game;
|
Game game;
|
||||||
game.state()->setDeveloperMode(devMode);
|
game.state()->setDeveloperMode(devMode);
|
||||||
|
|
||||||
|
@ -89,6 +106,10 @@ int main(int argc, char *argv[])
|
||||||
while(window.running()){
|
while(window.running()){
|
||||||
window.poll();
|
window.poll();
|
||||||
io_service.poll();
|
io_service.poll();
|
||||||
|
|
||||||
|
//if (sounds != nullptr) {
|
||||||
|
// sounds.advance(game->state());
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
|
@ -33,5 +33,5 @@ private:
|
||||||
char m_rcv_data[max_length];
|
char m_rcv_data[max_length];
|
||||||
game::State* m_state;
|
game::State* m_state;
|
||||||
bool m_started = false;
|
bool m_started = false;
|
||||||
int m_pid;
|
size_t m_pid;
|
||||||
};
|
};
|
||||||
|
|
968
game/sound/sound.cpp
Normal file
968
game/sound/sound.cpp
Normal file
|
@ -0,0 +1,968 @@
|
||||||
|
#include "sound.hpp"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include <portaudio.h>
|
||||||
|
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
// for dumping sound
|
||||||
|
#include <sndfile.h>
|
||||||
|
|
||||||
|
namespace sound {
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
PaStream *stream;
|
||||||
|
|
||||||
|
unsigned int framesPerBuffer;
|
||||||
|
unsigned int sampleRate;
|
||||||
|
|
||||||
|
unsigned int sound_uid_counter;
|
||||||
|
|
||||||
|
// constant
|
||||||
|
unsigned int maxNumSounds;
|
||||||
|
|
||||||
|
// num active sonuds
|
||||||
|
unsigned int highestSoundIndex;
|
||||||
|
|
||||||
|
// pointer to array of sound handles that has this number of active sounds
|
||||||
|
SoundHandle **sounds;
|
||||||
|
|
||||||
|
bool dumping;
|
||||||
|
const char *dump_filename;
|
||||||
|
SNDFILE *outfile;
|
||||||
|
SF_INFO sfinfo;
|
||||||
|
|
||||||
|
bool init;
|
||||||
|
} SoundContext;
|
||||||
|
|
||||||
|
// singleton so no arguments must be supplied for play() functions etc.
|
||||||
|
static SoundContext context;
|
||||||
|
|
||||||
|
static double startup_time = -1.0;
|
||||||
|
static double global_time = -1.0;;
|
||||||
|
|
||||||
|
static double timestamp_double(void)
|
||||||
|
{
|
||||||
|
struct timeval now;
|
||||||
|
gettimeofday(&now, NULL);
|
||||||
|
|
||||||
|
//printf("%d %d\n", (int) now.tv_sec, (int) now.tv_usec);
|
||||||
|
|
||||||
|
double t = now.tv_sec;
|
||||||
|
t += now.tv_usec / 1000000.0f;
|
||||||
|
//printf("%f\n", t);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *stringFromEnvelope(enum EnvelopeState envelope)
|
||||||
|
{
|
||||||
|
switch(envelope) {
|
||||||
|
case New: return "New";
|
||||||
|
case Wait: return "Wait";
|
||||||
|
case Rise: return "Rise";
|
||||||
|
case BeginOvershoot: return "BeginOvershoot";
|
||||||
|
case EndOvershoot: return "EndOvershoot";
|
||||||
|
case Decay: return "Decay";
|
||||||
|
case Hold: return "Hold";
|
||||||
|
case Done: return "Done";
|
||||||
|
default: assert(false); return "<error>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkEnvelopeState(SoundHandle *handle, enum EnvelopeState next)
|
||||||
|
{
|
||||||
|
if (handle == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handle->envelope != next) {
|
||||||
|
#if 1
|
||||||
|
printf("[sound] global time %4.3f #%d (%s) t=%f %5s -> %5s\n",
|
||||||
|
global_time,
|
||||||
|
handle->uid,
|
||||||
|
handle->name,
|
||||||
|
handle->time,
|
||||||
|
stringFromEnvelope(handle->envelope),
|
||||||
|
stringFromEnvelope(next));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
handle->envelope = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopDumpingWav(void)
|
||||||
|
{
|
||||||
|
if (!context.init || !context.dumping) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int err = sf_close(context.outfile);
|
||||||
|
context.outfile = NULL;
|
||||||
|
context.dumping = false;
|
||||||
|
|
||||||
|
if (err < 0) {
|
||||||
|
printf("warning: stop dumping wav returned error: %d\n", err);
|
||||||
|
} else {
|
||||||
|
printf("sucessfully stopped dumping to file %s\n", context.dump_filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool dumpSamples(float *samples, size_t numSamples)
|
||||||
|
{
|
||||||
|
// TODO: dumping in mono?
|
||||||
|
|
||||||
|
if (!context.init || !context.dumping) {
|
||||||
|
printf("warning: can't dump samples: context not initialized or not currently dumping!\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sf_writef_float(context.outfile, samples, numSamples);
|
||||||
|
sf_write_sync(context.outfile); // force writing
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool startDumpingWav(const char *filename)
|
||||||
|
{
|
||||||
|
if (!context.init) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.dumping) {
|
||||||
|
printf("warning: already dumping to %s!\n", context.dump_filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.sfinfo.channels = 2;
|
||||||
|
context.sfinfo.format = SF_FORMAT_WAV | SF_FORMAT_FLOAT;
|
||||||
|
context.sfinfo.samplerate = context.sampleRate;
|
||||||
|
|
||||||
|
context.outfile = sf_open(filename, SFM_WRITE, &context.sfinfo);
|
||||||
|
|
||||||
|
if (context.outfile == NULL) {
|
||||||
|
printf("Unable to dump to output file %s.\n", filename);
|
||||||
|
sf_perror(NULL);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
//printf("start dumping wav to file %s\n", filename);
|
||||||
|
context.dump_filename = filename;
|
||||||
|
context.dumping = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static float amplitudeForTime(SoundHandle *handle)
|
||||||
|
{
|
||||||
|
//float amplitude = handle->amplitude;
|
||||||
|
float amplitude = 1.0;
|
||||||
|
|
||||||
|
float t = handle->time;
|
||||||
|
if (t < handle->_riseTime) {
|
||||||
|
// linear increase
|
||||||
|
float a = t / handle->_riseTime;
|
||||||
|
|
||||||
|
if (handle->_relativeOvershootTime == 0.0) {
|
||||||
|
checkEnvelopeState(handle, Rise);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//
|
||||||
|
// |--overshoot-up-| |-overshoot-down-|
|
||||||
|
// |-------------overshoot------------|
|
||||||
|
// |-----------------rise-time -----------------|
|
||||||
|
|
||||||
|
// make it raise to full level earlier
|
||||||
|
const float overshoot = 2.0*handle->_relativeOvershootTime;
|
||||||
|
|
||||||
|
a *= 1.0+overshoot;
|
||||||
|
|
||||||
|
// compensate the overshoot during last r/2 time to normal level
|
||||||
|
// (1.0) by inversing slope
|
||||||
|
if (a < 1.0) {
|
||||||
|
checkEnvelopeState(handle, Rise);
|
||||||
|
|
||||||
|
} else if (a < 1.0 + (overshoot/2.0)) {
|
||||||
|
checkEnvelopeState(handle, BeginOvershoot);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
a = 2.0 + overshoot - a;
|
||||||
|
checkEnvelopeState(handle, EndOvershoot);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
amplitude *= a;
|
||||||
|
//printf("amplitude rising: %f\n", a);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
t -= handle->_riseTime;
|
||||||
|
if (t < handle->_holdTime) {
|
||||||
|
// stays constant during this time
|
||||||
|
checkEnvelopeState(handle, Hold);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
t -= handle->_holdTime;
|
||||||
|
if (t < handle->_decayTime) {
|
||||||
|
// linear decrease
|
||||||
|
float a = 1.0 - (t / handle->_decayTime);
|
||||||
|
//printf("amplitude decaying : %f\n", a);
|
||||||
|
amplitude *= a;
|
||||||
|
checkEnvelopeState(handle, Decay);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
bool switchToDone = false;
|
||||||
|
t -= handle->_decayTime;
|
||||||
|
if (t < handle->_waitTime) {
|
||||||
|
// still waiting
|
||||||
|
checkEnvelopeState(handle, Wait);
|
||||||
|
|
||||||
|
} else if (handle->looping && (handle->_loopCount == -1 || handle->_loopCount > 0)) {
|
||||||
|
// waited long enough to restart
|
||||||
|
// TODO: dont waste time! does stuff stay synchronized
|
||||||
|
// over a long period of time?
|
||||||
|
//handle->time = 0.0;
|
||||||
|
handle->time -= handle->_riseTime + handle->_waitTime + handle->_holdTime + handle->_decayTime;
|
||||||
|
|
||||||
|
if (handle->_loopCount != -1) {
|
||||||
|
handle->_loopCount--;
|
||||||
|
//printf("decrement loopCount to %d\n", handle->_loopCount);
|
||||||
|
if (handle->_loopCount == 0) {
|
||||||
|
switchToDone = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switchToDone = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (switchToDone) {
|
||||||
|
// zero
|
||||||
|
checkEnvelopeState(handle, Done);
|
||||||
|
handle->_done = true;
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
amplitude = 0.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//int phase = handle->envelope;
|
||||||
|
//printf("%f, %d, %f\n", handle->time, phase, amplitude);
|
||||||
|
|
||||||
|
if (handle->minAmplitude != 0.0) {
|
||||||
|
amplitude = handle->minAmplitude + amplitude/(1.0-handle->minAmplitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
amplitude *= handle->amplitude;
|
||||||
|
|
||||||
|
return amplitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: support stereo fully
|
||||||
|
|
||||||
|
// args:
|
||||||
|
// n: n-th last history value
|
||||||
|
static float historyValue(SoundHandle *handle, size_t n)
|
||||||
|
{
|
||||||
|
if (handle == NULL) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (handle->history == NULL) {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
if (n >= handle->numHistorySamples) {
|
||||||
|
// TODO clear history when looping?
|
||||||
|
// or add zero samples?
|
||||||
|
printf("warning: try to access more history values than saved.\n");
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle->history[2 * ((handle->lastHistorySample+n) % handle->numHistorySamples)];
|
||||||
|
}
|
||||||
|
|
||||||
|
void saveHistoryValue(SoundHandle *handle, float v)
|
||||||
|
{
|
||||||
|
if (handle == NULL || handle->history == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle->history[2*handle->lastHistorySample] = v;
|
||||||
|
handle->lastHistorySample = (handle->lastHistorySample+1) % handle->numHistorySamples;
|
||||||
|
}
|
||||||
|
|
||||||
|
float sumHistory(SoundHandle *handle, float initialWeight, float v)
|
||||||
|
{
|
||||||
|
float result = initialWeight * v;
|
||||||
|
float totalWeight = initialWeight;
|
||||||
|
|
||||||
|
size_t i;
|
||||||
|
for (i=0; i<handle->numHistorySamples; i++) {
|
||||||
|
float weight = 1.0f / (float) (1+i);
|
||||||
|
//float weight = 1.0f;
|
||||||
|
result += weight * historyValue(handle, i);
|
||||||
|
totalWeight += weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
result /= totalWeight;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return false if playing done.
|
||||||
|
static bool advanceSound(SoundHandle *handle, float *out, unsigned int framesPerBuffer)
|
||||||
|
{
|
||||||
|
assert(handle != NULL);
|
||||||
|
assert(framesPerBuffer > 0);
|
||||||
|
assert(out != NULL);
|
||||||
|
|
||||||
|
if (handle == NULL || framesPerBuffer == 0 || out == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add length to sounds
|
||||||
|
|
||||||
|
// TODO: find out the period, write just one period and then copy the
|
||||||
|
// values.
|
||||||
|
//float dt = handle->freq / context.sampleRate;
|
||||||
|
|
||||||
|
// TODO: divide into three parts for envelops
|
||||||
|
// because they are linear in the phases so interpolating between that
|
||||||
|
// should be made easier
|
||||||
|
|
||||||
|
// advance parameters for testing
|
||||||
|
switch(handle->_type) {
|
||||||
|
//float div = 10.0;
|
||||||
|
//float t = (((int)(100*handle->time/div)) % 101) / 100.0;
|
||||||
|
//handle->leakage = 0.5+0.5*sin(2.0*M_PI*t);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float dt = 1.0 / context.sampleRate;
|
||||||
|
const float ds = 2.0 * M_PI * handle->freq * dt;
|
||||||
|
|
||||||
|
// XXX too long range when becomes done
|
||||||
|
unsigned int i;
|
||||||
|
for (i=0; i<framesPerBuffer; i++) {
|
||||||
|
handle->time += dt;
|
||||||
|
|
||||||
|
// TODO: save this state too?
|
||||||
|
if (handle->time < 0.0) {
|
||||||
|
checkEnvelopeState(handle, Wait);
|
||||||
|
// this sound can not yet be heard
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add phase as argument.
|
||||||
|
|
||||||
|
float v;
|
||||||
|
switch (handle->_type) {
|
||||||
|
case SoundFrequency:
|
||||||
|
handle->state += ds;
|
||||||
|
v = sin(handle->state);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SoundWhiteNoise:
|
||||||
|
v = util::randf_m1_1();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SoundPinkNoise:
|
||||||
|
//v = (historyValue(handle, 0) + Randf_m1_1()) / 2.0f;
|
||||||
|
//saveHistoryValue(handle, v);
|
||||||
|
v = (handle->state + util::randf_m1_1()) / 2.0f;
|
||||||
|
handle->state = v;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SoundBrownianNoise:
|
||||||
|
// this is created by integrating white noise.
|
||||||
|
|
||||||
|
{
|
||||||
|
float white = util::randf_m1_1();
|
||||||
|
float p = handle->leakage * handle->state;
|
||||||
|
float s = handle->scaling * white;
|
||||||
|
float z = p + s;
|
||||||
|
if (handle->skipDirectionIfClamping && fabsf(z) >= 1.0f) {
|
||||||
|
z = p - s;
|
||||||
|
}
|
||||||
|
handle->state = z;
|
||||||
|
v = z;
|
||||||
|
}
|
||||||
|
//v = sumHistory(handle, handle->freq, randf_m1_1());
|
||||||
|
//v = sumHistory(handle, 0.1, randf_m1_1());
|
||||||
|
//v = 0.2*historyValue(handle, 1) + 0.3*historyValue(handle, 0) + 0.5*randf_m1_1();
|
||||||
|
//v = (historyValue(handle, 1) + historyValue(handle, 0) + randf_m1_1()) / 3.0;
|
||||||
|
//saveHistoryValue(handle, v);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
{
|
||||||
|
float a = 0.8;
|
||||||
|
v = a*handle->state + (1.0-a)*randf_m1_1();
|
||||||
|
handle->state = v;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
printf("warning: unimplemented sound type for #%d: %d\n", handle->uid, handle->_type);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handle->_useEnvelope) {
|
||||||
|
v *= amplitudeForTime(handle);
|
||||||
|
if (handle->_done) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v *= handle->amplitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add values from this sound te to current sound level
|
||||||
|
*out++ += v;
|
||||||
|
*out++ += v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This routine will be called by the PortAudio engine when audio is needed.
|
||||||
|
It may called at interrupt level on some machines so don't do anything
|
||||||
|
that could mess up the system like calling malloc() or free().
|
||||||
|
*/
|
||||||
|
static int sound_callback(const void *inputBuffer,
|
||||||
|
void *outputBuffer,
|
||||||
|
unsigned long framesPerBuffer,
|
||||||
|
const PaStreamCallbackTimeInfo *timeInfo,
|
||||||
|
PaStreamCallbackFlags statusFlags,
|
||||||
|
void *userData)
|
||||||
|
{
|
||||||
|
(void) inputBuffer;
|
||||||
|
(void) timeInfo;
|
||||||
|
(void) statusFlags;
|
||||||
|
(void) userData;
|
||||||
|
|
||||||
|
// update time
|
||||||
|
double now = timestamp_double();
|
||||||
|
|
||||||
|
// should not happen. just in case.
|
||||||
|
global_time = now - startup_time;
|
||||||
|
|
||||||
|
//printf("global time raw: %f\n", global_time);
|
||||||
|
if (global_time < 0.0) {
|
||||||
|
global_time = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have 32 bit float format so cast here
|
||||||
|
float *out = (float *) outputBuffer;
|
||||||
|
|
||||||
|
// set all to zero
|
||||||
|
unsigned int i;
|
||||||
|
for (i=0; i<framesPerBuffer; i++) {
|
||||||
|
*out++ = 0.0f;
|
||||||
|
*out++ = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset pointer
|
||||||
|
out = (float *) outputBuffer;
|
||||||
|
|
||||||
|
// advance all sounds
|
||||||
|
for (i=0; i<context.highestSoundIndex; i++) {
|
||||||
|
SoundHandle *handle = context.sounds[i];
|
||||||
|
// XXX
|
||||||
|
if (handle == NULL) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handle->_done) {
|
||||||
|
#if 0
|
||||||
|
printf("play sound 0x%x with loop=%d, sample=%d, freq=%f amplitude=%f\n",
|
||||||
|
(void*) handle,
|
||||||
|
handle->looping,
|
||||||
|
(unsigned int) handle->_currentSample, handle->freq, handle->amplitude);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!advanceSound(handle, out, framesPerBuffer)) {
|
||||||
|
handle->_done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: when we should not free in the loop, where else?
|
||||||
|
if (handle->_done) {
|
||||||
|
//printf("[sound] %f freeing sound #%d handle 0x%p\n", global_time, handle->uid, handle);
|
||||||
|
context.sounds[i] = NULL;
|
||||||
|
if (!handle->keep_when_done) {
|
||||||
|
deleteSound(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure all -1.0 >= x <= 1
|
||||||
|
#if 0
|
||||||
|
out = outputBuffer;
|
||||||
|
for (i=0; i<2*framesPerBuffer; i++) {
|
||||||
|
if (*out < -1.0) { *out = -1.0; }
|
||||||
|
else if (*out > 1.0) { *out = 1.0; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (context.dumping) {
|
||||||
|
dumpSamples((float *) outputBuffer, framesPerBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check!
|
||||||
|
#if 0
|
||||||
|
// apply post-processing
|
||||||
|
out = outputBuffer;
|
||||||
|
// advance all sounds
|
||||||
|
for (i=0; i<context.highestSoundIndex; i++) {
|
||||||
|
float v = *out;
|
||||||
|
v = pow(v, 2.0);
|
||||||
|
*out++ = v;
|
||||||
|
|
||||||
|
v = *out;
|
||||||
|
v = pow(v, 2.0);
|
||||||
|
*out++ = v;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool initSound(void)
|
||||||
|
{
|
||||||
|
startup_time = timestamp_double();
|
||||||
|
global_time = 0.0;
|
||||||
|
|
||||||
|
// TODO: make all customizable too
|
||||||
|
context.init = false; // for now, set on succes to true
|
||||||
|
context.dumping = false;
|
||||||
|
context.dump_filename = NULL;
|
||||||
|
context.sampleRate = 44800;
|
||||||
|
context.framesPerBuffer = 256;
|
||||||
|
//context.framesPerBuffer = 1024;
|
||||||
|
//context.framesPerBuffer = 100;
|
||||||
|
//context.framesPerBuffer = 44100;
|
||||||
|
|
||||||
|
// TODO: add max number of simultanous playing sounds, that's more important.
|
||||||
|
|
||||||
|
context.sound_uid_counter = 0;
|
||||||
|
context.maxNumSounds = 50;
|
||||||
|
context.highestSoundIndex = 0;
|
||||||
|
context.sounds = (SoundHandle **) calloc(context.maxNumSounds, 1000); // XXX HACK
|
||||||
|
assert(context.sounds != NULL);
|
||||||
|
|
||||||
|
PaError err = Pa_Initialize();
|
||||||
|
if (err != paNoError) {
|
||||||
|
printf("error: sound failed to initialize. error=%s\n", Pa_GetErrorText(err));
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//printf("sound initialized :)");
|
||||||
|
}
|
||||||
|
|
||||||
|
int numDevices;
|
||||||
|
numDevices = Pa_GetDeviceCount();
|
||||||
|
if (numDevices < 0 ) {
|
||||||
|
printf("ERROR: Pa_CountDevices returned %i\n", numDevices);
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
#if 0
|
||||||
|
printf("sound: num devices=%d\n", numDevices);
|
||||||
|
const PaDeviceInfo *deviceInfo;
|
||||||
|
int i;
|
||||||
|
for( i=0; i<numDevices; i++ ) {
|
||||||
|
deviceInfo = Pa_GetDeviceInfo( i );
|
||||||
|
printf("dev [%i]: %s\n", i, deviceInfo->name);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Open an audio I/O stream. */
|
||||||
|
err = Pa_OpenDefaultStream(
|
||||||
|
&context.stream,
|
||||||
|
0, /* no input channels */
|
||||||
|
2, /* stereo output */
|
||||||
|
paFloat32, /* 32 bit floating point output */
|
||||||
|
context.sampleRate, /* sample rate */
|
||||||
|
context.framesPerBuffer, /* frames per buffer */
|
||||||
|
sound_callback, /* this is your callback function */
|
||||||
|
NULL); /* callback function pointer */
|
||||||
|
|
||||||
|
if (err != paNoError) {
|
||||||
|
printf("error: opening default stream: %s\n", Pa_GetErrorText(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Pa_StartStream(context.stream);
|
||||||
|
if (err != paNoError) {
|
||||||
|
printf("error: starting default stream failed: %s\n", Pa_GetErrorText(err));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear that
|
||||||
|
//sound_context.num_active_handles = 0;
|
||||||
|
//sound_context.num_cached = 0;
|
||||||
|
|
||||||
|
context.init = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void allocateHistory(SoundHandle *handle, size_t numHistorySamples)
|
||||||
|
{
|
||||||
|
handle->lastHistorySample = 0;
|
||||||
|
|
||||||
|
if (numHistorySamples == 0) {
|
||||||
|
handle->numHistorySamples = 0;
|
||||||
|
handle->history = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle->numHistorySamples = numHistorySamples;
|
||||||
|
handle->history = (float *) calloc(sizeof(float)*2, handle->numHistorySamples);
|
||||||
|
|
||||||
|
// clear all
|
||||||
|
size_t i;
|
||||||
|
for (i=0; i<2*handle->numHistorySamples; i++) {
|
||||||
|
handle->history[i] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundHandle *playFrequency(float freq, float amplitude)
|
||||||
|
{
|
||||||
|
SoundHandle *handle = playSound(SoundFrequency, amplitude);
|
||||||
|
if (handle != NULL) {
|
||||||
|
handle->freq = freq;
|
||||||
|
//handle->looping = true;
|
||||||
|
}
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundHandle *playSound(enum Sound type, float amplitude)
|
||||||
|
{
|
||||||
|
if (!context.init) {
|
||||||
|
//printf("warning: called playSound() with no initialized sound system!\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t nextFreeIndex = 0;
|
||||||
|
while(nextFreeIndex < context.highestSoundIndex) {
|
||||||
|
if (context.sounds[nextFreeIndex] == NULL) {
|
||||||
|
// got an empyt sound to use
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
nextFreeIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextFreeIndex >= context.maxNumSounds) {
|
||||||
|
printf("error: can't create more sounds, got maximum of: %d\n", context.maxNumSounds);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundHandle *handle = (SoundHandle *) calloc(1, sizeof(SoundHandle));
|
||||||
|
handle->envelope = New;
|
||||||
|
handle->amplitude = amplitude;
|
||||||
|
handle->minAmplitude = 0.0;
|
||||||
|
handle->freq = 1000;
|
||||||
|
handle->looping = false;
|
||||||
|
handle->_loopCount = 0;
|
||||||
|
handle->_type = type;
|
||||||
|
handle->time = 0.0;
|
||||||
|
handle->keep_when_done = false;
|
||||||
|
handle->state = 0.0;
|
||||||
|
handle->leakage = 0.0;
|
||||||
|
handle->scaling = 0.0;
|
||||||
|
handle->_done = false;
|
||||||
|
handle->skipDirectionIfClamping = false;
|
||||||
|
|
||||||
|
handle->_useEnvelope = false;
|
||||||
|
handle->_riseTime = 0.0;
|
||||||
|
handle->_relativeOvershootTime = 0.0;
|
||||||
|
handle->_decayTime = 0.0;
|
||||||
|
handle->_holdTime = 0.0;
|
||||||
|
handle->_waitTime = 0.0;
|
||||||
|
handle->decayType = SoundDecayLinear;
|
||||||
|
handle->riseType = SoundDecayLinear;
|
||||||
|
handle->name = "<unnamed>";
|
||||||
|
|
||||||
|
// filters may to access values from their history
|
||||||
|
int numHistoryValues = 0;
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
// pink/white/brown noise need to save only one sample, state is enough
|
||||||
|
// for that.
|
||||||
|
case SoundBrownianNoise:
|
||||||
|
// something like lowpass filter constant.
|
||||||
|
// lower value == lower freq. noise
|
||||||
|
// values that audacity uses
|
||||||
|
//handle->leakage = 0.997;
|
||||||
|
//handle->scaling = 0.05;
|
||||||
|
|
||||||
|
handle->leakage = (context.sampleRate - 144.0) / context.sampleRate;
|
||||||
|
handle->scaling = 9.0 / sqrt(context.sampleRate);
|
||||||
|
//printf("computed leakage and scaling: %f %F\n", handle->leakage, handle->scaling);
|
||||||
|
|
||||||
|
//handle->leakage = 0.7;
|
||||||
|
//handle->scaling = 0.2;
|
||||||
|
handle->skipDirectionIfClamping = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SoundPinkNoise:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SoundWhiteNoise:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
allocateHistory(handle, numHistoryValues);
|
||||||
|
|
||||||
|
handle->uid = context.sound_uid_counter++;
|
||||||
|
|
||||||
|
context.sounds[nextFreeIndex] = handle;
|
||||||
|
|
||||||
|
if (nextFreeIndex == context.highestSoundIndex) {
|
||||||
|
context.highestSoundIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool stopSound(SoundHandle *handle)
|
||||||
|
{
|
||||||
|
if (!context.init) {
|
||||||
|
printf("warning: called stopSound() with no initialized sound system!\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handle == NULL) {
|
||||||
|
printf("warning: stopSound() handle is NULL\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handle->_done) {
|
||||||
|
printf("warning: stopSound() sound is already stopped\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: kill with variable
|
||||||
|
handle->_done = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopAllSounds(void)
|
||||||
|
{
|
||||||
|
if (!context.init) {
|
||||||
|
printf("warning: called stopAllSounds() with no initialized sound system!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int i;
|
||||||
|
for (i=0; i<context.highestSoundIndex; i++) {
|
||||||
|
if (context.sounds[i] != NULL && !context.sounds[i]->_done) {
|
||||||
|
stopSound(context.sounds[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void computeEnvelopeUse(SoundHandle *handle)
|
||||||
|
{
|
||||||
|
if (handle == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wait time has no influence (yet)
|
||||||
|
handle->_useEnvelope = (handle->_riseTime != 0.0 || handle->_holdTime != 0.0 || handle->_decayTime != 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool configureEnvelope(SoundHandle *handle, float rise, float hold, float decay)
|
||||||
|
{
|
||||||
|
if (handle == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check inf/nan too
|
||||||
|
if (rise < 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hold < 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (decay < 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle->_riseTime = rise;
|
||||||
|
handle->_holdTime = hold;
|
||||||
|
handle->_decayTime = decay;
|
||||||
|
|
||||||
|
// set depending on flags
|
||||||
|
computeEnvelopeUse(handle);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool configureEnvelopeLooping(SoundHandle *handle, float rise, float hold, float decay, float wait)
|
||||||
|
{
|
||||||
|
if (wait < 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!configureEnvelope(handle, rise, hold, decay)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle->looping = true;
|
||||||
|
handle->_loopCount = -1;
|
||||||
|
handle->_waitTime = wait;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getRiseDuration(SoundHandle *handle)
|
||||||
|
{
|
||||||
|
if (handle == NULL) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle->_riseTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getHoldDuration(SoundHandle *handle)
|
||||||
|
{
|
||||||
|
if (handle == NULL) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle->_holdTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getDecayDuration(SoundHandle *handle)
|
||||||
|
{
|
||||||
|
if (handle == NULL) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle->_decayTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
float getwaitTime(SoundHandle *handle)
|
||||||
|
{
|
||||||
|
if (handle == NULL) {
|
||||||
|
return -1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle->_waitTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
void teardownSound(void)
|
||||||
|
{
|
||||||
|
if (!context.init) {
|
||||||
|
printf("[sonud] can't teardown sound when not initialized!\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PaError err = Pa_CloseStream(context.stream);
|
||||||
|
if (err != paNoError) {
|
||||||
|
printf("[sound] portaudio failed to close stream: %d\n", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Pa_Terminate();
|
||||||
|
if (err != paNoError) {
|
||||||
|
printf("[sound] portaudio failed to terminate: %d\n", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.dumping) {
|
||||||
|
stopDumpingWav();
|
||||||
|
}
|
||||||
|
|
||||||
|
context.init = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool configureOvershoot(SoundHandle *handle, float relativeOvershootTime)
|
||||||
|
{
|
||||||
|
if (handle == NULL) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (relativeOvershootTime < 0.0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle->_relativeOvershootTime = relativeOvershootTime;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLoopCount(SoundHandle *handle, int numRepetitions)
|
||||||
|
{
|
||||||
|
if (numRepetitions < -1) {
|
||||||
|
numRepetitions = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle->_loopCount = numRepetitions;
|
||||||
|
|
||||||
|
if (numRepetitions == 0) {
|
||||||
|
handle->looping = false;
|
||||||
|
} else if (numRepetitions == -1) {
|
||||||
|
handle->looping = true;
|
||||||
|
} else {
|
||||||
|
handle->looping = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int numActiveSounds(void)
|
||||||
|
{
|
||||||
|
int num = 0;
|
||||||
|
size_t i;
|
||||||
|
for (i=0; i<context.highestSoundIndex; i++) {
|
||||||
|
if (context.sounds[i] != NULL) {
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
|
void deleteSound(SoundHandle *handle)
|
||||||
|
{
|
||||||
|
if (handle == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// just for sure
|
||||||
|
handle->_done = true;
|
||||||
|
|
||||||
|
// clear from that
|
||||||
|
size_t i;
|
||||||
|
for (i=0; i<context.highestSoundIndex; i++) {
|
||||||
|
if (context.sounds[i] == handle) {
|
||||||
|
context.sounds[i] = NULL;
|
||||||
|
if (i+1 == context.highestSoundIndex) {
|
||||||
|
context.highestSoundIndex--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handle->history != NULL) {
|
||||||
|
free(handle->history);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool configureEnvelopeWait(SoundHandle *handle, float rise, float hold, float decay, float wait)
|
||||||
|
{
|
||||||
|
if (configureEnvelopeLooping(handle, rise, hold, decay, wait)) {
|
||||||
|
handle->looping = false;
|
||||||
|
handle->_loopCount = -1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
185
game/sound/sound.hpp
Normal file
185
game/sound/sound.hpp
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
#ifndef _SOUND_HPP_
|
||||||
|
#define _SOUND_HPP_
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
namespace sound {
|
||||||
|
|
||||||
|
// type of sound.
|
||||||
|
// the envelope settings modify each sound.
|
||||||
|
enum Sound {
|
||||||
|
SoundFrequency=0, // one frequency
|
||||||
|
SoundWhiteNoise=1, // all frequencies are equal
|
||||||
|
SoundBrownianNoise=2, // 1/f^2 noise
|
||||||
|
SoundPinkNoise=3, // 1/f noise
|
||||||
|
};
|
||||||
|
|
||||||
|
enum EnvelopeState {
|
||||||
|
New=0, // newly spawned
|
||||||
|
Wait, // ready but not yet started (waiting before starting or before repeating)
|
||||||
|
Rise, // increasing
|
||||||
|
BeginOvershoot,
|
||||||
|
EndOvershoot,
|
||||||
|
Hold, // staying at normal level
|
||||||
|
Decay, // deacying
|
||||||
|
Done, // done
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SoundDecayType {
|
||||||
|
SoundDecayLinear = 0,
|
||||||
|
SoundDecayExp = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
// just for debugging/logging. don't rely on use
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
// unique ID for accessing them via lookups etc. if sound is dead, this will be never reused
|
||||||
|
int uid;
|
||||||
|
|
||||||
|
// if true, don't deallocate once this is done.
|
||||||
|
bool keep_when_done;
|
||||||
|
|
||||||
|
// these are public and can be changed by the user.
|
||||||
|
enum EnvelopeState envelope;
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
/* Special Parameters for specific types */
|
||||||
|
/****************************************************************/
|
||||||
|
|
||||||
|
/******************** SoundFrequency ****************************/
|
||||||
|
|
||||||
|
float freq; // base frequency for type: SoundFrequency
|
||||||
|
|
||||||
|
/******************** White / Brownian / Pink Noise ****************/
|
||||||
|
float leakage; // influence of previous state on current value (0..1)
|
||||||
|
float scaling; // influence of white noise on next value
|
||||||
|
|
||||||
|
// If true and the value goes outside (-1..1), do the step in the other
|
||||||
|
// direction to stay inside the valid range.
|
||||||
|
bool skipDirectionIfClamping;
|
||||||
|
|
||||||
|
/******************** SoundFrequency: ***************************/
|
||||||
|
|
||||||
|
/****************************************************************/
|
||||||
|
/* Common sound state / parameters */
|
||||||
|
/****************************************************************/
|
||||||
|
// progress of the sound which is used for the envelope but can be used for
|
||||||
|
// other things too
|
||||||
|
float time;
|
||||||
|
|
||||||
|
// scalar state that can be used arbitrarily to derive the waveform from it
|
||||||
|
float state;
|
||||||
|
|
||||||
|
// time when the sound is done
|
||||||
|
// if != 0.0, the sound changes with that
|
||||||
|
// TODO: make attack gain decay stuff as envelope
|
||||||
|
// TODO: add amplitude noise
|
||||||
|
float _riseTime;
|
||||||
|
float _relativeOvershootTime; // percentage of end of rise time that is used for overshoot (max amplitude at t_overshoot/2)
|
||||||
|
float _holdTime;
|
||||||
|
float _decayTime;
|
||||||
|
float _waitTime;
|
||||||
|
|
||||||
|
// waveform of type of rise. default: linear
|
||||||
|
enum SoundDecayType riseType;
|
||||||
|
|
||||||
|
// waveform of type of decay. default: linear
|
||||||
|
enum SoundDecayType decayType;
|
||||||
|
|
||||||
|
// if true, play infinitely
|
||||||
|
bool looping;
|
||||||
|
|
||||||
|
// base amplitude of the sound
|
||||||
|
float amplitude;
|
||||||
|
|
||||||
|
// amplitude when signal is at minimum (before rise, after release and in wait time)
|
||||||
|
float minAmplitude;
|
||||||
|
|
||||||
|
// these are al private!
|
||||||
|
// never acces them
|
||||||
|
enum Sound _type; // which type of sound
|
||||||
|
|
||||||
|
int _loopCount;
|
||||||
|
|
||||||
|
// if true, will get removed
|
||||||
|
bool _done;
|
||||||
|
bool _useEnvelope;
|
||||||
|
|
||||||
|
float *history; // if != 0, history is a pointer to 2*numHistorySamples samples.
|
||||||
|
size_t numHistorySamples; // if != 0, save history for this sound.
|
||||||
|
size_t lastHistorySample; // index/2 of last history sample. used
|
||||||
|
} SoundHandle;
|
||||||
|
|
||||||
|
// initialize sound subsystem. return true if working, false on errors.
|
||||||
|
bool initSound(void);
|
||||||
|
|
||||||
|
// start playing a sound.
|
||||||
|
// args:
|
||||||
|
// type - type of sound.
|
||||||
|
// amplitude : 0..1
|
||||||
|
// handle: if != NULL will point to the internal sound handle to change the
|
||||||
|
// manipulate playing of the sound.
|
||||||
|
// TODO: add sound from file to play too
|
||||||
|
SoundHandle *playSound(enum Sound type, float amplitude);
|
||||||
|
|
||||||
|
// start playing a looping frequency.
|
||||||
|
// args:
|
||||||
|
// freq: frequency in Hz
|
||||||
|
SoundHandle *playFrequency(float freq, float amplitude);
|
||||||
|
|
||||||
|
// set them.
|
||||||
|
// args:
|
||||||
|
// time of phases
|
||||||
|
bool configureEnvelope(SoundHandle *handle, float rise, float hold, float decay);
|
||||||
|
|
||||||
|
bool configureOvershoot(SoundHandle *handle, float relativeOvershootTime);
|
||||||
|
|
||||||
|
// same as above but set to looping and set wait time between intervals
|
||||||
|
bool configureEnvelopeWait(SoundHandle *handle, float rise, float hold, float decay, float wait);
|
||||||
|
|
||||||
|
// without looping but with wait
|
||||||
|
bool configureEnvelopeLooping(SoundHandle *handle, float rise, float hold, float decay, float wait);
|
||||||
|
|
||||||
|
// getter for attributes. if 0.0, unused.
|
||||||
|
float getRiseDuration(SoundHandle *handle);
|
||||||
|
float getHoldDuration(SoundHandle *handle);
|
||||||
|
float getDecayDuration(SoundHandle *handle);
|
||||||
|
|
||||||
|
float getWaitTime(SoundHandle *handle);
|
||||||
|
void configureWaitTime(SoundHandle *handle, float waitTime);
|
||||||
|
|
||||||
|
// Set looping behaviour of the sound.
|
||||||
|
// args:
|
||||||
|
// / > 0, sound is looping for this number of repetitions
|
||||||
|
// numRepetitions { == 0, sound is stopped when it is over
|
||||||
|
// \ == -1, loop the sound forever
|
||||||
|
//
|
||||||
|
void setLoopCount(SoundHandle *handle, int numRepetitions);
|
||||||
|
|
||||||
|
// stop playing a sound. the handle is invalid now.
|
||||||
|
bool stopSound(SoundHandle *handle);
|
||||||
|
|
||||||
|
// stop and remove all playing sounds. all handle become invalid after calling
|
||||||
|
// this.
|
||||||
|
void stopAllSounds(void);
|
||||||
|
|
||||||
|
void teardownSound(void);
|
||||||
|
|
||||||
|
// for debugging
|
||||||
|
bool startDumpingWav(const char *filename);
|
||||||
|
|
||||||
|
// is deletede when 'keep' not set after stopping
|
||||||
|
void stopDumpingWav(void);
|
||||||
|
|
||||||
|
int numActiveSounds(void);
|
||||||
|
|
||||||
|
void deleteSound(SoundHandle *handle);
|
||||||
|
|
||||||
|
//SoundHandle *playFreqEnvelope(float freq, float rise, float hold, float decay);
|
||||||
|
|
||||||
|
// TODO: add fade out/fade in overlay stuff for stopping sounds smoothly
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
77
game/sound/sound_effects.cpp
Normal file
77
game/sound/sound_effects.cpp
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
#include "sound_effects.hpp"
|
||||||
|
|
||||||
|
#include "sound.hpp"
|
||||||
|
|
||||||
|
namespace sound {
|
||||||
|
#if 0
|
||||||
|
class MissileFlySoundEffect : public SoundEffect {
|
||||||
|
MissileFlySoundEffect(const game::Missile *missile)
|
||||||
|
: m_missileId(missile->id)
|
||||||
|
, m_handle(sound::playSound(sound::playFrequency(440, 0.3))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~MissileFlySoundEffect()
|
||||||
|
{
|
||||||
|
delete(m_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool advance(float dt)
|
||||||
|
{
|
||||||
|
for (const game::StateUpdateEvent *event : updates) {
|
||||||
|
handleUpdateEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add event if sth. comes into life or dies in a state
|
||||||
|
// cycle.. delete objects that died last cycle in the current cycle
|
||||||
|
// so pointers don't get invalid.
|
||||||
|
|
||||||
|
bool keep = false;
|
||||||
|
for (const game::Missile *missile : state->missiles) {
|
||||||
|
if (missile->id == m_missileId) {
|
||||||
|
keep = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keep) {
|
||||||
|
sound::stopSound(m_handle);
|
||||||
|
sound::deleteSound(m_handle);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SoundHandle *m_handle;
|
||||||
|
size_t m_missileId;
|
||||||
|
};
|
||||||
|
|
||||||
|
void SoundEffects::advance(float dt, const std::list<game::StateUpdateEvent*> &updates)
|
||||||
|
{
|
||||||
|
//StateUpdateEvent::LifeCycle::Create,
|
||||||
|
//StateUpdateEvent::Type::Explosion
|
||||||
|
|
||||||
|
std::vector<SoundEffect*> rm;
|
||||||
|
|
||||||
|
for (SoundEffect *effect : m_effects) {
|
||||||
|
bool keep = effect->advance(dt, m_gameState);
|
||||||
|
if (!keep) {
|
||||||
|
rm.push_back(effect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (SoundEffect *effect : rm) {
|
||||||
|
m_effects.remove(rm);
|
||||||
|
delete(rm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// spawn new sounds
|
||||||
|
//for (const game::Missile *missile : state->missiles) {
|
||||||
|
// for (SoundEffect
|
||||||
|
// if (missile->
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
36
game/sound/sound_effects.hpp
Normal file
36
game/sound/sound_effects.hpp
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
|
||||||
|
#include "state/state.hpp"
|
||||||
|
|
||||||
|
namespace sound {
|
||||||
|
#if 0
|
||||||
|
namespace game {
|
||||||
|
class StateUpdateEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SoundEffect {
|
||||||
|
public:
|
||||||
|
virtual ~SoundEffects()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
// return false if it can be deleted
|
||||||
|
virtual bool advance(float dt) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SoundEffects {
|
||||||
|
public:
|
||||||
|
SoundEffects()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleUpdateEvent(game::StateUpdateEvent *event);
|
||||||
|
void advance(float dt, const std::list<game::StateUpdateEvent*> &updates);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::list<SoundEffect*> m_effects;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
|
@ -14,18 +14,16 @@ namespace game {
|
||||||
|
|
||||||
// angles are supplied in degrees and are CCW
|
// angles are supplied in degrees and are CCW
|
||||||
Missile *missile = new Missile(
|
Missile *missile = new Missile(
|
||||||
|
state->generateMissileId(),
|
||||||
player,
|
player,
|
||||||
player->ship->position,
|
player->ship->position,
|
||||||
-util::deg2rad(m_angle),
|
-util::deg2rad(m_angle),
|
||||||
0.005*player->speed);
|
0.005*player->speed);
|
||||||
|
|
||||||
Trace *trace = new Trace(missile);
|
|
||||||
missile->trace = trace;
|
|
||||||
|
|
||||||
player->energy -= player->speed;
|
player->energy -= player->speed;
|
||||||
player->missiles.push_back(missile);
|
player->missiles.push_back(missile);
|
||||||
|
|
||||||
state->addTrace(trace);
|
state->addMissile(missile);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShootCommand::ready(const Player *player, const State *state) const
|
bool ShootCommand::ready(const Player *player, const State *state) const
|
||||||
|
|
21
game/state/events/explosion_event.hpp
Normal file
21
game/state/events/explosion_event.hpp
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "state/explosion.hpp"
|
||||||
|
#include "state/state_update_event.hpp"
|
||||||
|
|
||||||
|
namespace game {
|
||||||
|
class ExplosionEvent : public StateUpdateEvent {
|
||||||
|
public:
|
||||||
|
ExplosionEvent(StateUpdateEvent::LifeCycle lifeCycle, Explosion *explosion)
|
||||||
|
: StateUpdateEvent(lifeCycle, StateUpdateEvent::EventType::Explosion)
|
||||||
|
, explosion(explosion)
|
||||||
|
|
||||||
|
{
|
||||||
|
std::string typeStr = StateUpdateEvent::lifeCycleToString(lifeCycle);
|
||||||
|
std::cout<<"created explosion event for id " << explosion->id << " of type " << typeStr << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
Explosion *explosion;
|
||||||
|
};
|
||||||
|
}
|
|
@ -11,8 +11,9 @@
|
||||||
#include <glm/gtx/norm.hpp>
|
#include <glm/gtx/norm.hpp>
|
||||||
|
|
||||||
namespace game {
|
namespace game {
|
||||||
Missile::Missile(Player *player, const glm::vec2 &pos, float angle, float speed)
|
Missile::Missile(size_t id, Player *player, const glm::vec2 &pos, float angle, float speed)
|
||||||
: player(player)
|
: id(id)
|
||||||
|
, player(player)
|
||||||
, position(pos)
|
, position(pos)
|
||||||
{
|
{
|
||||||
velocity = speed * glm::vec2(std::sin(angle), std::cos(angle));
|
velocity = speed * glm::vec2(std::sin(angle), std::cos(angle));
|
||||||
|
|
|
@ -57,13 +57,14 @@ namespace game {
|
||||||
int planetId;
|
int planetId;
|
||||||
};
|
};
|
||||||
|
|
||||||
Missile(Player *player, const glm::vec2 &pos, float angle, float speed);
|
Missile(size_t id, Player *player, const glm::vec2 &pos, float angle, float speed);
|
||||||
~Missile();
|
~Missile();
|
||||||
|
|
||||||
// try to advance. if something will be hit, return the first hit in
|
// try to advance. if something will be hit, return the first hit in
|
||||||
// time.
|
// time.
|
||||||
Missile::Event advance(const game::State *state, float dt);
|
Missile::Event advance(const game::State *state, float dt);
|
||||||
|
|
||||||
|
const size_t id;
|
||||||
Player *player; // owner won't be hit by own missiles
|
Player *player; // owner won't be hit by own missiles
|
||||||
glm::vec2 position;
|
glm::vec2 position;
|
||||||
glm::vec2 velocity;
|
glm::vec2 velocity;
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace game {
|
||||||
|
|
||||||
class Player {
|
class Player {
|
||||||
public:
|
public:
|
||||||
int id;
|
size_t id;
|
||||||
bool alive;
|
bool alive;
|
||||||
float speed;
|
float speed;
|
||||||
float energy;
|
float energy;
|
||||||
|
@ -23,7 +23,7 @@ namespace game {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::list<Missile*> missiles;
|
std::list<Missile*> missiles;
|
||||||
|
|
||||||
Player(int id) : id(id), alive(true), speed(1.0), energy(0.0), ship(nullptr), name("<unnamed>")
|
Player(size_t id) : id(id), alive(true), speed(1.0), energy(0.0), ship(nullptr), name("<unnamed>")
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,9 @@
|
||||||
#include "trace.hpp"
|
#include "trace.hpp"
|
||||||
#include "explosion.hpp"
|
#include "explosion.hpp"
|
||||||
|
|
||||||
|
#include "state_update_event.hpp"
|
||||||
|
#include "events/explosion_event.hpp"
|
||||||
|
|
||||||
#include "util.hpp"
|
#include "util.hpp"
|
||||||
|
|
||||||
namespace game {
|
namespace game {
|
||||||
|
@ -28,7 +31,8 @@ namespace game {
|
||||||
//for (Player *player : players) {
|
//for (Player *player : players) {
|
||||||
//}
|
//}
|
||||||
|
|
||||||
m_nextId = 0;
|
m_nextPlayerId = 0;
|
||||||
|
m_nextMissileId = 0;
|
||||||
m_time = 0.0;
|
m_time = 0.0;
|
||||||
m_shipRadius = 0.02;
|
m_shipRadius = 0.02;
|
||||||
m_maxMissileDistance = 2.0;
|
m_maxMissileDistance = 2.0;
|
||||||
|
@ -146,15 +150,15 @@ namespace game {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int State::addPlayer()
|
size_t State::addPlayer()
|
||||||
{
|
{
|
||||||
int playerId = m_nextId++;
|
size_t playerId = m_nextPlayerId++;
|
||||||
Player *player = new Player(playerId);
|
Player *player = new Player(playerId);
|
||||||
players.push_back(player);
|
players.push_back(player);
|
||||||
return playerId;
|
return playerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void State::quitPlayer(int playerId)
|
void State::quitPlayer(size_t playerId)
|
||||||
{
|
{
|
||||||
std::cout << playerId << " quit" << std::endl;
|
std::cout << playerId << " quit" << std::endl;
|
||||||
|
|
||||||
|
@ -168,12 +172,12 @@ namespace game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void State::clear(int playerId)
|
void State::clear(size_t playerId)
|
||||||
{
|
{
|
||||||
std::cout << playerId << " clear" << std::endl;
|
std::cout << playerId << " clear" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
void State::setName(int playerId, std::string name)
|
void State::setName(size_t playerId, std::string name)
|
||||||
{
|
{
|
||||||
// discard if not unique
|
// discard if not unique
|
||||||
for (const Player *other : players) {
|
for (const Player *other : players) {
|
||||||
|
@ -186,7 +190,7 @@ namespace game {
|
||||||
playerForId(playerId)->name = name;
|
playerForId(playerId)->name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
void State::setSpeed(int playerId, double speed)
|
void State::setSpeed(size_t playerId, double speed)
|
||||||
{
|
{
|
||||||
playerForId(playerId)->speed = speed;
|
playerForId(playerId)->speed = speed;
|
||||||
}
|
}
|
||||||
|
@ -338,12 +342,15 @@ namespace game {
|
||||||
|
|
||||||
for (Explosion *explosion : rm) {
|
for (Explosion *explosion : rm) {
|
||||||
explosions.remove(explosion);
|
explosions.remove(explosion);
|
||||||
delete(explosion);
|
m_nextEvents.push_back(new ExplosionEvent(StateUpdateEvent::LifeCycle::Destroy, explosion));
|
||||||
|
//delete(explosion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void State::advance(float dt)
|
void State::advance(float dt)
|
||||||
{
|
{
|
||||||
|
//std::cout<<"[state] (init) cycle: update events length is " << m_nextEvents.size() << std::endl;
|
||||||
|
|
||||||
m_time += dt;
|
m_time += dt;
|
||||||
|
|
||||||
advancePlayerShipSpawns(dt);
|
advancePlayerShipSpawns(dt);
|
||||||
|
@ -355,9 +362,19 @@ namespace game {
|
||||||
advancePlayerCommands(dt);
|
advancePlayerCommands(dt);
|
||||||
|
|
||||||
advancePlayerMissiles(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));
|
||||||
|
|
||||||
|
//std::cout<<"[state] (after move) cycle: update events length is " << m_nextEvents.size() << std::endl;
|
||||||
|
|
||||||
|
// finally remove things.
|
||||||
|
//m_nextEvents.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
Player *State::playerForId(int playerId)
|
Player *State::playerForId(size_t playerId)
|
||||||
{
|
{
|
||||||
for (Player *p : players) {
|
for (Player *p : players) {
|
||||||
if (p->id == playerId) {
|
if (p->id == playerId) {
|
||||||
|
@ -402,7 +419,7 @@ namespace game {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void State::commandForPlayer(int playerId, game::Command *cmd)
|
void State::commandForPlayer(size_t playerId, game::Command *cmd)
|
||||||
{
|
{
|
||||||
Player *player = playerForId(playerId);
|
Player *player = playerForId(playerId);
|
||||||
if (player != nullptr) {
|
if (player != nullptr) {
|
||||||
|
@ -425,6 +442,14 @@ namespace game {
|
||||||
traces.push_back(trace);
|
traces.push_back(trace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void State::addMissile(Missile *missile)
|
||||||
|
{
|
||||||
|
Trace *trace = new Trace(missile);
|
||||||
|
missile->trace = trace;
|
||||||
|
|
||||||
|
addTrace(trace);
|
||||||
|
}
|
||||||
|
|
||||||
void State::deleteTrace(Trace *trace)
|
void State::deleteTrace(Trace *trace)
|
||||||
{
|
{
|
||||||
if (trace->missile != nullptr) {
|
if (trace->missile != nullptr) {
|
||||||
|
@ -442,7 +467,10 @@ namespace game {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
explosions.push_back(new Explosion(m_nextExplosionId++, evt->position, evt->missileVelocity, evt->hit));
|
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)
|
void State::advanceTraceAges(float dt)
|
||||||
|
@ -472,4 +500,23 @@ namespace game {
|
||||||
{
|
{
|
||||||
m_playingFieldSize = glm::vec2(width, 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();
|
||||||
|
}
|
||||||
|
m_allEvents.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ namespace game {
|
||||||
// forward declarations
|
// forward declarations
|
||||||
class Command;
|
class Command;
|
||||||
class Player;
|
class Player;
|
||||||
|
class StateUpdateEvent;
|
||||||
|
|
||||||
class Ship;
|
class Ship;
|
||||||
class Trace;
|
class Trace;
|
||||||
|
@ -53,15 +54,15 @@ namespace game {
|
||||||
|
|
||||||
// The upper layer (network/renderer) calling these three functions
|
// The upper layer (network/renderer) calling these three functions
|
||||||
// should keep id's unique and give one (network) input an id.
|
// should keep id's unique and give one (network) input an id.
|
||||||
int addPlayer();
|
size_t addPlayer();
|
||||||
void clear(int playerId);
|
void clear(size_t playerId);
|
||||||
void setName(int playerId, std::string name);
|
void setName(size_t playerId, std::string name);
|
||||||
void setSpeed(int playerId, double speed);
|
void setSpeed(size_t playerId, double speed);
|
||||||
void quitPlayer(int playerId);
|
void quitPlayer(size_t playerId);
|
||||||
void commandForPlayer(int playerId, Command *cmd);
|
void commandForPlayer(size_t playerId, Command *cmd);
|
||||||
|
|
||||||
// lookup. return nullptr on invalid playerId
|
// lookup. return nullptr on invalid playerId
|
||||||
Player *playerForId(int playerId);
|
Player *playerForId(size_t playerId);
|
||||||
|
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Mixed stuff */
|
/* Mixed stuff */
|
||||||
|
@ -75,6 +76,9 @@ namespace game {
|
||||||
// add a trace to the list of traces.
|
// add a trace to the list of traces.
|
||||||
void addTrace(Trace *trace);
|
void addTrace(Trace *trace);
|
||||||
|
|
||||||
|
// add a missile
|
||||||
|
void addMissile(Missile *missile);
|
||||||
|
|
||||||
// delete traces with this command
|
// delete traces with this command
|
||||||
void deleteTrace(Trace *trace); // using a pointer
|
void deleteTrace(Trace *trace); // using a pointer
|
||||||
|
|
||||||
|
@ -97,10 +101,17 @@ namespace game {
|
||||||
void setPlayingFieldSize(int width, int height);
|
void setPlayingFieldSize(int width, int height);
|
||||||
void setPlayingFieldCenter(int width, int height);
|
void setPlayingFieldCenter(int width, int height);
|
||||||
|
|
||||||
|
size_t generateMissileId();
|
||||||
|
|
||||||
|
void applyAndClearAllOldStateUpdates();
|
||||||
|
|
||||||
|
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
/* Rendering */
|
/* Rendering */
|
||||||
/*************************************************************************/
|
/*************************************************************************/
|
||||||
|
|
||||||
|
// TODO: hide and replace by events
|
||||||
|
|
||||||
// Game items which should be rendered are here:
|
// Game items which should be rendered are here:
|
||||||
// (access missiles by iterating over player's missiles attribute)
|
// (access missiles by iterating over player's missiles attribute)
|
||||||
std::vector<Planet*> planets;
|
std::vector<Planet*> planets;
|
||||||
|
@ -139,13 +150,17 @@ namespace game {
|
||||||
float m_playerRespawnTime;
|
float m_playerRespawnTime;
|
||||||
float m_shipRadius;
|
float m_shipRadius;
|
||||||
float m_defaultEnergy;
|
float m_defaultEnergy;
|
||||||
int m_nextId;
|
|
||||||
int m_maxNumTraces;
|
int m_maxNumTraces;
|
||||||
|
size_t m_nextPlayerId;
|
||||||
size_t m_nextExplosionId;
|
size_t m_nextExplosionId;
|
||||||
|
size_t m_nextMissileId;
|
||||||
float m_time;
|
float m_time;
|
||||||
bool m_developerMode;
|
bool m_developerMode;
|
||||||
|
|
||||||
glm::vec2 m_playingFieldCenter;
|
glm::vec2 m_playingFieldCenter;
|
||||||
glm::vec2 m_playingFieldSize;
|
glm::vec2 m_playingFieldSize;
|
||||||
|
|
||||||
|
std::list<StateUpdateEvent*> m_nextEvents;
|
||||||
|
std::vector<std::list<StateUpdateEvent*>> m_allEvents;
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
51
game/state/state_update_event.hpp
Normal file
51
game/state/state_update_event.hpp
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// TODO: make life cycle object class.
|
||||||
|
// objects from that class can be created and destroyed only through factory
|
||||||
|
// methods which create updates too.
|
||||||
|
|
||||||
|
namespace game {
|
||||||
|
class StateUpdateEvent {
|
||||||
|
public:
|
||||||
|
enum class LifeCycle {
|
||||||
|
Create, // something was created
|
||||||
|
Modify, // something was modified (look at attributes)
|
||||||
|
Destroy // something was destroyed
|
||||||
|
};
|
||||||
|
|
||||||
|
// add all possible classes here
|
||||||
|
enum class EventType {
|
||||||
|
Explosion
|
||||||
|
};
|
||||||
|
|
||||||
|
StateUpdateEvent(LifeCycle cycle, EventType event)
|
||||||
|
: m_lifeCycle(cycle), m_eventType(event)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LifeCycle lifeCycle() const { return m_lifeCycle; }
|
||||||
|
EventType eventType() const { return m_eventType; }
|
||||||
|
|
||||||
|
std::string description() const
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
return "<event>";
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string lifeCycleToString(LifeCycle lifeCycle)
|
||||||
|
{
|
||||||
|
switch(lifeCycle) {
|
||||||
|
case StateUpdateEvent::LifeCycle::Create: return "create";
|
||||||
|
case StateUpdateEvent::LifeCycle::Modify: return "modify";
|
||||||
|
case StateUpdateEvent::LifeCycle::Destroy: return "destroy";
|
||||||
|
default: return "<invalid>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const LifeCycle m_lifeCycle;
|
||||||
|
const EventType m_eventType;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue