584 lines
16 KiB
C
584 lines
16 KiB
C
/**
|
|
* @file spfx.c
|
|
*
|
|
* @brief Handle the specific effects.
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
#include <SDL/SDL.h>
|
|
#if SDL_VERSION_ATLEAST(1,3,0)
|
|
#include "SDL_haptic.h"
|
|
#endif
|
|
#include "lephisto.h"
|
|
#include "log.h"
|
|
#include "pilot.h"
|
|
#include "physics.h"
|
|
#include "opengl.h"
|
|
#include "pause.h"
|
|
#include "rng.h"
|
|
#include "ldata.h"
|
|
#include "lxml.h"
|
|
#include "spfx.h"
|
|
|
|
#define SPFX_XML_ID "spfxs" /**< XML Document tag. */
|
|
#define SPFX_XML_TAG "spfx" /**< SPFX XML node tag. */
|
|
|
|
#define SPFX_DATA "../dat/spfx.xml" /**< Location of the spfx datafile. */
|
|
#define SPFX_GFX_PRE "../gfx/spfx/" /**< Location of the graphic. */
|
|
#define SPFX_GFX_SUF ".png" /**< Suffix of graphics. */
|
|
|
|
#define CHUNK_SIZE 32 /**< Chunk size to allocate spfx bases. */
|
|
#define SPFX_CHUNK 32 /**< Chunk to allocate when needed. */
|
|
#define SHAKE_VEL_MOD 0.0008 /**< Shake modifier. */
|
|
|
|
#define HAPTIC_UPDATE_INTERVAL 0.1 /**< Time between haptic updates. */
|
|
|
|
/* Special hardcoded effects.. */
|
|
|
|
/* Shake, AKA RUMBLE! */
|
|
static double shake_rad = 0.; /**< Current shake radius (0 = no shake). */
|
|
Vec2 shake_pos = { .x = 0., .y = 0. }; /**< Current shake pos. Used in nebulae.c */
|
|
static Vec2 shake_vel = { .x = 0., .y = 0. }; /**< Current shake vel. */
|
|
static int shake_off = 1; /**< 1 if shake is not active. */
|
|
|
|
#if SDL_VERSION_ATLEAST(1,3,0)
|
|
extern SDL_Haptic* haptic; /**< From joystick.c */
|
|
extern unsigned int haptic_query; /**< From joystick.c */
|
|
static int haptic_rumble = -1; /**< Haptic rumble effect ID. */
|
|
static SDL_HapticEffect haptic_rumbleEffect; /**< Haptic rumble effect. */
|
|
static double haptic_lastUpdate = 0.; /**< Timer to update haptic effect again. */
|
|
#endif
|
|
|
|
/**
|
|
* @struct SPFX_Base
|
|
*
|
|
* @brief Generic special effect.
|
|
*/
|
|
typedef struct SPFX_Base_ {
|
|
char* name; /**< Name of the special effect. */
|
|
|
|
double ttl; /**< Time to live. */
|
|
double anim; /**< Total duration in ms. */
|
|
|
|
glTexture* gfx; /**< Will use each sprite as a frame. */
|
|
} SPFX_Base;
|
|
|
|
static SPFX_Base* spfx_effects = NULL; /**< Total special effects. */
|
|
static int spfx_neffects = 0; /**< Total number of special effects. */
|
|
|
|
/**
|
|
* @struct SPFX
|
|
*
|
|
* @brief An actual in-game active special effect.
|
|
*/
|
|
typedef struct SPFX_ {
|
|
Vec2 pos; /**< Current position. */
|
|
Vec2 vel; /**< Current velocity. */
|
|
|
|
int lastframe; /**< Need when pausing. */
|
|
int effect; /**< Actual effect. */
|
|
double timer; /**M Time left. */
|
|
} SPFX;
|
|
|
|
/* Front stack is for effects on player. */
|
|
/* Back is for everything else. */
|
|
static SPFX* spfx_stack_front = NULL; /**< Frontal special effect layer. */
|
|
static int spfx_nstack_front = 0; /**< Number of special effects in front. */
|
|
static int spfx_mstack_front = 0; /**< Memory allocated for frontal special effects. */
|
|
static SPFX* spfx_stack_back = NULL; /**< Back special effect layer. */
|
|
static int spfx_nstack_back = 0; /**< Number of special effects in the back. */
|
|
static int spfx_mstack_back = 0; /**< Memory allocated for special effects in back. */
|
|
|
|
/* General. */
|
|
static int spfx_base_parse(SPFX_Base* tmp, const xmlNodePtr parent);
|
|
static void spfx_base_free(SPFX_Base* effect);
|
|
static void spfx_destroy(SPFX* layer, int* nlayer, int spfx);
|
|
static void spfx_update_layer(SPFX* layer, int* nlayer, const double dt);
|
|
/* Haptic. */
|
|
static int spfx_hapticInit(void);
|
|
static void spfx_hapticRumble(double mod);
|
|
|
|
/**
|
|
* @brief Parse an xml node containing an spfx.
|
|
* @param tmp Address to load sfx into.
|
|
* @param parent XML node containing the spfx data.
|
|
* @return 0 on success.
|
|
*/
|
|
static int spfx_base_parse(SPFX_Base* tmp, const xmlNodePtr parent) {
|
|
xmlNodePtr node;
|
|
|
|
/* Clear data. */
|
|
memset(tmp, 0, sizeof(SPFX_Base));
|
|
|
|
/* Get the name (mallocs). */
|
|
tmp->name = xml_nodeProp(parent, "name");
|
|
|
|
/* Extract the data. */
|
|
node = parent->xmlChildrenNode;
|
|
do {
|
|
xmlr_float(node, "anim", tmp->anim);
|
|
xmlr_float(node, "ttl", tmp->ttl);
|
|
if(xml_isNode(node, "gfx"))
|
|
tmp->gfx = xml_parseTexture(node,
|
|
SPFX_GFX_PRE"%s"SPFX_GFX_SUF, 6, 5, 0);
|
|
} while(xml_nextNode(node));
|
|
|
|
/* Convert from ms to s. */
|
|
tmp->anim /= 1000.;
|
|
tmp->ttl /= 1000.;
|
|
if(tmp->ttl == 0.)
|
|
tmp->ttl = tmp->anim;
|
|
|
|
#define MELEMENT(o,s) \
|
|
if(o) WARN("SPFX '%s' missing/invalid '"s"' element", tmp->name) /**< Define to help check for data errors. */
|
|
MELEMENT(tmp->anim == 0., "anim");
|
|
MELEMENT(tmp->ttl == 0., "ttl");
|
|
MELEMENT(tmp->gfx == NULL, "gfx");
|
|
#undef ELEMENT
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Free an SPFX_Base.
|
|
* @param effect SPFX_Base to free.
|
|
*/
|
|
static void spfx_base_free(SPFX_Base* effect) {
|
|
if(effect->name != NULL) {
|
|
free(effect->name);
|
|
effect->name = NULL;
|
|
}
|
|
if(effect->gfx != NULL) {
|
|
gl_freeTexture(effect->gfx);
|
|
effect->gfx = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Get the id of an spfx based on name.
|
|
* @param name Name to match.
|
|
* @return ID of the special effect or -1 on error.
|
|
*/
|
|
int spfx_get(char* name) {
|
|
int i;
|
|
for(i = 0; i < spfx_neffects; i++)
|
|
if(strcmp(spfx_effects[i].name, name)==0)
|
|
return i;
|
|
WARN("SPFX '%s' not found!", name);
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* @brief Load the spfx stack.
|
|
* @return 0 on success.
|
|
*
|
|
* @todo Make spfx not hardcoded.
|
|
*/
|
|
int spfx_load(void) {
|
|
int mem;
|
|
uint32_t bufsize;
|
|
char* buf;
|
|
xmlNodePtr node;
|
|
xmlDocPtr doc;
|
|
|
|
/* Load and read the data. */
|
|
buf = ldata_read(SPFX_DATA, &bufsize);
|
|
doc = xmlParseMemory(buf, bufsize);
|
|
|
|
/* Check to see if document exists. */
|
|
node = doc->xmlChildrenNode;
|
|
if(!xml_isNode(node, SPFX_XML_ID)) {
|
|
ERR("Malformed '"SPFX_DATA"' file: missing root element '"SPFX_XML_ID"'");
|
|
return -1;
|
|
}
|
|
|
|
/* Check to see if it's populated. */
|
|
node = node->xmlChildrenNode; /* First system node. */
|
|
if(node == NULL) {
|
|
ERR("Malformed '"SPFX_DATA"' file: does not contain elements");
|
|
return -1;
|
|
}
|
|
|
|
/* First pass, loads up ammunition. */
|
|
mem = 0;
|
|
do {
|
|
if(xml_isNode(node, SPFX_XML_TAG)) {
|
|
spfx_neffects++;
|
|
if(spfx_neffects > mem) {
|
|
mem += CHUNK_SIZE;
|
|
spfx_effects = realloc(spfx_effects, sizeof(SPFX_Base)*mem);
|
|
}
|
|
spfx_base_parse(&spfx_effects[spfx_neffects-1], node);
|
|
}
|
|
} while(xml_nextNode(node));
|
|
|
|
/* Shrink back to minimum - shouldn't change ever. */
|
|
spfx_effects = realloc(spfx_effects, sizeof(SPFX_Base) * spfx_neffects);
|
|
|
|
/* Clean up. */
|
|
xmlFreeDoc(doc);
|
|
free(buf);
|
|
|
|
/* Now initialize force feedback. */
|
|
spfx_hapticInit();
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Free the spfx stack.
|
|
*/
|
|
void spfx_free(void) {
|
|
int i;
|
|
|
|
/* Get rid of all the particles and free the stacks. */
|
|
spfx_clear();
|
|
if(spfx_stack_front) free(spfx_stack_front);
|
|
spfx_stack_front = NULL;
|
|
spfx_mstack_front = 0;
|
|
if(spfx_stack_back) free(spfx_stack_back);
|
|
spfx_stack_back = NULL;
|
|
spfx_mstack_back = 0;
|
|
|
|
for(i = 0; i < spfx_neffects; i++)
|
|
spfx_base_free(&spfx_effects[i]);
|
|
free(spfx_effects);
|
|
spfx_effects = NULL;
|
|
spfx_neffects = 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Create a new special effect.
|
|
* @param effect Base effect identifier to use.
|
|
* @param px X position of the effect.
|
|
* @param py Y position of the effect.
|
|
* @param vx X velocity of the effect.
|
|
* @param vy Y velocity of the effect.
|
|
* @param layer Layer to put the effect on.
|
|
*/
|
|
void spfx_add(int effect,
|
|
const double px, const double py,
|
|
const double vx, const double vy,
|
|
const int layer) {
|
|
|
|
SPFX* cur_spfx;
|
|
double ttl, anim;
|
|
|
|
if((effect < 0) || (effect > spfx_neffects)) {
|
|
WARN("Trying to add spfx with invalid effect!");
|
|
return;
|
|
}
|
|
|
|
/* Select the layer. */
|
|
if(layer == SPFX_LAYER_FRONT) {
|
|
/* Front layer. */
|
|
if(spfx_mstack_front < spfx_nstack_front+1) {
|
|
/* We need more memory. */
|
|
spfx_mstack_front += SPFX_CHUNK;
|
|
spfx_stack_front = realloc(spfx_stack_front, spfx_mstack_front*sizeof(SPFX));
|
|
}
|
|
cur_spfx = &spfx_stack_front[spfx_nstack_front];
|
|
spfx_nstack_front++;
|
|
}
|
|
else if(layer == SPFX_LAYER_BACK) {
|
|
/* Back layer. */
|
|
if(spfx_mstack_back < spfx_nstack_back+1) {
|
|
/* Need more memory. */
|
|
spfx_mstack_back += SPFX_CHUNK;
|
|
spfx_stack_back = realloc(spfx_stack_back, spfx_mstack_back*sizeof(SPFX));
|
|
}
|
|
cur_spfx = &spfx_stack_back[spfx_nstack_back];
|
|
spfx_nstack_back++;
|
|
}
|
|
|
|
/* The actualy adding of the spfx. */
|
|
cur_spfx->effect = effect;
|
|
vect_csetmin(&cur_spfx->pos, px, py);
|
|
vect_csetmin(&cur_spfx->vel, vx, vy);
|
|
/* timer magic if ttl != anim. */
|
|
ttl = spfx_effects[effect].ttl;
|
|
anim = spfx_effects[effect].anim;
|
|
if(ttl != anim)
|
|
cur_spfx->timer = ttl + RNGF()*anim;
|
|
else
|
|
cur_spfx->timer = ttl;
|
|
}
|
|
|
|
/**
|
|
* @brief Clear all the currently running effects.
|
|
*/
|
|
void spfx_clear(void) {
|
|
int i;
|
|
|
|
/* Clear front layer. */
|
|
for(i = spfx_nstack_front-1; i >= 0; i--)
|
|
spfx_destroy(spfx_stack_front, &spfx_nstack_front, i);
|
|
|
|
/* Clear back layer. */
|
|
for(i = spfx_nstack_back-1; i >= 0; i--)
|
|
spfx_destroy(spfx_stack_back, &spfx_nstack_back, i);
|
|
|
|
/* Clear rumble. */
|
|
shake_rad = 0.;
|
|
shake_pos.x = shake_pos.y = 0.;
|
|
shake_vel.x = shake_vel.y = 0.;
|
|
}
|
|
|
|
/**
|
|
* @brief Destroys an active spfx.
|
|
* @param layer Layer the spfx is on.
|
|
* @param nlayer Pointer to the number of elements in the layer.
|
|
* @param spfx Position of the spfx in the stack.
|
|
*/
|
|
static void spfx_destroy(SPFX* layer, int* nlayer, int spfx) {
|
|
(*nlayer)--;
|
|
memmove(&layer[spfx], &layer[spfx+1], (*nlayer-spfx)*sizeof(SPFX));
|
|
}
|
|
|
|
/**
|
|
* @brief Update all the spfx.
|
|
* @param dt Current delta tick.
|
|
*/
|
|
void spfx_update(const double dt) {
|
|
spfx_update_layer(spfx_stack_front, &spfx_nstack_front, dt);
|
|
spfx_update_layer(spfx_stack_back, &spfx_nstack_back, dt);
|
|
}
|
|
|
|
/**
|
|
* @brief Update an individual spfx.
|
|
* @param layer Layer the spfx is on.
|
|
* @param nlayer Pointer to the assosiated nlayer.
|
|
* @param dt Current delta tick.
|
|
*/
|
|
static void spfx_update_layer(SPFX* layer, int* nlayer, const double dt) {
|
|
int i;
|
|
|
|
for(i = 0; i < *nlayer; i++) {
|
|
layer[i].timer -= dt; /* Less time to live. */
|
|
|
|
/* Time to die!!! */
|
|
if(layer[i].timer < 0.) {
|
|
spfx_destroy(layer, nlayer, i);
|
|
i--;
|
|
continue;
|
|
}
|
|
/* Mkay. Update it now. */
|
|
vect_cadd(&layer[i].pos, dt*VX(layer[i].vel), dt*VY(layer[i].vel));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Prepare the rendering for the special effects.
|
|
*
|
|
* Should be called at the beginning of the rendering loop.
|
|
* @param dt Current delta tick.
|
|
*/
|
|
void spfx_start(const double dt) {
|
|
GLdouble bx, by, x, y;
|
|
double inc;
|
|
|
|
if(shake_off == 1) return; /* Save the cycles. */
|
|
|
|
#if SDL_VERSION_ATLEAST(1,3,0)
|
|
/* Decrement the haptic timer. */
|
|
if(haptic_lastUpdate > 0.)
|
|
haptic_lastUpdate -= dt;
|
|
#endif
|
|
|
|
bx = SCREEN_W / 2;
|
|
by = SCREEN_H / 2;
|
|
|
|
if(!paused) {
|
|
inc = dt*100000.;
|
|
|
|
/* Calculate new position. */
|
|
if(shake_rad > 0.01) {
|
|
vect_cadd(&shake_pos, shake_vel.x * inc, shake_vel.y * inc);
|
|
|
|
if(VMOD(shake_pos) > shake_rad) {
|
|
/* Change direction. */
|
|
vect_pset(&shake_pos, shake_rad, VANGLE(shake_pos));
|
|
vect_pset(&shake_vel, SHAKE_VEL_MOD*shake_rad,
|
|
-VANGLE(shake_pos) + (RNGF()-0.5) * M_PI);
|
|
}
|
|
/* The shake decays over time. */
|
|
shake_rad -= SHAKE_DECAY * dt;
|
|
if(shake_rad < 0.) shake_rad = 0.;
|
|
|
|
x = shake_pos.x;
|
|
y = shake_pos.y;
|
|
} else {
|
|
shake_rad = 0.;
|
|
shake_off = 1;
|
|
x = 0.;
|
|
y = 0.;
|
|
}
|
|
}
|
|
glMatrixMode(GL_PROJECTION);
|
|
glLoadIdentity();
|
|
glOrtho(-bx+x, bx+x, -by+y, by+y, -1., 1.);
|
|
}
|
|
|
|
/**
|
|
* @brief Increases the current rumble level.
|
|
*
|
|
* Rumble will decay over time.
|
|
* @param mod Modifier to increase levely by.
|
|
*/
|
|
void spfx_shake(double mod) {
|
|
/* Add the modifier. */
|
|
shake_rad += mod;
|
|
if(shake_rad > SHAKE_MAX) shake_rad = SHAKE_MAX;
|
|
vect_pset(&shake_vel, SHAKE_VEL_MOD*shake_rad, RNGF() * 2. * M_PI);
|
|
|
|
/* Rumble if it wasn't rumbling before. */
|
|
spfx_hapticRumble(mod);
|
|
|
|
/* Notify that rumble is active. */
|
|
shake_off = 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Initializes the rumble effect.
|
|
* @return 0 on success.
|
|
*/
|
|
static int spfx_hapticInit(void) {
|
|
#if SDL_VERSION_ATLEAST(1,3,0)
|
|
SDL_HapticEffect* efx;
|
|
|
|
/* Haptic must be enabled. */
|
|
if(haptic == NULL)
|
|
return 0;
|
|
|
|
efx = &haptic_rumbleEffect;
|
|
memset(efx, 0, sizeof(SDL_HapticEffect));
|
|
efx->type = SDL_HAPTIC_SINE;
|
|
efx->periodic.direction.type = SDL_HAPTIC_POLAR;
|
|
efx->periodic.length = 1000;
|
|
efx->periodic.period = 200;
|
|
efx->periodic.magnitude = 0x4000;
|
|
efx->periodic.fade_length = 1000;
|
|
efx->periodic.fade_level = 0;
|
|
|
|
haptic_rumble = SDL_HapticNewEffect(haptic, efx);
|
|
if(haptic_rumble < 0) {
|
|
WARN("Unable to upload haptic effect: %s.", SDL_GetError());
|
|
return -1;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Run a rumble effect.
|
|
* @param mod Current modifier being added.
|
|
*/
|
|
static void spfx_hapticRumble(double mod) {
|
|
#if SDL_VERSION_ATLEAST(1,3,0)
|
|
SDL_HapticEffect* efx;
|
|
double len, mag;
|
|
|
|
if(haptic_rumble >= 0) {
|
|
/* Not time to update yet. */
|
|
if((haptic_lastUpdate > 0.) || shake_off || (mod > SHAKE_MAX/3.))
|
|
return;
|
|
|
|
/* Stop the effect if it was playing. */
|
|
SDL_HapticStopEffect(haptic, haptic_rumble);
|
|
|
|
/* Get length and magnitude. */
|
|
len = 1000. * shake_rad / SHAKE_DECAY;
|
|
mag = 32767. * (shake_rad / SHAKE_MAX);
|
|
|
|
/* Update the effect. */
|
|
efx = &haptic_rumbleEffect;
|
|
efx->periodic.magnitude = (uint32_t)mag;
|
|
efx->periodic.length = (uint32_t)len;
|
|
efx->periodic.fade_length = MIN(efx->periodic.length, 1000);
|
|
if(SDL_HapticUpdateEffect(haptic, haptic_rumble, &haptic_rumbleEffect) < 0) {
|
|
WARN("Failed to update effect: %s.", SDL_GetError());
|
|
return;
|
|
}
|
|
|
|
/* Run the new effect. */
|
|
SDL_HapticRunEffect(haptic, haptic_rumble, 1);
|
|
|
|
/* Set timer again. */
|
|
haptic_lastUpdate = HAPTIC_UPDATE_INTERVAL;
|
|
}
|
|
#else
|
|
(void)mod;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @brief Set the cinematic mode.
|
|
*
|
|
* Should be run at the end of the render loop if needed.
|
|
*/
|
|
void spfx_cinematic(void) {
|
|
glMatrixMode(GL_MODELVIEW);
|
|
glPushMatrix(); /* Translation matrix. */
|
|
glTranslated(-(double)SCREEN_W/2., -(double)SCREEN_H/2., 0);
|
|
|
|
COLOUR(cBlack);
|
|
glBegin(GL_QUADS);
|
|
glVertex2d(0., 0.);
|
|
glVertex2d(0., SCREEN_H*0.2);
|
|
glVertex2d(SCREEN_W, SCREEN_H*0.2);
|
|
glVertex2d(SCREEN_W, 0.);
|
|
glVertex2d(0., SCREEN_H);
|
|
glVertex2d(SCREEN_W, SCREEN_H);
|
|
glVertex2d(SCREEN_W, SCREEN_H*0.8);
|
|
glVertex2d(0., SCREEN_H*0.8);
|
|
glEnd();
|
|
|
|
glPopMatrix(); /* Translation matrix. */
|
|
}
|
|
|
|
/**
|
|
* @brief Render the entire spfx layer.
|
|
* @param layer Layer to render.
|
|
*/
|
|
void spfx_render(const int layer) {
|
|
SPFX* spfx_stack;
|
|
int i, spfx_nstack;
|
|
SPFX_Base* effect;
|
|
int sx, sy;
|
|
double time;
|
|
|
|
/* Get the appropriate layer. */
|
|
switch(layer) {
|
|
case SPFX_LAYER_FRONT:
|
|
spfx_stack = spfx_stack_front;
|
|
spfx_nstack = spfx_nstack_front;
|
|
break;
|
|
case SPFX_LAYER_BACK:
|
|
spfx_stack = spfx_stack_back;
|
|
spfx_nstack = spfx_nstack_back;
|
|
break;
|
|
}
|
|
|
|
/* Now render the layer. */
|
|
for(i = spfx_nstack-1; i >= 0; i--) {
|
|
effect = &spfx_effects[spfx_stack[i].effect];
|
|
|
|
/* Simplifies. */
|
|
sx = (int)effect->gfx->sx;
|
|
sy = (int)effect->gfx->sy;
|
|
|
|
if(!paused) { /* Don't calculate frame if paused. */
|
|
time = fmod(spfx_stack[i].timer, effect->anim) / effect->anim;
|
|
spfx_stack[i].lastframe = sx * sy * MIN(time, 1.);
|
|
}
|
|
|
|
/* Render. */
|
|
gl_blitSprite(effect->gfx,
|
|
VX(spfx_stack[i].pos), VY(spfx_stack[i].pos),
|
|
spfx_stack[i].lastframe % sx,
|
|
spfx_stack[i].lastframe / sx,
|
|
NULL);
|
|
}
|
|
}
|
|
|