1 /* $Id: asteroid.c,v 5.18 2003/09/16 21:00:25 bertg Exp $
2  *
3  * XPilot, a multiplayer gravity war game.  Copyright (C) 1991-2001 by
4  *
5  *      Bj�rn Stabell        <bjoern@xpilot.org>
6  *      Ken Ronny Schouten   <ken@xpilot.org>
7  *      Bert Gijsbers        <bert@xpilot.org>
8  *      Dick Balaska         <dick@xpilot.org>
9  *  	Kimiko Koopman        <kimiko@xpilot.org>
10  *
11  * This program is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software
23  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24  */
25 
26 #include <stdlib.h>
27 #include <string.h>
28 #include <stdio.h>
29 #include <math.h>
30 #include <limits.h>
31 
32 #ifdef _WINDOWS
33 # include "NT/winServer.h"
34 #endif
35 
36 #define SERVER
37 #include "version.h"
38 #include "config.h"
39 #include "serverconst.h"
40 #include "list.h"
41 #include "global.h"
42 #include "proto.h"
43 #include "saudio.h"
44 #include "bit.h"
45 #include "objpos.h"
46 #include "asteroid.h"
47 #include "commonproto.h"
48 
49 
50 char asteroid_version[] = VERSION;
51 
52 
53 /* list containing pointers to all asteroids */
54 static list_t	Asteroid_list = NULL;
55 
56 
57 /*
58 ** Prototypes.
59 */
60 static void Make_asteroid(DFLOAT x, DFLOAT y,
61 			  int size, int dir,
62 			  DFLOAT speed);
63 
64 
65 /*
66 ** Return the asteroid list.
67 */
Asteroid_get_list(void)68 list_t Asteroid_get_list(void)
69 {
70     return Asteroid_list;
71 }
72 
73 
Asteroid_add_to_list(wireobject * ast)74 static bool Asteroid_add_to_list(wireobject *ast)
75 {
76     list_iter_t		list_pos;
77     bool		result = false;
78 
79     if (Asteroid_list == NULL) {
80 	Asteroid_list = List_new();
81     }
82 
83     if (Asteroid_list != NULL) {
84 	list_pos = List_push_back(Asteroid_list, ast);
85 	if (list_pos != NULL) {
86 	    result = true;
87 	}
88     }
89 
90     return result;
91 }
92 
93 
Asteroid_remove_from_list(wireobject * ast)94 static bool Asteroid_remove_from_list(wireobject *ast)
95 {
96     list_iter_t		list_pos;
97     bool		result = false;
98 
99     if (Asteroid_list != NULL) {
100 	list_pos = List_find(Asteroid_list, ast);
101 	if (list_pos != List_end(Asteroid_list)) {
102 	    List_erase(Asteroid_list, list_pos);
103 	    result = true;
104 	}
105     }
106 
107     return result;
108 }
109 
110 
111 /*
112  * Breaks up an asteroid:
113  * Asteroids of size > 1 break up into two asteroids of size n - 1
114  * and some wreckage and debris.
115  * Asteroids of size 1 break up into wreckage and debris only.
116  * In both cases the sum of the masses of the smaller asteroids,
117  * the wreckage and the debris should be about equal to the mass
118  * of the original asteroid.
119  */
Break_asteroid(int ind)120 void Break_asteroid(int ind)
121 {
122     wireobject	*asteroid = WIRE_IND(ind);
123     DFLOAT	mass, mass3;
124     DFLOAT	speed, speed1, speed2, radius;
125     DFLOAT	velx1, vely1, velx2, vely2, velx3, vely3;
126     int		dir, dir1, dir2, split_dir;
127     int		x1, y1, x2, y2;
128 
129     if (asteroid->size == 1) {
130 	mass = asteroid->mass / 2;
131 	Make_wreckage(asteroid->pos.x, asteroid->pos.y,
132 		      asteroid->vel.x, asteroid->vel.y,
133 		      -1,
134 		      TEAM_NOT_SET,
135 		      mass / 20, mass / 3,
136 		      mass,
137 		      GRAVITY,
138 		      WHITE,
139 		      10,
140 		      0, RES-1,
141 		      5, 10,
142 		      3, 10);
143 	Make_debris(asteroid->pos.x, asteroid->pos.y,
144 		    asteroid->vel.x, asteroid->vel.y,
145 		    -1,
146 		    TEAM_NOT_SET,
147 		    OBJ_DEBRIS,
148 		    mass,
149 		    GRAVITY,
150 		    RED,
151 		    8,
152 		    20, 50,
153 		    0, RES-1,
154 		    5, 10,
155 		    3, 10);
156     } else {
157 	/* foo[12] refer to the mini-asteroids
158 	   foo3 refers to the wreckage and debris */
159 	speed = VECTOR_LENGTH(asteroid->vel);
160 	dir = (int)findDir(asteroid->vel.x, asteroid->vel.y);
161 	mass3 = asteroid->mass * ASTEROID_DUST_FACT;
162 	mass = ASTEROID_MASS(asteroid->size - 1);
163 	dir1 = MOD2((int)(dir
164 			  - ASTEROID_DELTA_DIR / 4
165 			  - (rfrac() * ASTEROID_DELTA_DIR / 4)), RES);
166 	dir2 = MOD2((int)(dir
167 			  + ASTEROID_DELTA_DIR / 4
168 			  + (rfrac() * ASTEROID_DELTA_DIR / 4)), RES);
169 	speed1 = (speed * (1 - ASTEROID_DUST_FACT))
170 		 / tcos(ABS(dir - dir1));
171 	speed2 = (speed * (1 - ASTEROID_DUST_FACT))
172 		 / tcos(ABS(dir2 - dir));
173 	velx1 = tcos(dir1) * speed1;
174 	vely1 = tsin(dir1) * speed1;
175 	velx2 = tcos(dir2) * speed2;
176 	vely2 = tsin(dir2) * speed2;
177 	split_dir = MOD2(dir - RES/4, RES);
178 	radius = ASTEROID_RADIUS(asteroid->size - 1);
179 	x1 = WRAP_XPIXEL(asteroid->pos.x + tcos(split_dir) * radius);
180 	y1 = WRAP_YPIXEL(asteroid->pos.y + tsin(split_dir) * radius);
181 	x2 = WRAP_XPIXEL(asteroid->pos.x - tcos(split_dir) * radius);
182 	y2 = WRAP_YPIXEL(asteroid->pos.y - tsin(split_dir) * radius);
183 	velx3 = asteroid->vel.x;
184 	vely3 = asteroid->vel.y;
185 	Make_asteroid(x1, y1, asteroid->size - 1, dir1, speed1);
186 	Make_asteroid(x2, y2, asteroid->size - 1, dir2, speed2);
187 	Make_wreckage(asteroid->pos.x, asteroid->pos.y,
188 		      velx3, vely3,
189 		      -1,
190 		      TEAM_NOT_SET,
191 		      mass3 / 20, mass3 / 3,
192 		      mass3 / 2,
193 		      GRAVITY,
194 		      WHITE,
195 		      10,
196 		      0, RES-1,
197 		      5, 10,
198 		      3, 10);
199 	Make_debris(asteroid->pos.x, asteroid->pos.y,
200 		    velx3, vely3,
201 		    -1,
202 		    TEAM_NOT_SET,
203 		    OBJ_DEBRIS,
204 		    mass3 / 2,
205 		    GRAVITY,
206 		    RED,
207 		    8,
208 		    20, 50,
209 		    0, RES-1,
210 		    5, 10,
211 		    3, 10);
212     }
213 
214     if ((asteroidMaxItems > 0) && (rfrac() < asteroidItemProb)) {
215 	int	nitems = (int)(rfrac() * asteroidMaxItems) + 1;
216 	int	i;
217 	int	vx, vy;
218 	int	item, item_dir, num_per_pack;
219 	DFLOAT	item_speed;
220 	long	status;
221 
222 	for (i = 0; i < nitems; i++) {
223 	    item = Choose_random_item();
224 	    item_dir = (int)(rfrac() * RES);
225 	    item_speed = rfrac() * 10;
226 	    vx = asteroid->vel.x + item_speed * tcos(item_dir);
227 	    vy = asteroid->vel.y + item_speed * tsin(item_dir);
228 	    status = GRAVITY;
229 	    if (rfrac() < randomItemProb)
230 		status |= RANDOM_ITEM;
231 	    if (World.items[item].min_per_pack == World.items[item].max_per_pack) {
232 		num_per_pack = World.items[item].max_per_pack;
233 	    } else {
234 		num_per_pack = World.items[item].min_per_pack
235 			     + (int)(rfrac() * (1 + World.items[item].max_per_pack
236 						  - World.items[item].min_per_pack));
237 	    }
238 
239 	    Make_item(asteroid->pos.x, asteroid->pos.y,
240 		      vx, vy,
241 		      item, num_per_pack,
242 		      status);
243 	}
244     }
245 
246     sound_play_sensors(asteroid->pos.x, asteroid->pos.y, ASTEROID_BREAK_SOUND);
247 
248     World.asteroids.num -= 1 << (asteroid->size - 1);
249 
250     Asteroid_remove_from_list(asteroid);
251 }
252 
253 
254 /*
255  * Creates an asteroid with the given characteristics.
256  */
Make_asteroid(DFLOAT x,DFLOAT y,int size,int dir,DFLOAT speed)257 static void Make_asteroid(DFLOAT x, DFLOAT y,
258 			  int size, int dir,
259 			  DFLOAT speed)
260 {
261     wireobject	*asteroid;
262     DFLOAT	radius;
263 	int		bx;
264 	int		by;
265 
266     if (NumObjs >= MAX_TOTAL_SHOTS) {
267 	return;
268     }
269 
270     if (size < 1 || size > ASTEROID_MAX_SIZE) {
271 	return;
272     }
273 
274     if (BIT(World.rules->mode, WRAP_PLAY)) {
275 	if (x < 0) x += World.width;
276 	else if (x >= World.width) x -= World.width;
277 	if (y < 0) y += World.height;
278 	else if (y >= World.height) y -= World.height;
279     }
280     if (x < 0 || x >= World.width || y < 0 || y >= World.height) {
281 	return;
282     }
283 
284     bx = x / BLOCK_SZ;
285 	by = y / BLOCK_SZ;
286     if (BIT(World.block[bx][by], FILLED_BIT|FUEL_BIT|TARGET_BIT|TREASURE_BIT)) {
287 	return;
288     } else if (BIT(World.block[bx][by], REC_LU|REC_RU|REC_LD|REC_RD)) {
289 	DFLOAT	x_in_b = x - bx * BLOCK_SZ,
290 		y_in_b = y - by * BLOCK_SZ;
291 	switch (World.block[bx][by]) {
292 	case REC_LU:
293 	    if (x_in_b < y_in_b)
294 		return;
295 	    break;
296 	case REC_RU:
297 	    if (x_in_b + y_in_b > BLOCK_SZ)
298 		return;
299 	    break;
300 	case REC_LD:
301 	    if (x_in_b + y_in_b < BLOCK_SZ)
302 		return;
303 	    break;
304 	case REC_RD:
305 	    if (x_in_b > y_in_b)
306 		return;
307 	    break;
308 	}
309     }
310 
311     asteroid = WIRE_PTR(Object_allocate());
312     if (asteroid == NULL) {
313 	return;
314     }
315 
316     asteroid->color = WHITE;
317     asteroid->id = NO_ID;
318     asteroid->team = TEAM_NOT_SET;
319     asteroid->type = OBJ_ASTEROID;
320 
321     /* Position */
322     Object_position_init_pixels(OBJ_PTR(asteroid), x, y);
323 
324     asteroid->vel.x = tcos(dir) * speed;
325     asteroid->vel.y = tsin(dir) * speed;
326     asteroid->acc.x = asteroid->acc.y = 0;
327     asteroid->mass = ASTEROID_MASS(size);
328     asteroid->life = ASTEROID_LIFE;
329     asteroid->turnspeed = 0.02 + rfrac() * 0.05;
330     asteroid->rotation = (int)(rfrac() * RES);
331     asteroid->size = size;
332     asteroid->info = (int)(rfrac() * 256);
333     radius = ASTEROID_RADIUS(size);
334     asteroid->pl_range = radius;
335     asteroid->pl_radius = radius;
336     asteroid->fuselife = asteroid->life - 1;
337     asteroid->status = GRAVITY;
338     CLEAR_MODS(asteroid->mods);
339 
340     if (Asteroid_add_to_list(asteroid) == true) {
341 	World.asteroids.num += 1 << (size - 1);
342 	Cell_add_object(OBJ_PTR(asteroid));
343     }
344     else {
345 	Object_free_ptr(OBJ_PTR(asteroid));
346     }
347 }
348 
349 
350 /*
351  * Tries to place a new asteroid on the map.
352  * Calls Make_asteroid() to actually create the new asteroid
353  */
Place_asteroid(void)354 static void Place_asteroid(void)
355 {
356     int			place_count;
357     int			px = 0, py = 0;
358     int			bx, by;
359     int			dir, dist;
360     unsigned		space;
361     int			okay;
362     asteroid_concentrator_t	*con;
363 
364     space = SPACE_BLOCKS;
365     space &= ~(BASE_BIT | WORMHOLE_BIT);
366     space |= FRICTION_BIT;
367     /* would be dubious: space |= CANNON_BIT; */
368 
369     if (World.NumAsteroidConcs > 0 && rfrac() < asteroidConcentratorProb) {
370 	con = &World.asteroidConcs[(int)(rfrac() * World.NumAsteroidConcs)];
371     } else {
372 	con = NULL;
373     }
374 
375     /* we bail out after 8 unsuccessful attempts to avoid wasting
376      * too much time on crowded maps */
377     okay = false;
378     for (place_count = 0; okay != true; place_count++) {
379 	if (place_count >= 10) {
380 	    return;
381 	}
382 
383 	if (con) {
384 	    dir = (int)(rfrac() * RES);
385 	    dist = (int)(rfrac() * ((asteroidConcentratorRadius * BLOCK_SZ) + 1));
386 	    px = (int)((con->pos.x + 0.5) * BLOCK_SZ + dist * tcos(dir));
387 	    py = (int)((con->pos.y + 0.5) * BLOCK_SZ + dist * tsin(dir));
388 
389 	    if (BIT(World.rules->mode, WRAP_PLAY)) {
390 		if (px < 0) px += World.width;
391 		if (py < 0) py += World.height;
392 		if (px > World.width) px -= World.width;
393 		if (py > World.height) py -= World.height;
394 	    }
395 	    if (px < 0 || px >= World.width
396 		|| py < 0 || py >= World.height) {
397 		continue;
398 	    }
399 	} else {
400 	    px = (int)(rfrac() * World.width);
401 	    py = (int)(rfrac() * World.height);
402 	}
403 	bx = px / BLOCK_SZ;
404 	by = py / BLOCK_SZ;
405 
406 	if (BIT(1U << World.block[bx][by], space)) {
407 	    int i, dpx, dpy, ox, oy;
408 
409 	    okay = true;
410 
411 	    for (i = 0; i < NumPlayers; i++) {
412 		if (IS_HUMAN_IND(i)) {
413 		    ox = OBJ_X_IN_PIXELS(Players[i]);
414 		    oy = OBJ_Y_IN_PIXELS(Players[i]);
415 		    dpx = WRAP_DX(px - ox);
416 		    dpy = WRAP_DY(py - oy);
417 		    if (sqr(dpx) + sqr(dpy) < sqr(ASTEROID_MIN_DIST)) {
418 			/* too close to player */
419 			okay = false;
420 			break;
421 		    }
422 		}
423 	    }
424 	}
425     }
426     if (okay == true) {
427 	Make_asteroid(px, py,
428 		      (int)(1 + rfrac() * ASTEROID_MAX_SIZE),
429 		      (int)(rfrac() * RES),
430 		      (DFLOAT)ASTEROID_START_SPEED);
431     }
432 }
433 
434 
Asteroid_move(wireobject * wireobj)435 static void Asteroid_move(wireobject *wireobj)
436 {
437     Move_object((object *) wireobj);
438 }
439 
440 
Asteroid_rotate(wireobject * wireobj)441 static void Asteroid_rotate(wireobject *wireobj)
442 {
443     wireobj->rotation =
444 	(wireobj->rotation + (int) (wireobj->turnspeed * RES)) % RES;
445 }
446 
447 
448 /*
449 ** Called once each frame update to update everything
450 ** related to asteroids including creation, destruction,
451 ** rotation and movement.
452 */
Asteroid_update(void)453 void Asteroid_update(void)
454 {
455     int		num;
456     list_t	list;
457     list_iter_t	iter;
458     wireobject	*asteroid;
459 
460     list = Asteroid_get_list();
461     if (list) {
462 	/* if there are more asteroids than are wanted, mark
463 	 * all asteroids to be removed (by Delete_shot()),
464 	 * until enough of size 1 have been removed
465 	 * (only breaking of size 1 actually lowers the
466 	 * total number of asteroids)
467 	 * one iteration may not remove enough asteroids
468 	 * the rest are left until the next frame then
469 	 * */
470 	num = World.asteroids.num;
471 	if (num > World.asteroids.max) {
472 	    for (iter = List_begin(list);
473 		 iter != List_end(list);
474 		 LI_FORWARD(iter)) {
475 		asteroid = (wireobject *) LI_DATA(iter);
476 		if (asteroid->life > 0) {
477 		    asteroid->life = 0;
478 		    if (asteroid->size == 1) {
479 			num--;
480 		    }
481 		}
482 		if (num <= World.asteroids.max) {
483 		    break;
484 		}
485 	    }
486 	}
487 
488 	/* rotate asteroids */
489 	for (iter = List_begin(list);
490 	     iter != List_end(list);
491 	     LI_FORWARD(iter)) {
492 	    asteroid = (wireobject *) LI_DATA(iter);
493 	    if (asteroid->life > 0) {
494 		Asteroid_rotate(asteroid);
495 	    }
496 	}
497 
498 	/* move asteroids */
499 	for (iter = List_begin(list);
500 	     iter != List_end(list);
501 	     LI_FORWARD(iter)) {
502 	    asteroid = (wireobject *) LI_DATA(iter);
503 	    if (asteroid->life > 0) {
504 		Asteroid_move(asteroid);
505 	    }
506 	}
507     }
508 
509     /* place new asteroid if room left */
510     if (World.asteroids.chance > 0) {
511 	int incr = (1 << (ASTEROID_MAX_SIZE - 1));
512 	if (World.asteroids.num + incr < World.asteroids.max) {
513 	    if ((rfrac() * World.asteroids.chance) < 1.0f) {
514 		Place_asteroid();
515 	    }
516 	}
517     }
518 }
519 
520