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