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
|
||||
game.cpp
|
||||
|
||||
# TODO: make optional!
|
||||
sound/sound.cpp
|
||||
sound/sound_effects.cpp
|
||||
|
||||
state/object.cpp
|
||||
state/explosion.cpp
|
||||
state/trace.cpp
|
||||
|
@ -43,4 +47,4 @@ include_directories(${assimp_INCLUDE_DIRS})
|
|||
add_executable(game ${GAME_SRC})
|
||||
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 "options.hpp"
|
||||
|
||||
#include "sound/sound_effects.hpp"
|
||||
#include "state/state_update_event.hpp"
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
uint64_t optionsFlags;
|
||||
|
@ -19,6 +22,7 @@ using asio::ip::tcp;
|
|||
int main(int argc, char *argv[])
|
||||
{
|
||||
bool devMode = false;
|
||||
bool soundEnabled = false;
|
||||
char port[]="3490";
|
||||
|
||||
static struct option long_options[] =
|
||||
|
@ -33,6 +37,7 @@ int main(int argc, char *argv[])
|
|||
// {"delete", required_argument, 0, 'd'},
|
||||
{"autorun", required_argument, 0, 'a'},
|
||||
{"port", required_argument, 0, 'p'},
|
||||
{"sound", no_argument, 0, 's'},
|
||||
{"fps", no_argument, 0, 'f'},
|
||||
{"dev", no_argument, 0, 'd'},
|
||||
{0, 0, 0, 0}
|
||||
|
@ -41,7 +46,7 @@ int main(int argc, char *argv[])
|
|||
int option_index = 0;
|
||||
|
||||
while(1){
|
||||
char c = getopt_long (argc, argv, "p:fad",
|
||||
char c = getopt_long (argc, argv, "p:fads",
|
||||
long_options, &option_index);
|
||||
if (c == -1)
|
||||
break;
|
||||
|
@ -51,6 +56,7 @@ int main(int argc, char *argv[])
|
|||
case 'f':
|
||||
SET_FLAG(SHOW_FPS,true);
|
||||
break;
|
||||
|
||||
case 'd':
|
||||
std::cout<<"enabling developer mode" << std::endl;
|
||||
devMode = true;
|
||||
|
@ -60,6 +66,10 @@ int main(int argc, char *argv[])
|
|||
strcpy(port,optarg);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
soundEnabled = true;
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
SET_FLAG(TEST_AUTORUN, true);
|
||||
break;
|
||||
|
@ -75,6 +85,13 @@ int main(int argc, char *argv[])
|
|||
|
||||
srand(time(NULL));
|
||||
|
||||
//sound::SoundEffects *sounds = nullptr;
|
||||
if (soundEnabled) {
|
||||
//if (sound::initSound()) {
|
||||
//sounds = new sound::SoundEffects();
|
||||
//}
|
||||
}
|
||||
|
||||
Game game;
|
||||
game.state()->setDeveloperMode(devMode);
|
||||
|
||||
|
@ -89,6 +106,10 @@ int main(int argc, char *argv[])
|
|||
while(window.running()){
|
||||
window.poll();
|
||||
io_service.poll();
|
||||
|
||||
//if (sounds != nullptr) {
|
||||
// sounds.advance(game->state());
|
||||
//}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -33,5 +33,5 @@ private:
|
|||
char m_rcv_data[max_length];
|
||||
game::State* m_state;
|
||||
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
|
||||
Missile *missile = new Missile(
|
||||
state->generateMissileId(),
|
||||
player,
|
||||
player->ship->position,
|
||||
-util::deg2rad(m_angle),
|
||||
0.005*player->speed);
|
||||
|
||||
Trace *trace = new Trace(missile);
|
||||
missile->trace = trace;
|
||||
|
||||
player->energy -= player->speed;
|
||||
player->missiles.push_back(missile);
|
||||
|
||||
state->addTrace(trace);
|
||||
state->addMissile(missile);
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
namespace game {
|
||||
Missile::Missile(Player *player, const glm::vec2 &pos, float angle, float speed)
|
||||
: player(player)
|
||||
Missile::Missile(size_t id, Player *player, const glm::vec2 &pos, float angle, float speed)
|
||||
: id(id)
|
||||
, player(player)
|
||||
, position(pos)
|
||||
{
|
||||
velocity = speed * glm::vec2(std::sin(angle), std::cos(angle));
|
||||
|
|
|
@ -57,17 +57,18 @@ namespace game {
|
|||
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();
|
||||
|
||||
// try to advance. if something will be hit, return the first hit in
|
||||
// time.
|
||||
Missile::Event advance(const game::State *state, float dt);
|
||||
|
||||
Player *player; // owner won't be hit by own missiles
|
||||
glm::vec2 position;
|
||||
glm::vec2 velocity;
|
||||
Trace *trace;
|
||||
const size_t id;
|
||||
Player *player; // owner won't be hit by own missiles
|
||||
glm::vec2 position;
|
||||
glm::vec2 velocity;
|
||||
Trace *trace;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace game {
|
|||
|
||||
class Player {
|
||||
public:
|
||||
int id;
|
||||
size_t id;
|
||||
bool alive;
|
||||
float speed;
|
||||
float energy;
|
||||
|
@ -23,7 +23,7 @@ namespace game {
|
|||
std::string name;
|
||||
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 "explosion.hpp"
|
||||
|
||||
#include "state_update_event.hpp"
|
||||
#include "events/explosion_event.hpp"
|
||||
|
||||
#include "util.hpp"
|
||||
|
||||
namespace game {
|
||||
|
@ -28,7 +31,8 @@ namespace game {
|
|||
//for (Player *player : players) {
|
||||
//}
|
||||
|
||||
m_nextId = 0;
|
||||
m_nextPlayerId = 0;
|
||||
m_nextMissileId = 0;
|
||||
m_time = 0.0;
|
||||
m_shipRadius = 0.02;
|
||||
m_maxMissileDistance = 2.0;
|
||||
|
@ -146,15 +150,15 @@ namespace game {
|
|||
return true;
|
||||
}
|
||||
|
||||
int State::addPlayer()
|
||||
size_t State::addPlayer()
|
||||
{
|
||||
int playerId = m_nextId++;
|
||||
size_t playerId = m_nextPlayerId++;
|
||||
Player *player = new Player(playerId);
|
||||
players.push_back(player);
|
||||
return playerId;
|
||||
}
|
||||
|
||||
void State::quitPlayer(int playerId)
|
||||
void State::quitPlayer(size_t playerId)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
void State::setName(int playerId, std::string name)
|
||||
void State::setName(size_t playerId, std::string name)
|
||||
{
|
||||
// discard if not unique
|
||||
for (const Player *other : players) {
|
||||
|
@ -186,7 +190,7 @@ namespace game {
|
|||
playerForId(playerId)->name = name;
|
||||
}
|
||||
|
||||
void State::setSpeed(int playerId, double speed)
|
||||
void State::setSpeed(size_t playerId, double speed)
|
||||
{
|
||||
playerForId(playerId)->speed = speed;
|
||||
}
|
||||
|
@ -338,12 +342,15 @@ namespace game {
|
|||
|
||||
for (Explosion *explosion : rm) {
|
||||
explosions.remove(explosion);
|
||||
delete(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);
|
||||
|
@ -355,9 +362,19 @@ namespace game {
|
|||
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));
|
||||
|
||||
//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) {
|
||||
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);
|
||||
if (player != nullptr) {
|
||||
|
@ -425,6 +442,14 @@ namespace game {
|
|||
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) {
|
||||
|
@ -442,7 +467,10 @@ namespace game {
|
|||
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)
|
||||
|
@ -472,4 +500,23 @@ namespace game {
|
|||
{
|
||||
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
|
||||
class Command;
|
||||
class Player;
|
||||
class StateUpdateEvent;
|
||||
|
||||
class Ship;
|
||||
class Trace;
|
||||
|
@ -53,15 +54,15 @@ namespace game {
|
|||
|
||||
// The upper layer (network/renderer) calling these three functions
|
||||
// should keep id's unique and give one (network) input an id.
|
||||
int addPlayer();
|
||||
void clear(int playerId);
|
||||
void setName(int playerId, std::string name);
|
||||
void setSpeed(int playerId, double speed);
|
||||
void quitPlayer(int playerId);
|
||||
void commandForPlayer(int playerId, Command *cmd);
|
||||
size_t addPlayer();
|
||||
void clear(size_t playerId);
|
||||
void setName(size_t playerId, std::string name);
|
||||
void setSpeed(size_t playerId, double speed);
|
||||
void quitPlayer(size_t playerId);
|
||||
void commandForPlayer(size_t playerId, Command *cmd);
|
||||
|
||||
// lookup. return nullptr on invalid playerId
|
||||
Player *playerForId(int playerId);
|
||||
Player *playerForId(size_t playerId);
|
||||
|
||||
/*************************************************************************/
|
||||
/* Mixed stuff */
|
||||
|
@ -75,6 +76,9 @@ namespace game {
|
|||
// add a trace to the list of traces.
|
||||
void addTrace(Trace *trace);
|
||||
|
||||
// add a missile
|
||||
void addMissile(Missile *missile);
|
||||
|
||||
// delete traces with this command
|
||||
void deleteTrace(Trace *trace); // using a pointer
|
||||
|
||||
|
@ -97,10 +101,17 @@ namespace game {
|
|||
void setPlayingFieldSize(int width, int height);
|
||||
void setPlayingFieldCenter(int width, int height);
|
||||
|
||||
size_t generateMissileId();
|
||||
|
||||
void applyAndClearAllOldStateUpdates();
|
||||
|
||||
|
||||
/*************************************************************************/
|
||||
/* Rendering */
|
||||
/*************************************************************************/
|
||||
|
||||
// TODO: hide and replace by events
|
||||
|
||||
// Game items which should be rendered are here:
|
||||
// (access missiles by iterating over player's missiles attribute)
|
||||
std::vector<Planet*> planets;
|
||||
|
@ -135,17 +146,21 @@ namespace game {
|
|||
// usefule for spanwing things
|
||||
bool findFreePositionWithRadius(float r, glm::vec2 &pos);
|
||||
|
||||
float m_maxMissileDistance;
|
||||
float m_playerRespawnTime;
|
||||
float m_shipRadius;
|
||||
float m_defaultEnergy;
|
||||
int m_nextId;
|
||||
int m_maxNumTraces;
|
||||
float m_maxMissileDistance;
|
||||
float m_playerRespawnTime;
|
||||
float m_shipRadius;
|
||||
float m_defaultEnergy;
|
||||
int m_maxNumTraces;
|
||||
size_t m_nextPlayerId;
|
||||
size_t m_nextExplosionId;
|
||||
float m_time;
|
||||
bool m_developerMode;
|
||||
size_t m_nextMissileId;
|
||||
float m_time;
|
||||
bool m_developerMode;
|
||||
|
||||
glm::vec2 m_playingFieldCenter;
|
||||
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