2 * See Licensing and Copyright notice in naev.h
8 * @brief Handles the special effects.
19 #if SDL_VERSION_ATLEAST(1,3,0)
20 #include "SDL_haptic.h"
21 #endif /* SDL_VERSION_ATLEAST(1,3,0) */
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) */
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 */
84 static SPFX_Base
*spfx_effects
= NULL
; /**< Total special effects. */
85 static int spfx_neffects
= 0; /**< Total number of special effects. */
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 */
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. */
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
);
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
)
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
;
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. */
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");
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
) {
181 if (effect
->gfx
!= NULL
) {
182 gl_freeTexture(effect
->gfx
);
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
)
197 for (i
=0; i
<spfx_neffects
; i
++)
198 if (strcmp(spfx_effects
[i
].name
, name
)==0)
205 * @brief Loads the spfx stack.
207 * @return 0 on success.
209 * @todo Make spfx not hardcoded.
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
"'");
230 /* Check to see if is populated. */
231 node
= node
->xmlChildrenNode
; /* first system node */
233 ERR("Malformed '"SPFX_DATA
"' file: does not contain elements");
237 /* First pass, loads up ammunition. */
240 if (xml_isNode(node
,SPFX_XML_TAG
)) {
243 if (spfx_neffects
> mem
) {
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
);
259 * Now initialize force feedback.
268 * @brief Frees the spfx stack.
270 void spfx_free (void)
274 /* Clean up the debris. */
277 /* get rid of all the particles and free the stacks */
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
] );
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
,
313 if ((effect
< 0) || (effect
> spfx_neffects
)) {
314 WARN("Trying to add spfx with invalid effect!");
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
;
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
];
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
;
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
];
344 WARN("Invalid SPFX layer.");
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
;
356 cur_spfx
->timer
= ttl
+ RNGF()*anim
;
358 cur_spfx
->timer
= ttl
;
363 * @brief Clears all the currently running effects.
365 void spfx_clear (void)
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
);
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
)
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
)
420 for (i
=0; i
<*nlayer
; i
++) {
421 layer
[i
].timer
-= dt
; /* less time to live */
424 if (layer
[i
].timer
< 0.) {
425 spfx_destroy( layer
, nlayer
, i
);
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
;
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) */
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
;
495 /* set the new viewport */
496 glMatrixMode(GL_PROJECTION
);
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.
513 /* set the new viewport */
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. */
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. */
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
) {
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. */
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());
589 #endif /* SDL_VERSION_ATLEAST(1,3,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
;
606 if (haptic_rumble
>= 0) {
608 /* Not time to update yet. */
609 if ((haptic_lastUpdate
> 0.) || shake_off
|| (mod
> SHAKE_MAX
/3.))
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());
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) */
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)
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
)
672 /* get the appropriate layer */
674 case SPFX_LAYER_FRONT
:
675 spfx_stack
= spfx_stack_front
;
676 spfx_nstack
= spfx_nstack_front
;
679 case SPFX_LAYER_BACK
:
680 spfx_stack
= spfx_stack_back
;
681 spfx_nstack
= spfx_nstack_back
;
685 WARN("Rendering invalid SPFX layer.");
689 /* Now render the layer */
690 for (i
=spfx_nstack
-1; i
>=0; i
--) {
691 effect
= &spfx_effects
[ spfx_stack
[i
].effect
];
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.);
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
,