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