1 /*------------------------------------------------------------------------.
2 | Copyright 2000  Alexandre Duret-Lutz <duret_g@epita.fr>                 |
3 |                                                                         |
4 | This file is part of Heroes.                                            |
5 |                                                                         |
6 | Heroes is free software; you can redistribute it and/or modify it under |
7 | the terms of the GNU General Public License version 2 as published by   |
8 | the Free Software Foundation.                                           |
9 |                                                                         |
10 | Heroes is distributed in the hope that it will be useful, but WITHOUT   |
11 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or   |
12 | FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License   |
13 | for more details.                                                       |
14 |                                                                         |
15 | You should have received a copy of the GNU General Public License along |
16 | with this program; if not, write to the Free Software Foundation, Inc., |
17 | 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA                   |
18 `------------------------------------------------------------------------*/
19 
20 #include "system.h"
21 #include "explosions.h"
22 #include "sprzcol.h"
23 #include "const.h"
24 #include "timer.h"
25 #include "debugmsg.h"
26 
27 a_sprite *explosions[NBR_EXPLOSION_KINDS][NBR_EXPLOSION_FRAMES];
28 
29 void
init_explosions(void)30 init_explosions (void)
31 {
32   int i;
33   for (i = 0; i < 6; ++i)
34     explosions[0][i] = compile_sprzcol (IMGPOS (vehicles_img, 65, (5 - i)*33),
35 					0, 32, 32, vehicles_img.width, xbuf);
36   for (; i < NBR_EXPLOSION_FRAMES; ++i)
37     explosions[0][i] = compile_sprzcol (IMGPOS (vehicles_img, 32,
38 						(NBR_EXPLOSION_FRAMES - i - 1)
39 						* 33),
40 					0, 32, 32, vehicles_img.width, xbuf);
41   for (i = 0; i < 6; ++i)
42     explosions[1][i] = compile_sprzcol (IMGPOS (vehicles_img, 131, (5 - i)*33),
43 					0, 32, 32, vehicles_img.width, xbuf);
44   for (; i < NBR_EXPLOSION_FRAMES; ++i)
45     explosions[1][i] = compile_sprzcol (IMGPOS (vehicles_img, 98,
46 						(NBR_EXPLOSION_FRAMES - i - 1)
47 						* 33),
48 					0, 32, 32, vehicles_img.width, xbuf);
49 }
50 
51 void
uninit_explosions(void)52 uninit_explosions (void)
53 {
54   int i, j;
55   for (i = 0; i < NBR_EXPLOSION_KINDS; ++i)
56     for (j = 0; j < NBR_EXPLOSION_FRAMES; ++j)
57       FREE_SPRITE0 (explosions[i][j]);
58 }
59 
60 an_explosion *square_explo_state;
61 a_u8 *square_explo_type;
62 
63 typedef struct an_explosion_info an_explosion_info;
64 struct an_explosion_info {
65   long orig_time;		/* Date (in 70th of secs) when the explosion
66 				   was triggered.  */
67   a_square_index idx;
68   bool neighb_done;		/* True if the explosion has propagated
69 				   to neighbors.  */
70 };
71 
72 static an_explosion_info *explo_list;
73 static unsigned int explo_list_max;
74 static unsigned int explo_list_first_unused;
75 
76 static a_timer explo_timer;
77 static long explo_time;		/* Updated from explo_timer on each call
78 				   to update_explosion.  */
79 
80 void
allocate_explosions(void)81 allocate_explosions (void)
82 {
83   XSALLOC_ARRAY (square_explo_state, lvl.square_count, EXPLOSION_UNTRIGGERED);
84   XMALLOC_ARRAY (square_explo_type, lvl.square_count);
85   explo_list_max = 64;
86   explo_list_first_unused = 0;
87   XMALLOC_ARRAY (explo_list, explo_list_max);
88   explo_timer = new_htimer (T_GLOBAL, HZ (70));
89   explo_time = 0;
90 }
91 
92 void
release_explosions(void)93 release_explosions (void)
94 {
95   free_htimer (explo_timer);
96   XFREE0 (square_explo_state);
97   XFREE0 (square_explo_type);
98   XFREE0 (explo_list);
99 }
100 
101 static void trigger_explosion_at_time (a_square_index idx, long orig_time);
102 
103 
104 /* Usually, this will return values between EXPLOSION_TRIGGERED and 0.
105    I.e., positive values that fit in the 'an_explosion' type.  But
106    this function can also be used on very old explosions for which it
107    returns a negative value.  Hence the 'int' return type.  */
108 static int
compute_explosion_state(long orig_time)109 compute_explosion_state (long orig_time)
110 {
111   /* Elapsed time in "frames".  */
112   int ef = (explo_time - orig_time) / EXPLOSION_SLICES_PER_FRAMES;
113   assert (ef >= 0);
114   return EXPLOSION_TRIGGERED - ef;
115 }
116 
117 /* Trigger neighbor squares if needed.
118 
119    NOTE: This function should be called only after EXPLOSION_DELAY has
120    elapsed.  See the call in trigger_explosion_at_time() for example.
121    */
122 static void
propagate_to_neighbors_maybe(a_square_index idx,long orig_time)123 propagate_to_neighbors_maybe (a_square_index idx, long orig_time)
124 {
125   /* Propagation to neighbors occurs EXPLOSION_DELAY 70th of seconds
126      after the explosion has been triggered.  This is a precise
127      instant in the life of an explosion, but since we may miss this
128      instant (because of a low frame rate) we also need to be able to
129      propagate explosions retro-actively at a subsequent time,
130      counterbalancing the lag to have neighbors explosions behaving as
131      if they were activated at the right time.
132 
133      This is simply achieved by calling trigger_explosion_at_time()
134      with an ORIG_TIME based on the ORIG_TIME of the current explosion
135      (i.e. no computations are based on the current time).  See the
136      computation of NEIGH_ORIG_TIME.
137 
138      One may wonder why we care so much about counterbalancing the
139      lag.  After all, the user won't notice if a neighboring square
140      explodes one or two frames later.  Older versions of Heroes
141      behaved this way.  There are two problems with this scheme:
142 
143      - It is non deterministic.  The propagation of the explosions
144      depends on the frame rate achieved by the game.  From the same
145      original state, different games with different frame rate with
146      have different evolutions.  This will be a problem the day (the
147      century?) Heroes become a networked game.
148 
149      - The frame rate can fall very very low.  Then funny effects can
150      occur if we don't counterbalance.  For instance if the frame rate
151      is so low that an explosion has the opportunity to activate its
152      neighbor only less than EXPLOSION_DELAY/70 seconds before it
153      vanish, then it will be in a triggerable state when this neighbor
154      start to propagate to its own neighbors.  So our square will be
155      activated by the neighbor it had activated and vice-versa: these
156      two squares will explode alternatively and infinitely (or until
157      the frame rate change).  */
158   long neigh_orig_time =
159     orig_time + ((EXPLOSION_TRIGGERED - EXPLOSION_TRIGGER_NEIGHBORS)
160 		 * EXPLOSION_SLICES_PER_FRAMES);
161 
162   /* Iterate on neighbor squares.  */
163   a_dir d;
164   for (d = 0; d < 4; ++d) {
165     a_square_index ngb = lvl.square_move[d][idx];
166     if (ngb != INVALID_INDEX && EXPLOSION_SQUARE_TRIGGERABLE_P (ngb))
167       trigger_explosion_at_time (ngb, neigh_orig_time);
168   }
169 }
170 
171 static void
trigger_explosion_at_time(a_square_index idx,long orig_time)172 trigger_explosion_at_time (a_square_index idx, long orig_time)
173 {
174   int state;
175   bool propagated;
176 
177   /* We don't trigger explosions in the future.  */
178   assert (explo_time >= orig_time);
179 
180   state = compute_explosion_state (orig_time);
181 
182   /* Maybe we need to propagate this explosion.  Note that STATE may
183      happen to be negative, meaning the explosion vanished; we still
184      try propagate.  */
185   if (state <= EXPLOSION_TRIGGER_NEIGHBORS) {
186     propagate_to_neighbors_maybe (idx, orig_time);
187     propagated = true;
188   } else {
189     propagated = false;
190   }
191 
192   /* Insert the explosion into the list of active explosions
193      only if it's active...  */
194   if (state >= 0) {
195     if (explo_list_max <= explo_list_first_unused) {
196       explo_list_max += 64;
197       XREALLOC_ARRAY (explo_list, explo_list_max);
198     }
199 
200     explo_list[explo_list_first_unused].orig_time = orig_time;
201     explo_list[explo_list_first_unused].idx = idx;
202     explo_list[explo_list_first_unused].neighb_done = propagated;
203     ++explo_list_first_unused;
204 
205     /* Update level rendering information.  */
206     square_explo_state[idx] = state;
207     square_explo_type[idx] = rand () % NBR_EXPLOSION_KINDS;
208   }
209 }
210 
211 /* Trigger an explosion but set the ORIG_TIME so that the explosion
212    is at frame FRAME_START now.  */
213 void
trigger_explosion(a_square_index idx,an_explosion frame_start)214 trigger_explosion (a_square_index idx, an_explosion frame_start)
215 {
216   assert (frame_start <= EXPLOSION_TRIGGERED);
217 
218   trigger_explosion_at_time (idx, (explo_time
219 				   - ((EXPLOSION_TRIGGERED - frame_start)
220 				      * EXPLOSION_SLICES_PER_FRAMES)));
221 }
222 
223 void
trigger_possible_explosion(a_square_index idx)224 trigger_possible_explosion (a_square_index idx)
225 {
226   if (EXPLOSION_SQUARE_TRIGGERABLE_P (idx))
227     trigger_explosion (idx, EXPLOSION_TRIGGERED);
228 }
229 
230 void
update_explosions(void)231 update_explosions (void)
232 {
233   unsigned int i;
234   /*
235     Imagine that the array of explosions is as follow, where
236     A is an active explosion, and V a vanished explosion.
237        0A 1A 2V 3V 4V 5A 6A 7A 8A 9V 10V 11V...
238     Since the V's are vanished, we want to remove them from the
239     array.
240 
241     One idea is to shift the A's one by one as we process the
242     array, overwriting the V's.  So, for instance, after processing
243     the A at position 5, we would copy it at position 2. Similarly
244     the A at position 6 would be copied at position 3, etc.
245 
246     However we can do better.  Granted, the explosions are not sorted
247     over the ORIG_TIME: that why A's and V's can be mixed.  This is
248     unfortunate because if we add all the V at the beginning, and the
249     A's at the end and we could shift all the latter over the former in
250     one shot (of memmove).  However, they are usually _almost_ sorted.
251     Indeed: if we were only calling
252       trigger_explosion (index, EXPLOSION_TRIGGERED)
253     to append new explosions, they would be naturally kept in a
254     chronological order.  The trouble comes from calls like
255       trigger_explosion (index, EXPLOSION_IMMEDIATE)
256     that creates an *older* explosion in order to have it exploding
257     immediately (this is used when a player die, or with fire trails).
258     This, combined with how the explosions are propagated means
259     that the array is not sorted, but you can split this
260     array in slices in which explosions are sorted.  The good thing
261     is that these slices are easy to shift as explained above.
262     Therefore, instead of shifting activated explosions one by one,
263     we will shift them slice per slice.
264 
265     The two variables below are used for this purpose.  SHIFT_SRC
266     is the first explosion of the last sequence of activated explosion.
267     SHIFT_DEST is the for explosion of the last sequence of
268     vanished explosions.  When we are processing 8A in the
269     example above we have SHIFT_DEST=2 and SHIFT_SRC=5.  When
270     we encounter 9V, we can shift all the A starting at SHIFT_SRC
271     toward position SHIFT_SRC in a single shot.
272   */
273   int shift_src;
274   int shift_dest;
275 
276   explo_time = read_htimer (explo_timer);
277 
278   if (explo_list_first_unused)
279     dmsg (D_MISC, "explosions: %d/%d",
280 	  explo_list_first_unused, explo_list_max);
281 
282   shift_src = -1;
283   shift_dest = 0;
284   for (i = 0; i < explo_list_first_unused; ++i) {
285     a_square_index idx = explo_list[i].idx;
286     int state = compute_explosion_state (explo_list[i].orig_time);
287 
288     /* Propagate to neighbors.  */
289     if (! explo_list[i].neighb_done
290 	&& state <= EXPLOSION_TRIGGER_NEIGHBORS) {
291       propagate_to_neighbors_maybe (idx, explo_list[i].orig_time);
292       explo_list[i].neighb_done = true;
293     }
294 
295     if (state >= 0) {
296       /* Update rendering info.  */
297       square_explo_state[idx] = state;
298 
299       /* Mark position of for explosition in a sequence of
300 	 activated explosions.  */
301       if (shift_src < 0)
302 	shift_src = i;
303     } else {
304       square_explo_state[idx] = EXPLOSION_UNTRIGGERED;
305 
306 #define SHIFT_ACTIVATED_EXPLOSIONS				\
307       /* Shift activated explosions over vanished ones.  */	\
308       if (shift_src > shift_dest) {				\
309 	int activated_qty = i - shift_src;			\
310 	memmove (explo_list + shift_dest,			\
311 		 explo_list + shift_src,			\
312 		 activated_qty * sizeof (*explo_list));		\
313 	shift_dest += activated_qty;				\
314 	shift_src = -1;						\
315       } else {							\
316 	shift_dest = i;						\
317       }
318       SHIFT_ACTIVATED_EXPLOSIONS;
319     }
320   }
321 
322   SHIFT_ACTIVATED_EXPLOSIONS;
323   explo_list_first_unused = shift_dest;
324 }
325