small structural code change for global animations preparation
[rocksndiamonds.git] / src / libgame / toons.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // toons.c
10 // ============================================================================
11
12 #include "toons.h"
13 #include "misc.h"
14
15
16 static struct ToonScreenInfo screen_info;
17
18
19 /* ========================================================================= */
20 /* generic animation frame calculation                                       */
21 /* ========================================================================= */
22
23 int getAnimationFrame(int num_frames, int delay, int mode, int start_frame,
24                       int sync_frame)
25 {
26   int frame = 0;
27
28   sync_frame += start_frame * delay;
29
30   if (mode & ANIM_LOOP)                 /* looping animation */
31   {
32     frame = (sync_frame % (delay * num_frames)) / delay;
33   }
34   else if (mode & ANIM_LINEAR)          /* linear (non-looping) animation */
35   {
36     frame = sync_frame / delay;
37
38     if (frame > num_frames - 1)
39       frame = num_frames - 1;
40   }
41   else if (mode & ANIM_PINGPONG)        /* oscillate (border frames once) */
42   {
43     int max_anim_frames = (num_frames > 1 ? 2 * num_frames - 2 : 1);
44
45     frame = (sync_frame % (delay * max_anim_frames)) / delay;
46     frame = (frame < num_frames ? frame : max_anim_frames - frame);
47   }
48   else if (mode & ANIM_PINGPONG2)       /* oscillate (border frames twice) */
49   {
50     int max_anim_frames = 2 * num_frames;
51
52     frame = (sync_frame % (delay * max_anim_frames)) / delay;
53     frame = (frame < num_frames ? frame : max_anim_frames - frame - 1);
54   }
55   else if (mode & ANIM_RANDOM)          /* play frames in random order */
56   {
57     /* note: expect different frames for the same delay cycle! */
58
59     if (gfx.anim_random_frame < 0)
60       frame = GetSimpleRandom(num_frames);
61     else
62       frame = gfx.anim_random_frame % num_frames;
63   }
64   else if (mode & (ANIM_CE_VALUE | ANIM_CE_SCORE | ANIM_CE_DELAY))
65   {
66     frame = sync_frame % num_frames;
67   }
68
69   if (mode & ANIM_REVERSE)              /* use reverse animation direction */
70     frame = num_frames - frame - 1;
71
72   return frame;
73 }
74
75
76 /* ========================================================================= */
77 /* toon animation functions                                                  */
78 /* ========================================================================= */
79
80 static int get_toon_direction(char *direction_string_raw)
81 {
82   char *direction_string = getStringToLower(direction_string_raw);
83   int direction = (strEqual(direction_string, "left")  ? MV_LEFT :
84                    strEqual(direction_string, "right") ? MV_RIGHT :
85                    strEqual(direction_string, "up")    ? MV_UP :
86                    strEqual(direction_string, "down")  ? MV_DOWN :
87                    MV_NONE);
88
89   free(direction_string);
90
91   return direction;
92 }
93
94 void InitToonScreen(Bitmap *save_buffer,
95                     void (*update_function)(void),
96                     void (*prepare_backbuffer_function)(void),
97                     boolean (*redraw_needed_function)(void),
98                     struct ToonInfo *toons, int num_toons,
99                     int startx, int starty,
100                     int width, int height,
101                     int frame_delay_value)
102 {
103   screen_info.save_buffer = save_buffer;
104   screen_info.update_function = update_function;
105   screen_info.prepare_backbuffer_function = prepare_backbuffer_function;
106   screen_info.redraw_needed_function = redraw_needed_function;
107   screen_info.toons = toons;
108   screen_info.num_toons = num_toons;
109   screen_info.startx = startx;
110   screen_info.starty = starty;
111   screen_info.width = width;
112   screen_info.height = height;
113   screen_info.frame_delay_value = frame_delay_value;
114 }
115
116 void DrawAnim(Bitmap *toon_bitmap, int src_x, int src_y, int width, int height,
117               int dest_x, int dest_y, int pad_x, int pad_y)
118 {
119   int pad_dest_x = dest_x - pad_x;
120   int pad_dest_y = dest_y - pad_y;
121   int pad_width  = width  + 2 * pad_x;
122   int pad_height = height + 2 * pad_y;
123   int buffer_x = 0;
124   int buffer_y = 0;
125
126   /* correct values to avoid off-screen blitting (start position) */
127   if (pad_dest_x < screen_info.startx)
128   {
129     pad_width -= (screen_info.startx - pad_dest_x);
130     pad_dest_x = screen_info.startx;
131   }
132   if (pad_dest_y < screen_info.starty)
133   {
134     pad_height -= (screen_info.starty - pad_dest_y);
135     pad_dest_y = screen_info.starty;
136   }
137
138   /* correct values to avoid off-screen blitting (blit size) */
139   if (pad_width > screen_info.width)
140     pad_width = screen_info.width;
141   if (pad_height > screen_info.height)
142     pad_height = screen_info.height;
143
144   /* special method to avoid flickering interference with BackToFront() */
145   BlitBitmap(backbuffer, screen_info.save_buffer, pad_dest_x, pad_dest_y,
146              pad_width, pad_height, buffer_x, buffer_y);
147   BlitBitmapMasked(toon_bitmap, backbuffer, src_x, src_y, width, height,
148                    dest_x, dest_y);
149   BlitBitmap(backbuffer, window, pad_dest_x, pad_dest_y, pad_width, pad_height,
150              pad_dest_x, pad_dest_y);
151
152   screen_info.update_function();
153
154   BlitBitmap(screen_info.save_buffer, backbuffer, buffer_x, buffer_y,
155              pad_width, pad_height, pad_dest_x, pad_dest_y);
156
157   /* prevent immediate redraw of restored toon area in backbuffer */
158   redraw_mask = REDRAW_NONE;
159 }
160
161 boolean AnimateToon(int toon_nr, boolean restart)
162 {
163   static unsigned int animation_frame_counter = 0;
164   static int pos_x = 0, pos_y = 0;
165   static int delta_x = 0, delta_y = 0;
166   static int frame = 0;
167   static boolean horiz_move, vert_move;
168   static unsigned int anim_delay = 0;
169   static unsigned int anim_delay_value = 0;
170   static int width,height;
171   static int pad_x,pad_y;
172   static int cut_x,cut_y;
173   static int src_x, src_y;
174   static int dest_x, dest_y;
175   struct ToonInfo *anim = &screen_info.toons[toon_nr];
176   Bitmap *anim_bitmap = screen_info.toons[toon_nr].bitmap;
177   int direction = get_toon_direction(anim->direction);
178
179   if (restart)
180   {
181     horiz_move = (direction & (MV_LEFT | MV_RIGHT));
182     vert_move = (direction & (MV_UP | MV_DOWN));
183     anim_delay_value = anim->step_delay * screen_info.frame_delay_value;
184
185     frame = getAnimationFrame(anim->anim_frames, anim->anim_delay,
186                               anim->anim_mode, anim->anim_start_frame,
187                               animation_frame_counter++);
188
189     if (horiz_move)
190     {
191       int pos_bottom = screen_info.height - anim->height;
192
193       if (strEqual(anim->position, "top"))
194         pos_y = 0;
195       else if (strEqual(anim->position, "bottom"))
196         pos_y = pos_bottom;
197       else if (strEqual(anim->position, "upper"))
198         pos_y = GetSimpleRandom(pos_bottom / 2);
199       else if (strEqual(anim->position, "lower"))
200         pos_y = pos_bottom / 2 + GetSimpleRandom(pos_bottom / 2);
201       else
202         pos_y = GetSimpleRandom(pos_bottom);
203
204       if (direction == MV_RIGHT)
205       {
206         delta_x = anim->step_offset;
207         pos_x = -anim->width + delta_x;
208       }
209       else
210       {
211         delta_x = -anim->step_offset;
212         pos_x = screen_info.width + delta_x;
213       }
214
215       delta_y = 0;
216     }
217     else
218     {
219       int pos_right = screen_info.width - anim->width;
220
221       if (strEqual(anim->position, "left"))
222         pos_x = 0;
223       else if (strEqual(anim->position, "right"))
224         pos_x = pos_right;
225       else
226         pos_x = GetSimpleRandom(pos_right);
227
228       if (direction == MV_DOWN)
229       {
230         delta_y = anim->step_offset;
231         pos_y = -anim->height + delta_y;
232       }
233       else
234       {
235         delta_y = -anim->step_offset;
236         pos_y = screen_info.height + delta_y;
237       }
238
239       delta_x = 0;
240     }
241   }
242
243   if (pos_x <= -anim->width        - anim->step_offset ||
244       pos_x >=  screen_info.width  + anim->step_offset ||
245       pos_y <= -anim->height       - anim->step_offset ||
246       pos_y >=  screen_info.height + anim->step_offset)
247     return TRUE;
248
249   if (!DelayReached(&anim_delay, anim_delay_value))
250   {
251     if (screen_info.redraw_needed_function() && !restart)
252       DrawAnim(anim_bitmap,
253                src_x + cut_x, src_y + cut_y,
254                width, height,
255                screen_info.startx + dest_x,
256                screen_info.starty + dest_y,
257                pad_x, pad_y);
258
259     return FALSE;
260   }
261
262   if (pos_x < -anim->width)
263     pos_x = -anim->width;
264   else if (pos_x > screen_info.width)
265     pos_x = screen_info.width;
266   if (pos_y < -anim->height)
267     pos_y = -anim->height;
268   else if (pos_y > screen_info.height)
269     pos_y = screen_info.height;
270
271   pad_x = (horiz_move ? anim->step_offset : 0);
272   pad_y = (vert_move  ? anim->step_offset : 0);
273   src_x = anim->src_x + frame * anim->width;
274   src_y = anim->src_y;
275   dest_x = pos_x;
276   dest_y = pos_y;
277   cut_x = cut_y = 0;
278   width  = anim->width;
279   height = anim->height;
280
281   if (pos_x < 0)
282   {
283     dest_x = 0;
284     width += pos_x;
285     cut_x = -pos_x;
286   }
287   else if (pos_x > screen_info.width - anim->width)
288     width -= (pos_x - (screen_info.width - anim->width));
289
290   if (pos_y < 0)
291   {
292     dest_y = 0;
293     height += pos_y;
294     cut_y = -pos_y;
295   }
296   else if (pos_y > screen_info.height - anim->height)
297     height -= (pos_y - (screen_info.height - anim->height));
298
299   DrawAnim(anim_bitmap,
300            src_x + cut_x, src_y + cut_y,
301            width, height,
302            screen_info.startx + dest_x,
303            screen_info.starty + dest_y,
304            pad_x, pad_y);
305
306   pos_x += delta_x;
307   pos_y += delta_y;
308
309   frame = getAnimationFrame(anim->anim_frames, anim->anim_delay,
310                             anim->anim_mode, anim->anim_start_frame,
311                             animation_frame_counter++);
312
313   return FALSE;
314 }
315
316 void HandleAnimation(int mode)
317 {
318   static unsigned int animstart_delay = -1;
319   static unsigned int animstart_delay_value = 0;
320   static boolean anim_running = FALSE;
321   static boolean anim_restart = TRUE;
322   static boolean reset_delay = TRUE;
323   static int toon_nr = 0;
324
325   if (!setup.toons || screen_info.num_toons == 0)
326     return;
327
328   /* this may happen after reloading graphics and redefining "num_toons" */
329   if (toon_nr >= screen_info.num_toons)
330     anim_restart = TRUE;
331
332   switch(mode)
333   {
334     case ANIM_START:
335       screen_info.prepare_backbuffer_function();
336
337       anim_running = TRUE;
338       anim_restart = TRUE;
339       reset_delay = TRUE;
340
341       return;
342
343     case ANIM_CONTINUE:
344       if (!anim_running)
345         return;
346
347       break;
348
349     case ANIM_STOP:
350       if (anim_running)
351       {
352         redraw_mask |= REDRAW_FIELD;
353
354         screen_info.update_function();
355
356         anim_running = FALSE;
357       }
358
359       return;
360
361     default:
362       break;
363   }
364
365   if (reset_delay)
366   {
367     animstart_delay = Counter();
368     animstart_delay_value = GetSimpleRandom(3000);
369     reset_delay = FALSE;
370   }
371
372   if (anim_restart)
373   {
374     if (!DelayReached(&animstart_delay, animstart_delay_value))
375       return;
376
377     toon_nr = GetSimpleRandom(screen_info.num_toons);
378   }
379
380   anim_restart = reset_delay = AnimateToon(toon_nr, anim_restart);
381 }