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