Merge branch 'master' of git://github.com/BTAxis/naev into testmission
[naev.git] / src / spfx.c
blobd3c22ac1da1b5633d118f750777f496dcb80ec52
1 /*
2 * See Licensing and Copyright notice in naev.h
3 */
5 /**
6 * @file spfx.c
8 * @brief Handles the special effects.
9 */
12 #include "spfx.h"
14 #include "naev.h"
16 #include <inttypes.h>
18 #include "SDL.h"
19 #if SDL_VERSION_ATLEAST(1,3,0)
20 #include "SDL_haptic.h"
21 #endif /* SDL_VERSION_ATLEAST(1,3,0) */
23 #include "log.h"
24 #include "pilot.h"
25 #include "physics.h"
26 #include "opengl.h"
27 #include "pause.h"
28 #include "rng.h"
29 #include "ndata.h"
30 #include "nxml.h"
31 #include "debris.h"
34 #define SPFX_XML_ID "spfxs" /**< XML Document tag. */
35 #define SPFX_XML_TAG "spfx" /**< SPFX XML node tag. */
37 #define SPFX_DATA "dat/spfx.xml" /**< Location of the spfx datafile. */
38 #define SPFX_GFX_PRE "gfx/spfx/" /**< location of the graphic */
39 #define SPFX_GFX_SUF ".png" /**< Suffix of graphics. */
41 #define CHUNK_SIZE 128 /**< Chunk size to allocate spfx bases. */
43 #define SPFX_CHUNK_MAX 16384 /**< Maximum chunk to alloc when needed */
44 #define SPFX_CHUNK_MIN 256 /**< Minimum chunk to alloc when needed */
46 #define SHAKE_VEL_MOD 0.0008 /**< Shake modifier. */
48 #define HAPTIC_UPDATE_INTERVAL 0.1 /**< Time between haptic updates. */
52 * special hardcoded special effects
54 /* shake aka rumble */
55 static double shake_rad = 0.; /**< Current shake radius (0 = no shake). */
56 static Vector2d shake_pos = { .x = 0., .y = 0. }; /**< Current shake position. */
57 static Vector2d shake_vel = { .x = 0., .y = 0. }; /**< Current shake velocity. */
58 static int shake_off = 1; /**< 1 if shake is not active. */
61 #if SDL_VERSION_ATLEAST(1,3,0)
62 extern SDL_Haptic *haptic; /**< From joystick.c */
63 extern unsigned int haptic_query; /**< From joystick.c */
64 static int haptic_rumble = -1; /**< Haptic rumble effect ID. */
65 static SDL_HapticEffect haptic_rumbleEffect; /**< Haptic rumble effect. */
66 static double haptic_lastUpdate = 0.; /**< Timer to update haptic effect again. */
67 #endif /* SDL_VERSION_ATLEAST(1,3,0) */
70 /**
71 * @struct SPFX_Base
73 * @brief Generic special effect.
75 typedef struct SPFX_Base_ {
76 char* name; /**< Name of the special effect. */
78 double ttl; /**< Time to live */
79 double anim; /**< Total duration in ms */
81 glTexture *gfx; /**< will use each sprite as a frame */
82 } SPFX_Base;
84 static SPFX_Base *spfx_effects = NULL; /**< Total special effects. */
85 static int spfx_neffects = 0; /**< Total number of special effects. */
88 /**
89 * @struct SPFX
91 * @brief An actual in-game active special effect.
93 typedef struct SPFX_ {
94 Vector2d pos; /**< Current position. */
95 Vector2d vel; /**< Current velocity. */
97 int lastframe; /**< Needed when paused */
98 int effect; /**< The real effect */
100 double timer; /**< Time left */
101 } SPFX;
104 /* front stack is for effects on player, back is for the rest */
105 static SPFX *spfx_stack_front = NULL; /**< Frontal special effect layer. */
106 static int spfx_nstack_front = 0; /**< Number of special effects in front. */
107 static int spfx_mstack_front = 0; /**< Memory allocated for frontal special effects. */
108 static SPFX *spfx_stack_back = NULL; /**< Back special effect layer. */
109 static int spfx_nstack_back = 0; /**< Number of special effects in back. */
110 static int spfx_mstack_back = 0; /**< Memory allocated for special effects in back. */
114 * prototypes
116 /* General. */
117 static int spfx_base_parse( SPFX_Base *temp, const xmlNodePtr parent );
118 static void spfx_base_free( SPFX_Base *effect );
119 static void spfx_destroy( SPFX *layer, int *nlayer, int spfx );
120 static void spfx_update_layer( SPFX *layer, int *nlayer, const double dt );
121 /* Haptic. */
122 static int spfx_hapticInit (void);
123 static void spfx_hapticRumble( double mod );
127 * @brief Parses an xml node containing a SPFX.
129 * @param temp Address to load SPFX into.
130 * @param parent XML Node containing the SPFX data.
131 * @return 0 on success.
133 static int spfx_base_parse( SPFX_Base *temp, const xmlNodePtr parent )
135 xmlNodePtr node;
137 /* Clear data. */
138 memset( temp, 0, sizeof(SPFX_Base) );
140 /* Get the name (mallocs). */
141 temp->name = xml_nodeProp(parent,"name");
143 /* Extract the data. */
144 node = parent->xmlChildrenNode;
145 do {
146 xmlr_float(node, "anim", temp->anim);
147 xmlr_float(node, "ttl", temp->ttl);
148 if (xml_isNode(node,"gfx"))
149 temp->gfx = xml_parseTexture( node,
150 SPFX_GFX_PRE"%s"SPFX_GFX_SUF, 6, 5, 0 );
151 } while (xml_nextNode(node));
153 /* Convert from ms to s. */
154 temp->anim /= 1000.;
155 temp->ttl /= 1000.;
156 if (temp->ttl == 0.)
157 temp->ttl = temp->anim;
159 #define MELEMENT(o,s) \
160 if (o) WARN("SPFX '%s' missing/invalid '"s"' element", temp->name) /**< Define to help check for data errors. */
161 MELEMENT(temp->anim==0.,"anim");
162 MELEMENT(temp->ttl==0.,"ttl");
163 MELEMENT(temp->gfx==NULL,"gfx");
164 #undef MELEMENT
166 return 0;
171 * @brief Frees a SPFX_Base.
173 * @param effect SPFX_Base to free.
175 static void spfx_base_free( SPFX_Base *effect )
177 if (effect->name != NULL) {
178 free(effect->name);
179 effect->name = NULL;
181 if (effect->gfx != NULL) {
182 gl_freeTexture(effect->gfx);
183 effect->gfx = NULL;
189 * @brief Gets the id of an spfx based on name.
191 * @param name Name to match.
192 * @return ID of the special effect or -1 on error.
194 int spfx_get( char* name )
196 int i;
197 for (i=0; i<spfx_neffects; i++)
198 if (strcmp(spfx_effects[i].name, name)==0)
199 return i;
200 return -1;
205 * @brief Loads the spfx stack.
207 * @return 0 on success.
209 * @todo Make spfx not hardcoded.
211 int spfx_load (void)
213 int mem;
214 uint32_t bufsize;
215 char *buf;
216 xmlNodePtr node;
217 xmlDocPtr doc;
219 /* Load and read the data. */
220 buf = ndata_read( SPFX_DATA, &bufsize );
221 doc = xmlParseMemory( buf, bufsize );
223 /* Check to see if document exists. */
224 node = doc->xmlChildrenNode;
225 if (!xml_isNode(node,SPFX_XML_ID)) {
226 ERR("Malformed '"SPFX_DATA"' file: missing root element '"SPFX_XML_ID"'");
227 return -1;
230 /* Check to see if is populated. */
231 node = node->xmlChildrenNode; /* first system node */
232 if (node == NULL) {
233 ERR("Malformed '"SPFX_DATA"' file: does not contain elements");
234 return -1;
237 /* First pass, loads up ammunition. */
238 mem = 0;
239 do {
240 if (xml_isNode(node,SPFX_XML_TAG)) {
242 spfx_neffects++;
243 if (spfx_neffects > mem) {
244 mem += CHUNK_SIZE;
245 spfx_effects = realloc(spfx_effects, sizeof(SPFX_Base)*mem);
247 spfx_base_parse( &spfx_effects[spfx_neffects-1], node );
249 } while (xml_nextNode(node));
250 /* Shrink back to minimum - shouldn't change ever. */
251 spfx_effects = realloc(spfx_effects, sizeof(SPFX_Base) * spfx_neffects);
253 /* Clean up. */
254 xmlFreeDoc(doc);
255 free(buf);
259 * Now initialize force feedback.
261 spfx_hapticInit();
263 return 0;
268 * @brief Frees the spfx stack.
270 void spfx_free (void)
272 int i;
274 /* Clean up the debris. */
275 debris_cleanup();
277 /* get rid of all the particles and free the stacks */
278 spfx_clear();
279 if (spfx_stack_front) free(spfx_stack_front);
280 spfx_stack_front = NULL;
281 spfx_mstack_front = 0;
282 if (spfx_stack_back) free(spfx_stack_back);
283 spfx_stack_back = NULL;
284 spfx_mstack_back = 0;
286 /* now clear the effects */
287 for (i=0; i<spfx_neffects; i++)
288 spfx_base_free( &spfx_effects[i] );
289 free(spfx_effects);
290 spfx_effects = NULL;
291 spfx_neffects = 0;
296 * @brief Creates a new special effect.
298 * @param effect Base effect identifier to use.
299 * @param px X position of the effect.
300 * @param py Y position of the effect.
301 * @param vx X velocity of the effect.
302 * @param vy Y velocity of the effect.
303 * @param layer Layer to put the effect on.
305 void spfx_add( int effect,
306 const double px, const double py,
307 const double vx, const double vy,
308 const int layer )
310 SPFX *cur_spfx;
311 double ttl, anim;
313 if ((effect < 0) || (effect > spfx_neffects)) {
314 WARN("Trying to add spfx with invalid effect!");
315 return;
319 * Select the Layer
321 if (layer == SPFX_LAYER_FRONT) { /* front layer */
322 if (spfx_mstack_front < spfx_nstack_front+1) { /* need more memory */
323 if (spfx_mstack_front == 0)
324 spfx_mstack_front = SPFX_CHUNK_MIN;
325 else
326 spfx_mstack_front += MIN( spfx_mstack_front, SPFX_CHUNK_MAX );
327 spfx_stack_front = realloc( spfx_stack_front, spfx_mstack_front*sizeof(SPFX) );
329 cur_spfx = &spfx_stack_front[spfx_nstack_front];
330 spfx_nstack_front++;
332 else if (layer == SPFX_LAYER_BACK) { /* back layer */
333 if (spfx_mstack_back < spfx_nstack_back+1) { /* need more memory */
334 if (spfx_mstack_back == 0)
335 spfx_mstack_back = SPFX_CHUNK_MIN;
336 else
337 spfx_mstack_back += MIN( spfx_mstack_back, SPFX_CHUNK_MAX );
338 spfx_stack_back = realloc( spfx_stack_back, spfx_mstack_back*sizeof(SPFX) );
340 cur_spfx = &spfx_stack_back[spfx_nstack_back];
341 spfx_nstack_back++;
343 else {
344 WARN("Invalid SPFX layer.");
345 return;
348 /* The actual adding of the spfx */
349 cur_spfx->effect = effect;
350 vect_csetmin( &cur_spfx->pos, px, py );
351 vect_csetmin( &cur_spfx->vel, vx, vy );
352 /* Timer magic if ttl != anim */
353 ttl = spfx_effects[effect].ttl;
354 anim = spfx_effects[effect].anim;
355 if (ttl != anim)
356 cur_spfx->timer = ttl + RNGF()*anim;
357 else
358 cur_spfx->timer = ttl;
363 * @brief Clears all the currently running effects.
365 void spfx_clear (void)
367 int i;
369 /* Clear front layer */
370 for (i=spfx_nstack_front-1; i>=0; i--)
371 spfx_destroy( spfx_stack_front, &spfx_nstack_front, i );
373 /* Clear back layer */
374 for (i=spfx_nstack_back-1; i>=0; i--)
375 spfx_destroy( spfx_stack_back, &spfx_nstack_back, i );
377 /* Clear rumble */
378 shake_rad = 0.;
379 shake_pos.x = shake_pos.y = 0.;
380 shake_vel.x = shake_vel.y = 0.;
384 * @brief Destroys an active spfx.
386 * @param layer Layer the spfx is on.
387 * @param nlayer Pointer to the number of elements in the layer.
388 * @param spfx Position of the spfx in the stack.
390 static void spfx_destroy( SPFX *layer, int *nlayer, int spfx )
392 (*nlayer)--;
393 memmove( &layer[spfx], &layer[spfx+1], (*nlayer-spfx)*sizeof(SPFX) );
398 * @brief Updates all the spfx.
400 * @param dt Current delta tick.
402 void spfx_update( const double dt )
404 spfx_update_layer( spfx_stack_front, &spfx_nstack_front, dt );
405 spfx_update_layer( spfx_stack_back, &spfx_nstack_back, dt );
410 * @brief Updates an individual spfx.
412 * @param layer Layer the spfx is on.
413 * @param nlayer Pointer to the assosciated nlayer.
414 * @param dt Current delta tick.
416 static void spfx_update_layer( SPFX *layer, int *nlayer, const double dt )
418 int i;
420 for (i=0; i<*nlayer; i++) {
421 layer[i].timer -= dt; /* less time to live */
423 /* time to die! */
424 if (layer[i].timer < 0.) {
425 spfx_destroy( layer, nlayer, i );
426 i--;
427 continue;
430 /* actually update it */
431 vect_cadd( &layer[i].pos, dt*VX(layer[i].vel), dt*VY(layer[i].vel) );
437 * @brief Prepares the rendering for the special effects.
439 * Should be called at the beginning of the rendering loop.
441 * @param dt Current delta tick.
443 void spfx_begin( const double dt )
445 GLdouble bx, by, x, y;
446 double inc;
448 /* Save cycles. */
449 if (shake_off == 1)
450 return;
452 #if SDL_VERSION_ATLEAST(1,3,0)
453 /* Decrement the haptic timer. */
454 if (haptic_lastUpdate > 0.)
455 haptic_lastUpdate -= dt;
456 #endif /* SDL_VERSION_ATLEAST(1,3,0) */
458 /* set defaults */
459 bx = SCREEN_W/2;
460 by = SCREEN_H/2;
462 if (!paused) {
463 inc = dt*100000.;
465 /* calculate new position */
466 if (shake_rad > 0.01) {
467 vect_cadd( &shake_pos, shake_vel.x * inc, shake_vel.y * inc );
469 if (VMOD(shake_pos) > shake_rad) { /* change direction */
470 vect_pset( &shake_pos, shake_rad, VANGLE(shake_pos) );
471 vect_pset( &shake_vel, SHAKE_VEL_MOD*shake_rad,
472 -VANGLE(shake_pos) + (RNGF()-0.5) * M_PI );
475 /* the shake decays over time */
476 shake_rad -= SHAKE_DECAY*dt;
477 if (shake_rad < 0.)
478 shake_rad = 0.;
480 x = shake_pos.x;
481 y = shake_pos.y;
483 else {
484 shake_rad = 0.;
485 shake_off = 1;
486 x = 0.;
487 y = 0.;
490 else {
491 x = 0.;
492 y = 0.;
495 /* set the new viewport */
496 glMatrixMode(GL_PROJECTION);
497 glLoadIdentity();
498 glOrtho( -bx+x, bx+x, -by+y, by+y, -1., 1. );
503 * @brief Indicates the end of the spfx loop.
505 * Should be called before the HUD.
507 void spfx_end (void)
509 /* Save cycles. */
510 if (shake_off == 1)
511 return;
513 /* set the new viewport */
514 gl_defViewport();
519 * @brief Increases the current rumble level.
521 * Rumble will decay over time.
523 * @param mod Modifier to increase level by.
525 void spfx_shake( double mod )
527 /* Add the modifier. */
528 shake_rad += mod;
529 if (shake_rad > SHAKE_MAX)
530 shake_rad = SHAKE_MAX;
531 vect_pset( &shake_vel, SHAKE_VEL_MOD*shake_rad, RNGF() * 2. * M_PI );
533 /* Rumble if it wasn't rumbling before. */
534 spfx_hapticRumble(mod);
536 /* Notify that rumble is active. */
537 shake_off = 0;
542 * @brief Gets the current shake position.
544 * @param[out] X X shake position.
545 * @param[out] Y Y shake position.
547 void spfx_getShake( double *x, double *y )
549 if (shake_off || paused) {
550 *x = 0.;
551 *y = 0.;
553 else {
554 *x = shake_pos.x;
555 *y = shake_pos.y;
561 * @brief Initializes the rumble effect.
563 * @return 0 on success.
565 static int spfx_hapticInit (void)
567 #if SDL_VERSION_ATLEAST(1,3,0)
568 SDL_HapticEffect *efx;
570 /* Haptic must be enabled. */
571 if (haptic == NULL)
572 return 0;
574 efx = &haptic_rumbleEffect;
575 memset( efx, 0, sizeof(SDL_HapticEffect) );
576 efx->type = SDL_HAPTIC_SINE;
577 efx->periodic.direction.type = SDL_HAPTIC_POLAR;
578 efx->periodic.length = 1000;
579 efx->periodic.period = 200;
580 efx->periodic.magnitude = 0x4000;
581 efx->periodic.fade_length = 1000;
582 efx->periodic.fade_level = 0;
584 haptic_rumble = SDL_HapticNewEffect( haptic, efx );
585 if (haptic_rumble < 0) {
586 WARN("Unable to upload haptic effect: %s.", SDL_GetError());
587 return -1;
589 #endif /* SDL_VERSION_ATLEAST(1,3,0) */
591 return 0;
596 * @brief Runs a rumble effect.
598 * @brief Current modifier being added.
600 static void spfx_hapticRumble( double mod )
602 #if SDL_VERSION_ATLEAST(1,3,0)
603 SDL_HapticEffect *efx;
604 double len, mag;
606 if (haptic_rumble >= 0) {
608 /* Not time to update yet. */
609 if ((haptic_lastUpdate > 0.) || shake_off || (mod > SHAKE_MAX/3.))
610 return;
612 /* Stop the effect if it was playing. */
613 SDL_HapticStopEffect( haptic, haptic_rumble );
615 /* Get length and magnitude. */
616 len = 1000. * shake_rad / SHAKE_DECAY;
617 mag = 32767. * (shake_rad / SHAKE_MAX);
619 /* Update the effect. */
620 efx = &haptic_rumbleEffect;
621 efx->periodic.magnitude = (uint32_t)mag;;
622 efx->periodic.length = (uint32_t)len;
623 efx->periodic.fade_length = MIN( efx->periodic.length, 1000 );
624 if (SDL_HapticUpdateEffect( haptic, haptic_rumble, &haptic_rumbleEffect ) < 0) {
625 WARN("Failed to update haptic effect: %s.", SDL_GetError());
626 return;
629 /* Run the new effect. */
630 SDL_HapticRunEffect( haptic, haptic_rumble, 1 );
632 /* Set timer again. */
633 haptic_lastUpdate = HAPTIC_UPDATE_INTERVAL;
635 #else /* SDL_VERSION_ATLEAST(1,3,0) */
636 (void) mod;
637 #endif /* SDL_VERSION_ATLEAST(1,3,0) */
642 * @brief Sets the cinematic mode.
644 * Should be run at the end of the render loop if needed.
646 void spfx_cinematic (void)
648 double hw, hh;
650 hw = SCREEN_W/2.;
651 hh = SCREEN_H/2.;
653 gl_renderRect( -hw, -hh, SCREEN_W, SCREEN_H*0.2, &cBlack );
654 gl_renderRect( -hw, 0.6*hh, SCREEN_W, SCREEN_H*0.2, &cBlack );
659 * @brief Renders the entire spfx layer.
661 * @param layer Layer to render.
663 void spfx_render( const int layer )
665 SPFX *spfx_stack;
666 int i, spfx_nstack;
667 SPFX_Base *effect;
668 int sx, sy;
669 double time;
672 /* get the appropriate layer */
673 switch (layer) {
674 case SPFX_LAYER_FRONT:
675 spfx_stack = spfx_stack_front;
676 spfx_nstack = spfx_nstack_front;
677 break;
679 case SPFX_LAYER_BACK:
680 spfx_stack = spfx_stack_back;
681 spfx_nstack = spfx_nstack_back;
682 break;
684 default:
685 WARN("Rendering invalid SPFX layer.");
686 return;
689 /* Now render the layer */
690 for (i=spfx_nstack-1; i>=0; i--) {
691 effect = &spfx_effects[ spfx_stack[i].effect ];
693 /* Simplifies */
694 sx = (int)effect->gfx->sx;
695 sy = (int)effect->gfx->sy;
697 if (!paused) { /* don't calculate frame if paused */
698 time = fmod(spfx_stack[i].timer,effect->anim) / effect->anim;
699 spfx_stack[i].lastframe = sx * sy * MIN(time, 1.);
702 /* Renders */
703 gl_blitSprite( effect->gfx,
704 VX(spfx_stack[i].pos), VY(spfx_stack[i].pos),
705 spfx_stack[i].lastframe % sx,
706 spfx_stack[i].lastframe / sx,
707 NULL );