1 /*
2  * XPilot NG, a multiplayer space war game.
3  *
4  * Copyright (C) 1991-2001 by
5  *
6  *      Bj�rn Stabell        <bjoern@xpilot.org>
7  *      Ken Ronny Schouten   <ken@xpilot.org>
8  *      Bert Gijsbers        <bert@xpilot.org>
9  *      Dick Balaska         <dick@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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  */
25 
26 #include "xpserver.h"
27 
28 /******************************
29  * Functions for ship movement.
30  */
31 
Make_thrust_sparks(player_t * pl)32 void Make_thrust_sparks(player_t *pl)
33 {
34     static int min_dir, max_dir;
35     static double max_speed; /* kps - why are these static ? */
36     clpos_t engine = Ship_get_engine_clpos(pl->ship, pl->dir), pos;
37     int afterburners;
38     double max_life, tot_sparks, alt_sparks, power;
39 
40     if (pl->fuel.sum > 0.0) {
41 	power = pl->power;
42 	afterburners = (Player_uses_emergency_thrust(pl)
43 			? MAX_AFTERBURNER
44 			: pl->item[ITEM_AFTERBURNER]);
45     }
46     else {
47 	/* read comment about this in update.c */
48 	power = MIN_PLAYER_POWER * 0.6;
49 	afterburners = 0;
50     }
51 
52     max_life = 3 + power * 0.35;
53     tot_sparks = (power * 0.15 + 2.5) * timeStep;
54 
55     min_dir = (int)(pl->dir + RES/2 - (RES*0.2 + 1) * options.thrustWidth);
56     max_dir = (int)(pl->dir + RES/2 + (RES*0.2 + 1) * options.thrustWidth);
57     max_speed = (1 + (power * 0.14)) * options.sparkSpeed;
58     alt_sparks = tot_sparks * afterburners * (1. / (MAX_AFTERBURNER + 1));
59 
60     pos.cx = pl->pos.cx + engine.cx;
61     pos.cy = pl->pos.cy + engine.cy;
62 
63     sound_play_sensors(pl->pos, THRUST_SOUND);
64 
65     /* floor(tot_sparks + rfrac()) randomly rounds up or down to an integer,
66      * so that the expectation value of the result is tot_sparks */
67     Make_debris(pos,
68 		pl->vel,
69 		pl->id,
70 		pl->team,
71 		OBJ_SPARK,
72 		options.thrustMass,
73 		GRAVITY | OWNERIMMUNE,
74 		RED,
75 		8,
76 		(int)((tot_sparks-alt_sparks) + rfrac()),
77 		min_dir, max_dir,
78 		1.0, max_speed,
79 		3.0, max_life);
80 
81     Make_debris(pos,
82 		pl->vel,
83 		pl->id,
84 		pl->team,
85 		OBJ_SPARK,
86 		options.thrustMass * ALT_SPARK_MASS_FACT,
87 		GRAVITY | OWNERIMMUNE,
88 		BLUE,
89 		8,
90 		(int)(alt_sparks + rfrac()),
91 		min_dir, max_dir,
92 		1.0, max_speed,
93 		3.0, max_life);
94 }
95 
Record_shove(player_t * pl,player_t * pusher,long shove_time)96 void Record_shove(player_t *pl, player_t *pusher, long shove_time)
97 {
98     shove_t		*shove = &pl->shove_record[pl->shove_next];
99 
100     if (++pl->shove_next == MAX_RECORDED_SHOVES)
101 	pl->shove_next = 0;
102 
103     shove->pusher_id = pusher->id;
104     shove->time = shove_time;
105 }
106 
107 /* Calculates the effect of a collision between two objects */
108 /* This calculates a completely inelastic collision. Ie. the
109  * objects remain stuck together (same velocity and direction.
110  * Use this function if one of the objects will die in the
111  * collision. */
Delta_mv(object_t * ship,object_t * obj)112 void Delta_mv(object_t *ship, object_t *obj)
113 {
114     double	vx, vy, m;
115 
116     m = ship->mass + ABS(obj->mass);
117     vx = (ship->vel.x * ship->mass + obj->vel.x * obj->mass) / m;
118     vy = (ship->vel.y * ship->mass + obj->vel.y * obj->mass) / m;
119     if (ship->type == OBJ_PLAYER
120 	&& obj->id != NO_ID
121 	&& BIT(obj->obj_status, COLLISIONSHOVE)) {
122 	player_t *pl = (player_t *)ship;
123 	player_t *pusher = Player_by_id(obj->id);
124 	if (pusher != pl)
125 	    Record_shove(pl, pusher, frame_loops);
126     }
127     ship->vel.x = vx;
128     ship->vel.y = vy;
129     obj->vel.x = vx;
130     obj->vel.y = vy;
131 }
132 
133 /* Calculates the effect of a collision between two objects */
134 /* And now for a completely elastic collision. Ie. the objects
135  * will bounce off of eachother. Use this function if both
136  * objects stay alive after the collision. */
Delta_mv_elastic(object_t * obj1,object_t * obj2)137 void Delta_mv_elastic(object_t *obj1, object_t *obj2)
138 {
139     double	m1 = (double)obj1->mass,
140 		m2 = (double)obj2->mass,
141 		ms = m1 + m2;
142     double	v1x = obj1->vel.x,
143 		v1y = obj1->vel.y,
144 		v2x = obj2->vel.x,
145 		v2y = obj2->vel.y;
146 
147     obj1->vel.x = (m1 - m2) / ms * v1x
148 		  + 2 * m2 / ms * v2x;
149     obj1->vel.y = (m1 - m2) / ms * v1y
150 		  + 2 * m2 / ms * v2y;
151     obj2->vel.x = 2 * m1 / ms * v1x
152 		  + (m2 - m1) / ms * v2x;
153     obj2->vel.y = 2 * m1 / ms * v1y
154 		  + (m2 - m1) / ms * v2y;
155     if (obj1->type == OBJ_PLAYER
156 	&& obj2->id != NO_ID
157 	&& BIT(obj2->obj_status, COLLISIONSHOVE)) {
158 	player_t *pl = (player_t *)obj1;
159 	player_t *pusher = Player_by_id(obj2->id);
160 	if (pusher != pl)
161 	    Record_shove(pl, pusher, frame_loops);
162     }
163 }
164 
Delta_mv_partly_elastic(object_t * obj1,object_t * obj2,double elastic)165 void Delta_mv_partly_elastic(object_t *obj1, object_t *obj2, double elastic)
166 {
167     double	m1 = (double)obj1->mass,
168 		m2 = (double)obj2->mass,
169 		ms = m1 + m2;
170     double	v1x = obj1->vel.x,
171 		v1y = obj1->vel.y,
172 		v2x = obj2->vel.x,
173 		v2y = obj2->vel.y;
174     double	vx, vy;
175     double      vxd, vyd, xd, yd;
176     if(elastic < 0)
177 	return;
178 
179     vxd = v1x - v2x;
180     vyd = v1y - v2y;
181     xd = WRAP_DCX(obj2->pos.cx - obj1->pos.cx);
182     yd = WRAP_DCY(obj2->pos.cy - obj1->pos.cy);
183 
184     /* KHS objects  going away from each other, dont do anything */
185     if((vxd * xd + vyd * yd) < 0){
186       /* this sometimes has false positives for fast moves */
187       /* because the objects have already passed each other */
188       /* so lets check with positions from  1 frame back */
189 
190       xd = WRAP_DCX(obj2->prevpos.cx - obj1->prevpos.cx);
191       yd = WRAP_DCY(obj2->prevpos.cy - obj1->prevpos.cy);
192 
193       if((vxd * xd + vyd * yd) < 0)
194 	return;
195     }
196 
197     vx = (v1x * m1 + v2x * m2) / ms;
198     vy = (v1y * m1 + v2y * m2) / ms;
199 
200 
201     obj1->vel.x = ((m1 - m2) / ms * v1x
202 		   + 2 * m2 / ms * v2x) * elastic
203 	+ vx * (1 - elastic);
204     obj1->vel.y = ((m1 - m2) / ms * v1y
205 		   + 2 * m2 / ms * v2y) * elastic
206 	+ vy * (1 - elastic);
207     obj2->vel.x = (2 * m1 / ms * v1x
208 		   + (m2 - m1) / ms * v2x) * elastic
209 	+ vx * (1 - elastic);
210     obj2->vel.y = (2 * m1 / ms * v1y
211 		   + (m2 - m1) / ms * v2y) * elastic
212 	+ vy * (1 - elastic);
213 
214     if (obj1->type == OBJ_PLAYER
215 	&& obj2->id != NO_ID
216 	&& BIT(obj2->obj_status, COLLISIONSHOVE)) {
217 	player_t *pl = (player_t *)obj1;
218 	player_t *pusher = Player_by_id(obj2->id);
219 	if (pusher != pl)
220 	    Record_shove(pl, pusher, frame_loops);
221     }
222 }
223 
224 
Obj_repel(object_t * obj1,object_t * obj2,int repel_dist)225 void Obj_repel(object_t *obj1, object_t *obj2, int repel_dist)
226 {
227     double xd, yd, force, dm, dvx1, dvy1, dvx2, dvy2, a;
228     int obj_theta;
229 
230     xd = WRAP_DCX(obj2->pos.cx - obj1->pos.cx);
231     yd = WRAP_DCY(obj2->pos.cy - obj1->pos.cy);
232     force = CLICK_TO_PIXEL((int)(repel_dist - LENGTH(xd, yd)));
233 
234     if (force <= 0)
235 	return;
236 
237     force = MIN(force, 10);
238 
239     a = findDir(xd, yd);
240     obj_theta = MOD2((int) (a + 0.5), RES);
241 
242     dm = obj1->mass / obj2->mass;
243     dvx2 = tcos(obj_theta) * force * dm;
244     dvy2 = tsin(obj_theta) * force * dm;
245 
246     dvx1 = -(tcos(obj_theta) * force / dm);
247     dvy1 = -(tsin(obj_theta) * force / dm);
248 
249     if (obj1->type == OBJ_PLAYER && obj2->id != NO_ID) {
250 	player_t *pl = (player_t *)obj1;
251 	player_t *pusher = Player_by_id(obj2->id);
252 	if (pusher != pl)
253 	    Record_shove(pl, pusher, frame_loops);
254     }
255 
256     if (obj2->type == OBJ_PLAYER && obj1->id != NO_ID) {
257 	player_t *pl = (player_t *)obj2;
258 	player_t *pusher = Player_by_id(obj1->id);
259 	if (pusher != pl)
260 	    Record_shove(pl, pusher, frame_loops);
261     }
262 
263     obj1->vel.x += dvx1;
264     obj1->vel.y += dvy1;
265 
266     obj2->vel.x += dvx2;
267     obj2->vel.y += dvy2;
268 }
269 
270 
271 /*
272  * Add fuel to fighter's tanks.
273  * Maybe use more than one of tank to store the fuel.
274  */
Add_fuel(pl_fuel_t * ft,double fuel)275 void Add_fuel(pl_fuel_t *ft, double fuel)
276 {
277     if (ft->sum + fuel > ft->max)
278 	fuel = ft->max - ft->sum;
279     else if (ft->sum + fuel < 0.0)
280 	fuel = -ft->sum;
281     ft->sum += fuel;
282     ft->tank[ft->current] += fuel;
283 }
284 
285 
286 /*
287  * Move fuel from add-on tanks to main tank,
288  * handle over and underflow of tanks.
289  */
Update_tanks(pl_fuel_t * ft)290 void Update_tanks(pl_fuel_t *ft)
291 {
292     if (ft->num_tanks) {
293 	int  t, check;
294 	double low_level;
295 	double fuel;
296 	double *f;
297 	double frame_refuel = REFUEL_RATE * timeStep;
298 
299 	/* Set low_level to minimum fuel in each tank */
300 	low_level = ft->sum / (ft->num_tanks + 1) - 1;
301 	if (low_level < 0.0)
302 	    low_level = 0.0;
303 	if (TANK_REFILL_LIMIT < low_level)
304 	    low_level = TANK_REFILL_LIMIT;
305 
306 	t = ft->num_tanks;
307 	check = MAX_TANKS << 2;
308 	fuel = 0;
309 	f = ft->tank + t;
310 
311 	while (t >= 0 && check--) {
312 	    double m = TANK_CAP(t);
313 
314 	    /* Add the previous over/underflow and do a new cut */
315 	    *f += fuel;
316 	    if (*f > m) {
317 		fuel = *f - m;
318 		*f = m;
319 	    } else if (*f < 0) {
320 		fuel = *f;
321 		*f = 0;
322 	    } else
323 		fuel = 0;
324 
325 	    /* If there is no over/underflow, let the fuel run to main-tank */
326 	    if (!fuel) {
327 		if (t
328 		    && t != ft->current
329 		    && *f >= low_level + frame_refuel
330 		    && *(f-1) <= TANK_CAP(t-1) - frame_refuel) {
331 
332 		    *f -= frame_refuel;
333 		    fuel = frame_refuel;
334 		} else if (t && *f < low_level) {
335 		    *f += frame_refuel;
336 		    fuel = frame_refuel;
337 		}
338 	    }
339 	    if (fuel && t == 0) {
340 	       t = ft->num_tanks;
341 	       f = ft->tank + t;
342 	    } else {
343 		t--;
344 		f--;
345 	    }
346 	}
347 	if (!check) {
348 	    error("fuel problem");
349 	    fuel = ft->sum;
350 	    ft->sum =
351 	    ft->max = 0;
352 	    t = 0;
353 	    while (t <= ft->num_tanks) {
354 		if (fuel) {
355 		    if (fuel>TANK_CAP(t)) {
356 			ft->tank[t] = TANK_CAP(t);
357 			fuel -= TANK_CAP(t);
358 		    } else {
359 			ft->tank[t] = fuel;
360 			fuel = 0;
361 		    }
362 		    ft->sum += ft->tank[t];
363 		} else
364 		    ft->tank[t] = 0;
365 		ft->max += TANK_CAP(t);
366 		t++;
367 	    }
368 	}
369     } else
370 	ft->tank[0] = ft->sum;
371 }
372 
373 
374 /*
375  * Use current tank as dummy target for heat seeking missles.
376  */
Tank_handle_detach(player_t * pl)377 void Tank_handle_detach(player_t *pl)
378 {
379     player_t *tank;
380     int i, ct;
381 
382     if (Player_is_phasing(pl))
383 	return;
384 
385     /* Return, if no more players or no tanks */
386     if (pl->fuel.num_tanks == 0
387 	|| NumPseudoPlayers == MAX_PSEUDO_PLAYERS
388 	|| peek_ID() == 0)
389 	return;
390 
391     /* If current tank is main, use another one */
392     if ((ct = pl->fuel.current) == 0)
393 	ct = pl->fuel.num_tanks;
394 
395     Update_tanks(&(pl->fuel));
396 
397     /* Fork the current player */
398     tank = Player_by_index(NumPlayers);
399 
400     /*
401      * MWAAH: this was ... naieve at least:
402      * *tank = *pl;
403      * Player structures contain pointers to dynamic memory...
404      */
405 
406     Init_player(NumPlayers,
407 		options.allowShipShapes
408 		? Parse_shape_str(options.tankShipShape) : NULL,
409 		PL_TYPE_TANK);
410 
411     /* Released tanks don't have tanks... */
412     while (tank->fuel.num_tanks > 0)
413 	Player_remove_tank(tank, tank->fuel.num_tanks);
414 
415     Object_position_init_clpos(OBJ_PTR(tank), pl->pos);
416     tank->vel = pl->vel;
417     tank->acc = pl->acc;
418     tank->dir = pl->dir;
419     tank->turnspeed = pl->turnspeed;
420     tank->velocity = pl->velocity;
421     Player_set_float_dir(tank, pl->float_dir);
422     tank->turnresistance = pl->turnresistance;
423     tank->turnvel = pl->turnvel;
424     tank->oldturnvel = pl->oldturnvel;
425     tank->turnacc = pl->turnacc;
426     tank->power = pl->power;
427 
428     strlcpy(tank->name, pl->name, MAX_CHARS);
429     strlcat(tank->name, "'s tank", MAX_CHARS);
430     strlcpy(tank->username, options.tankUserName, MAX_CHARS);
431     strlcpy(tank->hostname, options.tankHostName, MAX_CHARS);
432     tank->home_base = pl->home_base;
433     tank->team = pl->team;
434     tank->pseudo_team = pl->pseudo_team;
435     tank->alliance = ALLIANCE_NOT_SET;
436     tank->invite = NO_ID;
437     tank->score =  Get_Score(pl) - options.tankScoreDecrement;
438 
439     /* Fuel is the one from chosen tank */
440     tank->fuel.sum =
441     tank->fuel.tank[0] = pl->fuel.tank[ct];
442     tank->fuel.max = TANK_CAP(ct);
443     tank->fuel.current = 0;
444     tank->fuel.num_tanks = 0;
445 
446     /* Mass is only tank + fuel */
447     tank->emptymass = options.shipMass;
448     tank->mass = tank->emptymass + FUEL_MASS(tank->fuel.sum);
449     tank->power *= TANK_THRUST_FACT;
450 
451     /* Reset visibility. */
452     tank->updateVisibility = true;
453     for (i = 0; i <= NumPlayers; i++) {
454 	tank->visibility[i].lastChange = 0;
455 	Player_by_index(i)->visibility[NumPlayers].lastChange = 0;
456     }
457 
458     /* Remember whose tank this is */
459     tank->lock.pl_id = pl->id;
460 
461     request_ID();
462     NumPlayers++;
463     NumPseudoPlayers++;
464     updateScores = true;
465 
466     /* Possibly join alliance. */
467     if (pl->alliance != ALLIANCE_NOT_SET)
468 	Player_join_alliance(tank, pl);
469 
470     sound_play_sensors(pl->pos, TANK_DETACH_SOUND);
471 
472     /* The tank uses shield and thrust */
473     tank->obj_status = GRAVITY;
474     Thrust(tank, true);
475     tank->have = DEF_HAVE;
476     tank->used = (DEF_USED & ~USED_KILL & pl->have) | HAS_SHIELD;
477 
478     if (!options.allowShields) {
479 	tank->shield_time = 30 * 12;
480 	tank->have |= HAS_SHIELD;
481     }
482 
483     /* Maybe heat-seekers to retarget? */
484     for (i = 0; i < NumObjs; i++) {
485 	object_t *obj = Obj[i];
486 
487 	if (obj->type == OBJ_HEAT_SHOT) {
488 	    heatobject_t *heat = HEAT_PTR(obj);
489 
490 	    if (heat->heat_lock_id > 0
491 		&& Player_by_id(heat->heat_lock_id) == pl)
492 		/* kps - is this right ? */
493 		heat->heat_lock_id = NumPlayers - 1;
494 	}
495     }
496 
497     /* Remove tank, fuel and mass from myself */
498     Player_remove_tank(pl, ct);
499 
500     for (i = 0; i < NumPlayers - 1; i++) {
501 	player_t *pl_i = Player_by_index(i);
502 
503 	if (pl_i->conn != NULL) {
504 	    Send_player(pl_i->conn, tank->id);
505 	    Send_score(pl_i->conn, tank->id,
506 		       tank->score, (int)tank->life,
507 		       tank->mychar, tank->alliance);
508 	}
509     }
510 
511     for (i = 0; i < NumSpectators - 1; i++) {
512 	player_t *pl_i = Player_by_index(i + spectatorStart);
513 
514 	Send_player(pl_i->conn, tank->id);
515 	Send_score(pl_i->conn, tank->id, tank->score,
516 		   (int)tank->life, tank->mychar, tank->alliance);
517     }
518 }
519 
520 
521 
522 
523 
524 
525 /****************************
526  * Functions for explosions.
527  */
528 
529 /* Create debris particles */
Make_debris(clpos_t pos,vector_t vel,int owner_id,int owner_team,int type,double mass,int status,int color,int radius,int num_debris,int min_dir,int max_dir,double min_speed,double max_speed,double min_life,double max_life)530 void Make_debris(clpos_t  pos,
531 		 vector_t vel,
532 		 int      owner_id,
533 		 int      owner_team,
534 		 int      type,
535 		 double   mass,
536 		 int      status,
537 		 int      color,
538 		 int      radius,
539 		 int      num_debris,
540 		 int      min_dir,   int     max_dir,
541 		 double   min_speed, double  max_speed,
542 		 double   min_life,  double  max_life)
543 {
544     object_t *debris;
545     int i;
546     double life;
547     modifiers_t mods;
548 
549     if (!options.useDebris)
550 	return;
551 
552     pos = World_wrap_clpos(pos);
553     if (!World_contains_clpos(pos))
554 	return;
555 
556     if (max_life < min_life)
557 	max_life = min_life;
558 
559     if (max_speed < min_speed)
560 	max_speed = min_speed;
561 
562     Mods_clear(&mods);
563 
564     if (type == OBJ_SHOT) {
565 	Mods_set(&mods, ModsCluster, 1);
566 	if (!options.shotsGravity)
567 	    CLR_BIT(status, GRAVITY);
568     }
569 
570     if (num_debris > MAX_TOTAL_SHOTS - NumObjs)
571 	num_debris = MAX_TOTAL_SHOTS - NumObjs;
572 
573     for (i = 0; i < num_debris; i++) {
574 	double speed, dx, dy, diroff;
575 	int dir, dirplus;
576 
577 	if ((debris = Object_allocate()) == NULL)
578 	    break;
579 
580 	debris->color = color;
581 	debris->id = owner_id;
582 	debris->team = owner_team;
583 	Object_position_init_clpos(debris, pos);
584 	dir = MOD2(min_dir + (int)(rfrac() * (max_dir - min_dir)), RES);
585 	dirplus = MOD2(dir + 1, RES);
586 	diroff = rfrac();
587 	dx = tcos(dir) + (tcos(dirplus) - tcos(dir)) * diroff;
588 	dy = tsin(dir) + (tsin(dirplus) - tsin(dir)) * diroff;
589 	speed = min_speed + rfrac() * (max_speed - min_speed);
590 	debris->vel.x = vel.x + dx * speed;
591 	debris->vel.y = vel.y + dy * speed;
592 	debris->acc.x = 0;
593 	debris->acc.y = 0;
594 	if (options.shotHitFuelDrainUsesKineticEnergy
595 	    && type == OBJ_SHOT) {
596 	    /* compensate so that m*v^2 is constant */
597 	    double sp_shotsp = speed / options.shotSpeed;
598 
599 	    debris->mass = mass / (sp_shotsp * sp_shotsp);
600 	} else
601 	    debris->mass = mass;
602 	debris->type = type;
603 	life = min_life + rfrac() * (max_life - min_life);
604 	debris->life = life;
605 	debris->fuse = 0;
606 	debris->pl_range = radius;
607 	debris->pl_radius = radius;
608 	debris->obj_status = status;
609 	debris->mods = mods;
610 	Cell_add_object(debris);
611     }
612 }
613 
614 
Make_wreckage(clpos_t pos,vector_t vel,int owner_id,int owner_team,double min_mass,double max_mass,double total_mass,int status,int max_wreckage,int min_dir,int max_dir,double min_speed,double max_speed,double min_life,double max_life)615 void Make_wreckage(clpos_t  pos,
616 		   vector_t vel,
617 		   int      owner_id,
618 		   int      owner_team,
619 		   double   min_mass,     double max_mass,
620 		   double   total_mass,
621 		   int      status,
622 		   int      max_wreckage,
623 		   int      min_dir,      int    max_dir,
624 		   double   min_speed,    double max_speed,
625 		   double   min_life,     double max_life)
626 {
627     wireobject_t *wreckage;
628     int i, size;
629     double life, mass, sum_mass = 0.0;
630     modifiers_t mods;
631 
632     if (!options.useWreckage)
633 	return;
634 
635     pos = World_wrap_clpos(pos);
636     if (!World_contains_clpos(pos))
637 	return;
638 
639     if (max_life < min_life)
640 	max_life = min_life;
641 
642     if (max_speed < min_speed)
643 	max_speed = min_speed;
644 
645     if (max_wreckage > MAX_TOTAL_SHOTS - NumObjs)
646 	max_wreckage = MAX_TOTAL_SHOTS - NumObjs;
647 
648     Mods_clear(&mods);
649 
650     for (i = 0; i < max_wreckage && sum_mass < total_mass; i++) {
651 
652 	double		speed;
653 	int		dir, radius;
654 
655 	/* Calculate mass */
656 	mass = min_mass + rfrac() * (max_mass - min_mass);
657 	if ( sum_mass + mass > total_mass )
658 	    mass = total_mass - sum_mass;
659 
660 	if (mass < min_mass)
661 	    /* not enough mass available. */
662 	    break;
663 
664 	/* Allocate object */
665 	if ((wreckage = WIRE_PTR(Object_allocate())) == NULL)
666 	    break;
667 
668 	wreckage->color = WHITE;
669 	wreckage->id = owner_id;
670 	wreckage->team = owner_team;
671 	wreckage->type = OBJ_WRECKAGE;
672 
673 	/* Position */
674 	Object_position_init_clpos(OBJ_PTR(wreckage), pos);
675 
676 	/* Direction */
677 	dir = MOD2(min_dir + (int)(rfrac() * MOD2(max_dir - min_dir, RES)),
678 		   RES);
679 
680 	/* Velocity and acceleration */
681 	speed = min_speed + rfrac() * (max_speed - min_speed);
682 	wreckage->vel.x = vel.x + tcos(dir) * speed;
683 	wreckage->vel.y = vel.y + tsin(dir) * speed;
684 	wreckage->acc.x = 0;
685 	wreckage->acc.y = 0;
686 
687 	/* Mass */
688 	wreckage->mass = mass;
689 	sum_mass += mass;
690 
691 	/* Lifespan  */
692 	life = min_life + rfrac() * (max_life - min_life);
693 
694 	wreckage->life = life;
695 	wreckage->fuse = 0;
696 
697 	/* Wreckage type, rotation, and size */
698 	wreckage->wire_turnspeed = 0.02 + rfrac() * 0.35;
699 	wreckage->wire_rotation = (int)(rfrac() * RES);
700 	size = (int) ( 256.0 * 1.5 * mass / total_mass );
701 	if (size > 255)
702 	    size = 255;
703 	wreckage->wire_size = size;
704 	wreckage->wire_type = (uint8_t)(rfrac() * 256);
705 
706 	radius = wreckage->wire_size * 16 / 256;
707 	if (radius < 8)
708 	    radius = 8;
709 
710 	wreckage->pl_range = radius;
711 	wreckage->pl_radius = radius;
712 	wreckage->obj_status = status;
713 	wreckage->mods = mods;
714 	Cell_add_object(OBJ_PTR(wreckage));
715     }
716 }
717 
718 
Explode_fighter(player_t * pl)719 void Explode_fighter(player_t *pl)
720 {
721     int min_debris;
722     double debris_range;
723 
724     sound_play_sensors(pl->pos, PLAYER_EXPLOSION_SOUND);
725 
726     min_debris = (int)(1 + (pl->fuel.sum / 8.0));
727     debris_range = pl->mass;
728     /* reduce debris since we also create wreckage objects */
729     min_debris >>= 1; /* Removed *2.0 from range */
730 
731     Make_debris(pl->pos,
732 		pl->vel,
733 		pl->id,
734 		pl->team,
735 		OBJ_DEBRIS,
736 		3.5,
737 		GRAVITY,
738 		RED,
739 		8,
740 		(int)(min_debris + debris_range * rfrac()),
741 		0, RES-1,
742 		20.0, 20.0 + pl->mass * 0.5,
743 		5.0, 5.0 + pl->mass * 1.5);
744 
745     Make_wreckage(pl->pos,
746 		  pl->vel,
747 		  pl->id,
748 		  pl->team,
749 		  MAX(pl->mass/8.0, 0.33), pl->mass,
750 		  2.0 * pl->mass,
751 		  GRAVITY,
752 		  10,
753 		  0, RES-1,
754 		  10.0, 10.0 + pl->mass * 0.5,
755 		  5.0, 5.0 + pl->mass * 1.5);
756 }
757