1 /*
2  * Luola - 2D multiplayer cave-flying game
3  * Copyright (C) 2001-2005 Calle Laakkonen
4  *
5  * File        : level.c
6  * Description : Level handling and animation
7  * Author(s)   : Calle Laakkonen
8  *
9  * Luola is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Luola is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22  */
23 
24 #include <stdlib.h>
25 #include <stdio.h>
26 #include <math.h>
27 #include <string.h>
28 #include "console.h"
29 #include "player.h"
30 #include "fs.h"
31 #include "level.h"
32 #include "levelfile.h"
33 #include "particle.h"
34 #include "decor.h"
35 #include "animation.h"
36 #include "ship.h"   /* for bump_ship() */
37 
38 #define BASE_REGEN_SPEED 9 /* Delay between each regenerated pixel */
39 
40 /* Level effects */
41 typedef struct {
42     int x, y;
43     int brake;
44     unsigned char value;
45     char icicle;
46     LevelFXType type;
47 } LevelFX;
48 
49 /* Linked lists for level effects */
50 struct LevelEffects {
51     LevelFX *fx;
52     struct LevelEffects *next;
53     struct LevelEffects *prev;
54 } *level_effects;
55 
56 static struct LevelEffects *lev_lastfx;
57 
58 /* Stars */
59 typedef struct {
60     int x, y;
61 } Star;
62 
63 /* Internally used globals */
64 static Star lev_stars[15];
65 
66 /* Exported globals */
67 Uint32 burncolor[FIRE_FRAMES];
68 Uint32 lev_watercol;
69 SDL_Rect cam_rects[4];
70 SDL_Rect viewport_rects[4]; /* Use only x and y. w and h get overwritten by SDL_BlitSurface */
71 Level lev_level;
72 
73 /* Bullet hole bitmap */
74 #define HOLE_W 9
75 #define HOLE_H 9
76 static const Uint8 hole_bm[HOLE_H][HOLE_W] = {
77     {1, 1, 1, 1, 0, 1, 1, 1, 1},
78     {1, 1, 1, 0, 0, 0, 1, 1, 1},
79     {1, 0, 0, 0, 0, 0, 0, 0, 1},
80     {1, 0, 0, 0, 0, 0, 0, 0, 1},
81     {0, 0, 0, 0, 0, 0, 0, 0, 0},
82     {1, 0, 0, 0, 0, 0, 0, 0, 1},
83     {1, 0, 0, 0, 0, 0, 0, 0, 1},
84     {1, 1, 1, 0, 0, 0, 1, 1, 1},
85     {1, 1, 1, 1, 0, 1, 1, 1, 1}
86 };
87 
touch_wall(int x,int y)88 static int touch_wall (int x, int y)
89 {
90     int x1, y1;
91     int solid;
92     for (x1 = x - 3; x1 < x + 4; x1++) {
93         if (x1 >= lev_level.width)
94             return 0;
95         for (y1 = y - 3; y1 < y + 4; y1++) {
96             if (y1 >= lev_level.height)
97                 return 0;
98             solid = lev_level.solid[x1][y1];
99             if (solid == TER_GROUND || solid == TER_INDESTRUCT
100                 || solid == TER_BASE || solid == TER_COMBUSTABLE
101                 || solid == TER_EXPLOSIVE || solid == TER_WALKWAY)
102                 return 1;
103         }
104     }
105 
106     return 0;
107 }
108 
draw_stars(SDL_Rect * cam,SDL_Rect * targ)109 static void draw_stars (SDL_Rect * cam, SDL_Rect * targ)
110 {
111     int r, x, y;
112     Uint8 *col;
113     for (r = 0; r < sizeof(lev_stars)/sizeof(Star); r++) {
114         x = cam->x + lev_stars[r].x;
115         y = cam->y + lev_stars[r].y;
116         col =
117             (Uint8 *) lev_level.terrain->pixels +
118             y * lev_level.terrain->pitch +
119             x * lev_level.terrain->format->BytesPerPixel;
120         if (x < lev_level.width && y < lev_level.height)
121           if (lev_level.solid[x][y] == TER_FREE && col[0] < 5 && col[1] < 5 && col[2] < 5)
122             putpixel (screen, targ->x + lev_stars[r].x,
123                       targ->y + lev_stars[r].y, col_white);
124     }
125 }
126 
127 /* Draw the level for all players */
draw_level(void)128 static inline void draw_level (void)
129 {
130     int p;
131     for (p = 0; p < 4; p++) {
132         if (players[p].state==ALIVE || players[p].state==DEAD) {
133             SDL_BlitSurface (lev_level.terrain, &cam_rects[p], screen,
134                              &viewport_rects[p]);
135             if(level_settings.stars)
136                 draw_stars (&cam_rects[p], &viewport_rects[p]);
137         }
138     }
139 }
140 
141 /* Initialize level subsystem */
init_level(void)142 void init_level (void)
143 {
144     int r,red, green, blue;
145     lev_watercol = map_rgba(0x64,0x64,0xff,0xff);
146     level_effects = NULL;
147     lev_lastfx = NULL;
148     for (r = 0; r < FIRE_FRAMES; r++) {
149         if (r == 0)
150             red = 0;
151         else
152             red = 128 + (255.0 / FIRE_FRAMES) * r;
153         green = (255.0 / FIRE_FRAMES) * r;
154         blue = 0;
155         if (red > 255)
156             red = 255;
157         burncolor[r] = map_rgba(red,green,blue,0xff);
158     }
159 }
160 
161 /* Calculate star positions. This must be done when player screen */
162 /* geometry changes. */
reinit_stars(void)163 void reinit_stars(void) {
164     SDL_Rect size = get_viewport_size();
165     int r;
166     for (r = 0; r < sizeof(lev_stars)/sizeof(Star); r++) {
167         lev_stars[r].x = rand () % size.w;
168         lev_stars[r].y = rand () % size.h;
169     }
170 }
171 
172 /* Find the first occurance of a color in the level palette */
find_color(Uint8 color,Uint8 * palette,SDL_Palette * source,SDL_Color ** target)173 static void find_color(Uint8 color,Uint8 *palette,SDL_Palette *source,SDL_Color **target) {
174     int c=0;
175     while (c < source->ncolors) {
176         if (palette[c]==color) {
177             *target=source->colors+c;
178             break;
179         }
180         c++;
181     }
182 }
183 
184 /* Sort base regeneration array */
sort_regen(const void * ptr1,const void * ptr2)185 static int sort_regen(const void *ptr1,const void *ptr2) {
186     const RegenCoord *c1=ptr1,*c2=ptr2;
187     if(c1->y<c2->y) return 1;
188     else if(c1->y==c2->y) return 0;
189     else return -1;
190 }
191 
192 /* Load level and prepare for new game */
load_level(struct LevelFile * lev)193 void load_level (struct LevelFile *lev) {
194     SDL_Surface *collmap;
195     int x, y, p, r;
196     int freepix, otherpix;
197     SDL_Color *tmpcol, defaultwater;
198     Uint8 *bits;
199     int basebufsize;
200 
201     defaultwater.r = 0;
202     defaultwater.g = 0;
203     defaultwater.b = 255;
204 
205     /* Load level artwork */
206     lev_level.terrain = load_level_art (lev);
207     lev_level.width = lev_level.terrain->w;
208     lev_level.height = lev_level.terrain->h;
209     /* Load level collisionmap */
210     collmap = load_level_coll (lev);
211     if (!collmap) {
212         printf ("An error occured while loading collisionmap\n");
213         exit (1);
214     }
215     if (collmap->w != lev_level.width || collmap->h != lev_level.height) {
216         printf
217             ("Error Collision map image \"%s\" has incorrect size (%dx%d), should be %dx%d!\n",
218              lev->settings->mainblock.collmap, collmap->w, collmap->h,
219              lev_level.width, lev_level.height);
220         exit (1);
221     }
222     /* Get palette entries */
223     /* Get water colour */
224     tmpcol = &defaultwater;
225     find_color(TER_WATER,lev->settings->palette.entries,
226             collmap->format->palette,&tmpcol);
227     lev_watercol = map_rgba(tmpcol->r, tmpcol->g, tmpcol->b,0xff);
228     /* Calculate underwater clay colour */
229     col_clay_uw = map_rgba((tmpcol->r+255)/2, (tmpcol->r+200)/2, (tmpcol->r+128)/2, 0xff);
230     /* Get snow colour */
231     tmpcol = NULL;
232     find_color(TER_SNOW,lev->settings->palette.entries,
233             collmap->format->palette,&tmpcol);
234     if (tmpcol) {
235         col_snow = map_rgba(tmpcol->r, tmpcol->g, tmpcol->b,0xff);
236     }
237     /* Prepare base regeneration array */
238     if(game_settings.base_regen) {
239         basebufsize=512;   /* Some arbitary size */
240         lev_level.base = malloc(sizeof(RegenCoord)*basebufsize);
241     } else {
242         basebufsize=0;
243         lev_level.base = NULL;
244     }
245     /* Load data map */
246     lev_level.base_area = 0;
247     lev_level.regen_area = 0;
248     lev_level.solid = malloc (sizeof (char *) * lev_level.width);
249     freepix = 0;
250     otherpix = 0;
251     for (x = 0; x < lev_level.width; x++) {
252         bits = ((Uint8 *) collmap->pixels)+x;
253         lev_level.solid[x] = malloc (lev_level.height);
254         for (y = 0; y < lev_level.height; y++,bits+=collmap->pitch) {
255             lev_level.solid[x][y] = lev->settings->palette.entries[*bits];
256             if (lev_level.solid[x][y] == TER_FREE
257                 || lev_level.solid[x][y] == TER_WATER)
258                 freepix++;
259             else {
260                 otherpix++;
261                 if (lev_level.solid[x][y] == TER_BASE) {
262                     if(lev_level.base) {
263                         Uint8 r,g,b;
264                         lev_level.base[lev_level.base_area].x=x;
265                         lev_level.base[lev_level.base_area].y=y;
266                         SDL_GetRGB(getpixel(lev_level.terrain,x,y),
267                                 screen->format,
268                                 &r,
269                                 &g,
270                                 &b);
271                         lev_level.base[lev_level.base_area].c=
272                             map_rgba(r, g, b, 0xff);
273 
274                         if(lev_level.base_area==basebufsize-1) {
275                             basebufsize+=512;
276                             lev_level.base=realloc(lev_level.base,
277                                     sizeof(RegenCoord)*basebufsize);
278                         }
279                     }
280                     lev_level.base_area++;
281                 }
282             }
283         }
284     }
285     SDL_FreeSurface (collmap);
286     /* Finalize the base regeneration buffer */
287     if(lev_level.base) {
288         lev_level.regen_timer = 0;
289         lev_level.regen_area = lev_level.base_area;
290         if(lev_level.base_area<basebufsize) {
291             basebufsize=lev_level.base_area;
292             lev_level.base=realloc(lev_level.base,
293                     sizeof(RegenCoord)*basebufsize);
294         }
295         qsort(lev_level.base,lev_level.base_area,sizeof(RegenCoord),sort_regen);
296     }
297     /* Position players */
298     if (game_settings.playmode == OutsideShip
299         || game_settings.playmode == OutsideShip1)
300         y = 2;
301     else
302         y = 1;
303     for (p = 0; p < 4; p++)
304         if (players[p].state != INACTIVE) {
305             for (x = 0; x < y; x++) {
306                 r = 0;
307                 do {
308                     lev_level.player_def_x[x][p] = rand () % lev_level.width;
309                     lev_level.player_def_y[x][p] = rand () % lev_level.height;
310                     r++;
311                     if (r > 100000) {
312                         fprintf(stderr,
313                                 "Warning: while searching for player %d startup position, loop counter reached 100000!\n",
314                              p);
315                         fprintf(stderr,
316                             "Number of free space (including water) pixels: %d, number of other pixels: %d\n",
317                              freepix, otherpix);
318                         fprintf (stderr,
319                                 "%0.3f%% of the surface area is available\n",
320                                 ((double) freepix / (double) otherpix) * 100);
321                         exit (0);
322                     }
323                 } while (lev_level.
324                          solid[lev_level.player_def_x[x][p]][lev_level.
325                                                              player_def_y[x]
326                                                              [p]] !=
327                          TER_FREE);
328             }
329         }
330 }
331 
332 /* Release level from memory */
unload_level(void)333 void unload_level (void)
334 {
335     int x;
336     struct LevelEffects *next;
337     SDL_FreeSurface (lev_level.terrain);
338     for (x = 0; x < lev_level.width; x++)
339         free (lev_level.solid[x]);
340     free (lev_level.solid);
341     if(lev_level.base)
342         free(lev_level.base);
343     while (level_effects) {
344         next = level_effects->next;
345         free (level_effects->fx);
346         free (level_effects);
347         level_effects = next;
348     }
349     lev_lastfx = NULL;
350 }
351 
352 /* Pixel perfect collision detection. */
hit_solid_line(int startx,int starty,int endx,int endy,int * newx,int * newy)353 int hit_solid_line (int startx, int starty, int endx, int endy, int *newx,
354                      int *newy)
355 {
356     int dx, dy, ax, ay, sx, sy, x, y, d;
357     if (startx<0 || endx < 0 || startx >= lev_level.width || endx >= lev_level.width) {
358         *newx = endx<0?0:endx>=lev_level.width?lev_level.width-1:endx;
359         *newy = endy<0?0:endy>=lev_level.height?lev_level.height-1:endy;
360         return TER_INDESTRUCT;
361     }
362     if (startx<0 || endy < 0 || startx >= lev_level.width || endy >= lev_level.height) {
363         *newx = endx<0?0:endx>=lev_level.width?lev_level.width-1:endx;
364         *newy = endy<0?0:endy>=lev_level.height?lev_level.height-1:endy;
365         return TER_INDESTRUCT;
366     }
367     dx = endx - startx;
368     dy = endy - starty;
369     ax = abs (dx) << 1;
370     ay = abs (dy) << 1;
371     sx = (dx >= 0) ? 1 : -1;
372     sy = (dy >= 0) ? 1 : -1;
373     x = startx;
374     y = starty;
375     if (ax > ay) {
376         d = ay - (ax >> 1);
377         while (x != endx) {
378             if (is_solid (x, y)) {
379                 *newx = x;
380                 *newy = y;
381                 return lev_level.solid[x][y];
382             }
383             if (d > 0 || (d == 0 && sx == 1)) {
384                 y += sy;
385                 d -= ax;
386             }
387             x += sx;
388             d += ay;
389         }
390     } else {
391         d = ax - (ay >> 1);
392         while (y != endy) {
393             if (is_solid (x, y)) {
394                 *newx = x;
395                 *newy = y;
396                 return lev_level.solid[x][y];
397             }
398             if (d > 0 || (d == 0 && sy == 1)) {
399                 x += sx;
400                 d -= ay;
401             }
402             y += sy;
403             d += ax;
404         }
405     }
406     *newx = endx;
407     *newy = endy;
408     return lev_level.solid[endx][endy];
409 }
410 
find_rainy(int x)411 int find_rainy (int x)
412 {
413     int y, loops = 0, r, c;
414     do {
415         y = 15 + rand () % (screen->w/4);
416         loops++;
417         if (loops > 100)
418             return -1;
419         c = 0;
420         for (r = y; r < y + 25; r++)
421             c += lev_level.solid[x][r] != TER_FREE;
422     } while (c > 17);
423     return y;
424 }
425 
start_burning(int x,int y)426 void start_burning (int x, int y)
427 {
428     struct LevelEffects *newentry;
429     LevelFX *fx;
430     if (x <= 0 || y <= 0 || x >= lev_level.width || y >= lev_level.height)
431         return;
432     if (is_water(x,y) || is_indestructable(x,y))
433         return;
434     newentry = malloc (sizeof (struct LevelEffects));
435     newentry->next = NULL;
436     newentry->prev = lev_lastfx;
437     fx = malloc (sizeof (LevelFX));
438     fx->x = x;
439     fx->y = y;
440     fx->type = Fire;
441     fx->value = FIRE_FRAMES;
442     if (lev_level.solid[x][y] == TER_COMBUSTABL2)
443         lev_level.solid[x][y] = TER_GROUND;
444     else
445         lev_level.solid[x][y] = TER_FREE;
446     newentry->fx = fx;
447     if (lev_lastfx == NULL)
448         level_effects = newentry;
449     else
450         lev_lastfx->next = newentry;
451     lev_lastfx = newentry;
452 }
453 
start_melting(int x,int y,unsigned int recurse)454 void start_melting (int x, int y, unsigned int recurse)
455 {
456     struct LevelEffects *newentry;
457     LevelFX *fx;
458     if (x <= 0 || y <= 0 || x >= lev_level.width || y >= lev_level.height
459         || recurse == 0)
460         return;
461     if (is_water(x,y) || is_indestructable(x,y))
462         return;
463     newentry = malloc (sizeof (struct LevelEffects));
464     newentry->next = NULL;
465     newentry->prev = lev_lastfx;
466     fx = malloc (sizeof (LevelFX));
467     fx->x = x;
468     fx->y = y;
469     fx->type = Melt;
470     fx->value = 4;
471     fx->brake = recurse;
472     if(game_settings.base_regen && lev_level.solid[x][y]==TER_BASE)
473         lev_level.base_area--;
474     lev_level.solid[x][y] = TER_FREE;
475     putpixel (lev_level.terrain, x, y, col_green);
476     newentry->fx = fx;
477     if (lev_lastfx == NULL)
478         level_effects = newentry;
479     else
480         lev_lastfx->next = newentry;
481     lev_lastfx = newentry;
482 }
483 
alter_level(int x,int y,int recurse,LevelFXType type)484 void alter_level (int x, int y, int recurse, LevelFXType type)
485 {
486     struct LevelEffects *newentry;
487     LevelFX *fx;
488     if (recurse == 0 || x<0 || y<0 || x>=lev_level.width || y>=lev_level.height)
489         return;
490     newentry = malloc (sizeof (struct LevelEffects));
491     newentry->next = NULL;
492     newentry->prev = lev_lastfx;
493     fx = malloc (sizeof (LevelFX));
494     fx->x = x;
495     fx->y = y;
496     fx->value = 3;
497     fx->type = type;
498     fx->brake = recurse;
499     fx->icicle = 0;
500     if (type == Explosive) {
501         lev_level.solid[x][y] = TER_EXPLOSIVE;
502     } else if (type == Ice) {
503         if (lev_level.solid[x][y] == TER_WATER)
504             lev_level.solid[x][y] = TER_ICE;
505         else if (lev_level.solid[x][y] == TER_FREE
506                  || lev_level.solid[x][y] == TER_TUNNEL)
507             lev_level.solid[x][y] = TER_SNOW;
508         if (rand () % 15 == 0) {
509             fx->icicle = 1;
510             fx->value = 6;
511         }
512     } else {
513         if (lev_level.solid[x][y] == TER_FREE
514             || lev_level.solid[x][y] == TER_TUNNEL)
515             lev_level.solid[x][y] = TER_GROUND;
516         else if (lev_level.solid[x][y] == TER_WATER)
517             lev_level.solid[x][y] = TER_UNDERWATER;
518     }
519     newentry->fx = fx;
520     if (lev_lastfx == NULL)
521         level_effects = newentry;
522     else
523         lev_lastfx->next = newentry;
524     lev_lastfx = newentry;
525 }
526 
527 /* Make a bullet hole in the ground */
make_hole(int x,int y)528 void make_hole(int x,int y) {
529     if (lev_level.solid[x][y] != TER_INDESTRUCT
530         && !(level_settings.indstr_base
531              && (lev_level.solid[x][y] == TER_BASE
532                  || lev_level.solid[x][y] == TER_BASEMAT))) {
533         int fx,fy;
534         for (fx = 0; fx < HOLE_W; fx++) {
535             for (fy = 0; fy < HOLE_H; fy++) {
536                 int terrain;
537                 int rx,ry;
538                 if (hole_bm[fx][fy])
539                     continue;
540                 rx = fx - HOLE_W/2 + x;
541                 ry = fy - HOLE_H/2 + y;
542                 if (rx < 0 || ry < 0 || rx >= lev_level.width
543                     || ry >= lev_level.height)
544                     continue;
545                 terrain = lev_level.solid[rx][ry];
546                 if((ter_semisolid(terrain) || ter_solid(terrain))
547                     && ter_indestructable(terrain)==0)
548                 {
549                     if(terrain == TER_BASE)
550                         lev_level.base_area--;
551                     if (terrain == TER_UNDERWATER || terrain == TER_ICE) {
552                         lev_level.solid[rx][ry] = TER_WATER;
553                         putpixel (lev_level.terrain, rx, ry, lev_watercol);
554                     } else {
555                         lev_level.solid[rx][ry] = TER_FREE;
556                         putpixel (lev_level.terrain, rx, ry, col_black);
557                     }
558                 }
559             }
560         }
561     }
562 }
563 
564 /* Burn a patch of ground */
burn_hole(int x,int y)565 void burn_hole(int x,int y) {
566     int fx,fy;
567     if(is_burnable(x,y))
568         start_burning(x,y);
569     for (fx = 0; fx < HOLE_W; fx++)
570         for (fy = 0; fy < HOLE_H; fy++) {
571             int rx,ry,terrain;
572             if (hole_bm[fx][fy])
573                 continue;
574             rx = fx - HOLE_W/2 + x;
575             ry = fy - HOLE_H/2 + y;
576             if (rx < 0 || ry < 0 || rx >= lev_level.width
577                 || ry >= lev_level.height)
578                 continue;
579             terrain = lev_level.solid[rx][ry];
580             if((ter_semisolid(terrain) || ter_solid(terrain))
581                 && ter_indestructable(terrain)==0)
582             {
583                 putpixel (lev_level.terrain, rx, ry, col_gray);
584             }
585         }
586 }
587 
animate_level(void)588 void animate_level (void)
589 {
590     struct LevelEffects *list = level_effects;
591     struct LevelEffects *next = NULL;
592     double f;
593     int v, tx, ty, nx, ny;
594     char solid;
595     struct Particle *part;
596     /* Base regeneration */
597     if(lev_level.base && lev_level.base_area<lev_level.regen_area) {
598         if(lev_level.regen_timer>BASE_REGEN_SPEED) {
599             int r,x,y;
600             lev_level.regen_timer=0;
601             for(r=0;r<lev_level.regen_area;r++) {
602                 x=lev_level.base[r].x;
603                 y=lev_level.base[r].y;
604                 if(lev_level.solid[x][y]==TER_FREE) {
605                     bump_ship(x,y);
606                     lev_level.solid[x][y]=TER_BASE;
607                     putpixel (lev_level.terrain, x, y, lev_level.base[r].c);
608                     putpixel (lev_level.terrain, x, y, lev_level.base[r].c);
609                     lev_level.base_area++;
610                     break;
611                 }
612             }
613         } else {
614             lev_level.regen_timer++;
615         }
616     }
617     /* Level effects */
618     while (list) {
619         next = list->next;
620         if (list->fx->type == Fire) {   /* Fire */
621             list->fx->value--;
622             if (list->fx->value > 0)
623                 v = list->fx->value + rand () % FIRE_RANDOM;
624             else
625                 v = list->fx->value;
626             if (list->fx->x < 3 || list->fx->y < 3
627                 || list->fx->x > lev_level.width - 3
628                 || list->fx->y > lev_level.height - 3) {
629                 list->fx->value = 0;
630             } else {
631                 solid = lev_level.solid[list->fx->x][list->fx->y];
632                 if (solid != TER_INDESTRUCT && solid != TER_BASE
633                     && solid != TER_BASEMAT)
634                     solid = 1;
635                 else
636                     solid = 0;
637                 if (solid
638                     && lev_level.solid[list->fx->x][list->fx->y + 1] ==
639                     TER_FREE && list->fx->value < FIRE_SPREAD) {
640                     if (lev_level.solid[list->fx->x][list->fx->y] == TER_GROUND)        /* Combustable2 turns into ground, remember ? */
641                         putpixel (lev_level.terrain, list->fx->x, list->fx->y,
642                                   col_gray);
643                     else
644                         putpixel (lev_level.terrain, list->fx->x, list->fx->y,
645                                   burncolor[0]);
646                     list->fx->y -= rand () % 3;
647                 }
648                 solid = lev_level.solid[list->fx->x][list->fx->y];
649                 if (v < FIRE_FRAMES && solid != TER_INDESTRUCT
650                     && solid != TER_BASE && solid != TER_BASEMAT) {
651                     if (v == 0
652                         && lev_level.solid[list->fx->x][list->fx->y] ==
653                         TER_GROUND)
654                         putpixel (lev_level.terrain, list->fx->x, list->fx->y,
655                                   col_gray);
656                     else
657                         putpixel (lev_level.terrain, list->fx->x, list->fx->y,
658                                   burncolor[v]);
659                 }
660                 if (list->fx->value == FIRE_SPREAD) {
661                     for (f = -M_PI; f < M_PI; f += M_PI / 4.0) {
662                         nx = list->fx->x + Round(sin (f) * 3.0);
663                         ny = list->fx->y + Round(cos (f) * 3.0);
664                         if (nx >= lev_level.width || ny >= lev_level.height
665                             || nx <= 0 || ny <= 0)
666                             continue;
667                         solid = lev_level.solid[nx][ny];
668                         if (solid == TER_COMBUSTABLE
669                             || solid == TER_COMBUSTABL2)
670                             start_burning (nx, ny);
671                         else if (solid == TER_EXPLOSIVE)
672                             spawn_clusters (nx, ny,5.6, 6, make_bullet);
673                         else if (solid == TER_EXPLOSIVE2)
674                             spawn_clusters (nx, ny,5.6, 3, make_grenade);
675                         else if (game_settings.enable_smoke
676                                  && solid == TER_FREE && cos (f) < 0
677                                  && rand () % 3 == 1) {
678                             part = make_particle (nx, ny, 9);
679                             part->vector.y = -2.5;
680                             part->vector.x = -weather_wind_vector;
681                             part->color[0] = 255;
682                             part->color[1] = 178;
683                             part->color[2] = 0;
684 #if HAVE_LIBSDL_GFX
685                             part->rd = 0;
686                             part->gd = 0;
687                             part->bd = 11;
688                             part->ad = -15;
689 #else
690                             part->rd = -17;
691                             part->gd = -8;
692                             part->bd = 11;
693 #endif
694                         } else if (solid == TER_ICE) {
695                             lev_level.solid[nx][ny] = TER_WATER;
696                             putpixel (lev_level.terrain, nx, ny,
697                                       lev_watercol);
698                         } else if (solid == TER_SNOW) {
699                             lev_level.solid[nx][ny] = TER_FREE;
700                             putpixel (lev_level.terrain, nx, ny,
701                                       burncolor[0]);
702                         } else if (solid == TER_WALKWAY)
703                             putpixel (lev_level.terrain, nx, ny, col_gray);
704                     }
705                 }
706             }
707         } else if (list->fx->type == Melt) {    /* Acid */
708             list->fx->value--;
709             if (list->fx->value == 1) {
710                 if (list->fx->brake)
711                     list->fx->brake--;
712                 putpixel (lev_level.terrain, list->fx->x, list->fx->y,
713                           col_black);
714                 for (f = -M_PI; f < M_PI; f += M_PI / 4.0) {
715                     nx = list->fx->x + Round(sin (f) * 3.0);
716                     ny = list->fx->y + Round(cos (f) * 3.0);
717                     if (nx >= lev_level.width || ny >= lev_level.height
718                         || nx <= 0 || ny <= 0)
719                         continue;
720                     solid = lev_level.solid[nx][ny];
721                     if (solid == TER_GROUND || solid == TER_COMBUSTABLE
722                         || solid == TER_COMBUSTABL2 || solid == TER_SNOW
723                         || solid == TER_TUNNEL || solid == TER_WALKWAY)
724                         start_melting (nx, ny, list->fx->brake);
725                     else if ((solid == TER_BASE || solid == TER_BASEMAT)
726                              && !level_settings.indstr_base)
727                         start_melting (nx, ny, list->fx->brake);
728                     else if (game_settings.enable_smoke && solid == TER_FREE
729                              && cos (f) < 0 && rand () % 3 == 1) {
730                         part = make_particle (nx, ny, 9);
731                         part->vector.y = -2.5;
732                         part->vector.x = -weather_wind_vector;
733                         part->color[0] = 0;
734                         part->color[1] = 255;
735                         part->color[2] = 0;
736                         part->rd = -22;
737                         part->gd = -28;
738                         part->bd = 22;
739                     }
740                 }
741             }
742         } else {                /* Ice, Earth or Explosive */
743             solid = lev_level.solid[list->fx->x][list->fx->y];
744             if (list->fx->value > 0)
745                 list->fx->value--;
746             if (list->fx->icicle
747                 && lev_level.solid[list->fx->x][list->fx->y + 1] ==
748                 TER_FREE) {
749                 list->fx->y++;
750                 putpixel (lev_level.terrain, list->fx->x, list->fx->y,
751                           col_snow);
752             }
753             if (list->fx->value == 1) {
754                 if (list->fx->type == Earth) {
755                     if (solid == TER_UNDERWATER)
756                         putpixel (lev_level.terrain, list->fx->x, list->fx->y,
757                                   col_clay_uw);
758                     else
759                         putpixel (lev_level.terrain, list->fx->x, list->fx->y,
760                                   col_clay);
761                 } else if (list->fx->type == Ice)
762                     putpixel (lev_level.terrain, list->fx->x, list->fx->y,
763                               col_snow);
764                 else
765                     putpixel (lev_level.terrain, list->fx->x, list->fx->y,
766                               col_gray);
767                 if (list->fx->brake > 0)
768                     list->fx->brake--;
769                 if (list->fx->x > 5 && list->fx->y > 5
770                     && list->fx->x < lev_level.width - 5
771                     && list->fx->y < lev_level.height - 5) {
772                     if (list->fx->type == Ice
773                         && (solid == TER_GROUND || solid == TER_FREE
774                             || solid == TER_SNOW || solid == TER_INDESTRUCT
775                             || solid == TER_COMBUSTABLE
776                             || solid == TER_COMBUSTABL2
777                             || solid == TER_WALKWAY)) {
778                         for (tx = list->fx->x - 3; tx < list->fx->x + 4; tx++)
779                             for (ty = list->fx->y - 3; ty < list->fx->y + 4;
780                                  ty++) {
781                                 if (lev_level.solid[tx][ty] == TER_WATER)
782                                     alter_level (tx, ty, list->fx->brake,
783                                                  Ice);
784                                 else if ((lev_level.solid[tx][ty] == TER_FREE
785                                           || lev_level.solid[tx][ty] ==
786                                           TER_TUNNEL) && touch_wall (tx, ty))
787                                     alter_level (tx, ty, list->fx->brake,
788                                                  list->fx->type);
789                             }
790                     } else {
791                         for (f = -M_PI; f < M_PI; f += M_PI / 4.0) {
792                             solid =
793                                 lev_level.
794                                 solid[(int) (list->fx->x + sin (f) * 4.0)][(int) (list->fx->y + cos (f) * 4.0)];
795                             if (list->fx->type == Ice) {
796                                 if (solid == TER_WATER && list->fx->brake)
797                                     alter_level ((int)
798                                                  (list->fx->x +
799                                                   sin (f) * 4.0),
800                                                  (int) (list->fx->y +
801                                                         cos (f) * 4.0),
802                                                  list->fx->brake,
803                                                  list->fx->type);
804                             } else if (list->fx->type == Earth) {
805                                 if ((solid == TER_FREE || solid == TER_WATER
806                                      || solid == TER_TUNNEL)
807                                     && list->fx->brake)
808                                     alter_level ((int)
809                                                  (list->fx->x +
810                                                   sin (f) * 4.0),
811                                                  (int) (list->fx->y +
812                                                         cos (f) * 4.0),
813                                                  list->fx->brake,
814                                                  list->fx->type);
815                             } else {
816                                 if ((solid == TER_GROUND
817                                      || solid == TER_COMBUSTABLE
818                                      || solid == TER_COMBUSTABL2)
819                                     && list->fx->brake)
820                                     alter_level ((int)
821                                                  (list->fx->x +
822                                                   sin (f) * 4.0),
823                                                  (int) (list->fx->y +
824                                                         cos (f) * 4.0),
825                                                  list->fx->brake,
826                                                  list->fx->type);
827                             }
828                         }
829                     }
830                 }
831             }
832         }
833         if (list->fx->value == 0) {
834             free (list->fx);
835             if (list->prev)
836                 list->prev->next = list->next;
837             else
838                 level_effects = list->next;
839             if (list->next)
840                 list->next->prev = list->prev;
841             else
842                 lev_lastfx = list->prev;
843             free (list);
844         }
845         list = next;
846     }
847     draw_level ();
848 }
849 
850