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