1 /*
2  * Luola - 2D multiplayer cave-flying game
3  * Copyright (C) 2003-2006 Calle Laakkonen
4  *
5  * File        : special.c
6  * Description : Level special objects
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 <math.h>
26 
27 #include "SDL.h"
28 
29 #ifdef HAVE_CONFIG_H
30 #include "../config.h"
31 #endif
32 
33 #include "list.h"
34 #include "fs.h"
35 #include "player.h"
36 #include "level.h"
37 #include "weapon.h"
38 #include "special.h"
39 #include "ship.h"
40 #include "audio.h"
41 
42 /* List of special objects */
43 static struct dllist *special_list;
44 
45 /* Special object graphics */
46 static SDL_Surface **jumpgate_gfx;
47 static int jumpgate_frames;
48 static SDL_Surface **turret_gfx[2]; /* Normal and missile turrets */
49 static int turret_frames[2];
50 static SDL_Surface **jumppoint_gfx[2]; /* Entry and exit points */
51 static int jumppoint_frames[2];
52 
53 /* Check if there is any solid terrain inside a rectangle */
hitsolid_rect(int x,int y,int w,int h)54 static int hitsolid_rect (int x, int y, int w, int h)
55 {
56     int x2 = x+w, y2 = y+h;
57     for (; x < x2; x++) {
58         for (; y < y2; y++) {
59             if (is_solid(x,y))
60                 return 1;
61         }
62         y-=h;
63     }
64     return 0;
65 }
66 
67 /* Search for a good place for a turret */
68 /* If ground!=0, only y+ axis is searched */
find_turret_xy(int * tx,int * ty,int ground)69 static int find_turret_xy (int *tx, int *ty,int ground)
70 {
71     int x[4]={*tx,*tx,*tx,*tx}, y[4]={*ty,*ty,*ty,*ty};
72     int dx[4]={0,1,0,-1}, dy[4]={1,0,-1,0};
73     int r,loops=0;
74     while(++loops<400) {
75         for(r=0;r<4;r++) {
76             if((ground&&r!=0) || is_water(x[r],y[r])) continue;
77             x[r] += dx[r];
78             y[r] += dy[r];
79             if(is_solid(x[r],y[r])) {
80                 *tx = x[r];
81                 *ty = y[r];
82                 return 1;
83             }
84         }
85     }
86     return 0;
87 }
88 
89 /* Load level special object images */
init_specials(LDAT * specialfile)90 void init_specials (LDAT *specialfile) {
91     jumppoint_gfx[0] = load_image_array(specialfile, 0, T_ALPHA, "WARP",
92             &jumppoint_frames[0]);
93     jumppoint_gfx[1] = load_image_array(specialfile, 0, T_ALPHA, "WARPE",
94             &jumppoint_frames[1]);
95 
96     turret_gfx[0] = load_image_array(specialfile, 0, T_ALPHA, "TURRET",
97             &turret_frames[0]);
98     turret_gfx[1] = load_image_array(specialfile, 0, T_ALPHA, "SAMSITE",
99             &turret_frames[1]);
100 
101     jumpgate_gfx = load_image_array (specialfile, 0, T_ALPHA, "JUMPGATE",
102             &jumpgate_frames);
103 }
104 
105 /* Clear all level specials at the end of the level */
clear_specials(void)106 void clear_specials (void) {
107     dllist_free(special_list,free);
108     special_list=NULL;
109 }
110 
111 /* Transport a ship between two jumppoints */
jumppoint_hitship(struct SpecialObj * point,struct Ship * ship)112 static void jumppoint_hitship(struct SpecialObj *point, struct Ship *ship) {
113     if(point->timer==0 && point->link && point->frame>=point->frames/2) {
114         point->timer=0.8*GAME_SPEED;
115         point->link->timer=point->timer;
116         ship->physics.x = point->link->x;
117         ship->physics.y = point->link->y;
118     }
119 }
120 
121 /* Transport a projectile between two jumppoints */
jumppoint_hitprojectile(struct SpecialObj * point,struct Projectile * p)122 static void jumppoint_hitprojectile(struct SpecialObj *point, struct Projectile *p) {
123     if(p->physics.radius>=3.5 && point->timer==0 && point->link &&
124             point->frame>=point->frames/2)
125     {
126         point->timer=0.1*GAME_SPEED;
127         point->link->timer=point->timer;
128         p->physics.x = point->link->x;
129         p->physics.y = point->link->y;
130     }
131 }
132 
133 /* Jump-point animation */
jumppoint_animate(struct SpecialObj * point)134 static void jumppoint_animate(struct SpecialObj *point) {
135     point->frame++;
136     if(point->frame>=point->frames) /* Loop at the middle */
137         point->frame=point->frames/2;
138 }
139 
140 /* Create a jump-point */
make_jumppoint(int x,int y,int owner,int exit)141 struct SpecialObj *make_jumppoint(int x,int y,int owner,int exit) {
142     struct SpecialObj *point= malloc(sizeof(struct SpecialObj));
143     if(!point) {
144         perror(__func__);
145         return NULL;
146     }
147     point->gfx = jumppoint_gfx[exit?1:0];
148     point->frames = jumppoint_frames[exit?1:0];
149     point->frame=0;
150     point->x = x;
151     point->y = y;
152     point->owner = owner;
153     point->secret = 0;
154     switch(game_settings.jumplife) {
155         case JLIFE_SHORT: point->life = point->frames*1.5; break;
156         case JLIFE_MEDIUM: point->life = point->frames*3.5; break;
157         case JLIFE_LONG: point->life = point->frames*6.5; break;
158     }
159     point->timer = 0;
160     point->link = NULL;
161     point->hitship = jumppoint_hitship;
162     point->hitprojectile = jumppoint_hitprojectile;
163     point->animate = jumppoint_animate;
164     point->destroy = NULL;
165 
166     return point;
167 }
168 
169 /* Open a wormhole between two jumpgates */
jumpgate_hitship(struct SpecialObj * gate,struct Ship * ship)170 static void jumpgate_hitship(struct SpecialObj *gate, struct Ship *ship) {
171     if(gate->timer==0 && gate->link) {
172         struct SpecialObj *exit = make_jumppoint(gate->link->x,gate->link->y,gate->owner,1);
173         struct SpecialObj *entry = make_jumppoint(gate->x,gate->y,gate->owner,0);
174 
175         entry->link = exit;
176         exit->link = entry;
177         if(game_settings.onewayjp) {
178             exit->hitship = NULL;
179             exit->hitprojectile = NULL;
180         }
181         gate->timer=exit->life*1.5;
182         gate->link->timer=gate->timer;
183         add_special(exit);
184         add_special(entry);
185     }
186 }
187 
188 /* Create a jumpgate */
make_jumpgate(int x,int y)189 static struct SpecialObj *make_jumpgate(int x,int y) {
190     struct SpecialObj *gate = malloc(sizeof(struct SpecialObj));
191     if(!gate) {
192         perror(__func__);
193         return NULL;
194     }
195     gate->gfx = jumpgate_gfx;
196     gate->frames = jumpgate_frames;
197     gate->frame=0;
198     gate->x = x;
199     gate->y = y;
200     gate->owner = -1;
201     gate->life = -1;
202     gate->timer = 0;
203     gate->secret = 0;
204     gate->link = NULL;
205     gate->hitship = jumpgate_hitship;
206     gate->hitprojectile = NULL;
207     gate->animate = NULL;
208     gate->destroy = NULL;
209 
210     return gate;
211 }
212 
213 /* Add random jumpgates to the level */
add_random_gates(int count)214 static void add_random_gates(int count) {
215     int w=jumpgate_gfx[0]->w;
216     int h=jumpgate_gfx[0]->h;
217     int r;
218     for(r=0;r<count;r++) {
219         struct SpecialObj *gate[2];
220         int g;
221         for(g=0;g<2;g++) {
222             int x,y,loops=0;
223             do {
224                 x = rand()%lev_level.width;
225                 y = rand()%lev_level.height;
226             } while((hitsolid_rect(x-w/2,y-h/2,w,h) ||
227                         (g==1 && hypot(gate[0]->x-x,gate[0]->y-y)<500.0))
228                     && ++loops<1000);
229             if(loops>=1000) {
230                 fprintf(stderr,"Warning: Couldn't find place for a jumpgate!\n");
231                 if(g>0)
232                     free(gate[0]);
233                 return;
234             }
235             gate[g] = make_jumpgate(x,y);
236         }
237         gate[0]->link = gate[1];
238         gate[1]->link = gate[0];
239         add_special(gate[0]);
240         add_special(gate[1]);
241     }
242 }
243 
244 /* Turret fires a projectile */
turret_shoot(struct SpecialObj * turret)245 static void turret_shoot(struct SpecialObj *turret) {
246     struct Projectile *bullet;
247     double x = turret->x + cos(turret->angle)*6;
248     double y = turret->y - sin(turret->angle)*6;
249     Vector v = get_muzzle_vel(turret->angle);
250 
251     switch(turret->type) {
252         case 2:
253             bullet = make_missile(x,y,v);
254             bullet->angle = 2*M_PI-turret->angle;
255             turret->timer = 1.5*GAME_SPEED;
256             playwave_3d (WAV_MISSILE, turret->x, turret->y);
257             break;
258         case 1:
259             bullet = make_grenade(x,y,v);
260             turret->timer = 0.9 * GAME_SPEED;
261             playwave_3d (WAV_NORMALWEAP, turret->x, turret->y);
262             break;
263         default:
264             bullet = make_bullet(x,y,v);
265             turret->timer = 0.5 * GAME_SPEED;
266             playwave_3d (WAV_NORMALWEAP, turret->x, turret->y);
267             break;
268     }
269     bullet->owner = turret->owner;
270     add_projectile(bullet);
271 }
272 
273 /* Turret explodes when it is destroyed */
turret_explode(struct SpecialObj * turret)274 static void turret_explode(struct SpecialObj *turret) {
275     spawn_clusters(turret->x,turret->y, 5.6, 8, make_bullet);
276     spawn_clusters(turret->x,turret->y, 5.6, 16, make_firestarter);
277     add_explosion (turret->x,turret->y);
278 }
279 
280 /* Turret animation */
turret_animate(struct SpecialObj * turret)281 static void turret_animate(struct SpecialObj *turret) {
282     double dist;
283     float targx=-1,targy=-1;
284     int targplr;
285     /* Get a target */
286     targplr = find_nearest_enemy(turret->x,turret->y,turret->owner, &dist);
287     if(dist<160.0) {
288         targx = players[targplr].ship->physics.x;
289         targy = players[targplr].ship->physics.y;
290     } else {
291         targplr = find_nearest_pilot(turret->x,turret->y,turret->owner, &dist);
292         if(dist<160.0) {
293             targx = players[targplr].pilot.walker.physics.x;
294             targy = players[targplr].pilot.walker.physics.y;
295         }
296     }
297     /* Aim at target if found */
298     if(targx>=0) {
299         double a = atan2(turret->y-targy, targx-turret->x);
300         double d;
301         if(a<0) a = 2*M_PI + a;
302         d = shortestRotation(turret->angle,a);
303         if(d<-0.2) {
304             turret->turn = -0.2;
305         } else if(d>0.2) {
306             turret->turn = 0.2;
307         } else {
308             turret->turn = d/2;
309             if(turret->timer==0)
310                 turret_shoot(turret);
311         }
312     } else {
313         /* Restore normal turning speed */
314         if(turret->turn<0)
315             turret->turn=-0.05;
316         else
317             turret->turn=0.05;
318     }
319     /* Rotate turret */
320     turret->angle += turret->turn;
321     if(is_solid(turret->x+cos(turret->angle)*5,turret->y-sin(turret->angle)*5))
322     {
323         turret->angle -= turret->turn;
324         turret->turn = -turret->turn;
325     }
326     if(turret->angle>2*M_PI) turret->angle=0;
327     else if(turret->angle<0) turret->angle=2*M_PI;
328     turret->frame = Round(turret->angle/(2*M_PI)*(turret->frames-1));
329 }
330 
331 /* Missile turret animation */
mturret_animate(struct SpecialObj * turret)332 static void mturret_animate(struct SpecialObj *turret) {
333     /* Find a target */
334     int targplr;
335     double dist;
336     if(turret->timer==0) {
337         targplr = find_nearest_enemy(turret->x,turret->y,turret->owner, &dist);
338         if(dist<250.0) {
339             turret->angle = (turret->frames-turret->frame)/(double)turret->frames*M_PI_2+M_PI_4;
340             turret_shoot(turret);
341         }
342     }
343 
344     /* Animation */
345     if(turret->turn>0) {
346         if(++turret->frame>=turret->frames) {
347             turret->frame=turret->frames-1;
348             turret->turn=-1;
349         }
350     } else {
351         if(--turret->frame>turret->frames) {
352             turret->frame=0;
353             turret->turn=1;
354         }
355     }
356 }
357 
358 /* Projectile hits a turret */
turret_hitprojectile(struct SpecialObj * turret,struct Projectile * p)359 static void turret_hitprojectile(struct SpecialObj *turret, struct Projectile *p) {
360     if(p->critter) { /* Collide only with the type of projectiles that
361                         can hit critters and pilots */
362         turret->health -= p->damage;
363         if(turret->health<=0) turret->life=0;
364         p->life=0;
365         if(p->explode)
366             p->explode(p);
367     }
368 }
369 
370 /* Create a turret */
make_turret(int x,int y,int type)371 static struct SpecialObj *make_turret(int x,int y,int type) {
372     double a;
373     struct SpecialObj *turret = malloc(sizeof(struct SpecialObj));
374     if(!turret) {
375         perror(__func__);
376         return NULL;
377     }
378     turret->gfx = turret_gfx[type==2];
379     turret->frames = turret_frames[type==2];
380     turret->type = type;
381     turret->frame=0;
382     turret->x = x;
383     turret->y = y;
384     turret->owner = -1;
385     turret->life = -1;
386     turret->timer = 0;
387     turret->secret = 0;
388     turret->health = 0.20;
389     turret->hitship = NULL;
390     turret->hitprojectile = turret_hitprojectile;
391     if(type==2)
392         turret->animate = mturret_animate;
393     else
394         turret->animate = turret_animate;
395     turret->destroy = turret_explode;
396     turret->turn = 0.05;
397 
398     /* Find a good starting angle */
399     if(type<2)
400         for(a=0;a<2*M_PI;a++)
401             if(!is_solid(x+cos(a)*5,y-sin(a)*5)) {turret->angle = a; break;}
402 
403     return turret;
404 }
405 
406 /* Add random turrets to level */
add_random_turrets(int count)407 static void add_random_turrets(int count) {
408     int r;
409     for(r=0;r<count;r++) {
410         int x,y,loops=0;
411         int type=rand()%3;
412         do {
413             x = rand()%lev_level.width;
414             y = rand()%lev_level.height;
415             if(is_free(x,y)) {
416                 if(find_turret_xy(&x,&y,type==2))
417                     break;
418             }
419         } while(++loops<1000);
420         if(loops>=1000) {
421             fprintf(stderr,"Warning: Couldn't find place for a turret!\n");
422             return;
423         }
424         add_special(make_turret(x,y,type));
425     }
426 }
427 
428 /* Place level specials at the start of the level */
prepare_specials(struct LevelSettings * settings)429 void prepare_specials (struct LevelSettings * settings) {
430     struct dllist *objects=NULL;
431 
432     /* Add random objects */
433     add_random_gates(level_settings.jumpgates);
434     add_random_turrets(level_settings.turrets);
435 
436     /* Add manually placed objects */
437     if(settings)
438         objects=settings->objects;
439     while(objects) {
440         struct LSB_Object *objdef = objects->data;
441         struct SpecialObj *obj=NULL;
442         switch(objdef->type) {
443             case OBJ_TURRET:
444                 obj = make_turret(objdef->x,objdef->y,objdef->value);
445                 break;
446             case OBJ_JUMPGATE:
447                 obj = make_jumpgate(objdef->x,objdef->y);
448                 obj->owner = objdef->id; /* Store id here temporarily */
449                 obj->link = (struct SpecialObj*)objdef->link;
450                 break;
451             default: break;
452         }
453         add_special(obj);
454         objects = objects->next;
455     }
456     /* Pair up the manually placed jumpgates */
457     objects = special_list;
458     while(objects) {
459         struct SpecialObj *obj = objects->data;
460         if(obj->gfx == jumpgate_gfx && (int)obj->link<=255) {
461             struct dllist *pair = objects->next;
462             while(pair) {
463                 struct SpecialObj *p = pair->data;
464                 if(p->gfx == jumpgate_gfx && (int)p->link == obj->owner &&
465                         p->owner == (int)obj->link)
466                 {
467                     obj->owner = -1;
468                     obj->link = p;
469                     p->owner = -1;
470                     p->link = obj;
471                     break;
472                 }
473                 pair = pair->next;
474             }
475             if(pair==NULL) {
476                 fprintf(stderr,"No pair (%d) found for jumpgate %d\n",
477                         (int)obj->link,obj->owner);
478                 obj->life=0; /* mark the jumpgate for deletion */
479             }
480         }
481         objects = objects->next;
482     }
483 }
484 
485 /* Add a new level special */
add_special(struct SpecialObj * special)486 void add_special (struct SpecialObj *special) {
487     if(special) {
488         if(special_list)
489             dllist_append(special_list,special);
490         else
491             special_list=dllist_append(special_list,special);
492     }
493 }
494 
495 /* Drop a jump point. The jump-point will wait for a pair to be dropped */
496 /* before it will open. */
drop_jumppoint(int x,int y,int player)497 void drop_jumppoint (int x, int y, int player) {
498     /* First search for a exit point to this jumppoint */
499     struct dllist *ptr=special_list;
500     struct SpecialObj *exit,*point;
501     while(ptr) {
502         exit = ptr->data;
503         if(exit->gfx==jumppoint_gfx[1] && exit->owner == player &&
504                 exit->link==NULL)
505             break;
506         ptr=ptr->next;
507     }
508     if(ptr) { /* If exit point exists, create the wormhole */
509         point = make_jumppoint(x,y,player,0);
510         point->link = exit;
511         exit->link = point;
512 
513         exit->life = point->life;
514         exit->animate = point->animate;
515         exit->secret = 0;
516         if(game_settings.onewayjp==0) {
517             exit->hitship = point->hitship;
518             exit->hitprojectile = point->hitprojectile;
519         }
520         playwave (WAV_JUMP);
521     } else { /* No exit point, create one */
522         point = make_jumppoint(x,y,player,1);
523         point->animate = NULL;
524         point->hitship = NULL;
525         point->hitprojectile = NULL;
526         point->life=-1;
527         point->secret = 1;
528     }
529     add_special(point);
530 }
531 
532 /* Draw a special object on all viewports */
draw_special(struct SpecialObj * object)533 static void draw_special (struct SpecialObj *object)
534 {
535     int p;
536     for(p=0;p<4;p++) {
537         if (players[p].state==ALIVE||players[p].state==DEAD) {
538             SDL_Rect rect = {0,0, object->gfx[0]->w, object->gfx[0]->h};
539             SDL_Rect rect2;
540             if(object->secret && p!=object->owner) continue;
541             rect.x = object->x - rect.w/2 - cam_rects[p].x + viewport_rects[p].x;
542             rect.y = object->y - rect.h/2 - cam_rects[p].y + viewport_rects[p].y;
543             if ((rect.x > viewport_rects[p].x - rect.w
544                  && rect.x < viewport_rects[p].x + cam_rects[p].w)
545                 && (rect.y > viewport_rects[p].y - rect.h
546                     && rect.y < viewport_rects[p].y + cam_rects[p].h)) {
547                 rect2 =
548                     cliprect (rect.x, rect.y, rect.w, rect.h, viewport_rects[p].x,
549                               viewport_rects[p].y, viewport_rects[p].x + cam_rects[p].w,
550                               viewport_rects[p].y + cam_rects[p].h);
551                 if (rect.x < viewport_rects[p].x)
552                     rect.x = viewport_rects[p].x;
553                 if (rect.y < viewport_rects[p].y)
554                     rect.y = viewport_rects[p].y;
555                 SDL_BlitSurface (object->gfx[object->frame], &rect2, screen,
556                                  &rect);
557             }
558         }
559     }
560 }
561 
562 /* Check if a ship hits a special object */
ship_hit_special(struct SpecialObj * obj)563 static void ship_hit_special(struct SpecialObj *obj) {
564     struct dllist *ptr = ship_list;
565     int w2 = obj->gfx[0]->w/2;
566     int h2 = obj->gfx[0]->h/2;
567     while(ptr) {
568         struct Ship *ship=ptr->data;
569 
570         if(ship->physics.x>=obj->x-w2 && ship->physics.x<=obj->x+w2 &&
571                 ship->physics.y>=obj->y-h2 && ship->physics.y<=obj->y+h2)
572         {
573             obj->hitship(obj,ship);
574             break;
575         }
576         ptr=ptr->next;
577     }
578 }
579 
580 /* Check if a projectile hits a special object */
projectile_hit_special(struct SpecialObj * obj)581 static void projectile_hit_special (struct SpecialObj *obj) {
582     struct dllist *ptr = projectile_list;
583     int w2 = obj->gfx[0]->w/2;
584     int h2 = obj->gfx[0]->h/2;
585     while(ptr) {
586         struct Projectile *p = ptr->data;
587 
588         if(p->physics.x>=obj->x-w2 && p->physics.x<=obj->x+w2 &&
589                 p->physics.y>=obj->y-h2 && p->physics.y<=obj->y+h2)
590         {
591             obj->hitprojectile(obj,p);
592         }
593         ptr=ptr->next;
594     }
595 }
596 
597 /* Animate special objects */
animate_specials(void)598 void animate_specials (void) {
599     struct dllist *list = special_list,*next;
600     while (list) {
601         struct SpecialObj *obj=list->data;
602 
603         /* Check for collisions */
604         if(obj->hitship)
605             ship_hit_special (obj);
606         if(obj->hitprojectile)
607             projectile_hit_special(obj);
608 
609         /* Animate */
610         if(obj->animate)
611             obj->animate(obj);
612         if (obj->timer > 0)
613             obj->timer--;
614 
615         /* Draw the object on all viewports */
616         draw_special (obj);
617 
618         /* Check if the object has expired.
619          * If life < 0, the object has no time limit */
620         next = list->next;
621         if(obj->life > 0) obj->life--;
622         else if (obj->life == 0) {
623             if(obj->destroy)
624                 obj->destroy(obj);
625             free (obj);
626             if(list==special_list)
627                 special_list=dllist_remove(list);
628             else
629                 dllist_remove(list);
630         }
631         list = next;
632     }
633 }
634 
635